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