@tsiky/components-r19 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/index.ts +35 -33
  2. package/package.json +1 -1
  3. package/src/components/Charts/area-chart-admission/AreaChartAdmission.tsx +123 -89
  4. package/src/components/Charts/bar-chart/BarChart.tsx +167 -132
  5. package/src/components/Charts/mixed-chart/MixedChart.tsx +65 -9
  6. package/src/components/Charts/sankey-chart/SankeyChart.tsx +183 -155
  7. package/src/components/Confirmationpopup/ConfirmationPopup.module.css +88 -0
  8. package/src/components/Confirmationpopup/ConfirmationPopup.stories.tsx +94 -0
  9. package/src/components/Confirmationpopup/ConfirmationPopup.tsx +47 -0
  10. package/src/components/Confirmationpopup/index.ts +6 -0
  11. package/src/components/Confirmationpopup/useConfirmationPopup.ts +48 -0
  12. package/src/components/DayStatCard/DayStatCard.tsx +96 -69
  13. package/src/components/DynamicTable/AdvancedFilters.tsx +196 -196
  14. package/src/components/DynamicTable/ColumnSorter.tsx +185 -185
  15. package/src/components/DynamicTable/Pagination.tsx +115 -115
  16. package/src/components/DynamicTable/TableauDynamique.module.css +1287 -1287
  17. package/src/components/DynamicTable/filters/SelectFilter.tsx +69 -69
  18. package/src/components/EntryControl/EntryControl.tsx +117 -117
  19. package/src/components/Grid/Grid.tsx +5 -0
  20. package/src/components/Header/Header.tsx +4 -2
  21. package/src/components/Header/header.css +61 -31
  22. package/src/components/MetricsPanel/MetricsPanel.module.css +688 -636
  23. package/src/components/MetricsPanel/MetricsPanel.tsx +220 -282
  24. package/src/components/MetricsPanel/renderers/CompactRenderer.tsx +148 -125
  25. package/src/components/NavBar/NavBar.tsx +1 -1
  26. package/src/components/SelectFilter/SelectFilter.module.css +249 -0
  27. package/src/components/SelectFilter/SelectFilter.stories.tsx +321 -0
  28. package/src/components/SelectFilter/SelectFilter.tsx +219 -0
  29. package/src/components/SelectFilter/index.ts +2 -0
  30. package/src/components/SelectFilter/types.ts +19 -0
  31. package/src/components/TranslationKey/TranslationKey.tsx +265 -245
  32. package/src/components/TrendList/TrendList.tsx +72 -45
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import styles from './ConfirmationPopup.module.css';
3
+
4
+ interface ConfirmationPopupProps {
5
+ isOpen: boolean;
6
+ title: string;
7
+ message: string;
8
+ onConfirm: () => void;
9
+ onCancel: () => void;
10
+ confirmText?: string;
11
+ cancelText?: string;
12
+ type?: 'warning' | 'info' | 'danger';
13
+ }
14
+
15
+ export const ConfirmationPopup: React.FC<ConfirmationPopupProps> = ({
16
+ isOpen,
17
+ title,
18
+ message,
19
+ onConfirm,
20
+ onCancel,
21
+ confirmText = 'Confirmer',
22
+ cancelText = 'Annuler',
23
+ type = 'warning',
24
+ }) => {
25
+ if (!isOpen) return null;
26
+
27
+ return (
28
+ <div className={styles.overlay}>
29
+ <div className={`${styles.popup} ${styles[type]}`}>
30
+ <div className={styles.header}>
31
+ <h3>{title}</h3>
32
+ </div>
33
+ <div className={styles.body}>
34
+ <p>{message}</p>
35
+ </div>
36
+ <div className={styles.footer}>
37
+ <button className={styles.cancelButton} onClick={onCancel}>
38
+ {cancelText}
39
+ </button>
40
+ <button className={styles.confirmButton} onClick={onConfirm}>
41
+ {confirmText}
42
+ </button>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ );
47
+ };
@@ -0,0 +1,6 @@
1
+ import { ConfirmationPopup } from './ConfirmationPopup';
2
+ import { useConfirmationPopup } from './useConfirmationPopup';
3
+
4
+ export { ConfirmationPopup, useConfirmationPopup };
5
+
6
+ export default { ConfirmationPopup, useConfirmationPopup };
@@ -0,0 +1,48 @@
1
+ import { useState, useCallback } from 'react';
2
+
3
+ interface ConfirmationState {
4
+ isOpen: boolean;
5
+ title: string;
6
+ message: string;
7
+ onConfirm: (() => void) | null;
8
+ }
9
+
10
+ export const useConfirmationPopup = () => {
11
+ const [confirmationState, setConfirmationState] = useState<ConfirmationState>({
12
+ isOpen: false,
13
+ title: '',
14
+ message: '',
15
+ onConfirm: null,
16
+ });
17
+
18
+ const showConfirmation = useCallback((title: string, message: string, onConfirm: () => void) => {
19
+ setConfirmationState({
20
+ isOpen: true,
21
+ title,
22
+ message,
23
+ onConfirm,
24
+ });
25
+ }, []);
26
+
27
+ const hideConfirmation = useCallback(() => {
28
+ setConfirmationState((prev) => ({
29
+ ...prev,
30
+ isOpen: false,
31
+ onConfirm: null,
32
+ }));
33
+ }, []);
34
+
35
+ const handleConfirm = useCallback(() => {
36
+ confirmationState.onConfirm?.();
37
+ hideConfirmation();
38
+ }, [confirmationState.onConfirm, hideConfirmation]);
39
+
40
+ return {
41
+ isOpen: confirmationState.isOpen,
42
+ title: confirmationState.title,
43
+ message: confirmationState.message,
44
+ showConfirmation,
45
+ hideConfirmation,
46
+ handleConfirm,
47
+ };
48
+ };
@@ -1,69 +1,96 @@
1
- import React from 'react';
2
- import type { CSSProperties } from 'react';
3
- import './DayStatCard.css';
4
- import { CircularProgress } from '../CircularProgress/CircularProgress';
5
- import type { CircularProgressProps } from '../CircularProgress/CircularProgress';
6
-
7
- export type DayStatCardProps = {
8
- background?: string;
9
- headerTop: string;
10
- headerBottom?: string;
11
- headerTopStyle?: CSSProperties;
12
- headerBottomStyle?: CSSProperties;
13
- progress1: CircularProgressProps;
14
- progress2: CircularProgressProps;
15
- progress3: CircularProgressProps;
16
- style?: CSSProperties;
17
- size?: number;
18
- className?: string;
19
- };
20
-
21
- export const DayStatCard: React.FC<DayStatCardProps> = ({
22
- size = 200,
23
- background = '#fff',
24
- headerTop,
25
- headerBottom,
26
- headerTopStyle,
27
- headerBottomStyle,
28
- progress1,
29
- progress2,
30
- progress3,
31
- style,
32
- className = '',
33
- }) => {
34
- const progress1WithDefaults = { size: size * 0.5, ...progress1 };
35
- const progress2WithDefaults = { size: size * 0.4, ...progress2 };
36
- const progress3WithDefaults = { size: size * 0.4, ...progress3 };
37
-
38
- return (
39
- <div style={{ width: size, background, ...style }} className={`stat-card ${className}`}>
40
- <div className='stat-card-header'>
41
- <div className='stat-card-header-top' style={headerTopStyle}>
42
- {headerTop}
43
- </div>
44
- {headerBottom && (
45
- <div className='stat-card-header-bottom' style={headerBottomStyle}>
46
- {headerBottom}
47
- </div>
48
- )}
49
- </div>
50
-
51
- <div className='stat-card-body'>
52
- {/* Première ligne - Progress Centré */}
53
- <div className='progress-row-single'>
54
- <CircularProgress {...progress1WithDefaults} />
55
- </div>
56
-
57
- {/* Deuxième ligne - Deux Progress côte à côte */}
58
- <div className='progress-row-double'>
59
- <div className='progress-item'>
60
- <CircularProgress {...progress2WithDefaults} />
61
- </div>
62
- <div className='progress-item'>
63
- <CircularProgress {...progress3WithDefaults} />
64
- </div>
65
- </div>
66
- </div>
67
- </div>
68
- );
69
- };
1
+ import React from 'react';
2
+ import type { CSSProperties } from 'react';
3
+ import './DayStatCard.css';
4
+ import { CircularProgress } from '../CircularProgress/CircularProgress';
5
+ import type { CircularProgressProps } from '../CircularProgress/CircularProgress';
6
+ import { Move } from 'lucide-react';
7
+
8
+ export type DayStatCardProps = {
9
+ background?: string;
10
+ headerTop: string;
11
+ headerBottom?: string;
12
+ headerTopStyle?: CSSProperties;
13
+ headerBottomStyle?: CSSProperties;
14
+ progress1: CircularProgressProps;
15
+ progress2: CircularProgressProps;
16
+ progress3: CircularProgressProps;
17
+ style?: CSSProperties;
18
+ size?: number;
19
+ className?: string;
20
+ draggable?: boolean; // nouvelle prop
21
+ };
22
+
23
+ export const DayStatCard: React.FC<DayStatCardProps> = ({
24
+ size = 200,
25
+ background = '#fff',
26
+ headerTop,
27
+ headerBottom,
28
+ headerTopStyle,
29
+ headerBottomStyle,
30
+ progress1,
31
+ progress2,
32
+ progress3,
33
+ style,
34
+ className = '',
35
+ draggable = false,
36
+ }) => {
37
+ const progress1WithDefaults = { size: size * 0.5, ...progress1 };
38
+ const progress2WithDefaults = { size: size * 0.4, ...progress2 };
39
+ const progress3WithDefaults = { size: size * 0.4, ...progress3 };
40
+
41
+ return (
42
+ <div
43
+ style={{ width: size, background, position: 'relative', ...style }}
44
+ className={`stat-card ${className}`}
45
+ >
46
+ {/* Header toujours visible */}
47
+ <div className='stat-card-header'>
48
+ <div className='stat-card-header-top' style={headerTopStyle}>
49
+ {headerTop}
50
+ </div>
51
+ {headerBottom && (
52
+ <div className='stat-card-header-bottom' style={headerBottomStyle}>
53
+ {headerBottom}
54
+ </div>
55
+ )}
56
+ </div>
57
+
58
+ {/* Body */}
59
+ <div className='stat-card-body' style={{ opacity: draggable ? 0 : 1 }}>
60
+ {/* Première ligne - Progress Centré */}
61
+ <div className='progress-row-single'>
62
+ <CircularProgress {...progress1WithDefaults} />
63
+ </div>
64
+
65
+ {/* Deuxième ligne - Deux Progress côte à côte */}
66
+ <div className='progress-row-double'>
67
+ <div className='progress-item'>
68
+ <CircularProgress {...progress2WithDefaults} />
69
+ </div>
70
+ <div className='progress-item'>
71
+ <CircularProgress {...progress3WithDefaults} />
72
+ </div>
73
+ </div>
74
+ </div>
75
+
76
+ {/* Overlay Move */}
77
+ {draggable && (
78
+ <div
79
+ style={{
80
+ position: 'absolute',
81
+ top: 0,
82
+ left: 0,
83
+ width: '100%',
84
+ height: '100%',
85
+ display: 'flex',
86
+ alignItems: 'center',
87
+ justifyContent: 'center',
88
+ pointerEvents: 'none',
89
+ }}
90
+ >
91
+ <Move size={32} strokeWidth={1.5} style={{ opacity: 0.8 }} />
92
+ </div>
93
+ )}
94
+ </div>
95
+ );
96
+ };
@@ -1,196 +1,196 @@
1
- 'use client';
2
-
3
- import React, { useEffect, useRef, useState } from 'react';
4
- import { X, Filter } from 'lucide-react';
5
- import type { FilterConfig, AppliedFilter } from './tools/filterTypes';
6
- import { useFilters } from './hooks/useFilters';
7
- import FilterRenderer from './filters/FilterRenderer';
8
- import styles from './TableauDynamique.module.css';
9
-
10
- interface AdvancedFiltersProps {
11
- filters: FilterConfig[];
12
- externalFilters?: AppliedFilter[];
13
- onFiltersChange?: (filters: AppliedFilter[]) => void;
14
- onApply?: (filters: AppliedFilter[]) => void;
15
- isControlled?: boolean;
16
- debounceTimeout?: number;
17
- resultsCount?: number;
18
- expanded?: boolean;
19
- onToggle?: (expanded: boolean) => void;
20
- panelPlacement?: 'overlay' | 'top';
21
- onPanelHeightChange?: (height: number) => void;
22
- }
23
-
24
- const AdvancedFilters: React.FC<AdvancedFiltersProps> = ({
25
- filters,
26
- externalFilters = [],
27
- onFiltersChange,
28
- onApply,
29
- isControlled = false,
30
- debounceTimeout = 300,
31
- resultsCount = 0,
32
- expanded,
33
- onToggle,
34
- panelPlacement = 'top',
35
- onPanelHeightChange,
36
- }) => {
37
- const [localExpanded, setLocalExpanded] = useState(false);
38
- const isExpanded = typeof expanded === 'boolean' ? expanded : localExpanded;
39
-
40
- const { appliedFilters, tempFilters, updateFilter, applyFilters, clearAllFilters } = useFilters({
41
- filters,
42
- externalFilters,
43
- onFiltersChange,
44
- onApply,
45
- debounceTimeout,
46
- isControlled,
47
- });
48
-
49
- const panelRef = useRef<HTMLDivElement | null>(null);
50
- const wrapperRef = useRef<HTMLDivElement | null>(null);
51
-
52
- useEffect(() => {
53
- if (isExpanded && panelRef.current) {
54
- const h = panelRef.current.getBoundingClientRect().height;
55
- onPanelHeightChange?.(h);
56
- } else {
57
- onPanelHeightChange?.(0);
58
- }
59
- }, [isExpanded, filters, tempFilters, onPanelHeightChange]);
60
-
61
- const toggle = () => {
62
- if (typeof expanded === 'boolean') {
63
- onToggle?.(!expanded);
64
- } else {
65
- setLocalExpanded((s) => {
66
- const next = !s;
67
- onToggle?.(next);
68
- return next;
69
- });
70
- }
71
- };
72
-
73
- const handleApply = async () => {
74
- await applyFilters();
75
- onToggle?.(false);
76
- if (typeof expanded !== 'boolean') setLocalExpanded(false);
77
- };
78
-
79
- useEffect(() => {
80
- const onPointerDown = (e: PointerEvent) => {
81
- if (!isExpanded) return;
82
- if (!wrapperRef.current) return;
83
- if (!wrapperRef.current.contains(e.target as Node)) {
84
- onToggle?.(false);
85
- if (typeof expanded !== 'boolean') setLocalExpanded(false);
86
- }
87
- };
88
-
89
- const onKey = (e: KeyboardEvent) => {
90
- if (e.key === 'Escape' && isExpanded) {
91
- onToggle?.(false);
92
- if (typeof expanded !== 'boolean') setLocalExpanded(false);
93
- }
94
- };
95
-
96
- if (isExpanded) {
97
- document.addEventListener('pointerdown', onPointerDown);
98
- document.addEventListener('keydown', onKey);
99
- }
100
-
101
- return () => {
102
- document.removeEventListener('pointerdown', onPointerDown);
103
- document.removeEventListener('keydown', onKey);
104
- };
105
- }, [isExpanded, expanded, onToggle]);
106
-
107
- return (
108
- <div className={styles.advancedFiltersContainer} ref={wrapperRef}>
109
- <div className={styles.filtersHeader}>
110
- <button
111
- className={styles.toggleFiltersButton}
112
- onClick={toggle}
113
- aria-expanded={isExpanded}
114
- aria-controls='advanced-filters-panel'
115
- >
116
- <Filter size={14} />
117
- <span className={styles.buttonTextCompact}>Advanced-Filters</span>
118
- </button>
119
-
120
- {appliedFilters.length > 0 && (
121
- <button className={styles.clearFiltersButton} onClick={clearAllFilters}>
122
- <X size={14} /> <span className={styles.buttonTextCompact}>Effacer</span>
123
- </button>
124
- )}
125
- </div>
126
-
127
- {isExpanded && (
128
- <div
129
- id='advanced-filters-panel'
130
- ref={panelRef}
131
- className={`${styles.filtersPanel} ${
132
- panelPlacement === 'top'
133
- ? `${styles.filtersPanelInline} ${styles.filtersPanelHorizontal}`
134
- : ''
135
- }`}
136
- role='region'
137
- aria-label='Panneau de filtres avancés'
138
- >
139
- <div className={styles.filtersRow}>
140
- {filters.map((filter) => (
141
- <div key={filter.id} className={`${styles.filterItem} ${styles.filterItemInline}`}>
142
- <label className={styles.filterLabel}>
143
- {filter.label}
144
- {filter.required && <span className={styles.required}>*</span>}
145
- </label>
146
- <FilterRenderer
147
- config={filter}
148
- value={tempFilters[filter.id]}
149
- onChange={(value) => updateFilter(filter.id, value)}
150
- disabled={filter.disabled}
151
- />
152
- </div>
153
- ))}
154
- </div>
155
-
156
- <div className={styles.filtersActionsHorizontal}>
157
- <div className={styles.activeFiltersWrap}>
158
- {appliedFilters.length > 0 && (
159
- <div className={styles.activeFilters}>
160
- <span className={styles.activeLabelCompact}>Actifs :</span>
161
- {appliedFilters.map((f) => (
162
- <div key={f.id} className={styles.activeFilterTag}>
163
- {f.label ?? f.id}
164
- <button
165
- onClick={() => updateFilter(f.id, undefined)}
166
- aria-label={`Supprimer ${f.id}`}
167
- >
168
- ×
169
- </button>
170
- </div>
171
- ))}
172
- </div>
173
- )}
174
- </div>
175
-
176
- <div className={styles.actionsGroup}>
177
- <button
178
- className={styles.cancelButton}
179
- onClick={() => {
180
- onToggle?.(false);
181
- }}
182
- >
183
- Cancel
184
- </button>
185
- <button className={styles.applyButton} onClick={handleApply}>
186
- Apply
187
- </button>
188
- </div>
189
- </div>
190
- </div>
191
- )}
192
- </div>
193
- );
194
- };
195
-
196
- export default AdvancedFilters;
1
+ 'use client';
2
+
3
+ import React, { useEffect, useRef, useState } from 'react';
4
+ import { X, Filter } from 'lucide-react';
5
+ import type { FilterConfig, AppliedFilter } from './tools/filterTypes';
6
+ import { useFilters } from './hooks/useFilters';
7
+ import FilterRenderer from './filters/FilterRenderer';
8
+ import styles from './TableauDynamique.module.css';
9
+
10
+ interface AdvancedFiltersProps {
11
+ filters: FilterConfig[];
12
+ externalFilters?: AppliedFilter[];
13
+ onFiltersChange?: (filters: AppliedFilter[]) => void;
14
+ onApply?: (filters: AppliedFilter[]) => void;
15
+ isControlled?: boolean;
16
+ debounceTimeout?: number;
17
+ resultsCount?: number;
18
+ expanded?: boolean;
19
+ onToggle?: (expanded: boolean) => void;
20
+ panelPlacement?: 'overlay' | 'top';
21
+ onPanelHeightChange?: (height: number) => void;
22
+ }
23
+
24
+ const AdvancedFilters: React.FC<AdvancedFiltersProps> = ({
25
+ filters,
26
+ externalFilters = [],
27
+ onFiltersChange,
28
+ onApply,
29
+ isControlled = false,
30
+ debounceTimeout = 300,
31
+ resultsCount = 0,
32
+ expanded,
33
+ onToggle,
34
+ panelPlacement = 'top',
35
+ onPanelHeightChange,
36
+ }) => {
37
+ const [localExpanded, setLocalExpanded] = useState(false);
38
+ const isExpanded = typeof expanded === 'boolean' ? expanded : localExpanded;
39
+
40
+ const { appliedFilters, tempFilters, updateFilter, applyFilters, clearAllFilters } = useFilters({
41
+ filters,
42
+ externalFilters,
43
+ onFiltersChange,
44
+ onApply,
45
+ debounceTimeout,
46
+ isControlled,
47
+ });
48
+
49
+ const panelRef = useRef<HTMLDivElement | null>(null);
50
+ const wrapperRef = useRef<HTMLDivElement | null>(null);
51
+
52
+ useEffect(() => {
53
+ if (isExpanded && panelRef.current) {
54
+ const h = panelRef.current.getBoundingClientRect().height;
55
+ onPanelHeightChange?.(h);
56
+ } else {
57
+ onPanelHeightChange?.(0);
58
+ }
59
+ }, [isExpanded, filters, tempFilters, onPanelHeightChange]);
60
+
61
+ const toggle = () => {
62
+ if (typeof expanded === 'boolean') {
63
+ onToggle?.(!expanded);
64
+ } else {
65
+ setLocalExpanded((s) => {
66
+ const next = !s;
67
+ onToggle?.(next);
68
+ return next;
69
+ });
70
+ }
71
+ };
72
+
73
+ const handleApply = async () => {
74
+ await applyFilters();
75
+ onToggle?.(false);
76
+ if (typeof expanded !== 'boolean') setLocalExpanded(false);
77
+ };
78
+
79
+ useEffect(() => {
80
+ const onPointerDown = (e: PointerEvent) => {
81
+ if (!isExpanded) return;
82
+ if (!wrapperRef.current) return;
83
+ if (!wrapperRef.current.contains(e.target as Node)) {
84
+ onToggle?.(false);
85
+ if (typeof expanded !== 'boolean') setLocalExpanded(false);
86
+ }
87
+ };
88
+
89
+ const onKey = (e: KeyboardEvent) => {
90
+ if (e.key === 'Escape' && isExpanded) {
91
+ onToggle?.(false);
92
+ if (typeof expanded !== 'boolean') setLocalExpanded(false);
93
+ }
94
+ };
95
+
96
+ if (isExpanded) {
97
+ document.addEventListener('pointerdown', onPointerDown);
98
+ document.addEventListener('keydown', onKey);
99
+ }
100
+
101
+ return () => {
102
+ document.removeEventListener('pointerdown', onPointerDown);
103
+ document.removeEventListener('keydown', onKey);
104
+ };
105
+ }, [isExpanded, expanded, onToggle]);
106
+
107
+ return (
108
+ <div className={styles.advancedFiltersContainer} ref={wrapperRef}>
109
+ <div className={styles.filtersHeader}>
110
+ <button
111
+ className={styles.toggleFiltersButton}
112
+ onClick={toggle}
113
+ aria-expanded={isExpanded}
114
+ aria-controls='advanced-filters-panel'
115
+ >
116
+ <Filter size={14} />
117
+ <span className={styles.buttonTextCompact}>Advanced-Filters</span>
118
+ </button>
119
+
120
+ {appliedFilters.length > 0 && (
121
+ <button className={styles.clearFiltersButton} onClick={clearAllFilters}>
122
+ <X size={14} /> <span className={styles.buttonTextCompact}>Effacer</span>
123
+ </button>
124
+ )}
125
+ </div>
126
+
127
+ {isExpanded && (
128
+ <div
129
+ id='advanced-filters-panel'
130
+ ref={panelRef}
131
+ className={`${styles.filtersPanel} ${
132
+ panelPlacement === 'top'
133
+ ? `${styles.filtersPanelInline} ${styles.filtersPanelHorizontal}`
134
+ : ''
135
+ }`}
136
+ role='region'
137
+ aria-label='Panneau de filtres avancés'
138
+ >
139
+ <div className={styles.filtersRow}>
140
+ {filters.map((filter) => (
141
+ <div key={filter.id} className={`${styles.filterItem} ${styles.filterItemInline}`}>
142
+ <label className={styles.filterLabel}>
143
+ {filter.label}
144
+ {filter.required && <span className={styles.required}>*</span>}
145
+ </label>
146
+ <FilterRenderer
147
+ config={filter}
148
+ value={tempFilters[filter.id]}
149
+ onChange={(value) => updateFilter(filter.id, value)}
150
+ disabled={filter.disabled}
151
+ />
152
+ </div>
153
+ ))}
154
+ </div>
155
+
156
+ <div className={styles.filtersActionsHorizontal}>
157
+ <div className={styles.activeFiltersWrap}>
158
+ {appliedFilters.length > 0 && (
159
+ <div className={styles.activeFilters}>
160
+ <span className={styles.activeLabelCompact}>Actifs :</span>
161
+ {appliedFilters.map((f) => (
162
+ <div key={f.id} className={styles.activeFilterTag}>
163
+ {f.label ?? f.id}
164
+ <button
165
+ onClick={() => updateFilter(f.id, undefined)}
166
+ aria-label={`Supprimer ${f.id}`}
167
+ >
168
+ ×
169
+ </button>
170
+ </div>
171
+ ))}
172
+ </div>
173
+ )}
174
+ </div>
175
+
176
+ <div className={styles.actionsGroup}>
177
+ <button
178
+ className={styles.cancelButton}
179
+ onClick={() => {
180
+ onToggle?.(false);
181
+ }}
182
+ >
183
+ Cancel
184
+ </button>
185
+ <button className={styles.applyButton} onClick={handleApply}>
186
+ Apply
187
+ </button>
188
+ </div>
189
+ </div>
190
+ </div>
191
+ )}
192
+ </div>
193
+ );
194
+ };
195
+
196
+ export default AdvancedFilters;