diff --git a/src/app/(protected)/workorders/_components/list/advanced-search/adv-filter-sections.tsx b/src/app/(protected)/workorders/_components/list/advanced-search/adv-filter-sections.tsx new file mode 100644 index 00000000..9bfed9a1 --- /dev/null +++ b/src/app/(protected)/workorders/_components/list/advanced-search/adv-filter-sections.tsx @@ -0,0 +1,187 @@ +import type { Dispatch, SetStateAction } from "react"; +import { + CalendarDays, + CheckCircle2, + FileText, + Flame, + MapPin, + Tag, + Truck, + UserX, + Users, +} from "lucide-react"; +import { AdvDocPill, AdvFilterPill, AdvStatusPill } from "./adv-filter-pill"; +import { AdvMultiSelect } from "./adv-multi-select"; +import { AdvSection } from "./adv-section"; +import { AdvTypeGrid } from "./adv-type-grid"; +import { DateField } from "@/components/ui/date-input"; +import { SelectAllAction } from "@/components/ui/select-all-action"; +import { + ALL_WIZARD_STATUSES, + ASSIGNEE_FILTER_UNASSIGNED, + COMPLETION_DOC_OPTIONS, + DATE_RANGE_OPTIONS, + DOC_FILTER_LABELS, + type AdvancedFilterSetKey, + type WorkOrderAdvancedFilters, +} from "@/domain/work-orders/types/work-order-advanced-filters"; +import type { WorkOrderUser } from "@/domain/work-orders/types/work-order"; +import { WIZARD_STATUS_DOTS } from "@/domain/work-orders/types/work-order-wizard"; +import { isDefaultDateRange } from "@/domain/work-orders/utils/advanced-filters-utils"; + +type AdvancedSearchFilterSectionsProps = { + draft: WorkOrderAdvancedFilters; + setDraft: Dispatch>; + toggleIn: (key: AdvancedFilterSetKey, value: string) => void; + setMany: (key: AdvancedFilterSetKey, values: string[], select: boolean) => void; + sites: { id: string | number; name: string }[]; + users: WorkOrderUser[]; + pmTypes: string[]; + vendorTechs: string[]; +}; + +export function AdvancedSearchFilterSections({ + draft, + setDraft, + toggleIn, + setMany, + sites, + users, + pmTypes, + vendorTechs, +}: AdvancedSearchFilterSectionsProps) { + const selectAllRow = (key: AdvancedFilterSetKey, all: string[], selectedSize: number) => ( +
+ 0} + onClick={() => setMany(key, all, selectedSize === 0)} + /> +
+ ); + + return ( +
+ +
+ {DATE_RANGE_OPTIONS.map((option) => ( + setDraft((prev) => ({ ...prev, dateRange: option.key }))} + /> + ))} +
+ {draft.dateRange === "custom" && ( +
+ setDraft((prev) => ({ ...prev, customFrom: value }))} + /> + setDraft((prev) => ({ ...prev, customTo: value }))} + /> +
+ )} +
+ + + ({ + value: String(site.id), + label: site.name, + }))} + selected={draft.sites} + onToggle={(value) => toggleIn("sites", value)} + onBulkToggle={(values, select) => setMany("sites", values, select)} + placeholder="Search site…" + /> + + + + toggleIn("types", type)} + onSelectAll={(types, select) => setMany("types", types, select)} + /> + + + + ({ value: String(user.id), label: user.name })), + ]} + selected={draft.dispatchers} + onToggle={(value) => toggleIn("dispatchers", value)} + onBulkToggle={(values, select) => setMany("dispatchers", values, select)} + placeholder="Search assigned to…" + renderLeft={(id) => + id === ASSIGNEE_FILTER_UNASSIGNED ? ( + + ) : ( + + ) + } + /> + + + + {selectAllRow("statuses", [...ALL_WIZARD_STATUSES], draft.statuses.size)} +
+ {ALL_WIZARD_STATUSES.map((status) => ( + toggleIn("statuses", status)} + /> + ))} +
+
+ + + ({ value: pmType, label: pmType }))} + selected={draft.pmTypes} + onToggle={(value) => toggleIn("pmTypes", value)} + onBulkToggle={(values, select) => setMany("pmTypes", values, select)} + placeholder="Search pm type…" + /> + + + + ({ value: name, label: name }))} + selected={draft.vendorTechs} + onToggle={(value) => toggleIn("vendorTechs", value)} + onBulkToggle={(values, select) => setMany("vendorTechs", values, select)} + placeholder="Search vendor / tech…" + /> + + + + {selectAllRow("docs", [...COMPLETION_DOC_OPTIONS], draft.docs.size)} +
+ {COMPLETION_DOC_OPTIONS.map((doc) => ( + toggleIn("docs", doc)} + /> + ))} +
+
+
+ ); +} diff --git a/src/app/(protected)/workorders/_components/list/advanced-search/use-advanced-search-draft.ts b/src/app/(protected)/workorders/_components/list/advanced-search/use-advanced-search-draft.ts new file mode 100644 index 00000000..4f367f18 --- /dev/null +++ b/src/app/(protected)/workorders/_components/list/advanced-search/use-advanced-search-draft.ts @@ -0,0 +1,68 @@ +import { useEffect, useState } from "react"; +import { + EMPTY_ADVANCED_FILTERS, + type AdvancedFilterSetKey, + type WorkOrderAdvancedFilters, +} from "@/domain/work-orders/types/work-order-advanced-filters"; +import { + cloneAdvancedFilters, + countAdvancedFilters, +} from "@/domain/work-orders/utils/advanced-filters-utils"; + +type UseAdvancedSearchDraftOptions = { + open: boolean; + initial: WorkOrderAdvancedFilters; + onApply: (filters: WorkOrderAdvancedFilters) => void; + onClearAll: () => void; + onOpenChange: (open: boolean) => void; +}; + +export function useAdvancedSearchDraft({ + open, + initial, + onApply, + onClearAll, + onOpenChange, +}: UseAdvancedSearchDraftOptions) { + const [draft, setDraft] = useState(initial); + + useEffect(() => { + if (open) { + setDraft(cloneAdvancedFilters(initial)); + } + }, [open, initial]); + + const activeCount = countAdvancedFilters(draft); + + const toggleIn = (key: AdvancedFilterSetKey, value: string) => { + setDraft((prev) => { + const next = new Set(prev[key] as Set); + if (next.has(value)) { + next.delete(value); + } else { + next.add(value); + } + return { ...prev, [key]: next }; + }); + }; + + const setMany = (key: AdvancedFilterSetKey, values: string[], select: boolean) => { + setDraft((prev) => { + const next = new Set(prev[key] as Set); + values.forEach((value) => (select ? next.add(value) : next.delete(value))); + return { ...prev, [key]: next }; + }); + }; + + const handleClearAll = () => { + setDraft(cloneAdvancedFilters(EMPTY_ADVANCED_FILTERS)); + onClearAll(); + }; + + const handleApply = () => { + onApply(cloneAdvancedFilters(draft)); + onOpenChange(false); + }; + + return { draft, setDraft, activeCount, toggleIn, setMany, handleClearAll, handleApply }; +} diff --git a/src/app/(protected)/workorders/_components/list/advanced-search/work-orders-advanced-search-sheet.tsx b/src/app/(protected)/workorders/_components/list/advanced-search/work-orders-advanced-search-sheet.tsx index 06a3b283..c42e1276 100644 --- a/src/app/(protected)/workorders/_components/list/advanced-search/work-orders-advanced-search-sheet.tsx +++ b/src/app/(protected)/workorders/_components/list/advanced-search/work-orders-advanced-search-sheet.tsx @@ -1,42 +1,11 @@ -import { useEffect, useState } from "react"; -import { - CalendarDays, - CheckCircle2, - FileText, - Flame, - MapPin, - SlidersHorizontal, - Tag, - Truck, - UserX, - Users, -} from "lucide-react"; -import { AdvDocPill, AdvFilterPill, AdvStatusPill } from "./adv-filter-pill"; -import { AdvMultiSelect } from "./adv-multi-select"; -import { AdvSection } from "./adv-section"; -import { AdvTypeGrid } from "./adv-type-grid"; +import { SlidersHorizontal } from "lucide-react"; +import { AdvancedSearchFilterSections } from "./adv-filter-sections"; +import { useAdvancedSearchDraft } from "./use-advanced-search-draft"; import { Button } from "@/components/ui/button"; -import { DateField } from "@/components/ui/date-input"; import { IconCloseButton } from "@/components/ui/icon-close-button"; -import { SelectAllAction } from "@/components/ui/select-all-action"; import { SideSheet } from "@/components/ui/side-sheet"; -import { - ALL_WIZARD_STATUSES, - ASSIGNEE_FILTER_UNASSIGNED, - COMPLETION_DOC_OPTIONS, - DATE_RANGE_OPTIONS, - DOC_FILTER_LABELS, - EMPTY_ADVANCED_FILTERS, - type AdvancedFilterSetKey, - type WorkOrderAdvancedFilters, -} from "@/domain/work-orders/types/work-order-advanced-filters"; +import type { WorkOrderAdvancedFilters } from "@/domain/work-orders/types/work-order-advanced-filters"; import type { WorkOrderUser } from "@/domain/work-orders/types/work-order"; -import { WIZARD_STATUS_DOTS } from "@/domain/work-orders/types/work-order-wizard"; -import { - cloneAdvancedFilters, - countAdvancedFilters, - isDefaultDateRange, -} from "@/domain/work-orders/utils/advanced-filters-utils"; type WorkOrdersAdvancedSearchSheetProps = { open: boolean; @@ -61,54 +30,8 @@ export function WorkOrdersAdvancedSearchSheet({ pmTypes, vendorTechs, }: WorkOrdersAdvancedSearchSheetProps) { - const [draft, setDraft] = useState(initial); - - useEffect(() => { - if (open) { - setDraft(cloneAdvancedFilters(initial)); - } - }, [open, initial]); - - const activeCount = countAdvancedFilters(draft); - - const toggleIn = (key: AdvancedFilterSetKey, value: string) => { - setDraft((prev) => { - const next = new Set(prev[key] as Set); - if (next.has(value)) { - next.delete(value); - } else { - next.add(value); - } - return { ...prev, [key]: next }; - }); - }; - - const setMany = (key: AdvancedFilterSetKey, values: string[], select: boolean) => { - setDraft((prev) => { - const next = new Set(prev[key] as Set); - values.forEach((value) => (select ? next.add(value) : next.delete(value))); - return { ...prev, [key]: next }; - }); - }; - - const selectAllRow = (key: AdvancedFilterSetKey, all: string[], selectedSize: number) => ( -
- 0} - onClick={() => setMany(key, all, selectedSize === 0)} - /> -
- ); - - const handleClearAll = () => { - setDraft(cloneAdvancedFilters(EMPTY_ADVANCED_FILTERS)); - onClearAll(); - }; - - const handleApply = () => { - onApply(cloneAdvancedFilters(draft)); - onOpenChange(false); - }; + const { draft, setDraft, activeCount, toggleIn, setMany, handleClearAll, handleApply } = + useAdvancedSearchDraft({ open, initial, onApply, onClearAll, onOpenChange }); return ( onOpenChange(false)} aria-label="Advanced Filters"> @@ -129,129 +52,16 @@ export function WorkOrdersAdvancedSearchSheet({ onOpenChange(false)} /> -
- -
- {DATE_RANGE_OPTIONS.map((option) => ( - setDraft((prev) => ({ ...prev, dateRange: option.key }))} - /> - ))} -
- {draft.dateRange === "custom" && ( -
- setDraft((prev) => ({ ...prev, customFrom: value }))} - /> - setDraft((prev) => ({ ...prev, customTo: value }))} - /> -
- )} -
- - - ({ - value: String(site.id), - label: site.name, - }))} - selected={draft.sites} - onToggle={(value) => toggleIn("sites", value)} - onBulkToggle={(values, select) => setMany("sites", values, select)} - placeholder="Search site…" - /> - - - - toggleIn("types", type)} - onSelectAll={(types, select) => setMany("types", types, select)} - /> - - - - ({ value: String(user.id), label: user.name })), - ]} - selected={draft.dispatchers} - onToggle={(value) => toggleIn("dispatchers", value)} - onBulkToggle={(values, select) => setMany("dispatchers", values, select)} - placeholder="Search assigned to…" - renderLeft={(id) => - id === ASSIGNEE_FILTER_UNASSIGNED ? ( - - ) : ( - - ) - } - /> - - - - {selectAllRow("statuses", [...ALL_WIZARD_STATUSES], draft.statuses.size)} -
- {ALL_WIZARD_STATUSES.map((status) => ( - toggleIn("statuses", status)} - /> - ))} -
-
- - - ({ value: pmType, label: pmType }))} - selected={draft.pmTypes} - onToggle={(value) => toggleIn("pmTypes", value)} - onBulkToggle={(values, select) => setMany("pmTypes", values, select)} - placeholder="Search pm type…" - /> - - - - ({ value: name, label: name }))} - selected={draft.vendorTechs} - onToggle={(value) => toggleIn("vendorTechs", value)} - onBulkToggle={(values, select) => setMany("vendorTechs", values, select)} - placeholder="Search vendor / tech…" - /> - - - - {selectAllRow("docs", [...COMPLETION_DOC_OPTIONS], draft.docs.size)} -
- {COMPLETION_DOC_OPTIONS.map((doc) => ( - toggleIn("docs", doc)} - /> - ))} -
-
-
+