@tsiky/components-r19 1.1.0 → 1.2.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/package.json +1 -1
- package/src/components/AnnouncementPanel/FlexRowContainer.css +17 -17
- package/src/components/AnnouncementPanel/FlexRowContainer.stories.tsx +329 -329
- package/src/components/AnnouncementPanel/FlexRowContainer.tsx +24 -24
- package/src/components/AnnouncementPanel/ListBox/CounterListBox.css +56 -56
- package/src/components/AnnouncementPanel/ListBox/CounterListBox.stories.tsx +292 -292
- package/src/components/AnnouncementPanel/ListBox/CounterListBox.tsx +106 -106
- package/src/components/AnnouncementPanel/ListBox/SimpleListBox.css +57 -57
- package/src/components/AnnouncementPanel/ListBox/SimpleListBox.stories.tsx +189 -189
- package/src/components/AnnouncementPanel/ListBox/SimpleListBox.tsx +138 -138
- package/src/components/AnnouncementPanel/ListBox/TrendListBox.css +61 -61
- package/src/components/AnnouncementPanel/ListBox/TrendListBox.stories.tsx +257 -257
- package/src/components/AnnouncementPanel/ListBox/TrendListBox.tsx +90 -90
- package/src/components/AnnouncementPanel/ListBox/index.ts +3 -3
- package/src/components/AnnouncementPanel/ListContentContainer.css +23 -23
- package/src/components/AnnouncementPanel/ListContentContainer.stories.tsx +212 -212
- package/src/components/AnnouncementPanel/ListContentContainer.tsx +33 -33
- package/src/components/AnnouncementPanel/index.ts +3 -3
- package/src/components/Charts/area-chart-admission/AreaChartAdmission.tsx +7 -1
- package/src/components/Charts/bar-chart/BarChart.tsx +6 -2
- package/src/components/Charts/boxplot-chart/BoxPlotChart.tsx +114 -114
- package/src/components/Charts/mixed-chart/MixedChart.tsx +1 -1
- package/src/components/Charts/sankey-adaptation/sankey.tsx +70 -70
- package/src/components/DraggableSwitcher/DraggableSwitcherButton.tsx +58 -58
- package/src/components/DraggableSwitcher/context/useDraggableSwitcher.tsx +45 -45
- package/src/components/DraggableSwitcher/index.ts +2 -2
- package/src/components/DynamicInput/input/SelectInput.tsx +75 -75
- package/src/components/DynamicInput/input/assets/SelectInput.module.css +95 -95
- package/src/components/DynamicTable/AdvancedFilters.tsx +196 -196
- package/src/components/DynamicTable/ColumnSorter.tsx +185 -185
- package/src/components/DynamicTable/Pagination.tsx +115 -115
- package/src/components/DynamicTable/TableCell.tsx +38 -30
- package/src/components/DynamicTable/TableHeader.tsx +39 -34
- package/src/components/DynamicTable/TableauDynamique.module.css +79 -33
- package/src/components/DynamicTable/TableauDynamique.tsx +154 -154
- package/src/components/DynamicTable/filters/SelectFilter.tsx +69 -69
- package/src/components/DynamicTable/tools/tableTypes.ts +63 -63
- package/src/components/EntryControl/EntryControl.tsx +117 -117
- package/src/components/Grid/grid.css +285 -285
- package/src/components/MetricsPanel/MetricsPanel.tsx +37 -37
- package/src/components/MetricsPanel/renderers/CompactRenderer.tsx +1 -1
- package/src/components/NavItem/NavItem.tsx +58 -58
- package/src/components/PeriodRange/PeriodRange.module.css +158 -158
- package/src/components/PeriodRange/PeriodRange.tsx +130 -130
- package/src/components/SearchBar/SearchBar.css +40 -40
- package/src/components/TranslationKey/TranslationKey.css +272 -272
- package/src/components/TranslationKey/TranslationKey.tsx +8 -7
|
@@ -1,30 +1,38 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import styles from './TableauDynamique.module.css';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import type { TableColumn } from './tools/tableTypes';
|
|
5
|
+
import styles from './TableauDynamique.module.css';
|
|
6
|
+
|
|
7
|
+
interface TableCellProps<T = unknown> {
|
|
8
|
+
value: any;
|
|
9
|
+
row: T;
|
|
10
|
+
column: TableColumn<T>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const TableCell = <T,>({ value, row, column }: TableCellProps<T>) => {
|
|
14
|
+
const alignClass =
|
|
15
|
+
column.align === 'right'
|
|
16
|
+
? styles.alignRight
|
|
17
|
+
: column.align === 'left'
|
|
18
|
+
? styles.alignLeft
|
|
19
|
+
: styles.alignCenter;
|
|
20
|
+
|
|
21
|
+
const content =
|
|
22
|
+
typeof (column as any).render === 'function'
|
|
23
|
+
? (column as any).render(value, row)
|
|
24
|
+
: value === null || value === undefined
|
|
25
|
+
? ''
|
|
26
|
+
: String(value);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<td
|
|
30
|
+
className={`${styles.tableCell} ${alignClass}`}
|
|
31
|
+
style={{ width: column.width ?? undefined }}
|
|
32
|
+
>
|
|
33
|
+
<div className={styles.cellContent}>{content}</div>
|
|
34
|
+
</td>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default TableCell;
|
|
@@ -1,34 +1,39 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import type { TableColumn } from './tools/tableTypes';
|
|
4
|
-
import styles from './TableauDynamique.module.css';
|
|
5
|
-
|
|
6
|
-
interface TableHeaderProps<T = unknown> {
|
|
7
|
-
columns: TableColumn<T>[];
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const TableHeader = <T,>({ columns }: TableHeaderProps<T>) => {
|
|
11
|
-
return (
|
|
12
|
-
<thead className={styles.tableHeader}>
|
|
13
|
-
<tr>
|
|
14
|
-
{columns.map((column) =>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { TableColumn } from './tools/tableTypes';
|
|
4
|
+
import styles from './TableauDynamique.module.css';
|
|
5
|
+
|
|
6
|
+
interface TableHeaderProps<T = unknown> {
|
|
7
|
+
columns: TableColumn<T>[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const TableHeader = <T,>({ columns }: TableHeaderProps<T>) => {
|
|
11
|
+
return (
|
|
12
|
+
<thead className={styles.tableHeader}>
|
|
13
|
+
<tr>
|
|
14
|
+
{columns.map((column) => {
|
|
15
|
+
const alignClass =
|
|
16
|
+
column.align === 'right'
|
|
17
|
+
? styles.alignRight
|
|
18
|
+
: column.align === 'left'
|
|
19
|
+
? styles.alignLeft
|
|
20
|
+
: styles.alignCenter;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<th
|
|
24
|
+
key={column.id}
|
|
25
|
+
className={`${styles.headerCell} ${alignClass}`}
|
|
26
|
+
style={{ width: column.width ?? undefined }}
|
|
27
|
+
>
|
|
28
|
+
<div className={styles.headerContent}>
|
|
29
|
+
<span>{column.label}</span>
|
|
30
|
+
</div>
|
|
31
|
+
</th>
|
|
32
|
+
);
|
|
33
|
+
})}
|
|
34
|
+
</tr>
|
|
35
|
+
</thead>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default TableHeader;
|
|
@@ -1053,18 +1053,18 @@
|
|
|
1053
1053
|
|
|
1054
1054
|
@media (max-width: 480px) {
|
|
1055
1055
|
.tableControls {
|
|
1056
|
-
justify-content: center
|
|
1056
|
+
justify-content: center;
|
|
1057
1057
|
}
|
|
1058
1058
|
|
|
1059
1059
|
.controlsRight {
|
|
1060
|
-
justify-content: center
|
|
1060
|
+
justify-content: center;
|
|
1061
1061
|
width: auto;
|
|
1062
1062
|
gap: 8px;
|
|
1063
1063
|
}
|
|
1064
1064
|
|
|
1065
1065
|
.toggleFiltersButton,
|
|
1066
1066
|
.toggleSortButton {
|
|
1067
|
-
flex: 0 0 auto
|
|
1067
|
+
flex: 0 0 auto;
|
|
1068
1068
|
min-width: 0;
|
|
1069
1069
|
margin: 0 6px;
|
|
1070
1070
|
}
|
|
@@ -1079,20 +1079,20 @@
|
|
|
1079
1079
|
|
|
1080
1080
|
@media (max-width: 360px) {
|
|
1081
1081
|
.tableControls {
|
|
1082
|
-
justify-content: center
|
|
1082
|
+
justify-content: center;
|
|
1083
1083
|
padding-left: 6px;
|
|
1084
1084
|
padding-right: 6px;
|
|
1085
1085
|
}
|
|
1086
1086
|
|
|
1087
1087
|
.controlsRight {
|
|
1088
1088
|
width: 100%;
|
|
1089
|
-
justify-content: center
|
|
1089
|
+
justify-content: center;
|
|
1090
1090
|
gap: 6px;
|
|
1091
1091
|
}
|
|
1092
1092
|
|
|
1093
1093
|
.toggleFiltersButton,
|
|
1094
1094
|
.toggleSortButton {
|
|
1095
|
-
flex: 0 0 auto
|
|
1095
|
+
flex: 0 0 auto;
|
|
1096
1096
|
padding: 6px 10px;
|
|
1097
1097
|
font-size: 11px;
|
|
1098
1098
|
}
|
|
@@ -1137,29 +1137,10 @@
|
|
|
1137
1137
|
box-shadow: 0 10px 22px rgba(2, 6, 23, 0.12);
|
|
1138
1138
|
}
|
|
1139
1139
|
|
|
1140
|
-
.table thead,
|
|
1141
|
-
.headerCell {
|
|
1142
|
-
font-weight: 400 !important;
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
.table tbody td {
|
|
1146
|
-
font-weight: 400 !important;
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
.tableRow,
|
|
1150
|
-
.tableRow td,
|
|
1151
|
-
.headerCell,
|
|
1152
|
-
.pageSizeLabel,
|
|
1153
|
-
.pageSizeSuffix,
|
|
1154
|
-
.paginationInfo {
|
|
1155
|
-
font-weight: 400 !important;
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
1140
|
.toggleSortButton,
|
|
1159
1141
|
.toggleFiltersButton,
|
|
1160
1142
|
.buttonTextCompact {
|
|
1161
1143
|
font-size: var(--controls-text-size);
|
|
1162
|
-
font-weight: 400;
|
|
1163
1144
|
}
|
|
1164
1145
|
|
|
1165
1146
|
.sortDropdownHeader,
|
|
@@ -1213,14 +1194,6 @@
|
|
|
1213
1194
|
font-weight: 400;
|
|
1214
1195
|
}
|
|
1215
1196
|
|
|
1216
|
-
.uppercaseSmall,
|
|
1217
|
-
.sortDropdownHeader,
|
|
1218
|
-
.operatorFilterContainer,
|
|
1219
|
-
.rangeSeparator,
|
|
1220
|
-
.pageSizeLabel {
|
|
1221
|
-
font-weight: 400 !important;
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
1197
|
@media (max-width: 480px) {
|
|
1225
1198
|
.toggleFiltersButton,
|
|
1226
1199
|
.toggleSortButton,
|
|
@@ -1285,3 +1258,76 @@
|
|
|
1285
1258
|
font-weight: 400;
|
|
1286
1259
|
}
|
|
1287
1260
|
}
|
|
1261
|
+
|
|
1262
|
+
/* ====== Header : centré par défaut & poids normal ====== */
|
|
1263
|
+
.headerCell {
|
|
1264
|
+
padding: 12px 14px;
|
|
1265
|
+
cursor: pointer;
|
|
1266
|
+
letter-spacing: 0.2px;
|
|
1267
|
+
white-space: nowrap;
|
|
1268
|
+
font-size: 13px;
|
|
1269
|
+
font-weight: 400; /* plus de gras */
|
|
1270
|
+
vertical-align: middle;
|
|
1271
|
+
background: transparent;
|
|
1272
|
+
border-bottom: 1px solid rgba(230, 233, 239, 0.6);
|
|
1273
|
+
/* text-align will be set by alignment utility classes */
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
/* centre le contenu interne proprement */
|
|
1277
|
+
.headerContent {
|
|
1278
|
+
display: flex;
|
|
1279
|
+
align-items: center;
|
|
1280
|
+
justify-content: center; /* centre horizontalement */
|
|
1281
|
+
gap: 6px;
|
|
1282
|
+
font-size: 13px;
|
|
1283
|
+
font-weight: 400;
|
|
1284
|
+
line-height: 1;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
.tableCell {
|
|
1288
|
+
padding: 10px 14px;
|
|
1289
|
+
background: transparent;
|
|
1290
|
+
vertical-align: middle;
|
|
1291
|
+
font-size: 13px;
|
|
1292
|
+
min-width: 0;
|
|
1293
|
+
overflow: hidden;
|
|
1294
|
+
text-overflow: ellipsis;
|
|
1295
|
+
white-space: nowrap;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
.cellContent {
|
|
1299
|
+
display: flex;
|
|
1300
|
+
align-items: center;
|
|
1301
|
+
justify-content: center;
|
|
1302
|
+
min-height: 20px;
|
|
1303
|
+
gap: 6px;
|
|
1304
|
+
width: 100%;
|
|
1305
|
+
box-sizing: border-box;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
.alignLeft {
|
|
1309
|
+
text-align: left;
|
|
1310
|
+
}
|
|
1311
|
+
.alignCenter {
|
|
1312
|
+
text-align: center;
|
|
1313
|
+
}
|
|
1314
|
+
.alignRight {
|
|
1315
|
+
text-align: right;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
.tableCell.alignRight .cellContent,
|
|
1319
|
+
.headerCell.alignRight .headerContent {
|
|
1320
|
+
justify-content: flex-end;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
.tableCell.alignLeft .cellContent,
|
|
1324
|
+
.headerCell.alignLeft .headerContent {
|
|
1325
|
+
justify-content: flex-start;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
.emptyRow {
|
|
1329
|
+
text-align: center;
|
|
1330
|
+
padding: 24px;
|
|
1331
|
+
color: var(--muted);
|
|
1332
|
+
font-size: 13px;
|
|
1333
|
+
}
|
|
@@ -1,154 +1,154 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { useState, useRef, useEffect } from 'react';
|
|
4
|
-
import TableHeader from './TableHeader';
|
|
5
|
-
import TableBody from './TableBody';
|
|
6
|
-
import Pagination from './Pagination';
|
|
7
|
-
import ColumnSorter from './ColumnSorter';
|
|
8
|
-
import AdvancedFilters from './AdvancedFilters';
|
|
9
|
-
import type { TableauDynamiqueProps, SortConfig } from './tools/tableTypes';
|
|
10
|
-
import type { FilterConfig, AppliedFilter } from './tools/filterTypes';
|
|
11
|
-
import styles from './TableauDynamique.module.css';
|
|
12
|
-
import { useTableData } from './hooks/useTableData';
|
|
13
|
-
|
|
14
|
-
export interface EnhancedTableauDynamiqueProps<T> extends TableauDynamiqueProps<T> {
|
|
15
|
-
columnFilterable?: boolean;
|
|
16
|
-
filterConfig?: FilterConfig[];
|
|
17
|
-
externalFilters?: AppliedFilter[];
|
|
18
|
-
onFiltersChange?: (filters: AppliedFilter[]) => void;
|
|
19
|
-
onApplyFilters?: (filters: AppliedFilter[]) => void;
|
|
20
|
-
filtersControlled?: boolean;
|
|
21
|
-
debounceTimeout?: number;
|
|
22
|
-
getRowStyle?: (row: T) => React.CSSProperties;
|
|
23
|
-
background?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const TableauDynamique = <T,>({
|
|
27
|
-
columns = [],
|
|
28
|
-
data = [],
|
|
29
|
-
pagination = { pageSize: 10, currentPage: 1 },
|
|
30
|
-
onRowClick,
|
|
31
|
-
searchTerm = '',
|
|
32
|
-
className = '',
|
|
33
|
-
filterConfig = [],
|
|
34
|
-
externalFilters = [],
|
|
35
|
-
onFiltersChange,
|
|
36
|
-
onApplyFilters,
|
|
37
|
-
filtersControlled = false,
|
|
38
|
-
debounceTimeout = 300,
|
|
39
|
-
getRowStyle,
|
|
40
|
-
background,
|
|
41
|
-
onNewPage,
|
|
42
|
-
}: EnhancedTableauDynamiqueProps<T>) => {
|
|
43
|
-
const [sortConfig, setSortConfig] = useState<SortConfig>({ key: null, direction: 'asc' });
|
|
44
|
-
const [currentPage, setCurrentPage] = useState(pagination.currentPage ?? 1);
|
|
45
|
-
const [pageSize, setPageSize] = useState(pagination.pageSize ?? 10);
|
|
46
|
-
|
|
47
|
-
const [filtersExpanded, setFiltersExpanded] = useState(false);
|
|
48
|
-
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
49
|
-
const [internalFilters, setInternalFilters] = useState<AppliedFilter[]>([]);
|
|
50
|
-
const appliedFilters = filtersControlled ? externalFilters : internalFilters;
|
|
51
|
-
|
|
52
|
-
const { paginatedData, sortedData, totalPages, startIndex } = useTableData<T>(
|
|
53
|
-
data,
|
|
54
|
-
columns,
|
|
55
|
-
searchTerm,
|
|
56
|
-
sortConfig,
|
|
57
|
-
currentPage,
|
|
58
|
-
pageSize,
|
|
59
|
-
appliedFilters
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
const safeTotal = Math.max(1, Math.floor(totalPages));
|
|
64
|
-
if (currentPage > safeTotal) {
|
|
65
|
-
setCurrentPage(safeTotal);
|
|
66
|
-
}
|
|
67
|
-
if (currentPage < 1) {
|
|
68
|
-
setCurrentPage(1);
|
|
69
|
-
}
|
|
70
|
-
}, [totalPages]);
|
|
71
|
-
|
|
72
|
-
const handleFiltersChange = (filters: AppliedFilter[]) => {
|
|
73
|
-
if (!filtersControlled) setInternalFilters(filters);
|
|
74
|
-
onFiltersChange?.(filters);
|
|
75
|
-
setCurrentPage(1);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const handleApplyFilters = (filters: AppliedFilter[]) => {
|
|
79
|
-
if (!filtersControlled) setInternalFilters(filters);
|
|
80
|
-
onApplyFilters?.(filters);
|
|
81
|
-
setCurrentPage(1);
|
|
82
|
-
setFiltersExpanded(false);
|
|
83
|
-
if (containerRef.current)
|
|
84
|
-
containerRef.current.style.setProperty('--filters-panel-height', `0px`);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const handlePageChange = (page: number) => setCurrentPage(page);
|
|
88
|
-
const handlePageSizeChange = (size: number) => {
|
|
89
|
-
setPageSize(size);
|
|
90
|
-
setCurrentPage(1);
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const handlePanelHeightChange = (h: number) => {
|
|
94
|
-
if (containerRef.current) {
|
|
95
|
-
containerRef.current.style.setProperty('--filters-panel-height', `${Math.round(h)}px`);
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<div
|
|
101
|
-
ref={containerRef}
|
|
102
|
-
className={`${styles.container} ${className} ${filtersExpanded ? 'filters-open' : ''}`}
|
|
103
|
-
style={{ background }}
|
|
104
|
-
>
|
|
105
|
-
<div className={styles.tableControls}>
|
|
106
|
-
<div className={styles.controlsRight}>
|
|
107
|
-
<ColumnSorter columns={columns} sortConfig={sortConfig} onSort={setSortConfig} />
|
|
108
|
-
{filterConfig.length > 0 && (
|
|
109
|
-
<AdvancedFilters
|
|
110
|
-
filters={filterConfig}
|
|
111
|
-
externalFilters={externalFilters}
|
|
112
|
-
onFiltersChange={handleFiltersChange}
|
|
113
|
-
onApply={handleApplyFilters}
|
|
114
|
-
isControlled={filtersControlled}
|
|
115
|
-
debounceTimeout={debounceTimeout}
|
|
116
|
-
resultsCount={sortedData.length}
|
|
117
|
-
expanded={filtersExpanded}
|
|
118
|
-
onToggle={(v: boolean | ((prevState: boolean) => boolean)) => setFiltersExpanded(v)}
|
|
119
|
-
panelPlacement='top'
|
|
120
|
-
onPanelHeightChange={handlePanelHeightChange}
|
|
121
|
-
/>
|
|
122
|
-
)}
|
|
123
|
-
</div>
|
|
124
|
-
</div>
|
|
125
|
-
|
|
126
|
-
<div className={styles.topPagination}>
|
|
127
|
-
<Pagination
|
|
128
|
-
currentPage={currentPage}
|
|
129
|
-
totalPages={Math.max(1, totalPages)}
|
|
130
|
-
pageSize={pageSize}
|
|
131
|
-
totalItems={sortedData.length}
|
|
132
|
-
onPageChange={handlePageChange}
|
|
133
|
-
onPageSizeChange={handlePageSizeChange}
|
|
134
|
-
onNewPage={onNewPage}
|
|
135
|
-
/>
|
|
136
|
-
</div>
|
|
137
|
-
|
|
138
|
-
<div className={styles.tableWrapper} data-table-wrapper>
|
|
139
|
-
<table className={styles.table}>
|
|
140
|
-
<TableHeader columns={columns} />
|
|
141
|
-
<TableBody
|
|
142
|
-
data={paginatedData}
|
|
143
|
-
columns={columns}
|
|
144
|
-
onRowClick={onRowClick}
|
|
145
|
-
startIndex={startIndex}
|
|
146
|
-
getRowStyle={getRowStyle}
|
|
147
|
-
/>
|
|
148
|
-
</table>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
);
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
export default TableauDynamique;
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
4
|
+
import TableHeader from './TableHeader';
|
|
5
|
+
import TableBody from './TableBody';
|
|
6
|
+
import Pagination from './Pagination';
|
|
7
|
+
import ColumnSorter from './ColumnSorter';
|
|
8
|
+
import AdvancedFilters from './AdvancedFilters';
|
|
9
|
+
import type { TableauDynamiqueProps, SortConfig } from './tools/tableTypes';
|
|
10
|
+
import type { FilterConfig, AppliedFilter } from './tools/filterTypes';
|
|
11
|
+
import styles from './TableauDynamique.module.css';
|
|
12
|
+
import { useTableData } from './hooks/useTableData';
|
|
13
|
+
|
|
14
|
+
export interface EnhancedTableauDynamiqueProps<T> extends TableauDynamiqueProps<T> {
|
|
15
|
+
columnFilterable?: boolean;
|
|
16
|
+
filterConfig?: FilterConfig[];
|
|
17
|
+
externalFilters?: AppliedFilter[];
|
|
18
|
+
onFiltersChange?: (filters: AppliedFilter[]) => void;
|
|
19
|
+
onApplyFilters?: (filters: AppliedFilter[]) => void;
|
|
20
|
+
filtersControlled?: boolean;
|
|
21
|
+
debounceTimeout?: number;
|
|
22
|
+
getRowStyle?: (row: T) => React.CSSProperties;
|
|
23
|
+
background?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const TableauDynamique = <T,>({
|
|
27
|
+
columns = [],
|
|
28
|
+
data = [],
|
|
29
|
+
pagination = { pageSize: 10, currentPage: 1 },
|
|
30
|
+
onRowClick,
|
|
31
|
+
searchTerm = '',
|
|
32
|
+
className = '',
|
|
33
|
+
filterConfig = [],
|
|
34
|
+
externalFilters = [],
|
|
35
|
+
onFiltersChange,
|
|
36
|
+
onApplyFilters,
|
|
37
|
+
filtersControlled = false,
|
|
38
|
+
debounceTimeout = 300,
|
|
39
|
+
getRowStyle,
|
|
40
|
+
background,
|
|
41
|
+
onNewPage,
|
|
42
|
+
}: EnhancedTableauDynamiqueProps<T>) => {
|
|
43
|
+
const [sortConfig, setSortConfig] = useState<SortConfig>({ key: null, direction: 'asc' });
|
|
44
|
+
const [currentPage, setCurrentPage] = useState(pagination.currentPage ?? 1);
|
|
45
|
+
const [pageSize, setPageSize] = useState(pagination.pageSize ?? 10);
|
|
46
|
+
|
|
47
|
+
const [filtersExpanded, setFiltersExpanded] = useState(false);
|
|
48
|
+
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
49
|
+
const [internalFilters, setInternalFilters] = useState<AppliedFilter[]>([]);
|
|
50
|
+
const appliedFilters = filtersControlled ? externalFilters : internalFilters;
|
|
51
|
+
|
|
52
|
+
const { paginatedData, sortedData, totalPages, startIndex } = useTableData<T>(
|
|
53
|
+
data,
|
|
54
|
+
columns,
|
|
55
|
+
searchTerm,
|
|
56
|
+
sortConfig,
|
|
57
|
+
currentPage,
|
|
58
|
+
pageSize,
|
|
59
|
+
appliedFilters
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
const safeTotal = Math.max(1, Math.floor(totalPages));
|
|
64
|
+
if (currentPage > safeTotal) {
|
|
65
|
+
setCurrentPage(safeTotal);
|
|
66
|
+
}
|
|
67
|
+
if (currentPage < 1) {
|
|
68
|
+
setCurrentPage(1);
|
|
69
|
+
}
|
|
70
|
+
}, [totalPages]);
|
|
71
|
+
|
|
72
|
+
const handleFiltersChange = (filters: AppliedFilter[]) => {
|
|
73
|
+
if (!filtersControlled) setInternalFilters(filters);
|
|
74
|
+
onFiltersChange?.(filters);
|
|
75
|
+
setCurrentPage(1);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const handleApplyFilters = (filters: AppliedFilter[]) => {
|
|
79
|
+
if (!filtersControlled) setInternalFilters(filters);
|
|
80
|
+
onApplyFilters?.(filters);
|
|
81
|
+
setCurrentPage(1);
|
|
82
|
+
setFiltersExpanded(false);
|
|
83
|
+
if (containerRef.current)
|
|
84
|
+
containerRef.current.style.setProperty('--filters-panel-height', `0px`);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const handlePageChange = (page: number) => setCurrentPage(page);
|
|
88
|
+
const handlePageSizeChange = (size: number) => {
|
|
89
|
+
setPageSize(size);
|
|
90
|
+
setCurrentPage(1);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const handlePanelHeightChange = (h: number) => {
|
|
94
|
+
if (containerRef.current) {
|
|
95
|
+
containerRef.current.style.setProperty('--filters-panel-height', `${Math.round(h)}px`);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div
|
|
101
|
+
ref={containerRef}
|
|
102
|
+
className={`${styles.container} ${className} ${filtersExpanded ? 'filters-open' : ''}`}
|
|
103
|
+
style={{ background }}
|
|
104
|
+
>
|
|
105
|
+
<div className={styles.tableControls}>
|
|
106
|
+
<div className={styles.controlsRight}>
|
|
107
|
+
<ColumnSorter columns={columns} sortConfig={sortConfig} onSort={setSortConfig} />
|
|
108
|
+
{filterConfig.length > 0 && (
|
|
109
|
+
<AdvancedFilters
|
|
110
|
+
filters={filterConfig}
|
|
111
|
+
externalFilters={externalFilters}
|
|
112
|
+
onFiltersChange={handleFiltersChange}
|
|
113
|
+
onApply={handleApplyFilters}
|
|
114
|
+
isControlled={filtersControlled}
|
|
115
|
+
debounceTimeout={debounceTimeout}
|
|
116
|
+
resultsCount={sortedData.length}
|
|
117
|
+
expanded={filtersExpanded}
|
|
118
|
+
onToggle={(v: boolean | ((prevState: boolean) => boolean)) => setFiltersExpanded(v)}
|
|
119
|
+
panelPlacement='top'
|
|
120
|
+
onPanelHeightChange={handlePanelHeightChange}
|
|
121
|
+
/>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<div className={styles.topPagination}>
|
|
127
|
+
<Pagination
|
|
128
|
+
currentPage={currentPage}
|
|
129
|
+
totalPages={Math.max(1, totalPages)}
|
|
130
|
+
pageSize={pageSize}
|
|
131
|
+
totalItems={sortedData.length}
|
|
132
|
+
onPageChange={handlePageChange}
|
|
133
|
+
onPageSizeChange={handlePageSizeChange}
|
|
134
|
+
onNewPage={onNewPage}
|
|
135
|
+
/>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div className={styles.tableWrapper} data-table-wrapper>
|
|
139
|
+
<table className={styles.table}>
|
|
140
|
+
<TableHeader columns={columns} />
|
|
141
|
+
<TableBody
|
|
142
|
+
data={paginatedData}
|
|
143
|
+
columns={columns}
|
|
144
|
+
onRowClick={onRowClick}
|
|
145
|
+
startIndex={startIndex}
|
|
146
|
+
getRowStyle={getRowStyle}
|
|
147
|
+
/>
|
|
148
|
+
</table>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export default TableauDynamique;
|