@skalfa/skalfa-app 1.0.3 → 1.0.6

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 (90) hide show
  1. package/.env.example +8 -16
  2. package/app/auth/edit/page.tsx +1 -1
  3. package/app/auth/login/page.tsx +1 -1
  4. package/app/auth/me/page.tsx +1 -1
  5. package/app/auth/register/page.tsx +1 -1
  6. package/app/auth/verify/page.tsx +1 -1
  7. package/app/dashboard/layout.tsx +2 -2
  8. package/app/dashboard/page.tsx +1 -1
  9. package/app/index.ts +1 -0
  10. package/app/layout.tsx +2 -4
  11. package/app/page.tsx +2 -2
  12. package/bun.lock +7 -2
  13. package/components/index.ts +1 -3
  14. package/package.json +10 -7
  15. package/styles/components.css +1392 -0
  16. package/styles/globals.css +40 -175
  17. package/styles/utilities.css +37 -0
  18. package/tsconfig.json +4 -2
  19. package/utils/commands/skalfa.ts +1 -1
  20. package/components/base.components/accordion/Accordion.component.tsx +0 -82
  21. package/components/base.components/breadcrumb/Breadcrumb.component.tsx +0 -80
  22. package/components/base.components/button/Button.component.tsx +0 -91
  23. package/components/base.components/button/IconButton.component.tsx +0 -88
  24. package/components/base.components/button/button.decorate.ts +0 -82
  25. package/components/base.components/card/AlertCard.component.tsx +0 -69
  26. package/components/base.components/card/Card.component.tsx +0 -25
  27. package/components/base.components/card/DashboardCard.component.tsx +0 -44
  28. package/components/base.components/card/GalleryCard.component.tsx +0 -50
  29. package/components/base.components/card/ProductCard.component.tsx +0 -65
  30. package/components/base.components/card/ProfileCard.component.tsx +0 -71
  31. package/components/base.components/carousel/Carousel.component.tsx +0 -113
  32. package/components/base.components/chip/Chip.component.tsx +0 -39
  33. package/components/base.components/document/DocumentViewer.component.tsx +0 -164
  34. package/components/base.components/document/ExportExcel.component.tsx +0 -340
  35. package/components/base.components/document/ImportExcel.component.tsx +0 -315
  36. package/components/base.components/document/PrintTable.component.tsx +0 -204
  37. package/components/base.components/document/RenderPDF.component.tsx +0 -416
  38. package/components/base.components/index.ts +0 -85
  39. package/components/base.components/input/Checkbox.component.tsx +0 -109
  40. package/components/base.components/input/Input.component.tsx +0 -332
  41. package/components/base.components/input/InputCheckbox.component.tsx +0 -174
  42. package/components/base.components/input/InputCurrency.component.tsx +0 -163
  43. package/components/base.components/input/InputDate.component.tsx +0 -352
  44. package/components/base.components/input/InputDatetime.component.tsx +0 -260
  45. package/components/base.components/input/InputDocument.component.tsx +0 -352
  46. package/components/base.components/input/InputImage.component.tsx +0 -533
  47. package/components/base.components/input/InputMap.component.tsx +0 -318
  48. package/components/base.components/input/InputNumber.component.tsx +0 -192
  49. package/components/base.components/input/InputOtp.component.tsx +0 -169
  50. package/components/base.components/input/InputPassword.component.tsx +0 -236
  51. package/components/base.components/input/InputRadio.component.tsx +0 -175
  52. package/components/base.components/input/InputTime.component.tsx +0 -276
  53. package/components/base.components/input/InputValues.component.tsx +0 -68
  54. package/components/base.components/input/Radio.component.tsx +0 -102
  55. package/components/base.components/input/Select.component.tsx +0 -541
  56. package/components/base.components/modal/BottomSheet.component.tsx +0 -246
  57. package/components/base.components/modal/FloatingPage.component.tsx +0 -104
  58. package/components/base.components/modal/Modal.component.tsx +0 -96
  59. package/components/base.components/modal/ModalConfirm.component.tsx +0 -218
  60. package/components/base.components/modal/Toast.component.tsx +0 -126
  61. package/components/base.components/nav/Bottombar.component.tsx +0 -116
  62. package/components/base.components/nav/Footer.component.tsx +0 -144
  63. package/components/base.components/nav/Headbar.component.tsx +0 -104
  64. package/components/base.components/nav/Navbar.component.tsx +0 -100
  65. package/components/base.components/nav/Sidebar.component.tsx +0 -301
  66. package/components/base.components/nav/Tabbar.component.tsx +0 -60
  67. package/components/base.components/nav/Wizard.component.tsx +0 -73
  68. package/components/base.components/supervision/FormSupervision.component.tsx +0 -434
  69. package/components/base.components/supervision/TableSupervision.component.tsx +0 -697
  70. package/components/base.components/table/ControlBar.component.tsx +0 -497
  71. package/components/base.components/table/FilterComponent.tsx +0 -518
  72. package/components/base.components/table/Pagination.component.tsx +0 -159
  73. package/components/base.components/table/Table.component.tsx +0 -469
  74. package/components/base.components/typography/TypographyArticle.component.tsx +0 -26
  75. package/components/base.components/typography/TypographyColumn.component.tsx +0 -20
  76. package/components/base.components/typography/TypographyContent.component.tsx +0 -20
  77. package/components/base.components/typography/TypographyTips.component.tsx +0 -20
  78. package/components/base.components/wrap/Draggable.component.tsx +0 -303
  79. package/components/base.components/wrap/IDBProvider.tsx +0 -12
  80. package/components/base.components/wrap/Image.component.tsx +0 -10
  81. package/components/base.components/wrap/OutsideClick.component.tsx +0 -48
  82. package/components/base.components/wrap/ScrollContainer.component.tsx +0 -104
  83. package/components/base.components/wrap/ShortcutProvider.tsx +0 -57
  84. package/components/base.components/wrap/Swipe.component.tsx +0 -93
  85. package/components/construct.components/example.tsx +0 -1
  86. package/components/construct.components/index.ts +0 -5
  87. package/components/structure.components/example.tsx +0 -1
  88. package/components/structure.components/index.ts +0 -5
  89. package/schema/idb/app.schema.ts +0 -9
  90. package/schema/index.ts +0 -5
