@rozaqi02/reusable-dashboard 1.1.1 → 1.1.3
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.
- package/README.md +1101 -58
- package/dist/index.cjs +437 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +284 -0
- package/dist/index.css.map +1 -1
- package/dist/index.js +434 -40
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -41,6 +41,7 @@ __export(index_exports, {
|
|
|
41
41
|
Input: () => Input,
|
|
42
42
|
ReusableDashboardView: () => ReusableDashboardView,
|
|
43
43
|
SearchBar: () => SearchBar,
|
|
44
|
+
SetupWizard: () => SetupWizard,
|
|
44
45
|
SidebarNavigation: () => SidebarNavigation,
|
|
45
46
|
SkeletonLoader: () => SkeletonLoader,
|
|
46
47
|
StatCard: () => StatCard,
|
|
@@ -52,6 +53,7 @@ __export(index_exports, {
|
|
|
52
53
|
buildDayBuckets: () => buildDayBuckets,
|
|
53
54
|
cidikaWidgetConfig: () => cidikaWidgetConfig,
|
|
54
55
|
createCidikaSupabaseSource: () => createCidikaSupabaseSource,
|
|
56
|
+
createDashboardConfig: () => createDashboardConfig,
|
|
55
57
|
createDashboardLabels: () => createDashboardLabels,
|
|
56
58
|
createDefaultFilters: () => createDefaultFilters,
|
|
57
59
|
createEmptyDashboardData: () => createEmptyDashboardData,
|
|
@@ -69,7 +71,8 @@ __export(index_exports, {
|
|
|
69
71
|
toNumber: () => toNumber,
|
|
70
72
|
tokoSepatuWidgetConfig: () => tokoSepatuWidgetConfig,
|
|
71
73
|
useRealtimeUpdate: () => useRealtimeUpdate,
|
|
72
|
-
useReusableDashboard: () => useReusableDashboard
|
|
74
|
+
useReusableDashboard: () => useReusableDashboard,
|
|
75
|
+
validateDashboardConfig: () => validateDashboardConfig
|
|
73
76
|
});
|
|
74
77
|
module.exports = __toCommonJS(index_exports);
|
|
75
78
|
|
|
@@ -312,6 +315,78 @@ var tokoSepatuWidgetConfig = {
|
|
|
312
315
|
}
|
|
313
316
|
};
|
|
314
317
|
|
|
318
|
+
// src/config/createDashboardConfig.js
|
|
319
|
+
function createDashboardConfig({
|
|
320
|
+
widgetConfig,
|
|
321
|
+
dataSource,
|
|
322
|
+
adapter,
|
|
323
|
+
createEmptyState,
|
|
324
|
+
labels,
|
|
325
|
+
languageCode = "id",
|
|
326
|
+
dateLocale = "id-ID"
|
|
327
|
+
}) {
|
|
328
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
329
|
+
const missing = [];
|
|
330
|
+
if (!widgetConfig) missing.push("widgetConfig");
|
|
331
|
+
if (!dataSource) missing.push("dataSource");
|
|
332
|
+
if (!adapter) missing.push("adapter");
|
|
333
|
+
if (!createEmptyState) missing.push("createEmptyState");
|
|
334
|
+
return {
|
|
335
|
+
// Properti yang langsung dibaca useReusableDashboard
|
|
336
|
+
config: widgetConfig,
|
|
337
|
+
dataSource,
|
|
338
|
+
adapter,
|
|
339
|
+
createEmptyState,
|
|
340
|
+
languageCode,
|
|
341
|
+
dateLocale,
|
|
342
|
+
labels,
|
|
343
|
+
// Metadata untuk setup wizard & validasi
|
|
344
|
+
_meta: {
|
|
345
|
+
isValid: missing.length === 0,
|
|
346
|
+
missing,
|
|
347
|
+
hasDataSource: Boolean(dataSource == null ? void 0 : dataSource.fetchDashboardSnapshot),
|
|
348
|
+
hasRealtimeSupport: Boolean(dataSource == null ? void 0 : dataSource.subscribeLiveUpdate),
|
|
349
|
+
hasLabels: Boolean(labels),
|
|
350
|
+
widgetCount: {
|
|
351
|
+
stats: ((_b = (_a = widgetConfig == null ? void 0 : widgetConfig.widgets) == null ? void 0 : _a.stats) == null ? void 0 : _b.length) ?? 0,
|
|
352
|
+
charts: ((_d = (_c = widgetConfig == null ? void 0 : widgetConfig.widgets) == null ? void 0 : _c.charts) == null ? void 0 : _d.length) ?? 0,
|
|
353
|
+
tableColumns: ((_g = (_f = (_e = widgetConfig == null ? void 0 : widgetConfig.widgets) == null ? void 0 : _e.table) == null ? void 0 : _f.columns) == null ? void 0 : _g.length) ?? 0
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function validateDashboardConfig(dashboardConfig) {
|
|
359
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
360
|
+
const issues = [];
|
|
361
|
+
if (!dashboardConfig) {
|
|
362
|
+
return { valid: false, issues: ["dashboardConfig tidak ditemukan. Pastikan sudah memanggil createDashboardConfig()."] };
|
|
363
|
+
}
|
|
364
|
+
const meta = dashboardConfig == null ? void 0 : dashboardConfig._meta;
|
|
365
|
+
if (!meta) {
|
|
366
|
+
issues.push("Config tidak dibuat melalui createDashboardConfig(). Gunakan factory function ini untuk validasi otomatis.");
|
|
367
|
+
} else {
|
|
368
|
+
if (meta.missing.length > 0) {
|
|
369
|
+
meta.missing.forEach((m) => issues.push(`Properti wajib belum diisi: "${m}"`));
|
|
370
|
+
}
|
|
371
|
+
if (!meta.hasDataSource) {
|
|
372
|
+
issues.push("dataSource.fetchDashboardSnapshot() tidak ditemukan. Pastikan sudah membuat data source yang benar.");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (!((_c = (_b = (_a = dashboardConfig.config) == null ? void 0 : _a.widgets) == null ? void 0 : _b.stats) == null ? void 0 : _c.length)) {
|
|
376
|
+
issues.push("widgetConfig.widgets.stats kosong. Tambahkan minimal 1 stat card.");
|
|
377
|
+
}
|
|
378
|
+
if (!((_f = (_e = (_d = dashboardConfig.config) == null ? void 0 : _d.widgets) == null ? void 0 : _e.charts) == null ? void 0 : _f.length)) {
|
|
379
|
+
issues.push("widgetConfig.widgets.charts kosong. Tambahkan minimal 1 chart.");
|
|
380
|
+
}
|
|
381
|
+
if (!((_j = (_i = (_h = (_g = dashboardConfig.config) == null ? void 0 : _g.widgets) == null ? void 0 : _h.table) == null ? void 0 : _i.columns) == null ? void 0 : _j.length)) {
|
|
382
|
+
issues.push("widgetConfig.widgets.table.columns kosong. Tambahkan minimal 1 kolom tabel.");
|
|
383
|
+
}
|
|
384
|
+
if (!dashboardConfig.labels) {
|
|
385
|
+
issues.push("labels belum diisi. Sediakan objek labels agar teks UI tampil dengan benar.");
|
|
386
|
+
}
|
|
387
|
+
return { valid: issues.length === 0, issues };
|
|
388
|
+
}
|
|
389
|
+
|
|
315
390
|
// src/utils/formatters.js
|
|
316
391
|
function formatIDR(value) {
|
|
317
392
|
try {
|
|
@@ -1567,7 +1642,7 @@ function ChartCard({
|
|
|
1567
1642
|
className = ""
|
|
1568
1643
|
}) {
|
|
1569
1644
|
const content = (() => {
|
|
1570
|
-
if (loading) return /* @__PURE__ */ import_react14.default.createElement(SkeletonLoader, {
|
|
1645
|
+
if (loading) return /* @__PURE__ */ import_react14.default.createElement(SkeletonLoader, { style: { height: 256 } });
|
|
1571
1646
|
if (widget.type === "dailyArea") {
|
|
1572
1647
|
return renderDailyArea(labels, filters, chartData.dailyTrends);
|
|
1573
1648
|
}
|
|
@@ -1580,9 +1655,9 @@ function ChartCard({
|
|
|
1580
1655
|
if (widget.type === "topPackagesBar") {
|
|
1581
1656
|
return renderTopPackages(chartData.topPackages, labels, filters.sortPkgBy);
|
|
1582
1657
|
}
|
|
1583
|
-
return /* @__PURE__ */ import_react14.default.createElement("div", { className: "
|
|
1658
|
+
return /* @__PURE__ */ import_react14.default.createElement("div", { className: "rdb-muted" }, "Unsupported chart type.");
|
|
1584
1659
|
})();
|
|
1585
|
-
return /* @__PURE__ */ import_react14.default.createElement("div", { className: `card
|
|
1660
|
+
return /* @__PURE__ */ import_react14.default.createElement("div", { className: `rdb-card rdb-chart-card ${className}` }, /* @__PURE__ */ import_react14.default.createElement(
|
|
1586
1661
|
ChartHeader,
|
|
1587
1662
|
{
|
|
1588
1663
|
title: labels[widget.label] || widget.label,
|
|
@@ -1831,8 +1906,281 @@ DashboardLayout.propTypes = {
|
|
|
1831
1906
|
};
|
|
1832
1907
|
|
|
1833
1908
|
// src/presentation/ReusableDashboardView.jsx
|
|
1909
|
+
var import_react20 = __toESM(require("react"), 1);
|
|
1910
|
+
var import_prop_types18 = __toESM(require("prop-types"), 1);
|
|
1911
|
+
|
|
1912
|
+
// src/presentation/SetupWizard.jsx
|
|
1834
1913
|
var import_react19 = __toESM(require("react"), 1);
|
|
1835
1914
|
var import_prop_types17 = __toESM(require("prop-types"), 1);
|
|
1915
|
+
function SetupWizard({ issues = [], onDismiss, supabase }) {
|
|
1916
|
+
const [step, setStep] = (0, import_react19.useState)(0);
|
|
1917
|
+
const [tables, setTables] = (0, import_react19.useState)(null);
|
|
1918
|
+
const [loadingTables, setLoadingTables] = (0, import_react19.useState)(false);
|
|
1919
|
+
const [tableError, setTableError] = (0, import_react19.useState)("");
|
|
1920
|
+
const [dismissed, setDismissed] = (0, import_react19.useState)(false);
|
|
1921
|
+
if (dismissed) return null;
|
|
1922
|
+
const steps = [
|
|
1923
|
+
{ id: 0, label: "Overview", icon: "\u{1F3E0}" },
|
|
1924
|
+
{ id: 1, label: "1. Data Source", icon: "\u{1F5C4}\uFE0F" },
|
|
1925
|
+
{ id: 2, label: "2. Adapter", icon: "\u{1F504}" },
|
|
1926
|
+
{ id: 3, label: "3. Widget Config", icon: "\u2699\uFE0F" }
|
|
1927
|
+
];
|
|
1928
|
+
async function handleReadTables() {
|
|
1929
|
+
if (!supabase) {
|
|
1930
|
+
setTableError("Supabase client belum tersambung. Pastikan prop supabase sudah diisi.");
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
setLoadingTables(true);
|
|
1934
|
+
setTableError("");
|
|
1935
|
+
try {
|
|
1936
|
+
const { data, error } = await supabase.from("information_schema.tables").select("table_name").eq("table_schema", "public").eq("table_type", "BASE TABLE").order("table_name");
|
|
1937
|
+
if (error) throw error;
|
|
1938
|
+
setTables((data || []).map((r) => r.table_name));
|
|
1939
|
+
} catch (err) {
|
|
1940
|
+
try {
|
|
1941
|
+
const { data: rpcData } = await supabase.rpc("get_tables");
|
|
1942
|
+
if (rpcData) {
|
|
1943
|
+
setTables(rpcData.map((r) => r.table_name || r));
|
|
1944
|
+
} else {
|
|
1945
|
+
setTableError("Tidak dapat membaca tabel. Pastikan RLS mengizinkan akses atau tambahkan policy SELECT untuk anon.");
|
|
1946
|
+
}
|
|
1947
|
+
} catch {
|
|
1948
|
+
setTableError("Gagal membaca tabel dari Supabase: " + ((err == null ? void 0 : err.message) || "Unknown error"));
|
|
1949
|
+
}
|
|
1950
|
+
} finally {
|
|
1951
|
+
setLoadingTables(false);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
function handleDismiss() {
|
|
1955
|
+
setDismissed(true);
|
|
1956
|
+
if (onDismiss) onDismiss();
|
|
1957
|
+
}
|
|
1958
|
+
return /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-overlay", role: "dialog", "aria-modal": "true", "aria-label": "Dashboard Setup Wizard" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-modal" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-header" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-header-title" }, /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-wizard-logo" }, "\u{1F6E0}\uFE0F"), /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-h2", style: { margin: 0 } }, "Setup Dashboard"), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption" }, "Panduan konfigurasi modul @rozaqi02/reusable-dashboard"))), /* @__PURE__ */ import_react19.default.createElement(
|
|
1959
|
+
"button",
|
|
1960
|
+
{
|
|
1961
|
+
type: "button",
|
|
1962
|
+
className: "rdb-wizard-close",
|
|
1963
|
+
onClick: handleDismiss,
|
|
1964
|
+
"aria-label": "Tutup wizard",
|
|
1965
|
+
title: "Lanjutkan tanpa wizard"
|
|
1966
|
+
},
|
|
1967
|
+
"\u2715"
|
|
1968
|
+
)), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-steps" }, steps.map((s) => /* @__PURE__ */ import_react19.default.createElement(
|
|
1969
|
+
"button",
|
|
1970
|
+
{
|
|
1971
|
+
key: s.id,
|
|
1972
|
+
type: "button",
|
|
1973
|
+
className: `rdb-wizard-step-btn ${step === s.id ? "rdb-wizard-step-active" : ""}`,
|
|
1974
|
+
onClick: () => setStep(s.id)
|
|
1975
|
+
},
|
|
1976
|
+
/* @__PURE__ */ import_react19.default.createElement("span", null, s.icon),
|
|
1977
|
+
/* @__PURE__ */ import_react19.default.createElement("span", null, s.label)
|
|
1978
|
+
))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-body" }, step === 0 && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-section" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-alert" }, /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-wizard-alert-icon" }, "\u26A0\uFE0F"), /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-body", style: { fontWeight: 600 } }, "Dashboard belum terkonfigurasi"), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 4 } }, "Ditemukan ", issues.length, " masalah yang perlu diselesaikan sebelum dashboard dapat berjalan."))), issues.length > 0 && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-issues" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 8 } }, "Masalah yang ditemukan:"), issues.map((issue, i) => /* @__PURE__ */ import_react19.default.createElement("div", { key: i, className: "rdb-wizard-issue-item" }, /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-wizard-issue-icon" }, "\u274C"), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-body" }, issue)))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-flow" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 12 } }, "Alur konfigurasi yang perlu dilakukan:"), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-flow-steps" }, [
|
|
1979
|
+
{ num: "1", label: "Data Source", desc: "Tulis fungsi query ke Supabase" },
|
|
1980
|
+
{ num: "\u2192", label: "", desc: "" },
|
|
1981
|
+
{ num: "2", label: "Adapter", desc: "Petakan data mentah ke format standar" },
|
|
1982
|
+
{ num: "\u2192", label: "", desc: "" },
|
|
1983
|
+
{ num: "3", label: "Widget Config", desc: "Definisikan widget yang ditampilkan" }
|
|
1984
|
+
].map((item, i) => item.num === "\u2192" ? /* @__PURE__ */ import_react19.default.createElement("div", { key: i, className: "rdb-wizard-flow-arrow" }, "\u2192") : /* @__PURE__ */ import_react19.default.createElement("div", { key: i, className: "rdb-wizard-flow-box" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-flow-num" }, item.num), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-flow-label" }, item.label), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-flow-desc" }, item.desc))))), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 16 } }, /* @__PURE__ */ import_react19.default.createElement("button", { type: "button", className: "rdb-btn rdb-btn-secondary rdb-btn-sm", onClick: handleDismiss }, "Lanjutkan tanpa wizard"), /* @__PURE__ */ import_react19.default.createElement("button", { type: "button", className: "rdb-btn rdb-btn-primary rdb-btn-sm", onClick: () => setStep(1) }, "Mulai panduan \u2192"))), step === 1 && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-section" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-step-title" }, /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-wizard-step-num" }, "1"), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-h3", style: { margin: 0 } }, "Data Source")), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-body", style: { color: "var(--rdb-text-muted)", marginBottom: 12 } }, "Data Source adalah fungsi yang membaca data dari Supabase. Buat file", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, " src/datasources/myDataSource.js"), " dengan isi berikut:"), /* @__PURE__ */ import_react19.default.createElement("pre", { className: "rdb-wizard-code" }, `// src/datasources/myDataSource.js
|
|
1985
|
+
export function createMySupabaseSource(supabase) {
|
|
1986
|
+
return {
|
|
1987
|
+
async fetchDashboardSnapshot({ fromISO, toISO, statusScope }) {
|
|
1988
|
+
const { data: orders = [] } = await supabase
|
|
1989
|
+
.from("MY_TABLE") // \u2190 ganti nama tabel kamu
|
|
1990
|
+
.select("id, created_at, total_price, status, customer_name")
|
|
1991
|
+
.gte("created_at", fromISO)
|
|
1992
|
+
.lte("created_at", toISO)
|
|
1993
|
+
.order("created_at", { ascending: true });
|
|
1994
|
+
|
|
1995
|
+
const { data: recent = [] } = await supabase
|
|
1996
|
+
.from("MY_TABLE")
|
|
1997
|
+
.select("id, created_at, customer_name, total_price, status")
|
|
1998
|
+
.order("created_at", { ascending: false })
|
|
1999
|
+
.limit(10);
|
|
2000
|
+
|
|
2001
|
+
return {
|
|
2002
|
+
bookings: orders, // WAJIB: nama harus "bookings"
|
|
2003
|
+
recent, // WAJIB: nama harus "recent"
|
|
2004
|
+
packageLocales: [],
|
|
2005
|
+
staticCounts: { packages: 0, sections: 0 },
|
|
2006
|
+
};
|
|
2007
|
+
},
|
|
2008
|
+
|
|
2009
|
+
// Opsional: live update
|
|
2010
|
+
subscribeLiveUpdate(onEvent) {
|
|
2011
|
+
const ch = supabase.channel("my-dashboard-live")
|
|
2012
|
+
.on("postgres_changes",
|
|
2013
|
+
{ event: "*", schema: "public", table: "MY_TABLE" }, onEvent)
|
|
2014
|
+
.subscribe();
|
|
2015
|
+
return () => supabase.removeChannel(ch);
|
|
2016
|
+
},
|
|
2017
|
+
};
|
|
2018
|
+
}`), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-supabase-reader" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 8 } }, "\u{1F50D} Baca tabel dari Supabase project kamu secara langsung:"), /* @__PURE__ */ import_react19.default.createElement(
|
|
2019
|
+
"button",
|
|
2020
|
+
{
|
|
2021
|
+
type: "button",
|
|
2022
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2023
|
+
onClick: handleReadTables,
|
|
2024
|
+
disabled: loadingTables
|
|
2025
|
+
},
|
|
2026
|
+
loadingTables ? "Membaca..." : "Tampilkan daftar tabel Supabase"
|
|
2027
|
+
), tableError && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-table-error" }, tableError), tables && tables.length > 0 && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-table-list" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginBottom: 6 } }, "Tabel ditemukan (", tables.length, "):"), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-table-chips" }, tables.map((t) => /* @__PURE__ */ import_react19.default.createElement("span", { key: t, className: "rdb-wizard-chip" }, t))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 8, color: "var(--rdb-text-muted)" } }, "Ganti ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "MY_TABLE"), " di contoh kode di atas dengan nama tabel yang sesuai.")), !supabase && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-table-error" }, "Tambahkan prop ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "supabase"), " ke", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, " <ReusableDashboardView supabase=", "{supabase}", " />"), " untuk mengaktifkan fitur ini.")), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 16 } }, /* @__PURE__ */ import_react19.default.createElement("button", { type: "button", className: "rdb-btn rdb-btn-secondary rdb-btn-sm", onClick: () => setStep(0) }, "\u2190 Kembali"), /* @__PURE__ */ import_react19.default.createElement("button", { type: "button", className: "rdb-btn rdb-btn-primary rdb-btn-sm", onClick: () => setStep(2) }, "Lanjut \u2192"))), step === 2 && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-section" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-step-title" }, /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-wizard-step-num" }, "2"), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-h3", style: { margin: 0 } }, "Data Adapter")), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-body", style: { color: "var(--rdb-text-muted)", marginBottom: 12 } }, "Adapter mengubah data mentah dari Data Source ke format standar yang dimengerti komponen dashboard. Buat file ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "src/adapters/myAdapter.js"), ":"), /* @__PURE__ */ import_react19.default.createElement("pre", { className: "rdb-wizard-code" }, `// src/adapters/myAdapter.js
|
|
2028
|
+
import { toNumber, buildDayBuckets } from "@rozaqi02/reusable-dashboard";
|
|
2029
|
+
|
|
2030
|
+
export function createEmptyMyData() {
|
|
2031
|
+
return {
|
|
2032
|
+
stats: { bookingsConfirm: 0, revenueConfirm: 0 },
|
|
2033
|
+
charts: {
|
|
2034
|
+
dailyTrends: [],
|
|
2035
|
+
statusDistribution: [],
|
|
2036
|
+
audienceDistribution: [],
|
|
2037
|
+
topPackages: [],
|
|
2038
|
+
},
|
|
2039
|
+
table: { recentBookings: [] },
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
export function adaptMyData({ raw, range, dateLocale, labels }) {
|
|
2044
|
+
if (!raw) return createEmptyMyData();
|
|
2045
|
+
|
|
2046
|
+
const buckets = buildDayBuckets(range.daysWindow, range.fromISO, dateLocale);
|
|
2047
|
+
const dayMap = new Map(buckets.map(b => [b.dateKey, b]));
|
|
2048
|
+
const statusMap = new Map();
|
|
2049
|
+
let confirmed = 0, revenue = 0;
|
|
2050
|
+
|
|
2051
|
+
(raw.bookings || []).forEach(row => {
|
|
2052
|
+
const status = String(row.status || "pending").toLowerCase();
|
|
2053
|
+
const amount = toNumber(row.total_price); // \u2190 sesuaikan nama kolom
|
|
2054
|
+
const dayKey = String(row.created_at || "").slice(0, 10);
|
|
2055
|
+
|
|
2056
|
+
statusMap.set(status, (statusMap.get(status) || 0) + 1);
|
|
2057
|
+
const bucket = dayMap.get(dayKey);
|
|
2058
|
+
if (bucket && status === "confirmed") {
|
|
2059
|
+
bucket.count += 1;
|
|
2060
|
+
bucket.revenue += amount;
|
|
2061
|
+
}
|
|
2062
|
+
if (status === "confirmed") { confirmed++; revenue += amount; }
|
|
2063
|
+
});
|
|
2064
|
+
|
|
2065
|
+
return {
|
|
2066
|
+
stats: { bookingsConfirm: confirmed, revenueConfirm: revenue },
|
|
2067
|
+
charts: {
|
|
2068
|
+
dailyTrends: buckets,
|
|
2069
|
+
statusDistribution: Array.from(statusMap.entries())
|
|
2070
|
+
.map(([status, count]) => ({
|
|
2071
|
+
status,
|
|
2072
|
+
label: labels?.formatStatusLabel?.(status) || status,
|
|
2073
|
+
count,
|
|
2074
|
+
})),
|
|
2075
|
+
audienceDistribution: [],
|
|
2076
|
+
topPackages: [],
|
|
2077
|
+
},
|
|
2078
|
+
table: {
|
|
2079
|
+
recentBookings: (raw.recent || []).map(row => ({
|
|
2080
|
+
id: row.id,
|
|
2081
|
+
createdAt: row.created_at,
|
|
2082
|
+
customerName: row.customer_name || "-",
|
|
2083
|
+
packageName: row.service_type || "-", // \u2190 sesuaikan nama kolom
|
|
2084
|
+
audienceLabel: "-",
|
|
2085
|
+
totalIDR: toNumber(row.total_price),
|
|
2086
|
+
status: String(row.status || "pending").toLowerCase(),
|
|
2087
|
+
statusLabel: labels?.formatStatusLabel?.(row.status) || row.status,
|
|
2088
|
+
})),
|
|
2089
|
+
},
|
|
2090
|
+
};
|
|
2091
|
+
}`), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-tip" }, "\u{1F4A1} ", /* @__PURE__ */ import_react19.default.createElement("strong", null, "Aturan penting:"), " key di ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "stats"), " (mis. ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "bookingsConfirm"), ") harus cocok dengan ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "valueKey"), " di widget config pada Step 3."), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 16 } }, /* @__PURE__ */ import_react19.default.createElement("button", { type: "button", className: "rdb-btn rdb-btn-secondary rdb-btn-sm", onClick: () => setStep(1) }, "\u2190 Kembali"), /* @__PURE__ */ import_react19.default.createElement("button", { type: "button", className: "rdb-btn rdb-btn-primary rdb-btn-sm", onClick: () => setStep(3) }, "Lanjut \u2192"))), step === 3 && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-section" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-step-title" }, /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-wizard-step-num" }, "3"), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-h3", style: { margin: 0 } }, "Widget Config & Rangkaian Akhir")), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-body", style: { color: "var(--rdb-text-muted)", marginBottom: 12 } }, "Widget Config mendefinisikan tampilan dashboard: kartu mana, chart apa, kolom tabel apa. Gunakan ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "createDashboardConfig()"), " untuk mengemas semuanya jadi 1 objek:"), /* @__PURE__ */ import_react19.default.createElement("pre", { className: "rdb-wizard-code" }, `// src/pages/MyDashboard.jsx
|
|
2092
|
+
import { supabase } from "../lib/supabaseClient";
|
|
2093
|
+
import {
|
|
2094
|
+
ReusableDashboardView,
|
|
2095
|
+
useReusableDashboard,
|
|
2096
|
+
createDashboardConfig,
|
|
2097
|
+
} from "@rozaqi02/reusable-dashboard";
|
|
2098
|
+
|
|
2099
|
+
import { createMySupabaseSource } from "../datasources/myDataSource";
|
|
2100
|
+
import { adaptMyData, createEmptyMyData } from "../adapters/myAdapter";
|
|
2101
|
+
|
|
2102
|
+
// 1. Widget Config \u2014 deklaratif, tentukan apa yang tampil
|
|
2103
|
+
const myWidgetConfig = {
|
|
2104
|
+
id: "my.dashboard",
|
|
2105
|
+
defaultFilters: { statusScope: "confirmed", daysPreset: 30 },
|
|
2106
|
+
widgets: {
|
|
2107
|
+
stats: [
|
|
2108
|
+
{ id: "orders", label: "confirmedBookings", icon: "TrendingUp",
|
|
2109
|
+
valueKey: "bookingsConfirm", format: "number", accentColor: "blue" },
|
|
2110
|
+
{ id: "revenue", label: "confirmedRevenue", icon: "DollarSign",
|
|
2111
|
+
valueKey: "revenueConfirm", format: "currency", accentColor: "green" },
|
|
2112
|
+
],
|
|
2113
|
+
charts: [
|
|
2114
|
+
{ id: "trend", type: "dailyArea", label: "dailyTrends", icon: "BarChart3" },
|
|
2115
|
+
{ id: "status", type: "statusPie", label: "statusDistribution", icon: "PieChart" },
|
|
2116
|
+
],
|
|
2117
|
+
table: {
|
|
2118
|
+
id: "recent", label: "recentBookings", icon: "Calendar",
|
|
2119
|
+
emptyLabel: "noRecentBookings",
|
|
2120
|
+
columns: [
|
|
2121
|
+
{ id: "date", label: "date", accessor: "createdAt", type: "date" },
|
|
2122
|
+
{ id: "customer", label: "customer", accessor: "customerName" },
|
|
2123
|
+
{ id: "total", label: "total", accessor: "totalIDR", type: "currency" },
|
|
2124
|
+
{ id: "status", label: "status", accessor: "statusLabel",
|
|
2125
|
+
type: "statusBadge", statusAccessor: "status" },
|
|
2126
|
+
],
|
|
2127
|
+
},
|
|
2128
|
+
},
|
|
2129
|
+
};
|
|
2130
|
+
|
|
2131
|
+
// 2. Kemas semua jadi 1 objek (buat di luar komponen, sekali saja)
|
|
2132
|
+
const dashConfig = createDashboardConfig({
|
|
2133
|
+
widgetConfig: myWidgetConfig,
|
|
2134
|
+
dataSource: createMySupabaseSource(supabase),
|
|
2135
|
+
adapter: adaptMyData,
|
|
2136
|
+
createEmptyState: createEmptyMyData,
|
|
2137
|
+
languageCode: "id",
|
|
2138
|
+
dateLocale: "id-ID",
|
|
2139
|
+
});
|
|
2140
|
+
|
|
2141
|
+
// 3. Labels \u2014 teks UI
|
|
2142
|
+
const labels = { title: "Dashboard Saya", refresh: "Refresh",
|
|
2143
|
+
liveUpdate: "Live", loadFailed: "Gagal memuat.", retry: "Coba Lagi",
|
|
2144
|
+
confirmedBookings: "Total Order", confirmedRevenue: "Total Pendapatan",
|
|
2145
|
+
dailyTrends: "Tren Harian", statusDistribution: "Distribusi Status",
|
|
2146
|
+
recentBookings: "Order Terbaru", noRecentBookings: "Belum ada order",
|
|
2147
|
+
date: "Tanggal", customer: "Pelanggan", total: "Total", status: "Status",
|
|
2148
|
+
bookingsMetric: "Order", revenueMetric: "Pendapatan",
|
|
2149
|
+
confirmedBookingMetric: "Order (Confirmed)",
|
|
2150
|
+
confirmedRevenueMetric: "Pendapatan (Confirmed)",
|
|
2151
|
+
dayLabel: n => n + " hari",
|
|
2152
|
+
formatStatusLabel: s =>
|
|
2153
|
+
({ confirmed: "Selesai", pending: "Proses", cancelled: "Batal" })[s] || s,
|
|
2154
|
+
formatAudienceLabel: v => v || "-",
|
|
2155
|
+
};
|
|
2156
|
+
|
|
2157
|
+
// 4. Render \u2014 selesai!
|
|
2158
|
+
export default function MyDashboard() {
|
|
2159
|
+
const state = useReusableDashboard({ ...dashConfig, labels });
|
|
2160
|
+
return (
|
|
2161
|
+
<ReusableDashboardView
|
|
2162
|
+
config={dashConfig.config}
|
|
2163
|
+
labels={labels}
|
|
2164
|
+
loading={state.loading}
|
|
2165
|
+
error={state.error}
|
|
2166
|
+
filters={state.filters}
|
|
2167
|
+
onFilterChange={state.updateFilter}
|
|
2168
|
+
onResetFilters={state.resetFilters}
|
|
2169
|
+
onRefresh={state.refresh}
|
|
2170
|
+
data={state.data}
|
|
2171
|
+
dateLocale={dashConfig.dateLocale}
|
|
2172
|
+
liveUpdateEnabled={state.liveUpdateEnabled}
|
|
2173
|
+
/>
|
|
2174
|
+
);
|
|
2175
|
+
}`), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-tip" }, "\u2705 ", /* @__PURE__ */ import_react19.default.createElement("strong", null, "Selesai!"), " Jalankan ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "npm start"), " dan buka halaman dashboard. Wizard ini tidak akan muncul lagi setelah konfigurasi valid."), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 16 } }, /* @__PURE__ */ import_react19.default.createElement("button", { type: "button", className: "rdb-btn rdb-btn-secondary rdb-btn-sm", onClick: () => setStep(2) }, "\u2190 Kembali"), /* @__PURE__ */ import_react19.default.createElement("button", { type: "button", className: "rdb-btn rdb-btn-primary rdb-btn-sm", onClick: handleDismiss }, "Tutup & lanjutkan \u2713"))))));
|
|
2176
|
+
}
|
|
2177
|
+
SetupWizard.propTypes = {
|
|
2178
|
+
issues: import_prop_types17.default.arrayOf(import_prop_types17.default.string),
|
|
2179
|
+
onDismiss: import_prop_types17.default.func,
|
|
2180
|
+
supabase: import_prop_types17.default.object
|
|
2181
|
+
};
|
|
2182
|
+
|
|
2183
|
+
// src/presentation/ReusableDashboardView.jsx
|
|
1836
2184
|
function ReusableDashboardView({
|
|
1837
2185
|
config,
|
|
1838
2186
|
labels,
|
|
@@ -1844,9 +2192,48 @@ function ReusableDashboardView({
|
|
|
1844
2192
|
onRefresh,
|
|
1845
2193
|
data,
|
|
1846
2194
|
dateLocale,
|
|
1847
|
-
liveUpdateEnabled
|
|
2195
|
+
liveUpdateEnabled,
|
|
2196
|
+
supabase,
|
|
2197
|
+
dashboardConfig
|
|
1848
2198
|
}) {
|
|
1849
|
-
|
|
2199
|
+
var _a;
|
|
2200
|
+
const [wizardDismissed, setWizardDismissed] = import_react20.default.useState(false);
|
|
2201
|
+
const configIssues = import_react20.default.useMemo(() => {
|
|
2202
|
+
var _a2, _b, _c, _d, _e, _f, _g;
|
|
2203
|
+
if (wizardDismissed) return [];
|
|
2204
|
+
if (dashboardConfig) {
|
|
2205
|
+
const { issues: issues2 } = validateDashboardConfig(dashboardConfig);
|
|
2206
|
+
return issues2;
|
|
2207
|
+
}
|
|
2208
|
+
const issues = [];
|
|
2209
|
+
if (!config) issues.push("Prop 'config' (widgetConfig) belum diisi.");
|
|
2210
|
+
if (!((_b = (_a2 = config == null ? void 0 : config.widgets) == null ? void 0 : _a2.stats) == null ? void 0 : _b.length)) issues.push("widgetConfig.widgets.stats kosong. Tambahkan minimal 1 stat card.");
|
|
2211
|
+
if (!((_d = (_c = config == null ? void 0 : config.widgets) == null ? void 0 : _c.charts) == null ? void 0 : _d.length)) issues.push("widgetConfig.widgets.charts kosong. Tambahkan minimal 1 chart.");
|
|
2212
|
+
if (!((_g = (_f = (_e = config == null ? void 0 : config.widgets) == null ? void 0 : _e.table) == null ? void 0 : _f.columns) == null ? void 0 : _g.length)) issues.push("widgetConfig.widgets.table.columns kosong.");
|
|
2213
|
+
if (!labels) issues.push("Prop 'labels' belum diisi.");
|
|
2214
|
+
if (!(labels == null ? void 0 : labels.title)) issues.push("labels.title belum diisi.");
|
|
2215
|
+
if (!(labels == null ? void 0 : labels.formatStatusLabel)) issues.push("labels.formatStatusLabel (function) belum diisi.");
|
|
2216
|
+
return issues;
|
|
2217
|
+
}, [config, labels, dashboardConfig, wizardDismissed]);
|
|
2218
|
+
const showWizard = !wizardDismissed && configIssues.length > 0;
|
|
2219
|
+
if (!config || !config.widgets) {
|
|
2220
|
+
return /* @__PURE__ */ import_react20.default.createElement("div", { className: "rdb-view" }, /* @__PURE__ */ import_react20.default.createElement(
|
|
2221
|
+
SetupWizard,
|
|
2222
|
+
{
|
|
2223
|
+
issues: ["Prop 'config' (widgetConfig) belum diisi atau tidak valid."],
|
|
2224
|
+
onDismiss: () => setWizardDismissed(true),
|
|
2225
|
+
supabase
|
|
2226
|
+
}
|
|
2227
|
+
));
|
|
2228
|
+
}
|
|
2229
|
+
return /* @__PURE__ */ import_react20.default.createElement("div", { className: "rdb-view" }, showWizard && /* @__PURE__ */ import_react20.default.createElement(
|
|
2230
|
+
SetupWizard,
|
|
2231
|
+
{
|
|
2232
|
+
issues: configIssues,
|
|
2233
|
+
onDismiss: () => setWizardDismissed(true),
|
|
2234
|
+
supabase
|
|
2235
|
+
}
|
|
2236
|
+
), /* @__PURE__ */ import_react20.default.createElement("div", { className: "rdb-view-container" }, /* @__PURE__ */ import_react20.default.createElement("div", { className: "rdb-header-panel" }, /* @__PURE__ */ import_react20.default.createElement("div", { className: "rdb-header-top" }, /* @__PURE__ */ import_react20.default.createElement("div", { className: "rdb-header-title-group" }, /* @__PURE__ */ import_react20.default.createElement("span", { className: "rdb-h1" }, (labels == null ? void 0 : labels.title) ?? "Dashboard"), liveUpdateEnabled ? /* @__PURE__ */ import_react20.default.createElement(Badge, { status: "success" }, (labels == null ? void 0 : labels.liveUpdate) ?? "Live") : null), /* @__PURE__ */ import_react20.default.createElement(Button, { variant: "secondary", size: "sm", onClick: () => onRefresh(), title: (labels == null ? void 0 : labels.refresh) ?? "Refresh" }, /* @__PURE__ */ import_react20.default.createElement(Icon, { name: "RotateCcw", size: 16 }))), error ? /* @__PURE__ */ import_react20.default.createElement("div", { className: "rdb-error-banner" }, /* @__PURE__ */ import_react20.default.createElement("span", null, error), /* @__PURE__ */ import_react20.default.createElement(Button, { variant: "secondary", size: "sm", onClick: () => onRefresh() }, (labels == null ? void 0 : labels.retry) ?? "Retry")) : null, /* @__PURE__ */ import_react20.default.createElement(
|
|
1850
2237
|
FilterPanel,
|
|
1851
2238
|
{
|
|
1852
2239
|
filters,
|
|
@@ -1854,42 +2241,45 @@ function ReusableDashboardView({
|
|
|
1854
2241
|
onFilterChange,
|
|
1855
2242
|
onResetFilters
|
|
1856
2243
|
}
|
|
1857
|
-
)), /* @__PURE__ */
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
2244
|
+
)), /* @__PURE__ */ import_react20.default.createElement("div", { className: "rdb-stats-grid" }, loading ? Array.from({ length: config.widgets.stats.length || 4 }).map((_, i) => /* @__PURE__ */ import_react20.default.createElement(SkeletonLoader, { key: i, style: { height: 112 } })) : config.widgets.stats.map((widget) => {
|
|
2245
|
+
var _a2;
|
|
2246
|
+
return /* @__PURE__ */ import_react20.default.createElement(
|
|
2247
|
+
StatCard,
|
|
2248
|
+
{
|
|
2249
|
+
key: widget.id,
|
|
2250
|
+
label: (labels == null ? void 0 : labels[widget.label]) ?? widget.label,
|
|
2251
|
+
value: ((_a2 = data == null ? void 0 : data.stats) == null ? void 0 : _a2[widget.valueKey]) ?? 0,
|
|
2252
|
+
icon: widget.icon,
|
|
2253
|
+
format: widget.format,
|
|
2254
|
+
accentColor: widget.accentColor
|
|
2255
|
+
}
|
|
2256
|
+
);
|
|
2257
|
+
})), /* @__PURE__ */ import_react20.default.createElement("div", { className: "rdb-charts-grid" }, config.widgets.charts.map((widget) => /* @__PURE__ */ import_react20.default.createElement(
|
|
1868
2258
|
ChartCard,
|
|
1869
2259
|
{
|
|
1870
2260
|
key: widget.id,
|
|
1871
2261
|
widget,
|
|
1872
|
-
labels,
|
|
2262
|
+
labels: labels ?? {},
|
|
1873
2263
|
loading,
|
|
1874
|
-
filters,
|
|
1875
|
-
chartData: data.charts
|
|
2264
|
+
filters: filters ?? {},
|
|
2265
|
+
chartData: (data == null ? void 0 : data.charts) ?? {}
|
|
1876
2266
|
}
|
|
1877
|
-
))), /* @__PURE__ */
|
|
2267
|
+
))), /* @__PURE__ */ import_react20.default.createElement("div", { className: "rdb-card", style: { padding: 16 } }, /* @__PURE__ */ import_react20.default.createElement(
|
|
1878
2268
|
"div",
|
|
1879
2269
|
{
|
|
1880
2270
|
style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 12 },
|
|
1881
2271
|
className: "rdb-h3"
|
|
1882
2272
|
},
|
|
1883
|
-
/* @__PURE__ */
|
|
1884
|
-
labels[config.widgets.table.label]
|
|
1885
|
-
), loading ? /* @__PURE__ */
|
|
2273
|
+
/* @__PURE__ */ import_react20.default.createElement(Icon, { name: config.widgets.table.icon ?? "Calendar", size: 18 }),
|
|
2274
|
+
(labels == null ? void 0 : labels[config.widgets.table.label]) ?? config.widgets.table.label
|
|
2275
|
+
), loading ? /* @__PURE__ */ import_react20.default.createElement(SkeletonLoader, { style: { height: 192 } }) : /* @__PURE__ */ import_react20.default.createElement(
|
|
1886
2276
|
DataTable,
|
|
1887
2277
|
{
|
|
1888
2278
|
columns: config.widgets.table.columns,
|
|
1889
|
-
data: data.table.recentBookings,
|
|
1890
|
-
labels,
|
|
1891
|
-
dateLocale,
|
|
1892
|
-
emptyLabel: labels[config.widgets.table.emptyLabel]
|
|
2279
|
+
data: ((_a = data == null ? void 0 : data.table) == null ? void 0 : _a.recentBookings) ?? [],
|
|
2280
|
+
labels: labels ?? {},
|
|
2281
|
+
dateLocale: dateLocale ?? "id-ID",
|
|
2282
|
+
emptyLabel: (labels == null ? void 0 : labels[config.widgets.table.emptyLabel]) ?? config.widgets.table.emptyLabel,
|
|
1893
2283
|
searchable: true,
|
|
1894
2284
|
sortable: true,
|
|
1895
2285
|
pageSize: 10
|
|
@@ -1897,17 +2287,21 @@ function ReusableDashboardView({
|
|
|
1897
2287
|
))));
|
|
1898
2288
|
}
|
|
1899
2289
|
ReusableDashboardView.propTypes = {
|
|
1900
|
-
config:
|
|
1901
|
-
labels:
|
|
1902
|
-
loading:
|
|
1903
|
-
error:
|
|
1904
|
-
filters:
|
|
1905
|
-
onFilterChange:
|
|
1906
|
-
onResetFilters:
|
|
1907
|
-
onRefresh:
|
|
1908
|
-
data:
|
|
1909
|
-
dateLocale:
|
|
1910
|
-
liveUpdateEnabled:
|
|
2290
|
+
config: import_prop_types18.default.object.isRequired,
|
|
2291
|
+
labels: import_prop_types18.default.object.isRequired,
|
|
2292
|
+
loading: import_prop_types18.default.bool,
|
|
2293
|
+
error: import_prop_types18.default.string,
|
|
2294
|
+
filters: import_prop_types18.default.object,
|
|
2295
|
+
onFilterChange: import_prop_types18.default.func.isRequired,
|
|
2296
|
+
onResetFilters: import_prop_types18.default.func.isRequired,
|
|
2297
|
+
onRefresh: import_prop_types18.default.func.isRequired,
|
|
2298
|
+
data: import_prop_types18.default.object,
|
|
2299
|
+
dateLocale: import_prop_types18.default.string,
|
|
2300
|
+
liveUpdateEnabled: import_prop_types18.default.bool,
|
|
2301
|
+
/** Supabase client — aktifkan fitur baca tabel di wizard */
|
|
2302
|
+
supabase: import_prop_types18.default.object,
|
|
2303
|
+
/** Hasil createDashboardConfig() — untuk validasi otomatis wizard */
|
|
2304
|
+
dashboardConfig: import_prop_types18.default.object
|
|
1911
2305
|
};
|
|
1912
2306
|
|
|
1913
2307
|
// src/utils/labels.js
|
|
@@ -2043,6 +2437,7 @@ function createDashboardLabels(t) {
|
|
|
2043
2437
|
Input,
|
|
2044
2438
|
ReusableDashboardView,
|
|
2045
2439
|
SearchBar,
|
|
2440
|
+
SetupWizard,
|
|
2046
2441
|
SidebarNavigation,
|
|
2047
2442
|
SkeletonLoader,
|
|
2048
2443
|
StatCard,
|
|
@@ -2054,6 +2449,7 @@ function createDashboardLabels(t) {
|
|
|
2054
2449
|
buildDayBuckets,
|
|
2055
2450
|
cidikaWidgetConfig,
|
|
2056
2451
|
createCidikaSupabaseSource,
|
|
2452
|
+
createDashboardConfig,
|
|
2057
2453
|
createDashboardLabels,
|
|
2058
2454
|
createDefaultFilters,
|
|
2059
2455
|
createEmptyDashboardData,
|
|
@@ -2071,6 +2467,7 @@ function createDashboardLabels(t) {
|
|
|
2071
2467
|
toNumber,
|
|
2072
2468
|
tokoSepatuWidgetConfig,
|
|
2073
2469
|
useRealtimeUpdate,
|
|
2074
|
-
useReusableDashboard
|
|
2470
|
+
useReusableDashboard,
|
|
2471
|
+
validateDashboardConfig
|
|
2075
2472
|
});
|
|
2076
2473
|
//# sourceMappingURL=index.cjs.map
|