kelt-ui-kit-react 1.8.2 → 1.8.3

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 (245) hide show
  1. package/.github/workflows/publish.yml +31 -0
  2. package/dist/_core/type/generiqueType.d.ts +4 -0
  3. package/dist/badge/Badge.d.ts +3 -1
  4. package/dist/button/button.interface.d.ts +1 -1
  5. package/dist/dataTable/DataTable.d.ts +17 -3
  6. package/dist/dataTable/dataTable.interface.d.ts +21 -3
  7. package/dist/dataTable/dataTableActions/DataTableAction.d.ts +2 -2
  8. package/dist/dataTable/dataTableColumn/DataTableColumn.d.ts +13 -7
  9. package/dist/dataTable/dataTableColumn/dataTableColumnFilter/DataTableColumnFilter.d.ts +10 -0
  10. package/dist/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDate/DataTableColumnFilterDate.d.ts +9 -0
  11. package/dist/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDateRange/dataTableColumnFilterDateRange.d.ts +9 -0
  12. package/dist/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterEnum/DataTableColumnFilterEnum.d.ts +13 -0
  13. package/dist/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterString/DataTableColumnFilterString.d.ts +9 -0
  14. package/dist/dataTable/dataTableColumn/dataTableColumnSort/DataTableColumnSort.d.ts +11 -0
  15. package/dist/dataTable/dataTableFilters/DataTableFilters.d.ts +7 -0
  16. package/dist/dataTable/dataTableLoading/DataTableLoading.d.ts +1 -3
  17. package/dist/dataTable/dataTableRow/DataTableRow.d.ts +5 -3
  18. package/dist/dataTable/dataTableRow/dataTableRowOverlay/DataTableRowOverlay.d.ts +12 -0
  19. package/dist/dataTable/dataTableRow/dataTableRowValue/DataTableRowValue.d.ts +11 -0
  20. package/dist/expands/expand/expand.d.ts +1 -1
  21. package/dist/form/form.enum.d.ts +1 -0
  22. package/dist/form/input/Input.d.ts +3 -0
  23. package/dist/form/inputNumber/InputNumber.d.ts +0 -2
  24. package/dist/icon/Icon.d.ts +1 -1
  25. package/dist/icon/icon.interface.d.ts +1 -0
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.js +3652 -2836
  28. package/dist/overlayPanel/OverlayPanel.d.ts +3 -1
  29. package/dist/select/Select.d.ts +4 -1
  30. package/dist/select/selectOption.interface.d.ts +1 -0
  31. package/dist/style.css +1 -1
  32. package/dist/tabs/Tabs.d.ts +2 -1
  33. package/kelt-ui-kit-react-1.6.9.tgz +0 -0
  34. package/package/README.md +46 -0
  35. package/package/index.html +20 -0
  36. package/package/package.json +56 -0
  37. package/package/public/favicon.ico +0 -0
  38. package/package/public/index.html +18 -0
  39. package/package/public/logo192.png +0 -0
  40. package/package/public/logo512.png +0 -0
  41. package/package/public/manifest.json +25 -0
  42. package/package/public/robots.txt +3 -0
  43. package/package/src/App.css +11 -0
  44. package/package/src/App.menu.tsx +249 -0
  45. package/package/src/App.routes.tsx +16 -0
  46. package/package/src/App.tsx +30 -0
  47. package/package/src/_core/hooks/HistoryProvider.tsx +25 -0
  48. package/package/src/_core/hooks/ImageChecker.tsx +26 -0
  49. package/package/src/_core/hooks/imageExist.tsx +9 -0
  50. package/package/src/_core/hooks/isEAN13.tsx +16 -0
  51. package/package/src/_core/hooks/objectToQueryParams.tsx +32 -0
  52. package/package/src/_core/hooks/useCurrency.tsx +18 -0
  53. package/package/src/_core/hooks/useDateutils.ts +64 -0
  54. package/package/src/_core/hooks/useIsMobile.ts +18 -0
  55. package/package/src/_core/hooks/useToken.tsx +19 -0
  56. package/package/src/_core/hooks/useValidate.tsx +18 -0
  57. package/package/src/_core/hooks/widgetSupport.tsx +39 -0
  58. package/package/src/action/Action.view.tsx +21 -0
  59. package/package/src/action/ButtonAction.tsx +32 -0
  60. package/package/src/action/action.css +20 -0
  61. package/package/src/badge/Badge.tsx +34 -0
  62. package/package/src/badge/Badge.view.tsx +15 -0
  63. package/package/src/badge/badge.css +55 -0
  64. package/package/src/button/Button.tsx +46 -0
  65. package/package/src/button/Button.view.tsx +85 -0
  66. package/package/src/button/button.css +53 -0
  67. package/package/src/button/button.interface.tsx +21 -0
  68. package/package/src/button/buttonActions/ButtonActions.tsx +104 -0
  69. package/package/src/button/buttonFile/ButtonFile.tsx +59 -0
  70. package/package/src/button/buttonFile/ButtonFile.view.tsx +70 -0
  71. package/package/src/button/buttonFilters/ButtonFilters.tsx +102 -0
  72. package/package/src/button/buttonFilters/buttonFilters.css +22 -0
  73. package/package/src/card/Card.tsx +125 -0
  74. package/package/src/card/Card.view.tsx +73 -0
  75. package/package/src/card/card.css +145 -0
  76. package/package/src/card/card.interface.tsx +9 -0
  77. package/package/src/card/cardAction/CardAction.tsx +135 -0
  78. package/package/src/card/cardAction/cardAction.css +10 -0
  79. package/package/src/card/cardAction.interface.tsx +10 -0
  80. package/package/src/card/hook/useCardInteractions.tsx +30 -0
  81. package/package/src/carousel/Carousel.css +44 -0
  82. package/package/src/carousel/Carousel.tsx +115 -0
  83. package/package/src/carousel/Carousel.view.tsx +13 -0
  84. package/package/src/damier/Damier.tsx +64 -0
  85. package/package/src/damier/Damier.view.tsx +31 -0
  86. package/package/src/damier/damier.css +50 -0
  87. package/package/src/damier/damierCell/DamierCell.tsx +18 -0
  88. package/package/src/damier/damierCell/damierCell.interface.tsx +5 -0
  89. package/package/src/dataTable/DataTable.tsx +259 -0
  90. package/package/src/dataTable/DataTable.view.tsx +62 -0
  91. package/package/src/dataTable/dataTable.css +30 -0
  92. package/package/src/dataTable/dataTable.interface.ts +30 -0
  93. package/package/src/dataTable/dataTableActions/DataTableAction.tsx +44 -0
  94. package/package/src/dataTable/dataTableColumn/DataTableColumn.tsx +27 -0
  95. package/package/src/dataTable/dataTableLoading/DataTableLoading.tsx +21 -0
  96. package/package/src/dataTable/dataTableRow/DataTableRow.tsx +54 -0
  97. package/package/src/datePicker/DatePicker.tsx +110 -0
  98. package/package/src/datePicker/DatePicker.view.tsx +9 -0
  99. package/package/src/datePicker/datePicker.css +77 -0
  100. package/package/src/expands/Expands.tsx +42 -0
  101. package/package/src/expands/Expands.view.tsx +90 -0
  102. package/package/src/expands/expand/expand.interface.tsx +8 -0
  103. package/package/src/expands/expand/expand.tsx +75 -0
  104. package/package/src/expands/expands.css +97 -0
  105. package/package/src/filAriane/FilAriane.tsx +57 -0
  106. package/package/src/filAriane/FilAriane.view.tsx +28 -0
  107. package/package/src/filAriane/filAriane.css +23 -0
  108. package/package/src/filAriane/filAriane.interface.tsx +6 -0
  109. package/package/src/form/Form.tsx +279 -0
  110. package/package/src/form/Form.view.tsx +59 -0
  111. package/package/src/form/form.css +62 -0
  112. package/package/src/form/form.enum.ts +13 -0
  113. package/package/src/form/form.interface.tsx +37 -0
  114. package/package/src/form/input/Input.tsx +106 -0
  115. package/package/src/form/input/input.css +26 -0
  116. package/package/src/form/inputNumber/InputNumber.tsx +132 -0
  117. package/package/src/form/inputNumber/InputNumber.view.tsx +15 -0
  118. package/package/src/form/inputPrice/InputPrice.tsx +42 -0
  119. package/package/src/form/inputPrice/InputPrice.view.tsx +15 -0
  120. package/package/src/form/textArea/TextArea.tsx +59 -0
  121. package/package/src/form/textArea/TextArea.view.tsx +34 -0
  122. package/package/src/form/textArea/textArea.css +9 -0
  123. package/package/src/grid/Grid.tsx +21 -0
  124. package/package/src/grid/Grid.view.tsx +24 -0
  125. package/package/src/grid/col/Col.tsx +15 -0
  126. package/package/src/grid/col/colStyled/ColStyled.tsx +41 -0
  127. package/package/src/grid/container/Container.tsx +8 -0
  128. package/package/src/grid/container/container.css +5 -0
  129. package/package/src/grid/grid.interface.tsx +7 -0
  130. package/package/src/grid/row/Row.tsx +12 -0
  131. package/package/src/grid/row/row.css +18 -0
  132. package/package/src/header/Header.tsx +62 -0
  133. package/package/src/header/Header.view.tsx +5 -0
  134. package/package/src/header/header.css +26 -0
  135. package/package/src/header/header.interface.tsx +10 -0
  136. package/package/src/home/Home.tsx +3 -0
  137. package/package/src/icon/Icon.tsx +6 -0
  138. package/package/src/icon/Icons.view.tsx +29 -0
  139. package/package/src/icon/icon.css +20 -0
  140. package/package/src/icon/icon.interface.tsx +6 -0
  141. package/package/src/icon/iconSize.enum.ts +7 -0
  142. package/package/src/index.css +502 -0
  143. package/package/src/index.ts +58 -0
  144. package/package/src/loader/Loader.tsx +37 -0
  145. package/package/src/loader/Loader.view.tsx +20 -0
  146. package/package/src/loader/loader.css +30 -0
  147. package/package/src/logo.svg +1 -0
  148. package/package/src/main.tsx +10 -0
  149. package/package/src/menus/Menus.tsx +42 -0
  150. package/package/src/menus/Menus.view.tsx +39 -0
  151. package/package/src/menus/menu/Menu.tsx +17 -0
  152. package/package/src/menus/menu/menu.interface.tsx +9 -0
  153. package/package/src/menus/menus.css +47 -0
  154. package/package/src/message/Message.tsx +30 -0
  155. package/package/src/message/Message.view.tsx +22 -0
  156. package/package/src/message/message.css +29 -0
  157. package/package/src/modal/Modal.tsx +59 -0
  158. package/package/src/modal/Modal.view.tsx +26 -0
  159. package/package/src/modal/modal.css +71 -0
  160. package/package/src/notFound/NotFound.tsx +3 -0
  161. package/package/src/overlayPanel/OverlayPanel.tsx +195 -0
  162. package/package/src/overlayPanel/OverlayPanel.view.tsx +25 -0
  163. package/package/src/overlayPanel/overlay.context.tsx +28 -0
  164. package/package/src/overlayPanel/overlayPanel.css +35 -0
  165. package/package/src/overlayPanel/overlayPanelStyled/OverlayPanelStyled.tsx +18 -0
  166. package/package/src/pdfViewer/PdfViewer.tsx +73 -0
  167. package/package/src/pdfViewer/PdfViewer.view.tsx +5 -0
  168. package/package/src/quantity/Quantity.tsx +103 -0
  169. package/package/src/quantity/Quantity.view.tsx +0 -0
  170. package/package/src/quantity/quantity.css +26 -0
  171. package/package/src/search/Search.tsx +161 -0
  172. package/package/src/search/Search.view.tsx +14 -0
  173. package/package/src/search/search.css +59 -0
  174. package/package/src/select/Select.tsx +103 -0
  175. package/package/src/select/Select.view.tsx +71 -0
  176. package/package/src/select/select.css +69 -0
  177. package/package/src/select/selectOption.interface.ts +4 -0
  178. package/package/src/sidebar/Sidebar.tsx +208 -0
  179. package/package/src/sidebar/Sidebar.view.tsx +17 -0
  180. package/package/src/sidebar/sidebar.css +104 -0
  181. package/package/src/sidebarData/SidebarData.tsx +19 -0
  182. package/package/src/sidebarData/SidebarData.view.tsx +19 -0
  183. package/package/src/sidebarData/sidebarData.css +27 -0
  184. package/package/src/tabs/Tabs.tsx +90 -0
  185. package/package/src/tabs/Tabs.view.tsx +20 -0
  186. package/package/src/tabs/tabs.css +24 -0
  187. package/package/src/toaster/Toaster.tsx +55 -0
  188. package/package/src/toaster/Toaster.view.tsx +24 -0
  189. package/package/src/toaster/store/useToasterStore.tsx +41 -0
  190. package/package/src/toaster/toaster.css +87 -0
  191. package/package/tsconfig.json +28 -0
  192. package/package/vite.config.ts +20 -0
  193. package/package/vite.config.ts.timestamp-1733262892554-a13dfef6e8a29.mjs +24 -0
  194. package/package/vite.config.ts.timestamp-1757847121745-e5a5bf54b81b.mjs +28 -0
  195. package/package/vite.config.ts.timestamp-1759742478362-4ac5c5a7db34b.mjs +28 -0
  196. package/package.json +1 -1
  197. package/src/_core/type/generiqueType.tsx +4 -0
  198. package/src/badge/Badge.tsx +9 -1
  199. package/src/badge/badge.css +4 -0
  200. package/src/button/Button.tsx +1 -1
  201. package/src/button/button.interface.tsx +1 -1
  202. package/src/card/card.css +23 -3
  203. package/src/card/cardAction/CardAction.tsx +4 -4
  204. package/src/dataTable/DataTable.tsx +670 -45
  205. package/src/dataTable/DataTable.view.tsx +47 -14
  206. package/src/dataTable/dataTable.css +153 -5
  207. package/src/dataTable/dataTable.interface.ts +21 -3
  208. package/src/dataTable/dataTableActions/DataTableAction.tsx +2 -2
  209. package/src/dataTable/dataTableColumn/DataTableColumn.tsx +64 -14
  210. package/src/dataTable/dataTableColumn/dataTableColumnFilter/DataTableColumnFilter.tsx +36 -0
  211. package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilter.css +0 -0
  212. package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDate/DataTableColumnFilterDate.tsx +39 -0
  213. package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDateRange/dataTableColumnFilterDateRange.tsx +80 -0
  214. package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterEnum/DataTableColumnFilterEnum.tsx +89 -0
  215. package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterString/DataTableColumnFilterString.tsx +37 -0
  216. package/src/dataTable/dataTableColumn/dataTableColumnSort/DataTableColumnSort.tsx +42 -0
  217. package/src/dataTable/dataTableFilters/DataTableFilters.tsx +91 -0
  218. package/src/dataTable/dataTableFilters/dataTableFilters.css +16 -0
  219. package/src/dataTable/dataTableLoading/DataTableLoading.tsx +4 -15
  220. package/src/dataTable/dataTableRow/DataTableRow.tsx +60 -33
  221. package/src/dataTable/dataTableRow/dataTableRowOverlay/DataTableRowOverlay.tsx +80 -0
  222. package/src/dataTable/dataTableRow/dataTableRowValue/DataTableRowValue.tsx +123 -0
  223. package/src/expands/Expands.tsx +2 -16
  224. package/src/expands/expand/expand.tsx +6 -18
  225. package/src/form/Form.tsx +13 -12
  226. package/src/form/form.css +13 -2
  227. package/src/form/form.enum.ts +1 -0
  228. package/src/form/input/Input.tsx +61 -7
  229. package/src/form/input/input.css +23 -1
  230. package/src/form/inputNumber/InputNumber.tsx +6 -11
  231. package/src/form/inputPrice/InputPrice.tsx +2 -4
  232. package/src/icon/Icon.tsx +6 -2
  233. package/src/icon/icon.interface.tsx +1 -0
  234. package/src/index.ts +2 -0
  235. package/src/overlayPanel/OverlayPanel.tsx +23 -10
  236. package/src/overlayPanel/overlay.context.tsx +6 -4
  237. package/src/overlayPanel/overlayPanel.css +10 -1
  238. package/src/select/Select.tsx +134 -38
  239. package/src/select/select.css +65 -14
  240. package/src/select/selectOption.interface.ts +1 -0
  241. package/src/sidebar/Sidebar.tsx +9 -80
  242. package/src/sidebar/sidebar.css +1 -18
  243. package/src/tabs/Tabs.tsx +19 -1
  244. package/vite.config.ts.timestamp-1757847121745-e5a5bf54b81b.mjs +28 -28
  245. package/vite.config.ts.timestamp-1759742478362-4ac5c5a7db34b.mjs +28 -28
