@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/dist/index.cjs CHANGED
@@ -1102,42 +1102,20 @@ function Button({
1102
1102
  children,
1103
1103
  ...rest
1104
1104
  }) {
1105
- const base = "inline-flex items-center justify-center font-medium rounded-xl transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed";
1106
- const variants = {
1107
- primary: "bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500",
1108
- secondary: "border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-700 focus:ring-slate-400",
1109
- ghost: "text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800 focus:ring-slate-400"
1110
- };
1111
- const sizes = {
1112
- sm: "px-2.5 py-1 text-xs gap-1",
1113
- md: "px-3 py-1.5 text-sm gap-1.5",
1114
- lg: "px-4 py-2 text-base gap-2"
1115
- };
1116
- const classes = [base, variants[variant] || variants.primary, sizes[size] || sizes.md, className].filter(Boolean).join(" ");
1117
- return /* @__PURE__ */ import_react3.default.createElement(
1118
- "button",
1119
- {
1120
- type: "button",
1121
- className: classes,
1122
- disabled,
1123
- onClick,
1124
- ...rest
1125
- },
1126
- children
1127
- );
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);
1128
1112
  }
1129
1113
  Button.propTypes = {
1130
- /** Varian visual tombol. */
1131
1114
  variant: import_prop_types.default.oneOf(["primary", "secondary", "ghost"]),
1132
- /** Ukuran tombol. */
1133
1115
  size: import_prop_types.default.oneOf(["sm", "md", "lg"]),
1134
- /** Apakah tombol non-aktif. */
1135
1116
  disabled: import_prop_types.default.bool,
1136
- /** ClassName tambahan dari consumer. */
1137
1117
  className: import_prop_types.default.string,
1138
- /** Callback saat tombol diklik. */
1139
1118
  onClick: import_prop_types.default.func,
1140
- /** Konten tombol. */
1141
1119
  children: import_prop_types.default.node.isRequired
1142
1120
  };
1143
1121
 
@@ -1154,14 +1132,7 @@ function Input({
1154
1132
  className = "",
1155
1133
  ...rest
1156
1134
  }) {
1157
- const inputClasses = [
1158
- "px-3 py-2 rounded-2xl border border-slate-300 dark:border-slate-600",
1159
- "bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100",
1160
- "text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500",
1161
- "disabled:opacity-50 disabled:cursor-not-allowed",
1162
- className
1163
- ].filter(Boolean).join(" ");
1164
- 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(
1165
1136
  "input",
1166
1137
  {
1167
1138
  type,
@@ -1169,25 +1140,18 @@ function Input({
1169
1140
  onChange,
1170
1141
  placeholder,
1171
1142
  disabled,
1172
- className: inputClasses,
1143
+ className: ["rdb-input", className].filter(Boolean).join(" "),
1173
1144
  ...rest
1174
1145
  }
1175
1146
  ));
1176
1147
  }
1177
1148
  Input.propTypes = {
1178
- /** Tipe input. */
1179
1149
  type: import_prop_types2.default.string,
1180
- /** Nilai input. */
1181
1150
  value: import_prop_types2.default.oneOfType([import_prop_types2.default.string, import_prop_types2.default.number]),
1182
- /** Callback saat nilai berubah. */
1183
1151
  onChange: import_prop_types2.default.func,
1184
- /** Placeholder teks. */
1185
1152
  placeholder: import_prop_types2.default.string,
1186
- /** Label di atas input. */
1187
1153
  label: import_prop_types2.default.string,
1188
- /** Apakah input non-aktif. */
1189
1154
  disabled: import_prop_types2.default.bool,
1190
- /** ClassName tambahan. */
1191
1155
  className: import_prop_types2.default.string
1192
1156
  };
1193
1157
 
@@ -1230,81 +1194,83 @@ Icon.propTypes = {
1230
1194
  // src/presentation/atoms/Typography.jsx
1231
1195
  var import_react6 = __toESM(require("react"), 1);
1232
1196
  var import_prop_types4 = __toESM(require("prop-types"), 1);
1233
- var VARIANT_MAP = {
1234
- h1: { tag: "h1", className: "text-2xl sm:text-3xl font-bold" },
1235
- h2: { tag: "h2", className: "text-xl sm:text-2xl font-bold" },
1236
- h3: { tag: "h3", className: "text-lg font-semibold" },
1237
- subheading: { tag: "p", className: "text-sm font-medium text-slate-500 dark:text-slate-400" },
1238
- body: { tag: "p", className: "text-sm text-slate-700 dark:text-slate-300" },
1239
- caption: { tag: "span", className: "text-xs text-slate-500 dark:text-slate-400" },
1240
- metric: { tag: "span", className: "text-3xl font-bold text-slate-900 dark:text-slate-100" }
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"
1241
1205
  };
1242
- function Typography({
1243
- variant = "body",
1244
- className = "",
1245
- children,
1246
- ...rest
1247
- }) {
1248
- const config = VARIANT_MAP[variant] || VARIANT_MAP.body;
1249
- const Tag = config.tag;
1250
- const classes = [config.className, className].filter(Boolean).join(" ");
1251
- return /* @__PURE__ */ import_react6.default.createElement(Tag, { className: classes, ...rest }, children);
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);
1252
1219
  }
1253
1220
  Typography.propTypes = {
1254
- /** Varian tipografi. */
1255
1221
  variant: import_prop_types4.default.oneOf(["h1", "h2", "h3", "subheading", "body", "caption", "metric"]),
1256
- /** ClassName tambahan. */
1257
1222
  className: import_prop_types4.default.string,
1258
- /** Konten teks. */
1259
1223
  children: import_prop_types4.default.node.isRequired
1260
1224
  };
1261
1225
 
1262
1226
  // src/presentation/atoms/Badge.jsx
1263
1227
  var import_react7 = __toESM(require("react"), 1);
1264
1228
  var import_prop_types5 = __toESM(require("prop-types"), 1);
1265
- var STATUS_CLASSES = {
1266
- confirmed: "bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-200",
1267
- pending: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-200",
1268
- cancelled: "bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-200",
1269
- default: "bg-slate-100 text-slate-800 dark:bg-slate-700 dark:text-slate-200",
1270
- success: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300",
1271
- info: "bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300"
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"
1272
1236
  };
1273
1237
  function Badge({ status = "default", className = "", children, ...rest }) {
1274
- const colorClass = STATUS_CLASSES[status] || STATUS_CLASSES.default;
1275
- const classes = ["px-2 py-1 rounded-full text-xs font-medium", colorClass, className].filter(Boolean).join(" ");
1276
- 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);
1277
1240
  }
