@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.
- package/.prettierignore +4 -0
- package/.prettierrc +9 -0
- package/.storybook/main.ts +17 -0
- package/.storybook/preview.ts +21 -0
- package/.storybook/vitest.setup.ts +7 -0
- package/README.md +69 -0
- package/chart.ts +1 -0
- package/eslint.config.js +40 -0
- package/index.html +13 -0
- package/index.ts +33 -0
- package/package.json +63 -0
- package/public/vite.svg +1 -0
- package/src/App.css +42 -0
- package/src/App.tsx +12 -0
- package/src/assets/accessibility.png +0 -0
- package/src/assets/accessibility.svg +1 -0
- package/src/assets/addon-library.png +0 -0
- package/src/assets/assets.png +0 -0
- package/src/assets/avif-test-image.avif +0 -0
- package/src/assets/context.png +0 -0
- package/src/assets/discord.svg +1 -0
- package/src/assets/docs.png +0 -0
- package/src/assets/figma-plugin.png +0 -0
- package/src/assets/github.svg +1 -0
- package/src/assets/react.svg +1 -0
- package/src/assets/share.png +0 -0
- package/src/assets/styling.png +0 -0
- package/src/assets/testing.png +0 -0
- package/src/assets/theming.png +0 -0
- package/src/assets/tutorials.svg +1 -0
- package/src/assets/youtube.svg +1 -0
- package/src/components/AddItemModal/AddItemModal.module.css +72 -0
- package/src/components/AddItemModal/AddItemModal.tsx +82 -0
- package/src/components/AddItemModal/index.ts +1 -0
- package/src/components/Alert/Alert.css +54 -0
- package/src/components/Alert/Alert.stories.tsx +82 -0
- package/src/components/Alert/Alert.tsx +85 -0
- package/src/components/Alert/AlertContext.tsx +200 -0
- package/src/components/Alert/AlertModels.ts +34 -0
- package/src/components/Alert/index.ts +3 -0
- package/src/components/AnnouncementPanel/FlexRowContainer.css +17 -0
- package/src/components/AnnouncementPanel/FlexRowContainer.stories.tsx +329 -0
- package/src/components/AnnouncementPanel/FlexRowContainer.tsx +24 -0
- package/src/components/AnnouncementPanel/ListBox/CounterListBox.css +56 -0
- package/src/components/AnnouncementPanel/ListBox/CounterListBox.stories.tsx +292 -0
- package/src/components/AnnouncementPanel/ListBox/CounterListBox.tsx +106 -0
- package/src/components/AnnouncementPanel/ListBox/SimpleListBox.css +57 -0
- package/src/components/AnnouncementPanel/ListBox/SimpleListBox.stories.tsx +189 -0
- package/src/components/AnnouncementPanel/ListBox/SimpleListBox.tsx +138 -0
- package/src/components/AnnouncementPanel/ListBox/TrendListBox.css +61 -0
- package/src/components/AnnouncementPanel/ListBox/TrendListBox.stories.tsx +257 -0
- package/src/components/AnnouncementPanel/ListBox/TrendListBox.tsx +90 -0
- package/src/components/AnnouncementPanel/ListBox/index.ts +3 -0
- package/src/components/AnnouncementPanel/ListContentContainer.css +23 -0
- package/src/components/AnnouncementPanel/ListContentContainer.stories.tsx +212 -0
- package/src/components/AnnouncementPanel/ListContentContainer.tsx +33 -0
- package/src/components/AnnouncementPanel/index.ts +3 -0
- package/src/components/BandChart/index.tsx +282 -0
- package/src/components/Button/Button.module.css +165 -0
- package/src/components/Button/Button.stories.ts +132 -0
- package/src/components/Button/Button.tsx +55 -0
- package/src/components/Button/button.css +29 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/ChartContainer/ChartContainer.css +116 -0
- package/src/components/ChartContainer/ChartContainer.stories.tsx +159 -0
- package/src/components/ChartContainer/ChartContainer.tsx +155 -0
- package/src/components/ChartContainer/index.ts +1 -0
- package/src/components/Charts/area-chart-admission/AreaChartAdmission.stories.tsx +65 -0
- package/src/components/Charts/area-chart-admission/AreaChartAdmission.tsx +89 -0
- package/src/components/Charts/area-chart-admission/content.json +48 -0
- package/src/components/Charts/area-chart-hospitalisation/AreaChartHospitalisation.stories.tsx +141 -0
- package/src/components/Charts/area-chart-hospitalisation/AreaChartHospitalisation.tsx +262 -0
- package/src/components/Charts/area-chart-hospitalisation/content.json +55 -0
- package/src/components/Charts/bar-chart/BarChart.stories.tsx +50 -0
- package/src/components/Charts/bar-chart/BarChart.tsx +132 -0
- package/src/components/Charts/bar-chart/content.json +15 -0
- package/src/components/Charts/boxplot-chart/BoxPlotChart.stories.tsx +63 -0
- package/src/components/Charts/boxplot-chart/BoxPlotChart.tsx +114 -0
- package/src/components/Charts/boxplot-chart/boxUtils.ts +22 -0
- package/src/components/Charts/boxplot-chart/content.json +11 -0
- package/src/components/Charts/mixed-chart/MixedChart.stories.tsx +83 -0
- package/src/components/Charts/mixed-chart/MixedChart.tsx +625 -0
- package/src/components/Charts/mixed-chart/content.json +34 -0
- package/src/components/Charts/sankey-adaptation/sankey.tsx +70 -0
- package/src/components/Charts/sankey-chart/SankeyChart.stories.tsx +69 -0
- package/src/components/Charts/sankey-chart/SankeyChart.tsx +155 -0
- package/src/components/Charts/sankey-chart/content.json +15 -0
- package/src/components/Charts/stacked-column/StackedColumn.stories.tsx +72 -0
- package/src/components/Charts/stacked-column/StackedColumn.tsx +406 -0
- package/src/components/Charts/stacked-column/content.json +37 -0
- package/src/components/Charts/stacked-column-one-hundred-percent/StackedColumnOneHundredPercent.stories.tsx +43 -0
- package/src/components/Charts/stacked-column-one-hundred-percent/StackedColumnOneHundredPercent.tsx +75 -0
- package/src/components/Charts/stacked-column-one-hundred-percent/content.json +6 -0
- package/src/components/CircularProgress/CircularProgress.css +79 -0
- package/src/components/CircularProgress/CircularProgress.stories.tsx +251 -0
- package/src/components/CircularProgress/CircularProgress.tsx +101 -0
- package/src/components/CircularProgress/index.ts +2 -0
- package/src/components/Configure.mdx +369 -0
- package/src/components/DayStatCard/DayStatCard.css +50 -0
- package/src/components/DayStatCard/DayStatCard.stories.tsx +273 -0
- package/src/components/DayStatCard/DayStatCard.tsx +69 -0
- package/src/components/DayStatCard/index.ts +2 -0
- package/src/components/DraggableSwitcher/DraggableSwitcherButton.tsx +58 -0
- package/src/components/DraggableSwitcher/context/useDraggableSwitcher.tsx +45 -0
- package/src/components/DraggableSwitcher/index.ts +2 -0
- package/src/components/DropdownMenu/DropdownMenu.css +100 -0
- package/src/components/DropdownMenu/DropdownMenu.stories.tsx +174 -0
- package/src/components/DropdownMenu/DropdownMenu.tsx +106 -0
- package/src/components/DropdownMenu/index.ts +1 -0
- package/src/components/DynamicForm/DynamicFom.stories.ts +773 -0
- package/src/components/DynamicForm/DynamicForm.module.css +468 -0
- package/src/components/DynamicForm/DynamicForm.tsx +224 -0
- package/src/components/DynamicForm/index.ts +3 -0
- package/src/components/DynamicForm/tools/form-metadata.ts +82 -0
- package/src/components/DynamicForm/tools/validation.ts +168 -0
- package/src/components/DynamicInput/DynamicInput.module.css +126 -0
- package/src/components/DynamicInput/DynamicInput.stories.ts +350 -0
- package/src/components/DynamicInput/DynamicInput.tsx +144 -0
- package/src/components/DynamicInput/index.ts +2 -0
- package/src/components/DynamicInput/input/CheckboxInput.tsx +41 -0
- package/src/components/DynamicInput/input/ColorInput.tsx +46 -0
- package/src/components/DynamicInput/input/DateInput.tsx +57 -0
- package/src/components/DynamicInput/input/FileInput.tsx +174 -0
- package/src/components/DynamicInput/input/InputWrapper.tsx +26 -0
- package/src/components/DynamicInput/input/RadioInput.tsx +46 -0
- package/src/components/DynamicInput/input/RangeInput.tsx +46 -0
- package/src/components/DynamicInput/input/SelectInput.tsx +75 -0
- package/src/components/DynamicInput/input/TextInput.tsx +101 -0
- package/src/components/DynamicInput/input/TextareaInput.tsx +47 -0
- package/src/components/DynamicInput/input/assets/ColorInput.module.css +48 -0
- package/src/components/DynamicInput/input/assets/DateInput.module.css +83 -0
- package/src/components/DynamicInput/input/assets/FileInput.module.css +133 -0
- package/src/components/DynamicInput/input/assets/RangeInput.module.css +169 -0
- package/src/components/DynamicInput/input/assets/SelectInput.module.css +95 -0
- package/src/components/DynamicInput/input/index.ts +10 -0
- package/src/components/DynamicTable/AdvancedFilters.tsx +196 -0
- package/src/components/DynamicTable/ColumnSorter.tsx +185 -0
- package/src/components/DynamicTable/Pagination.tsx +115 -0
- package/src/components/DynamicTable/TableBody.tsx +42 -0
- package/src/components/DynamicTable/TableCell.tsx +30 -0
- package/src/components/DynamicTable/TableHeader.tsx +34 -0
- package/src/components/DynamicTable/TableRow.tsx +56 -0
- package/src/components/DynamicTable/TableauDynamic.stories.ts +1014 -0
- package/src/components/DynamicTable/TableauDynamique.module.css +1287 -0
- package/src/components/DynamicTable/TableauDynamique.tsx +154 -0
- package/src/components/DynamicTable/filters/BooleanFilter.tsx +30 -0
- package/src/components/DynamicTable/filters/DateFilter.tsx +28 -0
- package/src/components/DynamicTable/filters/DateRangeFilter.tsx +51 -0
- package/src/components/DynamicTable/filters/FilterRenderer.tsx +117 -0
- package/src/components/DynamicTable/filters/MultiSelectFilter.tsx +59 -0
- package/src/components/DynamicTable/filters/NumberFilter.tsx +37 -0
- package/src/components/DynamicTable/filters/OperatorFilter.tsx +64 -0
- package/src/components/DynamicTable/filters/SelectFilter.tsx +69 -0
- package/src/components/DynamicTable/filters/TextFilter.tsx +39 -0
- package/src/components/DynamicTable/filters/index.ts +9 -0
- package/src/components/DynamicTable/hooks/index.ts +2 -0
- package/src/components/DynamicTable/hooks/useAsyncActions.ts +36 -0
- package/src/components/DynamicTable/hooks/useFilters.ts +142 -0
- package/src/components/DynamicTable/hooks/useTableData.ts +216 -0
- package/src/components/DynamicTable/index.ts +11 -0
- package/src/components/DynamicTable/tools/filterTypes.ts +118 -0
- package/src/components/DynamicTable/tools/index.ts +3 -0
- package/src/components/DynamicTable/tools/tableConfig.ts +96 -0
- package/src/components/DynamicTable/tools/tableTypes.ts +63 -0
- package/src/components/EntryControl/EntryControl.module.css +218 -0
- package/src/components/EntryControl/EntryControl.stories.tsx +71 -0
- package/src/components/EntryControl/EntryControl.tsx +117 -0
- package/src/components/EntryControl/index.ts +2 -0
- package/src/components/Grid/Grid.stories.tsx +94 -0
- package/src/components/Grid/Grid.tsx +214 -0
- package/src/components/Grid/grid.css +285 -0
- package/src/components/Grid/index.ts +2 -0
- package/src/components/Header/Header.stories.tsx +164 -0
- package/src/components/Header/Header.tsx +59 -0
- package/src/components/Header/header.css +31 -0
- package/src/components/Header/index.ts +1 -0
- package/src/components/IconText/IconText..stories.tsx +135 -0
- package/src/components/IconText/IconText.css +43 -0
- package/src/components/IconText/IconText.tsx +43 -0
- package/src/components/IconText/index.ts +1 -0
- package/src/components/LanguageSelector/LanguageSelector.css +31 -0
- package/src/components/LanguageSelector/LanguageSelector.stories.tsx +38 -0
- package/src/components/LanguageSelector/LanguageSelector.tsx +37 -0
- package/src/components/LanguageSelector/index.ts +1 -0
- package/src/components/Logo/Logo.css +89 -0
- package/src/components/Logo/Logo.stories.tsx +79 -0
- package/src/components/Logo/Logo.tsx +22 -0
- package/src/components/Logo/index.ts +2 -0
- package/src/components/MetricsPanel/MetricsItem.tsx +128 -0
- package/src/components/MetricsPanel/MetricsPanel.module.css +636 -0
- package/src/components/MetricsPanel/MetricsPanel.stories.tsx +692 -0
- package/src/components/MetricsPanel/MetricsPanel.tsx +282 -0
- package/src/components/MetricsPanel/PanelHeader.tsx +19 -0
- package/src/components/MetricsPanel/SummaryCard.tsx +61 -0
- package/src/components/MetricsPanel/index.ts +4 -0
- package/src/components/MetricsPanel/renderers/CompactRenderer.tsx +125 -0
- package/src/components/MetricsPanel/renderers/ImageCard.tsx +62 -0
- package/src/components/MetricsPanel/renderers/PertinenceCard.tsx +55 -0
- package/src/components/MetricsPanel/tools/MetricsPanelTypes.ts +62 -0
- package/src/components/MetricsPanel/tools/chooseDefaultRender.ts +50 -0
- package/src/components/MetricsPanel/tools/colorUtils.ts +39 -0
- package/src/components/MetricsPanel/tools/index.ts +2 -0
- package/src/components/ModuleHeader/ModuleHeader.css +37 -0
- package/src/components/ModuleHeader/ModuleHeader.stories.tsx +37 -0
- package/src/components/ModuleHeader/ModuleHeader.tsx +42 -0
- package/src/components/ModuleHeader/index.ts +1 -0
- package/src/components/ModuleSideBar/ModuleSideBar.css +227 -0
- package/src/components/ModuleSideBar/ModuleSideBar.stories.tsx +40 -0
- package/src/components/ModuleSideBar/ModuleSideBar.tsx +155 -0
- package/src/components/ModuleSideBar/index.ts +1 -0
- package/src/components/NavBar/NavBar.css +58 -0
- package/src/components/NavBar/NavBar.stories.tsx +169 -0
- package/src/components/NavBar/NavBar.tsx +100 -0
- package/src/components/NavBar/NavContext.tsx +30 -0
- package/src/components/NavBar/index.ts +2 -0
- package/src/components/NavItem/NavItem.css +29 -0
- package/src/components/NavItem/NavItem.tsx +58 -0
- package/src/components/NavItem/index.ts +1 -0
- package/src/components/Page/Dashboard.tsx +93 -0
- package/src/components/Page/Page.stories.ts +33 -0
- package/src/components/Page/Page.tsx +73 -0
- package/src/components/Page/page.css +81 -0
- package/src/components/PerformanceCard/PerformanceCard.module.css +232 -0
- package/src/components/PerformanceCard/PerformanceCard.stories.tsx +441 -0
- package/src/components/PerformanceCard/PerformanceCard.tsx +198 -0
- package/src/components/PerformanceCard/defaultHistogramRenderer.tsx +32 -0
- package/src/components/PerformanceCard/tools/types.ts +50 -0
- package/src/components/PerformanceCard/tools/usePerformanceCard.ts +93 -0
- package/src/components/PeriodRange/PeriodRange.module.css +158 -0
- package/src/components/PeriodRange/PeriodRange.stories.tsx +66 -0
- package/src/components/PeriodRange/PeriodRange.tsx +130 -0
- package/src/components/PeriodSelect/PeriodSelect.module.css +65 -0
- package/src/components/PeriodSelect/PeriodSelect.stories.tsx +40 -0
- package/src/components/PeriodSelect/PeriodSelect.tsx +42 -0
- package/src/components/PeriodSelect/index.ts +1 -0
- package/src/components/ScrollableHorizontale/ScrollableHorizontale.css +40 -0
- package/src/components/ScrollableHorizontale/ScrollableHorizontale.stories.tsx +133 -0
- package/src/components/ScrollableHorizontale/ScrollableHorizontale.tsx +29 -0
- package/src/components/ScrollableHorizontale/index.ts +1 -0
- package/src/components/SearchBar/SearchBar.css +40 -0
- package/src/components/SearchBar/SearchBar.stories.tsx +36 -0
- package/src/components/SearchBar/SearchBar.tsx +30 -0
- package/src/components/SearchBar/index.ts +1 -0
- package/src/components/SectionTitle/SectionTitle.css +21 -0
- package/src/components/SectionTitle/SectionTitle.stories.tsx +39 -0
- package/src/components/SectionTitle/SectionTitle.tsx +18 -0
- package/src/components/SideComponent/PatientEditor.tsx +64 -0
- package/src/components/SideComponent/SideComponent.css +179 -0
- package/src/components/SideComponent/SideComponent.stories.tsx +547 -0
- package/src/components/SideComponent/SideComponent.tsx +243 -0
- package/src/components/SideComponent/hooks/useBodyScrollLock.ts +15 -0
- package/src/components/SideComponent/index.ts +2 -0
- package/src/components/SideComponent/portal.ts +11 -0
- package/src/components/SubNavBar/SubNavBar.tsx +41 -0
- package/src/components/SubNavBar/index.ts +1 -0
- package/src/components/Switcher/Switcher.css +65 -0
- package/src/components/Switcher/Switcher.stories.tsx +153 -0
- package/src/components/Switcher/Switcher.tsx +83 -0
- package/src/components/Switcher/index.ts +1 -0
- package/src/components/Title/Title.stories.tsx +18 -0
- package/src/components/Title/Title.tsx +26 -0
- package/src/components/Title/index.ts +1 -0
- package/src/components/TranslationKey/TranslationKey.css +272 -0
- package/src/components/TranslationKey/TranslationKey.stories.tsx +50 -0
- package/src/components/TranslationKey/TranslationKey.tsx +245 -0
- package/src/components/TranslationKey/index.ts +1 -0
- package/src/components/TrendItem/TrendItem.css +36 -0
- package/src/components/TrendItem/TrendItem.stories.tsx +276 -0
- package/src/components/TrendItem/TrendItem.tsx +46 -0
- package/src/components/TrendItem/index.ts +1 -0
- package/src/components/TrendList/TrendList.css +16 -0
- package/src/components/TrendList/TrendList.stories.tsx +337 -0
- package/src/components/TrendList/TrendList.tsx +45 -0
- package/src/components/TrendList/index.ts +1 -0
- package/src/components/theme/ThemeSwitcherButton.tsx +64 -0
- package/src/components/theme/context/ThemeContext.tsx +61 -0
- package/src/components/theme/context/index.ts +2 -0
- package/src/components/theme/context/useThemeSwitcher.ts +18 -0
- package/src/components/theme/index.ts +1 -0
- package/src/index.css +68 -0
- package/src/main.tsx +10 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.app.json +27 -0
- package/tsconfig.json +4 -0
- package/tsconfig.node.json +25 -0
- package/vite.config.ts +43 -0
- 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,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
|
+
}
|