@addev-be/ui 0.2.16 → 0.2.18

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 (88) hide show
  1. package/eslint.config.js +28 -0
  2. package/package.json +13 -20
  3. package/src/Icons.tsx +108 -0
  4. package/src/components/data/AdvancedRequestDataGrid/helpers/advancedRequests.ts +93 -0
  5. package/src/components/data/AdvancedRequestDataGrid/helpers/columns.tsx +262 -0
  6. package/src/components/data/AdvancedRequestDataGrid/helpers/index.ts +2 -0
  7. package/src/components/data/AdvancedRequestDataGrid/index.tsx +267 -0
  8. package/src/components/data/AdvancedRequestDataGrid/types.ts +47 -0
  9. package/src/components/data/DataGrid/DataGridCell.tsx +73 -0
  10. package/src/components/data/DataGrid/DataGridColumnsModal/helpers.ts +14 -0
  11. package/src/components/data/DataGrid/DataGridColumnsModal/hooks.tsx +59 -0
  12. package/src/components/data/DataGrid/DataGridColumnsModal/index.tsx +181 -0
  13. package/src/components/data/DataGrid/DataGridColumnsModal/styles.ts +104 -0
  14. package/src/components/data/DataGrid/DataGridEditableCell.tsx +43 -0
  15. package/src/components/data/DataGrid/DataGridFilterMenu/FilterValuesScroller.tsx +120 -0
  16. package/src/components/data/DataGrid/DataGridFilterMenu/hooks.tsx +75 -0
  17. package/src/components/data/DataGrid/DataGridFilterMenu/index.tsx +360 -0
  18. package/src/components/data/DataGrid/DataGridFilterMenu/styles.ts +96 -0
  19. package/src/components/data/DataGrid/DataGridFooter.tsx +42 -0
  20. package/src/components/data/DataGrid/DataGridHeader.tsx +126 -0
  21. package/src/components/data/DataGrid/DataGridHeaderCell.tsx +132 -0
  22. package/src/components/data/DataGrid/FilterModalContent/index.tsx +136 -0
  23. package/src/components/data/DataGrid/FilterModalContent/styles.ts +22 -0
  24. package/src/components/data/DataGrid/VirtualScroller.tsx +46 -0
  25. package/src/components/data/DataGrid/helpers/columns.tsx +295 -0
  26. package/src/components/data/DataGrid/helpers/filters.ts +287 -0
  27. package/src/components/data/DataGrid/helpers/index.ts +2 -0
  28. package/src/components/data/DataGrid/hooks/index.ts +30 -0
  29. package/src/components/data/DataGrid/hooks/useDataGrid.tsx +306 -0
  30. package/src/components/data/DataGrid/hooks/useDataGridCopy.ts +175 -0
  31. package/src/components/data/DataGrid/hooks/useDataGridSettings.ts +48 -0
  32. package/src/components/data/DataGrid/index.tsx +140 -0
  33. package/src/components/data/DataGrid/styles.ts +323 -0
  34. package/src/components/data/DataGrid/types.ts +267 -0
  35. package/src/components/data/SqlRequestDataGrid/helpers/columns.tsx +277 -0
  36. package/src/components/data/SqlRequestDataGrid/helpers/index.ts +2 -0
  37. package/src/components/data/SqlRequestDataGrid/helpers/sqlRequests.ts +16 -0
  38. package/src/components/data/SqlRequestDataGrid/index.tsx +347 -0
  39. package/src/components/data/SqlRequestDataGrid/types.ts +47 -0
  40. package/src/components/data/index.ts +8 -0
  41. package/src/components/forms/Button.tsx +99 -0
  42. package/src/components/forms/IconButton.tsx +56 -0
  43. package/src/components/forms/IndeterminateCheckbox.tsx +46 -0
  44. package/src/components/forms/Select.tsx +40 -0
  45. package/src/components/forms/index.ts +5 -0
  46. package/src/components/forms/styles.ts +20 -0
  47. package/src/components/index.ts +3 -0
  48. package/src/components/layout/Dropdown/index.tsx +79 -0
  49. package/src/components/layout/Dropdown/styles.ts +44 -0
  50. package/src/components/layout/Loading/index.tsx +29 -0
  51. package/src/components/layout/Loading/styles.ts +29 -0
  52. package/src/components/layout/Modal/index.tsx +51 -0
  53. package/src/components/layout/Modal/styles.ts +110 -0
  54. package/src/components/layout/index.ts +3 -0
  55. package/src/components/ui/ContextMenu/index.tsx +79 -0
  56. package/src/components/ui/ContextMenu/styles.ts +119 -0
  57. package/src/config/index.ts +14 -0
  58. package/src/helpers/dates.ts +9 -0
  59. package/src/helpers/getScrollbarSize.ts +14 -0
  60. package/src/helpers/numbers.ts +26 -0
  61. package/src/hooks/index.ts +2 -0
  62. package/src/hooks/useElementSize.ts +24 -0
  63. package/src/hooks/useWindowSize.ts +20 -0
  64. package/src/index.ts +7 -0
  65. package/src/providers/PortalsProvider/index.tsx +54 -0
  66. package/src/providers/PortalsProvider/styles.ts +27 -0
  67. package/src/providers/SettingsProvider/index.tsx +70 -0
  68. package/src/providers/ThemeProvider/ThemeProvider.ts +55 -0
  69. package/src/providers/ThemeProvider/defaultTheme.ts +444 -0
  70. package/src/providers/ThemeProvider/index.ts +3 -0
  71. package/src/providers/ThemeProvider/types.ts +123 -0
  72. package/src/providers/UiProviders/index.tsx +65 -0
  73. package/src/providers/UiProviders/styles.ts +10 -0
  74. package/src/providers/hooks.ts +8 -0
  75. package/src/providers/index.ts +5 -0
  76. package/src/services/HttpService.ts +80 -0
  77. package/src/services/WebSocketService.ts +147 -0
  78. package/src/services/advancedRequests.ts +101 -0
  79. package/src/services/base.ts +31 -0
  80. package/src/services/globalSearch.ts +27 -0
  81. package/src/services/hooks.ts +23 -0
  82. package/src/services/index.ts +2 -0
  83. package/src/services/sqlRequests.ts +110 -0
  84. package/src/styles/animations.scss +30 -0
  85. package/src/styles/index.scss +42 -0
  86. package/src/typings.d.ts +6 -0
  87. package/tsconfig.json +18 -0
  88. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,104 @@
