@stackcraftmx/calcita 0.56.0 → 0.58.0
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/README.md +83 -4
- package/dist/DataTable/ServerTableFilterDrawer.js +3 -1
- package/dist/DataTable/ServerTableFilterDrawer.js.map +1 -1
- package/dist/locales/en.js +15 -0
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +15 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/sortable/FormSortableField.js +27 -0
- package/dist/sortable/FormSortableField.js.map +1 -0
- package/dist/sortable/SortableTable.js +230 -0
- package/dist/sortable/SortableTable.js.map +1 -0
- package/dist/sortable/sortableRows.js +14 -0
- package/dist/sortable/sortableRows.js.map +1 -0
- package/dist/sortable.js +9 -0
- package/dist/sortable.js.map +1 -0
- package/dist/theme/components/minimalComponents.js +15 -0
- package/dist/theme/components/minimalComponents.js.map +1 -1
- package/dist/theme/glassSurface.js +10 -12
- package/dist/theme/glassSurface.js.map +1 -1
- package/dist/theme/themePieces.js +10 -4
- package/dist/theme/themePieces.js.map +1 -1
- package/dist/types/DataTable/ServerTableFilterDrawer.d.ts +2 -0
- package/dist/types/downloads/fileTypes.d.ts +4 -4
- package/dist/types/forms/SubmitButton.d.ts +1 -1
- package/dist/types/locales/en.d.ts +15 -0
- package/dist/types/locales/es.d.ts +15 -0
- package/dist/types/sortable/FormSortableField.d.ts +15 -0
- package/dist/types/sortable/SortableTable.d.ts +77 -0
- package/dist/types/sortable/sortableRows.d.ts +12 -0
- package/dist/types/sortable.d.ts +3 -0
- package/dist/types/ui/filters/FilterDrawer.d.ts +1 -0
- package/dist/types/ui/filters/filterDrawerFields.d.ts +77 -2
- package/dist/ui/filters/FilterDrawer.js +118 -53
- package/dist/ui/filters/FilterDrawer.js.map +1 -1
- package/dist/ui/filters/filterDrawerFields.js +65 -14
- package/dist/ui/filters/filterDrawerFields.js.map +1 -1
- package/package.json +5 -1
|
@@ -10,6 +10,15 @@ export declare const FILTER_FIELD_KIND: Readonly<{
|
|
|
10
10
|
comparison: "comparison";
|
|
11
11
|
custom: "custom";
|
|
12
12
|
}>;
|
|
13
|
+
/**
|
|
14
|
+
* Lo que puede estar mal en un campo del cajón; cada uno es su texto en common.filterDrawer. Mientras haya uno, «Buscar» no
|
|
15
|
+
* busca.
|
|
16
|
+
*/
|
|
17
|
+
export declare const FILTER_FIELD_PROBLEM: Readonly<{
|
|
18
|
+
rangeBackwards: "rangeBackwards";
|
|
19
|
+
beforeMinDate: "dateBeforeMin";
|
|
20
|
+
afterMaxDate: "dateAfterMax";
|
|
21
|
+
}>;
|
|
13
22
|
/** Los operadores de un campo de comparación, como viajan ('gte:500'). */
|
|
14
23
|
export declare const COMPARISON_OPERATOR: Readonly<{
|
|
15
24
|
equals: "eq";
|
|
@@ -29,7 +38,20 @@ export declare function isListField(field: {
|
|
|
29
38
|
multiple?: boolean;
|
|
30
39
|
}): boolean;
|
|
31
40
|
/**
|
|
32
|
-
*
|
|
41
|
+
* ¿El campo guarda un «desde» y un «hasta»? Los rangos de la casa y los propios.
|
|
42
|
+
*
|
|
43
|
+
* @param {{kind: string, keys?: {from?: string, to?: string}}} field
|
|
44
|
+
* @returns {boolean}
|
|
45
|
+
*/
|
|
46
|
+
export declare function isRangeField(field: {
|
|
47
|
+
kind: string;
|
|
48
|
+
keys?: {
|
|
49
|
+
from?: string;
|
|
50
|
+
to?: string;
|
|
51
|
+
};
|
|
52
|
+
}): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Los nombres con que viaja un campo en los filtros: uno, o dos para un rango (también uno propio: custom con keys).
|
|
33
55
|
*
|
|
34
56
|
* @param {{name: string, kind: string, keys?: {from?: string, to?: string}}} field
|
|
35
57
|
* @returns {string[]}
|
|
@@ -106,13 +128,66 @@ export declare function filterDrawerDefaultsOf(fields: object[]): Record<string,
|
|
|
106
128
|
*/
|
|
107
129
|
export declare function activeDrawerFilterCountOf(fields: object[], filters: Record<string, unknown>): number;
|
|
108
130
|
/**
|
|
109
|
-
* Los rangos que no tienen sentido («hasta» menor que «desde»): {llave de «hasta»: true}.
|
|
131
|
+
* Los rangos que no tienen sentido («hasta» menor que «desde»): {llave de «hasta»: true}. También los propios (custom con
|
|
132
|
+
* keys) que traen isBackwards.
|
|
110
133
|
*
|
|
111
134
|
* @param {object[]} fields
|
|
112
135
|
* @param {Record<string, unknown>} values
|
|
113
136
|
* @returns {Record<string, boolean>}
|
|
114
137
|
*/
|
|
115
138
|
export declare function invalidRangesOf(fields: object[], values: Record<string, unknown>): Record<string, boolean>;
|
|
139
|
+
/**
|
|
140
|
+
* Los topes de un dateRange: minDate y maxDate del campo, y hoy si pide disablePast o disableFuture (el más estrecho gana).
|
|
141
|
+
*
|
|
142
|
+
* @param {{minDate?: string, maxDate?: string, disablePast?: boolean, disableFuture?: boolean}} field
|
|
143
|
+
* @param {string} today AAAA-MM-DD en la zona del producto (todayInZone)
|
|
144
|
+
* @returns {{minDate?: string, maxDate?: string}}
|
|
145
|
+
*/
|
|
146
|
+
export declare function dateLimitsOf(field: {
|
|
147
|
+
minDate?: string;
|
|
148
|
+
maxDate?: string;
|
|
149
|
+
disablePast?: boolean;
|
|
150
|
+
disableFuture?: boolean;
|
|
151
|
+
}, today: string): {
|
|
152
|
+
minDate?: string;
|
|
153
|
+
maxDate?: string;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Los topes de cada extremo de un dateRange para su calendario: los del campo y, además, cada extremo acota al otro (el
|
|
157
|
+
* «desde» no pasa del «hasta» puesto y el «hasta» no baja del «desde»).
|
|
158
|
+
*
|
|
159
|
+
* @param {object} field
|
|
160
|
+
* @param {Record<string, unknown>} values el borrador del cajón
|
|
161
|
+
* @param {string} today AAAA-MM-DD en la zona del producto
|
|
162
|
+
* @returns {{from: {minDate?: string, maxDate?: string}, to: {minDate?: string, maxDate?: string}}}
|
|
163
|
+
*/
|
|
164
|
+
export declare function dateRangeBoundsOf(field: object, values: Record<string, unknown>, today: string): {
|
|
165
|
+
from: {
|
|
166
|
+
minDate?: string;
|
|
167
|
+
maxDate?: string;
|
|
168
|
+
};
|
|
169
|
+
to: {
|
|
170
|
+
minDate?: string;
|
|
171
|
+
maxDate?: string;
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Lo que está mal en los campos del cajón, por llave: un rango al revés (en su «hasta») y una fecha fuera de los topes de
|
|
176
|
+
* su dateRange (la que se tecleó: el calendario ya no deja escogerla). Mientras haya algo, «Buscar» no busca.
|
|
177
|
+
*
|
|
178
|
+
* {created_to: {problem: 'rangeBackwards'}, paid_from: {problem: 'dateBeforeMin', date: '2026-01-01'}}
|
|
179
|
+
*
|
|
180
|
+
* @param {object[]} fields
|
|
181
|
+
* @param {Record<string, unknown>} values
|
|
182
|
+
* @param {{today?: string}} [options] hoy AAAA-MM-DD en la zona del producto (para disablePast y disableFuture)
|
|
183
|
+
* @returns {Record<string, {problem: string, date?: string}>} problem es FILTER_FIELD_PROBLEM; date, el tope que se pasó
|
|
184
|
+
*/
|
|
185
|
+
export declare function drawerProblemsOf(fields: object[], values: Record<string, unknown>, { today }?: {
|
|
186
|
+
today?: string;
|
|
187
|
+
}): Record<string, {
|
|
188
|
+
problem: string;
|
|
189
|
+
date?: string;
|
|
190
|
+
}>;
|
|
116
191
|
/**
|
|
117
192
|
* Los chips de los campos del cajón, con la forma de buildChipConfig: cada valor con su etiqueta, ya dicho en palabras.
|
|
118
193
|
*
|
|
@@ -15,20 +15,48 @@ import NumberInput from "../../inputs/NumberInput.js";
|
|
|
15
15
|
import SelectInput from "../../inputs/SelectInput.js";
|
|
16
16
|
import TextInput from "../../inputs/TextInput.js";
|
|
17
17
|
import { piecePart, withPieceSlots, usePieceProps } from "../../theme/themePieces.js";
|
|
18
|
+
import { useToolkitSettings } from "../../toolkit/toolkitContext.js";
|
|
19
|
+
import { useFormatters } from "../../toolkit/useFormatters.js";
|
|
18
20
|
import { useToolkitTranslation } from "../../toolkit/useToolkitTranslation.js";
|
|
19
21
|
import { warnUnknownProps } from "../../toolkit/unknownProps.js";
|
|
20
|
-
import { drawerValuesOf,
|
|
22
|
+
import { drawerValuesOf, drawerProblemsOf, emptyDrawerValuesOf, filterKeysOf, FILTER_FIELD_KIND, comparisonOf, COMPARISON_OPERATOR, comparisonTextOf, dateRangeBoundsOf } from "./filterDrawerFields.js";
|
|
23
|
+
import { todayInZone } from "../metrics/heatmapScale.js";
|
|
21
24
|
const FILTER_DRAWER_ANCHOR = Object.freeze({ left: "left", right: "right" });
|
|
22
25
|
const DEFAULT_WIDTH = 380;
|
|
23
26
|
const ANY_OPTION = "__calcita_any__";
|
|
27
|
+
const DRAWER_CONTAINER_NAME = "calcita-filter-drawer";
|
|
28
|
+
function BrowserDateInput({ name, label, value, onChange, error, helperText, minDate, maxDate, className }) {
|
|
29
|
+
return /* @__PURE__ */ jsx(
|
|
30
|
+
TextInput,
|
|
31
|
+
{
|
|
32
|
+
type: "date",
|
|
33
|
+
name,
|
|
34
|
+
label,
|
|
35
|
+
value,
|
|
36
|
+
onChange,
|
|
37
|
+
error,
|
|
38
|
+
helperText,
|
|
39
|
+
clearable: false,
|
|
40
|
+
className,
|
|
41
|
+
slotProps: { htmlInput: { min: minDate, max: maxDate } }
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
24
45
|
const THEME_NAME = "CalcitaFilterDrawer";
|
|
25
46
|
const FilterDrawerHeader = piecePart(THEME_NAME, "Header", Stack);
|
|
26
47
|
const FilterDrawerBody = piecePart(THEME_NAME, "Body", Stack);
|
|
27
48
|
const FilterDrawerFooter = piecePart(THEME_NAME, "Footer", Stack);
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
const FilterDrawerDateInput = piecePart(THEME_NAME, "DateInput", BrowserDateInput);
|
|
50
|
+
function PairedField({ label, inputId, children, template = "minmax(0, 1fr) minmax(0, 1fr)", stackPairsBelow }) {
|
|
51
|
+
return /* @__PURE__ */ jsx(FieldFrame, { inputId, label, children: /* @__PURE__ */ jsx(Box, { sx: {
|
|
52
|
+
display: "grid",
|
|
53
|
+
gridTemplateColumns: template,
|
|
54
|
+
gap: 1.5,
|
|
55
|
+
alignItems: "start",
|
|
56
|
+
...stackPairsBelow && { [`@container ${DRAWER_CONTAINER_NAME} (width < ${stackPairsBelow}px)`]: { gridTemplateColumns: "minmax(0, 1fr)" } }
|
|
57
|
+
}, children }) });
|
|
30
58
|
}
|
|
31
|
-
function DrawerField({ field, values, setValue,
|
|
59
|
+
function DrawerField({ field, values, setValue, errorOf, today, stackPairsBelow, t }) {
|
|
32
60
|
const fieldId = useId();
|
|
33
61
|
const [key] = filterKeysOf(field);
|
|
34
62
|
switch (field.kind) {
|
|
@@ -66,7 +94,7 @@ function DrawerField({ field, values, setValue, invalidRanges, t }) {
|
|
|
66
94
|
case FILTER_FIELD_KIND.numberRange: {
|
|
67
95
|
const [fromKey, toKey] = filterKeysOf(field);
|
|
68
96
|
const numberProps = { decimals: field.decimals ?? 0, prefix: field.prefix, suffix: field.suffix, helperText: "" };
|
|
69
|
-
return /* @__PURE__ */ jsxs(PairedField, { label: field.label, inputId: `${fieldId}-from`, children: [
|
|
97
|
+
return /* @__PURE__ */ jsxs(PairedField, { label: field.label, inputId: `${fieldId}-from`, stackPairsBelow, children: [
|
|
70
98
|
/* @__PURE__ */ jsx(NumberInput, { label: t("filterDrawer.from"), value: values[fromKey], onChange: (number) => setValue(fromKey, number), ...numberProps }),
|
|
71
99
|
/* @__PURE__ */ jsx(
|
|
72
100
|
NumberInput,
|
|
@@ -75,24 +103,36 @@ function DrawerField({ field, values, setValue, invalidRanges, t }) {
|
|
|
75
103
|
value: values[toKey],
|
|
76
104
|
onChange: (number) => setValue(toKey, number),
|
|
77
105
|
...numberProps,
|
|
78
|
-
error:
|
|
106
|
+
error: errorOf(toKey)
|
|
79
107
|
}
|
|
80
108
|
)
|
|
81
109
|
] });
|
|
82
110
|
}
|
|
83
111
|
case FILTER_FIELD_KIND.dateRange: {
|
|
84
112
|
const [fromKey, toKey] = filterKeysOf(field);
|
|
85
|
-
|
|
86
|
-
|
|
113
|
+
const bounds = dateRangeBoundsOf(field, values, today);
|
|
114
|
+
return /* @__PURE__ */ jsxs(PairedField, { label: field.label, inputId: `${fieldId}-from`, stackPairsBelow, children: [
|
|
115
|
+
/* @__PURE__ */ jsx(
|
|
116
|
+
FilterDrawerDateInput,
|
|
117
|
+
{
|
|
118
|
+
name: fromKey,
|
|
119
|
+
label: t("filterDrawer.from"),
|
|
120
|
+
value: values[fromKey],
|
|
121
|
+
onChange: (date) => setValue(fromKey, date),
|
|
122
|
+
error: errorOf(fromKey),
|
|
123
|
+
helperText: "",
|
|
124
|
+
...bounds.from
|
|
125
|
+
}
|
|
126
|
+
),
|
|
87
127
|
/* @__PURE__ */ jsx(
|
|
88
|
-
|
|
128
|
+
FilterDrawerDateInput,
|
|
89
129
|
{
|
|
90
|
-
|
|
130
|
+
name: toKey,
|
|
91
131
|
label: t("filterDrawer.to"),
|
|
92
132
|
value: values[toKey],
|
|
93
133
|
onChange: (date) => setValue(toKey, date),
|
|
94
|
-
|
|
95
|
-
|
|
134
|
+
error: errorOf(toKey),
|
|
135
|
+
...bounds.to
|
|
96
136
|
}
|
|
97
137
|
)
|
|
98
138
|
] });
|
|
@@ -101,7 +141,7 @@ function DrawerField({ field, values, setValue, invalidRanges, t }) {
|
|
|
101
141
|
const parts = comparisonOf(values[key]);
|
|
102
142
|
const operators = field.operators ?? Object.values(COMPARISON_OPERATOR);
|
|
103
143
|
const operator = parts?.operator ?? field.defaultOperator ?? operators[0];
|
|
104
|
-
return /* @__PURE__ */ jsxs(PairedField, { label: field.label, inputId: `${fieldId}-value`, template: "minmax(0, 1fr) minmax(0, 1.2fr)", children: [
|
|
144
|
+
return /* @__PURE__ */ jsxs(PairedField, { label: field.label, inputId: `${fieldId}-value`, template: "minmax(0, 1fr) minmax(0, 1.2fr)", stackPairsBelow, children: [
|
|
105
145
|
/* @__PURE__ */ jsx(
|
|
106
146
|
SelectInput,
|
|
107
147
|
{
|
|
@@ -126,8 +166,11 @@ function DrawerField({ field, values, setValue, invalidRanges, t }) {
|
|
|
126
166
|
)
|
|
127
167
|
] });
|
|
128
168
|
}
|
|
129
|
-
case FILTER_FIELD_KIND.custom:
|
|
130
|
-
|
|
169
|
+
case FILTER_FIELD_KIND.custom: {
|
|
170
|
+
const keys = filterKeysOf(field);
|
|
171
|
+
const errors = Object.fromEntries(keys.map((fieldKey) => [fieldKey, errorOf(fieldKey)]).filter(([, text]) => text));
|
|
172
|
+
return field.render({ value: values[key], onChange: (next) => setValue(key, next), values, setValue, keys, errors });
|
|
173
|
+
}
|
|
131
174
|
default:
|
|
132
175
|
return null;
|
|
133
176
|
}
|
|
@@ -144,12 +187,15 @@ function FilterDrawerContent(props) {
|
|
|
144
187
|
title,
|
|
145
188
|
applyLabel,
|
|
146
189
|
closeOnApply = true,
|
|
190
|
+
stackPairsBelow,
|
|
147
191
|
children,
|
|
148
192
|
texts,
|
|
149
193
|
...unknownProps
|
|
150
194
|
} = usePieceProps(props, THEME_NAME);
|
|
151
195
|
warnUnknownProps(THEME_NAME, unknownProps);
|
|
152
196
|
const { t } = useToolkitTranslation("common", texts);
|
|
197
|
+
const { timeZone } = useToolkitSettings();
|
|
198
|
+
const { formatDate } = useFormatters();
|
|
153
199
|
const titleId = useId();
|
|
154
200
|
const [draft, setDraft] = useState(() => drawerValuesOf(fields, values));
|
|
155
201
|
useEffect(() => {
|
|
@@ -157,8 +203,10 @@ function FilterDrawerContent(props) {
|
|
|
157
203
|
setDraft(drawerValuesOf(fields, values));
|
|
158
204
|
}
|
|
159
205
|
}, [open]);
|
|
160
|
-
const
|
|
161
|
-
const
|
|
206
|
+
const today = todayInZone(timeZone);
|
|
207
|
+
const problems = drawerProblemsOf(fields, draft, { today });
|
|
208
|
+
const hasProblems = Object.keys(problems).length > 0;
|
|
209
|
+
const errorOf = (key) => problems[key] ? t(`filterDrawer.${problems[key].problem}`, { date: formatDate(problems[key].date) }) : void 0;
|
|
162
210
|
const setValue = (key, value) => setDraft((current) => ({ ...current, [key]: value }));
|
|
163
211
|
const apply = () => {
|
|
164
212
|
onApply(draft);
|
|
@@ -166,42 +214,59 @@ function FilterDrawerContent(props) {
|
|
|
166
214
|
onClose();
|
|
167
215
|
}
|
|
168
216
|
};
|
|
169
|
-
return
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
{
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
217
|
+
return (
|
|
218
|
+
// en la capa de las ventanas (modal), no en la del menú lateral (drawer): la barra de la app va encima del menú a
|
|
219
|
+
// propósito y, en la misma capa, tapaba el título y la × del cajón (medido en Chrome sin vidrio y con el minimalista)
|
|
220
|
+
/* @__PURE__ */ jsx(
|
|
221
|
+
Drawer,
|
|
222
|
+
{
|
|
223
|
+
anchor,
|
|
224
|
+
open,
|
|
225
|
+
onClose,
|
|
226
|
+
sx: { zIndex: (theme) => theme.zIndex.modal },
|
|
227
|
+
slotProps: { paper: { "aria-labelledby": titleId, className: `${THEME_NAME}-paper`, sx: { width: { xs: "100%", sm: width }, maxWidth: "100%" } } },
|
|
228
|
+
children: /* @__PURE__ */ jsxs(
|
|
229
|
+
Box,
|
|
230
|
+
{
|
|
231
|
+
component: "form",
|
|
232
|
+
noValidate: true,
|
|
233
|
+
sx: { display: "flex", flexDirection: "column", height: "100%", ...stackPairsBelow && { containerType: "inline-size", containerName: DRAWER_CONTAINER_NAME } },
|
|
234
|
+
onSubmit: (event) => {
|
|
235
|
+
event.preventDefault();
|
|
236
|
+
if (!hasProblems) {
|
|
237
|
+
apply();
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
children: [
|
|
241
|
+
/* @__PURE__ */ jsxs(FilterDrawerHeader, { direction: "row", ownSx: { alignItems: "center", gap: 1, px: 2, py: 1.5, borderBottom: 1, borderColor: "divider" }, children: [
|
|
242
|
+
/* @__PURE__ */ jsx(IconButton, { "aria-label": t("filterDrawer.close"), onClick: onClose, edge: "start", children: /* @__PURE__ */ jsx(CloseRoundedIcon, {}) }),
|
|
243
|
+
/* @__PURE__ */ jsx(Typography, { id: titleId, variant: "h6", component: "h2", sx: { flexGrow: 1 }, children: title ?? t("filterDrawer.title") })
|
|
244
|
+
] }),
|
|
245
|
+
/* @__PURE__ */ jsxs(FilterDrawerBody, { spacing: 1, ownSx: { flex: 1, overflowY: "auto", px: 2.5, py: 2 }, children: [
|
|
246
|
+
fields.map((field) => /* @__PURE__ */ jsx(
|
|
247
|
+
DrawerField,
|
|
248
|
+
{
|
|
249
|
+
field,
|
|
250
|
+
values: draft,
|
|
251
|
+
setValue,
|
|
252
|
+
errorOf,
|
|
253
|
+
today,
|
|
254
|
+
stackPairsBelow,
|
|
255
|
+
t
|
|
256
|
+
},
|
|
257
|
+
field.name
|
|
258
|
+
)),
|
|
259
|
+
children
|
|
260
|
+
] }),
|
|
261
|
+
/* @__PURE__ */ jsxs(FilterDrawerFooter, { direction: "row", ownSx: { gap: 1, justifyContent: "flex-end", px: 2.5, py: 1.5, borderTop: 1, borderColor: "divider" }, children: [
|
|
262
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => setDraft(emptyDrawerValuesOf(fields)), children: t("filterDrawer.clear") }),
|
|
263
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", variant: "contained", disabled: hasProblems, children: applyLabel ?? t("filterDrawer.apply") })
|
|
264
|
+
] })
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
)
|
|
205
270
|
);
|
|
206
271
|
}
|
|
207
272
|
const FilterDrawer = withPieceSlots(FilterDrawerContent, THEME_NAME);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilterDrawer.js","sources":["../../../src/ui/filters/FilterDrawer.jsx"],"sourcesContent":["// El cajón de búsqueda: un panel que sale de un lado con los campos que filtran una lista (las listas, un rango\n// «desde-hasta», unas fechas, un monto con su operador), para cuando los filtros ya no caben arriba de la tabla.\n//\n// Lo que se cambia adentro es un BORRADOR: la lista no se mueve hasta «Buscar» (no se consulta al servidor con cada tecla).\n// «Limpiar» vacía los campos del cajón; «Buscar» manda todos juntos (onApply) y cierra. Un rango al revés («hasta» menor que\n// «desde») se dice en su campo y no deja buscar.\n//\n// Los campos se declaran (filterDrawerFields.js dice cómo viaja cada uno); con kind 'custom' va uno propio (render). Para\n// conectarlo a ServerTable con su botón y sus chips: ServerTableFilterDrawer de /table.\n//\n// Configurable: de qué lado sale (anchor), su ancho, su título y los textos de sus botones. En el teléfono ocupa todo el\n// ancho.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaFilterDrawer con defaultProps y\n// styleOverrides de sus partes: paper, header, body, footer.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useId, useState} from 'react';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Drawer from '@mui/material/Drawer';\nimport IconButton from '@mui/material/IconButton';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport CloseRoundedIcon from '@mui/icons-material/CloseRounded';\n\nimport CatalogInput from '@/inputs/CatalogInput.jsx';\nimport CheckboxGroupInput from '@/inputs/CheckboxGroupInput.jsx';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport NumberInput from '@/inputs/NumberInput.jsx';\nimport SelectInput from '@/inputs/SelectInput.jsx';\nimport TextInput from '@/inputs/TextInput.jsx';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport {\n COMPARISON_OPERATOR, FILTER_FIELD_KIND, comparisonOf, comparisonTextOf, drawerValuesOf, emptyDrawerValuesOf, filterKeysOf,\n invalidRangesOf,\n} from '@/ui/filters/filterDrawerFields.js';\n\n/** De qué lado sale el cajón. */\nexport const FILTER_DRAWER_ANCHOR = Object.freeze({left: 'left', right: 'right'});\nconst DEFAULT_WIDTH = 380;\n// «Cualquiera» de una lista de una sola opción: no es una opción y viaja como '' (sin filtro)\nconst ANY_OPTION = '__calcita_any__';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaFilterDrawer)\nconst THEME_NAME = 'CalcitaFilterDrawer';\nconst FilterDrawerHeader = piecePart(THEME_NAME, 'Header', Stack);\nconst FilterDrawerBody = piecePart(THEME_NAME, 'Body', Stack);\nconst FilterDrawerFooter = piecePart(THEME_NAME, 'Footer', Stack);\n\n/**\n * Dos campos lado a lado (desde y hasta, operador y número); en un cajón angosto se ven bien juntos.\n *\n * @param {{label: import('react').ReactNode, inputId: string, children: import('react').ReactNode, template?: string}} props\n */\nfunction PairedField({label, inputId, children, template = '1fr 1fr'}) {\n return (\n <FieldFrame inputId={inputId} label={label}>\n <Box sx={{display: 'grid', gridTemplateColumns: template, gap: 1.5, alignItems: 'start'}}>{children}</Box>\n </FieldFrame>\n );\n}\n\n/**\n * Un campo del cajón según su tipo, con las piezas de la casa.\n *\n * @param {object} props\n */\nfunction DrawerField({field, values, setValue, invalidRanges, t}) {\n const fieldId = useId();\n const [key] = filterKeysOf(field);\n switch (field.kind) {\n case FILTER_FIELD_KIND.text:\n return <TextInput label={field.label} value={values[key]} onChange={(text) => setValue(key, text)} placeholder={field.placeholder} />;\n case FILTER_FIELD_KIND.select:\n return (\n <SelectInput label={field.label} value={values[key] || ANY_OPTION} onChange={(chosen) => setValue(key, chosen === ANY_OPTION ? '' : chosen)}\n options={[{value: ANY_OPTION, label: t('filterDrawer.any')}, ...field.options]} />\n );\n case FILTER_FIELD_KIND.multiSelect:\n return <SelectInput label={field.label} value={values[key]} onChange={(chosen) => setValue(key, chosen)} options={field.options} multiple selectAll checkboxes />;\n case FILTER_FIELD_KIND.catalog:\n return (\n <CatalogInput label={field.label} value={values[key]} onChange={(chosen) => setValue(key, chosen)} options={field.options}\n multiple={field.multiple} selectAll={field.multiple} checkboxes={field.multiple} formatLabel={field.formatLabel}\n placeholder={field.placeholder} />\n );\n case FILTER_FIELD_KIND.checkboxes:\n return <CheckboxGroupInput label={field.label} value={values[key]} onChange={(chosen) => setValue(key, chosen)} options={field.options} selectAll={field.selectAll} />;\n case FILTER_FIELD_KIND.numberRange: {\n const [fromKey, toKey] = filterKeysOf(field);\n const numberProps = {decimals: field.decimals ?? 0, prefix: field.prefix, suffix: field.suffix, helperText: ''};\n return (\n <PairedField label={field.label} inputId={`${fieldId}-from`}>\n <NumberInput label={t('filterDrawer.from')} value={values[fromKey]} onChange={(number) => setValue(fromKey, number)} {...numberProps} />\n <NumberInput label={t('filterDrawer.to')} value={values[toKey]} onChange={(number) => setValue(toKey, number)} {...numberProps}\n error={invalidRanges[toKey] ? t('filterDrawer.rangeBackwards') : undefined} />\n </PairedField>\n );\n }\n case FILTER_FIELD_KIND.dateRange: {\n const [fromKey, toKey] = filterKeysOf(field);\n // el calendario del navegador (como DateRangeFilter): viaja AAAA-MM-DD, sin pedir MUI X\n return (\n <PairedField label={field.label} inputId={`${fieldId}-from`}>\n <TextInput type=\"date\" label={t('filterDrawer.from')} value={values[fromKey]} onChange={(date) => setValue(fromKey, date)} clearable={false} helperText=\"\" />\n <TextInput type=\"date\" label={t('filterDrawer.to')} value={values[toKey]} onChange={(date) => setValue(toKey, date)} clearable={false}\n error={invalidRanges[toKey] ? t('filterDrawer.rangeBackwards') : undefined} />\n </PairedField>\n );\n }\n case FILTER_FIELD_KIND.comparison: {\n const parts = comparisonOf(values[key]);\n const operators = field.operators ?? Object.values(COMPARISON_OPERATOR);\n const operator = parts?.operator ?? field.defaultOperator ?? operators[0];\n return (\n <PairedField label={field.label} inputId={`${fieldId}-value`} template=\"minmax(0, 1fr) minmax(0, 1.2fr)\">\n <SelectInput label={t('filterDrawer.operator')} value={operator} helperText=\"\"\n options={operators.map((option) => ({value: option, label: t(`filterDrawer.operators.${option}`)}))}\n onChange={(chosen) => setValue(key, comparisonTextOf(chosen, parts?.value ?? ''))} />\n <NumberInput label={t('filterDrawer.value')} value={parts?.value ?? ''} decimals={field.decimals ?? 2} prefix={field.prefix} suffix={field.suffix}\n helperText=\"\" onChange={(number) => setValue(key, comparisonTextOf(operator, number))} />\n </PairedField>\n );\n }\n case FILTER_FIELD_KIND.custom:\n return field.render({value: values[key], onChange: (next) => setValue(key, next), values, setValue});\n default:\n return null;\n }\n}\n\n/**\n * @param {object} props\n * @param {boolean} props.open\n * @param {() => void} props.onClose\n * @param {object[]} props.fields los campos (filterDrawerFields.js): {name, label, kind, options?, keys?, prefix?, suffix?, decimals?, operators?, render?…}\n * @param {Record<string, unknown>} props.values los filtros puestos hoy (de ahí sale el borrador al abrir)\n * @param {(values: Record<string, unknown>) => void} props.onApply todos los campos del cajón juntos, al picar «Buscar»\n * @param {'left'|'right'} [props.anchor] de qué lado sale (FILTER_DRAWER_ANCHOR); por omisión la derecha\n * @param {number} [props.width] su ancho en escritorio, en px\n * @param {string} [props.title] ya traducido; por omisión «Filtros»\n * @param {string} [props.applyLabel] ya traducido; por omisión «Buscar»\n * @param {boolean} [props.closeOnApply] por omisión sí\n * @param {import('react').ReactNode} [props.children] algo más abajo de los campos (una nota, un enlace)\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction FilterDrawerContent(props) {\n const {\n open, onClose, fields, values, onApply, anchor = FILTER_DRAWER_ANCHOR.right, width = DEFAULT_WIDTH, title, applyLabel,\n closeOnApply = true, children, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const titleId = useId();\n const [draft, setDraft] = useState(() => drawerValuesOf(fields, values));\n // cada vez que se abre, el borrador empieza de lo que hay puesto (lo de la vez pasada sin buscar no se queda)\n useEffect(() => {\n if (open) {\n setDraft(drawerValuesOf(fields, values));\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open]);\n const invalidRanges = invalidRangesOf(fields, draft);\n const hasInvalidRange = Object.keys(invalidRanges).length > 0;\n const setValue = (key, value) => setDraft((current) => ({...current, [key]: value}));\n const apply = () => {\n onApply(draft);\n if (closeOnApply) {\n onClose();\n }\n };\n\n return (\n <Drawer\n anchor={anchor}\n open={open}\n onClose={onClose}\n slotProps={{paper: {'aria-labelledby': titleId, className: `${THEME_NAME}-paper`, sx: {width: {xs: '100%', sm: width}, maxWidth: '100%'}}}}\n >\n <Box component=\"form\" noValidate sx={{display: 'flex', flexDirection: 'column', height: '100%'}}\n onSubmit={(event) => {\n event.preventDefault();\n if (!hasInvalidRange) {\n apply();\n }\n }}>\n <FilterDrawerHeader direction=\"row\" ownSx={{alignItems: 'center', gap: 1, px: 2, py: 1.5, borderBottom: 1, borderColor: 'divider'}}>\n <IconButton aria-label={t('filterDrawer.close')} onClick={onClose} edge=\"start\"><CloseRoundedIcon /></IconButton>\n <Typography id={titleId} variant=\"h6\" component=\"h2\" sx={{flexGrow: 1}}>{title ?? t('filterDrawer.title')}</Typography>\n </FilterDrawerHeader>\n <FilterDrawerBody spacing={1} ownSx={{flex: 1, overflowY: 'auto', px: 2.5, py: 2}}>\n {fields.map((field) => (\n <DrawerField key={field.name} field={field} values={draft} setValue={setValue} invalidRanges={invalidRanges} t={t} />\n ))}\n {children}\n </FilterDrawerBody>\n <FilterDrawerFooter direction=\"row\" ownSx={{gap: 1, justifyContent: 'flex-end', px: 2.5, py: 1.5, borderTop: 1, borderColor: 'divider'}}>\n <Button onClick={() => setDraft(emptyDrawerValuesOf(fields))}>{t('filterDrawer.clear')}</Button>\n <Button type=\"submit\" variant=\"contained\" disabled={hasInvalidRange}>{applyLabel ?? t('filterDrawer.apply')}</Button>\n </FilterDrawerFooter>\n </Box>\n </Drawer>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst FilterDrawer = withPieceSlots(FilterDrawerContent, THEME_NAME);\nexport default FilterDrawer;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FilterDrawer.js","sources":["../../../src/ui/filters/FilterDrawer.jsx"],"sourcesContent":["// El cajón de búsqueda: un panel que sale de un lado con los campos que filtran una lista (las listas, un rango\n// «desde-hasta», unas fechas, un monto con su operador), para cuando los filtros ya no caben arriba de la tabla.\n//\n// Lo que se cambia adentro es un BORRADOR: la lista no se mueve hasta «Buscar» (no se consulta al servidor con cada tecla).\n// «Limpiar» vacía los campos del cajón; «Buscar» manda todos juntos (onApply) y cierra. Un rango al revés («hasta» menor que\n// «desde») o una fecha fuera de sus topes se dice en su campo y no deja buscar; nada se borra solo.\n//\n// Los campos se declaran (filterDrawerFields.js dice cómo viaja cada uno); con kind 'custom' va uno propio (render), o un\n// rango propio con sus dos llaves (keys). Para conectarlo a ServerTable con su botón y sus chips: ServerTableFilterDrawer de\n// /table.\n//\n// LAS FECHAS: por omisión, el calendario del navegador (así el cajón no obliga a instalar MUI X). Una app que ya trae\n// /date-pickers pone el suyo para que el cajón y sus formularios se vean igual: slots={{dateInput: DateInput}} en el cajón,\n// o una vez en el tema para todas las listas (components.CalcitaFilterDrawer.defaultProps.slots). El campo recibe name,\n// label, value, onChange, error, helperText, minDate y maxDate: lo mismo que DateInput.\n//\n// Configurable: de qué lado sale (anchor), su ancho, su título, los textos de sus botones y si los pares («desde» y\n// «hasta», operador y número) se ponen uno arriba del otro cuando el cajón es angosto (stackPairsBelow). En el teléfono\n// ocupa todo el ancho.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaFilterDrawer con defaultProps y\n// styleOverrides de sus partes: paper, header, body, footer y dateInput.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useId, useState} from 'react';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Drawer from '@mui/material/Drawer';\nimport IconButton from '@mui/material/IconButton';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport CloseRoundedIcon from '@mui/icons-material/CloseRounded';\n\nimport CatalogInput from '@/inputs/CatalogInput.jsx';\nimport CheckboxGroupInput from '@/inputs/CheckboxGroupInput.jsx';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport NumberInput from '@/inputs/NumberInput.jsx';\nimport SelectInput from '@/inputs/SelectInput.jsx';\nimport TextInput from '@/inputs/TextInput.jsx';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {useToolkitSettings} from '@/toolkit/toolkitContext.js';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport {\n COMPARISON_OPERATOR, FILTER_FIELD_KIND, comparisonOf, comparisonTextOf, dateRangeBoundsOf, drawerProblemsOf, drawerValuesOf,\n emptyDrawerValuesOf, filterKeysOf,\n} from '@/ui/filters/filterDrawerFields.js';\nimport {todayInZone} from '@/ui/metrics/heatmapScale.js';\n\n/** De qué lado sale el cajón. */\nexport const FILTER_DRAWER_ANCHOR = Object.freeze({left: 'left', right: 'right'});\nconst DEFAULT_WIDTH = 380;\n// «Cualquiera» de una lista de una sola opción: no es una opción y viaja como '' (sin filtro)\nconst ANY_OPTION = '__calcita_any__';\n// el nombre del cajón para medir su ancho (stackPairsBelow): los pares se apilan con una regla de CSS sobre el cajón, no\n// sobre la pantalla (en escritorio el cajón es angosto aunque la pantalla no)\nconst DRAWER_CONTAINER_NAME = 'calcita-filter-drawer';\n\n/**\n * El campo de fecha de fábrica: el calendario del navegador, sin pedir MUI X; viaja AAAA-MM-DD. Recibe lo mismo que DateInput\n * de /date-pickers (name, label, value, onChange, error, helperText, minDate, maxDate), así que se cambia por ese con\n * slots={{dateInput: DateInput}}.\n *\n * @param {object} props\n */\nfunction BrowserDateInput({name, label, value, onChange, error, helperText, minDate, maxDate, className}) {\n return (\n <TextInput type=\"date\" name={name} label={label} value={value} onChange={onChange} error={error} helperText={helperText} clearable={false}\n className={className} slotProps={{htmlInput: {min: minDate, max: maxDate}}} />\n );\n}\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaFilterDrawer)\nconst THEME_NAME = 'CalcitaFilterDrawer';\nconst FilterDrawerHeader = piecePart(THEME_NAME, 'Header', Stack);\nconst FilterDrawerBody = piecePart(THEME_NAME, 'Body', Stack);\nconst FilterDrawerFooter = piecePart(THEME_NAME, 'Footer', Stack);\nconst FilterDrawerDateInput = piecePart(THEME_NAME, 'DateInput', BrowserDateInput);\n\n/**\n * Dos campos lado a lado (desde y hasta, operador y número). Con stackPairsBelow, si el cajón mide menos, uno arriba del\n * otro: con un calendario de MUI X, en un cajón angosto cada fecha quedaba muy apretada.\n *\n * OJO, trampa pagada: las columnas van con minmax(0, …). Con '1fr 1fr' una columna no baja del ancho mínimo de su campo, y\n * el calendario de MUI X no se encoge: empujaba la columna y el «hasta» se salía del cajón (medido en Chrome, 460 px).\n *\n * @param {{label: import('react').ReactNode, inputId: string, children: import('react').ReactNode, template?: string, stackPairsBelow?: number}} props\n */\nfunction PairedField({label, inputId, children, template = 'minmax(0, 1fr) minmax(0, 1fr)', stackPairsBelow}) {\n return (\n <FieldFrame inputId={inputId} label={label}>\n <Box sx={{\n display: 'grid', gridTemplateColumns: template, gap: 1.5, alignItems: 'start',\n ...(stackPairsBelow && {[`@container ${DRAWER_CONTAINER_NAME} (width < ${stackPairsBelow}px)`]: {gridTemplateColumns: 'minmax(0, 1fr)'}}),\n }}>\n {children}\n </Box>\n </FieldFrame>\n );\n}\n\n/**\n * Un campo del cajón según su tipo, con las piezas de la casa.\n *\n * @param {object} props\n */\nfunction DrawerField({field, values, setValue, errorOf, today, stackPairsBelow, t}) {\n const fieldId = useId();\n const [key] = filterKeysOf(field);\n switch (field.kind) {\n case FILTER_FIELD_KIND.text:\n return <TextInput label={field.label} value={values[key]} onChange={(text) => setValue(key, text)} placeholder={field.placeholder} />;\n case FILTER_FIELD_KIND.select:\n return (\n <SelectInput label={field.label} value={values[key] || ANY_OPTION} onChange={(chosen) => setValue(key, chosen === ANY_OPTION ? '' : chosen)}\n options={[{value: ANY_OPTION, label: t('filterDrawer.any')}, ...field.options]} />\n );\n case FILTER_FIELD_KIND.multiSelect:\n return <SelectInput label={field.label} value={values[key]} onChange={(chosen) => setValue(key, chosen)} options={field.options} multiple selectAll checkboxes />;\n case FILTER_FIELD_KIND.catalog:\n return (\n <CatalogInput label={field.label} value={values[key]} onChange={(chosen) => setValue(key, chosen)} options={field.options}\n multiple={field.multiple} selectAll={field.multiple} checkboxes={field.multiple} formatLabel={field.formatLabel}\n placeholder={field.placeholder} />\n );\n case FILTER_FIELD_KIND.checkboxes:\n return <CheckboxGroupInput label={field.label} value={values[key]} onChange={(chosen) => setValue(key, chosen)} options={field.options} selectAll={field.selectAll} />;\n case FILTER_FIELD_KIND.numberRange: {\n const [fromKey, toKey] = filterKeysOf(field);\n const numberProps = {decimals: field.decimals ?? 0, prefix: field.prefix, suffix: field.suffix, helperText: ''};\n return (\n <PairedField label={field.label} inputId={`${fieldId}-from`} stackPairsBelow={stackPairsBelow}>\n <NumberInput label={t('filterDrawer.from')} value={values[fromKey]} onChange={(number) => setValue(fromKey, number)} {...numberProps} />\n <NumberInput label={t('filterDrawer.to')} value={values[toKey]} onChange={(number) => setValue(toKey, number)} {...numberProps}\n error={errorOf(toKey)} />\n </PairedField>\n );\n }\n case FILTER_FIELD_KIND.dateRange: {\n const [fromKey, toKey] = filterKeysOf(field);\n // los topes del campo y cada extremo acota al otro: el calendario no deja escoger fuera ni un rango al revés\n const bounds = dateRangeBoundsOf(field, values, today);\n // el «hasta» sin helperText: deja su renglón vacío debajo del par, como siempre\n return (\n <PairedField label={field.label} inputId={`${fieldId}-from`} stackPairsBelow={stackPairsBelow}>\n <FilterDrawerDateInput name={fromKey} label={t('filterDrawer.from')} value={values[fromKey]} onChange={(date) => setValue(fromKey, date)}\n error={errorOf(fromKey)} helperText=\"\" {...bounds.from} />\n <FilterDrawerDateInput name={toKey} label={t('filterDrawer.to')} value={values[toKey]} onChange={(date) => setValue(toKey, date)}\n error={errorOf(toKey)} {...bounds.to} />\n </PairedField>\n );\n }\n case FILTER_FIELD_KIND.comparison: {\n const parts = comparisonOf(values[key]);\n const operators = field.operators ?? Object.values(COMPARISON_OPERATOR);\n const operator = parts?.operator ?? field.defaultOperator ?? operators[0];\n return (\n <PairedField label={field.label} inputId={`${fieldId}-value`} template=\"minmax(0, 1fr) minmax(0, 1.2fr)\" stackPairsBelow={stackPairsBelow}>\n <SelectInput label={t('filterDrawer.operator')} value={operator} helperText=\"\"\n options={operators.map((option) => ({value: option, label: t(`filterDrawer.operators.${option}`)}))}\n onChange={(chosen) => setValue(key, comparisonTextOf(chosen, parts?.value ?? ''))} />\n <NumberInput label={t('filterDrawer.value')} value={parts?.value ?? ''} decimals={field.decimals ?? 2} prefix={field.prefix} suffix={field.suffix}\n helperText=\"\" onChange={(number) => setValue(key, comparisonTextOf(operator, number))} />\n </PairedField>\n );\n }\n case FILTER_FIELD_KIND.custom: {\n // el contrato del campo propio: su valor, todo el borrador (values y setValue: un rango propio acota un extremo con\n // el otro o escribe en la otra llave), sus llaves y lo que esté mal en cada una, ya dicho (errors)\n const keys = filterKeysOf(field);\n const errors = Object.fromEntries(keys.map((fieldKey) => [fieldKey, errorOf(fieldKey)]).filter(([, text]) => text));\n return field.render({value: values[key], onChange: (next) => setValue(key, next), values, setValue, keys, errors});\n }\n default:\n return null;\n }\n}\n\n/**\n * @param {object} props\n * @param {boolean} props.open\n * @param {() => void} props.onClose\n * @param {object[]} props.fields los campos (filterDrawerFields.js): {name, label, kind, options?, keys?, prefix?, suffix?, decimals?, operators?,\n * minDate?, maxDate?, disablePast?, disableFuture?, render?, isBackwards?…}\n * @param {Record<string, unknown>} props.values los filtros puestos hoy (de ahí sale el borrador al abrir)\n * @param {(values: Record<string, unknown>) => void} props.onApply todos los campos del cajón juntos, al picar «Buscar»\n * @param {'left'|'right'} [props.anchor] de qué lado sale (FILTER_DRAWER_ANCHOR); por omisión la derecha\n * @param {number} [props.width] su ancho en escritorio, en px\n * @param {string} [props.title] ya traducido; por omisión «Filtros»\n * @param {string} [props.applyLabel] ya traducido; por omisión «Buscar»\n * @param {boolean} [props.closeOnApply] por omisión sí\n * @param {number} [props.stackPairsBelow] en px: si el cajón mide menos, cada par («desde» y «hasta», operador y número) va uno\n * arriba del otro (también en el teléfono, donde ocupa todo el ancho). Sin la opción, lado a lado como siempre\n * @param {import('react').ReactNode} [props.children] algo más abajo de los campos (una nota, un enlace)\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction FilterDrawerContent(props) {\n const {\n open, onClose, fields, values, onApply, anchor = FILTER_DRAWER_ANCHOR.right, width = DEFAULT_WIDTH, title, applyLabel,\n closeOnApply = true, stackPairsBelow, children, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const {timeZone} = useToolkitSettings();\n const {formatDate} = useFormatters();\n const titleId = useId();\n const [draft, setDraft] = useState(() => drawerValuesOf(fields, values));\n // cada vez que se abre, el borrador empieza de lo que hay puesto (lo de la vez pasada sin buscar no se queda)\n useEffect(() => {\n if (open) {\n setDraft(drawerValuesOf(fields, values));\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open]);\n // hoy en la zona del producto: el tope de disableFuture y disablePast\n const today = todayInZone(timeZone);\n const problems = drawerProblemsOf(fields, draft, {today});\n const hasProblems = Object.keys(problems).length > 0;\n // lo que está mal en una llave, ya dicho (con la fecha del tope que se pasó)\n const errorOf = (key) => (problems[key] ? t(`filterDrawer.${problems[key].problem}`, {date: formatDate(problems[key].date)}) : undefined);\n const setValue = (key, value) => setDraft((current) => ({...current, [key]: value}));\n const apply = () => {\n onApply(draft);\n if (closeOnApply) {\n onClose();\n }\n };\n\n return (\n // en la capa de las ventanas (modal), no en la del menú lateral (drawer): la barra de la app va encima del menú a\n // propósito y, en la misma capa, tapaba el título y la × del cajón (medido en Chrome sin vidrio y con el minimalista)\n <Drawer\n anchor={anchor}\n open={open}\n onClose={onClose}\n sx={{zIndex: (theme) => theme.zIndex.modal}}\n slotProps={{paper: {'aria-labelledby': titleId, className: `${THEME_NAME}-paper`, sx: {width: {xs: '100%', sm: width}, maxWidth: '100%'}}}}\n >\n <Box component=\"form\" noValidate\n sx={{display: 'flex', flexDirection: 'column', height: '100%', ...(stackPairsBelow && {containerType: 'inline-size', containerName: DRAWER_CONTAINER_NAME})}}\n onSubmit={(event) => {\n event.preventDefault();\n if (!hasProblems) {\n apply();\n }\n }}>\n <FilterDrawerHeader direction=\"row\" ownSx={{alignItems: 'center', gap: 1, px: 2, py: 1.5, borderBottom: 1, borderColor: 'divider'}}>\n <IconButton aria-label={t('filterDrawer.close')} onClick={onClose} edge=\"start\"><CloseRoundedIcon /></IconButton>\n <Typography id={titleId} variant=\"h6\" component=\"h2\" sx={{flexGrow: 1}}>{title ?? t('filterDrawer.title')}</Typography>\n </FilterDrawerHeader>\n <FilterDrawerBody spacing={1} ownSx={{flex: 1, overflowY: 'auto', px: 2.5, py: 2}}>\n {fields.map((field) => (\n <DrawerField key={field.name} field={field} values={draft} setValue={setValue} errorOf={errorOf} today={today}\n stackPairsBelow={stackPairsBelow} t={t} />\n ))}\n {children}\n </FilterDrawerBody>\n <FilterDrawerFooter direction=\"row\" ownSx={{gap: 1, justifyContent: 'flex-end', px: 2.5, py: 1.5, borderTop: 1, borderColor: 'divider'}}>\n <Button onClick={() => setDraft(emptyDrawerValuesOf(fields))}>{t('filterDrawer.clear')}</Button>\n <Button type=\"submit\" variant=\"contained\" disabled={hasProblems}>{applyLabel ?? t('filterDrawer.apply')}</Button>\n </FilterDrawerFooter>\n </Box>\n </Drawer>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst FilterDrawer = withPieceSlots(FilterDrawerContent, THEME_NAME);\nexport default FilterDrawer;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAmDO,MAAM,uBAAuB,OAAO,OAAO,EAAC,MAAM,QAAQ,OAAO,SAAQ;AAChF,MAAM,gBAAgB;AAEtB,MAAM,aAAa;AAGnB,MAAM,wBAAwB;AAS9B,SAAS,iBAAiB,EAAC,MAAM,OAAO,OAAO,UAAU,OAAO,YAAY,SAAS,SAAS,UAAA,GAAY;AACtG,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MAAU,MAAK;AAAA,MAAO;AAAA,MAAY;AAAA,MAAc;AAAA,MAAc;AAAA,MAAoB;AAAA,MAAc;AAAA,MAAwB,WAAW;AAAA,MACzH;AAAA,MAAsB,WAAW,EAAC,WAAW,EAAC,KAAK,SAAS,KAAK,UAAO;AAAA,IAAC;AAAA,EAAA;AAE5F;AAGA,MAAM,aAAa;AACnB,MAAM,qBAAqB,UAAU,YAAY,UAAU,KAAK;AAChE,MAAM,mBAAmB,UAAU,YAAY,QAAQ,KAAK;AAC5D,MAAM,qBAAqB,UAAU,YAAY,UAAU,KAAK;AAChE,MAAM,wBAAwB,UAAU,YAAY,aAAa,gBAAgB;AAWjF,SAAS,YAAY,EAAC,OAAO,SAAS,UAAU,WAAW,iCAAiC,mBAAkB;AAC1G,6BACK,YAAA,EAAW,SAAkB,OAC1B,UAAA,oBAAC,OAAI,IAAI;AAAA,IACL,SAAS;AAAA,IAAQ,qBAAqB;AAAA,IAAU,KAAK;AAAA,IAAK,YAAY;AAAA,IACtE,GAAI,mBAAmB,EAAC,CAAC,cAAc,qBAAqB,aAAa,eAAe,KAAK,GAAG,EAAC,qBAAqB,mBAAgB;AAAA,EAAC,GAEtI,UACL,GACJ;AAER;AAOA,SAAS,YAAY,EAAC,OAAO,QAAQ,UAAU,SAAS,OAAO,iBAAiB,KAAI;AAChF,QAAM,UAAU,MAAA;AAChB,QAAM,CAAC,GAAG,IAAI,aAAa,KAAK;AAChC,UAAQ,MAAM,MAAA;AAAA,IACV,KAAK,kBAAkB;AACnB,iCAAQ,WAAA,EAAU,OAAO,MAAM,OAAO,OAAO,OAAO,GAAG,GAAG,UAAU,CAAC,SAAS,SAAS,KAAK,IAAI,GAAG,aAAa,MAAM,aAAa;AAAA,IACvI,KAAK,kBAAkB;AACnB,aACI;AAAA,QAAC;AAAA,QAAA;AAAA,UAAY,OAAO,MAAM;AAAA,UAAO,OAAO,OAAO,GAAG,KAAK;AAAA,UAAY,UAAU,CAAC,WAAW,SAAS,KAAK,WAAW,aAAa,KAAK,MAAM;AAAA,UAC7H,SAAS,CAAC,EAAC,OAAO,YAAY,OAAO,EAAE,kBAAkB,EAAA,GAAI,GAAG,MAAM,OAAO;AAAA,QAAA;AAAA,MAAA;AAAA,IAElG,KAAK,kBAAkB;AACnB,aAAO,oBAAC,aAAA,EAAY,OAAO,MAAM,OAAO,OAAO,OAAO,GAAG,GAAG,UAAU,CAAC,WAAW,SAAS,KAAK,MAAM,GAAG,SAAS,MAAM,SAAS,UAAQ,MAAC,WAAS,MAAC,YAAU,KAAA,CAAC;AAAA,IACnK,KAAK,kBAAkB;AACnB,aACI;AAAA,QAAC;AAAA,QAAA;AAAA,UAAa,OAAO,MAAM;AAAA,UAAO,OAAO,OAAO,GAAG;AAAA,UAAG,UAAU,CAAC,WAAW,SAAS,KAAK,MAAM;AAAA,UAAG,SAAS,MAAM;AAAA,UACpG,UAAU,MAAM;AAAA,UAAU,WAAW,MAAM;AAAA,UAAU,YAAY,MAAM;AAAA,UAAU,aAAa,MAAM;AAAA,UACpG,aAAa,MAAM;AAAA,QAAA;AAAA,MAAA;AAAA,IAEzC,KAAK,kBAAkB;AACnB,aAAO,oBAAC,sBAAmB,OAAO,MAAM,OAAO,OAAO,OAAO,GAAG,GAAG,UAAU,CAAC,WAAW,SAAS,KAAK,MAAM,GAAG,SAAS,MAAM,SAAS,WAAW,MAAM,WAAW;AAAA,IACxK,KAAK,kBAAkB,aAAa;AAChC,YAAM,CAAC,SAAS,KAAK,IAAI,aAAa,KAAK;AAC3C,YAAM,cAAc,EAAC,UAAU,MAAM,YAAY,GAAG,QAAQ,MAAM,QAAQ,QAAQ,MAAM,QAAQ,YAAY,GAAA;AAC5G,aACI,qBAAC,eAAY,OAAO,MAAM,OAAO,SAAS,GAAG,OAAO,SAAS,iBACzD,UAAA;AAAA,QAAA,oBAAC,eAAY,OAAO,EAAE,mBAAmB,GAAG,OAAO,OAAO,OAAO,GAAG,UAAU,CAAC,WAAW,SAAS,SAAS,MAAM,GAAI,GAAG,aAAa;AAAA,QACtI;AAAA,UAAC;AAAA,UAAA;AAAA,YAAY,OAAO,EAAE,iBAAiB;AAAA,YAAG,OAAO,OAAO,KAAK;AAAA,YAAG,UAAU,CAAC,WAAW,SAAS,OAAO,MAAM;AAAA,YAAI,GAAG;AAAA,YACtG,OAAO,QAAQ,KAAK;AAAA,UAAA;AAAA,QAAA;AAAA,MAAG,GACxC;AAAA,IAER;AAAA,IACA,KAAK,kBAAkB,WAAW;AAC9B,YAAM,CAAC,SAAS,KAAK,IAAI,aAAa,KAAK;AAE3C,YAAM,SAAS,kBAAkB,OAAO,QAAQ,KAAK;AAErD,aACI,qBAAC,eAAY,OAAO,MAAM,OAAO,SAAS,GAAG,OAAO,SAAS,iBACzD,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YAAsB,MAAM;AAAA,YAAS,OAAO,EAAE,mBAAmB;AAAA,YAAG,OAAO,OAAO,OAAO;AAAA,YAAG,UAAU,CAAC,SAAS,SAAS,SAAS,IAAI;AAAA,YAChH,OAAO,QAAQ,OAAO;AAAA,YAAG,YAAW;AAAA,YAAI,GAAG,OAAO;AAAA,UAAA;AAAA,QAAA;AAAA,QACzE;AAAA,UAAC;AAAA,UAAA;AAAA,YAAsB,MAAM;AAAA,YAAO,OAAO,EAAE,iBAAiB;AAAA,YAAG,OAAO,OAAO,KAAK;AAAA,YAAG,UAAU,CAAC,SAAS,SAAS,OAAO,IAAI;AAAA,YACxG,OAAO,QAAQ,KAAK;AAAA,YAAI,GAAG,OAAO;AAAA,UAAA;AAAA,QAAA;AAAA,MAAI,GACjE;AAAA,IAER;AAAA,IACA,KAAK,kBAAkB,YAAY;AAC/B,YAAM,QAAQ,aAAa,OAAO,GAAG,CAAC;AACtC,YAAM,YAAY,MAAM,aAAa,OAAO,OAAO,mBAAmB;AACtE,YAAM,WAAW,OAAO,YAAY,MAAM,mBAAmB,UAAU,CAAC;AACxE,aACI,qBAAC,aAAA,EAAY,OAAO,MAAM,OAAO,SAAS,GAAG,OAAO,UAAU,UAAS,mCAAkC,iBACrG,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YAAY,OAAO,EAAE,uBAAuB;AAAA,YAAG,OAAO;AAAA,YAAU,YAAW;AAAA,YAC/D,SAAS,UAAU,IAAI,CAAC,YAAY,EAAC,OAAO,QAAQ,OAAO,EAAE,0BAA0B,MAAM,EAAE,IAAG;AAAA,YAClG,UAAU,CAAC,WAAW,SAAS,KAAK,iBAAiB,QAAQ,OAAO,SAAS,EAAE,CAAC;AAAA,UAAA;AAAA,QAAA;AAAA,QAC7F;AAAA,UAAC;AAAA,UAAA;AAAA,YAAY,OAAO,EAAE,oBAAoB;AAAA,YAAG,OAAO,OAAO,SAAS;AAAA,YAAI,UAAU,MAAM,YAAY;AAAA,YAAG,QAAQ,MAAM;AAAA,YAAQ,QAAQ,MAAM;AAAA,YAC9H,YAAW;AAAA,YAAG,UAAU,CAAC,WAAW,SAAS,KAAK,iBAAiB,UAAU,MAAM,CAAC;AAAA,UAAA;AAAA,QAAA;AAAA,MAAG,GACxG;AAAA,IAER;AAAA,IACA,KAAK,kBAAkB,QAAQ;AAG3B,YAAM,OAAO,aAAa,KAAK;AAC/B,YAAM,SAAS,OAAO,YAAY,KAAK,IAAI,CAAC,aAAa,CAAC,UAAU,QAAQ,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,IAAI,MAAM,IAAI,CAAC;AAClH,aAAO,MAAM,OAAO,EAAC,OAAO,OAAO,GAAG,GAAG,UAAU,CAAC,SAAS,SAAS,KAAK,IAAI,GAAG,QAAQ,UAAU,MAAM,QAAO;AAAA,IACrH;AAAA,IACA;AACI,aAAO;AAAA,EAAA;AAEnB;AAoBA,SAAS,oBAAoB,OAAO;AAChC,QAAM;AAAA,IACF;AAAA,IAAM;AAAA,IAAS;AAAA,IAAQ;AAAA,IAAQ;AAAA,IAAS,SAAS,qBAAqB;AAAA,IAAO,QAAQ;AAAA,IAAe;AAAA,IAAO;AAAA,IAC3G,eAAe;AAAA,IAAM;AAAA,IAAiB;AAAA,IAAU;AAAA,IAChD,GAAG;AAAA,EAAA,IACH,cAAc,OAAO,UAAU;AACnC,mBAAiB,YAAY,YAAY;AACzC,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,EAAC,SAAA,IAAY,mBAAA;AACnB,QAAM,EAAC,WAAA,IAAc,cAAA;AACrB,QAAM,UAAU,MAAA;AAChB,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,MAAM,eAAe,QAAQ,MAAM,CAAC;AAEvE,YAAU,MAAM;AACZ,QAAI,MAAM;AACN,eAAS,eAAe,QAAQ,MAAM,CAAC;AAAA,IAC3C;AAAA,EAEJ,GAAG,CAAC,IAAI,CAAC;AAET,QAAM,QAAQ,YAAY,QAAQ;AAClC,QAAM,WAAW,iBAAiB,QAAQ,OAAO,EAAC,OAAM;AACxD,QAAM,cAAc,OAAO,KAAK,QAAQ,EAAE,SAAS;AAEnD,QAAM,UAAU,CAAC,QAAS,SAAS,GAAG,IAAI,EAAE,gBAAgB,SAAS,GAAG,EAAE,OAAO,IAAI,EAAC,MAAM,WAAW,SAAS,GAAG,EAAE,IAAI,EAAA,CAAE,IAAI;AAC/H,QAAM,WAAW,CAAC,KAAK,UAAU,SAAS,CAAC,aAAa,EAAC,GAAG,SAAS,CAAC,GAAG,GAAG,QAAO;AACnF,QAAM,QAAQ,MAAM;AAChB,YAAQ,KAAK;AACb,QAAI,cAAc;AACd,cAAA;AAAA,IACJ;AAAA,EACJ;AAEA;AAAA;AAAA;AAAA,IAGI;AAAA,MAAC;AAAA,MAAA;AAAA,QACG;AAAA,QACA;AAAA,QACA;AAAA,QACA,IAAI,EAAC,QAAQ,CAAC,UAAU,MAAM,OAAO,MAAA;AAAA,QACrC,WAAW,EAAC,OAAO,EAAC,mBAAmB,SAAS,WAAW,GAAG,UAAU,UAAU,IAAI,EAAC,OAAO,EAAC,IAAI,QAAQ,IAAI,SAAQ,UAAU,OAAA,IAAO;AAAA,QAExI,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YAAI,WAAU;AAAA,YAAO,YAAU;AAAA,YAC3B,IAAI,EAAC,SAAS,QAAQ,eAAe,UAAU,QAAQ,QAAQ,GAAI,mBAAmB,EAAC,eAAe,eAAe,eAAe,wBAAqB;AAAA,YACzJ,UAAU,CAAC,UAAU;AACjB,oBAAM,eAAA;AACN,kBAAI,CAAC,aAAa;AACd,sBAAA;AAAA,cACJ;AAAA,YACJ;AAAA,YACD,UAAA;AAAA,cAAA,qBAAC,sBAAmB,WAAU,OAAM,OAAO,EAAC,YAAY,UAAU,KAAK,GAAG,IAAI,GAAG,IAAI,KAAK,cAAc,GAAG,aAAa,aACpH,UAAA;AAAA,gBAAA,oBAAC,YAAA,EAAW,cAAY,EAAE,oBAAoB,GAAG,SAAS,SAAS,MAAK,SAAQ,UAAA,oBAAC,kBAAA,CAAA,CAAiB,GAAE;AAAA,oCACnG,YAAA,EAAW,IAAI,SAAS,SAAQ,MAAK,WAAU,MAAK,IAAI,EAAC,UAAU,EAAA,GAAK,UAAA,SAAS,EAAE,oBAAoB,EAAA,CAAE;AAAA,cAAA,GAC9G;AAAA,cACA,qBAAC,kBAAA,EAAiB,SAAS,GAAG,OAAO,EAAC,MAAM,GAAG,WAAW,QAAQ,IAAI,KAAK,IAAI,KAC1E,UAAA;AAAA,gBAAA,OAAO,IAAI,CAAC,UACT;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBAA6B;AAAA,oBAAc,QAAQ;AAAA,oBAAO;AAAA,oBAAoB;AAAA,oBAAkB;AAAA,oBACpF;AAAA,oBAAkC;AAAA,kBAAA;AAAA,kBAD7B,MAAM;AAAA,gBAAA,CAE3B;AAAA,gBACA;AAAA,cAAA,GACL;AAAA,mCACC,oBAAA,EAAmB,WAAU,OAAM,OAAO,EAAC,KAAK,GAAG,gBAAgB,YAAY,IAAI,KAAK,IAAI,KAAK,WAAW,GAAG,aAAa,aACzH,UAAA;AAAA,gBAAA,oBAAC,QAAA,EAAO,SAAS,MAAM,SAAS,oBAAoB,MAAM,CAAC,GAAI,UAAA,EAAE,oBAAoB,EAAA,CAAE;AAAA,gBACvF,oBAAC,QAAA,EAAO,MAAK,UAAS,SAAQ,aAAY,UAAU,aAAc,UAAA,cAAc,EAAE,oBAAoB,EAAA,CAAE;AAAA,cAAA,EAAA,CAC5G;AAAA,YAAA;AAAA,UAAA;AAAA,QAAA;AAAA,MACJ;AAAA,IAAA;AAAA;AAGZ;AAGA,MAAM,eAAe,eAAe,qBAAqB,UAAU;"}
|
|
@@ -9,6 +9,7 @@ const FILTER_FIELD_KIND = Object.freeze({
|
|
|
9
9
|
comparison: "comparison",
|
|
10
10
|
custom: "custom"
|
|
11
11
|
});
|
|
12
|
+
const FILTER_FIELD_PROBLEM = Object.freeze({ rangeBackwards: "rangeBackwards", beforeMinDate: "dateBeforeMin", afterMaxDate: "dateAfterMax" });
|
|
12
13
|
const COMPARISON_OPERATOR = Object.freeze({ equals: "eq", greaterThan: "gt", greaterOrEqual: "gte", lessThan: "lt", lessOrEqual: "lte" });
|
|
13
14
|
const OPERATOR_SYMBOLS = Object.freeze({ eq: "=", gt: ">", gte: "≥", lt: "<", lte: "≤" });
|
|
14
15
|
const COMPARISON_SEPARATOR = ":";
|
|
@@ -16,6 +17,12 @@ const LIST_KINDS = /* @__PURE__ */ new Set([FILTER_FIELD_KIND.multiSelect, FILTE
|
|
|
16
17
|
function isListField(field) {
|
|
17
18
|
return LIST_KINDS.has(field.kind) || field.kind === FILTER_FIELD_KIND.catalog && Boolean(field.multiple);
|
|
18
19
|
}
|
|
20
|
+
function isCustomRange(field) {
|
|
21
|
+
return field.kind === FILTER_FIELD_KIND.custom && Boolean(field.keys?.from) && Boolean(field.keys?.to);
|
|
22
|
+
}
|
|
23
|
+
function isRangeField(field) {
|
|
24
|
+
return field.kind === FILTER_FIELD_KIND.numberRange || field.kind === FILTER_FIELD_KIND.dateRange || isCustomRange(field);
|
|
25
|
+
}
|
|
19
26
|
function filterKeysOf(field) {
|
|
20
27
|
if (field.kind === FILTER_FIELD_KIND.numberRange) {
|
|
21
28
|
return [field.keys?.from ?? `${field.name}_min`, field.keys?.to ?? `${field.name}_max`];
|
|
@@ -23,6 +30,9 @@ function filterKeysOf(field) {
|
|
|
23
30
|
if (field.kind === FILTER_FIELD_KIND.dateRange) {
|
|
24
31
|
return [field.keys?.from ?? `${field.name}_from`, field.keys?.to ?? `${field.name}_to`];
|
|
25
32
|
}
|
|
33
|
+
if (isCustomRange(field)) {
|
|
34
|
+
return [field.keys.from, field.keys.to];
|
|
35
|
+
}
|
|
26
36
|
return [field.name];
|
|
27
37
|
}
|
|
28
38
|
function comparisonOf(text) {
|
|
@@ -51,25 +61,61 @@ function filterDrawerDefaultsOf(fields) {
|
|
|
51
61
|
function activeDrawerFilterCountOf(fields, filters) {
|
|
52
62
|
return fields.filter((field) => filterKeysOf(field).some((key) => isFilterValueSet(filters?.[key]))).length;
|
|
53
63
|
}
|
|
64
|
+
function isBackwardsRange(field, from, to) {
|
|
65
|
+
if (field.kind === FILTER_FIELD_KIND.numberRange) {
|
|
66
|
+
return Number(to) < Number(from);
|
|
67
|
+
}
|
|
68
|
+
if (field.kind === FILTER_FIELD_KIND.dateRange) {
|
|
69
|
+
return String(to) < String(from);
|
|
70
|
+
}
|
|
71
|
+
return Boolean(field.isBackwards?.(from, to));
|
|
72
|
+
}
|
|
54
73
|
function invalidRangesOf(fields, values) {
|
|
55
74
|
const invalid = {};
|
|
56
|
-
for (const field of fields) {
|
|
57
|
-
if (field.kind !== FILTER_FIELD_KIND.numberRange && field.kind !== FILTER_FIELD_KIND.dateRange) {
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
75
|
+
for (const field of fields.filter(isRangeField)) {
|
|
60
76
|
const [fromKey, toKey] = filterKeysOf(field);
|
|
61
77
|
const from = values?.[fromKey];
|
|
62
78
|
const to = values?.[toKey];
|
|
63
|
-
if (
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
const isBackwards = field.kind === FILTER_FIELD_KIND.numberRange ? Number(to) < Number(from) : String(to) < String(from);
|
|
67
|
-
if (isBackwards) {
|
|
79
|
+
if (isFilterValueSet(from) && isFilterValueSet(to) && isBackwardsRange(field, from, to)) {
|
|
68
80
|
invalid[toKey] = true;
|
|
69
81
|
}
|
|
70
82
|
}
|
|
71
83
|
return invalid;
|
|
72
84
|
}
|
|
85
|
+
const earliestDateOf = (...dates) => dates.filter(Boolean).sort()[0];
|
|
86
|
+
const latestDateOf = (...dates) => dates.filter(Boolean).sort().at(-1);
|
|
87
|
+
function dateLimitsOf(field, today) {
|
|
88
|
+
return {
|
|
89
|
+
minDate: latestDateOf(field.minDate, field.disablePast && today),
|
|
90
|
+
maxDate: earliestDateOf(field.maxDate, field.disableFuture && today)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function dateRangeBoundsOf(field, values, today) {
|
|
94
|
+
const [fromKey, toKey] = filterKeysOf(field);
|
|
95
|
+
const { minDate, maxDate } = dateLimitsOf(field, today);
|
|
96
|
+
return {
|
|
97
|
+
from: { minDate, maxDate: earliestDateOf(maxDate, values?.[toKey] || void 0) },
|
|
98
|
+
to: { minDate: latestDateOf(minDate, values?.[fromKey] || void 0), maxDate }
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function drawerProblemsOf(fields, values, { today } = {}) {
|
|
102
|
+
const problems = Object.fromEntries(Object.keys(invalidRangesOf(fields, values)).map((key) => [key, { problem: FILTER_FIELD_PROBLEM.rangeBackwards }]));
|
|
103
|
+
for (const field of fields.filter((candidate) => candidate.kind === FILTER_FIELD_KIND.dateRange)) {
|
|
104
|
+
const { minDate, maxDate } = dateLimitsOf(field, today);
|
|
105
|
+
for (const key of filterKeysOf(field)) {
|
|
106
|
+
const date = values?.[key];
|
|
107
|
+
if (!isFilterValueSet(date)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (minDate && String(date) < minDate) {
|
|
111
|
+
problems[key] = { problem: FILTER_FIELD_PROBLEM.beforeMinDate, date: minDate };
|
|
112
|
+
} else if (maxDate && String(date) > maxDate) {
|
|
113
|
+
problems[key] = { problem: FILTER_FIELD_PROBLEM.afterMaxDate, date: maxDate };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return problems;
|
|
118
|
+
}
|
|
73
119
|
function filterDrawerChipsOf(fields, { t, formatDate = (date) => date }) {
|
|
74
120
|
const text = [];
|
|
75
121
|
const multiSelect = [];
|
|
@@ -78,11 +124,11 @@ function filterDrawerChipsOf(fields, { t, formatDate = (date) => date }) {
|
|
|
78
124
|
const withUnit = (value) => `${field.prefix ?? ""}${value}${field.suffix ? ` ${field.suffix}` : ""}`;
|
|
79
125
|
if (isListField(field)) {
|
|
80
126
|
multiSelect.push({ key: field.name, label: field.label, itemLabel: (value) => optionLabelOf(field, value) });
|
|
81
|
-
} else if (field
|
|
127
|
+
} else if (isRangeField(field)) {
|
|
82
128
|
const [fromKey, toKey] = filterKeysOf(field);
|
|
83
|
-
const format = field.kind === FILTER_FIELD_KIND.dateRange ? formatDate : withUnit;
|
|
84
|
-
text.push({ key: fromKey, label: t("filterDrawer.chipFrom", { label: field.label }), format });
|
|
85
|
-
text.push({ key: toKey, label: t("filterDrawer.chipTo", { label: field.label }), format });
|
|
129
|
+
const format = field.kind === FILTER_FIELD_KIND.dateRange ? formatDate : isCustomRange(field) ? field.chipFormat : withUnit;
|
|
130
|
+
text.push({ key: fromKey, label: t("filterDrawer.chipFrom", { label: field.label }), ...format && { format } });
|
|
131
|
+
text.push({ key: toKey, label: t("filterDrawer.chipTo", { label: field.label }), ...format && { format } });
|
|
86
132
|
} else if (field.kind === FILTER_FIELD_KIND.comparison) {
|
|
87
133
|
text.push({
|
|
88
134
|
key: field.name,
|
|
@@ -103,9 +149,13 @@ function filterDrawerChipsOf(fields, { t, formatDate = (date) => date }) {
|
|
|
103
149
|
export {
|
|
104
150
|
COMPARISON_OPERATOR,
|
|
105
151
|
FILTER_FIELD_KIND,
|
|
152
|
+
FILTER_FIELD_PROBLEM,
|
|
106
153
|
activeDrawerFilterCountOf,
|
|
107
154
|
comparisonOf,
|
|
108
155
|
comparisonTextOf,
|
|
156
|
+
dateLimitsOf,
|
|
157
|
+
dateRangeBoundsOf,
|
|
158
|
+
drawerProblemsOf,
|
|
109
159
|
drawerValuesOf,
|
|
110
160
|
emptyDrawerValuesOf,
|
|
111
161
|
filterDrawerChipsOf,
|
|
@@ -114,6 +164,7 @@ export {
|
|
|
114
164
|
filterKeysOf,
|
|
115
165
|
invalidRangesOf,
|
|
116
166
|
isFilterValueSet,
|
|
117
|
-
isListField
|
|
167
|
+
isListField,
|
|
168
|
+
isRangeField
|
|
118
169
|
};
|
|
119
170
|
//# sourceMappingURL=filterDrawerFields.js.map
|