rez-table-listing-mui 1.2.18 → 1.3.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 (34) hide show
  1. package/dist/index.d.ts +63 -1
  2. package/dist/index.js +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +1 -1
  5. package/dist/index.mjs.map +1 -1
  6. package/package.json +2 -3
  7. package/src/kanban/index.tsx +24 -21
  8. package/src/listing/components/common/loader/loader.tsx +1 -0
  9. package/src/listing/components/filter/components/attributes-filter.tsx +3 -91
  10. package/src/listing/components/filter/components/forms/components/Date.tsx +2 -2
  11. package/src/listing/components/filter/components/forms/components/Dropdown.tsx +2 -2
  12. package/src/listing/components/filter/components/forms/components/Filter-criteria.tsx +31 -82
  13. package/src/listing/components/filter/components/forms/components/Multi-Select.tsx +2 -2
  14. package/src/listing/components/filter/components/forms/components/Select.tsx +2 -2
  15. package/src/listing/components/filter/components/forms/components/Textfield.tsx +2 -2
  16. package/src/listing/components/filter/components/forms/components/empty-list.tsx +17 -0
  17. package/src/listing/components/filter/components/forms/components/filter-criteria-entity-list.tsx +92 -0
  18. package/src/listing/components/filter/components/forms/components/filter-criteria-list.tsx +104 -0
  19. package/src/listing/components/filter/components/forms/components/styles.tsx +2 -1
  20. package/src/listing/components/filter/components/forms/index.tsx +238 -174
  21. package/src/listing/components/filter/components/main-filter.tsx +6 -14
  22. package/src/listing/components/filter/components/saved-edit-filter.tsx +0 -31
  23. package/src/listing/components/filter/components/saved-filter.tsx +0 -22
  24. package/src/listing/components/filter/index.tsx +162 -130
  25. package/src/listing/components/filter/style.ts +20 -3
  26. package/src/listing/libs/hooks/useCraftTable.tsx +9 -0
  27. package/src/listing/libs/hooks/useEntityTableAPI.tsx +25 -0
  28. package/src/listing/libs/utils/apiColumn.ts +27 -1
  29. package/src/listing/libs/utils/common.ts +1 -1
  30. package/src/listing/libs/utils/deep-merge-objects.ts +18 -0
  31. package/src/listing/types/common.ts +0 -2
  32. package/src/listing/types/filter.ts +54 -7
  33. package/src/listing/types/table-options.ts +8 -0
  34. package/src/view/FIlterWrapper.tsx +45 -6
