From 8175b05b231fe4d82756e87198bfb97aca35c1e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gjermund=20H=C3=B8s=C3=B8ien=20Wiggen?= Date: Sun, 7 Jun 2026 22:34:28 +0200 Subject: [PATCH] feat: fuzzy ticket search in command palette, improved styling --- web/src/components/command-palette.tsx | 71 ++++++++++++++++++-------- 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/web/src/components/command-palette.tsx b/web/src/components/command-palette.tsx index b4be93b..fb5861c 100644 --- a/web/src/components/command-palette.tsx +++ b/web/src/components/command-palette.tsx @@ -7,7 +7,10 @@ import { PlusIcon, LayoutGridIcon, SettingsIcon, + MessageSquareIcon, } from "lucide-react"; +import { getTickets } from "@/lib/api"; +import type { Ticket } from "@/lib/types"; interface CommandItem { id: string; @@ -28,8 +31,17 @@ export function CommandPalette({ open, onOpenChange }: CommandPaletteProps) { const listRef = useRef(null); const [query, setQuery] = useState(""); const [selectedIndex, setSelectedIndex] = useState(0); + const [tickets, setTickets] = useState([]); - const commands: CommandItem[] = [ + useEffect(() => { + if (open) { + getTickets().then(({ data }) => { + if (data) setTickets(data); + }); + } + }, [open]); + + const alwaysCommands: CommandItem[] = [ { id: "new-ticket", label: "New ticket", @@ -40,16 +52,6 @@ export function CommandPalette({ open, onOpenChange }: CommandPaletteProps) { }, category: "Actions", }, - { - id: "all-tickets", - label: "All tickets", - icon: LayoutGridIcon, - action: () => { - onOpenChange(false); - router.push("/"); - }, - category: "Navigate", - }, { id: "admin", label: "Go to admin", @@ -60,12 +62,37 @@ export function CommandPalette({ open, onOpenChange }: CommandPaletteProps) { }, category: "Navigate", }, + { + id: "all-tickets", + label: "All tickets", + icon: LayoutGridIcon, + action: () => { + onOpenChange(false); + router.push("/"); + }, + category: "Navigate", + }, ]; - const filtered = commands.filter((cmd) => + const ticketCommands: CommandItem[] = tickets + .filter((t) => t.subject.toLowerCase().includes(query.toLowerCase())) + .map((t) => ({ + id: `ticket-${t.id}`, + label: t.subject, + icon: MessageSquareIcon, + action: () => { + onOpenChange(false); + router.push(`/tickets/${t.id}`); + }, + category: "Tickets", + })); + + const alwaysFiltered = alwaysCommands.filter((cmd) => cmd.label.toLowerCase().includes(query.toLowerCase()) ); + const filtered = [...alwaysFiltered, ...ticketCommands]; + const grouped = filtered.reduce>((acc, cmd) => { const cat = cmd.category || "Other"; if (!acc[cat]) acc[cat] = []; @@ -114,27 +141,27 @@ export function CommandPalette({ open, onOpenChange }: CommandPaletteProps) { onClick={() => onOpenChange(false)} />
-
-
- +
+
+ setQuery(e.target.value)} onKeyDown={handleKeyDown} placeholder="Type a command or search..." - className="w-full h-10 bg-transparent text-sm text-[#f7f8f8] placeholder:text-[#8a8f98] outline-none" + className="w-full h-10 bg-transparent text-sm text-foreground placeholder:text-muted-foreground outline-none" />
{filtered.length === 0 && ( -
+
No results found
)} {Object.entries(grouped).map(([category, items]) => (
-
+
{category}
{items.map((item) => { @@ -144,10 +171,10 @@ export function CommandPalette({ open, onOpenChange }: CommandPaletteProps) { return (
))}
-
+
↑↓ Navigate ↵ Select Esc Dismiss