SearchForm.jsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. "use client";
  2. import React from "react";
  3. import { ChevronDown, Search } from "lucide-react";
  4. import { SEARCH_SCOPE } from "@/lib/frontend/search/urlState";
  5. import { Button } from "@/components/ui/button";
  6. import { Input } from "@/components/ui/input";
  7. import { Label } from "@/components/ui/label";
  8. import { Skeleton } from "@/components/ui/skeleton";
  9. import {
  10. DropdownMenu,
  11. DropdownMenuContent,
  12. DropdownMenuLabel,
  13. DropdownMenuRadioGroup,
  14. DropdownMenuRadioItem,
  15. DropdownMenuSeparator,
  16. DropdownMenuTrigger,
  17. } from "@/components/ui/dropdown-menu";
  18. const SCOPE_LABELS = Object.freeze({
  19. [SEARCH_SCOPE.SINGLE]: "Diese Niederlassung",
  20. [SEARCH_SCOPE.MULTI]: "Mehrere Niederlassungen",
  21. [SEARCH_SCOPE.ALL]: "Alle Niederlassungen",
  22. });
  23. export default function SearchForm({
  24. branch,
  25. qDraft,
  26. onQDraftChange,
  27. onSubmit,
  28. currentQuery,
  29. isSubmitting,
  30. isAdminDev,
  31. scope,
  32. onScopeChange,
  33. availableBranches,
  34. branchesStatus,
  35. selectedBranches,
  36. onToggleBranch,
  37. }) {
  38. const canSearch = typeof qDraft === "string" && qDraft.trim().length > 0;
  39. const scopeLabel = SCOPE_LABELS[scope] || "Unbekannt";
  40. return (
  41. <div className="space-y-4">
  42. <form
  43. onSubmit={(e) => {
  44. e.preventDefault();
  45. if (!canSearch) return;
  46. onSubmit();
  47. }}
  48. className="space-y-3"
  49. >
  50. <div className="grid gap-2">
  51. <Label htmlFor="q">Suchbegriff</Label>
  52. <div className="flex flex-col gap-2 sm:flex-row sm:items-center">
  53. <Input
  54. id="q"
  55. name="q"
  56. value={qDraft}
  57. onChange={(e) => onQDraftChange(e.target.value)}
  58. placeholder="z. B. Bridgestone, Rechnung, Kundennummer…"
  59. disabled={isSubmitting}
  60. />
  61. <Button type="submit" disabled={!canSearch || isSubmitting}>
  62. <Search className="h-4 w-4" />
  63. Suchen
  64. </Button>
  65. </div>
  66. {currentQuery ? (
  67. <div className="text-xs text-muted-foreground">
  68. Aktuelle Suche:{" "}
  69. <span className="ml-1 rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground">
  70. {currentQuery}
  71. </span>
  72. </div>
  73. ) : (
  74. <div className="text-xs text-muted-foreground">
  75. Tipp: Die Suche ist URL-basiert und kann als Link geteilt werden.
  76. </div>
  77. )}
  78. </div>
  79. {isAdminDev ? (
  80. <div className="grid gap-2">
  81. <Label>Suchbereich</Label>
  82. <div className="flex flex-wrap items-center gap-2">
  83. <DropdownMenu>
  84. <DropdownMenuTrigger asChild>
  85. <Button
  86. type="button"
  87. variant="outline"
  88. disabled={isSubmitting}
  89. title="Suchbereich auswählen"
  90. >
  91. {scopeLabel}
  92. <ChevronDown className="h-4 w-4" />
  93. </Button>
  94. </DropdownMenuTrigger>
  95. <DropdownMenuContent align="start" className="min-w-[16rem]">
  96. <DropdownMenuLabel>Suchbereich</DropdownMenuLabel>
  97. <DropdownMenuSeparator />
  98. <DropdownMenuRadioGroup
  99. value={scope}
  100. onValueChange={(value) => onScopeChange(value)}
  101. >
  102. <DropdownMenuRadioItem value={SEARCH_SCOPE.SINGLE}>
  103. Diese Niederlassung ({branch})
  104. </DropdownMenuRadioItem>
  105. <DropdownMenuRadioItem value={SEARCH_SCOPE.MULTI}>
  106. Mehrere Niederlassungen
  107. </DropdownMenuRadioItem>
  108. <DropdownMenuRadioItem value={SEARCH_SCOPE.ALL}>
  109. Alle Niederlassungen
  110. </DropdownMenuRadioItem>
  111. </DropdownMenuRadioGroup>
  112. </DropdownMenuContent>
  113. </DropdownMenu>
  114. {scope === SEARCH_SCOPE.SINGLE ? (
  115. <span className="text-xs text-muted-foreground">
  116. Aktiv: {branch}
  117. </span>
  118. ) : null}
  119. </div>
  120. </div>
  121. ) : null}
  122. </form>
  123. {isAdminDev && scope === SEARCH_SCOPE.MULTI ? (
  124. <div className="rounded-lg border bg-card p-4 text-card-foreground shadow-sm">
  125. <div className="space-y-2">
  126. <p className="text-sm font-medium">Niederlassungen auswählen</p>
  127. <p className="text-xs text-muted-foreground">
  128. Wählen Sie eine oder mehrere Niederlassungen. Die Suche wird nach
  129. jeder Änderung aktualisiert.
  130. </p>
  131. {branchesStatus === "loading" ? (
  132. <div className="grid grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-4">
  133. {Array.from({ length: 8 }).map((_, i) => (
  134. <div key={i} className="flex items-center gap-2">
  135. <Skeleton className="h-4 w-4" />
  136. <Skeleton className="h-4 w-16" />
  137. </div>
  138. ))}
  139. </div>
  140. ) : branchesStatus === "error" ? (
  141. <p className="text-sm text-muted-foreground">
  142. Niederlassungen konnten nicht geladen werden.
  143. </p>
  144. ) : (
  145. <div className="grid grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-4">
  146. {(Array.isArray(availableBranches)
  147. ? availableBranches
  148. : []
  149. ).map((b) => {
  150. const checked = Array.isArray(selectedBranches)
  151. ? selectedBranches.includes(b)
  152. : false;
  153. return (
  154. <label
  155. key={b}
  156. className="flex items-center gap-2 rounded-md px-2 py-1 hover:bg-muted/50"
  157. >
  158. <input
  159. type="checkbox"
  160. checked={checked}
  161. onChange={() => onToggleBranch(b)}
  162. disabled={isSubmitting}
  163. />
  164. <span className="text-sm">{b}</span>
  165. </label>
  166. );
  167. })}
  168. </div>
  169. )}
  170. </div>
  171. </div>
  172. ) : null}
  173. </div>
  174. );
  175. }