@akinon/akifilter 1.0.4 → 1.1.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 (39) hide show
  1. package/dist/cjs/akifilter.d.ts.map +1 -1
  2. package/dist/cjs/akifilter.js +86 -35
  3. package/dist/cjs/common/storage.d.ts.map +1 -1
  4. package/dist/cjs/common/storage.js +7 -6
  5. package/dist/cjs/constants.d.ts +6 -0
  6. package/dist/cjs/constants.d.ts.map +1 -1
  7. package/dist/cjs/constants.js +7 -1
  8. package/dist/cjs/i18n/translations/en.d.ts +8 -0
  9. package/dist/cjs/i18n/translations/en.d.ts.map +1 -1
  10. package/dist/cjs/i18n/translations/en.js +9 -1
  11. package/dist/cjs/i18n/translations/tr.d.ts +8 -0
  12. package/dist/cjs/i18n/translations/tr.d.ts.map +1 -1
  13. package/dist/cjs/i18n/translations/tr.js +9 -1
  14. package/dist/cjs/styles.css +30 -0
  15. package/dist/cjs/utils/schema.d.ts +14 -0
  16. package/dist/cjs/utils/schema.d.ts.map +1 -1
  17. package/dist/cjs/utils/schema.js +35 -1
  18. package/dist/cjs/utils/values.d.ts.map +1 -1
  19. package/dist/cjs/utils/values.js +5 -1
  20. package/dist/esm/akifilter.d.ts.map +1 -1
  21. package/dist/esm/akifilter.js +88 -37
  22. package/dist/esm/common/storage.d.ts.map +1 -1
  23. package/dist/esm/common/storage.js +7 -6
  24. package/dist/esm/constants.d.ts +6 -0
  25. package/dist/esm/constants.d.ts.map +1 -1
  26. package/dist/esm/constants.js +6 -0
  27. package/dist/esm/i18n/translations/en.d.ts +8 -0
  28. package/dist/esm/i18n/translations/en.d.ts.map +1 -1
  29. package/dist/esm/i18n/translations/en.js +9 -1
  30. package/dist/esm/i18n/translations/tr.d.ts +8 -0
  31. package/dist/esm/i18n/translations/tr.d.ts.map +1 -1
  32. package/dist/esm/i18n/translations/tr.js +9 -1
  33. package/dist/esm/styles.css +30 -0
  34. package/dist/esm/utils/schema.d.ts +14 -0
  35. package/dist/esm/utils/schema.d.ts.map +1 -1
  36. package/dist/esm/utils/schema.js +32 -0
  37. package/dist/esm/utils/values.d.ts.map +1 -1
  38. package/dist/esm/utils/values.js +5 -1
  39. package/package.json +22 -21
@@ -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';
@@ -28,10 +29,10 @@ import { themeOverrides } from './common/theme-overrides';
28
29
  import { AppliedFilters } from './components/applied-filters';
29
30
  import { FilterToolbar } from './components/filter-toolbar';
30
31
  import { VisibilityModal } from './components/visibility-modal';
31
- import { DEFAULT_MODAL_PAGE_SIZE, FILTER_DEBOUNCE_DELAY } from './constants';
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
- const storageKey = React.useMemo(() => buildStorageKey(filterSchema, storageNamespace), [filterSchema, storageNamespace]);
56
- const schemaDefaults = React.useMemo(() => normaliseValuesBySchema(filterSchema, extractDefaultValues(filterSchema)), [filterSchema]);
57
- const externalDefaults = React.useMemo(() => normaliseValuesBySchema(filterSchema, defaultValues), [filterSchema, defaultValues]);
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(filterSchema, (_a = readStoredValues(filterSchema, storageKey)) !== null && _a !== void 0 ? _a : undefined);
62
- }, [filterSchema, storageKey]);
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 filterSchema.reduce((acc, field) => {
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) {
@@ -104,14 +109,17 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
104
109
  if (field.type === 'date') {
105
110
  const iso = akidate.toIsoDate(currentValue);
106
111
  if (iso) {
107
- return akidate.formatIsoDate(iso, 'YYYY-MM-DD');
112
+ // Use localized format with time if showTime is enabled
113
+ const hasShowTime = 'showTime' in field && field.showTime;
114
+ const format = hasShowTime ? 'L LTS' : 'YYYY-MM-DD';
115
+ return akidate.formatIsoDate(iso, format);
108
116
  }
109
117
  }
110
118
  if (Array.isArray(currentValue)) {
111
119
  return currentValue.map((value) => String(value)).join(', ');
112
120
  }
113
121
  if (typeof currentValue === 'boolean') {
114
- return currentValue ? 'true' : 'false';
122
+ return currentValue ? BOOLEAN_STRING.TRUE : BOOLEAN_STRING.FALSE;
115
123
  }
116
124
  if (typeof currentValue === 'string' ||
117
125
  typeof currentValue === 'number') {
@@ -119,31 +127,31 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
119
127
  }
120
128
  const iso = akidate.toIsoDate(currentValue);
121
129
  if (iso) {
122
- return akidate.formatIsoDate(iso, 'YYYY-MM-DD');
130
+ return akidate.formatIsoDate(iso, DATE_FORMAT);
123
131
  }
124
132
  return String(currentValue);
125
133
  };
126
134
  acc.push({ key, label, value: resolveValue() });
127
135
  return acc;
128
136
  }, []);
