@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
@@ -1,245 +1,265 @@
1
- import React, { useState } from 'react';
2
- import { Edit3, Trash2, X, ChevronDown, ChevronRight } from 'lucide-react';
3
- import './TranslationKey.css';
4
- import { AddItemModal } from '../AddItemModal/AddItemModal';
5
-
6
- export interface Translation {
7
- language: string;
8
- value: string;
9
- }
10
-
11
- interface TranslationKeyProps {
12
- keyName: string;
13
- translations: Translation[];
14
- availableLanguages: { code: string; name: string }[];
15
- onDeleteKey?: (keyName: string) => void;
16
- onEditKey?: (oldKey: string, newKey: string) => void; // <- nouvelle prop
17
- onEditTranslation?: (keyName: string, language: string, newValue: string) => void;
18
- onDeleteTranslation?: (keyName: string, language: string) => void;
19
- onAddTranslation?: (keyName: string, language: string, value: string) => void;
20
- }
21
-
22
- export const TranslationKey: React.FC<TranslationKeyProps> = ({
23
- keyName,
24
- translations,
25
- availableLanguages,
26
- onDeleteKey,
27
- onEditKey,
28
- onEditTranslation,
29
- onDeleteTranslation,
30
- onAddTranslation,
31
- }) => {
32
- const [showAddLanguage, setShowAddLanguage] = useState(false);
33
- const [selectedLanguage, setSelectedLanguage] = useState('');
34
- const [editingTranslation, setEditingTranslation] = useState<string | null>(null);
35
- const [editValue, setEditValue] = useState('');
36
- const [newTranslationValue, setNewTranslationValue] = useState('');
37
-
38
- // état pour le modal de renommage de la clé
39
- const [isRenameModalOpen, setIsRenameModalOpen] = useState(false);
40
-
41
- // état pour replier/déplier
42
- const [isCollapsed, setIsCollapsed] = useState(false);
43
-
44
- const usedLanguages = translations.map((t) => t.language);
45
- const availableToAdd = availableLanguages.filter((lang) => !usedLanguages.includes(lang.code));
46
-
47
- const handleAddTranslation = () => {
48
- if (selectedLanguage && newTranslationValue.trim()) {
49
- onAddTranslation?.(keyName, selectedLanguage, newTranslationValue.trim());
50
- setSelectedLanguage('');
51
- setNewTranslationValue('');
52
- setShowAddLanguage(false);
53
- }
54
- };
55
-
56
- const handleEditStart = (language: string, currentValue: string) => {
57
- setEditingTranslation(language);
58
- setEditValue(currentValue);
59
- };
60
-
61
- const handleEditSave = (language: string) => {
62
- onEditTranslation?.(keyName, language, editValue);
63
- setEditingTranslation(null);
64
- setEditValue('');
65
- };
66
-
67
- const handleEditCancel = () => {
68
- setEditingTranslation(null);
69
- setEditValue('');
70
- };
71
-
72
- const handleRenameSave = (values: Record<string, string | number>) => {
73
- const newName = (values.name as string)?.trim();
74
- if (!newName) return;
75
- if (newName === keyName) {
76
- setIsRenameModalOpen(false);
77
- return;
78
- }
79
- onEditKey?.(keyName, newName);
80
- setIsRenameModalOpen(false);
81
- };
82
-
83
- return (
84
- <div className='translation-card'>
85
- {/* Header */}
86
- <div className='card-header'>
87
- <div className='header-left'>
88
- {/* Toggle button aligné à gauche */}
89
- <button
90
- className='collapse-button'
91
- onClick={() => setIsCollapsed(!isCollapsed)}
92
- title={isCollapsed ? 'Déplier' : 'Replier'}
93
- >
94
- {isCollapsed ? (
95
- <ChevronRight className='collapse-icon' />
96
- ) : (
97
- <ChevronDown className='collapse-icon' />
98
- )}
99
- </button>
100
-
101
- <h3 className='card-title'>{keyName}</h3>
102
- </div>
103
-
104
- <div className='header-actions'>
105
- <button
106
- className='action-button'
107
- onClick={(e) => {
108
- e.stopPropagation();
109
- setIsRenameModalOpen(true);
110
- }}
111
- title='Renommer la clé'
112
- >
113
- <Edit3 className='action-icon' />
114
- </button>
115
- <button
116
- onClick={(e) => {
117
- e.stopPropagation();
118
- onDeleteKey?.(keyName);
119
- }}
120
- className='action-button delete'
121
- title='Supprimer la clé'
122
- >
123
- <Trash2 className='action-icon' />
124
- </button>
125
- </div>
126
- </div>
127
-
128
- {/* Contenu (masqué si replié) */}
129
- {!isCollapsed && (
130
- <div className='translations-container'>
131
- {translations.map((translation) => (
132
- <div key={translation.language} className='translation-row'>
133
- <div className='language-code'>{translation.language}</div>
134
- <div className='translation-content'>
135
- {editingTranslation === translation.language ? (
136
- <>
137
- <input
138
- type='text'
139
- value={editValue}
140
- onChange={(e) => setEditValue(e.target.value)}
141
- className='edit-input'
142
- autoFocus
143
- />
144
- <button
145
- onClick={() => handleEditSave(translation.language)}
146
- className='save-button'
147
- >
148
- Enregistrer
149
- </button>
150
- <button onClick={handleEditCancel} className='cancel-button'>
151
- <X className='action-icon' />
152
- </button>
153
- </>
154
- ) : (
155
- <>
156
- <span className='translation-text'>{translation.value}</span>
157
- <button
158
- onClick={() => handleEditStart(translation.language, translation.value)}
159
- className='edit-button'
160
- title='Éditer la traduction'
161
- >
162
- <Edit3 className='action-icon' />
163
- </button>
164
- <button
165
- onClick={() => onDeleteTranslation?.(keyName, translation.language)}
166
- className='delete-translation-button'
167
- title='Supprimer la traduction'
168
- >
169
- <X className='action-icon' />
170
- </button>
171
- </>
172
- )}
173
- </div>
174
- </div>
175
- ))}
176
-
177
- {/* Add language */}
178
- {showAddLanguage ? (
179
- <div className='add-language-row'>
180
- <div className='language-code'></div>
181
- <div className='add-language-content'>
182
- <div className='language-select-container'>
183
- <select
184
- value={selectedLanguage}
185
- onChange={(e) => setSelectedLanguage(e.target.value)}
186
- className='language-select'
187
- >
188
- <option value=''>Sélectionnez la langue</option>
189
- {availableToAdd.map((lang) => (
190
- <option key={lang.code} value={lang.code}>
191
- {lang.name}
192
- </option>
193
- ))}
194
- </select>
195
- </div>
196
- {selectedLanguage && (
197
- <input
198
- type='text'
199
- value={newTranslationValue}
200
- onChange={(e) => setNewTranslationValue(e.target.value)}
201
- placeholder='Entrez la traduction...'
202
- className='translation-input'
203
- />
204
- )}
205
- <button
206
- onClick={handleAddTranslation}
207
- disabled={!selectedLanguage || !newTranslationValue.trim()}
208
- className='show-add-button'
209
- >
210
- <span className='plus-icon'>+</span>
211
- Ajouter
212
- </button>
213
- <button
214
- onClick={() => {
215
- setShowAddLanguage(false);
216
- setSelectedLanguage('');
217
- setNewTranslationValue('');
218
- }}
219
- className='cancel-button'
220
- >
221
- <X className='action-icon' />
222
- </button>
223
- </div>
224
- </div>
225
- ) : (
226
- <button onClick={() => setShowAddLanguage(true)} className='show-add-button'>
227
- <span className='plus-icon'>+</span>
228
- Ajouter
229
- </button>
230
- )}
231
- </div>
232
- )}
233
-
234
- {/* Modal pour renommer la clé */}
235
- <AddItemModal
236
- isOpen={isRenameModalOpen}
237
- onClose={() => setIsRenameModalOpen(false)}
238
- onSave={handleRenameSave}
239
- title='Renommer la clé'
240
- initialValues={{ name: keyName }}
241
- fields={[{ name: 'name', label: 'Nom de la clé', placeholder: 'Nom de la clé' }]}
242
- />
243
- </div>
244
- );
245
- };
1
+ import React, { useState } from 'react';
2
+ import { Edit3, Trash2, X, ChevronDown, ChevronRight, Move } from 'lucide-react';
3
+ import './TranslationKey.css';
4
+ import { AddItemModal } from '../AddItemModal/AddItemModal';
5
+
6
+ export interface Translation {
7
+ language: string;
8
+ value: string;
9
+ }
10
+
11
+ interface TranslationKeyProps {
12
+ keyName: string;
13
+ translations: Translation[];
14
+ availableLanguages: { code: string; name: string }[];
15
+ onDeleteKey?: (keyName: string) => void;
16
+ onEditKey?: (oldKey: string, newKey: string) => void; // <- nouvelle prop
17
+ onEditTranslation?: (keyName: string, language: string, newValue: string) => void;
18
+ onDeleteTranslation?: (keyName: string, language: string) => void;
19
+ onAddTranslation?: (keyName: string, language: string, value: string) => void;
20
+ draggable?: boolean;
21
+ }
22
+
23
+ export const TranslationKey: React.FC<TranslationKeyProps> = ({
24
+ keyName,
25
+ translations,
26
+ availableLanguages,
27
+ onDeleteKey,
28
+ onEditKey,
29
+ onEditTranslation,
30
+ onDeleteTranslation,
31
+ onAddTranslation,
32
+ draggable = false,
33
+ }) => {
34
+ const [showAddLanguage, setShowAddLanguage] = useState(false);
35
+ const [selectedLanguage, setSelectedLanguage] = useState('');
36
+ const [editingTranslation, setEditingTranslation] = useState<string | null>(null);
37
+ const [editValue, setEditValue] = useState('');
38
+ const [newTranslationValue, setNewTranslationValue] = useState('');
39
+
40
+ // état pour le modal de renommage de la clé
41
+ const [isRenameModalOpen, setIsRenameModalOpen] = useState(false);
42
+
43
+ // état pour replier/déplier
44
+ const [isCollapsed, setIsCollapsed] = useState(false);
45
+
46
+ const usedLanguages = translations.map((t) => t.language);
47
+ const availableToAdd = availableLanguages.filter((lang) => !usedLanguages.includes(lang.code));
48
+
49
+ const handleAddTranslation = () => {
50
+ if (selectedLanguage && newTranslationValue.trim()) {
51
+ onAddTranslation?.(keyName, selectedLanguage, newTranslationValue.trim());
52
+ setSelectedLanguage('');
53
+ setNewTranslationValue('');
54
+ setShowAddLanguage(false);
55
+ }
56
+ };
57
+
58
+ const handleEditStart = (language: string, currentValue: string) => {
59
+ setEditingTranslation(language);
60
+ setEditValue(currentValue);
61
+ };
62
+
63
+ const handleEditSave = (language: string) => {
64
+ onEditTranslation?.(keyName, language, editValue);
65
+ setEditingTranslation(null);
66
+ setEditValue('');
67
+ };
68
+
69
+ const handleEditCancel = () => {
70
+ setEditingTranslation(null);
71
+ setEditValue('');
72
+ };
73
+
74
+ const handleRenameSave = (values: Record<string, string | number>) => {
75
+ const newName = (values.name as string)?.trim();
76
+ if (!newName) return;
77
+ if (newName === keyName) {
78
+ setIsRenameModalOpen(false);
79
+ return;
80
+ }
81
+ onEditKey?.(keyName, newName);
82
+ setIsRenameModalOpen(false);
83
+ };
84
+
85
+ return (
86
+ <div className='translation-card' style={{ position: 'relative' }}>
87
+ {/* Header toujours visible */}
88
+ <div className='card-header'>
89
+ <div className='header-left'>
90
+ <button
91
+ className='collapse-button'
92
+ onClick={() => setIsCollapsed(!isCollapsed)}
93
+ title={isCollapsed ? 'Déplier' : 'Replier'}
94
+ >
95
+ {isCollapsed ? <ChevronRight className='collapse-icon' /> : <ChevronDown className='collapse-icon' />}
96
+ </button>
97
+ <h3 className='card-title'>{keyName}</h3>
98
+ </div>
99
+
100
+ <div className='header-actions'>
101
+ <button
102
+ className='action-button'
103
+ onClick={(e) => {
104
+ e.stopPropagation();
105
+ setIsRenameModalOpen(true);
106
+ }}
107
+ title='Renommer la clé'
108
+ >
109
+ <Edit3 className='action-icon' />
110
+ </button>
111
+ <button
112
+ onClick={(e) => {
113
+ e.stopPropagation();
114
+ onDeleteKey?.(keyName);
115
+ }}
116
+ className='action-button delete'
117
+ title='Supprimer la clé'
118
+ >
119
+ <Trash2 className='action-icon' />
120
+ </button>
121
+ </div>
122
+ </div>
123
+
124
+ {/* Contenu (overlay draggable si nécessaire) */}
125
+ <div
126
+ className='translations-container'
127
+ style={{ opacity: draggable ? 0 : 1 }}
128
+ >
129
+ {!isCollapsed && !draggable &&
130
+ <>
131
+ {translations.map((translation) => (
132
+ <div key={translation.language} className='translation-row'>
133
+ <div className='language-code'>{translation.language}</div>
134
+ <div className='translation-content'>
135
+ {editingTranslation === translation.language ? (
136
+ <>
137
+ <input
138
+ type='text'
139
+ value={editValue}
140
+ onChange={(e) => setEditValue(e.target.value)}
141
+ className='edit-input'
142
+ autoFocus
143
+ />
144
+ <button
145
+ onClick={() => handleEditSave(translation.language)}
146
+ className='save-button'
147
+ >
148
+ Enregistrer
149
+ </button>
150
+ <button onClick={handleEditCancel} className='cancel-button'>
151
+ <X className='action-icon' />
152
+ </button>
153
+ </>
154
+ ) : (
155
+ <>
156
+ <span className='translation-text'>{translation.value}</span>
157
+ <button
158
+ onClick={() => handleEditStart(translation.language, translation.value)}
159
+ className='edit-button'
160
+ title='Éditer la traduction'
161
+ >
162
+ <Edit3 className='action-icon' />
163
+ </button>
164
+ <button
165
+ onClick={() => onDeleteTranslation?.(keyName, translation.language)}
166
+ className='delete-translation-button'
167
+ title='Supprimer la traduction'
168
+ >
169
+ <X className='action-icon' />
170
+ </button>
171
+ </>
172
+ )}
173
+ </div>
174
+ </div>
175
+ ))}
176
+
177
+ {/* Add language */}
178
+ {showAddLanguage ? (
179
+ <div className='add-language-row'>
180
+ <div className='language-code'></div>
181
+ <div className='add-language-content'>
182
+ <div className='language-select-container'>
183
+ <select
184
+ value={selectedLanguage}
185
+ onChange={(e) => setSelectedLanguage(e.target.value)}
186
+ className='language-select'
187
+ >
188
+ <option value=''>Sélectionnez la langue</option>
189
+ {availableToAdd.map((lang) => (
190
+ <option key={lang.code} value={lang.code}>
191
+ {lang.name}
192
+ </option>
193
+ ))}
194
+ </select>
195
+ </div>
196
+ {selectedLanguage && (
197
+ <input
198
+ type='text'
199
+ value={newTranslationValue}
200
+ onChange={(e) => setNewTranslationValue(e.target.value)}
201
+ placeholder='Entrez la traduction...'
202
+ className='translation-input'
203
+ />
204
+ )}
205
+ <button
206
+ onClick={handleAddTranslation}
207
+ disabled={!selectedLanguage || !newTranslationValue.trim()}
208
+ className='show-add-button'
209
+ >
210
+ <span className='plus-icon'>+</span>
211
+ Ajouter
212
+ </button>
213
+ <button
214
+ onClick={() => {
215
+ setShowAddLanguage(false);
216
+ setSelectedLanguage('');
217
+ setNewTranslationValue('');
218
+ }}
219
+ className='cancel-button'
220
+ >
221
+ <X className='action-icon' />
222
+ </button>
223
+ </div>
224
+ </div>
225
+ ) : (
226
+ <button onClick={() => setShowAddLanguage(true)} className='show-add-button'>
227
+ <span className='plus-icon'>+</span>
228
+ Ajouter
229
+ </button>
230
+ )}
231
+ </>
232
+ }
233
+ </div>
234
+
235
+ {/* Overlay Move */}
236
+ {draggable && (
237
+ <div
238
+ style={{
239
+ position: 'absolute',
240
+ top: 40, // pour laisser le header visible
241
+ left: 0,
242
+ width: '100%',
243
+ height: 'calc(100% - 40px)',
244
+ display: 'flex',
245
+ alignItems: 'center',
246
+ justifyContent: 'center',
247
+ pointerEvents: 'none',
248
+ }}
249
+ >
250
+ <Move size={32} strokeWidth={1.5} style={{ opacity: 0.8 }} />
251
+ </div>
252
+ )}
253
+
254
+ {/* Modal pour renommer la clé */}
255
+ <AddItemModal
256
+ isOpen={isRenameModalOpen}
257
+ onClose={() => setIsRenameModalOpen(false)}
258
+ onSave={handleRenameSave}
259
+ title='Renommer la clé'
260
+ initialValues={{ name: keyName }}
261
+ fields={[{ name: 'name', label: 'Nom de la clé', placeholder: 'Nom de la clé' }]}
262
+ />
263
+ </div>
264
+ );
265
+ };
@@ -1,45 +1,72 @@
1
- import React from 'react';
2
- import './TrendList.css';
3
- import { TrendItem } from '../TrendItem/TrendItem';
4
- import type { TrendItemProps } from '../TrendItem/TrendItem';
5
-
6
- type TrendListProps = {
7
- items: TrendItemProps[];
8
- header?: string;
9
- headerStyle?: React.CSSProperties;
10
- listStyle?: React.CSSProperties;
11
- size?: number | string;
12
- gap?: number;
13
- className?: string;
14
- background?: string; // Nouvelle prop pour le background
15
- };
16
-
17
- export const TrendList: React.FC<TrendListProps> = ({
18
- items,
19
- header = 'Tendance',
20
- headerStyle,
21
- listStyle,
22
- size,
23
- gap,
24
- className = '',
25
- background = 'var(--color-card-background)',
26
- }) => {
27
- return (
28
- <div
29
- className={`trend-list ${className}`}
30
- style={{
31
- width: size,
32
- gap: `${gap}px`,
33
- background,
34
- ...listStyle,
35
- }}
36
- >
37
- <h3 className='trend-header' style={headerStyle}>
38
- {header}
39
- </h3>
40
- {items.map((item, idx) => (
41
- <TrendItem key={idx} {...item} />
42
- ))}
43
- </div>
44
- );
45
- };
1
+ import React from 'react';
2
+ import './TrendList.css';
3
+ import { TrendItem } from '../TrendItem/TrendItem';
4
+ import type { TrendItemProps } from '../TrendItem/TrendItem';
5
+ import { Move } from 'lucide-react';
6
+
7
+ type TrendListProps = {
8
+ items: TrendItemProps[];
9
+ header?: string;
10
+ headerStyle?: React.CSSProperties;
11
+ listStyle?: React.CSSProperties;
12
+ size?: number | string;
13
+ gap?: number;
14
+ className?: string;
15
+ background?: string;
16
+ draggable?: boolean; // nouvelle prop
17
+ };
18
+
19
+ export const TrendList: React.FC<TrendListProps> = ({
20
+ items,
21
+ header = 'Tendance',
22
+ headerStyle,
23
+ listStyle,
24
+ size,
25
+ gap = 0,
26
+ className = '',
27
+ background = 'var(--color-card-background)',
28
+ draggable = false,
29
+ }) => {
30
+ return (
31
+ <div
32
+ className={`trend-list ${className}`}
33
+ style={{
34
+ width: size,
35
+ gap: `${gap}px`,
36
+ background,
37
+ position: 'relative', // nécessaire pour overlay
38
+ ...listStyle,
39
+ }}
40
+ >
41
+ <h3 className='trend-header' style={headerStyle}>
42
+ {header}
43
+ </h3>
44
+
45
+ {/* Liste réelle */}
46
+ <div style={{ opacity: draggable ? 0 : 1 }}>
47
+ {items.map((item, idx) => (
48
+ <TrendItem key={idx} {...item} />
49
+ ))}
50
+ </div>
51
+
52
+ {/* Overlay Move */}
53
+ {draggable && (
54
+ <div
55
+ style={{
56
+ position: 'absolute',
57
+ top: 0,
58
+ left: 0,
59
+ width: '100%',
60
+ height: '100%',
61
+ display: 'flex',
62
+ alignItems: 'center',
63
+ justifyContent: 'center',
64
+ pointerEvents: 'none', // laisse passer les événements si nécessaire
65
+ }}
66
+ >
67
+ <Move size={48} strokeWidth={1.5} style={{ opacity: 0.8 }} />
68
+ </div>
69
+ )}
70
+ </div>
71
+ );
72
+ };