@salesforce/webapp-template-app-react-sample-b2e-experimental 1.117.0 → 1.117.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/CHANGELOG.md +8 -0
  2. package/dist/force-app/main/default/webapplications/propertymanagementapp/package.json +3 -3
  3. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/components/layout/FilterRow.tsx +1 -1
  4. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/features/object-search/__examples__/pages/AccountSearch.tsx +15 -6
  5. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/features/object-search/components/FilterContext.tsx +1 -32
  6. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/features/object-search/components/filters/BooleanFilter.tsx +9 -5
  7. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/features/object-search/components/filters/DateFilter.tsx +15 -8
  8. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/features/object-search/components/filters/DateRangeFilter.tsx +8 -7
  9. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/features/object-search/components/filters/FilterFieldWrapper.tsx +33 -0
  10. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/features/object-search/components/filters/MultiSelectFilter.tsx +4 -5
  11. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/features/object-search/components/filters/NumericRangeFilter.tsx +113 -82
  12. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/features/object-search/components/filters/SearchFilter.tsx +24 -11
  13. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/features/object-search/components/filters/SelectFilter.tsx +9 -5
  14. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/features/object-search/components/filters/TextFilter.tsx +29 -12
  15. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/features/object-search/hooks/useDebouncedCallback.ts +34 -0
  16. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/features/object-search/hooks/useObjectSearchParams.ts +5 -86
  17. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/features/object-search/utils/debounce.ts +4 -1
  18. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/features/object-search/utils/filterUtils.ts +24 -1
  19. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/pages/ApplicationSearch.tsx +13 -18
  20. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/pages/MaintenanceRequestSearch.tsx +13 -18
  21. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/pages/MaintenanceWorkerSearch.tsx +11 -18
  22. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/pages/PropertySearch.tsx +6 -15
  23. package/dist/package-lock.json +2 -2
  24. package/dist/package.json +1 -1
  25. package/package.json +2 -2
  26. package/dist/force-app/main/default/webapplications/propertymanagementapp/src/lib/filterUtils.ts +0 -11
@@ -1,7 +1,9 @@
1
- import { Label } from "../../../../components/ui/label";
2
- import { cn } from "../../../../lib/utils";
1
+ import { useEffect, useState } from "react";
2
+
3
3
  import { SearchBar } from "../SearchBar";
4
4
  import { useFilterField } from "../FilterContext";
5
+ import { FilterFieldWrapper } from "./FilterFieldWrapper";
6
+ import { useDebouncedCallback } from "../../hooks/useDebouncedCallback";
5
7
 
6
8
  interface SearchFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
7
9
  field: string;
@@ -17,21 +19,32 @@ export function SearchFilter({
17
19
  ...props
18
20
  }: SearchFilterProps) {
19
21
  const { value, onChange } = useFilterField(field);
22
+ const [localValue, setLocalValue] = useState(value?.value ?? "");
23
+
24
+ const externalValue = value?.value ?? "";
25
+ useEffect(() => {
26
+ setLocalValue(externalValue);
27
+ }, [externalValue]);
28
+
29
+ const debouncedOnChange = useDebouncedCallback((v: string) => {
30
+ if (v) {
31
+ onChange({ field, label, type: "search", value: v });
32
+ } else {
33
+ onChange(undefined);
34
+ }
35
+ });
36
+
20
37
  return (
21
- <div className={cn("space-y-1.5", className)} {...props}>
22
- <Label htmlFor={`filter-${field}`}>{label}</Label>
38
+ <FilterFieldWrapper label={label} htmlFor={`filter-${field}`} className={className} {...props}>
23
39
  <SearchBar
24
- value={value?.value ?? ""}
40
+ value={localValue}
25
41
  handleChange={(v) => {
26
- if (v) {
27
- onChange({ field, label, type: "search", value: v });
28
- } else {
29
- onChange(undefined);
30
- }
42
+ setLocalValue(v);
43
+ debouncedOnChange(v);
31
44
  }}
32
45
  placeholder={placeholder}
33
46
  inputProps={{ id: `filter-${field}` }}
34
47
  />
35
- </div>
48
+ </FilterFieldWrapper>
36
49
  );
37
50
  }
@@ -5,9 +5,9 @@ import {
5
5
  SelectTrigger,
6
6
  SelectValue,
7
7
  } from "../../../../components/ui/select";
