@skalfa/skalfa-component 1.0.7 → 1.0.9

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 (107) hide show
  1. package/dist/accordion/Accordion.component.js +2 -3
  2. package/dist/breadcrumb/Breadcrumb.component.js +2 -3
  3. package/dist/button/Button.component.d.ts +2 -1
  4. package/dist/button/Button.component.js +2 -2
  5. package/dist/carousel/Carousel.component.js +2 -3
  6. package/dist/chip/Chip.component.js +2 -3
  7. package/dist/contexts-mock.d.ts +2 -0
  8. package/dist/contexts-mock.js +10 -0
  9. package/dist/input/Checkbox.component.js +2 -3
  10. package/dist/input/Input.component.d.ts +3 -2
  11. package/dist/input/Input.component.js +3 -3
  12. package/dist/input/InputCheckbox.component.js +1 -1
  13. package/dist/input/InputCurrency.component.d.ts +3 -2
  14. package/dist/input/InputCurrency.component.js +3 -3
  15. package/dist/input/InputDate.component.js +4 -5
  16. package/dist/input/InputDatetime.component.d.ts +3 -2
  17. package/dist/input/InputDatetime.component.js +3 -3
  18. package/dist/input/InputDocument.component.js +7 -8
  19. package/dist/input/InputImage.component.js +3 -4
  20. package/dist/input/InputNumber.component.js +3 -4
  21. package/dist/input/InputPassword.component.js +3 -3
  22. package/dist/input/InputRadio.component.js +1 -1
  23. package/dist/input/InputTime.component.js +3 -3
  24. package/dist/input/InputValues.component.js +3 -4
  25. package/dist/input/Select.component.js +6 -7
  26. package/dist/modal/FloatingPage.component.d.ts +3 -1
  27. package/dist/modal/FloatingPage.component.js +7 -3
  28. package/dist/modal/Modal.component.js +1 -2
  29. package/dist/modal/ModalConfirm.component.d.ts +2 -2
  30. package/dist/modal/ModalConfirm.component.js +14 -12
  31. package/dist/modal/Toast.component.js +1 -2
  32. package/dist/nav/Bottombar.component.d.ts +2 -2
  33. package/dist/nav/Bottombar.component.js +7 -8
  34. package/dist/nav/Footer.component.d.ts +2 -2
  35. package/dist/nav/Footer.component.js +6 -7
  36. package/dist/nav/Headbar.component.js +2 -3
  37. package/dist/nav/Navbar.component.d.ts +2 -2
  38. package/dist/nav/Navbar.component.js +3 -4
  39. package/dist/nav/Sidebar.component.d.ts +3 -7
  40. package/dist/nav/Sidebar.component.js +14 -17
  41. package/dist/supervision/FormSupervision.component.d.ts +4 -29
  42. package/dist/supervision/FormSupervision.component.js +84 -136
  43. package/dist/supervision/TableSupervision.component.d.ts +17 -26
  44. package/dist/supervision/TableSupervision.component.js +207 -193
  45. package/dist/table/ControlBar.component.js +15 -16
  46. package/dist/table/FilterComponent.js +4 -6
  47. package/dist/table/Pagination.component.js +2 -3
  48. package/dist/table/Table.component.js +3 -4
  49. package/dist/wrap/Swipe.component.js +2 -2
  50. package/package.json +18 -10
  51. package/src/accordion/Accordion.component.tsx +87 -0
  52. package/src/breadcrumb/Breadcrumb.component.tsx +79 -0
  53. package/src/button/Button.component.tsx +89 -0
  54. package/src/card/AlertCard.component.tsx +69 -0
  55. package/src/card/Card.component.tsx +25 -0
  56. package/src/card/DashboardCard.component.tsx +44 -0
  57. package/src/card/GalleryCard.component.tsx +50 -0
  58. package/src/card/ProductCard.component.tsx +65 -0
  59. package/src/card/ProfileCard.component.tsx +71 -0
  60. package/src/carousel/Carousel.component.tsx +111 -0
  61. package/src/chip/Chip.component.tsx +38 -0
  62. package/src/contexts-mock.ts +6 -0
  63. package/src/index.ts +70 -0
  64. package/src/input/Checkbox.component.tsx +102 -0
  65. package/src/input/Input.component.tsx +334 -0
  66. package/src/input/InputCheckbox.component.tsx +174 -0
  67. package/src/input/InputCurrency.component.tsx +165 -0
  68. package/src/input/InputDate.component.tsx +355 -0
  69. package/src/input/InputDatetime.component.tsx +267 -0
  70. package/src/input/InputDocument.component.tsx +360 -0
  71. package/src/input/InputImage.component.tsx +534 -0
  72. package/src/input/InputNumber.component.tsx +193 -0
  73. package/src/input/InputOtp.component.tsx +169 -0
  74. package/src/input/InputPassword.component.tsx +245 -0
  75. package/src/input/InputRadio.component.tsx +174 -0
  76. package/src/input/InputTime.component.tsx +280 -0
  77. package/src/input/InputValues.component.tsx +70 -0
  78. package/src/input/Radio.component.tsx +98 -0
  79. package/src/input/Select.component.tsx +557 -0
  80. package/src/modal/BottomSheet.component.tsx +246 -0
  81. package/src/modal/FloatingPage.component.tsx +133 -0
  82. package/src/modal/Modal.component.tsx +94 -0
  83. package/src/modal/ModalConfirm.component.tsx +204 -0
  84. package/src/modal/Toast.component.tsx +124 -0
  85. package/src/nav/Bottombar.component.tsx +71 -0
  86. package/src/nav/Footer.component.tsx +176 -0
  87. package/src/nav/Headbar.component.tsx +33 -0
  88. package/src/nav/Navbar.component.tsx +137 -0
  89. package/src/nav/Sidebar.component.tsx +307 -0
  90. package/src/nav/Tabbar.component.tsx +61 -0
  91. package/src/nav/Wizard.component.tsx +80 -0
  92. package/src/supervision/FormSupervision.component.tsx +404 -0
  93. package/src/supervision/TableSupervision.component.tsx +653 -0
  94. package/src/table/ControlBar.component.tsx +501 -0
  95. package/src/table/FilterComponent.tsx +517 -0
  96. package/src/table/Pagination.component.tsx +152 -0
  97. package/src/table/Table.component.tsx +436 -0
  98. package/src/typography/TypographyArticle.component.tsx +26 -0
  99. package/src/typography/TypographyColumn.component.tsx +20 -0
  100. package/src/typography/TypographyContent.component.tsx +20 -0
  101. package/src/typography/TypographyTips.component.tsx +20 -0
  102. package/src/wrap/Draggable.component.tsx +303 -0
  103. package/src/wrap/Image.component.tsx +10 -0
  104. package/src/wrap/OutsideClick.component.tsx +48 -0
  105. package/src/wrap/ScrollContainer.component.tsx +107 -0
  106. package/src/wrap/ShortcutProvider.tsx +57 -0
  107. package/src/wrap/Swipe.component.tsx +121 -0