1278
1241
  Badge.propTypes = {
1279
- /** Status yang menentukan warna badge. */
1280
1242
  status: import_prop_types5.default.string,
1281
- /** ClassName tambahan. */
1282
1243
  className: import_prop_types5.default.string,
1283
- /** Label teks badge. */
1284
1244
  children: import_prop_types5.default.node.isRequired
1285
1245
  };
1286
1246
 
1287
1247
  // src/presentation/atoms/SkeletonLoader.jsx
1288
1248
  var import_react8 = __toESM(require("react"), 1);
1289
1249
  var import_prop_types6 = __toESM(require("prop-types"), 1);
1290
- function SkeletonLoader({ className = "" }) {
1250
+ function SkeletonLoader({ className = "", style = {} }) {
1291
1251
  return /* @__PURE__ */ import_react8.default.createElement(
1292
1252
  "div",
1293
1253
  {
1294
- className: `animate-pulse bg-slate-200 dark:bg-slate-700 rounded-xl ${className}`,
1254
+ className: ["rdb-skeleton", className].filter(Boolean).join(" "),
1255
+ style: { minHeight: 20, ...style },
1295
1256
  role: "status",
1296
1257
  "aria-label": "Loading..."
1297
1258
  }
1298
1259
  );
1299
1260
  }
1300
1261
  SkeletonLoader.propTypes = {
1301
- /** ClassName tambahan untuk mengatur ukuran skeleton (misal: "h-28", "h-64"). */
1302
- className: import_prop_types6.default.string
1262
+ className: import_prop_types6.default.string,
1263
+ style: import_prop_types6.default.object
1303
1264
  };
1304
1265
 
1305
1266
  // src/presentation/molecules/StatCard.jsx
1306
1267
  var import_react9 = __toESM(require("react"), 1);
1307
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
+ }
1308
1274
  function StatCard({
1309
1275
  label,
1310
1276
  value,
@@ -1314,29 +1280,14 @@ function StatCard({
1314
1280
  accentColor = "blue",
1315
1281
  className = ""
1316
1282
  }) {
1317
- const formattedValue = renderValue(format, value);
1318
- const ACCENT = {
1319
- blue: { bg: "bg-blue-50 dark:bg-blue-900/20", icon: "text-blue-600 dark:text-blue-400", bar: "bg-blue-500", border: "border-l-4 border-blue-400" },
1320
- green: { bg: "bg-emerald-50 dark:bg-emerald-900/20", icon: "text-emerald-600 dark:text-emerald-400", bar: "bg-emerald-500", border: "border-l-4 border-emerald-400" },
1321
- violet: { bg: "bg-violet-50 dark:bg-violet-900/20", icon: "text-violet-600 dark:text-violet-400", bar: "bg-violet-500", border: "border-l-4 border-violet-400" },
1322
- orange: { bg: "bg-orange-50 dark:bg-orange-900/20", icon: "text-orange-600 dark:text-orange-400", bar: "bg-orange-500", border: "border-l-4 border-orange-400" },
1323
- sky: { bg: "bg-sky-50 dark:bg-sky-900/20", icon: "text-sky-600 dark:text-sky-400", bar: "bg-sky-500", border: "border-l-4 border-sky-400" },
1324
- rose: { bg: "bg-rose-50 dark:bg-rose-900/20", icon: "text-rose-600 dark:text-rose-400", bar: "bg-rose-500", border: "border-l-4 border-rose-400" }
1325
- };
1326
- const accent = ACCENT[accentColor] || ACCENT.blue;
1327
- return /* @__PURE__ */ import_react9.default.createElement("div", { className: `card p-4 flex flex-col justify-between gap-3 ${accent.border} ${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("div", { className: `w-8 h-8 rounded-xl flex items-center justify-center ${accent.bg} ${accent.icon} shrink-0` }, /* @__PURE__ */ import_react9.default.createElement(Icon, { name: icon, size: 16 })), /* @__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(
1328
1284
  Icon,
1329
1285
  {
1330
1286
  name: trend === "up" ? "TrendingUp" : "TrendingDown",
1331
1287
  size: 16,
1332
- className: trend === "up" ? "text-emerald-500" : "text-red-500"
1288
+ style: { color: trend === "up" ? "#10b981" : "#ef4444" }
1333
1289
  }
1334
- ) : null), /* @__PURE__ */ import_react9.default.createElement(Typography, { variant: "metric" }, formattedValue));
1335
- }
1336
- function renderValue(format, value) {
1337
- if (format === "currency") return `Rp ${formatIDR(value)}`;
1338
- if (format === "percent") return `${Number(value) || 0}%`;
1339
- return String(Number(value) || 0);
1290
+ ) : null), /* @__PURE__ */ import_react9.default.createElement("div", { className: "rdb-statcard-value" }, renderValue(format, value)));
1340
1291
  }
1341
1292
  StatCard.propTypes = {
1342
1293
  label: import_prop_types7.default.string.isRequired,
@@ -1344,7 +1295,6 @@ StatCard.propTypes = {
1344
1295
  icon: import_prop_types7.default.string,
1345
1296
  format: import_prop_types7.default.oneOf(["number", "currency", "percent"]),
1346
1297
  trend: import_prop_types7.default.oneOf(["up", "down", null]),
1347
- /** Warna aksen: blue | green | violet | orange | sky | rose */
1348
1298
  accentColor: import_prop_types7.default.string,
1349
1299
  className: import_prop_types7.default.string
1350
1300
  };
@@ -1362,45 +1312,33 @@ function SearchBar({
1362
1312
  const [internalValue, setInternalValue] = (0, import_react10.useState)("");
1363
1313
  const isControlled = controlledValue !== void 0;
1364
1314
  const currentValue = isControlled ? controlledValue : internalValue;
1365
- const handleChange = (0, import_react10.useCallback)(
1366
- (event) => {
1367
- const newValue = event.target.value;
1368
- if (!isControlled) setInternalValue(newValue);
1369
- if (onChange) onChange(newValue);
1370
- if (onSearch) onSearch(newValue);
1371
- },
1372
- [isControlled, onChange, onSearch]
1373
- );
1374
- const handleKeyDown = (0, import_react10.useCallback)(
1375
- (event) => {
1376
- if (event.key === "Enter" && onSearch) {
1377
- onSearch(currentValue);
1378
- }
1379
- },
1380
- [currentValue, onSearch]
1381
- );
1382
- 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(
1383
- 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",
1384
1326
  {
1385
1327
  type: "search",
1386
1328
  value: currentValue,
1387
1329
  onChange: handleChange,
1388
1330
  onKeyDown: handleKeyDown,
1389
1331
  placeholder,
1390
- className: "pl-9 w-full"
1332
+ className: "rdb-input rdb-searchbar-input",
1333
+ style: { width: "100%" }
1391
1334
  }
1392
1335
  ));
1393
1336
  }
1394
1337
  SearchBar.propTypes = {
1395
- /** Nilai pencarian saat ini (controlled). */
1396
1338
  value: import_prop_types8.default.string,
1397
- /** Callback saat nilai input berubah. */
1398
1339
  onChange: import_prop_types8.default.func,
1399
- /** Callback saat pencarian disubmit. */
1400
1340
  onSearch: import_prop_types8.default.func,
1401
- /** Placeholder teks. */
1402
1341
  placeholder: import_prop_types8.default.string,
1403
- /** ClassName tambahan. */
1404
1342
  className: import_prop_types8.default.string
1405
1343
  };
1406
1344
 
@@ -1410,26 +1348,11 @@ var import_prop_types9 = __toESM(require("prop-types"), 1);
1410
1348
  var CURRENT_YEAR = (/* @__PURE__ */ new Date()).getFullYear();
1411
1349
  var MIN_YEAR2 = 2020;
1412
1350
  var MAX_YEAR = CURRENT_YEAR + 1;
1413
- var MONTHS = [
1414
- "Jan",
1415
- "Feb",
1416
- "Mar",
1417
- "Apr",
1418
- "May",
1419
- "Jun",
1420
- "Jul",
1421
- "Aug",
1422
- "Sep",
1423
- "Oct",
1424
- "Nov",
1425
- "Dec"
1426
- ];
1351
+ var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1427
1352
  function getYears() {
1428
- const years = [];
1429
- for (let y = MAX_YEAR; y >= MIN_YEAR2; y--) {
1430
- years.push(y);
1431
- }
1432
- return years;
1353
+ const y = [];
1354
+ for (let i = MAX_YEAR; i >= MIN_YEAR2; i--) y.push(i);
1355
+ return y;
1433
1356
  }
1434
1357
  function getDaysInMonth(year, month) {
1435
1358
  return new Date(year, month, 0).getDate();
@@ -1437,68 +1360,57 @@ function getDaysInMonth(year, month) {
1437
1360
  function parseDate(value) {
1438
1361
  if (value && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
1439
1362
  const [y, m, d] = value.split("-").map(Number);
1440
- if (y >= MIN_YEAR2 && y <= MAX_YEAR && m >= 1 && m <= 12) {
1363
+ if (y >= MIN_YEAR2 && y <= MAX_YEAR && m >= 1 && m <= 12)
1441
1364
  return { year: y, month: m, day: d };
1442
- }
1443
1365
  }
1444
1366
  const now = /* @__PURE__ */ new Date();
1445
1367
  return { year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate() };
1446
1368
  }
1447
- function formatDate2({ year, month, day }) {
1369
+ function fmt({ year, month, day }) {
1448
1370
  return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
1449
1371
  }
1450
1372
  function DatePicker({ value, onChange, disabled = false, label = "" }) {
1451
- const parsed = parseDate(value);
1373
+ const p = parseDate(value);
1452
1374
  const years = getYears();
1453
- const maxDay = getDaysInMonth(parsed.year, parsed.month);
1454
- const handleChange = (0, import_react11.useCallback)(
1455
- (field, newVal) => {
1456
- const updated = { ...parsed, [field]: Number(newVal) };
1457
- const maxD = getDaysInMonth(updated.year, updated.month);
1458
- if (updated.day > maxD) updated.day = maxD;
1459
- onChange(formatDate2(updated));
1460
- },
1461
- [parsed, onChange]
1462
- );
1463
- 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";
1464
- 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(
1465
1383
  "select",
1466
1384
  {
1467
- className: selectClass,
1468
- value: parsed.day,
1385
+ className: "rdb-select",
1386
+ style: { width: 52 },
1387
+ value: p.day,
1469
1388
  onChange: (e) => handleChange("day", e.target.value),
1470
- disabled,
1471
- "aria-label": `${label} day`
1389
+ disabled
1472
1390
  },
1473
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")))
1474
1392
  ), /* @__PURE__ */ import_react11.default.createElement(
1475
1393
  "select",
1476
1394
  {
1477
- className: selectClass,
1478
- value: parsed.month,
1395
+ className: "rdb-select",
1396
+ style: { width: 62 },
1397
+ value: p.month,
1479
1398
  onChange: (e) => handleChange("month", e.target.value),
1480
- disabled,
1481
- "aria-label": `${label} month`
1399
+ disabled
1482
1400
  },
1483
1401
  MONTHS.map((name, idx) => /* @__PURE__ */ import_react11.default.createElement("option", { key: idx + 1, value: idx + 1 }, name))
1484
1402
  ), /* @__PURE__ */ import_react11.default.createElement(
1485
1403
  "select",
1486
1404
  {
1487
- className: selectClass,
1488
- value: parsed.year,
1405
+ className: "rdb-select",
1406
+ style: { width: 72 },
1407
+ value: p.year,
1489
1408
  onChange: (e) => handleChange("year", e.target.value),
1490
- disabled,
1491
- "aria-label": `${label} year`
1409
+ disabled
1492
1410
  },
1493
1411
  years.map((y) => /* @__PURE__ */ import_react11.default.createElement("option", { key: y, value: y }, y))
1494
1412
  )));
1495
1413
  }
1496
- DatePicker.propTypes = {
1497
- value: import_prop_types9.default.string.isRequired,
1498
- onChange: import_prop_types9.default.func.isRequired,
1499
- disabled: import_prop_types9.default.bool,
1500
- label: import_prop_types9.default.string
1501
- };
1502
1414
  function DateRangeFilter({
1503
1415
  dateFrom,
1504
1416
  dateTo,
@@ -1508,31 +1420,9 @@ function DateRangeFilter({
1508
1420
  labelTo = "To",
1509
1421
  className = ""
1510
1422
  }) {
1511
- const handleFromChange = (0, import_react11.useCallback)(
1512
- (newDate) => onRangeChange == null ? void 0 : onRangeChange({ dateFrom: newDate, dateTo }),
1513
- [dateTo, onRangeChange]
1514
- );
1515
- const handleToChange = (0, import_react11.useCallback)(
1516
- (newDate) => onRangeChange == null ? void 0 : onRangeChange({ dateFrom, dateTo: newDate }),
1517
- [dateFrom, onRangeChange]
1518
- );
1519
- return /* @__PURE__ */ import_react11.default.createElement("div", { className: `flex flex-wrap items-end gap-3 ${className}` }, /* @__PURE__ */ import_react11.default.createElement(
1520
- DatePicker,
1521
- {
1522
- value: dateFrom,
1523
- onChange: handleFromChange,
1524
- disabled,
1525
- label: labelFrom
1526
- }
1527
- ), /* @__PURE__ */ import_react11.default.createElement(
1528
- DatePicker,
1529
- {
1530
- value: dateTo,
1531
- onChange: handleToChange,
1532
- disabled,
1533
- label: labelTo
1534
- }
1535
- ));
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 }));
1536
1426
  }
1537
1427
  DateRangeFilter.propTypes = {
1538
1428
  dateFrom: import_prop_types9.default.string.isRequired,
@@ -1547,22 +1437,13 @@ DateRangeFilter.propTypes = {
1547
1437
  // src/presentation/molecules/ChartHeader.jsx
1548
1438
  var import_react12 = __toESM(require("react"), 1);
1549
1439
  var import_prop_types10 = __toESM(require("prop-types"), 1);
1550
- function ChartHeader({
1551
- title,
1552
- icon = "BarChart3",
1553
- actions = null,
1554
- className = ""
1555
- }) {
1556
- 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);
1557
1442
  }
1558
1443
  ChartHeader.propTypes = {
1559
- /** Judul grafik. */
1560
1444
  title: import_prop_types10.default.string.isRequired,
1561
- /** Nama ikon header. */
1562
1445
  icon: import_prop_types10.default.string,
1563
- /** Elemen aksi tambahan di sisi kanan. */
1564
1446
  actions: import_prop_types10.default.node,
1565
- /** ClassName tambahan. */
1566
1447
  className: import_prop_types10.default.string
1567
1448
  };
1568
1449
 
@@ -1587,28 +1468,24 @@ function DataTable({
1587
1468
  const [currentPage, setCurrentPage] = (0, import_react13.useState)(1);
1588
1469
  const filteredData = (0, import_react13.useMemo)(() => {
1589
1470
  if (!searchQuery.trim()) return data;
1590
- const query = searchQuery.toLowerCase();
1471
+ const q = searchQuery.toLowerCase();
1591
1472
  return data.filter(
1592
1473
  (row) => columns.some((col) => {
1593
- const value = row[col.accessor];
1594
- return value != null && String(value).toLowerCase().includes(query);
1474
+ const v = row[col.accessor];
1475
+ return v != null && String(v).toLowerCase().includes(q);
1595
1476
  })
1596
1477
  );
1597
1478
  }, [data, searchQuery, columns]);
1598
1479
  const sortedData = (0, import_react13.useMemo)(() => {
1599
1480
  if (!sortField) return filteredData;
1600
- const sorted = [...filteredData].sort((a, b) => {
1601
- const aVal = a[sortField] ?? "";
1602
- const bVal = b[sortField] ?? "";
1603
- if (typeof aVal === "number" && typeof bVal === "number") {
1604
- return sortDirection === "asc" ? aVal - bVal : bVal - aVal;
1605
- }
1606
- const comparison = String(aVal).localeCompare(String(bVal), void 0, {
1607
- numeric: true
1608
- });
1609
- 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;
1610
1488
  });
1611
- return sorted;
1612
1489
  }, [filteredData, sortField, sortDirection]);
1613
1490
  const totalPages = Math.max(1, Math.ceil(sortedData.length / pageSize));
1614
1491
  const safePage = Math.min(currentPage, totalPages);
@@ -1616,90 +1493,29 @@ function DataTable({
1616
1493
  const start = (safePage - 1) * pageSize;
1617
1494
  return sortedData.slice(start, start + pageSize);
1618
1495
  }, [sortedData, safePage, pageSize]);
1619
- const handleSearch = (0, import_react13.useCallback)((value) => {
1620
- setSearchQuery(value);
1496
+ const handleSearch = (0, import_react13.useCallback)((v) => {
1497
+ setSearchQuery(v);
1621
1498
  setCurrentPage(1);
1622
1499
  }, []);
1623
- const handleSort = (0, import_react13.useCallback)(
1624
- (accessor) => {
1625
- if (!sortable) return;
1626
- if (sortField === accessor) {
1627
- setSortDirection((prev) => prev === "asc" ? "desc" : "asc");
1628
- } else {
1629
- setSortField(accessor);
1630
- setSortDirection("asc");
1631
- }
1632
- setCurrentPage(1);
1633
- },
1634
- [sortable, sortField]
1635
- );
1636
- const handlePrevPage = (0, import_react13.useCallback)(() => {
1637
- setCurrentPage((prev) => Math.max(1, prev - 1));
1638
- }, []);
1639
- const handleNextPage = (0, import_react13.useCallback)(() => {
1640
- setCurrentPage((prev) => Math.min(totalPages, prev + 1));
1641
- }, [totalPages]);
1642
- return /* @__PURE__ */ import_react13.default.createElement("div", { className: `space-y-3 ${className}` }, searchable ? /* @__PURE__ */ import_react13.default.createElement(
1643
- SearchBar,
1644
- {
1645
- value: searchQuery,
1646
- onSearch: handleSearch,
1647
- placeholder: searchPlaceholder,
1648
- 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");
1649
1506
  }
1650
- ) : null, /* @__PURE__ */ import_react13.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ import_react13.default.createElement("table", { className: "min-w-full text-sm border-collapse" }, /* @__PURE__ */ import_react13.default.createElement("thead", null, /* @__PURE__ */ import_react13.default.createElement("tr", { className: "bg-slate-100 dark:bg-slate-800 text-left" }, columns.map((column) => /* @__PURE__ */ import_react13.default.createElement(
1651
- "th",
1652
- {
1653
- key: column.id,
1654
- className: [
1655
- "p-3",
1656
- sortable ? "cursor-pointer select-none hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors" : ""
1657
- ].join(" "),
1658
- onClick: () => handleSort(column.accessor)
1659
- },
1660
- /* @__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(
1661
- Icon,
1662
- {
1663
- name: sortDirection === "asc" ? "ArrowUp" : "ArrowDown",
1664
- size: 14,
1665
- className: "text-blue-500"
1666
- }
1667
- ) : null)
1668
- )))), /* @__PURE__ */ import_react13.default.createElement("tbody", null, paginatedData.map((row) => /* @__PURE__ */ import_react13.default.createElement(
1669
- "tr",
1670
- {
1671
- key: row.id,
1672
- className: "border-b border-slate-200 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800/50"
1673
- },
1674
- columns.map((column) => /* @__PURE__ */ import_react13.default.createElement("td", { key: `${row.id}-${column.id}`, className: "p-3" }, renderCell(column, row, dateLocale)))
1675
- )), paginatedData.length === 0 ? /* @__PURE__ */ import_react13.default.createElement("tr", null, /* @__PURE__ */ import_react13.default.createElement(
1676
- "td",
1677
- {
1678
- className: "p-6 text-center text-slate-500",
1679
- colSpan: columns.length
1680
- },
1681
- emptyLabel
1682
- )) : 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(
1683
- "button",
1684
- {
1685
- type: "button",
1686
- className: "p-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 disabled:opacity-40 disabled:cursor-not-allowed transition-colors",
1687
- onClick: handlePrevPage,
1688
- disabled: safePage <= 1,
1689
- "aria-label": "Previous page"
1690
- },
1691
- /* @__PURE__ */ import_react13.default.createElement(Icon, { name: "ChevronLeft", size: 16 })
1692
- ), /* @__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(
1693
- "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,
1694
1513
  {
1695
- type: "button",
1696
- className: "p-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 disabled:opacity-40 disabled:cursor-not-allowed transition-colors",
1697
- onClick: handleNextPage,
1698
- disabled: safePage >= totalPages,
1699
- "aria-label": "Next page"
1700
- },
1701
- /* @__PURE__ */ import_react13.default.createElement(Icon, { name: "ChevronRight", size: 16 })
1702
- ))) : 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);
1703
1519
  }
1704
1520
  function renderCell(column, row, dateLocale) {
1705
1521
  const value = row[column.accessor];
@@ -1712,33 +1528,21 @@ function renderCell(column, row, dateLocale) {
1712
1528
  return value || "-";
1713
1529
  }
1714
1530
  DataTable.propTypes = {
1715
- /** Definisi kolom tabel. */
1716
- columns: import_prop_types11.default.arrayOf(
1717
- import_prop_types11.default.shape({
1718
- id: import_prop_types11.default.string.isRequired,
1719
- label: import_prop_types11.default.string.isRequired,
1720
- accessor: import_prop_types11.default.string.isRequired,
1721
- type: import_prop_types11.default.oneOf(["date", "currency", "statusBadge"]),
1722
- statusAccessor: import_prop_types11.default.string
1723
- })
1724
- ).isRequired,
1725
- /** 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,
1726
1538
  data: import_prop_types11.default.arrayOf(import_prop_types11.default.object),
1727
- /** Objek label i18n. */
1728
1539
  labels: import_prop_types11.default.object,
1729
- /** Locale untuk format tanggal. */
1730
1540
  dateLocale: import_prop_types11.default.string,
1731
- /** Apakah tabel mendukung pencarian. */
1732
1541
  searchable: import_prop_types11.default.bool,
1733
- /** Apakah tabel mendukung pengurutan. */
1734
1542
  sortable: import_prop_types11.default.bool,
1735
- /** Jumlah baris per halaman. */
1736
1543
  pageSize: import_prop_types11.default.number,
1737
- /** Label saat data kosong. */
1738
1544
  emptyLabel: import_prop_types11.default.string,
1739
- /** Placeholder pencarian. */
1740
1545
  searchPlaceholder: import_prop_types11.default.string,
1741
- /** ClassName tambahan. */
1742
1546
  className: import_prop_types11.default.string
1743
1547
  };
1744
1548
 
@@ -1892,44 +1696,38 @@ var AUDIENCE_OPTIONS = ["", "domestic", "foreign"];
1892
1696
  var DAY_PRESETS = [7, 30, 90, 0];
1893
1697
  var SORT_BY_OPTIONS = ["bookings", "revenue"];
1894
1698
  var SORT_DIR_OPTIONS = ["desc", "asc"];
1895
- function FilterPanel({
1896
- filters,
1897
- labels,
1898
- onFilterChange,
1899
- onResetFilters,
1900
- className = ""
1901
- }) {
1902
- 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(
1903
1701
  "select",
1904
1702
  {
1905
- className: "px-3 py-2 rounded-2xl border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-sm",
1703
+ className: "rdb-select",
1906
1704
  value: filters.statusScope,
1907
- onChange: (event) => onFilterChange("statusScope", event.target.value)
1705
+ onChange: (e) => onFilterChange("statusScope", e.target.value)
1908
1706
  },
1909
- STATUS_OPTIONS.map((status) => /* @__PURE__ */ import_react15.default.createElement("option", { key: status, value: status }, status === "confirmed" ? labels.confirmedOnly : status === "pending" ? labels.pendingOnly : labels.allStatus))
1910
- ), /* @__PURE__ */ import_react15.default.createElement("label", { className: "inline-flex items-center gap-2 px-3 py-2 rounded-2xl border border-slate-300 dark:border-slate-600" }, /* @__PURE__ */ import_react15.default.createElement(
1911
- Input,
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",
1912
1710
  {
1913
1711
  type: "checkbox",
1914
1712
  checked: filters.includePendingOverlay,
1915
- onChange: (event) => onFilterChange("includePendingOverlay", event.target.checked)
1713
+ onChange: (e) => onFilterChange("includePendingOverlay", e.target.checked)
1916
1714
  }
1917
- ), /* @__PURE__ */ import_react15.default.createElement(Typography, { variant: "body" }, labels.showPendingOverlay)), /* @__PURE__ */ import_react15.default.createElement(
1715
+ ), /* @__PURE__ */ import_react15.default.createElement("span", { className: "rdb-body" }, labels.showPendingOverlay)), /* @__PURE__ */ import_react15.default.createElement(
1918
1716
  "select",
1919
1717
  {
1920
- className: "px-3 py-2 rounded-2xl border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-sm",
1718
+ className: "rdb-select",
1921
1719
  value: filters.audience,
1922
- onChange: (event) => onFilterChange("audience", event.target.value)
1720
+ onChange: (e) => onFilterChange("audience", e.target.value)
1923
1721
  },
1924
- AUDIENCE_OPTIONS.map((audience) => /* @__PURE__ */ import_react15.default.createElement("option", { key: audience || "all", value: audience }, audience === "domestic" ? labels.audienceDomestic : audience === "foreign" ? labels.audienceForeign : labels.allAudience))
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))
1925
1723
  ), /* @__PURE__ */ import_react15.default.createElement(
1926
1724
  "select",
1927
1725
  {
1928
- className: "px-3 py-2 rounded-2xl border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-sm",
1726
+ className: "rdb-select",
1929
1727
  value: String(filters.daysPreset),
1930
- onChange: (event) => onFilterChange("daysPreset", Number(event.target.value))
1728
+ onChange: (e) => onFilterChange("daysPreset", Number(e.target.value))
1931
1729
  },
1932
- DAY_PRESETS.map((days) => /* @__PURE__ */ import_react15.default.createElement("option", { key: String(days), value: String(days) }, days === 0 ? labels.customDate : labels.dayLabel(days)))
1730
+ DAY_PRESETS.map((d) => /* @__PURE__ */ import_react15.default.createElement("option", { key: String(d), value: String(d) }, d === 0 ? labels.customDate : labels.dayLabel(d)))
1933
1731
  ), /* @__PURE__ */ import_react15.default.createElement(
1934
1732
  DateRangeFilter,
1935
1733
  {
@@ -1940,160 +1738,95 @@ function FilterPanel({
1940
1738
  if (dateTo !== filters.dateTo) onFilterChange("dateTo", dateTo);
1941
1739
  },
1942
1740
  disabled: filters.daysPreset !== 0,
1943
- className: "lg:col-span-2"
1741
+ className: "rdb-filter-date"
1944
1742
  }
1945
- )), /* @__PURE__ */ import_react15.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ import_react15.default.createElement(Button, { variant: "secondary", size: "sm", onClick: onResetFilters }, labels.reset), /* @__PURE__ */ import_react15.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react15.default.createElement(Typography, { variant: "caption" }, labels.topSort, ":"), /* @__PURE__ */ import_react15.default.createElement(
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(
1946
1744
  "select",
1947
1745
  {
1948
- className: "px-2 py-1.5 rounded-xl border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-sm",
1746
+ className: "rdb-select",
1747
+ style: { width: "auto" },
1949
1748
  value: filters.sortPkgBy,
1950
- onChange: (event) => onFilterChange("sortPkgBy", event.target.value)
1749
+ onChange: (e) => onFilterChange("sortPkgBy", e.target.value)
1951
1750
  },
1952
- SORT_BY_OPTIONS.map((sortBy) => /* @__PURE__ */ import_react15.default.createElement("option", { key: sortBy, value: sortBy }, sortBy === "bookings" ? labels.sortBookings : labels.sortRevenue))
1751
+ SORT_BY_OPTIONS.map((s) => /* @__PURE__ */ import_react15.default.createElement("option", { key: s, value: s }, s === "bookings" ? labels.sortBookings : labels.sortRevenue))
1953
1752
  ), /* @__PURE__ */ import_react15.default.createElement(
1954
1753
  "select",
1955
1754
  {
1956
- className: "px-2 py-1.5 rounded-xl border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-sm",
1755
+ className: "rdb-select",
1756
+ style: { width: "auto" },
1957
1757
  value: filters.sortPkgDir,
1958
- onChange: (event) => onFilterChange("sortPkgDir", event.target.value)
1758
+ onChange: (e) => onFilterChange("sortPkgDir", e.target.value)
1959
1759
  },
1960
- SORT_DIR_OPTIONS.map((direction) => /* @__PURE__ */ import_react15.default.createElement("option", { key: direction, value: direction }, direction === "desc" ? labels.sortDesc : labels.sortAsc))
1760
+ SORT_DIR_OPTIONS.map((d) => /* @__PURE__ */ import_react15.default.createElement("option", { key: d, value: d }, d === "desc" ? labels.sortDesc : labels.sortAsc))
1961
1761
  ))));
1962
1762
  }
1963
1763
  FilterPanel.propTypes = {
1964
- /** State filter saat ini. */
1965
- filters: import_prop_types13.default.shape({
1966
- statusScope: import_prop_types13.default.string,
1967
- includePendingOverlay: import_prop_types13.default.bool,
1968
- audience: import_prop_types13.default.string,
1969
- daysPreset: import_prop_types13.default.number,
1970
- dateFrom: import_prop_types13.default.string,
1971
- dateTo: import_prop_types13.default.string,
1972
- sortPkgBy: import_prop_types13.default.string,
1973
- sortPkgDir: import_prop_types13.default.string
1974
- }).isRequired,
1975
- /** Objek label i18n. */
1764
+ filters: import_prop_types13.default.object.isRequired,
1976
1765
  labels: import_prop_types13.default.object.isRequired,
1977
- /** Callback saat filter berubah (field, value). */
1978
1766
  onFilterChange: import_prop_types13.default.func.isRequired,
1979
- /** Callback saat filter di-reset. */
1980
1767
  onResetFilters: import_prop_types13.default.func.isRequired,
1981
- /** ClassName tambahan. */
1982
1768
  className: import_prop_types13.default.string
1983
1769
  };
1984
1770
 
1985
1771
  // src/presentation/organisms/SidebarNavigation.jsx
1986
1772
  var import_react16 = __toESM(require("react"), 1);
1987
1773
  var import_prop_types14 = __toESM(require("prop-types"), 1);
1988
- function SidebarNavigation({
1989
- items = [],
1990
- activeItem = "",
1991
- onItemClick,
1992
- logo = null,
1993
- className = ""
1994
- }) {
1995
- return /* @__PURE__ */ import_react16.default.createElement(
1996
- "aside",
1997
- {
1998
- className: `flex flex-col w-60 min-h-screen bg-white dark:bg-slate-900 border-r border-slate-200 dark:border-slate-800 ${className}`
1999
- },
2000
- logo ? /* @__PURE__ */ import_react16.default.createElement("div", { className: "px-4 py-4 border-b border-slate-100 dark:border-slate-800" }, logo) : null,
2001
- /* @__PURE__ */ import_react16.default.createElement("nav", { className: "flex-1 px-2 py-3 space-y-0.5", "aria-label": "Sidebar navigation" }, items.map((item) => {
2002
- const isActive = item.id === activeItem;
2003
- return /* @__PURE__ */ import_react16.default.createElement(
2004
- "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,
2005
1788
  {
2006
- key: item.id,
2007
- type: "button",
2008
- id: `sidebar-nav-${item.id}`,
2009
- onClick: () => onItemClick == null ? void 0 : onItemClick(item.id),
2010
- "aria-current": isActive ? "page" : void 0,
2011
- className: [
2012
- "w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-colors text-left",
2013
- isActive ? "bg-blue-50 dark:bg-blue-950/60 text-blue-700 dark:text-blue-300" : "text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800 hover:text-slate-900 dark:hover:text-slate-100"
2014
- ].join(" ")
2015
- },
2016
- item.icon ? /* @__PURE__ */ import_react16.default.createElement(
2017
- Icon,
2018
- {
2019
- name: item.icon,
2020
- size: 18,
2021
- className: isActive ? "text-blue-600 dark:text-blue-400" : "text-slate-400 dark:text-slate-500"
2022
- }
2023
- ) : null,
2024
- /* @__PURE__ */ import_react16.default.createElement("span", null, item.label),
2025
- isActive ? /* @__PURE__ */ import_react16.default.createElement("span", { className: "ml-auto w-1.5 h-1.5 rounded-full bg-blue-500" }) : null
2026
- );
2027
- }))
2028
- );
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
+ })));
2029
1798
  }
2030
1799
  SidebarNavigation.propTypes = {
2031
- /** Daftar item menu navigasi. */
2032
- items: import_prop_types14.default.arrayOf(
2033
- import_prop_types14.default.shape({
2034
- id: import_prop_types14.default.string.isRequired,
2035
- label: import_prop_types14.default.string.isRequired,
2036
- icon: import_prop_types14.default.string
2037
- })
2038
- ),
2039
- /** 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 })),
2040
1801
  activeItem: import_prop_types14.default.string,
2041
- /** Callback saat item diklik, menerima id item sebagai argumen. */
2042
1802
  onItemClick: import_prop_types14.default.func,
2043
- /** Konten logo/brand di atas sidebar. */
2044
1803
  logo: import_prop_types14.default.node,
2045
- /** ClassName tambahan. */
2046
1804
  className: import_prop_types14.default.string
2047
1805
  };
2048
1806
 
2049
1807
  // src/presentation/organisms/TopbarHeader.jsx
2050
1808
  var import_react17 = __toESM(require("react"), 1);
2051
1809
  var import_prop_types15 = __toESM(require("prop-types"), 1);
2052
- function TopbarHeader({
2053
- title = "",
2054
- actions = null,
2055
- leading = null,
2056
- className = ""
2057
- }) {
2058
- return /* @__PURE__ */ import_react17.default.createElement(
2059
- "header",
2060
- {
2061
- 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}`
2062
- },
2063
- /* @__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),
2064
- actions ? /* @__PURE__ */ import_react17.default.createElement("div", { className: "flex items-center gap-2" }, actions) : null
2065
- );
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);
2066
1812
  }
2067
1813
  TopbarHeader.propTypes = {
2068
- /** Judul halaman yang ditampilkan. */
2069
1814
  title: import_prop_types15.default.string,
2070
- /** Elemen aksi di sisi kanan (tombol, avatar, dsb.). */
2071
1815
  actions: import_prop_types15.default.node,
2072
- /** Elemen di sisi kiri sebelum judul (misal: hamburger menu). */
2073
1816
  leading: import_prop_types15.default.node,
2074
- /** ClassName tambahan. */
2075
1817
  className: import_prop_types15.default.string
2076
1818
  };
2077
1819
 
2078
1820
  // src/presentation/templates/DashboardLayout.jsx
2079
1821
  var import_react18 = __toESM(require("react"), 1);
2080
1822
  var import_prop_types16 = __toESM(require("prop-types"), 1);
2081
- function DashboardLayout({
2082
- header = null,
2083
- sidebar = null,
2084
- children,
2085
- className = ""
2086
- }) {
2087
- 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)));
2088
1825
  }
