@rozaqi02/reusable-dashboard 1.1.3 → 1.1.5
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 +215 -2
- package/dist/index.cjs +585 -194
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +586 -195
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1912,267 +1912,658 @@ var import_prop_types18 = __toESM(require("prop-types"), 1);
|
|
|
1912
1912
|
// src/presentation/SetupWizard.jsx
|
|
1913
1913
|
var import_react19 = __toESM(require("react"), 1);
|
|
1914
1914
|
var import_prop_types17 = __toESM(require("prop-types"), 1);
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
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) {
|
|
1915
|
+
var PRESET_SIGNATURES = {
|
|
1916
|
+
cidika: { name: "Cidika Travel", tables: ["bookings", "packages", "package_locales"] },
|
|
1917
|
+
tokoSepatu: { name: "Toko Sepatu / E-Commerce", tables: ["orders", "products", "customers"] }
|
|
1918
|
+
};
|
|
1919
|
+
function detectPreset(tables) {
|
|
1920
|
+
if (!(tables == null ? void 0 : tables.length)) return null;
|
|
1921
|
+
const s = new Set(tables);
|
|
1922
|
+
if (PRESET_SIGNATURES.cidika.tables.every((t) => s.has(t))) return "cidika";
|
|
1923
|
+
if (PRESET_SIGNATURES.tokoSepatu.tables.every((t) => s.has(t))) return "tokoSepatu";
|
|
1924
|
+
return null;
|
|
1925
|
+
}
|
|
1926
|
+
var SQL_GRANT_SCHEMA = `GRANT SELECT ON information_schema.tables TO anon;
|
|
1927
|
+
GRANT SELECT ON information_schema.columns TO anon;`;
|
|
1928
|
+
var sqlRls = (tbl) => `-- Pilih salah satu sesuai kebutuhan (jalankan di Supabase SQL Editor):
|
|
1929
|
+
|
|
1930
|
+
-- Opsi 1: Untuk DEVELOPMENT \u2014 nonaktifkan RLS (paling cepat)
|
|
1931
|
+
ALTER TABLE public.${tbl} DISABLE ROW LEVEL SECURITY;
|
|
1932
|
+
|
|
1933
|
+
-- Opsi 2: Untuk PRODUCTION \u2014 izinkan semua orang baca
|
|
1934
|
+
ALTER TABLE public.${tbl} ENABLE ROW LEVEL SECURITY;
|
|
1935
|
+
CREATE POLICY "dashboard_read" ON public.${tbl}
|
|
1936
|
+
FOR SELECT TO anon USING (true);
|
|
1937
|
+
|
|
1938
|
+
-- Opsi 3: Untuk PRODUCTION \u2014 hanya user yang login
|
|
1939
|
+
ALTER TABLE public.${tbl} ENABLE ROW LEVEL SECURITY;
|
|
1940
|
+
CREATE POLICY "dashboard_auth" ON public.${tbl}
|
|
1941
|
+
FOR SELECT TO authenticated USING (true);`;
|
|
1942
|
+
function generateCode({
|
|
1943
|
+
tableName,
|
|
1944
|
+
colDate,
|
|
1945
|
+
colStatus,
|
|
1946
|
+
colTotal,
|
|
1947
|
+
colCustomer,
|
|
1948
|
+
colItem,
|
|
1949
|
+
dashTitle,
|
|
1950
|
+
confirmedValue,
|
|
1951
|
+
pendingValue
|
|
1952
|
+
}) {
|
|
1953
|
+
const title = dashTitle || "Dashboard";
|
|
1954
|
+
const confirmed = confirmedValue || "confirmed";
|
|
1955
|
+
const pending = pendingValue || "pending";
|
|
1956
|
+
const custCol = colCustomer || "customer_name";
|
|
1957
|
+
const itemCol = colItem || "-";
|
|
1958
|
+
const totalCol = colTotal || "total";
|
|
1959
|
+
const cols = [
|
|
1960
|
+
"id",
|
|
1961
|
+
colDate,
|
|
1962
|
+
colStatus,
|
|
1963
|
+
totalCol,
|
|
1964
|
+
...custCol !== "customer_name" ? [custCol] : ["customer_name"],
|
|
1965
|
+
...itemCol !== "-" ? [itemCol] : []
|
|
1966
|
+
].filter(Boolean).join(", ");
|
|
1967
|
+
const dataSource = `// src/datasources/myDashboardSource.js
|
|
1968
|
+
// AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
|
|
1969
|
+
|
|
1970
|
+
export function createMyDashboardSource(supabase) {
|
|
1986
1971
|
return {
|
|
1987
1972
|
async fetchDashboardSnapshot({ fromISO, toISO, statusScope }) {
|
|
1988
|
-
const
|
|
1989
|
-
.from("
|
|
1990
|
-
.select("
|
|
1991
|
-
.gte("
|
|
1992
|
-
.lte("
|
|
1993
|
-
.order("
|
|
1973
|
+
const allQ = supabase
|
|
1974
|
+
.from("${tableName}")
|
|
1975
|
+
.select("${cols}")
|
|
1976
|
+
.gte("${colDate}", fromISO)
|
|
1977
|
+
.lte("${colDate}", toISO)
|
|
1978
|
+
.order("${colDate}", { ascending: true });
|
|
1994
1979
|
|
|
1995
|
-
const
|
|
1996
|
-
.from("
|
|
1997
|
-
.select("
|
|
1998
|
-
.
|
|
1980
|
+
const recentQ = supabase
|
|
1981
|
+
.from("${tableName}")
|
|
1982
|
+
.select("${cols}")
|
|
1983
|
+
.gte("${colDate}", fromISO)
|
|
1984
|
+
.lte("${colDate}", toISO)
|
|
1985
|
+
.order("${colDate}", { ascending: false })
|
|
1999
1986
|
.limit(10);
|
|
2000
1987
|
|
|
1988
|
+
if (statusScope && statusScope !== "all") {
|
|
1989
|
+
recentQ.eq("${colStatus}", statusScope);
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
const [all, recent] = await Promise.all([allQ, recentQ]);
|
|
2001
1993
|
return {
|
|
2002
|
-
bookings:
|
|
2003
|
-
recent
|
|
1994
|
+
bookings: all.data || [],
|
|
1995
|
+
recent: recent.data || [],
|
|
2004
1996
|
packageLocales: [],
|
|
2005
1997
|
staticCounts: { packages: 0, sections: 0 },
|
|
2006
1998
|
};
|
|
2007
1999
|
},
|
|
2008
2000
|
|
|
2009
|
-
// Opsional: live update
|
|
2010
2001
|
subscribeLiveUpdate(onEvent) {
|
|
2011
|
-
const ch = supabase.channel("
|
|
2002
|
+
const ch = supabase.channel("rdb-${tableName}-live")
|
|
2012
2003
|
.on("postgres_changes",
|
|
2013
|
-
|
|
2004
|
+
{ event: "*", schema: "public", table: "${tableName}" }, onEvent)
|
|
2014
2005
|
.subscribe();
|
|
2015
2006
|
return () => supabase.removeChannel(ch);
|
|
2016
2007
|
},
|
|
2017
2008
|
};
|
|
2018
|
-
}
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
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
|
|
2009
|
+
}`;
|
|
2010
|
+
const adapter = `// src/adapters/myDashboardAdapter.js
|
|
2011
|
+
// AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
|
|
2012
|
+
|
|
2028
2013
|
import { toNumber, buildDayBuckets } from "@rozaqi02/reusable-dashboard";
|
|
2029
2014
|
|
|
2030
|
-
export function
|
|
2015
|
+
export function createEmptyMyDashboardData() {
|
|
2031
2016
|
return {
|
|
2032
|
-
stats:
|
|
2033
|
-
charts: {
|
|
2034
|
-
|
|
2035
|
-
statusDistribution: [],
|
|
2036
|
-
audienceDistribution: [],
|
|
2037
|
-
topPackages: [],
|
|
2038
|
-
},
|
|
2039
|
-
table: { recentBookings: [] },
|
|
2017
|
+
stats: { bookingsConfirm: 0, revenueConfirm: 0, avgRevenue: 0, conversionRate: 0 },
|
|
2018
|
+
charts: { dailyTrends: [], statusDistribution: [], audienceDistribution: [], topPackages: [] },
|
|
2019
|
+
table: { recentBookings: [] },
|
|
2040
2020
|
};
|
|
2041
2021
|
}
|
|
2042
2022
|
|
|
2043
|
-
export function
|
|
2044
|
-
if (!raw) return
|
|
2023
|
+
export function adaptMyDashboardData({ raw, range, dateLocale, labels }) {
|
|
2024
|
+
if (!raw) return createEmptyMyDashboardData();
|
|
2045
2025
|
|
|
2046
|
-
const buckets
|
|
2047
|
-
const dayMap
|
|
2026
|
+
const buckets = buildDayBuckets(range.daysWindow, range.fromISO, dateLocale);
|
|
2027
|
+
const dayMap = new Map(buckets.map(b => [b.dateKey, b]));
|
|
2048
2028
|
const statusMap = new Map();
|
|
2049
2029
|
let confirmed = 0, revenue = 0;
|
|
2050
2030
|
|
|
2051
2031
|
(raw.bookings || []).forEach(row => {
|
|
2052
|
-
const
|
|
2053
|
-
const
|
|
2054
|
-
const
|
|
2032
|
+
const st = String(row["${colStatus}"] || "").toLowerCase();
|
|
2033
|
+
const amt = toNumber(row["${totalCol}"]);
|
|
2034
|
+
const dk = String(row["${colDate}"] || "").slice(0, 10);
|
|
2055
2035
|
|
|
2056
|
-
statusMap.set(
|
|
2057
|
-
const
|
|
2058
|
-
if (
|
|
2059
|
-
|
|
2060
|
-
|
|
2036
|
+
statusMap.set(st, (statusMap.get(st) || 0) + 1);
|
|
2037
|
+
const b = dayMap.get(dk);
|
|
2038
|
+
if (b) {
|
|
2039
|
+
if (st === "${confirmed}") { b.count++; b.revenue += amt; }
|
|
2040
|
+
if (st === "${pending}") { b.pendingCount++; }
|
|
2061
2041
|
}
|
|
2062
|
-
if (
|
|
2042
|
+
if (st === "${confirmed}") { confirmed++; revenue += amt; }
|
|
2063
2043
|
});
|
|
2064
2044
|
|
|
2045
|
+
const total = (raw.bookings || []).length;
|
|
2065
2046
|
return {
|
|
2066
|
-
stats: {
|
|
2047
|
+
stats: {
|
|
2048
|
+
bookingsConfirm: confirmed,
|
|
2049
|
+
revenueConfirm: revenue,
|
|
2050
|
+
avgRevenue: confirmed > 0 ? Math.round(revenue / confirmed) : 0,
|
|
2051
|
+
conversionRate: total > 0 ? Math.round((confirmed / total) * 100) : 0,
|
|
2052
|
+
},
|
|
2067
2053
|
charts: {
|
|
2068
2054
|
dailyTrends: buckets,
|
|
2069
2055
|
statusDistribution: Array.from(statusMap.entries())
|
|
2070
|
-
.
|
|
2071
|
-
|
|
2072
|
-
label: labels?.formatStatusLabel?.(status) || status,
|
|
2073
|
-
count,
|
|
2074
|
-
})),
|
|
2056
|
+
.sort((a, b) => b[1] - a[1])
|
|
2057
|
+
.map(([s, c]) => ({ status: s, count: c, label: labels?.formatStatusLabel?.(s) || s })),
|
|
2075
2058
|
audienceDistribution: [],
|
|
2076
2059
|
topPackages: [],
|
|
2077
2060
|
},
|
|
2078
2061
|
table: {
|
|
2079
2062
|
recentBookings: (raw.recent || []).map(row => ({
|
|
2080
|
-
id:
|
|
2081
|
-
createdAt:
|
|
2082
|
-
customerName:
|
|
2083
|
-
packageName: row
|
|
2063
|
+
id: row.id,
|
|
2064
|
+
createdAt: row["${colDate}"],
|
|
2065
|
+
customerName: row["${custCol}"] || "-",
|
|
2066
|
+
packageName: ${itemCol !== "-" ? `row["${itemCol}"] || "-"` : '"-"'},
|
|
2084
2067
|
audienceLabel: "-",
|
|
2085
|
-
totalIDR:
|
|
2086
|
-
status:
|
|
2087
|
-
statusLabel:
|
|
2068
|
+
totalIDR: toNumber(row["${totalCol}"]),
|
|
2069
|
+
status: String(row["${colStatus}"] || "").toLowerCase(),
|
|
2070
|
+
statusLabel: labels?.formatStatusLabel?.(row["${colStatus}"]) || row["${colStatus}"] || "-",
|
|
2088
2071
|
})),
|
|
2089
2072
|
},
|
|
2090
2073
|
};
|
|
2091
|
-
}
|
|
2092
|
-
|
|
2093
|
-
|
|
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";
|
|
2074
|
+
}`;
|
|
2075
|
+
const widgetConfig = `// src/config/myDashboardConfig.js
|
|
2076
|
+
// AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
|
|
2101
2077
|
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2078
|
+
export const myDashboardConfig = {
|
|
2079
|
+
id: "my.custom.dashboard",
|
|
2080
|
+
defaultFilters: { statusScope: "${confirmed}", daysPreset: 30,
|
|
2081
|
+
sortPkgBy: "bookings", sortPkgDir: "desc" },
|
|
2106
2082
|
widgets: {
|
|
2107
2083
|
stats: [
|
|
2108
|
-
{ id:
|
|
2109
|
-
valueKey:
|
|
2110
|
-
{ id:
|
|
2111
|
-
valueKey:
|
|
2084
|
+
{ id:"orders", label:"confirmedBookings", icon:"TrendingUp",
|
|
2085
|
+
valueKey:"bookingsConfirm", format:"number", accentColor:"blue" },
|
|
2086
|
+
{ id:"revenue", label:"confirmedRevenue", icon:"DollarSign",
|
|
2087
|
+
valueKey:"revenueConfirm", format:"currency", accentColor:"green" },
|
|
2088
|
+
{ id:"avg", label:"avgRevenue", icon:"Users",
|
|
2089
|
+
valueKey:"avgRevenue", format:"currency", accentColor:"violet" },
|
|
2090
|
+
{ id:"conversion", label:"conversionRate", icon:"PieChart",
|
|
2091
|
+
valueKey:"conversionRate", format:"percent", accentColor:"orange" },
|
|
2112
2092
|
],
|
|
2113
2093
|
charts: [
|
|
2114
|
-
{ id:
|
|
2115
|
-
{ id:
|
|
2094
|
+
{ id:"trend", type:"dailyArea", label:"dailyTrends", icon:"BarChart3" },
|
|
2095
|
+
{ id:"status", type:"statusPie", label:"statusDistribution", icon:"PieChart" },
|
|
2116
2096
|
],
|
|
2117
2097
|
table: {
|
|
2118
|
-
id:
|
|
2119
|
-
emptyLabel: "noRecentBookings",
|
|
2098
|
+
id:"recentTx", label:"recentBookings", icon:"Calendar", emptyLabel:"noRecentBookings",
|
|
2120
2099
|
columns: [
|
|
2121
|
-
{ id:
|
|
2122
|
-
{ id:
|
|
2123
|
-
{ id:
|
|
2124
|
-
{ id:
|
|
2125
|
-
|
|
2100
|
+
{ id:"date", label:"date", accessor:"createdAt", type:"date" },
|
|
2101
|
+
{ id:"customer", label:"customer", accessor:"customerName" },${itemCol !== "-" ? `
|
|
2102
|
+
{ id:"item", label:"package", accessor:"packageName" },` : ""}
|
|
2103
|
+
{ id:"total", label:"total", accessor:"totalIDR", type:"currency" },
|
|
2104
|
+
{ id:"status", label:"status", accessor:"statusLabel",
|
|
2105
|
+
type:"statusBadge", statusAccessor:"status" },
|
|
2126
2106
|
],
|
|
2127
2107
|
},
|
|
2128
2108
|
},
|
|
2129
|
-
}
|
|
2109
|
+
};`;
|
|
2110
|
+
const dashboard = `// src/pages/admin/Dashboard.jsx
|
|
2111
|
+
// AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
|
|
2130
2112
|
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
createEmptyState: createEmptyMyData,
|
|
2137
|
-
languageCode: "id",
|
|
2138
|
-
dateLocale: "id-ID",
|
|
2139
|
-
});
|
|
2113
|
+
import React from "react";
|
|
2114
|
+
import { supabase } from "../../lib/supabaseClient.js";
|
|
2115
|
+
import {
|
|
2116
|
+
ReusableDashboardView, useReusableDashboard, createDashboardConfig,
|
|
2117
|
+
} from "@rozaqi02/reusable-dashboard";
|
|
2140
2118
|
|
|
2141
|
-
//
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2119
|
+
// Tiga file hasil generate wizard
|
|
2120
|
+
import { myDashboardConfig } from "../../config/myDashboardConfig";
|
|
2121
|
+
import { createMyDashboardSource } from "../../datasources/myDashboardSource";
|
|
2122
|
+
import { adaptMyDashboardData, createEmptyMyDashboardData } from "../../adapters/myDashboardAdapter";
|
|
2123
|
+
|
|
2124
|
+
const source = createMyDashboardSource(supabase);
|
|
2125
|
+
|
|
2126
|
+
const labels = {
|
|
2127
|
+
title: "${title}", refresh: "Refresh", liveUpdate: "Live update",
|
|
2128
|
+
loadFailed: "Gagal memuat data.", retry: "Coba Lagi",
|
|
2129
|
+
confirmedOnly: "Selesai", pendingOnly: "Proses", allStatus: "Semua Status",
|
|
2130
|
+
showPendingOverlay: "Tampilkan pending", reset: "Reset",
|
|
2131
|
+
confirmedBookings: "Total Transaksi", confirmedRevenue: "Total Pendapatan",
|
|
2132
|
+
avgRevenue: "Rata-rata / Transaksi", conversionRate: "Conversion Rate",
|
|
2133
|
+
dailyTrends: "Tren Harian", statusDistribution: "Distribusi Status",
|
|
2134
|
+
recentBookings: "Transaksi Terbaru", noRecentBookings: "Belum ada transaksi",
|
|
2135
|
+
date: "Tanggal", customer: "Pelanggan", package: "Item",
|
|
2136
|
+
total: "Total", status: "Status",
|
|
2137
|
+
bookingsMetric: "Transaksi", revenueMetric: "Pendapatan",
|
|
2138
|
+
confirmedBookingMetric: "Transaksi (Selesai)",
|
|
2139
|
+
confirmedRevenueMetric: "Pendapatan (Selesai)",
|
|
2151
2140
|
dayLabel: n => n + " hari",
|
|
2152
|
-
formatStatusLabel: s
|
|
2153
|
-
({ confirmed: "Selesai", pending: "Proses", cancelled: "Batal" })[s] || s,
|
|
2141
|
+
formatStatusLabel: s => ({ "${confirmed}":"Selesai", "${pending}":"Proses" })[s] || s || "-",
|
|
2154
2142
|
formatAudienceLabel: v => v || "-",
|
|
2155
2143
|
};
|
|
2156
2144
|
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2145
|
+
const dashboardConfig = createDashboardConfig({
|
|
2146
|
+
widgetConfig: myDashboardConfig, dataSource: source,
|
|
2147
|
+
adapter: adaptMyDashboardData, createEmptyState: createEmptyMyDashboardData,
|
|
2148
|
+
languageCode: "id", dateLocale: "id-ID", labels,
|
|
2149
|
+
});
|
|
2150
|
+
|
|
2151
|
+
export default function Dashboard() {
|
|
2152
|
+
const state = useReusableDashboard({ ...dashboardConfig, labels });
|
|
2160
2153
|
return (
|
|
2161
2154
|
<ReusableDashboardView
|
|
2162
|
-
config={
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
onFilterChange={state.updateFilter}
|
|
2168
|
-
onResetFilters={state.resetFilters}
|
|
2169
|
-
onRefresh={state.refresh}
|
|
2170
|
-
data={state.data}
|
|
2171
|
-
dateLocale={dashConfig.dateLocale}
|
|
2155
|
+
config={dashboardConfig.config} labels={labels}
|
|
2156
|
+
loading={state.loading} error={state.error}
|
|
2157
|
+
filters={state.filters} onFilterChange={state.updateFilter}
|
|
2158
|
+
onResetFilters={state.resetFilters} onRefresh={state.refresh}
|
|
2159
|
+
data={state.data} dateLocale={dashboardConfig.dateLocale}
|
|
2172
2160
|
liveUpdateEnabled={state.liveUpdateEnabled}
|
|
2161
|
+
supabase={supabase} dashboardConfig={dashboardConfig}
|
|
2173
2162
|
/>
|
|
2174
2163
|
);
|
|
2175
|
-
}
|
|
2164
|
+
}`;
|
|
2165
|
+
return { dataSource, adapter, widgetConfig, dashboard };
|
|
2166
|
+
}
|
|
2167
|
+
function SetupWizard({ issues = [], onDismiss, supabase }) {
|
|
2168
|
+
var _a, _b;
|
|
2169
|
+
const [dismissed, setDismissed] = (0, import_react19.useState)(false);
|
|
2170
|
+
const [step, setStep] = (0, import_react19.useState)(0);
|
|
2171
|
+
const [detecting, setDetecting] = (0, import_react19.useState)(true);
|
|
2172
|
+
const [tables, setTables] = (0, import_react19.useState)([]);
|
|
2173
|
+
const [columns, setColumns] = (0, import_react19.useState)({});
|
|
2174
|
+
const [sampleData, setSampleData] = (0, import_react19.useState)({});
|
|
2175
|
+
const [statusValues, setStatusValues] = (0, import_react19.useState)({});
|
|
2176
|
+
const [supabaseOk, setSupabaseOk] = (0, import_react19.useState)(false);
|
|
2177
|
+
const [detectionDone, setDetectionDone] = (0, import_react19.useState)(false);
|
|
2178
|
+
const [tableBlocked, setTableBlocked] = (0, import_react19.useState)(false);
|
|
2179
|
+
const [selectedTable, setSelectedTable] = (0, import_react19.useState)("");
|
|
2180
|
+
const [colDate, setColDate] = (0, import_react19.useState)("");
|
|
2181
|
+
const [colStatus, setColStatus] = (0, import_react19.useState)("");
|
|
2182
|
+
const [colTotal, setColTotal] = (0, import_react19.useState)("");
|
|
2183
|
+
const [colCustomer, setColCustomer] = (0, import_react19.useState)("");
|
|
2184
|
+
const [colItem, setColItem] = (0, import_react19.useState)("");
|
|
2185
|
+
const [confirmedVal, setConfirmedVal] = (0, import_react19.useState)("");
|
|
2186
|
+
const [pendingVal, setPendingVal] = (0, import_react19.useState)("");
|
|
2187
|
+
const [dashTitle, setDashTitle] = (0, import_react19.useState)("Dashboard");
|
|
2188
|
+
const [loadingCols, setLoadingCols] = (0, import_react19.useState)(false);
|
|
2189
|
+
const [colsBlocked, setColsBlocked] = (0, import_react19.useState)(false);
|
|
2190
|
+
const [generatedCode, setGeneratedCode] = (0, import_react19.useState)(null);
|
|
2191
|
+
const [activeTab, setActiveTab] = (0, import_react19.useState)("dashboard");
|
|
2192
|
+
const [copied, setCopied] = (0, import_react19.useState)("");
|
|
2193
|
+
const [showRls, setShowRls] = (0, import_react19.useState)(false);
|
|
2194
|
+
(0, import_react19.useEffect)(() => {
|
|
2195
|
+
if (!supabase) {
|
|
2196
|
+
setDetecting(false);
|
|
2197
|
+
setDetectionDone(true);
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
(async () => {
|
|
2201
|
+
try {
|
|
2202
|
+
const { data: d1, error: e1 } = await supabase.from("information_schema.tables").select("table_name").eq("table_schema", "public").eq("table_type", "BASE TABLE").order("table_name");
|
|
2203
|
+
if (!e1 && (d1 == null ? void 0 : d1.length)) {
|
|
2204
|
+
setTables(d1.map((r) => r.table_name));
|
|
2205
|
+
setSupabaseOk(true);
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
const { data: d2, error: e2 } = await supabase.rpc("rdb_get_tables");
|
|
2209
|
+
if (!e2 && Array.isArray(d2)) {
|
|
2210
|
+
setTables(d2.map((r) => typeof r === "string" ? r : r.table_name || r));
|
|
2211
|
+
setSupabaseOk(true);
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
const { error: ping } = await supabase.from("_rdb_").select("*").limit(1);
|
|
2215
|
+
const ok = !ping || ping.code === "42P01" || ping.code === "PGRST116" || (ping.message || "").includes("does not exist");
|
|
2216
|
+
setSupabaseOk(ok);
|
|
2217
|
+
if (ok) setTableBlocked(true);
|
|
2218
|
+
} catch {
|
|
2219
|
+
setSupabaseOk(false);
|
|
2220
|
+
} finally {
|
|
2221
|
+
setDetecting(false);
|
|
2222
|
+
setDetectionDone(true);
|
|
2223
|
+
}
|
|
2224
|
+
})();
|
|
2225
|
+
}, [supabase]);
|
|
2226
|
+
const analyzeTable = (0, import_react19.useCallback)(async (tbl) => {
|
|
2227
|
+
var _a2;
|
|
2228
|
+
if (!supabase || !tbl) return;
|
|
2229
|
+
if (columns[tbl]) {
|
|
2230
|
+
autoDetect(tbl, columns[tbl]);
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
setLoadingCols(true);
|
|
2234
|
+
setColsBlocked(false);
|
|
2235
|
+
try {
|
|
2236
|
+
let cols = null;
|
|
2237
|
+
const { data: ca, error: ea } = await supabase.from("information_schema.columns").select("column_name, data_type").eq("table_schema", "public").eq("table_name", tbl).order("ordinal_position");
|
|
2238
|
+
if (!ea && (ca == null ? void 0 : ca.length)) cols = ca;
|
|
2239
|
+
if (!cols) {
|
|
2240
|
+
const { data: sb, error: eb } = await supabase.from(tbl).select("*").limit(1);
|
|
2241
|
+
if (!eb && (sb == null ? void 0 : sb.length) > 0)
|
|
2242
|
+
cols = Object.keys(sb[0]).map((k) => ({ column_name: k, data_type: typeof sb[0][k] }));
|
|
2243
|
+
}
|
|
2244
|
+
if (cols) {
|
|
2245
|
+
setColumns((p) => ({ ...p, [tbl]: cols }));
|
|
2246
|
+
autoDetect(tbl, cols);
|
|
2247
|
+
} else {
|
|
2248
|
+
setColsBlocked(true);
|
|
2249
|
+
}
|
|
2250
|
+
const { data: sample2 } = await supabase.from(tbl).select("*").limit(5);
|
|
2251
|
+
if ((sample2 == null ? void 0 : sample2.length) > 0) {
|
|
2252
|
+
setSampleData((p) => ({ ...p, [tbl]: sample2 }));
|
|
2253
|
+
const statusCol = (_a2 = (cols || []).find(
|
|
2254
|
+
(c) => ["status", "state", "kondisi", "order_status"].includes(c.column_name.toLowerCase())
|
|
2255
|
+
)) == null ? void 0 : _a2.column_name;
|
|
2256
|
+
if (statusCol) {
|
|
2257
|
+
const uniqVals = [...new Set(sample2.map((r) => r[statusCol]).filter(Boolean))];
|
|
2258
|
+
setStatusValues((p) => ({ ...p, [tbl]: { [statusCol]: uniqVals } }));
|
|
2259
|
+
const vals = uniqVals.map((v) => String(v).toLowerCase());
|
|
2260
|
+
const cfm = uniqVals.find((v) => ["confirmed", "selesai", "lunas", "paid", "done", "completed", "approve"].includes(String(v).toLowerCase()));
|
|
2261
|
+
const pnd = uniqVals.find((v) => ["pending", "proses", "menunggu", "processing", "waiting", "draft"].includes(String(v).toLowerCase()));
|
|
2262
|
+
if (cfm) setConfirmedVal(String(cfm));
|
|
2263
|
+
if (pnd) setPendingVal(String(pnd));
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
} catch {
|
|
2267
|
+
setColsBlocked(true);
|
|
2268
|
+
} finally {
|
|
2269
|
+
setLoadingCols(false);
|
|
2270
|
+
}
|
|
2271
|
+
}, [supabase, columns]);
|
|
2272
|
+
function autoDetect(tbl, cols) {
|
|
2273
|
+
const f = (opts) => {
|
|
2274
|
+
var _a2;
|
|
2275
|
+
return ((_a2 = cols.find((c) => opts.includes(c.column_name.toLowerCase()))) == null ? void 0 : _a2.column_name) || "";
|
|
2276
|
+
};
|
|
2277
|
+
setColDate(f(["created_at", "tanggal", "date", "transaction_date", "order_date", "waktu"]));
|
|
2278
|
+
setColStatus(f(["status", "state", "kondisi", "order_status", "payment_status"]));
|
|
2279
|
+
setColTotal(f(["total", "total_idr", "total_amount", "total_price", "harga_total", "amount", "nominal", "harga", "grand_total"]));
|
|
2280
|
+
setColCustomer(f(["customer_name", "nama_pelanggan", "nama", "name", "client_name", "buyer_name", "pelanggan"]));
|
|
2281
|
+
setColItem(f(["service_type", "item_name", "product_name", "package_name", "layanan", "produk", "jenis", "paket"]));
|
|
2282
|
+
}
|
|
2283
|
+
function copyCode(text, key) {
|
|
2284
|
+
var _a2;
|
|
2285
|
+
(_a2 = navigator.clipboard) == null ? void 0 : _a2.writeText(text).then(() => {
|
|
2286
|
+
setCopied(key);
|
|
2287
|
+
setTimeout(() => setCopied(""), 2200);
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
function handleDismiss() {
|
|
2291
|
+
setDismissed(true);
|
|
2292
|
+
onDismiss == null ? void 0 : onDismiss();
|
|
2293
|
+
}
|
|
2294
|
+
function handleGenerate() {
|
|
2295
|
+
if (!selectedTable || !colDate || !colStatus || !colTotal) return;
|
|
2296
|
+
setGeneratedCode(generateCode({
|
|
2297
|
+
tableName: selectedTable.trim(),
|
|
2298
|
+
colDate,
|
|
2299
|
+
colStatus,
|
|
2300
|
+
colTotal,
|
|
2301
|
+
colCustomer: colCustomer || "customer_name",
|
|
2302
|
+
colItem: colItem || "-",
|
|
2303
|
+
dashTitle,
|
|
2304
|
+
confirmedValue: confirmedVal || "confirmed",
|
|
2305
|
+
pendingValue: pendingVal || "pending"
|
|
2306
|
+
}));
|
|
2307
|
+
setStep(3);
|
|
2308
|
+
}
|
|
2309
|
+
if (dismissed) return null;
|
|
2310
|
+
const preset = detectPreset(tables);
|
|
2311
|
+
const tblCols = selectedTable ? columns[selectedTable] || [] : [];
|
|
2312
|
+
const colNames = tblCols.map((c) => c.column_name);
|
|
2313
|
+
const sample = selectedTable ? sampleData[selectedTable] || [] : [];
|
|
2314
|
+
const stVals = selectedTable && colStatus ? ((_a = statusValues[selectedTable]) == null ? void 0 : _a[colStatus]) || [] : [];
|
|
2315
|
+
const canGen = selectedTable.trim() && colDate && colStatus && colTotal;
|
|
2316
|
+
const TAB_INFO = {
|
|
2317
|
+
dashboard: { label: "Dashboard.jsx", path: "src/pages/admin/Dashboard.jsx" },
|
|
2318
|
+
dataSource: { label: "myDashboardSource.js", path: "src/datasources/myDashboardSource.js" },
|
|
2319
|
+
adapter: { label: "myDashboardAdapter.js", path: "src/adapters/myDashboardAdapter.js" },
|
|
2320
|
+
widgetConfig: { label: "myDashboardConfig.js", path: "src/config/myDashboardConfig.js" }
|
|
2321
|
+
};
|
|
2322
|
+
function PreviewTable({ rows, cols: previewCols }) {
|
|
2323
|
+
if (!(rows == null ? void 0 : rows.length) || !(previewCols == null ? void 0 : previewCols.length)) return null;
|
|
2324
|
+
const validCols = previewCols.filter((c) => {
|
|
2325
|
+
var _a2;
|
|
2326
|
+
return c && ((_a2 = rows[0]) == null ? void 0 : _a2[c]) !== void 0;
|
|
2327
|
+
});
|
|
2328
|
+
if (!validCols.length) return null;
|
|
2329
|
+
return /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-table-wrapper", style: { marginTop: 8, fontSize: "0.75rem" } }, /* @__PURE__ */ import_react19.default.createElement("table", { className: "rdb-table", style: { fontSize: "0.75rem" } }, /* @__PURE__ */ import_react19.default.createElement("thead", null, /* @__PURE__ */ import_react19.default.createElement("tr", null, validCols.map((c) => /* @__PURE__ */ import_react19.default.createElement("th", { key: c, style: { padding: "4px 8px", fontSize: "0.7rem" } }, c)))), /* @__PURE__ */ import_react19.default.createElement("tbody", null, rows.slice(0, 3).map((row, i) => /* @__PURE__ */ import_react19.default.createElement("tr", { key: i }, validCols.map((c) => /* @__PURE__ */ import_react19.default.createElement("td", { key: c, style: { padding: "3px 8px" } }, String(row[c] ?? "-").slice(0, 30))))))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 4, color: "var(--rdb-text-muted)" } }, "Preview 3 baris data nyata dari database kamu"));
|
|
2330
|
+
}
|
|
2331
|
+
return /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-overlay", role: "dialog", "aria-modal": "true" }, /* @__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 Wizard"), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption" }, detecting ? "Menghubungi Supabase\u2026" : supabaseOk ? tables.length > 0 ? `\u2705 Tersambung \xB7 ${tables.length} tabel ditemukan` : "\u2705 Tersambung \xB7 ketik nama tabel di Step 2" : "Ikuti langkah di bawah untuk menghubungkan Supabase"))), /* @__PURE__ */ import_react19.default.createElement("button", { type: "button", className: "rdb-wizard-close", onClick: handleDismiss }, "\u2715")), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-steps" }, ["1. Koneksi", "2. Pilih Tabel", "3. Konfirmasi", "4. Kode Jadi"].map((lbl, i) => /* @__PURE__ */ import_react19.default.createElement(
|
|
2332
|
+
"button",
|
|
2333
|
+
{
|
|
2334
|
+
key: i,
|
|
2335
|
+
type: "button",
|
|
2336
|
+
className: `rdb-wizard-step-btn ${step === i ? "rdb-wizard-step-active" : ""}`,
|
|
2337
|
+
onClick: () => setStep(i)
|
|
2338
|
+
},
|
|
2339
|
+
step > i ? "\u2705 " : "",
|
|
2340
|
+
lbl
|
|
2341
|
+
))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-body" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-section" }, step === 0 && /* @__PURE__ */ import_react19.default.createElement(import_react19.default.Fragment, null, issues.length > 0 && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-issues", style: { marginBottom: 12 } }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 6 } }, "Konfigurasi yang belum lengkap:"), issues.map((iss, i) => /* @__PURE__ */ import_react19.default.createElement("div", { key: i, className: "rdb-wizard-issue-item" }, /* @__PURE__ */ import_react19.default.createElement("span", null, "\u274C"), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-body" }, iss)))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-flow", style: { marginBottom: 16 } }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 10 } }, "\u{1F50D} Status koneksi Supabase:"), detecting ? /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-body", style: { color: "var(--rdb-text-muted)" } }, "Mendeteksi\u2026") : /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-issue-item" }, /* @__PURE__ */ import_react19.default.createElement("span", null, supabaseOk ? "\u2705" : "\u274C"), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-body" }, supabaseOk ? "Supabase tersambung" : "Tidak tersambung \u2014 tambahkan prop supabase={supabase} ke <ReusableDashboardView>")), supabaseOk && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-issue-item" }, /* @__PURE__ */ import_react19.default.createElement("span", null, tables.length > 0 ? "\u2705" : "\u26A0\uFE0F"), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-body" }, tables.length > 0 ? `Wizard membaca ${tables.length} tabel langsung dari database` : "Daftar tabel belum bisa terbaca (butuh izin \u2014 lihat SQL di bawah)")))), supabaseOk && tableBlocked && /* @__PURE__ */ import_react19.default.createElement("div", { style: { marginBottom: 16 } }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-alert", style: { marginBottom: 8 } }, /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-wizard-alert-icon" }, "\u{1F511}"), /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-body", style: { fontWeight: 600 } }, "Agar wizard bisa baca daftar tabel otomatis"), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 4 } }, "Jalankan SQL ini ", /* @__PURE__ */ import_react19.default.createElement("strong", null, "sekali"), " di Supabase SQL Editor, lalu refresh halaman. Atau lewati dan ketik nama tabel manual di Step 2."))), /* @__PURE__ */ import_react19.default.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ import_react19.default.createElement(
|
|
2342
|
+
"button",
|
|
2343
|
+
{
|
|
2344
|
+
type: "button",
|
|
2345
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2346
|
+
style: { position: "absolute", top: 6, right: 6 },
|
|
2347
|
+
onClick: () => copyCode(SQL_GRANT_SCHEMA, "grant")
|
|
2348
|
+
},
|
|
2349
|
+
copied === "grant" ? "\u2705" : "\u{1F4CB}",
|
|
2350
|
+
" Salin"
|
|
2351
|
+
), /* @__PURE__ */ import_react19.default.createElement("pre", { className: "rdb-wizard-code", style: { fontSize: "0.72rem" } }, SQL_GRANT_SCHEMA))), supabaseOk && /* @__PURE__ */ import_react19.default.createElement("div", { style: { marginBottom: 16 } }, /* @__PURE__ */ import_react19.default.createElement(
|
|
2352
|
+
"button",
|
|
2353
|
+
{
|
|
2354
|
+
type: "button",
|
|
2355
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2356
|
+
onClick: () => setShowRls((r) => !r)
|
|
2357
|
+
},
|
|
2358
|
+
showRls ? "\u25B2 Sembunyikan" : "\u25BC Lihat",
|
|
2359
|
+
" panduan RLS \u2014 agar data tabel bisa terbaca dashboard"
|
|
2360
|
+
), showRls && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-flow", style: { marginTop: 10 } }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-body", style: { fontWeight: 600, marginBottom: 6 } }, "Apa itu RLS?"), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-body", style: { color: "var(--rdb-text-muted)", marginBottom: 10 } }, "Row Level Security (RLS) adalah fitur Supabase yang mengontrol siapa yang boleh membaca data. Jika aktif tanpa aturan, dashboard tidak bisa mengambil data apapun."), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 6 } }, "SQL \u2014 ganti ", /* @__PURE__ */ import_react19.default.createElement("code", null, "nama_tabel"), " dengan nama tabelmu:"), /* @__PURE__ */ import_react19.default.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ import_react19.default.createElement(
|
|
2361
|
+
"button",
|
|
2362
|
+
{
|
|
2363
|
+
type: "button",
|
|
2364
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2365
|
+
style: { position: "absolute", top: 6, right: 6 },
|
|
2366
|
+
onClick: () => copyCode(sqlRls("nama_tabel"), "rls")
|
|
2367
|
+
},
|
|
2368
|
+
copied === "rls" ? "\u2705" : "\u{1F4CB}",
|
|
2369
|
+
" Salin"
|
|
2370
|
+
), /* @__PURE__ */ import_react19.default.createElement("pre", { className: "rdb-wizard-code", style: { fontSize: "0.7rem" } }, sqlRls("nama_tabel"))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 8, color: "var(--rdb-text-muted)" } }, "\u{1F4A1} ", /* @__PURE__ */ import_react19.default.createElement("strong", null, "Development:"), " pakai Opsi 1.", /* @__PURE__ */ import_react19.default.createElement("strong", null, " Production:"), " pakai Opsi 2 atau 3."))), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" } }, /* @__PURE__ */ import_react19.default.createElement(
|
|
2371
|
+
"button",
|
|
2372
|
+
{
|
|
2373
|
+
type: "button",
|
|
2374
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2375
|
+
onClick: handleDismiss
|
|
2376
|
+
},
|
|
2377
|
+
"Lanjutkan tanpa wizard"
|
|
2378
|
+
), supabaseOk && /* @__PURE__ */ import_react19.default.createElement(
|
|
2379
|
+
"button",
|
|
2380
|
+
{
|
|
2381
|
+
type: "button",
|
|
2382
|
+
className: "rdb-btn rdb-btn-primary rdb-btn-sm",
|
|
2383
|
+
onClick: () => setStep(1)
|
|
2384
|
+
},
|
|
2385
|
+
"Lanjut \u2192 Pilih Tabel"
|
|
2386
|
+
))), step === 1 && /* @__PURE__ */ import_react19.default.createElement(import_react19.default.Fragment, null, /* @__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 } }, "Pilih Tabel Transaksi")), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-body", style: { color: "var(--rdb-text-muted)", marginBottom: 16 } }, "Klik tabel yang berisi data transaksi bisnis kamu. Wizard akan", /* @__PURE__ */ import_react19.default.createElement("strong", null, " otomatis membaca kolom dan sample data"), " dari tabel tersebut."), tables.length > 0 && /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 16 } }, tables.map((tbl) => {
|
|
2387
|
+
var _a2, _b2;
|
|
2388
|
+
const isSelected = selectedTable === tbl;
|
|
2389
|
+
const hasSample = ((_a2 = sampleData[tbl]) == null ? void 0 : _a2.length) > 0;
|
|
2390
|
+
const hasColumns = ((_b2 = columns[tbl]) == null ? void 0 : _b2.length) > 0;
|
|
2391
|
+
return /* @__PURE__ */ import_react19.default.createElement(
|
|
2392
|
+
"button",
|
|
2393
|
+
{
|
|
2394
|
+
key: tbl,
|
|
2395
|
+
type: "button",
|
|
2396
|
+
onClick: () => {
|
|
2397
|
+
setSelectedTable(tbl);
|
|
2398
|
+
analyzeTable(tbl);
|
|
2399
|
+
},
|
|
2400
|
+
className: `rdb-btn rdb-btn-sm ${isSelected ? "rdb-btn-primary" : "rdb-btn-secondary"}`,
|
|
2401
|
+
style: { fontFamily: "monospace", fontSize: "0.82rem" }
|
|
2402
|
+
},
|
|
2403
|
+
isSelected ? "\u2705 " : "",
|
|
2404
|
+
tbl,
|
|
2405
|
+
isSelected && hasColumns ? ` (${columns[tbl].length} kolom)` : ""
|
|
2406
|
+
);
|
|
2407
|
+
})), /* @__PURE__ */ import_react19.default.createElement("div", { style: { marginBottom: 16 } }, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, tables.length > 0 ? "Atau ketik nama tabel lain:" : "Ketik nama tabel kamu:"), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8 } }, /* @__PURE__ */ import_react19.default.createElement(
|
|
2408
|
+
"input",
|
|
2409
|
+
{
|
|
2410
|
+
className: "rdb-input",
|
|
2411
|
+
value: selectedTable,
|
|
2412
|
+
onChange: (e) => setSelectedTable(e.target.value.replace(/[,\s].*/, "")),
|
|
2413
|
+
placeholder: "cth: orders / transaksi / bookings",
|
|
2414
|
+
style: { maxWidth: 280 }
|
|
2415
|
+
}
|
|
2416
|
+
), /* @__PURE__ */ import_react19.default.createElement(
|
|
2417
|
+
"button",
|
|
2418
|
+
{
|
|
2419
|
+
type: "button",
|
|
2420
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2421
|
+
disabled: !selectedTable || loadingCols,
|
|
2422
|
+
onClick: () => analyzeTable(selectedTable)
|
|
2423
|
+
},
|
|
2424
|
+
loadingCols ? "Menganalisis\u2026" : "Analisis tabel"
|
|
2425
|
+
))), selectedTable && !loadingCols && /* @__PURE__ */ import_react19.default.createElement(import_react19.default.Fragment, null, colNames.length > 0 && /* @__PURE__ */ import_react19.default.createElement(import_react19.default.Fragment, null, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-tip", style: { marginBottom: 8 } }, "\u2705 ", /* @__PURE__ */ import_react19.default.createElement("strong", null, colNames.length, " kolom"), " ditemukan.", sample.length > 0 && ` ${sample.length} baris sample data terbaca.`, " ", "Kolom sudah otomatis diisi di step berikutnya."), sample.length > 0 && /* @__PURE__ */ import_react19.default.createElement(
|
|
2426
|
+
PreviewTable,
|
|
2427
|
+
{
|
|
2428
|
+
rows: sample,
|
|
2429
|
+
cols: [colDate, colStatus, colTotal, colCustomer].filter(Boolean)
|
|
2430
|
+
}
|
|
2431
|
+
)), colsBlocked && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-alert" }, /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-wizard-alert-icon" }, "\u2139\uFE0F"), /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-body", style: { fontWeight: 600 } }, "Kolom tidak bisa terbaca otomatis"), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 4 } }, "Kemungkinan RLS aktif. Kamu tetap bisa lanjut dan ketik nama kolom manual di step berikutnya.")))), /* @__PURE__ */ import_react19.default.createElement("div", { style: { marginBottom: 12, marginTop: 12 } }, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, "Judul dashboard:"), /* @__PURE__ */ import_react19.default.createElement(
|
|
2432
|
+
"input",
|
|
2433
|
+
{
|
|
2434
|
+
className: "rdb-input",
|
|
2435
|
+
value: dashTitle,
|
|
2436
|
+
style: { maxWidth: 280 },
|
|
2437
|
+
onChange: (e) => setDashTitle(e.target.value),
|
|
2438
|
+
placeholder: "cth: Dashboard Laundry / Dashboard Klinik"
|
|
2439
|
+
}
|
|
2440
|
+
)), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" } }, /* @__PURE__ */ import_react19.default.createElement(
|
|
2441
|
+
"button",
|
|
2442
|
+
{
|
|
2443
|
+
type: "button",
|
|
2444
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2445
|
+
onClick: () => setStep(0)
|
|
2446
|
+
},
|
|
2447
|
+
"\u2190 Kembali"
|
|
2448
|
+
), /* @__PURE__ */ import_react19.default.createElement(
|
|
2449
|
+
"button",
|
|
2450
|
+
{
|
|
2451
|
+
type: "button",
|
|
2452
|
+
className: "rdb-btn rdb-btn-primary rdb-btn-sm",
|
|
2453
|
+
disabled: !selectedTable || loadingCols,
|
|
2454
|
+
onClick: () => setStep(2)
|
|
2455
|
+
},
|
|
2456
|
+
"Lanjut \u2192 Konfirmasi Kolom"
|
|
2457
|
+
))), step === 2 && /* @__PURE__ */ import_react19.default.createElement(import_react19.default.Fragment, null, /* @__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 } }, "Konfirmasi \u2014 Tabel: ", /* @__PURE__ */ import_react19.default.createElement("code", { style: { fontSize: "0.9em" } }, selectedTable))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-body", style: { color: "var(--rdb-text-muted)", marginBottom: 16 } }, "Wizard sudah mengisi kolom secara otomatis. Periksa sebentar dan sesuaikan jika ada yang salah."), colNames.length > 0 && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-supabase-reader", style: { marginBottom: 16 } }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 6 } }, "Semua kolom di tabel ", selectedTable, ":"), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-table-chips" }, colNames.map((c) => /* @__PURE__ */ import_react19.default.createElement("span", { key: c, className: "rdb-wizard-chip", style: { cursor: "default" } }, c)))), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 16 } }, [
|
|
2458
|
+
{ label: "Kolom Tanggal *", hint: "Tipe timestamp/date", val: colDate, set: setColDate, ph: "created_at" },
|
|
2459
|
+
{ label: "Kolom Status *", hint: "Berisi nilai selesai/pending", val: colStatus, set: setColStatus, ph: "status" },
|
|
2460
|
+
{ label: "Kolom Total (uang) *", hint: "Integer Rupiah", val: colTotal, set: setColTotal, ph: "total_amount" },
|
|
2461
|
+
{ label: "Kolom Nama Pelanggan", hint: "Opsional", val: colCustomer, set: setColCustomer, ph: "customer_name (opsional)" },
|
|
2462
|
+
{ label: "Kolom Nama Item / Layanan", hint: "Opsional", val: colItem, set: setColItem, ph: "product_name (opsional)" }
|
|
2463
|
+
].map(({ label, hint, val, set, ph }) => /* @__PURE__ */ import_react19.default.createElement("div", { key: label }, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, label), colNames.length > 0 ? /* @__PURE__ */ import_react19.default.createElement("select", { className: "rdb-select", value: val, onChange: (e) => set(e.target.value) }, /* @__PURE__ */ import_react19.default.createElement("option", { value: "" }, "\u2014 pilih \u2014"), colNames.map((c) => /* @__PURE__ */ import_react19.default.createElement("option", { key: c, value: c }, c))) : /* @__PURE__ */ import_react19.default.createElement("input", { className: "rdb-input", value: val, onChange: (e) => set(e.target.value), placeholder: ph }), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 2 } }, hint)))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-flow", style: { marginBottom: 16 } }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 4 } }, "Nilai status yang ditemukan di data kamu:"), stVals.length > 0 ? /* @__PURE__ */ import_react19.default.createElement(import_react19.default.Fragment, null, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { color: "var(--rdb-text-muted)", marginBottom: 8 } }, "Wizard membaca nilai unik dari kolom ", /* @__PURE__ */ import_react19.default.createElement("strong", null, colStatus), ". Klik nilai yang sesuai:"), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 12 } }, stVals.map((v) => /* @__PURE__ */ import_react19.default.createElement("div", { key: v, style: { display: "flex", gap: 4, alignItems: "center" } }, /* @__PURE__ */ import_react19.default.createElement(
|
|
2464
|
+
"button",
|
|
2465
|
+
{
|
|
2466
|
+
type: "button",
|
|
2467
|
+
className: `rdb-btn rdb-btn-sm ${confirmedVal === String(v) ? "rdb-btn-primary" : "rdb-btn-secondary"}`,
|
|
2468
|
+
onClick: () => setConfirmedVal(String(v))
|
|
2469
|
+
},
|
|
2470
|
+
confirmedVal === String(v) ? "\u2705 " : "",
|
|
2471
|
+
"Selesai = ",
|
|
2472
|
+
/* @__PURE__ */ import_react19.default.createElement("strong", null, String(v))
|
|
2473
|
+
), /* @__PURE__ */ import_react19.default.createElement(
|
|
2474
|
+
"button",
|
|
2475
|
+
{
|
|
2476
|
+
type: "button",
|
|
2477
|
+
className: `rdb-btn rdb-btn-sm ${pendingVal === String(v) ? "rdb-btn-primary" : "rdb-btn-secondary"}`,
|
|
2478
|
+
onClick: () => setPendingVal(String(v))
|
|
2479
|
+
},
|
|
2480
|
+
pendingVal === String(v) ? "\u2705 " : "",
|
|
2481
|
+
"Proses = ",
|
|
2482
|
+
/* @__PURE__ */ import_react19.default.createElement("strong", null, String(v))
|
|
2483
|
+
))))) : /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { color: "var(--rdb-text-muted)", marginBottom: 8 } }, "Nilai status tidak terbaca otomatis. Ketik manual:"), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 } }, /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, 'Nilai "Selesai/Confirmed"'), /* @__PURE__ */ import_react19.default.createElement(
|
|
2484
|
+
"input",
|
|
2485
|
+
{
|
|
2486
|
+
className: "rdb-input",
|
|
2487
|
+
value: confirmedVal,
|
|
2488
|
+
onChange: (e) => setConfirmedVal(e.target.value),
|
|
2489
|
+
placeholder: "confirmed"
|
|
2490
|
+
}
|
|
2491
|
+
)), /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, 'Nilai "Pending/Proses"'), /* @__PURE__ */ import_react19.default.createElement(
|
|
2492
|
+
"input",
|
|
2493
|
+
{
|
|
2494
|
+
className: "rdb-input",
|
|
2495
|
+
value: pendingVal,
|
|
2496
|
+
onChange: (e) => setPendingVal(e.target.value),
|
|
2497
|
+
placeholder: "pending"
|
|
2498
|
+
}
|
|
2499
|
+
)))), sample.length > 0 && colDate && colStatus && colTotal && /* @__PURE__ */ import_react19.default.createElement("div", { style: { marginBottom: 16 } }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 4 } }, "Preview data nyata sesuai mapping kamu:"), /* @__PURE__ */ import_react19.default.createElement(
|
|
2500
|
+
PreviewTable,
|
|
2501
|
+
{
|
|
2502
|
+
rows: sample,
|
|
2503
|
+
cols: [colDate, colStatus, colTotal, colCustomer, colItem].filter((c) => c && c !== "-")
|
|
2504
|
+
}
|
|
2505
|
+
)), !canGen && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-error-banner", style: { marginBottom: 12 } }, "Isi Kolom Tanggal, Status, dan Total untuk melanjutkan."), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" } }, /* @__PURE__ */ import_react19.default.createElement(
|
|
2506
|
+
"button",
|
|
2507
|
+
{
|
|
2508
|
+
type: "button",
|
|
2509
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2510
|
+
onClick: () => setStep(1)
|
|
2511
|
+
},
|
|
2512
|
+
"\u2190 Kembali"
|
|
2513
|
+
), /* @__PURE__ */ import_react19.default.createElement(
|
|
2514
|
+
"button",
|
|
2515
|
+
{
|
|
2516
|
+
type: "button",
|
|
2517
|
+
className: "rdb-btn rdb-btn-primary rdb-btn-sm",
|
|
2518
|
+
disabled: !canGen,
|
|
2519
|
+
onClick: handleGenerate
|
|
2520
|
+
},
|
|
2521
|
+
"\u2728 Generate Kode \u2192"
|
|
2522
|
+
))), step === 3 && generatedCode && /* @__PURE__ */ import_react19.default.createElement(import_react19.default.Fragment, null, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-step-title" }, /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-wizard-step-num" }, "\u2705"), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-h3", style: { margin: 0 } }, "Kode Sudah Jadi!")), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-tip", style: { marginBottom: 12 } }, /* @__PURE__ */ import_react19.default.createElement("strong", null, "4 file digenerate."), " Klik tab file \u2192 Salin \u2192 Simpan ke lokasi yang tertera di bawah tab. Tidak perlu mengubah isi kode \u2014 langsung save dan jalankan."), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-issues", style: { marginBottom: 12 } }, Object.entries(TAB_INFO).map(([key, info], i) => /* @__PURE__ */ import_react19.default.createElement("div", { key, className: "rdb-wizard-issue-item" }, /* @__PURE__ */ import_react19.default.createElement("span", { style: { color: "var(--rdb-blue-500)", fontWeight: 700, minWidth: 18 } }, i + 1, "."), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-body" }, "Salin ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, info.label), " \u2192 simpan ke ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, info.path)))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-issue-item" }, /* @__PURE__ */ import_react19.default.createElement("span", { style: { color: "var(--rdb-blue-500)", fontWeight: 700, minWidth: 18 } }, "5."), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-body" }, "Di ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "src/index.css"), ", tambahkan:", " ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, '@import "../node_modules/@rozaqi02/reusable-dashboard/dist/index.css";'))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-issue-item" }, /* @__PURE__ */ import_react19.default.createElement("span", { style: { color: "var(--rdb-blue-500)", fontWeight: 700, minWidth: 18 } }, "6."), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-body" }, "Restart dev server (", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "npm start"), ") \u2192 buka halaman dashboard \u2192 selesai!"))), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 4, flexWrap: "wrap", marginBottom: 4 } }, Object.entries(TAB_INFO).map(([key, info]) => /* @__PURE__ */ import_react19.default.createElement(
|
|
2523
|
+
"button",
|
|
2524
|
+
{
|
|
2525
|
+
key,
|
|
2526
|
+
type: "button",
|
|
2527
|
+
onClick: () => setActiveTab(key),
|
|
2528
|
+
className: `rdb-btn rdb-btn-sm ${activeTab === key ? "rdb-btn-primary" : "rdb-btn-secondary"}`,
|
|
2529
|
+
style: { fontFamily: "monospace", fontSize: "0.73rem" }
|
|
2530
|
+
},
|
|
2531
|
+
info.label
|
|
2532
|
+
))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { color: "var(--rdb-text-muted)", marginBottom: 6 } }, "\u{1F4C1} Simpan ke: ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, (_b = TAB_INFO[activeTab]) == null ? void 0 : _b.path)), /* @__PURE__ */ import_react19.default.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ import_react19.default.createElement(
|
|
2533
|
+
"button",
|
|
2534
|
+
{
|
|
2535
|
+
type: "button",
|
|
2536
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2537
|
+
style: { position: "absolute", top: 8, right: 8, zIndex: 1 },
|
|
2538
|
+
onClick: () => copyCode(generatedCode[activeTab], activeTab)
|
|
2539
|
+
},
|
|
2540
|
+
copied === activeTab ? "\u2705 Tersalin!" : "\u{1F4CB} Salin kode"
|
|
2541
|
+
), /* @__PURE__ */ import_react19.default.createElement("pre", { className: "rdb-wizard-code", style: { maxHeight: 280, overflow: "auto" } }, generatedCode[activeTab])), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-alert", style: { marginTop: 10, marginBottom: 4 } }, /* @__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 } }, "Data tidak muncul di dashboard?"), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 4 } }, "Jalankan SQL ini di Supabase SQL Editor:"), /* @__PURE__ */ import_react19.default.createElement("div", { style: { position: "relative", marginTop: 6 } }, /* @__PURE__ */ import_react19.default.createElement(
|
|
2542
|
+
"button",
|
|
2543
|
+
{
|
|
2544
|
+
type: "button",
|
|
2545
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2546
|
+
style: { position: "absolute", top: 4, right: 4 },
|
|
2547
|
+
onClick: () => copyCode(`ALTER TABLE public.${selectedTable} DISABLE ROW LEVEL SECURITY;`, "rlsquick")
|
|
2548
|
+
},
|
|
2549
|
+
copied === "rlsquick" ? "\u2705" : "\u{1F4CB}"
|
|
2550
|
+
), /* @__PURE__ */ import_react19.default.createElement("pre", { className: "rdb-wizard-code", style: { fontSize: "0.72rem" } }, `ALTER TABLE public.${selectedTable} DISABLE ROW LEVEL SECURITY;`)))), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 10 } }, /* @__PURE__ */ import_react19.default.createElement(
|
|
2551
|
+
"button",
|
|
2552
|
+
{
|
|
2553
|
+
type: "button",
|
|
2554
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2555
|
+
onClick: () => setStep(2)
|
|
2556
|
+
},
|
|
2557
|
+
"\u2190 Edit mapping"
|
|
2558
|
+
), /* @__PURE__ */ import_react19.default.createElement(
|
|
2559
|
+
"button",
|
|
2560
|
+
{
|
|
2561
|
+
type: "button",
|
|
2562
|
+
className: "rdb-btn rdb-btn-primary rdb-btn-sm",
|
|
2563
|
+
onClick: handleDismiss
|
|
2564
|
+
},
|
|
2565
|
+
"Tutup wizard \u2713"
|
|
2566
|
+
)))))));
|
|
2176
2567
|
}
|
|
2177
2568
|
SetupWizard.propTypes = {
|
|
2178
2569
|
issues: import_prop_types17.default.arrayOf(import_prop_types17.default.string),
|