129
- }, [filterSchema, formValues]);
137
+ }, [flattenedSchema, formValues]);
130
138
  const resolveInitialVisibleKeys = React.useCallback(() => {
131
- const storedKeys = readVisibleKeys(filterSchema, storageKey);
139
+ const storedKeys = readVisibleKeys(regularFields, storageKey);
132
140
  if (storedKeys && storedKeys.length > 0) {
133
- return ensureSchemaOrder(filterSchema, storedKeys);
141
+ return ensureSchemaOrder(regularFields, storedKeys);
134
142
  }
135
- return deriveDefaultVisibleKeys(filterSchema);
136
- }, [filterSchema, storageKey]);
143
+ return deriveDefaultVisibleKeys(regularFields);
144
+ }, [regularFields, storageKey]);
137
145
  const [visibleKeys, setVisibleKeys] = React.useState(resolveInitialVisibleKeys);
138
146
  React.useEffect(() => {
139
147
  setVisibleKeys(previous => {
140
- const ordered = ensureSchemaOrder(filterSchema, previous);
148
+ const ordered = ensureSchemaOrder(regularFields, previous);
141
149
  if (ordered.length) {
142
150
  return ordered;
143
151
  }
144
152
  return resolveInitialVisibleKeys();
145
153
  });
146
- }, [filterSchema, resolveInitialVisibleKeys]);
154
+ }, [regularFields, resolveInitialVisibleKeys]);
147
155
  React.useEffect(() => {
148
156
  writeVisibleKeys(storageKey, visibleKeys);
149
157
  onVisibleFieldsChange === null || onVisibleFieldsChange === void 0 ? void 0 : onVisibleFieldsChange(visibleKeys);
@@ -151,12 +159,12 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
151
159
  React.useEffect(() => {
152
160
  formMethods.reset(mergedDefaultValues);
153
161
  }, [formMethods, mergedDefaultValues]);
154
- const normalisedValues = React.useMemo(() => normaliseOutputValues(filterSchema, formValues), [filterSchema, formValues]);
162
+ const normalisedValues = React.useMemo(() => normaliseOutputValues(flattenedSchema, formValues), [flattenedSchema, formValues]);
155
163
  const hasInitialValuesRef = React.useRef(false);
156
164
  React.useEffect(() => {
157
- const initialValues = normaliseOutputValues(filterSchema, mergedDefaultValues);
165
+ const initialValues = normaliseOutputValues(flattenedSchema, mergedDefaultValues);
158
166
  hasInitialValuesRef.current = Object.keys(initialValues).length > 0;
159
- }, [filterSchema, mergedDefaultValues]);
167
+ }, [flattenedSchema, mergedDefaultValues]);
160
168
  const serialisedValues = React.useMemo(() => JSON.stringify(normalisedValues), [normalisedValues]);
161
169
  const debouncedSerialisedValues = useDebouncedValue(serialisedValues, FILTER_DEBOUNCE_DELAY);
162
170
  const lastPersistedValuesRef = React.useRef(null);
@@ -171,14 +179,25 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
171
179
  writeStoredValues(storageKey, values);