8
- import { Label } from "../../../../components/ui/label";
9
8
  import { cn } from "../../../../lib/utils";
10
9
  import { useFilterField } from "../FilterContext";
10
+ import { FilterFieldWrapper } from "./FilterFieldWrapper";
11
11
  import type { ActiveFilterValue } from "../../utils/filterUtils";
12
12
 
13
13
  const ALL_VALUE = "__all__";
@@ -29,8 +29,13 @@ export function SelectFilter({
29
29
  }: SelectFilterProps) {
30
30
  const { value, onChange } = useFilterField(field);
31
31
  return (
32
- <div className={cn("space-y-1.5", className)} {...props}>
33
- <Label htmlFor={`filter-${field}`}>{label}</Label>
32
+ <FilterFieldWrapper
33
+ label={label}
34
+ htmlFor={`filter-${field}`}
35
+ helpText={helpText}
36
+ className={className}
37
+ {...props}
38
+ >
34
39
  <SelectFilterControl
35
40
  field={field}
36
41
  label={label}
@@ -38,8 +43,7 @@ export function SelectFilter({
38
43
  value={value}
39
44
  onChange={onChange}
40
45
  />
41
- {helpText && <p className="text-xs text-muted-foreground">{helpText}</p>}
42
- </div>
46
+ </FilterFieldWrapper>
43
47
  );
44
48
  }
45
49
 
@@ -1,7 +1,9 @@
1
+ import { useEffect, useState } from "react";
1
2
  import { Input } from "../../../../components/ui/input";
2
- import { Label } from "../../../../components/ui/label";
3
3
  import { cn } from "../../../../lib/utils";
4
4
  import { useFilterField } from "../FilterContext";
5
+ import { FilterFieldWrapper } from "./FilterFieldWrapper";
6
+ import { useDebouncedCallback } from "../../hooks/useDebouncedCallback";
5
7
  import type { ActiveFilterValue } from "../../utils/filterUtils";
6
8
 
7
9
  interface TextFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
@@ -21,8 +23,13 @@ export function TextFilter({
21
23
  }: TextFilterProps) {
22
24
  const { value, onChange } = useFilterField(field);
23
25
  return (
24
- <div className={cn("space-y-1.5", className)} {...props}>
25
- <Label htmlFor={`filter-${field}`}>{label}</Label>
26
+ <FilterFieldWrapper
27
+ label={label}
28
+ htmlFor={`filter-${field}`}
29
+ helpText={helpText}
30
+ className={className}
31
+ {...props}
32
+ >
26
33
  <TextFilterInput
27
34
  field={field}
28
35
  label={label}
@@ -30,8 +37,7 @@ export function TextFilter({
30
37
  value={value}
31
38
  onChange={onChange}
32
39
  />
33
- {helpText && <p className="text-xs text-muted-foreground">{helpText}</p>}
34
- </div>
40
+ </FilterFieldWrapper>
35
41
  );
36
42
  }
37
43
 
@@ -53,19 +59,30 @@ export function TextFilterInput({
53
59
  className,
54
60
  ...props
55
61
  }: TextFilterInputProps) {
62
+ const [localValue, setLocalValue] = useState(value?.value ?? "");
63
+
64
+ const externalValue = value?.value ?? "";
65
+ useEffect(() => {
66
+ setLocalValue(externalValue);
67
+ }, [externalValue]);
68
+
69
+ const debouncedOnChange = useDebouncedCallback((v: string) => {
70
+ if (v) {
71
+ onChange({ field, label, type: "text", value: v });
72
+ } else {
73
+ onChange(undefined);
74
+ }
75
+ });
76
+
56
77
  return (
57
78
  <Input
58
79
  id={`filter-${field}`}
59
80
  type="text"
60
81
  placeholder={props.placeholder ?? `Filter by ${label.toLowerCase()}...`}
61
- value={value?.value ?? ""}
82
+ value={localValue}
62
83
  onChange={(e) => {
63
- const v = e.target.value;
64
- if (v) {
65
- onChange({ field, label, type: "text", value: v });
66
- } else {
67
- onChange(undefined);
68
- }
84
+ setLocalValue(e.target.value);
85
+ debouncedOnChange(e.target.value);
69
86
  }}
70
87
  className={cn(className)}
71
88
  {...props}
@@ -0,0 +1,34 @@
1
+ import { useCallback, useEffect, useRef } from "react";
2
+ import { debounce, FILTER_DEBOUNCE_MS } from "../utils/debounce";
3
+
4
+ /**
5
+ * Returns a stable debounced wrapper around the provided callback.
6
+ *
7
+ * The wrapper always invokes the *latest* version of `fn` (via a ref),
8
+ * so the debounce timer is never reset when `fn` changes — only when
9
+ * the caller invokes the returned function again.
10
+ *
11
+ * @param fn - The callback to debounce.
12
+ * @param delay - Debounce delay in ms. Defaults to `FILTER_DEBOUNCE_MS`.
13
+ */
14
+ export function useDebouncedCallback<T extends (...args: any[]) => void>(
15
+ fn: T,
16
+ delay: number = FILTER_DEBOUNCE_MS,
17
+ ): (...args: Parameters<T>) => void {
18
+ const fnRef = useRef(fn);
19
+ const debouncedRef = useRef<((...args: any[]) => void) | null>(null);
20
+
21
+ useEffect(() => {
22
+ fnRef.current = fn;
23
+ });
24
+
25
+ useEffect(() => {
26
+ debouncedRef.current = debounce((...args: any[]) => {
27
+ fnRef.current(...(args as Parameters<T>));
28
+ }, delay);
29
+ }, [delay]);
30
+
31
+ return useCallback((...args: Parameters<T>) => {
32
+ debouncedRef.current?.(...args);
33
+ }, []);
34
+ }
@@ -20,11 +20,6 @@ export interface UseObjectSearchParamsReturn<TFilter, TOrderBy> {
20
20
  set: (field: string, value: ActiveFilterValue | undefined) => void;
21
21
  remove: (field: string) => void;
22
22
  };
23
- filterState: {
24
- apply: () => void;
25
- hasPendingChanges: boolean;
26
- hasValidationError: boolean;
27
- };
28
23
  sort: {
29
24
  current: SortState | null;
30
25
  set: (sort: SortState | null) => void;
@@ -41,40 +36,6 @@ export interface UseObjectSearchParamsReturn<TFilter, TOrderBy> {
41
36
  resetAll: () => void;
42
37
  }
43
38
 
44
- export interface UseObjectSearchParamsOptions {
45
- filterSyncMode?: "immediate" | "manual";
46
- }
47
-
48
- function areFiltersEqual(left: ActiveFilterValue[], right: ActiveFilterValue[]) {
49
- if (left.length !== right.length) return false;
50
- const normalize = (filters: ActiveFilterValue[]) =>
51
- [...filters]
52
- .sort((a, b) => a.field.localeCompare(b.field))
53
- .map((filter) => ({
54
- field: filter.field,
55
- type: filter.type,
56
- value: filter.value ?? "",
57
- min: filter.min ?? "",
58
- max: filter.max ?? "",
59
- }));
60
- return JSON.stringify(normalize(left)) === JSON.stringify(normalize(right));
61
- }
62
-
63
- function hasFilterValidationError(filters: ActiveFilterValue[]) {
64
- for (const filter of filters) {
65
- if (filter.type !== "numeric") continue;
66
- const min = filter.min?.trim();
67
- const max = filter.max?.trim();
68
- if (!min || !max) continue;
69
- const minValue = Number(min);
70
- const maxValue = Number(max);
71
- if (!Number.isNaN(minValue) && !Number.isNaN(maxValue) && minValue >= maxValue) {
72
- return true;
73
- }
74
- }
75
- return false;
76
- }
77
-
78
39
  /**
79
40
  * Manages filter, sort, and cursor-based pagination state for an object search page.
80
41
  *
@@ -98,11 +59,7 @@ export function useObjectSearchParams<TFilter, TOrderBy>(
98
59
  filterConfigs: FilterFieldConfig[],
99
60
  _sortConfigs?: SortFieldConfig[],
100
61
  paginationConfig?: PaginationConfig,
101
- options?: UseObjectSearchParamsOptions,
102
62
  ) {
103
- const filterSyncMode = options?.filterSyncMode ?? "immediate";
104
- const isManualFilterSync = filterSyncMode === "manual";
105
-
106
63
  const defaultPageSize = paginationConfig?.defaultPageSize ?? 10;
107
64
  const validPageSizes = useMemo(
108
65
  () => paginationConfig?.validPageSizes ?? [defaultPageSize],
@@ -119,7 +76,6 @@ export function useObjectSearchParams<TFilter, TOrderBy>(
119
76
  );
120
77
 
121
78
  const [filters, setFilters] = useState<ActiveFilterValue[]>(initial.filters);
122
- const [appliedFilters, setAppliedFilters] = useState<ActiveFilterValue[]>(initial.filters);
123
79
  const [sort, setLocalSort] = useState<SortState | null>(initial.sort);
124
80
 
125
81
  // Pagination — cursor-based with a stack to support "previous page" navigation.
@@ -170,15 +126,6 @@ export function useObjectSearchParams<TFilter, TOrderBy>(
170
126
 
171
127
  const setFilter = useCallback(
172
128
  (field: string, value: ActiveFilterValue | undefined) => {
173
- if (isManualFilterSync) {
174
- setFilters((prev) => {
175
- const next = prev.filter((f) => f.field !== field);
176
- if (value) next.push(value);
177
- return next;
178
- });
179
- return;
180
- }
181
-
182
129
  const { sort: s, pageSize: ps } = stateRef.current;
183
130
  setFilters((prev) => {
184
131
  const next = prev.filter((f) => f.field !== field);
@@ -188,16 +135,11 @@ export function useObjectSearchParams<TFilter, TOrderBy>(
188
135
  });
189
136
  resetPagination();
190
137
  },
191
- [isManualFilterSync, resetPagination],
138
+ [resetPagination],
192
139
  );
193
140
 
194
141
  const removeFilter = useCallback(
195
142
  (field: string) => {
196
- if (isManualFilterSync) {
197
- setFilters((prev) => prev.filter((f) => f.field !== field));
198
- return;
199
- }
200
-
201
143
  const { sort: s, pageSize: ps } = stateRef.current;
202
144
  setFilters((prev) => {
203
145
  const next = prev.filter((f) => f.field !== field);
@@ -206,17 +148,9 @@ export function useObjectSearchParams<TFilter, TOrderBy>(
206
148
  });
207
149
  resetPagination();
208
150
  },
209
- [isManualFilterSync, resetPagination],
151
+ [resetPagination],
210
152
  );
211
153
 
212
- const applyFilters = useCallback(() => {
213
- if (!isManualFilterSync) return;
214
- const { filters: nextFilters, sort: s, pageSize: ps } = stateRef.current;
215
- setAppliedFilters(nextFilters);
216
- resetPagination();
217
- syncToUrl(nextFilters, s, ps);
218
- }, [isManualFilterSync, resetPagination, syncToUrl]);
219
-
220
154
  // -- Sort callback ----------------------------------------------------------
221
155
 
222
156
  const setSort = useCallback(
@@ -233,7 +167,6 @@ export function useObjectSearchParams<TFilter, TOrderBy>(
233
167
 
234
168
  const resetAll = useCallback(() => {
235
169
  setFilters([]);
236
- setAppliedFilters([]);
237
170
  setLocalSort(null);
238
171
  resetPagination();
239
172
  syncToUrl([], null, defaultPageSize, 0);
@@ -283,8 +216,8 @@ export function useObjectSearchParams<TFilter, TOrderBy>(
283
216
  // Translate local filter/sort state into API-ready `where` and `orderBy`.
284
217
 
285
218
  const where = useMemo(
286
- () => buildFilter<TFilter>(isManualFilterSync ? appliedFilters : filters, filterConfigs),
287
- [appliedFilters, filters, filterConfigs, isManualFilterSync],
219
+ () => buildFilter<TFilter>(filters, filterConfigs),
220
+ [filters, filterConfigs],
288
221
  );
289
222
 
290
223
  const orderBy = useMemo(() => buildOrderBy<TOrderBy>(sort), [sort]);
@@ -296,23 +229,10 @@ export function useObjectSearchParams<TFilter, TOrderBy>(
296
229
  // causing unnecessary re-renders.
297
230
 
298
231
  const filtersGroup = useMemo(
299
- () => ({
300
- active: filters,
301
- set: setFilter,
302
- remove: removeFilter,
303
- }),
232
+ () => ({ active: filters, set: setFilter, remove: removeFilter }),
304
233
  [filters, setFilter, removeFilter],
305
234
  );
306
235
 
307
- const filterState = useMemo(
308
- () => ({
309
- apply: applyFilters,
310
- hasPendingChanges: isManualFilterSync ? !areFiltersEqual(filters, appliedFilters) : false,
311
- hasValidationError: hasFilterValidationError(filters),
312
- }),
313
- [applyFilters, isManualFilterSync, filters, appliedFilters],
314
- );
315
-
316
236
  const sortGroup = useMemo(() => ({ current: sort, set: setSort }), [sort, setSort]);
317
237
 
318
238
  const query = useMemo(() => ({ where, orderBy }), [where, orderBy]);
@@ -324,7 +244,6 @@ export function useObjectSearchParams<TFilter, TOrderBy>(
324
244
 
325
245
  return {
326
246
  filters: filtersGroup,
327
- filterState,
328
247
  sort: sortGroup,
329
248
  query,
330
249
  pagination,
@@ -1,3 +1,6 @@
1
+ /** Default debounce delay for keystroke-driven filter inputs (search, text, numeric). */
2
+ export const FILTER_DEBOUNCE_MS = 300;
3
+
1
4
  /**
2
5
  * Creates a debounced version of the provided function.
3
6
  *
@@ -10,7 +13,7 @@
10
13
  * @param ms - The debounce delay in milliseconds.
11
14
  * @returns A new function with the same signature that delays execution.
12
15
  */
13
- export function debounce<T extends (...args: never[]) => void>(
16
+ export function debounce<T extends (...args: any[]) => void>(
14
17
  fn: T,
15
18
  ms: number,
16
19
  ): (...args: Parameters<T>) => void {
@@ -25,6 +25,8 @@ export type FilterFieldType =
25
25
  | "boolean"
26
26
  | "date"
27
27
  | "daterange"
28
+ | "datetime"
29
+ | "datetimerange"
28
30
  | "multipicklist"
29
31
  | "search";
30
32
 
@@ -337,13 +339,34 @@ function buildSingleFilter<TFilter>(
337
339
  return { [field]: { in: values } } as TFilter;
338
340
  }
339
341
  case "date": {
342
+ if (!min && !max) return null;
343
+ const op = value ?? (min ? "gte" : "lte");
344
+ const dateStr = min ?? max;
345
+ return { [field]: { [op]: { value: dateStr } } } as TFilter;
346
+ }
347
+ case "daterange": {
348
+ if (!min && !max) return null;
349
+ const clauses: TFilter[] = [];
350
+ if (min) {
351
+ clauses.push({
352
+ [field]: { gte: { value: min } },
353
+ } as TFilter);
354
+ }
355
+ if (max) {
356
+ clauses.push({
357
+ [field]: { lte: { value: max } },
358
+ } as TFilter);
359
+ }
360
+ return clauses.length === 1 ? clauses[0] : ({ and: clauses } as TFilter);
361
+ }
362
+ case "datetime": {
340
363
  if (!min && !max) return null;
341
364
  const op = value ?? (min ? "gte" : "lte");
342
365
  const dateStr = min ?? max;
343
366
  const isoStr = op === "gte" || op === "gt" ? toStartOfDay(dateStr!) : toEndOfDay(dateStr!);
344
367
  return { [field]: { [op]: { value: isoStr } } } as TFilter;
345
368
  }
346
- case "daterange": {
369
+ case "datetimerange": {
347
370
  if (!min && !max) return null;
348
371
  const clauses: TFilter[] = [];
349
372
  if (min) {
@@ -16,7 +16,6 @@ import type { ApplicationSearchNode } from "../api/applications/applicationSearc
16
16
  import { PageHeader } from "../components/layout/PageHeader";
17
17
  import { PageContainer } from "../components/layout/PageContainer";
18
18
  import {
19
- FilterApplyButton,
20
19
  FilterProvider,
21
20
  FilterResetButton,
22
21
  } from "../features/object-search/components/FilterContext";
@@ -56,7 +55,7 @@ const FILTER_CONFIGS: FilterFieldConfig[] = [
56
55
  },
57
56
  { field: "Status__c", label: "Status", type: "picklist" },
58
57
  { field: "Start_Date__c", label: "Start Date", type: "date" },
59
- { field: "CreatedDate", label: "Created Date", type: "date" },
58
+ { field: "CreatedDate", label: "Created Date", type: "datetime" },
60
59
  ];
61
60
 
62
61
  const SORT_CONFIGS: SortFieldConfig<string>[] = [
@@ -76,13 +75,14 @@ export default function ApplicationSearch() {
76
75
  ttl: 30_000,
77
76
  });
78
77
 
79
- const { filters, filterState, query, pagination, resetAll } = useObjectSearchParams<
78
+ const { filters, query, pagination, resetAll } = useObjectSearchParams<
80
79
  Application__C_Filter,
81
80
  Application__C_OrderBy
82
- >(FILTER_CONFIGS, SORT_CONFIGS, PAGINATION_CONFIG, { filterSyncMode: "manual" });
83
- const effectiveOrderBy: Application__C_OrderBy = query.orderBy ?? {
84
- CreatedDate: { order: ResultOrder.Desc },
85
- };
81
+ >(FILTER_CONFIGS, SORT_CONFIGS, PAGINATION_CONFIG);
82
+ const effectiveOrderBy = useMemo<Application__C_OrderBy>(
83
+ () => query.orderBy ?? { CreatedDate: { order: ResultOrder.Desc } },
84
+ [query.orderBy],
85
+ );
86
86
  const searchKey = `applications:${JSON.stringify({ where: query.where, orderBy: effectiveOrderBy, first: pagination.pageSize, after: pagination.afterCursor })}`;
87
87
  const { data, loading, error } = useCachedAsyncData(
88
88
  () =>
@@ -138,7 +138,6 @@ export default function ApplicationSearch() {
138
138
  <PageHeader title="Applications" description="Manage and review rental applications" />
139
139
  <ApplicationSearchFilters
140
140
  filters={filters}
141
- filterState={filterState}
142
141
  statusOptions={statusOptions ?? []}
143
142
  resetAll={resetAll}
144
143
  />
@@ -193,15 +192,10 @@ export default function ApplicationSearch() {
193
192
 
194
193
  function ApplicationSearchFilters({
195
194
  filters,
196
- filterState,
197
195
  statusOptions,
198
196
  resetAll,
199
197
  }: {
200
198
  filters: UseObjectSearchParamsReturn<Application__C_Filter, Application__C_OrderBy>["filters"];
201
- filterState: UseObjectSearchParamsReturn<
202
- Application__C_Filter,
203
- Application__C_OrderBy
204
- >["filterState"];
205
199
  statusOptions: Array<{ value: string; label: string }>;
206
200
  resetAll: () => void;
207
201
  }) {
@@ -211,9 +205,6 @@ function ApplicationSearchFilters({
211
205
  onFilterChange={filters.set}
212
206
  onFilterRemove={filters.remove}
213
207
  onReset={resetAll}
214
- onApply={filterState.apply}
215
- hasPendingChanges={filterState.hasPendingChanges}
216
- hasValidationError={filterState.hasValidationError}
217
208
  >
218
209
  <FilterRow ariaLabel="Applications filters">
219
210
  <SearchFilter
@@ -229,8 +220,12 @@ function ApplicationSearchFilters({
229
220
  className="w-full sm:w-36"
230
221
  />
231
222
  <DateFilter field="Start_Date__c" label="Start Date" className="w-full sm:w-56" />
232
- <DateFilter field="CreatedDate" label="Created Date" className="w-full sm:w-56" />
233
- <FilterApplyButton className="h-8 px-3 rounded-lg bg-purple-600 text-white text-sm font-medium hover:bg-purple-700 disabled:bg-purple-300 disabled:cursor-not-allowed transition-colors" />
223
+ <DateFilter
224
+ field="CreatedDate"
225
+ label="Created Date"
226
+ filterType="datetime"
227
+ className="w-full sm:w-56"
228
+ />
234
229
  <FilterResetButton />
235
230
  </FilterRow>
236
231
  </FilterProvider>
@@ -18,7 +18,6 @@ import type { SortFieldConfig } from "../features/object-search/utils/sortUtils"
18
18
  import { PageHeader } from "../components/layout/PageHeader";
19
19
  import { PageContainer } from "../components/layout/PageContainer";
20
20
  import {
21
- FilterApplyButton,
22
21
  FilterProvider,
23
22
  FilterResetButton,
24
23
  } from "../features/object-search/components/FilterContext";
@@ -74,7 +73,7 @@ const FILTER_CONFIGS: FilterFieldConfig[] = [
74
73
  { field: "Status__c", label: "Status", type: "picklist" },
75
74
  { field: "Type__c", label: "Type", type: "picklist" },
76
75
  { field: "Priority__c", label: "Priority", type: "picklist" },
77
- { field: "Scheduled__c", label: "Scheduled", type: "date" },
76
+ { field: "Scheduled__c", label: "Scheduled", type: "datetime" },
78
77
  ];
79
78
 
80
79
  const SORT_CONFIGS: SortFieldConfig<string>[] = [
@@ -102,13 +101,14 @@ export default function MaintenanceRequestSearch() {
102
101
  { key: "distinctMaintenanceRequestPriority", ttl: 30_000 },
103
102
  );
104
103
 
105
- const { filters, filterState, query, pagination, resetAll } = useObjectSearchParams<
104
+ const { filters, query, pagination, resetAll } = useObjectSearchParams<
106
105
  Maintenance_Request__C_Filter,
107
106
  Maintenance_Request__C_OrderBy
108
- >(FILTER_CONFIGS, SORT_CONFIGS, PAGINATION_CONFIG, { filterSyncMode: "manual" });
109
- const effectiveOrderBy: Maintenance_Request__C_OrderBy = query.orderBy ?? {
110
- CreatedDate: { order: ResultOrder.Desc },
111
- };
107
+ >(FILTER_CONFIGS, SORT_CONFIGS, PAGINATION_CONFIG);
108
+ const effectiveOrderBy = useMemo<Maintenance_Request__C_OrderBy>(
109
+ () => query.orderBy ?? { CreatedDate: { order: ResultOrder.Desc } },
110
+ [query.orderBy],
111
+ );
112
112
  const searchKey = `maintenance-requests:${JSON.stringify({ where: query.where, orderBy: effectiveOrderBy, first: pagination.pageSize, after: pagination.afterCursor })}`;
113
113
  const { data, loading, error } = useCachedAsyncData(
114
114
  () =>
@@ -167,7 +167,6 @@ export default function MaintenanceRequestSearch() {
167
167
  />
168
168
  <MaintenanceRequestSearchFilters
169
169
  filters={filters}
170
- filterState={filterState}
171
170
  statusOptions={statusOptions ?? []}
172
171
  typeOptions={typeOptions ?? []}
173
172
  priorityOptions={priorityOptions ?? []}
@@ -225,7 +224,6 @@ export default function MaintenanceRequestSearch() {
225
224
 
226
225
  function MaintenanceRequestSearchFilters({
227
226
  filters,
228
- filterState,
229
227
  statusOptions,
230
228
  typeOptions,
231
229
  priorityOptions,
@@ -235,10 +233,6 @@ function MaintenanceRequestSearchFilters({
235
233
  Maintenance_Request__C_Filter,
236
234
  Maintenance_Request__C_OrderBy
237
235
  >["filters"];
238
- filterState: UseObjectSearchParamsReturn<
239
- Maintenance_Request__C_Filter,
240
- Maintenance_Request__C_OrderBy
241
- >["filterState"];
242
236
  statusOptions: Array<{ value: string; label: string }>;
243
237
  typeOptions: Array<{ value: string; label: string }>;
244
238
  priorityOptions: Array<{ value: string; label: string }>;
@@ -250,9 +244,6 @@ function MaintenanceRequestSearchFilters({
250
244
  onFilterChange={filters.set}
251
245
  onFilterRemove={filters.remove}
252
246
  onReset={resetAll}
253
- onApply={filterState.apply}
254
- hasPendingChanges={filterState.hasPendingChanges}
255
- hasValidationError={filterState.hasValidationError}
256
247
  >
257
248
  <FilterRow ariaLabel="Maintenance Requests filters">
258
249
  <SearchFilter
@@ -279,8 +270,12 @@ function MaintenanceRequestSearchFilters({
279
270
  options={priorityOptions}
280
271
  className="w-full sm:w-36"
281
272
  />
282
- <DateFilter field="Scheduled__c" label="Scheduled" className="w-full sm:w-56" />
283
- <FilterApplyButton className="h-8 px-3 rounded-lg bg-purple-600 text-white text-sm font-medium hover:bg-purple-700 disabled:bg-purple-300 disabled:cursor-not-allowed transition-colors" />
273
+ <DateFilter
274
+ field="Scheduled__c"
275
+ label="Scheduled"
276
+ filterType="datetime"
277
+ className="w-full sm:w-56"
278
+ />
284
279
  <FilterResetButton />
285
280
  </FilterRow>
286
281
  </FilterProvider>