| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- "use client";
- import React from "react";
- import { Clock3, Search } from "lucide-react";
- import { Button } from "@/components/ui/button";
- import { Input } from "@/components/ui/input";
- import { Label } from "@/components/ui/label";
- import {
- Popover,
- PopoverContent,
- PopoverTrigger,
- } from "@/components/ui/popover";
- import {
- Command,
- CommandGroup,
- CommandItem,
- CommandList,
- } from "@/components/ui/command";
- function toBranchShort(value) {
- const normalized = String(value || "").trim().toUpperCase();
- const match = /^NL(\d+)$/i.exec(normalized);
- if (!match) return null;
- const digits = match[1];
- return digits.length === 1 ? `0${digits}` : digits;
- }
- function getMultiBranchSummary(branches) {
- const compact = (Array.isArray(branches) ? branches : [])
- .map((branch) => toBranchShort(branch))
- .filter(Boolean);
- if (compact.length === 0) return null;
- return `NL: ${compact.join(",")}`;
- }
- function getScopeSummary(entry) {
- if (entry?.scope === "all") return "Alle Niederlassungen";
- if (entry?.scope === "multi") {
- const compactBranches = getMultiBranchSummary(entry?.branches);
- if (compactBranches) return compactBranches;
- const count = Array.isArray(entry?.branches) ? entry.branches.length : 0;
- return count > 0
- ? `Mehrere Niederlassungen (${count})`
- : "Mehrere Niederlassungen";
- }
- return `Niederlassung ${entry?.routeBranch || ""}`.trim();
- }
- function getDateSummary(entry) {
- const from = typeof entry?.from === "string" ? entry.from.trim() : "";
- const to = typeof entry?.to === "string" ? entry.to.trim() : "";
- if (from && to) return `${from} bis ${to}`;
- if (from) return `ab ${from}`;
- if (to) return `bis ${to}`;
- return "";
- }
- function getEntryMetaSummary(entry) {
- const parts = [getScopeSummary(entry)];
- const dateSummary = getDateSummary(entry);
- if (dateSummary) parts.push(dateSummary);
- return parts.join(" • ");
- }
- export default function SearchQueryBox({
- qDraft,
- onQDraftChange,
- onSubmit,
- currentQuery,
- isSubmitting,
- canSearch,
- recentSearches,
- onSelectRecentSearch,
- onClearRecentSearches,
- }) {
- const [open, setOpen] = React.useState(false);
- const historyItems = Array.isArray(recentSearches) ? recentSearches : [];
- const hasHistory = historyItems.length > 0;
- const canClearHistory =
- hasHistory && typeof onClearRecentSearches === "function";
- React.useEffect(() => {
- if (hasHistory) return;
- setOpen(false);
- }, [hasHistory]);
- const handleInputFocus = React.useCallback(() => {
- const trimmedDraft = typeof qDraft === "string" ? qDraft.trim() : "";
- if (trimmedDraft) return;
- if (!hasHistory) return;
- setOpen(true);
- }, [qDraft, hasHistory]);
- return (
- // Important for flex layouts:
- // - w-full ensures the box fills its container width.
- // - min-w-0 allows the input row to shrink without overflow.
- <div className="grid w-full min-w-0 gap-2">
- <Label htmlFor="q">Suchbegriff</Label>
- <Popover open={open} onOpenChange={setOpen}>
- <div className="flex w-full min-w-0 flex-col gap-2 sm:flex-row sm:items-center">
- <Input
- id="q"
- name="q"
- value={qDraft}
- onChange={(e) => onQDraftChange(e.target.value)}
- onFocus={handleInputFocus}
- placeholder="z. B. Bridgestone, Rechnung, Kundennummer…"
- disabled={isSubmitting}
- // Make the input take the remaining space next to the buttons.
- className="flex-1 min-w-0"
- />
- <PopoverTrigger asChild>
- <Button
- type="button"
- variant="outline"
- size="icon"
- disabled={isSubmitting}
- aria-label="Letzte Suchen öffnen"
- title="Letzte Suchen öffnen"
- className="shrink-0"
- >
- <Clock3 className="h-4 w-4" />
- </Button>
- </PopoverTrigger>
- <Button
- type="submit"
- disabled={!canSearch || isSubmitting}
- className="shrink-0"
- >
- <Search className="h-4 w-4" />
- Suchen
- </Button>
- </div>
- <PopoverContent align="start" className="w-[22rem] p-0">
- {hasHistory ? (
- <div className="overflow-hidden">
- <Command>
- <CommandList className="max-h-72">
- <CommandGroup heading="Letzte Suchen">
- {historyItems.map((entry, index) => (
- <CommandItem
- key={`${entry.routeBranch}|${entry.q}|${entry.createdAt}|${index}`}
- value={`${entry.q} ${entry.routeBranch} ${entry.scope} ${(entry.branches || []).join(" ")}`}
- onSelect={() => {
- if (typeof onSelectRecentSearch !== "function") return;
- onSelectRecentSearch(entry);
- setOpen(false);
- }}
- className="items-start gap-3 py-2"
- >
- <Clock3 className="mt-0.5 h-4 w-4 text-muted-foreground" />
- <div className="min-w-0 flex-1">
- <p className="truncate text-sm font-medium">{entry.q}</p>
- <p className="truncate text-xs text-muted-foreground">
- {getEntryMetaSummary(entry)}
- </p>
- </div>
- </CommandItem>
- ))}
- </CommandGroup>
- </CommandList>
- </Command>
- <div className="border-t p-2">
- <Button
- type="button"
- variant="ghost"
- size="sm"
- className="w-full justify-start"
- disabled={!canClearHistory}
- onClick={() => {
- if (!canClearHistory) return;
- onClearRecentSearches();
- setOpen(false);
- }}
- >
- Verlauf löschen
- </Button>
- </div>
- </div>
- ) : (
- <div className="p-3 text-sm text-muted-foreground">
- Keine letzten Suchen vorhanden.
- </div>
- )}
- </PopoverContent>
- </Popover>
- {currentQuery ? (
- <div className="text-xs text-muted-foreground">
- Aktuelle Suche:{" "}
- <span className="ml-1 rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground">
- {currentQuery}
- </span>
- </div>
- ) : (
- <div className="text-xs text-muted-foreground">
- Tipp: Die Suche ist URL-basiert und kann als Link geteilt werden.
- </div>
- )}
- </div>
- );
- }
|