@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,36 @@
1
+ // hooks/useAsyncActions.ts
2
+ import { useState, useCallback } from 'react';
3
+
4
+ export const useAsyncActions = () => {
5
+ const [loadingStates, setLoadingStates] = useState<Record<string, boolean>>({});
6
+ const [errorStates, setErrorStates] = useState<Record<string, string>>({});
7
+
8
+ const executeAsyncAction = useCallback(
9
+ async (
10
+ filterId: string,
11
+ asyncFunction: (currentValue: unknown) => Promise<unknown>,
12
+ currentValue: unknown
13
+ ) => {
14
+ setLoadingStates((prev) => ({ ...prev, [filterId]: true }));
15
+ setErrorStates((prev) => ({ ...prev, [filterId]: '' }));
16
+
17
+ try {
18
+ const result = await asyncFunction(currentValue);
19
+ setLoadingStates((prev) => ({ ...prev, [filterId]: false }));
20
+ return result;
21
+ } catch (error) {
22
+ const errorMessage = error instanceof Error ? error.message : 'Une erreur est survenue';
23
+ setErrorStates((prev) => ({ ...prev, [filterId]: errorMessage }));
24
+ setLoadingStates((prev) => ({ ...prev, [filterId]: false }));
25
+ throw error;
26
+ }
27
+ },
28
+ []
29
+ );
30
+
31
+ return {
32
+ loadingStates,
33
+ errorStates,
34
+ executeAsyncAction,
35
+ };
36
+ };
@@ -0,0 +1,142 @@
1
+ // hooks/useFilters.ts
2
+ import { useState, useCallback } from 'react';
3
+ import type { AppliedFilter, FilterConfig } from '../tools/filterTypes';
4
+
5
+ interface UseFiltersProps {
6
+ filters: FilterConfig[];
7
+ externalFilters?: AppliedFilter[];
8
+ onFiltersChange?: (filters: AppliedFilter[]) => void;
9
+ onApply?: (filters: AppliedFilter[]) => void;
10
+ debounceTimeout?: number;
11
+ isControlled?: boolean;
12
+ }
13
+
14
+ export const useFilters = ({
15
+ filters,
16
+ externalFilters = [],
17
+ onFiltersChange,
18
+ onApply,
19
+ isControlled = false,
20
+ }: UseFiltersProps) => {
21
+ const [internalFilters, setInternalFilters] = useState<AppliedFilter[]>([]);
22
+ const [tempFilters, setTempFilters] = useState<Record<string, unknown>>(() => {
23
+ const initialFilters: Record<string, unknown> = {};
24
+ filters.forEach((filter) => {
25
+ if (filter.type === 'daterange') {
26
+ initialFilters[filter.id] = { start: '', end: '' };
27
+ } else {
28
+ initialFilters[filter.id] = filter.defaultValue ?? '';
29
+ }
30
+ });
31
+ return initialFilters;
32
+ });
33
+
34
+ const appliedFilters = isControlled ? externalFilters : internalFilters;
35
+
36
+ const updateFilter = useCallback((filterId: string, value: unknown) => {
37
+ setTempFilters((prev) => ({ ...prev, [filterId]: value }));
38
+ }, []);
39
+
40
+ /**
41
+ * Applique les filtres.
42
+ * Supporte :
43
+ * - valeur simple
44
+ * - valeur fonction (async) : la fonction est appelée avec tempFilters et son résultat est utilisé
45
+ * - config.executeOnApply : si présente, elle est appelée et son résultat est utilisé
46
+ */
47
+ const applyFilters = useCallback(async (): Promise<AppliedFilter[]> => {
48
+ const newFilters: AppliedFilter[] = [];
49
+
50
+ // clone snapshot parce qu'on peut appeler des fonctions asynchrones
51
+ const snapshot = { ...tempFilters };
52
+
53
+ for (const [filterId, rawValue] of Object.entries(snapshot)) {
54
+ const config = filters.find((f) => f.id === filterId);
55
+ if (!config) continue;
56
+
57
+ let resolvedValue: unknown = rawValue;
58
+
59
+ try {
60
+ // 1) Si la valeur est une fonction, exécute-la (on la considère parfois fournie par l'utilisateur)
61
+ if (typeof rawValue === 'function') {
62
+ // note : on autorise fonctions sync ou async
63
+ // @ts-ignore - on sait que c'est callable
64
+ resolvedValue = await (
65
+ rawValue as (temp: Record<string, unknown>) => Promise<unknown> | unknown
66
+ )(snapshot);
67
+ }
68
+
69
+ // 2) Si la config du filtre propose executeOnApply, elle prend la priorité
70
+ if (typeof config.executeOnApply === 'function') {
71
+ resolvedValue = await config.executeOnApply(resolvedValue, snapshot);
72
+ }
73
+ } catch (err) {
74
+ // En cas d'erreur dans la fonction fournie, on skip le filtre (ou on pourrait journaliser)
75
+ // Ici on considère que le filtre n'est pas appliqué si l'exécution échoue.
76
+ // Tu peux adapter pour propager l'erreur si tu veux.
77
+ // console.error(`Erreur executeOnApply pour ${filterId}`, err);
78
+ continue;
79
+ }
80
+
81
+ // 3) Condition d'ajout : valeur définie et non vide
82
+ const empty =
83
+ resolvedValue === undefined ||
84
+ resolvedValue === null ||
85
+ (typeof resolvedValue === 'string' && (resolvedValue as string).trim() === '') ||
86
+ (Array.isArray(resolvedValue) && resolvedValue.length === 0);
87
+
88
+ if (!empty) {
89
+ newFilters.push({
90
+ id: filterId,
91
+ type: config.type,
92
+ value: resolvedValue,
93
+ label: config.label,
94
+ });
95
+ }
96
+ }
97
+
98
+ // Notify
99
+ if (isControlled) {
100
+ onFiltersChange?.(newFilters);
101
+ } else {
102
+ setInternalFilters(newFilters);
103
+ onApply?.(newFilters);
104
+ }
105
+
106
+ return newFilters;
107
+ }, [tempFilters, filters, isControlled, onFiltersChange, onApply]);
108
+
109
+ const clearFilter = useCallback(
110
+ (filterId: string) => {
111
+ setTempFilters((prev) => ({ ...prev, [filterId]: undefined }));
112
+ updateFilter(filterId, undefined);
113
+ },
114
+ [updateFilter]
115
+ );
116
+
117
+ const clearAllFilters = useCallback(() => {
118
+ const reset: Record<string, unknown> = {};
119
+ filters.forEach((f) => {
120
+ if (f.type === 'daterange') reset[f.id] = { start: '', end: '' };
121
+ else reset[f.id] = f.defaultValue ?? '';
122
+ });
123
+
124
+ setTempFilters(reset);
125
+
126
+ if (isControlled) {
127
+ onFiltersChange?.([]);
128
+ } else {
129
+ setInternalFilters([]);
130
+ onApply?.([]);
131
+ }
132
+ }, [filters, isControlled, onFiltersChange, onApply]);
133
+
134
+ return {
135
+ appliedFilters,
136
+ tempFilters,
137
+ updateFilter,
138
+ applyFilters,
139
+ clearFilter,
140
+ clearAllFilters,
141
+ };
142
+ };
@@ -0,0 +1,216 @@
1
+ // hooks/useTableData.ts
2
+ import { useMemo } from 'react';
3
+ import type { TableColumn, TableRowData, SortConfig } from '../tools/tableTypes';
4
+ import type { AppliedFilter } from '../tools/filterTypes';
5
+
6
+ // Fonction utilitaire pour comparer les valeurs en fonction du type de filtre
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ const matchesFilter = (value: any, filter: AppliedFilter): boolean => {
9
+ if (value === null || value === undefined) return false;
10
+
11
+ const filterValue = filter.value;
12
+
13
+ // Si filterValue est undefined ou null, retourner false
14
+ if (filterValue === null || filterValue === undefined) {
15
+ return false;
16
+ }
17
+
18
+ switch (filter.type) {
19
+ case 'text':
20
+ case 'search':
21
+ return String(value).toLowerCase().includes(String(filterValue).toLowerCase());
22
+
23
+ case 'number':
24
+ case 'range':
25
+ return Number(value) === Number(filterValue);
26
+
27
+ case 'select':
28
+ case 'multiselect':
29
+ if (Array.isArray(filterValue)) {
30
+ return (
31
+ filterValue.includes(value) || filterValue.map((v) => String(v)).includes(String(value))
32
+ );
33
+ }
34
+ return String(value) === String(filterValue);
35
+
36
+ case 'boolean':
37
+ return Boolean(value) === Boolean(filterValue);
38
+
39
+ case 'date':
40
+ return new Date(value).getTime() === new Date(String(filterValue)).getTime();
41
+
42
+ case 'daterange':
43
+ try {
44
+ if (!filterValue || typeof filterValue !== 'object') {
45
+ return false;
46
+ }
47
+
48
+ const rangeValue = filterValue as Record<string, unknown>;
49
+
50
+ // Vérification plus robuste des propriétés
51
+ if (!rangeValue.start || !rangeValue.end) {
52
+ return false;
53
+ }
54
+
55
+ const start = rangeValue.start;
56
+ const end = rangeValue.end;
57
+
58
+ if (typeof start !== 'string' || typeof end !== 'string' || !start.trim() || !end.trim()) {
59
+ return false;
60
+ }
61
+
62
+ const dateValue = new Date(value).getTime();
63
+ const startDate = new Date(start).getTime();
64
+ const endDate = new Date(end).getTime();
65
+
66
+ if (isNaN(dateValue) || isNaN(startDate) || isNaN(endDate)) {
67
+ return false;
68
+ }
69
+
70
+ return dateValue >= startDate && dateValue <= endDate;
71
+ } catch (error) {
72
+ console.error('Error processing daterange filter:', error);
73
+ return false;
74
+ }
75
+
76
+ case 'operator':
77
+ if (typeof filterValue === 'object' && filterValue !== null) {
78
+ const operatorFilter = filterValue as { operator?: unknown; value?: unknown };
79
+
80
+ // Vérifier que les propriétés existent
81
+ if (!('operator' in operatorFilter) || !('value' in operatorFilter)) {
82
+ return false;
83
+ }
84
+
85
+ const { operator, value: opValue } = operatorFilter as { operator: string; value: string };
86
+ const numValue = Number(value);
87
+ const numOpValue = Number(opValue);
88
+
89
+ // Vérifier que les valeurs numériques sont valides
90
+ if (isNaN(numValue) || isNaN(numOpValue)) {
91
+ return String(value).includes(String(opValue));
92
+ }
93
+
94
+ switch (operator) {
95
+ case 'equals':
96
+ return numValue === numOpValue;
97
+ case 'gt':
98
+ return numValue > numOpValue;
99
+ case 'lt':
100
+ return numValue < numOpValue;
101
+ case 'gte':
102
+ return numValue >= numOpValue;
103
+ case 'lte':
104
+ return numValue <= numOpValue;
105
+ default:
106
+ return String(value).includes(String(opValue));
107
+ }
108
+ }
109
+ return false;
110
+
111
+ default:
112
+ return String(value).toLowerCase().includes(String(filterValue).toLowerCase());
113
+ }
114
+ };
115
+
116
+ export function useTableData<T>(
117
+ data: TableRowData<T>[],
118
+ columns: TableColumn<T>[],
119
+ searchTerm: string = '',
120
+ sortConfig: SortConfig = { key: null, direction: 'asc' },
121
+ currentPage: number = 1,
122
+ pageSize: number = 10,
123
+ appliedFilters: AppliedFilter[] = []
124
+ ): {
125
+ paginatedData: TableRowData<T>[];
126
+ sortedData: TableRowData<T>[];
127
+ filteredData: TableRowData<T>[];
128
+ totalPages: number;
129
+ startIndex: number;
130
+ endIndex: number;
131
+ } {
132
+ const safePageSize = Math.max(1, pageSize);
133
+ const normalizedSearch = (searchTerm || '').trim().toLowerCase();
134
+
135
+ const filteredData = useMemo(() => {
136
+ let result = data;
137
+
138
+ // Filtrage par recherche globale
139
+ if (normalizedSearch) {
140
+ result = result.filter((row) =>
141
+ columns.some((column) => {
142
+ const value = row.data[column.id as keyof T];
143
+ if (value === null || value === undefined) return false;
144
+ return String(value).toLowerCase().includes(normalizedSearch);
145
+ })
146
+ );
147
+ }
148
+
149
+ // Filtrage par les filtres appliqués
150
+ if (appliedFilters.length > 0) {
151
+ result = result.filter((row) => {
152
+ return appliedFilters.every((filter) => {
153
+ try {
154
+ const value = row.data[filter.id as keyof T];
155
+ return matchesFilter(value, filter);
156
+ } catch (error) {
157
+ console.error('Error applying filter:', error);
158
+ return false;
159
+ }
160
+ });
161
+ });
162
+ }
163
+
164
+ return result;
165
+ }, [data, columns, normalizedSearch, appliedFilters]);
166
+
167
+ const sortedData = useMemo(() => {
168
+ if (!sortConfig?.key) return filteredData;
169
+
170
+ const key = sortConfig.key as keyof T;
171
+ return [...filteredData].sort((a, b) => {
172
+ const aValue = a.data[key];
173
+ const bValue = b.data[key];
174
+
175
+ // Gestion des valeurs nulles ou non définies
176
+ if (aValue === null || aValue === undefined) return 1;
177
+ if (bValue === null || bValue === undefined) return -1;
178
+
179
+ // Nombres
180
+ if (typeof aValue === 'number' && typeof bValue === 'number') {
181
+ return sortConfig.direction === 'desc' ? bValue - aValue : aValue - bValue;
182
+ }
183
+
184
+ // Dates
185
+ if (aValue instanceof Date && bValue instanceof Date) {
186
+ const diff = aValue.getTime() - bValue.getTime();
187
+ return sortConfig.direction === 'desc' ? -diff : diff;
188
+ }
189
+
190
+ // Fallback à la comparaison de chaînes
191
+ const cmp = String(aValue).localeCompare(String(bValue), undefined, {
192
+ numeric: true,
193
+ sensitivity: 'base',
194
+ });
195
+ return sortConfig.direction === 'desc' ? -cmp : cmp;
196
+ });
197
+ }, [filteredData, sortConfig]);
198
+
199
+ const paginatedData = useMemo(() => {
200
+ const start = (currentPage - 1) * safePageSize;
201
+ return sortedData.slice(start, start + safePageSize);
202
+ }, [sortedData, currentPage, safePageSize]);
203
+
204
+ const totalPages = Math.ceil(sortedData.length / safePageSize);
205
+ const startIndex = sortedData.length === 0 ? 0 : (currentPage - 1) * safePageSize + 1;
206
+ const endIndex = Math.min(currentPage * safePageSize, sortedData.length);
207
+
208
+ return {
209
+ paginatedData,
210
+ sortedData,
211
+ filteredData,
212
+ totalPages,
213
+ startIndex,
214
+ endIndex,
215
+ };
216
+ }
@@ -0,0 +1,11 @@
1
+ export * from './filters';
2
+ export * from './hooks';
3
+ export * from './tools';
4
+ export * from './AdvancedFilters';
5
+ export * from './ColumnSorter';
6
+ export * from './Pagination';
7
+ export * from './TableauDynamique';
8
+ export * from './TableBody';
9
+ export * from './TableHeader';
10
+ export * from './TableRow';
11
+ export * from './TableCell';
@@ -0,0 +1,118 @@
1
+ // utils/filterTypes.ts
2
+ export type FilterType =
3
+ | 'text'
4
+ | 'number'
5
+ | 'date'
6
+ | 'select'
7
+ | 'checkbox'
8
+ | 'range'
9
+ | 'toggle'
10
+ | 'search'
11
+ | 'operator'
12
+ | 'multiselect'
13
+ | 'daterange'
14
+ | 'boolean';
15
+
16
+ export interface OperatorOption {
17
+ value: string | number | boolean;
18
+ label: string;
19
+ }
20
+
21
+ export interface BaseFilterConfig {
22
+ id: string;
23
+ type: FilterType;
24
+ label: string;
25
+ placeholder?: string;
26
+ defaultValue?: unknown;
27
+ debounce?: number;
28
+ required?: boolean;
29
+ disabled?: boolean;
30
+
31
+ /**
32
+ * Optionnel : fonction appelée quand on "applique" les filtres.
33
+ * Utile si le filtre doit exécuter du code asynchrone (ex : appeler une API)
34
+ * avant de retourner la valeur finale à utiliser pour filtrer.
35
+ *
36
+ * signature : (value, tempFilters) => Promise<resolvedValue> | resolvedValue
37
+ */
38
+ executeOnApply?: (
39
+ value: unknown,
40
+ tempFilters: Record<string, unknown>
41
+ ) => Promise<unknown> | unknown;
42
+ }
43
+
44
+ export interface TextFilterConfig extends BaseFilterConfig {
45
+ type: 'text' | 'search';
46
+ }
47
+
48
+ export interface NumberFilterConfig extends BaseFilterConfig {
49
+ type: 'number';
50
+ min?: number;
51
+ max?: number;
52
+ step?: number;
53
+ }
54
+
55
+ export interface RangeFilterConfig extends BaseFilterConfig {
56
+ type: 'range';
57
+ min: number;
58
+ max: number;
59
+ step?: number;
60
+ defaultValue?: { min: number; max: number };
61
+ }
62
+
63
+ export interface SelectFilterConfig extends BaseFilterConfig {
64
+ type: 'select';
65
+ options?: OperatorOption[];
66
+ asyncOptions?: () => Promise<OperatorOption[]>;
67
+ isMulti?: boolean;
68
+ }
69
+
70
+ export interface MultiSelectFilterConfig extends BaseFilterConfig {
71
+ type: 'multiselect';
72
+ options?: OperatorOption[];
73
+ asyncOptions?: () => Promise<OperatorOption[]>;
74
+ }
75
+
76
+ export interface OperatorFilterConfig extends BaseFilterConfig {
77
+ type: 'operator';
78
+ operators: OperatorOption[];
79
+ defaultValue?: { operator: string; value: string };
80
+ }
81
+
82
+ export interface DateFilterConfig extends BaseFilterConfig {
83
+ type: 'date';
84
+ }
85
+
86
+ export interface DateRangeFilterConfig extends BaseFilterConfig {
87
+ type: 'daterange';
88
+ defaultValue?: { start: string; end: string };
89
+ }
90
+
91
+ export interface BooleanFilterConfig extends BaseFilterConfig {
92
+ type: 'boolean';
93
+ }
94
+
95
+ export type FilterConfig =
96
+ | TextFilterConfig
97
+ | NumberFilterConfig
98
+ | RangeFilterConfig
99
+ | SelectFilterConfig
100
+ | MultiSelectFilterConfig
101
+ | OperatorFilterConfig
102
+ | DateFilterConfig
103
+ | DateRangeFilterConfig
104
+ | BooleanFilterConfig;
105
+
106
+ export interface AppliedFilter {
107
+ id: string;
108
+ type: FilterType;
109
+ value: unknown;
110
+ label: string;
111
+ }
112
+
113
+ export interface FilterComponentProps {
114
+ config: FilterConfig;
115
+ value: unknown;
116
+ onChange: (value: unknown) => void;
117
+ disabled?: boolean;
118
+ }
@@ -0,0 +1,3 @@
1
+ export * from './filterTypes';
2
+ export * from './tableConfig';
3
+ export * from './tableTypes';
@@ -0,0 +1,96 @@
1
+ import React, { type ComponentType } from 'react';
2
+ import { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react';
3
+ import type { RendererProps, TableConfigType } from './tableTypes';
4
+
5
+ // Renderers par défaut (inchangés)
6
+ export const TextRenderer: React.FC<RendererProps> = ({ value }) => value;
7
+ export const EmailRenderer: React.FC<RendererProps> = ({ value }) =>
8
+ React.createElement(
9
+ 'a',
10
+ {
11
+ href: `mailto:${value}`,
12
+ className: 'email-link',
13
+ },
14
+ value
15
+ );
16
+ export const LinkRenderer: React.FC<RendererProps> = ({ value }) =>
17
+ React.createElement(
18
+ 'a',
19
+ {
20
+ href: String(value),
21
+ target: '_blank',
22
+ rel: 'noopener noreferrer',
23
+ className: 'text-link',
24
+ },
25
+ value
26
+ );
27
+ export const BadgeRenderer: React.FC<RendererProps> = ({ value }) => {
28
+ if (!value) return null;
29
+ const badgeTypes: Record<string, string> = {
30
+ success: 'success-badge',
31
+ warning: 'warning-badge',
32
+ error: 'error-badge',
33
+ info: 'info-badge',
34
+ default: 'default-badge',
35
+ };
36
+ const type = typeof value === 'object' ? value.type : 'default';
37
+ const text = typeof value === 'object' ? value.text : value;
38
+ const className = badgeTypes[type] || badgeTypes.default;
39
+ return React.createElement('span', { className: `badge ${className}` }, text);
40
+ };
41
+ export const DateRenderer: React.FC<RendererProps> = ({ value }) => {
42
+ try {
43
+ return new Date(value).toLocaleDateString();
44
+ } catch {
45
+ return String(value);
46
+ }
47
+ };
48
+ export const NumberRenderer: React.FC<RendererProps> = ({ value }) =>
49
+ React.createElement('span', { className: 'number-value' }, Number(value).toLocaleString());
50
+ export const CurrencyRenderer: React.FC<RendererProps> = ({ value }) =>
51
+ React.createElement(
52
+ 'span',
53
+ { className: 'currency-value' },
54
+ new Intl.NumberFormat('fr-FR', {
55
+ style: 'currency',
56
+ currency: 'EUR',
57
+ }).format(Number(value))
58
+ );
59
+ export const ImageRenderer: React.FC<RendererProps> = ({ value }) =>
60
+ React.createElement(
61
+ 'div',
62
+ { className: 'image-container' },
63
+ React.createElement('img', {
64
+ src: value,
65
+ alt: 'Table content',
66
+ className: 'table-image',
67
+ onError: (e) => (e.currentTarget.style.display = 'none'),
68
+ })
69
+ );
70
+
71
+ // Configuration globale modifiée
72
+ export const tableConfig: TableConfigType = {
73
+ renderers: {
74
+ text: TextRenderer,
75
+ email: EmailRenderer,
76
+ link: LinkRenderer,
77
+ badge: BadgeRenderer,
78
+ date: DateRenderer,
79
+ number: NumberRenderer,
80
+ currency: CurrencyRenderer,
81
+ image: ImageRenderer,
82
+ },
83
+ icons: {
84
+ sortAsc: React.createElement(ChevronUp, { className: 'sort-icon active', size: 14 }),
85
+ sortDesc: React.createElement(ChevronDown, { className: 'sort-icon active', size: 14 }),
86
+ sortDefault: React.createElement(ChevronsUpDown, { className: 'sort-icon', size: 14 }),
87
+ },
88
+ };
89
+
90
+ // Fonction d'extension (inchangée)
91
+ export const registerRenderer = (name: string, renderer: ComponentType<RendererProps>) => {
92
+ if (tableConfig.renderers[name]) {
93
+ console.warn(`Le renderer "${name}" existe déjà et sera écrasé.`);
94
+ }
95
+ tableConfig.renderers[name] = renderer;
96
+ };
@@ -0,0 +1,63 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type { ComponentType } from 'react';
3
+
4
+ export interface TableColumn<T = any> {
5
+ id: string;
6
+ label: string;
7
+ width?: string | number;
8
+ align?: 'left' | 'center' | 'right';
9
+ sortable?: boolean; // Rendre optionnel
10
+ render?: (value: any, row: T, column: TableColumn<T>) => React.ReactNode;
11
+ component?: ComponentType<{ value: any; row: T; column: TableColumn<T> }>;
12
+ type?: string;
13
+ options?: { value: string; label: string }[];
14
+ }
15
+
16
+ export interface TableRowData<T = any> {
17
+ id: string;
18
+ data: T;
19
+ }
20
+
21
+ export interface RendererProps<T = any> {
22
+ value: any;
23
+ row: T;
24
+ column: TableColumn<T>;
25
+ }
26
+
27
+ export interface TableConfigType {
28
+ renderers: Record<string, ComponentType<RendererProps>>;
29
+ icons: {
30
+ sortAsc: React.ReactElement;
31
+ sortDesc: React.ReactElement;
32
+ sortDefault: React.ReactElement;
33
+ };
34
+ }
35
+
36
+ export interface SortConfig {
37
+ key: string | null;
38
+ direction: 'asc' | 'desc';
39
+ }
40
+
41
+ export interface TableauDynamiqueProps<T = any> {
42
+ columns?: TableColumn<T>[];
43
+ data?: TableRowData<T>[];
44
+ pagination?: { pageSize: number; currentPage: number };
45
+ onSort?: (sortConfig: SortConfig) => void;
46
+ onPageChange?: (page: number) => void;
47
+ onPageSizeChange?: (size: number) => void;
48
+ onRowClick?: (row: T, index: number) => void;
49
+ filterable?: boolean;
50
+ searchTerm?: string;
51
+ onSearch?: (term: string) => void;
52
+ className?: string;
53
+ onNewPage?: (page: number) => void;
54
+ }
55
+
56
+ export interface TableBodyProps<T> {
57
+ data: TableRowData<T>[];
58
+ columns: TableColumn<T>[];
59
+ onRowClick?: (row: T, index: number) => void;
60
+ startIndex: number;
61
+ searchTerm?: string;
62
+ getRowStyle?: (row: T) => React.CSSProperties;
63
+ }