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