172
180
  return nextSerialised;
173
181
  }, [storageKey]);
182
+ // Use a ref to track the current serialised values for staleness checks
183
+ const currentSerialisedValuesRef = React.useRef(serialisedValues);
184
+ React.useEffect(() => {
185
+ currentSerialisedValuesRef.current = serialisedValues;
186
+ }, [serialisedValues]);
174
187
  React.useEffect(() => {
175
188
  if (debouncedSerialisedValues == null) {
176
189
  return;
177
190
  }
191
+ // Skip if we've already processed this exact debounced value
178
192
  if (hasEmittedValuesRef.current &&
179
193
  lastPersistedValuesRef.current === debouncedSerialisedValues) {
180
194
  return;
181
195
  }
196
+ // Skip if the debounced value is stale (doesn't match current state)
197
+ // This prevents old debounced values from overwriting manual updates
198
+ if (currentSerialisedValuesRef.current !== debouncedSerialisedValues) {
199
+ return;
200
+ }
182
201
  const parsedValues = JSON.parse(debouncedSerialisedValues);
183
202
  const isEmpty = Object.keys(parsedValues).length === 0;
184
203
  if (!hasEmittedValuesRef.current &&
@@ -197,7 +216,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
197
216
  }, [storageKey]);
198
217
  const handleClearAll = React.useCallback(() => {
199
218
  const clearedDefaults = Object.assign({}, baseDefaultValues);
200
- filterSchema.forEach(field => {
219
+ flattenedSchema.forEach(field => {
201
220
  const key = String(field.key);
202
221
  const defaultValue = baseDefaultValues[key];
203
222
  const resolved = resolveClearedFieldValue(field, defaultValue);
@@ -213,13 +232,20 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
213
232
  keepTouched: false
214
233
  });
215
234
  onClearAll === null || onClearAll === void 0 ? void 0 : onClearAll();
216
- const nextValues = normaliseOutputValues(filterSchema, clearedDefaults);
235
+ const nextValues = normaliseOutputValues(flattenedSchema, clearedDefaults);
217
236
  hasInitialValuesRef.current = Object.keys(nextValues).length > 0;
218
237
  const nextSerialised = persistValues(nextValues);
219
238
  hasEmittedValuesRef.current = true;
220
239
  lastPersistedValuesRef.current = nextSerialised !== null && nextSerialised !== void 0 ? nextSerialised : null;
221
240
  onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(nextValues);
222
- }, [baseDefaultValues, filterSchema, formMethods, onClearAll, persistValues]);
241
+ }, [
242
+ baseDefaultValues,
243
+ flattenedSchema,
244
+ formMethods,
245
+ onClearAll,
246
+ onValuesChange,
247
+ persistValues
248
+ ]);
223
249
  const handleOpenModal = React.useCallback(() => {
224
250
  setIsModalOpen(true);
225
251
  setSearchTerm('');
@@ -237,31 +263,40 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
237
263
  const nextKeys = exists
238
264
  ? prev.filter(item => item !== key)
239
265
  : [...prev, key];
240
- return ensureSchemaOrder(filterSchema, nextKeys);
266
+ return ensureSchemaOrder(regularFields, nextKeys);
241
267
  });
242
- }, [filterSchema]);
268
+ }, [regularFields]);
243
269
  const handleRemoveFilter = React.useCallback((key) => {
244
- const schemaField = filterSchema.find(field => String(field.key) === key);
270
+ const schemaField = flattenedSchema.find(field => String(field.key) === key);
245
271
  if (!schemaField) {
246
272
  return;
247
273
  }
248
274
  const defaultValue = baseDefaultValues[String(schemaField.key)];
249
275
  const fieldPath = schemaField.key;
250
276
  const nextValue = resolveClearedFieldValue(schemaField, defaultValue);
277
+ // Update the form value
251
278
  formMethods.setValue(fieldPath, nextValue, {
252
279
  shouldDirty: false,
253
280
  shouldTouch: false,
254
281
  shouldValidate: false
255
282
  });
256
- const nextValues = normaliseOutputValues(filterSchema, formMethods.getValues());
257
- const nextSerialised = persistValues(nextValues);
283
+ // Compute the updated values immediately (don't wait for debounce)
284
+ const currentValues = formMethods.getValues() || {};
285
+ const updatedFormValues = Object.assign(Object.assign({}, currentValues), { [fieldPath]: nextValue });
286
+ const nextValues = normaliseOutputValues(flattenedSchema, updatedFormValues);
287
+ const nextSerialised = JSON.stringify(nextValues);
288
+ // Persist immediately (bypass debounce for remove action)
289
+ persistValues(nextValues);
290
+ // Update refs to mark this as the latest persisted state
291
+ lastPersistedValuesRef.current = nextSerialised;
292
+ currentSerialisedValuesRef.current = nextSerialised;
258
293
  hasEmittedValuesRef.current = true;
259
- lastPersistedValuesRef.current = nextSerialised !== null && nextSerialised !== void 0 ? nextSerialised : null;
260
294
  hasInitialValuesRef.current = Object.keys(nextValues).length > 0;
295
+ // Emit the change to parent
261
296
  onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(nextValues);
262
297
  }, [
263
298
  baseDefaultValues,
264
- filterSchema,
299
+ flattenedSchema,
265
300
  formMethods,
266
301
  onValuesChange,
267
302
  persistValues
@@ -269,20 +304,20 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
269
304
  const filteredFields = React.useMemo(() => {
270
305
  const normalisedTerm = searchTerm.trim().toLowerCase();
271
306
  if (!normalisedTerm) {
272
- return filterSchema;
307
+ return regularFields;
273
308
  }
274
- return filterSchema.filter(field => {
309
+ return regularFields.filter(field => {
275
310
  const searchable = [field.label, field.placeholder, String(field.key)]
276
311
  .filter(Boolean)
277
312
  .map(value => String(value).toLowerCase());
278
313
  return searchable.some(text => text.includes(normalisedTerm));
279
314
  });
280
- }, [filterSchema, searchTerm]);
315
+ }, [regularFields, searchTerm]);
281
316
  const paginatedFields = React.useMemo(() => {
282
317
  const start = (modalPage - 1) * modalPageSize;
283
318
  return filteredFields.slice(start, start + modalPageSize);
284
319
  }, [filteredFields, modalPage, modalPageSize]);
285
- const visibleFields = React.useMemo(() => filterSchema.filter(field => visibleKeys.includes(String(field.key))), [filterSchema, visibleKeys]);
320
+ const visibleFields = React.useMemo(() => regularFields.filter(field => visibleKeys.includes(String(field.key))), [regularFields, visibleKeys]);
286
321
  const renderFieldComponent = (field) => {
287
322
  const ariaLabel = getFieldAriaLabel(field);
288
323
  switch (field.type) {
@@ -309,10 +344,17 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
309
344
  });
310
345
  }
311
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;
312
351
  default:
313
352
  return (React.createElement(Text, { type: "secondary", className: "akinon-filter__unsupported" }, i18n.t('form.unsupportedField', { field: String(field.type) })));
314
353
  }
