@rozaqi02/reusable-dashboard 1.1.5 → 1.2.0
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/CHANGELOG.md +14 -0
- package/README.md +69 -1
- package/dist/index.cjs +474 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +469 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -309,6 +309,111 @@ function validateDashboardConfig(dashboardConfig) {
|
|
|
309
309
|
return { valid: issues.length === 0, issues };
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
// src/config/createUniversalWidgetConfig.js
|
|
313
|
+
function createUniversalWidgetConfig(columns = {}, opts = {}) {
|
|
314
|
+
const hasAudience = Boolean(columns.audience);
|
|
315
|
+
const hasItem = Boolean(columns.item);
|
|
316
|
+
const hasCustomer = Boolean(columns.customer);
|
|
317
|
+
const hasTotal = Boolean(columns.total);
|
|
318
|
+
const charts = [
|
|
319
|
+
{ id: "dailyTrends", type: "dailyArea", label: "dailyTrends", icon: "BarChart3" },
|
|
320
|
+
{ id: "statusDistribution", type: "statusPie", label: "statusDistribution", icon: "PieChart" }
|
|
321
|
+
];
|
|
322
|
+
if (hasAudience) {
|
|
323
|
+
charts.push({
|
|
324
|
+
id: "audienceDistribution",
|
|
325
|
+
type: "audiencePie",
|
|
326
|
+
label: "audienceDistribution",
|
|
327
|
+
icon: "Users"
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
if (hasItem) {
|
|
331
|
+
charts.push({
|
|
332
|
+
id: "topPackages",
|
|
333
|
+
type: "topPackagesBar",
|
|
334
|
+
label: "topPackages",
|
|
335
|
+
icon: "TrendingUp"
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
const tableColumns = [
|
|
339
|
+
{ id: "date", label: "date", accessor: "createdAt", type: "date" }
|
|
340
|
+
];
|
|
341
|
+
if (hasCustomer) {
|
|
342
|
+
tableColumns.push({ id: "customer", label: "customer", accessor: "customerName" });
|
|
343
|
+
}
|
|
344
|
+
if (hasItem) {
|
|
345
|
+
tableColumns.push({ id: "package", label: "package", accessor: "packageName" });
|
|
346
|
+
}
|
|
347
|
+
if (hasAudience) {
|
|
348
|
+
tableColumns.push({ id: "audience", label: "audience", accessor: "audienceLabel" });
|
|
349
|
+
}
|
|
350
|
+
if (hasTotal) {
|
|
351
|
+
tableColumns.push({ id: "total", label: "total", accessor: "totalIDR", type: "currency" });
|
|
352
|
+
}
|
|
353
|
+
tableColumns.push({
|
|
354
|
+
id: "status",
|
|
355
|
+
label: "status",
|
|
356
|
+
accessor: "statusLabel",
|
|
357
|
+
type: "statusBadge",
|
|
358
|
+
statusAccessor: "status"
|
|
359
|
+
});
|
|
360
|
+
return {
|
|
361
|
+
id: opts.id || "universal.dashboard",
|
|
362
|
+
defaultFilters: {
|
|
363
|
+
statusScope: "confirmed",
|
|
364
|
+
includePendingOverlay: false,
|
|
365
|
+
audience: "",
|
|
366
|
+
daysPreset: 30,
|
|
367
|
+
sortPkgBy: "bookings",
|
|
368
|
+
sortPkgDir: "desc"
|
|
369
|
+
},
|
|
370
|
+
widgets: {
|
|
371
|
+
stats: [
|
|
372
|
+
{
|
|
373
|
+
id: "bookingsConfirm",
|
|
374
|
+
label: "confirmedBookings",
|
|
375
|
+
icon: "TrendingUp",
|
|
376
|
+
valueKey: "bookingsConfirm",
|
|
377
|
+
format: "number",
|
|
378
|
+
accentColor: "blue"
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
id: "revenueConfirm",
|
|
382
|
+
label: "confirmedRevenue",
|
|
383
|
+
icon: "DollarSign",
|
|
384
|
+
valueKey: "revenueConfirm",
|
|
385
|
+
format: "currency",
|
|
386
|
+
accentColor: "green"
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
id: "avgRevenue",
|
|
390
|
+
label: "avgRevenue",
|
|
391
|
+
icon: "Users",
|
|
392
|
+
valueKey: "avgRevenue",
|
|
393
|
+
format: "currency",
|
|
394
|
+
accentColor: "violet"
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
id: "conversionRate",
|
|
398
|
+
label: "conversionRate",
|
|
399
|
+
icon: "PieChart",
|
|
400
|
+
valueKey: "conversionRate",
|
|
401
|
+
format: "percent",
|
|
402
|
+
accentColor: "orange"
|
|
403
|
+
}
|
|
404
|
+
],
|
|
405
|
+
charts,
|
|
406
|
+
table: {
|
|
407
|
+
id: "recentBookings",
|
|
408
|
+
label: "recentBookings",
|
|
409
|
+
icon: "Calendar",
|
|
410
|
+
emptyLabel: "noRecentBookings",
|
|
411
|
+
columns: tableColumns
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
312
417
|
// src/utils/formatters.js
|
|
313
418
|
function formatIDR(value) {
|
|
314
419
|
try {
|
|
@@ -714,6 +819,131 @@ function adaptTokoSepatuData({ raw, filters, range, dateLocale, labels }) {
|
|
|
714
819
|
};
|
|
715
820
|
}
|
|
716
821
|
|
|
822
|
+
// src/data-adapter/universalAdapter.js
|
|
823
|
+
function createEmptyUniversalData() {
|
|
824
|
+
return {
|
|
825
|
+
stats: {
|
|
826
|
+
bookingsConfirm: 0,
|
|
827
|
+
bookingsPending: 0,
|
|
828
|
+
revenueConfirm: 0,
|
|
829
|
+
avgRevenue: 0,
|
|
830
|
+
conversionRate: 0
|
|
831
|
+
},
|
|
832
|
+
charts: {
|
|
833
|
+
dailyTrends: [],
|
|
834
|
+
statusDistribution: [],
|
|
835
|
+
audienceDistribution: [],
|
|
836
|
+
topPackages: []
|
|
837
|
+
},
|
|
838
|
+
table: {
|
|
839
|
+
recentBookings: []
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
function adaptUniversalData({
|
|
844
|
+
raw,
|
|
845
|
+
filters = {},
|
|
846
|
+
range,
|
|
847
|
+
dateLocale = "id-ID",
|
|
848
|
+
labels = {},
|
|
849
|
+
options = {}
|
|
850
|
+
}) {
|
|
851
|
+
if (!raw) return createEmptyUniversalData();
|
|
852
|
+
const confirmedValue = String(options.confirmedValue || "confirmed").toLowerCase();
|
|
853
|
+
const pendingValue = String(options.pendingValue || "pending").toLowerCase();
|
|
854
|
+
const dailyBuckets = buildDayBuckets(range.daysWindow, range.fromISO, dateLocale);
|
|
855
|
+
const dayLookup = new Map(dailyBuckets.map((bucket) => [bucket.dateKey, bucket]));
|
|
856
|
+
const statusMap = /* @__PURE__ */ new Map();
|
|
857
|
+
const audienceMap = /* @__PURE__ */ new Map();
|
|
858
|
+
const itemCountMap = /* @__PURE__ */ new Map();
|
|
859
|
+
const itemRevenueMap = /* @__PURE__ */ new Map();
|
|
860
|
+
let bookingsConfirm = 0;
|
|
861
|
+
let bookingsPending = 0;
|
|
862
|
+
let revenueConfirm = 0;
|
|
863
|
+
const safeStatusLabel = typeof labels.formatStatusLabel === "function" ? labels.formatStatusLabel : (s) => String(s);
|
|
864
|
+
const safeAudienceLabel = typeof labels.formatAudienceLabel === "function" ? labels.formatAudienceLabel : (a) => String(a);
|
|
865
|
+
(raw.bookings || []).forEach((row) => {
|
|
866
|
+
const dayKey = String(row.created_at || "").slice(0, 10);
|
|
867
|
+
const status = String(row.status || pendingValue).toLowerCase();
|
|
868
|
+
const total = toNumber(row.total_idr);
|
|
869
|
+
const audience = row.audience || "unknown";
|
|
870
|
+
const item = row.item || "-";
|
|
871
|
+
statusMap.set(status, (statusMap.get(status) || 0) + 1);
|
|
872
|
+
audienceMap.set(audience, (audienceMap.get(audience) || 0) + 1);
|
|
873
|
+
const bucket = dayLookup.get(dayKey);
|
|
874
|
+
if (bucket) {
|
|
875
|
+
if (status === confirmedValue) {
|
|
876
|
+
bucket.count += 1;
|
|
877
|
+
bucket.revenue += total;
|
|
878
|
+
}
|
|
879
|
+
if (status === pendingValue) {
|
|
880
|
+
bucket.pendingCount += 1;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (status === confirmedValue) {
|
|
884
|
+
bookingsConfirm += 1;
|
|
885
|
+
revenueConfirm += total;
|
|
886
|
+
itemCountMap.set(item, (itemCountMap.get(item) || 0) + 1);
|
|
887
|
+
itemRevenueMap.set(item, (itemRevenueMap.get(item) || 0) + total);
|
|
888
|
+
} else if (status === pendingValue) {
|
|
889
|
+
bookingsPending += 1;
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
const statusDistribution = sortMapEntries(statusMap, "desc").map(
|
|
893
|
+
([status, count]) => ({
|
|
894
|
+
status,
|
|
895
|
+
label: safeStatusLabel(status),
|
|
896
|
+
count
|
|
897
|
+
})
|
|
898
|
+
);
|
|
899
|
+
const audienceDistribution = sortMapEntries(audienceMap, "desc").map(
|
|
900
|
+
([audience, count]) => ({
|
|
901
|
+
audience,
|
|
902
|
+
label: safeAudienceLabel(audience),
|
|
903
|
+
count
|
|
904
|
+
})
|
|
905
|
+
);
|
|
906
|
+
const metricMap = filters.sortPkgBy === "revenue" ? itemRevenueMap : itemCountMap;
|
|
907
|
+
const topPackages = sortMapEntries(metricMap, filters.sortPkgDir || "desc").slice(0, 5).map(([item, value]) => ({
|
|
908
|
+
packageId: item,
|
|
909
|
+
name: item,
|
|
910
|
+
value: toNumber(value)
|
|
911
|
+
}));
|
|
912
|
+
const recentBookings = (raw.recent || []).map((row) => {
|
|
913
|
+
const status = String(row.status || pendingValue).toLowerCase();
|
|
914
|
+
return {
|
|
915
|
+
id: row.id,
|
|
916
|
+
createdAt: row.created_at,
|
|
917
|
+
customerName: row.customer_name || "-",
|
|
918
|
+
packageName: row.item || "-",
|
|
919
|
+
audienceLabel: safeAudienceLabel(row.audience),
|
|
920
|
+
totalIDR: toNumber(row.total_idr),
|
|
921
|
+
status,
|
|
922
|
+
statusLabel: safeStatusLabel(status)
|
|
923
|
+
};
|
|
924
|
+
});
|
|
925
|
+
const avgRevenue = bookingsConfirm > 0 ? Math.round(revenueConfirm / bookingsConfirm) : 0;
|
|
926
|
+
const conversionRate = bookingsConfirm + bookingsPending > 0 ? Math.round(bookingsConfirm / (bookingsConfirm + bookingsPending) * 100) : 0;
|
|
927
|
+
return {
|
|
928
|
+
stats: {
|
|
929
|
+
bookingsConfirm,
|
|
930
|
+
bookingsPending,
|
|
931
|
+
revenueConfirm,
|
|
932
|
+
avgRevenue,
|
|
933
|
+
conversionRate
|
|
934
|
+
},
|
|
935
|
+
charts: {
|
|
936
|
+
dailyTrends: dailyBuckets,
|
|
937
|
+
statusDistribution,
|
|
938
|
+
audienceDistribution,
|
|
939
|
+
topPackages
|
|
940
|
+
},
|
|
941
|
+
table: {
|
|
942
|
+
recentBookings
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
|
|
717
947
|
// src/data-source/cidikaSupabaseSource.js
|
|
718
948
|
function ensureNoError(response, message) {
|
|
719
949
|
if (response == null ? void 0 : response.error) {
|
|
@@ -887,6 +1117,84 @@ function createTokoSepatuSupabaseSource(supabase) {
|
|
|
887
1117
|
};
|
|
888
1118
|
}
|
|
889
1119
|
|
|
1120
|
+
// src/data-source/universalSupabaseSource.js
|
|
1121
|
+
function createUniversalSource(supabase, { table, columns = {} } = {}) {
|
|
1122
|
+
if (!supabase) {
|
|
1123
|
+
throw new Error("createUniversalSource: prop 'supabase' wajib diisi.");
|
|
1124
|
+
}
|
|
1125
|
+
if (!table) {
|
|
1126
|
+
throw new Error("createUniversalSource: opsi 'table' wajib diisi.");
|
|
1127
|
+
}
|
|
1128
|
+
if (!columns.date) {
|
|
1129
|
+
throw new Error(
|
|
1130
|
+
"createUniversalSource: columns.date wajib diisi (kolom timestamp)."
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
const map = {
|
|
1134
|
+
date: columns.date,
|
|
1135
|
+
status: columns.status || null,
|
|
1136
|
+
total: columns.total || null,
|
|
1137
|
+
customer: columns.customer || null,
|
|
1138
|
+
item: columns.item || null,
|
|
1139
|
+
audience: columns.audience || null
|
|
1140
|
+
};
|
|
1141
|
+
const selectCols = Array.from(
|
|
1142
|
+
/* @__PURE__ */ new Set(["id", ...Object.values(map).filter(Boolean)])
|
|
1143
|
+
).join(", ");
|
|
1144
|
+
const normalize = (row) => ({
|
|
1145
|
+
id: row.id ?? row[map.date],
|
|
1146
|
+
created_at: row[map.date],
|
|
1147
|
+
status: map.status ? row[map.status] : "confirmed",
|
|
1148
|
+
total_idr: map.total ? row[map.total] : 0,
|
|
1149
|
+
item: map.item ? row[map.item] : null,
|
|
1150
|
+
customer_name: map.customer ? row[map.customer] : null,
|
|
1151
|
+
audience: map.audience ? row[map.audience] : "unknown"
|
|
1152
|
+
});
|
|
1153
|
+
return {
|
|
1154
|
+
async fetchDashboardSnapshot({ fromISO, toISO, audience, statusScope }) {
|
|
1155
|
+
const baseQuery = () => supabase.from(table).select(selectCols).gte(map.date, fromISO).lte(map.date, toISO);
|
|
1156
|
+
const bookingsQuery = baseQuery().order(map.date, { ascending: true });
|
|
1157
|
+
if (audience && map.audience) bookingsQuery.eq(map.audience, audience);
|
|
1158
|
+
const recentQuery = baseQuery().order(map.date, { ascending: false }).limit(10);
|
|
1159
|
+
if (audience && map.audience) recentQuery.eq(map.audience, audience);
|
|
1160
|
+
if (statusScope && statusScope !== "all" && map.status) {
|
|
1161
|
+
recentQuery.eq(map.status, statusScope);
|
|
1162
|
+
}
|
|
1163
|
+
const [bookingsRes, recentRes] = await Promise.all([
|
|
1164
|
+
bookingsQuery,
|
|
1165
|
+
recentQuery
|
|
1166
|
+
]);
|
|
1167
|
+
if (bookingsRes == null ? void 0 : bookingsRes.error) {
|
|
1168
|
+
const err = new Error(
|
|
1169
|
+
`Gagal membaca tabel "${table}". Periksa nama tabel/kolom & RLS policy.`
|
|
1170
|
+
);
|
|
1171
|
+
err.cause = bookingsRes.error;
|
|
1172
|
+
throw err;
|
|
1173
|
+
}
|
|
1174
|
+
const bookings = (bookingsRes.data || []).map(normalize);
|
|
1175
|
+
const recent = (recentRes.error ? [] : recentRes.data || []).map(
|
|
1176
|
+
normalize
|
|
1177
|
+
);
|
|
1178
|
+
return {
|
|
1179
|
+
bookings,
|
|
1180
|
+
recent,
|
|
1181
|
+
packageLocales: [],
|
|
1182
|
+
staticCounts: {}
|
|
1183
|
+
};
|
|
1184
|
+
},
|
|
1185
|
+
subscribeLiveUpdate(onEvent) {
|
|
1186
|
+
const channel = supabase.channel(`reusable-dashboard-universal-${table}`).on(
|
|
1187
|
+
"postgres_changes",
|
|
1188
|
+
{ event: "*", schema: "public", table },
|
|
1189
|
+
onEvent
|
|
1190
|
+
).subscribe();
|
|
1191
|
+
return () => {
|
|
1192
|
+
supabase.removeChannel(channel);
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
|
|
890
1198
|
// src/hooks/useReusableDashboard.js
|
|
891
1199
|
import { useCallback, useEffect as useEffect2, useMemo, useState as useState2 } from "react";
|
|
892
1200
|
|
|
@@ -2162,12 +2470,17 @@ function SetupWizard({ issues = [], onDismiss, supabase }) {
|
|
|
2162
2470
|
setSupabaseOk(true);
|
|
2163
2471
|
return;
|
|
2164
2472
|
}
|
|
2165
|
-
const { error: ping } = await supabase.from("
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2473
|
+
const { error: ping } = await supabase.from("_rdb_wizard_ping_").select("*").limit(1);
|
|
2474
|
+
if (!ping || ping.code === "42P01" || ping.code === "PGRST116" || ping.code === "PGRST200" || ping.message && (ping.message.includes("does not exist") || ping.message.includes("not found") || ping.message.includes("relation"))) {
|
|
2475
|
+
setSupabaseOk(true);
|
|
2476
|
+
setTableBlocked(true);
|
|
2477
|
+
} else {
|
|
2478
|
+
setSupabaseOk(!!supabase);
|
|
2479
|
+
setTableBlocked(true);
|
|
2480
|
+
}
|
|
2169
2481
|
} catch {
|
|
2170
|
-
setSupabaseOk(
|
|
2482
|
+
setSupabaseOk(!!supabase);
|
|
2483
|
+
setTableBlocked(true);
|
|
2171
2484
|
} finally {
|
|
2172
2485
|
setDetecting(false);
|
|
2173
2486
|
setDetectionDone(true);
|
|
@@ -2646,6 +2959,152 @@ ReusableDashboardView.propTypes = {
|
|
|
2646
2959
|
dashboardConfig: PropTypes18.object
|
|
2647
2960
|
};
|
|
2648
2961
|
|
|
2962
|
+
// src/presentation/AutoDashboard.jsx
|
|
2963
|
+
import React19 from "react";
|
|
2964
|
+
import PropTypes19 from "prop-types";
|
|
2965
|
+
var DEFAULT_LABELS = {
|
|
2966
|
+
title: "Dashboard",
|
|
2967
|
+
refresh: "Muat ulang",
|
|
2968
|
+
liveUpdate: "Live",
|
|
2969
|
+
loadFailed: "Gagal memuat data dashboard.",
|
|
2970
|
+
retry: "Coba lagi",
|
|
2971
|
+
confirmedOnly: "Hanya berhasil",
|
|
2972
|
+
pendingOnly: "Hanya menunggu",
|
|
2973
|
+
allStatus: "Semua status",
|
|
2974
|
+
showPendingOverlay: "Tampilkan overlay menunggu",
|
|
2975
|
+
allAudience: "Semua segmen",
|
|
2976
|
+
audienceDomestic: "Domestik",
|
|
2977
|
+
audienceForeign: "Asing",
|
|
2978
|
+
customDate: "Custom",
|
|
2979
|
+
reset: "Reset",
|
|
2980
|
+
topSort: "Urutkan item",
|
|
2981
|
+
sortBookings: "Jumlah",
|
|
2982
|
+
sortRevenue: "Nilai",
|
|
2983
|
+
sortDesc: "Turun",
|
|
2984
|
+
sortAsc: "Naik",
|
|
2985
|
+
confirmedBookings: "Transaksi Berhasil",
|
|
2986
|
+
confirmedRevenue: "Pendapatan (Berhasil)",
|
|
2987
|
+
avgRevenue: "Rata-rata Nilai / Transaksi",
|
|
2988
|
+
conversionRate: "Tingkat Konversi",
|
|
2989
|
+
dailyTrends: "Tren Harian",
|
|
2990
|
+
statusDistribution: "Distribusi Status",
|
|
2991
|
+
audienceDistribution: "Distribusi Segmen",
|
|
2992
|
+
topPackages: "Item Teratas",
|
|
2993
|
+
recentBookings: "Transaksi Terbaru",
|
|
2994
|
+
date: "Tanggal",
|
|
2995
|
+
customer: "Pelanggan",
|
|
2996
|
+
package: "Item",
|
|
2997
|
+
audience: "Segmen",
|
|
2998
|
+
total: "Total",
|
|
2999
|
+
status: "Status",
|
|
3000
|
+
noRecentBookings: "Belum ada transaksi terbaru",
|
|
3001
|
+
unknownAudience: "Tidak diketahui"
|
|
3002
|
+
};
|
|
3003
|
+
function buildLabels(overrides = {}) {
|
|
3004
|
+
const labels = { ...DEFAULT_LABELS, ...overrides };
|
|
3005
|
+
labels.dayLabel = overrides.dayLabel || ((count) => count ? `${count} hari` : "Custom");
|
|
3006
|
+
labels.formatStatusLabel = overrides.formatStatusLabel || ((status) => {
|
|
3007
|
+
const s = String(status || "pending");
|
|
3008
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
3009
|
+
});
|
|
3010
|
+
labels.formatAudienceLabel = overrides.formatAudienceLabel || ((value) => {
|
|
3011
|
+
if (value === "domestic") return labels.audienceDomestic;
|
|
3012
|
+
if (value === "foreign") return labels.audienceForeign;
|
|
3013
|
+
if (!value || value === "unknown") return labels.unknownAudience;
|
|
3014
|
+
return String(value);
|
|
3015
|
+
});
|
|
3016
|
+
return labels;
|
|
3017
|
+
}
|
|
3018
|
+
function AutoDashboard({
|
|
3019
|
+
supabase,
|
|
3020
|
+
table,
|
|
3021
|
+
columns,
|
|
3022
|
+
confirmedValue = "confirmed",
|
|
3023
|
+
pendingValue = "pending",
|
|
3024
|
+
title = "Dashboard",
|
|
3025
|
+
labels: labelOverrides,
|
|
3026
|
+
languageCode = "id",
|
|
3027
|
+
dateLocale = "id-ID"
|
|
3028
|
+
}) {
|
|
3029
|
+
const isConfigured = Boolean(supabase && table && (columns == null ? void 0 : columns.date));
|
|
3030
|
+
const labels = React19.useMemo(
|
|
3031
|
+
() => buildLabels({ ...labelOverrides, title }),
|
|
3032
|
+
[labelOverrides, title]
|
|
3033
|
+
);
|
|
3034
|
+
const widgetConfig = React19.useMemo(
|
|
3035
|
+
() => createUniversalWidgetConfig(columns || {}),
|
|
3036
|
+
[columns]
|
|
3037
|
+
);
|
|
3038
|
+
const dataSource = React19.useMemo(() => {
|
|
3039
|
+
if (!isConfigured) return null;
|
|
3040
|
+
return createUniversalSource(supabase, { table, columns });
|
|
3041
|
+
}, [isConfigured, supabase, table, columns]);
|
|
3042
|
+
const adapter = React19.useMemo(
|
|
3043
|
+
() => (args) => adaptUniversalData({ ...args, options: { confirmedValue, pendingValue } }),
|
|
3044
|
+
[confirmedValue, pendingValue]
|
|
3045
|
+
);
|
|
3046
|
+
const dashboard = useReusableDashboard({
|
|
3047
|
+
config: widgetConfig,
|
|
3048
|
+
dataSource: dataSource || { fetchDashboardSnapshot: null },
|
|
3049
|
+
adapter,
|
|
3050
|
+
createEmptyState: createEmptyUniversalData,
|
|
3051
|
+
languageCode,
|
|
3052
|
+
dateLocale,
|
|
3053
|
+
labels
|
|
3054
|
+
});
|
|
3055
|
+
if (!isConfigured) {
|
|
3056
|
+
const issues = [];
|
|
3057
|
+
if (!supabase) issues.push("Prop 'supabase' belum diisi (Supabase client).");
|
|
3058
|
+
if (!table) issues.push("Prop 'table' belum diisi (nama tabel sumber data).");
|
|
3059
|
+
if (!(columns == null ? void 0 : columns.date))
|
|
3060
|
+
issues.push("Prop 'columns.date' belum diisi (kolom timestamp).");
|
|
3061
|
+
return /* @__PURE__ */ React19.createElement("div", { className: "rdb-view" }, /* @__PURE__ */ React19.createElement(SetupWizard, { issues, supabase, onDismiss: () => {
|
|
3062
|
+
} }));
|
|
3063
|
+
}
|
|
3064
|
+
return /* @__PURE__ */ React19.createElement(
|
|
3065
|
+
ReusableDashboardView,
|
|
3066
|
+
{
|
|
3067
|
+
config: widgetConfig,
|
|
3068
|
+
labels,
|
|
3069
|
+
loading: dashboard.loading,
|
|
3070
|
+
error: dashboard.error,
|
|
3071
|
+
filters: dashboard.filters,
|
|
3072
|
+
onFilterChange: dashboard.updateFilter,
|
|
3073
|
+
onResetFilters: dashboard.resetFilters,
|
|
3074
|
+
onRefresh: dashboard.refresh,
|
|
3075
|
+
data: dashboard.data,
|
|
3076
|
+
dateLocale,
|
|
3077
|
+
liveUpdateEnabled: dashboard.liveUpdateEnabled,
|
|
3078
|
+
supabase
|
|
3079
|
+
}
|
|
3080
|
+
);
|
|
3081
|
+
}
|
|
3082
|
+
AutoDashboard.propTypes = {
|
|
3083
|
+
/** Supabase client instance (WAJIB). */
|
|
3084
|
+
supabase: PropTypes19.object,
|
|
3085
|
+
/** Nama tabel sumber data (WAJIB), mis. "bookings" / "orders". */
|
|
3086
|
+
table: PropTypes19.string,
|
|
3087
|
+
/** Pemetaan kolom: { date, status, total, customer, item, audience }. date WAJIB. */
|
|
3088
|
+
columns: PropTypes19.shape({
|
|
3089
|
+
date: PropTypes19.string,
|
|
3090
|
+
status: PropTypes19.string,
|
|
3091
|
+
total: PropTypes19.string,
|
|
3092
|
+
customer: PropTypes19.string,
|
|
3093
|
+
item: PropTypes19.string,
|
|
3094
|
+
audience: PropTypes19.string
|
|
3095
|
+
}),
|
|
3096
|
+
/** Nilai status yang dihitung "berhasil" (default "confirmed"). */
|
|
3097
|
+
confirmedValue: PropTypes19.string,
|
|
3098
|
+
/** Nilai status yang dihitung "menunggu" (default "pending"). */
|
|
3099
|
+
pendingValue: PropTypes19.string,
|
|
3100
|
+
/** Judul dashboard. */
|
|
3101
|
+
title: PropTypes19.string,
|
|
3102
|
+
/** Override sebagian/seluruh label UI. */
|
|
3103
|
+
labels: PropTypes19.object,
|
|
3104
|
+
languageCode: PropTypes19.string,
|
|
3105
|
+
dateLocale: PropTypes19.string
|
|
3106
|
+
};
|
|
3107
|
+
|
|
2649
3108
|
// src/utils/labels.js
|
|
2650
3109
|
function createDashboardLabels(t) {
|
|
2651
3110
|
const labels = {
|
|
@@ -2766,6 +3225,7 @@ function createDashboardLabels(t) {
|
|
|
2766
3225
|
return labels;
|
|
2767
3226
|
}
|
|
2768
3227
|
export {
|
|
3228
|
+
AutoDashboard,
|
|
2769
3229
|
Badge,
|
|
2770
3230
|
Button,
|
|
2771
3231
|
ChartCard,
|
|
@@ -2787,6 +3247,7 @@ export {
|
|
|
2787
3247
|
adaptCidikaDashboardData,
|
|
2788
3248
|
adaptDummyUmkmData,
|
|
2789
3249
|
adaptTokoSepatuData,
|
|
3250
|
+
adaptUniversalData,
|
|
2790
3251
|
buildDayBuckets,
|
|
2791
3252
|
cidikaWidgetConfig,
|
|
2792
3253
|
createCidikaSupabaseSource,
|
|
@@ -2796,7 +3257,10 @@ export {
|
|
|
2796
3257
|
createEmptyDashboardData,
|
|
2797
3258
|
createEmptyDummyUmkmData,
|
|
2798
3259
|
createEmptyTokoSepatuData,
|
|
3260
|
+
createEmptyUniversalData,
|
|
2799
3261
|
createTokoSepatuSupabaseSource,
|
|
3262
|
+
createUniversalSource,
|
|
3263
|
+
createUniversalWidgetConfig,
|
|
2800
3264
|
dummyUmkmWidgetConfig,
|
|
2801
3265
|
formatDate,
|
|
2802
3266
|
formatIDR,
|