@rozaqi02/reusable-dashboard 1.1.4 → 1.1.5

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