@tsiky/components-r19 1.0.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 (287) hide show
  1. package/.prettierignore +4 -0
  2. package/.prettierrc +9 -0
  3. package/.storybook/main.ts +17 -0
  4. package/.storybook/preview.ts +21 -0
  5. package/.storybook/vitest.setup.ts +7 -0
  6. package/README.md +69 -0
  7. package/chart.ts +1 -0
  8. package/eslint.config.js +40 -0
  9. package/index.html +13 -0
  10. package/index.ts +33 -0
  11. package/package.json +63 -0
  12. package/public/vite.svg +1 -0
  13. package/src/App.css +42 -0
  14. package/src/App.tsx +12 -0
  15. package/src/assets/accessibility.png +0 -0
  16. package/src/assets/accessibility.svg +1 -0
  17. package/src/assets/addon-library.png +0 -0
  18. package/src/assets/assets.png +0 -0
  19. package/src/assets/avif-test-image.avif +0 -0
  20. package/src/assets/context.png +0 -0
  21. package/src/assets/discord.svg +1 -0
  22. package/src/assets/docs.png +0 -0
  23. package/src/assets/figma-plugin.png +0 -0
  24. package/src/assets/github.svg +1 -0
  25. package/src/assets/react.svg +1 -0
  26. package/src/assets/share.png +0 -0
  27. package/src/assets/styling.png +0 -0
  28. package/src/assets/testing.png +0 -0
  29. package/src/assets/theming.png +0 -0
  30. package/src/assets/tutorials.svg +1 -0
  31. package/src/assets/youtube.svg +1 -0
  32. package/src/components/AddItemModal/AddItemModal.module.css +72 -0
  33. package/src/components/AddItemModal/AddItemModal.tsx +82 -0
  34. package/src/components/AddItemModal/index.ts +1 -0
  35. package/src/components/Alert/Alert.css +54 -0
  36. package/src/components/Alert/Alert.stories.tsx +82 -0
  37. package/src/components/Alert/Alert.tsx +85 -0
  38. package/src/components/Alert/AlertContext.tsx +200 -0
  39. package/src/components/Alert/AlertModels.ts +34 -0
  40. package/src/components/Alert/index.ts +3 -0
  41. package/src/components/AnnouncementPanel/FlexRowContainer.css +17 -0
  42. package/src/components/AnnouncementPanel/FlexRowContainer.stories.tsx +329 -0
  43. package/src/components/AnnouncementPanel/FlexRowContainer.tsx +24 -0
  44. package/src/components/AnnouncementPanel/ListBox/CounterListBox.css +56 -0
  45. package/src/components/AnnouncementPanel/ListBox/CounterListBox.stories.tsx +292 -0
  46. package/src/components/AnnouncementPanel/ListBox/CounterListBox.tsx +106 -0
  47. package/src/components/AnnouncementPanel/ListBox/SimpleListBox.css +57 -0
  48. package/src/components/AnnouncementPanel/ListBox/SimpleListBox.stories.tsx +189 -0
  49. package/src/components/AnnouncementPanel/ListBox/SimpleListBox.tsx +138 -0
  50. package/src/components/AnnouncementPanel/ListBox/TrendListBox.css +61 -0
  51. package/src/components/AnnouncementPanel/ListBox/TrendListBox.stories.tsx +257 -0
  52. package/src/components/AnnouncementPanel/ListBox/TrendListBox.tsx +90 -0
  53. package/src/components/AnnouncementPanel/ListBox/index.ts +3 -0
  54. package/src/components/AnnouncementPanel/ListContentContainer.css +23 -0
  55. package/src/components/AnnouncementPanel/ListContentContainer.stories.tsx +212 -0
  56. package/src/components/AnnouncementPanel/ListContentContainer.tsx +33 -0
  57. package/src/components/AnnouncementPanel/index.ts +3 -0
  58. package/src/components/BandChart/index.tsx +282 -0
  59. package/src/components/Button/Button.module.css +165 -0
  60. package/src/components/Button/Button.stories.ts +132 -0
  61. package/src/components/Button/Button.tsx +55 -0
  62. package/src/components/Button/button.css +29 -0
  63. package/src/components/Button/index.ts +2 -0
  64. package/src/components/ChartContainer/ChartContainer.css +116 -0
  65. package/src/components/ChartContainer/ChartContainer.stories.tsx +159 -0
  66. package/src/components/ChartContainer/ChartContainer.tsx +155 -0
  67. package/src/components/ChartContainer/index.ts +1 -0
  68. package/src/components/Charts/area-chart-admission/AreaChartAdmission.stories.tsx +65 -0
  69. package/src/components/Charts/area-chart-admission/AreaChartAdmission.tsx +89 -0
  70. package/src/components/Charts/area-chart-admission/content.json +48 -0
  71. package/src/components/Charts/area-chart-hospitalisation/AreaChartHospitalisation.stories.tsx +141 -0
  72. package/src/components/Charts/area-chart-hospitalisation/AreaChartHospitalisation.tsx +262 -0
  73. package/src/components/Charts/area-chart-hospitalisation/content.json +55 -0
  74. package/src/components/Charts/bar-chart/BarChart.stories.tsx +50 -0
  75. package/src/components/Charts/bar-chart/BarChart.tsx +132 -0
  76. package/src/components/Charts/bar-chart/content.json +15 -0
  77. package/src/components/Charts/boxplot-chart/BoxPlotChart.stories.tsx +63 -0
  78. package/src/components/Charts/boxplot-chart/BoxPlotChart.tsx +114 -0
  79. package/src/components/Charts/boxplot-chart/boxUtils.ts +22 -0
  80. package/src/components/Charts/boxplot-chart/content.json +11 -0
  81. package/src/components/Charts/mixed-chart/MixedChart.stories.tsx +83 -0
  82. package/src/components/Charts/mixed-chart/MixedChart.tsx +625 -0
  83. package/src/components/Charts/mixed-chart/content.json +34 -0
  84. package/src/components/Charts/sankey-adaptation/sankey.tsx +70 -0
  85. package/src/components/Charts/sankey-chart/SankeyChart.stories.tsx +69 -0
  86. package/src/components/Charts/sankey-chart/SankeyChart.tsx +155 -0
  87. package/src/components/Charts/sankey-chart/content.json +15 -0
  88. package/src/components/Charts/stacked-column/StackedColumn.stories.tsx +72 -0
  89. package/src/components/Charts/stacked-column/StackedColumn.tsx +406 -0
  90. package/src/components/Charts/stacked-column/content.json +37 -0
  91. package/src/components/Charts/stacked-column-one-hundred-percent/StackedColumnOneHundredPercent.stories.tsx +43 -0
  92. package/src/components/Charts/stacked-column-one-hundred-percent/StackedColumnOneHundredPercent.tsx +75 -0
  93. package/src/components/Charts/stacked-column-one-hundred-percent/content.json +6 -0
  94. package/src/components/CircularProgress/CircularProgress.css +79 -0
  95. package/src/components/CircularProgress/CircularProgress.stories.tsx +251 -0
  96. package/src/components/CircularProgress/CircularProgress.tsx +101 -0
  97. package/src/components/CircularProgress/index.ts +2 -0
  98. package/src/components/Configure.mdx +369 -0
  99. package/src/components/DayStatCard/DayStatCard.css +50 -0
  100. package/src/components/DayStatCard/DayStatCard.stories.tsx +273 -0
  101. package/src/components/DayStatCard/DayStatCard.tsx +69 -0
  102. package/src/components/DayStatCard/index.ts +2 -0
  103. package/src/components/DraggableSwitcher/DraggableSwitcherButton.tsx +58 -0
  104. package/src/components/DraggableSwitcher/context/useDraggableSwitcher.tsx +45 -0
  105. package/src/components/DraggableSwitcher/index.ts +2 -0
  106. package/src/components/DropdownMenu/DropdownMenu.css +100 -0
  107. package/src/components/DropdownMenu/DropdownMenu.stories.tsx +174 -0
  108. package/src/components/DropdownMenu/DropdownMenu.tsx +106 -0
  109. package/src/components/DropdownMenu/index.ts +1 -0
  110. package/src/components/DynamicForm/DynamicFom.stories.ts +773 -0
  111. package/src/components/DynamicForm/DynamicForm.module.css +468 -0
  112. package/src/components/DynamicForm/DynamicForm.tsx +224 -0
  113. package/src/components/DynamicForm/index.ts +3 -0
  114. package/src/components/DynamicForm/tools/form-metadata.ts +82 -0
  115. package/src/components/DynamicForm/tools/validation.ts +168 -0
  116. package/src/components/DynamicInput/DynamicInput.module.css +126 -0
  117. package/src/components/DynamicInput/DynamicInput.stories.ts +350 -0
  118. package/src/components/DynamicInput/DynamicInput.tsx +144 -0
  119. package/src/components/DynamicInput/index.ts +2 -0
  120. package/src/components/DynamicInput/input/CheckboxInput.tsx +41 -0
  121. package/src/components/DynamicInput/input/ColorInput.tsx +46 -0
  122. package/src/components/DynamicInput/input/DateInput.tsx +57 -0
  123. package/src/components/DynamicInput/input/FileInput.tsx +174 -0
  124. package/src/components/DynamicInput/input/InputWrapper.tsx +26 -0
  125. package/src/components/DynamicInput/input/RadioInput.tsx +46 -0
  126. package/src/components/DynamicInput/input/RangeInput.tsx +46 -0
  127. package/src/components/DynamicInput/input/SelectInput.tsx +75 -0
  128. package/src/components/DynamicInput/input/TextInput.tsx +101 -0
  129. package/src/components/DynamicInput/input/TextareaInput.tsx +47 -0
  130. package/src/components/DynamicInput/input/assets/ColorInput.module.css +48 -0
  131. package/src/components/DynamicInput/input/assets/DateInput.module.css +83 -0
  132. package/src/components/DynamicInput/input/assets/FileInput.module.css +133 -0
  133. package/src/components/DynamicInput/input/assets/RangeInput.module.css +169 -0
  134. package/src/components/DynamicInput/input/assets/SelectInput.module.css +95 -0
  135. package/src/components/DynamicInput/input/index.ts +10 -0
  136. package/src/components/DynamicTable/AdvancedFilters.tsx +196 -0
  137. package/src/components/DynamicTable/ColumnSorter.tsx +185 -0
  138. package/src/components/DynamicTable/Pagination.tsx +115 -0
  139. package/src/components/DynamicTable/TableBody.tsx +42 -0
  140. package/src/components/DynamicTable/TableCell.tsx +30 -0
  141. package/src/components/DynamicTable/TableHeader.tsx +34 -0
  142. package/src/components/DynamicTable/TableRow.tsx +56 -0
  143. package/src/components/DynamicTable/TableauDynamic.stories.ts +1014 -0
  144. package/src/components/DynamicTable/TableauDynamique.module.css +1287 -0
  145. package/src/components/DynamicTable/TableauDynamique.tsx +154 -0
  146. package/src/components/DynamicTable/filters/BooleanFilter.tsx +30 -0
  147. package/src/components/DynamicTable/filters/DateFilter.tsx +28 -0
  148. package/src/components/DynamicTable/filters/DateRangeFilter.tsx +51 -0
  149. package/src/components/DynamicTable/filters/FilterRenderer.tsx +117 -0
  150. package/src/components/DynamicTable/filters/MultiSelectFilter.tsx +59 -0
  151. package/src/components/DynamicTable/filters/NumberFilter.tsx +37 -0
  152. package/src/components/DynamicTable/filters/OperatorFilter.tsx +64 -0
  153. package/src/components/DynamicTable/filters/SelectFilter.tsx +69 -0
  154. package/src/components/DynamicTable/filters/TextFilter.tsx +39 -0
  155. package/src/components/DynamicTable/filters/index.ts +9 -0
  156. package/src/components/DynamicTable/hooks/index.ts +2 -0
  157. package/src/components/DynamicTable/hooks/useAsyncActions.ts +36 -0
  158. package/src/components/DynamicTable/hooks/useFilters.ts +142 -0
  159. package/src/components/DynamicTable/hooks/useTableData.ts +216 -0
  160. package/src/components/DynamicTable/index.ts +11 -0
  161. package/src/components/DynamicTable/tools/filterTypes.ts +118 -0
  162. package/src/components/DynamicTable/tools/index.ts +3 -0
  163. package/src/components/DynamicTable/tools/tableConfig.ts +96 -0
  164. package/src/components/DynamicTable/tools/tableTypes.ts +63 -0
  165. package/src/components/EntryControl/EntryControl.module.css +218 -0
  166. package/src/components/EntryControl/EntryControl.stories.tsx +71 -0
  167. package/src/components/EntryControl/EntryControl.tsx +117 -0
  168. package/src/components/EntryControl/index.ts +2 -0
  169. package/src/components/Grid/Grid.stories.tsx +94 -0
  170. package/src/components/Grid/Grid.tsx +214 -0
  171. package/src/components/Grid/grid.css +285 -0
  172. package/src/components/Grid/index.ts +2 -0
  173. package/src/components/Header/Header.stories.tsx +164 -0
  174. package/src/components/Header/Header.tsx +59 -0
  175. package/src/components/Header/header.css +31 -0
  176. package/src/components/Header/index.ts +1 -0
  177. package/src/components/IconText/IconText..stories.tsx +135 -0
  178. package/src/components/IconText/IconText.css +43 -0
  179. package/src/components/IconText/IconText.tsx +43 -0
  180. package/src/components/IconText/index.ts +1 -0
  181. package/src/components/LanguageSelector/LanguageSelector.css +31 -0
  182. package/src/components/LanguageSelector/LanguageSelector.stories.tsx +38 -0
  183. package/src/components/LanguageSelector/LanguageSelector.tsx +37 -0
  184. package/src/components/LanguageSelector/index.ts +1 -0
  185. package/src/components/Logo/Logo.css +89 -0
  186. package/src/components/Logo/Logo.stories.tsx +79 -0
  187. package/src/components/Logo/Logo.tsx +22 -0
  188. package/src/components/Logo/index.ts +2 -0
  189. package/src/components/MetricsPanel/MetricsItem.tsx +128 -0
  190. package/src/components/MetricsPanel/MetricsPanel.module.css +636 -0
  191. package/src/components/MetricsPanel/MetricsPanel.stories.tsx +692 -0
  192. package/src/components/MetricsPanel/MetricsPanel.tsx +282 -0
  193. package/src/components/MetricsPanel/PanelHeader.tsx +19 -0
  194. package/src/components/MetricsPanel/SummaryCard.tsx +61 -0
  195. package/src/components/MetricsPanel/index.ts +4 -0
  196. package/src/components/MetricsPanel/renderers/CompactRenderer.tsx +125 -0
  197. package/src/components/MetricsPanel/renderers/ImageCard.tsx +62 -0
  198. package/src/components/MetricsPanel/renderers/PertinenceCard.tsx +55 -0
  199. package/src/components/MetricsPanel/tools/MetricsPanelTypes.ts +62 -0
  200. package/src/components/MetricsPanel/tools/chooseDefaultRender.ts +50 -0
  201. package/src/components/MetricsPanel/tools/colorUtils.ts +39 -0
  202. package/src/components/MetricsPanel/tools/index.ts +2 -0
  203. package/src/components/ModuleHeader/ModuleHeader.css +37 -0
  204. package/src/components/ModuleHeader/ModuleHeader.stories.tsx +37 -0
  205. package/src/components/ModuleHeader/ModuleHeader.tsx +42 -0
  206. package/src/components/ModuleHeader/index.ts +1 -0
  207. package/src/components/ModuleSideBar/ModuleSideBar.css +227 -0
  208. package/src/components/ModuleSideBar/ModuleSideBar.stories.tsx +40 -0
  209. package/src/components/ModuleSideBar/ModuleSideBar.tsx +155 -0
  210. package/src/components/ModuleSideBar/index.ts +1 -0
  211. package/src/components/NavBar/NavBar.css +58 -0
  212. package/src/components/NavBar/NavBar.stories.tsx +169 -0
  213. package/src/components/NavBar/NavBar.tsx +100 -0
  214. package/src/components/NavBar/NavContext.tsx +30 -0
  215. package/src/components/NavBar/index.ts +2 -0
  216. package/src/components/NavItem/NavItem.css +29 -0
  217. package/src/components/NavItem/NavItem.tsx +58 -0
  218. package/src/components/NavItem/index.ts +1 -0
  219. package/src/components/Page/Dashboard.tsx +93 -0
  220. package/src/components/Page/Page.stories.ts +33 -0
  221. package/src/components/Page/Page.tsx +73 -0
  222. package/src/components/Page/page.css +81 -0
  223. package/src/components/PerformanceCard/PerformanceCard.module.css +232 -0
  224. package/src/components/PerformanceCard/PerformanceCard.stories.tsx +441 -0
  225. package/src/components/PerformanceCard/PerformanceCard.tsx +198 -0
  226. package/src/components/PerformanceCard/defaultHistogramRenderer.tsx +32 -0
  227. package/src/components/PerformanceCard/tools/types.ts +50 -0
  228. package/src/components/PerformanceCard/tools/usePerformanceCard.ts +93 -0
  229. package/src/components/PeriodRange/PeriodRange.module.css +158 -0
  230. package/src/components/PeriodRange/PeriodRange.stories.tsx +66 -0
  231. package/src/components/PeriodRange/PeriodRange.tsx +130 -0
  232. package/src/components/PeriodSelect/PeriodSelect.module.css +65 -0
  233. package/src/components/PeriodSelect/PeriodSelect.stories.tsx +40 -0
  234. package/src/components/PeriodSelect/PeriodSelect.tsx +42 -0
  235. package/src/components/PeriodSelect/index.ts +1 -0
  236. package/src/components/ScrollableHorizontale/ScrollableHorizontale.css +40 -0
  237. package/src/components/ScrollableHorizontale/ScrollableHorizontale.stories.tsx +133 -0
  238. package/src/components/ScrollableHorizontale/ScrollableHorizontale.tsx +29 -0
  239. package/src/components/ScrollableHorizontale/index.ts +1 -0
  240. package/src/components/SearchBar/SearchBar.css +40 -0
  241. package/src/components/SearchBar/SearchBar.stories.tsx +36 -0
  242. package/src/components/SearchBar/SearchBar.tsx +30 -0
  243. package/src/components/SearchBar/index.ts +1 -0
  244. package/src/components/SectionTitle/SectionTitle.css +21 -0
  245. package/src/components/SectionTitle/SectionTitle.stories.tsx +39 -0
  246. package/src/components/SectionTitle/SectionTitle.tsx +18 -0
  247. package/src/components/SideComponent/PatientEditor.tsx +64 -0
  248. package/src/components/SideComponent/SideComponent.css +179 -0
  249. package/src/components/SideComponent/SideComponent.stories.tsx +547 -0
  250. package/src/components/SideComponent/SideComponent.tsx +243 -0
  251. package/src/components/SideComponent/hooks/useBodyScrollLock.ts +15 -0
  252. package/src/components/SideComponent/index.ts +2 -0
  253. package/src/components/SideComponent/portal.ts +11 -0
  254. package/src/components/SubNavBar/SubNavBar.tsx +41 -0
  255. package/src/components/SubNavBar/index.ts +1 -0
  256. package/src/components/Switcher/Switcher.css +65 -0
  257. package/src/components/Switcher/Switcher.stories.tsx +153 -0
  258. package/src/components/Switcher/Switcher.tsx +83 -0
  259. package/src/components/Switcher/index.ts +1 -0
  260. package/src/components/Title/Title.stories.tsx +18 -0
  261. package/src/components/Title/Title.tsx +26 -0
  262. package/src/components/Title/index.ts +1 -0
  263. package/src/components/TranslationKey/TranslationKey.css +272 -0
  264. package/src/components/TranslationKey/TranslationKey.stories.tsx +50 -0
  265. package/src/components/TranslationKey/TranslationKey.tsx +245 -0
  266. package/src/components/TranslationKey/index.ts +1 -0
  267. package/src/components/TrendItem/TrendItem.css +36 -0
  268. package/src/components/TrendItem/TrendItem.stories.tsx +276 -0
  269. package/src/components/TrendItem/TrendItem.tsx +46 -0
  270. package/src/components/TrendItem/index.ts +1 -0
  271. package/src/components/TrendList/TrendList.css +16 -0
  272. package/src/components/TrendList/TrendList.stories.tsx +337 -0
  273. package/src/components/TrendList/TrendList.tsx +45 -0
  274. package/src/components/TrendList/index.ts +1 -0
  275. package/src/components/theme/ThemeSwitcherButton.tsx +64 -0
  276. package/src/components/theme/context/ThemeContext.tsx +61 -0
  277. package/src/components/theme/context/index.ts +2 -0
  278. package/src/components/theme/context/useThemeSwitcher.ts +18 -0
  279. package/src/components/theme/index.ts +1 -0
  280. package/src/index.css +68 -0
  281. package/src/main.tsx +10 -0
  282. package/src/vite-env.d.ts +1 -0
  283. package/tsconfig.app.json +27 -0
  284. package/tsconfig.json +4 -0
  285. package/tsconfig.node.json +25 -0
  286. package/vite.config.ts +43 -0
  287. package/vitest.shims.d.ts +1 -0
