venice-ui 2.3.6 → 2.4.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.
@@ -32,166 +32,286 @@ const Icons_1 = require("../Icons");
32
32
  const Modal_1 = require("../Modal");
33
33
  const Filters_styles_1 = require("./Filters.styles");
34
34
  const Theme_1 = require("../../Theme");
35
- const common_1 = require("../common");
36
35
  const Input_1 = require("../Input");
37
36
  const Chips_1 = require("../Chips");
38
- const Filters = ({ theme = Theme_1.mainTheme, title = 'Select filters', labelConfirm = 'Approve', labelClose = "Cancel", filters = [], labelClearAll = 'Clear all filters', handleSubmitFilters }) => {
39
- const generateStateObj = () => {
37
+ const List_1 = require("../List");
38
+ const Filters = ({ theme = Theme_1.mainTheme, title = 'Select filters', labelConfirm = 'Approve', labelClose = 'Cancel', filters = [], headers = [], elements = [], fullElements, labelClearAll = 'Wyczyść', handleSubmitFilters, externalClearSignal, onClear, }) => {
39
+ const filtersConfig = (0, react_1.useMemo)(() => {
40
+ const uniq = (arr) => Array.from(new Set(arr.filter((v) => v !== undefined && v !== null)));
41
+ if (filters && filters.length) {
42
+ return filters.map((h) => ({
43
+ label: h.label,
44
+ name: h.name,
45
+ type: h.type || 'select',
46
+ allowValues: (h.allowValues || []).map((v) => String(v)),
47
+ min: h.min,
48
+ max: h.max,
49
+ collapsible: h.collapsible ?? true,
50
+ }));
51
+ }
52
+ if (!headers || headers.length === 0)
53
+ return [];
54
+ return headers
55
+ .filter((h) => !h.action)
56
+ .map((h) => {
57
+ const key = h.valueSource || h.name;
58
+ const values = (elements || []).map((it) => it?.[key]).filter((v) => v !== undefined && v !== null);
59
+ const nameLower = (h.name || '').toString().toLowerCase();
60
+ if (nameLower === 'age' || nameLower.includes('age')) {
61
+ const nums = values.map((v) => Number(v)).filter((n) => !Number.isNaN(n));
62
+ const min = nums.length ? Math.min(...nums) : 0;
63
+ const max = nums.length ? Math.max(...nums) : 0;
64
+ return { label: h.name, name: key, type: 'range', min, max, collapsible: true };
65
+ }
66
+ if (h.scope && h.scope.length) {
67
+ return { label: h.name, name: key, type: 'select', allowValues: h.scope.map((s) => (s.label ?? s.value ?? s.key ?? String(s))), collapsible: true };
68
+ }
69
+ if (h.date) {
70
+ const dates = values.map((v) => (v ? new Date(v).getTime() : NaN)).filter((d) => !Number.isNaN(d));
71
+ const min = dates.length ? new Date(Math.min(...dates)).toISOString() : undefined;
72
+ const max = dates.length ? new Date(Math.max(...dates)).toISOString() : undefined;
73
+ return { label: h.name, name: key, type: 'date', min, max, collapsible: true };
74
+ }
75
+ if (h.isCount) {
76
+ const nums = values.map((v) => Number(v)).filter((n) => !Number.isNaN(n));
77
+ return { label: h.name, name: key, type: 'range', min: nums.length ? Math.min(...nums) : 0, max: nums.length ? Math.max(...nums) : 0, collapsible: true };
78
+ }
79
+ const numericValues = values.map((v) => Number(v)).filter((n) => !Number.isNaN(n));
80
+ if (numericValues.length === values.length && values.length > 0) {
81
+ return { label: h.name, name: key, type: 'range', min: Math.min(...numericValues), max: Math.max(...numericValues), collapsible: true };
82
+ }
83
+ return { label: h.name, name: key, type: 'select', allowValues: uniq(values).map((v) => String(v)), collapsible: true };
84
+ });
85
+ }, [filters, headers, elements]);
86
+ const generateStateObj = (0, react_1.useCallback)(() => {
40
87
  const obj = {};
41
- filters.forEach((filter) => {
88
+ filtersConfig.forEach((filter) => {
42
89
  obj[filter.name] = {
43
90
  name: filter.name,
44
91
  type: filter.type,
45
92
  value: [],
46
93
  rangeMin: filter.min,
47
94
  rangeMax: filter.max,
48
- min: '',
49
- max: '',
95
+ min: (filter.type === 'range' || filter.type === 'date') && filter.min !== undefined ? String(filter.min) : '',
96
+ max: (filter.type === 'range' || filter.type === 'date') && filter.max !== undefined ? String(filter.max) : '',
50
97
  };
51
98
  });
52
99
  return obj;
53
- };
100
+ }, [filtersConfig]);
54
101
  const [openFilters, setOpenFilters] = (0, react_1.useState)(false);
55
102
  const [activeFilters, setActiveFilters] = (0, react_1.useState)(generateStateObj());
56
- const toogleFilter = () => {
57
- setOpenFilters(!openFilters);
58
- };
59
- const isFilterActive = (filter) => {
60
- let isActive = false;
61
- if (filter.type === 'select') {
62
- isActive = filter.value.length > 0;
63
- }
64
- else if (filter.type === 'range') {
65
- isActive = filter.min.length > 0 || filter.max.length > 0;
103
+ const [collapsedFilters, setCollapsedFilters] = (0, react_1.useState)({});
104
+ (0, react_1.useEffect)(() => {
105
+ const init = {};
106
+ filtersConfig.forEach((f) => (init[f.name] = false));
107
+ const prevKeys = Object.keys(collapsedFilters).sort().join(',');
108
+ const newKeys = Object.keys(init).sort().join(',');
109
+ if (prevKeys !== newKeys)
110
+ setCollapsedFilters(init);
111
+ }, [filtersConfig]);
112
+ const toggleCollapse = (0, react_1.useCallback)((name) => {
113
+ setCollapsedFilters((prev) => ({ ...prev, [name]: !prev[name] }));
114
+ }, []);
115
+ const toggleFilter = (0, react_1.useCallback)(() => setOpenFilters((v) => !v), []);
116
+ const isFilterActive = (0, react_1.useCallback)((filter) => {
117
+ if (filter.type === 'select' || filter.type === 'text') {
118
+ return !!(filter.value && filter.value.length > 0);
66
119
  }
67
- return isActive;
68
- };
69
- const isAnyFilterActive = () => {
70
- const activs = Object.keys(activeFilters).find((key) => isFilterActive(activeFilters[key]));
71
- if (activs) {
72
- return activs.length > 0;
120
+ if (filter.type === 'range' || filter.type === 'date') {
121
+ const defaultMin = filter.rangeMin !== undefined ? String(filter.rangeMin) : '';
122
+ const defaultMax = filter.rangeMax !== undefined ? String(filter.rangeMax) : '';
123
+ const curMin = filter.min ?? '';
124
+ const curMax = filter.max ?? '';
125
+ return (curMin !== '' && curMin !== defaultMin) || (curMax !== '' && curMax !== defaultMax);
73
126
  }
74
127
  return false;
75
- };
76
- const setIconColor = () => {
77
- return isAnyFilterActive() ? theme.action : theme.textColor;
78
- };
79
- const clearAll = () => {
80
- const resetedFilter = { ...activeFilters };
81
- Object.keys(resetedFilter).forEach((key) => {
82
- if (resetedFilter[key].type === 'select') {
83
- resetedFilter[key].value = [];
84
- }
85
- else if (resetedFilter[key].type === 'range') {
86
- resetedFilter[key].min = '';
87
- resetedFilter[key].max = '';
88
- }
128
+ }, []);
129
+ const isAnyFilterActive = (0, react_1.useCallback)(() => Object.values(activeFilters).some((f) => isFilterActive(f)), [activeFilters, isFilterActive]);
130
+ const setIconColor = (0, react_1.useCallback)(() => (isAnyFilterActive() ? theme.action : theme.textColor), [isAnyFilterActive, theme.action, theme.textColor]);
131
+ const clearAll = (0, react_1.useCallback)(() => {
132
+ const resetedFilter = {};
133
+ Object.keys(activeFilters).forEach((key) => {
134
+ const f = activeFilters[key];
135
+ resetedFilter[key] = {
136
+ ...f,
137
+ value: f.type === 'select' || f.type === 'text' ? [] : f.value,
138
+ min: (f.type === 'range' || f.type === 'date') && f.rangeMin !== undefined ? String(f.rangeMin) : '',
139
+ max: (f.type === 'range' || f.type === 'date') && f.rangeMax !== undefined ? String(f.rangeMax) : '',
140
+ };
89
141
  });
90
142
  return resetedFilter;
91
- };
92
- const resetAllFilters = () => {
143
+ }, [activeFilters]);
144
+ const resetAllFilters = (0, react_1.useCallback)(() => {
93
145
  const cleared = clearAll();
94
- setActiveFilters({
95
- ...cleared,
96
- });
97
- };
98
- const resetAllFiltersAndSubmit = () => {
146
+ setActiveFilters({ ...cleared });
147
+ }, [clearAll]);
148
+ const resetAllFiltersAndSubmit = (0, react_1.useCallback)(() => {
99
149
  resetAllFilters();
100
150
  applyFilter();
101
- };
102
- const resetFilter = (filterName, filterType) => {
103
- if (filterType === 'select') {
104
- setActiveFilters({
105
- ...activeFilters,
106
- [filterName]: {
107
- ...activeFilters[filterName],
108
- value: [],
109
- },
110
- });
111
- }
112
- if (filterType === 'range') {
113
- setActiveFilters({
114
- ...activeFilters,
115
- [filterName]: {
116
- ...activeFilters[filterName],
117
- min: '',
118
- max: '',
119
- },
120
- });
121
- }
122
- };
123
- const setSelectFilter = (filterValue, filterName) => {
124
- let values = [];
125
- const existingValues = activeFilters[filterName].value;
126
- if (existingValues.includes(filterValue)) {
127
- values = existingValues.filter((item) => item !== filterValue);
128
- }
129
- else {
130
- existingValues.push(filterValue);
131
- values = [...existingValues];
132
- }
133
- setActiveFilters({
134
- ...activeFilters,
135
- [filterName]: {
136
- ...activeFilters[filterName],
137
- value: values,
138
- },
139
- });
140
- };
141
- const setRangeFilter = (name, value) => {
142
- const filterName = name.split('_')[0];
143
- const filterParam = name.split('_')[1];
144
- setActiveFilters({
145
- ...activeFilters,
146
- [filterName]: {
147
- ...activeFilters[filterName],
148
- [filterParam]: value,
149
- },
151
+ // eslint-disable-next-line react-hooks/exhaustive-deps
152
+ }, [resetAllFilters]);
153
+ const resetFilter = (0, react_1.useCallback)((filterName, filterType) => {
154
+ setActiveFilters((prev) => {
155
+ const current = prev[filterName] || {};
156
+ if (filterType === 'select' || filterType === 'text') {
157
+ return { ...prev, [filterName]: { ...current, value: [] } };
158
+ }
159
+ if (filterType === 'range' || filterType === 'date') {
160
+ const defaultMin = current.rangeMin !== undefined ? String(current.rangeMin) : '';
161
+ const defaultMax = current.rangeMax !== undefined ? String(current.rangeMax) : '';
162
+ return { ...prev, [filterName]: { ...current, min: defaultMin, max: defaultMax } };
163
+ }
164
+ return prev;
150
165
  });
151
- };
152
- const applyFilter = () => {
153
- const obj = [];
166
+ }, []);
167
+ const setRangeFilter = (0, react_1.useCallback)((name, value) => {
168
+ const [filterName, filterParam] = String(name).split('_');
169
+ setActiveFilters((prev) => ({ ...prev, [filterName]: { ...prev[filterName], [filterParam]: value } }));
170
+ }, []);
171
+ const setTextFilter = (0, react_1.useCallback)((name, value) => {
172
+ const filterName = (name || '').split('_')[0];
173
+ setActiveFilters((prev) => ({ ...prev, [filterName]: { ...(prev[filterName] || { name: filterName, type: 'text', value: [] }), value: value ? [String(value)] : [] } }));
174
+ }, []);
175
+ const applyFilter = (0, react_1.useCallback)(() => {
176
+ let result = (elements || []).slice();
154
177
  Object.keys(activeFilters).forEach((key) => {
155
- if (activeFilters[key].type === 'select') {
156
- if (activeFilters[key].value.length > 0) {
157
- obj.push({
158
- name: activeFilters[key].name,
159
- type: activeFilters[key].type,
160
- value: activeFilters[key].value,
161
- });
178
+ const f = activeFilters[key];
179
+ if (!f)
180
+ return;
181
+ if (f.type === 'select') {
182
+ if (f.value && f.value.length > 0) {
183
+ const sel = new Set(f.value.map((v) => String(v)));
184
+ result = result.filter((it) => sel.has(String(it?.[key])));
162
185
  }
163
186
  }
164
- else if (activeFilters[key].type === 'range') {
165
- if (activeFilters[key].min.length > 0 || activeFilters[key].max.length > 0) {
166
- obj.push({
167
- name: activeFilters[key].name,
168
- type: activeFilters[key].type,
169
- min: activeFilters[key].min.length > 0 ? activeFilters[key].min : undefined,
170
- max: activeFilters[key].max.length > 0 ? activeFilters[key].max : undefined,
171
- });
187
+ else if (f.type === 'range') {
188
+ if (!isFilterActive(f))
189
+ return;
190
+ const min = f.min !== '' ? Number(f.min) : undefined;
191
+ const max = f.max !== '' ? Number(f.max) : undefined;
192
+ result = result.filter((it) => {
193
+ const val = Number(it?.[key]);
194
+ if (Number.isNaN(val))
195
+ return false;
196
+ if (min !== undefined && val < min)
197
+ return false;
198
+ if (max !== undefined && val > max)
199
+ return false;
200
+ return true;
201
+ });
202
+ }
203
+ else if (f.type === 'date') {
204
+ if (!isFilterActive(f))
205
+ return;
206
+ const fromDate = f.min ? new Date(f.min) : null;
207
+ const toDate = f.max ? new Date(f.max) : null;
208
+ const fromTs = fromDate ? new Date(fromDate.getFullYear(), fromDate.getMonth(), fromDate.getDate(), 0, 0, 0, 0).getTime() : null;
209
+ const toTs = toDate ? new Date(toDate.getFullYear(), toDate.getMonth(), toDate.getDate(), 23, 59, 59, 999).getTime() : null;
210
+ result = result.filter((it) => {
211
+ const raw = it?.[key];
212
+ if (!raw)
213
+ return false;
214
+ const d = new Date(raw);
215
+ if (Number.isNaN(d.getTime()))
216
+ return false;
217
+ const ts = d.getTime();
218
+ if (fromTs !== null && ts < fromTs)
219
+ return false;
220
+ if (toTs !== null && ts > toTs)
221
+ return false;
222
+ return true;
223
+ });
224
+ }
225
+ else {
226
+ const q = Array.isArray(f.value) ? f.value[0] : f.value;
227
+ if (q) {
228
+ const qs = String(q).toLowerCase();
229
+ result = result.filter((it) => String(it?.[key] ?? '').toLowerCase().includes(qs));
172
230
  }
173
231
  }
174
232
  });
175
- handleSubmitFilters && handleSubmitFilters(obj);
233
+ handleSubmitFilters && handleSubmitFilters(result);
176
234
  setOpenFilters(false);
177
- };
235
+ }, [activeFilters, elements, handleSubmitFilters, isFilterActive]);
236
+ const prevConfigKeysRef = (0, react_1.useRef)(null);
237
+ (0, react_1.useEffect)(() => {
238
+ const newState = generateStateObj();
239
+ const keysSerialized = JSON.stringify(Object.keys(newState));
240
+ if (prevConfigKeysRef.current !== keysSerialized) {
241
+ setActiveFilters(newState);
242
+ prevConfigKeysRef.current = keysSerialized;
243
+ }
244
+ }, [generateStateObj]);
245
+ const prevExternalClearRef = (0, react_1.useRef)(externalClearSignal ?? 0);
246
+ (0, react_1.useEffect)(() => {
247
+ if (externalClearSignal === undefined)
248
+ return;
249
+ if (prevExternalClearRef.current !== externalClearSignal) {
250
+ prevExternalClearRef.current = externalClearSignal;
251
+ const cleared = clearAll();
252
+ setActiveFilters(cleared);
253
+ handleSubmitFilters && handleSubmitFilters((fullElements && fullElements.length ? fullElements : elements) || []);
254
+ }
255
+ }, [externalClearSignal, clearAll, handleSubmitFilters, fullElements, elements]);
256
+ const mapListItems = (0, react_1.useCallback)((filterName, allowValues) => {
257
+ const sel = (activeFilters[filterName]?.value || []).map(String);
258
+ return (allowValues || []).map((v) => ({ id: String(v), value: String(v), children: [], selected: sel.includes(String(v)) }));
259
+ }, [activeFilters]);
178
260
  return (react_1.default.createElement(styled_components_1.ThemeProvider, { theme: theme },
179
261
  react_1.default.createElement(Filters_styles_1.FilterIconArea, null,
180
- react_1.default.createElement(Icons_1.Icon, { name: "filters", onClick: toogleFilter, iconColor: setIconColor() }),
181
- isAnyFilterActive() && (react_1.default.createElement(Chips_1.Chips, { label: labelClearAll, handleClose: resetAllFiltersAndSubmit }))),
182
- openFilters && (react_1.default.createElement(Modal_1.Modal, { title: title, handleConfirm: applyFilter, handleClose: toogleFilter, handleAdditional: resetAllFilters, labelConfirm: labelConfirm, labelClose: labelClose, labelAdditional: labelClearAll, isOpen: openFilters },
183
- react_1.default.createElement(Aligment_1.Aligment, { direction: "column", align: 'flex-end' },
184
- react_1.default.createElement(Filters_styles_1.FiltersArea, null, filters.map((filter) => (react_1.default.createElement(Filters_styles_1.FilterSection, null,
185
- react_1.default.createElement(Aligment_1.Aligment, { wrap: "nowrap" },
186
- react_1.default.createElement(Typography_1.TextAccent, null, filter.label),
262
+ react_1.default.createElement(Icons_1.Icon, { name: "filters", onClick: toggleFilter, iconColor: setIconColor() }),
263
+ isAnyFilterActive() && (react_1.default.createElement(Chips_1.Chips, { label: labelClearAll, handleClose: () => {
264
+ if (onClear) {
265
+ onClear();
266
+ const cleared = clearAll();
267
+ setActiveFilters(cleared);
268
+ }
269
+ else {
270
+ const cleared = clearAll();
271
+ setActiveFilters(cleared);
272
+ handleSubmitFilters && handleSubmitFilters((fullElements && fullElements.length ? fullElements : elements) || []);
273
+ }
274
+ } }))),
275
+ openFilters && (react_1.default.createElement(Modal_1.Modal, { title: title, handleConfirm: applyFilter, handleClose: toggleFilter, handleAdditional: () => {
276
+ if (onClear) {
277
+ onClear();
278
+ const cleared = clearAll();
279
+ setActiveFilters(cleared);
280
+ setOpenFilters(false);
281
+ }
282
+ else {
283
+ resetAllFiltersAndSubmit();
284
+ }
285
+ }, labelConfirm: labelConfirm, labelClose: labelClose, labelAdditional: labelClearAll, isOpen: openFilters },
286
+ react_1.default.createElement(Aligment_1.Aligment, { direction: "column", align: "flex-end" },
287
+ react_1.default.createElement(Filters_styles_1.FiltersArea, null, filtersConfig.map((filter) => (react_1.default.createElement(Filters_styles_1.FilterSection, { key: filter.name },
288
+ react_1.default.createElement(Aligment_1.Aligment, { wrap: "nowrap", style: { alignItems: 'center', justifyContent: 'start' } },
289
+ react_1.default.createElement(Aligment_1.Aligment, { wrap: "nowrap", onClick: () => filter.collapsible && toggleCollapse(filter.name), style: { cursor: filter.collapsible ? 'pointer' : 'default', alignItems: 'center', justifyContent: 'start' } },
290
+ react_1.default.createElement(Typography_1.TextAccent, { style: { marginRight: 0 } }, filter.label),
291
+ react_1.default.createElement(Icons_1.Icon, { name: collapsedFilters[filter.name] ? 'arrow_drop_down' : 'arrow_drop_up', size: 16, iconColor: theme.textColor })),
187
292
  react_1.default.createElement(Icons_1.Icon, { name: "close", size: 14, onClick: () => resetFilter(filter.name, filter.type) })),
188
- filter.type === 'select' && (react_1.default.createElement(Aligment_1.Aligment, { wrap: "nowrap", direction: "column", align: "flex-start", vPadding: Theme_1.Theme.padding.s }, filter.allowValues?.map((item) => {
189
- return (react_1.default.createElement(common_1.PanelOption, { width: "100%", key: item, active: activeFilters[filter.name].value.includes(item), onClick: () => setSelectFilter(item, filter.name) }, item));
190
- }))),
191
- filter.type === 'range' && (react_1.default.createElement(Aligment_1.Aligment, { wrap: "nowrap", direction: "row", align: "flex-start", vPadding: Theme_1.Theme.padding.s, justify: "flex-start", gap: 8 },
293
+ !filter.collapsible || !collapsedFilters[filter.name] ? (filter.type === 'select' ? (react_1.default.createElement("div", { style: { width: '100%' } },
294
+ react_1.default.createElement(List_1.List, { items: mapListItems(filter.name, filter.allowValues), handleChange: (items) => {
295
+ const collectSelected = (list) => list.reduce((acc, it) => {
296
+ if (it.selected)
297
+ acc.push(String(it.id));
298
+ if (it.children && it.children.length)
299
+ acc.push(...collectSelected(it.children));
300
+ return acc;
301
+ }, []);
302
+ const selectedIds = collectSelected(items);
303
+ setActiveFilters((prev) => ({ ...prev, [filter.name]: { ...(prev[filter.name] || { name: filter.name, type: filter.type, value: [] }), value: selectedIds } }));
304
+ }, isCheckbox: true, isCollapsable: true }))) : filter.type === 'range' ? (react_1.default.createElement(Aligment_1.Aligment, { wrap: "nowrap", direction: "row", align: "flex-start", vPadding: Theme_1.Theme.padding.s, justify: "flex-start", gap: 8 },
305
+ react_1.default.createElement(Filters_styles_1.FilterInputWrapper, null,
306
+ react_1.default.createElement(Input_1.Input, { type: "number", name: `${filter.name}_min`, value: activeFilters[filter.name].min, handleChange: setRangeFilter })),
307
+ react_1.default.createElement(Filters_styles_1.FilterInputWrapper, null,
308
+ react_1.default.createElement(Input_1.Input, { type: "number", name: `${filter.name}_max`, value: activeFilters[filter.name].max, handleChange: setRangeFilter })))) : filter.type === 'date' ? (react_1.default.createElement(Aligment_1.Aligment, { wrap: "nowrap", direction: "row", align: "flex-start", vPadding: Theme_1.Theme.padding.s, justify: "flex-start", gap: 8 },
309
+ react_1.default.createElement(Filters_styles_1.FilterInputWrapper, null,
310
+ react_1.default.createElement(Input_1.Input, { type: "date", name: `${filter.name}_min`, value: activeFilters[filter.name].min, handleChange: setRangeFilter })),
192
311
  react_1.default.createElement(Filters_styles_1.FilterInputWrapper, null,
193
- react_1.default.createElement(Input_1.Input, { type: "number", name: `${filter.name}_min`, value: activeFilters[filter.name].min, placeholder: activeFilters[filter.name].rangeMin, handleChange: setRangeFilter })),
312
+ react_1.default.createElement(Input_1.Input, { type: "date", name: `${filter.name}_max`, value: activeFilters[filter.name].max, handleChange: setRangeFilter })))) : filter.type === 'text' ? (react_1.default.createElement(Aligment_1.Aligment, { wrap: "nowrap", direction: "row", align: "flex-start", vPadding: Theme_1.Theme.padding.s, justify: "flex-start", gap: 8 },
194
313
  react_1.default.createElement(Filters_styles_1.FilterInputWrapper, null,
195
- react_1.default.createElement(Input_1.Input, { type: "number", name: `${filter.name}_max`, value: activeFilters[filter.name].max, placeholder: activeFilters[filter.name].rangeMax, handleChange: setRangeFilter })))))))))))));
314
+ react_1.default.createElement(Input_1.Input, { type: "text", name: `${filter.name}_query`, value: (activeFilters[filter.name]?.value && activeFilters[filter.name].value[0]) || '', handleChange: setTextFilter })))) : null) : null)))))))));
196
315
  };
197
316
  exports.Filters = Filters;
317
+ exports.default = exports.Filters;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.applyFiltersToTable = exports.setTableFilterSet = exports.getMaxValue = exports.getMinValue = exports.getUniqueValues = void 0;
3
+ exports.applyFiltersToTable = exports.getMaxValue = exports.getMinValue = exports.getUniqueValues = void 0;
4
4
  const getUniqueValues = (elements, source) => {
5
5
  const result = [...new Set(elements.map((item) => item[source]))];
6
6
  return result;
@@ -14,21 +14,6 @@ const getMaxValue = (elements, source) => {
14
14
  return Math.max(...elements.map((item) => item[source]));
15
15
  };
16
16
  exports.getMaxValue = getMaxValue;
17
- const setTableFilterSet = (headers, elements) => {
18
- const filterSetting = [];
19
- headers.forEach((filter) => {
20
- filterSetting.push({
21
- label: filter.name,
22
- name: filter.valueSource,
23
- type: filter.filterType ? filter.filterType : 'select',
24
- allowValues: (0, exports.getUniqueValues)(elements, filter.valueSource),
25
- min: (0, exports.getMinValue)(elements, filter.valueSource),
26
- max: (0, exports.getMaxValue)(elements, filter.valueSource),
27
- });
28
- });
29
- return filterSetting;
30
- };
31
- exports.setTableFilterSet = setTableFilterSet;
32
17
  const applyFiltersToTable = (elements, filters) => {
33
18
  let filtered = [...elements];
34
19
  filters.forEach((filter) => {
@@ -30,7 +30,6 @@ const Aligment_1 = require("../Aligment");
30
30
  const Input_styles_1 = require("./Input.styles");
31
31
  const Icons_1 = require("../Icons");
32
32
  const Theme_1 = require("../../Theme");
33
- const lodash_1 = require("lodash");
34
33
  const Input = ({ label, labelPosition = 'top', value, type = 'text', name, disabled = false, size = 'medium', handleChange, handleSubmit, width, error = false, errorMsg, placeholder, min, max, step = 0.1, autoFocus, theme = Theme_1.mainTheme, readOnly = false, prefix, }) => {
35
34
  const [inputValue, setInputValue] = (0, react_1.useState)(value);
36
35
  (0, react_1.useEffect)(() => {
@@ -41,19 +40,14 @@ const Input = ({ label, labelPosition = 'top', value, type = 'text', name, disab
41
40
  ? numberValue
42
41
  : parseFloat(numberValue);
43
42
  };
44
- const debouncedRef = (0, react_1.useRef)((0, lodash_1.debounce)((n, v) => {
45
- console.log('checking debounce', n, v);
46
- handleChange(n, v);
47
- }, 300));
48
43
  const onChange = (e) => {
49
44
  let returnedValue = type === 'number' || type === 'increase'
50
45
  ? calculateNumberValue(e.target.value)
51
46
  : e.target.value.toString();
52
47
  setInputValue(returnedValue);
53
- debouncedRef.current(name, returnedValue);
48
+ handleChange(name, returnedValue);
54
49
  };
55
50
  const onBlurValidation = () => {
56
- debouncedRef.current.flush();
57
51
  if (type === 'number' || type === 'increase') {
58
52
  let validateValue = inputValue;
59
53
  if (isNaN(parseFloat(validateValue.toString()))) {
@@ -72,7 +66,6 @@ const Input = ({ label, labelPosition = 'top', value, type = 'text', name, disab
72
66
  };
73
67
  const onKeyDown = async (e) => {
74
68
  if (handleSubmit && e.key === 'Enter') {
75
- debouncedRef.current.cancel();
76
69
  const currentValue = type === 'number' || type === 'increase'
77
70
  ? calculateNumberValue(e.currentTarget.value)
78
71
  : e.currentTarget.value.toString();
@@ -32,10 +32,20 @@ const tableHelper_1 = require("./tableHelper");
32
32
  const HeaderCell_1 = require("./HeaderCell");
33
33
  const Cell_1 = require("./Cell");
34
34
  const Pagination_1 = require("../Pagination");
35
+ const Filters_1 = require("../Filters");
36
+ const Typography_1 = require("../Typography");
35
37
  const Table = ({ theme = Theme_1.mainTheme, headers, elements, hover = true, selectable = true, sortable = true, sort = {
36
38
  name: '',
37
39
  order: 'none',
38
40
  }, pageSize = 10, ifPagination = false, moreActions = [], onRowClick, }) => {
41
+ const initialElementsRef = (0, react_1.useRef)(null);
42
+ (0, react_1.useEffect)(() => {
43
+ if (!initialElementsRef.current && Array.isArray(elements) && elements.length > 0) {
44
+ initialElementsRef.current = elements.slice();
45
+ }
46
+ setTableProps((prev) => ({ ...prev, elements: Array.isArray(elements) ? elements : [] }));
47
+ // eslint-disable-next-line react-hooks/exhaustive-deps
48
+ }, [elements]);
39
49
  const checkHeaders = () => {
40
50
  const tableHeaders = [...headers];
41
51
  if (moreActions.length > 0) {
@@ -75,7 +85,6 @@ const Table = ({ theme = Theme_1.mainTheme, headers, elements, hover = true, sel
75
85
  });
76
86
  }
77
87
  };
78
- // PAGINACJA
79
88
  const [currentPage, setCurrentPage] = (0, react_1.useState)(1);
80
89
  const [currentPageSize, setCurrentPageSize] = (0, react_1.useState)(pageSize || 10);
81
90
  const totalPages = Math.ceil(tableProps.elements.length / currentPageSize);
@@ -83,7 +92,28 @@ const Table = ({ theme = Theme_1.mainTheme, headers, elements, hover = true, sel
83
92
  (0, react_1.useEffect)(() => {
84
93
  setCurrentPage(1);
85
94
  }, [elements, currentPageSize]);
95
+ const [noResultsMessage, setNoResultsMessage] = (0, react_1.useState)(null);
96
+ const handleApplyFilters = (0, react_1.useCallback)((filteredElements) => {
97
+ setTableProps((prev) => ({
98
+ ...prev,
99
+ elements: filteredElements,
100
+ }));
101
+ setCurrentPage(1);
102
+ setNoResultsMessage(filteredElements && filteredElements.length === 0 ? 'Nie ma żadnych wyników' : null);
103
+ }, [setCurrentPage, setTableProps]);
104
+ const [filtersClearSignal, setFiltersClearSignal] = (0, react_1.useState)(0);
105
+ const handleClearFilters = (0, react_1.useCallback)(() => {
106
+ setFiltersClearSignal((s) => s + 1);
107
+ const full = (initialElementsRef.current && initialElementsRef.current.length > 0)
108
+ ? initialElementsRef.current
109
+ : (Array.isArray(elements) ? elements : []);
110
+ setTableProps((prev) => ({ ...prev, elements: Array.isArray(full) ? full.slice() : [] }));
111
+ setCurrentPage(1);
112
+ setNoResultsMessage(null);
113
+ }, [elements, setCurrentPage, setNoResultsMessage]);
86
114
  return (react_1.default.createElement(styled_components_1.ThemeProvider, { theme: theme },
115
+ react_1.default.createElement("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: 12 } },
116
+ react_1.default.createElement(Filters_1.Filters, { title: "Select filters", labelConfirm: "Apply", labelClose: "Cancel", labelClearAll: "Clear", headers: headers, elements: initialElementsRef.current && initialElementsRef.current.length > 0 ? initialElementsRef.current : elements, fullElements: initialElementsRef.current && initialElementsRef.current.length > 0 ? initialElementsRef.current : elements, handleSubmitFilters: handleApplyFilters, externalClearSignal: filtersClearSignal, onClear: handleClearFilters })),
87
117
  react_1.default.createElement(Table_styles_1.TableWrapper, { cellPadding: "0", cellSpacing: "0" },
88
118
  react_1.default.createElement(Table_styles_1.TableHead, null,
89
119
  react_1.default.createElement(Table_styles_1.TableRow, { hover: false, selectable: false, active: false }, tableProps.headers.map((header) => (react_1.default.createElement(HeaderCell_1.HeaderCell, { key: `header_${header.name}`, header: header, sortable: sortable, handleSort: handleHeaderCellClick, sort: tableProps.sort }))))),
@@ -92,6 +122,8 @@ const Table = ({ theme = Theme_1.mainTheme, headers, elements, hover = true, sel
92
122
  return (react_1.default.createElement(Cell_1.Cell, { key: `cell_${header.name}_${item.id}`, header: header, moreActions: moreActions, item: item, theme: theme, marked: item.marked && index === 0, isDisabled: item.disabled }));
93
123
  })));
94
124
  }))),
125
+ noResultsMessage && (react_1.default.createElement("div", { style: { padding: '8px 12px', textAlign: 'center' } },
126
+ react_1.default.createElement(Typography_1.TextAccent, null, noResultsMessage))),
95
127
  ifPagination && (react_1.default.createElement(Pagination_1.Pagination, { currentPage: currentPage, totalPages: totalPages, pageSize: currentPageSize, onPageChange: setCurrentPage, onPageSizeChange: setCurrentPageSize }))));
96
128
  };
97
129
  exports.Table = Table;
@@ -1,8 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getValueFormScope = exports.setElements = exports.applySort = exports.setSortParams = void 0;
3
+ exports.getValueFormScope = exports.applySort = exports.setSortParams = void 0;
4
4
  const lodash_1 = require("lodash");
5
- const Filters_1 = require("../Filters");
6
5
  const setSortParams = (target, order, sort) => {
7
6
  const newSort = {
8
7
  name: target,
@@ -19,14 +18,6 @@ const applySort = (sort, elements) => {
19
18
  return sorted;
20
19
  };
21
20
  exports.applySort = applySort;
22
- const setElements = (elements, sort, filters) => {
23
- let tableElements = [...(0, Filters_1.applyFiltersToTable)(elements, filters)];
24
- if (sort.name !== '') {
25
- tableElements = (0, exports.applySort)(sort, tableElements);
26
- }
27
- return tableElements;
28
- };
29
- exports.setElements = setElements;
30
21
  const getValueFormScope = (item, valuesScope) => {
31
22
  return valuesScope.find((_value) => _value.key === item)?.value || '';
32
23
  };
@@ -1,4 +1,4 @@
1
- import React, { useState } from 'react';
1
+ import React, { useMemo, useState, useEffect, useRef, useCallback } from 'react';
2
2
  import { ThemeProvider } from 'styled-components';
3
3
  import { TextAccent } from '../Typography';
4
4
  import { Aligment } from '../Aligment';
@@ -6,165 +6,285 @@ import { Icon } from '../Icons';
6
6
  import { Modal } from '../Modal';
7
7
  import { FilterIconArea, FilterInputWrapper, FiltersArea, FilterSection, } from './Filters.styles';
8
8
  import { Theme, mainTheme } from '../../Theme';
9
- import { PanelOption } from '../common';
10
9
  import { Input } from '../Input';
11
10
  import { Chips } from '../Chips';
12
- export const Filters = ({ theme = mainTheme, title = 'Select filters', labelConfirm = 'Approve', labelClose = "Cancel", filters = [], labelClearAll = 'Clear all filters', handleSubmitFilters }) => {
13
- const generateStateObj = () => {
11
+ import { List } from '../List';
12
+ export const Filters = ({ theme = mainTheme, title = 'Select filters', labelConfirm = 'Approve', labelClose = 'Cancel', filters = [], headers = [], elements = [], fullElements, labelClearAll = 'Wyczyść', handleSubmitFilters, externalClearSignal, onClear, }) => {
13
+ const filtersConfig = useMemo(() => {
14
+ const uniq = (arr) => Array.from(new Set(arr.filter((v) => v !== undefined && v !== null)));
15
+ if (filters && filters.length) {
16
+ return filters.map((h) => ({
17
+ label: h.label,
18
+ name: h.name,
19
+ type: h.type || 'select',
20
+ allowValues: (h.allowValues || []).map((v) => String(v)),
21
+ min: h.min,
22
+ max: h.max,
23
+ collapsible: h.collapsible ?? true,
24
+ }));
25
+ }
26
+ if (!headers || headers.length === 0)
27
+ return [];
28
+ return headers
29
+ .filter((h) => !h.action)
30
+ .map((h) => {
31
+ const key = h.valueSource || h.name;
32
+ const values = (elements || []).map((it) => it?.[key]).filter((v) => v !== undefined && v !== null);
33
+ const nameLower = (h.name || '').toString().toLowerCase();
34
+ if (nameLower === 'age' || nameLower.includes('age')) {
35
+ const nums = values.map((v) => Number(v)).filter((n) => !Number.isNaN(n));
36
+ const min = nums.length ? Math.min(...nums) : 0;
37
+ const max = nums.length ? Math.max(...nums) : 0;
38
+ return { label: h.name, name: key, type: 'range', min, max, collapsible: true };
39
+ }
40
+ if (h.scope && h.scope.length) {
41
+ return { label: h.name, name: key, type: 'select', allowValues: h.scope.map((s) => (s.label ?? s.value ?? s.key ?? String(s))), collapsible: true };
42
+ }
43
+ if (h.date) {
44
+ const dates = values.map((v) => (v ? new Date(v).getTime() : NaN)).filter((d) => !Number.isNaN(d));
45
+ const min = dates.length ? new Date(Math.min(...dates)).toISOString() : undefined;
46
+ const max = dates.length ? new Date(Math.max(...dates)).toISOString() : undefined;
47
+ return { label: h.name, name: key, type: 'date', min, max, collapsible: true };
48
+ }
49
+ if (h.isCount) {
50
+ const nums = values.map((v) => Number(v)).filter((n) => !Number.isNaN(n));
51
+ return { label: h.name, name: key, type: 'range', min: nums.length ? Math.min(...nums) : 0, max: nums.length ? Math.max(...nums) : 0, collapsible: true };
52
+ }
53
+ const numericValues = values.map((v) => Number(v)).filter((n) => !Number.isNaN(n));
54
+ if (numericValues.length === values.length && values.length > 0) {
55
+ return { label: h.name, name: key, type: 'range', min: Math.min(...numericValues), max: Math.max(...numericValues), collapsible: true };
56
+ }
57
+ return { label: h.name, name: key, type: 'select', allowValues: uniq(values).map((v) => String(v)), collapsible: true };
58
+ });
59
+ }, [filters, headers, elements]);
60
+ const generateStateObj = useCallback(() => {
14
61
  const obj = {};
15
- filters.forEach((filter) => {
62
+ filtersConfig.forEach((filter) => {
16
63
  obj[filter.name] = {
17
64
  name: filter.name,
18
65
  type: filter.type,
19
66
  value: [],
20
67
  rangeMin: filter.min,
21
68
  rangeMax: filter.max,
22
- min: '',
23
- max: '',
69
+ min: (filter.type === 'range' || filter.type === 'date') && filter.min !== undefined ? String(filter.min) : '',
70
+ max: (filter.type === 'range' || filter.type === 'date') && filter.max !== undefined ? String(filter.max) : '',
24
71
  };
25
72
  });
26
73
  return obj;
27
- };
74
+ }, [filtersConfig]);
28
75
  const [openFilters, setOpenFilters] = useState(false);
29
76
  const [activeFilters, setActiveFilters] = useState(generateStateObj());
30
- const toogleFilter = () => {
31
- setOpenFilters(!openFilters);
32
- };
33
- const isFilterActive = (filter) => {
34
- let isActive = false;
35
- if (filter.type === 'select') {
36
- isActive = filter.value.length > 0;
37
- }
38
- else if (filter.type === 'range') {
39
- isActive = filter.min.length > 0 || filter.max.length > 0;
77
+ const [collapsedFilters, setCollapsedFilters] = useState({});
78
+ useEffect(() => {
79
+ const init = {};
80
+ filtersConfig.forEach((f) => (init[f.name] = false));
81
+ const prevKeys = Object.keys(collapsedFilters).sort().join(',');
82
+ const newKeys = Object.keys(init).sort().join(',');
83
+ if (prevKeys !== newKeys)
84
+ setCollapsedFilters(init);
85
+ }, [filtersConfig]);
86
+ const toggleCollapse = useCallback((name) => {
87
+ setCollapsedFilters((prev) => ({ ...prev, [name]: !prev[name] }));
88
+ }, []);
89
+ const toggleFilter = useCallback(() => setOpenFilters((v) => !v), []);
90
+ const isFilterActive = useCallback((filter) => {
91
+ if (filter.type === 'select' || filter.type === 'text') {
92
+ return !!(filter.value && filter.value.length > 0);
40
93
  }
41
- return isActive;
42
- };
43
- const isAnyFilterActive = () => {
44
- const activs = Object.keys(activeFilters).find((key) => isFilterActive(activeFilters[key]));
45
- if (activs) {
46
- return activs.length > 0;
94
+ if (filter.type === 'range' || filter.type === 'date') {
95
+ const defaultMin = filter.rangeMin !== undefined ? String(filter.rangeMin) : '';
96
+ const defaultMax = filter.rangeMax !== undefined ? String(filter.rangeMax) : '';
97
+ const curMin = filter.min ?? '';
98
+ const curMax = filter.max ?? '';
99
+ return (curMin !== '' && curMin !== defaultMin) || (curMax !== '' && curMax !== defaultMax);
47
100
  }
48
101
  return false;
49
- };
50
- const setIconColor = () => {
51
- return isAnyFilterActive() ? theme.action : theme.textColor;
52
- };
53
- const clearAll = () => {
54
- const resetedFilter = { ...activeFilters };
55
- Object.keys(resetedFilter).forEach((key) => {
56
- if (resetedFilter[key].type === 'select') {
57
- resetedFilter[key].value = [];
58
- }
59
- else if (resetedFilter[key].type === 'range') {
60
- resetedFilter[key].min = '';
61
- resetedFilter[key].max = '';
62
- }
102
+ }, []);
103
+ const isAnyFilterActive = useCallback(() => Object.values(activeFilters).some((f) => isFilterActive(f)), [activeFilters, isFilterActive]);
104
+ const setIconColor = useCallback(() => (isAnyFilterActive() ? theme.action : theme.textColor), [isAnyFilterActive, theme.action, theme.textColor]);
105
+ const clearAll = useCallback(() => {
106
+ const resetedFilter = {};
107
+ Object.keys(activeFilters).forEach((key) => {
108
+ const f = activeFilters[key];
109
+ resetedFilter[key] = {
110
+ ...f,
111
+ value: f.type === 'select' || f.type === 'text' ? [] : f.value,
112
+ min: (f.type === 'range' || f.type === 'date') && f.rangeMin !== undefined ? String(f.rangeMin) : '',
113
+ max: (f.type === 'range' || f.type === 'date') && f.rangeMax !== undefined ? String(f.rangeMax) : '',
114
+ };
63
115
  });
64
116
  return resetedFilter;
65
- };
66
- const resetAllFilters = () => {
117
+ }, [activeFilters]);
118
+ const resetAllFilters = useCallback(() => {
67
119
  const cleared = clearAll();
68
- setActiveFilters({
69
- ...cleared,
70
- });
71
- };
72
- const resetAllFiltersAndSubmit = () => {
120
+ setActiveFilters({ ...cleared });
121
+ }, [clearAll]);
122
+ const resetAllFiltersAndSubmit = useCallback(() => {
73
123
  resetAllFilters();
74
124
  applyFilter();
75
- };
76
- const resetFilter = (filterName, filterType) => {
77
- if (filterType === 'select') {
78
- setActiveFilters({
79
- ...activeFilters,
80
- [filterName]: {
81
- ...activeFilters[filterName],
82
- value: [],
83
- },
84
- });
85
- }
86
- if (filterType === 'range') {
87
- setActiveFilters({
88
- ...activeFilters,
89
- [filterName]: {
90
- ...activeFilters[filterName],
91
- min: '',
92
- max: '',
93
- },
94
- });
95
- }
96
- };
97
- const setSelectFilter = (filterValue, filterName) => {
98
- let values = [];
99
- const existingValues = activeFilters[filterName].value;
100
- if (existingValues.includes(filterValue)) {
101
- values = existingValues.filter((item) => item !== filterValue);
102
- }
103
- else {
104
- existingValues.push(filterValue);
105
- values = [...existingValues];
106
- }
107
- setActiveFilters({
108
- ...activeFilters,
109
- [filterName]: {
110
- ...activeFilters[filterName],
111
- value: values,
112
- },
113
- });
114
- };
115
- const setRangeFilter = (name, value) => {
116
- const filterName = name.split('_')[0];
117
- const filterParam = name.split('_')[1];
118
- setActiveFilters({
119
- ...activeFilters,
120
- [filterName]: {
121
- ...activeFilters[filterName],
122
- [filterParam]: value,
123
- },
125
+ // eslint-disable-next-line react-hooks/exhaustive-deps
126
+ }, [resetAllFilters]);
127
+ const resetFilter = useCallback((filterName, filterType) => {
128
+ setActiveFilters((prev) => {
129
+ const current = prev[filterName] || {};
130
+ if (filterType === 'select' || filterType === 'text') {
131
+ return { ...prev, [filterName]: { ...current, value: [] } };
132
+ }
133
+ if (filterType === 'range' || filterType === 'date') {
134
+ const defaultMin = current.rangeMin !== undefined ? String(current.rangeMin) : '';
135
+ const defaultMax = current.rangeMax !== undefined ? String(current.rangeMax) : '';
136
+ return { ...prev, [filterName]: { ...current, min: defaultMin, max: defaultMax } };
137
+ }
138
+ return prev;
124
139
  });
125
- };
126
- const applyFilter = () => {
127
- const obj = [];
140
+ }, []);
141
+ const setRangeFilter = useCallback((name, value) => {
142
+ const [filterName, filterParam] = String(name).split('_');
143
+ setActiveFilters((prev) => ({ ...prev, [filterName]: { ...prev[filterName], [filterParam]: value } }));
144
+ }, []);
145
+ const setTextFilter = useCallback((name, value) => {
146
+ const filterName = (name || '').split('_')[0];
147
+ setActiveFilters((prev) => ({ ...prev, [filterName]: { ...(prev[filterName] || { name: filterName, type: 'text', value: [] }), value: value ? [String(value)] : [] } }));
148
+ }, []);
149
+ const applyFilter = useCallback(() => {
150
+ let result = (elements || []).slice();
128
151
  Object.keys(activeFilters).forEach((key) => {
129
- if (activeFilters[key].type === 'select') {
130
- if (activeFilters[key].value.length > 0) {
131
- obj.push({
132
- name: activeFilters[key].name,
133
- type: activeFilters[key].type,
134
- value: activeFilters[key].value,
135
- });
152
+ const f = activeFilters[key];
153
+ if (!f)
154
+ return;
155
+ if (f.type === 'select') {
156
+ if (f.value && f.value.length > 0) {
157
+ const sel = new Set(f.value.map((v) => String(v)));
158
+ result = result.filter((it) => sel.has(String(it?.[key])));
136
159
  }
137
160
  }
138
- else if (activeFilters[key].type === 'range') {
139
- if (activeFilters[key].min.length > 0 || activeFilters[key].max.length > 0) {
140
- obj.push({
141
- name: activeFilters[key].name,
142
- type: activeFilters[key].type,
143
- min: activeFilters[key].min.length > 0 ? activeFilters[key].min : undefined,
144
- max: activeFilters[key].max.length > 0 ? activeFilters[key].max : undefined,
145
- });
161
+ else if (f.type === 'range') {
162
+ if (!isFilterActive(f))
163
+ return;
164
+ const min = f.min !== '' ? Number(f.min) : undefined;
165
+ const max = f.max !== '' ? Number(f.max) : undefined;
166
+ result = result.filter((it) => {
167
+ const val = Number(it?.[key]);
168
+ if (Number.isNaN(val))
169
+ return false;
170
+ if (min !== undefined && val < min)
171
+ return false;
172
+ if (max !== undefined && val > max)
173
+ return false;
174
+ return true;
175
+ });
176
+ }
177
+ else if (f.type === 'date') {
178
+ if (!isFilterActive(f))
179
+ return;
180
+ const fromDate = f.min ? new Date(f.min) : null;
181
+ const toDate = f.max ? new Date(f.max) : null;
182
+ const fromTs = fromDate ? new Date(fromDate.getFullYear(), fromDate.getMonth(), fromDate.getDate(), 0, 0, 0, 0).getTime() : null;
183
+ const toTs = toDate ? new Date(toDate.getFullYear(), toDate.getMonth(), toDate.getDate(), 23, 59, 59, 999).getTime() : null;
184
+ result = result.filter((it) => {
185
+ const raw = it?.[key];
186
+ if (!raw)
187
+ return false;
188
+ const d = new Date(raw);
189
+ if (Number.isNaN(d.getTime()))
190
+ return false;
191
+ const ts = d.getTime();
192
+ if (fromTs !== null && ts < fromTs)
193
+ return false;
194
+ if (toTs !== null && ts > toTs)
195
+ return false;
196
+ return true;
197
+ });
198
+ }
199
+ else {
200
+ const q = Array.isArray(f.value) ? f.value[0] : f.value;
201
+ if (q) {
202
+ const qs = String(q).toLowerCase();
203
+ result = result.filter((it) => String(it?.[key] ?? '').toLowerCase().includes(qs));
146
204
  }
147
205
  }
148
206
  });
149
- handleSubmitFilters && handleSubmitFilters(obj);
207
+ handleSubmitFilters && handleSubmitFilters(result);
150
208
  setOpenFilters(false);
151
- };
209
+ }, [activeFilters, elements, handleSubmitFilters, isFilterActive]);
210
+ const prevConfigKeysRef = useRef(null);
211
+ useEffect(() => {
212
+ const newState = generateStateObj();
213
+ const keysSerialized = JSON.stringify(Object.keys(newState));
214
+ if (prevConfigKeysRef.current !== keysSerialized) {
215
+ setActiveFilters(newState);
216
+ prevConfigKeysRef.current = keysSerialized;
217
+ }
218
+ }, [generateStateObj]);
219
+ const prevExternalClearRef = useRef(externalClearSignal ?? 0);
220
+ useEffect(() => {
221
+ if (externalClearSignal === undefined)
222
+ return;
223
+ if (prevExternalClearRef.current !== externalClearSignal) {
224
+ prevExternalClearRef.current = externalClearSignal;
225
+ const cleared = clearAll();
226
+ setActiveFilters(cleared);
227
+ handleSubmitFilters && handleSubmitFilters((fullElements && fullElements.length ? fullElements : elements) || []);
228
+ }
229
+ }, [externalClearSignal, clearAll, handleSubmitFilters, fullElements, elements]);
230
+ const mapListItems = useCallback((filterName, allowValues) => {
231
+ const sel = (activeFilters[filterName]?.value || []).map(String);
232
+ return (allowValues || []).map((v) => ({ id: String(v), value: String(v), children: [], selected: sel.includes(String(v)) }));
233
+ }, [activeFilters]);
152
234
  return (React.createElement(ThemeProvider, { theme: theme },
153
235
  React.createElement(FilterIconArea, null,
154
- React.createElement(Icon, { name: "filters", onClick: toogleFilter, iconColor: setIconColor() }),
155
- isAnyFilterActive() && (React.createElement(Chips, { label: labelClearAll, handleClose: resetAllFiltersAndSubmit }))),
156
- openFilters && (React.createElement(Modal, { title: title, handleConfirm: applyFilter, handleClose: toogleFilter, handleAdditional: resetAllFilters, labelConfirm: labelConfirm, labelClose: labelClose, labelAdditional: labelClearAll, isOpen: openFilters },
157
- React.createElement(Aligment, { direction: "column", align: 'flex-end' },
158
- React.createElement(FiltersArea, null, filters.map((filter) => (React.createElement(FilterSection, null,
159
- React.createElement(Aligment, { wrap: "nowrap" },
160
- React.createElement(TextAccent, null, filter.label),
236
+ React.createElement(Icon, { name: "filters", onClick: toggleFilter, iconColor: setIconColor() }),
237
+ isAnyFilterActive() && (React.createElement(Chips, { label: labelClearAll, handleClose: () => {
238
+ if (onClear) {
239
+ onClear();
240
+ const cleared = clearAll();
241
+ setActiveFilters(cleared);
242
+ }
243
+ else {
244
+ const cleared = clearAll();
245
+ setActiveFilters(cleared);
246
+ handleSubmitFilters && handleSubmitFilters((fullElements && fullElements.length ? fullElements : elements) || []);
247
+ }
248
+ } }))),
249
+ openFilters && (React.createElement(Modal, { title: title, handleConfirm: applyFilter, handleClose: toggleFilter, handleAdditional: () => {
250
+ if (onClear) {
251
+ onClear();
252
+ const cleared = clearAll();
253
+ setActiveFilters(cleared);
254
+ setOpenFilters(false);
255
+ }
256
+ else {
257
+ resetAllFiltersAndSubmit();
258
+ }
259
+ }, labelConfirm: labelConfirm, labelClose: labelClose, labelAdditional: labelClearAll, isOpen: openFilters },
260
+ React.createElement(Aligment, { direction: "column", align: "flex-end" },
261
+ React.createElement(FiltersArea, null, filtersConfig.map((filter) => (React.createElement(FilterSection, { key: filter.name },
262
+ React.createElement(Aligment, { wrap: "nowrap", style: { alignItems: 'center', justifyContent: 'start' } },
263
+ React.createElement(Aligment, { wrap: "nowrap", onClick: () => filter.collapsible && toggleCollapse(filter.name), style: { cursor: filter.collapsible ? 'pointer' : 'default', alignItems: 'center', justifyContent: 'start' } },
264
+ React.createElement(TextAccent, { style: { marginRight: 0 } }, filter.label),
265
+ React.createElement(Icon, { name: collapsedFilters[filter.name] ? 'arrow_drop_down' : 'arrow_drop_up', size: 16, iconColor: theme.textColor })),
161
266
  React.createElement(Icon, { name: "close", size: 14, onClick: () => resetFilter(filter.name, filter.type) })),
162
- filter.type === 'select' && (React.createElement(Aligment, { wrap: "nowrap", direction: "column", align: "flex-start", vPadding: Theme.padding.s }, filter.allowValues?.map((item) => {
163
- return (React.createElement(PanelOption, { width: "100%", key: item, active: activeFilters[filter.name].value.includes(item), onClick: () => setSelectFilter(item, filter.name) }, item));
164
- }))),
165
- filter.type === 'range' && (React.createElement(Aligment, { wrap: "nowrap", direction: "row", align: "flex-start", vPadding: Theme.padding.s, justify: "flex-start", gap: 8 },
267
+ !filter.collapsible || !collapsedFilters[filter.name] ? (filter.type === 'select' ? (React.createElement("div", { style: { width: '100%' } },
268
+ React.createElement(List, { items: mapListItems(filter.name, filter.allowValues), handleChange: (items) => {
269
+ const collectSelected = (list) => list.reduce((acc, it) => {
270
+ if (it.selected)
271
+ acc.push(String(it.id));
272
+ if (it.children && it.children.length)
273
+ acc.push(...collectSelected(it.children));
274
+ return acc;
275
+ }, []);
276
+ const selectedIds = collectSelected(items);
277
+ setActiveFilters((prev) => ({ ...prev, [filter.name]: { ...(prev[filter.name] || { name: filter.name, type: filter.type, value: [] }), value: selectedIds } }));
278
+ }, isCheckbox: true, isCollapsable: true }))) : filter.type === 'range' ? (React.createElement(Aligment, { wrap: "nowrap", direction: "row", align: "flex-start", vPadding: Theme.padding.s, justify: "flex-start", gap: 8 },
279
+ React.createElement(FilterInputWrapper, null,
280
+ React.createElement(Input, { type: "number", name: `${filter.name}_min`, value: activeFilters[filter.name].min, handleChange: setRangeFilter })),
281
+ React.createElement(FilterInputWrapper, null,
282
+ React.createElement(Input, { type: "number", name: `${filter.name}_max`, value: activeFilters[filter.name].max, handleChange: setRangeFilter })))) : filter.type === 'date' ? (React.createElement(Aligment, { wrap: "nowrap", direction: "row", align: "flex-start", vPadding: Theme.padding.s, justify: "flex-start", gap: 8 },
283
+ React.createElement(FilterInputWrapper, null,
284
+ React.createElement(Input, { type: "date", name: `${filter.name}_min`, value: activeFilters[filter.name].min, handleChange: setRangeFilter })),
166
285
  React.createElement(FilterInputWrapper, null,
167
- React.createElement(Input, { type: "number", name: `${filter.name}_min`, value: activeFilters[filter.name].min, placeholder: activeFilters[filter.name].rangeMin, handleChange: setRangeFilter })),
286
+ React.createElement(Input, { type: "date", name: `${filter.name}_max`, value: activeFilters[filter.name].max, handleChange: setRangeFilter })))) : filter.type === 'text' ? (React.createElement(Aligment, { wrap: "nowrap", direction: "row", align: "flex-start", vPadding: Theme.padding.s, justify: "flex-start", gap: 8 },
168
287
  React.createElement(FilterInputWrapper, null,
169
- React.createElement(Input, { type: "number", name: `${filter.name}_max`, value: activeFilters[filter.name].max, placeholder: activeFilters[filter.name].rangeMax, handleChange: setRangeFilter })))))))))))));
288
+ React.createElement(Input, { type: "text", name: `${filter.name}_query`, value: (activeFilters[filter.name]?.value && activeFilters[filter.name].value[0]) || '', handleChange: setTextFilter })))) : null) : null)))))))));
170
289
  };
290
+ export default Filters;
@@ -8,20 +8,6 @@ export const getMinValue = (elements, source) => {
8
8
  export const getMaxValue = (elements, source) => {
9
9
  return Math.max(...elements.map((item) => item[source]));
10
10
  };
11
- export const setTableFilterSet = (headers, elements) => {
12
- const filterSetting = [];
13
- headers.forEach((filter) => {
14
- filterSetting.push({
15
- label: filter.name,
16
- name: filter.valueSource,
17
- type: filter.filterType ? filter.filterType : 'select',
18
- allowValues: getUniqueValues(elements, filter.valueSource),
19
- min: getMinValue(elements, filter.valueSource),
20
- max: getMaxValue(elements, filter.valueSource),
21
- });
22
- });
23
- return filterSetting;
24
- };
25
11
  export const applyFiltersToTable = (elements, filters) => {
26
12
  let filtered = [...elements];
27
13
  filters.forEach((filter) => {
@@ -1,10 +1,9 @@
1
- import React, { useEffect, useRef, useState } from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
2
  import { ThemeProvider } from 'styled-components';
3
3
  import { Aligment } from '../Aligment';
4
4
  import { InputTextElement, InputLabelElement, InputErrorMsg, InputWrapper, EyeIconWrapper, IconsWrapper, InputReadOnlyElement, Prefix, } from './Input.styles';
5
5
  import { Icon } from '../Icons';
6
6
  import { mainTheme } from '../../Theme';
7
- import { debounce } from 'lodash';
8
7
  export const Input = ({ label, labelPosition = 'top', value, type = 'text', name, disabled = false, size = 'medium', handleChange, handleSubmit, width, error = false, errorMsg, placeholder, min, max, step = 0.1, autoFocus, theme = mainTheme, readOnly = false, prefix, }) => {
9
8
  const [inputValue, setInputValue] = useState(value);
10
9
  useEffect(() => {
@@ -15,19 +14,14 @@ export const Input = ({ label, labelPosition = 'top', value, type = 'text', name
15
14
  ? numberValue
16
15
  : parseFloat(numberValue);
17
16
  };
18
- const debouncedRef = useRef(debounce((n, v) => {
19
- console.log('checking debounce', n, v);
20
- handleChange(n, v);
21
- }, 300));
22
17
  const onChange = (e) => {
23
18
  let returnedValue = type === 'number' || type === 'increase'
24
19
  ? calculateNumberValue(e.target.value)
25
20
  : e.target.value.toString();
26
21
  setInputValue(returnedValue);
27
- debouncedRef.current(name, returnedValue);
22
+ handleChange(name, returnedValue);
28
23
  };
29
24
  const onBlurValidation = () => {
30
- debouncedRef.current.flush();
31
25
  if (type === 'number' || type === 'increase') {
32
26
  let validateValue = inputValue;
33
27
  if (isNaN(parseFloat(validateValue.toString()))) {
@@ -46,7 +40,6 @@ export const Input = ({ label, labelPosition = 'top', value, type = 'text', name
46
40
  };
47
41
  const onKeyDown = async (e) => {
48
42
  if (handleSubmit && e.key === 'Enter') {
49
- debouncedRef.current.cancel();
50
43
  const currentValue = type === 'number' || type === 'increase'
51
44
  ? calculateNumberValue(e.currentTarget.value)
52
45
  : e.currentTarget.value.toString();
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useEffect, useState, useCallback, useRef } from 'react';
2
2
  import { ThemeProvider } from 'styled-components';
3
3
  import { TableHead, TableRow, TableWrapper } from './Table.styles';
4
4
  import { mainTheme } from '../../Theme';
@@ -6,10 +6,20 @@ import { applySort, setSortParams } from './tableHelper';
6
6
  import { HeaderCell } from './HeaderCell';
7
7
  import { Cell } from './Cell';
8
8
  import { Pagination } from '../Pagination';
9
+ import { Filters } from '../Filters';
10
+ import { TextAccent } from '../Typography';
9
11
  export const Table = ({ theme = mainTheme, headers, elements, hover = true, selectable = true, sortable = true, sort = {
10
12
  name: '',
11
13
  order: 'none',
12
14
  }, pageSize = 10, ifPagination = false, moreActions = [], onRowClick, }) => {
15
+ const initialElementsRef = useRef(null);
16
+ useEffect(() => {
17
+ if (!initialElementsRef.current && Array.isArray(elements) && elements.length > 0) {
18
+ initialElementsRef.current = elements.slice();
19
+ }
20
+ setTableProps((prev) => ({ ...prev, elements: Array.isArray(elements) ? elements : [] }));
21
+ // eslint-disable-next-line react-hooks/exhaustive-deps
22
+ }, [elements]);
13
23
  const checkHeaders = () => {
14
24
  const tableHeaders = [...headers];
15
25
  if (moreActions.length > 0) {
@@ -49,7 +59,6 @@ export const Table = ({ theme = mainTheme, headers, elements, hover = true, sele
49
59
  });
50
60
  }
51
61
  };
52
- // PAGINACJA
53
62
  const [currentPage, setCurrentPage] = useState(1);
54
63
  const [currentPageSize, setCurrentPageSize] = useState(pageSize || 10);
55
64
  const totalPages = Math.ceil(tableProps.elements.length / currentPageSize);
@@ -57,7 +66,28 @@ export const Table = ({ theme = mainTheme, headers, elements, hover = true, sele
57
66
  useEffect(() => {
58
67
  setCurrentPage(1);
59
68
  }, [elements, currentPageSize]);
69
+ const [noResultsMessage, setNoResultsMessage] = useState(null);
70
+ const handleApplyFilters = useCallback((filteredElements) => {
71
+ setTableProps((prev) => ({
72
+ ...prev,
73
+ elements: filteredElements,
74
+ }));
75
+ setCurrentPage(1);
76
+ setNoResultsMessage(filteredElements && filteredElements.length === 0 ? 'Nie ma żadnych wyników' : null);
77
+ }, [setCurrentPage, setTableProps]);
78
+ const [filtersClearSignal, setFiltersClearSignal] = useState(0);
79
+ const handleClearFilters = useCallback(() => {
80
+ setFiltersClearSignal((s) => s + 1);
81
+ const full = (initialElementsRef.current && initialElementsRef.current.length > 0)
82
+ ? initialElementsRef.current
83
+ : (Array.isArray(elements) ? elements : []);
84
+ setTableProps((prev) => ({ ...prev, elements: Array.isArray(full) ? full.slice() : [] }));
85
+ setCurrentPage(1);
86
+ setNoResultsMessage(null);
87
+ }, [elements, setCurrentPage, setNoResultsMessage]);
60
88
  return (React.createElement(ThemeProvider, { theme: theme },
89
+ React.createElement("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: 12 } },
90
+ React.createElement(Filters, { title: "Select filters", labelConfirm: "Apply", labelClose: "Cancel", labelClearAll: "Clear", headers: headers, elements: initialElementsRef.current && initialElementsRef.current.length > 0 ? initialElementsRef.current : elements, fullElements: initialElementsRef.current && initialElementsRef.current.length > 0 ? initialElementsRef.current : elements, handleSubmitFilters: handleApplyFilters, externalClearSignal: filtersClearSignal, onClear: handleClearFilters })),
61
91
  React.createElement(TableWrapper, { cellPadding: "0", cellSpacing: "0" },
62
92
  React.createElement(TableHead, null,
63
93
  React.createElement(TableRow, { hover: false, selectable: false, active: false }, tableProps.headers.map((header) => (React.createElement(HeaderCell, { key: `header_${header.name}`, header: header, sortable: sortable, handleSort: handleHeaderCellClick, sort: tableProps.sort }))))),
@@ -66,5 +96,7 @@ export const Table = ({ theme = mainTheme, headers, elements, hover = true, sele
66
96
  return (React.createElement(Cell, { key: `cell_${header.name}_${item.id}`, header: header, moreActions: moreActions, item: item, theme: theme, marked: item.marked && index === 0, isDisabled: item.disabled }));
67
97
  })));
68
98
  }))),
99
+ noResultsMessage && (React.createElement("div", { style: { padding: '8px 12px', textAlign: 'center' } },
100
+ React.createElement(TextAccent, null, noResultsMessage))),
69
101
  ifPagination && (React.createElement(Pagination, { currentPage: currentPage, totalPages: totalPages, pageSize: currentPageSize, onPageChange: setCurrentPage, onPageSizeChange: setCurrentPageSize }))));
70
102
  };
@@ -1,5 +1,4 @@
1
1
  import { orderBy } from 'lodash';
2
- import { applyFiltersToTable } from '../Filters';
3
2
  export const setSortParams = (target, order, sort) => {
4
3
  const newSort = {
5
4
  name: target,
@@ -14,13 +13,6 @@ export const applySort = (sort, elements) => {
14
13
  }
15
14
  return sorted;
16
15
  };
17
- export const setElements = (elements, sort, filters) => {
18
- let tableElements = [...applyFiltersToTable(elements, filters)];
19
- if (sort.name !== '') {
20
- tableElements = applySort(sort, tableElements);
21
- }
22
- return tableElements;
23
- };
24
16
  export const getValueFormScope = (item, valuesScope) => {
25
17
  return valuesScope.find((_value) => _value.key === item)?.value || '';
26
18
  };
@@ -1,9 +1,14 @@
1
1
  import { FC } from 'react';
2
- export interface IFilterConfigProps {
2
+ interface IHeader {
3
3
  name: string;
4
- valueSource: string;
4
+ valueSource?: string;
5
5
  date?: boolean;
6
- filterType?: 'select' | 'range';
6
+ isCount?: boolean;
7
+ scope?: Array<{
8
+ key?: string;
9
+ value?: string;
10
+ label?: string;
11
+ }>;
7
12
  }
8
13
  export interface IApplyFilterResults {
9
14
  name: string;
@@ -13,6 +18,15 @@ export interface IApplyFilterResults {
13
18
  max?: number;
14
19
  }
15
20
  export interface IFilterProps {
21
+ collapsible: boolean;
22
+ valueSource?: string;
23
+ date?: boolean;
24
+ isCount?: boolean;
25
+ scope?: Array<{
26
+ key?: string;
27
+ value?: string;
28
+ label?: string;
29
+ }>;
16
30
  label: string;
17
31
  name: string;
18
32
  type: 'select' | 'range' | string;
@@ -25,9 +39,14 @@ interface IFiltersProps {
25
39
  title: string;
26
40
  labelConfirm: string;
27
41
  labelClose: string;
28
- filters: IFilterProps[];
29
- labelClearAll: string;
30
- handleSubmitFilters: (filters: IApplyFilterResults[]) => void;
42
+ filters?: IFilterProps[];
43
+ headers?: IHeader[];
44
+ elements?: any[];
45
+ fullElements?: any[];
46
+ labelClearAll?: string;
47
+ handleSubmitFilters: (filteredElements: any[]) => void;
48
+ externalClearSignal?: number;
49
+ onClear?: () => void;
31
50
  }
32
51
  export declare const Filters: FC<IFiltersProps>;
33
- export {};
52
+ export default Filters;
@@ -1,7 +1,6 @@
1
- import { IApplyFilterResults, IFilterConfigProps, IFilterProps } from './Filters';
1
+ import { IApplyFilterResults } from './Filters';
2
2
  export type TAllowValues = 'string' | 'number';
3
3
  export declare const getUniqueValues: (elements: any, source: string) => string[];
4
4
  export declare const getMinValue: (elements: any, source: string) => number;
5
5
  export declare const getMaxValue: (elements: any, source: string) => number;
6
- export declare const setTableFilterSet: (headers: IFilterConfigProps[], elements: any) => IFilterProps[];
7
6
  export declare const applyFiltersToTable: (elements: any, filters: IApplyFilterResults[]) => any[];
@@ -1,7 +1,7 @@
1
1
  import { FC } from 'react';
2
2
  import { InputType, IFormElement } from '../../types';
3
3
  export interface IInputProps extends IFormElement {
4
- type?: InputType;
4
+ type?: InputType | 'date';
5
5
  handleChange: (name: string, value: string | number) => void;
6
6
  placeholder?: string;
7
7
  min?: number;
@@ -1,6 +1,4 @@
1
- import { IApplyFilterResults } from '../Filters';
2
1
  import { ITableScopeProps, ITableSortProps } from './Table';
3
2
  export declare const setSortParams: (target: string, order: string, sort: ITableSortProps) => ITableSortProps;
4
3
  export declare const applySort: (sort: ITableSortProps, elements: any) => any;
5
- export declare const setElements: (elements: any, sort: ITableSortProps, filters: IApplyFilterResults[]) => any;
6
4
  export declare const getValueFormScope: (item: string, valuesScope: ITableScopeProps[]) => string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "venice-ui",
3
- "version": "2.3.6",
3
+ "version": "2.4.0",
4
4
  "description": "Component library",
5
5
  "main": "index.js",
6
6
  "module": "./dist/esm/index.js",