@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,1014 @@
1
+ import React, { useState } from 'react';
2
+ // eslint-disable-next-line storybook/no-renderer-packages
3
+ import type { Meta, StoryObj } from '@storybook/react';
4
+ import TableauDynamique from './TableauDynamique';
5
+ import type { TableColumn, TableRowData } from './tools/tableTypes';
6
+ import type { FilterConfig, AppliedFilter } from './tools/filterTypes';
7
+ import {
8
+ User,
9
+ Mail,
10
+ Calendar,
11
+ Banknote,
12
+ LogIn,
13
+ Clock,
14
+ Stethoscope,
15
+ Microscope,
16
+ Image,
17
+ FileText,
18
+ MessageCircle,
19
+ LogOut,
20
+ } from 'lucide-react';
21
+
22
+ /**
23
+ * Extrait TSX principal (affiché dans "Show code") — prêt à copier/coller
24
+ * Contient une demo multi-tables + démonstrations de executeOnApply / fonctions-as-value
25
+ */
26
+ const exampleUsage = `import React, { useState } from 'react';
27
+ import TableauDynamique from './TableauDynamique';
28
+ import type { TableColumn, TableRowData } from './tools/tableTypes';
29
+ import type { FilterConfig } from './tools/filterTypes';
30
+
31
+ /* Données d'exemple */
32
+ const data = [
33
+ { id: 'u1', data: { name: 'Alice Martin', email: 'alice@example.com', age: 34, status: 'Actif', balance: 1234.56, joinDate: '2022-01-12' } },
34
+ { id: 'u2', data: { name: 'Bob Durand', email: 'bob@example.com', age: 28, status: 'Inactif', balance: 0, joinDate: '2023-08-05' } },
35
+ ];
36
+
37
+ /* Colonnes d'exemple */
38
+ const columns = [
39
+ { id: 'name', label: 'Nom' },
40
+ { id: 'email', label: 'Email' },
41
+ { id: 'age', label: 'Âge' },
42
+ { id: 'status', label: 'Statut' },
43
+ { id: 'balance', label: 'Solde' },
44
+ { id: 'joinDate', label: 'Date d\\'inscription' },
45
+ ];
46
+
47
+ /* Exemple 1 — executeOnApply (appelle "backend" asynchrone au moment d'Appliquer) */
48
+ const executeOnApplyFilterConfig = [
49
+ {
50
+ id: 'customerLookup',
51
+ type: 'text',
52
+ label: 'Client (recherche enrichie)',
53
+ placeholder: 'Tapez un nom puis Appliquer...',
54
+ // simulate API lookup on apply
55
+ executeOnApply: async (value) => {
56
+ if (!value) return value;
57
+ // simulation d'appel backend
58
+ await new Promise((r) => setTimeout(r, 700));
59
+ // retourne un id "résolu" depuis la saisie
60
+ return { resolvedCustomerId: 'c-' + String(value).toLowerCase().replace(/\\s+/g, '-') };
61
+ },
62
+ },
63
+ ];
64
+
65
+ /* Exemple 2 — valeur par défaut fournie comme fonction async
66
+ (useFilters initialise tempFilters avec defaultValue et apply() l'exécute)
67
+ */
68
+ const functionValueFilterConfig = [
69
+ {
70
+ id: 'autoIds',
71
+ type: 'multiselect',
72
+ label: 'IDs dynamiques',
73
+ // defaultValue est une fonction async — lorsqu'on cliquera Appliquer, la fonction sera exécutée
74
+ defaultValue: async (tempFilters) => {
75
+ // peut utiliser tempFilters pour décider du résultat
76
+ await new Promise((r) => setTimeout(r, 600));
77
+ return ['id-42', 'id-99'];
78
+ },
79
+ },
80
+ ];
81
+
82
+ export default function DemoExecuteExamples() {
83
+ const [applied, setApplied] = useState([]);
84
+
85
+ return (
86
+ <div style={{ padding: 16, display: 'grid', gap: 24 }}>
87
+ <section>
88
+ <h4>executeOnApply (exécution back-end au moment d'Appliquer)</h4>
89
+ <TableauDynamique
90
+ columns={columns}
91
+ data={data}
92
+ filterConfig={executeOnApplyFilterConfig}
93
+ filterable
94
+ columnFilterable
95
+ filtersControlled={false}
96
+ onApplyFilters={(filters) => {
97
+ console.log('Filters applied (executeOnApply):', filters);
98
+ setApplied(filters);
99
+ }}
100
+ pagination={{ pageSize: 5, currentPage: 1 }}
101
+ />
102
+ </section>
103
+
104
+ <section>
105
+ <h4>defaultValue as function (valeur fournie par fonction async)</h4>
106
+ <TableauDynamique
107
+ columns={columns}
108
+ data={data}
109
+ filterConfig={functionValueFilterConfig}
110
+ filterable
111
+ columnFilterable
112
+ filtersControlled={false}
113
+ onApplyFilters={(filters) => {
114
+ console.log('Filters applied (function defaultValue):', filters);
115
+ }}
116
+ pagination={{ pageSize: 5, currentPage: 1 }}
117
+ />
118
+ </section>
119
+ </div>
120
+ );
121
+ }
122
+ `;
123
+
124
+ /* -----------------------
125
+ Meta & configuration
126
+ ----------------------- */
127
+ const meta: Meta<typeof TableauDynamique> = {
128
+ title: 'Components/TableauDynamique',
129
+ component: TableauDynamique,
130
+ parameters: {
131
+ layout: 'padded',
132
+ docs: {
133
+ source: {
134
+ language: 'tsx',
135
+ code: exampleUsage,
136
+ },
137
+ },
138
+ },
139
+ tags: ['autodocs'],
140
+ argTypes: {
141
+ filterable: { control: 'boolean' },
142
+ columnFilterable: { control: 'boolean' },
143
+ },
144
+ };
145
+
146
+ export default meta;
147
+ type Story = StoryObj<typeof TableauDynamique>;
148
+
149
+ /* -----------------------
150
+ Helpers & données (identiques à ce que tu avais)
151
+ ----------------------- */
152
+ const createData = (count: number): TableRowData[] =>
153
+ Array.from({ length: count }, (_, i) => ({
154
+ id: `id-${i}`,
155
+ data: {
156
+ name: `Utilisateur ${i + 1}`,
157
+ email: `user${i + 1}@example.com`,
158
+ age: Math.floor(Math.random() * 50) + 18,
159
+ status: i % 3 === 0 ? 'Actif' : i % 3 === 1 ? 'Inactif' : 'En attente',
160
+ balance: Math.round(Math.random() * 10000) / 100,
161
+ joinDate: new Date(Date.now() - Math.random() * 1e10).toISOString().split('T')[0],
162
+ },
163
+ }));
164
+
165
+ const renderStatusBadge = (value: unknown) => {
166
+ const status = (value ?? '').toString();
167
+ const baseStyle: React.CSSProperties = {
168
+ display: 'inline-block',
169
+ padding: '4px 8px',
170
+ borderRadius: 999,
171
+ fontSize: '12px',
172
+ fontWeight: 700,
173
+ minWidth: '72px',
174
+ textAlign: 'center',
175
+ boxShadow: '0 1px 2px rgba(0,0,0,0.05)',
176
+ };
177
+
178
+ let style: React.CSSProperties = {};
179
+ switch (status) {
180
+ case 'Actif':
181
+ style = { background: 'linear-gradient(135deg,#10b981,#059669)', color: '#fff' };
182
+ break;
183
+ case 'Inactif':
184
+ style = { background: 'linear-gradient(135deg,#ef4444,#dc2626)', color: '#fff' };
185
+ break;
186
+ case 'En attente':
187
+ style = { background: 'linear-gradient(135deg,#f59e0b,#f97316)', color: '#fff' };
188
+ break;
189
+ default:
190
+ style = { background: '#e5e7eb', color: '#374151' };
191
+ }
192
+
193
+ return React.createElement('span', { style: { ...baseStyle, ...style } }, status || '--');
194
+ };
195
+
196
+ const StepCell = ({
197
+ icon: Icon,
198
+ time,
199
+ variant,
200
+ }: {
201
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
202
+ icon: any;
203
+ time?: string;
204
+ variant?: string;
205
+ noLeft?: boolean;
206
+ noRight?: boolean;
207
+ }) => {
208
+ const getVariantStyle = () => {
209
+ switch (variant) {
210
+ case 'admissions':
211
+ return { background: 'linear-gradient(135deg,#2ecc71,#28a745)', color: '#fff' };
212
+ case 'tri':
213
+ return { background: 'linear-gradient(135deg,#f6d365,#fda085)', color: '#fff' };
214
+ case 'examen':
215
+ return { background: 'linear-gradient(135deg,#6f86ff,#8b5cf6)', color: '#fff' };
216
+ case 'labo':
217
+ return { background: 'linear-gradient(135deg,#7dd3fc,#38bdf8)', color: '#fff' };
218
+ case 'radio':
219
+ return { background: 'linear-gradient(135deg,#ffb86b,#ff7a59)', color: '#fff' };
220
+ case 'diag':
221
+ return { background: 'linear-gradient(135deg,#ffd6a5,#ffadad)', color: '#fff' };
222
+ case 'sortie':
223
+ return { background: 'linear-gradient(135deg,#94d82d,#51cf66)', color: '#fff' };
224
+ default:
225
+ return { background: '#e5e7eb', color: '#374151' };
226
+ }
227
+ };
228
+
229
+ return React.createElement(
230
+ 'div',
231
+ {
232
+ style: {
233
+ display: 'flex',
234
+ flexDirection: 'column',
235
+ alignItems: 'center',
236
+ gap: '6px',
237
+ minWidth: '60px',
238
+ whiteSpace: 'nowrap',
239
+ position: 'relative',
240
+ },
241
+ },
242
+ React.createElement(
243
+ 'div',
244
+ {
245
+ style: {
246
+ width: '40px',
247
+ height: '40px',
248
+ borderRadius: '50%',
249
+ display: 'grid',
250
+ placeItems: 'center',
251
+ fontSize: '18px',
252
+ boxShadow: '0 3px 8px rgba(15, 23, 42, 0.06)',
253
+ position: 'relative',
254
+ zIndex: 2,
255
+ ...getVariantStyle(),
256
+ },
257
+ },
258
+ React.createElement(Icon, { 'aria-hidden': 'true' })
259
+ ),
260
+ React.createElement(
261
+ 'div',
262
+ { style: { fontSize: '12px', color: '#1370a6', fontWeight: '600' } },
263
+ time || '--:--'
264
+ )
265
+ );
266
+ };
267
+
268
+ const basicColumns: TableColumn<unknown>[] = [
269
+ { id: 'name', label: 'Nom', width: 200, sortable: true },
270
+ { id: 'email', label: 'Email', width: 250, sortable: true },
271
+ { id: 'age', label: 'Âge', width: 100, sortable: true, align: 'center' },
272
+ {
273
+ id: 'status',
274
+ label: 'Statut',
275
+ width: 120,
276
+ sortable: true,
277
+ render: (value: unknown) => renderStatusBadge(value),
278
+ },
279
+ {
280
+ id: 'balance',
281
+ label: 'Solde',
282
+ width: 120,
283
+ sortable: true,
284
+ align: 'right',
285
+ type: 'currency',
286
+ render: (value: unknown) =>
287
+ React.createElement(
288
+ 'div',
289
+ { style: { textAlign: 'right' } },
290
+ new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(Number(value))
291
+ ),
292
+ },
293
+ { id: 'joinDate', label: "Date d'inscription", width: 150, sortable: true, type: 'date' },
294
+ ];
295
+
296
+ const iconColumns: TableColumn<unknown>[] = [
297
+ {
298
+ id: 'name',
299
+ label: 'Nom',
300
+ width: 200,
301
+ sortable: true,
302
+ render: (value) =>
303
+ React.createElement(
304
+ 'div',
305
+ { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
306
+ React.createElement(User, { 'aria-hidden': 'true' }),
307
+ value
308
+ ),
309
+ },
310
+ {
311
+ id: 'email',
312
+ label: 'Email',
313
+ width: 250,
314
+ sortable: true,
315
+ render: (value) =>
316
+ React.createElement(
317
+ 'div',
318
+ { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
319
+ React.createElement(Mail, { 'aria-hidden': 'true' }),
320
+ value
321
+ ),
322
+ },
323
+ {
324
+ id: 'joinDate',
325
+ label: "Date d'inscription",
326
+ width: 150,
327
+ sortable: true,
328
+ render: (value) =>
329
+ React.createElement(
330
+ 'div',
331
+ { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
332
+ React.createElement(Calendar, { 'aria-hidden': 'true' }),
333
+ new Date(value).toLocaleDateString()
334
+ ),
335
+ },
336
+ {
337
+ id: 'balance',
338
+ label: 'Solde',
339
+ width: 120,
340
+ sortable: true,
341
+ align: 'right',
342
+ render: (value) =>
343
+ React.createElement(
344
+ 'div',
345
+ {
346
+ style: { display: 'flex', alignItems: 'center', gap: '8px', justifyContent: 'flex-end' },
347
+ },
348
+ React.createElement(Banknote, { 'aria-hidden': 'true' }),
349
+ new Intl.NumberFormat('fr-FR', {
350
+ style: 'currency',
351
+ currency: 'EUR',
352
+ }).format(Number(value))
353
+ ),
354
+ },
355
+ {
356
+ id: 'status',
357
+ label: 'Statut',
358
+ width: 120,
359
+ sortable: true,
360
+ render: (value: unknown) => renderStatusBadge(value),
361
+ },
362
+ ];
363
+
364
+ const getRowStyle = () => ({
365
+ backgroundColor: '#f0fff4',
366
+ accentColor: '#22c55e',
367
+ });
368
+
369
+ const TableWrapper: React.FC<{
370
+ columns: TableColumn<unknown>[];
371
+ data: TableRowData[];
372
+ filterable?: boolean;
373
+ columnFilterable?: boolean;
374
+ filterConfig?: FilterConfig[];
375
+ }> = (args) => {
376
+ const [searchTerm, setSearchTerm] = useState('');
377
+ const [appliedFilters, setAppliedFilters] = useState<AppliedFilter[]>([]);
378
+
379
+ return React.createElement(TableauDynamique, {
380
+ columns: args.columns,
381
+ data: args.data,
382
+ filterable: args.filterable ?? false,
383
+ columnFilterable: args.columnFilterable ?? false,
384
+ searchTerm,
385
+ onSearch: setSearchTerm,
386
+ pagination: { pageSize: 5, currentPage: 1 },
387
+ filterConfig: args.filterConfig,
388
+ externalFilters: appliedFilters,
389
+ onFiltersChange: setAppliedFilters,
390
+ filtersControlled: true,
391
+ getRowStyle,
392
+ });
393
+ };
394
+
395
+ const basicFilterConfig: FilterConfig[] = [
396
+ { id: 'name', type: 'text', label: 'Nom', placeholder: 'Filtrer par nom...' },
397
+ { id: 'email', type: 'text', label: 'Email', placeholder: 'Filtrer par email...' },
398
+ { id: 'age', type: 'number', label: 'Âge', placeholder: 'Filtrer par âge...', min: 18, max: 100 },
399
+ {
400
+ id: 'status',
401
+ type: 'select',
402
+ label: 'Statut',
403
+ options: [
404
+ { value: 'Actif', label: 'Actif' },
405
+ { value: 'Inactif', label: 'Inactif' },
406
+ { value: 'En attente', label: 'En attente' },
407
+ ],
408
+ },
409
+ { id: 'balance', type: 'number', label: 'Solde', placeholder: 'Filtrer par solde...' },
410
+ { id: 'joinDate', type: 'date', label: "Date d'inscription" },
411
+ ];
412
+
413
+ export const BasicTable: Story = {
414
+ args: { filterable: true, columnFilterable: true },
415
+ render: (args) =>
416
+ React.createElement(TableWrapper, {
417
+ columns: basicColumns,
418
+ data: createData(15),
419
+ filterable: args.filterable,
420
+ columnFilterable: args.columnFilterable,
421
+ filterConfig: basicFilterConfig,
422
+ }),
423
+ parameters: {
424
+ docs: {
425
+ source: {
426
+ code: `
427
+ import React from 'react';
428
+ import TableauDynamique from './TableauDynamique';
429
+ import { basicColumns, createData, basicFilterConfig } from './__stories__/helpers';
430
+
431
+ function Example() {
432
+ return <TableauDynamique columns={basicColumns} data={createData(15)} filterConfig={basicFilterConfig} filterable columnFilterable />;
433
+ }
434
+ export default Example;
435
+ `.trim(),
436
+ },
437
+ },
438
+ },
439
+ };
440
+
441
+ export const TableWithIcons: Story = {
442
+ args: { filterable: true, columnFilterable: true },
443
+ render: (args) =>
444
+ React.createElement(TableWrapper, {
445
+ columns: iconColumns,
446
+ data: createData(10),
447
+ filterable: args.filterable,
448
+ columnFilterable: args.columnFilterable,
449
+ filterConfig: basicFilterConfig,
450
+ }),
451
+ parameters: {
452
+ docs: {
453
+ source: {
454
+ code: `
455
+ import React from 'react';
456
+ import TableauDynamique from './TableauDynamique';
457
+ import { iconColumns, createData, basicFilterConfig } from './__stories__/helpers';
458
+
459
+ function Example() {
460
+ return <TableauDynamique columns={iconColumns} data={createData(10)} filterConfig={basicFilterConfig} filterable columnFilterable />;
461
+ }
462
+ export default Example;
463
+ `.trim(),
464
+ },
465
+ },
466
+ },
467
+ };
468
+
469
+ export const EmptyTable: Story = {
470
+ args: { filterable: true, columnFilterable: true },
471
+ render: (args) =>
472
+ React.createElement(TableWrapper, {
473
+ columns: basicColumns,
474
+ data: [],
475
+ filterable: args.filterable,
476
+ columnFilterable: args.columnFilterable,
477
+ filterConfig: basicFilterConfig,
478
+ }),
479
+ };
480
+
481
+ const medicalFilterConfig: FilterConfig[] = [
482
+ { id: 'iep', type: 'text', label: 'IEP', placeholder: 'Filtrer par IEP...' },
483
+ { id: 'ddn', type: 'text', label: 'DDN', placeholder: 'Filtrer par date de naissance...' },
484
+ {
485
+ id: 'genre',
486
+ type: 'select',
487
+ label: 'Genre',
488
+ options: [
489
+ { value: 'M', label: 'Masculin' },
490
+ { value: 'F', label: 'Féminin' },
491
+ ],
492
+ },
493
+ { id: 'motif', type: 'text', label: 'Motif', placeholder: 'Filtrer par motif...' },
494
+ {
495
+ id: 'gravite',
496
+ type: 'select',
497
+ label: 'Gravité',
498
+ options: [
499
+ { value: 'Élevée', label: 'Élevée' },
500
+ { value: 'Moyenne', label: 'Moyenne' },
501
+ { value: 'Modérée', label: 'Modérée' },
502
+ ],
503
+ },
504
+ { id: 'urgentiste', type: 'text', label: 'Urgentiste', placeholder: 'Filtrer par urgentiste...' },
505
+ ];
506
+
507
+ export const MedicalTable: Story = {
508
+ args: { filterable: true, columnFilterable: true },
509
+ render: (args) => {
510
+ const medicalData: TableRowData[] = [
511
+ {
512
+ id: '1',
513
+ data: {
514
+ iep: '12345',
515
+ ddn: '01/01/1980 (43)',
516
+ genre: 'M',
517
+ motif: 'Douleur thoracique',
518
+ gravite: 'Élevée',
519
+ urgentiste: 'Dr. Martin',
520
+ admissions: '08:30',
521
+ tri: '08:45',
522
+ examen: '09:15',
523
+ labo: '09:45',
524
+ radio: '10:30',
525
+ diag: '11:00',
526
+ avis: ['11:30', '12:00'],
527
+ sortie: '13:15',
528
+ status: 'Actif',
529
+ },
530
+ },
531
+ {
532
+ id: '2',
533
+ data: {
534
+ iep: '67890',
535
+ ddn: '15/05/1995 (28)',
536
+ genre: 'F',
537
+ motif: 'Fracture bras',
538
+ gravite: 'Moyenne',
539
+ urgentiste: 'Dr. Dupont',
540
+ admissions: '09:15',
541
+ tri: '09:30',
542
+ examen: '10:00',
543
+ labo: null,
544
+ radio: '10:45',
545
+ diag: '11:30',
546
+ avis: ['12:15'],
547
+ sortie: '13:45',
548
+ status: 'Inactif',
549
+ },
550
+ },
551
+ {
552
+ id: '3',
553
+ data: {
554
+ iep: '24680',
555
+ ddn: '20/11/1972 (51)',
556
+ genre: 'M',
557
+ motif: 'Malaise',
558
+ gravite: 'Modérée',
559
+ urgentiste: 'Dr. Leroy',
560
+ admissions: '10:20',
561
+ tri: '10:35',
562
+ examen: '11:05',
563
+ labo: '11:40',
564
+ radio: null,
565
+ diag: '12:20',
566
+ avis: ['13:00'],
567
+ sortie: '14:30',
568
+ status: 'En attente',
569
+ },
570
+ },
571
+ ];
572
+
573
+ const medicalColumns: TableColumn<unknown>[] = [
574
+ { id: 'iep', label: 'IEP', width: 100, sortable: true },
575
+ { id: 'ddn', label: 'DDN (AGE)', width: 120, sortable: true },
576
+ { id: 'genre', label: 'GENRE', width: 80, sortable: true },
577
+ { id: 'motif', label: 'MOTIF', width: 150, sortable: true },
578
+ { id: 'gravite', label: 'GRAVITE', width: 100, sortable: true },
579
+ { id: 'urgentiste', label: 'URGENTISTE', width: 150, sortable: true },
580
+ {
581
+ id: 'status',
582
+ label: 'STATUT',
583
+ width: 120,
584
+ sortable: true,
585
+ render: (v: unknown) => renderStatusBadge(v),
586
+ },
587
+ {
588
+ id: 'admissions',
589
+ label: 'ADMISSIONS',
590
+ width: 120,
591
+ sortable: true,
592
+ render: (v) =>
593
+ React.createElement(StepCell, {
594
+ icon: LogIn,
595
+ time: v,
596
+ variant: 'admissions',
597
+ noLeft: true,
598
+ }),
599
+ },
600
+ {
601
+ id: 'tri',
602
+ label: 'TRI',
603
+ width: 80,
604
+ sortable: true,
605
+ render: (v) => React.createElement(StepCell, { icon: Clock, time: v, variant: 'tri' }),
606
+ },
607
+ {
608
+ id: 'examen',
609
+ label: 'EXAMEN',
610
+ width: 80,
611
+ render: (v) =>
612
+ React.createElement(StepCell, { icon: Stethoscope, time: v, variant: 'examen' }),
613
+ },
614
+ {
615
+ id: 'labo',
616
+ label: 'LABO',
617
+ width: 80,
618
+ sortable: true,
619
+ render: (v) =>
620
+ React.createElement(StepCell, { icon: Microscope, time: v, variant: 'labo' }),
621
+ },
622
+ {
623
+ id: 'radio',
624
+ label: 'RADIO',
625
+ width: 80,
626
+ render: (v) => React.createElement(StepCell, { icon: Image, time: v, variant: 'radio' }),
627
+ },
628
+ {
629
+ id: 'diag',
630
+ label: 'DIAG',
631
+ width: 80,
632
+ sortable: true,
633
+ render: (v) => React.createElement(StepCell, { icon: FileText, time: v, variant: 'diag' }),
634
+ },
635
+ {
636
+ id: 'avis',
637
+ label: 'AVIS',
638
+ width: 160,
639
+ sortable: true,
640
+ render: (value) => {
641
+ if (!value || !Array.isArray(value)) return null;
642
+ return React.createElement(
643
+ 'div',
644
+ {
645
+ style: {
646
+ display: 'flex',
647
+ gap: '8px',
648
+ alignItems: 'center',
649
+ whiteSpace: 'nowrap',
650
+ overflowX: 'auto',
651
+ padding: '6px 0',
652
+ },
653
+ },
654
+ ...value.map((item: string, index: number) =>
655
+ React.createElement(
656
+ 'div',
657
+ {
658
+ key: index,
659
+ style: {
660
+ display: 'flex',
661
+ flexDirection: 'column',
662
+ alignItems: 'center',
663
+ minWidth: '46px',
664
+ gap: '6px',
665
+ },
666
+ },
667
+ React.createElement(
668
+ 'div',
669
+ {
670
+ style: {
671
+ width: '30px',
672
+ height: '30px',
673
+ borderRadius: '50%',
674
+ display: 'grid',
675
+ placeItems: 'center',
676
+ fontSize: '14px',
677
+ background: 'linear-gradient(135deg,#6b46ff,#8b5cf6)',
678
+ color: '#fff',
679
+ },
680
+ },
681
+ React.createElement(MessageCircle, { 'aria-hidden': 'true' })
682
+ ),
683
+ React.createElement('div', { style: { fontSize: '11px', color: '#1370a6' } }, item)
684
+ )
685
+ )
686
+ );
687
+ },
688
+ },
689
+ {
690
+ id: 'sortie',
691
+ label: 'SORTIE',
692
+ width: 100,
693
+ sortable: true,
694
+ render: (v) =>
695
+ React.createElement(StepCell, {
696
+ icon: LogOut,
697
+ time: v,
698
+ variant: 'sortie',
699
+ noRight: true,
700
+ }),
701
+ },
702
+ ];
703
+
704
+ return React.createElement(TableWrapper, {
705
+ columns: medicalColumns,
706
+ data: medicalData,
707
+ filterable: args.filterable,
708
+ columnFilterable: args.columnFilterable,
709
+ filterConfig: medicalFilterConfig,
710
+ });
711
+ },
712
+ };
713
+
714
+ export const TableWithActions: Story = {
715
+ args: { filterable: false, columnFilterable: false },
716
+ render: (args) => {
717
+ const actionColumns: TableColumn<unknown>[] = [
718
+ ...basicColumns,
719
+ {
720
+ id: 'actions',
721
+ label: 'Actions',
722
+ width: 120,
723
+ align: 'center',
724
+ render: (_: unknown, row: unknown) =>
725
+ React.createElement(
726
+ 'div',
727
+ { style: { display: 'flex', gap: '8px', justifyContent: 'center' } },
728
+ React.createElement(
729
+ 'button',
730
+ {
731
+ onClick: () => console.log('Edit:', row),
732
+ style: {
733
+ padding: '4px 8px',
734
+ border: '1px solid #3b82f6',
735
+ background: 'white',
736
+ color: '#3b82f6',
737
+ borderRadius: '4px',
738
+ cursor: 'pointer',
739
+ },
740
+ },
741
+ 'Modifier'
742
+ ),
743
+ React.createElement(
744
+ 'button',
745
+ {
746
+ onClick: () => console.log('Delete:', row),
747
+ style: {
748
+ padding: '4px 8px',
749
+ border: '1px solid #ef4444',
750
+ background: 'white',
751
+ color: '#ef4444',
752
+ borderRadius: '4px',
753
+ cursor: 'pointer',
754
+ },
755
+ },
756
+ 'Supprimer'
757
+ )
758
+ ),
759
+ },
760
+ ];
761
+
762
+ return React.createElement(TableWrapper, {
763
+ columns: actionColumns,
764
+ data: createData(8),
765
+ filterable: args.filterable,
766
+ columnFilterable: args.columnFilterable,
767
+ filterConfig: basicFilterConfig,
768
+ });
769
+ },
770
+ };
771
+
772
+ const allFilterConfig: FilterConfig[] = [
773
+ { id: 'text-filter', type: 'text', label: 'Filtre texte', placeholder: 'Rechercher...' },
774
+ {
775
+ id: 'number-filter',
776
+ type: 'number',
777
+ label: 'Filtre numérique',
778
+ placeholder: 'Entrez un nombre...',
779
+ min: 0,
780
+ max: 100,
781
+ step: 1,
782
+ },
783
+ {
784
+ id: 'select-filter',
785
+ type: 'select',
786
+ label: 'Filtre sélect',
787
+ options: [
788
+ { value: 'option1', label: 'Option 1' },
789
+ { value: 'option2', label: 'Option 2' },
790
+ { value: 'option3', label: 'Option 3' },
791
+ ],
792
+ },
793
+ {
794
+ id: 'operator-filter',
795
+ type: 'operator',
796
+ label: 'Filtre avec opérateurs',
797
+ operators: [
798
+ { value: '=', label: 'Égal à' },
799
+ { value: '>', label: 'Supérieur à' },
800
+ { value: '<', label: 'Inférieur à' },
801
+ { value: '>=', label: 'Supérieur ou égal' },
802
+ { value: '<=', label: 'Inférieur ou égal' },
803
+ ],
804
+ },
805
+ { id: 'date-filter', type: 'date', label: 'Filtre date' },
806
+ { id: 'daterange-filter', type: 'daterange', label: 'Plage de dates' },
807
+ { id: 'boolean-filter', type: 'boolean', label: 'Filtre booléen' },
808
+ ];
809
+
810
+ export const AllFilterTypes: Story = {
811
+ args: { filterable: true, columnFilterable: true },
812
+ render: (args) => {
813
+ const [searchTerm, setSearchTerm] = useState('');
814
+ const [appliedFilters, setAppliedFilters] = useState<AppliedFilter[]>([]);
815
+
816
+ return React.createElement(TableauDynamique, {
817
+ columns: basicColumns,
818
+ data: createData(15),
819
+ filterable: args.filterable,
820
+ columnFilterable: args.columnFilterable ?? false,
821
+ searchTerm,
822
+ onSearch: setSearchTerm,
823
+ pagination: { pageSize: 5, currentPage: 1 },
824
+ filterConfig: allFilterConfig,
825
+ externalFilters: appliedFilters,
826
+ onFiltersChange: setAppliedFilters,
827
+ filtersControlled: true,
828
+ getRowStyle,
829
+ });
830
+ },
831
+ };
832
+
833
+ const asyncOptions = async (): Promise<{ value: string; label: string }[]> =>
834
+ new Promise((resolve) => {
835
+ setTimeout(() => {
836
+ resolve([
837
+ { value: 'async1', label: 'Option asynchrone 1' },
838
+ { value: 'async2', label: 'Option asynchrone 2' },
839
+ { value: 'async3', label: 'Option asynchrone 3' },
840
+ ]);
841
+ }, 1000);
842
+ });
843
+
844
+ const asyncFilterConfig: FilterConfig[] = [
845
+ { id: 'async-select', type: 'select', label: 'Select asynchrone', options: [], asyncOptions },
846
+ ];
847
+
848
+ export const AsyncOptionsFilter: Story = {
849
+ args: { filterable: true, columnFilterable: true },
850
+ render: (args) => {
851
+ const [searchTerm, setSearchTerm] = useState('');
852
+ const [appliedFilters, setAppliedFilters] = useState<AppliedFilter[]>([]);
853
+
854
+ return React.createElement(TableauDynamique, {
855
+ columns: basicColumns,
856
+ data: createData(10),
857
+ filterable: args.filterable,
858
+ columnFilterable: args.columnFilterable ?? false,
859
+ searchTerm,
860
+ onSearch: setSearchTerm,
861
+ pagination: { pageSize: 5, currentPage: 1 },
862
+ filterConfig: asyncFilterConfig,
863
+ externalFilters: appliedFilters,
864
+ onFiltersChange: setAppliedFilters,
865
+ filtersControlled: true,
866
+ getRowStyle,
867
+ });
868
+ },
869
+ parameters: {
870
+ docs: {
871
+ source: {
872
+ code: `
873
+ const asyncFilterConfig = [
874
+ { id: 'async-select', type: 'select', label: 'Select asynchrone', options: [], asyncOptions: async () => {
875
+ // fetch les options au chargement du filtre
876
+ const res = await fetch('/api/options');
877
+ return res.json();
878
+ } },
879
+ ];
880
+ `.trim(),
881
+ },
882
+ },
883
+ },
884
+ };
885
+
886
+ export const WithColumnFilters: Story = {
887
+ args: { filterable: true, columnFilterable: true },
888
+ render: (args) => {
889
+ const [searchTerm, setSearchTerm] = useState('');
890
+ const [appliedFilters, setAppliedFilters] = useState<AppliedFilter[]>([]);
891
+
892
+ return React.createElement(TableauDynamique, {
893
+ columns: basicColumns,
894
+ data: createData(20),
895
+ filterable: args.filterable,
896
+ columnFilterable: args.columnFilterable ?? false,
897
+ searchTerm,
898
+ onSearch: setSearchTerm,
899
+ pagination: { pageSize: 5, currentPage: 1 },
900
+ filterConfig: basicFilterConfig,
901
+ externalFilters: appliedFilters,
902
+ onFiltersChange: setAppliedFilters,
903
+ filtersControlled: true,
904
+ getRowStyle,
905
+ });
906
+ },
907
+ };
908
+
909
+ /* -----------------------
910
+ --- NOUVEAU : story démontrant executeOnApply et defaultValue function
911
+ ----------------------- */
912
+ const executeOnApplyFilterConfig: FilterConfig[] = [
913
+ {
914
+ id: 'customerLookup',
915
+ type: 'text',
916
+ label: 'Client (recherche enrichie)',
917
+ placeholder: 'Tapez un nom puis Appliquer...',
918
+ executeOnApply: async (value) => {
919
+ if (!value) return value;
920
+ // simulate API call
921
+ await new Promise((r) => setTimeout(r, 700));
922
+ return { resolvedCustomerId: 'c-' + String(value).toLowerCase().replace(/\s+/g, '-') };
923
+ },
924
+ },
925
+ ];
926
+
927
+ const functionValueFilterConfig: FilterConfig[] = [
928
+ {
929
+ id: 'autoIds',
930
+ type: 'multiselect',
931
+ label: 'IDs dynamiques',
932
+ defaultValue: async () => {
933
+ // use tempFilters if needed; simulate backend
934
+ await new Promise((r) => setTimeout(r, 600));
935
+ return ['id-42', 'id-99'];
936
+ },
937
+ },
938
+ ];
939
+
940
+ export const ExecuteOnApplyFilter: Story = {
941
+ args: { filterable: true, columnFilterable: true },
942
+ render: (args) => {
943
+ const [applied, setApplied] = useState<AppliedFilter[]>([]);
944
+
945
+ return React.createElement('div', { style: { padding: 12 } }, [
946
+ React.createElement('h3', { key: 'h' }, 'executeOnApply & function defaultValue example'),
947
+ React.createElement(TableauDynamique, {
948
+ key: 't1',
949
+ columns: basicColumns,
950
+ data: createData(8),
951
+ filterable: true,
952
+ columnFilterable: true,
953
+ filterConfig: executeOnApplyFilterConfig,
954
+ filtersControlled: false,
955
+ onApplyFilters: (f: AppliedFilter[]) => {
956
+ console.log('Applied (executeOnApply):', f);
957
+ setApplied(f);
958
+ },
959
+ pagination: { pageSize: 5, currentPage: 1 },
960
+ }),
961
+ React.createElement('div', { key: 'sep', style: { height: 18 } }),
962
+ React.createElement(TableauDynamique, {
963
+ key: 't2',
964
+ columns: basicColumns,
965
+ data: createData(6),
966
+ filterable: true,
967
+ columnFilterable: true,
968
+ filterConfig: functionValueFilterConfig,
969
+ filtersControlled: false,
970
+ onApplyFilters: (f: AppliedFilter[]) => {
971
+ console.log('Applied (function defaultValue):', f);
972
+ },
973
+ pagination: { pageSize: 5, currentPage: 1 },
974
+ }),
975
+ ]);
976
+ },
977
+ parameters: {
978
+ docs: {
979
+ source: {
980
+ code: `
981
+ /* executeOnApply example (config) */
982
+ const executeOnApplyFilterConfig = [
983
+ {
984
+ id: 'customerLookup',
985
+ type: 'text',
986
+ label: 'Client (recherche enrichie)',
987
+ executeOnApply: async (value) => {
988
+ if (!value) return value;
989
+ const res = await fetch('/api/resolve-customer?q=' + encodeURIComponent(value));
990
+ return await res.json(); // retourne la valeur finale
991
+ }
992
+ }
993
+ ];
994
+
995
+ /* defaultValue as function example */
996
+ const functionValueFilterConfig = [
997
+ {
998
+ id: 'autoIds',
999
+ type: 'multiselect',
1000
+ label: 'IDs dynamiques',
1001
+ defaultValue: async (tempFilters) => {
1002
+ const res = await fetch('/api/ids');
1003
+ return await res.json();
1004
+ }
1005
+ }
1006
+ ];
1007
+
1008
+ <TableauDynamique filterConfig={executeOnApplyFilterConfig} />
1009
+ <TableauDynamique filterConfig={functionValueFilterConfig} />
1010
+ `.trim(),
1011
+ },
1012
+ },
1013
+ },
1014
+ };