@@ -0,0 +1,104 @@
1
+ import {
2
+ Box,
3
+ IconButton,
4
+ List,
5
+ ListItem,
6
+ ListItemText,
7
+ Typography,
8
+ } from "@mui/material";
9
+ import CustomSearch from "../../search";
10
+ import React from "react";
11
+ import { CraftTableOptionsProps } from "../../../../../types/table-options";
12
+ import { filterFormStyles } from "../../../style";
13
+ import { CloseIcon } from "../../../../../../assets/svg";
14
+ import EmptyList from "./empty-list";
15
+ import Loader from "../../../../common/loader/loader";
16
+ import { FilterDataMainFilterEntityWiseCriteriaProps } from "../../../../../types/filter";
17
+
18
+ interface Props {
19
+ tableStates: CraftTableOptionsProps;
20
+ handleAddFilter: (
21
+ column: FilterDataMainFilterEntityWiseCriteriaProps
22
+ ) => void;
23
+ }
24
+
25
+ const FilterCriteriaList = ({ tableStates, handleAddFilter }: Props) => {
26
+ const [searchTerm, setSearchTerm] = React.useState<string>("");
27
+
28
+ const { filterData, filters, selectedFilterEntity, setSelectedFilterEntity } =
29
+ tableStates;
30
+
31
+ return (
32
+ <>
33
+ <Box
34
+ className="group-header"
35
+ sx={{
36
+ ...filterFormStyles.formListSectionHeader,
37
+ position: "sticky",
38
+ top: 0,
39
+ zIndex: 1,
40
+ }}
41
+ >
42
+ <Typography fontSize={14}>{selectedFilterEntity?.label}</Typography>
43
+ <IconButton
44
+ size="small"
45
+ onClick={() => setSelectedFilterEntity(undefined)}
46
+ >
47
+ <CloseIcon />
48
+ </IconButton>
49
+ </Box>
50
+ {filterData?.mainFilter?.entityWiseCriteria?.isPending ? (
51
+ <Loader />
52
+ ) : (
53
+ <List>
54
+ <CustomSearch
55
+ placeholder="Filter by..."
56
+ value={searchTerm}
57
+ onChange={setSearchTerm}
58
+ />
59
+
60
+ <Box
61
+ sx={{
62
+ my: 2,
63
+ overflowY: "auto",
64
+ transition: "all 0.4s ease-in-out",
65
+ }}
66
+ >
67
+ {(() => {
68
+ const filteredEntities =
69
+ filterData?.mainFilter?.entityWiseCriteria?.data?.filter(
70
+ (entity) =>
71
+ entity.name.toLowerCase().includes(searchTerm.toLowerCase())
72
+ ) ?? [];
73
+
74
+ if (filteredEntities.length === 0) {
75
+ return <EmptyList />;
76
+ }
77
+
78
+ return filteredEntities.map((entity, index) => {
79
+ const isAlreadySelected = filters?.some(
80
+ (filter) => filter.filter_attribute === entity.attribute_key
81
+ );
82
+
83
+ return (
84
+ <ListItem
85
+ key={index}
86
+ sx={{
87
+ opacity: isAlreadySelected ? 0.5 : 1,
88
+ cursor: isAlreadySelected ? "not-allowed" : "pointer",
89
+ }}
90
+ onClick={() => handleAddFilter(entity)}
91
+ >
92
+ <ListItemText primary={entity.name} />
93
+ </ListItem>
94
+ );
95
+ });
96
+ })()}
97
+ </Box>
98
+ </List>
99
+ )}
100
+ </>
101
+ );
102
+ };
103
+
104
+ export default FilterCriteriaList;
@@ -2,7 +2,8 @@ export const TextFieldStyles = {
2
2
  "& .MuiOutlinedInput-root": {
3
3
  borderRadius: "6px",
4
4
  fontSize: "14px",
5
- bgcolor: "#fafafa",
5
+ bgcolor: "#fff",
6
+
6
7
  "& fieldset": { borderColor: "#c1c1c1" },
7
8
  "&.Mui-focused fieldset": {
8
9
  borderColor: "#7A5AF8",
@@ -9,9 +9,10 @@ import {
9
9
  import { CloseIcon, DeleteIcon } from "../../../../../assets/svg";
10
10
  import {
11
11
  FilterColumnsDataProps,
12
+ FilterComponentOptions,
12
13
  FilterDropdownDataProps,
13
14
  FilterMasterStateProps,
14
- UpdatedFilterStateProps,
15
+ FilterStateProps,
15
16
  } from "../../../../types/filter";
16
17
  import { Controller, useForm } from "react-hook-form";
17
18
  import FormTextfield from "./components/Textfield";
@@ -43,14 +44,13 @@ const FilterForm = ({
43
44
  setSearchTerm,
44
45
  criteriaSearchTerm = "",
45
46
  setCriteriaSearchTerm,
46
- selectedFilters,
47
- setSelectedFilters,
48
47
  handleRemoveFilter,
49
48
  editMode = false,
50
49
  tableStates,
51
50
  setSavedFilterModalOpen,
52
51
  setDeleteFilterModalOpen,
53
52
  onChangeFunction,
53
+ filterComponentOptions,
54
54
  }: {
55
55
  columnsData: FilterColumnsDataProps;
56
56
  dropdownData: FilterDropdownDataProps;
@@ -58,10 +58,6 @@ const FilterForm = ({
58
58
  setSearchTerm: React.Dispatch<React.SetStateAction<string>>;
59
59
  criteriaSearchTerm?: string;
60
60
  setCriteriaSearchTerm: React.Dispatch<React.SetStateAction<string>>;
61
- selectedFilters: UpdatedFilterStateProps[];
62
- setSelectedFilters: React.Dispatch<
63
- React.SetStateAction<UpdatedFilterStateProps[]>
64
- >;
65
61
  handleRemoveFilter: (filter_attribute: string) => void;
66
62
  editMode?: boolean;
67
63
  tableStates: CraftTableOptionsProps;
@@ -71,14 +67,20 @@ const FilterForm = ({
71
67
  updatedFilters,
72
68
  filterMaster,
73
69
  }: onFilterChangeFunctionProps) => void;
70
+ filterComponentOptions?: FilterComponentOptions;
74
71
  }) => {
75
- const { filterMaster, setFilters, setFilterMaster, setPagination } =
72
+ const { filterMaster, filters, setFilters, setFilterMaster, setPagination } =
76
73
  tableStates;
77
74
 
75
+ const showSaveButton =
76
+ filterComponentOptions?.tabOptions?.mainFilter?.showSaveButton;
77
+ const showClearAllButton =
78
+ filterComponentOptions?.tabOptions?.mainFilter?.showClearAllButton;
79
+
78
80
  const filterName = filterMaster?.saved_filters?.selectedName || "";
79
81
 
80
82
  const defaultValues = useMemo(() => {
81
- const filterValues = selectedFilters.reduce((acc, curr) => {
83
+ const filterValues = filters?.reduce((acc, curr) => {
82
84
  if (curr.name) {
83
85
  acc[curr.name] = {
84
86
  value: curr.filter_value ?? "",
@@ -94,7 +96,7 @@ const FilterForm = ({
94
96
  dummyChange: "",
95
97
  ...filterValues,
96
98
  };
97
- }, [selectedFilters, filterName]);
99
+ }, [filters, filterName]);
98
100
 
99
101
  const { control, watch, reset, setValue, unregister } = useForm<
100
102
  FormValues & { dummyChange: string }
@@ -111,11 +113,10 @@ const FilterForm = ({
111
113
 
112
114
  useEffect(() => {
113
115
  reset(defaultValues);
114
- }, [selectedFilters]);
116
+ }, [filters]);
115
117
 
116
118
  const debouncedUpdateFilters = useCallback(
117
- customDebounce((updatedFilters: UpdatedFilterStateProps[]) => {
118
- setSelectedFilters(updatedFilters);
119
+ customDebounce((updatedFilters: FilterStateProps[]) => {
119
120
  setFilters(updatedFilters);
120
121
 
121
122
  const newState = {
@@ -125,11 +126,11 @@ const FilterForm = ({
125
126
 
126
127
  onChangeFunction && onChangeFunction(newState);
127
128
  }, 1000),
128
- [setSelectedFilters, setFilters]
129
+ [setFilters]
129
130
  );
130
131
 
131
132
  const updateFiltersFromForm = useCallback(() => {
132
- const updatedFilters = selectedFilters.map((filter) => {
133
+ const updatedFilters = filters?.map((filter) => {
133
134
  if (filter.name && typeof formValues[filter.name] === "object") {
134
135
  const filterValue = formValues[filter.name] as {
135
136
  value: string | string[];
@@ -147,12 +148,12 @@ const FilterForm = ({
147
148
 
148
149
  setPagination((prev) => ({ ...prev, pageIndex: 0 }));
149
150
  debouncedUpdateFilters(updatedFilters);
150
- }, [formValues, selectedFilters, debouncedUpdateFilters]);
151
+ }, [formValues, filters, debouncedUpdateFilters]);
151
152
 
152
153
  useEffect(() => {
153
154
  return () => {
154
155
  reset();
155
- selectedFilters.forEach((filter) => {
156
+ filters?.forEach((filter) => {
156
157
  if (filter.name) {
157
158
  unregister(filter.name);
158
159
  }
@@ -160,6 +161,40 @@ const FilterForm = ({
160
161
  };
161
162
  }, []);
162
163
 
164
+ const groupedFilters = useMemo(() => {
165
+ return filters?.reduce((acc, filter) => {
166
+ const key = filter?.filter_entity_name || "";
167
+ if (!acc[key]) {
168
+ acc[key] = [];
169
+ }
170
+ acc[key].push(filter);
171
+ return acc;
172
+ }, {} as Record<string, FilterStateProps[]>);
173
+ }, [filters]);
174
+
175
+ const handleRemoveEntityType = (entityType: string) => {
176
+ const remainingFilters = filters?.filter(
177
+ (f) => f.filter_entity_name !== entityType
178
+ );
179
+
180
+ // unregister all fields belonging to this entity type
181
+ filters?.forEach((f) => {
182
+ if (f.filter_entity_name === entityType && f.name) {
183
+ unregister(`${f.name}.value`);
184
+ unregister(`${f.name}.operator`);
185
+ }
186
+ });
187
+
188
+ setFilters(remainingFilters);
189
+
190
+ const newState = {
191
+ filterMaster,
192
+ filters: remainingFilters,
193
+ };
194
+
195
+ onChangeFunction && onChangeFunction(newState);
196
+ };
197
+
163
198
  return (
164
199
  <form
165
200
  onSubmit={(e) => {
@@ -245,7 +280,6 @@ const FilterForm = ({
245
280
  <FilterCriteria
246
281
  columnsData={columnsData}
247
282
  tableStates={tableStates}
248
- setSelectedFilters={setSelectedFilters}
249
283
  searchTerm={criteriaSearchTerm}
250
284
  setSearchTerm={setCriteriaSearchTerm}
251
285
  onChangeFunction={onChangeFunction}
@@ -259,170 +293,200 @@ const FilterForm = ({
259
293
  className="filter-form-inputs"
260
294
  sx={filterFormStyles.formFlexContainer}
261
295
  >
262
- {selectedFilters
263
- .filter(
264
- (filter) =>
265
- filter.name
266
- ?.toLowerCase()
267
- .includes(searchTerm.toLowerCase()) ||
268
- filter.filter_value
269
- ?.toString()
270
- .toLowerCase()
271
- .includes(searchTerm.toLowerCase())
272
- )
273
- .reverse()
274
- .map((filter) => {
275
- const { dropdown_list = [] } = filter;
276
- return (
277
- <Box
278
- key={filter.filter_attribute}
279
- sx={filterFormStyles.formListItem}
296
+ {Object.entries(groupedFilters).map(([entityType, filters]) => (
297
+ <Box
298
+ key={entityType}
299
+ sx={{
300
+ border: "1px solid #c5c5c5",
301
+ borderRadius: 2,
302
+ overflow: "hidden",
303
+ }}
304
+ >
305
+ {/* Group Header */}
306
+ <Box
307
+ className="group-header"
308
+ sx={filterFormStyles.formListSectionHeader}
309
+ >
310
+ <Typography fontSize={14}>{entityType}</Typography>
311
+ <IconButton
312
+ size="small"
313
+ onClick={() => handleRemoveEntityType(entityType)}
280
314
  >
281
- <Box sx={filterFormStyles.formListItemHeader}>
282
- <Typography sx={filterFormStyles.formListItemHeaderName}>
283
- {filter.name}
284
- </Typography>
285
- <FormDropdown
286
- filter={filter}
287
- control={control}
288
- setValue={setValue}
289
- dropdownList={dropdown_list}
290
- sx={filterFormStyles.formListItemHeaderDropdown}
291
- onValueChange={updateFiltersFromForm}
292
- />
293
- <IconButton
294
- sx={{ marginLeft: "auto" }}
295
- onClick={() => {
296
- unregister(`${filter.name}.value`);
297
- unregister(`${filter.name}.operator`);
298
-
299
- // ✅ Toggle dummy field to force form dirty
300
- const dummy = watch("dummyChange");
301
- setValue(
302
- "dummyChange",
303
- dummy === "changed" ? "reset" : "changed",
304
- {
305
- shouldDirty: true,
306
- }
307
- );
308
-
309
- handleRemoveFilter(filter.filter_attribute);
310
- }}
311
- size="small"
315
+ <CloseIcon />
316
+ </IconButton>
317
+ </Box>
318
+
319
+ {filters
320
+ .filter(
321
+ (filter) =>
322
+ filter.name
323
+ ?.toLowerCase()
324
+ .includes(searchTerm.toLowerCase()) ||
325
+ filter.filter_value
326
+ ?.toString()
327
+ .toLowerCase()
328
+ .includes(searchTerm.toLowerCase())
329
+ )
330
+ .reverse()
331
+ .map((filter) => {
332
+ const dropdown_list = filter.dropdown_list || [];
333
+ return (
334
+ <Box
335
+ key={filter.filter_attribute}
336
+ sx={filterFormStyles.formListItem}
312
337
  >
313
- <CloseIcon />
314
- </IconButton>
315
- </Box>
316
-
317
- <Box>
318
- {filter.data_type === "text" ||
319
- filter.data_type === "number" ? (
320
- <FormTextfield
321
- filter={filter}
322
- control={control}
323
- onValueChange={updateFiltersFromForm}
324
- />
325
- ) : filter.data_type === "year" ? (
326
- <FormDatePicker
327
- filter={filter}
328
- control={control}
329
- views={["year"]}
330
- onValueChange={updateFiltersFromForm}
331
- />
332
- ) : filter.data_type === "date" ? (
333
- <FormDatePicker
334
- filter={filter}
335
- control={control}
336
- onValueChange={updateFiltersFromForm}
337
- />
338
- ) : filter.data_type === "select" ? (
339
- <FormMultiSelect
340
- filter={filter}
341
- control={control}
342
- dropdownData={dropdownData}
343
- onValueChange={updateFiltersFromForm}
344
- />
345
- ) : filter.data_type === "multiselect" ? (
346
- <FormMultiSelect
347
- filter={filter}
348
- control={control}
349
- dropdownData={dropdownData}
350
- onValueChange={updateFiltersFromForm}
351
- />
352
- ) : filter.data_type === "radio" ? (
353
- <FormMultiSelect
354
- filter={filter}
355
- control={control}
356
- dropdownData={dropdownData}
357
- onValueChange={updateFiltersFromForm}
358
- />
359
- ) : filter.data_type === "checkbox" ? (
360
- <FormMultiSelect
361
- filter={filter}
362
- control={control}
363
- dropdownData={dropdownData}
364
- onValueChange={updateFiltersFromForm}
365
- />
366
- ) : (
367
- <FormControl fullWidth size="small" />
368
- )}
369
- </Box>
370
- </Box>
371
- );
372
- })}
338
+ <Box sx={filterFormStyles.formListItemHeader}>
339
+ <Typography
340
+ sx={filterFormStyles.formListItemHeaderName}
341
+ >
342
+ {filter.name}
343
+ </Typography>
344
+ <FormDropdown
345
+ filter={filter}
346
+ control={control}
347
+ setValue={setValue}
348
+ dropdownList={dropdown_list}
349
+ sx={filterFormStyles.formListItemHeaderDropdown}
350
+ onValueChange={updateFiltersFromForm}
351
+ />
352
+ <IconButton
353
+ sx={{ marginLeft: "auto" }}
354
+ onClick={() => {
355
+ unregister(`${filter.name}.value`);
356
+ unregister(`${filter.name}.operator`);
357
+
358
+ // ✅ Toggle dummy field to force form dirty
359
+ const dummy = watch("dummyChange");
360
+ setValue(
361
+ "dummyChange",
362
+ dummy === "changed" ? "reset" : "changed",
363
+ {
364
+ shouldDirty: true,
365
+ }
366
+ );
367
+
368
+ handleRemoveFilter(filter.filter_attribute);
369
+ }}
370
+ size="small"
371
+ >
372
+ <CloseIcon />
373
+ </IconButton>
374
+ </Box>
375
+
376
+ <Box>
377
+ {filter.data_type === "text" ||
378
+ filter.data_type === "number" ? (
379
+ <FormTextfield
380
+ filter={filter}
381
+ control={control}
382
+ onValueChange={updateFiltersFromForm}
383
+ />
384
+ ) : filter.data_type === "year" ? (
385
+ <FormDatePicker
386
+ filter={filter}
387
+ control={control}
388
+ views={["year"]}
389
+ onValueChange={updateFiltersFromForm}
390
+ />
391
+ ) : filter.data_type === "date" ? (
392
+ <FormDatePicker
393
+ filter={filter}
394
+ control={control}
395
+ onValueChange={updateFiltersFromForm}
396
+ />
397
+ ) : filter.data_type === "select" ? (
398
+ <FormMultiSelect
399
+ filter={filter}
400
+ control={control}
401
+ dropdownData={dropdownData}
402
+ onValueChange={updateFiltersFromForm}
403
+ />
404
+ ) : filter.data_type === "multiselect" ? (
405
+ <FormMultiSelect
406
+ filter={filter}
407
+ control={control}
408
+ dropdownData={dropdownData}
409
+ onValueChange={updateFiltersFromForm}
410
+ />
411
+ ) : filter.data_type === "radio" ? (
412
+ <FormMultiSelect
413
+ filter={filter}
414
+ control={control}
415
+ dropdownData={dropdownData}
416
+ onValueChange={updateFiltersFromForm}
417
+ />
418
+ ) : filter.data_type === "checkbox" ? (
419
+ <FormMultiSelect
420
+ filter={filter}
421
+ control={control}
422
+ dropdownData={dropdownData}
423
+ onValueChange={updateFiltersFromForm}
424
+ />
425
+ ) : (
426
+ <FormControl fullWidth size="small" />
427
+ )}
428
+ </Box>
429
+ </Box>
430
+ );
431
+ })}
432
+ </Box>
433
+ ))}
373
434
  </Box>
374
435
  </Box>
375
436
  </Box>
376
437
 
377
- {selectedFilters.length > 0 && (
438
+ {filters?.length > 0 && (showClearAllButton || showSaveButton) && (
378
439
  <Box sx={{ display: "flex", justifyContent: "center", gap: 1, mt: 3 }}>
379
- <Button
380
- variant="outlined"
381
- sx={{
382
- color: "#7A5AF8",
383
- border: `1px solid #7A5AF8`,
384
- borderRadius: "6px",
385
- textTransform: "none",
386
- fontSize: "14px",
387
- }}
388
- fullWidth
389
- onClick={() => {
390
- setFilters([]);
391
- setSelectedFilters([]);
392
-
393
- const filterMaster = {
394
- ...tableStates.filterMaster,
395
- activeFilterTabIndex: -1,
396
- };
397
-
398
- const newState = {
399
- filterMaster: filterMaster,
400
- filters: [],
401
- };
402
-
403
- onChangeFunction && onChangeFunction(newState);
404
- }}
405
- >
406
- Clear All
407
- </Button>
440
+ {showClearAllButton && (
441
+ <Button
442
+ variant="outlined"
443
+ sx={{
444
+ color: "#7A5AF8",
445
+ border: `1px solid #7A5AF8`,
446
+ borderRadius: "6px",
447
+ textTransform: "none",
448
+ fontSize: "14px",
449
+ }}
450
+ fullWidth
451
+ onClick={() => {
452
+ setFilters([]);
408
453
 
409
- <Button
410
- variant="contained"
411
- fullWidth
412
- sx={{
413
- color: "white",
414
- backgroundColor: "#7A5AF8",
415
- "&.Mui-disabled": {
416
- backgroundColor: "#d7cefd",
417
- color: "rgba(255, 255, 255, 0.7)",
418
- },
419
- }}
420
- onClick={() => {
421
- setSavedFilterModalOpen && setSavedFilterModalOpen(true);
422
- }}
423
- >
424
- Save Filter
425
- </Button>
454
+ const filterMaster = {
455
+ ...tableStates.filterMaster,
456
+ activeFilterTabIndex: -1,
457
+ };
458
+
459
+ const newState = {
460
+ filterMaster: filterMaster,
461
+ filters: [],
462
+ };
463
+
464
+ onChangeFunction && onChangeFunction(newState);
465
+ }}
466
+ >
467
+ Clear All
468
+ </Button>
469
+ )}
470
+
471
+ {showSaveButton && (
472
+ <Button
473
+ variant="contained"
474
+ fullWidth
475
+ sx={{
476
+ color: "white",
477
+ backgroundColor: "#7A5AF8",
478
+ "&.Mui-disabled": {
479
+ backgroundColor: "#d7cefd",
480
+ color: "rgba(255, 255, 255, 0.7)",
481
+ },
482
+ }}
483
+ onClick={() => {
484
+ setSavedFilterModalOpen && setSavedFilterModalOpen(true);
485
+ }}
486
+ >
487
+ Save Filter
488
+ </Button>
489
+ )}
426
490
  </Box>
427
491
  )}
428
492
  </form>