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