@rozaqi02/reusable-dashboard 1.1.3 → 1.1.4

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,254 +1912,270 @@ 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();
1915
+ var PRESET_SIGNATURES = {
1916
+ cidika: {
1917
+ name: "Cidika Travel",
1918
+ tables: ["bookings", "packages", "package_locales"]
1919
+ },
1920
+ tokoSepatu: {
1921
+ name: "Toko Sepatu / E-Commerce",
1922
+ tables: ["orders", "products", "customers"]
1957
1923
  }
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) {
1924
+ };
1925
+ function detectPreset(tables) {
1926
+ if (!tables || tables.length === 0) return null;
1927
+ const tSet = new Set(tables);
1928
+ if (PRESET_SIGNATURES.cidika.tables.every((t) => tSet.has(t))) return "cidika";
1929
+ if (PRESET_SIGNATURES.tokoSepatu.tables.every((t) => tSet.has(t))) return "tokoSepatu";
1930
+ return null;
1931
+ }
1932
+ function generateCode(mapping) {
1933
+ const {
1934
+ tableName,
1935
+ colDate,
1936
+ colStatus,
1937
+ colTotal,
1938
+ colCustomer,
1939
+ colItem,
1940
+ dashTitle,
1941
+ confirmedValue,
1942
+ pendingValue
1943
+ } = mapping;
1944
+ const safeTitle = dashTitle || "Dashboard";
1945
+ const confirmed = confirmedValue || "confirmed";
1946
+ const pending = pendingValue || "pending";
1947
+ const safeCustomer = colCustomer || "customer_name";
1948
+ const safeItem = colItem || "-";
1949
+ const colTotalFull = colTotal || "total";
1950
+ const dataSource = `// src/datasources/myDashboardSource.js
1951
+ // AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
1952
+ // Tabel: ${tableName}
1953
+
1954
+ export function createMyDashboardSource(supabase) {
1986
1955
  return {
1987
1956
  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 });
1957
+ const allQuery = supabase
1958
+ .from("${tableName}")
1959
+ .select("id, ${colDate}, ${colStatus}, ${colTotalFull}${colCustomer !== "customer_name" ? `, ${colCustomer}` : ", customer_name"}${colItem !== "-" ? `, ${colItem}` : ""}")
1960
+ .gte("${colDate}", fromISO)
1961
+ .lte("${colDate}", toISO)
1962
+ .order("${colDate}", { ascending: true });
1994
1963
 
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 })
1964
+ const recentQuery = supabase
1965
+ .from("${tableName}")
1966
+ .select("id, ${colDate}, ${colStatus}, ${colTotalFull}${colCustomer !== "customer_name" ? `, ${colCustomer}` : ", customer_name"}${colItem !== "-" ? `, ${colItem}` : ""}")
1967
+ .gte("${colDate}", fromISO)
1968
+ .lte("${colDate}", toISO)
1969
+ .order("${colDate}", { ascending: false })
1999
1970
  .limit(10);
2000
1971
 
1972
+ if (statusScope && statusScope !== "all") {
1973
+ recentQuery.eq("${colStatus}", statusScope);
1974
+ }
1975
+
1976
+ const [allRes, recentRes] = await Promise.all([allQuery, recentQuery]);
1977
+
2001
1978
  return {
2002
- bookings: orders, // WAJIB: nama harus "bookings"
2003
- recent, // WAJIB: nama harus "recent"
1979
+ bookings: allRes.data || [], // semua transaksi (untuk chart & stats)
1980
+ recent: recentRes.data || [], // 10 terbaru (untuk tabel)
2004
1981
  packageLocales: [],
2005
1982
  staticCounts: { packages: 0, sections: 0 },
2006
1983
  };
2007
1984
  },
2008
1985
 
2009
- // Opsional: live update
2010
1986
  subscribeLiveUpdate(onEvent) {
2011
- const ch = supabase.channel("my-dashboard-live")
2012
- .on("postgres_changes",
2013
- { event: "*", schema: "public", table: "MY_TABLE" }, onEvent)
1987
+ const ch = supabase
1988
+ .channel("rdb-dashboard-live")
1989
+ .on("postgres_changes", { event: "*", schema: "public", table: "${tableName}" }, onEvent)
2014
1990
  .subscribe();
2015
1991
  return () => supabase.removeChannel(ch);
2016
1992
  },
2017
1993
  };
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
1994
+ }`;
1995
+ const adapter = `// src/adapters/myDashboardAdapter.js
1996
+ // AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
1997
+ // Mapping kolom: status="${colStatus}", total="${colTotalFull}", tanggal="${colDate}"
1998
+
2028
1999
  import { toNumber, buildDayBuckets } from "@rozaqi02/reusable-dashboard";
2029
2000
 
2030
- export function createEmptyMyData() {
2001
+ export function createEmptyMyDashboardData() {
2031
2002
  return {
2032
- stats: { bookingsConfirm: 0, revenueConfirm: 0 },
2033
- charts: {
2034
- dailyTrends: [],
2035
- statusDistribution: [],
2036
- audienceDistribution: [],
2037
- topPackages: [],
2038
- },
2039
- table: { recentBookings: [] },
2003
+ stats: { bookingsConfirm: 0, revenueConfirm: 0 },
2004
+ charts: { dailyTrends: [], statusDistribution: [],
2005
+ audienceDistribution: [], topPackages: [] },
2006
+ table: { recentBookings: [] },
2040
2007
  };
2041
2008
  }
2042
2009
 
2043
- export function adaptMyData({ raw, range, dateLocale, labels }) {
2044
- if (!raw) return createEmptyMyData();
2010
+ export function adaptMyDashboardData({ raw, range, dateLocale, labels }) {
2011
+ if (!raw) return createEmptyMyDashboardData();
2045
2012
 
2046
- const buckets = buildDayBuckets(range.daysWindow, range.fromISO, dateLocale);
2047
- const dayMap = new Map(buckets.map(b => [b.dateKey, b]));
2013
+ const buckets = buildDayBuckets(range.daysWindow, range.fromISO, dateLocale);
2014
+ const dayMap = new Map(buckets.map(b => [b.dateKey, b]));
2048
2015
  const statusMap = new Map();
2049
2016
  let confirmed = 0, revenue = 0;
2050
2017
 
2051
2018
  (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);
2019
+ const status = String(row["${colStatus}"] || "pending").toLowerCase();
2020
+ const amount = toNumber(row["${colTotalFull}"]);
2021
+ const dayKey = String(row["${colDate}"] || "").slice(0, 10);
2055
2022
 
2056
2023
  statusMap.set(status, (statusMap.get(status) || 0) + 1);
2057
2024
  const bucket = dayMap.get(dayKey);
2058
- if (bucket && status === "confirmed") {
2059
- bucket.count += 1;
2060
- bucket.revenue += amount;
2025
+ if (bucket) {
2026
+ if (status === "${confirmed}") { bucket.count++; bucket.revenue += amount; }
2027
+ if (status === "${pending}") { bucket.pendingCount++; }
2061
2028
  }
2062
- if (status === "confirmed") { confirmed++; revenue += amount; }
2029
+ if (status === "${confirmed}") { confirmed++; revenue += amount; }
2063
2030
  });
2064
2031
 
2032
+ const avgRevenue = confirmed > 0 ? Math.round(revenue / confirmed) : 0;
2033
+ const totalTx = (raw.bookings || []).length;
2034
+ const conversionRate = totalTx > 0 ? Math.round((confirmed / totalTx) * 100) : 0;
2035
+
2065
2036
  return {
2066
- stats: { bookingsConfirm: confirmed, revenueConfirm: revenue },
2037
+ stats: {
2038
+ bookingsConfirm: confirmed,
2039
+ revenueConfirm: revenue,
2040
+ avgRevenue,
2041
+ conversionRate,
2042
+ },
2067
2043
  charts: {
2068
2044
  dailyTrends: buckets,
2069
2045
  statusDistribution: Array.from(statusMap.entries())
2046
+ .sort((a, b) => b[1] - a[1])
2070
2047
  .map(([status, count]) => ({
2071
- status,
2048
+ status, count,
2072
2049
  label: labels?.formatStatusLabel?.(status) || status,
2073
- count,
2074
2050
  })),
2075
2051
  audienceDistribution: [],
2076
2052
  topPackages: [],
2077
2053
  },
2078
2054
  table: {
2079
2055
  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
2056
+ id: row.id,
2057
+ createdAt: row["${colDate}"],
2058
+ customerName: row["${colCustomer}"] || "-",
2059
+ packageName: ${colItem !== "-" ? `row["${colItem}"] || "-"` : '"-"'},
2084
2060
  audienceLabel: "-",
2085
- totalIDR: toNumber(row.total_price),
2086
- status: String(row.status || "pending").toLowerCase(),
2087
- statusLabel: labels?.formatStatusLabel?.(row.status) || row.status,
2061
+ totalIDR: toNumber(row["${colTotalFull}"]),
2062
+ status: String(row["${colStatus}"] || "pending").toLowerCase(),
2063
+ statusLabel: labels?.formatStatusLabel?.(row["${colStatus}"]) || row["${colStatus}"],
2088
2064
  })),
2089
2065
  },
2090
2066
  };
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";
2067
+ }`;
2068
+ const widgetConfig = `// src/config/myDashboardConfig.js
2069
+ // AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
2098
2070
 
2099
- import { createMySupabaseSource } from "../datasources/myDataSource";
2100
- import { adaptMyData, createEmptyMyData } from "../adapters/myAdapter";
2101
-
2102
- // 1. Widget Config \u2014 deklaratif, tentukan apa yang tampil
2103
- const myWidgetConfig = {
2104
- id: "my.dashboard",
2105
- defaultFilters: { statusScope: "confirmed", daysPreset: 30 },
2071
+ export const myDashboardConfig = {
2072
+ id: "my.custom.dashboard",
2073
+ defaultFilters: {
2074
+ statusScope: "${confirmed}",
2075
+ daysPreset: 30,
2076
+ sortPkgBy: "bookings",
2077
+ sortPkgDir: "desc",
2078
+ },
2106
2079
  widgets: {
2107
2080
  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" },
2081
+ { id: "orders", label: "confirmedBookings", icon: "TrendingUp",
2082
+ valueKey: "bookingsConfirm", format: "number", accentColor: "blue" },
2083
+ { id: "revenue", label: "confirmedRevenue", icon: "DollarSign",
2084
+ valueKey: "revenueConfirm", format: "currency", accentColor: "green" },
2085
+ { id: "avg", label: "avgRevenue", icon: "Users",
2086
+ valueKey: "avgRevenue", format: "currency", accentColor: "violet" },
2087
+ { id: "conversion", label: "conversionRate", icon: "PieChart",
2088
+ valueKey: "conversionRate", format: "percent", accentColor: "orange" },
2112
2089
  ],
2113
2090
  charts: [
2114
- { id: "trend", type: "dailyArea", label: "dailyTrends", icon: "BarChart3" },
2091
+ { id: "trend", type: "dailyArea", label: "dailyTrends", icon: "BarChart3" },
2115
2092
  { id: "status", type: "statusPie", label: "statusDistribution", icon: "PieChart" },
2116
2093
  ],
2117
2094
  table: {
2118
- id: "recent", label: "recentBookings", icon: "Calendar",
2095
+ id: "recentTx", label: "recentBookings", icon: "Calendar",
2119
2096
  emptyLabel: "noRecentBookings",
2120
2097
  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",
2098
+ { id: "date", label: "date", accessor: "createdAt", type: "date" },
2099
+ { id: "customer", label: "customer", accessor: "customerName" },${colItem !== "-" ? `
2100
+ { id: "item", label: "package", accessor: "packageName" },` : ""}
2101
+ { id: "total", label: "total", accessor: "totalIDR", type: "currency" },
2102
+ { id: "status", label: "status", accessor: "statusLabel",
2125
2103
  type: "statusBadge", statusAccessor: "status" },
2126
2104
  ],
2127
2105
  },
2128
2106
  },
2129
- };
2107
+ };`;
2108
+ const dashboard = `// src/pages/admin/Dashboard.jsx
2109
+ // AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
2110
+ // Salin file ini ke halaman dashboard kamu.
2130
2111
 
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
- });
2112
+ import React from "react";
2113
+ import { supabase } from "../../lib/supabaseClient.js";
2114
+ import {
2115
+ ReusableDashboardView,
2116
+ useReusableDashboard,
2117
+ createDashboardConfig,
2118
+ } from "@rozaqi02/reusable-dashboard";
2140
2119
 
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)",
2120
+ // Import 3 file yang digenerate wizard
2121
+ import { myDashboardConfig } from "./myDashboardConfig";
2122
+ import { createMyDashboardSource } from "./myDashboardSource";
2123
+ import { adaptMyDashboardData, createEmptyMyDashboardData } from "./myDashboardAdapter";
2124
+
2125
+ // Data source \u2014 dibuat sekali di luar komponen
2126
+ const source = createMyDashboardSource(supabase);
2127
+
2128
+ // Label UI \u2014 sesuaikan teks dengan bahasa/konteks bisnis kamu
2129
+ const labels = {
2130
+ title: "${safeTitle}",
2131
+ refresh: "Refresh",
2132
+ liveUpdate: "Live update",
2133
+ loadFailed: "Gagal memuat data.",
2134
+ retry: "Coba Lagi",
2135
+ confirmedOnly: "Selesai",
2136
+ pendingOnly: "Proses",
2137
+ allStatus: "Semua Status",
2138
+ showPendingOverlay: "Tampilkan pending",
2139
+ reset: "Reset",
2140
+ confirmedBookings: "Total Transaksi",
2141
+ confirmedRevenue: "Total Pendapatan",
2142
+ avgRevenue: "Rata-rata / Transaksi",
2143
+ conversionRate: "Conversion Rate",
2144
+ dailyTrends: "Tren Harian",
2145
+ statusDistribution: "Distribusi Status",
2146
+ recentBookings: "Transaksi Terbaru",
2147
+ noRecentBookings: "Belum ada transaksi",
2148
+ date: "Tanggal",
2149
+ customer: "Pelanggan",
2150
+ package: "Item",
2151
+ total: "Total",
2152
+ status: "Status",
2153
+ bookingsMetric: "Transaksi",
2154
+ revenueMetric: "Pendapatan",
2155
+ confirmedBookingMetric: "Transaksi (Selesai)",
2156
+ confirmedRevenueMetric: "Pendapatan (Selesai)",
2151
2157
  dayLabel: n => n + " hari",
2152
2158
  formatStatusLabel: s =>
2153
- ({ confirmed: "Selesai", pending: "Proses", cancelled: "Batal" })[s] || s,
2159
+ ({ "${confirmed}": "Selesai", "${pending}": "Proses" })[s] || (s || "-"),
2154
2160
  formatAudienceLabel: v => v || "-",
2155
2161
  };
2156
2162
 
2157
- // 4. Render \u2014 selesai!
2158
- export default function MyDashboard() {
2159
- const state = useReusableDashboard({ ...dashConfig, labels });
2163
+ // Kemas semua jadi 1 objek
2164
+ const dashboardConfig = createDashboardConfig({
2165
+ widgetConfig: myDashboardConfig,
2166
+ dataSource: source,
2167
+ adapter: adaptMyDashboardData,
2168
+ createEmptyState: createEmptyMyDashboardData,
2169
+ languageCode: "id",
2170
+ dateLocale: "id-ID",
2171
+ labels,
2172
+ });
2173
+
2174
+ export default function Dashboard() {
2175
+ const state = useReusableDashboard({ ...dashboardConfig, labels });
2160
2176
  return (
2161
2177
  <ReusableDashboardView
2162
- config={dashConfig.config}
2178
+ config={dashboardConfig.config}
2163
2179
  labels={labels}
2164
2180
  loading={state.loading}
2165
2181
  error={state.error}
@@ -2168,11 +2184,427 @@ export default function MyDashboard() {
2168
2184
  onResetFilters={state.resetFilters}
2169
2185
  onRefresh={state.refresh}
2170
2186
  data={state.data}
2171
- dateLocale={dashConfig.dateLocale}
2187
+ dateLocale={dashboardConfig.dateLocale}
2172
2188
  liveUpdateEnabled={state.liveUpdateEnabled}
2189
+ supabase={supabase}
2190
+ dashboardConfig={dashboardConfig}
2173
2191
  />
2174
2192
  );
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"))))));
2193
+ }`;
2194
+ return { dataSource, adapter, widgetConfig, dashboard };
2195
+ }
2196
+ function SetupWizard({ issues = [], onDismiss, supabase }) {
2197
+ const [dismissed, setDismissed] = (0, import_react19.useState)(false);
2198
+ const [step, setStep] = (0, import_react19.useState)(0);
2199
+ const [detecting, setDetecting] = (0, import_react19.useState)(true);
2200
+ const [tables, setTables] = (0, import_react19.useState)([]);
2201
+ const [columns, setColumns] = (0, import_react19.useState)({});
2202
+ const [supabaseOk, setSupabaseOk] = (0, import_react19.useState)(false);
2203
+ const [detectionDone, setDetectionDone] = (0, import_react19.useState)(false);
2204
+ const [userHasDashboard, setUserHasDashboard] = (0, import_react19.useState)(null);
2205
+ const [selectedTable, setSelectedTable] = (0, import_react19.useState)("");
2206
+ const [colDate, setColDate] = (0, import_react19.useState)("");
2207
+ const [colStatus, setColStatus] = (0, import_react19.useState)("");
2208
+ const [colTotal, setColTotal] = (0, import_react19.useState)("");
2209
+ const [colCustomer, setColCustomer] = (0, import_react19.useState)("");
2210
+ const [colItem, setColItem] = (0, import_react19.useState)("");
2211
+ const [confirmedVal, setConfirmedVal] = (0, import_react19.useState)("confirmed");
2212
+ const [pendingVal, setPendingVal] = (0, import_react19.useState)("pending");
2213
+ const [dashTitle, setDashTitle] = (0, import_react19.useState)("Dashboard");
2214
+ const [loadingColumns, setLoadingColumns] = (0, import_react19.useState)(false);
2215
+ const [generatedCode, setGeneratedCode] = (0, import_react19.useState)(null);
2216
+ const [activeCodeTab, setActiveCodeTab] = (0, import_react19.useState)("dataSource");
2217
+ const [copied, setCopied] = (0, import_react19.useState)("");
2218
+ (0, import_react19.useEffect)(() => {
2219
+ if (!supabase) {
2220
+ setDetecting(false);
2221
+ setDetectionDone(true);
2222
+ return;
2223
+ }
2224
+ (async () => {
2225
+ try {
2226
+ const { data: rpcData, error: rpcErr } = await supabase.rpc("rdb_get_tables");
2227
+ if (!rpcErr && Array.isArray(rpcData)) {
2228
+ setTables(rpcData.map((r) => typeof r === "string" ? r : r.table_name || r.name || r));
2229
+ setSupabaseOk(true);
2230
+ } else {
2231
+ const { data: schemaData, error: schemaErr } = await supabase.from("information_schema.tables").select("table_name").eq("table_schema", "public").eq("table_type", "BASE TABLE").order("table_name");
2232
+ if (!schemaErr && schemaData) {
2233
+ setTables(schemaData.map((r) => r.table_name));
2234
+ setSupabaseOk(true);
2235
+ } else {
2236
+ const { error: pingErr } = await supabase.from("_rdb_ping_").select("*").limit(1);
2237
+ const connected = !pingErr || pingErr.code === "42P01" || pingErr.code === "PGRST116" || (pingErr.message || "").includes("does not exist");
2238
+ setSupabaseOk(connected);
2239
+ setTables([]);
2240
+ }
2241
+ }
2242
+ } catch {
2243
+ setSupabaseOk(false);
2244
+ } finally {
2245
+ setDetecting(false);
2246
+ setDetectionDone(true);
2247
+ }
2248
+ })();
2249
+ }, [supabase]);
2250
+ const loadColumns = (0, import_react19.useCallback)(async (tbl) => {
2251
+ if (!supabase || !tbl || columns[tbl]) return;
2252
+ setLoadingColumns(true);
2253
+ try {
2254
+ const { data: rpcCols, error: rpcErr } = await supabase.rpc("rdb_get_columns", { p_table: tbl });
2255
+ if (!rpcErr && Array.isArray(rpcCols)) {
2256
+ const colObjs = rpcCols.map(
2257
+ (c) => typeof c === "string" ? { column_name: c, data_type: "unknown" } : { column_name: c.column_name || c.name || c, data_type: c.data_type || "unknown" }
2258
+ );
2259
+ setColumns((prev) => ({ ...prev, [tbl]: colObjs }));
2260
+ autoDetectCols(colObjs);
2261
+ return;
2262
+ }
2263
+ const { data: schemaCols, error: schemaErr } = await supabase.from("information_schema.columns").select("column_name, data_type").eq("table_schema", "public").eq("table_name", tbl).order("ordinal_position");
2264
+ if (!schemaErr && schemaCols) {
2265
+ setColumns((prev) => ({ ...prev, [tbl]: schemaCols }));
2266
+ autoDetectCols(schemaCols);
2267
+ return;
2268
+ }
2269
+ const { data: sampleRow, error: sampleErr } = await supabase.from(tbl).select("*").limit(1);
2270
+ if (!sampleErr && sampleRow && sampleRow.length > 0) {
2271
+ const colObjs = Object.keys(sampleRow[0]).map((k) => ({
2272
+ column_name: k,
2273
+ data_type: typeof sampleRow[0][k]
2274
+ }));
2275
+ setColumns((prev) => ({ ...prev, [tbl]: colObjs }));
2276
+ autoDetectCols(colObjs);
2277
+ return;
2278
+ }
2279
+ setColumns((prev) => ({ ...prev, [tbl]: [] }));
2280
+ } catch {
2281
+ setColumns((prev) => ({ ...prev, [tbl]: [] }));
2282
+ } finally {
2283
+ setLoadingColumns(false);
2284
+ }
2285
+ }, [supabase, columns]);
2286
+ function autoDetectCols(colObjs) {
2287
+ const find = (candidates) => {
2288
+ var _a;
2289
+ return ((_a = colObjs.find((c) => candidates.includes(c.column_name.toLowerCase()))) == null ? void 0 : _a.column_name) || "";
2290
+ };
2291
+ setColDate(find(["created_at", "tanggal", "date", "transaction_date", "order_date", "waktu"]));
2292
+ setColStatus(find(["status", "state", "kondisi", "order_status", "payment_status"]));
2293
+ setColTotal(find(["total", "total_idr", "total_amount", "total_price", "harga_total", "amount", "nominal", "harga", "biaya"]));
2294
+ setColCustomer(find(["customer_name", "nama_pelanggan", "nama", "name", "client_name", "buyer_name", "pelanggan"]));
2295
+ setColItem(find(["service_type", "item_name", "product_name", "package_name", "layanan", "produk", "nama_layanan", "jenis", "nama_produk"]));
2296
+ }
2297
+ function copyCode(text, label) {
2298
+ navigator.clipboard.writeText(text).then(() => {
2299
+ setCopied(label);
2300
+ setTimeout(() => setCopied(""), 2e3);
2301
+ });
2302
+ }
2303
+ function handleDismiss() {
2304
+ setDismissed(true);
2305
+ if (onDismiss) onDismiss();
2306
+ }
2307
+ function handleGenerate() {
2308
+ if (!selectedTable || !colDate || !colStatus || !colTotal) return;
2309
+ const code = generateCode({
2310
+ tableName: selectedTable,
2311
+ colDate,
2312
+ colStatus,
2313
+ colTotal,
2314
+ colCustomer: colCustomer || "customer_name",
2315
+ colItem: colItem || "-",
2316
+ dashTitle,
2317
+ confirmedValue: confirmedVal,
2318
+ pendingValue: pendingVal
2319
+ });
2320
+ setGeneratedCode(code);
2321
+ setStep(3);
2322
+ }
2323
+ if (dismissed) return null;
2324
+ const detectedPreset = detectPreset(tables);
2325
+ const tableColumns = selectedTable ? columns[selectedTable] || [] : [];
2326
+ const colNames = tableColumns.map((c) => c.column_name);
2327
+ const mappingValid = selectedTable && colDate && colStatus && colTotal;
2328
+ const stepLabels = ["Deteksi", "Pilih Tabel", "Mapping Kolom", "Kode Siap Pakai"];
2329
+ return /* @__PURE__ */ import_react19.default.createElement(
2330
+ "div",
2331
+ {
2332
+ className: "rdb-wizard-overlay",
2333
+ role: "dialog",
2334
+ "aria-modal": "true",
2335
+ "aria-label": "Setup Wizard @rozaqi02/reusable-dashboard"
2336
+ },
2337
+ /* @__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" }, detecting ? "Mendeteksi kondisi project\u2026" : supabaseOk ? `Supabase tersambung \xB7 ${tables.length} tabel ditemukan` : "Panduan konfigurasi @rozaqi02/reusable-dashboard"))), /* @__PURE__ */ import_react19.default.createElement(
2338
+ "button",
2339
+ {
2340
+ type: "button",
2341
+ className: "rdb-wizard-close",
2342
+ onClick: handleDismiss,
2343
+ title: "Tutup"
2344
+ },
2345
+ "\u2715"
2346
+ )), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-steps" }, stepLabels.map((label, i) => /* @__PURE__ */ import_react19.default.createElement(
2347
+ "button",
2348
+ {
2349
+ key: i,
2350
+ type: "button",
2351
+ className: `rdb-wizard-step-btn ${step === i ? "rdb-wizard-step-active" : ""}`,
2352
+ onClick: () => {
2353
+ if (i < step || i === step + 1 && step < 2) setStep(i);
2354
+ }
2355
+ },
2356
+ /* @__PURE__ */ import_react19.default.createElement("span", null, step > i ? "\u2705" : i + 1, "."),
2357
+ /* @__PURE__ */ import_react19.default.createElement("span", null, label)
2358
+ ))), /* @__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 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: 12 } }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 10 } }, "\u{1F50D} Hasil deteksi otomatis:"), 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" }, "Supabase ", supabaseOk ? "tersambung" : "tidak tersambung \u2014 pastikan supabaseClient.js sudah dikonfigurasi dan prop supabase dikirim 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 ? `${tables.length} tabel public ditemukan` : "Daftar tabel tidak bisa dibaca otomatis (RLS aktif) \u2014 kamu bisa ketik nama tabel di step berikutnya")), supabaseOk && tables.length > 0 && detectedPreset && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-issue-item" }, /* @__PURE__ */ import_react19.default.createElement("span", null, "\u2705"), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-body" }, "Tabel cocok dengan preset ", /* @__PURE__ */ import_react19.default.createElement("strong", null, PRESET_SIGNATURES[detectedPreset].name))))), !supabaseOk && detectionDone && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-alert", style: { marginBottom: 12 } }, /* @__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 } }, "Aktifkan koneksi Supabase"), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 4 } }, "Tambahkan prop ke ReusableDashboardView:", " ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "supabase=", "{supabase}")))), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" } }, /* @__PURE__ */ import_react19.default.createElement(
2359
+ "button",
2360
+ {
2361
+ type: "button",
2362
+ className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
2363
+ onClick: handleDismiss
2364
+ },
2365
+ "Lanjutkan tanpa wizard"
2366
+ ), supabaseOk && /* @__PURE__ */ import_react19.default.createElement(
2367
+ "button",
2368
+ {
2369
+ type: "button",
2370
+ className: "rdb-btn rdb-btn-primary rdb-btn-sm",
2371
+ onClick: () => setStep(1)
2372
+ },
2373
+ "Lanjut \u2192 Pilih Tabel"
2374
+ ))), 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 } }, "Pilih tabel yang berisi data transaksi bisnis kamu."), tables.length > 0 && /* @__PURE__ */ import_react19.default.createElement(import_react19.default.Fragment, null, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 8 } }, "Tabel yang ditemukan:"), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6, marginBottom: 16 } }, tables.map((tbl) => /* @__PURE__ */ import_react19.default.createElement(
2375
+ "button",
2376
+ {
2377
+ key: tbl,
2378
+ type: "button",
2379
+ onClick: () => {
2380
+ setSelectedTable(tbl);
2381
+ loadColumns(tbl);
2382
+ },
2383
+ className: `rdb-btn rdb-btn-sm ${selectedTable === tbl ? "rdb-btn-primary" : "rdb-btn-secondary"}`,
2384
+ style: { justifyContent: "flex-start", fontFamily: "monospace" }
2385
+ },
2386
+ selectedTable === tbl ? "\u2705 " : "\u25CB ",
2387
+ tbl
2388
+ )))), tables.length === 0 && /* @__PURE__ */ import_react19.default.createElement("div", { style: { marginBottom: 16 } }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-alert", style: { marginBottom: 12 } }, /* @__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 } }, "Daftar tabel tidak bisa terbaca otomatis"), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 4 } }, "Ini normal \u2014 Supabase memblokir akses ke ", /* @__PURE__ */ import_react19.default.createElement("code", null, "information_schema"), " via API publik. Ketik nama tabel secara manual di bawah."))), /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, "Nama tabel transaksi kamu"), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8 } }, /* @__PURE__ */ import_react19.default.createElement(
2389
+ "input",
2390
+ {
2391
+ className: "rdb-input",
2392
+ value: selectedTable,
2393
+ onChange: (e) => setSelectedTable(e.target.value),
2394
+ placeholder: "cth: orders, transaksi, bookings, penjualan",
2395
+ style: { maxWidth: 320 }
2396
+ }
2397
+ ), /* @__PURE__ */ import_react19.default.createElement(
2398
+ "button",
2399
+ {
2400
+ type: "button",
2401
+ className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
2402
+ disabled: !selectedTable || loadingColumns,
2403
+ onClick: () => loadColumns(selectedTable)
2404
+ },
2405
+ loadingColumns ? "Membaca\u2026" : "Baca kolom"
2406
+ ))), tables.length > 0 && !selectedTable && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { color: "var(--rdb-text-muted)", marginBottom: 12 } }, "Atau ketik nama tabel secara manual:", /* @__PURE__ */ import_react19.default.createElement(
2407
+ "input",
2408
+ {
2409
+ className: "rdb-input",
2410
+ style: { marginTop: 6, maxWidth: 280 },
2411
+ placeholder: "nama tabel lain",
2412
+ onChange: (e) => {
2413
+ setSelectedTable(e.target.value);
2414
+ if (e.target.value) loadColumns(e.target.value);
2415
+ }
2416
+ }
2417
+ )), selectedTable && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-tip", style: { marginBottom: 12 } }, "Tabel ", /* @__PURE__ */ import_react19.default.createElement("strong", null, selectedTable), " dipilih.", loadingColumns ? " Membaca kolom\u2026" : tableColumns.length > 0 ? ` ${tableColumns.length} kolom ditemukan \u2014 kolom sudah ter-auto-detect di step berikutnya.` : tableColumns.length === 0 && !loadingColumns && columns[selectedTable] !== void 0 ? " Tabel kosong atau tidak ada kolom terbaca \u2014 kamu bisa ketik nama kolom manual di step berikutnya." : ""), /* @__PURE__ */ import_react19.default.createElement("div", { style: { marginBottom: 12 } }, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, "Judul dashboard (opsional)"), /* @__PURE__ */ import_react19.default.createElement(
2418
+ "input",
2419
+ {
2420
+ className: "rdb-input",
2421
+ value: dashTitle,
2422
+ style: { maxWidth: 280 },
2423
+ onChange: (e) => setDashTitle(e.target.value),
2424
+ placeholder: "cth: Dashboard Laundry Bersih"
2425
+ }
2426
+ )), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" } }, /* @__PURE__ */ import_react19.default.createElement(
2427
+ "button",
2428
+ {
2429
+ type: "button",
2430
+ className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
2431
+ onClick: () => setStep(0)
2432
+ },
2433
+ "\u2190 Kembali"
2434
+ ), /* @__PURE__ */ import_react19.default.createElement(
2435
+ "button",
2436
+ {
2437
+ type: "button",
2438
+ className: "rdb-btn rdb-btn-primary rdb-btn-sm",
2439
+ disabled: !selectedTable || loadingColumns,
2440
+ onClick: () => setStep(2)
2441
+ },
2442
+ "Lanjut \u2192 Mapping Kolom"
2443
+ ))), 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 } }, "Mapping Kolom \u2014 Tabel: ", /* @__PURE__ */ import_react19.default.createElement("code", { style: { fontSize: "0.9rem" } }, selectedTable))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-body", style: { color: "var(--rdb-text-muted)", marginBottom: 16 } }, "Pilih kolom yang berperan sebagai masing-masing data. Kolom sudah ter-deteksi otomatis \u2014 cukup verifikasi dan sesuaikan jika perlu."), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 16 } }, /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, "Kolom Tanggal ", /* @__PURE__ */ import_react19.default.createElement("span", { style: { color: "red" } }, "*")), colNames.length > 0 ? /* @__PURE__ */ import_react19.default.createElement(
2444
+ "select",
2445
+ {
2446
+ className: "rdb-select",
2447
+ value: colDate,
2448
+ onChange: (e) => setColDate(e.target.value)
2449
+ },
2450
+ /* @__PURE__ */ import_react19.default.createElement("option", { value: "" }, "\u2014 pilih kolom \u2014"),
2451
+ colNames.map((c) => /* @__PURE__ */ import_react19.default.createElement("option", { key: c, value: c }, c))
2452
+ ) : /* @__PURE__ */ import_react19.default.createElement(
2453
+ "input",
2454
+ {
2455
+ className: "rdb-input",
2456
+ value: colDate,
2457
+ onChange: (e) => setColDate(e.target.value),
2458
+ placeholder: "cth: created_at"
2459
+ }
2460
+ ), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 2 } }, "Tipe timestamp/date")), /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, "Kolom Status ", /* @__PURE__ */ import_react19.default.createElement("span", { style: { color: "red" } }, "*")), colNames.length > 0 ? /* @__PURE__ */ import_react19.default.createElement(
2461
+ "select",
2462
+ {
2463
+ className: "rdb-select",
2464
+ value: colStatus,
2465
+ onChange: (e) => setColStatus(e.target.value)
2466
+ },
2467
+ /* @__PURE__ */ import_react19.default.createElement("option", { value: "" }, "\u2014 pilih kolom \u2014"),
2468
+ colNames.map((c) => /* @__PURE__ */ import_react19.default.createElement("option", { key: c, value: c }, c))
2469
+ ) : /* @__PURE__ */ import_react19.default.createElement(
2470
+ "input",
2471
+ {
2472
+ className: "rdb-input",
2473
+ value: colStatus,
2474
+ onChange: (e) => setColStatus(e.target.value),
2475
+ placeholder: "cth: status"
2476
+ }
2477
+ ), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 2 } }, "Nilai selesai/pending")), /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, "Kolom Total (uang) ", /* @__PURE__ */ import_react19.default.createElement("span", { style: { color: "red" } }, "*")), colNames.length > 0 ? /* @__PURE__ */ import_react19.default.createElement(
2478
+ "select",
2479
+ {
2480
+ className: "rdb-select",
2481
+ value: colTotal,
2482
+ onChange: (e) => setColTotal(e.target.value)
2483
+ },
2484
+ /* @__PURE__ */ import_react19.default.createElement("option", { value: "" }, "\u2014 pilih kolom \u2014"),
2485
+ colNames.map((c) => /* @__PURE__ */ import_react19.default.createElement("option", { key: c, value: c }, c))
2486
+ ) : /* @__PURE__ */ import_react19.default.createElement(
2487
+ "input",
2488
+ {
2489
+ className: "rdb-input",
2490
+ value: colTotal,
2491
+ onChange: (e) => setColTotal(e.target.value),
2492
+ placeholder: "cth: total_amount"
2493
+ }
2494
+ ), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 2 } }, "Integer Rupiah")), /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, "Kolom Nama Pelanggan"), colNames.length > 0 ? /* @__PURE__ */ import_react19.default.createElement(
2495
+ "select",
2496
+ {
2497
+ className: "rdb-select",
2498
+ value: colCustomer,
2499
+ onChange: (e) => setColCustomer(e.target.value)
2500
+ },
2501
+ /* @__PURE__ */ import_react19.default.createElement("option", { value: "" }, "\u2014 opsional \u2014"),
2502
+ colNames.map((c) => /* @__PURE__ */ import_react19.default.createElement("option", { key: c, value: c }, c))
2503
+ ) : /* @__PURE__ */ import_react19.default.createElement(
2504
+ "input",
2505
+ {
2506
+ className: "rdb-input",
2507
+ value: colCustomer,
2508
+ onChange: (e) => setColCustomer(e.target.value),
2509
+ placeholder: "cth: customer_name (opsional)"
2510
+ }
2511
+ )), /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, "Kolom Nama Item/Layanan"), colNames.length > 0 ? /* @__PURE__ */ import_react19.default.createElement(
2512
+ "select",
2513
+ {
2514
+ className: "rdb-select",
2515
+ value: colItem,
2516
+ onChange: (e) => setColItem(e.target.value)
2517
+ },
2518
+ /* @__PURE__ */ import_react19.default.createElement("option", { value: "" }, "\u2014 opsional \u2014"),
2519
+ colNames.map((c) => /* @__PURE__ */ import_react19.default.createElement("option", { key: c, value: c }, c))
2520
+ ) : /* @__PURE__ */ import_react19.default.createElement(
2521
+ "input",
2522
+ {
2523
+ className: "rdb-input",
2524
+ value: colItem,
2525
+ onChange: (e) => setColItem(e.target.value),
2526
+ placeholder: "cth: service_type (opsional)"
2527
+ }
2528
+ ))), /* @__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: 8 } }, "Nilai status di tabel kamu:"), /* @__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(
2529
+ "input",
2530
+ {
2531
+ className: "rdb-input",
2532
+ value: confirmedVal,
2533
+ onChange: (e) => setConfirmedVal(e.target.value),
2534
+ placeholder: "cth: confirmed, selesai, lunas"
2535
+ }
2536
+ )), /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, 'Nilai "Pending/Proses"'), /* @__PURE__ */ import_react19.default.createElement(
2537
+ "input",
2538
+ {
2539
+ className: "rdb-input",
2540
+ value: pendingVal,
2541
+ onChange: (e) => setPendingVal(e.target.value),
2542
+ placeholder: "cth: pending, proses, menunggu"
2543
+ }
2544
+ )))), !mappingValid && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-error-banner", style: { marginBottom: 12 } }, "Pilih minimal kolom Tanggal, Status, dan Total untuk generate kode."), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" } }, /* @__PURE__ */ import_react19.default.createElement(
2545
+ "button",
2546
+ {
2547
+ type: "button",
2548
+ className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
2549
+ onClick: () => setStep(1)
2550
+ },
2551
+ "\u2190 Kembali"
2552
+ ), /* @__PURE__ */ import_react19.default.createElement(
2553
+ "button",
2554
+ {
2555
+ type: "button",
2556
+ className: "rdb-btn rdb-btn-primary rdb-btn-sm",
2557
+ disabled: !mappingValid,
2558
+ onClick: handleGenerate
2559
+ },
2560
+ "\u2728 Generate Kode \u2192"
2561
+ ))), 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 Siap Pakai")), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-tip", style: { marginBottom: 16 } }, /* @__PURE__ */ import_react19.default.createElement("strong", null, "4 file sudah digenerate."), " Salin masing-masing ke project kamu. Tidak perlu memahami logika di dalamnya \u2014 cukup save dan jalankan."), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-issues", style: { marginBottom: 16 } }, [
2562
+ ["myDashboardSource.js", "src/datasources/", "File koneksi ke Supabase"],
2563
+ ["myDashboardAdapter.js", "src/adapters/", "Transformasi data \u2192 format dashboard"],
2564
+ ["myDashboardConfig.js", "src/config/ (atau langsung di Dashboard.jsx)", "Konfigurasi widget"],
2565
+ ["Dashboard.jsx", "src/pages/admin/", "Ganti halaman dashboard lama"]
2566
+ ].map(([file, path, desc], i) => /* @__PURE__ */ import_react19.default.createElement("div", { key: i, className: "rdb-wizard-issue-item" }, /* @__PURE__ */ import_react19.default.createElement("span", { style: { color: "var(--rdb-blue-500)", fontWeight: 700 } }, i + 1, "."), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-body" }, "Simpan ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, file), " ke", " ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, path), " \u2014 ", desc))), /* @__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 } }, "5."), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-body" }, "Import CSS di ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "src/index.css"), ":", " ", /* @__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", { style: { display: "flex", gap: 4, marginBottom: 8, flexWrap: "wrap" } }, [
2567
+ ["dataSource", "myDashboardSource.js"],
2568
+ ["adapter", "myDashboardAdapter.js"],
2569
+ ["widgetConfig", "myDashboardConfig.js"],
2570
+ ["dashboard", "Dashboard.jsx"]
2571
+ ].map(([key, label]) => /* @__PURE__ */ import_react19.default.createElement(
2572
+ "button",
2573
+ {
2574
+ key,
2575
+ type: "button",
2576
+ onClick: () => setActiveCodeTab(key),
2577
+ className: `rdb-btn rdb-btn-sm ${activeCodeTab === key ? "rdb-btn-primary" : "rdb-btn-secondary"}`,
2578
+ style: { fontFamily: "monospace", fontSize: "0.75rem" }
2579
+ },
2580
+ label
2581
+ ))), /* @__PURE__ */ import_react19.default.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ import_react19.default.createElement(
2582
+ "button",
2583
+ {
2584
+ type: "button",
2585
+ onClick: () => copyCode(generatedCode[activeCodeTab], activeCodeTab),
2586
+ className: "rdb-btn rdb-btn-sm rdb-btn-secondary",
2587
+ style: { position: "absolute", top: 8, right: 8, zIndex: 1 }
2588
+ },
2589
+ copied === activeCodeTab ? "\u2705 Disalin!" : "\u{1F4CB} Salin"
2590
+ ), /* @__PURE__ */ import_react19.default.createElement("pre", { className: "rdb-wizard-code", style: { maxHeight: 320, overflow: "auto" } }, generatedCode[activeCodeTab])), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-tip", style: { marginTop: 12 } }, "Setelah semua file tersimpan dan dev server direstart, wizard tidak akan muncul lagi."), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 12 } }, /* @__PURE__ */ import_react19.default.createElement(
2591
+ "button",
2592
+ {
2593
+ type: "button",
2594
+ className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
2595
+ onClick: () => setStep(2)
2596
+ },
2597
+ "\u2190 Edit mapping"
2598
+ ), /* @__PURE__ */ import_react19.default.createElement(
2599
+ "button",
2600
+ {
2601
+ type: "button",
2602
+ className: "rdb-btn rdb-btn-primary rdb-btn-sm",
2603
+ onClick: handleDismiss
2604
+ },
2605
+ "Tutup wizard \u2713"
2606
+ ))))))
2607
+ );
2176
2608
  }
2177
2609
  SetupWizard.propTypes = {
2178
2610
  issues: import_prop_types17.default.arrayOf(import_prop_types17.default.string),