@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/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
- 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) {
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 { 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 });
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 { data: recent = [] } = await supabase
1996
- .from("MY_TABLE")
1997
- .select("id, created_at, customer_name, total_price, status")
1998
- .order("created_at", { ascending: false })
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: orders, // WAJIB: nama harus "bookings"
2003
- recent, // WAJIB: nama harus "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("my-dashboard-live")
2002
+ const ch = supabase.channel("rdb-${tableName}-live")
2012
2003
  .on("postgres_changes",
2013
- { event: "*", schema: "public", table: "MY_TABLE" }, onEvent)
2004
+ { event: "*", schema: "public", table: "${tableName}" }, onEvent)
2014
2005
  .subscribe();
2015
2006
  return () => supabase.removeChannel(ch);
2016
2007
  },
2017
2008
  };
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
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 createEmptyMyData() {
2015
+ export function createEmptyMyDashboardData() {
2031
2016
  return {
2032
- stats: { bookingsConfirm: 0, revenueConfirm: 0 },
2033
- charts: {
2034
- dailyTrends: [],
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 adaptMyData({ raw, range, dateLocale, labels }) {
2044
- if (!raw) return createEmptyMyData();
2023
+ export function adaptMyDashboardData({ raw, range, dateLocale, labels }) {
2024
+ if (!raw) return createEmptyMyDashboardData();
2045
2025
 
2046
- const buckets = buildDayBuckets(range.daysWindow, range.fromISO, dateLocale);
2047
- const dayMap = new Map(buckets.map(b => [b.dateKey, b]));
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 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);
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(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;
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 (status === "confirmed") { confirmed++; revenue += amount; }
2042
+ if (st === "${confirmed}") { confirmed++; revenue += amt; }
2063
2043
  });
2064
2044
 
2045
+ const total = (raw.bookings || []).length;
2065
2046
  return {
2066
- stats: { bookingsConfirm: confirmed, revenueConfirm: revenue },
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
- .map(([status, count]) => ({
2071
- status,
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: row.id,
2081
- createdAt: row.created_at,
2082
- customerName: row.customer_name || "-",
2083
- packageName: row.service_type || "-", // \u2190 sesuaikan nama kolom
2063
+ id: row.id,
2064
+ createdAt: row["${colDate}"],
2065
+ customerName: row["${custCol}"] || "-",
2066
+ packageName: ${itemCol !== "-" ? `row["${itemCol}"] || "-"` : '"-"'},
2084
2067
  audienceLabel: "-",
2085
- totalIDR: toNumber(row.total_price),
2086
- status: String(row.status || "pending").toLowerCase(),
2087
- statusLabel: labels?.formatStatusLabel?.(row.status) || row.status,
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
- }`), /* @__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";
2074
+ }`;
2075
+ const widgetConfig = `// src/config/myDashboardConfig.js
2076
+ // AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
2101
2077
 
2102
- // 1. Widget Config \u2014 deklaratif, tentukan apa yang tampil
2103
- const myWidgetConfig = {
2104
- id: "my.dashboard",
2105
- defaultFilters: { statusScope: "confirmed", daysPreset: 30 },
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: "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" },
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: "trend", type: "dailyArea", label: "dailyTrends", icon: "BarChart3" },
2115
- { id: "status", type: "statusPie", label: "statusDistribution", icon: "PieChart" },
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: "recent", label: "recentBookings", icon: "Calendar",
2119
- emptyLabel: "noRecentBookings",
2098
+ id:"recentTx", label:"recentBookings", icon:"Calendar", emptyLabel:"noRecentBookings",
2120
2099
  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" },
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
- // 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
- });
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
- // 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)",
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
- // 4. Render \u2014 selesai!
2158
- export default function MyDashboard() {
2159
- const state = useReusableDashboard({ ...dashConfig, labels });
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={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}
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
- }`), /* @__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"))))));
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),