@salesforce/webapp-template-app-react-template-b2e-experimental 1.112.6 → 1.112.7

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 (21) hide show
  1. package/dist/CHANGELOG.md +8 -0
  2. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/package.json +3 -3
  3. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/api/graphqlClient.ts +25 -0
  4. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/__examples__/pages/AccountSearch.tsx +82 -54
  5. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/FilterContext.tsx +73 -0
  6. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/PaginationControls.tsx +45 -87
  7. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/filters/BooleanFilter.tsx +16 -36
  8. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/filters/DateFilter.tsx +33 -77
  9. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/filters/DateRangeFilter.tsx +14 -23
  10. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/filters/MultiSelectFilter.tsx +18 -26
  11. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/filters/NumericRangeFilter.tsx +22 -39
  12. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/filters/SearchFilter.tsx +12 -15
  13. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/filters/SelectFilter.tsx +30 -34
  14. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/filters/TextFilter.tsx +27 -30
  15. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/hooks/useAsyncData.ts +1 -0
  16. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/hooks/useCachedAsyncData.ts +1 -0
  17. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/hooks/useObjectSearchParams.ts +22 -0
  18. package/dist/package-lock.json +2 -2
  19. package/dist/package.json +1 -1
  20. package/package.json +1 -1
  21. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/FilterPanel.tsx +0 -127
@@ -1,13 +1,6 @@
1
1
  import { useState } from "react";
2
2
  import { parseISO } from "date-fns";
3
- import { ChevronDown, Check } from "lucide-react";
4
3
  import { Label } from "../../../../components/ui/label";
5
- import { Button } from "../../../../components/ui/button";
6
- import {
7
- Popover,
8
- PopoverTrigger,
9
- PopoverContent,
10
- } from "../../../../components/ui/popover";
11
4
  import {
12
5
  DatePicker,
13
6
  DatePickerTrigger,
@@ -15,7 +8,14 @@ import {
15
8
  DatePickerCalendar,
16
9
  } from "../../../../components/ui/datePicker";
17
10
  import { cn } from "../../../../lib/utils";
18
- import type { FilterFieldConfig, ActiveFilterValue } from "../../utils/filterUtils";
11
+ import { useFilterField } from "../FilterContext";
12
+ import {
13
+ Select,
14
+ SelectContent,
15
+ SelectItem,
16
+ SelectTrigger,
17
+ SelectValue,
18
+ } from "../../../../components/ui/select";
19
19
 
20
20
  type DateOperator = "gt" | "lt";
21
21
 
@@ -24,29 +24,19 @@ const OPERATOR_OPTIONS: { value: DateOperator; label: string }[] = [
24
24
  { value: "lt", label: "Before" },
25
25
  ];
26
26
 
27
- /** Maps operator to the ActiveFilterValue field used to carry the date. */
28
27
  function operatorToField(op: DateOperator): "min" | "max" {
29
28
  return op === "gt" ? "min" : "max";
30
29
  }
31
30
 
32
31
  interface DateFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
33
- config: FilterFieldConfig;
34
- value: ActiveFilterValue | undefined;
35
- onChange: (value: ActiveFilterValue | undefined) => void;
36
- labelProps?: React.ComponentProps<typeof Label>;
37
- helpTextProps?: React.ComponentProps<"p">;
32
+ field: string;
33
+ label: string;
34
+ helpText?: string;
38
35
  }
39
36
 