@@ -1,164 +0,0 @@
1
- "use client"
2
-
3
- import { useEffect, useMemo, useRef, useState } from "react";
4
- import Image from "next/image";
5
- import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
6
- import { faFilePdf, faFileImage, faFileWord, faFileExcel, faFile } from "@fortawesome/free-solid-svg-icons";
7
- import { cn } from "@/utils";
8
-
9
-
10
-
11
- type Props = {
12
- file ?: File | string | null;
13
- width ?: number;
14
- height ?: number;
15
- mode ?: "full" | "thumb";
16
- };
17
-
18
-
19
-
20
- export const DocumentViewerIcon = (ext: string) => {
21
- switch (ext) {
22
- case "jpg":
23
- case "jpeg":
24
- case "png":
25
- case "webp":
26
- return faFileImage;
27
- case "pdf":
28
- return faFilePdf;
29
- case "doc":
30
- case "docx":
31
- return faFileWord;
32
- case "xls":
33
- case "xlsx":
34
- return faFileExcel;
35
- default:
36
- return faFile;
37
- }
38
- };
39
-
40
-
41
-
42
- export function DocumentViewerComponent({ file, width, height, mode = "full" }: Props) {
43
- const [fileUrl, setFileUrl] = useState<string | null>(null);
44
- const canvasRef = useRef<HTMLCanvasElement>(null);
45
-
46
- useEffect(() => {
47
- if (!file) {
48
- setFileUrl(null);
49
- return;
50
- }
51
-
52
- if (typeof file === "string") {
53
- setFileUrl(file);
54
- return;
55
- }
56
-
57
- const objectUrl = URL.createObjectURL(file);
58
- setFileUrl(objectUrl);
59
- return () => URL.revokeObjectURL(objectUrl);
60
- }, [file]);
61
-
62
- const extension = useMemo(() => {
63
- if (!file) return null;
64
- if (typeof file === "string") {
65
- const clean = file.split("?")[0].split("#")[0];
66
- return clean.split(".").pop()?.toLowerCase() || null;
67
- }
68
- return file.name.split(".").pop()?.toLowerCase() || null;
69
- }, [file]);
70
-
71
- const isImage = ["jpg", "jpeg", "png", "webp"].includes(extension || "");
72
- const isPdf = extension === "pdf";
73
-
74
- const renderTaskRef = useRef<any>(null);
75
-
76
- useEffect(() => {
77
- if (!file || !isPdf || !fileUrl || !canvasRef.current) return;
78
-
79
- let cancelled = false;
80
-
81
- const renderPdf = async () => {
82
- const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
83
- pdfjs.GlobalWorkerOptions.workerSrc = "/pdf.worker.min.mjs";
84
-
85
- if (renderTaskRef.current) {
86
- try {
87
- await renderTaskRef.current.cancel();
88
- } catch {}
89
- renderTaskRef.current = null;
90
- }
91
-
92
- let loadingTask;
93
-
94
- if (typeof file === "string") {
95
- loadingTask = pdfjs.getDocument(fileUrl);
96
- } else {
97
- const buf = await file.arrayBuffer();
98
- const uint8 = new Uint8Array(buf);
99
- loadingTask = pdfjs.getDocument({ data: uint8 });
100
- }
101
-
102
- const pdf = await loadingTask.promise;
103
- if (cancelled) return;
104
-
105
- const page = await pdf.getPage(1);
106
- if (cancelled) return;
107
-
108
- const scale = mode === "thumb" ? 0.35 : 1;
109
- const viewport = page.getViewport({ scale });
110
-
111
- const canvas = canvasRef.current;
112
- if (!canvas) return;
113
-
114
- const ctx = canvas.getContext("2d");
115
- if (!ctx) return;
116
-
117
- canvas.width = viewport.width;
118
- canvas.height = viewport.height;
119
-
120
- const renderTask = page.render({ canvasContext: ctx, viewport, canvas });;
121
- renderTaskRef.current = renderTask;
122
-
123
- await renderTask.promise;
124
- renderTaskRef.current = null;
125
- };
126
-
127
- renderPdf();
128
-
129
- return () => {
130
- cancelled = true;
131
- if (renderTaskRef.current) {
132
- try {
133
- renderTaskRef.current.cancel();
134
- } catch {}
135
- renderTaskRef.current = null;
136
- }
137
- };
138
- }, [isPdf, fileUrl, mode]);
139
-
140
- if (!fileUrl) return null;
141
-
142
-
143
- return (
144
- <div
145
- style={{
146
- width: width ? width : "100%",
147
- height: height ? height : "100%",
148
- overflow: "hidden",
149
- position: "relative",
150
- }}
151
- >
152
- {isImage && <Image src={fileUrl} alt="document" fill className={mode === "thumb" ? "object-cover" : "object-contain"}/>}
153
-
154
- {isPdf && <canvas ref={canvasRef} className={cn("block w-full h-full", mode === "thumb" ? "object-cover" : "object-contain")} />}
155
-
156
- {!isImage && !isPdf && (
157
- <div className={cn("w-full h-full flex flex-col items-center justify-center opacity-50 gap-4")}>
158
- <FontAwesomeIcon icon={DocumentViewerIcon(extension || "")} className={mode !== "thumb" ? "text-3xl" : "text-lg"} />
159
- {mode !== "thumb" && file instanceof File && (<p className="text-center text-sm">{file.name}</p>)}
160
- </div>
161
- )}
162
- </div>
163
- );
164
- }
@@ -1,340 +0,0 @@
1
- "use client";
2
-
3
- import ExcelJS from "exceljs";
4
- import { useEffect, useMemo, useState } from "react";
5
- import { faArrowLeft, faArrowRight, faEdit, faPlus, faTimes } from "@fortawesome/free-solid-svg-icons";
6
- import { api, FetchControlType, useTable } from "@utils";
7
- import { TableComponent, ButtonComponent, IconButtonComponent, SelectComponent, ModalComponent } from "@components";
8
- import { useToggleContext } from "@contexts";
9
- import { FilterComponent } from "../table/FilterComponent";
10
-
11
-
12
-
13
- type ExportColumn = {
14
- selector : string;
15
- label : string;
16
- source : string | null;
17
- };
18
-
19
- export type ExportExcelColumnControlType = {
20
- label : string;
21
- selector : string;
22
- status ?: "default" | "optional" | "hidden";
23
- };
24
-
25
- type ExportExcelProps = {
26
- fetchControl : FetchControlType;
27
- columnControl ?: ExportExcelColumnControlType[];
28
- filename ?: string;
29
- };
30
-
31
-
32
-
33
- const ADD_COLUMN_KEY = "__add_column__";
34
-
35
- function numberToExcelColumn(index: number): string {
36
- let column = "";
37
- let n = index;
38
-
39
- while (n >= 0) {
40
- column = String.fromCharCode((n % 26) + 65) + column;
41
- n = Math.floor(n / 26) - 1;
42
- }
43
-
44
- return column;
45
- }
46
-
47
-
48
-
49
- export function ExportExcel({ fetchControl, columnControl, filename }: ExportExcelProps) {
50
- const { toggle, setToggle } = useToggleContext()
51
-
52
- const { data, tableControl, setParam, params } = useTable(fetchControl, undefined, undefined, false);
53
-
54
- const fields = useMemo(() => {
55
- if (columnControl?.length) {
56
- return columnControl.filter(c => c.status !== "hidden").map(c => c.selector);
57
- }
58
-
59
- if (!data?.data?.[0]) return [];
60
- return Object.keys(data.data[0]);
61
- }, [data, columnControl]);
62
-
63
- const [columns, setColumns] = useState<ExportColumn[]>([]);
64
-
65
-
66
- useEffect(() => {
67
- if (!data?.data?.[0]) return;
68
-
69
- setColumns(prev => {
70
- if (prev.length) return prev;
71
-
72
- const baseFields = columnControl?.length ? columnControl.filter(c => c.status === "default" || c.status === undefined).map(c => c.selector) : Object.keys(data.data[0]);
73
-
74
- return baseFields.map((field, i) => {
75
- const label = numberToExcelColumn(i);
76
-
77
- return {
78
- selector : label,
79
- label : label,
80
- source : field,
81
- };
82
- });
83
- });
84
- }, [data, columnControl]);
85
-
86
-
87
- const addColumn = (selector: string, source: string) => {
88
- setColumns(prev => [...prev, { selector, label: selector, source }]);
89
- };
90
-
91
- const removeColumn = (selector: string) => {
92
- setColumns(prev => prev.filter(c => c.selector !== selector).map((c, i) => {
93
- const label = numberToExcelColumn(i);
94
- return { ...c, selector: label, label };
95
- }));
96
- };
97
-
98
- const moveColumn = (selector: string, dir: "left" | "right") => {
99
- setColumns(prev => {
100
- const idx = prev.findIndex(c => c.selector === selector);
101
- if (idx === -1) return prev;
102
-
103
- const target = dir === "left" ? idx - 1 : idx + 1;
104
- if (target < 0 || target >= prev.length) return prev;
105
-
106
- const next = [...prev];
107
- [next[idx], next[target]] = [next[target], next[idx]];
108
-
109
- return next.map((c, i) => {
110
- const label = numberToExcelColumn(i);
111
- return { ...c, selector: label, label };
112
- });
113
- });
114
- };
115
-
116
- const setSource = (selector: string, source: string | null) => setColumns(prev => prev.map(c => (c.selector === selector ? { ...c, source } : c)));
117
-
118
- const getColumnLabel = (source: string | null) => {
119
- if (!source) return "";
120
-
121
- const found = columnControl?.find(c => c.selector === source);
122
-
123
- return found?.label ?? source;
124
- };
125
-
126
-
127
- const tableColumns = useMemo(() => {
128
- return [
129
- ...columns?.map((c => ({
130
- ...c,
131
- label: <div className="w-full text-center">{c.label}</div>
132
- }))),
133
- { selector: ADD_COLUMN_KEY, label: "" } as any,
134
- ];
135
- }, [columns]);
136
-
137
- const tableData = useMemo(() => {
138
- if (!data?.data || !columns.length) return [];
139
-
140
- const controlRow: Record<string, any> = {};
141
-
142
- columns.forEach((col, iCol) => {
143
- controlRow[col.selector] = (
144
- <div className="flex justify-between">
145
- <p className="font-semibold">{getColumnLabel(col.source)}</p>
146
-
147
- <div className="flex gap-1">
148
- {iCol > 0 && (
149
- <IconButtonComponent
150
- icon={faArrowLeft}
151
- size="xs"
152
- variant="outline"
153
- className="!text-foreground"
154
- onClick={() => moveColumn(col.selector, "left")}
155
- />
156
- )}
157
-
158
- {iCol < (columns.length - 1) && (
159
- <IconButtonComponent
160
- icon={faArrowRight}
161
- size="xs"
162
- variant="outline"
163
- className="!text-foreground"
164
- onClick={() => moveColumn(col.selector, "right")}
165
- />
166
- )}
167
-
168
- <IconButtonComponent
169
- icon={faEdit}
170
- size="xs"
171
- paint="warning"
172
- variant="outline"
173
- disabled={columns.length <= 1}
174
- onClick={() => setToggle("MODAL_FIELD_EXPORT", {selector: col.selector, value: col.source})}
175
- />
176
-
177
- <IconButtonComponent
178
- icon={faTimes}
179
- size="xs"
180
- paint="danger"
181
- variant="outline"
182
- disabled={columns.length <= 1}
183
- onClick={() => removeColumn(col.selector)}
184
- />
185
- </div>
186
-
187
-
188
- </div>
189
- );
190
- });
191
-
192
- controlRow[ADD_COLUMN_KEY] = (
193
- <div className="flex justify-center">
194
- <ButtonComponent
195
- icon={faPlus}
196
- label="Tambah Kolom"
197
- size="xs"
198
- variant="outline"
199
- onClick={() => {
200
- const idx = columns.length;
201
- const label = numberToExcelColumn(idx);
202
- setToggle("MODAL_FIELD_EXPORT", { selector: label, value: "", add: true })
203
- }}
204
- />
205
- </div>
206
- );
207
-
208
- const excelRows = data.data.map((item: Record<string, any>) => {
209
- const row: Record<string, any> = {};
210
-
211
- row[ADD_COLUMN_KEY] = "-"
212
- columns.forEach(col => {
213
- row[col.selector] = col.source ? <span className="line-clamp-1">{item[col.source] || "-"}</span> : "-";
214
- });
215
-
216
- return row;
217
- });
218
-
219
- return [controlRow, ...excelRows];
220
- }, [data, columns, fields]);
221
-
222
-
223
- const handleDownloadExcel = async () => {
224
- const record = await api({...fetchControl, params: {...fetchControl.params, ...params, page: 1, paginate: 9999}});
225
- if (!record?.data?.data?.length || !columns.length) return;
226
-
227
- const workbook = new ExcelJS.Workbook();
228
- const worksheet = workbook.addWorksheet("Export");
229
-
230
- worksheet.columns = columns.map(col => ({
231
- header : getColumnLabel(col.source),
232
- key : col.selector,
233
- width : 20,
234
- }));
235
-
236
- worksheet.getRow(1).eachCell(cell => {
237
- cell.font = { bold: true };
238
- cell.alignment = { horizontal: "center", vertical: "middle" };
239
- });
240
-
241
- record.data.data.forEach((item: Record<string, any>) => {
242
- const row: Record<string, any> = {};
243
-
244
- columns.forEach(col => {
245
- row[col.selector] = col.source ? item[col.source] : "";
246
- });
247
-
248
- worksheet.addRow(row);
249
- });
250
-
251
- const buffer = await workbook.xlsx.writeBuffer();
252
- const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
253
-
254
- const link = document.createElement("a");
255
- link.href = URL.createObjectURL(blob);
256
- link.download = filename ? filename + ".xlsx" : "export.xlsx";
257
- link.click();
258
-
259
- URL.revokeObjectURL(link.href);
260
- };
261
-
262
-
263
-
264
- return (
265
- <>
266
- <div className="p-4">
267
- <FilterComponent
268
- columns={columnControl?.length
269
- ? columnControl.filter(c => c.status !== "hidden").map(c => ({
270
- label: c.label,
271
- selector: c.selector,
272
- type: "text"
273
- }))
274
- : (data?.data?.[0] ? Object.keys(data.data[0]).map((c) => ({
275
- label: c,
276
- selector: c,
277
- type: "text"
278
- })) : [])
279
- }
280
- onChange={(filters) => setParam('filter', filters)}
281
- value={params?.filter || []}
282
- />
283
- </div>
284
-
285
- <TableComponent
286
- controlBar={false}
287
- columns={tableColumns}
288
- data={tableData}
289
- {...tableControl}
290
- pagination={false}
291
- className="p-4 bg-background row::bg-transparent row::border-0 row::gap-0 row::!hover:bg-background column::p-2 column::border head-column::p-2 head-column::border"
292
- />
293
-
294
- <div className="px-4 mt-8">
295
- <ButtonComponent label="Download Excel" block onClick={() => handleDownloadExcel()} />
296
- </div>
297
-
298
- <ModalComponent
299
- show={!!toggle["MODAL_FIELD_EXPORT"]}
300
- onClose={() => setToggle("MODAL_FIELD_EXPORT", false)}
301
- title="Pilih Kolom"
302
- footer={
303
- <div className="flex justify-end">
304
- <ButtonComponent
305
- label="Terapkan"
306
- onClick={() => {
307
- if(!!(toggle["MODAL_FIELD_EXPORT"] as { value: string })?.value) {
308
- if((toggle["MODAL_FIELD_EXPORT"] as { add: boolean })?.add) {
309
- addColumn((toggle["MODAL_FIELD_EXPORT"] as { selector: string })?.selector, (toggle["MODAL_FIELD_EXPORT"] as { value: string })?.value)
310
- } else {
311
- setSource((toggle["MODAL_FIELD_EXPORT"] as { selector: string })?.selector, (toggle["MODAL_FIELD_EXPORT"] as { value: string })?.value)
312
- }
313
- }
314
- setToggle("MODAL_FIELD_EXPORT", false)
315
- }}
316
- />
317
- </div>
318
- }
319
- >
320
- <div className="p-4">
321
- <SelectComponent
322
- name={`column_${(toggle["MODAL_FIELD_EXPORT"] as { selector: string })?.selector}`}
323
- placeholder="Pilih kolom data..."
324
- value={(toggle["MODAL_FIELD_EXPORT"] as { value: string })?.value ?? ""}
325
- onChange={e => setToggle("MODAL_FIELD_EXPORT", {...(toggle["MODAL_FIELD_EXPORT"] as object), value: e})}
326
- options={fields.map((f: string) => {
327
- const col = columnControl?.find(c => c.selector === f);
328
- if (col?.status === "hidden") return null;
329
-
330
- return {
331
- label: col?.label || f,
332
- value: f,
333
- };
334
- }).filter(Boolean) as any}
335
- />
336
- </div>
337
- </ModalComponent>
338
- </>
339
- );
340
- }