"use client"; import { ArrowDownUpIcon, RefreshCwIcon, SearchIcon, XIcon } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import type { Range, Sort } from "@/lib/filter"; import { cn } from "@/lib/utils"; const RANGE_LABELS: Record = { future: "Kommend", past: "Vergangen", all: "Alle" }; const SORT_LABELS: Record = { start_asc: "Beginn – früheste zuerst", start_desc: "Beginn – späteste zuerst", created_asc: "Erstellt – älteste zuerst", created_desc: "Erstellt – neueste zuerst", }; /** * Search, sort, reload and the time range. The search box keeps its own text * and writes it to the URL after a short pause, so typing doesn't replace the * URL on every keystroke. */ export function EventsToolbar({ query, range, sort, loading, onQueryChange, onRangeChange, onSortChange, onReload, }: { query: string; range: Range; sort: Sort; loading: boolean; onQueryChange: (query: string) => void; onRangeChange: (range: Range) => void; onSortChange: (sort: Sort) => void; onReload: () => void; }) { const [text, setText] = useState(query); // Take over a query that changed from outside (the empty state's "Suche // leeren", the back button) - but not our own write coming back through the // URL, which would drop whatever was typed while it travelled. const [seenQuery, setSeenQuery] = useState(query); const [emitted, setEmitted] = useState(query); if (query !== seenQuery) { setSeenQuery(query); if (query !== emitted) { setText(query); } } const onQueryChangeRef = useRef(onQueryChange); useEffect(() => { onQueryChangeRef.current = onQueryChange; }); useEffect(() => { if (text === query) return; const timer = setTimeout(() => { setEmitted(text); onQueryChangeRef.current(text); }, 250); return () => clearTimeout(timer); }, [text, query]); return (
setText(e.target.value)} placeholder="Titel oder Ort suchen" aria-label="Termine durchsuchen" className="pr-9 pl-9 [&::-webkit-search-cancel-button]:hidden" /> {text !== "" && ( )}
}> Sortieren nach onSortChange(value as Sort)}> {(Object.keys(SORT_LABELS) as Sort[]).map((key) => ( // Base UI keeps a menu open after a radio pick by default; one choice is all there is here. {SORT_LABELS[key]} ))}
{ // A segmented control: clicking the pressed item must not leave nothing selected. const next = value[0] as Range | undefined; if (next) onRangeChange(next); }} variant="outline" spacing={0} className="grid w-full grid-cols-3 sm:inline-grid sm:w-auto" aria-label="Zeitraum" > {(Object.keys(RANGE_LABELS) as Range[]).map((key) => ( {RANGE_LABELS[key]} ))}
); }