315
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
+ };
316
358
  return (React.createElement(Card, { size: "small", className: "akinon-filter shadow", "data-testid": "akifilter-root" },
317
359
  React.createElement(ConfigProvider, { theme: themeOverrides },
318
360
  React.createElement(FilterToolbar, { onOpenModal: handleOpenModal, enableImportCsv: enableImportCsv, enableImportXls: enableImportXls, onImportCsv: onImportCsv, onImportXls: onImportXls }),
@@ -320,8 +362,17 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
320
362
  React.createElement("div", { className: "akinon-filter__body" },
321
363
  React.createElement(Akiform, { layout: "vertical", className: "akinon-filter__form" },
322
364
  React.createElement("div", { className: "akinon-filter__form-grid", "data-testid": "akifilter-form-grid" },
323
- visibleFields.map(field => (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)))),
324
- 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
+ }))))),
325
376
  React.createElement(VisibilityModal, { open: isModalOpen, onClose: handleCloseModal, searchTerm: searchTerm, paginatedFields: paginatedFields, filteredCount: filteredFields.length, visibleKeys: visibleKeys, onSearchTermChange: value => {
326
377
  setSearchTerm(value);
327
378
  setModalPage(1);
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/common/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAqB/C,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,EAC9D,QAAQ,cAAc,CAAC,YAAY,CAAC,EAAE,EACtC,YAAY,MAAM,KACjB,MAKF,CAAC;AAIF,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,EAC9D,QAAQ,cAAc,CAAC,YAAY,CAAC,EAAE,EACtC,YAAY,MAAM,KACjB,MAAM,EAAE,GAAG,IAuBb,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,YAAY,MAAM,EAAE,MAAM,MAAM,EAAE,KAAG,IAQrE,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,YAAY,MAAM,KAAG,IAQrD,CAAC;AAKF,eAAO,MAAM,gBAAgB,GAAI,YAAY,SAAS,WAAW,EAC/D,QAAQ,cAAc,CAAC,YAAY,CAAC,EAAE,EACtC,YAAY,MAAM,KACjB,OAAO,CAAC,YAAY,CAAC,GAAG,IA+B1B,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,YAAY,SAAS,WAAW,EAChE,YAAY,MAAM,EAClB,QAAQ,OAAO,CAAC,YAAY,CAAC,KAC5B,IAeF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,YAAY,MAAM,KAAG,IAQtD,CAAC"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/common/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAqB/C,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,EAC9D,QAAQ,cAAc,CAAC,YAAY,CAAC,EAAE,EACtC,YAAY,MAAM,KACjB,MAKF,CAAC;AAIF,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,EAC9D,QAAQ,cAAc,CAAC,YAAY,CAAC,EAAE,EACtC,YAAY,MAAM,KACjB,MAAM,EAAE,GAAG,IAoBb,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,YAAY,MAAM,EAAE,MAAM,MAAM,EAAE,KAAG,IAQrE,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,YAAY,MAAM,KAAG,IAQrD,CAAC;AAKF,eAAO,MAAM,gBAAgB,GAAI,YAAY,SAAS,WAAW,EAC/D,QAAQ,cAAc,CAAC,YAAY,CAAC,EAAE,EACtC,YAAY,MAAM,KACjB,OAAO,CAAC,YAAY,CAAC,GAAG,IA+B1B,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,YAAY,SAAS,WAAW,EAChE,YAAY,MAAM,EAClB,QAAQ,OAAO,CAAC,YAAY,CAAC,KAC5B,IAeF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,YAAY,MAAM,KAAG,IAQtD,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { i18n } from '../i18n';
1
2
  const STORAGE_PREFIX = 'akifilter:v1';
2
3
  const VALUES_SUFFIX = ':values';
3
4
  const hashString = (value) => {
@@ -35,7 +36,7 @@ export const readVisibleKeys = (schema, storageKey) => {
35
36
  return parsed.filter(key => schemaKeys.has(String(key)));
36
37
  }
37
38
  catch (error) {
38
- console.warn('Akifilter: unable to read visibility state from storage', error);
39
+ console.warn(i18n.t('errors.storage.readVisibility'), error);
39
40
  return null;
40
41
  }
41
42
  };
@@ -46,7 +47,7 @@ export const writeVisibleKeys = (storageKey, keys) => {
46
47
  window.localStorage.setItem(storageKey, JSON.stringify(keys));
47
48
  }
48
49
  catch (error) {
49
- console.warn('Akifilter: unable to persist visibility state', error);
50
+ console.warn(i18n.t('errors.storage.writeVisibility'), error);
50
51
  }
51
52
  };
52
53
  export const clearVisibleKeys = (storageKey) => {
@@ -56,7 +57,7 @@ export const clearVisibleKeys = (storageKey) => {
56
57
  window.localStorage.removeItem(storageKey);
57
58
  }
58
59
  catch (error) {
59
- console.warn('Akifilter: unable to clear visibility state', error);
60
+ console.warn(i18n.t('errors.storage.clearVisibility'), error);
60
61
  }
61
62
  };
62
63
  const buildValuesStorageKey = (storageKey) => `${storageKey}${VALUES_SUFFIX}`;
@@ -82,7 +83,7 @@ export const readStoredValues = (schema, storageKey) => {
82
83
  }, {});
83
84
  }
