@rozaqi02/reusable-dashboard 1.1.3 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -2
- package/dist/index.cjs +609 -177
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +610 -178
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1912,254 +1912,270 @@ var import_prop_types18 = __toESM(require("prop-types"), 1);
|
|
|
1912
1912
|
// src/presentation/SetupWizard.jsx
|
|
1913
1913
|
var import_react19 = __toESM(require("react"), 1);
|
|
1914
1914
|
var import_prop_types17 = __toESM(require("prop-types"), 1);
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
{ id: 0, label: "Overview", icon: "\u{1F3E0}" },
|
|
1924
|
-
{ id: 1, label: "1. Data Source", icon: "\u{1F5C4}\uFE0F" },
|
|
1925
|
-
{ id: 2, label: "2. Adapter", icon: "\u{1F504}" },
|
|
1926
|
-
{ id: 3, label: "3. Widget Config", icon: "\u2699\uFE0F" }
|
|
1927
|
-
];
|
|
1928
|
-
async function handleReadTables() {
|
|
1929
|
-
if (!supabase) {
|
|
1930
|
-
setTableError("Supabase client belum tersambung. Pastikan prop supabase sudah diisi.");
|
|
1931
|
-
return;
|
|
1932
|
-
}
|
|
1933
|
-
setLoadingTables(true);
|
|
1934
|
-
setTableError("");
|
|
1935
|
-
try {
|
|
1936
|
-
const { data, error } = await supabase.from("information_schema.tables").select("table_name").eq("table_schema", "public").eq("table_type", "BASE TABLE").order("table_name");
|
|
1937
|
-
if (error) throw error;
|
|
1938
|
-
setTables((data || []).map((r) => r.table_name));
|
|
1939
|
-
} catch (err) {
|
|
1940
|
-
try {
|
|
1941
|
-
const { data: rpcData } = await supabase.rpc("get_tables");
|
|
1942
|
-
if (rpcData) {
|
|
1943
|
-
setTables(rpcData.map((r) => r.table_name || r));
|
|
1944
|
-
} else {
|
|
1945
|
-
setTableError("Tidak dapat membaca tabel. Pastikan RLS mengizinkan akses atau tambahkan policy SELECT untuk anon.");
|
|
1946
|
-
}
|
|
1947
|
-
} catch {
|
|
1948
|
-
setTableError("Gagal membaca tabel dari Supabase: " + ((err == null ? void 0 : err.message) || "Unknown error"));
|
|
1949
|
-
}
|
|
1950
|
-
} finally {
|
|
1951
|
-
setLoadingTables(false);
|
|
1952
|
-
}
|
|
1953
|
-
}
|
|
1954
|
-
function handleDismiss() {
|
|
1955
|
-
setDismissed(true);
|
|
1956
|
-
if (onDismiss) onDismiss();
|
|
1915
|
+
var PRESET_SIGNATURES = {
|
|
1916
|
+
cidika: {
|
|
1917
|
+
name: "Cidika Travel",
|
|
1918
|
+
tables: ["bookings", "packages", "package_locales"]
|
|
1919
|
+
},
|
|
1920
|
+
tokoSepatu: {
|
|
1921
|
+
name: "Toko Sepatu / E-Commerce",
|
|
1922
|
+
tables: ["orders", "products", "customers"]
|
|
1957
1923
|
}
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1924
|
+
};
|
|
1925
|
+
function detectPreset(tables) {
|
|
1926
|
+
if (!tables || tables.length === 0) return null;
|
|
1927
|
+
const tSet = new Set(tables);
|
|
1928
|
+
if (PRESET_SIGNATURES.cidika.tables.every((t) => tSet.has(t))) return "cidika";
|
|
1929
|
+
if (PRESET_SIGNATURES.tokoSepatu.tables.every((t) => tSet.has(t))) return "tokoSepatu";
|
|
1930
|
+
return null;
|
|
1931
|
+
}
|
|
1932
|
+
function generateCode(mapping) {
|
|
1933
|
+
const {
|
|
1934
|
+
tableName,
|
|
1935
|
+
colDate,
|
|
1936
|
+
colStatus,
|
|
1937
|
+
colTotal,
|
|
1938
|
+
colCustomer,
|
|
1939
|
+
colItem,
|
|
1940
|
+
dashTitle,
|
|
1941
|
+
confirmedValue,
|
|
1942
|
+
pendingValue
|
|
1943
|
+
} = mapping;
|
|
1944
|
+
const safeTitle = dashTitle || "Dashboard";
|
|
1945
|
+
const confirmed = confirmedValue || "confirmed";
|
|
1946
|
+
const pending = pendingValue || "pending";
|
|
1947
|
+
const safeCustomer = colCustomer || "customer_name";
|
|
1948
|
+
const safeItem = colItem || "-";
|
|
1949
|
+
const colTotalFull = colTotal || "total";
|
|
1950
|
+
const dataSource = `// src/datasources/myDashboardSource.js
|
|
1951
|
+
// AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
|
|
1952
|
+
// Tabel: ${tableName}
|
|
1953
|
+
|
|
1954
|
+
export function createMyDashboardSource(supabase) {
|
|
1986
1955
|
return {
|
|
1987
1956
|
async fetchDashboardSnapshot({ fromISO, toISO, statusScope }) {
|
|
1988
|
-
const
|
|
1989
|
-
.from("
|
|
1990
|
-
.select("id,
|
|
1991
|
-
.gte("
|
|
1992
|
-
.lte("
|
|
1993
|
-
.order("
|
|
1957
|
+
const allQuery = supabase
|
|
1958
|
+
.from("${tableName}")
|
|
1959
|
+
.select("id, ${colDate}, ${colStatus}, ${colTotalFull}${colCustomer !== "customer_name" ? `, ${colCustomer}` : ", customer_name"}${colItem !== "-" ? `, ${colItem}` : ""}")
|
|
1960
|
+
.gte("${colDate}", fromISO)
|
|
1961
|
+
.lte("${colDate}", toISO)
|
|
1962
|
+
.order("${colDate}", { ascending: true });
|
|
1994
1963
|
|
|
1995
|
-
const
|
|
1996
|
-
.from("
|
|
1997
|
-
.select("id,
|
|
1998
|
-
.
|
|
1964
|
+
const recentQuery = supabase
|
|
1965
|
+
.from("${tableName}")
|
|
1966
|
+
.select("id, ${colDate}, ${colStatus}, ${colTotalFull}${colCustomer !== "customer_name" ? `, ${colCustomer}` : ", customer_name"}${colItem !== "-" ? `, ${colItem}` : ""}")
|
|
1967
|
+
.gte("${colDate}", fromISO)
|
|
1968
|
+
.lte("${colDate}", toISO)
|
|
1969
|
+
.order("${colDate}", { ascending: false })
|
|
1999
1970
|
.limit(10);
|
|
2000
1971
|
|
|
1972
|
+
if (statusScope && statusScope !== "all") {
|
|
1973
|
+
recentQuery.eq("${colStatus}", statusScope);
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
const [allRes, recentRes] = await Promise.all([allQuery, recentQuery]);
|
|
1977
|
+
|
|
2001
1978
|
return {
|
|
2002
|
-
bookings:
|
|
2003
|
-
recent,
|
|
1979
|
+
bookings: allRes.data || [], // semua transaksi (untuk chart & stats)
|
|
1980
|
+
recent: recentRes.data || [], // 10 terbaru (untuk tabel)
|
|
2004
1981
|
packageLocales: [],
|
|
2005
1982
|
staticCounts: { packages: 0, sections: 0 },
|
|
2006
1983
|
};
|
|
2007
1984
|
},
|
|
2008
1985
|
|
|
2009
|
-
// Opsional: live update
|
|
2010
1986
|
subscribeLiveUpdate(onEvent) {
|
|
2011
|
-
const ch = supabase
|
|
2012
|
-
.
|
|
2013
|
-
|
|
1987
|
+
const ch = supabase
|
|
1988
|
+
.channel("rdb-dashboard-live")
|
|
1989
|
+
.on("postgres_changes", { event: "*", schema: "public", table: "${tableName}" }, onEvent)
|
|
2014
1990
|
.subscribe();
|
|
2015
1991
|
return () => supabase.removeChannel(ch);
|
|
2016
1992
|
},
|
|
2017
1993
|
};
|
|
2018
|
-
}
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
onClick: handleReadTables,
|
|
2024
|
-
disabled: loadingTables
|
|
2025
|
-
},
|
|
2026
|
-
loadingTables ? "Membaca..." : "Tampilkan daftar tabel Supabase"
|
|
2027
|
-
), tableError && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-table-error" }, tableError), tables && tables.length > 0 && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-table-list" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginBottom: 6 } }, "Tabel ditemukan (", tables.length, "):"), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-table-chips" }, tables.map((t) => /* @__PURE__ */ import_react19.default.createElement("span", { key: t, className: "rdb-wizard-chip" }, t))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 8, color: "var(--rdb-text-muted)" } }, "Ganti ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "MY_TABLE"), " di contoh kode di atas dengan nama tabel yang sesuai.")), !supabase && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-table-error" }, "Tambahkan prop ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "supabase"), " ke", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, " <ReusableDashboardView supabase=", "{supabase}", " />"), " untuk mengaktifkan fitur ini.")), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 16 } }, /* @__PURE__ */ import_react19.default.createElement("button", { type: "button", className: "rdb-btn rdb-btn-secondary rdb-btn-sm", onClick: () => setStep(0) }, "\u2190 Kembali"), /* @__PURE__ */ import_react19.default.createElement("button", { type: "button", className: "rdb-btn rdb-btn-primary rdb-btn-sm", onClick: () => setStep(2) }, "Lanjut \u2192"))), step === 2 && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-section" }, /* @__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 } }, "Data Adapter")), /* @__PURE__ */ import_react19.default.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__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "src/adapters/myAdapter.js"), ":"), /* @__PURE__ */ import_react19.default.createElement("pre", { className: "rdb-wizard-code" }, `// src/adapters/myAdapter.js
|
|
1994
|
+
}`;
|
|
1995
|
+
const adapter = `// src/adapters/myDashboardAdapter.js
|
|
1996
|
+
// AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
|
|
1997
|
+
// Mapping kolom: status="${colStatus}", total="${colTotalFull}", tanggal="${colDate}"
|
|
1998
|
+
|
|
2028
1999
|
import { toNumber, buildDayBuckets } from "@rozaqi02/reusable-dashboard";
|
|
2029
2000
|
|
|
2030
|
-
export function
|
|
2001
|
+
export function createEmptyMyDashboardData() {
|
|
2031
2002
|
return {
|
|
2032
|
-
stats:
|
|
2033
|
-
charts: {
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
audienceDistribution: [],
|
|
2037
|
-
topPackages: [],
|
|
2038
|
-
},
|
|
2039
|
-
table: { recentBookings: [] },
|
|
2003
|
+
stats: { bookingsConfirm: 0, revenueConfirm: 0 },
|
|
2004
|
+
charts: { dailyTrends: [], statusDistribution: [],
|
|
2005
|
+
audienceDistribution: [], topPackages: [] },
|
|
2006
|
+
table: { recentBookings: [] },
|
|
2040
2007
|
};
|
|
2041
2008
|
}
|
|
2042
2009
|
|
|
2043
|
-
export function
|
|
2044
|
-
if (!raw) return
|
|
2010
|
+
export function adaptMyDashboardData({ raw, range, dateLocale, labels }) {
|
|
2011
|
+
if (!raw) return createEmptyMyDashboardData();
|
|
2045
2012
|
|
|
2046
|
-
const buckets
|
|
2047
|
-
const dayMap
|
|
2013
|
+
const buckets = buildDayBuckets(range.daysWindow, range.fromISO, dateLocale);
|
|
2014
|
+
const dayMap = new Map(buckets.map(b => [b.dateKey, b]));
|
|
2048
2015
|
const statusMap = new Map();
|
|
2049
2016
|
let confirmed = 0, revenue = 0;
|
|
2050
2017
|
|
|
2051
2018
|
(raw.bookings || []).forEach(row => {
|
|
2052
|
-
const status = String(row
|
|
2053
|
-
const amount = toNumber(row
|
|
2054
|
-
const dayKey = String(row
|
|
2019
|
+
const status = String(row["${colStatus}"] || "pending").toLowerCase();
|
|
2020
|
+
const amount = toNumber(row["${colTotalFull}"]);
|
|
2021
|
+
const dayKey = String(row["${colDate}"] || "").slice(0, 10);
|
|
2055
2022
|
|
|
2056
2023
|
statusMap.set(status, (statusMap.get(status) || 0) + 1);
|
|
2057
2024
|
const bucket = dayMap.get(dayKey);
|
|
2058
|
-
if (bucket
|
|
2059
|
-
bucket.count +=
|
|
2060
|
-
bucket.
|
|
2025
|
+
if (bucket) {
|
|
2026
|
+
if (status === "${confirmed}") { bucket.count++; bucket.revenue += amount; }
|
|
2027
|
+
if (status === "${pending}") { bucket.pendingCount++; }
|
|
2061
2028
|
}
|
|
2062
|
-
if (status === "confirmed") { confirmed++; revenue += amount; }
|
|
2029
|
+
if (status === "${confirmed}") { confirmed++; revenue += amount; }
|
|
2063
2030
|
});
|
|
2064
2031
|
|
|
2032
|
+
const avgRevenue = confirmed > 0 ? Math.round(revenue / confirmed) : 0;
|
|
2033
|
+
const totalTx = (raw.bookings || []).length;
|
|
2034
|
+
const conversionRate = totalTx > 0 ? Math.round((confirmed / totalTx) * 100) : 0;
|
|
2035
|
+
|
|
2065
2036
|
return {
|
|
2066
|
-
stats: {
|
|
2037
|
+
stats: {
|
|
2038
|
+
bookingsConfirm: confirmed,
|
|
2039
|
+
revenueConfirm: revenue,
|
|
2040
|
+
avgRevenue,
|
|
2041
|
+
conversionRate,
|
|
2042
|
+
},
|
|
2067
2043
|
charts: {
|
|
2068
2044
|
dailyTrends: buckets,
|
|
2069
2045
|
statusDistribution: Array.from(statusMap.entries())
|
|
2046
|
+
.sort((a, b) => b[1] - a[1])
|
|
2070
2047
|
.map(([status, count]) => ({
|
|
2071
|
-
status,
|
|
2048
|
+
status, count,
|
|
2072
2049
|
label: labels?.formatStatusLabel?.(status) || status,
|
|
2073
|
-
count,
|
|
2074
2050
|
})),
|
|
2075
2051
|
audienceDistribution: [],
|
|
2076
2052
|
topPackages: [],
|
|
2077
2053
|
},
|
|
2078
2054
|
table: {
|
|
2079
2055
|
recentBookings: (raw.recent || []).map(row => ({
|
|
2080
|
-
id:
|
|
2081
|
-
createdAt:
|
|
2082
|
-
customerName: row
|
|
2083
|
-
packageName: row
|
|
2056
|
+
id: row.id,
|
|
2057
|
+
createdAt: row["${colDate}"],
|
|
2058
|
+
customerName: row["${colCustomer}"] || "-",
|
|
2059
|
+
packageName: ${colItem !== "-" ? `row["${colItem}"] || "-"` : '"-"'},
|
|
2084
2060
|
audienceLabel: "-",
|
|
2085
|
-
totalIDR:
|
|
2086
|
-
status:
|
|
2087
|
-
statusLabel:
|
|
2061
|
+
totalIDR: toNumber(row["${colTotalFull}"]),
|
|
2062
|
+
status: String(row["${colStatus}"] || "pending").toLowerCase(),
|
|
2063
|
+
statusLabel: labels?.formatStatusLabel?.(row["${colStatus}"]) || row["${colStatus}"],
|
|
2088
2064
|
})),
|
|
2089
2065
|
},
|
|
2090
2066
|
};
|
|
2091
|
-
}
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
ReusableDashboardView,
|
|
2095
|
-
useReusableDashboard,
|
|
2096
|
-
createDashboardConfig,
|
|
2097
|
-
} from "@rozaqi02/reusable-dashboard";
|
|
2067
|
+
}`;
|
|
2068
|
+
const widgetConfig = `// src/config/myDashboardConfig.js
|
|
2069
|
+
// AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
|
|
2098
2070
|
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2071
|
+
export const myDashboardConfig = {
|
|
2072
|
+
id: "my.custom.dashboard",
|
|
2073
|
+
defaultFilters: {
|
|
2074
|
+
statusScope: "${confirmed}",
|
|
2075
|
+
daysPreset: 30,
|
|
2076
|
+
sortPkgBy: "bookings",
|
|
2077
|
+
sortPkgDir: "desc",
|
|
2078
|
+
},
|
|
2106
2079
|
widgets: {
|
|
2107
2080
|
stats: [
|
|
2108
|
-
{ id: "orders",
|
|
2109
|
-
valueKey: "bookingsConfirm", format: "number",
|
|
2110
|
-
{ id: "revenue",
|
|
2111
|
-
valueKey: "revenueConfirm",
|
|
2081
|
+
{ id: "orders", label: "confirmedBookings", icon: "TrendingUp",
|
|
2082
|
+
valueKey: "bookingsConfirm", format: "number", accentColor: "blue" },
|
|
2083
|
+
{ id: "revenue", label: "confirmedRevenue", icon: "DollarSign",
|
|
2084
|
+
valueKey: "revenueConfirm", format: "currency", accentColor: "green" },
|
|
2085
|
+
{ id: "avg", label: "avgRevenue", icon: "Users",
|
|
2086
|
+
valueKey: "avgRevenue", format: "currency", accentColor: "violet" },
|
|
2087
|
+
{ id: "conversion", label: "conversionRate", icon: "PieChart",
|
|
2088
|
+
valueKey: "conversionRate", format: "percent", accentColor: "orange" },
|
|
2112
2089
|
],
|
|
2113
2090
|
charts: [
|
|
2114
|
-
{ id: "trend",
|
|
2091
|
+
{ id: "trend", type: "dailyArea", label: "dailyTrends", icon: "BarChart3" },
|
|
2115
2092
|
{ id: "status", type: "statusPie", label: "statusDistribution", icon: "PieChart" },
|
|
2116
2093
|
],
|
|
2117
2094
|
table: {
|
|
2118
|
-
id: "
|
|
2095
|
+
id: "recentTx", label: "recentBookings", icon: "Calendar",
|
|
2119
2096
|
emptyLabel: "noRecentBookings",
|
|
2120
2097
|
columns: [
|
|
2121
|
-
{ id: "date",
|
|
2122
|
-
{ id: "customer", label: "customer", accessor: "customerName" }
|
|
2123
|
-
{ id: "
|
|
2124
|
-
{ id: "
|
|
2098
|
+
{ id: "date", label: "date", accessor: "createdAt", type: "date" },
|
|
2099
|
+
{ id: "customer", label: "customer", accessor: "customerName" },${colItem !== "-" ? `
|
|
2100
|
+
{ id: "item", label: "package", accessor: "packageName" },` : ""}
|
|
2101
|
+
{ id: "total", label: "total", accessor: "totalIDR", type: "currency" },
|
|
2102
|
+
{ id: "status", label: "status", accessor: "statusLabel",
|
|
2125
2103
|
type: "statusBadge", statusAccessor: "status" },
|
|
2126
2104
|
],
|
|
2127
2105
|
},
|
|
2128
2106
|
},
|
|
2129
|
-
}
|
|
2107
|
+
};`;
|
|
2108
|
+
const dashboard = `// src/pages/admin/Dashboard.jsx
|
|
2109
|
+
// AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
|
|
2110
|
+
// Salin file ini ke halaman dashboard kamu.
|
|
2130
2111
|
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
dateLocale: "id-ID",
|
|
2139
|
-
});
|
|
2112
|
+
import React from "react";
|
|
2113
|
+
import { supabase } from "../../lib/supabaseClient.js";
|
|
2114
|
+
import {
|
|
2115
|
+
ReusableDashboardView,
|
|
2116
|
+
useReusableDashboard,
|
|
2117
|
+
createDashboardConfig,
|
|
2118
|
+
} from "@rozaqi02/reusable-dashboard";
|
|
2140
2119
|
|
|
2141
|
-
// 3
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2120
|
+
// Import 3 file yang digenerate wizard
|
|
2121
|
+
import { myDashboardConfig } from "./myDashboardConfig";
|
|
2122
|
+
import { createMyDashboardSource } from "./myDashboardSource";
|
|
2123
|
+
import { adaptMyDashboardData, createEmptyMyDashboardData } from "./myDashboardAdapter";
|
|
2124
|
+
|
|
2125
|
+
// Data source \u2014 dibuat sekali di luar komponen
|
|
2126
|
+
const source = createMyDashboardSource(supabase);
|
|
2127
|
+
|
|
2128
|
+
// Label UI \u2014 sesuaikan teks dengan bahasa/konteks bisnis kamu
|
|
2129
|
+
const labels = {
|
|
2130
|
+
title: "${safeTitle}",
|
|
2131
|
+
refresh: "Refresh",
|
|
2132
|
+
liveUpdate: "Live update",
|
|
2133
|
+
loadFailed: "Gagal memuat data.",
|
|
2134
|
+
retry: "Coba Lagi",
|
|
2135
|
+
confirmedOnly: "Selesai",
|
|
2136
|
+
pendingOnly: "Proses",
|
|
2137
|
+
allStatus: "Semua Status",
|
|
2138
|
+
showPendingOverlay: "Tampilkan pending",
|
|
2139
|
+
reset: "Reset",
|
|
2140
|
+
confirmedBookings: "Total Transaksi",
|
|
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",
|
|
2155
|
+
confirmedBookingMetric: "Transaksi (Selesai)",
|
|
2156
|
+
confirmedRevenueMetric: "Pendapatan (Selesai)",
|
|
2151
2157
|
dayLabel: n => n + " hari",
|
|
2152
2158
|
formatStatusLabel: s =>
|
|
2153
|
-
({ confirmed: "Selesai", pending: "Proses"
|
|
2159
|
+
({ "${confirmed}": "Selesai", "${pending}": "Proses" })[s] || (s || "-"),
|
|
2154
2160
|
formatAudienceLabel: v => v || "-",
|
|
2155
2161
|
};
|
|
2156
2162
|
|
|
2157
|
-
//
|
|
2158
|
-
|
|
2159
|
-
|
|
2163
|
+
// Kemas semua jadi 1 objek
|
|
2164
|
+
const dashboardConfig = createDashboardConfig({
|
|
2165
|
+
widgetConfig: myDashboardConfig,
|
|
2166
|
+
dataSource: source,
|
|
2167
|
+
adapter: adaptMyDashboardData,
|
|
2168
|
+
createEmptyState: createEmptyMyDashboardData,
|
|
2169
|
+
languageCode: "id",
|
|
2170
|
+
dateLocale: "id-ID",
|
|
2171
|
+
labels,
|
|
2172
|
+
});
|
|
2173
|
+
|
|
2174
|
+
export default function Dashboard() {
|
|
2175
|
+
const state = useReusableDashboard({ ...dashboardConfig, labels });
|
|
2160
2176
|
return (
|
|
2161
2177
|
<ReusableDashboardView
|
|
2162
|
-
config={
|
|
2178
|
+
config={dashboardConfig.config}
|
|
2163
2179
|
labels={labels}
|
|
2164
2180
|
loading={state.loading}
|
|
2165
2181
|
error={state.error}
|
|
@@ -2168,11 +2184,427 @@ export default function MyDashboard() {
|
|
|
2168
2184
|
onResetFilters={state.resetFilters}
|
|
2169
2185
|
onRefresh={state.refresh}
|
|
2170
2186
|
data={state.data}
|
|
2171
|
-
dateLocale={
|
|
2187
|
+
dateLocale={dashboardConfig.dateLocale}
|
|
2172
2188
|
liveUpdateEnabled={state.liveUpdateEnabled}
|
|
2189
|
+
supabase={supabase}
|
|
2190
|
+
dashboardConfig={dashboardConfig}
|
|
2173
2191
|
/>
|
|
2174
2192
|
);
|
|
2175
|
-
}
|
|
2193
|
+
}`;
|
|
2194
|
+
return { dataSource, adapter, widgetConfig, dashboard };
|
|
2195
|
+
}
|
|
2196
|
+
function SetupWizard({ issues = [], onDismiss, supabase }) {
|
|
2197
|
+
const [dismissed, setDismissed] = (0, import_react19.useState)(false);
|
|
2198
|
+
const [step, setStep] = (0, import_react19.useState)(0);
|
|
2199
|
+
const [detecting, setDetecting] = (0, import_react19.useState)(true);
|
|
2200
|
+
const [tables, setTables] = (0, import_react19.useState)([]);
|
|
2201
|
+
const [columns, setColumns] = (0, import_react19.useState)({});
|
|
2202
|
+
const [supabaseOk, setSupabaseOk] = (0, import_react19.useState)(false);
|
|
2203
|
+
const [detectionDone, setDetectionDone] = (0, import_react19.useState)(false);
|
|
2204
|
+
const [userHasDashboard, setUserHasDashboard] = (0, import_react19.useState)(null);
|
|
2205
|
+
const [selectedTable, setSelectedTable] = (0, import_react19.useState)("");
|
|
2206
|
+
const [colDate, setColDate] = (0, import_react19.useState)("");
|
|
2207
|
+
const [colStatus, setColStatus] = (0, import_react19.useState)("");
|
|
2208
|
+
const [colTotal, setColTotal] = (0, import_react19.useState)("");
|
|
2209
|
+
const [colCustomer, setColCustomer] = (0, import_react19.useState)("");
|
|
2210
|
+
const [colItem, setColItem] = (0, import_react19.useState)("");
|
|
2211
|
+
const [confirmedVal, setConfirmedVal] = (0, import_react19.useState)("confirmed");
|
|
2212
|
+
const [pendingVal, setPendingVal] = (0, import_react19.useState)("pending");
|
|
2213
|
+
const [dashTitle, setDashTitle] = (0, import_react19.useState)("Dashboard");
|
|
2214
|
+
const [loadingColumns, setLoadingColumns] = (0, import_react19.useState)(false);
|
|
2215
|
+
const [generatedCode, setGeneratedCode] = (0, import_react19.useState)(null);
|
|
2216
|
+
const [activeCodeTab, setActiveCodeTab] = (0, import_react19.useState)("dataSource");
|
|
2217
|
+
const [copied, setCopied] = (0, import_react19.useState)("");
|
|
2218
|
+
(0, import_react19.useEffect)(() => {
|
|
2219
|
+
if (!supabase) {
|
|
2220
|
+
setDetecting(false);
|
|
2221
|
+
setDetectionDone(true);
|
|
2222
|
+
return;
|
|
2223
|
+
}
|
|
2224
|
+
(async () => {
|
|
2225
|
+
try {
|
|
2226
|
+
const { data: rpcData, error: rpcErr } = await supabase.rpc("rdb_get_tables");
|
|
2227
|
+
if (!rpcErr && Array.isArray(rpcData)) {
|
|
2228
|
+
setTables(rpcData.map((r) => typeof r === "string" ? r : r.table_name || r.name || r));
|
|
2229
|
+
setSupabaseOk(true);
|
|
2230
|
+
} else {
|
|
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
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
} catch {
|
|
2243
|
+
setSupabaseOk(false);
|
|
2244
|
+
} finally {
|
|
2245
|
+
setDetecting(false);
|
|
2246
|
+
setDetectionDone(true);
|
|
2247
|
+
}
|
|
2248
|
+
})();
|
|
2249
|
+
}, [supabase]);
|
|
2250
|
+
const loadColumns = (0, import_react19.useCallback)(async (tbl) => {
|
|
2251
|
+
if (!supabase || !tbl || columns[tbl]) return;
|
|
2252
|
+
setLoadingColumns(true);
|
|
2253
|
+
try {
|
|
2254
|
+
const { data: rpcCols, error: rpcErr } = await supabase.rpc("rdb_get_columns", { p_table: tbl });
|
|
2255
|
+
if (!rpcErr && Array.isArray(rpcCols)) {
|
|
2256
|
+
const colObjs = rpcCols.map(
|
|
2257
|
+
(c) => typeof c === "string" ? { column_name: c, data_type: "unknown" } : { column_name: c.column_name || c.name || c, data_type: c.data_type || "unknown" }
|
|
2258
|
+
);
|
|
2259
|
+
setColumns((prev) => ({ ...prev, [tbl]: colObjs }));
|
|
2260
|
+
autoDetectCols(colObjs);
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
const { data: schemaCols, error: schemaErr } = await supabase.from("information_schema.columns").select("column_name, data_type").eq("table_schema", "public").eq("table_name", tbl).order("ordinal_position");
|
|
2264
|
+
if (!schemaErr && schemaCols) {
|
|
2265
|
+
setColumns((prev) => ({ ...prev, [tbl]: schemaCols }));
|
|
2266
|
+
autoDetectCols(schemaCols);
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
const { data: sampleRow, error: sampleErr } = await supabase.from(tbl).select("*").limit(1);
|
|
2270
|
+
if (!sampleErr && sampleRow && sampleRow.length > 0) {
|
|
2271
|
+
const colObjs = Object.keys(sampleRow[0]).map((k) => ({
|
|
2272
|
+
column_name: k,
|
|
2273
|
+
data_type: typeof sampleRow[0][k]
|
|
2274
|
+
}));
|
|
2275
|
+
setColumns((prev) => ({ ...prev, [tbl]: colObjs }));
|
|
2276
|
+
autoDetectCols(colObjs);
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
setColumns((prev) => ({ ...prev, [tbl]: [] }));
|
|
2280
|
+
} catch {
|
|
2281
|
+
setColumns((prev) => ({ ...prev, [tbl]: [] }));
|
|
2282
|
+
} finally {
|
|
2283
|
+
setLoadingColumns(false);
|
|
2284
|
+
}
|
|
2285
|
+
}, [supabase, columns]);
|
|
2286
|
+
function autoDetectCols(colObjs) {
|
|
2287
|
+
const find = (candidates) => {
|
|
2288
|
+
var _a;
|
|
2289
|
+
return ((_a = colObjs.find((c) => candidates.includes(c.column_name.toLowerCase()))) == null ? void 0 : _a.column_name) || "";
|
|
2290
|
+
};
|
|
2291
|
+
setColDate(find(["created_at", "tanggal", "date", "transaction_date", "order_date", "waktu"]));
|
|
2292
|
+
setColStatus(find(["status", "state", "kondisi", "order_status", "payment_status"]));
|
|
2293
|
+
setColTotal(find(["total", "total_idr", "total_amount", "total_price", "harga_total", "amount", "nominal", "harga", "biaya"]));
|
|
2294
|
+
setColCustomer(find(["customer_name", "nama_pelanggan", "nama", "name", "client_name", "buyer_name", "pelanggan"]));
|
|
2295
|
+
setColItem(find(["service_type", "item_name", "product_name", "package_name", "layanan", "produk", "nama_layanan", "jenis", "nama_produk"]));
|
|
2296
|
+
}
|
|
2297
|
+
function copyCode(text, label) {
|
|
2298
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
2299
|
+
setCopied(label);
|
|
2300
|
+
setTimeout(() => setCopied(""), 2e3);
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
function handleDismiss() {
|
|
2304
|
+
setDismissed(true);
|
|
2305
|
+
if (onDismiss) onDismiss();
|
|
2306
|
+
}
|
|
2307
|
+
function handleGenerate() {
|
|
2308
|
+
if (!selectedTable || !colDate || !colStatus || !colTotal) return;
|
|
2309
|
+
const code = generateCode({
|
|
2310
|
+
tableName: selectedTable,
|
|
2311
|
+
colDate,
|
|
2312
|
+
colStatus,
|
|
2313
|
+
colTotal,
|
|
2314
|
+
colCustomer: colCustomer || "customer_name",
|
|
2315
|
+
colItem: colItem || "-",
|
|
2316
|
+
dashTitle,
|
|
2317
|
+
confirmedValue: confirmedVal,
|
|
2318
|
+
pendingValue: pendingVal
|
|
2319
|
+
});
|
|
2320
|
+
setGeneratedCode(code);
|
|
2321
|
+
setStep(3);
|
|
2322
|
+
}
|
|
2323
|
+
if (dismissed) return null;
|
|
2324
|
+
const detectedPreset = detectPreset(tables);
|
|
2325
|
+
const tableColumns = selectedTable ? columns[selectedTable] || [] : [];
|
|
2326
|
+
const colNames = tableColumns.map((c) => c.column_name);
|
|
2327
|
+
const mappingValid = selectedTable && colDate && colStatus && colTotal;
|
|
2328
|
+
const stepLabels = ["Deteksi", "Pilih Tabel", "Mapping Kolom", "Kode Siap Pakai"];
|
|
2329
|
+
return /* @__PURE__ */ import_react19.default.createElement(
|
|
2330
|
+
"div",
|
|
2331
|
+
{
|
|
2332
|
+
className: "rdb-wizard-overlay",
|
|
2333
|
+
role: "dialog",
|
|
2334
|
+
"aria-modal": "true",
|
|
2335
|
+
"aria-label": "Setup Wizard @rozaqi02/reusable-dashboard"
|
|
2336
|
+
},
|
|
2337
|
+
/* @__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"), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption" }, detecting ? "Mendeteksi kondisi project\u2026" : supabaseOk ? `Supabase tersambung \xB7 ${tables.length} tabel ditemukan` : "Panduan konfigurasi @rozaqi02/reusable-dashboard"))), /* @__PURE__ */ import_react19.default.createElement(
|
|
2338
|
+
"button",
|
|
2339
|
+
{
|
|
2340
|
+
type: "button",
|
|
2341
|
+
className: "rdb-wizard-close",
|
|
2342
|
+
onClick: handleDismiss,
|
|
2343
|
+
title: "Tutup"
|
|
2344
|
+
},
|
|
2345
|
+
"\u2715"
|
|
2346
|
+
)), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-steps" }, stepLabels.map((label, i) => /* @__PURE__ */ import_react19.default.createElement(
|
|
2347
|
+
"button",
|
|
2348
|
+
{
|
|
2349
|
+
key: i,
|
|
2350
|
+
type: "button",
|
|
2351
|
+
className: `rdb-wizard-step-btn ${step === i ? "rdb-wizard-step-active" : ""}`,
|
|
2352
|
+
onClick: () => {
|
|
2353
|
+
if (i < step || i === step + 1 && step < 2) setStep(i);
|
|
2354
|
+
}
|
|
2355
|
+
},
|
|
2356
|
+
/* @__PURE__ */ import_react19.default.createElement("span", null, step > i ? "\u2705" : i + 1, "."),
|
|
2357
|
+
/* @__PURE__ */ import_react19.default.createElement("span", null, label)
|
|
2358
|
+
))), /* @__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 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: 12 } }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 10 } }, "\u{1F50D} Hasil deteksi otomatis:"), 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" }, "Supabase ", supabaseOk ? "tersambung" : "tidak tersambung \u2014 pastikan supabaseClient.js sudah dikonfigurasi dan prop supabase dikirim 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 ? `${tables.length} tabel public ditemukan` : "Daftar tabel tidak bisa dibaca otomatis (RLS aktif) \u2014 kamu bisa ketik nama tabel di step berikutnya")), supabaseOk && tables.length > 0 && detectedPreset && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-issue-item" }, /* @__PURE__ */ import_react19.default.createElement("span", null, "\u2705"), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-body" }, "Tabel cocok dengan preset ", /* @__PURE__ */ import_react19.default.createElement("strong", null, PRESET_SIGNATURES[detectedPreset].name))))), !supabaseOk && detectionDone && /* @__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 } }, "Aktifkan koneksi Supabase"), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 4 } }, "Tambahkan prop ke ReusableDashboardView:", " ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "supabase=", "{supabase}")))), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" } }, /* @__PURE__ */ import_react19.default.createElement(
|
|
2359
|
+
"button",
|
|
2360
|
+
{
|
|
2361
|
+
type: "button",
|
|
2362
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2363
|
+
onClick: handleDismiss
|
|
2364
|
+
},
|
|
2365
|
+
"Lanjutkan tanpa wizard"
|
|
2366
|
+
), supabaseOk && /* @__PURE__ */ import_react19.default.createElement(
|
|
2367
|
+
"button",
|
|
2368
|
+
{
|
|
2369
|
+
type: "button",
|
|
2370
|
+
className: "rdb-btn rdb-btn-primary rdb-btn-sm",
|
|
2371
|
+
onClick: () => setStep(1)
|
|
2372
|
+
},
|
|
2373
|
+
"Lanjut \u2192 Pilih Tabel"
|
|
2374
|
+
))), 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 } }, "Pilih tabel yang berisi data transaksi bisnis kamu."), tables.length > 0 && /* @__PURE__ */ import_react19.default.createElement(import_react19.default.Fragment, null, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 8 } }, "Tabel yang ditemukan:"), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6, marginBottom: 16 } }, tables.map((tbl) => /* @__PURE__ */ import_react19.default.createElement(
|
|
2375
|
+
"button",
|
|
2376
|
+
{
|
|
2377
|
+
key: tbl,
|
|
2378
|
+
type: "button",
|
|
2379
|
+
onClick: () => {
|
|
2380
|
+
setSelectedTable(tbl);
|
|
2381
|
+
loadColumns(tbl);
|
|
2382
|
+
},
|
|
2383
|
+
className: `rdb-btn rdb-btn-sm ${selectedTable === tbl ? "rdb-btn-primary" : "rdb-btn-secondary"}`,
|
|
2384
|
+
style: { justifyContent: "flex-start", fontFamily: "monospace" }
|
|
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)
|
|
2449
|
+
},
|
|
2450
|
+
/* @__PURE__ */ import_react19.default.createElement("option", { value: "" }, "\u2014 pilih kolom \u2014"),
|
|
2451
|
+
colNames.map((c) => /* @__PURE__ */ import_react19.default.createElement("option", { key: c, value: c }, c))
|
|
2452
|
+
) : /* @__PURE__ */ import_react19.default.createElement(
|
|
2453
|
+
"input",
|
|
2454
|
+
{
|
|
2455
|
+
className: "rdb-input",
|
|
2456
|
+
value: colDate,
|
|
2457
|
+
onChange: (e) => setColDate(e.target.value),
|
|
2458
|
+
placeholder: "cth: created_at"
|
|
2459
|
+
}
|
|
2460
|
+
), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 2 } }, "Tipe timestamp/date")), /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, "Kolom Status ", /* @__PURE__ */ import_react19.default.createElement("span", { style: { color: "red" } }, "*")), colNames.length > 0 ? /* @__PURE__ */ import_react19.default.createElement(
|
|
2461
|
+
"select",
|
|
2462
|
+
{
|
|
2463
|
+
className: "rdb-select",
|
|
2464
|
+
value: colStatus,
|
|
2465
|
+
onChange: (e) => setColStatus(e.target.value)
|
|
2466
|
+
},
|
|
2467
|
+
/* @__PURE__ */ import_react19.default.createElement("option", { value: "" }, "\u2014 pilih kolom \u2014"),
|
|
2468
|
+
colNames.map((c) => /* @__PURE__ */ import_react19.default.createElement("option", { key: c, value: c }, c))
|
|
2469
|
+
) : /* @__PURE__ */ import_react19.default.createElement(
|
|
2470
|
+
"input",
|
|
2471
|
+
{
|
|
2472
|
+
className: "rdb-input",
|
|
2473
|
+
value: colStatus,
|
|
2474
|
+
onChange: (e) => setColStatus(e.target.value),
|
|
2475
|
+
placeholder: "cth: status"
|
|
2476
|
+
}
|
|
2477
|
+
), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 2 } }, "Nilai selesai/pending")), /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, "Kolom Total (uang) ", /* @__PURE__ */ import_react19.default.createElement("span", { style: { color: "red" } }, "*")), colNames.length > 0 ? /* @__PURE__ */ import_react19.default.createElement(
|
|
2478
|
+
"select",
|
|
2479
|
+
{
|
|
2480
|
+
className: "rdb-select",
|
|
2481
|
+
value: colTotal,
|
|
2482
|
+
onChange: (e) => setColTotal(e.target.value)
|
|
2483
|
+
},
|
|
2484
|
+
/* @__PURE__ */ import_react19.default.createElement("option", { value: "" }, "\u2014 pilih kolom \u2014"),
|
|
2485
|
+
colNames.map((c) => /* @__PURE__ */ import_react19.default.createElement("option", { key: c, value: c }, c))
|
|
2486
|
+
) : /* @__PURE__ */ import_react19.default.createElement(
|
|
2487
|
+
"input",
|
|
2488
|
+
{
|
|
2489
|
+
className: "rdb-input",
|
|
2490
|
+
value: colTotal,
|
|
2491
|
+
onChange: (e) => setColTotal(e.target.value),
|
|
2492
|
+
placeholder: "cth: total_amount"
|
|
2493
|
+
}
|
|
2494
|
+
), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-caption", style: { marginTop: 2 } }, "Integer Rupiah")), /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, "Kolom Nama Pelanggan"), colNames.length > 0 ? /* @__PURE__ */ import_react19.default.createElement(
|
|
2495
|
+
"select",
|
|
2496
|
+
{
|
|
2497
|
+
className: "rdb-select",
|
|
2498
|
+
value: colCustomer,
|
|
2499
|
+
onChange: (e) => setColCustomer(e.target.value)
|
|
2500
|
+
},
|
|
2501
|
+
/* @__PURE__ */ import_react19.default.createElement("option", { value: "" }, "\u2014 opsional \u2014"),
|
|
2502
|
+
colNames.map((c) => /* @__PURE__ */ import_react19.default.createElement("option", { key: c, value: c }, c))
|
|
2503
|
+
) : /* @__PURE__ */ import_react19.default.createElement(
|
|
2504
|
+
"input",
|
|
2505
|
+
{
|
|
2506
|
+
className: "rdb-input",
|
|
2507
|
+
value: colCustomer,
|
|
2508
|
+
onChange: (e) => setColCustomer(e.target.value),
|
|
2509
|
+
placeholder: "cth: customer_name (opsional)"
|
|
2510
|
+
}
|
|
2511
|
+
)), /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, "Kolom Nama Item/Layanan"), colNames.length > 0 ? /* @__PURE__ */ import_react19.default.createElement(
|
|
2512
|
+
"select",
|
|
2513
|
+
{
|
|
2514
|
+
className: "rdb-select",
|
|
2515
|
+
value: colItem,
|
|
2516
|
+
onChange: (e) => setColItem(e.target.value)
|
|
2517
|
+
},
|
|
2518
|
+
/* @__PURE__ */ import_react19.default.createElement("option", { value: "" }, "\u2014 opsional \u2014"),
|
|
2519
|
+
colNames.map((c) => /* @__PURE__ */ import_react19.default.createElement("option", { key: c, value: c }, c))
|
|
2520
|
+
) : /* @__PURE__ */ import_react19.default.createElement(
|
|
2521
|
+
"input",
|
|
2522
|
+
{
|
|
2523
|
+
className: "rdb-input",
|
|
2524
|
+
value: colItem,
|
|
2525
|
+
onChange: (e) => setColItem(e.target.value),
|
|
2526
|
+
placeholder: "cth: service_type (opsional)"
|
|
2527
|
+
}
|
|
2528
|
+
))), /* @__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: 8 } }, "Nilai status di tabel kamu:"), /* @__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(
|
|
2529
|
+
"input",
|
|
2530
|
+
{
|
|
2531
|
+
className: "rdb-input",
|
|
2532
|
+
value: confirmedVal,
|
|
2533
|
+
onChange: (e) => setConfirmedVal(e.target.value),
|
|
2534
|
+
placeholder: "cth: confirmed, selesai, lunas"
|
|
2535
|
+
}
|
|
2536
|
+
)), /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("label", { className: "rdb-label" }, 'Nilai "Pending/Proses"'), /* @__PURE__ */ import_react19.default.createElement(
|
|
2537
|
+
"input",
|
|
2538
|
+
{
|
|
2539
|
+
className: "rdb-input",
|
|
2540
|
+
value: pendingVal,
|
|
2541
|
+
onChange: (e) => setPendingVal(e.target.value),
|
|
2542
|
+
placeholder: "cth: pending, proses, menunggu"
|
|
2543
|
+
}
|
|
2544
|
+
)))), !mappingValid && /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-error-banner", style: { marginBottom: 12 } }, "Pilih minimal kolom Tanggal, Status, dan Total untuk generate kode."), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" } }, /* @__PURE__ */ import_react19.default.createElement(
|
|
2545
|
+
"button",
|
|
2546
|
+
{
|
|
2547
|
+
type: "button",
|
|
2548
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2549
|
+
onClick: () => setStep(1)
|
|
2550
|
+
},
|
|
2551
|
+
"\u2190 Kembali"
|
|
2552
|
+
), /* @__PURE__ */ import_react19.default.createElement(
|
|
2553
|
+
"button",
|
|
2554
|
+
{
|
|
2555
|
+
type: "button",
|
|
2556
|
+
className: "rdb-btn rdb-btn-primary rdb-btn-sm",
|
|
2557
|
+
disabled: !mappingValid,
|
|
2558
|
+
onClick: handleGenerate
|
|
2559
|
+
},
|
|
2560
|
+
"\u2728 Generate Kode \u2192"
|
|
2561
|
+
))), 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 Siap Pakai")), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-tip", style: { marginBottom: 16 } }, /* @__PURE__ */ import_react19.default.createElement("strong", null, "4 file sudah digenerate."), " Salin masing-masing ke project kamu. Tidak perlu memahami logika di dalamnya \u2014 cukup save dan jalankan."), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-issues", style: { marginBottom: 16 } }, [
|
|
2562
|
+
["myDashboardSource.js", "src/datasources/", "File koneksi ke Supabase"],
|
|
2563
|
+
["myDashboardAdapter.js", "src/adapters/", "Transformasi data \u2192 format dashboard"],
|
|
2564
|
+
["myDashboardConfig.js", "src/config/ (atau langsung di Dashboard.jsx)", "Konfigurasi widget"],
|
|
2565
|
+
["Dashboard.jsx", "src/pages/admin/", "Ganti halaman dashboard lama"]
|
|
2566
|
+
].map(([file, path, desc], i) => /* @__PURE__ */ import_react19.default.createElement("div", { key: i, className: "rdb-wizard-issue-item" }, /* @__PURE__ */ import_react19.default.createElement("span", { style: { color: "var(--rdb-blue-500)", fontWeight: 700 } }, i + 1, "."), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-body" }, "Simpan ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, file), " ke", " ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, path), " \u2014 ", desc))), /* @__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 } }, "5."), /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-body" }, "Import CSS di ", /* @__PURE__ */ import_react19.default.createElement("code", { className: "rdb-wizard-code-inline" }, "src/index.css"), ":", " ", /* @__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", { style: { display: "flex", gap: 4, marginBottom: 8, flexWrap: "wrap" } }, [
|
|
2567
|
+
["dataSource", "myDashboardSource.js"],
|
|
2568
|
+
["adapter", "myDashboardAdapter.js"],
|
|
2569
|
+
["widgetConfig", "myDashboardConfig.js"],
|
|
2570
|
+
["dashboard", "Dashboard.jsx"]
|
|
2571
|
+
].map(([key, label]) => /* @__PURE__ */ import_react19.default.createElement(
|
|
2572
|
+
"button",
|
|
2573
|
+
{
|
|
2574
|
+
key,
|
|
2575
|
+
type: "button",
|
|
2576
|
+
onClick: () => setActiveCodeTab(key),
|
|
2577
|
+
className: `rdb-btn rdb-btn-sm ${activeCodeTab === key ? "rdb-btn-primary" : "rdb-btn-secondary"}`,
|
|
2578
|
+
style: { fontFamily: "monospace", fontSize: "0.75rem" }
|
|
2579
|
+
},
|
|
2580
|
+
label
|
|
2581
|
+
))), /* @__PURE__ */ import_react19.default.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ import_react19.default.createElement(
|
|
2582
|
+
"button",
|
|
2583
|
+
{
|
|
2584
|
+
type: "button",
|
|
2585
|
+
onClick: () => copyCode(generatedCode[activeCodeTab], activeCodeTab),
|
|
2586
|
+
className: "rdb-btn rdb-btn-sm rdb-btn-secondary",
|
|
2587
|
+
style: { position: "absolute", top: 8, right: 8, zIndex: 1 }
|
|
2588
|
+
},
|
|
2589
|
+
copied === activeCodeTab ? "\u2705 Disalin!" : "\u{1F4CB} Salin"
|
|
2590
|
+
), /* @__PURE__ */ import_react19.default.createElement("pre", { className: "rdb-wizard-code", style: { maxHeight: 320, overflow: "auto" } }, generatedCode[activeCodeTab])), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-wizard-tip", style: { marginTop: 12 } }, "Setelah semua file tersimpan dan dev server direstart, wizard tidak akan muncul lagi."), /* @__PURE__ */ import_react19.default.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 12 } }, /* @__PURE__ */ import_react19.default.createElement(
|
|
2591
|
+
"button",
|
|
2592
|
+
{
|
|
2593
|
+
type: "button",
|
|
2594
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2595
|
+
onClick: () => setStep(2)
|
|
2596
|
+
},
|
|
2597
|
+
"\u2190 Edit mapping"
|
|
2598
|
+
), /* @__PURE__ */ import_react19.default.createElement(
|
|
2599
|
+
"button",
|
|
2600
|
+
{
|
|
2601
|
+
type: "button",
|
|
2602
|
+
className: "rdb-btn rdb-btn-primary rdb-btn-sm",
|
|
2603
|
+
onClick: handleDismiss
|
|
2604
|
+
},
|
|
2605
|
+
"Tutup wizard \u2713"
|
|
2606
|
+
))))))
|
|
2607
|
+
);
|
|
2176
2608
|
}
|
|
2177
2609
|
SetupWizard.propTypes = {
|
|
2178
2610
|
issues: import_prop_types17.default.arrayOf(import_prop_types17.default.string),
|