2089
1826
  DashboardLayout.propTypes = {
2090
- /** Konten header atas. */
2091
1827
  header: import_prop_types16.default.node,
2092
- /** Konten sidebar. */
2093
1828
  sidebar: import_prop_types16.default.node,
2094
- /** Konten utama dashboard. */
2095
1829
  children: import_prop_types16.default.node.isRequired,
2096
- /** ClassName tambahan. */
2097
1830
  className: import_prop_types16.default.string
2098
1831
  };
2099
1832
 
@@ -2113,16 +1846,7 @@ function ReusableDashboardView({
2113
1846
  dateLocale,
2114
1847
  liveUpdateEnabled
2115
1848
  }) {
2116
- return /* @__PURE__ */ import_react19.default.createElement("div", { className: "container mt-3 space-y-4" }, /* @__PURE__ */ import_react19.default.createElement("div", null, /* @__PURE__ */ import_react19.default.createElement("div", { className: "rounded-2xl border border-slate-200/60 dark:border-slate-800/60 backdrop-blur-md px-3 sm:px-4 py-2 glass shadow-smooth" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "flex flex-col gap-2" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "flex flex-wrap items-center justify-between gap-2" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ import_react19.default.createElement(Typography, { variant: "h1" }, labels.title), liveUpdateEnabled ? /* @__PURE__ */ import_react19.default.createElement(Badge, { status: "success" }, labels.liveUpdate) : null), /* @__PURE__ */ import_react19.default.createElement(
2117
- Button,
2118
- {
2119
- variant: "secondary",
2120
- size: "sm",
2121
- onClick: () => onRefresh(),
2122
- title: labels.refresh
2123
- },
2124
- /* @__PURE__ */ import_react19.default.createElement(Icon, { name: "RotateCcw", size: 16 })
2125
- )), 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(
2126
1850
  FilterPanel,
2127
1851
  {
2128
1852
  filters,
@@ -2130,7 +1854,7 @@ function ReusableDashboardView({
2130
1854
  onFilterChange,
2131
1855
  onResetFilters
2132
1856
  }
2133
- )))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4" }, loading ? Array.from({ length: 4 }).map((_, index) => /* @__PURE__ */ import_react19.default.createElement(SkeletonLoader, { key: `stat-skeleton-${index}`, className: "h-28" })) : config.widgets.stats.map((widget) => /* @__PURE__ */ import_react19.default.createElement(
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(
2134
1858
  StatCard,
2135
1859
  {
2136
1860
  key: widget.id,
@@ -2140,7 +1864,7 @@ function ReusableDashboardView({
2140
1864
  format: widget.format,
2141
1865
  accentColor: widget.accentColor
2142
1866
  }
2143
- ))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-4" }, config.widgets.charts.map((widget) => /* @__PURE__ */ import_react19.default.createElement(
1867
+ ))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "rdb-charts-grid" }, config.widgets.charts.map((widget) => /* @__PURE__ */ import_react19.default.createElement(
2144
1868
  ChartCard,
2145
1869
  {
2146
1870
  key: widget.id,
@@ -2150,7 +1874,15 @@ function ReusableDashboardView({
2150
1874
  filters,
2151
1875
  chartData: data.charts
2152
1876
  }
2153
- ))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "card p-4" }, /* @__PURE__ */ import_react19.default.createElement(Typography, { variant: "h3", className: "flex items-center gap-2 mb-3" }, /* @__PURE__ */ import_react19.default.createElement(Icon, { name: config.widgets.table.icon, size: 18 }), labels[config.widgets.table.label] || config.widgets.table.label), loading ? /* @__PURE__ */ import_react19.default.createElement(SkeletonLoader, { className: "h-48" }) : /* @__PURE__ */ import_react19.default.createElement(
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(
2154
1886
  DataTable,
2155
1887
  {
2156
1888
  columns: config.widgets.table.columns,
@@ -2162,30 +1894,19 @@ function ReusableDashboardView({
2162
1894
  sortable: true,
2163
1895
  pageSize: 10
2164
1896
  }
2165
- )));
1897
+ ))));
2166
1898
  }
