|
|
@@ -1,33 +1,14 @@
|
|
|
"use client";
|
|
|
|
|
|
import React from "react";
|
|
|
-import { ChevronDown, Search } from "lucide-react";
|
|
|
|
|
|
-import {
|
|
|
- SEARCH_SCOPE,
|
|
|
- SEARCH_LIMITS,
|
|
|
- DEFAULT_SEARCH_LIMIT,
|
|
|
-} from "@/lib/frontend/search/urlState";
|
|
|
+import { SEARCH_SCOPE } from "@/lib/frontend/search/urlState";
|
|
|
|
|
|
-import { Button } from "@/components/ui/button";
|
|
|
-import { Input } from "@/components/ui/input";
|
|
|
-import { Label } from "@/components/ui/label";
|
|
|
-import { Skeleton } from "@/components/ui/skeleton";
|
|
|
-import {
|
|
|
- DropdownMenu,
|
|
|
- DropdownMenuContent,
|
|
|
- DropdownMenuLabel,
|
|
|
- DropdownMenuRadioGroup,
|
|
|
- DropdownMenuRadioItem,
|
|
|
- DropdownMenuSeparator,
|
|
|
- DropdownMenuTrigger,
|
|
|
-} from "@/components/ui/dropdown-menu";
|
|
|
-
|
|
|
-const SCOPE_LABELS = Object.freeze({
|
|
|
- [SEARCH_SCOPE.SINGLE]: "Diese Niederlassung",
|
|
|
- [SEARCH_SCOPE.MULTI]: "Mehrere Niederlassungen",
|
|
|
- [SEARCH_SCOPE.ALL]: "Alle Niederlassungen",
|
|
|
-});
|
|
|
+import SearchQueryBox from "@/components/search/form/SearchQueryBox";
|
|
|
+import SearchScopeSelect from "@/components/search/form/SearchScopeSelect";
|
|
|
+import SearchLimitSelect from "@/components/search/form/SearchLimitSelect";
|
|
|
+import SearchSingleBranchCombobox from "@/components/search/form/SearchSingleBranchCombobox";
|
|
|
+import SearchMultiBranchPicker from "@/components/search/form/SearchMultiBranchPicker";
|
|
|
|
|
|
export default function SearchForm({
|
|
|
branch,
|
|
|
@@ -39,6 +20,7 @@ export default function SearchForm({
|
|
|
isAdminDev,
|
|
|
scope,
|
|
|
onScopeChange,
|
|
|
+ onSingleBranchChange,
|
|
|
availableBranches,
|
|
|
branchesStatus,
|
|
|
selectedBranches,
|
|
|
@@ -48,13 +30,6 @@ export default function SearchForm({
|
|
|
}) {
|
|
|
const canSearch = typeof qDraft === "string" && qDraft.trim().length > 0;
|
|
|
|
|
|
- const scopeLabel = SCOPE_LABELS[scope] || "Unbekannt";
|
|
|
-
|
|
|
- const normalizedLimit =
|
|
|
- Number.isInteger(limit) && SEARCH_LIMITS.includes(limit)
|
|
|
- ? limit
|
|
|
- : DEFAULT_SEARCH_LIMIT;
|
|
|
-
|
|
|
return (
|
|
|
<div className="space-y-4">
|
|
|
<form
|
|
|
@@ -65,177 +40,51 @@ export default function SearchForm({
|
|
|
}}
|
|
|
className="space-y-3"
|
|
|
>
|
|
|
- <div className="grid gap-2">
|
|
|
- <Label htmlFor="q">Suchbegriff</Label>
|
|
|
-
|
|
|
- <div className="flex flex-col gap-2 sm:flex-row sm:items-center">
|
|
|
- <Input
|
|
|
- id="q"
|
|
|
- name="q"
|
|
|
- value={qDraft}
|
|
|
- onChange={(e) => onQDraftChange(e.target.value)}
|
|
|
- placeholder="z. B. Bridgestone, Rechnung, Kundennummer…"
|
|
|
- disabled={isSubmitting}
|
|
|
- />
|
|
|
-
|
|
|
- <Button type="submit" disabled={!canSearch || isSubmitting}>
|
|
|
- <Search className="h-4 w-4" />
|
|
|
- Suchen
|
|
|
- </Button>
|
|
|
- </div>
|
|
|
-
|
|
|
- {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>
|
|
|
+ <SearchQueryBox
|
|
|
+ qDraft={qDraft}
|
|
|
+ onQDraftChange={onQDraftChange}
|
|
|
+ onSubmit={onSubmit}
|
|
|
+ currentQuery={currentQuery}
|
|
|
+ isSubmitting={isSubmitting}
|
|
|
+ canSearch={canSearch}
|
|
|
+ />
|
|
|
|
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
|
{isAdminDev ? (
|
|
|
- <div className="grid gap-2">
|
|
|
- <Label>Suchbereich</Label>
|
|
|
-
|
|
|
- <DropdownMenu>
|
|
|
- <DropdownMenuTrigger asChild>
|
|
|
- <Button
|
|
|
- type="button"
|
|
|
- variant="outline"
|
|
|
- disabled={isSubmitting}
|
|
|
- title="Suchbereich auswählen"
|
|
|
- >
|
|
|
- {scopeLabel}
|
|
|
- <ChevronDown className="h-4 w-4" />
|
|
|
- </Button>
|
|
|
- </DropdownMenuTrigger>
|
|
|
-
|
|
|
- <DropdownMenuContent align="start" className="min-w-[16rem]">
|
|
|
- <DropdownMenuLabel>Suchbereich</DropdownMenuLabel>
|
|
|
- <DropdownMenuSeparator />
|
|
|
-
|
|
|
- <DropdownMenuRadioGroup
|
|
|
- value={scope}
|
|
|
- onValueChange={(value) => onScopeChange(value)}
|
|
|
- >
|
|
|
- <DropdownMenuRadioItem value={SEARCH_SCOPE.SINGLE}>
|
|
|
- Diese Niederlassung ({branch})
|
|
|
- </DropdownMenuRadioItem>
|
|
|
- <DropdownMenuRadioItem value={SEARCH_SCOPE.MULTI}>
|
|
|
- Mehrere Niederlassungen
|
|
|
- </DropdownMenuRadioItem>
|
|
|
- <DropdownMenuRadioItem value={SEARCH_SCOPE.ALL}>
|
|
|
- Alle Niederlassungen
|
|
|
- </DropdownMenuRadioItem>
|
|
|
- </DropdownMenuRadioGroup>
|
|
|
- </DropdownMenuContent>
|
|
|
- </DropdownMenu>
|
|
|
- </div>
|
|
|
+ <SearchScopeSelect
|
|
|
+ branch={branch}
|
|
|
+ scope={scope}
|
|
|
+ onScopeChange={onScopeChange}
|
|
|
+ isSubmitting={isSubmitting}
|
|
|
+ />
|
|
|
) : null}
|
|
|
|
|
|
- <div className="grid gap-2">
|
|
|
- <Label>Treffer pro Seite</Label>
|
|
|
-
|
|
|
- <DropdownMenu>
|
|
|
- <DropdownMenuTrigger asChild>
|
|
|
- <Button
|
|
|
- type="button"
|
|
|
- variant="outline"
|
|
|
- disabled={isSubmitting}
|
|
|
- title="Treffer pro Seite auswählen"
|
|
|
- >
|
|
|
- {normalizedLimit}
|
|
|
- <ChevronDown className="h-4 w-4" />
|
|
|
- </Button>
|
|
|
- </DropdownMenuTrigger>
|
|
|
-
|
|
|
- <DropdownMenuContent align="start" className="min-w-[12rem]">
|
|
|
- <DropdownMenuLabel>Treffer pro Seite</DropdownMenuLabel>
|
|
|
- <DropdownMenuSeparator />
|
|
|
-
|
|
|
- <DropdownMenuRadioGroup
|
|
|
- value={String(normalizedLimit)}
|
|
|
- onValueChange={(value) => {
|
|
|
- const n = Number(value);
|
|
|
- if (!Number.isInteger(n)) return;
|
|
|
- onLimitChange(n);
|
|
|
- }}
|
|
|
- >
|
|
|
- {SEARCH_LIMITS.map((n) => (
|
|
|
- <DropdownMenuRadioItem key={n} value={String(n)}>
|
|
|
- {n}
|
|
|
- </DropdownMenuRadioItem>
|
|
|
- ))}
|
|
|
- </DropdownMenuRadioGroup>
|
|
|
- </DropdownMenuContent>
|
|
|
- </DropdownMenu>
|
|
|
- </div>
|
|
|
-
|
|
|
{isAdminDev && scope === SEARCH_SCOPE.SINGLE ? (
|
|
|
- <span className="pt-6 text-xs text-muted-foreground">
|
|
|
- Aktiv: {branch}
|
|
|
- </span>
|
|
|
+ <SearchSingleBranchCombobox
|
|
|
+ branch={branch}
|
|
|
+ branchesStatus={branchesStatus}
|
|
|
+ availableBranches={availableBranches}
|
|
|
+ onSingleBranchChange={onSingleBranchChange}
|
|
|
+ isSubmitting={isSubmitting}
|
|
|
+ />
|
|
|
) : null}
|
|
|
+
|
|
|
+ <SearchLimitSelect
|
|
|
+ limit={limit}
|
|
|
+ onLimitChange={onLimitChange}
|
|
|
+ isSubmitting={isSubmitting}
|
|
|
+ />
|
|
|
</div>
|
|
|
</form>
|
|
|
|
|
|
{isAdminDev && scope === SEARCH_SCOPE.MULTI ? (
|
|
|
- <div className="rounded-lg border bg-card p-4 text-card-foreground shadow-sm">
|
|
|
- <div className="space-y-2">
|
|
|
- <p className="text-sm font-medium">Niederlassungen auswählen</p>
|
|
|
- <p className="text-xs text-muted-foreground">
|
|
|
- Wählen Sie eine oder mehrere Niederlassungen. Die Suche wird nach
|
|
|
- jeder Änderung aktualisiert.
|
|
|
- </p>
|
|
|
-
|
|
|
- {branchesStatus === "loading" ? (
|
|
|
- <div className="grid grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-4">
|
|
|
- {Array.from({ length: 8 }).map((_, i) => (
|
|
|
- <div key={i} className="flex items-center gap-2">
|
|
|
- <Skeleton className="h-4 w-4" />
|
|
|
- <Skeleton className="h-4 w-16" />
|
|
|
- </div>
|
|
|
- ))}
|
|
|
- </div>
|
|
|
- ) : branchesStatus === "error" ? (
|
|
|
- <p className="text-sm text-muted-foreground">
|
|
|
- Niederlassungen konnten nicht geladen werden.
|
|
|
- </p>
|
|
|
- ) : (
|
|
|
- <div className="grid grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-4">
|
|
|
- {(Array.isArray(availableBranches)
|
|
|
- ? availableBranches
|
|
|
- : []
|
|
|
- ).map((b) => {
|
|
|
- const checked = Array.isArray(selectedBranches)
|
|
|
- ? selectedBranches.includes(b)
|
|
|
- : false;
|
|
|
-
|
|
|
- return (
|
|
|
- <label
|
|
|
- key={b}
|
|
|
- className="flex items-center gap-2 rounded-md px-2 py-1 hover:bg-muted/50"
|
|
|
- >
|
|
|
- <input
|
|
|
- type="checkbox"
|
|
|
- checked={checked}
|
|
|
- onChange={() => onToggleBranch(b)}
|
|
|
- disabled={isSubmitting}
|
|
|
- />
|
|
|
- <span className="text-sm">{b}</span>
|
|
|
- </label>
|
|
|
- );
|
|
|
- })}
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <SearchMultiBranchPicker
|
|
|
+ branchesStatus={branchesStatus}
|
|
|
+ availableBranches={availableBranches}
|
|
|
+ selectedBranches={selectedBranches}
|
|
|
+ onToggleBranch={onToggleBranch}
|
|
|
+ isSubmitting={isSubmitting}
|
|
|
+ />
|
|
|
) : null}
|
|
|
</div>
|
|
|
);
|