@rozaqi02/reusable-dashboard 1.0.0 → 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 +220 -484
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +742 -0
- package/dist/index.css.map +1 -0
- package/dist/index.js +220 -484
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
package/dist/index.cjs
CHANGED
|
@@ -91,28 +91,32 @@ var cidikaWidgetConfig = {
|
|
|
91
91
|
label: "confirmedBookings",
|
|
92
92
|
icon: "TrendingUp",
|
|
93
93
|
valueKey: "bookingsConfirm",
|
|
94
|
-
format: "number"
|
|
94
|
+
format: "number",
|
|
95
|
+
accentColor: "blue"
|
|
95
96
|
},
|
|
96
97
|
{
|
|
97
98
|
id: "revenueConfirm",
|
|
98
99
|
label: "confirmedRevenue",
|
|
99
100
|
icon: "DollarSign",
|
|
100
101
|
valueKey: "revenueConfirm",
|
|
101
|
-
format: "currency"
|
|
102
|
+
format: "currency",
|
|
103
|
+
accentColor: "green"
|
|
102
104
|
},
|
|
103
105
|
{
|
|
104
106
|
id: "avgRevenue",
|
|
105
107
|
label: "avgRevenue",
|
|
106
108
|
icon: "Users",
|
|
107
109
|
valueKey: "avgRevenue",
|
|
108
|
-
format: "currency"
|
|
110
|
+
format: "currency",
|
|
111
|
+
accentColor: "violet"
|
|
109
112
|
},
|
|
110
113
|
{
|
|
111
114
|
id: "conversionRate",
|
|
112
115
|
label: "conversionRate",
|
|
113
116
|
icon: "PieChart",
|
|
114
117
|
valueKey: "conversionRate",
|
|
115
|
-
format: "percent"
|
|
118
|
+
format: "percent",
|
|
119
|
+
accentColor: "orange"
|
|
116
120
|
}
|
|
117
121
|
],
|
|
118
122
|
charts: [
|
|
@@ -238,28 +242,32 @@ var tokoSepatuWidgetConfig = {
|
|
|
238
242
|
label: "confirmedBookings",
|
|
239
243
|
icon: "TrendingUp",
|
|
240
244
|
valueKey: "bookingsConfirm",
|
|
241
|
-
format: "number"
|
|
245
|
+
format: "number",
|
|
246
|
+
accentColor: "sky"
|
|
242
247
|
},
|
|
243
248
|
{
|
|
244
249
|
id: "totalRevenue",
|
|
245
250
|
label: "confirmedRevenue",
|
|
246
251
|
icon: "DollarSign",
|
|
247
252
|
valueKey: "revenueConfirm",
|
|
248
|
-
format: "currency"
|
|
253
|
+
format: "currency",
|
|
254
|
+
accentColor: "green"
|
|
249
255
|
},
|
|
250
256
|
{
|
|
251
257
|
id: "avgOrderValue",
|
|
252
258
|
label: "avgRevenue",
|
|
253
259
|
icon: "BarChart3",
|
|
254
260
|
valueKey: "avgRevenue",
|
|
255
|
-
format: "currency"
|
|
261
|
+
format: "currency",
|
|
262
|
+
accentColor: "violet"
|
|
256
263
|
},
|
|
257
264
|
{
|
|
258
265
|
id: "totalProducts",
|
|
259
266
|
label: "totalProducts",
|
|
260
267
|
icon: "PieChart",
|
|
261
268
|
valueKey: "packages",
|
|
262
|
-
format: "number"
|
|
269
|
+
format: "number",
|
|
270
|
+
accentColor: "orange"
|
|
263
271
|
}
|
|
264
272
|
],
|
|
265
273
|
charts: [
|
|
@@ -1094,42 +1102,20 @@ function Button({
|
|
|
1094
1102
|
children,
|
|
1095
1103
|
...rest
|
|
1096
1104
|
}) {
|
|
1097
|
-
const
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
sm: "px-2.5 py-1 text-xs gap-1",
|
|
1105
|
-
md: "px-3 py-1.5 text-sm gap-1.5",
|
|
1106
|
-
lg: "px-4 py-2 text-base gap-2"
|
|
1107
|
-
};
|
|
1108
|
-
const classes = [base, variants[variant] || variants.primary, sizes[size] || sizes.md, className].filter(Boolean).join(" ");
|
|
1109
|
-
return /* @__PURE__ */ import_react3.default.createElement(
|
|
1110
|
-
"button",
|
|
1111
|
-
{
|
|
1112
|
-
type: "button",
|
|
1113
|
-
className: classes,
|
|
1114
|
-
disabled,
|
|
1115
|
-
onClick,
|
|
1116
|
-
...rest
|
|
1117
|
-
},
|
|
1118
|
-
children
|
|
1119
|
-
);
|
|
1105
|
+
const classes = [
|
|
1106
|
+
"rdb-btn",
|
|
1107
|
+
`rdb-btn-${variant}`,
|
|
1108
|
+
`rdb-btn-${size}`,
|
|
1109
|
+
className
|
|
1110
|
+
].filter(Boolean).join(" ");
|
|
1111
|
+
return /* @__PURE__ */ import_react3.default.createElement("button", { type: "button", className: classes, disabled, onClick, ...rest }, children);
|
|
1120
1112
|
}
|
|
1121
1113
|
Button.propTypes = {
|
|
1122
|
-
/** Varian visual tombol. */
|
|
1123
1114
|
variant: import_prop_types.default.oneOf(["primary", "secondary", "ghost"]),
|
|
1124
|
-
/** Ukuran tombol. */
|
|
1125
1115
|
size: import_prop_types.default.oneOf(["sm", "md", "lg"]),
|
|
1126
|
-
/** Apakah tombol non-aktif. */
|
|
1127
1116
|
disabled: import_prop_types.default.bool,
|
|
1128
|
-
/** ClassName tambahan dari consumer. */
|
|
1129
1117
|
className: import_prop_types.default.string,
|
|
1130
|
-
/** Callback saat tombol diklik. */
|
|
1131
1118
|
onClick: import_prop_types.default.func,
|
|
1132
|
-
/** Konten tombol. */
|
|
1133
1119
|
children: import_prop_types.default.node.isRequired
|
|
1134
1120
|
};
|
|
1135
1121
|
|
|
@@ -1146,14 +1132,7 @@ function Input({
|
|
|
1146
1132
|
className = "",
|
|
1147
1133
|
...rest
|
|
1148
1134
|
}) {
|
|
1149
|
-
|
|
1150
|
-
"px-3 py-2 rounded-2xl border border-slate-300 dark:border-slate-600",
|
|
1151
|
-
"bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100",
|
|
1152
|
-
"text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500",
|
|
1153
|
-
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
1154
|
-
className
|
|
1155
|
-
].filter(Boolean).join(" ");
|
|
1156
|
-
return /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex flex-col gap-1" }, label ? /* @__PURE__ */ import_react4.default.createElement("label", { className: "text-xs font-medium text-slate-500 dark:text-slate-400" }, label) : null, /* @__PURE__ */ import_react4.default.createElement(
|
|
1135
|
+
return /* @__PURE__ */ import_react4.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 4 } }, label ? /* @__PURE__ */ import_react4.default.createElement("label", { className: "rdb-label" }, label) : null, /* @__PURE__ */ import_react4.default.createElement(
|
|
1157
1136
|
"input",
|
|
1158
1137
|
{
|
|
1159
1138
|
type,
|
|
@@ -1161,25 +1140,18 @@ function Input({
|
|
|
1161
1140
|
onChange,
|
|
1162
1141
|
placeholder,
|
|
1163
1142
|
disabled,
|
|
1164
|
-
className:
|
|
1143
|
+
className: ["rdb-input", className].filter(Boolean).join(" "),
|
|
1165
1144
|
...rest
|
|
1166
1145
|
}
|
|
1167
1146
|
));
|
|
1168
1147
|
}
|
|
1169
1148
|
Input.propTypes = {
|
|
1170
|
-
/** Tipe input. */
|
|
1171
1149
|
type: import_prop_types2.default.string,
|
|
1172
|
-
/** Nilai input. */
|
|
1173
1150
|
value: import_prop_types2.default.oneOfType([import_prop_types2.default.string, import_prop_types2.default.number]),
|
|
1174
|
-
/** Callback saat nilai berubah. */
|
|
1175
1151
|
onChange: import_prop_types2.default.func,
|
|
1176
|
-
/** Placeholder teks. */
|
|
1177
1152
|
placeholder: import_prop_types2.default.string,
|
|
1178
|
-
/** Label di atas input. */
|
|
1179
1153
|
label: import_prop_types2.default.string,
|
|
1180
|
-
/** Apakah input non-aktif. */
|
|
1181
1154
|
disabled: import_prop_types2.default.bool,
|
|
1182
|
-
/** ClassName tambahan. */
|
|
1183
1155
|
className: import_prop_types2.default.string
|
|
1184
1156
|
};
|
|
1185
1157
|
|
|
@@ -1222,116 +1194,108 @@ Icon.propTypes = {
|
|
|
1222
1194
|
// src/presentation/atoms/Typography.jsx
|
|
1223
1195
|
var import_react6 = __toESM(require("react"), 1);
|
|
1224
1196
|
var import_prop_types4 = __toESM(require("prop-types"), 1);
|
|
1225
|
-
var
|
|
1226
|
-
h1:
|
|
1227
|
-
h2:
|
|
1228
|
-
h3:
|
|
1229
|
-
subheading:
|
|
1230
|
-
body:
|
|
1231
|
-
caption:
|
|
1232
|
-
metric:
|
|
1197
|
+
var VARIANT_CLASS = {
|
|
1198
|
+
h1: "rdb-h1",
|
|
1199
|
+
h2: "rdb-h2",
|
|
1200
|
+
h3: "rdb-h3",
|
|
1201
|
+
subheading: "rdb-subheading",
|
|
1202
|
+
body: "rdb-body",
|
|
1203
|
+
caption: "rdb-caption",
|
|
1204
|
+
metric: "rdb-metric"
|
|
1233
1205
|
};
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1206
|
+
var VARIANT_TAG = {
|
|
1207
|
+
h1: "h1",
|
|
1208
|
+
h2: "h2",
|
|
1209
|
+
h3: "h3",
|
|
1210
|
+
subheading: "p",
|
|
1211
|
+
body: "p",
|
|
1212
|
+
caption: "span",
|
|
1213
|
+
metric: "span"
|
|
1214
|
+
};
|
|
1215
|
+
function Typography({ variant = "body", className = "", children, ...rest }) {
|
|
1216
|
+
const Tag = VARIANT_TAG[variant] || "p";
|
|
1217
|
+
const cls = [VARIANT_CLASS[variant] || "rdb-body", className].filter(Boolean).join(" ");
|
|
1218
|
+
return /* @__PURE__ */ import_react6.default.createElement(Tag, { className: cls, ...rest }, children);
|
|
1244
1219
|
}
|
|
1245
1220
|
Typography.propTypes = {
|
|
1246
|
-
/** Varian tipografi. */
|
|
1247
1221
|
variant: import_prop_types4.default.oneOf(["h1", "h2", "h3", "subheading", "body", "caption", "metric"]),
|
|
1248
|
-
/** ClassName tambahan. */
|
|
1249
1222
|
className: import_prop_types4.default.string,
|
|
1250
|
-
/** Konten teks. */
|
|
1251
1223
|
children: import_prop_types4.default.node.isRequired
|
|
1252
1224
|
};
|
|
1253
1225
|
|
|
1254
1226
|
// src/presentation/atoms/Badge.jsx
|
|
1255
1227
|
var import_react7 = __toESM(require("react"), 1);
|
|
1256
1228
|
var import_prop_types5 = __toESM(require("prop-types"), 1);
|
|
1257
|
-
var
|
|
1258
|
-
confirmed: "
|
|
1259
|
-
pending: "
|
|
1260
|
-
cancelled: "
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1229
|
+
var STATUS_CLASS = {
|
|
1230
|
+
confirmed: "rdb-badge-confirmed",
|
|
1231
|
+
pending: "rdb-badge-pending",
|
|
1232
|
+
cancelled: "rdb-badge-cancelled",
|
|
1233
|
+
success: "rdb-badge-success",
|
|
1234
|
+
info: "rdb-badge-info",
|
|
1235
|
+
default: "rdb-badge-default"
|
|
1264
1236
|
};
|
|
1265
1237
|
function Badge({ status = "default", className = "", children, ...rest }) {
|
|
1266
|
-
const
|
|
1267
|
-
|
|
1268
|
-
return /* @__PURE__ */ import_react7.default.createElement("span", { className: classes, ...rest }, children);
|
|
1238
|
+
const statusClass = STATUS_CLASS[status] || STATUS_CLASS.default;
|
|
1239
|
+
return /* @__PURE__ */ import_react7.default.createElement("span", { className: ["rdb-badge", statusClass, className].filter(Boolean).join(" "), ...rest }, children);
|
|
1269
1240
|
}
|
|
1270
1241
|
Badge.propTypes = {
|
|
1271
|
-
/** Status yang menentukan warna badge. */
|
|
1272
1242
|
status: import_prop_types5.default.string,
|
|
1273
|
-
/** ClassName tambahan. */
|
|
1274
1243
|
className: import_prop_types5.default.string,
|
|
1275
|
-
/** Label teks badge. */
|
|
1276
1244
|
children: import_prop_types5.default.node.isRequired
|
|
1277
1245
|
};
|
|
1278
1246
|
|
|
1279
1247
|
// src/presentation/atoms/SkeletonLoader.jsx
|
|
1280
1248
|
var import_react8 = __toESM(require("react"), 1);
|
|
1281
1249
|
var import_prop_types6 = __toESM(require("prop-types"), 1);
|
|
1282
|
-
function SkeletonLoader({ className = "" }) {
|
|
1250
|
+
function SkeletonLoader({ className = "", style = {} }) {
|
|
1283
1251
|
return /* @__PURE__ */ import_react8.default.createElement(
|
|
1284
1252
|
"div",
|
|
1285
1253
|
{
|
|
1286
|
-
className:
|
|
1254
|
+
className: ["rdb-skeleton", className].filter(Boolean).join(" "),
|
|
1255
|
+
style: { minHeight: 20, ...style },
|
|
1287
1256
|
role: "status",
|
|
1288
1257
|
"aria-label": "Loading..."
|
|
1289
1258
|
}
|
|
1290
1259
|
);
|
|
1291
1260
|
}
|
|
1292
1261
|
SkeletonLoader.propTypes = {
|
|
1293
|
-
|
|
1294
|
-
|
|
1262
|
+
className: import_prop_types6.default.string,
|
|
1263
|
+
style: import_prop_types6.default.object
|
|
1295
1264
|
};
|
|
1296
1265
|
|
|
1297
1266
|
// src/presentation/molecules/StatCard.jsx
|
|
1298
1267
|
var import_react9 = __toESM(require("react"), 1);
|
|
1299
1268
|
var import_prop_types7 = __toESM(require("prop-types"), 1);
|
|
1269
|
+
function renderValue(format, value) {
|
|
1270
|
+
if (format === "currency") return `Rp ${formatIDR(value)}`;
|
|
1271
|
+
if (format === "percent") return `${Number(value) || 0}%`;
|
|
1272
|
+
return String(Number(value) || 0);
|
|
1273
|
+
}
|
|
1300
1274
|
function StatCard({
|
|
1301
1275
|
label,
|
|
1302
1276
|
value,
|
|
1303
1277
|
icon = "TrendingUp",
|
|
1304
1278
|
format = "number",
|
|
1305
1279
|
trend = null,
|
|
1280
|
+
accentColor = "blue",
|
|
1306
1281
|
className = ""
|
|
1307
1282
|
}) {
|
|
1308
|
-
|
|
1309
|
-
return /* @__PURE__ */ import_react9.default.createElement("div", { className: `card p-4 flex flex-col justify-between gap-2 ${className}` }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "flex items-center gap-2 text-slate-500 dark:text-slate-400" }, /* @__PURE__ */ import_react9.default.createElement(Icon, { name: icon, size: 20 }), /* @__PURE__ */ import_react9.default.createElement(Typography, { variant: "subheading" }, label)), trend ? /* @__PURE__ */ import_react9.default.createElement(
|
|
1283
|
+
return /* @__PURE__ */ import_react9.default.createElement("div", { className: ["rdb-card rdb-statcard", `rdb-accent-${accentColor}`, className].filter(Boolean).join(" ") }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "rdb-statcard-header" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "rdb-statcard-label-group" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "rdb-statcard-icon" }, /* @__PURE__ */ import_react9.default.createElement(Icon, { name: icon, size: 16 })), /* @__PURE__ */ import_react9.default.createElement("span", { className: "rdb-statcard-label" }, label)), trend ? /* @__PURE__ */ import_react9.default.createElement(
|
|
1310
1284
|
Icon,
|
|
1311
1285
|
{
|
|
1312
1286
|
name: trend === "up" ? "TrendingUp" : "TrendingDown",
|
|
1313
1287
|
size: 16,
|
|
1314
|
-
|
|
1288
|
+
style: { color: trend === "up" ? "#10b981" : "#ef4444" }
|
|
1315
1289
|
}
|
|
1316
|
-
) : null), /* @__PURE__ */ import_react9.default.createElement(
|
|
1317
|
-
}
|
|
1318
|
-
function renderValue(format, value) {
|
|
1319
|
-
if (format === "currency") return `Rp ${formatIDR(value)}`;
|
|
1320
|
-
if (format === "percent") return `${Number(value) || 0}%`;
|
|
1321
|
-
return String(Number(value) || 0);
|
|
1290
|
+
) : null), /* @__PURE__ */ import_react9.default.createElement("div", { className: "rdb-statcard-value" }, renderValue(format, value)));
|
|
1322
1291
|
}
|
|
1323
1292
|
StatCard.propTypes = {
|
|
1324
|
-
/** Label deskriptif metrik. */
|
|
1325
1293
|
label: import_prop_types7.default.string.isRequired,
|
|
1326
|
-
/** Nilai metrik. */
|
|
1327
1294
|
value: import_prop_types7.default.oneOfType([import_prop_types7.default.number, import_prop_types7.default.string]),
|
|
1328
|
-
/** Nama ikon dari registry Lucide React. */
|
|
1329
1295
|
icon: import_prop_types7.default.string,
|
|
1330
|
-
/** Format tampilan nilai. */
|
|
1331
1296
|
format: import_prop_types7.default.oneOf(["number", "currency", "percent"]),
|
|
1332
|
-
/** Arah tren opsional. */
|
|
1333
1297
|
trend: import_prop_types7.default.oneOf(["up", "down", null]),
|
|
1334
|
-
|
|
1298
|
+
accentColor: import_prop_types7.default.string,
|
|
1335
1299
|
className: import_prop_types7.default.string
|
|
1336
1300
|
};
|
|
1337
1301
|
|
|
@@ -1348,45 +1312,33 @@ function SearchBar({
|
|
|
1348
1312
|
const [internalValue, setInternalValue] = (0, import_react10.useState)("");
|
|
1349
1313
|
const isControlled = controlledValue !== void 0;
|
|
1350
1314
|
const currentValue = isControlled ? controlledValue : internalValue;
|
|
1351
|
-
const handleChange = (0, import_react10.useCallback)(
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
);
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
if (event.key === "Enter" && onSearch) {
|
|
1363
|
-
onSearch(currentValue);
|
|
1364
|
-
}
|
|
1365
|
-
},
|
|
1366
|
-
[currentValue, onSearch]
|
|
1367
|
-
);
|
|
1368
|
-
return /* @__PURE__ */ import_react10.default.createElement("div", { className: `relative ${className}` }, /* @__PURE__ */ import_react10.default.createElement("div", { className: "absolute inset-y-0 left-3 flex items-center pointer-events-none" }, /* @__PURE__ */ import_react10.default.createElement(Icon, { name: "Search", size: 16, className: "text-slate-400" })), /* @__PURE__ */ import_react10.default.createElement(
|
|
1369
|
-
Input,
|
|
1315
|
+
const handleChange = (0, import_react10.useCallback)((event) => {
|
|
1316
|
+
const v = event.target.value;
|
|
1317
|
+
if (!isControlled) setInternalValue(v);
|
|
1318
|
+
if (onChange) onChange(v);
|
|
1319
|
+
if (onSearch) onSearch(v);
|
|
1320
|
+
}, [isControlled, onChange, onSearch]);
|
|
1321
|
+
const handleKeyDown = (0, import_react10.useCallback)((event) => {
|
|
1322
|
+
if (event.key === "Enter" && onSearch) onSearch(currentValue);
|
|
1323
|
+
}, [currentValue, onSearch]);
|
|
1324
|
+
return /* @__PURE__ */ import_react10.default.createElement("div", { className: ["rdb-searchbar", className].filter(Boolean).join(" ") }, /* @__PURE__ */ import_react10.default.createElement("div", { className: "rdb-searchbar-icon" }, /* @__PURE__ */ import_react10.default.createElement(Icon, { name: "Search", size: 16 })), /* @__PURE__ */ import_react10.default.createElement(
|
|
1325
|
+
"input",
|
|
1370
1326
|
{
|
|
1371
1327
|
type: "search",
|
|
1372
1328
|
value: currentValue,
|
|
1373
1329
|
onChange: handleChange,
|
|
1374
1330
|
onKeyDown: handleKeyDown,
|
|
1375
1331
|
placeholder,
|
|
1376
|
-
className: "
|
|
1332
|
+
className: "rdb-input rdb-searchbar-input",
|
|
1333
|
+
style: { width: "100%" }
|
|
1377
1334
|
}
|
|
1378
1335
|
));
|
|
1379
1336
|
}
|
|
1380
1337
|
SearchBar.propTypes = {
|
|
1381
|
-
/** Nilai pencarian saat ini (controlled). */
|
|
1382
1338
|
value: import_prop_types8.default.string,
|
|
1383
|
-
/** Callback saat nilai input berubah. */
|
|
1384
1339
|
onChange: import_prop_types8.default.func,
|
|
1385
|
-
/** Callback saat pencarian disubmit. */
|
|
1386
1340
|
onSearch: import_prop_types8.default.func,
|
|
1387
|
-
/** Placeholder teks. */
|
|
1388
1341
|
placeholder: import_prop_types8.default.string,
|
|
1389
|
-
/** ClassName tambahan. */
|
|
1390
1342
|
className: import_prop_types8.default.string
|
|
1391
1343
|
};
|
|
1392
1344
|
|
|
@@ -1396,26 +1348,11 @@ var import_prop_types9 = __toESM(require("prop-types"), 1);
|
|
|
1396
1348
|
var CURRENT_YEAR = (/* @__PURE__ */ new Date()).getFullYear();
|
|
1397
1349
|
var MIN_YEAR2 = 2020;
|
|
1398
1350
|
var MAX_YEAR = CURRENT_YEAR + 1;
|
|
1399
|
-
var MONTHS = [
|
|
1400
|
-
"Jan",
|
|
1401
|
-
"Feb",
|
|
1402
|
-
"Mar",
|
|
1403
|
-
"Apr",
|
|
1404
|
-
"May",
|
|
1405
|
-
"Jun",
|
|
1406
|
-
"Jul",
|
|
1407
|
-
"Aug",
|
|
1408
|
-
"Sep",
|
|
1409
|
-
"Oct",
|
|
1410
|
-
"Nov",
|
|
1411
|
-
"Dec"
|
|
1412
|
-
];
|
|
1351
|
+
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
1413
1352
|
function getYears() {
|
|
1414
|
-
const
|
|
1415
|
-
for (let
|
|
1416
|
-
|
|
1417
|
-
}
|
|
1418
|
-
return years;
|
|
1353
|
+
const y = [];
|
|
1354
|
+
for (let i = MAX_YEAR; i >= MIN_YEAR2; i--) y.push(i);
|
|
1355
|
+
return y;
|
|
1419
1356
|
}
|
|
1420
1357
|
function getDaysInMonth(year, month) {
|
|
1421
1358
|
return new Date(year, month, 0).getDate();
|
|
@@ -1423,68 +1360,57 @@ function getDaysInMonth(year, month) {
|
|
|
1423
1360
|
function parseDate(value) {
|
|
1424
1361
|
if (value && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
1425
1362
|
const [y, m, d] = value.split("-").map(Number);
|
|
1426
|
-
if (y >= MIN_YEAR2 && y <= MAX_YEAR && m >= 1 && m <= 12)
|
|
1363
|
+
if (y >= MIN_YEAR2 && y <= MAX_YEAR && m >= 1 && m <= 12)
|
|
1427
1364
|
return { year: y, month: m, day: d };
|
|
1428
|
-
}
|
|
1429
1365
|
}
|
|
1430
1366
|
const now = /* @__PURE__ */ new Date();
|
|
1431
1367
|
return { year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate() };
|
|
1432
1368
|
}
|
|
1433
|
-
function
|
|
1369
|
+
function fmt({ year, month, day }) {
|
|
1434
1370
|
return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
1435
1371
|
}
|
|
1436
1372
|
function DatePicker({ value, onChange, disabled = false, label = "" }) {
|
|
1437
|
-
const
|
|
1373
|
+
const p = parseDate(value);
|
|
1438
1374
|
const years = getYears();
|
|
1439
|
-
const maxDay = getDaysInMonth(
|
|
1440
|
-
const handleChange = (0, import_react11.useCallback)(
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
[parsed, onChange]
|
|
1448
|
-
);
|
|
1449
|
-
const selectClass = "px-2 py-1.5 rounded-xl border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed";
|
|
1450
|
-
return /* @__PURE__ */ import_react11.default.createElement("div", { className: "flex flex-col gap-1" }, label ? /* @__PURE__ */ import_react11.default.createElement(Typography, { variant: "caption", className: "font-medium" }, label) : null, /* @__PURE__ */ import_react11.default.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ import_react11.default.createElement(
|
|
1375
|
+
const maxDay = getDaysInMonth(p.year, p.month);
|
|
1376
|
+
const handleChange = (0, import_react11.useCallback)((field, val) => {
|
|
1377
|
+
const u = { ...p, [field]: Number(val) };
|
|
1378
|
+
const max = getDaysInMonth(u.year, u.month);
|
|
1379
|
+
if (u.day > max) u.day = max;
|
|
1380
|
+
onChange(fmt(u));
|
|
1381
|
+
}, [p, onChange]);
|
|
1382
|
+
return /* @__PURE__ */ import_react11.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 2 } }, label ? /* @__PURE__ */ import_react11.default.createElement("span", { className: "rdb-caption rdb-label" }, label) : null, /* @__PURE__ */ import_react11.default.createElement("div", { style: { display: "flex", gap: 4, alignItems: "center" } }, /* @__PURE__ */ import_react11.default.createElement(
|
|
1451
1383
|
"select",
|
|
1452
1384
|
{
|
|
1453
|
-
className:
|
|
1454
|
-
|
|
1385
|
+
className: "rdb-select",
|
|
1386
|
+
style: { width: 52 },
|
|
1387
|
+
value: p.day,
|
|
1455
1388
|
onChange: (e) => handleChange("day", e.target.value),
|
|
1456
|
-
disabled
|
|
1457
|
-
"aria-label": `${label} day`
|
|
1389
|
+
disabled
|
|
1458
1390
|
},
|
|
1459
1391
|
Array.from({ length: maxDay }, (_, i) => i + 1).map((d) => /* @__PURE__ */ import_react11.default.createElement("option", { key: d, value: d }, String(d).padStart(2, "0")))
|
|
1460
1392
|
), /* @__PURE__ */ import_react11.default.createElement(
|
|
1461
1393
|
"select",
|
|
1462
1394
|
{
|
|
1463
|
-
className:
|
|
1464
|
-
|
|
1395
|
+
className: "rdb-select",
|
|
1396
|
+
style: { width: 62 },
|
|
1397
|
+
value: p.month,
|
|
1465
1398
|
onChange: (e) => handleChange("month", e.target.value),
|
|
1466
|
-
disabled
|
|
1467
|
-
"aria-label": `${label} month`
|
|
1399
|
+
disabled
|
|
1468
1400
|
},
|
|
1469
1401
|
MONTHS.map((name, idx) => /* @__PURE__ */ import_react11.default.createElement("option", { key: idx + 1, value: idx + 1 }, name))
|
|
1470
1402
|
), /* @__PURE__ */ import_react11.default.createElement(
|
|
1471
1403
|
"select",
|
|
1472
1404
|
{
|
|
1473
|
-
className:
|
|
1474
|
-
|
|
1405
|
+
className: "rdb-select",
|
|
1406
|
+
style: { width: 72 },
|
|
1407
|
+
value: p.year,
|
|
1475
1408
|
onChange: (e) => handleChange("year", e.target.value),
|
|
1476
|
-
disabled
|
|
1477
|
-
"aria-label": `${label} year`
|
|
1409
|
+
disabled
|
|
1478
1410
|
},
|
|
1479
1411
|
years.map((y) => /* @__PURE__ */ import_react11.default.createElement("option", { key: y, value: y }, y))
|
|
1480
1412
|
)));
|
|
1481
1413
|
}
|
|
1482
|
-
DatePicker.propTypes = {
|
|
1483
|
-
value: import_prop_types9.default.string.isRequired,
|
|
1484
|
-
onChange: import_prop_types9.default.func.isRequired,
|
|
1485
|
-
disabled: import_prop_types9.default.bool,
|
|
1486
|
-
label: import_prop_types9.default.string
|
|
1487
|
-
};
|
|
1488
1414
|
function DateRangeFilter({
|
|
1489
1415
|
dateFrom,
|
|
1490
1416
|
dateTo,
|
|
@@ -1494,31 +1420,9 @@ function DateRangeFilter({
|
|
|
1494
1420
|
labelTo = "To",
|
|
1495
1421
|
className = ""
|
|
1496
1422
|
}) {
|
|
1497
|
-
const
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
);
|
|
1501
|
-
const handleToChange = (0, import_react11.useCallback)(
|
|
1502
|
-
(newDate) => onRangeChange == null ? void 0 : onRangeChange({ dateFrom, dateTo: newDate }),
|
|
1503
|
-
[dateFrom, onRangeChange]
|
|
1504
|
-
);
|
|
1505
|
-
return /* @__PURE__ */ import_react11.default.createElement("div", { className: `flex flex-wrap items-end gap-3 ${className}` }, /* @__PURE__ */ import_react11.default.createElement(
|
|
1506
|
-
DatePicker,
|
|
1507
|
-
{
|
|
1508
|
-
value: dateFrom,
|
|
1509
|
-
onChange: handleFromChange,
|
|
1510
|
-
disabled,
|
|
1511
|
-
label: labelFrom
|
|
1512
|
-
}
|
|
1513
|
-
), /* @__PURE__ */ import_react11.default.createElement(
|
|
1514
|
-
DatePicker,
|
|
1515
|
-
{
|
|
1516
|
-
value: dateTo,
|
|
1517
|
-
onChange: handleToChange,
|
|
1518
|
-
disabled,
|
|
1519
|
-
label: labelTo
|
|
1520
|
-
}
|
|
1521
|
-
));
|
|
1423
|
+
const handleFrom = (0, import_react11.useCallback)((v) => onRangeChange == null ? void 0 : onRangeChange({ dateFrom: v, dateTo }), [dateTo, onRangeChange]);
|
|
1424
|
+
const handleTo = (0, import_react11.useCallback)((v) => onRangeChange == null ? void 0 : onRangeChange({ dateFrom, dateTo: v }), [dateFrom, onRangeChange]);
|
|
1425
|
+
return /* @__PURE__ */ import_react11.default.createElement("div", { className: ["rdb-daterange", className].filter(Boolean).join(" ") }, /* @__PURE__ */ import_react11.default.createElement(DatePicker, { value: dateFrom, onChange: handleFrom, disabled, label: labelFrom }), /* @__PURE__ */ import_react11.default.createElement(DatePicker, { value: dateTo, onChange: handleTo, disabled, label: labelTo }));
|
|
1522
1426
|
}
|
|
1523
1427
|
DateRangeFilter.propTypes = {
|
|
1524
1428
|
dateFrom: import_prop_types9.default.string.isRequired,
|
|
@@ -1533,22 +1437,13 @@ DateRangeFilter.propTypes = {
|
|
|
1533
1437
|
// src/presentation/molecules/ChartHeader.jsx
|
|
1534
1438
|
var import_react12 = __toESM(require("react"), 1);
|
|
1535
1439
|
var import_prop_types10 = __toESM(require("prop-types"), 1);
|
|
1536
|
-
function ChartHeader({
|
|
1537
|
-
title,
|
|
1538
|
-
icon = "BarChart3",
|
|
1539
|
-
actions = null,
|
|
1540
|
-
className = ""
|
|
1541
|
-
}) {
|
|
1542
|
-
return /* @__PURE__ */ import_react12.default.createElement("div", { className: `flex items-center justify-between mb-2 ${className}` }, /* @__PURE__ */ import_react12.default.createElement(Typography, { variant: "h3", className: "flex items-center gap-2" }, /* @__PURE__ */ import_react12.default.createElement(Icon, { name: icon, size: 18 }), title), actions);
|
|
1440
|
+
function ChartHeader({ title, icon = "BarChart3", actions = null, className = "" }) {
|
|
1441
|
+
return /* @__PURE__ */ import_react12.default.createElement("div", { className: ["rdb-chart-title-wrap", className].filter(Boolean).join(" ") }, /* @__PURE__ */ import_react12.default.createElement("div", { className: "rdb-chart-title" }, /* @__PURE__ */ import_react12.default.createElement(Icon, { name: icon, size: 18 }), title), actions);
|
|
1543
1442
|
}
|
|
1544
1443
|
ChartHeader.propTypes = {
|
|
1545
|
-
/** Judul grafik. */
|
|
1546
1444
|
title: import_prop_types10.default.string.isRequired,
|
|
1547
|
-
/** Nama ikon header. */
|
|
1548
1445
|
icon: import_prop_types10.default.string,
|
|
1549
|
-
/** Elemen aksi tambahan di sisi kanan. */
|
|
1550
1446
|
actions: import_prop_types10.default.node,
|
|
1551
|
-
/** ClassName tambahan. */
|
|
1552
1447
|
className: import_prop_types10.default.string
|
|
1553
1448
|
};
|
|
1554
1449
|
|
|
@@ -1573,28 +1468,24 @@ function DataTable({
|
|
|
1573
1468
|
const [currentPage, setCurrentPage] = (0, import_react13.useState)(1);
|
|
1574
1469
|
const filteredData = (0, import_react13.useMemo)(() => {
|
|
1575
1470
|
if (!searchQuery.trim()) return data;
|
|
1576
|
-
const
|
|
1471
|
+
const q = searchQuery.toLowerCase();
|
|
1577
1472
|
return data.filter(
|
|
1578
1473
|
(row) => columns.some((col) => {
|
|
1579
|
-
const
|
|
1580
|
-
return
|
|
1474
|
+
const v = row[col.accessor];
|
|
1475
|
+
return v != null && String(v).toLowerCase().includes(q);
|
|
1581
1476
|
})
|
|
1582
1477
|
);
|
|
1583
1478
|
}, [data, searchQuery, columns]);
|
|
1584
1479
|
const sortedData = (0, import_react13.useMemo)(() => {
|
|
1585
1480
|
if (!sortField) return filteredData;
|
|
1586
|
-
|
|
1587
|
-
const
|
|
1588
|
-
const
|
|
1589
|
-
if (typeof
|
|
1590
|
-
return sortDirection === "asc" ?
|
|
1591
|
-
}
|
|
1592
|
-
|
|
1593
|
-
numeric: true
|
|
1594
|
-
});
|
|
1595
|
-
return sortDirection === "asc" ? comparison : -comparison;
|
|
1481
|
+
return [...filteredData].sort((a, b) => {
|
|
1482
|
+
const aV = a[sortField] ?? "";
|
|
1483
|
+
const bV = b[sortField] ?? "";
|
|
1484
|
+
if (typeof aV === "number" && typeof bV === "number")
|
|
1485
|
+
return sortDirection === "asc" ? aV - bV : bV - aV;
|
|
1486
|
+
const cmp = String(aV).localeCompare(String(bV), void 0, { numeric: true });
|
|
1487
|
+
return sortDirection === "asc" ? cmp : -cmp;
|
|
1596
1488
|
});
|
|
1597
|
-
return sorted;
|
|
1598
1489
|
}, [filteredData, sortField, sortDirection]);
|
|
1599
1490
|
const totalPages = Math.max(1, Math.ceil(sortedData.length / pageSize));
|
|
1600
1491
|
const safePage = Math.min(currentPage, totalPages);
|
|
@@ -1602,90 +1493,29 @@ function DataTable({
|
|
|
1602
1493
|
const start = (safePage - 1) * pageSize;
|
|
1603
1494
|
return sortedData.slice(start, start + pageSize);
|
|
1604
1495
|
}, [sortedData, safePage, pageSize]);
|
|
1605
|
-
const handleSearch = (0, import_react13.useCallback)((
|
|
1606
|
-
setSearchQuery(
|
|
1496
|
+
const handleSearch = (0, import_react13.useCallback)((v) => {
|
|
1497
|
+
setSearchQuery(v);
|
|
1607
1498
|
setCurrentPage(1);
|
|
1608
1499
|
}, []);
|
|
1609
|
-
const handleSort = (0, import_react13.useCallback)(
|
|
1610
|
-
(
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
setSortField(accessor);
|
|
1616
|
-
setSortDirection("asc");
|
|
1617
|
-
}
|
|
1618
|
-
setCurrentPage(1);
|
|
1619
|
-
},
|
|
1620
|
-
[sortable, sortField]
|
|
1621
|
-
);
|
|
1622
|
-
const handlePrevPage = (0, import_react13.useCallback)(() => {
|
|
1623
|
-
setCurrentPage((prev) => Math.max(1, prev - 1));
|
|
1624
|
-
}, []);
|
|
1625
|
-
const handleNextPage = (0, import_react13.useCallback)(() => {
|
|
1626
|
-
setCurrentPage((prev) => Math.min(totalPages, prev + 1));
|
|
1627
|
-
}, [totalPages]);
|
|
1628
|
-
return /* @__PURE__ */ import_react13.default.createElement("div", { className: `space-y-3 ${className}` }, searchable ? /* @__PURE__ */ import_react13.default.createElement(
|
|
1629
|
-
SearchBar,
|
|
1630
|
-
{
|
|
1631
|
-
value: searchQuery,
|
|
1632
|
-
onSearch: handleSearch,
|
|
1633
|
-
placeholder: searchPlaceholder,
|
|
1634
|
-
className: "max-w-sm"
|
|
1500
|
+
const handleSort = (0, import_react13.useCallback)((acc) => {
|
|
1501
|
+
if (!sortable) return;
|
|
1502
|
+
if (sortField === acc) setSortDirection((p) => p === "asc" ? "desc" : "asc");
|
|
1503
|
+
else {
|
|
1504
|
+
setSortField(acc);
|
|
1505
|
+
setSortDirection("asc");
|
|
1635
1506
|
}
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
sortable ? "cursor-pointer select-none hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors" : ""
|
|
1643
|
-
].join(" "),
|
|
1644
|
-
onClick: () => handleSort(column.accessor)
|
|
1645
|
-
},
|
|
1646
|
-
/* @__PURE__ */ import_react13.default.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ import_react13.default.createElement("span", null, labels[column.label] || column.label), sortable && sortField === column.accessor ? /* @__PURE__ */ import_react13.default.createElement(
|
|
1647
|
-
Icon,
|
|
1648
|
-
{
|
|
1649
|
-
name: sortDirection === "asc" ? "ArrowUp" : "ArrowDown",
|
|
1650
|
-
size: 14,
|
|
1651
|
-
className: "text-blue-500"
|
|
1652
|
-
}
|
|
1653
|
-
) : null)
|
|
1654
|
-
)))), /* @__PURE__ */ import_react13.default.createElement("tbody", null, paginatedData.map((row) => /* @__PURE__ */ import_react13.default.createElement(
|
|
1655
|
-
"tr",
|
|
1656
|
-
{
|
|
1657
|
-
key: row.id,
|
|
1658
|
-
className: "border-b border-slate-200 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800/50"
|
|
1659
|
-
},
|
|
1660
|
-
columns.map((column) => /* @__PURE__ */ import_react13.default.createElement("td", { key: `${row.id}-${column.id}`, className: "p-3" }, renderCell(column, row, dateLocale)))
|
|
1661
|
-
)), paginatedData.length === 0 ? /* @__PURE__ */ import_react13.default.createElement("tr", null, /* @__PURE__ */ import_react13.default.createElement(
|
|
1662
|
-
"td",
|
|
1663
|
-
{
|
|
1664
|
-
className: "p-6 text-center text-slate-500",
|
|
1665
|
-
colSpan: columns.length
|
|
1666
|
-
},
|
|
1667
|
-
emptyLabel
|
|
1668
|
-
)) : null))), sortedData.length > pageSize ? /* @__PURE__ */ import_react13.default.createElement("div", { className: "flex items-center justify-between px-1" }, /* @__PURE__ */ import_react13.default.createElement("span", { className: "text-xs text-slate-500" }, (safePage - 1) * pageSize + 1, "\u2013", Math.min(safePage * pageSize, sortedData.length), " of", " ", sortedData.length), /* @__PURE__ */ import_react13.default.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ import_react13.default.createElement(
|
|
1669
|
-
"button",
|
|
1670
|
-
{
|
|
1671
|
-
type: "button",
|
|
1672
|
-
className: "p-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 disabled:opacity-40 disabled:cursor-not-allowed transition-colors",
|
|
1673
|
-
onClick: handlePrevPage,
|
|
1674
|
-
disabled: safePage <= 1,
|
|
1675
|
-
"aria-label": "Previous page"
|
|
1676
|
-
},
|
|
1677
|
-
/* @__PURE__ */ import_react13.default.createElement(Icon, { name: "ChevronLeft", size: 16 })
|
|
1678
|
-
), /* @__PURE__ */ import_react13.default.createElement("span", { className: "text-xs text-slate-600 dark:text-slate-300 min-w-[60px] text-center" }, safePage, " / ", totalPages), /* @__PURE__ */ import_react13.default.createElement(
|
|
1679
|
-
"button",
|
|
1507
|
+
setCurrentPage(1);
|
|
1508
|
+
}, [sortable, sortField]);
|
|
1509
|
+
const handlePrev = (0, import_react13.useCallback)(() => setCurrentPage((p) => Math.max(1, p - 1)), []);
|
|
1510
|
+
const handleNext = (0, import_react13.useCallback)(() => setCurrentPage((p) => Math.min(totalPages, p + 1)), [totalPages]);
|
|
1511
|
+
return /* @__PURE__ */ import_react13.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, className }, searchable ? /* @__PURE__ */ import_react13.default.createElement(SearchBar, { value: searchQuery, onSearch: handleSearch, placeholder: searchPlaceholder }) : null, /* @__PURE__ */ import_react13.default.createElement("div", { className: "rdb-table-wrapper" }, /* @__PURE__ */ import_react13.default.createElement("table", { className: "rdb-table" }, /* @__PURE__ */ import_react13.default.createElement("thead", null, /* @__PURE__ */ import_react13.default.createElement("tr", null, columns.map((col) => /* @__PURE__ */ import_react13.default.createElement("th", { key: col.id, onClick: () => handleSort(col.accessor) }, /* @__PURE__ */ import_react13.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 4 } }, /* @__PURE__ */ import_react13.default.createElement("span", null, labels[col.label] || col.label), sortable && sortField === col.accessor ? /* @__PURE__ */ import_react13.default.createElement(
|
|
1512
|
+
Icon,
|
|
1680
1513
|
{
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
},
|
|
1687
|
-
/* @__PURE__ */ import_react13.default.createElement(Icon, { name: "ChevronRight", size: 16 })
|
|
1688
|
-
))) : null);
|
|
1514
|
+
name: sortDirection === "asc" ? "ArrowUp" : "ArrowDown",
|
|
1515
|
+
size: 13,
|
|
1516
|
+
style: { color: "var(--rdb-blue-500)" }
|
|
1517
|
+
}
|
|
1518
|
+
) : null))))), /* @__PURE__ */ import_react13.default.createElement("tbody", null, paginatedData.map((row) => /* @__PURE__ */ import_react13.default.createElement("tr", { key: row.id }, columns.map((col) => /* @__PURE__ */ import_react13.default.createElement("td", { key: `${row.id}-${col.id}` }, renderCell(col, row, dateLocale))))), paginatedData.length === 0 ? /* @__PURE__ */ import_react13.default.createElement("tr", null, /* @__PURE__ */ import_react13.default.createElement("td", { className: "rdb-table-empty", colSpan: columns.length }, emptyLabel)) : null))), sortedData.length > pageSize ? /* @__PURE__ */ import_react13.default.createElement("div", { className: "rdb-pagination" }, /* @__PURE__ */ import_react13.default.createElement("span", { className: "rdb-pagination-info" }, (safePage - 1) * pageSize + 1, "\u2013", Math.min(safePage * pageSize, sortedData.length), " of ", sortedData.length), /* @__PURE__ */ import_react13.default.createElement("div", { className: "rdb-pagination-controls" }, /* @__PURE__ */ import_react13.default.createElement("button", { type: "button", className: "rdb-page-btn", onClick: handlePrev, disabled: safePage <= 1, "aria-label": "Previous" }, /* @__PURE__ */ import_react13.default.createElement(Icon, { name: "ChevronLeft", size: 16 })), /* @__PURE__ */ import_react13.default.createElement("span", { className: "rdb-pagination-page" }, safePage, " / ", totalPages), /* @__PURE__ */ import_react13.default.createElement("button", { type: "button", className: "rdb-page-btn", onClick: handleNext, disabled: safePage >= totalPages, "aria-label": "Next" }, /* @__PURE__ */ import_react13.default.createElement(Icon, { name: "ChevronRight", size: 16 })))) : null);
|
|
1689
1519
|
}
|
|
1690
1520
|
function renderCell(column, row, dateLocale) {
|
|
1691
1521
|
const value = row[column.accessor];
|
|
@@ -1698,33 +1528,21 @@ function renderCell(column, row, dateLocale) {
|
|
|
1698
1528
|
return value || "-";
|
|
1699
1529
|
}
|
|
1700
1530
|
DataTable.propTypes = {
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
import_prop_types11.default.
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
statusAccessor: import_prop_types11.default.string
|
|
1709
|
-
})
|
|
1710
|
-
).isRequired,
|
|
1711
|
-
/** Array data baris. */
|
|
1531
|
+
columns: import_prop_types11.default.arrayOf(import_prop_types11.default.shape({
|
|
1532
|
+
id: import_prop_types11.default.string.isRequired,
|
|
1533
|
+
label: import_prop_types11.default.string.isRequired,
|
|
1534
|
+
accessor: import_prop_types11.default.string.isRequired,
|
|
1535
|
+
type: import_prop_types11.default.oneOf(["date", "currency", "statusBadge"]),
|
|
1536
|
+
statusAccessor: import_prop_types11.default.string
|
|
1537
|
+
})).isRequired,
|
|
1712
1538
|
data: import_prop_types11.default.arrayOf(import_prop_types11.default.object),
|
|
1713
|
-
/** Objek label i18n. */
|
|
1714
1539
|
labels: import_prop_types11.default.object,
|
|
1715
|
-
/** Locale untuk format tanggal. */
|
|
1716
1540
|
dateLocale: import_prop_types11.default.string,
|
|
1717
|
-
/** Apakah tabel mendukung pencarian. */
|
|
1718
1541
|
searchable: import_prop_types11.default.bool,
|
|
1719
|
-
/** Apakah tabel mendukung pengurutan. */
|
|
1720
1542
|
sortable: import_prop_types11.default.bool,
|
|
1721
|
-
/** Jumlah baris per halaman. */
|
|
1722
1543
|
pageSize: import_prop_types11.default.number,
|
|
1723
|
-
/** Label saat data kosong. */
|
|
1724
1544
|
emptyLabel: import_prop_types11.default.string,
|
|
1725
|
-
/** Placeholder pencarian. */
|
|
1726
1545
|
searchPlaceholder: import_prop_types11.default.string,
|
|
1727
|
-
/** ClassName tambahan. */
|
|
1728
1546
|
className: import_prop_types11.default.string
|
|
1729
1547
|
};
|
|
1730
1548
|
|
|
@@ -1878,44 +1696,38 @@ var AUDIENCE_OPTIONS = ["", "domestic", "foreign"];
|
|
|
1878
1696
|
var DAY_PRESETS = [7, 30, 90, 0];
|
|
1879
1697
|
var SORT_BY_OPTIONS = ["bookings", "revenue"];
|
|
1880
1698
|
var SORT_DIR_OPTIONS = ["desc", "asc"];
|
|
1881
|
-
function FilterPanel({
|
|
1882
|
-
|
|
1883
|
-
labels,
|
|
1884
|
-
onFilterChange,
|
|
1885
|
-
onResetFilters,
|
|
1886
|
-
className = ""
|
|
1887
|
-
}) {
|
|
1888
|
-
return /* @__PURE__ */ import_react15.default.createElement("div", { className: `flex flex-col gap-2 ${className}` }, /* @__PURE__ */ import_react15.default.createElement("div", { className: "grid grid-cols-1 lg:grid-cols-6 gap-2" }, /* @__PURE__ */ import_react15.default.createElement(
|
|
1699
|
+
function FilterPanel({ filters, labels, onFilterChange, onResetFilters, className = "" }) {
|
|
1700
|
+
return /* @__PURE__ */ import_react15.default.createElement("div", { className: ["rdb-filter-panel", className].filter(Boolean).join(" ") }, /* @__PURE__ */ import_react15.default.createElement("div", { className: "rdb-filter-row" }, /* @__PURE__ */ import_react15.default.createElement(
|
|
1889
1701
|
"select",
|
|
1890
1702
|
{
|
|
1891
|
-
className: "
|
|
1703
|
+
className: "rdb-select",
|
|
1892
1704
|
value: filters.statusScope,
|
|
1893
|
-
onChange: (
|
|
1705
|
+
onChange: (e) => onFilterChange("statusScope", e.target.value)
|
|
1894
1706
|
},
|
|
1895
|
-
STATUS_OPTIONS.map((
|
|
1896
|
-
), /* @__PURE__ */ import_react15.default.createElement("label", { className: "
|
|
1897
|
-
|
|
1707
|
+
STATUS_OPTIONS.map((s) => /* @__PURE__ */ import_react15.default.createElement("option", { key: s, value: s }, s === "confirmed" ? labels.confirmedOnly : s === "pending" ? labels.pendingOnly : labels.allStatus))
|
|
1708
|
+
), /* @__PURE__ */ import_react15.default.createElement("label", { className: "rdb-filter-overlay-label" }, /* @__PURE__ */ import_react15.default.createElement(
|
|
1709
|
+
"input",
|
|
1898
1710
|
{
|
|
1899
1711
|
type: "checkbox",
|
|
1900
1712
|
checked: filters.includePendingOverlay,
|
|
1901
|
-
onChange: (
|
|
1713
|
+
onChange: (e) => onFilterChange("includePendingOverlay", e.target.checked)
|
|
1902
1714
|
}
|
|
1903
|
-
), /* @__PURE__ */ import_react15.default.createElement(
|
|
1715
|
+
), /* @__PURE__ */ import_react15.default.createElement("span", { className: "rdb-body" }, labels.showPendingOverlay)), /* @__PURE__ */ import_react15.default.createElement(
|
|
1904
1716
|
"select",
|
|
1905
1717
|
{
|
|
1906
|
-
className: "
|
|
1718
|
+
className: "rdb-select",
|
|
1907
1719
|
value: filters.audience,
|
|
1908
|
-
onChange: (
|
|
1720
|
+
onChange: (e) => onFilterChange("audience", e.target.value)
|
|
1909
1721
|
},
|
|
1910
|
-
AUDIENCE_OPTIONS.map((
|
|
1722
|
+
AUDIENCE_OPTIONS.map((a) => /* @__PURE__ */ import_react15.default.createElement("option", { key: a || "all", value: a }, a === "domestic" ? labels.audienceDomestic : a === "foreign" ? labels.audienceForeign : labels.allAudience))
|
|
1911
1723
|
), /* @__PURE__ */ import_react15.default.createElement(
|
|
1912
1724
|
"select",
|
|
1913
1725
|
{
|
|
1914
|
-
className: "
|
|
1726
|
+
className: "rdb-select",
|
|
1915
1727
|
value: String(filters.daysPreset),
|
|
1916
|
-
onChange: (
|
|
1728
|
+
onChange: (e) => onFilterChange("daysPreset", Number(e.target.value))
|
|
1917
1729
|
},
|
|
1918
|
-
DAY_PRESETS.map((
|
|
1730
|
+
DAY_PRESETS.map((d) => /* @__PURE__ */ import_react15.default.createElement("option", { key: String(d), value: String(d) }, d === 0 ? labels.customDate : labels.dayLabel(d)))
|
|
1919
1731
|
), /* @__PURE__ */ import_react15.default.createElement(
|
|
1920
1732
|
DateRangeFilter,
|
|
1921
1733
|
{
|
|
@@ -1926,160 +1738,95 @@ function FilterPanel({
|
|
|
1926
1738
|
if (dateTo !== filters.dateTo) onFilterChange("dateTo", dateTo);
|
|
1927
1739
|
},
|
|
1928
1740
|
disabled: filters.daysPreset !== 0,
|
|
1929
|
-
className: "
|
|
1741
|
+
className: "rdb-filter-date"
|
|
1930
1742
|
}
|
|
1931
|
-
)), /* @__PURE__ */ import_react15.default.createElement("div", { className: "
|
|
1743
|
+
)), /* @__PURE__ */ import_react15.default.createElement("div", { className: "rdb-filter-actions" }, /* @__PURE__ */ import_react15.default.createElement(Button, { variant: "secondary", size: "sm", onClick: onResetFilters }, labels.reset), /* @__PURE__ */ import_react15.default.createElement("div", { className: "rdb-filter-sort" }, /* @__PURE__ */ import_react15.default.createElement("span", { className: "rdb-caption" }, labels.topSort, ":"), /* @__PURE__ */ import_react15.default.createElement(
|
|
1932
1744
|
"select",
|
|
1933
1745
|
{
|
|
1934
|
-
className: "
|
|
1746
|
+
className: "rdb-select",
|
|
1747
|
+
style: { width: "auto" },
|
|
1935
1748
|
value: filters.sortPkgBy,
|
|
1936
|
-
onChange: (
|
|
1749
|
+
onChange: (e) => onFilterChange("sortPkgBy", e.target.value)
|
|
1937
1750
|
},
|
|
1938
|
-
SORT_BY_OPTIONS.map((
|
|
1751
|
+
SORT_BY_OPTIONS.map((s) => /* @__PURE__ */ import_react15.default.createElement("option", { key: s, value: s }, s === "bookings" ? labels.sortBookings : labels.sortRevenue))
|
|
1939
1752
|
), /* @__PURE__ */ import_react15.default.createElement(
|
|
1940
1753
|
"select",
|
|
1941
1754
|
{
|
|
1942
|
-
className: "
|
|
1755
|
+
className: "rdb-select",
|
|
1756
|
+
style: { width: "auto" },
|
|
1943
1757
|
value: filters.sortPkgDir,
|
|
1944
|
-
onChange: (
|
|
1758
|
+
onChange: (e) => onFilterChange("sortPkgDir", e.target.value)
|
|
1945
1759
|
},
|
|
1946
|
-
SORT_DIR_OPTIONS.map((
|
|
1760
|
+
SORT_DIR_OPTIONS.map((d) => /* @__PURE__ */ import_react15.default.createElement("option", { key: d, value: d }, d === "desc" ? labels.sortDesc : labels.sortAsc))
|
|
1947
1761
|
))));
|
|
1948
1762
|
}
|
|
1949
1763
|
FilterPanel.propTypes = {
|
|
1950
|
-
|
|
1951
|
-
filters: import_prop_types13.default.shape({
|
|
1952
|
-
statusScope: import_prop_types13.default.string,
|
|
1953
|
-
includePendingOverlay: import_prop_types13.default.bool,
|
|
1954
|
-
audience: import_prop_types13.default.string,
|
|
1955
|
-
daysPreset: import_prop_types13.default.number,
|
|
1956
|
-
dateFrom: import_prop_types13.default.string,
|
|
1957
|
-
dateTo: import_prop_types13.default.string,
|
|
1958
|
-
sortPkgBy: import_prop_types13.default.string,
|
|
1959
|
-
sortPkgDir: import_prop_types13.default.string
|
|
1960
|
-
}).isRequired,
|
|
1961
|
-
/** Objek label i18n. */
|
|
1764
|
+
filters: import_prop_types13.default.object.isRequired,
|
|
1962
1765
|
labels: import_prop_types13.default.object.isRequired,
|
|
1963
|
-
/** Callback saat filter berubah (field, value). */
|
|
1964
1766
|
onFilterChange: import_prop_types13.default.func.isRequired,
|
|
1965
|
-
/** Callback saat filter di-reset. */
|
|
1966
1767
|
onResetFilters: import_prop_types13.default.func.isRequired,
|
|
1967
|
-
/** ClassName tambahan. */
|
|
1968
1768
|
className: import_prop_types13.default.string
|
|
1969
1769
|
};
|
|
1970
1770
|
|
|
1971
1771
|
// src/presentation/organisms/SidebarNavigation.jsx
|
|
1972
1772
|
var import_react16 = __toESM(require("react"), 1);
|
|
1973
1773
|
var import_prop_types14 = __toESM(require("prop-types"), 1);
|
|
1974
|
-
function SidebarNavigation({
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
const isActive = item.id === activeItem;
|
|
1989
|
-
return /* @__PURE__ */ import_react16.default.createElement(
|
|
1990
|
-
"button",
|
|
1774
|
+
function SidebarNavigation({ items = [], activeItem = "", onItemClick, logo = null, className = "" }) {
|
|
1775
|
+
return /* @__PURE__ */ import_react16.default.createElement("aside", { className: ["rdb-sidebar", className].filter(Boolean).join(" ") }, logo ? /* @__PURE__ */ import_react16.default.createElement("div", { className: "rdb-sidebar-logo" }, logo) : null, /* @__PURE__ */ import_react16.default.createElement("nav", { className: "rdb-sidebar-nav", "aria-label": "Sidebar navigation" }, items.map((item) => {
|
|
1776
|
+
const isActive = item.id === activeItem;
|
|
1777
|
+
return /* @__PURE__ */ import_react16.default.createElement(
|
|
1778
|
+
"button",
|
|
1779
|
+
{
|
|
1780
|
+
key: item.id,
|
|
1781
|
+
type: "button",
|
|
1782
|
+
onClick: () => onItemClick == null ? void 0 : onItemClick(item.id),
|
|
1783
|
+
"aria-current": isActive ? "page" : void 0,
|
|
1784
|
+
className: ["rdb-nav-item", isActive ? "rdb-nav-item-active" : ""].filter(Boolean).join(" ")
|
|
1785
|
+
},
|
|
1786
|
+
item.icon ? /* @__PURE__ */ import_react16.default.createElement(
|
|
1787
|
+
Icon,
|
|
1991
1788
|
{
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
},
|
|
2002
|
-
item.icon ? /* @__PURE__ */ import_react16.default.createElement(
|
|
2003
|
-
Icon,
|
|
2004
|
-
{
|
|
2005
|
-
name: item.icon,
|
|
2006
|
-
size: 18,
|
|
2007
|
-
className: isActive ? "text-blue-600 dark:text-blue-400" : "text-slate-400 dark:text-slate-500"
|
|
2008
|
-
}
|
|
2009
|
-
) : null,
|
|
2010
|
-
/* @__PURE__ */ import_react16.default.createElement("span", null, item.label),
|
|
2011
|
-
isActive ? /* @__PURE__ */ import_react16.default.createElement("span", { className: "ml-auto w-1.5 h-1.5 rounded-full bg-blue-500" }) : null
|
|
2012
|
-
);
|
|
2013
|
-
}))
|
|
2014
|
-
);
|
|
1789
|
+
name: item.icon,
|
|
1790
|
+
size: 18,
|
|
1791
|
+
style: { color: isActive ? "var(--rdb-blue-600)" : "var(--rdb-text-subtle)" }
|
|
1792
|
+
}
|
|
1793
|
+
) : null,
|
|
1794
|
+
/* @__PURE__ */ import_react16.default.createElement("span", null, item.label),
|
|
1795
|
+
isActive ? /* @__PURE__ */ import_react16.default.createElement("span", { className: "rdb-nav-dot" }) : null
|
|
1796
|
+
);
|
|
1797
|
+
})));
|
|
2015
1798
|
}
|
|
2016
1799
|
SidebarNavigation.propTypes = {
|
|
2017
|
-
|
|
2018
|
-
items: import_prop_types14.default.arrayOf(
|
|
2019
|
-
import_prop_types14.default.shape({
|
|
2020
|
-
id: import_prop_types14.default.string.isRequired,
|
|
2021
|
-
label: import_prop_types14.default.string.isRequired,
|
|
2022
|
-
icon: import_prop_types14.default.string
|
|
2023
|
-
})
|
|
2024
|
-
),
|
|
2025
|
-
/** ID item yang sedang aktif. */
|
|
1800
|
+
items: import_prop_types14.default.arrayOf(import_prop_types14.default.shape({ id: import_prop_types14.default.string.isRequired, label: import_prop_types14.default.string.isRequired, icon: import_prop_types14.default.string })),
|
|
2026
1801
|
activeItem: import_prop_types14.default.string,
|
|
2027
|
-
/** Callback saat item diklik, menerima id item sebagai argumen. */
|
|
2028
1802
|
onItemClick: import_prop_types14.default.func,
|
|
2029
|
-
/** Konten logo/brand di atas sidebar. */
|
|
2030
1803
|
logo: import_prop_types14.default.node,
|
|
2031
|
-
/** ClassName tambahan. */
|
|
2032
1804
|
className: import_prop_types14.default.string
|
|
2033
1805
|
};
|
|
2034
1806
|
|
|
2035
1807
|
// src/presentation/organisms/TopbarHeader.jsx
|
|
2036
1808
|
var import_react17 = __toESM(require("react"), 1);
|
|
2037
1809
|
var import_prop_types15 = __toESM(require("prop-types"), 1);
|
|
2038
|
-
function TopbarHeader({
|
|
2039
|
-
title
|
|
2040
|
-
actions = null,
|
|
2041
|
-
leading = null,
|
|
2042
|
-
className = ""
|
|
2043
|
-
}) {
|
|
2044
|
-
return /* @__PURE__ */ import_react17.default.createElement(
|
|
2045
|
-
"header",
|
|
2046
|
-
{
|
|
2047
|
-
className: `sticky top-0 z-30 flex items-center justify-between h-14 px-4 bg-white/80 dark:bg-slate-900/80 backdrop-blur-md border-b border-slate-200 dark:border-slate-800 ${className}`
|
|
2048
|
-
},
|
|
2049
|
-
/* @__PURE__ */ import_react17.default.createElement("div", { className: "flex items-center gap-3" }, leading, title ? /* @__PURE__ */ import_react17.default.createElement(Typography, { variant: "h2", className: "truncate" }, title) : null),
|
|
2050
|
-
actions ? /* @__PURE__ */ import_react17.default.createElement("div", { className: "flex items-center gap-2" }, actions) : null
|
|
2051
|
-
);
|
|
1810
|
+
function TopbarHeader({ title = "", actions = null, leading = null, className = "" }) {
|
|
1811
|
+
return /* @__PURE__ */ import_react17.default.createElement("header", { className: ["rdb-topbar", className].filter(Boolean).join(" ") }, /* @__PURE__ */ import_react17.default.createElement("div", { className: "rdb-topbar-left" }, leading, title ? /* @__PURE__ */ import_react17.default.createElement("span", { className: "rdb-h2", style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, title) : null), actions ? /* @__PURE__ */ import_react17.default.createElement("div", { className: "rdb-topbar-right" }, actions) : null);
|
|
2052
1812
|
}
|
|
2053
1813
|
TopbarHeader.propTypes = {
|
|
2054
|
-
/** Judul halaman yang ditampilkan. */
|
|
2055
1814
|
title: import_prop_types15.default.string,
|
|
2056
|
-
/** Elemen aksi di sisi kanan (tombol, avatar, dsb.). */
|
|
2057
1815
|
actions: import_prop_types15.default.node,
|
|
2058
|
-
/** Elemen di sisi kiri sebelum judul (misal: hamburger menu). */
|
|
2059
1816
|
leading: import_prop_types15.default.node,
|
|
2060
|
-
/** ClassName tambahan. */
|
|
2061
1817
|
className: import_prop_types15.default.string
|
|
2062
1818
|
};
|
|
2063
1819
|
|
|
2064
1820
|
// src/presentation/templates/DashboardLayout.jsx
|
|
2065
1821
|
var import_react18 = __toESM(require("react"), 1);
|
|
2066
1822
|
var import_prop_types16 = __toESM(require("prop-types"), 1);
|
|
2067
|
-
function DashboardLayout({
|
|
2068
|
-
header
|
|
2069
|
-
sidebar = null,
|
|
2070
|
-
children,
|
|
2071
|
-
className = ""
|
|
2072
|
-
}) {
|
|
2073
|
-
return /* @__PURE__ */ import_react18.default.createElement("div", { className: `min-h-screen bg-slate-50 dark:bg-slate-900 ${className}` }, header, /* @__PURE__ */ import_react18.default.createElement("div", { className: "flex" }, sidebar, /* @__PURE__ */ import_react18.default.createElement("main", { className: "flex-1 container mx-auto px-4 py-3 space-y-4" }, children)));
|
|
1823
|
+
function DashboardLayout({ header = null, sidebar = null, children, className = "" }) {
|
|
1824
|
+
return /* @__PURE__ */ import_react18.default.createElement("div", { className: ["rdb-layout", className].filter(Boolean).join(" ") }, header, /* @__PURE__ */ import_react18.default.createElement("div", { className: "rdb-layout-body" }, sidebar, /* @__PURE__ */ import_react18.default.createElement("main", { className: "rdb-layout-main" }, children)));
|
|
2074
1825
|
}
|
|
2075
1826
|
DashboardLayout.propTypes = {
|
|
2076
|
-
/** Konten header atas. */
|
|
2077
1827
|
header: import_prop_types16.default.node,
|
|
2078
|
-
/** Konten sidebar. */
|
|
2079
1828
|
sidebar: import_prop_types16.default.node,
|
|
2080
|
-
/** Konten utama dashboard. */
|
|
2081
1829
|
children: import_prop_types16.default.node.isRequired,
|
|
2082
|
-
/** ClassName tambahan. */
|
|
2083
1830
|
className: import_prop_types16.default.string
|
|
2084
1831
|
};
|
|
2085
1832
|
|
|
@@ -2099,16 +1846,7 @@ function ReusableDashboardView({
|
|
|
2099
1846
|
dateLocale,
|
|
2100
1847
|
liveUpdateEnabled
|
|
2101
1848
|
}) {
|
|
2102
|
-
return /* @__PURE__ */ import_react19.default.createElement("div", { className: "
|
|
2103
|
-
Button,
|
|
2104
|
-
{
|
|
2105
|
-
variant: "secondary",
|
|
2106
|
-
size: "sm",
|
|
2107
|
-
onClick: () => onRefresh(),
|
|
2108
|
-
title: labels.refresh
|
|
2109
|
-
},
|
|
2110
|
-
/* @__PURE__ */ import_react19.default.createElement(Icon, { name: "RotateCcw", size: 16 })
|
|
2111
|
-
)), error ? /* @__PURE__ */ import_react19.default.createElement("div", { className: "rounded-xl border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-300" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ import_react19.default.createElement("span", null, error), /* @__PURE__ */ import_react19.default.createElement(Button, { variant: "secondary", size: "sm", onClick: () => onRefresh() }, labels.retry))) : null, /* @__PURE__ */ import_react19.default.createElement(
|
|
1849
|
+
return /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-view" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-view-container" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-header-panel" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-header-top" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-header-title-group" }, /* @__PURE__ */ import_react19.default.createElement("span", { className: "rdb-h1" }, labels.title), liveUpdateEnabled ? /* @__PURE__ */ import_react19.default.createElement(Badge, { status: "success" }, labels.liveUpdate) : null), /* @__PURE__ */ import_react19.default.createElement(Button, { variant: "secondary", size: "sm", onClick: () => onRefresh(), title: labels.refresh }, /* @__PURE__ */ import_react19.default.createElement(Icon, { name: "RotateCcw", size: 16 }))), error ? /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-error-banner" }, /* @__PURE__ */ import_react19.default.createElement("span", null, error), /* @__PURE__ */ import_react19.default.createElement(Button, { variant: "secondary", size: "sm", onClick: () => onRefresh() }, labels.retry)) : null, /* @__PURE__ */ import_react19.default.createElement(
|
|
2112
1850
|
FilterPanel,
|
|
2113
1851
|
{
|
|
2114
1852
|
filters,
|
|
@@ -2116,16 +1854,17 @@ function ReusableDashboardView({
|
|
|
2116
1854
|
onFilterChange,
|
|
2117
1855
|
onResetFilters
|
|
2118
1856
|
}
|
|
2119
|
-
))
|
|
1857
|
+
)), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-stats-grid" }, loading ? Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ import_react19.default.createElement(SkeletonLoader, { key: i, style: { height: 112 } })) : config.widgets.stats.map((widget) => /* @__PURE__ */ import_react19.default.createElement(
|
|
2120
1858
|
StatCard,
|
|
2121
1859
|
{
|
|
2122
1860
|
key: widget.id,
|
|
2123
1861
|
label: labels[widget.label] || widget.label,
|
|
2124
1862
|
value: data.stats[widget.valueKey],
|
|
2125
1863
|
icon: widget.icon,
|
|
2126
|
-
format: widget.format
|
|
1864
|
+
format: widget.format,
|
|
1865
|
+
accentColor: widget.accentColor
|
|
2127
1866
|
}
|
|
2128
|
-
))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "
|
|
1867
|
+
))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-charts-grid" }, config.widgets.charts.map((widget) => /* @__PURE__ */ import_react19.default.createElement(
|
|
2129
1868
|
ChartCard,
|
|
2130
1869
|
{
|
|
2131
1870
|
key: widget.id,
|
|
@@ -2135,7 +1874,15 @@ function ReusableDashboardView({
|
|
|
2135
1874
|
filters,
|
|
2136
1875
|
chartData: data.charts
|
|
2137
1876
|
}
|
|
2138
|
-
))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "card
|
|
1877
|
+
))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-card", style: { padding: 16 } }, /* @__PURE__ */ import_react19.default.createElement(
|
|
1878
|
+
"div",
|
|
1879
|
+
{
|
|
1880
|
+
style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 12 },
|
|
1881
|
+
className: "rdb-h3"
|
|
1882
|
+
},
|
|
1883
|
+
/* @__PURE__ */ import_react19.default.createElement(Icon, { name: config.widgets.table.icon, size: 18 }),
|
|
1884
|
+
labels[config.widgets.table.label] || config.widgets.table.label
|
|
1885
|
+
), loading ? /* @__PURE__ */ import_react19.default.createElement(SkeletonLoader, { style: { height: 192 } }) : /* @__PURE__ */ import_react19.default.createElement(
|
|
2139
1886
|
DataTable,
|
|
2140
1887
|
{
|
|
2141
1888
|
columns: config.widgets.table.columns,
|
|
@@ -2147,30 +1894,19 @@ function ReusableDashboardView({
|
|
|
2147
1894
|
sortable: true,
|
|
2148
1895
|
pageSize: 10
|
|
2149
1896
|
}
|
|
2150
|
-
)));
|
|
1897
|
+
))));
|
|
2151
1898
|
}
|
|
2152
1899
|
ReusableDashboardView.propTypes = {
|
|
2153
|
-
/** Konfigurasi widget dashboard. */
|
|
2154
1900
|
config: import_prop_types17.default.object.isRequired,
|
|
2155
|
-
/** Objek label i18n. */
|
|
2156
1901
|
labels: import_prop_types17.default.object.isRequired,
|
|
2157
|
-
/** Status loading data. */
|
|
2158
1902
|
loading: import_prop_types17.default.bool,
|
|
2159
|
-
/** Pesan error jika ada. */
|
|
2160
1903
|
error: import_prop_types17.default.string,
|
|
2161
|
-
/** State filter dashboard. */
|
|
2162
1904
|
filters: import_prop_types17.default.object,
|
|
2163
|
-
/** Callback perubahan filter. */
|
|
2164
1905
|
onFilterChange: import_prop_types17.default.func.isRequired,
|
|
2165
|
-
/** Callback reset filter. */
|
|
2166
1906
|
onResetFilters: import_prop_types17.default.func.isRequired,
|
|
2167
|
-
/** Callback refresh data. */
|
|
2168
1907
|
onRefresh: import_prop_types17.default.func.isRequired,
|
|
2169
|
-
/** Data dashboard. */
|
|
2170
1908
|
data: import_prop_types17.default.object,
|
|
2171
|
-
/** Locale untuk format tanggal. */
|
|
2172
1909
|
dateLocale: import_prop_types17.default.string,
|
|
2173
|
-
/** Apakah live update aktif. */
|
|
2174
1910
|
liveUpdateEnabled: import_prop_types17.default.bool
|
|
2175
1911
|
};
|
|
2176
1912
|
|