@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.js
CHANGED
|
@@ -1861,256 +1861,272 @@ 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
|
-
{ id: 0, label: "Overview", icon: "\u{1F3E0}" },
|
|
1875
|
-
{ id: 1, label: "1. Data Source", icon: "\u{1F5C4}\uFE0F" },
|
|
1876
|
-
{ id: 2, label: "2. Adapter", icon: "\u{1F504}" },
|
|
1877
|
-
{ id: 3, label: "3. Widget Config", icon: "\u2699\uFE0F" }
|
|
1878
|
-
];
|
|
1879
|
-
async function handleReadTables() {
|
|
1880
|
-
if (!supabase) {
|
|
1881
|
-
setTableError("Supabase client belum tersambung. Pastikan prop supabase sudah diisi.");
|
|
1882
|
-
return;
|
|
1883
|
-
}
|
|
1884
|
-
setLoadingTables(true);
|
|
1885
|
-
setTableError("");
|
|
1886
|
-
try {
|
|
1887
|
-
const { data, error } = await supabase.from("information_schema.tables").select("table_name").eq("table_schema", "public").eq("table_type", "BASE TABLE").order("table_name");
|
|
1888
|
-
if (error) throw error;
|
|
1889
|
-
setTables((data || []).map((r) => r.table_name));
|
|
1890
|
-
} catch (err) {
|
|
1891
|
-
try {
|
|
1892
|
-
const { data: rpcData } = await supabase.rpc("get_tables");
|
|
1893
|
-
if (rpcData) {
|
|
1894
|
-
setTables(rpcData.map((r) => r.table_name || r));
|
|
1895
|
-
} else {
|
|
1896
|
-
setTableError("Tidak dapat membaca tabel. Pastikan RLS mengizinkan akses atau tambahkan policy SELECT untuk anon.");
|
|
1897
|
-
}
|
|
1898
|
-
} catch {
|
|
1899
|
-
setTableError("Gagal membaca tabel dari Supabase: " + ((err == null ? void 0 : err.message) || "Unknown error"));
|
|
1900
|
-
}
|
|
1901
|
-
} finally {
|
|
1902
|
-
setLoadingTables(false);
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
function handleDismiss() {
|
|
1906
|
-
setDismissed(true);
|
|
1907
|
-
if (onDismiss) onDismiss();
|
|
1866
|
+
var PRESET_SIGNATURES = {
|
|
1867
|
+
cidika: {
|
|
1868
|
+
name: "Cidika Travel",
|
|
1869
|
+
tables: ["bookings", "packages", "package_locales"]
|
|
1870
|
+
},
|
|
1871
|
+
tokoSepatu: {
|
|
1872
|
+
name: "Toko Sepatu / E-Commerce",
|
|
1873
|
+
tables: ["orders", "products", "customers"]
|
|
1908
1874
|
}
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1875
|
+
};
|
|
1876
|
+
function detectPreset(tables) {
|
|
1877
|
+
if (!tables || tables.length === 0) return null;
|
|
1878
|
+
const tSet = new Set(tables);
|
|
1879
|
+
if (PRESET_SIGNATURES.cidika.tables.every((t) => tSet.has(t))) return "cidika";
|
|
1880
|
+
if (PRESET_SIGNATURES.tokoSepatu.tables.every((t) => tSet.has(t))) return "tokoSepatu";
|
|
1881
|
+
return null;
|
|
1882
|
+
}
|
|
1883
|
+
function generateCode(mapping) {
|
|
1884
|
+
const {
|
|
1885
|
+
tableName,
|
|
1886
|
+
colDate,
|
|
1887
|
+
colStatus,
|
|
1888
|
+
colTotal,
|
|
1889
|
+
colCustomer,
|
|
1890
|
+
colItem,
|
|
1891
|
+
dashTitle,
|
|
1892
|
+
confirmedValue,
|
|
1893
|
+
pendingValue
|
|
1894
|
+
} = mapping;
|
|
1895
|
+
const safeTitle = dashTitle || "Dashboard";
|
|
1896
|
+
const confirmed = confirmedValue || "confirmed";
|
|
1897
|
+
const pending = pendingValue || "pending";
|
|
1898
|
+
const safeCustomer = colCustomer || "customer_name";
|
|
1899
|
+
const safeItem = colItem || "-";
|
|
1900
|
+
const colTotalFull = colTotal || "total";
|
|
1901
|
+
const dataSource = `// src/datasources/myDashboardSource.js
|
|
1902
|
+
// AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
|
|
1903
|
+
// Tabel: ${tableName}
|
|
1904
|
+
|
|
1905
|
+
export function createMyDashboardSource(supabase) {
|
|
1937
1906
|
return {
|
|
1938
1907
|
async fetchDashboardSnapshot({ fromISO, toISO, statusScope }) {
|
|
1939
|
-
const
|
|
1940
|
-
.from("
|
|
1941
|
-
.select("id,
|
|
1942
|
-
.gte("
|
|
1943
|
-
.lte("
|
|
1944
|
-
.order("
|
|
1908
|
+
const allQuery = supabase
|
|
1909
|
+
.from("${tableName}")
|
|
1910
|
+
.select("id, ${colDate}, ${colStatus}, ${colTotalFull}${colCustomer !== "customer_name" ? `, ${colCustomer}` : ", customer_name"}${colItem !== "-" ? `, ${colItem}` : ""}")
|
|
1911
|
+
.gte("${colDate}", fromISO)
|
|
1912
|
+
.lte("${colDate}", toISO)
|
|
1913
|
+
.order("${colDate}", { ascending: true });
|
|
1945
1914
|
|
|
1946
|
-
const
|
|
1947
|
-
.from("
|
|
1948
|
-
.select("id,
|
|
1949
|
-
.
|
|
1915
|
+
const recentQuery = supabase
|
|
1916
|
+
.from("${tableName}")
|
|
1917
|
+
.select("id, ${colDate}, ${colStatus}, ${colTotalFull}${colCustomer !== "customer_name" ? `, ${colCustomer}` : ", customer_name"}${colItem !== "-" ? `, ${colItem}` : ""}")
|
|
1918
|
+
.gte("${colDate}", fromISO)
|
|
1919
|
+
.lte("${colDate}", toISO)
|
|
1920
|
+
.order("${colDate}", { ascending: false })
|
|
1950
1921
|
.limit(10);
|
|
1951
1922
|
|
|
1923
|
+
if (statusScope && statusScope !== "all") {
|
|
1924
|
+
recentQuery.eq("${colStatus}", statusScope);
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
const [allRes, recentRes] = await Promise.all([allQuery, recentQuery]);
|
|
1928
|
+
|
|
1952
1929
|
return {
|
|
1953
|
-
bookings:
|
|
1954
|
-
recent,
|
|
1930
|
+
bookings: allRes.data || [], // semua transaksi (untuk chart & stats)
|
|
1931
|
+
recent: recentRes.data || [], // 10 terbaru (untuk tabel)
|
|
1955
1932
|
packageLocales: [],
|
|
1956
1933
|
staticCounts: { packages: 0, sections: 0 },
|
|
1957
1934
|
};
|
|
1958
1935
|
},
|
|
1959
1936
|
|
|
1960
|
-
// Opsional: live update
|
|
1961
1937
|
subscribeLiveUpdate(onEvent) {
|
|
1962
|
-
const ch = supabase
|
|
1963
|
-
.
|
|
1964
|
-
|
|
1938
|
+
const ch = supabase
|
|
1939
|
+
.channel("rdb-dashboard-live")
|
|
1940
|
+
.on("postgres_changes", { event: "*", schema: "public", table: "${tableName}" }, onEvent)
|
|
1965
1941
|
.subscribe();
|
|
1966
1942
|
return () => supabase.removeChannel(ch);
|
|
1967
1943
|
},
|
|
1968
1944
|
};
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
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
|
|
1945
|
+
}`;
|
|
1946
|
+
const adapter = `// src/adapters/myDashboardAdapter.js
|
|
1947
|
+
// AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
|
|
1948
|
+
// Mapping kolom: status="${colStatus}", total="${colTotalFull}", tanggal="${colDate}"
|
|
1949
|
+
|
|
1979
1950
|
import { toNumber, buildDayBuckets } from "@rozaqi02/reusable-dashboard";
|
|
1980
1951
|
|
|
1981
|
-
export function
|
|
1952
|
+
export function createEmptyMyDashboardData() {
|
|
1982
1953
|
return {
|
|
1983
|
-
stats:
|
|
1984
|
-
charts: {
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
audienceDistribution: [],
|
|
1988
|
-
topPackages: [],
|
|
1989
|
-
},
|
|
1990
|
-
table: { recentBookings: [] },
|
|
1954
|
+
stats: { bookingsConfirm: 0, revenueConfirm: 0 },
|
|
1955
|
+
charts: { dailyTrends: [], statusDistribution: [],
|
|
1956
|
+
audienceDistribution: [], topPackages: [] },
|
|
1957
|
+
table: { recentBookings: [] },
|
|
1991
1958
|
};
|
|
1992
1959
|
}
|
|
1993
1960
|
|
|
1994
|
-
export function
|
|
1995
|
-
if (!raw) return
|
|
1961
|
+
export function adaptMyDashboardData({ raw, range, dateLocale, labels }) {
|
|
1962
|
+
if (!raw) return createEmptyMyDashboardData();
|
|
1996
1963
|
|
|
1997
|
-
const buckets
|
|
1998
|
-
const dayMap
|
|
1964
|
+
const buckets = buildDayBuckets(range.daysWindow, range.fromISO, dateLocale);
|
|
1965
|
+
const dayMap = new Map(buckets.map(b => [b.dateKey, b]));
|
|
1999
1966
|
const statusMap = new Map();
|
|
2000
1967
|
let confirmed = 0, revenue = 0;
|
|
2001
1968
|
|
|
2002
1969
|
(raw.bookings || []).forEach(row => {
|
|
2003
|
-
const status = String(row
|
|
2004
|
-
const amount = toNumber(row
|
|
2005
|
-
const dayKey = String(row
|
|
1970
|
+
const status = String(row["${colStatus}"] || "pending").toLowerCase();
|
|
1971
|
+
const amount = toNumber(row["${colTotalFull}"]);
|
|
1972
|
+
const dayKey = String(row["${colDate}"] || "").slice(0, 10);
|
|
2006
1973
|
|
|
2007
1974
|
statusMap.set(status, (statusMap.get(status) || 0) + 1);
|
|
2008
1975
|
const bucket = dayMap.get(dayKey);
|
|
2009
|
-
if (bucket
|
|
2010
|
-
bucket.count +=
|
|
2011
|
-
bucket.
|
|
1976
|
+
if (bucket) {
|
|
1977
|
+
if (status === "${confirmed}") { bucket.count++; bucket.revenue += amount; }
|
|
1978
|
+
if (status === "${pending}") { bucket.pendingCount++; }
|
|
2012
1979
|
}
|
|
2013
|
-
if (status === "confirmed") { confirmed++; revenue += amount; }
|
|
1980
|
+
if (status === "${confirmed}") { confirmed++; revenue += amount; }
|
|
2014
1981
|
});
|
|
2015
1982
|
|
|
1983
|
+
const avgRevenue = confirmed > 0 ? Math.round(revenue / confirmed) : 0;
|
|
1984
|
+
const totalTx = (raw.bookings || []).length;
|
|
1985
|
+
const conversionRate = totalTx > 0 ? Math.round((confirmed / totalTx) * 100) : 0;
|
|
1986
|
+
|
|
2016
1987
|
return {
|
|
2017
|
-
stats: {
|
|
1988
|
+
stats: {
|
|
1989
|
+
bookingsConfirm: confirmed,
|
|
1990
|
+
revenueConfirm: revenue,
|
|
1991
|
+
avgRevenue,
|
|
1992
|
+
conversionRate,
|
|
1993
|
+
},
|
|
2018
1994
|
charts: {
|
|
2019
1995
|
dailyTrends: buckets,
|
|
2020
1996
|
statusDistribution: Array.from(statusMap.entries())
|
|
1997
|
+
.sort((a, b) => b[1] - a[1])
|
|
2021
1998
|
.map(([status, count]) => ({
|
|
2022
|
-
status,
|
|
1999
|
+
status, count,
|
|
2023
2000
|
label: labels?.formatStatusLabel?.(status) || status,
|
|
2024
|
-
count,
|
|
2025
2001
|
})),
|
|
2026
2002
|
audienceDistribution: [],
|
|
2027
2003
|
topPackages: [],
|
|
2028
2004
|
},
|
|
2029
2005
|
table: {
|
|
2030
2006
|
recentBookings: (raw.recent || []).map(row => ({
|
|
2031
|
-
id:
|
|
2032
|
-
createdAt:
|
|
2033
|
-
customerName: row
|
|
2034
|
-
packageName: row
|
|
2007
|
+
id: row.id,
|
|
2008
|
+
createdAt: row["${colDate}"],
|
|
2009
|
+
customerName: row["${colCustomer}"] || "-",
|
|
2010
|
+
packageName: ${colItem !== "-" ? `row["${colItem}"] || "-"` : '"-"'},
|
|
2035
2011
|
audienceLabel: "-",
|
|
2036
|
-
totalIDR:
|
|
2037
|
-
status:
|
|
2038
|
-
statusLabel:
|
|
2012
|
+
totalIDR: toNumber(row["${colTotalFull}"]),
|
|
2013
|
+
status: String(row["${colStatus}"] || "pending").toLowerCase(),
|
|
2014
|
+
statusLabel: labels?.formatStatusLabel?.(row["${colStatus}"]) || row["${colStatus}"],
|
|
2039
2015
|
})),
|
|
2040
2016
|
},
|
|
2041
2017
|
};
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
ReusableDashboardView,
|
|
2046
|
-
useReusableDashboard,
|
|
2047
|
-
createDashboardConfig,
|
|
2048
|
-
} from "@rozaqi02/reusable-dashboard";
|
|
2018
|
+
}`;
|
|
2019
|
+
const widgetConfig = `// src/config/myDashboardConfig.js
|
|
2020
|
+
// AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
|
|
2049
2021
|
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2022
|
+
export const myDashboardConfig = {
|
|
2023
|
+
id: "my.custom.dashboard",
|
|
2024
|
+
defaultFilters: {
|
|
2025
|
+
statusScope: "${confirmed}",
|
|
2026
|
+
daysPreset: 30,
|
|
2027
|
+
sortPkgBy: "bookings",
|
|
2028
|
+
sortPkgDir: "desc",
|
|
2029
|
+
},
|
|
2057
2030
|
widgets: {
|
|
2058
2031
|
stats: [
|
|
2059
|
-
{ id: "orders",
|
|
2060
|
-
valueKey: "bookingsConfirm", format: "number",
|
|
2061
|
-
{ id: "revenue",
|
|
2062
|
-
valueKey: "revenueConfirm",
|
|
2032
|
+
{ id: "orders", label: "confirmedBookings", icon: "TrendingUp",
|
|
2033
|
+
valueKey: "bookingsConfirm", format: "number", accentColor: "blue" },
|
|
2034
|
+
{ id: "revenue", label: "confirmedRevenue", icon: "DollarSign",
|
|
2035
|
+
valueKey: "revenueConfirm", format: "currency", accentColor: "green" },
|
|
2036
|
+
{ id: "avg", label: "avgRevenue", icon: "Users",
|
|
2037
|
+
valueKey: "avgRevenue", format: "currency", accentColor: "violet" },
|
|
2038
|
+
{ id: "conversion", label: "conversionRate", icon: "PieChart",
|
|
2039
|
+
valueKey: "conversionRate", format: "percent", accentColor: "orange" },
|
|
2063
2040
|
],
|
|
2064
2041
|
charts: [
|
|
2065
|
-
{ id: "trend",
|
|
2042
|
+
{ id: "trend", type: "dailyArea", label: "dailyTrends", icon: "BarChart3" },
|
|
2066
2043
|
{ id: "status", type: "statusPie", label: "statusDistribution", icon: "PieChart" },
|
|
2067
2044
|
],
|
|
2068
2045
|
table: {
|
|
2069
|
-
id: "
|
|
2046
|
+
id: "recentTx", label: "recentBookings", icon: "Calendar",
|
|
2070
2047
|
emptyLabel: "noRecentBookings",
|
|
2071
2048
|
columns: [
|
|
2072
|
-
{ id: "date",
|
|
2073
|
-
{ id: "customer", label: "customer", accessor: "customerName" }
|
|
2074
|
-
{ id: "
|
|
2075
|
-
{ id: "
|
|
2049
|
+
{ id: "date", label: "date", accessor: "createdAt", type: "date" },
|
|
2050
|
+
{ id: "customer", label: "customer", accessor: "customerName" },${colItem !== "-" ? `
|
|
2051
|
+
{ id: "item", label: "package", accessor: "packageName" },` : ""}
|
|
2052
|
+
{ id: "total", label: "total", accessor: "totalIDR", type: "currency" },
|
|
2053
|
+
{ id: "status", label: "status", accessor: "statusLabel",
|
|
2076
2054
|
type: "statusBadge", statusAccessor: "status" },
|
|
2077
2055
|
],
|
|
2078
2056
|
},
|
|
2079
2057
|
},
|
|
2080
|
-
}
|
|
2058
|
+
};`;
|
|
2059
|
+
const dashboard = `// src/pages/admin/Dashboard.jsx
|
|
2060
|
+
// AUTO-GENERATED oleh Setup Wizard @rozaqi02/reusable-dashboard
|
|
2061
|
+
// Salin file ini ke halaman dashboard kamu.
|
|
2081
2062
|
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2063
|
+
import React from "react";
|
|
2064
|
+
import { supabase } from "../../lib/supabaseClient.js";
|
|
2065
|
+
import {
|
|
2066
|
+
ReusableDashboardView,
|
|
2067
|
+
useReusableDashboard,
|
|
2068
|
+
createDashboardConfig,
|
|
2069
|
+
} from "@rozaqi02/reusable-dashboard";
|
|
2070
|
+
|
|
2071
|
+
// Import 3 file yang digenerate wizard
|
|
2072
|
+
import { myDashboardConfig } from "./myDashboardConfig";
|
|
2073
|
+
import { createMyDashboardSource } from "./myDashboardSource";
|
|
2074
|
+
import { adaptMyDashboardData, createEmptyMyDashboardData } from "./myDashboardAdapter";
|
|
2075
|
+
|
|
2076
|
+
// Data source \u2014 dibuat sekali di luar komponen
|
|
2077
|
+
const source = createMyDashboardSource(supabase);
|
|
2091
2078
|
|
|
2092
|
-
//
|
|
2093
|
-
const labels = {
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2079
|
+
// Label UI \u2014 sesuaikan teks dengan bahasa/konteks bisnis kamu
|
|
2080
|
+
const labels = {
|
|
2081
|
+
title: "${safeTitle}",
|
|
2082
|
+
refresh: "Refresh",
|
|
2083
|
+
liveUpdate: "Live update",
|
|
2084
|
+
loadFailed: "Gagal memuat data.",
|
|
2085
|
+
retry: "Coba Lagi",
|
|
2086
|
+
confirmedOnly: "Selesai",
|
|
2087
|
+
pendingOnly: "Proses",
|
|
2088
|
+
allStatus: "Semua Status",
|
|
2089
|
+
showPendingOverlay: "Tampilkan pending",
|
|
2090
|
+
reset: "Reset",
|
|
2091
|
+
confirmedBookings: "Total Transaksi",
|
|
2092
|
+
confirmedRevenue: "Total Pendapatan",
|
|
2093
|
+
avgRevenue: "Rata-rata / Transaksi",
|
|
2094
|
+
conversionRate: "Conversion Rate",
|
|
2095
|
+
dailyTrends: "Tren Harian",
|
|
2096
|
+
statusDistribution: "Distribusi Status",
|
|
2097
|
+
recentBookings: "Transaksi Terbaru",
|
|
2098
|
+
noRecentBookings: "Belum ada transaksi",
|
|
2099
|
+
date: "Tanggal",
|
|
2100
|
+
customer: "Pelanggan",
|
|
2101
|
+
package: "Item",
|
|
2102
|
+
total: "Total",
|
|
2103
|
+
status: "Status",
|
|
2104
|
+
bookingsMetric: "Transaksi",
|
|
2105
|
+
revenueMetric: "Pendapatan",
|
|
2106
|
+
confirmedBookingMetric: "Transaksi (Selesai)",
|
|
2107
|
+
confirmedRevenueMetric: "Pendapatan (Selesai)",
|
|
2102
2108
|
dayLabel: n => n + " hari",
|
|
2103
2109
|
formatStatusLabel: s =>
|
|
2104
|
-
({ confirmed: "Selesai", pending: "Proses"
|
|
2110
|
+
({ "${confirmed}": "Selesai", "${pending}": "Proses" })[s] || (s || "-"),
|
|
2105
2111
|
formatAudienceLabel: v => v || "-",
|
|
2106
2112
|
};
|
|
2107
2113
|
|
|
2108
|
-
//
|
|
2109
|
-
|
|
2110
|
-
|
|
2114
|
+
// Kemas semua jadi 1 objek
|
|
2115
|
+
const dashboardConfig = createDashboardConfig({
|
|
2116
|
+
widgetConfig: myDashboardConfig,
|
|
2117
|
+
dataSource: source,
|
|
2118
|
+
adapter: adaptMyDashboardData,
|
|
2119
|
+
createEmptyState: createEmptyMyDashboardData,
|
|
2120
|
+
languageCode: "id",
|
|
2121
|
+
dateLocale: "id-ID",
|
|
2122
|
+
labels,
|
|
2123
|
+
});
|
|
2124
|
+
|
|
2125
|
+
export default function Dashboard() {
|
|
2126
|
+
const state = useReusableDashboard({ ...dashboardConfig, labels });
|
|
2111
2127
|
return (
|
|
2112
2128
|
<ReusableDashboardView
|
|
2113
|
-
config={
|
|
2129
|
+
config={dashboardConfig.config}
|
|
2114
2130
|
labels={labels}
|
|
2115
2131
|
loading={state.loading}
|
|
2116
2132
|
error={state.error}
|
|
@@ -2119,11 +2135,427 @@ export default function MyDashboard() {
|
|
|
2119
2135
|
onResetFilters={state.resetFilters}
|
|
2120
2136
|
onRefresh={state.refresh}
|
|
2121
2137
|
data={state.data}
|
|
2122
|
-
dateLocale={
|
|
2138
|
+
dateLocale={dashboardConfig.dateLocale}
|
|
2123
2139
|
liveUpdateEnabled={state.liveUpdateEnabled}
|
|
2140
|
+
supabase={supabase}
|
|
2141
|
+
dashboardConfig={dashboardConfig}
|
|
2124
2142
|
/>
|
|
2125
2143
|
);
|
|
2126
|
-
}
|
|
2144
|
+
}`;
|
|
2145
|
+
return { dataSource, adapter, widgetConfig, dashboard };
|
|
2146
|
+
}
|
|
2147
|
+
function SetupWizard({ issues = [], onDismiss, supabase }) {
|
|
2148
|
+
const [dismissed, setDismissed] = useState6(false);
|
|
2149
|
+
const [step, setStep] = useState6(0);
|
|
2150
|
+
const [detecting, setDetecting] = useState6(true);
|
|
2151
|
+
const [tables, setTables] = useState6([]);
|
|
2152
|
+
const [columns, setColumns] = useState6({});
|
|
2153
|
+
const [supabaseOk, setSupabaseOk] = useState6(false);
|
|
2154
|
+
const [detectionDone, setDetectionDone] = useState6(false);
|
|
2155
|
+
const [userHasDashboard, setUserHasDashboard] = useState6(null);
|
|
2156
|
+
const [selectedTable, setSelectedTable] = useState6("");
|
|
2157
|
+
const [colDate, setColDate] = useState6("");
|
|
2158
|
+
const [colStatus, setColStatus] = useState6("");
|
|
2159
|
+
const [colTotal, setColTotal] = useState6("");
|
|
2160
|
+
const [colCustomer, setColCustomer] = useState6("");
|
|
2161
|
+
const [colItem, setColItem] = useState6("");
|
|
2162
|
+
const [confirmedVal, setConfirmedVal] = useState6("confirmed");
|
|
2163
|
+
const [pendingVal, setPendingVal] = useState6("pending");
|
|
2164
|
+
const [dashTitle, setDashTitle] = useState6("Dashboard");
|
|
2165
|
+
const [loadingColumns, setLoadingColumns] = useState6(false);
|
|
2166
|
+
const [generatedCode, setGeneratedCode] = useState6(null);
|
|
2167
|
+
const [activeCodeTab, setActiveCodeTab] = useState6("dataSource");
|
|
2168
|
+
const [copied, setCopied] = useState6("");
|
|
2169
|
+
useEffect3(() => {
|
|
2170
|
+
if (!supabase) {
|
|
2171
|
+
setDetecting(false);
|
|
2172
|
+
setDetectionDone(true);
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
(async () => {
|
|
2176
|
+
try {
|
|
2177
|
+
const { data: rpcData, error: rpcErr } = await supabase.rpc("rdb_get_tables");
|
|
2178
|
+
if (!rpcErr && Array.isArray(rpcData)) {
|
|
2179
|
+
setTables(rpcData.map((r) => typeof r === "string" ? r : r.table_name || r.name || r));
|
|
2180
|
+
setSupabaseOk(true);
|
|
2181
|
+
} else {
|
|
2182
|
+
const { data: schemaData, error: schemaErr } = await supabase.from("information_schema.tables").select("table_name").eq("table_schema", "public").eq("table_type", "BASE TABLE").order("table_name");
|
|
2183
|
+
if (!schemaErr && schemaData) {
|
|
2184
|
+
setTables(schemaData.map((r) => r.table_name));
|
|
2185
|
+
setSupabaseOk(true);
|
|
2186
|
+
} else {
|
|
2187
|
+
const { error: pingErr } = await supabase.from("_rdb_ping_").select("*").limit(1);
|
|
2188
|
+
const connected = !pingErr || pingErr.code === "42P01" || pingErr.code === "PGRST116" || (pingErr.message || "").includes("does not exist");
|
|
2189
|
+
setSupabaseOk(connected);
|
|
2190
|
+
setTables([]);
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
} catch {
|
|
2194
|
+
setSupabaseOk(false);
|
|
2195
|
+
} finally {
|
|
2196
|
+
setDetecting(false);
|
|
2197
|
+
setDetectionDone(true);
|
|
2198
|
+
}
|
|
2199
|
+
})();
|
|
2200
|
+
}, [supabase]);
|
|
2201
|
+
const loadColumns = useCallback5(async (tbl) => {
|
|
2202
|
+
if (!supabase || !tbl || columns[tbl]) return;
|
|
2203
|
+
setLoadingColumns(true);
|
|
2204
|
+
try {
|
|
2205
|
+
const { data: rpcCols, error: rpcErr } = await supabase.rpc("rdb_get_columns", { p_table: tbl });
|
|
2206
|
+
if (!rpcErr && Array.isArray(rpcCols)) {
|
|
2207
|
+
const colObjs = rpcCols.map(
|
|
2208
|
+
(c) => typeof c === "string" ? { column_name: c, data_type: "unknown" } : { column_name: c.column_name || c.name || c, data_type: c.data_type || "unknown" }
|
|
2209
|
+
);
|
|
2210
|
+
setColumns((prev) => ({ ...prev, [tbl]: colObjs }));
|
|
2211
|
+
autoDetectCols(colObjs);
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
const { data: schemaCols, error: schemaErr } = await supabase.from("information_schema.columns").select("column_name, data_type").eq("table_schema", "public").eq("table_name", tbl).order("ordinal_position");
|
|
2215
|
+
if (!schemaErr && schemaCols) {
|
|
2216
|
+
setColumns((prev) => ({ ...prev, [tbl]: schemaCols }));
|
|
2217
|
+
autoDetectCols(schemaCols);
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
const { data: sampleRow, error: sampleErr } = await supabase.from(tbl).select("*").limit(1);
|
|
2221
|
+
if (!sampleErr && sampleRow && sampleRow.length > 0) {
|
|
2222
|
+
const colObjs = Object.keys(sampleRow[0]).map((k) => ({
|
|
2223
|
+
column_name: k,
|
|
2224
|
+
data_type: typeof sampleRow[0][k]
|
|
2225
|
+
}));
|
|
2226
|
+
setColumns((prev) => ({ ...prev, [tbl]: colObjs }));
|
|
2227
|
+
autoDetectCols(colObjs);
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
setColumns((prev) => ({ ...prev, [tbl]: [] }));
|
|
2231
|
+
} catch {
|
|
2232
|
+
setColumns((prev) => ({ ...prev, [tbl]: [] }));
|
|
2233
|
+
} finally {
|
|
2234
|
+
setLoadingColumns(false);
|
|
2235
|
+
}
|
|
2236
|
+
}, [supabase, columns]);
|
|
2237
|
+
function autoDetectCols(colObjs) {
|
|
2238
|
+
const find = (candidates) => {
|
|
2239
|
+
var _a;
|
|
2240
|
+
return ((_a = colObjs.find((c) => candidates.includes(c.column_name.toLowerCase()))) == null ? void 0 : _a.column_name) || "";
|
|
2241
|
+
};
|
|
2242
|
+
setColDate(find(["created_at", "tanggal", "date", "transaction_date", "order_date", "waktu"]));
|
|
2243
|
+
setColStatus(find(["status", "state", "kondisi", "order_status", "payment_status"]));
|
|
2244
|
+
setColTotal(find(["total", "total_idr", "total_amount", "total_price", "harga_total", "amount", "nominal", "harga", "biaya"]));
|
|
2245
|
+
setColCustomer(find(["customer_name", "nama_pelanggan", "nama", "name", "client_name", "buyer_name", "pelanggan"]));
|
|
2246
|
+
setColItem(find(["service_type", "item_name", "product_name", "package_name", "layanan", "produk", "nama_layanan", "jenis", "nama_produk"]));
|
|
2247
|
+
}
|
|
2248
|
+
function copyCode(text, label) {
|
|
2249
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
2250
|
+
setCopied(label);
|
|
2251
|
+
setTimeout(() => setCopied(""), 2e3);
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
function handleDismiss() {
|
|
2255
|
+
setDismissed(true);
|
|
2256
|
+
if (onDismiss) onDismiss();
|
|
2257
|
+
}
|
|
2258
|
+
function handleGenerate() {
|
|
2259
|
+
if (!selectedTable || !colDate || !colStatus || !colTotal) return;
|
|
2260
|
+
const code = generateCode({
|
|
2261
|
+
tableName: selectedTable,
|
|
2262
|
+
colDate,
|
|
2263
|
+
colStatus,
|
|
2264
|
+
colTotal,
|
|
2265
|
+
colCustomer: colCustomer || "customer_name",
|
|
2266
|
+
colItem: colItem || "-",
|
|
2267
|
+
dashTitle,
|
|
2268
|
+
confirmedValue: confirmedVal,
|
|
2269
|
+
pendingValue: pendingVal
|
|
2270
|
+
});
|
|
2271
|
+
setGeneratedCode(code);
|
|
2272
|
+
setStep(3);
|
|
2273
|
+
}
|
|
2274
|
+
if (dismissed) return null;
|
|
2275
|
+
const detectedPreset = detectPreset(tables);
|
|
2276
|
+
const tableColumns = selectedTable ? columns[selectedTable] || [] : [];
|
|
2277
|
+
const colNames = tableColumns.map((c) => c.column_name);
|
|
2278
|
+
const mappingValid = selectedTable && colDate && colStatus && colTotal;
|
|
2279
|
+
const stepLabels = ["Deteksi", "Pilih Tabel", "Mapping Kolom", "Kode Siap Pakai"];
|
|
2280
|
+
return /* @__PURE__ */ React17.createElement(
|
|
2281
|
+
"div",
|
|
2282
|
+
{
|
|
2283
|
+
className: "rdb-wizard-overlay",
|
|
2284
|
+
role: "dialog",
|
|
2285
|
+
"aria-modal": "true",
|
|
2286
|
+
"aria-label": "Setup Wizard @rozaqi02/reusable-dashboard"
|
|
2287
|
+
},
|
|
2288
|
+
/* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-modal" }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-header" }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-header-title" }, /* @__PURE__ */ React17.createElement("span", { className: "rdb-wizard-logo" }, "\u{1F6E0}\uFE0F"), /* @__PURE__ */ React17.createElement("div", null, /* @__PURE__ */ React17.createElement("div", { className: "rdb-h2", style: { margin: 0 } }, "Setup Dashboard"), /* @__PURE__ */ React17.createElement("div", { className: "rdb-caption" }, detecting ? "Mendeteksi kondisi project\u2026" : supabaseOk ? `Supabase tersambung \xB7 ${tables.length} tabel ditemukan` : "Panduan konfigurasi @rozaqi02/reusable-dashboard"))), /* @__PURE__ */ React17.createElement(
|
|
2289
|
+
"button",
|
|
2290
|
+
{
|
|
2291
|
+
type: "button",
|
|
2292
|
+
className: "rdb-wizard-close",
|
|
2293
|
+
onClick: handleDismiss,
|
|
2294
|
+
title: "Tutup"
|
|
2295
|
+
},
|
|
2296
|
+
"\u2715"
|
|
2297
|
+
)), /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-steps" }, stepLabels.map((label, i) => /* @__PURE__ */ React17.createElement(
|
|
2298
|
+
"button",
|
|
2299
|
+
{
|
|
2300
|
+
key: i,
|
|
2301
|
+
type: "button",
|
|
2302
|
+
className: `rdb-wizard-step-btn ${step === i ? "rdb-wizard-step-active" : ""}`,
|
|
2303
|
+
onClick: () => {
|
|
2304
|
+
if (i < step || i === step + 1 && step < 2) setStep(i);
|
|
2305
|
+
}
|
|
2306
|
+
},
|
|
2307
|
+
/* @__PURE__ */ React17.createElement("span", null, step > i ? "\u2705" : i + 1, "."),
|
|
2308
|
+
/* @__PURE__ */ React17.createElement("span", null, label)
|
|
2309
|
+
))), /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-body" }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-section" }, step === 0 && /* @__PURE__ */ React17.createElement(React17.Fragment, null, issues.length > 0 && /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-issues", style: { marginBottom: 12 } }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 6 } }, "Konfigurasi belum lengkap:"), issues.map((iss, i) => /* @__PURE__ */ React17.createElement("div", { key: i, className: "rdb-wizard-issue-item" }, /* @__PURE__ */ React17.createElement("span", null, "\u274C"), /* @__PURE__ */ React17.createElement("span", { className: "rdb-body" }, iss)))), /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-flow", style: { marginBottom: 12 } }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 10 } }, "\u{1F50D} Hasil deteksi otomatis:"), detecting ? /* @__PURE__ */ React17.createElement("div", { className: "rdb-body", style: { color: "var(--rdb-text-muted)" } }, "Mendeteksi\u2026") : /* @__PURE__ */ React17.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-issue-item" }, /* @__PURE__ */ React17.createElement("span", null, supabaseOk ? "\u2705" : "\u274C"), /* @__PURE__ */ React17.createElement("span", { className: "rdb-body" }, "Supabase ", supabaseOk ? "tersambung" : "tidak tersambung \u2014 pastikan supabaseClient.js sudah dikonfigurasi dan prop supabase dikirim ke ReusableDashboardView")), supabaseOk && /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-issue-item" }, /* @__PURE__ */ React17.createElement("span", null, tables.length > 0 ? "\u2705" : "\u26A0\uFE0F"), /* @__PURE__ */ React17.createElement("span", { className: "rdb-body" }, tables.length > 0 ? `${tables.length} tabel public ditemukan` : "Daftar tabel tidak bisa dibaca otomatis (RLS aktif) \u2014 kamu bisa ketik nama tabel di step berikutnya")), supabaseOk && tables.length > 0 && detectedPreset && /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-issue-item" }, /* @__PURE__ */ React17.createElement("span", null, "\u2705"), /* @__PURE__ */ React17.createElement("span", { className: "rdb-body" }, "Tabel cocok dengan preset ", /* @__PURE__ */ React17.createElement("strong", null, PRESET_SIGNATURES[detectedPreset].name))))), !supabaseOk && detectionDone && /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-alert", style: { marginBottom: 12 } }, /* @__PURE__ */ React17.createElement("span", { className: "rdb-wizard-alert-icon" }, "\u2139\uFE0F"), /* @__PURE__ */ React17.createElement("div", null, /* @__PURE__ */ React17.createElement("div", { className: "rdb-body", style: { fontWeight: 600 } }, "Aktifkan koneksi Supabase"), /* @__PURE__ */ React17.createElement("div", { className: "rdb-caption", style: { marginTop: 4 } }, "Tambahkan prop ke ReusableDashboardView:", " ", /* @__PURE__ */ React17.createElement("code", { className: "rdb-wizard-code-inline" }, "supabase=", "{supabase}")))), /* @__PURE__ */ React17.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" } }, /* @__PURE__ */ React17.createElement(
|
|
2310
|
+
"button",
|
|
2311
|
+
{
|
|
2312
|
+
type: "button",
|
|
2313
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2314
|
+
onClick: handleDismiss
|
|
2315
|
+
},
|
|
2316
|
+
"Lanjutkan tanpa wizard"
|
|
2317
|
+
), supabaseOk && /* @__PURE__ */ React17.createElement(
|
|
2318
|
+
"button",
|
|
2319
|
+
{
|
|
2320
|
+
type: "button",
|
|
2321
|
+
className: "rdb-btn rdb-btn-primary rdb-btn-sm",
|
|
2322
|
+
onClick: () => setStep(1)
|
|
2323
|
+
},
|
|
2324
|
+
"Lanjut \u2192 Pilih Tabel"
|
|
2325
|
+
))), step === 1 && /* @__PURE__ */ React17.createElement(React17.Fragment, null, /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-step-title" }, /* @__PURE__ */ React17.createElement("span", { className: "rdb-wizard-step-num" }, "2"), /* @__PURE__ */ React17.createElement("span", { className: "rdb-h3", style: { margin: 0 } }, "Pilih Tabel Transaksi")), /* @__PURE__ */ React17.createElement("div", { className: "rdb-body", style: { color: "var(--rdb-text-muted)", marginBottom: 16 } }, "Pilih tabel yang berisi data transaksi bisnis kamu."), tables.length > 0 && /* @__PURE__ */ React17.createElement(React17.Fragment, null, /* @__PURE__ */ React17.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 8 } }, "Tabel yang ditemukan:"), /* @__PURE__ */ React17.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6, marginBottom: 16 } }, tables.map((tbl) => /* @__PURE__ */ React17.createElement(
|
|
2326
|
+
"button",
|
|
2327
|
+
{
|
|
2328
|
+
key: tbl,
|
|
2329
|
+
type: "button",
|
|
2330
|
+
onClick: () => {
|
|
2331
|
+
setSelectedTable(tbl);
|
|
2332
|
+
loadColumns(tbl);
|
|
2333
|
+
},
|
|
2334
|
+
className: `rdb-btn rdb-btn-sm ${selectedTable === tbl ? "rdb-btn-primary" : "rdb-btn-secondary"}`,
|
|
2335
|
+
style: { justifyContent: "flex-start", fontFamily: "monospace" }
|
|
2336
|
+
},
|
|
2337
|
+
selectedTable === tbl ? "\u2705 " : "\u25CB ",
|
|
2338
|
+
tbl
|
|
2339
|
+
)))), tables.length === 0 && /* @__PURE__ */ React17.createElement("div", { style: { marginBottom: 16 } }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-alert", style: { marginBottom: 12 } }, /* @__PURE__ */ React17.createElement("span", { className: "rdb-wizard-alert-icon" }, "\u2139\uFE0F"), /* @__PURE__ */ React17.createElement("div", null, /* @__PURE__ */ React17.createElement("div", { className: "rdb-body", style: { fontWeight: 600 } }, "Daftar tabel tidak bisa terbaca otomatis"), /* @__PURE__ */ React17.createElement("div", { className: "rdb-caption", style: { marginTop: 4 } }, "Ini normal \u2014 Supabase memblokir akses ke ", /* @__PURE__ */ React17.createElement("code", null, "information_schema"), " via API publik. Ketik nama tabel secara manual di bawah."))), /* @__PURE__ */ React17.createElement("label", { className: "rdb-label" }, "Nama tabel transaksi kamu"), /* @__PURE__ */ React17.createElement("div", { style: { display: "flex", gap: 8 } }, /* @__PURE__ */ React17.createElement(
|
|
2340
|
+
"input",
|
|
2341
|
+
{
|
|
2342
|
+
className: "rdb-input",
|
|
2343
|
+
value: selectedTable,
|
|
2344
|
+
onChange: (e) => setSelectedTable(e.target.value),
|
|
2345
|
+
placeholder: "cth: orders, transaksi, bookings, penjualan",
|
|
2346
|
+
style: { maxWidth: 320 }
|
|
2347
|
+
}
|
|
2348
|
+
), /* @__PURE__ */ React17.createElement(
|
|
2349
|
+
"button",
|
|
2350
|
+
{
|
|
2351
|
+
type: "button",
|
|
2352
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2353
|
+
disabled: !selectedTable || loadingColumns,
|
|
2354
|
+
onClick: () => loadColumns(selectedTable)
|
|
2355
|
+
},
|
|
2356
|
+
loadingColumns ? "Membaca\u2026" : "Baca kolom"
|
|
2357
|
+
))), tables.length > 0 && !selectedTable && /* @__PURE__ */ React17.createElement("div", { className: "rdb-caption", style: { color: "var(--rdb-text-muted)", marginBottom: 12 } }, "Atau ketik nama tabel secara manual:", /* @__PURE__ */ React17.createElement(
|
|
2358
|
+
"input",
|
|
2359
|
+
{
|
|
2360
|
+
className: "rdb-input",
|
|
2361
|
+
style: { marginTop: 6, maxWidth: 280 },
|
|
2362
|
+
placeholder: "nama tabel lain",
|
|
2363
|
+
onChange: (e) => {
|
|
2364
|
+
setSelectedTable(e.target.value);
|
|
2365
|
+
if (e.target.value) loadColumns(e.target.value);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
)), selectedTable && /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-tip", style: { marginBottom: 12 } }, "Tabel ", /* @__PURE__ */ React17.createElement("strong", null, selectedTable), " dipilih.", loadingColumns ? " Membaca kolom\u2026" : tableColumns.length > 0 ? ` ${tableColumns.length} kolom ditemukan \u2014 kolom sudah ter-auto-detect di step berikutnya.` : tableColumns.length === 0 && !loadingColumns && columns[selectedTable] !== void 0 ? " Tabel kosong atau tidak ada kolom terbaca \u2014 kamu bisa ketik nama kolom manual di step berikutnya." : ""), /* @__PURE__ */ React17.createElement("div", { style: { marginBottom: 12 } }, /* @__PURE__ */ React17.createElement("label", { className: "rdb-label" }, "Judul dashboard (opsional)"), /* @__PURE__ */ React17.createElement(
|
|
2369
|
+
"input",
|
|
2370
|
+
{
|
|
2371
|
+
className: "rdb-input",
|
|
2372
|
+
value: dashTitle,
|
|
2373
|
+
style: { maxWidth: 280 },
|
|
2374
|
+
onChange: (e) => setDashTitle(e.target.value),
|
|
2375
|
+
placeholder: "cth: Dashboard Laundry Bersih"
|
|
2376
|
+
}
|
|
2377
|
+
)), /* @__PURE__ */ React17.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" } }, /* @__PURE__ */ React17.createElement(
|
|
2378
|
+
"button",
|
|
2379
|
+
{
|
|
2380
|
+
type: "button",
|
|
2381
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2382
|
+
onClick: () => setStep(0)
|
|
2383
|
+
},
|
|
2384
|
+
"\u2190 Kembali"
|
|
2385
|
+
), /* @__PURE__ */ React17.createElement(
|
|
2386
|
+
"button",
|
|
2387
|
+
{
|
|
2388
|
+
type: "button",
|
|
2389
|
+
className: "rdb-btn rdb-btn-primary rdb-btn-sm",
|
|
2390
|
+
disabled: !selectedTable || loadingColumns,
|
|
2391
|
+
onClick: () => setStep(2)
|
|
2392
|
+
},
|
|
2393
|
+
"Lanjut \u2192 Mapping Kolom"
|
|
2394
|
+
))), step === 2 && /* @__PURE__ */ React17.createElement(React17.Fragment, null, /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-step-title" }, /* @__PURE__ */ React17.createElement("span", { className: "rdb-wizard-step-num" }, "3"), /* @__PURE__ */ React17.createElement("span", { className: "rdb-h3", style: { margin: 0 } }, "Mapping Kolom \u2014 Tabel: ", /* @__PURE__ */ React17.createElement("code", { style: { fontSize: "0.9rem" } }, selectedTable))), /* @__PURE__ */ React17.createElement("div", { className: "rdb-body", style: { color: "var(--rdb-text-muted)", marginBottom: 16 } }, "Pilih kolom yang berperan sebagai masing-masing data. Kolom sudah ter-deteksi otomatis \u2014 cukup verifikasi dan sesuaikan jika perlu."), /* @__PURE__ */ React17.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 16 } }, /* @__PURE__ */ React17.createElement("div", null, /* @__PURE__ */ React17.createElement("label", { className: "rdb-label" }, "Kolom Tanggal ", /* @__PURE__ */ React17.createElement("span", { style: { color: "red" } }, "*")), colNames.length > 0 ? /* @__PURE__ */ React17.createElement(
|
|
2395
|
+
"select",
|
|
2396
|
+
{
|
|
2397
|
+
className: "rdb-select",
|
|
2398
|
+
value: colDate,
|
|
2399
|
+
onChange: (e) => setColDate(e.target.value)
|
|
2400
|
+
},
|
|
2401
|
+
/* @__PURE__ */ React17.createElement("option", { value: "" }, "\u2014 pilih kolom \u2014"),
|
|
2402
|
+
colNames.map((c) => /* @__PURE__ */ React17.createElement("option", { key: c, value: c }, c))
|
|
2403
|
+
) : /* @__PURE__ */ React17.createElement(
|
|
2404
|
+
"input",
|
|
2405
|
+
{
|
|
2406
|
+
className: "rdb-input",
|
|
2407
|
+
value: colDate,
|
|
2408
|
+
onChange: (e) => setColDate(e.target.value),
|
|
2409
|
+
placeholder: "cth: created_at"
|
|
2410
|
+
}
|
|
2411
|
+
), /* @__PURE__ */ React17.createElement("div", { className: "rdb-caption", style: { marginTop: 2 } }, "Tipe timestamp/date")), /* @__PURE__ */ React17.createElement("div", null, /* @__PURE__ */ React17.createElement("label", { className: "rdb-label" }, "Kolom Status ", /* @__PURE__ */ React17.createElement("span", { style: { color: "red" } }, "*")), colNames.length > 0 ? /* @__PURE__ */ React17.createElement(
|
|
2412
|
+
"select",
|
|
2413
|
+
{
|
|
2414
|
+
className: "rdb-select",
|
|
2415
|
+
value: colStatus,
|
|
2416
|
+
onChange: (e) => setColStatus(e.target.value)
|
|
2417
|
+
},
|
|
2418
|
+
/* @__PURE__ */ React17.createElement("option", { value: "" }, "\u2014 pilih kolom \u2014"),
|
|
2419
|
+
colNames.map((c) => /* @__PURE__ */ React17.createElement("option", { key: c, value: c }, c))
|
|
2420
|
+
) : /* @__PURE__ */ React17.createElement(
|
|
2421
|
+
"input",
|
|
2422
|
+
{
|
|
2423
|
+
className: "rdb-input",
|
|
2424
|
+
value: colStatus,
|
|
2425
|
+
onChange: (e) => setColStatus(e.target.value),
|
|
2426
|
+
placeholder: "cth: status"
|
|
2427
|
+
}
|
|
2428
|
+
), /* @__PURE__ */ React17.createElement("div", { className: "rdb-caption", style: { marginTop: 2 } }, "Nilai selesai/pending")), /* @__PURE__ */ React17.createElement("div", null, /* @__PURE__ */ React17.createElement("label", { className: "rdb-label" }, "Kolom Total (uang) ", /* @__PURE__ */ React17.createElement("span", { style: { color: "red" } }, "*")), colNames.length > 0 ? /* @__PURE__ */ React17.createElement(
|
|
2429
|
+
"select",
|
|
2430
|
+
{
|
|
2431
|
+
className: "rdb-select",
|
|
2432
|
+
value: colTotal,
|
|
2433
|
+
onChange: (e) => setColTotal(e.target.value)
|
|
2434
|
+
},
|
|
2435
|
+
/* @__PURE__ */ React17.createElement("option", { value: "" }, "\u2014 pilih kolom \u2014"),
|
|
2436
|
+
colNames.map((c) => /* @__PURE__ */ React17.createElement("option", { key: c, value: c }, c))
|
|
2437
|
+
) : /* @__PURE__ */ React17.createElement(
|
|
2438
|
+
"input",
|
|
2439
|
+
{
|
|
2440
|
+
className: "rdb-input",
|
|
2441
|
+
value: colTotal,
|
|
2442
|
+
onChange: (e) => setColTotal(e.target.value),
|
|
2443
|
+
placeholder: "cth: total_amount"
|
|
2444
|
+
}
|
|
2445
|
+
), /* @__PURE__ */ React17.createElement("div", { className: "rdb-caption", style: { marginTop: 2 } }, "Integer Rupiah")), /* @__PURE__ */ React17.createElement("div", null, /* @__PURE__ */ React17.createElement("label", { className: "rdb-label" }, "Kolom Nama Pelanggan"), colNames.length > 0 ? /* @__PURE__ */ React17.createElement(
|
|
2446
|
+
"select",
|
|
2447
|
+
{
|
|
2448
|
+
className: "rdb-select",
|
|
2449
|
+
value: colCustomer,
|
|
2450
|
+
onChange: (e) => setColCustomer(e.target.value)
|
|
2451
|
+
},
|
|
2452
|
+
/* @__PURE__ */ React17.createElement("option", { value: "" }, "\u2014 opsional \u2014"),
|
|
2453
|
+
colNames.map((c) => /* @__PURE__ */ React17.createElement("option", { key: c, value: c }, c))
|
|
2454
|
+
) : /* @__PURE__ */ React17.createElement(
|
|
2455
|
+
"input",
|
|
2456
|
+
{
|
|
2457
|
+
className: "rdb-input",
|
|
2458
|
+
value: colCustomer,
|
|
2459
|
+
onChange: (e) => setColCustomer(e.target.value),
|
|
2460
|
+
placeholder: "cth: customer_name (opsional)"
|
|
2461
|
+
}
|
|
2462
|
+
)), /* @__PURE__ */ React17.createElement("div", null, /* @__PURE__ */ React17.createElement("label", { className: "rdb-label" }, "Kolom Nama Item/Layanan"), colNames.length > 0 ? /* @__PURE__ */ React17.createElement(
|
|
2463
|
+
"select",
|
|
2464
|
+
{
|
|
2465
|
+
className: "rdb-select",
|
|
2466
|
+
value: colItem,
|
|
2467
|
+
onChange: (e) => setColItem(e.target.value)
|
|
2468
|
+
},
|
|
2469
|
+
/* @__PURE__ */ React17.createElement("option", { value: "" }, "\u2014 opsional \u2014"),
|
|
2470
|
+
colNames.map((c) => /* @__PURE__ */ React17.createElement("option", { key: c, value: c }, c))
|
|
2471
|
+
) : /* @__PURE__ */ React17.createElement(
|
|
2472
|
+
"input",
|
|
2473
|
+
{
|
|
2474
|
+
className: "rdb-input",
|
|
2475
|
+
value: colItem,
|
|
2476
|
+
onChange: (e) => setColItem(e.target.value),
|
|
2477
|
+
placeholder: "cth: service_type (opsional)"
|
|
2478
|
+
}
|
|
2479
|
+
))), /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-flow", style: { marginBottom: 16 } }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-caption", style: { fontWeight: 600, marginBottom: 8 } }, "Nilai status di tabel kamu:"), /* @__PURE__ */ React17.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 } }, /* @__PURE__ */ React17.createElement("div", null, /* @__PURE__ */ React17.createElement("label", { className: "rdb-label" }, 'Nilai "Selesai/Confirmed"'), /* @__PURE__ */ React17.createElement(
|
|
2480
|
+
"input",
|
|
2481
|
+
{
|
|
2482
|
+
className: "rdb-input",
|
|
2483
|
+
value: confirmedVal,
|
|
2484
|
+
onChange: (e) => setConfirmedVal(e.target.value),
|
|
2485
|
+
placeholder: "cth: confirmed, selesai, lunas"
|
|
2486
|
+
}
|
|
2487
|
+
)), /* @__PURE__ */ React17.createElement("div", null, /* @__PURE__ */ React17.createElement("label", { className: "rdb-label" }, 'Nilai "Pending/Proses"'), /* @__PURE__ */ React17.createElement(
|
|
2488
|
+
"input",
|
|
2489
|
+
{
|
|
2490
|
+
className: "rdb-input",
|
|
2491
|
+
value: pendingVal,
|
|
2492
|
+
onChange: (e) => setPendingVal(e.target.value),
|
|
2493
|
+
placeholder: "cth: pending, proses, menunggu"
|
|
2494
|
+
}
|
|
2495
|
+
)))), !mappingValid && /* @__PURE__ */ React17.createElement("div", { className: "rdb-error-banner", style: { marginBottom: 12 } }, "Pilih minimal kolom Tanggal, Status, dan Total untuk generate kode."), /* @__PURE__ */ React17.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" } }, /* @__PURE__ */ React17.createElement(
|
|
2496
|
+
"button",
|
|
2497
|
+
{
|
|
2498
|
+
type: "button",
|
|
2499
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2500
|
+
onClick: () => setStep(1)
|
|
2501
|
+
},
|
|
2502
|
+
"\u2190 Kembali"
|
|
2503
|
+
), /* @__PURE__ */ React17.createElement(
|
|
2504
|
+
"button",
|
|
2505
|
+
{
|
|
2506
|
+
type: "button",
|
|
2507
|
+
className: "rdb-btn rdb-btn-primary rdb-btn-sm",
|
|
2508
|
+
disabled: !mappingValid,
|
|
2509
|
+
onClick: handleGenerate
|
|
2510
|
+
},
|
|
2511
|
+
"\u2728 Generate Kode \u2192"
|
|
2512
|
+
))), step === 3 && generatedCode && /* @__PURE__ */ React17.createElement(React17.Fragment, null, /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-step-title" }, /* @__PURE__ */ React17.createElement("span", { className: "rdb-wizard-step-num" }, "\u2705"), /* @__PURE__ */ React17.createElement("span", { className: "rdb-h3", style: { margin: 0 } }, "Kode Siap Pakai")), /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-tip", style: { marginBottom: 16 } }, /* @__PURE__ */ React17.createElement("strong", null, "4 file sudah digenerate."), " Salin masing-masing ke project kamu. Tidak perlu memahami logika di dalamnya \u2014 cukup save dan jalankan."), /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-issues", style: { marginBottom: 16 } }, [
|
|
2513
|
+
["myDashboardSource.js", "src/datasources/", "File koneksi ke Supabase"],
|
|
2514
|
+
["myDashboardAdapter.js", "src/adapters/", "Transformasi data \u2192 format dashboard"],
|
|
2515
|
+
["myDashboardConfig.js", "src/config/ (atau langsung di Dashboard.jsx)", "Konfigurasi widget"],
|
|
2516
|
+
["Dashboard.jsx", "src/pages/admin/", "Ganti halaman dashboard lama"]
|
|
2517
|
+
].map(([file, path, desc], i) => /* @__PURE__ */ React17.createElement("div", { key: i, className: "rdb-wizard-issue-item" }, /* @__PURE__ */ React17.createElement("span", { style: { color: "var(--rdb-blue-500)", fontWeight: 700 } }, i + 1, "."), /* @__PURE__ */ React17.createElement("span", { className: "rdb-body" }, "Simpan ", /* @__PURE__ */ React17.createElement("code", { className: "rdb-wizard-code-inline" }, file), " ke", " ", /* @__PURE__ */ React17.createElement("code", { className: "rdb-wizard-code-inline" }, path), " \u2014 ", desc))), /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-issue-item" }, /* @__PURE__ */ React17.createElement("span", { style: { color: "var(--rdb-blue-500)", fontWeight: 700 } }, "5."), /* @__PURE__ */ React17.createElement("span", { className: "rdb-body" }, "Import CSS di ", /* @__PURE__ */ React17.createElement("code", { className: "rdb-wizard-code-inline" }, "src/index.css"), ":", " ", /* @__PURE__ */ React17.createElement("code", { className: "rdb-wizard-code-inline" }, '@import "../node_modules/@rozaqi02/reusable-dashboard/dist/index.css";')))), /* @__PURE__ */ React17.createElement("div", { style: { display: "flex", gap: 4, marginBottom: 8, flexWrap: "wrap" } }, [
|
|
2518
|
+
["dataSource", "myDashboardSource.js"],
|
|
2519
|
+
["adapter", "myDashboardAdapter.js"],
|
|
2520
|
+
["widgetConfig", "myDashboardConfig.js"],
|
|
2521
|
+
["dashboard", "Dashboard.jsx"]
|
|
2522
|
+
].map(([key, label]) => /* @__PURE__ */ React17.createElement(
|
|
2523
|
+
"button",
|
|
2524
|
+
{
|
|
2525
|
+
key,
|
|
2526
|
+
type: "button",
|
|
2527
|
+
onClick: () => setActiveCodeTab(key),
|
|
2528
|
+
className: `rdb-btn rdb-btn-sm ${activeCodeTab === key ? "rdb-btn-primary" : "rdb-btn-secondary"}`,
|
|
2529
|
+
style: { fontFamily: "monospace", fontSize: "0.75rem" }
|
|
2530
|
+
},
|
|
2531
|
+
label
|
|
2532
|
+
))), /* @__PURE__ */ React17.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React17.createElement(
|
|
2533
|
+
"button",
|
|
2534
|
+
{
|
|
2535
|
+
type: "button",
|
|
2536
|
+
onClick: () => copyCode(generatedCode[activeCodeTab], activeCodeTab),
|
|
2537
|
+
className: "rdb-btn rdb-btn-sm rdb-btn-secondary",
|
|
2538
|
+
style: { position: "absolute", top: 8, right: 8, zIndex: 1 }
|
|
2539
|
+
},
|
|
2540
|
+
copied === activeCodeTab ? "\u2705 Disalin!" : "\u{1F4CB} Salin"
|
|
2541
|
+
), /* @__PURE__ */ React17.createElement("pre", { className: "rdb-wizard-code", style: { maxHeight: 320, overflow: "auto" } }, generatedCode[activeCodeTab])), /* @__PURE__ */ React17.createElement("div", { className: "rdb-wizard-tip", style: { marginTop: 12 } }, "Setelah semua file tersimpan dan dev server direstart, wizard tidak akan muncul lagi."), /* @__PURE__ */ React17.createElement("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 12 } }, /* @__PURE__ */ React17.createElement(
|
|
2542
|
+
"button",
|
|
2543
|
+
{
|
|
2544
|
+
type: "button",
|
|
2545
|
+
className: "rdb-btn rdb-btn-secondary rdb-btn-sm",
|
|
2546
|
+
onClick: () => setStep(2)
|
|
2547
|
+
},
|
|
2548
|
+
"\u2190 Edit mapping"
|
|
2549
|
+
), /* @__PURE__ */ React17.createElement(
|
|
2550
|
+
"button",
|
|
2551
|
+
{
|
|
2552
|
+
type: "button",
|
|
2553
|
+
className: "rdb-btn rdb-btn-primary rdb-btn-sm",
|
|
2554
|
+
onClick: handleDismiss
|
|
2555
|
+
},
|
|
2556
|
+
"Tutup wizard \u2713"
|
|
2557
|
+
))))))
|
|
2558
|
+
);
|
|
2127
2559
|
}
|
|
2128
2560
|
SetupWizard.propTypes = {
|
|
2129
2561
|
issues: PropTypes17.arrayOf(PropTypes17.string),
|