@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.cjs +452 -493
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +452 -493
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
1878
|
-
const
|
|
1879
|
-
if (PRESET_SIGNATURES.cidika.tables.every((t) =>
|
|
1880
|
-
if (PRESET_SIGNATURES.tokoSepatu.tables.every((t) =>
|
|
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
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
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
|
|
1899
|
-
const
|
|
1900
|
-
const
|
|
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
|
|
1924
|
+
const allQ = supabase
|
|
1909
1925
|
.from("${tableName}")
|
|
1910
|
-
.select("
|
|
1926
|
+
.select("${cols}")
|
|
1911
1927
|
.gte("${colDate}", fromISO)
|
|
1912
1928
|
.lte("${colDate}", toISO)
|
|
1913
1929
|
.order("${colDate}", { ascending: true });
|
|
1914
1930
|
|
|
1915
|
-
const
|
|
1931
|
+
const recentQ = supabase
|
|
1916
1932
|
.from("${tableName}")
|
|
1917
|
-
.select("
|
|
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
|
-
|
|
1940
|
+
recentQ.eq("${colStatus}", statusScope);
|
|
1925
1941
|
}
|
|
1926
1942
|
|
|
1927
|
-
const [
|
|
1928
|
-
|
|
1943
|
+
const [all, recent] = await Promise.all([allQ, recentQ]);
|
|
1929
1944
|
return {
|
|
1930
|
-
bookings:
|
|
1931
|
-
recent:
|
|
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
|
-
.
|
|
1940
|
-
|
|
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
|
|
1971
|
-
const
|
|
1972
|
-
const
|
|
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(
|
|
1975
|
-
const
|
|
1976
|
-
if (
|
|
1977
|
-
if (
|
|
1978
|
-
if (
|
|
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 (
|
|
1993
|
+
if (st === "${confirmed}") { confirmed++; revenue += amt; }
|
|
1981
1994
|
});
|
|
1982
1995
|
|
|
1983
|
-
const
|
|
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(([
|
|
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:
|
|
2008
|
-
createdAt:
|
|
2009
|
-
customerName:
|
|
2010
|
-
packageName:
|
|
2014
|
+
id: row.id,
|
|
2015
|
+
createdAt: row["${colDate}"],
|
|
2016
|
+
customerName: row["${custCol}"] || "-",
|
|
2017
|
+
packageName: ${itemCol !== "-" ? `row["${itemCol}"] || "-"` : '"-"'},
|
|
2011
2018
|
audienceLabel: "-",
|
|
2012
|
-
totalIDR:
|
|
2013
|
-
status:
|
|
2014
|
-
statusLabel:
|
|
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
|
-
|
|
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:
|
|
2033
|
-
valueKey:
|
|
2034
|
-
{ id:
|
|
2035
|
-
valueKey:
|
|
2036
|
-
{ id:
|
|
2037
|
-
valueKey:
|
|
2038
|
-
{ id:
|
|
2039
|
-
valueKey:
|
|
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:
|
|
2043
|
-
{ id:
|
|
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:
|
|
2047
|
-
emptyLabel: "noRecentBookings",
|
|
2049
|
+
id:"recentTx", label:"recentBookings", icon:"Calendar", emptyLabel:"noRecentBookings",
|
|
2048
2050
|
columns: [
|
|
2049
|
-
{ id:
|
|
2050
|
-
{ id:
|
|
2051
|
-
{ id:
|
|
2052
|
-
{ id:
|
|
2053
|
-
{ id:
|
|
2054
|
-
type:
|
|
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
|
-
//
|
|
2072
|
-
import { myDashboardConfig }
|
|
2073
|
-
import { createMyDashboardSource }
|
|
2074
|
-
import { adaptMyDashboardData, createEmptyMyDashboardData } from "
|
|
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: "${
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
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
|
-
|
|
2118
|
-
|
|
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
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
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 [
|
|
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("
|
|
2163
|
-
const [pendingVal, setPendingVal] = useState6("
|
|
2136
|
+
const [confirmedVal, setConfirmedVal] = useState6("");
|
|
2137
|
+
const [pendingVal, setPendingVal] = useState6("");
|
|
2164
2138
|
const [dashTitle, setDashTitle] = useState6("Dashboard");
|
|
2165
|
-
const [
|
|
2139
|
+
const [loadingCols, setLoadingCols] = useState6(false);
|
|
2140
|
+
const [colsBlocked, setColsBlocked] = useState6(false);
|
|
2166
2141
|
const [generatedCode, setGeneratedCode] = useState6(null);
|
|
2167
|
-
const [
|
|
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:
|
|
2178
|
-
if (!
|
|
2179
|
-
setTables(
|
|
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
|
-
|
|
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
|
|
2202
|
-
|
|
2203
|
-
|
|
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
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
);
|
|
2210
|
-
|
|
2211
|
-
|
|
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
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2195
|
+
if (cols) {
|
|
2196
|
+
setColumns((p) => ({ ...p, [tbl]: cols }));
|
|
2197
|
+
autoDetect(tbl, cols);
|
|
2198
|
+
} else {
|
|
2199
|
+
setColsBlocked(true);
|
|
2219
2200
|
}
|
|
2220
|
-
const { data:
|
|
2221
|
-
if (
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
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
|
-
|
|
2218
|
+
setColsBlocked(true);
|
|
2233
2219
|
} finally {
|
|
2234
|
-
|
|
2220
|
+
setLoadingCols(false);
|
|
2235
2221
|
}
|
|
2236
2222
|
}, [supabase, columns]);
|
|
2237
|
-
function
|
|
2238
|
-
const
|
|
2239
|
-
var
|
|
2240
|
-
return ((
|
|
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(
|
|
2243
|
-
setColStatus(
|
|
2244
|
-
setColTotal(
|
|
2245
|
-
setColCustomer(
|
|
2246
|
-
setColItem(
|
|
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,
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
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
|
-
|
|
2243
|
+
onDismiss == null ? void 0 : onDismiss();
|
|
2257
2244
|
}
|
|
2258
2245
|
function handleGenerate() {
|
|
2259
2246
|
if (!selectedTable || !colDate || !colStatus || !colTotal) return;
|
|
2260
|
-
|
|
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
|
|
2276
|
-
const
|
|
2277
|
-
const colNames =
|
|
2278
|
-
const
|
|
2279
|
-
const
|
|
2280
|
-
|
|
2281
|
-
|
|
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
|
-
|
|
2284
|
-
|
|
2285
|
-
"
|
|
2286
|
-
|
|
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
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
},
|
|
2296
|
-
"
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
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
|
-
|
|
2349
|
+
analyzeTable(tbl);
|
|
2333
2350
|
},
|
|
2334
|
-
className: `rdb-btn rdb-btn-sm ${
|
|
2335
|
-
style: {
|
|
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
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
},
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
"button",
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
"button",
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
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),
|