@salesforce/webapp-template-app-react-template-b2e-experimental 1.112.5 → 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.
- package/dist/.a4drules/webapp-data.md +5 -5
- package/dist/.a4drules/webapp-ui.md +2 -2
- package/dist/AGENT.md +6 -10
- package/dist/CHANGELOG.md +16 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/package.json +3 -3
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/api/graphqlClient.ts +25 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/__examples__/pages/AccountSearch.tsx +82 -54
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/FilterContext.tsx +73 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/PaginationControls.tsx +45 -87
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/filters/BooleanFilter.tsx +16 -36
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/filters/DateFilter.tsx +33 -77
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/filters/DateRangeFilter.tsx +14 -23
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/filters/MultiSelectFilter.tsx +18 -26
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/filters/NumericRangeFilter.tsx +22 -39
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/filters/SearchFilter.tsx +12 -15
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/filters/SelectFilter.tsx +30 -34
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/filters/TextFilter.tsx +27 -30
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/hooks/useAsyncData.ts +1 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/hooks/useCachedAsyncData.ts +1 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/hooks/useObjectSearchParams.ts +22 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/routes.tsx +0 -17
- package/dist/package-lock.json +2 -2
- package/dist/package.json +1 -1
- package/dist/{.a4drules/skills/using-salesforce-data → scripts}/graphql-search.sh +4 -4
- package/package.json +1 -1
- package/dist/.a4drules/skills/building-data-visualization/SKILL.md +0 -72
- package/dist/.a4drules/skills/building-data-visualization/implementation/bar-line-chart.md +0 -316
- package/dist/.a4drules/skills/building-data-visualization/implementation/dashboard-layout.md +0 -189
- package/dist/.a4drules/skills/building-data-visualization/implementation/donut-chart.md +0 -181
- package/dist/.a4drules/skills/building-data-visualization/implementation/stat-card.md +0 -150
- package/dist/.a4drules/skills/building-react-components/SKILL.md +0 -96
- package/dist/.a4drules/skills/building-react-components/implementation/component.md +0 -78
- package/dist/.a4drules/skills/building-react-components/implementation/header-footer.md +0 -132
- package/dist/.a4drules/skills/building-react-components/implementation/page.md +0 -93
- package/dist/.a4drules/skills/configuring-csp-trusted-sites/SKILL.md +0 -90
- package/dist/.a4drules/skills/configuring-csp-trusted-sites/implementation/metadata-format.md +0 -281
- package/dist/.a4drules/skills/configuring-webapp-metadata/SKILL.md +0 -158
- package/dist/.a4drules/skills/creating-webapp/SKILL.md +0 -140
- package/dist/.a4drules/skills/deploying-to-salesforce/SKILL.md +0 -226
- package/dist/.a4drules/skills/implementing-file-upload/SKILL.md +0 -396
- package/dist/.a4drules/skills/installing-webapp-features/SKILL.md +0 -210
- package/dist/.a4drules/skills/managing-agentforce-conversation-client/SKILL.md +0 -186
- package/dist/.a4drules/skills/managing-agentforce-conversation-client/references/constraints.md +0 -134
- package/dist/.a4drules/skills/managing-agentforce-conversation-client/references/examples.md +0 -132
- package/dist/.a4drules/skills/managing-agentforce-conversation-client/references/style-tokens.md +0 -101
- package/dist/.a4drules/skills/managing-agentforce-conversation-client/references/troubleshooting.md +0 -57
- package/dist/.a4drules/skills/using-salesforce-data/SKILL.md +0 -363
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/object-search/components/FilterPanel.tsx +0 -127
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/pages/TestAccPage.tsx +0 -19
|
@@ -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
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
|
62
|
+
const f = operatorToField(op);
|
|
73
63
|
onChange({
|
|
74
|
-
field
|
|
75
|
-
label
|
|
64
|
+
field,
|
|
65
|
+
label,
|
|
76
66
|
type: "date",
|
|
77
67
|
value: op,
|
|
78
|
-
min:
|
|
79
|
-
max:
|
|
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
|
|
89
|
-
<div className="flex
|
|
90
|
-
<
|
|
91
|
-
<
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
<
|
|
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
|
-
</
|
|
85
|
+
</SelectItem>
|
|
123
86
|
))}
|
|
124
|
-
</
|
|
125
|
-
</
|
|
87
|
+
</SelectContent>
|
|
88
|
+
</Select>
|
|
126
89
|
<DatePicker>
|
|
127
90
|
<DatePickerTrigger
|
|
128
|
-
className="
|
|
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={
|
|
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
|
-
{
|
|
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
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
39
|
-
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
|
|
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={
|
|
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
|
-
{
|
|
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
|
|
11
|
+
import { useFilterField } from "../FilterContext";
|
|
12
12
|
|
|
13
13
|
interface MultiSelectFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 ${
|
|
33
|
+
? `Select ${label.toLowerCase()}`
|
|
35
34
|
: selected.length === 1
|
|
36
|
-
? (
|
|
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
|
|
49
|
-
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
|
|
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
|
-
{
|
|
76
|
-
const id = `filter-${
|
|
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
|
-
{
|
|
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
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
31
|
-
<NumericRangeFilterInputs
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = (
|
|
49
|
+
const handleChange = (bound: "min" | "max", v: string) => {
|
|
67
50
|
const next = {
|
|
68
|
-
field
|
|
69
|
-
label
|
|
51
|
+
field,
|
|
52
|
+
label,
|
|
70
53
|
type: "numeric" as const,
|
|
71
54
|
min: value?.min ?? "",
|
|
72
55
|
max: value?.max ?? "",
|
|
73
|
-
[
|
|
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={`${
|
|
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={`${
|
|
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
|
|
4
|
+
import { useFilterField } from "../FilterContext";
|
|
5
5
|
|
|
6
6
|
interface SearchFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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-${
|
|
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
|
|
27
|
+
onChange({ field, label, type: "search", value: v });
|
|
31
28
|
} else {
|
|
32
29
|
onChange(undefined);
|
|
33
30
|
}
|
|
34
31
|
}}
|
|
35
|
-
placeholder={
|
|
36
|
-
inputProps={{ id: `filter-${
|
|
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
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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-${
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
72
|
+
onChange({ field, label, type: "picklist", value: v });
|
|
77
73
|
}
|
|
78
74
|
}}
|
|
79
75
|
>
|
|
80
76
|
<SelectTrigger
|
|
81
|
-
id={`filter-${
|
|
77
|
+
id={`filter-${field}`}
|
|
82
78
|
{...triggerProps}
|
|
83
79
|
className={cn("w-full", triggerProps?.className)}
|
|
84
80
|
>
|
|
85
|
-
<SelectValue placeholder={`Select ${
|
|
81
|
+
<SelectValue placeholder={`Select ${label.toLowerCase()}`} />
|
|
86
82
|
</SelectTrigger>
|
|
87
83
|
<SelectContent {...contentProps}>
|
|
88
84
|
<SelectItem value={ALL_VALUE}>All</SelectItem>
|
|
89
|
-
{
|
|
85
|
+
{options.map((opt) => (
|
|
90
86
|
<SelectItem key={opt.value} value={opt.value}>
|
|
91
87
|
{opt.label}
|
|
92
88
|
</SelectItem>
|