2167
1899
  ReusableDashboardView.propTypes = {
2168
- /** Konfigurasi widget dashboard. */
2169
1900
  config: import_prop_types17.default.object.isRequired,
2170
- /** Objek label i18n. */
2171
1901
  labels: import_prop_types17.default.object.isRequired,
2172
- /** Status loading data. */
2173
1902
  loading: import_prop_types17.default.bool,
2174
- /** Pesan error jika ada. */
2175
1903
  error: import_prop_types17.default.string,
2176
- /** State filter dashboard. */
2177
1904
  filters: import_prop_types17.default.object,
2178
- /** Callback perubahan filter. */
2179
1905
  onFilterChange: import_prop_types17.default.func.isRequired,
2180
- /** Callback reset filter. */
2181
1906
  onResetFilters: import_prop_types17.default.func.isRequired,
2182
- /** Callback refresh data. */
2183
1907
  onRefresh: import_prop_types17.default.func.isRequired,
2184
- /** Data dashboard. */
2185
1908
  data: import_prop_types17.default.object,
2186
- /** Locale untuk format tanggal. */
2187
1909
  dateLocale: import_prop_types17.default.string,
2188
- /** Apakah live update aktif. */
2189
1910
  liveUpdateEnabled: import_prop_types17.default.bool
2190
1911
  };
2191
1912