SearchForm.jsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. "use client";
  2. import React from "react";
  3. import { AlertCircleIcon } from "lucide-react";
  4. import { SEARCH_SCOPE } from "@/lib/frontend/search/urlState";
  5. import SearchQueryBox from "@/components/search/form/SearchQueryBox";
  6. import SearchScopeSelect from "@/components/search/form/SearchScopeSelect";
  7. import SearchLimitSelect from "@/components/search/form/SearchLimitSelect";
  8. import SearchSingleBranchCombobox from "@/components/search/form/SearchSingleBranchCombobox";
  9. import SearchMultiBranchPicker from "@/components/search/form/SearchMultiBranchPicker";
  10. import SearchDateRangePicker from "@/components/search/form/SearchDateRangePicker";
  11. import SearchDateFilterChip from "@/components/search/form/SearchDateFilterChip";
  12. import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
  13. export default function SearchForm({
  14. branch,
  15. qDraft,
  16. onQDraftChange,
  17. onSubmit,
  18. currentQuery,
  19. isSubmitting,
  20. isAdminDev,
  21. scope,
  22. onScopeChange,
  23. onSingleBranchChange,
  24. availableBranches,
  25. branchesStatus,
  26. selectedBranches,
  27. onToggleBranch,
  28. onClearAllBranches,
  29. limit,
  30. onLimitChange,
  31. from,
  32. to,
  33. onDateRangeChange,
  34. validationError,
  35. recentSearches,
  36. onSelectRecentSearch,
  37. onClearRecentSearches,
  38. }) {
  39. const canSearch = typeof qDraft === "string" && qDraft.trim().length > 0;
  40. // SINGLE scope (admin/dev only) shows an extra branch combobox.
  41. const showSingleBranchCombobox = Boolean(
  42. isAdminDev && scope === SEARCH_SCOPE.SINGLE
  43. );
  44. const hasDateFilter = Boolean(from || to);
  45. return (
  46. <div className="space-y-4">
  47. <form
  48. onSubmit={(e) => {
  49. e.preventDefault();
  50. if (!canSearch) return;
  51. onSubmit();
  52. }}
  53. className="space-y-4"
  54. >
  55. {/* Row 1: query full-width */}
  56. <div className="w-full">
  57. <SearchQueryBox
  58. qDraft={qDraft}
  59. onQDraftChange={onQDraftChange}
  60. onSubmit={onSubmit}
  61. currentQuery={currentQuery}
  62. isSubmitting={isSubmitting}
  63. canSearch={canSearch}
  64. recentSearches={recentSearches}
  65. onSelectRecentSearch={onSelectRecentSearch}
  66. onClearRecentSearches={onClearRecentSearches}
  67. />
  68. </div>
  69. {/* Row 2: left controls + spacer + limit on the right */}
  70. <div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
  71. {/* Left group: scope (+ optional single branch combobox) + date range */}
  72. <div className="flex flex-col gap-3 sm:flex-row sm:items-end">
  73. {isAdminDev ? (
  74. <div className="flex items-end gap-3">
  75. <SearchScopeSelect
  76. branch={branch}
  77. scope={scope}
  78. onScopeChange={onScopeChange}
  79. isSubmitting={isSubmitting}
  80. />
  81. {/* Animated presence (kept for smoothness when SINGLE shows the extra combobox) */}
  82. <div
  83. className={[
  84. "overflow-hidden transition-all duration-200 ease-in-out",
  85. showSingleBranchCombobox
  86. ? "max-w-44 max-h-24 opacity-100"
  87. : "max-w-0 max-h-0 opacity-0 pointer-events-none",
  88. ].join(" ")}
  89. aria-hidden={showSingleBranchCombobox ? "false" : "true"}
  90. >
  91. <SearchSingleBranchCombobox
  92. branch={branch}
  93. branchesStatus={branchesStatus}
  94. availableBranches={availableBranches}
  95. onSingleBranchChange={onSingleBranchChange}
  96. isSubmitting={isSubmitting || !showSingleBranchCombobox}
  97. />
  98. </div>
  99. </div>
  100. ) : null}
  101. <div className="shrink-0 flex items-end gap-4">
  102. <SearchDateRangePicker
  103. from={from}
  104. to={to}
  105. onDateRangeChange={onDateRangeChange}
  106. isSubmitting={isSubmitting}
  107. />
  108. {/* Active date filter chip (quick clear) */}
  109. {hasDateFilter ? (
  110. <div className="flex flex-wrap items-center gap-2 mb-2">
  111. <span className="text-xs text-muted-foreground">
  112. Aktive Filter:
  113. </span>
  114. <SearchDateFilterChip
  115. from={from}
  116. to={to}
  117. isSubmitting={isSubmitting}
  118. onClear={() => {
  119. if (typeof onDateRangeChange !== "function") return;
  120. onDateRangeChange({ from: null, to: null });
  121. }}
  122. />
  123. </div>
  124. ) : null}
  125. </div>
  126. </div>
  127. {/* Right group: limit aligned right */}
  128. <div className="shrink-0">
  129. <SearchLimitSelect
  130. limit={limit}
  131. onLimitChange={onLimitChange}
  132. isSubmitting={isSubmitting}
  133. />
  134. </div>
  135. </div>
  136. </form>
  137. {/* Validation feedback belongs near the inputs (not in results). */}
  138. {validationError ? (
  139. <Alert variant="destructive">
  140. <AlertCircleIcon />
  141. <AlertTitle>{validationError.title}</AlertTitle>
  142. <AlertDescription>{validationError.description}</AlertDescription>
  143. </Alert>
  144. ) : null}
  145. {/* Multi scope branch picker remains below */}
  146. {isAdminDev && scope === SEARCH_SCOPE.MULTI ? (
  147. <SearchMultiBranchPicker
  148. branchesStatus={branchesStatus}
  149. availableBranches={availableBranches}
  150. selectedBranches={selectedBranches}
  151. onToggleBranch={onToggleBranch}
  152. onClearAllBranches={onClearAllBranches}
  153. isSubmitting={isSubmitting}
  154. />
  155. ) : null}
  156. </div>
  157. );
  158. }