@rozaqi02/reusable-dashboard 1.0.1 → 1.1.1
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 +64 -473
- package/dist/index.cjs +200 -479
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +742 -0
- package/dist/index.css.map +1 -0
- package/dist/index.js +200 -479
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
package/README.md
CHANGED
|
@@ -1,148 +1,68 @@
|
|
|
1
1
|
# @rozaqi02/reusable-dashboard
|
|
2
2
|
|
|
3
|
-
Modul dashboard admin reusable untuk UMKM
|
|
4
|
-
Mendukung berbagai domain bisnis
|
|
5
|
-
cukup definisikan widget configuration dan data adapter yang sesuai.
|
|
3
|
+
Modul dashboard admin reusable untuk UMKM. Dibangun dengan React + Atomic Design + Supabase.
|
|
4
|
+
Mendukung berbagai domain bisnis tanpa mengubah komponen inti.
|
|
6
5
|
|
|
7
|
-
[](#)
|
|
6
|
+
[](./CHANGELOG.md)
|
|
7
|
+
[](./LICENSE)
|
|
10
8
|
|
|
11
9
|
---
|
|
12
10
|
|
|
13
|
-
##
|
|
11
|
+
## Install
|
|
14
12
|
|
|
15
13
|
```bash
|
|
16
14
|
npm install @rozaqi02/reusable-dashboard
|
|
15
|
+
npm install recharts @supabase/supabase-js
|
|
17
16
|
```
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- **Node.js** >= 16.x
|
|
22
|
-
- **React** >= 18.0.0
|
|
23
|
-
- **Supabase** account dan project yang sudah dibuat
|
|
24
|
-
- **Tailwind CSS** >= 3.0 dikonfigurasi pada project konsumen
|
|
25
|
-
|
|
26
|
-
## 🔧 Peer Dependencies
|
|
27
|
-
|
|
28
|
-
Install peer dependencies yang dibutuhkan:
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npm install react react-dom recharts @supabase/supabase-js lucide-react prop-types tailwindcss
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
> **Tailwind CSS wajib** karena komponen menggunakan utility classes Tailwind untuk styling.
|
|
35
|
-
> Pastikan Tailwind sudah dikonfigurasi di project kamu (lihat [panduan Tailwind](https://tailwindcss.com/docs/installation)).
|
|
18
|
+
Hanya 2 perintah. CSS sudah otomatis terbundle — tidak perlu konfigurasi Tailwind.
|
|
36
19
|
|
|
37
20
|
---
|
|
38
21
|
|
|
39
|
-
##
|
|
40
|
-
|
|
41
|
-
### Step 1: Setup Supabase Client
|
|
22
|
+
## Quick Start (5 menit)
|
|
42
23
|
|
|
43
|
-
Buat
|
|
24
|
+
### 1. Buat Supabase client
|
|
44
25
|
|
|
45
|
-
```
|
|
26
|
+
```js
|
|
27
|
+
// src/lib/supabaseClient.js
|
|
46
28
|
import { createClient } from '@supabase/supabase-js';
|
|
47
|
-
|
|
48
29
|
export const supabase = createClient(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
// atau process.env.REACT_APP_SUPABASE_URL jika pakai Create React App
|
|
30
|
+
process.env.REACT_APP_SUPABASE_URL,
|
|
31
|
+
process.env.REACT_APP_SUPABASE_ANON_KEY
|
|
52
32
|
);
|
|
53
33
|
```
|
|
54
34
|
|
|
55
|
-
Buat file `.env` di root project:
|
|
56
|
-
|
|
57
35
|
```env
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# Create React App
|
|
63
|
-
# REACT_APP_SUPABASE_URL=https://your-project.supabase.co
|
|
64
|
-
# REACT_APP_SUPABASE_ANON_KEY=your-anon-key
|
|
36
|
+
# .env
|
|
37
|
+
REACT_APP_SUPABASE_URL=https://xxx.supabase.co
|
|
38
|
+
REACT_APP_SUPABASE_ANON_KEY=eyJ...
|
|
65
39
|
```
|
|
66
40
|
|
|
67
|
-
###
|
|
68
|
-
|
|
69
|
-
Jalankan SQL schema di Supabase SQL Editor sesuai domain bisnis:
|
|
70
|
-
|
|
71
|
-
| Domain | File SQL |
|
|
72
|
-
|--------|----------|
|
|
73
|
-
| Cidika Travel | `examples/cidika-travel-example/supabase-schema.sql` |
|
|
74
|
-
| Toko Sepatu | `examples/toko-sepatu-example/supabase-schema.sql` |
|
|
75
|
-
|
|
76
|
-
### Step 3: Implementasi Dashboard
|
|
77
|
-
|
|
78
|
-
Contoh menggunakan preset **Toko Sepatu**:
|
|
41
|
+
### 2. Pakai di halaman dashboard
|
|
79
42
|
|
|
80
43
|
```jsx
|
|
81
44
|
import React, { useMemo } from "react";
|
|
82
45
|
import { supabase } from "./lib/supabaseClient";
|
|
83
46
|
import {
|
|
84
47
|
ReusableDashboardView,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
48
|
+
cidikaWidgetConfig,
|
|
49
|
+
createCidikaSupabaseSource,
|
|
50
|
+
adaptCidikaDashboardData,
|
|
51
|
+
createEmptyDashboardData,
|
|
52
|
+
createDashboardLabels,
|
|
89
53
|
useReusableDashboard,
|
|
90
54
|
} from "@rozaqi02/reusable-dashboard";
|
|
91
55
|
|
|
92
|
-
|
|
93
|
-
const source = createTokoSepatuSupabaseSource(supabase);
|
|
94
|
-
|
|
95
|
-
// Label minimal tanpa i18n — override semua key yang diperlukan
|
|
96
|
-
const labels = {
|
|
97
|
-
title: "Dashboard Toko Sepatu",
|
|
98
|
-
refresh: "Refresh",
|
|
99
|
-
liveUpdate: "Live",
|
|
100
|
-
loadFailed: "Gagal memuat data.",
|
|
101
|
-
retry: "Coba Lagi",
|
|
102
|
-
confirmedOnly: "Confirmed",
|
|
103
|
-
pendingOnly: "Pending",
|
|
104
|
-
allStatus: "Semua Status",
|
|
105
|
-
showPendingOverlay: "Tampilkan pending",
|
|
106
|
-
allAudience: "Semua",
|
|
107
|
-
customDate: "Kustom",
|
|
108
|
-
reset: "Reset",
|
|
109
|
-
topSort: "Urutkan",
|
|
110
|
-
sortBookings: "Qty",
|
|
111
|
-
sortRevenue: "Revenue",
|
|
112
|
-
sortDesc: "Turun",
|
|
113
|
-
sortAsc: "Naik",
|
|
114
|
-
confirmedBookings: "Total Pesanan",
|
|
115
|
-
confirmedRevenue: "Total Pendapatan",
|
|
116
|
-
avgRevenue: "Rata-rata / Pesanan",
|
|
117
|
-
conversionRate: "Conversion Rate",
|
|
118
|
-
totalProducts: "Total Produk",
|
|
119
|
-
dailyTrends: "Tren Harian",
|
|
120
|
-
statusDistribution: "Distribusi Status",
|
|
121
|
-
topPackages: "Produk Terlaris",
|
|
122
|
-
recentBookings: "Pesanan Terbaru",
|
|
123
|
-
date: "Tanggal",
|
|
124
|
-
customer: "Pelanggan",
|
|
125
|
-
package: "Produk",
|
|
126
|
-
total: "Total",
|
|
127
|
-
status: "Status",
|
|
128
|
-
noRecentBookings: "Belum ada pesanan",
|
|
129
|
-
bookingsMetric: "Pesanan",
|
|
130
|
-
revenueMetric: "Pendapatan",
|
|
131
|
-
pendingMetric: "Pesanan (Pending)",
|
|
132
|
-
confirmedBookingMetric: "Pesanan (Confirmed)",
|
|
133
|
-
confirmedRevenueMetric: "Pendapatan (Confirmed)",
|
|
134
|
-
unknownAudience: "Unknown",
|
|
135
|
-
dayLabel: (count) => `${count} hari`,
|
|
136
|
-
formatStatusLabel: (s) => ({ confirmed: "Confirmed", pending: "Pending", cancelled: "Cancelled" })[s] || s,
|
|
137
|
-
formatAudienceLabel: (v) => v || "Unknown",
|
|
138
|
-
};
|
|
56
|
+
const source = createCidikaSupabaseSource(supabase);
|
|
139
57
|
|
|
140
58
|
export default function Dashboard() {
|
|
59
|
+
const labels = useMemo(() => createDashboardLabels((key) => key), []);
|
|
60
|
+
|
|
141
61
|
const state = useReusableDashboard({
|
|
142
|
-
config:
|
|
62
|
+
config: cidikaWidgetConfig,
|
|
143
63
|
dataSource: source,
|
|
144
|
-
adapter:
|
|
145
|
-
createEmptyState:
|
|
64
|
+
adapter: adaptCidikaDashboardData,
|
|
65
|
+
createEmptyState: createEmptyDashboardData,
|
|
146
66
|
languageCode: "id",
|
|
147
67
|
dateLocale: "id-ID",
|
|
148
68
|
labels,
|
|
@@ -150,7 +70,7 @@ export default function Dashboard() {
|
|
|
150
70
|
|
|
151
71
|
return (
|
|
152
72
|
<ReusableDashboardView
|
|
153
|
-
config={
|
|
73
|
+
config={cidikaWidgetConfig}
|
|
154
74
|
labels={labels}
|
|
155
75
|
loading={state.loading}
|
|
156
76
|
error={state.error}
|
|
@@ -166,406 +86,77 @@ export default function Dashboard() {
|
|
|
166
86
|
}
|
|
167
87
|
```
|
|
168
88
|
|
|
169
|
-
|
|
170
|
-
> sebagai pengganti objek labels di atas. Lihat [bagian i18n](#-label--i18n).
|
|
171
|
-
|
|
172
|
-
### Step 4: Jalankan Aplikasi
|
|
89
|
+
### 3. Jalankan
|
|
173
90
|
|
|
174
91
|
```bash
|
|
175
|
-
npm
|
|
176
|
-
# atau
|
|
177
|
-
npm start # Create React App
|
|
92
|
+
npm start
|
|
178
93
|
```
|
|
179
94
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
## 📚 Available Presets
|
|
183
|
-
|
|
184
|
-
### 1. Cidika Travel (Travel Agency)
|
|
185
|
-
|
|
186
|
-
```javascript
|
|
187
|
-
import {
|
|
188
|
-
cidikaWidgetConfig,
|
|
189
|
-
createCidikaSupabaseSource,
|
|
190
|
-
adaptCidikaDashboardData,
|
|
191
|
-
createEmptyDashboardData,
|
|
192
|
-
} from "@rozaqi02/reusable-dashboard";
|
|
193
|
-
|
|
194
|
-
const source = createCidikaSupabaseSource(supabase);
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
Tabel Supabase yang dibutuhkan: `bookings`, `packages`, `package_locales`, `page_sections`
|
|
198
|
-
|
|
199
|
-
### 2. Toko Sepatu (Online Shop)
|
|
200
|
-
|
|
201
|
-
```javascript
|
|
202
|
-
import {
|
|
203
|
-
tokoSepatuWidgetConfig,
|
|
204
|
-
createTokoSepatuSupabaseSource,
|
|
205
|
-
adaptTokoSepatuData,
|
|
206
|
-
createEmptyTokoSepatuData,
|
|
207
|
-
} from "@rozaqi02/reusable-dashboard";
|
|
208
|
-
|
|
209
|
-
const source = createTokoSepatuSupabaseSource(supabase);
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
Tabel Supabase yang dibutuhkan: `orders`, `order_items`, `products`, `customers`
|
|
213
|
-
|
|
214
|
-
### 3. Dummy UMKM (Generic)
|
|
215
|
-
|
|
216
|
-
```javascript
|
|
217
|
-
import {
|
|
218
|
-
dummyUmkmWidgetConfig,
|
|
219
|
-
adaptDummyUmkmData,
|
|
220
|
-
createEmptyDummyUmkmData,
|
|
221
|
-
} from "@rozaqi02/reusable-dashboard";
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
> Dummy UMKM tidak memiliki data source khusus.
|
|
225
|
-
> Gunakan data source Toko Sepatu atau buat sendiri.
|
|
95
|
+
Selesai.
|
|
226
96
|
|
|
227
97
|
---
|
|
228
98
|
|
|
229
|
-
##
|
|
230
|
-
|
|
231
|
-
### Custom Widget Configuration
|
|
232
|
-
|
|
233
|
-
Struktur konfigurasi yang benar (sesuai kontrak API modul):
|
|
234
|
-
|
|
235
|
-
```javascript
|
|
236
|
-
export const myConfig = {
|
|
237
|
-
id: "my-business.dashboard",
|
|
238
|
-
defaultFilters: {
|
|
239
|
-
statusScope: "all", // "confirmed" | "pending" | "all"
|
|
240
|
-
daysPreset: 30, // preset hari (7 | 30 | 90 | 0 untuk kustom)
|
|
241
|
-
sortPkgBy: "revenue", // "bookings" | "revenue"
|
|
242
|
-
sortPkgDir: "desc", // "asc" | "desc"
|
|
243
|
-
includePendingOverlay: false,
|
|
244
|
-
audience: "",
|
|
245
|
-
},
|
|
246
|
-
widgets: {
|
|
247
|
-
stats: [
|
|
248
|
-
{
|
|
249
|
-
id: "totalOrders",
|
|
250
|
-
label: "confirmedBookings", // key pada objek labels
|
|
251
|
-
icon: "TrendingUp", // nama ikon dari Lucide React
|
|
252
|
-
valueKey: "bookingsConfirm", // key pada data.stats dari adapter
|
|
253
|
-
format: "number", // "number" | "currency" | "percent"
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
id: "totalRevenue",
|
|
257
|
-
label: "confirmedRevenue",
|
|
258
|
-
icon: "DollarSign",
|
|
259
|
-
valueKey: "revenueConfirm",
|
|
260
|
-
format: "currency",
|
|
261
|
-
},
|
|
262
|
-
],
|
|
263
|
-
charts: [
|
|
264
|
-
{
|
|
265
|
-
id: "dailyTrends",
|
|
266
|
-
type: "dailyArea", // "dailyArea" | "statusPie" | "audiencePie" | "topPackagesBar"
|
|
267
|
-
label: "dailyTrends",
|
|
268
|
-
icon: "BarChart3",
|
|
269
|
-
},
|
|
270
|
-
{
|
|
271
|
-
id: "statusDist",
|
|
272
|
-
type: "statusPie",
|
|
273
|
-
label: "statusDistribution",
|
|
274
|
-
icon: "PieChart",
|
|
275
|
-
},
|
|
276
|
-
],
|
|
277
|
-
table: {
|
|
278
|
-
id: "recentOrders",
|
|
279
|
-
label: "recentBookings",
|
|
280
|
-
icon: "Calendar",
|
|
281
|
-
emptyLabel: "noRecentBookings",
|
|
282
|
-
columns: [
|
|
283
|
-
{ id: "date", label: "date", accessor: "createdAt", type: "date" },
|
|
284
|
-
{ id: "customer", label: "customer", accessor: "customerName" },
|
|
285
|
-
{ id: "total", label: "total", accessor: "totalIDR", type: "currency" },
|
|
286
|
-
{
|
|
287
|
-
id: "status", label: "status", accessor: "statusLabel",
|
|
288
|
-
type: "statusBadge", statusAccessor: "status",
|
|
289
|
-
},
|
|
290
|
-
],
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
|
-
};
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
### Custom Data Source
|
|
297
|
-
|
|
298
|
-
Kontrak yang harus diimplementasikan:
|
|
299
|
-
|
|
300
|
-
```javascript
|
|
301
|
-
export function createMySupabaseSource(supabase) {
|
|
302
|
-
return {
|
|
303
|
-
// Wajib: mengambil snapshot data dashboard
|
|
304
|
-
async fetchDashboardSnapshot({ fromISO, toISO, audience, statusScope, languageCode }) {
|
|
305
|
-
const { data: orders, error } = await supabase
|
|
306
|
-
.from("orders")
|
|
307
|
-
.select("id, created_at, total_amount, status, customer_id")
|
|
308
|
-
.gte("created_at", fromISO)
|
|
309
|
-
.lte("created_at", toISO);
|
|
310
|
-
|
|
311
|
-
if (error) throw new Error("Failed to load orders.");
|
|
312
|
-
return { bookings: orders, recent: [], packageLocales: [], staticCounts: {} };
|
|
313
|
-
},
|
|
314
|
-
|
|
315
|
-
// Opsional: subscribe ke perubahan realtime (untuk live update)
|
|
316
|
-
subscribeLiveUpdate(onEvent) {
|
|
317
|
-
const channel = supabase
|
|
318
|
-
.channel("my-dashboard-live")
|
|
319
|
-
.on("postgres_changes", { event: "*", schema: "public", table: "orders" }, onEvent)
|
|
320
|
-
.subscribe();
|
|
321
|
-
return () => { supabase.removeChannel(channel); };
|
|
322
|
-
},
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
### Custom Data Adapter
|
|
328
|
-
|
|
329
|
-
Adapter menerima parameter objek, bukan positional argument:
|
|
330
|
-
|
|
331
|
-
```javascript
|
|
332
|
-
export function adaptMyBusinessData({ raw, filters, range, dateLocale, languageCode, labels }) {
|
|
333
|
-
if (!raw) return createMyEmptyData();
|
|
99
|
+
## Preset yang tersedia
|
|
334
100
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (row.status === "confirmed") {
|
|
341
|
-
totalConfirmed += 1;
|
|
342
|
-
totalRevenue += Number(row.total_amount) || 0;
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
return {
|
|
347
|
-
stats: {
|
|
348
|
-
bookingsConfirm: totalConfirmed,
|
|
349
|
-
revenueConfirm: totalRevenue,
|
|
350
|
-
avgRevenue: totalConfirmed > 0 ? Math.round(totalRevenue / totalConfirmed) : 0,
|
|
351
|
-
conversionRate: 0,
|
|
352
|
-
packages: 0,
|
|
353
|
-
sections: 0,
|
|
354
|
-
},
|
|
355
|
-
charts: {
|
|
356
|
-
dailyTrends: [],
|
|
357
|
-
statusDistribution: [],
|
|
358
|
-
audienceDistribution: [],
|
|
359
|
-
topPackages: [],
|
|
360
|
-
},
|
|
361
|
-
table: { recentBookings: [] },
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
export function createMyEmptyData() {
|
|
366
|
-
return {
|
|
367
|
-
stats: { bookingsConfirm: 0, revenueConfirm: 0, avgRevenue: 0,
|
|
368
|
-
conversionRate: 0, packages: 0, sections: 0 },
|
|
369
|
-
charts: { dailyTrends: [], statusDistribution: [],
|
|
370
|
-
audienceDistribution: [], topPackages: [] },
|
|
371
|
-
table: { recentBookings: [] },
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
```
|
|
101
|
+
| Preset | Import | Tabel Supabase yang dibutuhkan |
|
|
102
|
+
|--------|--------|-------------------------------|
|
|
103
|
+
| **Cidika Travel** | `cidikaWidgetConfig`, `createCidikaSupabaseSource`, `adaptCidikaDashboardData` | `bookings`, `packages`, `package_locales`, `page_sections` |
|
|
104
|
+
| **Toko Sepatu** | `tokoSepatuWidgetConfig`, `createTokoSepatuSupabaseSource`, `adaptTokoSepatuData` | `orders`, `order_items`, `products`, `customers` |
|
|
105
|
+
| **Dummy UMKM** | `dummyUmkmWidgetConfig`, `adaptDummyUmkmData` | (gunakan salah satu source di atas) |
|
|
375
106
|
|
|
376
107
|
---
|
|
377
108
|
|
|
378
|
-
##
|
|
379
|
-
|
|
380
|
-
### Tanpa i18n (plain object)
|
|
381
|
-
|
|
382
|
-
Buat objek label secara manual (lihat contoh di Quick Start).
|
|
109
|
+
## Pakai dengan i18n (react-i18next)
|
|
383
110
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
```javascript
|
|
111
|
+
```jsx
|
|
387
112
|
import { useTranslation } from "react-i18next";
|
|
388
113
|
import { createDashboardLabels } from "@rozaqi02/reusable-dashboard";
|
|
389
114
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const languageCode = (i18n.language || "id").slice(0, 2);
|
|
393
|
-
const dateLocale = languageCode === "id" ? "id-ID" : "en-US";
|
|
394
|
-
|
|
395
|
-
// createDashboardLabels menerima fungsi t dari react-i18next
|
|
396
|
-
const labels = useMemo(() => createDashboardLabels(t), [t]);
|
|
397
|
-
// ...
|
|
398
|
-
}
|
|
115
|
+
const { t } = useTranslation();
|
|
116
|
+
const labels = useMemo(() => createDashboardLabels(t), [t]);
|
|
399
117
|
```
|
|
400
118
|
|
|
401
119
|
---
|
|
402
120
|
|
|
403
|
-
##
|
|
404
|
-
|
|
405
|
-
### Configs
|
|
121
|
+
## API Ringkas
|
|
406
122
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
| `tokoSepatuWidgetConfig` | Config dashboard toko online |
|
|
411
|
-
| `dummyUmkmWidgetConfig` | Config dashboard bisnis generik |
|
|
123
|
+
```js
|
|
124
|
+
// Config
|
|
125
|
+
import { cidikaWidgetConfig, tokoSepatuWidgetConfig, dummyUmkmWidgetConfig }
|
|
412
126
|
|
|
413
|
-
|
|
127
|
+
// Data source
|
|
128
|
+
import { createCidikaSupabaseSource, createTokoSepatuSupabaseSource }
|
|
414
129
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
130
|
+
// Adapter
|
|
131
|
+
import { adaptCidikaDashboardData, createEmptyDashboardData }
|
|
132
|
+
import { adaptTokoSepatuData, createEmptyTokoSepatuData }
|
|
133
|
+
import { adaptDummyUmkmData, createEmptyDummyUmkmData }
|
|
419
134
|
|
|
420
|
-
|
|
135
|
+
// Hook
|
|
136
|
+
import { useReusableDashboard, useRealtimeUpdate }
|
|
421
137
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
| `adaptCidikaDashboardData({ raw, filters, range, dateLocale, languageCode, labels })` | Adapter Cidika Travel |
|
|
425
|
-
| `createEmptyDashboardData()` | Empty state Cidika Travel |
|
|
426
|
-
| `adaptTokoSepatuData({ raw, filters, range, dateLocale, languageCode, labels })` | Adapter Toko Sepatu |
|
|
427
|
-
| `createEmptyTokoSepatuData()` | Empty state Toko Sepatu |
|
|
428
|
-
| `adaptDummyUmkmData({ raw, filters, range, dateLocale, languageCode, labels })` | Adapter Dummy UMKM |
|
|
429
|
-
| `createEmptyDummyUmkmData()` | Empty state Dummy UMKM |
|
|
138
|
+
// View
|
|
139
|
+
import { ReusableDashboardView }
|
|
430
140
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|------|-----------|
|
|
435
|
-
| `useReusableDashboard(options)` | Hook utama orkestrasi dashboard |
|
|
436
|
-
| `useRealtimeUpdate(options)` | Hook subscription Supabase Realtime |
|
|
437
|
-
|
|
438
|
-
**useReusableDashboard options:**
|
|
439
|
-
|
|
440
|
-
```typescript
|
|
441
|
-
{
|
|
442
|
-
config: object, // Widget configuration
|
|
443
|
-
dataSource: object, // Objek dengan fetchDashboardSnapshot + subscribeLiveUpdate
|
|
444
|
-
adapter: Function, // ({ raw, filters, range, dateLocale, languageCode, labels }) => data
|
|
445
|
-
createEmptyState: Function, // () => data kosong
|
|
446
|
-
languageCode: string, // "id" | "en"
|
|
447
|
-
dateLocale: string, // "id-ID" | "en-US"
|
|
448
|
-
labels: object, // Objek label teks UI
|
|
449
|
-
}
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
**Return value useReusableDashboard:**
|
|
453
|
-
|
|
454
|
-
```typescript
|
|
455
|
-
{
|
|
456
|
-
data: object, // Data dashboard terproses
|
|
457
|
-
loading: boolean, // Status loading
|
|
458
|
-
error: string, // Pesan error (kosong jika tidak ada)
|
|
459
|
-
filters: object, // State filter aktif
|
|
460
|
-
updateFilter: (field, value) => void, // Update satu filter
|
|
461
|
-
resetFilters: () => void, // Reset semua filter ke default
|
|
462
|
-
refresh: ({ silent?: boolean }) => void, // Muat ulang data
|
|
463
|
-
range: { fromISO, toISO, daysWindow }, // Rentang tanggal aktif
|
|
464
|
-
liveUpdateEnabled: boolean, // Apakah realtime subscription aktif
|
|
465
|
-
lastUpdatedAt: Date | null, // Waktu terakhir data diperbarui
|
|
466
|
-
}
|
|
141
|
+
// Utils
|
|
142
|
+
import { createDashboardLabels, createDefaultFilters, resolveDateRange }
|
|
143
|
+
import { formatIDR, formatDate, shortId, formatYYYYMMDD }
|
|
467
144
|
```
|
|
468
145
|
|
|
469
|
-
### Components
|
|
470
|
-
|
|
471
|
-
#### Atoms
|
|
472
|
-
| Komponen | Props Utama | Deskripsi |
|
|
473
|
-
|----------|-------------|-----------|
|
|
474
|
-
| `Button` | `variant` (primary\|secondary\|ghost), `size` (sm\|md\|lg), `onClick`, `disabled` | Tombol aksi |
|
|
475
|
-
| `Input` | `type`, `value`, `onChange`, `placeholder`, `label`, `disabled` | Field teks |
|
|
476
|
-
| `Icon` | `name` (nama Lucide), `size`, `className` | Ikon SVG via registry |
|
|
477
|
-
| `Typography` | `variant` (h1\|h2\|h3\|subheading\|body\|caption\|metric) | Teks hierarki |
|
|
478
|
-
| `Badge` | `status` (confirmed\|pending\|cancelled\|success\|info\|default) | Label status |
|
|
479
|
-
| `SkeletonLoader` | `className` | Placeholder loading |
|
|
480
|
-
|
|
481
|
-
#### Molecules
|
|
482
|
-
| Komponen | Props Utama | Deskripsi |
|
|
483
|
-
|----------|-------------|-----------|
|
|
484
|
-
| `StatCard` | `label`, `value`, `icon`, `format` (number\|currency\|percent), `trend` (up\|down) | Kartu metrik |
|
|
485
|
-
| `SearchBar` | `value`, `onSearch`, `placeholder` | Input pencarian |
|
|
486
|
-
| `DateRangeFilter` | `value`, `onChange` | Filter tanggal |
|
|
487
|
-
| `ChartHeader` | `title`, `icon`, `actions` | Header grafik |
|
|
488
|
-
|
|
489
|
-
#### Organisms
|
|
490
|
-
| Komponen | Props Utama | Deskripsi |
|
|
491
|
-
|----------|-------------|-----------|
|
|
492
|
-
| `DataTable` | `columns`, `data`, `labels`, `dateLocale`, `searchable`, `sortable`, `pageSize`, `emptyLabel` | Tabel interaktif |
|
|
493
|
-
| `ChartCard` | `widget`, `labels`, `loading`, `filters`, `chartData` | Kartu grafik |
|
|
494
|
-
| `FilterPanel` | `filters`, `labels`, `onFilterChange`, `onResetFilters` | Panel filter |
|
|
495
|
-
| `SidebarNavigation` | `items`, `activeId`, `onSelect` | Sidebar nav |
|
|
496
|
-
| `TopbarHeader` | `title`, `onRefresh`, `liveUpdateEnabled` | Header atas |
|
|
497
|
-
|
|
498
|
-
#### Templates & Pages
|
|
499
|
-
| Komponen | Deskripsi |
|
|
500
|
-
|----------|-----------|
|
|
501
|
-
| `DashboardLayout` | Layout dengan slot sidebar dan main content |
|
|
502
|
-
| `ReusableDashboardView` | Halaman dashboard lengkap (gunakan ini untuk integrasi cepat) |
|
|
503
|
-
|
|
504
|
-
### Utility Functions
|
|
505
|
-
|
|
506
|
-
| Function | Signature | Deskripsi |
|
|
507
|
-
|----------|-----------|-----------|
|
|
508
|
-
| `createDashboardLabels(t)` | `t: Function` | Buat objek label dari fungsi i18n |
|
|
509
|
-
| `createDefaultFilters(base?)` | `base?: object` | Buat filter default |
|
|
510
|
-
| `resolveDateRange({ daysPreset, dateFrom, dateTo })` | — | Resolve range ke `{ fromISO, toISO, daysWindow }` |
|
|
511
|
-
| `formatYYYYMMDD(date)` | `date: Date` | Format tanggal ke `YYYY-MM-DD` |
|
|
512
|
-
| `formatIDR(value)` | `value: number` | Format angka ke Rupiah (`1.500.000`) |
|
|
513
|
-
| `formatDate(value, locale)` | `value: string, locale: string` | Format tanggal dengan locale |
|
|
514
|
-
| `shortId(value, length?)` | `value: string, length?: number` | Potong string (default 8 karakter) |
|
|
515
|
-
| `toNumber(value)` | `value: any` | Konversi ke angka finite, fallback 0 |
|
|
516
|
-
| `buildDayBuckets(daysWindow, fromISO, dateLocale)` | — | Bangun array bucket harian untuk chart |
|
|
517
|
-
| `sortMapEntries(map, direction)` | `direction: "asc"\|"desc"` | Urutkan entri Map berdasarkan nilai |
|
|
518
|
-
| `resolveIcon(name)` | `name: string` | Resolve nama string ke komponen Lucide |
|
|
519
|
-
|
|
520
146
|
---
|
|
521
147
|
|
|
522
|
-
##
|
|
148
|
+
## Development
|
|
523
149
|
|
|
524
150
|
```bash
|
|
525
|
-
|
|
526
|
-
git clone <repo-url>
|
|
151
|
+
git clone <repo>
|
|
527
152
|
cd reusable-dashboard-umkm
|
|
528
153
|
npm install
|
|
529
|
-
|
|
530
|
-
# Build production
|
|
531
|
-
npm run build
|
|
532
|
-
|
|
533
|
-
# Run tests
|
|
154
|
+
npm run build # output ke dist/
|
|
534
155
|
npm test
|
|
535
|
-
|
|
536
|
-
# Test via npm link
|
|
537
|
-
npm link
|
|
538
|
-
cd /path/to/your-project
|
|
539
|
-
npm link @rozaqi02/reusable-dashboard
|
|
540
156
|
```
|
|
541
157
|
|
|
542
|
-
**Output build** (`dist/`):
|
|
543
|
-
- `dist/index.js` — ESM (untuk Vite, modern bundler)
|
|
544
|
-
- `dist/index.cjs` — CommonJS (untuk CRA, Node.js)
|
|
545
|
-
- `dist/index.js.map` + `dist/index.cjs.map` — Source maps
|
|
546
|
-
|
|
547
158
|
---
|
|
548
159
|
|
|
549
|
-
##
|
|
550
|
-
|
|
551
|
-
| Folder | Domain | Konten |
|
|
552
|
-
|--------|--------|--------|
|
|
553
|
-
| `examples/cidika-travel-example/` | Travel agency | `AdminDashboard.jsx`, schema SQL |
|
|
554
|
-
| `examples/toko-sepatu-example/` | Toko online | `TokoSepatuDashboard.jsx`, schema SQL, seed data |
|
|
555
|
-
| `examples/dummy-umkm-example/` | UMKM generik | `DummyUmkmDashboard.jsx` |
|
|
556
|
-
|
|
557
|
-
---
|
|
558
|
-
|
|
559
|
-
## 🔒 License
|
|
560
|
-
|
|
561
|
-
Proprietary — hanya untuk penggunaan oleh pihak yang berwenang.
|
|
562
|
-
|
|
563
|
-
---
|
|
564
|
-
|
|
565
|
-
## 📞 Support
|
|
566
|
-
|
|
567
|
-
- Email: abrorrozaqi@gmail.com
|
|
568
|
-
|
|
569
|
-
---
|
|
160
|
+
## License
|
|
570
161
|
|
|
571
|
-
|
|
162
|
+
MIT — Ahmad Abror Rozaqi Fatoni
|