40
- export function DateFilter({
41
- config,
42
- value,
43
- onChange,
44
- className,
45
- labelProps,
46
- helpTextProps,
47
- ...props
48
- }: DateFilterProps) {
49
- // Derive initial operator from the existing value (min → gt, max → lt)
37
+ export function DateFilter({ field, label, helpText, className, ...props }: DateFilterProps) {
38
+ const { value, onChange } = useFilterField(field);
39
+
50
40
  const initialOp: DateOperator = value?.min ? "gt" : "lt";
51
41
  const [operator, setOperator] = useState<DateOperator>(initialOp);
52
42
 
@@ -69,67 +59,40 @@ export function DateFilter({
69
59
 
70
60
  function emitChange(op: DateOperator, date: Date) {
71
61
  const dateStr = toDateString(date);
72
- const field = operatorToField(op);
62
+ const f = operatorToField(op);
73
63
  onChange({
74
- field: config.field,
75
- label: config.label,
64
+ field,
65
+ label,
76
66
  type: "date",
77
67
  value: op,
78
- min: field === "min" ? dateStr : undefined,
79
- max: field === "max" ? dateStr : undefined,
68
+ min: f === "min" ? dateStr : undefined,
69
+ max: f === "max" ? dateStr : undefined,
80
70
  });
81
71
  }
82
72
 
83
- const [operatorOpen, setOperatorOpen] = useState(false);
84
- const operatorLabel = OPERATOR_OPTIONS.find((o) => o.value === operator)?.label ?? "After";
85
-
86
73
  return (
87
74
  <div className={cn("space-y-1.5", className)} {...props}>
88
- <Label {...labelProps}>{labelProps?.children ?? config.label}</Label>
89
- <div className="flex min-w-0 gap-2">
90
- <Popover open={operatorOpen} onOpenChange={setOperatorOpen}>
91
- <PopoverTrigger asChild>
92
- <Button
93
- variant="outline"
94
- className="h-9 w-[7rem] shrink-0 justify-between px-2.5 font-normal"
95
- aria-label="Date operator"
96
- >
97
- <span className="truncate">{operatorLabel}</span>
98
- <ChevronDown className="ml-1 size-4 shrink-0 opacity-50" />
99
- </Button>
100
- </PopoverTrigger>
101
- <PopoverContent align="start" className="w-auto min-w-[7rem] p-1">
75
+ <Label>{label}</Label>
76
+ <div className="flex gap-2">
77
+ <Select value={operator} onValueChange={(v) => handleOperatorChange(v as DateOperator)}>
78
+ <SelectTrigger className="w-full flex-1">
79
+ <SelectValue />
80
+ </SelectTrigger>
81
+ <SelectContent>
102
82
  {OPERATOR_OPTIONS.map((opt) => (
103
- <button
104
- key={opt.value}
105
- type="button"
106
- className={cn(
107
- "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none hover:bg-accent",
108
- opt.value === operator && "font-medium",
109
- )}
110
- onClick={() => {
111
- handleOperatorChange(opt.value);
112
- setOperatorOpen(false);
113
- }}
114
- >
115
- <Check
116
- className={cn(
117
- "size-4 shrink-0",
118
- opt.value === operator ? "opacity-100" : "opacity-0",
119
- )}
120
- />
83
+ <SelectItem key={opt.value} value={opt.value}>
121
84
  {opt.label}
122
- </button>
85
+ </SelectItem>
123
86
  ))}
124
- </PopoverContent>
125
- </Popover>
87
+ </SelectContent>
88
+ </Select>
126
89
  <DatePicker>
127
90
  <DatePickerTrigger
128
- className="min-w-0 flex-1 basis-0 !w-auto max-w-full"
91
+ className="w-full flex-2"
129
92
  date={currentDate}
130
93
  dateFormat="MMM do, yyyy"
131
94
  placeholder="Pick a date"
132
- aria-label={config.label}
95
+ aria-label={label}
133
96
  />
134
97
  <DatePickerContent>
135
98
  <DatePickerCalendar
@@ -141,14 +104,7 @@ export function DateFilter({
141
104
  </DatePickerContent>
142
105
  </DatePicker>
143
106
  </div>
144
- {config.helpText && (
145
- <p
146
- {...helpTextProps}
147
- className={cn("text-xs text-muted-foreground", helpTextProps?.className)}
148
- >
149
- {helpTextProps?.children ?? config.helpText}
150
- </p>
151
- )}
107
+ {helpText && <p className="text-xs text-muted-foreground">{helpText}</p>}
152
108
  </div>
153
109
  );
154
110
  }
@@ -7,26 +7,24 @@ import {
7
7
  DatePickerCalendar,
8
8
  } from "../../../../components/ui/datePicker";
9
9
  import { cn } from "../../../../lib/utils";
10
- import type { FilterFieldConfig, ActiveFilterValue } from "../../utils/filterUtils";
10
+ import { useFilterField } from "../FilterContext";
11
11
  import { toDate, toDateString } from "./DateFilter";
12
12
 
13
13
  interface DateRangeFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
14
- config: FilterFieldConfig;
15
- value: ActiveFilterValue | undefined;
16
- onChange: (value: ActiveFilterValue | undefined) => void;
17
- labelProps?: React.ComponentProps<typeof Label>;
18
- helpTextProps?: React.ComponentProps<"p">;
14
+ field: string;
15
+ label: string;
16
+ helpText?: string;
19
17
  }
20
18
 
21
19
  export function DateRangeFilter({
22
- config,
23
- value,
24
- onChange,
20
+ field,
21
+ label,
22
+ helpText,
25
23
  className,
26
- labelProps,
27
- helpTextProps,
28
24
  ...props
29
25
  }: DateRangeFilterProps) {
26
+ const { value, onChange } = useFilterField(field);
27
+
30
28
  const dateRange: DateRange | undefined =
31
29
  value?.min || value?.max ? { from: toDate(value?.min), to: toDate(value?.max) } : undefined;
32
30
 
@@ -35,8 +33,8 @@ export function DateRangeFilter({
35
33
  onChange(undefined);
36
34
  } else {
37
35
  onChange({
38
- field: config.field,
39
- label: config.label,
36
+ field,
37
+ label,
40
38
  type: "daterange",
41
39
  min: toDateString(range?.from),
42
40
  max: toDateString(range?.to),
@@ -46,13 +44,13 @@ export function DateRangeFilter({
46
44
 
47
45
  return (
48
46
  <div className={cn("space-y-1.5", className)} {...props}>
49
- <Label {...labelProps}>{labelProps?.children ?? config.label}</Label>
47
+ <Label>{label}</Label>
50
48
  <DatePicker>
51
49
  <DatePickerRangeTrigger
52
50
  className="w-full"
53
51
  dateRange={dateRange}
54
52
  placeholder="Pick a date range"
55
- aria-label={config.label}
53
+ aria-label={label}
56
54
  />
57
55
  <DatePickerContent align="start">
58
56
  <DatePickerCalendar
@@ -65,14 +63,7 @@ export function DateRangeFilter({
65
63
  />
66
64
  </DatePickerContent>
67
65
  </DatePicker>
68
- {config.helpText && (
69
- <p
70
- {...helpTextProps}
71
- className={cn("text-xs text-muted-foreground", helpTextProps?.className)}
72
- >
73
- {helpTextProps?.children ?? config.helpText}
74
- </p>
75
- )}
66
+ {helpText && <p className="text-xs text-muted-foreground">{helpText}</p>}
76
67
  </div>
77
68
  );
78
69
  }
@@ -8,32 +8,31 @@ import { Label } from "../../../../components/ui/label";
8
8
  import { Button } from "../../../../components/ui/button";
9
9
  import { cn } from "../../../../lib/utils";
10
10
  import { ChevronDown } from "lucide-react";
11
- import type { FilterFieldConfig, ActiveFilterValue } from "../../utils/filterUtils";
11
+ import { useFilterField } from "../FilterContext";
12
12
 
13
13
  interface MultiSelectFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
14
- config: FilterFieldConfig;
15
- value: ActiveFilterValue | undefined;
16
- onChange: (value: ActiveFilterValue | undefined) => void;
17
- labelProps?: React.ComponentProps<typeof Label>;
18
- helpTextProps?: React.ComponentProps<"p">;
14
+ field: string;
15
+ label: string;
16
+ options: Array<{ value: string; label: string }>;
17
+ helpText?: string;
19
18
  }
20
19
 
21
20
  export function MultiSelectFilter({
22
- config,
23
- value,
24
- onChange,
21
+ field,
22
+ label,
23
+ options,
24
+ helpText,
25
25
  className,
26
- labelProps,
27
- helpTextProps,
28
26
  ...props
29
27
  }: MultiSelectFilterProps) {
28
+ const { value, onChange } = useFilterField(field);
30
29
  const selected = value?.value ? value.value.split(",") : [];
31
30
 
32
31
  const triggerLabel =
33
32
  selected.length === 0
34
- ? `Select ${config.label.toLowerCase()}`
33
+ ? `Select ${label.toLowerCase()}`
35
34
  : selected.length === 1
36
- ? (config.options?.find((o) => o.value === selected[0])?.label ?? selected[0])
35
+ ? (options.find((o) => o.value === selected[0])?.label ?? selected[0])
37
36
  : `${selected.length} selected`;
38
37
 
39
38
  function handleToggle(optionValue: string) {
@@ -45,8 +44,8 @@ export function MultiSelectFilter({
45
44
  onChange(undefined);
46
45
  } else {
47
46
  onChange({
48
- field: config.field,
49
- label: config.label,
47
+ field,
48
+ label,
50
49
  type: "multipicklist",
51
50
  value: next.join(","),
52
51
  });
@@ -55,7 +54,7 @@ export function MultiSelectFilter({
55
54
 
56
55
  return (
57
56
  <div className={cn("space-y-1.5", className)} {...props}>
58
- <Label {...labelProps}>{labelProps?.children ?? config.label}</Label>
57
+ <Label>{label}</Label>
59
58
  <Popover>
60
59
  <PopoverTrigger asChild>
61
60
  <Button
@@ -72,8 +71,8 @@ export function MultiSelectFilter({
72
71
  </PopoverTrigger>
73
72
  <PopoverContent className="p-2" align="start">
74
73
  <div className="max-h-48 overflow-y-auto space-y-1">
75
- {config.options?.map((opt) => {
76
- const id = `filter-${config.field}-${opt.value}`;
74
+ {options.map((opt) => {
75
+ const id = `filter-${field}-${opt.value}`;
77
76
  return (
78
77
  <div
79
78
  key={opt.value}
@@ -93,14 +92,7 @@ export function MultiSelectFilter({
93
92
  </div>
94
93
  </PopoverContent>
95
94
  </Popover>
96
- {config.helpText && (
97
- <p
98
- {...helpTextProps}
99
- className={cn("text-xs text-muted-foreground", helpTextProps?.className)}
100
- >
101
- {helpTextProps?.children ?? config.helpText}
102
- </p>
103
- )}
95
+ {helpText && <p className="text-xs text-muted-foreground">{helpText}</p>}
104
96
  </div>
105
97
  );
106
98
  }
@@ -1,53 +1,35 @@
1
1
  import { Input } from "../../../../components/ui/input";
2
2
  import { Label } from "../../../../components/ui/label";
3
3
  import { cn } from "../../../../lib/utils";
4
- import type { FilterFieldConfig, ActiveFilterValue } from "../../utils/filterUtils";
4
+ import { useFilterField } from "../FilterContext";
5
+ import type { ActiveFilterValue } from "../../utils/filterUtils";
5
6
 
6
7
  interface NumericRangeFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
7
- config: FilterFieldConfig;
8
- value: ActiveFilterValue | undefined;
9
- onChange: (value: ActiveFilterValue | undefined) => void;
10
- labelProps?: React.ComponentProps<typeof Label>;
11
- controlProps?: Omit<
12
- React.ComponentProps<typeof NumericRangeFilterInputs>,
13
- "config" | "value" | "onChange"
14
- >;
15
- helpTextProps?: React.ComponentProps<"p">;
8
+ field: string;
9
+ label: string;
10
+ helpText?: string;
16
11
  }
17
12
 
18
13
  export function NumericRangeFilter({
19
- config,
20
- value,
21
- onChange,
14
+ field,
15
+ label,
16
+ helpText,
22
17
  className,
23
- labelProps,
24
- controlProps,
25
- helpTextProps,
26
18
  ...props
27
19
  }: NumericRangeFilterProps) {
20
+ const { value, onChange } = useFilterField(field);
28
21
  return (
29
22
  <div className={cn("space-y-1.5", className)} {...props}>
30
- <Label {...labelProps}>{labelProps?.children ?? config.label}</Label>
31
- <NumericRangeFilterInputs
32
- config={config}
33
- value={value}
34
- onChange={onChange}
35
- {...controlProps}
36
- />
37
- {config.helpText && (
38
- <p
39
- {...helpTextProps}
40
- className={cn("text-xs text-muted-foreground", helpTextProps?.className)}
41
- >
42
- {helpTextProps?.children ?? config.helpText}
43
- </p>
44
- )}
23
+ <Label>{label}</Label>
24
+ <NumericRangeFilterInputs field={field} label={label} value={value} onChange={onChange} />
25
+ {helpText && <p className="text-xs text-muted-foreground">{helpText}</p>}
45
26
  </div>
46
27
  );
47
28
  }
48
29
 
49
30
  interface NumericRangeFilterInputsProps extends Omit<React.ComponentProps<"div">, "onChange"> {
50
- config: FilterFieldConfig;
31
+ field: string;
32
+ label: string;
51
33
  value: ActiveFilterValue | undefined;
52
34
  onChange: (value: ActiveFilterValue | undefined) => void;
53
35
  minInputProps?: React.ComponentProps<typeof Input>;
@@ -55,7 +37,8 @@ interface NumericRangeFilterInputsProps extends Omit<React.ComponentProps<"div">
55
37
  }
56
38
 
57
39
  export function NumericRangeFilterInputs({
58
- config,
40
+ field,
41
+ label,
59
42
  value,
60
43
  onChange,
61
44
  className,
@@ -63,14 +46,14 @@ export function NumericRangeFilterInputs({
63
46
  maxInputProps,
64
47
  ...props
65
48
  }: NumericRangeFilterInputsProps) {
66
- const handleChange = (field: "min" | "max", v: string) => {
49
+ const handleChange = (bound: "min" | "max", v: string) => {
67
50
  const next = {
68
- field: config.field,
69
- label: config.label,
51
+ field,
52
+ label,
70
53
  type: "numeric" as const,
71
54
  min: value?.min ?? "",
72
55
  max: value?.max ?? "",
73
- [field]: v,
56
+ [bound]: v,
74
57
  };
75
58
  if (!next.min && !next.max) {
76
59
  onChange(undefined);
@@ -86,7 +69,7 @@ export function NumericRangeFilterInputs({
86
69
  placeholder="Min"
87
70
  value={value?.min ?? ""}
88
71
  onChange={(e) => handleChange("min", e.target.value)}
89
- aria-label={`${config.label} minimum`}
72
+ aria-label={`${label} minimum`}
90
73
  {...minInputProps}
91
74
  />
92
75
  <Input
@@ -94,7 +77,7 @@ export function NumericRangeFilterInputs({
94
77
  placeholder="Max"
95
78
  value={value?.max ?? ""}
96
79
  onChange={(e) => handleChange("max", e.target.value)}
97
- aria-label={`${config.label} maximum`}
80
+ aria-label={`${label} maximum`}
98
81
  {...maxInputProps}
99
82
  />
100
83
  </div>
@@ -1,39 +1,36 @@
1
1
  import { Label } from "../../../../components/ui/label";
2
2
  import { cn } from "../../../../lib/utils";
3
3
  import { SearchBar } from "../SearchBar";
4
- import type { FilterFieldConfig, ActiveFilterValue } from "../../utils/filterUtils";
4
+ import { useFilterField } from "../FilterContext";
5
5
 
6
6
  interface SearchFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
7
- config: FilterFieldConfig;
8
- value: ActiveFilterValue | undefined;
9
- onChange: (value: ActiveFilterValue | undefined) => void;
10
- labelProps?: React.ComponentProps<typeof Label>;
7
+ field: string;
8
+ label: string;
9
+ placeholder?: string;
11
10
  }
12
11
 
13
12
  export function SearchFilter({
14
- config,
15
- value,
16
- onChange,
13
+ field,
14
+ label,
15
+ placeholder,
17
16
  className,
18
- labelProps,
19
17
  ...props
20
18
  }: SearchFilterProps) {
19
+ const { value, onChange } = useFilterField(field);
21
20
  return (
22
21
  <div className={cn("space-y-1.5", className)} {...props}>
23
- <Label htmlFor={`filter-${config.field}`} {...labelProps}>
24
- {labelProps?.children ?? config.label}
25
- </Label>
22
+ <Label htmlFor={`filter-${field}`}>{label}</Label>
26
23
  <SearchBar
27
24
  value={value?.value ?? ""}
28
25
  handleChange={(v) => {
29
26
  if (v) {
30
- onChange({ field: config.field, label: config.label, type: "search", value: v });
27
+ onChange({ field, label, type: "search", value: v });
31
28
  } else {
32
29
  onChange(undefined);
33
30
  }
34
31
  }}
35
- placeholder={config.placeholder}
36
- inputProps={{ id: `filter-${config.field}` }}
32
+ placeholder={placeholder}
33
+ inputProps={{ id: `filter-${field}` }}
37
34
  />
38
35
  </div>
39
36
  );
@@ -7,52 +7,46 @@ import {
7
7
  } from "../../../../components/ui/select";
8
8
  import { Label } from "../../../../components/ui/label";
9
9
  import { cn } from "../../../../lib/utils";
10
- import type { FilterFieldConfig, ActiveFilterValue } from "../../utils/filterUtils";
10
+ import { useFilterField } from "../FilterContext";
11
+ import type { ActiveFilterValue } from "../../utils/filterUtils";
11
12
 
12
13
  const ALL_VALUE = "__all__";
13
14
 
14
15
  interface SelectFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
15
- config: FilterFieldConfig;
16
- value: ActiveFilterValue | undefined;
17
- onChange: (value: ActiveFilterValue | undefined) => void;
18
- labelProps?: React.ComponentProps<typeof Label>;
19
- controlProps?: Omit<
20
- React.ComponentProps<typeof SelectFilterControl>,
21
- "config" | "value" | "onChange"
22
- >;
23
- helpTextProps?: React.ComponentProps<"p">;
16
+ field: string;
17
+ label: string;
18
+ options: Array<{ value: string; label: string }>;
19
+ helpText?: string;
24
20
  }
25
21
 
26
22
  export function SelectFilter({
27
- config,
28
- value,
29
- onChange,
23
+ field,
24
+ label,
25
+ options,
26
+ helpText,
30
27
  className,
31
- labelProps,
32
- controlProps,
33
- helpTextProps,
34
28
  ...props
35
29
  }: SelectFilterProps) {
30
+ const { value, onChange } = useFilterField(field);
36
31
  return (
37
32
  <div className={cn("space-y-1.5", className)} {...props}>
38
- <Label htmlFor={`filter-${config.field}`} {...labelProps}>
39
- {labelProps?.children ?? config.label}
40
- </Label>
41
- <SelectFilterControl config={config} value={value} onChange={onChange} {...controlProps} />
42
- {config.helpText && (
43
- <p
44
- {...helpTextProps}
45
- className={cn("text-xs text-muted-foreground", helpTextProps?.className)}
46
- >
47
- {helpTextProps?.children ?? config.helpText}
48
- </p>
49
- )}
33
+ <Label htmlFor={`filter-${field}`}>{label}</Label>
34
+ <SelectFilterControl
35
+ field={field}
36
+ label={label}
37
+ options={options}
38
+ value={value}
39
+ onChange={onChange}
40
+ />
41
+ {helpText && <p className="text-xs text-muted-foreground">{helpText}</p>}
50
42
  </div>
51
43
  );
52
44
  }
53
45
 
54
46
  interface SelectFilterControlProps {
55
- config: FilterFieldConfig;
47
+ field: string;
48
+ label: string;
49
+ options: Array<{ value: string; label: string }>;
56
50
  value: ActiveFilterValue | undefined;
57
51
  onChange: (value: ActiveFilterValue | undefined) => void;
58
52
  triggerProps?: React.ComponentProps<typeof SelectTrigger>;
@@ -60,7 +54,9 @@ interface SelectFilterControlProps {
60
54
  }
61
55
 
62
56
  export function SelectFilterControl({
63
- config,
57
+ field,
58
+ label,
59
+ options,
64
60
  value,
65
61
  onChange,
66
62
  triggerProps,
@@ -73,20 +69,20 @@ export function SelectFilterControl({
73
69
  if (v === ALL_VALUE) {
74
70
  onChange(undefined);
75
71
  } else {
76
- onChange({ field: config.field, label: config.label, type: "picklist", value: v });
72
+ onChange({ field, label, type: "picklist", value: v });
77
73
  }
78
74
  }}
79
75
  >
80
76
  <SelectTrigger
81
- id={`filter-${config.field}`}
77
+ id={`filter-${field}`}
82
78
  {...triggerProps}
83
79
  className={cn("w-full", triggerProps?.className)}
84
80
  >
85
- <SelectValue placeholder={`Select ${config.label.toLowerCase()}`} />
81
+ <SelectValue placeholder={`Select ${label.toLowerCase()}`} />
86
82
  </SelectTrigger>
87
83
  <SelectContent {...contentProps}>
88
84
  <SelectItem value={ALL_VALUE}>All</SelectItem>
89
- {config.options?.map((opt) => (
85
+ {options.map((opt) => (
90
86
  <SelectItem key={opt.value} value={opt.value}>
91
87
  {opt.label}
92
88
  </SelectItem>