Files
task-manager/client/src/components/CommandPalette.tsx
T
paul ccfb674318
continuous-integration/drone/push Build is passing
feat: add social features, leaderboard, auth enhancements, and admin fixes
- Implement Social Features: Shared Tasks, Global Access, Privacy Settings (Leaderboard/Searchable).
- Add Leaderboard Page and API.
- Enhance Auth: Support Email/Username login, explicit duplicate registration errors.
- Fix: Admin login password hash regression.
- Refactor: Move to wouter for routing, add Admin Dashboard and User Management.
- Add Setup Wizard.
- Update UI with Sidebar and Gamification elements.
2025-12-10 14:04:26 +01:00

129 lines
7.2 KiB
TypeScript

import { useEffect, useState } from "react";
import { Command } from "cmdk";
import { useTranslation } from "react-i18next";
import {
Calculator,
Calendar,
CreditCard,
Settings,
Smile,
User,
LayoutGrid,
List,
CheckSquare,
Plus,
Moon,
Sun,
Search
} from "lucide-react";
import { useTheme } from "next-themes";
import { Dialog, DialogContent } from "@/components/ui/dialog";
interface CommandPaletteProps {
onNavigate: (tab: string) => void;
onCreateTask: () => void;
}
export function CommandPalette({ onNavigate, onCreateTask }: CommandPaletteProps) {
const [open, setOpen] = useState(false);
const { t } = useTranslation();
const { setTheme, theme } = useTheme();
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((open) => !open);
}
};
document.addEventListener("keydown", down);
return () => document.removeEventListener("keydown", down);
}, []);
const runCommand = (command: () => void) => {
setOpen(false);
command();
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="p-0 overflow-hidden shadow-2xl max-w-2xl">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<Command.Input
placeholder={t('search.placeholder') || "Type a command or search..."}
className="flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
/>
</div>
<Command.List className="max-h-[300px] overflow-y-auto overflow-x-hidden p-2">
<Command.Empty>{t('search.noResults') || "No results found."}</Command.Empty>
<Command.Group heading="Actions">
<Command.Item
onSelect={() => runCommand(onCreateTask)}
className="relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 cursor-pointer"
>
<Plus className="mr-2 h-4 w-4" />
<span>{t('taskCreation.title') || "Create New Task"}</span>
<span className="ml-auto text-xs text-muted-foreground">Cmd+N</span>
</Command.Item>
<Command.Item
onSelect={() => runCommand(() => setTheme(theme === 'dark' ? 'light' : 'dark'))}
className="relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 cursor-pointer"
>
{theme === 'dark' ? <Sun className="mr-2 h-4 w-4" /> : <Moon className="mr-2 h-4 w-4" />}
<span>{t('theme.toggle') || "Toggle Theme"}</span>
</Command.Item>
</Command.Group>
<Command.Group heading="Navigation">
<Command.Item
onSelect={() => runCommand(() => onNavigate('tasks'))}
className="relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 cursor-pointer"
>
<CheckSquare className="mr-2 h-4 w-4" />
<span>Tasks</span>
</Command.Item>
<Command.Item
onSelect={() => runCommand(() => onNavigate('calendar'))}
className="relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 cursor-pointer"
>
<Calendar className="mr-2 h-4 w-4" />
<span>Calendar</span>
</Command.Item>
<Command.Item
onSelect={() => runCommand(() => onNavigate('weeklist'))}
className="relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 cursor-pointer"
>
<List className="mr-2 h-4 w-4" />
<span>Week List</span>
</Command.Item>
<Command.Item
onSelect={() => runCommand(() => onNavigate('kanban'))}
className="relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 cursor-pointer"
>
<LayoutGrid className="mr-2 h-4 w-4" />
<span>Kanban Board</span>
</Command.Item>
<Command.Item
onSelect={() => runCommand(() => onNavigate('settings'))}
className="relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 cursor-pointer"
>
<Settings className="mr-2 h-4 w-4" />
<span>Settings</span>
</Command.Item>
</Command.Group>
</Command.List>
</Command>
</DialogContent>
</Dialog>
);
}