84
85
  catch (error) {
85
- console.warn('Akifilter: unable to read values from storage', error);
86
+ console.warn(i18n.t('errors.storage.readValues'), error);
86
87
  return null;
87
88
  }
88
89
  };
@@ -99,7 +100,7 @@ export const writeStoredValues = (storageKey, values) => {
99
100
  window.localStorage.setItem(valuesKey, JSON.stringify(values));
100
101
  }
101
102
  catch (error) {
102
- console.warn('Akifilter: unable to persist values', error);
103
+ console.warn(i18n.t('errors.storage.writeValues'), error);
103
104
  }
104
105
  };
105
106
  export const clearStoredValues = (storageKey) => {
@@ -109,6 +110,6 @@ export const clearStoredValues = (storageKey) => {
109
110
  window.localStorage.removeItem(buildValuesStorageKey(storageKey));
110
111
  }
111
112
  catch (error) {
112
- console.warn('Akifilter: unable to clear stored values', error);
113
+ console.warn(i18n.t('errors.storage.clearValues'), error);
113
114
  }
114
115
  };
@@ -1,4 +1,10 @@
1
1
  export declare const DEFAULT_VISIBLE_COUNT = 8;
2
2
  export declare const DEFAULT_MODAL_PAGE_SIZE = 40;
