@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.
Files changed (73) hide show
  1. package/dist/assets/style.css +4369 -0
  2. package/dist/assets/tailwind.css +1249 -1231
  3. package/dist/components/Button.js +1 -1
  4. package/dist/components/datatable/DataTable.js +1 -1
  5. package/dist/components/datatable/DataTableServer.js +1 -1
  6. package/dist/components/form/AutocompleteSearchBar.js +1 -1
  7. package/dist/components/form/AutocompleteSearchBarServer.js +1 -1
  8. package/dist/components/form/FileInput.js +1 -1
  9. package/dist/components/form/FileInputMultiple.js +1 -1
  10. package/dist/components/form/FormField.js +1 -1
  11. package/dist/components/form/PositionsSelectorSingle.js +1 -1
  12. package/dist/components/form/SelectField.js +1 -1
  13. package/dist/components/profiles/ProfileOverview.js +1 -1
  14. package/dist/main.js +1 -1
  15. package/dist/tailwind-l0sNRNKZ.js +2 -0
  16. package/dist/tailwind-l0sNRNKZ.js.map +1 -0
  17. package/lib/components/Button.tsx +57 -0
  18. package/lib/components/Calendar.tsx +242 -0
  19. package/lib/components/ConfirmationModalDialog.tsx +115 -0
  20. package/lib/components/Modal.tsx +73 -0
  21. package/lib/components/ModalDialog.tsx +63 -0
  22. package/lib/components/Spinner.tsx +25 -0
  23. package/lib/components/SpinnerIcon.tsx +12 -0
  24. package/lib/components/datatable/DataTable.tsx +442 -0
  25. package/lib/components/datatable/DataTableServer.tsx +939 -0
  26. package/lib/components/datatable/DatatableSettings.tsx +48 -0
  27. package/lib/components/datatable/Resizable.tsx +99 -0
  28. package/lib/components/datatable/types.ts +33 -0
  29. package/lib/components/form/AutocompleteSearchBar.tsx +424 -0
  30. package/lib/components/form/AutocompleteSearchBarServer.tsx +257 -0
  31. package/lib/components/form/DateField.tsx +124 -0
  32. package/lib/components/form/DateRangeField.tsx +116 -0
  33. package/lib/components/form/FileInput.tsx +188 -0
  34. package/lib/components/form/FileInputMultiple.tsx +186 -0
  35. package/lib/components/form/FormField.tsx +371 -0
  36. package/lib/components/form/InputField.tsx +230 -0
  37. package/lib/components/form/PositionsSelectorSingle.tsx +266 -0
  38. package/lib/components/form/RadioGroup.tsx +64 -0
  39. package/lib/components/form/SelectField.tsx +267 -0
  40. package/lib/components/layout/IconInCircle.tsx +29 -0
  41. package/lib/components/layout/PageTitle.tsx +19 -0
  42. package/lib/components/layout/SectionTitle.tsx +22 -0
  43. package/lib/components/profiles/ProfileOverview.tsx +212 -0
  44. package/lib/components/ui/Calendar.tsx +68 -0
  45. package/lib/components/ui/Combobox.tsx +122 -0
  46. package/lib/components/ui/DatePicker.tsx +124 -0
  47. package/lib/components/ui/DateTimePicker.tsx +187 -0
  48. package/lib/components/ui/Dialog.tsx +118 -0
  49. package/lib/components/ui/ScrollArea.tsx +45 -0
  50. package/lib/components/ui/button.tsx +56 -0
  51. package/lib/components/ui/command.tsx +153 -0
  52. package/lib/components/ui/form.tsx +177 -0
  53. package/lib/components/ui/input.tsx +22 -0
  54. package/lib/components/ui/label.tsx +24 -0
  55. package/lib/components/ui/popover.tsx +31 -0
  56. package/lib/components/ui/radioGroup.tsx +44 -0
  57. package/lib/components/ui/select.tsx +158 -0
  58. package/lib/contexts/FederationContext.tsx +28 -0
  59. package/lib/contexts/useFederationContext.ts +4 -0
  60. package/lib/css/tailwind.css +10 -0
  61. package/lib/fonts/arial.ts +3 -0
  62. package/lib/fonts/arialBold.ts +4 -0
  63. package/lib/main.ts +64 -0
  64. package/lib/types.ts +492 -0
  65. package/lib/utils/PdfManager.ts +224 -0
  66. package/lib/utils/getFullName.tsx +83 -0
  67. package/lib/utils/getIntersectingDays.ts +28 -0
  68. package/lib/utils/handleErrors.ts +28 -0
  69. package/lib/utils/hasRightInModule.ts +17 -0
  70. package/lib/utils/hasRole.ts +12 -0
  71. package/lib/utils/utils.ts +6 -0
  72. package/lib/vite-env.d.ts +1 -0
  73. 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;