1
+ import styled, { css } from 'styled-components';
2
+
3
+ export const Container = styled.div.attrs({
4
+ className: 'DataGridColumnsModal-Container',
5
+ })`
6
+ display: flex;
7
+ flex-direction: row;
8
+ gap: var(--space-2);
9
+ overflow: auto;
10
+ font-size: var(--text-base);
11
+ height: 100%;
12
+ `;
13
+
14
+ export const Panel = styled.div.attrs({
15
+ className: 'DataGridColumnsModal-Panel',
16
+ })`
17
+ display: flex;
18
+ flex-direction: column;
19
+ flex: 1;
20
+ `;
21
+
22
+ export const Title = styled.div.attrs({
23
+ className: 'DataGridColumnsModal-Title',
24
+ })`
25
+ margin: 0;
26
+ margin-bottom: var(--space-2);
27
+ font-weight: bold;
28
+ `;
29
+
30
+ export const ColumnsList = styled.ul.attrs({
31
+ className: 'DataGridColumnsModal-ColumnsList',
32
+ })`
33
+ display: flex;
34
+ flex-direction: column;
35
+ flex: 1;
36
+ overflow: auto;
37
+ border: 1px solid var(--color-neutral-200);
38
+ margin: 0;
39
+ background-color: var(--color-neutral-50);
40
+
41
+ & > li:not(:last-child) {
42
+ border-bottom: 1px solid var(--color-neutral-100);
43
+ }
44
+ `;
45
+
46
+ export const ColumnItem = styled.li.attrs({
47
+ className: 'DataGridColumnsModal-ColumnItem',
48
+ })<{
49
+ $visible?: boolean;
50
+ }>`
51
+ padding: var(--space-1);
52
+ transition: background-color 0.2s;
53
+ cursor: pointer;
54
+ display: flex;
55
+ flex-direction: row;
56
+ align-items: center;
57
+ gap: var(--space-2);
58
+
59
+ span {
60
+ flex: 1;
61
+ }
62
+
63
+ button {
64
+ display: none;
65
+ padding: var(--space-1);
66
+ width: var(--space-6);
67
+ height: var(--space-6);
68
+ border-radius: var(--rounded-full);
69
+ background-color: var(--color-neutral-200);
70
+ border: none;
71
+ align-items: center;
72
+ justify-content: center;
73
+ }
74
+
75
+ &:hover {
76
+ background-color: var(--color-neutral-100);
77
+ button {
78
+ display: flex;
79
+ background-color: var(--color-neutral-300);
80
+ }
81
+ }
82
+ `;
83
+
84
+ export const Buttons = styled.div.attrs({
85
+ className: 'DataGridColumnsModal-Buttons',
86
+ })<{ $visible?: boolean }>`
87
+ display: flex;
88
+ gap: var(--space-1);
89
+ height: var(--space-6);
90
+ ${({ $visible }) =>
91
+ $visible
92
+ ? css`
93
+ width: var(--space-24);
94
+ justify-content: flex-end;
95
+ `
96
+ : css`
97
+ width: var(--space-6);
98
+ `}
99
+
100
+ svg {
101
+ width: var(--space-4);
102
+ height: var(--space-4);
103
+ }
104
+ `;
@@ -0,0 +1,43 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
3
+
4
+ import { useCallback, useState } from 'react';
5
+
6
+ import { DataGridCellProps } from './types';
7
+ import { useDataGridContext } from './hooks';
8
+
9
+ export const DataGridEditableCell = <R,>({
10
+ row,
11
+ columnKey,
12
+ column,
13
+ context,
14
+ }: DataGridCellProps<R>) => {
15
+ const { onCellEdited, setEditingCell } = useDataGridContext(context);
16
+ const [value, setValue] = useState(
17
+ column.getter
18
+ ? column.getter(row)
19
+ : column.propertyName
20
+ ? String(row[column.propertyName])
21
+ : ''
22
+ );
23
+
24
+ const onClose = useCallback(() => {
25
+ onCellEdited?.(row, columnKey, value);
26
+ setEditingCell([-1, -1]);
27
+ }, [columnKey, onCellEdited, row, setEditingCell, value]);
28
+
29
+ return (
30
+ <td key={columnKey}>
31
+ <input
32
+ type="text"
33
+ // className="relative z-10 block text-normal w-full border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-sky-600 sm:text-sm sm:leading-6"
34
+ value={String(value)}
35
+ onChange={(e) => setValue(e.target.value)}
36
+ />
37
+ <div
38
+ // className="fixed inset-0 bg-black bg-opacity-10"
39
+ onClick={onClose}
40
+ />
41
+ </td>
42
+ );
43
+ };
@@ -0,0 +1,120 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import * as styles from './styles';
4
+
5
+ import {
6
+ DataGridFilterCheckbox,
7
+ DataGridFilterFormatter,
8
+ DataGridFilterGroup,
9
+ DataGridFilterRenderer,
10
+ DataGridFilterValue,
11
+ } from '../types';
12
+ import { defaultRendererAndFormatter, getCheckboxes } from '../helpers';
13
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
14
+
15
+ import { debounce } from 'lodash';
16
+ import { useElementSize } from '../../../../hooks';
17
+
18
+ const CheckboxTemplate = ({
19
+ selectedValues,
20
+ value,
21
+ index,
22
+ style,
23
+ onToggle,
24
+ }: {
25
+ selectedValues: DataGridFilterValue[];
26
+ value: DataGridFilterCheckbox;
27
+ index: number;
28
+ style?: React.CSSProperties;
29
+ onToggle?: (values: DataGridFilterValue[]) => void;
30
+ }) => {
31
+ const checked = useMemo(
32
+ () => value.values.every((v) => selectedValues.includes(v)),
33
+ [selectedValues, value.values]
34
+ );
35
+ return (
36
+ <styles.FilterValueContainer
37
+ key={index}
38
+ style={{ ...style, paddingLeft: `${value.level}rem` }}
39
+ title={value.title}
40
+ onClick={() => onToggle?.(value.values)}
41
+ >
42
+ <input type="checkbox" checked={checked} readOnly />
43
+ <span>{value.displayValue || '(Vides)'}</span>
44
+ </styles.FilterValueContainer>
45
+ );
46
+ };
47
+
48
+ type FilterValuesScrollerProps = {
49
+ values: DataGridFilterValue[];
50
+ rowHeight?: number;
51
+ formatter: DataGridFilterFormatter;
52
+ renderer: DataGridFilterRenderer;
53
+ onRangeChange?: (startIndex: number, length: number) => void;
54
+ onToggle?: (values: DataGridFilterValue[]) => void;
55
+ selectedValues?: DataGridFilterValue[];
56
+ groups?: DataGridFilterGroup[];
57
+ };
58
+
59
+ export const FilterValuesScroller = ({
60
+ values,
61
+ rowHeight = styles.DEFAULT_FILTER_ROW_HEIGHT,
62
+ onRangeChange,
63
+ onToggle,
64
+ formatter = defaultRendererAndFormatter,
65
+ renderer = defaultRendererAndFormatter,
66
+ selectedValues = [],
67
+ groups,
68
+ }: FilterValuesScrollerProps) => {
69
+ const scrollableRef = useRef<HTMLDivElement | null>(null);
70
+ const { height } = useElementSize(scrollableRef.current);
71
+
72
+ const tolerance = 20;
73
+ const [scrollTop, setScrollTop] = useState(0);
74
+ const index = Math.floor(scrollTop / rowHeight);
75
+ const length = Math.ceil(height / rowHeight);
76
+
77
+ // eslint-disable-next-line react-hooks/exhaustive-deps
78
+ const onScroll = useCallback(
79
+ debounce((e: React.UIEvent<HTMLDivElement>) => {
80
+ const target = e.target as HTMLDivElement;
81
+ setScrollTop(() => target.scrollTop);
82
+ }, 50),
83
+ []
84
+ );
85
+
86
+ const checkboxes = useMemo(
87
+ () => getCheckboxes(values, '', renderer, formatter, groups),
88
+ [values, groups, renderer, formatter]
89
+ );
90
+ const visibleCheckboxes = checkboxes.slice(
91
+ Math.max(0, index - tolerance),
92
+ index + length + 1 + tolerance
93
+ );
94
+ const firstCheckboxTop = Math.max(0, index - tolerance) * rowHeight;
95
+
96
+ useEffect(() => {
97
+ onRangeChange?.(index, length);
98
+ }, [index, length, onRangeChange]);
99
+
100
+ return (
101
+ <styles.FilterValuesScrollerContainer
102
+ ref={scrollableRef}
103
+ onScroll={onScroll}
104
+ $rowHeight={rowHeight}
105
+ >
106
+ <div style={{ height: `${checkboxes.length * rowHeight}px` }}>
107
+ {visibleCheckboxes.map((value, index) => (
108
+ <CheckboxTemplate
109
+ style={{ top: firstCheckboxTop + index * rowHeight + 'px' }}
110
+ key={index}
111
+ selectedValues={selectedValues}
112
+ value={value}
113
+ index={index}
114
+ onToggle={onToggle}
115
+ />
116
+ ))}
117
+ </div>
118
+ </styles.FilterValuesScrollerContainer>
119
+ );
120
+ };
@@ -0,0 +1,75 @@
1
+ import { DataGridContext, DataGridFilter } from '../types';
2
+ import { FilterIcon, FilterSlashIcon } from '../../../../Icons';
3
+ import { useCallback, useState } from 'react';
4
+
5
+ import { Button } from '../../../forms';
6
+ import { FilterModalContent } from '../FilterModalContent';
7
+ import { Modal } from '../../../layout/Modal';
8
+ import { useDataGridContext } from '../hooks';
9
+
10
+ export const useFilterModal = <R,>({
11
+ columnKey,
12
+ context,
13
+ }: {
14
+ columnKey: string;
15
+ context: DataGridContext<R>;
16
+ }) => {
17
+ const [isVisible, setIsVisible] = useState(false);
18
+ const { filters = {}, columns, setFilters } = useDataGridContext<R>(context);
19
+ const column = columns[columnKey];
20
+ const [currentFilter, setCurrentFilter] = useState<DataGridFilter>(
21
+ filters[columnKey] ?? column?.filter
22
+ );
23
+
24
+ const openModal = useCallback(() => {
25
+ setIsVisible(true);
26
+ }, []);
27
+ const closeModal = useCallback(() => {
28
+ setIsVisible(false);
29
+ }, []);
30
+
31
+ const onClearClicked = useCallback(() => {
32
+ const newFilters = { ...filters };
33
+ delete newFilters[columnKey];
34
+ setFilters(newFilters);
35
+ closeModal();
36
+ }, [closeModal, columnKey, filters, setFilters]);
37
+
38
+ const onApplyClicked = useCallback(() => {
39
+ setFilters({ ...filters, [columnKey]: currentFilter });
40
+ closeModal();
41
+ }, [closeModal, columnKey, currentFilter, filters, setFilters]);
42
+
43
+ const onCancelClicked = useCallback(() => {
44
+ closeModal();
45
+ }, [closeModal]);
46
+
47
+ const modal = isVisible ? (
48
+ <Modal>
49
+ <Modal.Header>Filtrer "{column.name}"</Modal.Header>
50
+ <Modal.ContentWithIcon>
51
+ <FilterIcon fill="var(--color-primary-400)" />
52
+ <FilterModalContent
53
+ filter={currentFilter}
54
+ onFilterChanged={setCurrentFilter}
55
+ />
56
+ </Modal.ContentWithIcon>
57
+ <Modal.Buttons>
58
+ <Button color="danger" icon={FilterSlashIcon} onClick={onClearClicked}>
59
+ Supprimer
60
+ </Button>
61
+ <Button
62
+ style={{ marginLeft: 'auto' }}
63
+ color="primary"
64
+ icon={FilterIcon}
65
+ onClick={onApplyClicked}
66
+ >
67
+ Filtrer
68
+ </Button>
69
+ <Button onClick={onCancelClicked}>Annuler</Button>
70
+ </Modal.Buttons>
71
+ </Modal>
72
+ ) : null;
73
+
74
+ return { openModal, closeModal, modal };
75
+ };
@@ -0,0 +1,360 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
3
+
4
+ import * as styles from './styles';
5
+
6
+ import {
7
+ ArrowDown19Icon,
8
+ ArrowDownAZIcon,
9
+ ArrowDownBigSmallIcon,
10
+ ArrowUp91Icon,
11
+ ArrowUpBigSmallIcon,
12
+ ArrowUpZAIcon,
13
+ FilterIcon,
14
+ FilterSlashIcon,
15
+ IconFC,
16
+ MagnifierIcon,
17
+ SigmaIcon,
18
+ SortCalendarAscendingIcon,
19
+ SortCalendarDescendingIcon,
20
+ TableFooterIcon,
21
+ TableFooterSlashIcon,
22
+ TallyIcon,
23
+ XBarIcon,
24
+ } from '../../../../Icons';
25
+ import {
26
+ DataGridContext,
27
+ DataGridFilterType,
28
+ DataGridFilterValue,
29
+ DataGridFooterPredefinedFunction,
30
+ } from '../types';
31
+ import {
32
+ applyFilters,
33
+ defaultRendererAndFormatter,
34
+ getDateGroups,
35
+ } from '../helpers';
36
+ import { intersection, uniq, without } from 'lodash';
37
+ import {
38
+ useCallback,
39
+ useContext,
40
+ useEffect,
41
+ useMemo,
42
+ useRef,
43
+ useState,
44
+ } from 'react';
45
+
46
+ import { ContextMenu } from '../../../ui/ContextMenu';
47
+ import { FilterValuesScroller } from './FilterValuesScroller';
48
+ import { Input } from '../../../forms';
49
+ import { useFilterModal } from './hooks';
50
+
51
+ type FilterValuesProps<R> = {
52
+ columnKey: string;
53
+ columnIndex: number;
54
+ context: DataGridContext<R>;
55
+ onClose?: () => void;
56
+ };
57
+
58
+ const sortAsc: Record<DataGridFilterType, [string, IconFC]> = {
59
+ number: ['Trier du plus petit au plus grand', ArrowDown19Icon],
60
+ text: ['Trier de A à Z', ArrowDownAZIcon],
61
+ date: ['Trier du plus ancien au plus récent', SortCalendarAscendingIcon],
62
+ };
63
+ const sortDesc: Record<DataGridFilterType, [string, IconFC]> = {
64
+ number: ['Trier du plus grand au plus petit', ArrowUp91Icon],
65
+ text: ['Trier de Z à A', ArrowUpZAIcon],
66
+ date: ['Trier du plus récent au plus ancien', SortCalendarDescendingIcon],
67
+ };
68
+
69
+ const footerFunctionsTexts: Record<DataGridFooterPredefinedFunction, string> = {
70
+ average: 'Moyenne',
71
+ avg: 'Moyenne',
72
+ count: 'Nombre',
73
+ max: 'Maximum',
74
+ min: 'Minimum',
75
+ sum: 'Somme',
76
+ };
77
+ const footerFunctionsIcons: Record<DataGridFooterPredefinedFunction, IconFC> = {
78
+ average: XBarIcon,
79
+ avg: XBarIcon,
80
+ count: TallyIcon,
81
+ max: ArrowUpBigSmallIcon,
82
+ min: ArrowDownBigSmallIcon,
83
+ sum: SigmaIcon,
84
+ };
85
+
86
+ export const DataGridFilterMenu = <R,>({
87
+ columnKey,
88
+ context,
89
+ onClose,
90
+ }: FilterValuesProps<R>) => {
91
+ const { openModal, modal } = useFilterModal({ columnKey, context });
92
+ const {
93
+ filters = {},
94
+ footers = {},
95
+ rows,
96
+ columns,
97
+ setFilters,
98
+ filterValuesLoader,
99
+ setSorts,
100
+ setFooters,
101
+ } = useContext(context);
102
+ const column = columns[columnKey] ?? {};
103
+ const textFilterInputRef = useRef<HTMLInputElement>(null);
104
+ const [textFilter, setTextFilter] = useState('');
105
+
106
+ const [availableValues, setAvailableValues] = useState<DataGridFilterValue[]>(
107
+ []
108
+ );
109
+
110
+ useEffect(() => {
111
+ if (filterValuesLoader) {
112
+ filterValuesLoader(columnKey).then((values) => {
113
+ setAvailableValues(() => values);
114
+ });
115
+ } else {
116
+ const otherFilters = Object.entries(filters)
117
+ .filter(([key]) => key !== columnKey)
118
+ .map(([, filter]) => filter);
119
+ const availableRows = applyFilters(rows, otherFilters);
120
+ const values = availableRows.map((row) => column.filter!.getter(row));
121
+ setAvailableValues(() =>
122
+ uniq(values).sort(
123
+ column.filter?.type === 'number'
124
+ ? (a, b) => (a as number) - (b as number)
125
+ : (a, b) => (a as string).localeCompare(b as string)
126
+ )
127
+ );
128
+ }
129
+ }, [column.filter, columnKey, filterValuesLoader, filters, rows]);
130
+
131
+ const selectedValues = useMemo(
132
+ () => filters?.[columnKey]?.values ?? [],
133
+ [columnKey, filters]
134
+ );
135
+
136
+ const clearFilter = useCallback(() => {
137
+ const newFilters = { ...filters };
138
+ delete newFilters[columnKey];
139
+ setFilters(newFilters);
140
+ onClose?.();
141
+ }, [filters, columnKey, setFilters, onClose]);
142
+
143
+ const setValuesChecked = useCallback(
144
+ (values: any[], checked?: boolean) => {
145
+ setFilters((prevFilters) => {
146
+ const newValues = checked
147
+ ? [...(prevFilters[columnKey]?.values ?? []), ...values]
148
+ : without(prevFilters[columnKey]?.values ?? [], ...values);
149
+ const newFilters = {
150
+ ...prevFilters,
151
+ };
152
+ if (newValues.length === 0) {
153
+ delete newFilters[columnKey];
154
+ } else {
155
+ newFilters[columnKey] = {
156
+ ...(prevFilters[columnKey] ?? column.filter),
157
+ operator: 'inArray',
158
+ values: newValues,
159
+ };
160
+ }
161
+ return newFilters;
162
+ });
163
+ },
164
+ [setFilters, columnKey, column.filter]
165
+ );
166
+
167
+ const toggleValues = useCallback(
168
+ (values: DataGridFilterValue[]) => {
169
+ const checked =
170
+ intersection(selectedValues, values).length === values.length;
171
+ setValuesChecked(values, !checked);
172
+ },
173
+ [setValuesChecked, selectedValues]
174
+ );
175
+
176
+ const formatter = useMemo(
177
+ () => column.filter?.formatter ?? defaultRendererAndFormatter,
178
+ [column.filter?.formatter]
179
+ );
180
+ const renderer = useMemo(
181
+ () => column.filter?.renderer ?? defaultRendererAndFormatter,
182
+ [column.filter?.renderer]
183
+ );
184
+
185
+ const filteredAvailableValues = useMemo(
186
+ () =>
187
+ !textFilter
188
+ ? availableValues
189
+ : availableValues.filter((value) =>
190
+ formatter(value)?.toLowerCase().includes(textFilter.toLowerCase())
191
+ ),
192
+ [availableValues, formatter, textFilter]
193
+ );
194
+
195
+ useEffect(() => {
196
+ if (textFilterInputRef.current) {
197
+ textFilterInputRef.current.focus();
198
+ }
199
+ }, []);
200
+
201
+ const checkboxesComponent = useMemo(() => {
202
+ const groups =
203
+ column.type === 'date'
204
+ ? getDateGroups(filteredAvailableValues)
205
+ : undefined;
206
+ return (
207
+ <FilterValuesScroller
208
+ values={filteredAvailableValues}
209
+ selectedValues={selectedValues}
210
+ onToggle={toggleValues}
211
+ formatter={formatter}
212
+ renderer={renderer}
213
+ groups={groups}
214
+ />
215
+ );
216
+ }, [
217
+ column.type,
218
+ filteredAvailableValues,
219
+ formatter,
220
+ renderer,
221
+ selectedValues,
222
+ toggleValues,
223
+ ]);
224
+
225
+ const onSortAscClicked = useCallback(() => {
226
+ setSorts({ [columnKey]: 'asc' });
227
+ onClose?.();
228
+ }, [columnKey, onClose, setSorts]);
229
+ const onSortDescClicked = useCallback(() => {
230
+ setSorts({ [columnKey]: 'desc' });
231
+ onClose?.();
232
+ }, [columnKey, onClose, setSorts]);
233
+
234
+ const hasFilters = filters[columnKey]?.values.length > 0;
235
+ const [[sortAscText, SortAscIcon], [sortDescText, SortDescIcon]] = [
236
+ sortAsc[column.filter?.type ?? 'text'],
237
+ sortDesc[column.filter?.type ?? 'text'],
238
+ ];
239
+
240
+ const isFooterVisible =
241
+ columnKey in footers && footers[columnKey] !== undefined;
242
+ const showFooter = useCallback(
243
+ (key: string) => {
244
+ setFooters((prevFooters) => ({
245
+ ...prevFooters,
246
+ [columnKey]: key,
247
+ }));
248
+ onClose?.();
249
+ },
250
+ [columnKey, onClose, setFooters]
251
+ );
252
+ const hideFooter = useCallback(() => {
253
+ setFooters((prevFooters) => {
254
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
255
+ const { [columnKey]: _, ...newFooters } = prevFooters;
256
+ return newFooters;
257
+ });
258
+ onClose?.();
259
+ }, [columnKey, onClose, setFooters]);
260
+
261
+ return (
262
+ <ContextMenu>
263
+ {modal}
264
+ {column.sortGetter && (
265
+ <>
266
+ <ContextMenu.Item onClick={onSortAscClicked}>
267
+ <SortAscIcon />
268
+ {sortAscText}
269
+ </ContextMenu.Item>
270
+ <ContextMenu.Item onClick={onSortDescClicked}>
271
+ <SortDescIcon />
272
+ {sortDescText}
273
+ </ContextMenu.Item>
274
+ <ContextMenu.Divider />
275
+ </>
276
+ )}
277
+ {isFooterVisible && typeof column.footer === 'function' && (
278
+ <>
279
+ <ContextMenu.Item onClick={hideFooter}>
280
+ <TableFooterSlashIcon />
281
+ Masquer le total
282
+ </ContextMenu.Item>
283
+ <ContextMenu.Divider />
284
+ </>
285
+ )}
286
+ {!isFooterVisible && typeof column.footer === 'function' && (
287
+ <>
288
+ <ContextMenu.Item onClick={() => showFooter('count')}>
289
+ <TableFooterIcon />
290
+ Afficher le total
291
+ </ContextMenu.Item>
292
+ <ContextMenu.Divider />
293
+ </>
294
+ )}
295
+ {typeof column.footer === 'object' && (
296
+ <>
297
+ <ContextMenu.ParentItem>
298
+ <TableFooterIcon />
299
+ Afficher le total
300
+ <ContextMenu.SubMenu>
301
+ {Object.keys(column.footer).map((key) => {
302
+ const TotalIcon =
303
+ footerFunctionsIcons[
304
+ key as DataGridFooterPredefinedFunction
305
+ ] ?? TableFooterIcon;
306
+ return (
307
+ <ContextMenu.Item key={key} onClick={() => showFooter(key)}>
308
+ <TotalIcon />
309
+ {key in footerFunctionsTexts
310
+ ? footerFunctionsTexts[
311
+ key as DataGridFooterPredefinedFunction
312
+ ]
313
+ : key}
314
+ </ContextMenu.Item>
315
+ );
316
+ })}
317
+ <ContextMenu.Divider />
318
+ <ContextMenu.Item
319
+ onClick={hideFooter}
320
+ disabled={!isFooterVisible}
321
+ >
322
+ <TableFooterSlashIcon />
323
+ Masquer le total
324
+ </ContextMenu.Item>
325
+ </ContextMenu.SubMenu>
326
+ </ContextMenu.ParentItem>
327
+ <ContextMenu.Divider />
328
+ </>
329
+ )}
330
+ <ContextMenu.Item onClick={openModal}>
331
+ <FilterIcon />
332
+ Filtrer ...
333
+ </ContextMenu.Item>
334
+ <ContextMenu.Item
335
+ $color="danger"
336
+ onClick={clearFilter}
337
+ disabled={!hasFilters}
338
+ >
339
+ <FilterSlashIcon />
340
+ Supprimer le filtre
341
+ </ContextMenu.Item>
342
+ <ContextMenu.Divider />
343
+ <styles.InputContainer>
344
+ <MagnifierIcon />
345
+ <Input
346
+ ref={textFilterInputRef}
347
+ type="text"
348
+ name="search"
349
+ id="search"
350
+ placeholder="Rechercher ..."
351
+ value={textFilter}
352
+ onChange={(e) => setTextFilter(e.target.value)}
353
+ />
354
+ </styles.InputContainer>
355
+ <styles.CheckboxesContainer>
356
+ {checkboxesComponent}
357
+ </styles.CheckboxesContainer>
358
+ </ContextMenu>
359
+ );
360
+ };