@akinon/akifilter 1.0.5 → 1.1.1-rc.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/dist/cjs/akifilter.d.ts.map +1 -1
- package/dist/cjs/akifilter.js +58 -30
- package/dist/cjs/styles.css +30 -0
- package/dist/cjs/utils/schema.d.ts +14 -0
- package/dist/cjs/utils/schema.d.ts.map +1 -1
- package/dist/cjs/utils/schema.js +35 -1
- package/dist/esm/akifilter.d.ts.map +1 -1
- package/dist/esm/akifilter.js +59 -31
- package/dist/esm/styles.css +30 -0
- package/dist/esm/utils/schema.d.ts +14 -0
- package/dist/esm/utils/schema.d.ts.map +1 -1
- package/dist/esm/utils/schema.js +32 -0
- package/package.json +17 -16
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"akifilter.d.ts","sourceRoot":"","sources":["../../src/akifilter.tsx"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AAGtB,OAAO,EAIL,WAAW,EAEX,IAAI,EAGL,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"akifilter.d.ts","sourceRoot":"","sources":["../../src/akifilter.tsx"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AAGtB,OAAO,EAIL,WAAW,EAEX,IAAI,EAGL,MAAM,iBAAiB,CAAC;AAWzB,OAAO,KAAK,MAAM,OAAO,CAAC;AA2B1B,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,SAAS,CAAC;AAY/D,KAAK,oBAAoB,GAAG,WAAW,CAAC;AA2BxC,MAAM,MAAM,cAAc,CACxB,YAAY,SAAS,oBAAoB,GAAG,oBAAoB,IAC9D;IACF;;OAEG;IACH,YAAY,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IAC7C;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACtC;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;IACzD;;OAEG;IACH,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,KAAK,IAAI,CAAC;IAClE;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAirBF,eAAO,MAAM,SAAS;KACpB,YAAY,SAAS,oBAAoB,uBAElC,cAAc,CAAC,YAAY,CAAC;;CAmBpC,CAAC"}
|
package/dist/cjs/akifilter.js
CHANGED
|
@@ -18,6 +18,7 @@ const akiform_1 = require("@akinon/akiform");
|
|
|
18
18
|
const ui_button_1 = require("@akinon/ui-button");
|
|
19
19
|
const ui_card_1 = require("@akinon/ui-card");
|
|
20
20
|
const ui_checkbox_1 = require("@akinon/ui-checkbox");
|
|
21
|
+
const ui_collapse_1 = require("@akinon/ui-collapse");
|
|
21
22
|
const ui_date_picker_1 = require("@akinon/ui-date-picker");
|
|
22
23
|
const ui_input_1 = require("@akinon/ui-input");
|
|
23
24
|
const ui_input_number_1 = require("@akinon/ui-input-number");
|
|
@@ -55,14 +56,18 @@ const resolveClearedFieldValue = (field, defaultValue) => {
|
|
|
55
56
|
}
|
|
56
57
|
};
|
|
57
58
|
const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onValuesChange, onVisibleFieldsChange, onImportCsv, onImportXls, onClearAll, enableImportCsv, enableImportXls }) => {
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
59
|
+
// Separate regular fields from section fields
|
|
60
|
+
const { regularFields, sectionFields } = react_1.default.useMemo(() => (0, schema_1.partitionSchema)(filterSchema), [filterSchema]);
|
|
61
|
+
// Flatten schema for storage and form value operations
|
|
62
|
+
const flattenedSchema = react_1.default.useMemo(() => (0, schema_1.flattenSchema)(filterSchema), [filterSchema]);
|
|
63
|
+
const storageKey = react_1.default.useMemo(() => (0, storage_1.buildStorageKey)(regularFields, storageNamespace), [regularFields, storageNamespace]);
|
|
64
|
+
const schemaDefaults = react_1.default.useMemo(() => (0, schema_1.normaliseValuesBySchema)(flattenedSchema, (0, schema_1.extractDefaultValues)(flattenedSchema)), [flattenedSchema]);
|
|
65
|
+
const externalDefaults = react_1.default.useMemo(() => (0, schema_1.normaliseValuesBySchema)(flattenedSchema, defaultValues), [flattenedSchema, defaultValues]);
|
|
61
66
|
const baseDefaultValues = react_1.default.useMemo(() => (Object.assign(Object.assign({}, schemaDefaults), externalDefaults)), [schemaDefaults, externalDefaults]);
|
|
62
67
|
const persistedDefaults = react_1.default.useMemo(() => {
|
|
63
68
|
var _a;
|
|
64
|
-
return (0, schema_1.normaliseValuesBySchema)(
|
|
65
|
-
}, [
|
|
69
|
+
return (0, schema_1.normaliseValuesBySchema)(flattenedSchema, (_a = (0, storage_1.readStoredValues)(regularFields, storageKey)) !== null && _a !== void 0 ? _a : undefined);
|
|
70
|
+
}, [flattenedSchema, regularFields, storageKey]);
|
|
66
71
|
const mergedDefaultValues = react_1.default.useMemo(() => (Object.assign(Object.assign({}, baseDefaultValues), persistedDefaults)), [baseDefaultValues, persistedDefaults]);
|
|
67
72
|
const formMethods = (0, akiform_1.useForm)({
|
|
68
73
|
defaultValues: mergedDefaultValues
|
|
@@ -76,7 +81,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
76
81
|
if (!formValues) {
|
|
77
82
|
return [];
|
|
78
83
|
}
|
|
79
|
-
return
|
|
84
|
+
return flattenedSchema.reduce((acc, field) => {
|
|
80
85
|
const key = String(field.key);
|
|
81
86
|
const currentValue = formValues[key];
|
|
82
87
|
if (currentValue === undefined || currentValue === null) {
|
|
@@ -132,24 +137,24 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
132
137
|
acc.push({ key, label, value: resolveValue() });
|
|
133
138
|
return acc;
|
|
134
139
|
}, []);
|
|
135
|
-
}, [
|
|
140
|
+
}, [flattenedSchema, formValues]);
|
|
136
141
|
const resolveInitialVisibleKeys = react_1.default.useCallback(() => {
|
|
137
|
-
const storedKeys = (0, storage_1.readVisibleKeys)(
|
|
142
|
+
const storedKeys = (0, storage_1.readVisibleKeys)(regularFields, storageKey);
|
|
138
143
|
if (storedKeys && storedKeys.length > 0) {
|
|
139
|
-
return (0, schema_1.ensureSchemaOrder)(
|
|
144
|
+
return (0, schema_1.ensureSchemaOrder)(regularFields, storedKeys);
|
|
140
145
|
}
|
|
141
|
-
return (0, schema_1.deriveDefaultVisibleKeys)(
|
|
142
|
-
}, [
|
|
146
|
+
return (0, schema_1.deriveDefaultVisibleKeys)(regularFields);
|
|
147
|
+
}, [regularFields, storageKey]);
|
|
143
148
|
const [visibleKeys, setVisibleKeys] = react_1.default.useState(resolveInitialVisibleKeys);
|
|
144
149
|
react_1.default.useEffect(() => {
|
|
145
150
|
setVisibleKeys(previous => {
|
|
146
|
-
const ordered = (0, schema_1.ensureSchemaOrder)(
|
|
151
|
+
const ordered = (0, schema_1.ensureSchemaOrder)(regularFields, previous);
|
|
147
152
|
if (ordered.length) {
|
|
148
153
|
return ordered;
|
|
149
154
|
}
|
|
150
155
|
return resolveInitialVisibleKeys();
|
|
151
156
|
});
|
|
152
|
-
}, [
|
|
157
|
+
}, [regularFields, resolveInitialVisibleKeys]);
|
|
153
158
|
react_1.default.useEffect(() => {
|
|
154
159
|
(0, storage_1.writeVisibleKeys)(storageKey, visibleKeys);
|
|
155
160
|
onVisibleFieldsChange === null || onVisibleFieldsChange === void 0 ? void 0 : onVisibleFieldsChange(visibleKeys);
|
|
@@ -157,12 +162,12 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
157
162
|
react_1.default.useEffect(() => {
|
|
158
163
|
formMethods.reset(mergedDefaultValues);
|
|
159
164
|
}, [formMethods, mergedDefaultValues]);
|
|
160
|
-
const normalisedValues = react_1.default.useMemo(() => (0, values_1.normaliseOutputValues)(
|
|
165
|
+
const normalisedValues = react_1.default.useMemo(() => (0, values_1.normaliseOutputValues)(flattenedSchema, formValues), [flattenedSchema, formValues]);
|
|
161
166
|
const hasInitialValuesRef = react_1.default.useRef(false);
|
|
162
167
|
react_1.default.useEffect(() => {
|
|
163
|
-
const initialValues = (0, values_1.normaliseOutputValues)(
|
|
168
|
+
const initialValues = (0, values_1.normaliseOutputValues)(flattenedSchema, mergedDefaultValues);
|
|
164
169
|
hasInitialValuesRef.current = Object.keys(initialValues).length > 0;
|
|
165
|
-
}, [
|
|
170
|
+
}, [flattenedSchema, mergedDefaultValues]);
|
|
166
171
|
const serialisedValues = react_1.default.useMemo(() => JSON.stringify(normalisedValues), [normalisedValues]);
|
|
167
172
|
const debouncedSerialisedValues = (0, use_debounced_value_1.useDebouncedValue)(serialisedValues, constants_1.FILTER_DEBOUNCE_DELAY);
|
|
168
173
|
const lastPersistedValuesRef = react_1.default.useRef(null);
|
|
@@ -214,7 +219,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
214
219
|
}, [storageKey]);
|
|
215
220
|
const handleClearAll = react_1.default.useCallback(() => {
|
|
216
221
|
const clearedDefaults = Object.assign({}, baseDefaultValues);
|
|
217
|
-
|
|
222
|
+
flattenedSchema.forEach(field => {
|
|
218
223
|
const key = String(field.key);
|
|
219
224
|
const defaultValue = baseDefaultValues[key];
|
|
220
225
|
const resolved = resolveClearedFieldValue(field, defaultValue);
|
|
@@ -230,13 +235,20 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
230
235
|
keepTouched: false
|
|
231
236
|
});
|
|
232
237
|
onClearAll === null || onClearAll === void 0 ? void 0 : onClearAll();
|
|
233
|
-
const nextValues = (0, values_1.normaliseOutputValues)(
|
|
238
|
+
const nextValues = (0, values_1.normaliseOutputValues)(flattenedSchema, clearedDefaults);
|
|
234
239
|
hasInitialValuesRef.current = Object.keys(nextValues).length > 0;
|
|
235
240
|
const nextSerialised = persistValues(nextValues);
|
|
236
241
|
hasEmittedValuesRef.current = true;
|
|
237
242
|
lastPersistedValuesRef.current = nextSerialised !== null && nextSerialised !== void 0 ? nextSerialised : null;
|
|
238
243
|
onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(nextValues);
|
|
239
|
-
}, [
|
|
244
|
+
}, [
|
|
245
|
+
baseDefaultValues,
|
|
246
|
+
flattenedSchema,
|
|
247
|
+
formMethods,
|
|
248
|
+
onClearAll,
|
|
249
|
+
onValuesChange,
|
|
250
|
+
persistValues
|
|
251
|
+
]);
|
|
240
252
|
const handleOpenModal = react_1.default.useCallback(() => {
|
|
241
253
|
setIsModalOpen(true);
|
|
242
254
|
setSearchTerm('');
|
|
@@ -254,11 +266,11 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
254
266
|
const nextKeys = exists
|
|
255
267
|
? prev.filter(item => item !== key)
|
|
256
268
|
: [...prev, key];
|
|
257
|
-
return (0, schema_1.ensureSchemaOrder)(
|
|
269
|
+
return (0, schema_1.ensureSchemaOrder)(regularFields, nextKeys);
|
|
258
270
|
});
|
|
259
|
-
}, [
|
|
271
|
+
}, [regularFields]);
|
|
260
272
|
const handleRemoveFilter = react_1.default.useCallback((key) => {
|
|
261
|
-
const schemaField =
|
|
273
|
+
const schemaField = flattenedSchema.find(field => String(field.key) === key);
|
|
262
274
|
if (!schemaField) {
|
|
263
275
|
return;
|
|
264
276
|
}
|
|
@@ -274,7 +286,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
274
286
|
// Compute the updated values immediately (don't wait for debounce)
|
|
275
287
|
const currentValues = formMethods.getValues() || {};
|
|
276
288
|
const updatedFormValues = Object.assign(Object.assign({}, currentValues), { [fieldPath]: nextValue });
|
|
277
|
-
const nextValues = (0, values_1.normaliseOutputValues)(
|
|
289
|
+
const nextValues = (0, values_1.normaliseOutputValues)(flattenedSchema, updatedFormValues);
|
|
278
290
|
const nextSerialised = JSON.stringify(nextValues);
|
|
279
291
|
// Persist immediately (bypass debounce for remove action)
|
|
280
292
|
persistValues(nextValues);
|
|
@@ -287,7 +299,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
287
299
|
onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(nextValues);
|
|
288
300
|
}, [
|
|
289
301
|
baseDefaultValues,
|
|
290
|
-
|
|
302
|
+
flattenedSchema,
|
|
291
303
|
formMethods,
|
|
292
304
|
onValuesChange,
|
|
293
305
|
persistValues
|
|
@@ -295,20 +307,20 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
295
307
|
const filteredFields = react_1.default.useMemo(() => {
|
|
296
308
|
const normalisedTerm = searchTerm.trim().toLowerCase();
|
|
297
309
|
if (!normalisedTerm) {
|
|
298
|
-
return
|
|
310
|
+
return regularFields;
|
|
299
311
|
}
|
|
300
|
-
return
|
|
312
|
+
return regularFields.filter(field => {
|
|
301
313
|
const searchable = [field.label, field.placeholder, String(field.key)]
|
|
302
314
|
.filter(Boolean)
|
|
303
315
|
.map(value => String(value).toLowerCase());
|
|
304
316
|
return searchable.some(text => text.includes(normalisedTerm));
|
|
305
317
|
});
|
|
306
|
-
}, [
|
|
318
|
+
}, [regularFields, searchTerm]);
|
|
307
319
|
const paginatedFields = react_1.default.useMemo(() => {
|
|
308
320
|
const start = (modalPage - 1) * modalPageSize;
|
|
309
321
|
return filteredFields.slice(start, start + modalPageSize);
|
|
310
322
|
}, [filteredFields, modalPage, modalPageSize]);
|
|
311
|
-
const visibleFields = react_1.default.useMemo(() =>
|
|
323
|
+
const visibleFields = react_1.default.useMemo(() => regularFields.filter(field => visibleKeys.includes(String(field.key))), [regularFields, visibleKeys]);
|
|
312
324
|
const renderFieldComponent = (field) => {
|
|
313
325
|
const ariaLabel = (0, schema_1.getFieldAriaLabel)(field);
|
|
314
326
|
switch (field.type) {
|
|
@@ -335,10 +347,17 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
335
347
|
});
|
|
336
348
|
}
|
|
337
349
|
return (react_1.default.createElement(ui_typography_1.Text, { type: "secondary", className: "akinon-filter__unsupported" }, i18n_1.i18n.t('form.unsupportedField', { field: String(field.type) })));
|
|
350
|
+
case 'section':
|
|
351
|
+
// Section fields should not be rendered inline
|
|
352
|
+
// They are rendered separately after the main grid
|
|
353
|
+
return null;
|
|
338
354
|
default:
|
|
339
355
|
return (react_1.default.createElement(ui_typography_1.Text, { type: "secondary", className: "akinon-filter__unsupported" }, i18n_1.i18n.t('form.unsupportedField', { field: String(field.type) })));
|
|
340
356
|
}
|
|
341
357
|
};
|
|
358
|
+
const renderFormField = (field) => {
|
|
359
|
+
return (react_1.default.createElement(akiform_1.FormItem, { key: String(field.key), control: formMethods.control, name: field.key, tooltip: field.tooltip, help: field.help, labelDescription: field.labelDescription, required: Boolean(field.validation), className: "mb-0", valuePropName: field.type === 'checkbox' ? 'checked' : undefined }, renderFieldComponent(field)));
|
|
360
|
+
};
|
|
342
361
|
return (react_1.default.createElement(ui_card_1.Card, { size: "small", className: "akinon-filter shadow", "data-testid": "akifilter-root" },
|
|
343
362
|
react_1.default.createElement(antd_1.ConfigProvider, { theme: theme_overrides_1.themeOverrides },
|
|
344
363
|
react_1.default.createElement(filter_toolbar_1.FilterToolbar, { onOpenModal: handleOpenModal, enableImportCsv: enableImportCsv, enableImportXls: enableImportXls, onImportCsv: onImportCsv, onImportXls: onImportXls }),
|
|
@@ -346,8 +365,17 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
346
365
|
react_1.default.createElement("div", { className: "akinon-filter__body" },
|
|
347
366
|
react_1.default.createElement(akiform_1.Akiform, { layout: "vertical", className: "akinon-filter__form" },
|
|
348
367
|
react_1.default.createElement("div", { className: "akinon-filter__form-grid", "data-testid": "akifilter-form-grid" },
|
|
349
|
-
visibleFields.map(
|
|
350
|
-
visibleFields.length === 0 ? (react_1.default.createElement("div", { className: "akinon-filter__empty" }, i18n_1.i18n.t('form.noVisibleFields'))) : null)
|
|
368
|
+
visibleFields.map(renderFormField),
|
|
369
|
+
visibleFields.length === 0 ? (react_1.default.createElement("div", { className: "akinon-filter__empty" }, i18n_1.i18n.t('form.noVisibleFields'))) : null),
|
|
370
|
+
sectionFields.length > 0 && (react_1.default.createElement("div", { className: "akinon-filter__section-fields" }, sectionFields.map(section => {
|
|
371
|
+
return (react_1.default.createElement(ui_collapse_1.Collapse, { key: section.key, defaultActiveKey: section.key, ghost: true, items: [
|
|
372
|
+
{
|
|
373
|
+
key: section.key,
|
|
374
|
+
label: section.label,
|
|
375
|
+
children: (react_1.default.createElement("div", { className: "akinon-filter__form-grid" }, section.fields.map(renderFormField)))
|
|
376
|
+
}
|
|
377
|
+
] }));
|
|
378
|
+
}))))),
|
|
351
379
|
react_1.default.createElement(visibility_modal_1.VisibilityModal, { open: isModalOpen, onClose: handleCloseModal, searchTerm: searchTerm, paginatedFields: paginatedFields, filteredCount: filteredFields.length, visibleKeys: visibleKeys, onSearchTermChange: value => {
|
|
352
380
|
setSearchTerm(value);
|
|
353
381
|
setModalPage(1);
|
package/dist/cjs/styles.css
CHANGED
|
@@ -51,6 +51,15 @@
|
|
|
51
51
|
top: 1px;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
.akinon-filter__common-filters {
|
|
55
|
+
margin-top: 2rem;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.akinon-filter__common-filters-title {
|
|
59
|
+
color: var(--color-gray-500);
|
|
60
|
+
margin-bottom: 1rem;
|
|
61
|
+
}
|
|
62
|
+
|
|
54
63
|
.akinon-filter__form-grid {
|
|
55
64
|
display: grid;
|
|
56
65
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
@@ -65,6 +74,27 @@
|
|
|
65
74
|
width: 100%;
|
|
66
75
|
}
|
|
67
76
|
|
|
77
|
+
.akinon-filter__section-fields {
|
|
78
|
+
margin-top: 2rem;
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
gap: 1rem;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.akinon-filter__section-fields .akinon-collapse-header {
|
|
85
|
+
padding-bottom: 0 !important;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.akinon-filter__section-fields .akinon-collapse-header-text {
|
|
89
|
+
color: var(--color-gray-500);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.akinon-filter__section-fields .akinon-collapse-expand-icon .akinon-collapse-arrow {
|
|
93
|
+
font-size: 1rem !important;
|
|
94
|
+
color: var(--color-gray-500) !important;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
68
98
|
@media (max-width: 1024px) {
|
|
69
99
|
.akinon-filter__form-grid {
|
|
70
100
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import type { FieldValues } from '@akinon/akiform';
|
|
2
2
|
import type { AkifilterField, AkifilterSchema } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Flattens schema by extracting all nested fields from section fields.
|
|
5
|
+
* This is used for storage operations and form value management.
|
|
6
|
+
*/
|
|
7
|
+
export declare const flattenSchema: <TFieldValues extends FieldValues = FieldValues>(schema: AkifilterSchema<TFieldValues>) => AkifilterSchema<TFieldValues>;
|
|
8
|
+
/**
|
|
9
|
+
* Separates section fields from regular fields.
|
|
10
|
+
*/
|
|
11
|
+
export declare const partitionSchema: <TFieldValues extends FieldValues = FieldValues>(schema: AkifilterSchema<TFieldValues>) => {
|
|
12
|
+
regularFields: AkifilterSchema<TFieldValues>;
|
|
13
|
+
sectionFields: Array<AkifilterField<TFieldValues> & {
|
|
14
|
+
fields: AkifilterSchema<TFieldValues>;
|
|
15
|
+
}>;
|
|
16
|
+
};
|
|
3
17
|
export declare const getDisplayLabel: <TFieldValues extends FieldValues = FieldValues>(field: AkifilterField<TFieldValues>) => string;
|
|
4
18
|
export declare const getFieldAriaLabel: <TFieldValues extends FieldValues = FieldValues>(field: AkifilterField<TFieldValues>) => string;
|
|
5
19
|
export declare const ensureSchemaOrder: <TFieldValues extends FieldValues = FieldValues>(schema: AkifilterSchema<TFieldValues>, keys: string[]) => string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/utils/schema.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhE,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,GAAG,WAAW,EAC5E,OAAO,cAAc,CAAC,YAAY,CAAC,KAClC,MAEF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,OAAO,cAAc,CAAC,YAAY,CAAC,KAClC,MAEF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,MAAM,MAAM,EAAE,KACb,MAAM,EAGR,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC,MAAM,EAUR,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC,OAAO,CAAC,YAAY,CAQtB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,SAAS,OAAO,CAAC,YAAY,CAAC,KAC7B,OAAO,CAAC,YAAY,CA+BtB,CAAC"}
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/utils/schema.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhE;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,YAAY,SAAS,WAAW,GAAG,WAAW,EAC1E,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC,eAAe,CAAC,YAAY,CAW9B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,GAAG,WAAW,EAC5E,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC;IACD,aAAa,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IAC7C,aAAa,EAAE,KAAK,CAClB,cAAc,CAAC,YAAY,CAAC,GAAG;QAC7B,MAAM,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;KACvC,CACF,CAAC;CAwBH,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,GAAG,WAAW,EAC5E,OAAO,cAAc,CAAC,YAAY,CAAC,KAClC,MAEF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,OAAO,cAAc,CAAC,YAAY,CAAC,KAClC,MAEF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,MAAM,MAAM,EAAE,KACb,MAAM,EAGR,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC,MAAM,EAUR,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC,OAAO,CAAC,YAAY,CAQtB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,SAAS,OAAO,CAAC,YAAY,CAAC,KAC7B,OAAO,CAAC,YAAY,CA+BtB,CAAC"}
|
package/dist/cjs/utils/schema.js
CHANGED
|
@@ -1,8 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.normaliseValuesBySchema = exports.extractDefaultValues = exports.deriveDefaultVisibleKeys = exports.ensureSchemaOrder = exports.getFieldAriaLabel = exports.getDisplayLabel = void 0;
|
|
3
|
+
exports.normaliseValuesBySchema = exports.extractDefaultValues = exports.deriveDefaultVisibleKeys = exports.ensureSchemaOrder = exports.getFieldAriaLabel = exports.getDisplayLabel = exports.partitionSchema = exports.flattenSchema = void 0;
|
|
4
4
|
const akidate_1 = require("@akinon/akidate");
|
|
5
5
|
const constants_1 = require("../constants");
|
|
6
|
+
/**
|
|
7
|
+
* Flattens schema by extracting all nested fields from section fields.
|
|
8
|
+
* This is used for storage operations and form value management.
|
|
9
|
+
*/
|
|
10
|
+
const flattenSchema = (schema) => {
|
|
11
|
+
return schema.reduce((acc, field) => {
|
|
12
|
+
if (field.type === 'section' && 'fields' in field) {
|
|
13
|
+
// Recursively flatten nested section fields
|
|
14
|
+
return [
|
|
15
|
+
...acc,
|
|
16
|
+
...(0, exports.flattenSchema)(field.fields)
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
return [...acc, field];
|
|
20
|
+
}, []);
|
|
21
|
+
};
|
|
22
|
+
exports.flattenSchema = flattenSchema;
|
|
23
|
+
/**
|
|
24
|
+
* Separates section fields from regular fields.
|
|
25
|
+
*/
|
|
26
|
+
const partitionSchema = (schema) => {
|
|
27
|
+
const regularFields = [];
|
|
28
|
+
const sectionFields = [];
|
|
29
|
+
schema.forEach(field => {
|
|
30
|
+
if (field.type === 'section') {
|
|
31
|
+
sectionFields.push(field);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
regularFields.push(field);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return { regularFields, sectionFields };
|
|
38
|
+
};
|
|
39
|
+
exports.partitionSchema = partitionSchema;
|
|
6
40
|
const getDisplayLabel = (field) => {
|
|
7
41
|
return field.label || field.placeholder || String(field.key);
|
|
8
42
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"akifilter.d.ts","sourceRoot":"","sources":["../../src/akifilter.tsx"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AAGtB,OAAO,EAIL,WAAW,EAEX,IAAI,EAGL,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"akifilter.d.ts","sourceRoot":"","sources":["../../src/akifilter.tsx"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AAGtB,OAAO,EAIL,WAAW,EAEX,IAAI,EAGL,MAAM,iBAAiB,CAAC;AAWzB,OAAO,KAAK,MAAM,OAAO,CAAC;AA2B1B,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,SAAS,CAAC;AAY/D,KAAK,oBAAoB,GAAG,WAAW,CAAC;AA2BxC,MAAM,MAAM,cAAc,CACxB,YAAY,SAAS,oBAAoB,GAAG,oBAAoB,IAC9D;IACF;;OAEG;IACH,YAAY,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IAC7C;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACtC;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;IACzD;;OAEG;IACH,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,KAAK,IAAI,CAAC;IAClE;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAirBF,eAAO,MAAM,SAAS;KACpB,YAAY,SAAS,oBAAoB,uBAElC,cAAc,CAAC,YAAY,CAAC;;CAmBpC,CAAC"}
|
package/dist/esm/akifilter.js
CHANGED
|
@@ -15,6 +15,7 @@ import { Akiform, FormItem, useForm, useWatch } from '@akinon/akiform';
|
|
|
15
15
|
import { Button } from '@akinon/ui-button';
|
|
16
16
|
import { Card } from '@akinon/ui-card';
|
|
17
17
|
import { Checkbox } from '@akinon/ui-checkbox';
|
|
18
|
+
import { Collapse } from '@akinon/ui-collapse';
|
|
18
19
|
import { DatePicker } from '@akinon/ui-date-picker';
|
|
19
20
|
import { Input, InputTextArea } from '@akinon/ui-input';
|
|
20
21
|
import { InputNumber } from '@akinon/ui-input-number';
|
|
@@ -31,7 +32,7 @@ import { VisibilityModal } from './components/visibility-modal';
|
|
|
31
32
|
import { BOOLEAN_STRING, DATE_FORMAT, DEFAULT_MODAL_PAGE_SIZE, FILTER_DEBOUNCE_DELAY } from './constants';
|
|
32
33
|
import { useDebouncedValue } from './hooks/use-debounced-value';
|
|
33
34
|
import { i18n } from './i18n';
|
|
34
|
-
import { deriveDefaultVisibleKeys, ensureSchemaOrder, extractDefaultValues, getFieldAriaLabel, normaliseValuesBySchema } from './utils/schema';
|
|
35
|
+
import { deriveDefaultVisibleKeys, ensureSchemaOrder, extractDefaultValues, flattenSchema, getFieldAriaLabel, normaliseValuesBySchema, partitionSchema } from './utils/schema';
|
|
35
36
|
import { normaliseOutputValues } from './utils/values';
|
|
36
37
|
const resolveClearedFieldValue = (field, defaultValue) => {
|
|
37
38
|
if (defaultValue !== undefined) {
|
|
@@ -52,14 +53,18 @@ const resolveClearedFieldValue = (field, defaultValue) => {
|
|
|
52
53
|
}
|
|
53
54
|
};
|
|
54
55
|
const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onValuesChange, onVisibleFieldsChange, onImportCsv, onImportXls, onClearAll, enableImportCsv, enableImportXls }) => {
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
56
|
+
// Separate regular fields from section fields
|
|
57
|
+
const { regularFields, sectionFields } = React.useMemo(() => partitionSchema(filterSchema), [filterSchema]);
|
|
58
|
+
// Flatten schema for storage and form value operations
|
|
59
|
+
const flattenedSchema = React.useMemo(() => flattenSchema(filterSchema), [filterSchema]);
|
|
60
|
+
const storageKey = React.useMemo(() => buildStorageKey(regularFields, storageNamespace), [regularFields, storageNamespace]);
|
|
61
|
+
const schemaDefaults = React.useMemo(() => normaliseValuesBySchema(flattenedSchema, extractDefaultValues(flattenedSchema)), [flattenedSchema]);
|
|
62
|
+
const externalDefaults = React.useMemo(() => normaliseValuesBySchema(flattenedSchema, defaultValues), [flattenedSchema, defaultValues]);
|
|
58
63
|
const baseDefaultValues = React.useMemo(() => (Object.assign(Object.assign({}, schemaDefaults), externalDefaults)), [schemaDefaults, externalDefaults]);
|
|
59
64
|
const persistedDefaults = React.useMemo(() => {
|
|
60
65
|
var _a;
|
|
61
|
-
return normaliseValuesBySchema(
|
|
62
|
-
}, [
|
|
66
|
+
return normaliseValuesBySchema(flattenedSchema, (_a = readStoredValues(regularFields, storageKey)) !== null && _a !== void 0 ? _a : undefined);
|
|
67
|
+
}, [flattenedSchema, regularFields, storageKey]);
|
|
63
68
|
const mergedDefaultValues = React.useMemo(() => (Object.assign(Object.assign({}, baseDefaultValues), persistedDefaults)), [baseDefaultValues, persistedDefaults]);
|
|
64
69
|
const formMethods = useForm({
|
|
65
70
|
defaultValues: mergedDefaultValues
|
|
@@ -73,7 +78,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
73
78
|
if (!formValues) {
|
|
74
79
|
return [];
|
|
75
80
|
}
|
|
76
|
-
return
|
|
81
|
+
return flattenedSchema.reduce((acc, field) => {
|
|
77
82
|
const key = String(field.key);
|
|
78
83
|
const currentValue = formValues[key];
|
|
79
84
|
if (currentValue === undefined || currentValue === null) {
|
|
@@ -129,24 +134,24 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
129
134
|
acc.push({ key, label, value: resolveValue() });
|
|
130
135
|
return acc;
|
|
131
136
|
}, []);
|
|
132
|
-
}, [
|
|
137
|
+
}, [flattenedSchema, formValues]);
|
|
133
138
|
const resolveInitialVisibleKeys = React.useCallback(() => {
|
|
134
|
-
const storedKeys = readVisibleKeys(
|
|
139
|
+
const storedKeys = readVisibleKeys(regularFields, storageKey);
|
|
135
140
|
if (storedKeys && storedKeys.length > 0) {
|
|
136
|
-
return ensureSchemaOrder(
|
|
141
|
+
return ensureSchemaOrder(regularFields, storedKeys);
|
|
137
142
|
}
|
|
138
|
-
return deriveDefaultVisibleKeys(
|
|
139
|
-
}, [
|
|
143
|
+
return deriveDefaultVisibleKeys(regularFields);
|
|
144
|
+
}, [regularFields, storageKey]);
|
|
140
145
|
const [visibleKeys, setVisibleKeys] = React.useState(resolveInitialVisibleKeys);
|
|
141
146
|
React.useEffect(() => {
|
|
142
147
|
setVisibleKeys(previous => {
|
|
143
|
-
const ordered = ensureSchemaOrder(
|
|
148
|
+
const ordered = ensureSchemaOrder(regularFields, previous);
|
|
144
149
|
if (ordered.length) {
|
|
145
150
|
return ordered;
|
|
146
151
|
}
|
|
147
152
|
return resolveInitialVisibleKeys();
|
|
148
153
|
});
|
|
149
|
-
}, [
|
|
154
|
+
}, [regularFields, resolveInitialVisibleKeys]);
|
|
150
155
|
React.useEffect(() => {
|
|
151
156
|
writeVisibleKeys(storageKey, visibleKeys);
|
|
152
157
|
onVisibleFieldsChange === null || onVisibleFieldsChange === void 0 ? void 0 : onVisibleFieldsChange(visibleKeys);
|
|
@@ -154,12 +159,12 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
154
159
|
React.useEffect(() => {
|
|
155
160
|
formMethods.reset(mergedDefaultValues);
|
|
156
161
|
}, [formMethods, mergedDefaultValues]);
|
|
157
|
-
const normalisedValues = React.useMemo(() => normaliseOutputValues(
|
|
162
|
+
const normalisedValues = React.useMemo(() => normaliseOutputValues(flattenedSchema, formValues), [flattenedSchema, formValues]);
|
|
158
163
|
const hasInitialValuesRef = React.useRef(false);
|
|
159
164
|
React.useEffect(() => {
|
|
160
|
-
const initialValues = normaliseOutputValues(
|
|
165
|
+
const initialValues = normaliseOutputValues(flattenedSchema, mergedDefaultValues);
|
|
161
166
|
hasInitialValuesRef.current = Object.keys(initialValues).length > 0;
|
|
162
|
-
}, [
|
|
167
|
+
}, [flattenedSchema, mergedDefaultValues]);
|
|
163
168
|
const serialisedValues = React.useMemo(() => JSON.stringify(normalisedValues), [normalisedValues]);
|
|
164
169
|
const debouncedSerialisedValues = useDebouncedValue(serialisedValues, FILTER_DEBOUNCE_DELAY);
|
|
165
170
|
const lastPersistedValuesRef = React.useRef(null);
|
|
@@ -211,7 +216,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
211
216
|
}, [storageKey]);
|
|
212
217
|
const handleClearAll = React.useCallback(() => {
|
|
213
218
|
const clearedDefaults = Object.assign({}, baseDefaultValues);
|
|
214
|
-
|
|
219
|
+
flattenedSchema.forEach(field => {
|
|
215
220
|
const key = String(field.key);
|
|
216
221
|
const defaultValue = baseDefaultValues[key];
|
|
217
222
|
const resolved = resolveClearedFieldValue(field, defaultValue);
|
|
@@ -227,13 +232,20 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
227
232
|
keepTouched: false
|
|
228
233
|
});
|
|
229
234
|
onClearAll === null || onClearAll === void 0 ? void 0 : onClearAll();
|
|
230
|
-
const nextValues = normaliseOutputValues(
|
|
235
|
+
const nextValues = normaliseOutputValues(flattenedSchema, clearedDefaults);
|
|
231
236
|
hasInitialValuesRef.current = Object.keys(nextValues).length > 0;
|
|
232
237
|
const nextSerialised = persistValues(nextValues);
|
|
233
238
|
hasEmittedValuesRef.current = true;
|
|
234
239
|
lastPersistedValuesRef.current = nextSerialised !== null && nextSerialised !== void 0 ? nextSerialised : null;
|
|
235
240
|
onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(nextValues);
|
|
236
|
-
}, [
|
|
241
|
+
}, [
|
|
242
|
+
baseDefaultValues,
|
|
243
|
+
flattenedSchema,
|
|
244
|
+
formMethods,
|
|
245
|
+
onClearAll,
|
|
246
|
+
onValuesChange,
|
|
247
|
+
persistValues
|
|
248
|
+
]);
|
|
237
249
|
const handleOpenModal = React.useCallback(() => {
|
|
238
250
|
setIsModalOpen(true);
|
|
239
251
|
setSearchTerm('');
|
|
@@ -251,11 +263,11 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
251
263
|
const nextKeys = exists
|
|
252
264
|
? prev.filter(item => item !== key)
|
|
253
265
|
: [...prev, key];
|
|
254
|
-
return ensureSchemaOrder(
|
|
266
|
+
return ensureSchemaOrder(regularFields, nextKeys);
|
|
255
267
|
});
|
|
256
|
-
}, [
|
|
268
|
+
}, [regularFields]);
|
|
257
269
|
const handleRemoveFilter = React.useCallback((key) => {
|
|
258
|
-
const schemaField =
|
|
270
|
+
const schemaField = flattenedSchema.find(field => String(field.key) === key);
|
|
259
271
|
if (!schemaField) {
|
|
260
272
|
return;
|
|
261
273
|
}
|
|
@@ -271,7 +283,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
271
283
|
// Compute the updated values immediately (don't wait for debounce)
|
|
272
284
|
const currentValues = formMethods.getValues() || {};
|
|
273
285
|
const updatedFormValues = Object.assign(Object.assign({}, currentValues), { [fieldPath]: nextValue });
|
|
274
|
-
const nextValues = normaliseOutputValues(
|
|
286
|
+
const nextValues = normaliseOutputValues(flattenedSchema, updatedFormValues);
|
|
275
287
|
const nextSerialised = JSON.stringify(nextValues);
|
|
276
288
|
// Persist immediately (bypass debounce for remove action)
|
|
277
289
|
persistValues(nextValues);
|
|
@@ -284,7 +296,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
284
296
|
onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(nextValues);
|
|
285
297
|
}, [
|
|
286
298
|
baseDefaultValues,
|
|
287
|
-
|
|
299
|
+
flattenedSchema,
|
|
288
300
|
formMethods,
|
|
289
301
|
onValuesChange,
|
|
290
302
|
persistValues
|
|
@@ -292,20 +304,20 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
292
304
|
const filteredFields = React.useMemo(() => {
|
|
293
305
|
const normalisedTerm = searchTerm.trim().toLowerCase();
|
|
294
306
|
if (!normalisedTerm) {
|
|
295
|
-
return
|
|
307
|
+
return regularFields;
|
|
296
308
|
}
|
|
297
|
-
return
|
|
309
|
+
return regularFields.filter(field => {
|
|
298
310
|
const searchable = [field.label, field.placeholder, String(field.key)]
|
|
299
311
|
.filter(Boolean)
|
|
300
312
|
.map(value => String(value).toLowerCase());
|
|
301
313
|
return searchable.some(text => text.includes(normalisedTerm));
|
|
302
314
|
});
|
|
303
|
-
}, [
|
|
315
|
+
}, [regularFields, searchTerm]);
|
|
304
316
|
const paginatedFields = React.useMemo(() => {
|
|
305
317
|
const start = (modalPage - 1) * modalPageSize;
|
|
306
318
|
return filteredFields.slice(start, start + modalPageSize);
|
|
307
319
|
}, [filteredFields, modalPage, modalPageSize]);
|
|
308
|
-
const visibleFields = React.useMemo(() =>
|
|
320
|
+
const visibleFields = React.useMemo(() => regularFields.filter(field => visibleKeys.includes(String(field.key))), [regularFields, visibleKeys]);
|
|
309
321
|
const renderFieldComponent = (field) => {
|
|
310
322
|
const ariaLabel = getFieldAriaLabel(field);
|
|
311
323
|
switch (field.type) {
|
|
@@ -332,10 +344,17 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
332
344
|
});
|
|
333
345
|
}
|
|
334
346
|
return (React.createElement(Text, { type: "secondary", className: "akinon-filter__unsupported" }, i18n.t('form.unsupportedField', { field: String(field.type) })));
|
|
347
|
+
case 'section':
|
|
348
|
+
// Section fields should not be rendered inline
|
|
349
|
+
// They are rendered separately after the main grid
|
|
350
|
+
return null;
|
|
335
351
|
default:
|
|
336
352
|
return (React.createElement(Text, { type: "secondary", className: "akinon-filter__unsupported" }, i18n.t('form.unsupportedField', { field: String(field.type) })));
|
|
337
353
|
}
|
|
338
354
|
};
|
|
355
|
+
const renderFormField = (field) => {
|
|
356
|
+
return (React.createElement(FormItem, { key: String(field.key), control: formMethods.control, name: field.key, tooltip: field.tooltip, help: field.help, labelDescription: field.labelDescription, required: Boolean(field.validation), className: "mb-0", valuePropName: field.type === 'checkbox' ? 'checked' : undefined }, renderFieldComponent(field)));
|
|
357
|
+
};
|
|
339
358
|
return (React.createElement(Card, { size: "small", className: "akinon-filter shadow", "data-testid": "akifilter-root" },
|
|
340
359
|
React.createElement(ConfigProvider, { theme: themeOverrides },
|
|
341
360
|
React.createElement(FilterToolbar, { onOpenModal: handleOpenModal, enableImportCsv: enableImportCsv, enableImportXls: enableImportXls, onImportCsv: onImportCsv, onImportXls: onImportXls }),
|
|
@@ -343,8 +362,17 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
|
|
|
343
362
|
React.createElement("div", { className: "akinon-filter__body" },
|
|
344
363
|
React.createElement(Akiform, { layout: "vertical", className: "akinon-filter__form" },
|
|
345
364
|
React.createElement("div", { className: "akinon-filter__form-grid", "data-testid": "akifilter-form-grid" },
|
|
346
|
-
visibleFields.map(
|
|
347
|
-
visibleFields.length === 0 ? (React.createElement("div", { className: "akinon-filter__empty" }, i18n.t('form.noVisibleFields'))) : null)
|
|
365
|
+
visibleFields.map(renderFormField),
|
|
366
|
+
visibleFields.length === 0 ? (React.createElement("div", { className: "akinon-filter__empty" }, i18n.t('form.noVisibleFields'))) : null),
|
|
367
|
+
sectionFields.length > 0 && (React.createElement("div", { className: "akinon-filter__section-fields" }, sectionFields.map(section => {
|
|
368
|
+
return (React.createElement(Collapse, { key: section.key, defaultActiveKey: section.key, ghost: true, items: [
|
|
369
|
+
{
|
|
370
|
+
key: section.key,
|
|
371
|
+
label: section.label,
|
|
372
|
+
children: (React.createElement("div", { className: "akinon-filter__form-grid" }, section.fields.map(renderFormField)))
|
|
373
|
+
}
|
|
374
|
+
] }));
|
|
375
|
+
}))))),
|
|
348
376
|
React.createElement(VisibilityModal, { open: isModalOpen, onClose: handleCloseModal, searchTerm: searchTerm, paginatedFields: paginatedFields, filteredCount: filteredFields.length, visibleKeys: visibleKeys, onSearchTermChange: value => {
|
|
349
377
|
setSearchTerm(value);
|
|
350
378
|
setModalPage(1);
|
package/dist/esm/styles.css
CHANGED
|
@@ -51,6 +51,15 @@
|
|
|
51
51
|
top: 1px;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
.akinon-filter__common-filters {
|
|
55
|
+
margin-top: 2rem;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.akinon-filter__common-filters-title {
|
|
59
|
+
color: var(--color-gray-500);
|
|
60
|
+
margin-bottom: 1rem;
|
|
61
|
+
}
|
|
62
|
+
|
|
54
63
|
.akinon-filter__form-grid {
|
|
55
64
|
display: grid;
|
|
56
65
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
@@ -65,6 +74,27 @@
|
|
|
65
74
|
width: 100%;
|
|
66
75
|
}
|
|
67
76
|
|
|
77
|
+
.akinon-filter__section-fields {
|
|
78
|
+
margin-top: 2rem;
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
gap: 1rem;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.akinon-filter__section-fields .akinon-collapse-header {
|
|
85
|
+
padding-bottom: 0 !important;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.akinon-filter__section-fields .akinon-collapse-header-text {
|
|
89
|
+
color: var(--color-gray-500);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.akinon-filter__section-fields .akinon-collapse-expand-icon .akinon-collapse-arrow {
|
|
93
|
+
font-size: 1rem !important;
|
|
94
|
+
color: var(--color-gray-500) !important;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
68
98
|
@media (max-width: 1024px) {
|
|
69
99
|
.akinon-filter__form-grid {
|
|
70
100
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import type { FieldValues } from '@akinon/akiform';
|
|
2
2
|
import type { AkifilterField, AkifilterSchema } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Flattens schema by extracting all nested fields from section fields.
|
|
5
|
+
* This is used for storage operations and form value management.
|
|
6
|
+
*/
|
|
7
|
+
export declare const flattenSchema: <TFieldValues extends FieldValues = FieldValues>(schema: AkifilterSchema<TFieldValues>) => AkifilterSchema<TFieldValues>;
|
|
8
|
+
/**
|
|
9
|
+
* Separates section fields from regular fields.
|
|
10
|
+
*/
|
|
11
|
+
export declare const partitionSchema: <TFieldValues extends FieldValues = FieldValues>(schema: AkifilterSchema<TFieldValues>) => {
|
|
12
|
+
regularFields: AkifilterSchema<TFieldValues>;
|
|
13
|
+
sectionFields: Array<AkifilterField<TFieldValues> & {
|
|
14
|
+
fields: AkifilterSchema<TFieldValues>;
|
|
15
|
+
}>;
|
|
16
|
+
};
|
|
3
17
|
export declare const getDisplayLabel: <TFieldValues extends FieldValues = FieldValues>(field: AkifilterField<TFieldValues>) => string;
|
|
4
18
|
export declare const getFieldAriaLabel: <TFieldValues extends FieldValues = FieldValues>(field: AkifilterField<TFieldValues>) => string;
|
|
5
19
|
export declare const ensureSchemaOrder: <TFieldValues extends FieldValues = FieldValues>(schema: AkifilterSchema<TFieldValues>, keys: string[]) => string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/utils/schema.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhE,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,GAAG,WAAW,EAC5E,OAAO,cAAc,CAAC,YAAY,CAAC,KAClC,MAEF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,OAAO,cAAc,CAAC,YAAY,CAAC,KAClC,MAEF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,MAAM,MAAM,EAAE,KACb,MAAM,EAGR,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC,MAAM,EAUR,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC,OAAO,CAAC,YAAY,CAQtB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,SAAS,OAAO,CAAC,YAAY,CAAC,KAC7B,OAAO,CAAC,YAAY,CA+BtB,CAAC"}
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/utils/schema.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhE;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,YAAY,SAAS,WAAW,GAAG,WAAW,EAC1E,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC,eAAe,CAAC,YAAY,CAW9B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,GAAG,WAAW,EAC5E,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC;IACD,aAAa,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IAC7C,aAAa,EAAE,KAAK,CAClB,cAAc,CAAC,YAAY,CAAC,GAAG;QAC7B,MAAM,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;KACvC,CACF,CAAC;CAwBH,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,GAAG,WAAW,EAC5E,OAAO,cAAc,CAAC,YAAY,CAAC,KAClC,MAEF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,OAAO,cAAc,CAAC,YAAY,CAAC,KAClC,MAEF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,MAAM,MAAM,EAAE,KACb,MAAM,EAGR,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC,MAAM,EAUR,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC,OAAO,CAAC,YAAY,CAQtB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,SAAS,OAAO,CAAC,YAAY,CAAC,KAC7B,OAAO,CAAC,YAAY,CA+BtB,CAAC"}
|
package/dist/esm/utils/schema.js
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
import { akidate } from '@akinon/akidate';
|
|
2
2
|
import { DEFAULT_VISIBLE_COUNT } from '../constants';
|
|
3
|
+
/**
|
|
4
|
+
* Flattens schema by extracting all nested fields from section fields.
|
|
5
|
+
* This is used for storage operations and form value management.
|
|
6
|
+
*/
|
|
7
|
+
export const flattenSchema = (schema) => {
|
|
8
|
+
return schema.reduce((acc, field) => {
|
|
9
|
+
if (field.type === 'section' && 'fields' in field) {
|
|
10
|
+
// Recursively flatten nested section fields
|
|
11
|
+
return [
|
|
12
|
+
...acc,
|
|
13
|
+
...flattenSchema(field.fields)
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
return [...acc, field];
|
|
17
|
+
}, []);
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Separates section fields from regular fields.
|
|
21
|
+
*/
|
|
22
|
+
export const partitionSchema = (schema) => {
|
|
23
|
+
const regularFields = [];
|
|
24
|
+
const sectionFields = [];
|
|
25
|
+
schema.forEach(field => {
|
|
26
|
+
if (field.type === 'section') {
|
|
27
|
+
sectionFields.push(field);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
regularFields.push(field);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return { regularFields, sectionFields };
|
|
34
|
+
};
|
|
3
35
|
export const getDisplayLabel = (field) => {
|
|
4
36
|
return field.label || field.placeholder || String(field.key);
|
|
5
37
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/akifilter",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.1-rc.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Akifilter is a filtering library for Akinon frontend applications.",
|
|
6
6
|
"type": "module",
|
|
@@ -14,30 +14,31 @@
|
|
|
14
14
|
"react-error-boundary": "^6.0.0",
|
|
15
15
|
"@akinon/akiform": "1.1.2",
|
|
16
16
|
"@akinon/akidate": "1.1.2",
|
|
17
|
+
"@akinon/icons": "1.1.2-rc.0",
|
|
17
18
|
"@akinon/akilocale": "1.2.1",
|
|
18
|
-
"@akinon/
|
|
19
|
-
"@akinon/ui-
|
|
20
|
-
"@akinon/ui-checkbox": "1.3.
|
|
21
|
-
"@akinon/ui-
|
|
22
|
-
"@akinon/ui-
|
|
23
|
-
"@akinon/ui-input
|
|
24
|
-
"@akinon/ui-modal": "1.1.2",
|
|
25
|
-
"@akinon/ui-select": "1.3.3",
|
|
26
|
-
"@akinon/ui-pagination": "1.3.3",
|
|
27
|
-
"@akinon/ui-space": "1.3.2",
|
|
19
|
+
"@akinon/ui-button": "1.4.0-rc.0",
|
|
20
|
+
"@akinon/ui-card": "1.1.3-rc.0",
|
|
21
|
+
"@akinon/ui-checkbox": "1.3.3-rc.0",
|
|
22
|
+
"@akinon/ui-date-picker": "1.3.3-rc.0",
|
|
23
|
+
"@akinon/ui-collapse": "1.3.2-rc.0",
|
|
24
|
+
"@akinon/ui-input": "1.1.3-rc.0",
|
|
28
25
|
"@akinon/ui-typography": "1.1.1",
|
|
29
|
-
"@akinon/ui-
|
|
26
|
+
"@akinon/ui-input-number": "1.3.3-rc.0",
|
|
27
|
+
"@akinon/ui-select": "1.3.4-rc.0",
|
|
28
|
+
"@akinon/ui-pagination": "1.3.4-rc.0",
|
|
29
|
+
"@akinon/ui-modal": "1.1.3-rc.0",
|
|
30
|
+
"@akinon/ui-space": "1.3.3-rc.0"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"clean-package": "2.2.0",
|
|
33
34
|
"copyfiles": "^2.4.1",
|
|
34
35
|
"rimraf": "^5.0.5",
|
|
35
36
|
"typescript": "*",
|
|
36
|
-
"@akinon/akiform-builder": "1.3.
|
|
37
|
-
"@akinon/
|
|
38
|
-
"@akinon/ui-theme": "1.1.
|
|
37
|
+
"@akinon/akiform-builder": "1.3.5-rc.0",
|
|
38
|
+
"@akinon/utils": "1.1.4-rc.0",
|
|
39
|
+
"@akinon/ui-theme": "1.1.3-rc.0",
|
|
39
40
|
"@akinon/vitest-config": "1.1.1",
|
|
40
|
-
"@akinon/
|
|
41
|
+
"@akinon/typescript-config": "1.1.1"
|
|
41
42
|
},
|
|
42
43
|
"peerDependencies": {
|
|
43
44
|
"react": "^18 || ^19",
|