@rozaqi02/reusable-dashboard 1.1.3 → 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/README.md +215 -2
- package/dist/index.cjs +585 -194
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +586 -195
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1861,269 +1861,660 @@ import React18 from "react";
|
|
|
1861
1861
|
import PropTypes18 from "prop-types";
|
|
1862
1862
|
|
|
1863
1863
|
// src/presentation/SetupWizard.jsx
|
|
1864
|
-
import React17, { useState as useState6 } from "react";
|
|
1864
|
+
import React17, { useState as useState6, useEffect as useEffect3, useCallback as useCallback5 } from "react";
|
|
1865
1865
|
import PropTypes17 from "prop-types";
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
key: s.id,
|
|
1923
|
-
type: "button",
|
|
1924
|
-
className: `rdb-wizard-step-btn ${step === s.id ? "rdb-wizard-step-active" : ""}`,
|
|
1925
|
-
onClick: () => setStep(s.id)
|
|
1926
|
-
},
|
|
1927
|
-
/* @__PURE__ */ React17.createElement("span", null, s.icon),
|
|
1928
|
-
/* @__PURE__ */ React17.createElement("span", null, s.label)
|
|
1929
|
-
))), /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-body" }, step === 0 && /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-section" }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-alert" }, /* @__PURE__ */ React17.createElement("span", { className: "rdb-wizard-alert-icon" }, "\u26A0\uFE0F"), /* @__PURE__ */ React17.createElement("div", null, /* @__PURE__ */ React17.createElement("div", { className: "rdb-body", style: { fontWeight: 600 } }, "Dashboard belum terkonfigurasi"), /* @__PURE__ */ React17.createElement("div", { className: "rdb-caption", style: { marginTop: 4 } }, "Ditemukan ", issues.length, " masalah yang perlu diselesaikan sebelum dashboard dapat berjalan."))), issues.length > 0 && /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-issues" }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 8 } }, "Masalah yang ditemukan:"), issues.map((issue, i) => /* @__PURE__ */ React17.createElement("div", { key: i, className: "rdb-wizard-issue-item" }, /* @__PURE__ */ React17.createElement("span", { className: "rdb-wizard-issue-icon" }, "\u274C"), /* @__PURE__ */ React17.createElement("span", { className: "rdb-body" }, issue)))), /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-flow" }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 12 } }, "Alur konfigurasi yang perlu dilakukan:"), /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-flow-steps" }, [
|
|
1930
|
-
{ num: "1", label: "Data Source", desc: "Tulis fungsi query ke Supabase" },
|
|
1931
|
-
{ num: "\u2192", label: "", desc: "" },
|
|
1932
|
-
{ num: "2", label: "Adapter", desc: "Petakan data mentah ke format standar" },
|
|
1933
|
-
{ num: "\u2192", label: "", desc: "" },
|
|
1934
|
-
{ num: "3", label: "Widget Config", desc: "Definisikan widget yang ditampilkan" }
|
|
1935
|
-
].map((item, i) => item.num === "\u2192" ? /* @__PURE__ */ React17.createElement("div", { key: i, className: "rdb-wizard-flow-arrow" }, "\u2192") : /* @__PURE__ */ React17.createElement("div", { key: i, className: "rdb-wizard-flow-box" }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-flow-num" }, item.num), /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-flow-label" }, item.label), /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-flow-desc" }, item.desc))))), /* @__PURE__ */ React17.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 16 } }, /* @__PURE__ */ React17.createElement("button", { type: "button", className: "rdb-btn rdb-btn-secondary rdb-btn-sm", onClick: handleDismiss }, "Lanjutkan tanpa wizard"), /* @__PURE__ */ React17.createElement("button", { type: "button", className: "rdb-btn rdb-btn-primary rdb-btn-sm", onClick: () => setStep(1) }, "Mulai panduan \u2192"))), step === 1 && /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-section" }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-step-title" }, /* @__PURE__ */ React17.createElement("span", { className: "rdb-wizard-step-num" }, "1"), /* @__PURE__ */ React17.createElement("span", { className: "rdb-h3", style: { margin: 0 } }, "Data Source")), /* @__PURE__ */ React17.createElement("div", { className: "rdb-body", style: { color: "var(--rdb-text-muted)", marginBottom: 12 } }, "Data Source adalah fungsi yang membaca data dari Supabase. Buat file", /* @__PURE__ */ React17.createElement("code", { className: "rdb-wizard-code-inline" }, " src/datasources/myDataSource.js"), " dengan isi berikut:"), /* @__PURE__ */ React17.createElement("pre", { className: "rdb-wizard-code" }, `// src/datasources/myDataSource.js
|
|
1936
|
-
export function createMySupabaseSource(supabase) {
|
|
1866
|
+
var PRESET_SIGNATURES = {
|
|
1867
|
+
cidika: { name: "Cidika Travel", tables: ["bookings", "packages", "package_locales"] },
|
|
1868
|
+
tokoSepatu: { name: "Toko Sepatu / E-Commerce", tables: ["orders", "products", "customers"] }
|
|
1869
|
+
};
|
|
1870
|
+
function detectPreset(tables) {
|
|
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";
|
|
1875
|
+
return null;
|
|
1876
|
+
}
|
|
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";
|
|
1905
|
+
const confirmed = confirmedValue || "confirmed";
|
|
1906
|
+
const pending = pendingValue || "pending";
|
|
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(", ");
|
|
1918
|
+
const dataSource = `// src/datasources/myDashboardSource.js
|
|
1919
|
+
// AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
|
|
1920
|
+
|
|
1921
|
+
export function createMyDashboardSource(supabase) {
|
|
1937
1922
|
return {
|
|
1938
1923
|
async fetchDashboardSnapshot({ fromISO, toISO, statusScope }) {
|
|
1939
|
-
const
|
|
1940
|
-
.from("
|
|
1941
|
-
.select("
|
|
1942
|
-
.gte("
|
|
1943
|
-
.lte("
|
|
1944
|
-
.order("
|
|
1924
|
+
const allQ = supabase
|
|
1925
|
+
.from("${tableName}")
|
|
1926
|
+
.select("${cols}")
|
|
1927
|
+
.gte("${colDate}", fromISO)
|
|
1928
|
+
.lte("${colDate}", toISO)
|
|
1929
|
+
.order("${colDate}", { ascending: true });
|
|
1945
1930
|
|
|
1946
|
-
const
|
|
1947
|
-
.from("
|
|
1948
|
-
.select("
|
|
1949
|
-
.
|
|
1931
|
+
const recentQ = supabase
|
|
1932
|
+
.from("${tableName}")
|
|
1933
|
+
.select("${cols}")
|
|
1934
|
+
.gte("${colDate}", fromISO)
|
|
1935
|
+
.lte("${colDate}", toISO)
|
|
1936
|
+
.order("${colDate}", { ascending: false })
|
|
1950
1937
|
.limit(10);
|
|
1951
1938
|
|
|
1939
|
+
if (statusScope && statusScope !== "all") {
|
|
1940
|
+
recentQ.eq("${colStatus}", statusScope);
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
const [all, recent] = await Promise.all([allQ, recentQ]);
|
|
1952
1944
|
return {
|
|
1953
|
-
bookings:
|
|
1954
|
-
recent
|
|
1945
|
+
bookings: all.data || [],
|
|
1946
|
+
recent: recent.data || [],
|
|
1955
1947
|
packageLocales: [],
|
|
1956
1948
|
staticCounts: { packages: 0, sections: 0 },
|
|
1957
1949
|
};
|
|
1958
1950
|
},
|
|
1959
1951
|
|
|
1960
|
-
// Opsional: live update
|
|
1961
1952
|
subscribeLiveUpdate(onEvent) {
|
|
1962
|
-
const ch = supabase.channel("
|
|
1953
|
+
const ch = supabase.channel("rdb-${tableName}-live")
|
|
1963
1954
|
.on("postgres_changes",
|
|
1964
|
-
|
|
1955
|
+
{ event: "*", schema: "public", table: "${tableName}" }, onEvent)
|
|
1965
1956
|
.subscribe();
|
|
1966
1957
|
return () => supabase.removeChannel(ch);
|
|
1967
1958
|
},
|
|
1968
1959
|
};
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
1974
|
-
onClick: handleReadTables,
|
|
1975
|
-
disabled: loadingTables
|
|
1976
|
-
},
|
|
1977
|
-
loadingTables ? "Membaca..." : "Tampilkan daftar tabel Supabase"
|
|
1978
|
-
), tableError && /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-table-error" }, tableError), tables && tables.length > 0 && /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-table-list" }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-caption", style: { marginBottom: 6 } }, "Tabel ditemukan (", tables.length, "):"), /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-table-chips" }, tables.map((t) => /* @__PURE__ */ React17.createElement("span", { key: t, className: "rdb-wizard-chip" }, t))), /* @__PURE__ */ React17.createElement("div", { className: "rdb-caption", style: { marginTop: 8, color: "var(--rdb-text-muted)" } }, "Ganti ", /* @__PURE__ */ React17.createElement("code", { className: "rdb-wizard-code-inline" }, "MY_TABLE"), " di contoh kode di atas dengan nama tabel yang sesuai.")), !supabase && /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-table-error" }, "Tambahkan prop ", /* @__PURE__ */ React17.createElement("code", { className: "rdb-wizard-code-inline" }, "supabase"), " ke", /* @__PURE__ */ React17.createElement("code", { className: "rdb-wizard-code-inline" }, " <ReusableDashboardView supabase=", "{supabase}", " />"), " untuk mengaktifkan fitur ini.")), /* @__PURE__ */ React17.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 16 } }, /* @__PURE__ */ React17.createElement("button", { type: "button", className: "rdb-btn rdb-btn-secondary rdb-btn-sm", onClick: () => setStep(0) }, "\u2190 Kembali"), /* @__PURE__ */ React17.createElement("button", { type: "button", className: "rdb-btn rdb-btn-primary rdb-btn-sm", onClick: () => setStep(2) }, "Lanjut \u2192"))), step === 2 && /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-section" }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-step-title" }, /* @__PURE__ */ React17.createElement("span", { className: "rdb-wizard-step-num" }, "2"), /* @__PURE__ */ React17.createElement("span", { className: "rdb-h3", style: { margin: 0 } }, "Data Adapter")), /* @__PURE__ */ React17.createElement("div", { className: "rdb-body", style: { color: "var(--rdb-text-muted)", marginBottom: 12 } }, "Adapter mengubah data mentah dari Data Source ke format standar yang dimengerti komponen dashboard. Buat file ", /* @__PURE__ */ React17.createElement("code", { className: "rdb-wizard-code-inline" }, "src/adapters/myAdapter.js"), ":"), /* @__PURE__ */ React17.createElement("pre", { className: "rdb-wizard-code" }, `// src/adapters/myAdapter.js
|
|
1960
|
+
}`;
|
|
1961
|
+
const adapter = `// src/adapters/myDashboardAdapter.js
|
|
1962
|
+
// AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
|
|
1963
|
+
|
|
1979
1964
|
import { toNumber, buildDayBuckets } from "@rozaqi02/reusable-dashboard";
|
|
1980
1965
|
|
|
1981
|
-
export function
|
|
1966
|
+
export function createEmptyMyDashboardData() {
|
|
1982
1967
|
return {
|
|
1983
|
-
stats:
|
|
1984
|
-
charts: {
|
|
1985
|
-
|
|
1986
|
-
statusDistribution: [],
|
|
1987
|
-
audienceDistribution: [],
|
|
1988
|
-
topPackages: [],
|
|
1989
|
-
},
|
|
1990
|
-
table: { recentBookings: [] },
|
|
1968
|
+
stats: { bookingsConfirm: 0, revenueConfirm: 0, avgRevenue: 0, conversionRate: 0 },
|
|
1969
|
+
charts: { dailyTrends: [], statusDistribution: [], audienceDistribution: [], topPackages: [] },
|
|
1970
|
+
table: { recentBookings: [] },
|
|
1991
1971
|
};
|
|
1992
1972
|
}
|
|
1993
1973
|
|
|
1994
|
-
export function
|
|
1995
|
-
if (!raw) return
|
|
1974
|
+
export function adaptMyDashboardData({ raw, range, dateLocale, labels }) {
|
|
1975
|
+
if (!raw) return createEmptyMyDashboardData();
|
|
1996
1976
|
|
|
1997
|
-
const buckets
|
|
1998
|
-
const dayMap
|
|
1977
|
+
const buckets = buildDayBuckets(range.daysWindow, range.fromISO, dateLocale);
|
|
1978
|
+
const dayMap = new Map(buckets.map(b => [b.dateKey, b]));
|
|
1999
1979
|
const statusMap = new Map();
|
|
2000
1980
|
let confirmed = 0, revenue = 0;
|
|
2001
1981
|
|
|
2002
1982
|
(raw.bookings || []).forEach(row => {
|
|
2003
|
-
const
|
|
2004
|
-
const
|
|
2005
|
-
const
|
|
1983
|
+
const st = String(row["${colStatus}"] || "").toLowerCase();
|
|
1984
|
+
const amt = toNumber(row["${totalCol}"]);
|
|
1985
|
+
const dk = String(row["${colDate}"] || "").slice(0, 10);
|
|
2006
1986
|
|
|
2007
|
-
statusMap.set(
|
|
2008
|
-
const
|
|
2009
|
-
if (
|
|
2010
|
-
|
|
2011
|
-
|
|
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++; }
|
|
2012
1992
|
}
|
|
2013
|
-
if (
|
|
1993
|
+
if (st === "${confirmed}") { confirmed++; revenue += amt; }
|
|
2014
1994
|
});
|
|
2015
1995
|
|
|
1996
|
+
const total = (raw.bookings || []).length;
|
|
2016
1997
|
return {
|
|
2017
|
-
stats: {
|
|
1998
|
+
stats: {
|
|
1999
|
+
bookingsConfirm: confirmed,
|
|
2000
|
+
revenueConfirm: revenue,
|
|
2001
|
+
avgRevenue: confirmed > 0 ? Math.round(revenue / confirmed) : 0,
|
|
2002
|
+
conversionRate: total > 0 ? Math.round((confirmed / total) * 100) : 0,
|
|
2003
|
+
},
|
|
2018
2004
|
charts: {
|
|
2019
2005
|
dailyTrends: buckets,
|
|
2020
2006
|
statusDistribution: Array.from(statusMap.entries())
|
|
2021
|
-
.
|
|
2022
|
-
|
|
2023
|
-
label: labels?.formatStatusLabel?.(status) || status,
|
|
2024
|
-
count,
|
|
2025
|
-
})),
|
|
2007
|
+
.sort((a, b) => b[1] - a[1])
|
|
2008
|
+
.map(([s, c]) => ({ status: s, count: c, label: labels?.formatStatusLabel?.(s) || s })),
|
|
2026
2009
|
audienceDistribution: [],
|
|
2027
2010
|
topPackages: [],
|
|
2028
2011
|
},
|
|
2029
2012
|
table: {
|
|
2030
2013
|
recentBookings: (raw.recent || []).map(row => ({
|
|
2031
|
-
id:
|
|
2032
|
-
createdAt:
|
|
2033
|
-
customerName:
|
|
2034
|
-
packageName: row
|
|
2014
|
+
id: row.id,
|
|
2015
|
+
createdAt: row["${colDate}"],
|
|
2016
|
+
customerName: row["${custCol}"] || "-",
|
|
2017
|
+
packageName: ${itemCol !== "-" ? `row["${itemCol}"] || "-"` : '"-"'},
|
|
2035
2018
|
audienceLabel: "-",
|
|
2036
|
-
totalIDR:
|
|
2037
|
-
status:
|
|
2038
|
-
statusLabel:
|
|
2019
|
+
totalIDR: toNumber(row["${totalCol}"]),
|
|
2020
|
+
status: String(row["${colStatus}"] || "").toLowerCase(),
|
|
2021
|
+
statusLabel: labels?.formatStatusLabel?.(row["${colStatus}"]) || row["${colStatus}"] || "-",
|
|
2039
2022
|
})),
|
|
2040
2023
|
},
|
|
2041
2024
|
};
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
ReusableDashboardView,
|
|
2046
|
-
useReusableDashboard,
|
|
2047
|
-
createDashboardConfig,
|
|
2048
|
-
} from "@rozaqi02/reusable-dashboard";
|
|
2025
|
+
}`;
|
|
2026
|
+
const widgetConfig = `// src/config/myDashboardConfig.js
|
|
2027
|
+
// AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
|
|
2049
2028
|
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
const myWidgetConfig = {
|
|
2055
|
-
id: "my.dashboard",
|
|
2056
|
-
defaultFilters: { statusScope: "confirmed", daysPreset: 30 },
|
|
2029
|
+
export const myDashboardConfig = {
|
|
2030
|
+
id: "my.custom.dashboard",
|
|
2031
|
+
defaultFilters: { statusScope: "${confirmed}", daysPreset: 30,
|
|
2032
|
+
sortPkgBy: "bookings", sortPkgDir: "desc" },
|
|
2057
2033
|
widgets: {
|
|
2058
2034
|
stats: [
|
|
2059
|
-
{ id:
|
|
2060
|
-
valueKey:
|
|
2061
|
-
{ id:
|
|
2062
|
-
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" },
|
|
2063
2043
|
],
|
|
2064
2044
|
charts: [
|
|
2065
|
-
{ id:
|
|
2066
|
-
{ id:
|
|
2045
|
+
{ id:"trend", type:"dailyArea", label:"dailyTrends", icon:"BarChart3" },
|
|
2046
|
+
{ id:"status", type:"statusPie", label:"statusDistribution", icon:"PieChart" },
|
|
2067
2047
|
],
|
|
2068
2048
|
table: {
|
|
2069
|
-
id:
|
|
2070
|
-
emptyLabel: "noRecentBookings",
|
|
2049
|
+
id:"recentTx", label:"recentBookings", icon:"Calendar", emptyLabel:"noRecentBookings",
|
|
2071
2050
|
columns: [
|
|
2072
|
-
{ id:
|
|
2073
|
-
{ id:
|
|
2074
|
-
{ id:
|
|
2075
|
-
{ id:
|
|
2076
|
-
|
|
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" },
|
|
2077
2057
|
],
|
|
2078
2058
|
},
|
|
2079
2059
|
},
|
|
2080
|
-
}
|
|
2060
|
+
};`;
|
|
2061
|
+
const dashboard = `// src/pages/admin/Dashboard.jsx
|
|
2062
|
+
// AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
|
|
2081
2063
|
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
createEmptyState: createEmptyMyData,
|
|
2088
|
-
languageCode: "id",
|
|
2089
|
-
dateLocale: "id-ID",
|
|
2090
|
-
});
|
|
2064
|
+
import React from "react";
|
|
2065
|
+
import { supabase } from "../../lib/supabaseClient.js";
|
|
2066
|
+
import {
|
|
2067
|
+
ReusableDashboardView, useReusableDashboard, createDashboardConfig,
|
|
2068
|
+
} from "@rozaqi02/reusable-dashboard";
|
|
2091
2069
|
|
|
2092
|
-
//
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
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";
|
|
2074
|
+
|
|
2075
|
+
const source = createMyDashboardSource(supabase);
|
|
2076
|
+
|
|
2077
|
+
const labels = {
|
|
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",
|
|
2089
|
+
confirmedBookingMetric: "Transaksi (Selesai)",
|
|
2090
|
+
confirmedRevenueMetric: "Pendapatan (Selesai)",
|
|
2102
2091
|
dayLabel: n => n + " hari",
|
|
2103
|
-
formatStatusLabel: s
|
|
2104
|
-
({ confirmed: "Selesai", pending: "Proses", cancelled: "Batal" })[s] || s,
|
|
2092
|
+
formatStatusLabel: s => ({ "${confirmed}":"Selesai", "${pending}":"Proses" })[s] || s || "-",
|
|
2105
2093
|
formatAudienceLabel: v => v || "-",
|
|
2106
2094
|
};
|
|
2107
2095
|
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2096
|
+
const dashboardConfig = createDashboardConfig({
|
|
2097
|
+
widgetConfig: myDashboardConfig, dataSource: source,
|
|
2098
|
+
adapter: adaptMyDashboardData, createEmptyState: createEmptyMyDashboardData,
|
|
2099
|
+
languageCode: "id", dateLocale: "id-ID", labels,
|
|
2100
|
+
});
|
|
2101
|
+
|
|
2102
|
+
export default function Dashboard() {
|
|
2103
|
+
const state = useReusableDashboard({ ...dashboardConfig, labels });
|
|
2111
2104
|
return (
|
|
2112
2105
|
<ReusableDashboardView
|
|
2113
|
-
config={
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
onFilterChange={state.updateFilter}
|
|
2119
|
-
onResetFilters={state.resetFilters}
|
|
2120
|
-
onRefresh={state.refresh}
|
|
2121
|
-
data={state.data}
|
|
2122
|
-
dateLocale={dashConfig.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}
|
|
2123
2111
|
liveUpdateEnabled={state.liveUpdateEnabled}
|
|
2112
|
+
supabase={supabase} dashboardConfig={dashboardConfig}
|
|
2124
2113
|
/>
|
|
2125
2114
|
);
|
|
2126
|
-
}
|
|
2115
|
+
}`;
|
|
2116
|
+
return { dataSource, adapter, widgetConfig, dashboard };
|
|
2117
|
+
}
|
|
2118
|
+
function SetupWizard({ issues = [], onDismiss, supabase }) {
|
|
2119
|
+
var _a, _b;
|
|
2120
|
+
const [dismissed, setDismissed] = useState6(false);
|
|
2121
|
+
const [step, setStep] = useState6(0);
|
|
2122
|
+
const [detecting, setDetecting] = useState6(true);
|
|
2123
|
+
const [tables, setTables] = useState6([]);
|
|
2124
|
+
const [columns, setColumns] = useState6({});
|
|
2125
|
+
const [sampleData, setSampleData] = useState6({});
|
|
2126
|
+
const [statusValues, setStatusValues] = useState6({});
|
|
2127
|
+
const [supabaseOk, setSupabaseOk] = useState6(false);
|
|
2128
|
+
const [detectionDone, setDetectionDone] = useState6(false);
|
|
2129
|
+
const [tableBlocked, setTableBlocked] = useState6(false);
|
|
2130
|
+
const [selectedTable, setSelectedTable] = useState6("");
|
|
2131
|
+
const [colDate, setColDate] = useState6("");
|
|
2132
|
+
const [colStatus, setColStatus] = useState6("");
|
|
2133
|
+
const [colTotal, setColTotal] = useState6("");
|
|
2134
|
+
const [colCustomer, setColCustomer] = useState6("");
|
|
2135
|
+
const [colItem, setColItem] = useState6("");
|
|
2136
|
+
const [confirmedVal, setConfirmedVal] = useState6("");
|
|
2137
|
+
const [pendingVal, setPendingVal] = useState6("");
|
|
2138
|
+
const [dashTitle, setDashTitle] = useState6("Dashboard");
|
|
2139
|
+
const [loadingCols, setLoadingCols] = useState6(false);
|
|
2140
|
+
const [colsBlocked, setColsBlocked] = useState6(false);
|
|
2141
|
+
const [generatedCode, setGeneratedCode] = useState6(null);
|
|
2142
|
+
const [activeTab, setActiveTab] = useState6("dashboard");
|
|
2143
|
+
const [copied, setCopied] = useState6("");
|
|
2144
|
+
const [showRls, setShowRls] = useState6(false);
|
|
2145
|
+
useEffect3(() => {
|
|
2146
|
+
if (!supabase) {
|
|
2147
|
+
setDetecting(false);
|
|
2148
|
+
setDetectionDone(true);
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
(async () => {
|
|
2152
|
+
try {
|
|
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));
|
|
2162
|
+
setSupabaseOk(true);
|
|
2163
|
+
return;
|
|
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);
|
|
2169
|
+
} catch {
|
|
2170
|
+
setSupabaseOk(false);
|
|
2171
|
+
} finally {
|
|
2172
|
+
setDetecting(false);
|
|
2173
|
+
setDetectionDone(true);
|
|
2174
|
+
}
|
|
2175
|
+
})();
|
|
2176
|
+
}, [supabase]);
|
|
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);
|
|
2186
|
+
try {
|
|
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] }));
|
|
2194
|
+
}
|
|
2195
|
+
if (cols) {
|
|
2196
|
+
setColumns((p) => ({ ...p, [tbl]: cols }));
|
|
2197
|
+
autoDetect(tbl, cols);
|
|
2198
|
+
} else {
|
|
2199
|
+
setColsBlocked(true);
|
|
2200
|
+
}
|
|
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
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
} catch {
|
|
2218
|
+
setColsBlocked(true);
|
|
2219
|
+
} finally {
|
|
2220
|
+
setLoadingCols(false);
|
|
2221
|
+
}
|
|
2222
|
+
}, [supabase, columns]);
|
|
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) || "";
|
|
2227
|
+
};
|
|
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"]));
|
|
2233
|
+
}
|
|
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);
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
2241
|
+
function handleDismiss() {
|
|
2242
|
+
setDismissed(true);
|
|
2243
|
+
onDismiss == null ? void 0 : onDismiss();
|
|
2244
|
+
}
|
|
2245
|
+
function handleGenerate() {
|
|
2246
|
+
if (!selectedTable || !colDate || !colStatus || !colTotal) return;
|
|
2247
|
+
setGeneratedCode(generateCode({
|
|
2248
|
+
tableName: selectedTable.trim(),
|
|
2249
|
+
colDate,
|
|
2250
|
+
colStatus,
|
|
2251
|
+
colTotal,
|
|
2252
|
+
colCustomer: colCustomer || "customer_name",
|
|
2253
|
+
colItem: colItem || "-",
|
|
2254
|
+
dashTitle,
|
|
2255
|
+
confirmedValue: confirmedVal || "confirmed",
|
|
2256
|
+
pendingValue: pendingVal || "pending"
|
|
2257
|
+
}));
|
|
2258
|
+
setStep(3);
|
|
2259
|
+
}
|
|
2260
|
+
if (dismissed) return null;
|
|
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",
|
|
2284
|
+
{
|
|
2285
|
+
key: i,
|
|
2286
|
+
type: "button",
|
|
2287
|
+
className: `rdb-wizard-step-btn ${step === i ? "rdb-wizard-step-active" : ""}`,
|
|
2288
|
+
onClick: () => setStep(i)
|
|
2289
|
+
},
|
|
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(
|
|
2343
|
+
"button",
|
|
2344
|
+
{
|
|
2345
|
+
key: tbl,
|
|
2346
|
+
type: "button",
|
|
2347
|
+
onClick: () => {
|
|
2348
|
+
setSelectedTable(tbl);
|
|
2349
|
+
analyzeTable(tbl);
|
|
2350
|
+
},
|
|
2351
|
+
className: `rdb-btn rdb-btn-sm ${isSelected ? "rdb-btn-primary" : "rdb-btn-secondary"}`,
|
|
2352
|
+
style: { fontFamily: "monospace", fontSize: "0.82rem" }
|
|
2353
|
+
},
|
|
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
|
+
)))))));
|
|
2127
2518
|
}
|
|
2128
2519
|
SetupWizard.propTypes = {
|
|
2129
2520
|
issues: PropTypes17.arrayOf(PropTypes17.string),
|