@addsign/moje-agenda-shared-lib 1.0.61 → 2.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/dist/assets/style.css +4369 -0
- package/dist/assets/tailwind.css +1249 -1231
- package/dist/components/Button.js +1 -1
- package/dist/components/datatable/DataTable.js +1 -1
- package/dist/components/datatable/DataTableServer.js +1 -1
- package/dist/components/form/AutocompleteSearchBar.js +1 -1
- package/dist/components/form/AutocompleteSearchBarServer.js +1 -1
- package/dist/components/form/FileInput.js +1 -1
- package/dist/components/form/FileInputMultiple.js +1 -1
- package/dist/components/form/FormField.js +1 -1
- package/dist/components/form/PositionsSelectorSingle.js +1 -1
- package/dist/components/form/SelectField.js +1 -1
- package/dist/components/profiles/ProfileOverview.js +1 -1
- package/dist/main.js +1 -1
- package/dist/tailwind-l0sNRNKZ.js +2 -0
- package/dist/tailwind-l0sNRNKZ.js.map +1 -0
- package/lib/components/Button.tsx +57 -0
- package/lib/components/Calendar.tsx +242 -0
- package/lib/components/ConfirmationModalDialog.tsx +115 -0
- package/lib/components/Modal.tsx +73 -0
- package/lib/components/ModalDialog.tsx +63 -0
- package/lib/components/Spinner.tsx +25 -0
- package/lib/components/SpinnerIcon.tsx +12 -0
- package/lib/components/datatable/DataTable.tsx +442 -0
- package/lib/components/datatable/DataTableServer.tsx +939 -0
- package/lib/components/datatable/DatatableSettings.tsx +48 -0
- package/lib/components/datatable/Resizable.tsx +99 -0
- package/lib/components/datatable/types.ts +33 -0
- package/lib/components/form/AutocompleteSearchBar.tsx +424 -0
- package/lib/components/form/AutocompleteSearchBarServer.tsx +257 -0
- package/lib/components/form/DateField.tsx +124 -0
- package/lib/components/form/DateRangeField.tsx +116 -0
- package/lib/components/form/FileInput.tsx +188 -0
- package/lib/components/form/FileInputMultiple.tsx +186 -0
- package/lib/components/form/FormField.tsx +371 -0
- package/lib/components/form/InputField.tsx +230 -0
- package/lib/components/form/PositionsSelectorSingle.tsx +266 -0
- package/lib/components/form/RadioGroup.tsx +64 -0
- package/lib/components/form/SelectField.tsx +267 -0
- package/lib/components/layout/IconInCircle.tsx +29 -0
- package/lib/components/layout/PageTitle.tsx +19 -0
- package/lib/components/layout/SectionTitle.tsx +22 -0
- package/lib/components/profiles/ProfileOverview.tsx +212 -0
- package/lib/components/ui/Calendar.tsx +68 -0
- package/lib/components/ui/Combobox.tsx +122 -0
- package/lib/components/ui/DatePicker.tsx +124 -0
- package/lib/components/ui/DateTimePicker.tsx +187 -0
- package/lib/components/ui/Dialog.tsx +118 -0
- package/lib/components/ui/ScrollArea.tsx +45 -0
- package/lib/components/ui/button.tsx +56 -0
- package/lib/components/ui/command.tsx +153 -0
- package/lib/components/ui/form.tsx +177 -0
- package/lib/components/ui/input.tsx +22 -0
- package/lib/components/ui/label.tsx +24 -0
- package/lib/components/ui/popover.tsx +31 -0
- package/lib/components/ui/radioGroup.tsx +44 -0
- package/lib/components/ui/select.tsx +158 -0
- package/lib/contexts/FederationContext.tsx +28 -0
- package/lib/contexts/useFederationContext.ts +4 -0
- package/lib/css/tailwind.css +10 -0
- package/lib/fonts/arial.ts +3 -0
- package/lib/fonts/arialBold.ts +4 -0
- package/lib/main.ts +64 -0
- package/lib/types.ts +492 -0
- package/lib/utils/PdfManager.ts +224 -0
- package/lib/utils/getFullName.tsx +83 -0
- package/lib/utils/getIntersectingDays.ts +28 -0
- package/lib/utils/handleErrors.ts +28 -0
- package/lib/utils/hasRightInModule.ts +17 -0
- package/lib/utils/hasRole.ts +12 -0
- package/lib/utils/utils.ts +6 -0
- package/lib/vite-env.d.ts +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,939 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from "react";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
Button,
|
|
11
|
+
DataTableColumn,
|
|
12
|
+
DateField,
|
|
13
|
+
DateRangeField,
|
|
14
|
+
FormField,
|
|
15
|
+
IPageable,
|
|
16
|
+
Spinner,
|
|
17
|
+
handleErrors,
|
|
18
|
+
useFederationContext,
|
|
19
|
+
} from "../../main";
|
|
20
|
+
import {
|
|
21
|
+
MdArrowBack,
|
|
22
|
+
MdArrowDownward,
|
|
23
|
+
MdArrowForward,
|
|
24
|
+
MdArrowUpward,
|
|
25
|
+
MdClose,
|
|
26
|
+
MdOutlineFilterAlt,
|
|
27
|
+
MdOutlineFilterAltOff,
|
|
28
|
+
MdSearch,
|
|
29
|
+
} from "react-icons/md";
|
|
30
|
+
import SelectField from "../form/SelectField";
|
|
31
|
+
import InputField from "../form/InputField";
|
|
32
|
+
import { Resizable } from "./Resizable";
|
|
33
|
+
import { DatatableSettings } from "./DatatableSettings";
|
|
34
|
+
|
|
35
|
+
import * as XLSX from "xlsx";
|
|
36
|
+
|
|
37
|
+
interface ISortConfig {
|
|
38
|
+
sortParam: string;
|
|
39
|
+
direction: "asc" | "desc" | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface DataTableServerProps<T> {
|
|
43
|
+
id: string;
|
|
44
|
+
url: string;
|
|
45
|
+
columns: DataTableColumn<T | "actions">[];
|
|
46
|
+
title?: string;
|
|
47
|
+
subtitle?: string;
|
|
48
|
+
allowSearch?: boolean;
|
|
49
|
+
showHeader?: boolean;
|
|
50
|
+
rowAction?: (item: T) => void;
|
|
51
|
+
// bulkActions?: BulkAction<T>[];
|
|
52
|
+
bulkAction?: (items: T[]) => JSX.Element;
|
|
53
|
+
filters?: object;
|
|
54
|
+
selectedItemKey?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type DataTableInternalItems = {
|
|
58
|
+
_isHighlighted?: boolean;
|
|
59
|
+
id: string; // Assuming items have an `id` field for identification
|
|
60
|
+
};
|
|
61
|
+
interface DataTableStorageObject {
|
|
62
|
+
columnFilters: Record<string, string>;
|
|
63
|
+
showColFilters: boolean;
|
|
64
|
+
currentPage: number;
|
|
65
|
+
itemsPerPage: number;
|
|
66
|
+
sortConfig: ISortConfig | null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const resetAllDataTablePaging = () => {
|
|
70
|
+
Object.keys(localStorage)
|
|
71
|
+
.filter((key) => key.startsWith("datatable:"))
|
|
72
|
+
.forEach((key) => {
|
|
73
|
+
const storageObject = localStorage.getItem(key);
|
|
74
|
+
if (storageObject) {
|
|
75
|
+
const parsedObject: DataTableStorageObject = JSON.parse(storageObject);
|
|
76
|
+
parsedObject.currentPage = 0;
|
|
77
|
+
localStorage.setItem(key, JSON.stringify(parsedObject));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
function DataTableServer<T extends DataTableInternalItems>({
|
|
82
|
+
id,
|
|
83
|
+
url,
|
|
84
|
+
columns,
|
|
85
|
+
title,
|
|
86
|
+
subtitle,
|
|
87
|
+
allowSearch = false,
|
|
88
|
+
showHeader = true,
|
|
89
|
+
rowAction,
|
|
90
|
+
bulkAction,
|
|
91
|
+
filters,
|
|
92
|
+
selectedItemKey = "id",
|
|
93
|
+
}: DataTableServerProps<T>) {
|
|
94
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
95
|
+
const [itemsPerPageLocal, setItemsPerPageLocal] = useState<number>();
|
|
96
|
+
const federationContext = useFederationContext();
|
|
97
|
+
const [data, setData] = useState<IPageable<T>>();
|
|
98
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
99
|
+
const [isLocalStorageLoaded, setIsLocalStorageLoaded] = useState(false);
|
|
100
|
+
const [tableKey, setTableKey] = useState(0);
|
|
101
|
+
|
|
102
|
+
const [hasMounted, setHasMounted] = useState(false);
|
|
103
|
+
|
|
104
|
+
const [currentPage, setCurrentPage] = useState<number>();
|
|
105
|
+
const [selectedItems, setSelectedItems] = useState<T[]>([]);
|
|
106
|
+
const [fulltextSearch, setFulltextSearch] = useState("");
|
|
107
|
+
const [filterOptions, setFilterOptions] = useState<Record<string, any[]>>({});
|
|
108
|
+
const [columnFilters, setColumnFilters] = useState<{ [key: string]: any }>(
|
|
109
|
+
{}
|
|
110
|
+
);
|
|
111
|
+
const [showColFilters, setShowColFilters] = useState<boolean>();
|
|
112
|
+
const [sortConfig, setSortConfig] = useState<ISortConfig | null>(null);
|
|
113
|
+
|
|
114
|
+
const createDataPageable = (
|
|
115
|
+
response: any,
|
|
116
|
+
itemsPerPage: number
|
|
117
|
+
): IPageable<T> => {
|
|
118
|
+
return {
|
|
119
|
+
content: response.data.content || response.data,
|
|
120
|
+
empty: response.data?.content ? response.data.empty : true,
|
|
121
|
+
first: response.data?.content ? response.data.first : true,
|
|
122
|
+
last: response.data?.content ? response.data.last : true,
|
|
123
|
+
number: response.data?.content ? response.data.number : 0,
|
|
124
|
+
numberOfElements: response.data.numberOfElements || response.data.length,
|
|
125
|
+
size: response.data?.size || itemsPerPage,
|
|
126
|
+
totalElements: response.data?.totalElements || response.data.length,
|
|
127
|
+
totalPages: response.data?.totalPages || 1,
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const [reloadData, setReloadData] = useState(false);
|
|
132
|
+
|
|
133
|
+
const mergedFilters: { [key: string]: any } = useMemo(() => {
|
|
134
|
+
return showColFilters ? { ...columnFilters, ...filters } : filters || {};
|
|
135
|
+
}, [columnFilters, filters, showColFilters]);
|
|
136
|
+
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
setReloadData(true);
|
|
139
|
+
}, [
|
|
140
|
+
url,
|
|
141
|
+
showColFilters,
|
|
142
|
+
// id,
|
|
143
|
+
columnFilters,
|
|
144
|
+
itemsPerPageLocal,
|
|
145
|
+
currentPage,
|
|
146
|
+
sortConfig,
|
|
147
|
+
federationContext.apiClient,
|
|
148
|
+
federationContext.emitter,
|
|
149
|
+
filters,
|
|
150
|
+
tableKey,
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (reloadData) {
|
|
155
|
+
if (abortControllerRef.current) {
|
|
156
|
+
abortControllerRef.current.abort();
|
|
157
|
+
}
|
|
158
|
+
// Create a new AbortController for the new request
|
|
159
|
+
abortControllerRef.current = new AbortController();
|
|
160
|
+
const currentAbortController = abortControllerRef.current;
|
|
161
|
+
// load data from API
|
|
162
|
+
|
|
163
|
+
if (currentPage === undefined) return;
|
|
164
|
+
setIsLoading(true);
|
|
165
|
+
|
|
166
|
+
//odebrani prazdny filteru
|
|
167
|
+
const filteredMergedFilters = Object.entries(mergedFilters).reduce(
|
|
168
|
+
(acc: Record<string, any>, [key, value]) => {
|
|
169
|
+
if (value !== null && value !== "") {
|
|
170
|
+
acc[key] = value;
|
|
171
|
+
}
|
|
172
|
+
return acc;
|
|
173
|
+
},
|
|
174
|
+
{}
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
federationContext.apiClient
|
|
178
|
+
.get(url, {
|
|
179
|
+
signal: currentAbortController.signal, // Pass the AbortController signal to the request
|
|
180
|
+
params: {
|
|
181
|
+
...filteredMergedFilters,
|
|
182
|
+
pageSize: itemsPerPageLocal,
|
|
183
|
+
page: currentPage,
|
|
184
|
+
sortBy: sortConfig?.sortParam,
|
|
185
|
+
sortDirection: sortConfig?.direction,
|
|
186
|
+
},
|
|
187
|
+
})
|
|
188
|
+
.then((response) => {
|
|
189
|
+
const dataPageable: IPageable<T> = createDataPageable(
|
|
190
|
+
response,
|
|
191
|
+
itemsPerPageLocal || 10
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
setData(dataPageable);
|
|
195
|
+
// setData(response.data);
|
|
196
|
+
setIsLoading(false);
|
|
197
|
+
})
|
|
198
|
+
.catch((error) => {
|
|
199
|
+
if (error.code === "ERR_CANCELED") {
|
|
200
|
+
console.log("Request was aborted");
|
|
201
|
+
} else {
|
|
202
|
+
console.error("Error fetching data:", error);
|
|
203
|
+
handleErrors(error, federationContext.emitter);
|
|
204
|
+
setData({
|
|
205
|
+
content: [],
|
|
206
|
+
empty: true,
|
|
207
|
+
first: true,
|
|
208
|
+
last: true,
|
|
209
|
+
number: 0,
|
|
210
|
+
numberOfElements: 0,
|
|
211
|
+
size: itemsPerPageLocal || 10,
|
|
212
|
+
totalElements: 0,
|
|
213
|
+
totalPages: 1,
|
|
214
|
+
});
|
|
215
|
+
setIsLoading(false);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
setReloadData(false);
|
|
220
|
+
}
|
|
221
|
+
}, [reloadData]);
|
|
222
|
+
// nastaveni currentPage u jinych datatable na 0
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
Object.keys(localStorage)
|
|
225
|
+
.filter(
|
|
226
|
+
(key) => key.startsWith("datatable:") && key !== `datatable:${id}`
|
|
227
|
+
)
|
|
228
|
+
.forEach((key) => {
|
|
229
|
+
const storageObject = localStorage.getItem(key);
|
|
230
|
+
if (storageObject) {
|
|
231
|
+
const parsedObject: DataTableStorageObject =
|
|
232
|
+
JSON.parse(storageObject);
|
|
233
|
+
parsedObject.currentPage = 0;
|
|
234
|
+
localStorage.setItem(key, JSON.stringify(parsedObject));
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}, [id]);
|
|
238
|
+
|
|
239
|
+
//load from localstorage
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
if (id) {
|
|
242
|
+
const storageKey = `datatable:${id}`;
|
|
243
|
+
|
|
244
|
+
const storedStorageObject = localStorage.getItem(storageKey);
|
|
245
|
+
if (storedStorageObject) {
|
|
246
|
+
const storageObject: DataTableStorageObject =
|
|
247
|
+
JSON.parse(storedStorageObject);
|
|
248
|
+
setColumnFilters(storageObject.columnFilters);
|
|
249
|
+
setShowColFilters(storageObject.showColFilters);
|
|
250
|
+
setCurrentPage(storageObject.currentPage || 0);
|
|
251
|
+
setSortConfig(storageObject.sortConfig || null);
|
|
252
|
+
|
|
253
|
+
setItemsPerPageLocal(storageObject.itemsPerPage || 10);
|
|
254
|
+
}
|
|
255
|
+
setIsLocalStorageLoaded(true);
|
|
256
|
+
}
|
|
257
|
+
}, [id]);
|
|
258
|
+
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
const fetchFilterOptions = async (column: DataTableColumn<T>) => {
|
|
261
|
+
if (column.filterOptions) {
|
|
262
|
+
// if filterOptions is provided, use it directly
|
|
263
|
+
return column.filterOptions;
|
|
264
|
+
} else if (column.filterSource) {
|
|
265
|
+
try {
|
|
266
|
+
const response = await federationContext.apiClient.get(
|
|
267
|
+
column.filterSource
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
const options = response.data.map((item: any) => ({
|
|
271
|
+
value: item[column.filterValueKey as keyof typeof item],
|
|
272
|
+
label: item[column.filterLabelKey as keyof typeof item],
|
|
273
|
+
}));
|
|
274
|
+
return options;
|
|
275
|
+
} catch (error) {
|
|
276
|
+
console.error("Error fetching filter options:", error);
|
|
277
|
+
return [];
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
const updateFilterOptions = async () => {
|
|
284
|
+
const newFilterOptions: Record<string, any[]> = {};
|
|
285
|
+
|
|
286
|
+
for (const column of columns) {
|
|
287
|
+
if (
|
|
288
|
+
column.filterType === "select" &&
|
|
289
|
+
(column.filterSource || column.filterOptions) &&
|
|
290
|
+
column.filterValueKey &&
|
|
291
|
+
column.filterLabelKey &&
|
|
292
|
+
column.filterParam
|
|
293
|
+
) {
|
|
294
|
+
const options = await fetchFilterOptions(column);
|
|
295
|
+
|
|
296
|
+
if (options) {
|
|
297
|
+
newFilterOptions[column.filterParam as string] = [
|
|
298
|
+
{ value: "", label: "" },
|
|
299
|
+
...options,
|
|
300
|
+
];
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
setFilterOptions(newFilterOptions);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
updateFilterOptions();
|
|
309
|
+
}, [columns, federationContext.apiClient]);
|
|
310
|
+
const hasSomeColFilters = useMemo(() => {
|
|
311
|
+
return columns.some((column) => !!column.filterParam);
|
|
312
|
+
}, [columns]);
|
|
313
|
+
|
|
314
|
+
const requestSort = (sortParam: string) => {
|
|
315
|
+
setSortConfig((prevSortConfig) => {
|
|
316
|
+
if (
|
|
317
|
+
prevSortConfig?.sortParam === sortParam &&
|
|
318
|
+
prevSortConfig.direction !== null
|
|
319
|
+
) {
|
|
320
|
+
return prevSortConfig.direction === "asc"
|
|
321
|
+
? { sortParam, direction: "desc" }
|
|
322
|
+
: null;
|
|
323
|
+
} else {
|
|
324
|
+
return { sortParam, direction: "asc" };
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const getSortIcon = (sortParam: string) => {
|
|
330
|
+
if (sortConfig?.sortParam === sortParam) {
|
|
331
|
+
return sortConfig.direction === "asc" ? (
|
|
332
|
+
<MdArrowUpward fontSize="small" />
|
|
333
|
+
) : sortConfig.direction === "desc" ? (
|
|
334
|
+
<MdArrowDownward fontSize="small" />
|
|
335
|
+
) : (
|
|
336
|
+
<MdArrowUpward
|
|
337
|
+
fontSize="small"
|
|
338
|
+
className="text-gray-300 invisible group-hover:visible "
|
|
339
|
+
/>
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
return (
|
|
343
|
+
<MdArrowUpward
|
|
344
|
+
fontSize="small"
|
|
345
|
+
className="text-gray-300 invisible group-hover:visible "
|
|
346
|
+
/>
|
|
347
|
+
);
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const handleSelectItem = (item: T) => {
|
|
351
|
+
setSelectedItems((prevSelectedItems) => {
|
|
352
|
+
if (
|
|
353
|
+
prevSelectedItems.find(
|
|
354
|
+
(selectedItem) =>
|
|
355
|
+
selectedItem[selectedItemKey as keyof T] ===
|
|
356
|
+
item[selectedItemKey as keyof T]
|
|
357
|
+
)
|
|
358
|
+
) {
|
|
359
|
+
return prevSelectedItems.filter(
|
|
360
|
+
(selectedItem) =>
|
|
361
|
+
selectedItem[selectedItemKey as keyof T] !==
|
|
362
|
+
item[selectedItemKey as keyof T]
|
|
363
|
+
);
|
|
364
|
+
} else {
|
|
365
|
+
return [...prevSelectedItems, item];
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const handleSelectAll = () => {
|
|
371
|
+
if (data && selectedItems.length === data.content.length) {
|
|
372
|
+
setSelectedItems([]);
|
|
373
|
+
} else if (data) {
|
|
374
|
+
setSelectedItems(data.content);
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const isSelected = useCallback(
|
|
379
|
+
(item: T) => {
|
|
380
|
+
return selectedItems.some(
|
|
381
|
+
(selectedItem) =>
|
|
382
|
+
selectedItem[selectedItemKey as keyof T] ===
|
|
383
|
+
item[selectedItemKey as keyof T]
|
|
384
|
+
);
|
|
385
|
+
},
|
|
386
|
+
[selectedItems, selectedItemKey]
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
const nextPage = () => {
|
|
390
|
+
setCurrentPage((currentPage || 0) + 1);
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const prevPage = () => {
|
|
394
|
+
setCurrentPage((currentPage || 0) - 1);
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const handleSearchChanged = (
|
|
398
|
+
e: React.ChangeEvent<
|
|
399
|
+
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
|
|
400
|
+
>
|
|
401
|
+
) => {
|
|
402
|
+
setFulltextSearch(e.target?.value);
|
|
403
|
+
setCurrentPage(0);
|
|
404
|
+
};
|
|
405
|
+
// Pagination display logic
|
|
406
|
+
const paginationDisplay = `Strana ${(currentPage || 0) + 1} z ${data?.totalPages || 1}`;
|
|
407
|
+
|
|
408
|
+
const filterHandler = (filterParam: keyof T, value: string) => {
|
|
409
|
+
setColumnFilters((prev) => ({ ...prev, [filterParam]: value }));
|
|
410
|
+
setCurrentPage(0);
|
|
411
|
+
};
|
|
412
|
+
const handleToggleShowColFilters = () => {
|
|
413
|
+
if (showColFilters && columnFilters !== undefined) {
|
|
414
|
+
setColumnFilters(columnFilters);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
setShowColFilters(!showColFilters);
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// osetreni logiky pro useEffect. prvni nacteni filteru currentPage neresetuje
|
|
421
|
+
useEffect(() => {
|
|
422
|
+
if (!hasMounted) {
|
|
423
|
+
setHasMounted(true); // Ensures this block won't run again
|
|
424
|
+
} else {
|
|
425
|
+
setCurrentPage(0);
|
|
426
|
+
}
|
|
427
|
+
}, [filters]);
|
|
428
|
+
|
|
429
|
+
//store table settings in localstorage
|
|
430
|
+
useEffect(() => {
|
|
431
|
+
if (id && isLocalStorageLoaded) {
|
|
432
|
+
const storageKey = `datatable:${id}`;
|
|
433
|
+
|
|
434
|
+
const storageObject: DataTableStorageObject = localStorage.getItem(
|
|
435
|
+
storageKey
|
|
436
|
+
)
|
|
437
|
+
? (JSON.parse(
|
|
438
|
+
localStorage.getItem(storageKey)!
|
|
439
|
+
) as DataTableStorageObject)
|
|
440
|
+
: ({} as DataTableStorageObject);
|
|
441
|
+
|
|
442
|
+
storageObject.columnFilters = columnFilters || {};
|
|
443
|
+
// if (showColFilters !== undefined) {
|
|
444
|
+
// storageObject.showColFilters = showColFilters;
|
|
445
|
+
// }
|
|
446
|
+
storageObject.showColFilters = showColFilters || false;
|
|
447
|
+
storageObject.currentPage = currentPage || 0;
|
|
448
|
+
storageObject.sortConfig = sortConfig || null;
|
|
449
|
+
|
|
450
|
+
storageObject.itemsPerPage = itemsPerPageLocal || 10;
|
|
451
|
+
|
|
452
|
+
localStorage.setItem(storageKey, JSON.stringify(storageObject));
|
|
453
|
+
}
|
|
454
|
+
}, [
|
|
455
|
+
columnFilters,
|
|
456
|
+
showColFilters,
|
|
457
|
+
currentPage,
|
|
458
|
+
// id, // predbihalo se to a ukladaly se sortCOnfigy z predchozich tabulek
|
|
459
|
+
itemsPerPageLocal,
|
|
460
|
+
isLocalStorageLoaded,
|
|
461
|
+
sortConfig,
|
|
462
|
+
]);
|
|
463
|
+
|
|
464
|
+
const rerenderTable = () => {
|
|
465
|
+
setTableKey((previous) => previous + 1);
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
const exportToXLSX = useCallback(() => {
|
|
469
|
+
// load fresh complete data without pagination ,filters
|
|
470
|
+
setIsLoading(true);
|
|
471
|
+
|
|
472
|
+
federationContext.apiClient
|
|
473
|
+
.get(url, {
|
|
474
|
+
params: {
|
|
475
|
+
...filters,
|
|
476
|
+
...(showColFilters ? columnFilters : {}),
|
|
477
|
+
pageSize: 1000000,
|
|
478
|
+
page: 0,
|
|
479
|
+
sortBy: sortConfig?.sortParam,
|
|
480
|
+
sortDirection: sortConfig?.direction,
|
|
481
|
+
},
|
|
482
|
+
})
|
|
483
|
+
.then((response) => {
|
|
484
|
+
setIsLoading(false);
|
|
485
|
+
const dataPageable: IPageable<T> = createDataPageable(
|
|
486
|
+
response,
|
|
487
|
+
1000000
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
const worksheet = XLSX.utils.json_to_sheet(
|
|
491
|
+
dataPageable.content.map((item: any) => {
|
|
492
|
+
const row = {};
|
|
493
|
+
columns.forEach((column) => {
|
|
494
|
+
if (column.render) {
|
|
495
|
+
(row as any)[column.header] = column.render(item)?.toString();
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
return row;
|
|
499
|
+
})
|
|
500
|
+
);
|
|
501
|
+
const workbook = XLSX.utils.book_new();
|
|
502
|
+
XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1");
|
|
503
|
+
XLSX.writeFile(workbook, "export.xlsx");
|
|
504
|
+
})
|
|
505
|
+
.catch((error) => {
|
|
506
|
+
console.error("Error fetching data:", error);
|
|
507
|
+
handleErrors(error, federationContext.emitter);
|
|
508
|
+
setIsLoading(false);
|
|
509
|
+
});
|
|
510
|
+
}, [
|
|
511
|
+
setIsLoading,
|
|
512
|
+
federationContext.apiClient,
|
|
513
|
+
url,
|
|
514
|
+
filters,
|
|
515
|
+
showColFilters,
|
|
516
|
+
columnFilters,
|
|
517
|
+
sortConfig,
|
|
518
|
+
handleErrors,
|
|
519
|
+
columns,
|
|
520
|
+
]);
|
|
521
|
+
|
|
522
|
+
const handleItemsPerPageChange = (
|
|
523
|
+
event: React.ChangeEvent<HTMLSelectElement>
|
|
524
|
+
) => {
|
|
525
|
+
const selectedItemsPerPage = Number(event.target.value);
|
|
526
|
+
setItemsPerPageLocal(selectedItemsPerPage);
|
|
527
|
+
setCurrentPage(0); // Reset the current page to 0 when changing items per page
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
return (
|
|
531
|
+
<>
|
|
532
|
+
<div
|
|
533
|
+
className="shadow-lg border border-gray-200 rounded-xl"
|
|
534
|
+
style={{ overflowY: "visible" }}
|
|
535
|
+
>
|
|
536
|
+
{showHeader && (
|
|
537
|
+
<div className="p-4 leading-9 flex ">
|
|
538
|
+
<div className="flex-grow content-center">
|
|
539
|
+
{title && (
|
|
540
|
+
<h1 className="font-semibold text-xl leading-[42px]">
|
|
541
|
+
{title}
|
|
542
|
+
</h1>
|
|
543
|
+
)}
|
|
544
|
+
{subtitle && (
|
|
545
|
+
<p className="font-normal text-gray-600">{subtitle}</p>
|
|
546
|
+
)}{" "}
|
|
547
|
+
{bulkAction && selectedItems.length > 0 && (
|
|
548
|
+
<div className="">{bulkAction(selectedItems)}</div>
|
|
549
|
+
)}
|
|
550
|
+
</div>
|
|
551
|
+
|
|
552
|
+
<DatatableSettings
|
|
553
|
+
tableId={id}
|
|
554
|
+
onSuccess={rerenderTable}
|
|
555
|
+
onExport={exportToXLSX}
|
|
556
|
+
></DatatableSettings>
|
|
557
|
+
{hasSomeColFilters && (
|
|
558
|
+
<div
|
|
559
|
+
className="flex items-center text-xl h-full p-3 cursor-pointer text-gray-500 hover:text-black"
|
|
560
|
+
title={
|
|
561
|
+
showColFilters
|
|
562
|
+
? "Zrušit filtr podle sloupců"
|
|
563
|
+
: "Filtrovat podle sloupců"
|
|
564
|
+
}
|
|
565
|
+
onClick={handleToggleShowColFilters}
|
|
566
|
+
>
|
|
567
|
+
{!showColFilters && <MdOutlineFilterAlt />}
|
|
568
|
+
{showColFilters && (
|
|
569
|
+
<MdOutlineFilterAltOff className="text-danger" />
|
|
570
|
+
)}
|
|
571
|
+
</div>
|
|
572
|
+
)}
|
|
573
|
+
{allowSearch && (
|
|
574
|
+
<div className="ml-5">
|
|
575
|
+
<FormField
|
|
576
|
+
placeholder="Vyhledávání"
|
|
577
|
+
name="search"
|
|
578
|
+
onInputChange={handleSearchChanged}
|
|
579
|
+
type="text"
|
|
580
|
+
value={fulltextSearch}
|
|
581
|
+
>
|
|
582
|
+
<div className=" text-gray-500 leading-5 flex items-center h-full">
|
|
583
|
+
{!fulltextSearch && <MdSearch></MdSearch>}
|
|
584
|
+
{fulltextSearch && (
|
|
585
|
+
<MdClose onClick={() => setFulltextSearch("")} />
|
|
586
|
+
)}
|
|
587
|
+
</div>
|
|
588
|
+
</FormField>
|
|
589
|
+
</div>
|
|
590
|
+
)}
|
|
591
|
+
</div>
|
|
592
|
+
)}
|
|
593
|
+
<div className="overflow-auto min-h-[500px]">
|
|
594
|
+
<table className="w-full leading-normal" key={tableKey}>
|
|
595
|
+
<thead>
|
|
596
|
+
<tr>
|
|
597
|
+
{data && bulkAction && (
|
|
598
|
+
<th className="w-[20px] h-10 hover:bg-gray-200 bg-gray-50 font-medium text-xs text-center text-gray-600 cursor-pointer border-t border-b border-gray-200">
|
|
599
|
+
<label className="w-full h-full flex items-center justify-center cursor-pointer px-2">
|
|
600
|
+
<input
|
|
601
|
+
id="selectAll"
|
|
602
|
+
type="checkbox"
|
|
603
|
+
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded !focus:ring-indigo-200 focus:ring-4"
|
|
604
|
+
onChange={handleSelectAll}
|
|
605
|
+
checked={
|
|
606
|
+
data &&
|
|
607
|
+
selectedItems.length === data.content.length &&
|
|
608
|
+
data.content.length > 0
|
|
609
|
+
}
|
|
610
|
+
/>
|
|
611
|
+
</label>
|
|
612
|
+
</th>
|
|
613
|
+
)}
|
|
614
|
+
{columns.map(
|
|
615
|
+
(
|
|
616
|
+
{
|
|
617
|
+
key,
|
|
618
|
+
header,
|
|
619
|
+
actions,
|
|
620
|
+
sortParam,
|
|
621
|
+
width,
|
|
622
|
+
filterType,
|
|
623
|
+
filterParam,
|
|
624
|
+
filterParam2,
|
|
625
|
+
},
|
|
626
|
+
index
|
|
627
|
+
) => (
|
|
628
|
+
<Resizable
|
|
629
|
+
// key={String(key) + currentPage}
|
|
630
|
+
key={String(key)}
|
|
631
|
+
tableId={id}
|
|
632
|
+
colKey={String(key)}
|
|
633
|
+
defaultWidth={width || "auto"}
|
|
634
|
+
>
|
|
635
|
+
{({ ref }: { ref: any }) => (
|
|
636
|
+
<th
|
|
637
|
+
className={`tableHeader relative font-medium text-xs !leading-9 text-left px-3 text-gray-600
|
|
638
|
+
bg-gray-50 border-t border-b border-gray-200 content-start ${
|
|
639
|
+
!title && !subtitle ? "border-t-0" : ""
|
|
640
|
+
} ${sortParam ? " cursor-pointer " : ""}`}
|
|
641
|
+
onClick={() =>
|
|
642
|
+
sortParam ? requestSort(sortParam) : undefined
|
|
643
|
+
}
|
|
644
|
+
>
|
|
645
|
+
<span className="inline-flex items-center gap-2 group select-none w-full">
|
|
646
|
+
{header}{" "}
|
|
647
|
+
{!actions && sortParam
|
|
648
|
+
? getSortIcon(sortParam)
|
|
649
|
+
: ""}
|
|
650
|
+
</span>
|
|
651
|
+
<div
|
|
652
|
+
className={`resizer absolute top-0 right-0 h-full w-2 bg-transparent ${
|
|
653
|
+
index < columns.length - 1
|
|
654
|
+
? "cursor-col-resize hover:border-x border-gray-300 border-r w-1"
|
|
655
|
+
: "w-0"
|
|
656
|
+
}`}
|
|
657
|
+
ref={ref}
|
|
658
|
+
onClick={(e) => e.stopPropagation()}
|
|
659
|
+
/>
|
|
660
|
+
{showColFilters && (
|
|
661
|
+
<div
|
|
662
|
+
className="p-0 m-0 pb-2"
|
|
663
|
+
onClick={(e) => e.stopPropagation()}
|
|
664
|
+
>
|
|
665
|
+
{filterType === "select" ? (
|
|
666
|
+
<SelectField
|
|
667
|
+
// label={header}
|
|
668
|
+
key={JSON.stringify(mergedFilters)}
|
|
669
|
+
name={String(key) + "_filter"}
|
|
670
|
+
onInputChange={(e) =>
|
|
671
|
+
filterHandler(
|
|
672
|
+
filterParam as keyof T,
|
|
673
|
+
e.target.value
|
|
674
|
+
)
|
|
675
|
+
}
|
|
676
|
+
type={filterType}
|
|
677
|
+
options={
|
|
678
|
+
filterOptions[String(filterParam)] || []
|
|
679
|
+
}
|
|
680
|
+
value={
|
|
681
|
+
mergedFilters?.[String(filterParam)] ?? ""
|
|
682
|
+
}
|
|
683
|
+
clearable
|
|
684
|
+
className=" px-0"
|
|
685
|
+
placeholder={"Zadejte filtr"}
|
|
686
|
+
rounded={true}
|
|
687
|
+
disabled={Object.keys(
|
|
688
|
+
(filters as object) || {}
|
|
689
|
+
).includes(String(filterParam))}
|
|
690
|
+
/>
|
|
691
|
+
) : filterType === "dateRange" ? (
|
|
692
|
+
<DateRangeField
|
|
693
|
+
// label={header}
|
|
694
|
+
key={JSON.stringify(mergedFilters)}
|
|
695
|
+
name={String(filterParam)}
|
|
696
|
+
nameEnd={String(filterParam2)}
|
|
697
|
+
onInputChange={(e) =>
|
|
698
|
+
filterHandler(
|
|
699
|
+
e.target.name as keyof T,
|
|
700
|
+
e.target.value
|
|
701
|
+
)
|
|
702
|
+
}
|
|
703
|
+
type={filterType}
|
|
704
|
+
value={{
|
|
705
|
+
startDate:
|
|
706
|
+
mergedFilters?.[String(filterParam)] ||
|
|
707
|
+
"",
|
|
708
|
+
endDate:
|
|
709
|
+
mergedFilters?.[String(filterParam2)] ||
|
|
710
|
+
"",
|
|
711
|
+
}}
|
|
712
|
+
clearable
|
|
713
|
+
className=" px-0 py-0 "
|
|
714
|
+
placeholder={"Zadejte filtr"}
|
|
715
|
+
rounded={true}
|
|
716
|
+
disabled={Object.keys(
|
|
717
|
+
(filters as object) || {}
|
|
718
|
+
).includes(String(filterParam))}
|
|
719
|
+
/>
|
|
720
|
+
) : filterType === "date" ? (
|
|
721
|
+
<DateField
|
|
722
|
+
// label={header}
|
|
723
|
+
key={JSON.stringify(mergedFilters)}
|
|
724
|
+
name={String(filterParam)}
|
|
725
|
+
onInputChange={(e) =>
|
|
726
|
+
filterHandler(
|
|
727
|
+
e.target.name as keyof T,
|
|
728
|
+
e.target.value
|
|
729
|
+
)
|
|
730
|
+
}
|
|
731
|
+
type={filterType}
|
|
732
|
+
value={
|
|
733
|
+
mergedFilters?.[String(filterParam)] || ""
|
|
734
|
+
}
|
|
735
|
+
clearable
|
|
736
|
+
className=" px-0 py-0 "
|
|
737
|
+
placeholder={"Zadejte filtr"}
|
|
738
|
+
rounded={true}
|
|
739
|
+
disabled={Object.keys(
|
|
740
|
+
(filters as object) || {}
|
|
741
|
+
).includes(String(filterParam))}
|
|
742
|
+
/>
|
|
743
|
+
) : filterType === "text" ? (
|
|
744
|
+
<InputField
|
|
745
|
+
// label={header}
|
|
746
|
+
key={filterParam}
|
|
747
|
+
name={String(key) + "_filter"}
|
|
748
|
+
onInputChange={(e) =>
|
|
749
|
+
filterHandler(
|
|
750
|
+
filterParam as keyof T,
|
|
751
|
+
e.target.value
|
|
752
|
+
)
|
|
753
|
+
}
|
|
754
|
+
type={filterType}
|
|
755
|
+
value={
|
|
756
|
+
mergedFilters?.[String(filterParam)] || ""
|
|
757
|
+
}
|
|
758
|
+
disabled={Object.keys(
|
|
759
|
+
(filters as object) || {}
|
|
760
|
+
).includes(String(filterParam))}
|
|
761
|
+
clearable
|
|
762
|
+
className=" min-w-[100px] px-0 "
|
|
763
|
+
rounded={true}
|
|
764
|
+
placeholder={"Zadejte filtr"}
|
|
765
|
+
debounceTimeout={1000}
|
|
766
|
+
/>
|
|
767
|
+
) : null}
|
|
768
|
+
</div>
|
|
769
|
+
)}
|
|
770
|
+
</th>
|
|
771
|
+
)}
|
|
772
|
+
</Resizable>
|
|
773
|
+
)
|
|
774
|
+
)}
|
|
775
|
+
</tr>
|
|
776
|
+
</thead>
|
|
777
|
+
{!isLoading &&
|
|
778
|
+
data &&
|
|
779
|
+
data?.content &&
|
|
780
|
+
data?.content.length > 0 && (
|
|
781
|
+
<tbody className="relative">
|
|
782
|
+
{data.content.map((item, rowIndex) => (
|
|
783
|
+
<tr
|
|
784
|
+
key={rowIndex}
|
|
785
|
+
className={`${
|
|
786
|
+
item._isHighlighted || isSelected(item)
|
|
787
|
+
? "bg-gray-50"
|
|
788
|
+
: ""
|
|
789
|
+
} hover:bg-gray-100 border-gray-200 border-b text-sm`}
|
|
790
|
+
>
|
|
791
|
+
{bulkAction && (
|
|
792
|
+
<td className="w-[20px] h-[52px] hover:bg-gray-200 font-medium text-xs text-center text-gray-600 cursor-pointer">
|
|
793
|
+
<label className="w-full h-full flex items-center justify-center cursor-pointer px-2">
|
|
794
|
+
<input
|
|
795
|
+
type="checkbox"
|
|
796
|
+
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded !focus:ring-indigo-200 focus:ring-4"
|
|
797
|
+
checked={isSelected(item) || false}
|
|
798
|
+
onChange={() => handleSelectItem(item)}
|
|
799
|
+
/>
|
|
800
|
+
</label>
|
|
801
|
+
</td>
|
|
802
|
+
)}
|
|
803
|
+
{columns.map(({ render, actions, classes }, colIndex) => (
|
|
804
|
+
<td
|
|
805
|
+
key={`${rowIndex}-${colIndex}`}
|
|
806
|
+
onClick={
|
|
807
|
+
rowAction ? () => rowAction(item) : undefined
|
|
808
|
+
}
|
|
809
|
+
className={`px-3 py-2 ${rowAction ? "cursor-pointer" : ""} ${
|
|
810
|
+
colIndex === 0
|
|
811
|
+
? "font-medium text-gray-900"
|
|
812
|
+
: "text-gray-700"
|
|
813
|
+
} ${classes || ""}`}
|
|
814
|
+
>
|
|
815
|
+
{render ? render(item) : ""}
|
|
816
|
+
{actions &&
|
|
817
|
+
actions
|
|
818
|
+
.filter((it) => {
|
|
819
|
+
if (it.rowAction) return false;
|
|
820
|
+
if (it.visible) {
|
|
821
|
+
return it.visible(item);
|
|
822
|
+
} else return true;
|
|
823
|
+
})
|
|
824
|
+
.map((action, actionIndex) => (
|
|
825
|
+
<div
|
|
826
|
+
key={`${rowIndex}-${colIndex}-${actionIndex}`}
|
|
827
|
+
className="flex inline-flex align-middle"
|
|
828
|
+
>
|
|
829
|
+
{action.icon && (
|
|
830
|
+
<Button
|
|
831
|
+
variant="icon"
|
|
832
|
+
onClick={() => action.onClick(item)}
|
|
833
|
+
>
|
|
834
|
+
{action.icon}
|
|
835
|
+
</Button>
|
|
836
|
+
)}
|
|
837
|
+
{!action.icon && (
|
|
838
|
+
<Button
|
|
839
|
+
variant="primary"
|
|
840
|
+
onClick={(e) => {
|
|
841
|
+
e.stopPropagation();
|
|
842
|
+
action.onClick(item);
|
|
843
|
+
}}
|
|
844
|
+
>
|
|
845
|
+
{action.label}
|
|
846
|
+
</Button>
|
|
847
|
+
)}
|
|
848
|
+
</div>
|
|
849
|
+
))}
|
|
850
|
+
</td>
|
|
851
|
+
))}
|
|
852
|
+
</tr>
|
|
853
|
+
))}
|
|
854
|
+
{data?.content?.length === 0 && (
|
|
855
|
+
<tr key="tr-nodata">
|
|
856
|
+
<td
|
|
857
|
+
key="td-nodata"
|
|
858
|
+
className="px-5 py-3 border-b border-gray-200 bg-white text-sm items-center justify-center align-middle"
|
|
859
|
+
colSpan={columns.length}
|
|
860
|
+
>
|
|
861
|
+
No data
|
|
862
|
+
</td>
|
|
863
|
+
</tr>
|
|
864
|
+
)}
|
|
865
|
+
</tbody>
|
|
866
|
+
)}
|
|
867
|
+
{isLoading && (
|
|
868
|
+
<tbody className="relative">
|
|
869
|
+
<tr>
|
|
870
|
+
<td colSpan={100}>
|
|
871
|
+
<div className="w-full flex items-center justify-center h-[500px] py-2">
|
|
872
|
+
<Spinner />
|
|
873
|
+
</div>
|
|
874
|
+
</td>
|
|
875
|
+
</tr>
|
|
876
|
+
</tbody>
|
|
877
|
+
)}
|
|
878
|
+
{!isLoading && (!data || data?.content?.length === 0) && (
|
|
879
|
+
<tbody className="relative h-[440px]">
|
|
880
|
+
<tr>
|
|
881
|
+
<td colSpan={100}>
|
|
882
|
+
<div className="w-full flex items-center justify-center h-full py-2 text-gray-600 font-medium text-xs ">
|
|
883
|
+
Žádná data
|
|
884
|
+
</div>
|
|
885
|
+
</td>
|
|
886
|
+
</tr>
|
|
887
|
+
</tbody>
|
|
888
|
+
)}
|
|
889
|
+
</table>
|
|
890
|
+
</div>
|
|
891
|
+
<div className="w-full p-5 flex gap-5 justify-between">
|
|
892
|
+
<div className="flex gap-5 text-sm ">
|
|
893
|
+
{data && (
|
|
894
|
+
<Button
|
|
895
|
+
variant="secondary"
|
|
896
|
+
onClick={prevPage}
|
|
897
|
+
className="flex items-center"
|
|
898
|
+
disabled={data.first || isLoading}
|
|
899
|
+
>
|
|
900
|
+
<MdArrowBack className="mr-1.5" /> Předchozí
|
|
901
|
+
</Button>
|
|
902
|
+
)}
|
|
903
|
+
{data && (
|
|
904
|
+
<Button
|
|
905
|
+
variant="secondary"
|
|
906
|
+
onClick={nextPage}
|
|
907
|
+
className="flex items-center"
|
|
908
|
+
disabled={data.last || isLoading}
|
|
909
|
+
>
|
|
910
|
+
Následující <MdArrowForward className="ml-2" size={20} />
|
|
911
|
+
</Button>
|
|
912
|
+
)}
|
|
913
|
+
<div className="flex items-center justify-end text-gray-800">
|
|
914
|
+
{paginationDisplay}
|
|
915
|
+
</div>
|
|
916
|
+
</div>{" "}
|
|
917
|
+
<div className="content-center w-auto items-center justify-end flex-row gap-5 flex">
|
|
918
|
+
<span className=" whitespace-nowrap flex-grow">
|
|
919
|
+
Počet řádků na stránku:
|
|
920
|
+
</span>
|
|
921
|
+
<SelectField
|
|
922
|
+
name="itemsPerPage"
|
|
923
|
+
onInputChange={handleItemsPerPageChange}
|
|
924
|
+
className="!w-[100px]"
|
|
925
|
+
options={[
|
|
926
|
+
{ value: 10, label: "10" },
|
|
927
|
+
{ value: 50, label: "50" },
|
|
928
|
+
{ value: 100, label: "100" },
|
|
929
|
+
]}
|
|
930
|
+
value={itemsPerPageLocal}
|
|
931
|
+
/>
|
|
932
|
+
</div>
|
|
933
|
+
</div>
|
|
934
|
+
</div>
|
|
935
|
+
</>
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
export default DataTableServer;
|