@skalfa/skalfa-component 1.0.54 → 1.0.55

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.
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.TableSupervisionComponent = TableSupervisionComponent;
5
5
  const jsx_runtime_1 = require("react/jsx-runtime");
6
6
  const react_1 = require("react");
7
+ const navigation_1 = require("next/navigation");
7
8
  const _utils_1 = require("@utils");
8
9
  const _contexts_1 = require("@contexts");
9
10
  const __1 = require("../");
@@ -14,10 +15,60 @@ const ImportExcel = _utils_1.registry.get("ImportExcel");
14
15
  ;
15
16
  function TableSupervisionComponent({ title, id, fetchControl, columnControl, formControl, onRowClick, detailControl, actionControl, actionBulkingControl, block, controlBar, filterBar, noIndex, responsiveControl, urlParam, }) {
16
17
  const l = (0, skalfa_lang_1.useLang)();
18
+ const router = (0, navigation_1.useRouter)();
19
+ const searchParams = (0, navigation_1.useSearchParams)();
17
20
  const { tableKey, tableControl, data, selected, setSelected, checks, setChecks, reset, focus, setFocus } = (0, _utils_1.useTable)(fetchControl, id, title, (urlParam || true));
18
21
  const { setToggle, toggle } = (0, _contexts_1.useToggleContext)();
19
22
  const { isSm } = (0, _utils_1.useResponsive)();
20
23
  const toggleKey = (0, react_1.useMemo)(() => _utils_1.conversion.strSnake(tableKey).toUpperCase(), [tableKey]);
24
+ const detailQueryId = searchParams.get("detail") || searchParams.get("openDetail");
25
+ const editQueryId = searchParams.get("edit");
26
+ const [directLoading, setDirectLoading] = (0, react_1.useState)(false);
27
+ // Auto-open floating detail or edit modal if query parameter is present in URL
28
+ (0, react_1.useEffect)(() => {
29
+ if (detailControl === false && !editQueryId)
30
+ return;
31
+ const targetId = detailQueryId || editQueryId;
32
+ const isEdit = !!editQueryId && !detailQueryId;
33
+ if (!targetId)
34
+ return;
35
+ if (!selected || String(selected?.id) !== String(targetId)) {
36
+ setDirectLoading(true);
37
+ if (isEdit) {
38
+ setToggle(`MODAL_FORM_${toggleKey}`, true);
39
+ }
40
+ else {
41
+ setToggle(`MODAL_SHOW_${toggleKey}`, true);
42
+ }
43
+ const basePath = fetchControl?.path;
44
+ if (basePath) {
45
+ (0, _utils_1.api)({
46
+ path: `${basePath}/${targetId}`,
47
+ method: "GET",
48
+ params: fetchControl?.params || {},
49
+ })
50
+ .then((res) => {
51
+ const item = res?.data?.data || res?.data || res;
52
+ if (item) {
53
+ setSelected(item);
54
+ }
55
+ else {
56
+ setSelected({ id: Number(targetId) });
57
+ }
58
+ })
59
+ .catch(() => {
60
+ setSelected({ id: Number(targetId) });
61
+ })
62
+ .finally(() => {
63
+ setDirectLoading(false);
64
+ });
65
+ }
66
+ else {
67
+ setSelected({ id: Number(targetId) });
68
+ setDirectLoading(false);
69
+ }
70
+ }
71
+ }, [detailQueryId, editQueryId, fetchControl, toggleKey]);
21
72
  (0, react_1.useEffect)(() => {
22
73
  if (data?.data?.length && !toggle[`MODAL_DELETE_${toggleKey}`] && !toggle[`MODAL_DELETE_${toggleKey}`] && !toggle[`MODAL_SHOW_${toggleKey}`]) {
23
74
  _utils_1.shortcut.register("arrowdown", () => {
@@ -136,7 +187,12 @@ function TableSupervisionComponent({ title, id, fetchControl, columnControl, for
136
187
  const detailPage = (0, react_1.useMemo)(() => {
137
188
  if (!toggle[`MODAL_SHOW_${toggleKey}`])
138
189
  return null;
139
- return ((0, jsx_runtime_1.jsx)("div", { className: "p-4", children: (0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)("flex flex-col gap-y-4"), children: !!selected && (typeof detailControl === "object" && typeof detailControl.content === "object" && detailControl?.content?.length ? detailControl?.content?.map((column, key) => {
190
+ if (directLoading && !selected) {
191
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "p-12 text-center text-slate-400 space-y-2", children: [(0, jsx_runtime_1.jsx)("div", { className: "inline-block animate-spin w-6 h-6 border-2 border-primary border-t-transparent rounded-full" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs font-semibold", children: l?.base?.loading ? l.base.loading() : "Memuat detail..." })] }));
192
+ }
193
+ if (!selected)
194
+ return null;
195
+ return ((0, jsx_runtime_1.jsx)("div", { className: "p-4", children: (0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)("flex flex-col gap-y-4"), children: typeof detailControl === "object" && typeof detailControl.content === "object" && detailControl?.content?.length ? detailControl?.content?.map((column, key) => {
140
196
  if (typeof column === "string") {
141
197
  return ((0, jsx_runtime_1.jsx)(__1.TypographyColumnComponent, { title: columns?.find((c) => c.selector == column)?.label, content: selected[column] }, key));
142
198
  }
@@ -146,8 +202,8 @@ function TableSupervisionComponent({ title, id, fetchControl, columnControl, for
146
202
  else {
147
203
  return column?.(selected);
148
204
  }
149
- }) : typeof detailControl === "object" && typeof detailControl.content === "function" ? detailControl?.content?.(selected) : columns?.map((column, key) => ((0, jsx_runtime_1.jsx)(__1.TypographyColumnComponent, { title: column.label, content: selected[column.selector] }, key)))) }) }));
150
- }, [selected, detailControl]);
205
+ }) : typeof detailControl === "object" && typeof detailControl.content === "function" ? detailControl?.content?.(selected) : columns?.map((column, key) => ((0, jsx_runtime_1.jsx)(__1.TypographyColumnComponent, { title: column.label, content: selected[column.selector] }, key))) }) }));
206
+ }, [selected, detailControl, toggle[`MODAL_SHOW_${toggleKey}`], directLoading, columns]);
151
207
  // ============================
152
208
  // ## Form preparation
153
209
  // ============================
@@ -206,6 +262,14 @@ function TableSupervisionComponent({ title, id, fetchControl, columnControl, for
206
262
  },
207
263
  } : { idb: fetchControl?.idb }, fields: fields?.filter((f) => f.visibility ? !selected?.id ? ["*", "create"]?.includes(f.visibility) : ["*", "update"]?.includes(f.visibility) : true), defaultValue: formControl?.defaultValue ? await formControl?.defaultValue(selected || null) : selected, payload: formControl?.payload, onSuccess: () => {
208
264
  reset();
265
+ if (typeof window !== "undefined") {
266
+ const url = new URL(window.location.href);
267
+ if (url.searchParams.has("edit")) {
268
+ url.searchParams.delete("edit");
269
+ const nextSearch = url.searchParams.toString();
270
+ router.replace(url.pathname + (nextSearch ? `?${nextSearch}` : ""), { scroll: false });
271
+ }
272
+ }
209
273
  setTimeout(() => { setToggle(`MODAL_FORM_${toggleKey}`, false); }, 900);
210
274
  } }));
211
275
  }, [selected, fetchControl, formControl]);
@@ -267,7 +331,29 @@ function TableSupervisionComponent({ title, id, fetchControl, columnControl, for
267
331
  } : undefined, ...tableControl }), controlBar?.find((cb) => cb == "CREATE") && ((0, jsx_runtime_1.jsx)(__1.ButtonComponent, { icon: "solid/plus", className: "fixed bottom-16 right-4 w-14 h-14 z-20 md:hidden table-supervision-mobile-create", size: "lg", rounded: true, onClick: () => {
268
332
  setToggle(`MODAL_FORM_${toggleKey}`);
269
333
  setSelected(null);
270
- } })), (0, jsx_runtime_1.jsx)(__1.FloatingPageComponent, { show: !!toggle[`MODAL_SHOW_${toggleKey}`], onClose: () => setToggle(`MODAL_SHOW_${toggleKey}`, false), title: l.base.detailTitle ? l.base.detailTitle() : "Detail", className: "bg-background", footer: renderTableAction(actionControl, undefined, { className: isSm ? "justify-end p-2 bg-background" : "justify-end", size: isSm ? "sm" : "md" }), ...(typeof detailControl === "object" ? detailControl?.modalControl || {} : {}), children: detailPage }), (0, jsx_runtime_1.jsx)(__1.FloatingPageComponent, { show: !!toggle[`MODAL_FORM_${toggleKey}`], onClose: () => setToggle(`MODAL_FORM_${toggleKey}`, false), title: !!selected ? (l.base.editTitle ? l.base.editTitle() : "Edit") : (l.base.addTitle ? l.base.addTitle() : "Add"), className: (0, _utils_1.cn)("bg-white", formControl?.modalControl?.className), ...formControl?.modalControl, children: (0, jsx_runtime_1.jsx)("div", { className: "p-4", children: formPage }) }), (0, jsx_runtime_1.jsx)(__1.FloatingPageComponent, { show: !!toggle[`MODAL_EXPORT_${toggleKey}`], onClose: () => setToggle(`MODAL_EXPORT_${toggleKey}`, false), title: l.base.exportTitle ? l.base.exportTitle() : "Export", className: "bg-white md:w-[1200px] max-w-[1200px]", children: (0, jsx_runtime_1.jsx)(ExportExcel, { fetchControl: fetchControl, filename: "export", columnControl: columns?.map((cc) => ({
334
+ } })), (0, jsx_runtime_1.jsx)(__1.FloatingPageComponent, { show: !!toggle[`MODAL_SHOW_${toggleKey}`], onClose: () => {
335
+ setToggle(`MODAL_SHOW_${toggleKey}`, false);
336
+ setSelected(null);
337
+ if (typeof window !== "undefined") {
338
+ const url = new URL(window.location.href);
339
+ if (url.searchParams.has("detail") || url.searchParams.has("openDetail")) {
340
+ url.searchParams.delete("detail");
341
+ url.searchParams.delete("openDetail");
342
+ const nextSearch = url.searchParams.toString();
343
+ router.replace(url.pathname + (nextSearch ? `?${nextSearch}` : ""), { scroll: false });
344
+ }
345
+ }
346
+ }, title: l.base.detailTitle ? l.base.detailTitle() : "Detail", className: "bg-background", footer: renderTableAction(actionControl, undefined, { className: isSm ? "justify-end p-2 bg-background" : "justify-end", size: isSm ? "sm" : "md" }), ...(typeof detailControl === "object" ? detailControl?.modalControl || {} : {}), children: detailPage }), (0, jsx_runtime_1.jsx)(__1.FloatingPageComponent, { show: !!toggle[`MODAL_FORM_${toggleKey}`], onClose: () => {
347
+ setToggle(`MODAL_FORM_${toggleKey}`, false);
348
+ if (typeof window !== "undefined") {
349
+ const url = new URL(window.location.href);
350
+ if (url.searchParams.has("edit")) {
351
+ url.searchParams.delete("edit");
352
+ const nextSearch = url.searchParams.toString();
353
+ router.replace(url.pathname + (nextSearch ? `?${nextSearch}` : ""), { scroll: false });
354
+ }
355
+ }
356
+ }, title: !!selected ? (l.base.editTitle ? l.base.editTitle() : "Edit") : (l.base.addTitle ? l.base.addTitle() : "Add"), className: (0, _utils_1.cn)("bg-white", formControl?.modalControl?.className), ...formControl?.modalControl, children: (0, jsx_runtime_1.jsx)("div", { className: "p-4", children: formPage }) }), (0, jsx_runtime_1.jsx)(__1.FloatingPageComponent, { show: !!toggle[`MODAL_EXPORT_${toggleKey}`], onClose: () => setToggle(`MODAL_EXPORT_${toggleKey}`, false), title: l.base.exportTitle ? l.base.exportTitle() : "Export", className: "bg-white md:w-[1200px] max-w-[1200px]", children: (0, jsx_runtime_1.jsx)(ExportExcel, { fetchControl: fetchControl, filename: "export", columnControl: columns?.map((cc) => ({
271
357
  label: cc.label || "",
272
358
  selector: cc.selector || "",
273
359
  })) }) }), (0, jsx_runtime_1.jsx)(__1.FloatingPageComponent, { show: !!toggle[`MODAL_IMPORT_${toggleKey}`], onClose: () => setToggle(`MODAL_IMPORT_${toggleKey}`, false), title: l.base.importTitle ? l.base.importTitle() : "Import", className: "bg-white md:w-[1200px] max-w-[1200px]", children: (0, jsx_runtime_1.jsx)(ImportExcel, { submitControl: { path: fetchControl?.path + "/import" }, columnControl: columns?.map((cc) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skalfa/skalfa-component",
3
- "version": "1.0.54",
3
+ "version": "1.0.55",
4
4
  "description": "Reusable UI components for Skalfa App.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -1,7 +1,8 @@
1
1
  "use client"
2
2
 
3
- import { Fragment, ReactNode, useEffect, useMemo } from "react";
4
- import { ApiType, cn, conversion, registry, shortcut, ShortcutHandler, UseResourceIdb, UseResourceProps, useResponsive, useTable } from "@utils";
3
+ import { Fragment, ReactNode, useEffect, useMemo, useState } from "react";
4
+ import { useRouter, useSearchParams } from "next/navigation";
5
+ import { api, ApiType, cn, conversion, registry, shortcut, ShortcutHandler, UseResourceIdb, UseResourceProps, useResponsive, useTable } from "@utils";
5
6
  import { useToggleContext } from "@contexts";
6
7
  import { FloatingPageComponent, FloatingPageProps, ButtonComponent, TableColumnType, TableComponent, FormSupervisionComponent, FormType, ModalConfirmComponent, TypographyColumnComponent, ButtonProps, ModalConfirmProps, TableProps, ControlBarOptionType, SwipeActionType, BottomSheetProps } from "../";
7
8
  import { useLang } from "@skalfa/skalfa-lang";
@@ -105,12 +106,61 @@ export function TableSupervisionComponent({
105
106
  urlParam,
106
107
  }: TableSupervisionProps) {
107
108
  const l = useLang();
109
+ const router = useRouter();
110
+ const searchParams = useSearchParams();
108
111
 
109
112
  const { tableKey, tableControl, data, selected, setSelected, checks, setChecks, reset, focus, setFocus } = useTable(fetchControl, id, title, (urlParam || true))
110
113
  const { setToggle, toggle } = useToggleContext()
111
114
  const { isSm } = useResponsive();
112
115
 
113
116
  const toggleKey = useMemo(() => conversion.strSnake(tableKey).toUpperCase(), [tableKey])
117
+ const detailQueryId = searchParams.get("detail") || searchParams.get("openDetail");
118
+ const editQueryId = searchParams.get("edit");
119
+ const [directLoading, setDirectLoading] = useState(false);
120
+
121
+ // Auto-open floating detail or edit modal if query parameter is present in URL
122
+ useEffect(() => {
123
+ if (detailControl === false && !editQueryId) return;
124
+
125
+ const targetId = detailQueryId || editQueryId;
126
+ const isEdit = !!editQueryId && !detailQueryId;
127
+ if (!targetId) return;
128
+
129
+ if (!selected || String((selected as any)?.id) !== String(targetId)) {
130
+ setDirectLoading(true);
131
+ if (isEdit) {
132
+ setToggle(`MODAL_FORM_${toggleKey}`, true);
133
+ } else {
134
+ setToggle(`MODAL_SHOW_${toggleKey}`, true);
135
+ }
136
+
137
+ const basePath = (fetchControl as ApiType)?.path;
138
+ if (basePath) {
139
+ api({
140
+ path: `${basePath}/${targetId}`,
141
+ method: "GET",
142
+ params: (fetchControl as ApiType)?.params || {},
143
+ })
144
+ .then((res: any) => {
145
+ const item = res?.data?.data || res?.data || res;
146
+ if (item) {
147
+ setSelected(item);
148
+ } else {
149
+ setSelected({ id: Number(targetId) });
150
+ }
151
+ })
152
+ .catch(() => {
153
+ setSelected({ id: Number(targetId) });
154
+ })
155
+ .finally(() => {
156
+ setDirectLoading(false);
157
+ });
158
+ } else {
159
+ setSelected({ id: Number(targetId) });
160
+ setDirectLoading(false);
161
+ }
162
+ }
163
+ }, [detailQueryId, editQueryId, fetchControl, toggleKey]);
114
164
 
115
165
 
116
166
  useEffect(() => {
@@ -297,23 +347,35 @@ export function TableSupervisionComponent({
297
347
  // ============================
298
348
  const detailPage = useMemo(() => {
299
349
  if (!toggle[`MODAL_SHOW_${toggleKey}`]) return null;
350
+
351
+ if (directLoading && !selected) {
352
+ return (
353
+ <div className="p-12 text-center text-slate-400 space-y-2">
354
+ <div className="inline-block animate-spin w-6 h-6 border-2 border-primary border-t-transparent rounded-full" />
355
+ <p className="text-xs font-semibold">{l?.base?.loading ? l.base.loading() : "Memuat detail..."}</p>
356
+ </div>
357
+ );
358
+ }
359
+
360
+ if (!selected) return null;
361
+
300
362
  return (
301
363
  <div className="p-4">
302
364
  <div className={cn(
303
365
  "flex flex-col gap-y-4",
304
366
  )}>
305
- {!!selected && (typeof detailControl === "object" && typeof detailControl.content === "object" && detailControl?.content?.length ? detailControl?.content?.map((column, key) => {
367
+ {typeof detailControl === "object" && typeof detailControl.content === "object" && detailControl?.content?.length ? detailControl?.content?.map((column, key) => {
306
368
  if (typeof column === "string") {
307
369
  return (<TypographyColumnComponent
308
370
  key={key}
309
371
  title={columns?.find((c) => c.selector == column)?.label}
310
- content={selected[column]}
372
+ content={(selected as any)[column]}
311
373
  />)
312
374
  } else if (typeof column === "object") {
313
375
  return (<TypographyColumnComponent
314
376
  key={key}
315
377
  title={column?.label}
316
- content={typeof column?.item === "string" ? selected[column?.item] : column?.item(selected)}
378
+ content={typeof column?.item === "string" ? (selected as any)[column?.item] : column?.item(selected)}
317
379
  />)
318
380
  } else {
319
381
  return column?.(selected)
@@ -322,13 +384,13 @@ export function TableSupervisionComponent({
322
384
  <TypographyColumnComponent
323
385
  key={key}
324
386
  title={column.label}
325
- content={selected[column.selector]}
387
+ content={(selected as any)[column.selector]}
326
388
  />
327
- )))}
389
+ ))}
328
390
  </div>
329
391
  </div>
330
392
  )
331
- }, [selected, detailControl]);
393
+ }, [selected, detailControl, toggle[`MODAL_SHOW_${toggleKey}`], directLoading, columns]);
332
394
 
333
395
 
334
396
 
@@ -401,6 +463,14 @@ export function TableSupervisionComponent({
401
463
  payload={formControl?.payload}
402
464
  onSuccess={() => {
403
465
  reset();
466
+ if (typeof window !== "undefined") {
467
+ const url = new URL(window.location.href);
468
+ if (url.searchParams.has("edit")) {
469
+ url.searchParams.delete("edit");
470
+ const nextSearch = url.searchParams.toString();
471
+ router.replace(url.pathname + (nextSearch ? `?${nextSearch}` : ""), { scroll: false });
472
+ }
473
+ }
404
474
  setTimeout(() => { setToggle(`MODAL_FORM_${toggleKey}`, false) }, 900);
405
475
  }}
406
476
  />
@@ -553,7 +623,19 @@ export function TableSupervisionComponent({
553
623
 
554
624
  <FloatingPageComponent
555
625
  show={!!toggle[`MODAL_SHOW_${toggleKey}`]}
556
- onClose={() => setToggle(`MODAL_SHOW_${toggleKey}`, false)}
626
+ onClose={() => {
627
+ setToggle(`MODAL_SHOW_${toggleKey}`, false);
628
+ setSelected(null);
629
+ if (typeof window !== "undefined") {
630
+ const url = new URL(window.location.href);
631
+ if (url.searchParams.has("detail") || url.searchParams.has("openDetail")) {
632
+ url.searchParams.delete("detail");
633
+ url.searchParams.delete("openDetail");
634
+ const nextSearch = url.searchParams.toString();
635
+ router.replace(url.pathname + (nextSearch ? `?${nextSearch}` : ""), { scroll: false });
636
+ }
637
+ }
638
+ }}
557
639
  title={l.base.detailTitle ? l.base.detailTitle() : "Detail"}
558
640
  className="bg-background"
559
641
  footer={renderTableAction(actionControl, undefined, {className: isSm ? "justify-end p-2 bg-background" : "justify-end", size: isSm ? "sm" : "md"})}
@@ -565,7 +647,17 @@ export function TableSupervisionComponent({
565
647
 
566
648
  <FloatingPageComponent
567
649
  show={!!toggle[`MODAL_FORM_${toggleKey}`]}
568
- onClose={() => setToggle(`MODAL_FORM_${toggleKey}`, false)}
650
+ onClose={() => {
651
+ setToggle(`MODAL_FORM_${toggleKey}`, false);
652
+ if (typeof window !== "undefined") {
653
+ const url = new URL(window.location.href);
654
+ if (url.searchParams.has("edit")) {
655
+ url.searchParams.delete("edit");
656
+ const nextSearch = url.searchParams.toString();
657
+ router.replace(url.pathname + (nextSearch ? `?${nextSearch}` : ""), { scroll: false });
658
+ }
659
+ }
660
+ }}
569
661
  title={!!selected ? (l.base.editTitle ? l.base.editTitle() : "Edit") : (l.base.addTitle ? l.base.addTitle() : "Add")}
570
662
  className={cn("bg-white", formControl?.modalControl?.className)}
571
663
  {...formControl?.modalControl}