@@ -0,0 +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;
@@ -0,0 +1,185 @@
1
+ 'use client';
2
+
3
+ import React, { useEffect, useRef, useState } from 'react';
4
+ import { ChevronsUpDown, ChevronUp, ChevronDown, X } from 'lucide-react';
5
+ import type { SortConfig, TableColumn } from './tools/tableTypes';
6
+ import styles from './TableauDynamique.module.css';
7
+
8
+ interface ColumnSorterProps<T = unknown> {
9
+ columns: TableColumn<T>[];
10
+ sortConfig: SortConfig;
11
+ onSort: (sortConfig: SortConfig) => void;
12
+ onToggleFilters?: () => void;
13
+ filtersActive?: boolean;
14
+ showFilters?: boolean;
15
+ }
16
+
17
+ const isAlphaUpperCase = (s: string) => {
18
+ const letters = s.replace(/[^A-Za-zÀ-ÖØ-öø-ÿ]/g, '');
19
+ return letters.length > 0 && letters === letters.toUpperCase();
20
+ };
21
+
22
+ const ColumnSorter = <T,>({ columns, sortConfig, onSort }: ColumnSorterProps<T>) => {
23
+ const [isOpen, setIsOpen] = useState(false);
24
+ const sortableColumns = columns.filter((col) => col.sortable);
25
+ const wrapperRef = useRef<HTMLDivElement | null>(null);
26
+
27
+ const handleSort = (key: string | null, direction: 'asc' | 'desc') => {
28
+ onSort({ key, direction });
29
+ setIsOpen(false);
30
+ };
31
+
32
+ const toggleOpen = () => setIsOpen((v) => !v);
33
+
34
+ const handleCancel = () => {
35
+ setIsOpen(false);
36
+ };
37
+
38
+ // fermer au clic extérieur + Échap (déjà en place)
39
+ useEffect(() => {
40
+ if (!isOpen) return;
41
+
42
+ const onPointerDown = (e: PointerEvent) => {
43
+ if (!wrapperRef.current) return;
44
+ const target = e.target as Node | null;
45
+ if (target && !wrapperRef.current.contains(target)) {
46
+ setIsOpen(false);
47
+ }
48
+ };
49
+
50
+ const onKeyDown = (e: KeyboardEvent) => {
51
+ if (e.key === 'Escape') {
52
+ setIsOpen(false);
53
+ }
54
+ };
55
+
56
+ document.addEventListener('pointerdown', onPointerDown);
57
+ document.addEventListener('keydown', onKeyDown);
58
+
59
+ return () => {
60
+ document.removeEventListener('pointerdown', onPointerDown);
61
+ document.removeEventListener('keydown', onKeyDown);
62
+ };
63
+ }, [isOpen]);
64
+
65
+ // --> NO SCROLL on the table container while dropdown is open
66
+ useEffect(() => {
67
+ const tableWrapper = document.querySelector('[data-table-wrapper]') as HTMLElement | null;
68
+ if (!tableWrapper) return;
69
+
70
+ // sauvegarde la valeur inline actuelle pour restaurer plus tard
71
+ const previousInline = tableWrapper.style.overflow ?? '';
72
+
73
+ if (isOpen) {
74
+ // masquer scrollbars (on cache à la fois X et Y)
75
+ tableWrapper.style.overflow = 'hidden';
76
+ } else {
77
+ // restaurer à la fermeture
78
+ tableWrapper.style.overflow = previousInline;
79
+ }
80
+
81
+ // cleanup si le composant se démonte pendant que c'est ouvert
82
+ return () => {
83
+ tableWrapper.style.overflow = previousInline;
84
+ };
85
+ }, [isOpen]);
86
+
87
+ return (
88
+ <div className={styles.columnSorterContainer}>
89
+ <div className={styles.sorterControls}>
90
+ {sortableColumns.length > 0 && (
91
+ <div className={styles.sortDropdownWrapper} ref={wrapperRef}>
92
+ <button
93
+ className={styles.toggleSortButton}
94
+ onClick={toggleOpen}
95
+ aria-expanded={isOpen}
96
+ aria-haspopup='true'
97
+ type='button'
98
+ >
99
+ <ChevronsUpDown size={14} />
100
+ <span
101
+ className={`${styles.buttonTextCompact} ${
102
+ isAlphaUpperCase('Sort') ? styles.uppercaseSmall : ''
103
+ }`}
104
+ >
105
+ Sort
106
+ </span>
107
+ {sortConfig.key &&
108
+ (sortConfig.direction === 'asc' ? (
109
+ <ChevronUp size={14} />
110
+ ) : (
111
+ <ChevronDown size={14} />
112
+ ))}
113
+ </button>
114
+
115
+ {isOpen && (
116
+ <div className={`${styles.sortDropdown} ${styles.sortDropdownInline}`} role='menu'>
117
+ <div className={styles.sortDropdownHeader}>
118
+ <span>Trier par:</span>
119
+ <button className={styles.closeButton} onClick={handleCancel} aria-label='Fermer'>
120
+ <X size={16} />
121
+ </button>
122
+ </div>
123
+
124
+ <div className={styles.sortDropdownContent}>
125
+ {sortableColumns.map((column) => {
126
+ const label = String(column.label ?? column.id);
127
+ const smallUpper = isAlphaUpperCase(label);
128
+ return (
129
+ <div
130
+ key={column.id}
131
+ className={styles.sortDropdownItem}
132
+ role='menuitem'
133
+ tabIndex={0}
134
+ onKeyDown={(e) => {
135
+ if (e.key === 'Enter') handleSort(column.id, 'asc');
136
+ }}
137
+ >
138
+ <span
139
+ className={`${styles.columnSorterLabel} ${smallUpper ? styles.uppercaseSmall : ''}`}
140
+ title={label}
141
+ aria-label={label}
142
+ >
143
+ {label}
144
+ </span>
145
+
146
+ <div className={styles.sortDirectionButtons}>
147
+ <button
148
+ className={`${styles.sortDirectionButton} ${
149
+ sortConfig.key === column.id && sortConfig.direction === 'asc'
150
+ ? styles.activeSort
151
+ : ''
152
+ }`}
153
+ onClick={() => handleSort(column.id, 'asc')}
154
+ title='Trier par ordre croissant'
155
+ type='button'
156
+ >
157
+ <ChevronUp size={12} />
158
+ </button>
159
+ <button
160
+ className={`${styles.sortDirectionButton} ${
161
+ sortConfig.key === column.id && sortConfig.direction === 'desc'
162
+ ? styles.activeSort
163
+ : ''
164
+ }`}
165
+ onClick={() => handleSort(column.id, 'desc')}
166
+ title='Trier par ordre décroissant'
167
+ type='button'
168
+ >
169
+ <ChevronDown size={12} />
170
+ </button>
171
+ </div>
172
+ </div>
173
+ );
174
+ })}
175
+ </div>
176
+ </div>
177
+ )}
178
+ </div>
179
+ )}
180
+ </div>
181
+ </div>
182
+ );
183
+ };
184
+
185
+ export default ColumnSorter;
@@ -0,0 +1,115 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import styles from './TableauDynamique.module.css';
5
+
6
+ interface PaginationProps {
7
+ currentPage: number;
8
+ totalPages: number;
9
+ pageSize: number;
10
+ totalItems: number;
11
+ onPageChange: (page: number) => void;
12
+ onPageSizeChange?: (size: number) => void;
13
+ onNewPage?: (page: number) => void;
14
+ }
15
+
16
+ const Pagination: React.FC<PaginationProps> = ({
17
+ currentPage,
18
+ totalPages,
19
+ pageSize,
20
+ totalItems,
21
+ onPageChange,
22
+ onPageSizeChange,
23
+ onNewPage,
24
+ }) => {
25
+ const safeTotalPages = Math.max(1, Math.floor(totalPages));
26
+ const safeCurrentPage = Math.min(Math.max(1, Math.floor(currentPage)), safeTotalPages);
27
+
28
+ const handlePrev = () => safeCurrentPage > 1 && onPageChange(safeCurrentPage - 1);
29
+ const handleNext = () => {
30
+ if (onNewPage && safeCurrentPage === safeTotalPages) {
31
+ onNewPage(safeTotalPages + 1);
32
+ return;
33
+ }
34
+ safeCurrentPage < safeTotalPages && onPageChange(safeCurrentPage + 1);
35
+ };
36
+
37
+ const startIndex = totalItems > 0 ? (safeCurrentPage - 1) * pageSize + 1 : 0;
38
+ const endIndex = totalItems > 0 ? Math.min(safeCurrentPage * pageSize, totalItems) : 0;
39
+
40
+ const getPageNumbers = () => {
41
+ const pages: number[] = [];
42
+ const maxVisible = 5;
43
+ let start = Math.max(1, safeCurrentPage - Math.floor(maxVisible / 2));
44
+ let end = start + maxVisible - 1;
45
+
46
+ if (end > safeTotalPages) {
47
+ end = safeTotalPages;
48
+ start = Math.max(1, end - maxVisible + 1);
49
+ }
50
+
51
+ for (let i = start; i <= end; i++) pages.push(i);
52
+ return pages;
53
+ };
54
+
55
+ return (
56
+ <div className={styles.paginationContainer} role='navigation' aria-label='Pagination'>
57
+ <div className={styles.leftGroup}>
58
+ {onPageSizeChange && (
59
+ <div className={styles.pageSizeControls}>
60
+ <label className={styles.pageSizeLabel}>Show</label>
61
+ <select
62
+ value={pageSize}
63
+ onChange={(e) => onPageSizeChange(Number(e.target.value))}
64
+ className={styles.pageSizeSelect}
65
+ aria-label='Taille de page'
66
+ >
67
+ {[5, 10, 20, 50].map((size) => (
68
+ <option key={size} value={size}>
69
+ {size}
70
+ </option>
71
+ ))}
72
+ </select>
73
+ <span className={styles.pageSizeSuffix}>entries</span>
74
+ </div>
75
+ )}
76
+ </div>
77
+
78
+ <div className={styles.paginationInfo} aria-live='polite'>
79
+ {startIndex}-{endIndex} out of {totalItems}
80
+ </div>
81
+
82
+ <div className={styles.paginationControls}>
83
+ <button
84
+ onClick={handlePrev}
85
+ disabled={safeCurrentPage === 1}
86
+ className={`${styles.paginationButton} ${styles.navigationButton} ${safeCurrentPage === 1 ? styles.disabledButton : ''}`}
87
+ aria-label='Page précédente'
88
+ >
89
+ «
90
+ </button>
91
+
92
+ {getPageNumbers().map((page) => (
93
+ <button
94
+ key={page}
95
+ onClick={() => onPageChange(page)}
96
+ aria-current={safeCurrentPage === page ? 'page' : undefined}
97
+ className={`${styles.paginationButton} ${safeCurrentPage === page ? styles.activeButton : ''}`}
98
+ >
99
+ {page}
100
+ </button>
101
+ ))}
102
+
103
+ <button
104
+ onClick={handleNext}
105
+ className={`${styles.paginationButton} ${styles.navigationButton}`}
106
+ aria-label='Page suivante'
107
+ >
108
+ »
109
+ </button>
110
+ </div>
111
+ </div>
112
+ );
113
+ };
114
+
115
+ export default Pagination;
@@ -0,0 +1,42 @@
1
+ import TableRow from './TableRow';
2
+ import type { TableBodyProps } from './tools/tableTypes';
3
+ import styles from './TableauDynamique.module.css';
4
+
5
+ const TableBody = <T,>({
6
+ data,
7
+ columns,
8
+ onRowClick,
9
+ startIndex,
10
+ searchTerm,
11
+ getRowStyle,
12
+ }: TableBodyProps<T>) => {
13
+ if (data.length === 0) {
14
+ return (
15
+ <tbody>
16
+ <tr>
17
+ <td colSpan={columns.length} className={styles.emptyRow}>
18
+ {searchTerm ? 'Aucun résultat trouvé' : 'Aucune donnée disponible'}
19
+ </td>
20
+ </tr>
21
+ </tbody>
22
+ );
23
+ }
24
+
25
+ return (
26
+ <tbody>
27
+ {data.map((row, index) => (
28
+ <TableRow
29
+ key={row.id}
30
+ row={row.data}
31
+ rowId={row.id}
32
+ columns={columns}
33
+ onRowClick={onRowClick}
34
+ index={startIndex + index - 1}
35
+ getRowStyle={getRowStyle}
36
+ />
37
+ ))}
38
+ </tbody>
39
+ );
40
+ };
41
+
42
+ export default TableBody;
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+
3
+ import type { RendererProps } from './tools/tableTypes';
4
+ import { tableConfig } from './tools/tableConfig';
5
+ import styles from './TableauDynamique.module.css';
6
+
7
+ const TableCell = <T,>({ value, row, column }: RendererProps<T>) => {
8
+ const getRenderer = () => {
9
+ if (column.component) {
10
+ const CustomComponent = column.component;
11
+ return <CustomComponent value={value} row={row} column={column} />;
12
+ }
13
+
14
+ if (column.render) {
15
+ return column.render(value, row, column);
16
+ }
17
+
18
+ const rendererKey = column.type || 'text';
19
+ const Renderer = tableConfig.renderers[rendererKey] || tableConfig.renderers.text;
20
+ return <Renderer value={value} row={row} column={column} />;
21
+ };
22
+
23
+ return (
24
+ <td className={styles.tableCell} style={{ textAlign: column.align || 'left' }}>
25
+ {getRenderer()}
26
+ </td>
27
+ );
28
+ };
29
+
30
+ export default TableCell;
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+
3
+ import type { TableColumn } from './tools/tableTypes';
4
+ import styles from './TableauDynamique.module.css';
5
+
6
+ interface TableHeaderProps<T = unknown> {
7
+ columns: TableColumn<T>[];
8
+ }
9
+
10
+ const TableHeader = <T,>({ columns }: TableHeaderProps<T>) => {
11
+ return (
12
+ <thead className={styles.tableHeader}>
13
+ <tr>
14
+ {columns.map((column) => (
15
+ <th
16
+ key={column.id}
17
+ className={styles.headerCell}
18
+ style={{
19
+ width: column.width,
20
+ textAlign: column.align,
21
+ padding: '1rem',
22
+ }}
23
+ >
24
+ <div className={styles.headerContent}>
25
+ <span>{column.label}</span>
26
+ </div>
27
+ </th>
28
+ ))}
29
+ </tr>
30
+ </thead>
31
+ );
32
+ };
33
+
34
+ export default TableHeader;
@@ -0,0 +1,56 @@
1
+ // TableRow.tsx
2
+ 'use client';
3
+
4
+ import TableCell from './TableCell';
5
+ import type { TableColumn } from './tools/tableTypes';
6
+ import styles from './TableauDynamique.module.css';
7
+
8
+ interface TableRowProps<T = unknown> {
9
+ row: T;
10
+ rowId: string;
11
+ columns: TableColumn<T>[];
12
+ onRowClick?: (row: T, index: number) => void;
13
+ index: number;
14
+ getRowStyle?: (row: T) => {
15
+ backgroundColor?: string;
16
+ accentColor?: string;
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ [key: string]: any;
19
+ };
20
+ }
21
+
22
+ const TableRow = <T,>({
23
+ row,
24
+ rowId,
25
+ columns,
26
+ onRowClick,
27
+ index,
28
+ getRowStyle,
29
+ }: TableRowProps<T>) => {
30
+ const styleConfig = getRowStyle ? getRowStyle(row) : {};
31
+ const { backgroundColor, accentColor, ...otherStyles } = styleConfig || {};
32
+ const styleWithVar = {
33
+ backgroundColor,
34
+ ...otherStyles,
35
+ ['--row-accent']: accentColor ?? 'transparent',
36
+ } as React.CSSProperties;
37
+
38
+ return (
39
+ <tr
40
+ className={`${styles.tableRow} ${onRowClick ? styles.clickableRow : ''}`}
41
+ onClick={() => onRowClick?.(row, index)}
42
+ style={styleWithVar}
43
+ >
44
+ {columns.map((column) => (
45
+ <TableCell
46
+ key={`${rowId}-${column.id}`}
47
+ value={row[column.id as keyof T]}
48
+ row={row}
49
+ column={column}
50
+ />
51
+ ))}
52
+ </tr>
53
+ );
54
+ };
55
+
56
+ export default TableRow;