@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.js CHANGED
@@ -1027,42 +1027,20 @@ function Button({
1027
1027
  children,
1028
1028
  ...rest
1029
1029
  }) {
1030
- 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";
1031
- const variants = {
1032
- primary: "bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500",
1033
- 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",
1034
- ghost: "text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800 focus:ring-slate-400"
1035
- };
1036
- const sizes = {
1037
- sm: "px-2.5 py-1 text-xs gap-1",
1038
- md: "px-3 py-1.5 text-sm gap-1.5",
1039
- lg: "px-4 py-2 text-base gap-2"
1040
- };
1041
- const classes = [base, variants[variant] || variants.primary, sizes[size] || sizes.md, className].filter(Boolean).join(" ");
1042
- return /* @__PURE__ */ React.createElement(
1043
- "button",
1044
- {
1045
- type: "button",
1046
- className: classes,
1047
- disabled,
1048
- onClick,
1049
- ...rest
1050
- },
1051
- children
1052
- );
1030
+ const classes = [
1031
+ "rdb-btn",
1032
+ `rdb-btn-${variant}`,
1033
+ `rdb-btn-${size}`,
1034
+ className
1035
+ ].filter(Boolean).join(" ");
1036
+ return /* @__PURE__ */ React.createElement("button", { type: "button", className: classes, disabled, onClick, ...rest }, children);
1053
1037
  }
1054
1038
  Button.propTypes = {
1055
- /** Varian visual tombol. */
1056
1039
  variant: PropTypes.oneOf(["primary", "secondary", "ghost"]),
1057
- /** Ukuran tombol. */
1058
1040
  size: PropTypes.oneOf(["sm", "md", "lg"]),
1059
- /** Apakah tombol non-aktif. */
1060
1041
  disabled: PropTypes.bool,
1061
- /** ClassName tambahan dari consumer. */
1062
1042
  className: PropTypes.string,
1063
- /** Callback saat tombol diklik. */
1064
1043
  onClick: PropTypes.func,
1065
- /** Konten tombol. */
1066
1044
  children: PropTypes.node.isRequired
1067
1045
  };
1068
1046
 