@@ -0,0 +1,653 @@
1
+ "use client"
2
+
3
+ import { ReactNode, useEffect, useMemo } from "react";
4
+ import { ApiType, cn, conversion, registry, shortcut, ShortcutHandler, UseResourceIdb, UseResourceProps, useResponsive, useTable } from "@utils";
5
+ import { useToggleContext } from "@contexts";
6
+ import { FloatingPageComponent, FloatingPageProps, ButtonComponent, TableColumnType, TableComponent, FormSupervisionComponent, FormType, ModalConfirmComponent, TypographyColumnComponent, ButtonProps, ModalConfirmProps, TableProps, ControlBarOptionType, SwipeActionType, BottomSheetProps } from "../";
7
+ import { useLang } from "@skalfa/skalfa-lang";
8
+
9
+ const ExportExcel = registry.get("ExportExcel");
10
+ const ImportExcel = registry.get("ImportExcel");
11
+
12
+
13
+ export interface TableSupervisionColumnProps {
14
+ selector : string;
15
+ label ?: string;
16
+ width ?: string;
17
+ sortable ?: boolean;
18
+ searchable ?: boolean;
19
+ filterable ?: boolean | {
20
+ type : "text" | "number" | "currency" | "date";
21
+ } | {
22
+ type : "select";
23
+ options : { label: string; value: any }[];
24
+ };
25
+ accessCode ?: string;
26
+ item ?: (data: any) => string | ReactNode;
27
+ tip ?: string | ((data: any) => string);
28
+ };
29
+
30
+ export interface TableSupervisionFormProps {
31
+ fields : string[] | (FormType & { visibility?: "*" | "create" | "update" })[];
32
+ defaultValue ?: (item: Record<string, any> | null) => Promise<Record<string, any>> | Record<string, any>;
33
+ payload ?: (values: any) => Promise<Record<string, any>> | object;
34
+ modalControl ?: Omit<FloatingPageProps, "show" | "onClose" | "children"> | Omit<BottomSheetProps, "show" | "onClose" | "children">;
35
+ contentType ?: "application/json" | "multipart/form-data";
36
+ };
37
+
38
+ export type TableSupervisionDetailProps = boolean
39
+ | (
40
+ | string
41
+ | { label: string, item: string | ((data: Record<string, any>) => ReactNode), conversion?: keyof typeof conversion }
42
+ | ((data: Record<string, any>) => ReactNode)
43
+ )[]
44
+ | ((data: Record<string, any>) => ReactNode);
45
+
46
+ export type TableSupervisionProps = {
47
+ fetchControl : UseResourceProps;
48
+ title ?: string;
49
+ id ?: string;
50
+ accessCode ?: number;
51
+ urlParam ?: boolean | { compressed ?: boolean }
52
+ onRowClick ?: (data: Record<string, any>) => void;
53
+ columnControl ?: string[] | TableSupervisionColumnProps[];
54
+ formControl ?: TableSupervisionFormProps;
55
+ detailControl ?: TableSupervisionDetailProps;
56
+ actionControl ?: boolean | (
57
+ | 'EDIT' | 'DELETE' | {
58
+ label : string,
59
+ modal ?: ModalConfirmProps,
60
+ button ?: ButtonProps,
61
+ shortcut ?: { key: string, description: string },
62
+ } | ((
63
+ row : object,
64
+ setModal ?: (type: "EDIT" | "DELETE") => void,
65
+ setDataSelected ?: () => void,
66
+ setShortcut ?: (key: string, handler: ShortcutHandler, description?: string) => void,
67
+ size ?: string,
68
+ ) => ReactNode)
69
+ )[];
70
+ block ?: boolean,
71
+ noIndex ?: boolean;
72
+ actionBulkingControl ?: TableProps["actionBulking"],
73
+ controlBar ?: (ControlBarOptionType | "CREATE" | "IMPORT" | "EXPORT" | "PRINT")[];
74
+ responsiveControl ?: {
75
+ mobile ?: boolean | {
76
+ item ?: (item: Record<string, any>, key: number) => ReactNode,
77
+ leftActionControl ?: Omit<SwipeActionType, "onAction"> & { onAction?: (item: Record<string, any>, key?: number) => void },
78
+ rightActionControl ?: Omit<SwipeActionType, "onAction"> & { onAction?: (item: Record<string, any>, key?: number) => void },
79
+ }
80
+ }
81
+ };
82
+
83
+
84
+
85
+ export function TableSupervisionComponent({
86
+ title,
87
+ id,
88
+ fetchControl,
89
+ columnControl,
90
+ formControl,
91
+ onRowClick,
92
+ detailControl,
93
+ actionControl,
94
+ actionBulkingControl,
95
+ block,
96
+ controlBar,
97
+ noIndex,
98
+ responsiveControl,
99
+ urlParam,
100
+ }: TableSupervisionProps) {
101
+ const l = useLang();
102
+
103
+ const { tableKey, tableControl, data, selected, setSelected, checks, setChecks, reset, focus, setFocus } = useTable(fetchControl, id, title, (urlParam || true))
104
+ const { setToggle, toggle } = useToggleContext()
105
+ const { isSm } = useResponsive();
106
+
107
+ const toggleKey = useMemo(() => conversion.strSnake(tableKey).toUpperCase(), [tableKey])
108
+
109
+
110
+ useEffect(() => {
111
+ if(data?.data?.length && !toggle[`MODAL_DELETE_${toggleKey}`] && !toggle[`MODAL_DELETE_${toggleKey}`] && !toggle[`MODAL_SHOW_${toggleKey}`]) {
112
+ shortcut.register("arrowdown", () => {
113
+ const max = data?.data?.length - 1;
114
+ setFocus(focus == null ? 0 : focus >= max ? max : (focus + 1))
115
+ }, "Pilih data kebawah")
116
+
117
+ shortcut.register("arrowup", () => {
118
+ setFocus(focus == null ? 0 : focus <= 0 ? 0 : (focus - 1))
119
+ }, "Pilih data keatas")
120
+
121
+ if(focus != null) {
122
+ shortcut.register("delete", () => {
123
+ setSelected(data?.data?.at(focus))
124
+ setToggle(`MODAL_DELETE_${toggleKey}`)
125
+ }, "Delete data yang dipilih")
126
+
127
+ shortcut.register(" ", () => {
128
+ setSelected(data?.data?.at(focus))
129
+ setToggle(`MODAL_FORM_${toggleKey}`)
130
+ }, "Edit data yang dipilih")
131
+
132
+ shortcut.register("enter", () => {
133
+ setSelected(data?.data?.at(focus))
134
+ setToggle(`MODAL_SHOW_${toggleKey}`)
135
+ }, "Detail data yang dipilih")
136
+
137
+ shortcut.register("escape", () => {
138
+ setFocus(null)
139
+ }, "Kembali")
140
+ }
141
+ }
142
+
143
+ return () => {
144
+ shortcut.unregister("arrowdown")
145
+ shortcut.unregister("arrowup")
146
+ shortcut.unregister("delete")
147
+ shortcut.unregister(" ")
148
+ shortcut.unregister("enter")
149
+ shortcut.unregister("escape")
150
+ }
151
+ }, [data?.data, actionControl, focus, toggle[`MODAL_DELETE_${toggleKey}`], toggle[`MODAL_DELETE_${toggleKey}`], toggle[`MODAL_SHOW_${toggleKey}`]])
152
+
153
+
154
+ // ============================
155
+ // ## Column preparation
156
+ // ============================
157
+ const columns = useMemo(() => {
158
+ return columnControl?.length ? columnControl.map((col) => {
159
+ if (typeof col === "string") {
160
+ return {
161
+ selector : col,
162
+ label : col,
163
+ };
164
+ } else {
165
+ return { ...col };
166
+ }
167
+ })
168
+ : data?.columns || data?.data?.at(0) ? Object.keys(data.data[0]).map((col) => {
169
+ return {
170
+ selector : col,
171
+ label : col,
172
+ };
173
+ })
174
+ : [];
175
+ }, [columnControl, data]);
176
+
177
+
178
+
179
+ const renderTableAction = (
180
+ actions : TableSupervisionProps["actionControl"],
181
+ item ?: Record<string, any>,
182
+ options ?: {size?: ButtonProps['size'], className?: string}
183
+ ) => {
184
+ return (
185
+ <>
186
+ <div className={cn("flex items-center gap-2", options?.className)}>
187
+ {(Array.isArray(actions) ? actions : (actions || actions == undefined) ? ['EDIT', "DELETE"] : [])?.map((action, key) => {
188
+ if(action == "EDIT") {
189
+ return (
190
+ <ButtonComponent
191
+ key={key}
192
+ icon={"solid/edit"}
193
+ label={l.base.edit ? l.base.edit() : "Edit"}
194
+ variant="outline"
195
+ paint="warning"
196
+ size={options?.size || "xs"}
197
+ rounded
198
+ onClick={() => {
199
+ setToggle(`MODAL_FORM_${toggleKey}`);
200
+ item && setSelected?.(item);
201
+ }}
202
+ />
203
+ )
204
+ }
205
+
206
+ if(action == "DELETE") {
207
+ return (
208
+ <ButtonComponent
209
+ key={key}
210
+ icon={"solid/trash"}
211
+ label={l.base.delete ? l.base.delete() : "Delete"}
212
+ variant="outline"
213
+ paint="danger"
214
+ size={options?.size || "xs"}
215
+ rounded
216
+ onClick={() => {
217
+ setToggle(`MODAL_DELETE_${toggleKey}`);
218
+ item && setSelected?.(item);
219
+ }}
220
+ />
221
+ )
222
+ }
223
+
224
+ if(typeof action == "object") {
225
+ <ButtonComponent
226
+ key={`action-object-${key}`}
227
+ label={action?.button?.label || action?.label}
228
+ variant={action?.button?.variant || "outline"}
229
+ paint={action?.button?.paint || "primary"}
230
+ size={action?.button?.size || options?.size || "xs"}
231
+ rounded={action?.button?.rounded || true}
232
+ onClick={() => {
233
+ if (action?.button?.onClick) {
234
+ action?.button?.onClick(item)
235
+ } else {
236
+ setToggle(`MODAL_${conversion.strSnake(action?.label).toUpperCase()}_${toggleKey}`);
237
+ item && setSelected?.(item);
238
+ }
239
+ }}
240
+ {...action.button}
241
+ />
242
+ }
243
+
244
+ if(typeof action == "function") {
245
+ return (
246
+ <span key={`action-fn-${key}`}>
247
+ {action(item || {}, (type: "EDIT" | "DELETE") => {
248
+ if(type == "EDIT") {
249
+ setToggle(`MODAL_FORM_${toggleKey}`);
250
+ item && setSelected?.(item);
251
+ }
252
+
253
+ if (type == "DELETE") {
254
+ setToggle(`MODAL_DELETE_${toggleKey}`);
255
+ item && setSelected?.(item);
256
+ }
257
+ }, () => item && setSelected?.(item), () => {}, options?.size)}
258
+ </span>
259
+ )
260
+ }
261
+
262
+ return <span key={`action-default-${key}`}></span>;
263
+ })}
264
+ </div>
265
+ </>
266
+ )
267
+ }
268
+
269
+
270
+ // ============================
271
+ // ## Data table preparation
272
+ // ============================
273
+ const dataTables = useMemo(() => {
274
+ return data?.data?.map((row: object) => {
275
+ return {
276
+ ...row,
277
+ action: renderTableAction(actionControl, row),
278
+ };
279
+ });
280
+ }, [actionControl, data]);
281
+
282
+
283
+ // ============================
284
+ // ## Render detail page
285
+ // ============================
286
+ const detailPage = useMemo(() => {
287
+ if (!toggle[`MODAL_SHOW_${toggleKey}`]) return null;
288
+ return (
289
+ <div className="p-4">
290
+ <div className={cn(
291
+ "flex flex-col gap-y-4",
292
+ )}>
293
+ {!!selected && (typeof detailControl === "object" && detailControl?.length ? detailControl?.map((column, key) => {
294
+ if (typeof column === "string") {
295
+ return (<TypographyColumnComponent
296
+ key={key}
297
+ title={columns?.find((c) => c.selector == column)?.label}
298
+ content={selected[column]}
299
+ />)
300
+ } else if (typeof column === "object") {
301
+ return (<TypographyColumnComponent
302
+ key={key}
303
+ title={column?.label}
304
+ content={typeof column?.item === "string" ? selected[column?.item] : column?.item(selected)}
305
+ />)
306
+ } else {
307
+ return column?.(selected)
308
+ }
309
+ }) : typeof detailControl == "function" ? detailControl(selected) : columns?.map((column, key) => (
310
+ <TypographyColumnComponent
311
+ key={key}
312
+ title={column.label}
313
+ content={selected[column.selector]}
314
+ />
315
+ )))}
316
+ </div>
317
+ </div>
318
+ )
319
+ }, [selected, detailControl]);
320
+
321
+
322
+
323
+
324
+ // ============================
325
+ // ## Form preparation
326
+ // ============================
327
+ const fields = useMemo(() => {
328
+ return formControl?.fields?.length ? formControl?.fields.map((form) => {
329
+ return typeof form === "string" ? {
330
+ col : 12,
331
+ type : "text",
332
+ construction : {
333
+ name : form,
334
+ label : form,
335
+ },
336
+ } : { ...form };
337
+ }) : data?.forms || data?.columns || columnControl?.map((col) => {
338
+ return {
339
+ col : 12,
340
+ type : "text",
341
+ construction : {
342
+ name : typeof col == "string" ? col : col?.selector,
343
+ label : typeof col == "string" ? col : col?.label,
344
+ placeholder : `Masukkan ${ typeof col == "string" ? col : col?.label}...`,
345
+ },
346
+ };
347
+ }) || (data?.data?.at(0) ? Object.keys(data.data[0]).map((col) => {
348
+ return {
349
+ col : 12,
350
+ type : "text",
351
+ construction : {
352
+ name : col,
353
+ label : col,
354
+ placeholder : `Masukkan ${col}...`,
355
+ },
356
+ };
357
+ })
358
+ : []);
359
+ }, [formControl, data]);
360
+
361
+
362
+
363
+ // ============================
364
+ // ## Render form page
365
+ // ============================
366
+ const formPage = useMemo(async () => {
367
+ return (
368
+ <FormSupervisionComponent
369
+ submitControl={(fetchControl as ApiType).path ? {
370
+ path: `${(fetchControl as ApiType).path}/${(selected as { id: number })?.id || "" }`,
371
+ method: !(selected as { id: number })?.id ? "POST" : "PUT",
372
+ } : (fetchControl as ApiType).url ? {
373
+ url: `${(fetchControl as ApiType).url}/${(selected as { id: number })?.id || ""}`,
374
+ method: !(selected as { id: number })?.id ? "POST" : "PUT",
375
+ } : { idb: (fetchControl as ({ idb: UseResourceIdb }))?.idb }
376
+ }
377
+ fields={fields?.filter((f: any) => f.visibility ? !(selected as { id: number })?.id ? ["*", "create"]?.includes(f.visibility) : ["*", "update"]?.includes(f.visibility) : true) as FormType[]}
378
+ defaultValue={formControl?.defaultValue ? await formControl?.defaultValue(selected || null) : selected}
379
+ payload={formControl?.payload}
380
+ onSuccess={() => {
381
+ reset();
382
+ setTimeout(() => { setToggle(`MODAL_FORM_${toggleKey}`, false) }, 900);
383
+ }}
384
+ />
385
+ )
386
+ }, [selected, fetchControl, formControl]);
387
+
388
+
389
+
390
+ useEffect(() => {
391
+ if(toggle[`REFRESH_${toggleKey}`] != undefined) reset();
392
+ }, [toggle[`REFRESH_${toggleKey}`]]);
393
+
394
+
395
+ return (
396
+ <>
397
+ {title && <h1 className="text-lg lg:text-xl font-bold mb-2 lg:mb-4">{title}</h1>}
398
+
399
+ <TableComponent
400
+ id={tableKey}
401
+ controlBar={controlBar?.map((cb) => {
402
+ if (cb == "CREATE") {
403
+ if (isSm) return
404
+ return (
405
+ <div className="pl-1.5 pr-3 mr-2 border-r" key="button-add">
406
+ <ButtonComponent
407
+ icon={"solid/plus"}
408
+ label="Tambah Data"
409
+ size="sm"
410
+ onClick={() => {
411
+ setToggle(`MODAL_FORM_${toggleKey}`)
412
+ setSelected(null)
413
+ }}
414
+ />
415
+ </div>
416
+ )
417
+ }
418
+
419
+ if (cb == "IMPORT") {
420
+ return (
421
+ <div className="px-1.5 rounded-md relative" key={"import"}>
422
+ <ButtonComponent
423
+ icon={"solid/file-excel"}
424
+ label="Import"
425
+ variant="outline"
426
+ className="!text-foreground"
427
+ onClick={() => setToggle(`MODAL_IMPORT_${toggleKey}`)}
428
+ size="sm"
429
+ />
430
+ </div>
431
+ )
432
+ }
433
+
434
+ if (cb == "EXPORT") {
435
+ return (
436
+ <div className="px-1.5 rounded-md relative" key={"export-excel"}>
437
+ <ButtonComponent
438
+ icon={"solid/file-excel"}
439
+ label="Export"
440
+ variant="outline"
441
+ className="!text-foreground"
442
+ onClick={() => setToggle(`MODAL_EXPORT_${toggleKey}`)}
443
+ size="sm"
444
+ />
445
+ </div>
446
+ )
447
+ }
448
+
449
+ if (cb == "PRINT") {
450
+ return (
451
+ <div className="px-1.5 rounded-md relative" key={"export-pdf"}>
452
+ <ButtonComponent
453
+ icon={"solid/file-pdf"}
454
+ label="Cetak"
455
+ variant="outline"
456
+ className="!text-foreground"
457
+ onClick={() => setToggle(`MODAL_PRINT_${toggleKey}`)}
458
+ size="sm"
459
+ />
460
+ </div>
461
+ )
462
+ }
463
+
464
+ return cb
465
+ }) || [
466
+ ...(!isSm ? [
467
+ <div className="pl-1.5 pr-3 mr-2 border-r" key="button-add">
468
+ <ButtonComponent
469
+ icon={"solid/plus"}
470
+ label="Tambah Data"
471
+ size="sm"
472
+ onClick={() => {
473
+ setToggle(`MODAL_FORM_${toggleKey}`)
474
+ setSelected(null)
475
+ }}
476
+ />
477
+ </div>
478
+ ] : []),
479
+ "SEARCH",
480
+ ...(columns?.filter((c) => !!(c as { filterable?: any }).filterable)?.length ? ["FILTER"] : []),
481
+ ...(columns?.filter((c) => !!(c as { sortable?: any }).sortable)?.length ? ["SORT"] : []),
482
+ "SELECTABLE", "REFRESH",
483
+ ]}
484
+ columns={columns as TableColumnType[]}
485
+ data={dataTables}
486
+ onRowClick={onRowClick ? onRowClick : detailControl != false ? (e) => {
487
+ setToggle(`MODAL_SHOW_${toggleKey}`)
488
+ setSelected(e)
489
+ } : undefined}
490
+ actionBulking={actionBulkingControl}
491
+ checks={checks || []}
492
+ onChangeChecks={(e) => setChecks(e)}
493
+ block={block}
494
+ focus={focus}
495
+ noIndex={noIndex}
496
+ responsiveControl={responsiveControl ? {
497
+ mobile: responsiveControl?.mobile ? {
498
+ ...(typeof responsiveControl.mobile === 'object' ? responsiveControl.mobile : {}),
499
+ leftActionControl: (typeof responsiveControl.mobile === 'object' && responsiveControl.mobile.leftActionControl !== undefined) ? responsiveControl.mobile.leftActionControl : (Array.isArray(actionControl) ? actionControl : (actionControl || actionControl == undefined) ? ['EDIT', "DELETE"] : []).includes('EDIT') ? {
500
+ icon: "solid/edit",
501
+ onAction: (item) => {
502
+ setToggle(`MODAL_FORM_${toggleKey}`);
503
+ item && setSelected?.(item);
504
+ }
505
+ } : undefined,
506
+ rightActionControl: (typeof responsiveControl.mobile === 'object' && responsiveControl.mobile.rightActionControl !== undefined) ? responsiveControl.mobile.rightActionControl : (Array.isArray(actionControl) ? actionControl : (actionControl || actionControl == undefined) ? ['EDIT', "DELETE"] : []).includes('DELETE') ? {
507
+ icon: "solid/trash",
508
+ onAction: (item) => {
509
+ setToggle(`MODAL_DELETE_${toggleKey}`);
510
+ item && setSelected?.(item);
511
+ }
512
+ } : undefined
513
+ } : undefined,
514
+ } : undefined}
515
+ {...tableControl}
516
+ />
517
+
518
+ {controlBar?.find((cb) => cb == "CREATE") && (
519
+ <ButtonComponent
520
+ icon={"solid/plus"}
521
+ className="fixed bottom-24 right-4 w-14 h-14 z-20 md:hidden"
522
+ size="lg"
523
+ rounded
524
+ onClick={() => {
525
+ setToggle(`MODAL_FORM_${toggleKey}`)
526
+ setSelected(null)
527
+ }}
528
+ />
529
+ )}
530
+
531
+
532
+ <FloatingPageComponent
533
+ show={!!toggle[`MODAL_SHOW_${toggleKey}`]}
534
+ onClose={() => setToggle(`MODAL_SHOW_${toggleKey}`, false)}
535
+ title="Detail"
536
+ className="bg-background"
537
+ footer={renderTableAction(actionControl, undefined, {className: isSm ? "justify-end p-2 bg-background" : "justify-end", size: isSm ? "sm" : "md"})}
538
+ >
539
+ {detailPage}
540
+ </FloatingPageComponent>
541
+
542
+
543
+ <FloatingPageComponent
544
+ show={!!toggle[`MODAL_FORM_${toggleKey}`]}
545
+ onClose={() => setToggle(`MODAL_FORM_${toggleKey}`, false)}
546
+ title={!!selected ? "Ubah Data" : "Tambah Data"}
547
+ className={cn("bg-white", formControl?.modalControl?.className)}
548
+ >
549
+ <div className="p-4">
550
+ {formPage}
551
+ </div>
552
+ </FloatingPageComponent>
553
+
554
+
555
+ <FloatingPageComponent
556
+ show={!!toggle[`MODAL_EXPORT_${toggleKey}`]}
557
+ onClose={() => setToggle(`MODAL_EXPORT_${toggleKey}`, false)}
558
+ title="Export Ke Excel"
559
+ className="bg-white md:w-[1200px] max-w-[1200px]"
560
+ >
561
+ <ExportExcel
562
+ fetchControl={fetchControl}
563
+ filename={"Export - " + title}
564
+ columnControl={columns?.map((cc) => ({
565
+ label: cc.label || "",
566
+ selector: cc.selector || "",
567
+ }))}
568
+ />
569
+ </FloatingPageComponent>
570
+
571
+
572
+ <FloatingPageComponent
573
+ show={!!toggle[`MODAL_IMPORT_${toggleKey}`]}
574
+ onClose={() => setToggle(`MODAL_IMPORT_${toggleKey}`, false)}
575
+ title="Import Dari Excel"
576
+ className="bg-white md:w-[1200px] max-w-[1200px]"
577
+ >
578
+ <ImportExcel
579
+ submitControl={{ path: (fetchControl as ApiType)?.path + "/import" }}
580
+ columnControl={columns?.map((cc) => ({
581
+ label: cc.label || "",
582
+ selector: cc.selector || "",
583
+ }))}
584
+ />
585
+ </FloatingPageComponent>
586
+
587
+
588
+ {/* <FloatingPageComponent
589
+ show={!!toggle[`MODAL_PRINT_${toggleKey}`]}
590
+ onClose={() => setToggle(`MODAL_PRINT_${toggleKey}`, false)}
591
+ title="Print PDF"
592
+ className="bg-white md:w-[1200px] max-w-[1200px]"
593
+ >
594
+ <PrintTable
595
+ fetchControl={fetchControl}
596
+ columnControl={columns?.map((cc) => ({
597
+ label: cc.label || "",
598
+ selector: cc.selector || "",
599
+ }))}
600
+ title={"Print - " + title}
601
+ />
602
+ </FloatingPageComponent> */}
603
+
604
+
605
+ <ModalConfirmComponent
606
+ show={!!toggle[`MODAL_DELETE_${toggleKey}`]}
607
+ onClose={() => setToggle(`MODAL_DELETE_${toggleKey}`, false)}
608
+ title={<>Menghapus Data <span className="font-semibold">&quot;{selected?.[columns?.at(0)?.selector || ""]}&quot;</span> ?</>}
609
+ submitControl={{
610
+ onSubmit: {
611
+ ...((fetchControl as ApiType).path
612
+ ? {path: `${(fetchControl as ApiType).path}/${(selected as { id: number })?.id || ""}`}
613
+ : (fetchControl as ApiType).url ? {url: `${(fetchControl as ApiType).url}/${(selected as { id: number })?.id || ""}`}
614
+ : { idb: { ...(fetchControl as ({ idb: UseResourceIdb }))?.idb, id: (selected as { id: number })?.id || "" }}
615
+ ),
616
+ method: "DELETE",
617
+ },
618
+ onSuccess: () => {
619
+ reset();
620
+ setTimeout(() => { setToggle(`MODAL_DELETE_${toggleKey}`, false) }, 900);
621
+ },
622
+ }}
623
+ />
624
+
625
+ {actionControl && Array.isArray(actionControl) && actionControl.filter((ac) => typeof ac == "object")?.map((ac, acKey) => {
626
+ const submitControl = ac.modal?.submitControl?.onSubmit as ApiType;
627
+ return (
628
+ <ModalConfirmComponent
629
+ key={acKey}
630
+ show={!!toggle[`MODAL_${conversion.strSnake(ac.label).toUpperCase()}_${toggleKey}`]}
631
+ onClose={() => setToggle(`MODAL_${conversion.strSnake(ac.label).toUpperCase()}_${toggleKey}`, false)}
632
+ title={ac?.modal?.title || ac.label}
633
+ submitControl={{
634
+ onSubmit: {
635
+ ...(submitControl?.path
636
+ ? {path: `${submitControl?.path}/${(selected as { id: number })?.id || ""}`}
637
+ : {url: `${submitControl?.url}/${(selected as { id: number })?.id || ""}`}
638
+ ),
639
+ method: submitControl?.method || "POST",
640
+ },
641
+ onSuccess: () => {
642
+ reset();
643
+ setToggle(`MODAL_${conversion.strSnake(ac.label).toUpperCase()}_${conversion.strSnake(tableKey).toUpperCase()}`, false);
644
+ setSelected(null)
645
+ ac.modal?.submitControl?.onSuccess?.()
646
+ },
647
+ }}
648
+ >{ac.modal?.children}</ModalConfirmComponent>
649
+ )
650
+ })}
651
+ </>
652
+ );
653
+ }