@@ -1,16 +1,62 @@
1
- import { useCallback, useEffect, useRef, useState } from "react";
1
+ import {
2
+ useCallback,
3
+ useDeferredValue,
4
+ useEffect,
5
+ useLayoutEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from "react";
2
10
  import { TypeInputEnum } from "../form/form.enum";
3
11
  import { Input } from "../form/input/Input";
4
12
  import { Loader } from "../loader/Loader";
13
+ import { Select } from "../select/Select";
5
14
  import "./dataTable.css";
6
15
  import {
7
- dataTableActionsInterface,
8
- dataTableColumnsInterface,
16
+ DataTableActionsInterface,
17
+ DataTableColumnsInterface,
9
18
  } from "./dataTable.interface";
10
19
  import { DataTableAction } from "./dataTableActions/DataTableAction";
11
20
  import { DataTableColumn } from "./dataTableColumn/DataTableColumn";
21
+ import { DataTableColumnFilter } from "./dataTableColumn/dataTableColumnFilter/DataTableColumnFilter";
22
+ import { DataTableColumnFilterDateRange } from "./dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDateRange/dataTableColumnFilterDateRange";
23
+ import { DataTableColumnSort } from "./dataTableColumn/dataTableColumnSort/DataTableColumnSort";
24
+ import { DataTableFilters } from "./dataTableFilters/DataTableFilters";
12
25
  import { DataTableLoading } from "./dataTableLoading/DataTableLoading";
13
26
  import { DataTableRow } from "./dataTableRow/DataTableRow";
27
+ export type DataTableDateRangeFilterValue = {
28
+ startDate: string;
29
+ endDate: string;
30
+ };
31
+
32
+ export type FilterValue =
33
+ | string
34
+ | number
35
+ | boolean
36
+ | string[]
37
+ | DataTableDateRangeFilterValue
38
+ | null;
39
+ export type DataTableFilterState = Record<string, FilterValue>;
40
+ export type DataTableSortDirection = "asc" | "desc";
41
+ export type DataTableSortState = {
42
+ key: string;
43
+ direction: DataTableSortDirection;
44
+ };
45
+ const DEFAULT_FILTER_COLUMN_WIDTH = 100;
46
+ const SELECTION_COLUMN_WIDTH = 40;
47
+ const DEFAULT_FILTER_ROW_STICKY_HEIGHT = 44;
48
+
49
+ const isDateRangeFilterValue = (
50
+ value: FilterValue | undefined,
51
+ ): value is DataTableDateRangeFilterValue => {
52
+ return (
53
+ typeof value === "object" &&
54
+ value !== null &&
55
+ "startDate" in value &&
56
+ "endDate" in value
57
+ );
58
+ };
59
+
14
60
  export type DataTableProps<T> = {
15
61
  id: string;
16
62
  name?: string;
@@ -19,11 +65,14 @@ export type DataTableProps<T> = {
19
65
  element?: JSX.Element;
20
66
  data: T[];
21
67
  emptyMessage?: string;
22
- actions?: dataTableActionsInterface<T>[];
23
- columns: dataTableColumnsInterface<T>[];
68
+ actions?: DataTableActionsInterface<T>[];
69
+ columns: DataTableColumnsInterface<T>[];
70
+ onFiltersChange?: (next: DataTableFilterState) => void;
71
+ onSortChange?: (next: DataTableSortState | null) => void;
24
72
  loading?: boolean;
25
73
  onRowClick?: (row: any) => void;
26
74
  onColumnClick?: (column: any) => void;
75
+ onEditColumnByOverlay?: (row: T) => void;
27
76
  onLoadMore?: () => void;
28
77
  hasMore?: boolean; // pour savoir s'il reste des données à charger
29
78
  onSelectionChange?: (selectedRows: T[]) => void;
@@ -45,25 +94,166 @@ export function DataTable<T>(props: DataTableProps<T>) {
45
94
  onRowClick,
46
95
  onColumnClick,
47
96
  onLoadMore,
97
+ onEditColumnByOverlay,
48
98
  emptyMessage,
49
99
  hasMore,
50
100
  onSelectionChange,
101
+ onFiltersChange,
102
+ onSortChange,
51
103
  hideToggleAll = false,
52
104
  limitSelect = undefined,
53
105
  getRowId,
54
106
  } = props;
107
+ const [currentFilters, setCurrentFilters] = useState<DataTableFilterState>(
108
+ {},
109
+ );
110
+ const [visibleFilterColumns, setVisibleFilterColumns] = useState<Set<string>>(
111
+ new Set(),
112
+ );
113
+ const [focusFilterKey, setFocusFilterKey] = useState<string | null>(null);
114
+ const [currentSort, setCurrentSort] = useState<DataTableSortState | null>(
115
+ null,
116
+ );
117
+
118
+ const getFilterKey = useCallback(
119
+ (column: DataTableColumnsInterface<T>) => column.name ?? column.id,
120
+ [],
121
+ );
122
+
123
+ const toggleColumnFilterVisibility = useCallback(
124
+ (column: DataTableColumnsInterface<T>) => {
125
+ if (!column.filterable) return;
126
+ const key = getFilterKey(column);
127
+ setVisibleFilterColumns((prev) => {
128
+ const next = new Set(prev);
129
+ if (next.has(key)) {
130
+ next.delete(key);
131
+ setFocusFilterKey(null);
132
+ } else {
133
+ next.add(key);
134
+ setFocusFilterKey(key);
135
+ }
136
+ return next;
137
+ });
138
+ },
139
+ [getFilterKey],
140
+ );
141
+
142
+ const toggleColumnSort = useCallback(
143
+ (column: DataTableColumnsInterface<T>) => {
144
+ if (!column.sortable) return;
145
+ const key = getFilterKey(column);
146
+
147
+ setCurrentSort((prev) => {
148
+ if (!prev || prev.key !== key) {
149
+ return { key, direction: "asc" };
150
+ }
151
+
152
+ if (prev.direction === "asc") {
153
+ return { key, direction: "desc" };
154
+ }
155
+
156
+ return null;
157
+ });
158
+ },
159
+ [getFilterKey],
160
+ );
161
+
162
+ const updateFilterValue = useCallback((key: string, value: string) => {
163
+ setCurrentFilters((prev) => {
164
+ if (!value) {
165
+ if (!(key in prev)) {
166
+ return prev;
167
+ }
168
+ const { [key]: _removed, ...rest } = prev;
169
+ return rest;
170
+ }
171
+
172
+ if (prev[key] === value) {
173
+ return prev;
174
+ }
175
+
176
+ return { ...prev, [key]: value };
177
+ });
178
+ }, []);
179
+
180
+ const updateDateRangeFilterValue = useCallback(
181
+ (key: string, startDate: string, endDate: string) => {
182
+ setCurrentFilters((prev) => {
183
+ if (!startDate && !endDate) {
184
+ if (!(key in prev)) {
185
+ return prev;
186
+ }
187
+ const { [key]: _removed, ...rest } = prev;
188
+ return rest;
189
+ }
190
+
191
+ const previousValue = prev[key];
192
+ if (
193
+ isDateRangeFilterValue(previousValue) &&
194
+ previousValue.startDate === startDate &&
195
+ previousValue.endDate === endDate
196
+ ) {
197
+ return prev;
198
+ }
55
199
 
200
+ return {
201
+ ...prev,
202
+ [key]: {
203
+ startDate,
204
+ endDate,
205
+ },
206
+ };
207
+ });
208
+ },
209
+ [],
210
+ );
211
+
212
+ const onFiltersChangeRef = useRef(onFiltersChange);
213
+ const deferredFilters = useDeferredValue(currentFilters);
214
+ useEffect(() => {
215
+ onFiltersChangeRef.current = onFiltersChange;
216
+ }, [onFiltersChange]);
217
+
218
+ useEffect(() => {
219
+ if (onFiltersChangeRef.current) {
220
+ onFiltersChangeRef.current(deferredFilters);
221
+ }
222
+ }, [deferredFilters]);
223
+ const onSortChangeRef = useRef(onSortChange);
224
+ useEffect(() => {
225
+ onSortChangeRef.current = onSortChange;
226
+ }, [onSortChange]);
227
+
228
+ useEffect(() => {
229
+ if (onSortChangeRef.current) {
230
+ onSortChangeRef.current(currentSort);
231
+ }
232
+ }, [currentSort]);
233
+
234
+ const getColumnValue = useCallback(
235
+ (row: T, column: DataTableColumnsInterface<T>) => {
236
+ return (row as any)[column.id];
237
+ },
238
+ [],
239
+ );
56
240
  const loaderRef = useRef<HTMLDivElement | null>(null);
57
241
  const [selectedRowIds, setSelectedRowIds] = useState<Set<string | number>>(
58
- new Set()
242
+ new Set(),
59
243
  );
244
+ const filterRowRef = useRef<HTMLTableRowElement | null>(null);
245
+ const headerRowRef = useRef<HTMLTableRowElement | null>(null);
246
+ const [filterRowHeight, setFilterRowHeight] = useState(
247
+ DEFAULT_FILTER_ROW_STICKY_HEIGHT,
248
+ );
249
+ const [headerRowHeight, setHeaderRowHeight] = useState(0);
60
250
  const toggleSelectAll = useCallback(() => {
61
251
  if (selectedRowIds.size === data.length) {
62
252
  setSelectedRowIds(new Set());
63
253
  onSelectionChange?.([]);
64
254
  } else {
65
255
  let allIds = data.map((d) =>
66
- getRowId ? getRowId(d) : (d as any).id ?? d
256
+ getRowId ? getRowId(d) : ((d as any).id ?? d),
67
257
  );
68
258
 
69
259
  if (limitSelect && allIds.length > limitSelect) {
@@ -74,8 +264,8 @@ export function DataTable<T>(props: DataTableProps<T>) {
74
264
  setSelectedRowIds(newSelection);
75
265
  onSelectionChange?.(
76
266
  data.filter((d) =>
77
- newSelection.has(getRowId ? getRowId(d) : (d as any).id ?? d)
78
- )
267
+ newSelection.has(getRowId ? getRowId(d) : ((d as any).id ?? d)),
268
+ ),
79
269
  );
80
270
  }
81
271
  }, [data, onSelectionChange, limitSelect, getRowId, selectedRowIds.size]);
@@ -87,12 +277,12 @@ export function DataTable<T>(props: DataTableProps<T>) {
87
277
  onRowClick?.(row);
88
278
  }
89
279
  },
90
- [onRowClick]
280
+ [onRowClick],
91
281
  );
92
282
  const toggleRow = useCallback(
93
283
  (e: React.ChangeEvent<HTMLInputElement>, row: T) => {
94
284
  e.stopPropagation();
95
- const rowId = getRowId ? getRowId(row) : (row as any).id ?? row;
285
+ const rowId = getRowId ? getRowId(row) : ((row as any).id ?? row);
96
286
  const newSelection = new Set(selectedRowIds);
97
287
 
98
288
  if (newSelection.has(rowId)) {
@@ -107,11 +297,11 @@ export function DataTable<T>(props: DataTableProps<T>) {
107
297
 
108
298
  setSelectedRowIds(newSelection);
109
299
  const selectedItems = data.filter((d) =>
110
- newSelection.has(getRowId ? getRowId(d) : (d as any).id ?? d)
300
+ newSelection.has(getRowId ? getRowId(d) : ((d as any).id ?? d)),
111
301
  );
112
302
  onSelectionChange?.(selectedItems);
113
303
  },
114
- [data, getRowId, limitSelect, onSelectionChange, selectedRowIds]
304
+ [data, getRowId, limitSelect, onSelectionChange, selectedRowIds],
115
305
  );
116
306
  useEffect(() => {
117
307
  if (!onLoadMore || !hasMore) return;
@@ -126,7 +316,7 @@ export function DataTable<T>(props: DataTableProps<T>) {
126
316
  root: null,
127
317
  rootMargin: "0px",
128
318
  threshold: 1.0,
129
- }
319
+ },
130
320
  );
131
321
 
132
322
  if (loaderRef.current) {
@@ -139,17 +329,357 @@ export function DataTable<T>(props: DataTableProps<T>) {
139
329
  }
140
330
  };
141
331
  }, [onLoadMore, hasMore]);
332
+ const hasNestedColumns = columns.some((col) => col.children?.length);
333
+
334
+ const flatColumns = columns.flatMap((col) =>
335
+ col.children?.length ? col.children : [col],
336
+ );
337
+
338
+ const stickyColumnsLeftByKey = useMemo(() => {
339
+ const offsets = new Map<string, number>();
340
+ const hasSelectionColumn = !!onSelectionChange;
341
+ let currentLeft = hasSelectionColumn ? SELECTION_COLUMN_WIDTH : 0;
342
+
343
+ flatColumns.forEach((column) => {
344
+ const key = getFilterKey(column);
345
+ const width =
346
+ column.width ??
347
+ column.minWidth ??
348
+ column.maxWidth ??
349
+ DEFAULT_FILTER_COLUMN_WIDTH;
350
+
351
+ if (column.sticky) {
352
+ offsets.set(key, currentLeft);
353
+ currentLeft += width;
354
+ }
355
+ });
356
+
357
+ return offsets;
358
+ }, [flatColumns, getFilterKey, onSelectionChange]);
359
+
360
+ const hasStickyColumns = stickyColumnsLeftByKey.size > 0;
361
+
362
+ const sortedData = useMemo(() => {
363
+ if (!currentSort) {
364
+ return data;
365
+ }
366
+
367
+ const sortedColumn = flatColumns.find(
368
+ (column) => getFilterKey(column) === currentSort.key,
369
+ );
370
+
371
+ if (!sortedColumn) {
372
+ return data;
373
+ }
374
+
375
+ const factor = currentSort.direction === "asc" ? 1 : -1;
376
+ const isDateColumn = sortedColumn.type === TypeInputEnum.DATE;
377
+
378
+ const normalizeDate = (value: unknown): number | null => {
379
+ if (value instanceof Date) {
380
+ return value.getTime();
381
+ }
382
+
383
+ if (typeof value === "string" || typeof value === "number") {
384
+ const timestamp = new Date(value).getTime();
385
+ return Number.isNaN(timestamp) ? null : timestamp;
386
+ }
387
+
388
+ return null;
389
+ };
390
+
391
+ const compareValues = (left: unknown, right: unknown): number => {
392
+ if (left == null && right == null) {
393
+ return 0;
394
+ }
395
+
396
+ if (left == null) {
397
+ return 1;
398
+ }
399
+
400
+ if (right == null) {
401
+ return -1;
402
+ }
403
+
404
+ if (typeof left === "number" && typeof right === "number") {
405
+ return left - right;
406
+ }
407
+
408
+ if (typeof left === "boolean" && typeof right === "boolean") {
409
+ return Number(left) - Number(right);
410
+ }
411
+
412
+ if (isDateColumn) {
413
+ const leftDate = normalizeDate(left);
414
+ const rightDate = normalizeDate(right);
415
+
416
+ if (leftDate !== null && rightDate !== null) {
417
+ return leftDate - rightDate;
418
+ }
419
+ }
420
+
421
+ return String(left).localeCompare(String(right), undefined, {
422
+ numeric: true,
423
+ sensitivity: "base",
424
+ });
425
+ };
426
+
427
+ return [...data].sort((leftRow, rightRow) => {
428
+ const leftValue = getColumnValue(leftRow, sortedColumn);
429
+ const rightValue = getColumnValue(rightRow, sortedColumn);
430
+ return factor * compareValues(leftValue, rightValue);
431
+ });
432
+ }, [currentSort, data, flatColumns, getColumnValue, getFilterKey]);
433
+
434
+ const hasVisibleFilterInputs = visibleFilterColumns.size > 0;
435
+ const headerStickyTop = hasVisibleFilterInputs ? filterRowHeight : 0;
436
+ const groupedHeaderStickyTop = headerStickyTop + headerRowHeight;
437
+
438
+ useLayoutEffect(() => {
439
+ if (!hasVisibleFilterInputs) {
440
+ setFilterRowHeight(DEFAULT_FILTER_ROW_STICKY_HEIGHT);
441
+ return;
442
+ }
443
+
444
+ const updateFilterRowHeight = () => {
445
+ const measuredHeight =
446
+ filterRowRef.current?.getBoundingClientRect().height ??
447
+ DEFAULT_FILTER_ROW_STICKY_HEIGHT;
448
+ setFilterRowHeight((prev) =>
449
+ Math.abs(prev - measuredHeight) < 0.1 ? prev : measuredHeight,
450
+ );
451
+ };
452
+
453
+ updateFilterRowHeight();
454
+
455
+ if (typeof ResizeObserver !== "undefined" && filterRowRef.current) {
456
+ const resizeObserver = new ResizeObserver(updateFilterRowHeight);
457
+ resizeObserver.observe(filterRowRef.current);
458
+ return () => resizeObserver.disconnect();
459
+ }
460
+
461
+ window.addEventListener("resize", updateFilterRowHeight);
462
+ return () => {
463
+ window.removeEventListener("resize", updateFilterRowHeight);
464
+ };
465
+ }, [hasVisibleFilterInputs]);
466
+
467
+ useLayoutEffect(() => {
468
+ const updateHeaderRowHeight = () => {
469
+ const measuredHeight =
470
+ headerRowRef.current?.getBoundingClientRect().height ?? 0;
471
+ setHeaderRowHeight((prev) =>
472
+ Math.abs(prev - measuredHeight) < 0.1 ? prev : measuredHeight,
473
+ );
474
+ };
475
+
476
+ updateHeaderRowHeight();
477
+
478
+ if (typeof ResizeObserver !== "undefined" && headerRowRef.current) {
479
+ const resizeObserver = new ResizeObserver(updateHeaderRowHeight);
480
+ resizeObserver.observe(headerRowRef.current);
481
+ return () => resizeObserver.disconnect();
482
+ }
483
+
484
+ window.addEventListener("resize", updateHeaderRowHeight);
485
+ return () => {
486
+ window.removeEventListener("resize", updateHeaderRowHeight);
487
+ };
488
+ }, [columns.length, hasVisibleFilterInputs]);
489
+
490
+ const filterRowCells = useMemo(
491
+ () =>
492
+ flatColumns.map((column, idx) => {
493
+ const key = getFilterKey(column);
494
+ const isVisible = visibleFilterColumns.has(key);
495
+ const currentFilterValue = currentFilters[key];
496
+ const currentValue = isDateRangeFilterValue(currentFilterValue)
497
+ ? ""
498
+ : String(currentFilterValue ?? "");
499
+ const currentDateRangeValue = isDateRangeFilterValue(currentFilterValue)
500
+ ? currentFilterValue
501
+ : { startDate: "", endDate: "" };
502
+ const shouldAutoFocus = focusFilterKey === key && isVisible;
503
+ const columnWidth = column.width ?? DEFAULT_FILTER_COLUMN_WIDTH;
504
+ const stickyLeft = stickyColumnsLeftByKey.get(key);
505
+ const isSticky = stickyLeft !== undefined;
506
+
507
+ const onTextDateChange = (e: React.ChangeEvent<HTMLInputElement>) =>
508
+ updateFilterValue(key, e.target.value);
509
+
510
+ const onEnumSelectMultipleChange = (values: string[]) =>
511
+ updateFilterValue(key, values.join(","));
512
+
513
+ return (
514
+ <th
515
+ key={`filter-${column.id}-${idx}`}
516
+ className={`filter-row-cell ${
517
+ isSticky ? "data-table-sticky-cell" : ""
518
+ }`.trim()}
519
+ style={{
520
+ width: columnWidth,
521
+ minWidth: columnWidth,
522
+ maxWidth: columnWidth,
523
+ position: "sticky",
524
+ top: 0,
525
+ zIndex: isSticky ? 14 : 13,
526
+ backgroundColor: "rgb(249, 251, 252)",
527
+ ...(isSticky
528
+ ? {
529
+ left: stickyLeft,
530
+ }
531
+ : {}),
532
+ }}
533
+ >
534
+ {isVisible && column.filterable && (
535
+ <div className="data-table-filter-input-wrapper">
536
+ {column.filterType === TypeInputEnum.DATE ? (
537
+ <Input
538
+ id={`${id}-filter-date-${column.id}`}
539
+ name={`${id}-filter-date-${column.id}`}
540
+ type={TypeInputEnum.DATE}
541
+ value={currentValue}
542
+ autoFocus={shouldAutoFocus}
543
+ disabledDebounce={true}
544
+ showClearButton
545
+ onChange={onTextDateChange}
546
+ onClick={(e) => e.stopPropagation()}
547
+ className="data-table-filter-input"
548
+ />
549
+ ) : column.filterType === TypeInputEnum.DATERANGE ? (
550
+ <DataTableColumnFilterDateRange
551
+ id={`${id}-filter-date-range-${column.id}`}
552
+ name={`${id}-filter-date-range-${column.id}`}
553
+ currentStartValue={currentDateRangeValue.startDate}
554
+ currentEndValue={currentDateRangeValue.endDate}
555
+ onChange={(startDate, endDate) =>
556
+ updateDateRangeFilterValue(key, startDate, endDate)
557
+ }
558
+ />
559
+ ) : column.filterType === TypeInputEnum.RADIO ||
560
+ column.filterType === TypeInputEnum.CHECKBOX ? (
561
+ <div onClick={(e) => e.stopPropagation()}>
562
+ <Select
563
+ id={`${id}-filter-${column.id}`}
564
+ options={column.filterDataEnum ?? []}
565
+ multiple={true}
566
+ showOptions={shouldAutoFocus}
567
+ value={currentValue.split(",").filter(Boolean)}
568
+ onChangeMultiple={onEnumSelectMultipleChange}
569
+ placeholder={`Filtrer ${column.label}`}
570
+ className="data-table-filter-select"
571
+ />
572
+ </div>
573
+ ) : (
574
+ <Input
575
+ id={`${id}-filter-text-${column.id}`}
576
+ name={`${id}-filter-text-${column.id}`}
577
+ type={TypeInputEnum.TEXT}
578
+ value={currentValue}
579
+ showClearButton
580
+ autoFocus={shouldAutoFocus}
581
+ disabledDebounce={true}
582
+ onChange={onTextDateChange}
583
+ onClick={(e) => e.stopPropagation()}
584
+ className="data-table-filter-input"
585
+ placeholder={`Filtrer ${column.label}`}
586
+ />
587
+ )}
588
+ </div>
589
+ )}
590
+ </th>
591
+ );
592
+ }),
593
+ [
594
+ currentFilters,
595
+ flatColumns,
596
+ focusFilterKey,
597
+ getFilterKey,
598
+ id,
599
+ stickyColumnsLeftByKey,
600
+ updateDateRangeFilterValue,
601
+ updateFilterValue,
602
+ visibleFilterColumns,
603
+ ],
604
+ );
142
605
  return (
143
606
  <div className="data-table-container">
144
607
  {name && <h2 className="mb-2">{name}</h2>}
145
608
  {element}
609
+ {currentFilters && (
610
+ <DataTableFilters
611
+ currentFilters={currentFilters}
612
+ setCurrentFilters={setCurrentFilters}
613
+ />
614
+ )}
146
615
  <table className={`data-table ${className ? className : ""}`} id={id}>
616
+ <colgroup>
617
+ {onSelectionChange && <col style={{ width: 20 }} />}
618
+ {flatColumns.map((column, idx) => (
619
+ <col
620
+ key={`col-${column.id}-${idx}`}
621
+ style={{
622
+ width: column.width,
623
+ minWidth: column.minWidth,
624
+ maxWidth: column.maxWidth,
625
+ }}
626
+ />
627
+ ))}
628
+ {actions.length > 0 && <col style={{ width: 200 }} />}
629
+ </colgroup>
147
630
  <thead>
148
- <tr>
631
+ {hasVisibleFilterInputs && (
632
+ <tr className="data-table-filter-row" ref={filterRowRef}>
633
+ {onSelectionChange && (
634
+ <th
635
+ className={`select-cell ${
636
+ hasStickyColumns ? "data-table-sticky-cell" : ""
637
+ }`}
638
+ style={{
639
+ position: "sticky",
640
+ top: 0,
641
+ zIndex: hasStickyColumns ? 14 : 13,
642
+ backgroundColor: "#f9fbfc",
643
+ ...(hasStickyColumns
644
+ ? {
645
+ left: 0,
646
+ }
647
+ : {}),
648
+ }}
649
+ ></th>
650
+ )}
651
+ {filterRowCells}
652
+ {actions.length > 0 && (
653
+ <th
654
+ style={{
655
+ position: "sticky",
656
+ top: 0,
657
+ zIndex: 13,
658
+ backgroundColor: "#f9fbfc",
659
+ }}
660
+ ></th>
661
+ )}
662
+ </tr>
663
+ )}
664
+ <tr className="data-table-header-row" ref={headerRowRef}>
149
665
  {onSelectionChange && (
150
666
  <th
151
- className={`select-cell ${hideToggleAll ? "hidden" : ""}`}
152
- style={{ width: "20px", textAlign: "center" }}
667
+ className={`select-cell ${hideToggleAll ? "hidden" : ""} ${
668
+ hasStickyColumns ? "data-table-sticky-cell" : ""
669
+ }`.trim()}
670
+ style={{
671
+ width: "20px",
672
+ textAlign: "center",
673
+ top: headerStickyTop,
674
+ ...(hasStickyColumns
675
+ ? {
676
+ position: "sticky",
677
+ left: 0,
678
+ zIndex: 6,
679
+ backgroundColor: "#fff",
680
+ }
681
+ : {}),
682
+ }}
153
683
  >
154
684
  <Input
155
685
  id={`select-th-${id}`}
@@ -162,45 +692,128 @@ export function DataTable<T>(props: DataTableProps<T>) {
162
692
  />
163
693
  </th>
164
694
  )}
695
+
165
696
  {columns.map((column, idx) => (
166
- <DataTableColumn
697
+ <DataTableColumn<T>
167
698
  key={idx}
699
+ hasNestedColumns={hasNestedColumns}
168
700
  column={column}
169
701
  idx={idx}
170
702
  onColumnClick={onColumnClick}
703
+ isFilterVisible={visibleFilterColumns.has(getFilterKey(column))}
704
+ onToggleFilterVisibility={toggleColumnFilterVisibility}
705
+ currentSortDirection={
706
+ currentSort?.key === getFilterKey(column)
707
+ ? currentSort.direction
708
+ : null
709
+ }
710
+ onToggleSort={toggleColumnSort}
711
+ stickyLeft={stickyColumnsLeftByKey.get(getFilterKey(column))}
712
+ stickyTop={headerStickyTop}
171
713
  />
172
714
  ))}
173
715
  {actions.length > 0 && (
174
716
  <th
175
717
  style={{
176
718
  width: "200px",
719
+ top: headerStickyTop,
177
720
  }}
178
721
  align="right"
179
722
  key={"actions"}
723
+ rowSpan={hasNestedColumns ? 2 : 1}
180
724
  ></th>
181
725
  )}
182
726
  </tr>
183
- </thead>
184
- <tbody>
185
- {loading ? (
727
+ {hasNestedColumns && (
186
728
  <tr>
187
- <td
188
- align="center"
189
- colSpan={columns.length + (actions.length > 0 ? 1 : 0)}
190
- >
191
- <div className="mt-3">
192
- <Loader display={loading} />
193
- </div>
194
- </td>
729
+ {columns.flatMap(
730
+ (col, idx) =>
731
+ col.children?.map(
732
+ (
733
+ child: DataTableColumnsInterface<any>,
734
+ childIdx: number,
735
+ ) => (
736
+ <th
737
+ key={`${idx}-${childIdx}`}
738
+ style={{
739
+ minWidth: child.minWidth,
740
+ maxWidth: child.maxWidth,
741
+ width: child.width,
742
+ textAlign: child.align ?? "left",
743
+ position: "sticky",
744
+ top: groupedHeaderStickyTop,
745
+ zIndex: 11,
746
+ backgroundColor: "#f9fbfc",
747
+ ...(stickyColumnsLeftByKey.has(getFilterKey(child))
748
+ ? {
749
+ left: stickyColumnsLeftByKey.get(
750
+ getFilterKey(child),
751
+ ),
752
+ zIndex: 12,
753
+ }
754
+ : {}),
755
+ }}
756
+ className={
757
+ stickyColumnsLeftByKey.has(getFilterKey(child))
758
+ ? "data-table-sticky-cell"
759
+ : undefined
760
+ }
761
+ onClick={() => onColumnClick?.(child)}
762
+ >
763
+ <div className="d-flex align-items-center">
764
+ <div className="flex-1">{child.label}</div>
765
+ {child.filterable && (
766
+ <DataTableColumnFilter
767
+ column={child}
768
+ isFilterVisible={visibleFilterColumns.has(
769
+ getFilterKey(child),
770
+ )}
771
+ onToggleVisibility={toggleColumnFilterVisibility}
772
+ />
773
+ )}
774
+ {child.sortable && (
775
+ <DataTableColumnSort
776
+ column={child}
777
+ sortDirection={
778
+ currentSort?.key === getFilterKey(child)
779
+ ? currentSort.direction
780
+ : null
781
+ }
782
+ onToggleSort={toggleColumnSort}
783
+ />
784
+ )}
785
+ </div>
786
+ </th>
787
+ ),
788
+ ) ?? [],
789
+ )}
195
790
  </tr>
196
- ) : (
197
- <></>
198
791
  )}
792
+ </thead>
793
+ <tbody>
199
794
  {!loading &&
200
- data.map((row, rowIndex) => (
201
- <tr key={rowIndex} onClick={(e) => handleClickRow?.(e, row)}>
795
+ sortedData.map((row, rowIndex) => (
796
+ <tr
797
+ key={getRowId ? getRowId(row) : ((row as any).id ?? rowIndex)}
798
+ onClick={(e) => handleClickRow?.(e, row)}
799
+ >
202
800
  {onSelectionChange && (
203
- <td className="select-cell" style={{ textAlign: "center" }}>
801
+ <td
802
+ className={`select-cell ${
803
+ hasStickyColumns ? "data-table-sticky-cell" : ""
804
+ }`.trim()}
805
+ style={{
806
+ textAlign: "center",
807
+ ...(hasStickyColumns
808
+ ? {
809
+ position: "sticky",
810
+ left: 0,
811
+ zIndex: 3,
812
+ backgroundColor: "#fff",
813
+ }
814
+ : {}),
815
+ }}
816
+ >
204
817
  <Input
205
818
  id={`select-row-${rowIndex}`}
206
819
  name={`select-row-${rowIndex}`}
@@ -209,18 +822,27 @@ export function DataTable<T>(props: DataTableProps<T>) {
209
822
  limitSelect !== undefined &&
210
823
  selectedRowIds.size >= limitSelect &&
211
824
  !selectedRowIds.has(
212
- getRowId ? getRowId(row) : (row as any).id ?? row
825
+ getRowId ? getRowId(row) : ((row as any).id ?? row),
213
826
  )
214
827
  }
215
828
  checked={selectedRowIds.has(
216
- getRowId ? getRowId(row) : (row as any).id ?? row
829
+ getRowId ? getRowId(row) : ((row as any).id ?? row),
217
830
  )}
218
831
  onChange={(e) => toggleRow(e, row)} // empêche le clic de déclencher onRowClick
219
832
  />
220
833
  </td>
221
834
  )}
222
- {columns.map((column, idx) => (
223
- <DataTableRow column={column} idx={idx} row={row} key={idx} />
835
+ {flatColumns.map((column, idx) => (
836
+ <DataTableRow
837
+ onEditColumnByOverlay={onEditColumnByOverlay}
838
+ column={column}
839
+ idx={idx}
840
+ row={row}
841
+ key={idx}
842
+ stickyLeft={stickyColumnsLeftByKey.get(
843
+ getFilterKey(column),
844
+ )}
845
+ />
224
846
  ))}
225
847
  {actions.length > 0 && (
226
848
  <td align="right">
@@ -238,16 +860,19 @@ export function DataTable<T>(props: DataTableProps<T>) {
238
860
  )}
239
861
  </tr>
240
862
  ))}
241
- {!loading && data.length === 0 && (
242
- <DataTableLoading
243
- columns={columns}
244
- actions={actions}
245
- emptyMessage={emptyMessage}
246
- />
247
- )}
248
863
  </tbody>
249
864
  </table>
250
865
 
866
+ {loading && (
867
+ <div className="data-table-empty-state-content mt-3">
868
+ <Loader display={true} />
869
+ </div>
870
+ )}
871
+
872
+ {!loading && data.length === 0 && (
873
+ <DataTableLoading emptyMessage={emptyMessage} />
874
+ )}
875
+
251
876
  {children}
252
877
  {hasMore && (
253
878
  <div ref={loaderRef} className="infinite-scroll-loader">