@rozaqi02/reusable-dashboard 1.0.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 +13 -0
- package/LICENSE +21 -0
- package/README.md +571 -0
- package/dist/index.cjs +2340 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +2293 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
## [1.0.0] - 2026-04-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
1. Initial reusable dashboard module release.
|
|
10
|
+
2. Reusable dashboard presentation components (`stat`, `chart`, `table`, `layout`).
|
|
11
|
+
3. Widget configuration layer for travel and dummy UMKM domain.
|
|
12
|
+
4. Data adapter and Supabase data source with live update subscription.
|
|
13
|
+
5. Hook orchestration for filters, snapshot refresh, and realtime update.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ahmad Abror Rozaqi Fatoni
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
# @rozaqi02/reusable-dashboard
|
|
2
|
+
|
|
3
|
+
Modul dashboard admin reusable untuk UMKM berbasis React + Atomic Design + Supabase.
|
|
4
|
+
Mendukung berbagai domain bisnis (travel, toko online, dll.) tanpa mengubah komponen inti —
|
|
5
|
+
cukup definisikan widget configuration dan data adapter yang sesuai.
|
|
6
|
+
|
|
7
|
+
[](./CHANGELOG.md)
|
|
8
|
+
[](./LICENSE)
|
|
9
|
+
[](#)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 📦 Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @rozaqi02/reusable-dashboard
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## ✅ Prerequisites
|
|
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)).
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 🚀 Quick Start
|
|
40
|
+
|
|
41
|
+
### Step 1: Setup Supabase Client
|
|
42
|
+
|
|
43
|
+
Buat file `src/lib/supabaseClient.js`:
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
import { createClient } from '@supabase/supabase-js';
|
|
47
|
+
|
|
48
|
+
export const supabase = createClient(
|
|
49
|
+
import.meta.env.VITE_SUPABASE_URL, // Vite
|
|
50
|
+
import.meta.env.VITE_SUPABASE_ANON_KEY // Vite
|
|
51
|
+
// atau process.env.REACT_APP_SUPABASE_URL jika pakai Create React App
|
|
52
|
+
);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Buat file `.env` di root project:
|
|
56
|
+
|
|
57
|
+
```env
|
|
58
|
+
# Vite
|
|
59
|
+
VITE_SUPABASE_URL=https://your-project.supabase.co
|
|
60
|
+
VITE_SUPABASE_ANON_KEY=your-anon-key
|
|
61
|
+
|
|
62
|
+
# Create React App
|
|
63
|
+
# REACT_APP_SUPABASE_URL=https://your-project.supabase.co
|
|
64
|
+
# REACT_APP_SUPABASE_ANON_KEY=your-anon-key
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Step 2: Setup Database Schema
|
|
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**:
|
|
79
|
+
|
|
80
|
+
```jsx
|
|
81
|
+
import React, { useMemo } from "react";
|
|
82
|
+
import { supabase } from "./lib/supabaseClient";
|
|
83
|
+
import {
|
|
84
|
+
ReusableDashboardView,
|
|
85
|
+
tokoSepatuWidgetConfig,
|
|
86
|
+
createTokoSepatuSupabaseSource,
|
|
87
|
+
adaptTokoSepatuData,
|
|
88
|
+
createEmptyTokoSepatuData,
|
|
89
|
+
useReusableDashboard,
|
|
90
|
+
} from "@rozaqi02/reusable-dashboard";
|
|
91
|
+
|
|
92
|
+
// Inisialisasi data source satu kali di luar komponen
|
|
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
|
+
};
|
|
139
|
+
|
|
140
|
+
export default function Dashboard() {
|
|
141
|
+
const state = useReusableDashboard({
|
|
142
|
+
config: tokoSepatuWidgetConfig,
|
|
143
|
+
dataSource: source,
|
|
144
|
+
adapter: adaptTokoSepatuData,
|
|
145
|
+
createEmptyState: createEmptyTokoSepatuData,
|
|
146
|
+
languageCode: "id",
|
|
147
|
+
dateLocale: "id-ID",
|
|
148
|
+
labels,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<ReusableDashboardView
|
|
153
|
+
config={tokoSepatuWidgetConfig}
|
|
154
|
+
labels={labels}
|
|
155
|
+
loading={state.loading}
|
|
156
|
+
error={state.error}
|
|
157
|
+
filters={state.filters}
|
|
158
|
+
onFilterChange={state.updateFilter}
|
|
159
|
+
onResetFilters={state.resetFilters}
|
|
160
|
+
onRefresh={state.refresh}
|
|
161
|
+
data={state.data}
|
|
162
|
+
dateLocale="id-ID"
|
|
163
|
+
liveUpdateEnabled={state.liveUpdateEnabled}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
> **Menggunakan react-i18next?** Gunakan `createDashboardLabels(t)` dari export modul
|
|
170
|
+
> sebagai pengganti objek labels di atas. Lihat [bagian i18n](#-label--i18n).
|
|
171
|
+
|
|
172
|
+
### Step 4: Jalankan Aplikasi
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npm run dev # Vite
|
|
176
|
+
# atau
|
|
177
|
+
npm start # Create React App
|
|
178
|
+
```
|
|
179
|
+
|
|
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.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 🎨 Customization
|
|
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();
|
|
334
|
+
|
|
335
|
+
const orders = raw.bookings || [];
|
|
336
|
+
let totalConfirmed = 0;
|
|
337
|
+
let totalRevenue = 0;
|
|
338
|
+
|
|
339
|
+
orders.forEach((row) => {
|
|
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
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## 🌐 Label & i18n
|
|
379
|
+
|
|
380
|
+
### Tanpa i18n (plain object)
|
|
381
|
+
|
|
382
|
+
Buat objek label secara manual (lihat contoh di Quick Start).
|
|
383
|
+
|
|
384
|
+
### Dengan react-i18next
|
|
385
|
+
|
|
386
|
+
```javascript
|
|
387
|
+
import { useTranslation } from "react-i18next";
|
|
388
|
+
import { createDashboardLabels } from "@rozaqi02/reusable-dashboard";
|
|
389
|
+
|
|
390
|
+
function Dashboard() {
|
|
391
|
+
const { t, i18n } = useTranslation();
|
|
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
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## 📖 API Reference
|
|
404
|
+
|
|
405
|
+
### Configs
|
|
406
|
+
|
|
407
|
+
| Export | Deskripsi |
|
|
408
|
+
|--------|-----------|
|
|
409
|
+
| `cidikaWidgetConfig` | Config dashboard travel agency |
|
|
410
|
+
| `tokoSepatuWidgetConfig` | Config dashboard toko online |
|
|
411
|
+
| `dummyUmkmWidgetConfig` | Config dashboard bisnis generik |
|
|
412
|
+
|
|
413
|
+
### Data Sources
|
|
414
|
+
|
|
415
|
+
| Export | Parameter | Deskripsi |
|
|
416
|
+
|--------|-----------|-----------|
|
|
417
|
+
| `createCidikaSupabaseSource(supabase)` | Supabase client | Data source Cidika Travel |
|
|
418
|
+
| `createTokoSepatuSupabaseSource(supabase)` | Supabase client | Data source Toko Sepatu |
|
|
419
|
+
|
|
420
|
+
### Data Adapters
|
|
421
|
+
|
|
422
|
+
| Export | Deskripsi |
|
|
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 |
|
|
430
|
+
|
|
431
|
+
### Hooks
|
|
432
|
+
|
|
433
|
+
| Hook | Deskripsi |
|
|
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
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
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
|
+
---
|
|
521
|
+
|
|
522
|
+
## 🛠️ Development
|
|
523
|
+
|
|
524
|
+
```bash
|
|
525
|
+
# Clone & install
|
|
526
|
+
git clone <repo-url>
|
|
527
|
+
cd reusable-dashboard-umkm
|
|
528
|
+
npm install
|
|
529
|
+
|
|
530
|
+
# Build production
|
|
531
|
+
npm run build
|
|
532
|
+
|
|
533
|
+
# Run tests
|
|
534
|
+
npm test
|
|
535
|
+
|
|
536
|
+
# Test via npm link
|
|
537
|
+
npm link
|
|
538
|
+
cd /path/to/your-project
|
|
539
|
+
npm link @rozaqi02/reusable-dashboard
|
|
540
|
+
```
|
|
541
|
+
|
|
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
|
+
---
|
|
548
|
+
|
|
549
|
+
## 📄 Examples
|
|
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
|
+
---
|
|
570
|
+
|
|
571
|
+
**Made with ❤️ for UMKM Indonesia**
|