3
3
  export declare const FILTER_DEBOUNCE_DELAY = 300;
4
+ export declare const DATE_FORMAT = "YYYY-MM-DD";
5
+ export declare const DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss";
6
+ export declare const BOOLEAN_STRING: {
7
+ readonly TRUE: "true";
8
+ readonly FALSE: "false";
9
+ };
4
10
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,eAAO,MAAM,qBAAqB,MAAM,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC,eAAO,MAAM,WAAW,eAAe,CAAC;AACxC,eAAO,MAAM,eAAe,wBAAwB,CAAC;AAErD,eAAO,MAAM,cAAc;;;CAGjB,CAAC"}
@@ -1,3 +1,9 @@
1
1
  export const DEFAULT_VISIBLE_COUNT = 8;
2
2
  export const DEFAULT_MODAL_PAGE_SIZE = 40;
3
3
  export const FILTER_DEBOUNCE_DELAY = 300;
4
+ export const DATE_FORMAT = 'YYYY-MM-DD';
5
+ export const DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
6
+ export const BOOLEAN_STRING = {
7
+ TRUE: 'true',
8
+ FALSE: 'false'
9
+ };
@@ -29,6 +29,14 @@ declare const translations: {
29
29
  readonly title: "Akifilter failed to load";
30
30
  readonly description: "An unexpected error occurred while rendering the filters. Try again or refresh the page.";
31
31
  readonly retry: "Try again";
32
+ readonly storage: {
33
+ readonly readVisibility: "Akifilter: unable to read visibility state from storage";
34
+ readonly writeVisibility: "Akifilter: unable to persist visibility state";
35
+ readonly clearVisibility: "Akifilter: unable to clear visibility state";
36
+ readonly readValues: "Akifilter: unable to read values from storage";
37
+ readonly writeValues: "Akifilter: unable to persist values";
38
+ readonly clearValues: "Akifilter: unable to clear stored values";
39
+ };
32
40
  };
33
41
  };
34
42
  export default translations;
@@ -1 +1 @@
1
- {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../../src/i18n/translations/en.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCR,CAAC;AAEX,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../../src/i18n/translations/en.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCR,CAAC;AAEX,eAAe,YAAY,CAAC"}
@@ -28,7 +28,15 @@ const translations = {
28
28
  errors: {
29
29
  title: 'Akifilter failed to load',
30
30
  description: 'An unexpected error occurred while rendering the filters. Try again or refresh the page.',
31
- retry: 'Try again'
31
+ retry: 'Try again',
32
+ storage: {
33
+ readVisibility: 'Akifilter: unable to read visibility state from storage',
34
+ writeVisibility: 'Akifilter: unable to persist visibility state',
35
+ clearVisibility: 'Akifilter: unable to clear visibility state',
36
+ readValues: 'Akifilter: unable to read values from storage',
37
+ writeValues: 'Akifilter: unable to persist values',
38
+ clearValues: 'Akifilter: unable to clear stored values'
39
+ }
32
40
  }
33
41
  };
34
42
  export default translations;
@@ -29,6 +29,14 @@ declare const translations: {
29
29
  readonly title: "Akifilter yüklenemedi";
30
30
  readonly description: "Filtreler render edilirken beklenmeyen bir hata oluştu. Lütfen tekrar deneyin veya sayfayı yenileyin.";
31
31
  readonly retry: "Tekrar dene";
32
+ readonly storage: {
33
+ readonly readVisibility: "Akifilter: görünürlük durumu depodan okunamadı";
34
+ readonly writeVisibility: "Akifilter: görünürlük durumu kalıcı hale getirilemedi";
35
+ readonly clearVisibility: "Akifilter: görünürlük durumu temizlenemedi";
36
+ readonly readValues: "Akifilter: değerler depodan okunamadı";
37
+ readonly writeValues: "Akifilter: değerler kalıcı hale getirilemedi";
38
+ readonly clearValues: "Akifilter: kayıtlı değerler temizlenemedi";
39
+ };
32
40
  };
33
41
  };