@@ -1079,14 +1057,7 @@ function Input({
1079
1057
  className = "",
1080
1058
  ...rest
1081
1059
  }) {
1082
- const inputClasses = [
1083
- "px-3 py-2 rounded-2xl border border-slate-300 dark:border-slate-600",
1084
- "bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100",
1085
- "text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500",
1086
- "disabled:opacity-50 disabled:cursor-not-allowed",
1087
- className
1088
- ].filter(Boolean).join(" ");
1089
- return /* @__PURE__ */ React2.createElement("div", { className: "flex flex-col gap-1" }, label ? /* @__PURE__ */ React2.createElement("label", { className: "text-xs font-medium text-slate-500 dark:text-slate-400" }, label) : null, /* @__PURE__ */ React2.createElement(
1060
+ return /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 4 } }, label ? /* @__PURE__ */ React2.createElement("label", { className: "rdb-label" }, label) : null, /* @__PURE__ */ React2.createElement(
1090
1061
  "input",
1091
1062
  {
1092
1063
  type,
@@ -1094,25 +1065,18 @@ function Input({
1094
1065
  onChange,
1095
1066
  placeholder,
1096
1067
  disabled,
1097
- className: inputClasses,
1068
+ className: ["rdb-input", className].filter(Boolean).join(" "),
1098
1069
  ...rest
1099
1070
  }
1100
1071
  ));
1101
1072
  }
1102
1073
  Input.propTypes = {
1103
- /** Tipe input. */
1104
1074
  type: PropTypes2.string,
1105
- /** Nilai input. */
1106
1075
  value: PropTypes2.oneOfType([PropTypes2.string, PropTypes2.number]),
1107
- /** Callback saat nilai berubah. */
1108
1076
  onChange: PropTypes2.func,
1109
- /** Placeholder teks. */
1110
1077
  placeholder: PropTypes2.string,
1111
- /** Label di atas input. */
1112
1078
  label: PropTypes2.string,
1113
- /** Apakah input non-aktif. */
1114
1079
  disabled: PropTypes2.bool,
1115
- /** ClassName tambahan. */
1116
1080
  className: PropTypes2.string
1117
1081
  };
1118
1082
 
@@ -1170,81 +1134,83 @@ Icon.propTypes = {
1170
1134
  // src/presentation/atoms/Typography.jsx
1171
1135
  import React4 from "react";
1172
1136
  import PropTypes4 from "prop-types";
1173
- var VARIANT_MAP = {
1174
- h1: { tag: "h1", className: "text-2xl sm:text-3xl font-bold" },
1175
- h2: { tag: "h2", className: "text-xl sm:text-2xl font-bold" },
1176
- h3: { tag: "h3", className: "text-lg font-semibold" },
1177
- subheading: { tag: "p", className: "text-sm font-medium text-slate-500 dark:text-slate-400" },
1178
- body: { tag: "p", className: "text-sm text-slate-700 dark:text-slate-300" },
1179
- caption: { tag: "span", className: "text-xs text-slate-500 dark:text-slate-400" },
1180
- metric: { tag: "span", className: "text-3xl font-bold text-slate-900 dark:text-slate-100" }
1137
+ var VARIANT_CLASS = {
1138
+ h1: "rdb-h1",
1139
+ h2: "rdb-h2",
1140
+ h3: "rdb-h3",
1141
+ subheading: "rdb-subheading",
1142
+ body: "rdb-body",
1143
+ caption: "rdb-caption",
1144
+ metric: "rdb-metric"
1181
1145
  };
1182
- function Typography({
1183
- variant = "body",
1184
- className = "",
1185
- children,
1186
- ...rest
1187
- }) {
1188
- const config = VARIANT_MAP[variant] || VARIANT_MAP.body;
1189
- const Tag = config.tag;
1190
- const classes = [config.className, className].filter(Boolean).join(" ");
1191
- return /* @__PURE__ */ React4.createElement(Tag, { className: classes, ...rest }, children);
1146
+ var VARIANT_TAG = {
1147
+ h1: "h1",
1148
+ h2: "h2",
1149
+ h3: "h3",
1150
+ subheading: "p",
1151
+ body: "p",
1152
+ caption: "span",
1153
+ metric: "span"
1154
+ };
1155
+ function Typography({ variant = "body", className = "", children, ...rest }) {
1156
+ const Tag = VARIANT_TAG[variant] || "p";
1157
+ const cls = [VARIANT_CLASS[variant] || "rdb-body", className].filter(Boolean).join(" ");
1158
+ return /* @__PURE__ */ React4.createElement(Tag, { className: cls, ...rest }, children);
1192
1159
  }
1193
1160
  Typography.propTypes = {
1194
- /** Varian tipografi. */
1195
1161
  variant: PropTypes4.oneOf(["h1", "h2", "h3", "subheading", "body", "caption", "metric"]),
1196
- /** ClassName tambahan. */
1197
1162
  className: PropTypes4.string,
1198
- /** Konten teks. */
1199
1163
  children: PropTypes4.node.isRequired
1200
1164
  };
1201
1165
 
1202
1166
  // src/presentation/atoms/Badge.jsx
1203
1167
  import React5 from "react";
1204
1168
  import PropTypes5 from "prop-types";
1205
- var STATUS_CLASSES = {
1206
- confirmed: "bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-200",
1207
- pending: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-200",
1208
- cancelled: "bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-200",
1209
- default: "bg-slate-100 text-slate-800 dark:bg-slate-700 dark:text-slate-200",
1210
- success: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300",
1211
- info: "bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300"
1169
+ var STATUS_CLASS = {
1170
+ confirmed: "rdb-badge-confirmed",
1171
+ pending: "rdb-badge-pending",
1172
+ cancelled: "rdb-badge-cancelled",
1173
+ success: "rdb-badge-success",
1174
+ info: "rdb-badge-info",
1175
+ default: "rdb-badge-default"
1212
1176
  };
1213
1177
  function Badge({ status = "default", className = "", children, ...rest }) {
1214
- const colorClass = STATUS_CLASSES[status] || STATUS_CLASSES.default;
1215
- const classes = ["px-2 py-1 rounded-full text-xs font-medium", colorClass, className].filter(Boolean).join(" ");
1216
- return /* @__PURE__ */ React5.createElement("span", { className: classes, ...rest }, children);
1178
+ const statusClass = STATUS_CLASS[status] || STATUS_CLASS.default;
1179
+ return /* @__PURE__ */ React5.createElement("span", { className: ["rdb-badge", statusClass, className].filter(Boolean).join(" "), ...rest }, children);
1217
1180
  }
1218
1181
  Badge.propTypes = {
1219
- /** Status yang menentukan warna badge. */
1220
1182
  status: PropTypes5.string,
1221
- /** ClassName tambahan. */
1222
1183
  className: PropTypes5.string,
1223
- /** Label teks badge. */
1224
1184
  children: PropTypes5.node.isRequired
1225
1185
  };
1226
1186
 
1227
1187
  // src/presentation/atoms/SkeletonLoader.jsx
1228
1188
  import React6 from "react";
1229
1189
  import PropTypes6 from "prop-types";
1230
- function SkeletonLoader({ className = "" }) {
1190
+ function SkeletonLoader({ className = "", style = {} }) {
1231
1191
  return /* @__PURE__ */ React6.createElement(
1232
1192
  "div",
1233
1193
  {
1234
- className: `animate-pulse bg-slate-200 dark:bg-slate-700 rounded-xl ${className}`,
1194
+ className: ["rdb-skeleton", className].filter(Boolean).join(" "),
1195
+ style: { minHeight: 20, ...style },
1235
1196
  role: "status",
1236
1197
  "aria-label": "Loading..."
1237
1198
  }
1238
1199
  );
1239
1200
  }
1240
1201
  SkeletonLoader.propTypes = {
1241
- /** ClassName tambahan untuk mengatur ukuran skeleton (misal: "h-28", "h-64"). */
1242
- className: PropTypes6.string
1202
+ className: PropTypes6.string,
1203
+ style: PropTypes6.object
1243
1204
  };
1244
1205
 
1245
1206
  // src/presentation/molecules/StatCard.jsx
1246
1207
  import React7 from "react";
1247
1208
  import PropTypes7 from "prop-types";
1209
+ function renderValue(format, value) {
1210
+ if (format === "currency") return `Rp ${formatIDR(value)}`;
1211
+ if (format === "percent") return `${Number(value) || 0}%`;
1212
+ return String(Number(value) || 0);
1213
+ }
1248
1214
  function StatCard({
1249
1215
  label,
1250
1216
  value,
@@ -1254,29 +1220,14 @@ function StatCard({
1254
1220
  accentColor = "blue",
1255
1221
  className = ""
1256
1222
  }) {
1257
- const formattedValue = renderValue(format, value);
1258
- const ACCENT = {
1259
- 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" },
1260
- 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" },
1261
- 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" },
1262
- 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" },
1263
- 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" },
1264
- 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" }
1265
- };
1266
- const accent = ACCENT[accentColor] || ACCENT.blue;
1267
- return /* @__PURE__ */ React7.createElement("div", { className: `card p-4 flex flex-col justify-between gap-3 ${accent.border} ${className}` }, /* @__PURE__ */ React7.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React7.createElement("div", { className: "flex items-center gap-2 text-slate-500 dark:text-slate-400" }, /* @__PURE__ */ React7.createElement("div", { className: `w-8 h-8 rounded-xl flex items-center justify-center ${accent.bg} ${accent.icon} shrink-0` }, /* @__PURE__ */ React7.createElement(Icon, { name: icon, size: 16 })), /* @__PURE__ */ React7.createElement(Typography, { variant: "subheading" }, label)), trend ? /* @__PURE__ */ React7.createElement(
1223
+ return /* @__PURE__ */ React7.createElement("div", { className: ["rdb-card rdb-statcard", `rdb-accent-${accentColor}`, className].filter(Boolean).join(" ") }, /* @__PURE__ */ React7.createElement("div", { className: "rdb-statcard-header" }, /* @__PURE__ */ React7.createElement("div", { className: "rdb-statcard-label-group" }, /* @__PURE__ */ React7.createElement("div", { className: "rdb-statcard-icon" }, /* @__PURE__ */ React7.createElement(Icon, { name: icon, size: 16 })), /* @__PURE__ */ React7.createElement("span", { className: "rdb-statcard-label" }, label)), trend ? /* @__PURE__ */ React7.createElement(
1268
1224
  Icon,
1269
1225
  {
1270
1226
  name: trend === "up" ? "TrendingUp" : "TrendingDown",
1271
1227
  size: 16,
1272
- className: trend === "up" ? "text-emerald-500" : "text-red-500"
1228
+ style: { color: trend === "up" ? "#10b981" : "#ef4444" }
1273
1229
  }
1274
- ) : null), /* @__PURE__ */ React7.createElement(Typography, { variant: "metric" }, formattedValue));
1275
- }
1276
- function renderValue(format, value) {
1277
- if (format === "currency") return `Rp ${formatIDR(value)}`;
1278
- if (format === "percent") return `${Number(value) || 0}%`;
1279
- return String(Number(value) || 0);
1230
+ ) : null), /* @__PURE__ */ React7.createElement("div", { className: "rdb-statcard-value" }, renderValue(format, value)));
1280
1231
  }
1281
1232
  StatCard.propTypes = {
1282
1233
  label: PropTypes7.string.isRequired,
@@ -1284,7 +1235,6 @@ StatCard.propTypes = {
1284
1235
  icon: PropTypes7.string,
1285
1236
  format: PropTypes7.oneOf(["number", "currency", "percent"]),
1286
1237
  trend: PropTypes7.oneOf(["up", "down", null]),
1287
- /** Warna aksen: blue | green | violet | orange | sky | rose */
1288
1238
  accentColor: PropTypes7.string,
1289
1239
  className: PropTypes7.string
1290
1240
  };
@@ -1302,45 +1252,33 @@ function SearchBar({
1302
1252
  const [internalValue, setInternalValue] = useState3("");
1303
1253
  const isControlled = controlledValue !== void 0;
1304
1254
  const currentValue = isControlled ? controlledValue : internalValue;
1305
- const handleChange = useCallback2(
1306
- (event) => {
1307
- const newValue = event.target.value;
1308
- if (!isControlled) setInternalValue(newValue);
1309
- if (onChange) onChange(newValue);
1310
- if (onSearch) onSearch(newValue);
1311
- },
1312
- [isControlled, onChange, onSearch]
1313
- );
1314
- const handleKeyDown = useCallback2(
1315
- (event) => {
1316
- if (event.key === "Enter" && onSearch) {
1317
- onSearch(currentValue);
1318
- }
1319
- },
1320
- [currentValue, onSearch]
1321
- );
1322
- return /* @__PURE__ */ React8.createElement("div", { className: `relative ${className}` }, /* @__PURE__ */ React8.createElement("div", { className: "absolute inset-y-0 left-3 flex items-center pointer-events-none" }, /* @__PURE__ */ React8.createElement(Icon, { name: "Search", size: 16, className: "text-slate-400" })), /* @__PURE__ */ React8.createElement(
1323
- Input,
1255
+ const handleChange = useCallback2((event) => {
1256
+ const v = event.target.value;
1257
+ if (!isControlled) setInternalValue(v);
1258
+ if (onChange) onChange(v);
1259
+ if (onSearch) onSearch(v);
1260
+ }, [isControlled, onChange, onSearch]);
1261
+ const handleKeyDown = useCallback2((event) => {
1262
+ if (event.key === "Enter" && onSearch) onSearch(currentValue);
1263
+ }, [currentValue, onSearch]);
1264
+ return /* @__PURE__ */ React8.createElement("div", { className: ["rdb-searchbar", className].filter(Boolean).join(" ") }, /* @__PURE__ */ React8.createElement("div", { className: "rdb-searchbar-icon" }, /* @__PURE__ */ React8.createElement(Icon, { name: "Search", size: 16 })), /* @__PURE__ */ React8.createElement(
1265
+ "input",
1324
1266
  {
1325
1267
  type: "search",
1326
1268
  value: currentValue,
1327
1269
  onChange: handleChange,
1328
1270
  onKeyDown: handleKeyDown,
1329
1271
  placeholder,
1330
- className: "pl-9 w-full"
1272
+ className: "rdb-input rdb-searchbar-input",
1273
+ style: { width: "100%" }
1331
1274
  }
1332
1275
  ));
1333
1276
  }
1334
1277
  SearchBar.propTypes = {
1335
- /** Nilai pencarian saat ini (controlled). */
1336
1278
  value: PropTypes8.string,
1337
- /** Callback saat nilai input berubah. */
1338
1279
  onChange: PropTypes8.func,
1339
- /** Callback saat pencarian disubmit. */
1340
1280
  onSearch: PropTypes8.func,
1341
- /** Placeholder teks. */
1342
1281
  placeholder: PropTypes8.string,
1343
- /** ClassName tambahan. */
1344
1282
  className: PropTypes8.string
1345
1283
  };
1346
1284
 
@@ -1350,26 +1288,11 @@ import PropTypes9 from "prop-types";
1350
1288
  var CURRENT_YEAR = (/* @__PURE__ */ new Date()).getFullYear();
1351
1289
  var MIN_YEAR2 = 2020;
1352
1290
  var MAX_YEAR = CURRENT_YEAR + 1;
1353
- var MONTHS = [
1354
- "Jan",
1355
- "Feb",
1356
- "Mar",
1357
- "Apr",
1358
- "May",
1359
- "Jun",
1360
- "Jul",
1361
- "Aug",
1362
- "Sep",
1363
- "Oct",
1364
- "Nov",
1365
- "Dec"
1366
- ];
1291
+ var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1367
1292
  function getYears() {
1368
- const years = [];
1369
- for (let y = MAX_YEAR; y >= MIN_YEAR2; y--) {
1370
- years.push(y);
1371
- }
1372
- return years;
1293
+ const y = [];
1294
+ for (let i = MAX_YEAR; i >= MIN_YEAR2; i--) y.push(i);
1295
+ return y;
1373
1296
  }
1374
1297
  function getDaysInMonth(year, month) {
1375
1298
  return new Date(year, month, 0).getDate();
@@ -1377,68 +1300,57 @@ function getDaysInMonth(year, month) {
1377
1300
  function parseDate(value) {
1378
1301
  if (value && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
1379
1302
  const [y, m, d] = value.split("-").map(Number);
1380
- if (y >= MIN_YEAR2 && y <= MAX_YEAR && m >= 1 && m <= 12) {
1303
+ if (y >= MIN_YEAR2 && y <= MAX_YEAR && m >= 1 && m <= 12)
1381
1304
  return { year: y, month: m, day: d };
1382
- }
1383
1305
  }
1384
1306
  const now = /* @__PURE__ */ new Date();
1385
1307
  return { year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate() };
1386
1308
  }
1387
- function formatDate2({ year, month, day }) {
1309
+ function fmt({ year, month, day }) {
1388
1310
  return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
1389
1311
  }
1390
1312
  function DatePicker({ value, onChange, disabled = false, label = "" }) {
1391
- const parsed = parseDate(value);
1313
+ const p = parseDate(value);
1392
1314
  const years = getYears();
1393
- const maxDay = getDaysInMonth(parsed.year, parsed.month);
1394
- const handleChange = useCallback3(
1395
- (field, newVal) => {
1396
- const updated = { ...parsed, [field]: Number(newVal) };
1397
- const maxD = getDaysInMonth(updated.year, updated.month);
1398
- if (updated.day > maxD) updated.day = maxD;
1399
- onChange(formatDate2(updated));
1400
- },
1401
- [parsed, onChange]
1402
- );
1403
- 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";
1404
- return /* @__PURE__ */ React9.createElement("div", { className: "flex flex-col gap-1" }, label ? /* @__PURE__ */ React9.createElement(Typography, { variant: "caption", className: "font-medium" }, label) : null, /* @__PURE__ */ React9.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React9.createElement(
1315
+ const maxDay = getDaysInMonth(p.year, p.month);
1316
+ const handleChange = useCallback3((field, val) => {
1317
+ const u = { ...p, [field]: Number(val) };
1318
+ const max = getDaysInMonth(u.year, u.month);
1319
+ if (u.day > max) u.day = max;
1320
+ onChange(fmt(u));
1321
+ }, [p, onChange]);
1322
+ return /* @__PURE__ */ React9.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 2 } }, label ? /* @__PURE__ */ React9.createElement("span", { className: "rdb-caption rdb-label" }, label) : null, /* @__PURE__ */ React9.createElement("div", { style: { display: "flex", gap: 4, alignItems: "center" } }, /* @__PURE__ */ React9.createElement(
1405
1323
  "select",
1406
1324
  {
1407
- className: selectClass,
1408
- value: parsed.day,
1325
+ className: "rdb-select",
1326
+ style: { width: 52 },
1327
+ value: p.day,
1409
1328
  onChange: (e) => handleChange("day", e.target.value),
1410
- disabled,
1411
- "aria-label": `${label} day`
1329
+ disabled
1412
1330
  },
1413
1331
  Array.from({ length: maxDay }, (_, i) => i + 1).map((d) => /* @__PURE__ */ React9.createElement("option", { key: d, value: d }, String(d).padStart(2, "0")))
1414
1332
  ), /* @__PURE__ */ React9.createElement(
1415
1333
  "select",
1416
1334
  {
1417
- className: selectClass,
1418
- value: parsed.month,
1335
+ className: "rdb-select",
1336
+ style: { width: 62 },
1337
+ value: p.month,
1419
1338
  onChange: (e) => handleChange("month", e.target.value),
1420
- disabled,
1421
- "aria-label": `${label} month`
1339
+ disabled
1422
1340
  },
1423
1341
  MONTHS.map((name, idx) => /* @__PURE__ */ React9.createElement("option", { key: idx + 1, value: idx + 1 }, name))
1424
1342
  ), /* @__PURE__ */ React9.createElement(
1425
1343
  "select",
1426
1344
  {
1427
- className: selectClass,
1428
- value: parsed.year,
1345
+ className: "rdb-select",
1346
+ style: { width: 72 },
1347
+ value: p.year,
1429
1348
  onChange: (e) => handleChange("year", e.target.value),
1430
- disabled,
1431
- "aria-label": `${label} year`
1349
+ disabled
1432
1350
  },
1433
1351
  years.map((y) => /* @__PURE__ */ React9.createElement("option", { key: y, value: y }, y))
1434
1352
  )));
1435
1353
  }
1436
- DatePicker.propTypes = {
1437
- value: PropTypes9.string.isRequired,
1438
- onChange: PropTypes9.func.isRequired,
1439
- disabled: PropTypes9.bool,
1440
- label: PropTypes9.string
1441
- };
1442
1354
  function DateRangeFilter({
1443
1355
  dateFrom,
1444
1356
  dateTo,
@@ -1448,31 +1360,9 @@ function DateRangeFilter({
1448
1360
  labelTo = "To",
1449
1361
  className = ""
1450
1362
  }) {
1451
- const handleFromChange = useCallback3(
1452
- (newDate) => onRangeChange == null ? void 0 : onRangeChange({ dateFrom: newDate, dateTo }),
1453
- [dateTo, onRangeChange]
1454
- );
1455
- const handleToChange = useCallback3(
1456
- (newDate) => onRangeChange == null ? void 0 : onRangeChange({ dateFrom, dateTo: newDate }),
1457
- [dateFrom, onRangeChange]
1458
- );
1459
- return /* @__PURE__ */ React9.createElement("div", { className: `flex flex-wrap items-end gap-3 ${className}` }, /* @__PURE__ */ React9.createElement(
1460
- DatePicker,
1461
- {
1462
- value: dateFrom,
1463
- onChange: handleFromChange,
1464
- disabled,
1465
- label: labelFrom
1466
- }
1467
- ), /* @__PURE__ */ React9.createElement(
1468
- DatePicker,
1469
- {
1470
- value: dateTo,
1471
- onChange: handleToChange,
1472
- disabled,
1473
- label: labelTo
1474
- }
1475
- ));
1363
+ const handleFrom = useCallback3((v) => onRangeChange == null ? void 0 : onRangeChange({ dateFrom: v, dateTo }), [dateTo, onRangeChange]);
1364
+ const handleTo = useCallback3((v) => onRangeChange == null ? void 0 : onRangeChange({ dateFrom, dateTo: v }), [dateFrom, onRangeChange]);
1365
+ return /* @__PURE__ */ React9.createElement("div", { className: ["rdb-daterange", className].filter(Boolean).join(" ") }, /* @__PURE__ */ React9.createElement(DatePicker, { value: dateFrom, onChange: handleFrom, disabled, label: labelFrom }), /* @__PURE__ */ React9.createElement(DatePicker, { value: dateTo, onChange: handleTo, disabled, label: labelTo }));
1476
1366
  }
1477
1367
  DateRangeFilter.propTypes = {
1478
1368
  dateFrom: PropTypes9.string.isRequired,
@@ -1487,22 +1377,13 @@ DateRangeFilter.propTypes = {
1487
1377
  // src/presentation/molecules/ChartHeader.jsx
1488
1378
  import React10 from "react";
1489
1379
  import PropTypes10 from "prop-types";
1490
- function ChartHeader({
1491
- title,
1492
- icon = "BarChart3",
1493
- actions = null,
1494
- className = ""
1495
- }) {
1496
- return /* @__PURE__ */ React10.createElement("div", { className: `flex items-center justify-between mb-2 ${className}` }, /* @__PURE__ */ React10.createElement(Typography, { variant: "h3", className: "flex items-center gap-2" }, /* @__PURE__ */ React10.createElement(Icon, { name: icon, size: 18 }), title), actions);
1380
+ function ChartHeader({ title, icon = "BarChart3", actions = null, className = "" }) {
1381
+ return /* @__PURE__ */ React10.createElement("div", { className: ["rdb-chart-title-wrap", className].filter(Boolean).join(" ") }, /* @__PURE__ */ React10.createElement("div", { className: "rdb-chart-title" }, /* @__PURE__ */ React10.createElement(Icon, { name: icon, size: 18 }), title), actions);
1497
1382
  }
1498
1383
  ChartHeader.propTypes = {
1499
- /** Judul grafik. */
1500
1384
  title: PropTypes10.string.isRequired,
1501
- /** Nama ikon header. */
1502
1385
  icon: PropTypes10.string,
1503
- /** Elemen aksi tambahan di sisi kanan. */
1504
1386
  actions: PropTypes10.node,
1505
- /** ClassName tambahan. */
1506
1387
  className: PropTypes10.string
1507
1388
  };
1508
1389
 
@@ -1527,28 +1408,24 @@ function DataTable({
1527
1408
  const [currentPage, setCurrentPage] = useState5(1);
1528
1409
  const filteredData = useMemo2(() => {
1529
1410
  if (!searchQuery.trim()) return data;
1530
- const query = searchQuery.toLowerCase();
1411
+ const q = searchQuery.toLowerCase();
1531
1412
  return data.filter(
1532
1413
  (row) => columns.some((col) => {
1533
- const value = row[col.accessor];
1534
- return value != null && String(value).toLowerCase().includes(query);
1414
+ const v = row[col.accessor];
1415
+ return v != null && String(v).toLowerCase().includes(q);
1535
1416
  })
1536
1417
  );
1537
1418
  }, [data, searchQuery, columns]);
1538
1419
  const sortedData = useMemo2(() => {
1539
1420
  if (!sortField) return filteredData;
1540
- const sorted = [...filteredData].sort((a, b) => {
1541
- const aVal = a[sortField] ?? "";
1542
- const bVal = b[sortField] ?? "";
1543
- if (typeof aVal === "number" && typeof bVal === "number") {
1544
- return sortDirection === "asc" ? aVal - bVal : bVal - aVal;
1545
- }
1546
- const comparison = String(aVal).localeCompare(String(bVal), void 0, {
1547
- numeric: true
1548
- });
1549
- return sortDirection === "asc" ? comparison : -comparison;
1421
+ return [...filteredData].sort((a, b) => {
1422
+ const aV = a[sortField] ?? "";
1423
+ const bV = b[sortField] ?? "";
1424
+ if (typeof aV === "number" && typeof bV === "number")
1425
+ return sortDirection === "asc" ? aV - bV : bV - aV;
1426
+ const cmp = String(aV).localeCompare(String(bV), void 0, { numeric: true });
1427
+ return sortDirection === "asc" ? cmp : -cmp;
1550
1428
  });
1551
- return sorted;
1552
1429
  }, [filteredData, sortField, sortDirection]);
1553
1430
  const totalPages = Math.max(1, Math.ceil(sortedData.length / pageSize));
1554
1431
  const safePage = Math.min(currentPage, totalPages);
@@ -1556,90 +1433,29 @@ function DataTable({
1556
1433
  const start = (safePage - 1) * pageSize;
1557
1434
  return sortedData.slice(start, start + pageSize);
1558
1435
  }, [sortedData, safePage, pageSize]);
1559
- const handleSearch = useCallback4((value) => {
1560
- setSearchQuery(value);
1436
+ const handleSearch = useCallback4((v) => {
1437
+ setSearchQuery(v);
1561
1438
  setCurrentPage(1);
1562
1439
  }, []);
1563
- const handleSort = useCallback4(
1564
- (accessor) => {
1565
- if (!sortable) return;
1566
- if (sortField === accessor) {
1567
- setSortDirection((prev) => prev === "asc" ? "desc" : "asc");
1568
- } else {
1569
- setSortField(accessor);
1570
- setSortDirection("asc");
1571
- }
1572
- setCurrentPage(1);
1573
- },
1574
- [sortable, sortField]
1575
- );
1576
- const handlePrevPage = useCallback4(() => {
1577
- setCurrentPage((prev) => Math.max(1, prev - 1));
1578
- }, []);
1579
- const handleNextPage = useCallback4(() => {
1580
- setCurrentPage((prev) => Math.min(totalPages, prev + 1));
1581
- }, [totalPages]);
1582
- return /* @__PURE__ */ React11.createElement("div", { className: `space-y-3 ${className}` }, searchable ? /* @__PURE__ */ React11.createElement(
1583
- SearchBar,
1584
- {
1585
- value: searchQuery,
1586
- onSearch: handleSearch,
1587
- placeholder: searchPlaceholder,
1588
- className: "max-w-sm"
1440
+ const handleSort = useCallback4((acc) => {
1441
+ if (!sortable) return;
1442
+ if (sortField === acc) setSortDirection((p) => p === "asc" ? "desc" : "asc");
1443
+ else {
1444
+ setSortField(acc);
1445
+ setSortDirection("asc");
1589
1446
  }
1590
- ) : null, /* @__PURE__ */ React11.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React11.createElement("table", { className: "min-w-full text-sm border-collapse" }, /* @__PURE__ */ React11.createElement("thead", null, /* @__PURE__ */ React11.createElement("tr", { className: "bg-slate-100 dark:bg-slate-800 text-left" }, columns.map((column) => /* @__PURE__ */ React11.createElement(
1591
- "th",
1592
- {
1593
- key: column.id,
1594
- className: [
1595
- "p-3",
1596
- sortable ? "cursor-pointer select-none hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors" : ""
1597
- ].join(" "),
1598
- onClick: () => handleSort(column.accessor)
1599
- },
1600
- /* @__PURE__ */ React11.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React11.createElement("span", null, labels[column.label] || column.label), sortable && sortField === column.accessor ? /* @__PURE__ */ React11.createElement(
1601
- Icon,
1602
- {
1603
- name: sortDirection === "asc" ? "ArrowUp" : "ArrowDown",
1604
- size: 14,
1605
- className: "text-blue-500"
1606
- }
1607
- ) : null)
1608
- )))), /* @__PURE__ */ React11.createElement("tbody", null, paginatedData.map((row) => /* @__PURE__ */ React11.createElement(
1609
- "tr",
1610
- {
1611
- key: row.id,
1612
- className: "border-b border-slate-200 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800/50"
1613
- },
1614
- columns.map((column) => /* @__PURE__ */ React11.createElement("td", { key: `${row.id}-${column.id}`, className: "p-3" }, renderCell(column, row, dateLocale)))
1615
- )), paginatedData.length === 0 ? /* @__PURE__ */ React11.createElement("tr", null, /* @__PURE__ */ React11.createElement(
1616
- "td",
1617
- {
1618
- className: "p-6 text-center text-slate-500",
1619
- colSpan: columns.length
1620
- },
1621
- emptyLabel
1622
- )) : null))), sortedData.length > pageSize ? /* @__PURE__ */ React11.createElement("div", { className: "flex items-center justify-between px-1" }, /* @__PURE__ */ React11.createElement("span", { className: "text-xs text-slate-500" }, (safePage - 1) * pageSize + 1, "\u2013", Math.min(safePage * pageSize, sortedData.length), " of", " ", sortedData.length), /* @__PURE__ */ React11.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React11.createElement(
1623
- "button",
1624
- {
1625
- type: "button",
1626
- className: "p-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 disabled:opacity-40 disabled:cursor-not-allowed transition-colors",
1627
- onClick: handlePrevPage,
1628
- disabled: safePage <= 1,
1629
- "aria-label": "Previous page"
1630
- },
1631
- /* @__PURE__ */ React11.createElement(Icon, { name: "ChevronLeft", size: 16 })
1632
- ), /* @__PURE__ */ React11.createElement("span", { className: "text-xs text-slate-600 dark:text-slate-300 min-w-[60px] text-center" }, safePage, " / ", totalPages), /* @__PURE__ */ React11.createElement(
1633
- "button",
1447
+ setCurrentPage(1);
1448
+ }, [sortable, sortField]);
1449
+ const handlePrev = useCallback4(() => setCurrentPage((p) => Math.max(1, p - 1)), []);
1450
+ const handleNext = useCallback4(() => setCurrentPage((p) => Math.min(totalPages, p + 1)), [totalPages]);
1451
+ return /* @__PURE__ */ React11.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, className }, searchable ? /* @__PURE__ */ React11.createElement(SearchBar, { value: searchQuery, onSearch: handleSearch, placeholder: searchPlaceholder }) : null, /* @__PURE__ */ React11.createElement("div", { className: "rdb-table-wrapper" }, /* @__PURE__ */ React11.createElement("table", { className: "rdb-table" }, /* @__PURE__ */ React11.createElement("thead", null, /* @__PURE__ */ React11.createElement("tr", null, columns.map((col) => /* @__PURE__ */ React11.createElement("th", { key: col.id, onClick: () => handleSort(col.accessor) }, /* @__PURE__ */ React11.createElement("div", { style: { display: "flex", alignItems: "center", gap: 4 } }, /* @__PURE__ */ React11.createElement("span", null, labels[col.label] || col.label), sortable && sortField === col.accessor ? /* @__PURE__ */ React11.createElement(
1452
+ Icon,
1634
1453
  {
1635
- type: "button",
1636
- className: "p-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 disabled:opacity-40 disabled:cursor-not-allowed transition-colors",
1637
- onClick: handleNextPage,
1638
- disabled: safePage >= totalPages,
1639
- "aria-label": "Next page"
1640
- },
1641
- /* @__PURE__ */ React11.createElement(Icon, { name: "ChevronRight", size: 16 })
1642
- ))) : null);
1454
+ name: sortDirection === "asc" ? "ArrowUp" : "ArrowDown",
1455
+ size: 13,
1456
+ style: { color: "var(--rdb-blue-500)" }
1457
+ }
1458
+ ) : null))))), /* @__PURE__ */ React11.createElement("tbody", null, paginatedData.map((row) => /* @__PURE__ */ React11.createElement("tr", { key: row.id }, columns.map((col) => /* @__PURE__ */ React11.createElement("td", { key: `${row.id}-${col.id}` }, renderCell(col, row, dateLocale))))), paginatedData.length === 0 ? /* @__PURE__ */ React11.createElement("tr", null, /* @__PURE__ */ React11.createElement("td", { className: "rdb-table-empty", colSpan: columns.length }, emptyLabel)) : null))), sortedData.length > pageSize ? /* @__PURE__ */ React11.createElement("div", { className: "rdb-pagination" }, /* @__PURE__ */ React11.createElement("span", { className: "rdb-pagination-info" }, (safePage - 1) * pageSize + 1, "\u2013", Math.min(safePage * pageSize, sortedData.length), " of ", sortedData.length), /* @__PURE__ */ React11.createElement("div", { className: "rdb-pagination-controls" }, /* @__PURE__ */ React11.createElement("button", { type: "button", className: "rdb-page-btn", onClick: handlePrev, disabled: safePage <= 1, "aria-label": "Previous" }, /* @__PURE__ */ React11.createElement(Icon, { name: "ChevronLeft", size: 16 })), /* @__PURE__ */ React11.createElement("span", { className: "rdb-pagination-page" }, safePage, " / ", totalPages), /* @__PURE__ */ React11.createElement("button", { type: "button", className: "rdb-page-btn", onClick: handleNext, disabled: safePage >= totalPages, "aria-label": "Next" }, /* @__PURE__ */ React11.createElement(Icon, { name: "ChevronRight", size: 16 })))) : null);
1643
1459
  }
1644
1460
  function renderCell(column, row, dateLocale) {
1645
1461
  const value = row[column.accessor];
@@ -1652,33 +1468,21 @@ function renderCell(column, row, dateLocale) {
1652
1468
  return value || "-";
1653
1469
  }
1654
1470
  DataTable.propTypes = {
1655
- /** Definisi kolom tabel. */
1656
- columns: PropTypes11.arrayOf(
1657
- PropTypes11.shape({
1658
- id: PropTypes11.string.isRequired,
1659
- label: PropTypes11.string.isRequired,
1660
- accessor: PropTypes11.string.isRequired,
1661
- type: PropTypes11.oneOf(["date", "currency", "statusBadge"]),
1662
- statusAccessor: PropTypes11.string
1663
- })
1664
- ).isRequired,
1665
- /** Array data baris. */
1471
+ columns: PropTypes11.arrayOf(PropTypes11.shape({
1472
+ id: PropTypes11.string.isRequired,
1473
+ label: PropTypes11.string.isRequired,
1474
+ accessor: PropTypes11.string.isRequired,
1475
+ type: PropTypes11.oneOf(["date", "currency", "statusBadge"]),
1476
+ statusAccessor: PropTypes11.string
1477
+ })).isRequired,
1666
1478
  data: PropTypes11.arrayOf(PropTypes11.object),
1667
- /** Objek label i18n. */
1668
1479
  labels: PropTypes11.object,
1669
- /** Locale untuk format tanggal. */
1670
1480
  dateLocale: PropTypes11.string,
1671
- /** Apakah tabel mendukung pencarian. */
1672
1481
  searchable: PropTypes11.bool,
1673
- /** Apakah tabel mendukung pengurutan. */
1674
1482
  sortable: PropTypes11.bool,
1675
- /** Jumlah baris per halaman. */
1676
1483
  pageSize: PropTypes11.number,
1677
- /** Label saat data kosong. */
1678
1484
  emptyLabel: PropTypes11.string,
1679
- /** Placeholder pencarian. */
1680
1485
  searchPlaceholder: PropTypes11.string,
1681
- /** ClassName tambahan. */
1682
1486
  className: PropTypes11.string
1683
1487
  };
1684
1488
 
@@ -1846,44 +1650,38 @@ var AUDIENCE_OPTIONS = ["", "domestic", "foreign"];
1846
1650
  var DAY_PRESETS = [7, 30, 90, 0];
1847
1651
  var SORT_BY_OPTIONS = ["bookings", "revenue"];
1848
1652
  var SORT_DIR_OPTIONS = ["desc", "asc"];
1849
- function FilterPanel({
1850
- filters,
1851
- labels,
1852
- onFilterChange,
1853
- onResetFilters,
1854
- className = ""
1855
- }) {
1856
- return /* @__PURE__ */ React13.createElement("div", { className: `flex flex-col gap-2 ${className}` }, /* @__PURE__ */ React13.createElement("div", { className: "grid grid-cols-1 lg:grid-cols-6 gap-2" }, /* @__PURE__ */ React13.createElement(
1653
+ function FilterPanel({ filters, labels, onFilterChange, onResetFilters, className = "" }) {
1654
+ return /* @__PURE__ */ React13.createElement("div", { className: ["rdb-filter-panel", className].filter(Boolean).join(" ") }, /* @__PURE__ */ React13.createElement("div", { className: "rdb-filter-row" }, /* @__PURE__ */ React13.createElement(
1857
1655
  "select",
1858
1656
  {
1859
- className: "px-3 py-2 rounded-2xl border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-sm",
1657
+ className: "rdb-select",
1860
1658
  value: filters.statusScope,
1861
- onChange: (event) => onFilterChange("statusScope", event.target.value)
1659
+ onChange: (e) => onFilterChange("statusScope", e.target.value)
1862
1660
  },
1863
- STATUS_OPTIONS.map((status) => /* @__PURE__ */ React13.createElement("option", { key: status, value: status }, status === "confirmed" ? labels.confirmedOnly : status === "pending" ? labels.pendingOnly : labels.allStatus))
1864
- ), /* @__PURE__ */ React13.createElement("label", { className: "inline-flex items-center gap-2 px-3 py-2 rounded-2xl border border-slate-300 dark:border-slate-600" }, /* @__PURE__ */ React13.createElement(
1865
- Input,
1661
+ STATUS_OPTIONS.map((s) => /* @__PURE__ */ React13.createElement("option", { key: s, value: s }, s === "confirmed" ? labels.confirmedOnly : s === "pending" ? labels.pendingOnly : labels.allStatus))
1662
+ ), /* @__PURE__ */ React13.createElement("label", { className: "rdb-filter-overlay-label" }, /* @__PURE__ */ React13.createElement(
1663
+ "input",
1866
1664
  {
1867
1665
  type: "checkbox",
1868
1666
  checked: filters.includePendingOverlay,
1869
- onChange: (event) => onFilterChange("includePendingOverlay", event.target.checked)
1667
+ onChange: (e) => onFilterChange("includePendingOverlay", e.target.checked)
1870
1668
  }
1871
- ), /* @__PURE__ */ React13.createElement(Typography, { variant: "body" }, labels.showPendingOverlay)), /* @__PURE__ */ React13.createElement(
1669
+ ), /* @__PURE__ */ React13.createElement("span", { className: "rdb-body" }, labels.showPendingOverlay)), /* @__PURE__ */ React13.createElement(
1872
1670
  "select",
1873
1671
  {
1874
- className: "px-3 py-2 rounded-2xl border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-sm",
1672
+ className: "rdb-select",
1875
1673
  value: filters.audience,
1876
- onChange: (event) => onFilterChange("audience", event.target.value)
1674
+ onChange: (e) => onFilterChange("audience", e.target.value)
1877
1675
  },
1878
- AUDIENCE_OPTIONS.map((audience) => /* @__PURE__ */ React13.createElement("option", { key: audience || "all", value: audience }, audience === "domestic" ? labels.audienceDomestic : audience === "foreign" ? labels.audienceForeign : labels.allAudience))
1676
+ AUDIENCE_OPTIONS.map((a) => /* @__PURE__ */ React13.createElement("option", { key: a || "all", value: a }, a === "domestic" ? labels.audienceDomestic : a === "foreign" ? labels.audienceForeign : labels.allAudience))
1879
1677
  ), /* @__PURE__ */ React13.createElement(
1880
1678
  "select",
1881
1679
  {
1882
- className: "px-3 py-2 rounded-2xl border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-sm",
1680
+ className: "rdb-select",
1883
1681
  value: String(filters.daysPreset),
1884
- onChange: (event) => onFilterChange("daysPreset", Number(event.target.value))
1682
+ onChange: (e) => onFilterChange("daysPreset", Number(e.target.value))
1885
1683
  },
1886
- DAY_PRESETS.map((days) => /* @__PURE__ */ React13.createElement("option", { key: String(days), value: String(days) }, days === 0 ? labels.customDate : labels.dayLabel(days)))
1684
+ DAY_PRESETS.map((d) => /* @__PURE__ */ React13.createElement("option", { key: String(d), value: String(d) }, d === 0 ? labels.customDate : labels.dayLabel(d)))
1887
1685
  ), /* @__PURE__ */ React13.createElement(
1888
1686
  DateRangeFilter,
1889
1687
  {
@@ -1894,160 +1692,95 @@ function FilterPanel({
1894
1692
  if (dateTo !== filters.dateTo) onFilterChange("dateTo", dateTo);
1895
1693
  },
1896
1694
  disabled: filters.daysPreset !== 0,
1897
- className: "lg:col-span-2"
1695
+ className: "rdb-filter-date"
1898
1696
  }
1899
- )), /* @__PURE__ */ React13.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React13.createElement(Button, { variant: "secondary", size: "sm", onClick: onResetFilters }, labels.reset), /* @__PURE__ */ React13.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React13.createElement(Typography, { variant: "caption" }, labels.topSort, ":"), /* @__PURE__ */ React13.createElement(
1697
+ )), /* @__PURE__ */ React13.createElement("div", { className: "rdb-filter-actions" }, /* @__PURE__ */ React13.createElement(Button, { variant: "secondary", size: "sm", onClick: onResetFilters }, labels.reset), /* @__PURE__ */ React13.createElement("div", { className: "rdb-filter-sort" }, /* @__PURE__ */ React13.createElement("span", { className: "rdb-caption" }, labels.topSort, ":"), /* @__PURE__ */ React13.createElement(
1900
1698
  "select",
1901
1699
  {
1902
- className: "px-2 py-1.5 rounded-xl border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-sm",
1700
+ className: "rdb-select",
1701
+ style: { width: "auto" },
1903
1702
  value: filters.sortPkgBy,
1904
- onChange: (event) => onFilterChange("sortPkgBy", event.target.value)
1703
+ onChange: (e) => onFilterChange("sortPkgBy", e.target.value)
1905
1704
  },
1906
- SORT_BY_OPTIONS.map((sortBy) => /* @__PURE__ */ React13.createElement("option", { key: sortBy, value: sortBy }, sortBy === "bookings" ? labels.sortBookings : labels.sortRevenue))
1705
+ SORT_BY_OPTIONS.map((s) => /* @__PURE__ */ React13.createElement("option", { key: s, value: s }, s === "bookings" ? labels.sortBookings : labels.sortRevenue))
1907
1706
  ), /* @__PURE__ */ React13.createElement(
1908
1707
  "select",
1909
1708
  {
1910
- className: "px-2 py-1.5 rounded-xl border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-sm",
1709
+ className: "rdb-select",
1710
+ style: { width: "auto" },
1911
1711
  value: filters.sortPkgDir,
1912
- onChange: (event) => onFilterChange("sortPkgDir", event.target.value)
1712
+ onChange: (e) => onFilterChange("sortPkgDir", e.target.value)
1913
1713
  },
1914
- SORT_DIR_OPTIONS.map((direction) => /* @__PURE__ */ React13.createElement("option", { key: direction, value: direction }, direction === "desc" ? labels.sortDesc : labels.sortAsc))
1714
+ SORT_DIR_OPTIONS.map((d) => /* @__PURE__ */ React13.createElement("option", { key: d, value: d }, d === "desc" ? labels.sortDesc : labels.sortAsc))
1915
1715
  ))));
1916
1716
  }
1917
1717
  FilterPanel.propTypes = {
1918
- /** State filter saat ini. */
1919
- filters: PropTypes13.shape({
1920
- statusScope: PropTypes13.string,
1921
- includePendingOverlay: PropTypes13.bool,
1922
- audience: PropTypes13.string,
1923
- daysPreset: PropTypes13.number,
1924
- dateFrom: PropTypes13.string,
1925
- dateTo: PropTypes13.string,
1926
- sortPkgBy: PropTypes13.string,
1927
- sortPkgDir: PropTypes13.string
1928
- }).isRequired,
1929
- /** Objek label i18n. */
1718
+ filters: PropTypes13.object.isRequired,
1930
1719
  labels: PropTypes13.object.isRequired,
1931
- /** Callback saat filter berubah (field, value). */
1932
1720
  onFilterChange: PropTypes13.func.isRequired,
1933
- /** Callback saat filter di-reset. */
1934
1721
  onResetFilters: PropTypes13.func.isRequired,
1935
- /** ClassName tambahan. */
1936
1722
  className: PropTypes13.string
1937
1723
  };
1938
1724
 
1939
1725
  // src/presentation/organisms/SidebarNavigation.jsx
1940
1726
  import React14 from "react";
1941
1727
  import PropTypes14 from "prop-types";
1942
- function SidebarNavigation({
1943
- items = [],
1944
- activeItem = "",
1945
- onItemClick,
1946
- logo = null,
1947
- className = ""
1948
- }) {
1949
- return /* @__PURE__ */ React14.createElement(
1950
- "aside",
1951
- {
1952
- 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}`
1953
- },
1954
- logo ? /* @__PURE__ */ React14.createElement("div", { className: "px-4 py-4 border-b border-slate-100 dark:border-slate-800" }, logo) : null,
1955
- /* @__PURE__ */ React14.createElement("nav", { className: "flex-1 px-2 py-3 space-y-0.5", "aria-label": "Sidebar navigation" }, items.map((item) => {
1956
- const isActive = item.id === activeItem;
1957
- return /* @__PURE__ */ React14.createElement(
1958
- "button",
1728
+ function SidebarNavigation({ items = [], activeItem = "", onItemClick, logo = null, className = "" }) {
1729
+ return /* @__PURE__ */ React14.createElement("aside", { className: ["rdb-sidebar", className].filter(Boolean).join(" ") }, logo ? /* @__PURE__ */ React14.createElement("div", { className: "rdb-sidebar-logo" }, logo) : null, /* @__PURE__ */ React14.createElement("nav", { className: "rdb-sidebar-nav", "aria-label": "Sidebar navigation" }, items.map((item) => {
1730
+ const isActive = item.id === activeItem;
1731
+ return /* @__PURE__ */ React14.createElement(
1732
+ "button",
1733
+ {
1734
+ key: item.id,
1735
+ type: "button",
1736
+ onClick: () => onItemClick == null ? void 0 : onItemClick(item.id),
1737
+ "aria-current": isActive ? "page" : void 0,
1738
+ className: ["rdb-nav-item", isActive ? "rdb-nav-item-active" : ""].filter(Boolean).join(" ")
1739
+ },
1740
+ item.icon ? /* @__PURE__ */ React14.createElement(
1741
+ Icon,
1959
1742
  {
1960
- key: item.id,
1961
- type: "button",
1962
- id: `sidebar-nav-${item.id}`,
1963
- onClick: () => onItemClick == null ? void 0 : onItemClick(item.id),
1964
- "aria-current": isActive ? "page" : void 0,
1965
- className: [
1966
- "w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-colors text-left",
1967
- 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"
1968
- ].join(" ")
1969
- },
1970
- item.icon ? /* @__PURE__ */ React14.createElement(
1971
- Icon,
1972
- {
1973
- name: item.icon,
1974
- size: 18,
1975
- className: isActive ? "text-blue-600 dark:text-blue-400" : "text-slate-400 dark:text-slate-500"
1976
- }
1977
- ) : null,
1978
- /* @__PURE__ */ React14.createElement("span", null, item.label),
1979
- isActive ? /* @__PURE__ */ React14.createElement("span", { className: "ml-auto w-1.5 h-1.5 rounded-full bg-blue-500" }) : null
1980
- );
1981
- }))
1982
- );
1743
+ name: item.icon,
1744
+ size: 18,
1745
+ style: { color: isActive ? "var(--rdb-blue-600)" : "var(--rdb-text-subtle)" }
1746
+ }
1747
+ ) : null,
1748
+ /* @__PURE__ */ React14.createElement("span", null, item.label),
1749
+ isActive ? /* @__PURE__ */ React14.createElement("span", { className: "rdb-nav-dot" }) : null
1750
+ );
1751
+ })));
1983
1752
  }
1984
1753
  SidebarNavigation.propTypes = {
1985
- /** Daftar item menu navigasi. */
1986
- items: PropTypes14.arrayOf(
1987
- PropTypes14.shape({
1988
- id: PropTypes14.string.isRequired,
1989
- label: PropTypes14.string.isRequired,
1990
- icon: PropTypes14.string
1991
- })
1992
- ),
1993
- /** ID item yang sedang aktif. */
1754
+ items: PropTypes14.arrayOf(PropTypes14.shape({ id: PropTypes14.string.isRequired, label: PropTypes14.string.isRequired, icon: PropTypes14.string })),
1994
1755
  activeItem: PropTypes14.string,
1995
- /** Callback saat item diklik, menerima id item sebagai argumen. */
1996
1756
  onItemClick: PropTypes14.func,
1997
- /** Konten logo/brand di atas sidebar. */
1998
1757
  logo: PropTypes14.node,
1999
- /** ClassName tambahan. */
2000
1758
  className: PropTypes14.string
2001
1759
  };
2002
1760
 
2003
1761
  // src/presentation/organisms/TopbarHeader.jsx
2004
1762
  import React15 from "react";
2005
1763
  import PropTypes15 from "prop-types";
2006
- function TopbarHeader({
2007
- title = "",
2008
- actions = null,
2009
- leading = null,
2010
- className = ""
2011
- }) {
2012
- return /* @__PURE__ */ React15.createElement(
2013
- "header",
2014
- {
2015
- 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}`
2016
- },
2017
- /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-3" }, leading, title ? /* @__PURE__ */ React15.createElement(Typography, { variant: "h2", className: "truncate" }, title) : null),
2018
- actions ? /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, actions) : null
2019
- );
1764
+ function TopbarHeader({ title = "", actions = null, leading = null, className = "" }) {
1765
+ return /* @__PURE__ */ React15.createElement("header", { className: ["rdb-topbar", className].filter(Boolean).join(" ") }, /* @__PURE__ */ React15.createElement("div", { className: "rdb-topbar-left" }, leading, title ? /* @__PURE__ */ React15.createElement("span", { className: "rdb-h2", style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, title) : null), actions ? /* @__PURE__ */ React15.createElement("div", { className: "rdb-topbar-right" }, actions) : null);
2020
1766
  }
2021
1767
  TopbarHeader.propTypes = {
2022
- /** Judul halaman yang ditampilkan. */
2023
1768
  title: PropTypes15.string,
2024
- /** Elemen aksi di sisi kanan (tombol, avatar, dsb.). */
2025
1769
  actions: PropTypes15.node,
2026
- /** Elemen di sisi kiri sebelum judul (misal: hamburger menu). */
2027
1770
  leading: PropTypes15.node,
2028
- /** ClassName tambahan. */
2029
1771
  className: PropTypes15.string
2030
1772
  };
2031
1773
 
2032
1774
  // src/presentation/templates/DashboardLayout.jsx
2033
1775
  import React16 from "react";
2034
1776
  import PropTypes16 from "prop-types";
2035
- function DashboardLayout({
2036
- header = null,
2037
- sidebar = null,
2038
- children,
2039
- className = ""
2040
- }) {
2041
- return /* @__PURE__ */ React16.createElement("div", { className: `min-h-screen bg-slate-50 dark:bg-slate-900 ${className}` }, header, /* @__PURE__ */ React16.createElement("div", { className: "flex" }, sidebar, /* @__PURE__ */ React16.createElement("main", { className: "flex-1 container mx-auto px-4 py-3 space-y-4" }, children)));
1777
+ function DashboardLayout({ header = null, sidebar = null, children, className = "" }) {
1778
+ return /* @__PURE__ */ React16.createElement("div", { className: ["rdb-layout", className].filter(Boolean).join(" ") }, header, /* @__PURE__ */ React16.createElement("div", { className: "rdb-layout-body" }, sidebar, /* @__PURE__ */ React16.createElement("main", { className: "rdb-layout-main" }, children)));
2042
1779
  }
2043
1780
  DashboardLayout.propTypes = {
2044
- /** Konten header atas. */
2045
1781
  header: PropTypes16.node,
2046
- /** Konten sidebar. */
2047
1782
  sidebar: PropTypes16.node,
2048
- /** Konten utama dashboard. */
2049
1783
  children: PropTypes16.node.isRequired,
2050
- /** ClassName tambahan. */
2051
1784
  className: PropTypes16.string
2052
1785
  };
2053
1786
 
@@ -2067,16 +1800,7 @@ function ReusableDashboardView({
2067
1800
  dateLocale,
2068
1801
  liveUpdateEnabled
2069
1802
  }) {
2070
- return /* @__PURE__ */ React17.createElement("div", { className: "container mt-3 space-y-4" }, /* @__PURE__ */ React17.createElement("div", null, /* @__PURE__ */ React17.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__ */ React17.createElement("div", { className: "flex flex-col gap-2" }, /* @__PURE__ */ React17.createElement("div", { className: "flex flex-wrap items-center justify-between gap-2" }, /* @__PURE__ */ React17.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React17.createElement(Typography, { variant: "h1" }, labels.title), liveUpdateEnabled ? /* @__PURE__ */ React17.createElement(Badge, { status: "success" }, labels.liveUpdate) : null), /* @__PURE__ */ React17.createElement(
2071
- Button,
2072
- {
2073
- variant: "secondary",
2074
- size: "sm",
2075
- onClick: () => onRefresh(),
2076
- title: labels.refresh
2077
- },
2078
- /* @__PURE__ */ React17.createElement(Icon, { name: "RotateCcw", size: 16 })
2079
- )), error ? /* @__PURE__ */ React17.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__ */ React17.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React17.createElement("span", null, error), /* @__PURE__ */ React17.createElement(Button, { variant: "secondary", size: "sm", onClick: () => onRefresh() }, labels.retry))) : null, /* @__PURE__ */ React17.createElement(
1803
+ return /* @__PURE__ */ React17.createElement("div", { className: "rdb-view" }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-view-container" }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-header-panel" }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-header-top" }, /* @__PURE__ */ React17.createElement("div", { className: "rdb-header-title-group" }, /* @__PURE__ */ React17.createElement("span", { className: "rdb-h1" }, labels.title), liveUpdateEnabled ? /* @__PURE__ */ React17.createElement(Badge, { status: "success" }, labels.liveUpdate) : null), /* @__PURE__ */ React17.createElement(Button, { variant: "secondary", size: "sm", onClick: () => onRefresh(), title: labels.refresh }, /* @__PURE__ */ React17.createElement(Icon, { name: "RotateCcw", size: 16 }))), error ? /* @__PURE__ */ React17.createElement("div", { className: "rdb-error-banner" }, /* @__PURE__ */ React17.createElement("span", null, error), /* @__PURE__ */ React17.createElement(Button, { variant: "secondary", size: "sm", onClick: () => onRefresh() }, labels.retry)) : null, /* @__PURE__ */ React17.createElement(
2080
1804
  FilterPanel,
2081
1805
  {
2082
1806
  filters,
@@ -2084,7 +1808,7 @@ function ReusableDashboardView({
2084
1808
  onFilterChange,
2085
1809
  onResetFilters
2086
1810
  }
2087
- )))), /* @__PURE__ */ React17.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__ */ React17.createElement(SkeletonLoader, { key: `stat-skeleton-${index}`, className: "h-28" })) : config.widgets.stats.map((widget) => /* @__PURE__ */ React17.createElement(
1811
+ )), /* @__PURE__ */ React17.createElement("div", { className: "rdb-stats-grid" }, loading ? Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ React17.createElement(SkeletonLoader, { key: i, style: { height: 112 } })) : config.widgets.stats.map((widget) => /* @__PURE__ */ React17.createElement(
2088
1812
  StatCard,
2089
1813
  {
2090
1814
  key: widget.id,
@@ -2094,7 +1818,7 @@ function ReusableDashboardView({
2094
1818
  format: widget.format,
2095
1819
  accentColor: widget.accentColor
2096
1820
  }
2097
- ))), /* @__PURE__ */ React17.createElement("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-4" }, config.widgets.charts.map((widget) => /* @__PURE__ */ React17.createElement(
1821
+ ))), /* @__PURE__ */ React17.createElement("div", { className: "rdb-charts-grid" }, config.widgets.charts.map((widget) => /* @__PURE__ */ React17.createElement(
2098
1822
  ChartCard,
2099
1823
  {
2100
1824
  key: widget.id,
@@ -2104,7 +1828,15 @@ function ReusableDashboardView({
2104
1828
  filters,
2105
1829
  chartData: data.charts
2106
1830
  }
2107
- ))), /* @__PURE__ */ React17.createElement("div", { className: "card p-4" }, /* @__PURE__ */ React17.createElement(Typography, { variant: "h3", className: "flex items-center gap-2 mb-3" }, /* @__PURE__ */ React17.createElement(Icon, { name: config.widgets.table.icon, size: 18 }), labels[config.widgets.table.label] || config.widgets.table.label), loading ? /* @__PURE__ */ React17.createElement(SkeletonLoader, { className: "h-48" }) : /* @__PURE__ */ React17.createElement(
1831
+ ))), /* @__PURE__ */ React17.createElement("div", { className: "rdb-card", style: { padding: 16 } }, /* @__PURE__ */ React17.createElement(
1832
+ "div",
1833
+ {
1834
+ style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 12 },
1835
+ className: "rdb-h3"
1836
+ },
1837
+ /* @__PURE__ */ React17.createElement(Icon, { name: config.widgets.table.icon, size: 18 }),
1838
+ labels[config.widgets.table.label] || config.widgets.table.label
1839
+ ), loading ? /* @__PURE__ */ React17.createElement(SkeletonLoader, { style: { height: 192 } }) : /* @__PURE__ */ React17.createElement(
2108
1840
  DataTable,
2109
1841
  {
2110
1842
  columns: config.widgets.table.columns,
@@ -2116,30 +1848,19 @@ function ReusableDashboardView({
2116
1848
  sortable: true,
2117
1849
  pageSize: 10
2118
1850
  }
2119
- )));
1851
+ ))));
2120
1852
  }
2121
1853
  ReusableDashboardView.propTypes = {
2122
- /** Konfigurasi widget dashboard. */
2123
1854
  config: PropTypes17.object.isRequired,
2124
- /** Objek label i18n. */
2125
1855
  labels: PropTypes17.object.isRequired,
2126
- /** Status loading data. */
2127
1856
  loading: PropTypes17.bool,
2128
- /** Pesan error jika ada. */
2129
1857
  error: PropTypes17.string,
2130
- /** State filter dashboard. */
2131
1858
  filters: PropTypes17.object,
2132
- /** Callback perubahan filter. */
2133
1859
  onFilterChange: PropTypes17.func.isRequired,
2134
- /** Callback reset filter. */
2135
1860
  onResetFilters: PropTypes17.func.isRequired,
2136
- /** Callback refresh data. */
2137
1861
  onRefresh: PropTypes17.func.isRequired,
2138
- /** Data dashboard. */
2139
1862
  data: PropTypes17.object,
2140
- /** Locale untuk format tanggal. */
2141
1863
  dateLocale: PropTypes17.string,
2142
- /** Apakah live update aktif. */
2143
1864
  liveUpdateEnabled: PropTypes17.bool
2144
1865
  };
2145
1866