@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.
Files changed (37) hide show
  1. package/README.md +83 -4
  2. package/dist/DataTable/ServerTableFilterDrawer.js +3 -1
  3. package/dist/DataTable/ServerTableFilterDrawer.js.map +1 -1
  4. package/dist/locales/en.js +15 -0
  5. package/dist/locales/en.js.map +1 -1
  6. package/dist/locales/es.js +15 -0
  7. package/dist/locales/es.js.map +1 -1
  8. package/dist/sortable/FormSortableField.js +27 -0
  9. package/dist/sortable/FormSortableField.js.map +1 -0
  10. package/dist/sortable/SortableTable.js +230 -0
  11. package/dist/sortable/SortableTable.js.map +1 -0
  12. package/dist/sortable/sortableRows.js +14 -0
  13. package/dist/sortable/sortableRows.js.map +1 -0
  14. package/dist/sortable.js +9 -0
  15. package/dist/sortable.js.map +1 -0
  16. package/dist/theme/components/minimalComponents.js +15 -0
  17. package/dist/theme/components/minimalComponents.js.map +1 -1
  18. package/dist/theme/glassSurface.js +10 -12
  19. package/dist/theme/glassSurface.js.map +1 -1
  20. package/dist/theme/themePieces.js +10 -4
  21. package/dist/theme/themePieces.js.map +1 -1
  22. package/dist/types/DataTable/ServerTableFilterDrawer.d.ts +2 -0
  23. package/dist/types/downloads/fileTypes.d.ts +4 -4
  24. package/dist/types/forms/SubmitButton.d.ts +1 -1
  25. package/dist/types/locales/en.d.ts +15 -0
  26. package/dist/types/locales/es.d.ts +15 -0
  27. package/dist/types/sortable/FormSortableField.d.ts +15 -0
  28. package/dist/types/sortable/SortableTable.d.ts +77 -0
  29. package/dist/types/sortable/sortableRows.d.ts +12 -0
  30. package/dist/types/sortable.d.ts +3 -0
  31. package/dist/types/ui/filters/FilterDrawer.d.ts +1 -0
  32. package/dist/types/ui/filters/filterDrawerFields.d.ts +77 -2
  33. package/dist/ui/filters/FilterDrawer.js +118 -53
  34. package/dist/ui/filters/FilterDrawer.js.map +1 -1
  35. package/dist/ui/filters/filterDrawerFields.js +65 -14
  36. package/dist/ui/filters/filterDrawerFields.js.map +1 -1
  37. 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
- * Los nombres con que viaja un campo en los filtros: uno, o dos para un rango.
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, invalidRangesOf, emptyDrawerValuesOf, filterKeysOf, FILTER_FIELD_KIND, comparisonOf, COMPARISON_OPERATOR, comparisonTextOf } from "./filterDrawerFields.js";
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
- function PairedField({ label, inputId, children, template = "1fr 1fr" }) {
29
- return /* @__PURE__ */ jsx(FieldFrame, { inputId, label, children: /* @__PURE__ */ jsx(Box, { sx: { display: "grid", gridTemplateColumns: template, gap: 1.5, alignItems: "start" }, children }) });
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, invalidRanges, t }) {
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: invalidRanges[toKey] ? t("filterDrawer.rangeBackwards") : void 0
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
- return /* @__PURE__ */ jsxs(PairedField, { label: field.label, inputId: `${fieldId}-from`, children: [
86
- /* @__PURE__ */ jsx(TextInput, { type: "date", label: t("filterDrawer.from"), value: values[fromKey], onChange: (date) => setValue(fromKey, date), clearable: false, helperText: "" }),
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
- TextInput,
128
+ FilterDrawerDateInput,
89
129
  {
90
- type: "date",
130
+ name: toKey,
91
131
  label: t("filterDrawer.to"),
92
132
  value: values[toKey],
93
133
  onChange: (date) => setValue(toKey, date),
94
- clearable: false,
95
- error: invalidRanges[toKey] ? t("filterDrawer.rangeBackwards") : void 0
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
- return field.render({ value: values[key], onChange: (next) => setValue(key, next), values, setValue });
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 invalidRanges = invalidRangesOf(fields, draft);
161
- const hasInvalidRange = Object.keys(invalidRanges).length > 0;
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 /* @__PURE__ */ jsx(
170
- Drawer,
171
- {
172
- anchor,
173
- open,
174
- onClose,
175
- slotProps: { paper: { "aria-labelledby": titleId, className: `${THEME_NAME}-paper`, sx: { width: { xs: "100%", sm: width }, maxWidth: "100%" } } },
176
- children: /* @__PURE__ */ jsxs(
177
- Box,
178
- {
179
- component: "form",
180
- noValidate: true,
181
- sx: { display: "flex", flexDirection: "column", height: "100%" },
182
- onSubmit: (event) => {
183
- event.preventDefault();
184
- if (!hasInvalidRange) {
185
- apply();
186
- }
187
- },
188
- children: [
189
- /* @__PURE__ */ jsxs(FilterDrawerHeader, { direction: "row", ownSx: { alignItems: "center", gap: 1, px: 2, py: 1.5, borderBottom: 1, borderColor: "divider" }, children: [
190
- /* @__PURE__ */ jsx(IconButton, { "aria-label": t("filterDrawer.close"), onClick: onClose, edge: "start", children: /* @__PURE__ */ jsx(CloseRoundedIcon, {}) }),
191
- /* @__PURE__ */ jsx(Typography, { id: titleId, variant: "h6", component: "h2", sx: { flexGrow: 1 }, children: title ?? t("filterDrawer.title") })
192
- ] }),
193
- /* @__PURE__ */ jsxs(FilterDrawerBody, { spacing: 1, ownSx: { flex: 1, overflowY: "auto", px: 2.5, py: 2 }, children: [
194
- fields.map((field) => /* @__PURE__ */ jsx(DrawerField, { field, values: draft, setValue, invalidRanges, t }, field.name)),
195
- children
196
- ] }),
197
- /* @__PURE__ */ jsxs(FilterDrawerFooter, { direction: "row", ownSx: { gap: 1, justifyContent: "flex-end", px: 2.5, py: 1.5, borderTop: 1, borderColor: "divider" }, children: [
198
- /* @__PURE__ */ jsx(Button, { onClick: () => setDraft(emptyDrawerValuesOf(fields)), children: t("filterDrawer.clear") }),
199
- /* @__PURE__ */ jsx(Button, { type: "submit", variant: "contained", disabled: hasInvalidRange, children: applyLabel ?? t("filterDrawer.apply") })
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 (!isFilterValueSet(from) || !isFilterValueSet(to)) {
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.kind === FILTER_FIELD_KIND.numberRange || field.kind === FILTER_FIELD_KIND.dateRange) {
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