34
42
  export default translations;
@@ -1 +1 @@
1
- {"version":3,"file":"tr.d.ts","sourceRoot":"","sources":["../../../../src/i18n/translations/tr.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCR,CAAC;AAEX,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"tr.d.ts","sourceRoot":"","sources":["../../../../src/i18n/translations/tr.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCR,CAAC;AAEX,eAAe,YAAY,CAAC"}
@@ -28,7 +28,15 @@ const translations = {
28
28
  errors: {
29
29
  title: 'Akifilter yüklenemedi',
30
30
  description: 'Filtreler render edilirken beklenmeyen bir hata oluştu. Lütfen tekrar deneyin veya sayfayı yenileyin.',
31
- retry: 'Tekrar dene'
31
+ retry: 'Tekrar dene',
32
+ storage: {
33
+ readVisibility: 'Akifilter: görünürlük durumu depodan okunamadı',
34
+ writeVisibility: 'Akifilter: görünürlük durumu kalıcı hale getirilemedi',
35
+ clearVisibility: 'Akifilter: görünürlük durumu temizlenemedi',
36
+ readValues: 'Akifilter: değerler depodan okunamadı',
37
+ writeValues: 'Akifilter: değerler kalıcı hale getirilemedi',
38
+ clearValues: 'Akifilter: kayıtlı değerler temizlenemedi'
39
+ }
32
40
  }
33
41
  };
34
42
  export default translations;
@@ -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"}
@@ -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
  };
@@ -1 +1 @@
1
- {"version":3,"file":"values.d.ts","sourceRoot":"","sources":["../../../src/utils/values.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhE,eAAO,MAAM,kBAAkB,GAC7B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,OAAO,cAAc,CAAC,YAAY,CAAC,GAAG,SAAS,EAC/C,OAAO,OAAO,KACb,OAkBF,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,SAAS,OAAO,CAAC,YAAY,CAAC,GAAG,WAAW,KAC3C,OAAO,CAAC,YAAY,CA8BtB,CAAC"}
1
+ {"version":3,"file":"values.d.ts","sourceRoot":"","sources":["../../../src/utils/values.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,kBAAkB,GAC7B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,OAAO,cAAc,CAAC,YAAY,CAAC,GAAG,SAAS,EAC/C,OAAO,OAAO,KACb,OAkBF,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,SAAS,OAAO,CAAC,YAAY,CAAC,GAAG,WAAW,KAC3C,OAAO,CAAC,YAAY,CAkCtB,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { akidate } from '@akinon/akidate';
2
+ import { DATE_FORMAT, DATETIME_FORMAT } from '../constants';
2
3
  export const shouldPersistValue = (field, value) => {
3
4
  if (value === undefined || value === null) {
4
5
  return false;
@@ -30,7 +31,10 @@ export const normaliseOutputValues = (schema, values) => {
30
31
  if (!normalised) {
31
32
  return acc;
32
33
  }
33
- normalisedValue = akidate.formatIsoDate(normalised, 'YYYY-MM-DD');
34
+ // Format with time if showTime is enabled, otherwise date only
35
+ const hasShowTime = 'showTime' in field && field.showTime;
36
+ const format = hasShowTime ? DATETIME_FORMAT : DATE_FORMAT;
37
+ normalisedValue = akidate.formatIsoDate(normalised, format);
34
38
  }
35
39
  acc[key] =
36
40
  normalisedValue;