@rozaqi02/reusable-dashboard 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -16,28 +16,32 @@ var cidikaWidgetConfig = {
16
16
  label: "confirmedBookings",
17
17
  icon: "TrendingUp",
18
18
  valueKey: "bookingsConfirm",
19
- format: "number"
19
+ format: "number",
20
+ accentColor: "blue"
20
21
  },
21
22
  {
22
23
  id: "revenueConfirm",
23
24
  label: "confirmedRevenue",
24
25
  icon: "DollarSign",
25
26
  valueKey: "revenueConfirm",
26
- format: "currency"
27
+ format: "currency",
28
+ accentColor: "green"
27
29
  },
28
30
  {
29
31
  id: "avgRevenue",
30
32
  label: "avgRevenue",
31
33
  icon: "Users",
32
34
  valueKey: "avgRevenue",
33
- format: "currency"
35
+ format: "currency",
36
+ accentColor: "violet"
34
37
  },
35
38
  {
36
39
  id: "conversionRate",
37
40
  label: "conversionRate",
38
41
  icon: "PieChart",
39
42
  valueKey: "conversionRate",
40
- format: "percent"
43
+ format: "percent",
44
+ accentColor: "orange"
41
45
  }
42
46
  ],
43
47
  charts: [
@@ -163,28 +167,32 @@ var tokoSepatuWidgetConfig = {
163
167
  label: "confirmedBookings",
164
168
  icon: "TrendingUp",
165
169
  valueKey: "bookingsConfirm",
166
- format: "number"
170
+ format: "number",
171
+ accentColor: "sky"
167
172
  },
168
173
  {
169
174
  id: "totalRevenue",
170
175
  label: "confirmedRevenue",
171
176
  icon: "DollarSign",
172
177
  valueKey: "revenueConfirm",
173
- format: "currency"
178
+ format: "currency",
179
+ accentColor: "green"
174
180
  },
175
181
  {
176
182
  id: "avgOrderValue",
177
183
  label: "avgRevenue",
178
184
  icon: "BarChart3",
179
185
  valueKey: "avgRevenue",
180
- format: "currency"
186
+ format: "currency",
187
+ accentColor: "violet"
181
188
  },
182
189
  {
183
190
  id: "totalProducts",
184
191
  label: "totalProducts",
185
192
  icon: "PieChart",
186
193
  valueKey: "packages",
187
- format: "number"
194
+ format: "number",
195
+ accentColor: "orange"
188
196
  }
189
197
  ],
190
198
  charts: [
@@ -1019,42 +1027,20 @@ function Button({
1019
1027
  children,
1020
1028
  ...rest
1021
1029
  }) {
1022
- 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";
1023
- const variants = {
1024
- primary: "bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500",
1025
- 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",
1026
- ghost: "text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800 focus:ring-slate-400"
1027
- };
1028
- const sizes = {
1029
- sm: "px-2.5 py-1 text-xs gap-1",
1030
- md: "px-3 py-1.5 text-sm gap-1.5",
1031
- lg: "px-4 py-2 text-base gap-2"
1032
- };
1033
- const classes = [base, variants[variant] || variants.primary, sizes[size] || sizes.md, className].filter(Boolean).join(" ");
1034
- return /* @__PURE__ */ React.createElement(
1035
- "button",
1036
- {
1037
- type: "button",
1038
- className: classes,
1039
- disabled,
1040
- onClick,
1041
- ...rest
1042
- },
1043
- children
1044
- );
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);
1045
1037
  }
1046
1038
  Button.propTypes = {
1047
- /** Varian visual tombol. */
1048
1039
  variant: PropTypes.oneOf(["primary", "secondary", "ghost"]),
1049
- /** Ukuran tombol. */
1050
1040
  size: PropTypes.oneOf(["sm", "md", "lg"]),
1051
- /** Apakah tombol non-aktif. */
1052
1041
  disabled: PropTypes.bool,
1053
- /** ClassName tambahan dari consumer. */
1054
1042
  className: PropTypes.string,
1055
- /** Callback saat tombol diklik. */
1056
1043
  onClick: PropTypes.func,
1057
- /** Konten tombol. */
1058
1044
  children: PropTypes.node.isRequired
1059
1045
  };
1060
1046
 
@@ -1071,14 +1057,7 @@ function Input({
1071
1057
  className = "",
1072
1058
  ...rest
1073
1059
  }) {
1074
- const inputClasses = [
1075
- "px-3 py-2 rounded-2xl border border-slate-300 dark:border-slate-600",
1076
- "bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100",
1077
- "text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500",
1078
- "disabled:opacity-50 disabled:cursor-not-allowed",
1079
- className
1080
- ].filter(Boolean).join(" ");
1081
- 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(
1082
1061
  "input",
1083
1062
  {
1084
1063
  type,
@@ -1086,25 +1065,18 @@ function Input({
1086
1065
  onChange,
1087
1066
  placeholder,
1088
1067
  disabled,
1089
- className: inputClasses,
1068
+ className: ["rdb-input", className].filter(Boolean).join(" "),
1090
1069
  ...rest
1091
1070
  }
1092
1071
  ));
1093
1072
  }
1094
1073
  Input.propTypes = {
1095
- /** Tipe input. */
1096
1074
  type: PropTypes2.string,
1097
- /** Nilai input. */
1098
1075
  value: PropTypes2.oneOfType([PropTypes2.string, PropTypes2.number]),
1099
- /** Callback saat nilai berubah. */
1100
1076
  onChange: PropTypes2.func,
1101
- /** Placeholder teks. */
1102
1077
  placeholder: PropTypes2.string,
1103
- /** Label di atas input. */
1104
1078
  label: PropTypes2.string,
1105
- /** Apakah input non-aktif. */
1106
1079
  disabled: PropTypes2.bool,
1107
- /** ClassName tambahan. */
1108
1080
  className: PropTypes2.string
1109
1081
  };
1110
1082
 
@@ -1162,116 +1134,108 @@ Icon.propTypes = {
1162
1134
  // src/presentation/atoms/Typography.jsx
1163
1135
  import React4 from "react";
1164
1136
  import PropTypes4 from "prop-types";
1165
- var VARIANT_MAP = {
1166
- h1: { tag: "h1", className: "text-2xl sm:text-3xl font-bold" },
1167
- h2: { tag: "h2", className: "text-xl sm:text-2xl font-bold" },
1168
- h3: { tag: "h3", className: "text-lg font-semibold" },
1169
- subheading: { tag: "p", className: "text-sm font-medium text-slate-500 dark:text-slate-400" },
1170
- body: { tag: "p", className: "text-sm text-slate-700 dark:text-slate-300" },
1171
- caption: { tag: "span", className: "text-xs text-slate-500 dark:text-slate-400" },
1172
- 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"
1173
1145
  };
1174
- function Typography({
1175
- variant = "body",
1176
- className = "",
1177
- children,
1178
- ...rest
1179
- }) {
1180
- const config = VARIANT_MAP[variant] || VARIANT_MAP.body;
1181
- const Tag = config.tag;
1182
- const classes = [config.className, className].filter(Boolean).join(" ");
1183
- 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);
1184
1159
  }
1185
1160
  Typography.propTypes = {
1186
- /** Varian tipografi. */
1187
1161
  variant: PropTypes4.oneOf(["h1", "h2", "h3", "subheading", "body", "caption", "metric"]),
1188
- /** ClassName tambahan. */
1189
1162
  className: PropTypes4.string,
1190
- /** Konten teks. */
1191
1163
  children: PropTypes4.node.isRequired
1192
1164
  };
1193
1165
 
1194
1166
  // src/presentation/atoms/Badge.jsx
1195
1167
  import React5 from "react";
1196
1168
  import PropTypes5 from "prop-types";
1197
- var STATUS_CLASSES = {
1198
- confirmed: "bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-200",
1199
- pending: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-200",
1200
- cancelled: "bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-200",
1201
- default: "bg-slate-100 text-slate-800 dark:bg-slate-700 dark:text-slate-200",
1202
- success: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300",
1203
- 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"
1204
1176
  };
1205
1177
  function Badge({ status = "default", className = "", children, ...rest }) {
1206
- const colorClass = STATUS_CLASSES[status] || STATUS_CLASSES.default;
1207
- const classes = ["px-2 py-1 rounded-full text-xs font-medium", colorClass, className].filter(Boolean).join(" ");
1208
- 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);
1209
1180
  }
1210
1181
  Badge.propTypes = {
1211
- /** Status yang menentukan warna badge. */
1212
1182
  status: PropTypes5.string,
1213
- /** ClassName tambahan. */
1214
1183
  className: PropTypes5.string,
1215
- /** Label teks badge. */
1216
1184
  children: PropTypes5.node.isRequired
1217
1185
  };
1218
1186
 
1219
1187
  // src/presentation/atoms/SkeletonLoader.jsx
1220
1188
  import React6 from "react";
1221
1189
  import PropTypes6 from "prop-types";
1222
- function SkeletonLoader({ className = "" }) {
1190
+ function SkeletonLoader({ className = "", style = {} }) {
1223
1191
  return /* @__PURE__ */ React6.createElement(
1224
1192
  "div",
1225
1193
  {
1226
- 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 },
1227
1196
  role: "status",
1228
1197
  "aria-label": "Loading..."
1229
1198
  }
1230
1199
  );
1231
1200
  }
1232
1201
  SkeletonLoader.propTypes = {
1233
- /** ClassName tambahan untuk mengatur ukuran skeleton (misal: "h-28", "h-64"). */
1234
- className: PropTypes6.string
1202
+ className: PropTypes6.string,
1203
+ style: PropTypes6.object
1235
1204
  };
1236
1205
 
1237
1206
  // src/presentation/molecules/StatCard.jsx
1238
1207
  import React7 from "react";
1239
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
+ }
1240
1214
  function StatCard({
1241
1215
  label,
1242
1216
  value,
1243
1217
  icon = "TrendingUp",
1244
1218
  format = "number",
1245
1219
  trend = null,
1220
+ accentColor = "blue",
1246
1221
  className = ""
1247
1222
  }) {
1248
- const formattedValue = renderValue(format, value);
1249
- return /* @__PURE__ */ React7.createElement("div", { className: `card p-4 flex flex-col justify-between gap-2 ${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(Icon, { name: icon, size: 20 }), /* @__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(
1250
1224
  Icon,
1251
1225
  {
1252
1226
  name: trend === "up" ? "TrendingUp" : "TrendingDown",
1253
1227
  size: 16,
1254
- className: trend === "up" ? "text-emerald-500" : "text-red-500"
1228
+ style: { color: trend === "up" ? "#10b981" : "#ef4444" }
1255
1229
  }
1256
- ) : null), /* @__PURE__ */ React7.createElement(Typography, { variant: "metric" }, formattedValue));
1257
- }
1258
- function renderValue(format, value) {
1259
- if (format === "currency") return `Rp ${formatIDR(value)}`;
1260
- if (format === "percent") return `${Number(value) || 0}%`;
1261
- return String(Number(value) || 0);
1230
+ ) : null), /* @__PURE__ */ React7.createElement("div", { className: "rdb-statcard-value" }, renderValue(format, value)));
1262
1231
  }
1263
1232
  StatCard.propTypes = {
1264
- /** Label deskriptif metrik. */
1265
1233
  label: PropTypes7.string.isRequired,
1266
- /** Nilai metrik. */
1267
1234
  value: PropTypes7.oneOfType([PropTypes7.number, PropTypes7.string]),
1268
- /** Nama ikon dari registry Lucide React. */
1269
1235
  icon: PropTypes7.string,
1270
- /** Format tampilan nilai. */
1271
1236
  format: PropTypes7.oneOf(["number", "currency", "percent"]),
1272
- /** Arah tren opsional. */
1273
1237
  trend: PropTypes7.oneOf(["up", "down", null]),
1274
- /** ClassName tambahan. */
1238
+ accentColor: PropTypes7.string,
1275
1239
  className: PropTypes7.string
1276
1240
  };
1277
1241
 
@@ -1288,45 +1252,33 @@ function SearchBar({
1288
1252
  const [internalValue, setInternalValue] = useState3("");
1289
1253
  const isControlled = controlledValue !== void 0;
1290
1254
  const currentValue = isControlled ? controlledValue : internalValue;
1291
- const handleChange = useCallback2(
1292
- (event) => {
1293
- const newValue = event.target.value;
1294
- if (!isControlled) setInternalValue(newValue);
1295
- if (onChange) onChange(newValue);
1296
- if (onSearch) onSearch(newValue);
1297
- },
1298
- [isControlled, onChange, onSearch]
1299
- );
1300
- const handleKeyDown = useCallback2(
1301
- (event) => {
1302
- if (event.key === "Enter" && onSearch) {
1303
- onSearch(currentValue);
1304
- }
1305
- },
1306
- [currentValue, onSearch]
1307
- );
1308
- 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(
1309
- 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",
1310
1266
  {
1311
1267
  type: "search",
1312
1268
  value: currentValue,
1313
1269
  onChange: handleChange,
1314
1270
  onKeyDown: handleKeyDown,
1315
1271
  placeholder,
1316
- className: "pl-9 w-full"
1272
+ className: "rdb-input rdb-searchbar-input",
1273
+ style: { width: "100%" }
1317
1274
  }
1318
1275
  ));
1319
1276
  }
1320
1277
  SearchBar.propTypes = {
1321
- /** Nilai pencarian saat ini (controlled). */
1322
1278
  value: PropTypes8.string,
1323
- /** Callback saat nilai input berubah. */
1324
1279
  onChange: PropTypes8.func,
1325
- /** Callback saat pencarian disubmit. */
1326
1280
  onSearch: PropTypes8.func,
1327
- /** Placeholder teks. */
1328
1281
  placeholder: PropTypes8.string,
1329
- /** ClassName tambahan. */
1330
1282
  className: PropTypes8.string
1331
1283
  };
1332
1284
 
@@ -1336,26 +1288,11 @@ import PropTypes9 from "prop-types";
1336
1288
  var CURRENT_YEAR = (/* @__PURE__ */ new Date()).getFullYear();
1337
1289
  var MIN_YEAR2 = 2020;
1338
1290
  var MAX_YEAR = CURRENT_YEAR + 1;
1339
- var MONTHS = [
1340
- "Jan",
1341
- "Feb",
1342
- "Mar",
1343
- "Apr",
1344
- "May",
1345
- "Jun",
1346
- "Jul",
1347
- "Aug",
1348
- "Sep",
1349
- "Oct",
1350
- "Nov",
1351
- "Dec"
1352
- ];
1291
+ var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1353
1292
  function getYears() {
1354
- const years = [];
1355
- for (let y = MAX_YEAR; y >= MIN_YEAR2; y--) {
1356
- years.push(y);
1357
- }
1358
- return years;
1293
+ const y = [];
1294
+ for (let i = MAX_YEAR; i >= MIN_YEAR2; i--) y.push(i);
1295
+ return y;
1359
1296
  }
1360
1297
  function getDaysInMonth(year, month) {
1361
1298
  return new Date(year, month, 0).getDate();
@@ -1363,68 +1300,57 @@ function getDaysInMonth(year, month) {
1363
1300
  function parseDate(value) {
1364
1301
  if (value && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
1365
1302
  const [y, m, d] = value.split("-").map(Number);
1366
- if (y >= MIN_YEAR2 && y <= MAX_YEAR && m >= 1 && m <= 12) {
1303
+ if (y >= MIN_YEAR2 && y <= MAX_YEAR && m >= 1 && m <= 12)
1367
1304
  return { year: y, month: m, day: d };
1368
- }
1369
1305
  }
1370
1306
  const now = /* @__PURE__ */ new Date();
1371
1307
  return { year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate() };
1372
1308
  }
1373
- function formatDate2({ year, month, day }) {
1309
+ function fmt({ year, month, day }) {
1374
1310
  return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
1375
1311
  }
1376
1312
  function DatePicker({ value, onChange, disabled = false, label = "" }) {
1377
- const parsed = parseDate(value);
1313
+ const p = parseDate(value);
1378
1314
  const years = getYears();
1379
- const maxDay = getDaysInMonth(parsed.year, parsed.month);
1380
- const handleChange = useCallback3(
1381
- (field, newVal) => {
1382
- const updated = { ...parsed, [field]: Number(newVal) };
1383
- const maxD = getDaysInMonth(updated.year, updated.month);
1384
- if (updated.day > maxD) updated.day = maxD;
1385
- onChange(formatDate2(updated));
1386
- },
1387
- [parsed, onChange]
1388
- );
1389
- 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";
1390
- 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(
1391
1323
  "select",
1392
1324
  {
1393
- className: selectClass,
1394
- value: parsed.day,
1325
+ className: "rdb-select",
1326
+ style: { width: 52 },
1327
+ value: p.day,
1395
1328
  onChange: (e) => handleChange("day", e.target.value),
1396
- disabled,
1397
- "aria-label": `${label} day`
1329
+ disabled
1398
1330
  },
1399
1331
  Array.from({ length: maxDay }, (_, i) => i + 1).map((d) => /* @__PURE__ */ React9.createElement("option", { key: d, value: d }, String(d).padStart(2, "0")))
1400
1332
  ), /* @__PURE__ */ React9.createElement(
1401
1333
  "select",
1402
1334
  {
1403
- className: selectClass,
1404
- value: parsed.month,
1335
+ className: "rdb-select",
1336
+ style: { width: 62 },
1337
+ value: p.month,
1405
1338
  onChange: (e) => handleChange("month", e.target.value),
1406
- disabled,
1407
- "aria-label": `${label} month`
1339
+ disabled
1408
1340
  },
1409
1341
  MONTHS.map((name, idx) => /* @__PURE__ */ React9.createElement("option", { key: idx + 1, value: idx + 1 }, name))
1410
1342
  ), /* @__PURE__ */ React9.createElement(
1411
1343
  "select",
1412
1344
  {
1413
- className: selectClass,
1414
- value: parsed.year,
1345
+ className: "rdb-select",
1346
+ style: { width: 72 },
1347
+ value: p.year,
1415
1348
  onChange: (e) => handleChange("year", e.target.value),
1416
- disabled,
1417
- "aria-label": `${label} year`
1349
+ disabled
1418
1350
  },
1419
1351
  years.map((y) => /* @__PURE__ */ React9.createElement("option", { key: y, value: y }, y))
1420
1352
  )));
1421
1353
  }
1422
- DatePicker.propTypes = {
1423
- value: PropTypes9.string.isRequired,
1424
- onChange: PropTypes9.func.isRequired,
1425
- disabled: PropTypes9.bool,
1426
- label: PropTypes9.string
1427
- };
1428
1354
  function DateRangeFilter({
1429
1355
  dateFrom,
1430
1356
  dateTo,
@@ -1434,31 +1360,9 @@ function DateRangeFilter({
1434
1360
  labelTo = "To",
1435
1361
  className = ""
1436
1362
  }) {
1437
- const handleFromChange = useCallback3(
1438
- (newDate) => onRangeChange == null ? void 0 : onRangeChange({ dateFrom: newDate, dateTo }),
1439
- [dateTo, onRangeChange]
1440
- );
1441
- const handleToChange = useCallback3(
1442
- (newDate) => onRangeChange == null ? void 0 : onRangeChange({ dateFrom, dateTo: newDate }),
1443
- [dateFrom, onRangeChange]
1444
- );
1445
- return /* @__PURE__ */ React9.createElement("div", { className: `flex flex-wrap items-end gap-3 ${className}` }, /* @__PURE__ */ React9.createElement(
1446
- DatePicker,
1447
- {
1448
- value: dateFrom,
1449
- onChange: handleFromChange,
1450
- disabled,
1451
- label: labelFrom
1452
- }
1453
- ), /* @__PURE__ */ React9.createElement(
1454
- DatePicker,
1455
- {
1456
- value: dateTo,
1457
- onChange: handleToChange,
1458
- disabled,
1459
- label: labelTo
1460
- }
1461
- ));
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 }));
1462
1366
  }
1463
1367
  DateRangeFilter.propTypes = {
1464
1368
  dateFrom: PropTypes9.string.isRequired,
@@ -1473,22 +1377,13 @@ DateRangeFilter.propTypes = {
1473
1377
  // src/presentation/molecules/ChartHeader.jsx
1474
1378
  import React10 from "react";
1475
1379
  import PropTypes10 from "prop-types";
1476
- function ChartHeader({
1477
- title,
1478
- icon = "BarChart3",
1479
- actions = null,
1480
- className = ""
1481
- }) {
1482
- 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);
1483
1382
  }
1484
1383
  ChartHeader.propTypes = {
1485
- /** Judul grafik. */
1486
1384
  title: PropTypes10.string.isRequired,
1487
- /** Nama ikon header. */
1488
1385
  icon: PropTypes10.string,
1489
- /** Elemen aksi tambahan di sisi kanan. */
1490
1386
  actions: PropTypes10.node,
1491
- /** ClassName tambahan. */
1492
1387
  className: PropTypes10.string
1493
1388
  };
1494
1389
 
@@ -1513,28 +1408,24 @@ function DataTable({
1513
1408
  const [currentPage, setCurrentPage] = useState5(1);
1514
1409
  const filteredData = useMemo2(() => {
1515
1410
  if (!searchQuery.trim()) return data;
1516
- const query = searchQuery.toLowerCase();
1411
+ const q = searchQuery.toLowerCase();
1517
1412
  return data.filter(
1518
1413
  (row) => columns.some((col) => {
1519
- const value = row[col.accessor];
1520
- return value != null && String(value).toLowerCase().includes(query);
1414
+ const v = row[col.accessor];
1415
+ return v != null && String(v).toLowerCase().includes(q);
1521
1416
  })
1522
1417
  );
1523
1418
  }, [data, searchQuery, columns]);
1524
1419
  const sortedData = useMemo2(() => {
1525
1420
  if (!sortField) return filteredData;
1526
- const sorted = [...filteredData].sort((a, b) => {
1527
- const aVal = a[sortField] ?? "";
1528
- const bVal = b[sortField] ?? "";
1529
- if (typeof aVal === "number" && typeof bVal === "number") {
1530
- return sortDirection === "asc" ? aVal - bVal : bVal - aVal;
1531
- }
1532
- const comparison = String(aVal).localeCompare(String(bVal), void 0, {
1533
- numeric: true
1534
- });
1535
- 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;
1536
1428
  });
1537
- return sorted;
1538
1429
  }, [filteredData, sortField, sortDirection]);
1539
1430
  const totalPages = Math.max(1, Math.ceil(sortedData.length / pageSize));
1540
1431
  const safePage = Math.min(currentPage, totalPages);
@@ -1542,90 +1433,29 @@ function DataTable({
1542
1433
  const start = (safePage - 1) * pageSize;
1543
1434
  return sortedData.slice(start, start + pageSize);
1544
1435
  }, [sortedData, safePage, pageSize]);
1545
- const handleSearch = useCallback4((value) => {
1546
- setSearchQuery(value);
1436
+ const handleSearch = useCallback4((v) => {
1437
+ setSearchQuery(v);
1547
1438
  setCurrentPage(1);
1548
1439
  }, []);
1549
- const handleSort = useCallback4(
1550
- (accessor) => {
1551
- if (!sortable) return;
1552
- if (sortField === accessor) {
1553
- setSortDirection((prev) => prev === "asc" ? "desc" : "asc");
1554
- } else {
1555
- setSortField(accessor);
1556
- setSortDirection("asc");
1557
- }
1558
- setCurrentPage(1);
1559
- },
1560
- [sortable, sortField]
1561
- );
1562
- const handlePrevPage = useCallback4(() => {
1563
- setCurrentPage((prev) => Math.max(1, prev - 1));
1564
- }, []);
1565
- const handleNextPage = useCallback4(() => {
1566
- setCurrentPage((prev) => Math.min(totalPages, prev + 1));
1567
- }, [totalPages]);
1568
- return /* @__PURE__ */ React11.createElement("div", { className: `space-y-3 ${className}` }, searchable ? /* @__PURE__ */ React11.createElement(
1569
- SearchBar,
1570
- {
1571
- value: searchQuery,
1572
- onSearch: handleSearch,
1573
- placeholder: searchPlaceholder,
1574
- 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");
1575
1446
  }
1576
- ) : 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(
1577
- "th",
1578
- {
1579
- key: column.id,
1580
- className: [
1581
- "p-3",
1582
- sortable ? "cursor-pointer select-none hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors" : ""
1583
- ].join(" "),
1584
- onClick: () => handleSort(column.accessor)
1585
- },
1586
- /* @__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(
1587
- Icon,
1588
- {
1589
- name: sortDirection === "asc" ? "ArrowUp" : "ArrowDown",
1590
- size: 14,
1591
- className: "text-blue-500"
1592
- }
1593
- ) : null)
1594
- )))), /* @__PURE__ */ React11.createElement("tbody", null, paginatedData.map((row) => /* @__PURE__ */ React11.createElement(
1595
- "tr",
1596
- {
1597
- key: row.id,
1598
- className: "border-b border-slate-200 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800/50"
1599
- },
1600
- columns.map((column) => /* @__PURE__ */ React11.createElement("td", { key: `${row.id}-${column.id}`, className: "p-3" }, renderCell(column, row, dateLocale)))
1601
- )), paginatedData.length === 0 ? /* @__PURE__ */ React11.createElement("tr", null, /* @__PURE__ */ React11.createElement(
1602
- "td",
1603
- {
1604
- className: "p-6 text-center text-slate-500",
1605
- colSpan: columns.length
1606
- },
1607
- emptyLabel
1608
- )) : 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(
1609
- "button",
1610
- {
1611
- type: "button",
1612
- className: "p-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 disabled:opacity-40 disabled:cursor-not-allowed transition-colors",
1613
- onClick: handlePrevPage,
1614
- disabled: safePage <= 1,
1615
- "aria-label": "Previous page"
1616
- },
1617
- /* @__PURE__ */ React11.createElement(Icon, { name: "ChevronLeft", size: 16 })
1618
- ), /* @__PURE__ */ React11.createElement("span", { className: "text-xs text-slate-600 dark:text-slate-300 min-w-[60px] text-center" }, safePage, " / ", totalPages), /* @__PURE__ */ React11.createElement(
1619
- "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,
1620
1453
  {
1621
- type: "button",
1622
- className: "p-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 disabled:opacity-40 disabled:cursor-not-allowed transition-colors",
1623
- onClick: handleNextPage,
1624
- disabled: safePage >= totalPages,
1625
- "aria-label": "Next page"
1626
- },
1627
- /* @__PURE__ */ React11.createElement(Icon, { name: "ChevronRight", size: 16 })
1628
- ))) : 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);
1629
1459
  }
1630
1460
  function renderCell(column, row, dateLocale) {
1631
1461
  const value = row[column.accessor];
@@ -1638,33 +1468,21 @@ function renderCell(column, row, dateLocale) {
1638
1468
  return value || "-";
1639
1469
  }
1640
1470
  DataTable.propTypes = {
1641
- /** Definisi kolom tabel. */
1642
- columns: PropTypes11.arrayOf(
1643
- PropTypes11.shape({
1644
- id: PropTypes11.string.isRequired,
1645
- label: PropTypes11.string.isRequired,
1646
- accessor: PropTypes11.string.isRequired,
1647
- type: PropTypes11.oneOf(["date", "currency", "statusBadge"]),
1648
- statusAccessor: PropTypes11.string
1649
- })
1650
- ).isRequired,
1651
- /** 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,
1652
1478
  data: PropTypes11.arrayOf(PropTypes11.object),
1653
- /** Objek label i18n. */
1654
1479
  labels: PropTypes11.object,
1655
- /** Locale untuk format tanggal. */
1656
1480
  dateLocale: PropTypes11.string,
1657
- /** Apakah tabel mendukung pencarian. */
1658
1481
  searchable: PropTypes11.bool,
1659
- /** Apakah tabel mendukung pengurutan. */
1660
1482
  sortable: PropTypes11.bool,
1661
- /** Jumlah baris per halaman. */
1662
1483
  pageSize: PropTypes11.number,
1663
- /** Label saat data kosong. */
1664
1484
  emptyLabel: PropTypes11.string,
1665
- /** Placeholder pencarian. */
1666
1485
  searchPlaceholder: PropTypes11.string,
1667
- /** ClassName tambahan. */
1668
1486
  className: PropTypes11.string
1669
1487
  };
1670
1488
 
@@ -1832,44 +1650,38 @@ var AUDIENCE_OPTIONS = ["", "domestic", "foreign"];
1832
1650
  var DAY_PRESETS = [7, 30, 90, 0];
1833
1651
  var SORT_BY_OPTIONS = ["bookings", "revenue"];
1834
1652
  var SORT_DIR_OPTIONS = ["desc", "asc"];
1835
- function FilterPanel({
1836
- filters,
1837
- labels,
1838
- onFilterChange,
1839
- onResetFilters,
1840
- className = ""
1841
- }) {
1842
- 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(
1843
1655
  "select",
1844
1656
  {
1845
- 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",
1846
1658
  value: filters.statusScope,
1847
- onChange: (event) => onFilterChange("statusScope", event.target.value)
1659
+ onChange: (e) => onFilterChange("statusScope", e.target.value)
1848
1660
  },
1849
- STATUS_OPTIONS.map((status) => /* @__PURE__ */ React13.createElement("option", { key: status, value: status }, status === "confirmed" ? labels.confirmedOnly : status === "pending" ? labels.pendingOnly : labels.allStatus))
1850
- ), /* @__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(
1851
- 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",
1852
1664
  {
1853
1665
  type: "checkbox",
1854
1666
  checked: filters.includePendingOverlay,
1855
- onChange: (event) => onFilterChange("includePendingOverlay", event.target.checked)
1667
+ onChange: (e) => onFilterChange("includePendingOverlay", e.target.checked)
1856
1668
  }
1857
- ), /* @__PURE__ */ React13.createElement(Typography, { variant: "body" }, labels.showPendingOverlay)), /* @__PURE__ */ React13.createElement(
1669
+ ), /* @__PURE__ */ React13.createElement("span", { className: "rdb-body" }, labels.showPendingOverlay)), /* @__PURE__ */ React13.createElement(
1858
1670
  "select",
1859
1671
  {
1860
- 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",
1861
1673
  value: filters.audience,
1862
- onChange: (event) => onFilterChange("audience", event.target.value)
1674
+ onChange: (e) => onFilterChange("audience", e.target.value)
1863
1675
  },
1864
- 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))
1865
1677
  ), /* @__PURE__ */ React13.createElement(
1866
1678
  "select",
1867
1679
  {
1868
- 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",
1869
1681
  value: String(filters.daysPreset),
1870
- onChange: (event) => onFilterChange("daysPreset", Number(event.target.value))
1682
+ onChange: (e) => onFilterChange("daysPreset", Number(e.target.value))
1871
1683
  },
1872
- 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)))
1873
1685
  ), /* @__PURE__ */ React13.createElement(
1874
1686
  DateRangeFilter,
1875
1687
  {
@@ -1880,160 +1692,95 @@ function FilterPanel({
1880
1692
  if (dateTo !== filters.dateTo) onFilterChange("dateTo", dateTo);
1881
1693
  },
1882
1694
  disabled: filters.daysPreset !== 0,
1883
- className: "lg:col-span-2"
1695
+ className: "rdb-filter-date"
1884
1696
  }
1885
- )), /* @__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(
1886
1698
  "select",
1887
1699
  {
1888
- 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" },
1889
1702
  value: filters.sortPkgBy,
1890
- onChange: (event) => onFilterChange("sortPkgBy", event.target.value)
1703
+ onChange: (e) => onFilterChange("sortPkgBy", e.target.value)
1891
1704
  },
1892
- 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))
1893
1706
  ), /* @__PURE__ */ React13.createElement(
1894
1707
  "select",
1895
1708
  {
1896
- 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" },
1897
1711
  value: filters.sortPkgDir,
1898
- onChange: (event) => onFilterChange("sortPkgDir", event.target.value)
1712
+ onChange: (e) => onFilterChange("sortPkgDir", e.target.value)
1899
1713
  },
1900
- 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))
1901
1715
  ))));
1902
1716
  }
1903
1717
  FilterPanel.propTypes = {
1904
- /** State filter saat ini. */
1905
- filters: PropTypes13.shape({
1906
- statusScope: PropTypes13.string,
1907
- includePendingOverlay: PropTypes13.bool,
1908
- audience: PropTypes13.string,
1909
- daysPreset: PropTypes13.number,
1910
- dateFrom: PropTypes13.string,
1911
- dateTo: PropTypes13.string,
1912
- sortPkgBy: PropTypes13.string,
1913
- sortPkgDir: PropTypes13.string
1914
- }).isRequired,
1915
- /** Objek label i18n. */
1718
+ filters: PropTypes13.object.isRequired,
1916
1719
  labels: PropTypes13.object.isRequired,
1917
- /** Callback saat filter berubah (field, value). */
1918
1720
  onFilterChange: PropTypes13.func.isRequired,
1919
- /** Callback saat filter di-reset. */
1920
1721
  onResetFilters: PropTypes13.func.isRequired,
1921
- /** ClassName tambahan. */
1922
1722
  className: PropTypes13.string
1923
1723
  };
1924
1724
 
1925
1725
  // src/presentation/organisms/SidebarNavigation.jsx
1926
1726
  import React14 from "react";
1927
1727
  import PropTypes14 from "prop-types";
1928
- function SidebarNavigation({
1929
- items = [],
1930
- activeItem = "",
1931
- onItemClick,
1932
- logo = null,
1933
- className = ""
1934
- }) {
1935
- return /* @__PURE__ */ React14.createElement(
1936
- "aside",
1937
- {
1938
- 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}`
1939
- },
1940
- logo ? /* @__PURE__ */ React14.createElement("div", { className: "px-4 py-4 border-b border-slate-100 dark:border-slate-800" }, logo) : null,
1941
- /* @__PURE__ */ React14.createElement("nav", { className: "flex-1 px-2 py-3 space-y-0.5", "aria-label": "Sidebar navigation" }, items.map((item) => {
1942
- const isActive = item.id === activeItem;
1943
- return /* @__PURE__ */ React14.createElement(
1944
- "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,
1945
1742
  {
1946
- key: item.id,
1947
- type: "button",
1948
- id: `sidebar-nav-${item.id}`,
1949
- onClick: () => onItemClick == null ? void 0 : onItemClick(item.id),
1950
- "aria-current": isActive ? "page" : void 0,
1951
- className: [
1952
- "w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-colors text-left",
1953
- 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"
1954
- ].join(" ")
1955
- },
1956
- item.icon ? /* @__PURE__ */ React14.createElement(
1957
- Icon,
1958
- {
1959
- name: item.icon,
1960
- size: 18,
1961
- className: isActive ? "text-blue-600 dark:text-blue-400" : "text-slate-400 dark:text-slate-500"
1962
- }
1963
- ) : null,
1964
- /* @__PURE__ */ React14.createElement("span", null, item.label),
1965
- isActive ? /* @__PURE__ */ React14.createElement("span", { className: "ml-auto w-1.5 h-1.5 rounded-full bg-blue-500" }) : null
1966
- );
1967
- }))
1968
- );
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
+ })));
1969
1752
  }
1970
1753
  SidebarNavigation.propTypes = {
1971
- /** Daftar item menu navigasi. */
1972
- items: PropTypes14.arrayOf(
1973
- PropTypes14.shape({
1974
- id: PropTypes14.string.isRequired,
1975
- label: PropTypes14.string.isRequired,
1976
- icon: PropTypes14.string
1977
- })
1978
- ),
1979
- /** ID item yang sedang aktif. */
1754
+ items: PropTypes14.arrayOf(PropTypes14.shape({ id: PropTypes14.string.isRequired, label: PropTypes14.string.isRequired, icon: PropTypes14.string })),
1980
1755
  activeItem: PropTypes14.string,
1981
- /** Callback saat item diklik, menerima id item sebagai argumen. */
1982
1756
  onItemClick: PropTypes14.func,
1983
- /** Konten logo/brand di atas sidebar. */
1984
1757
  logo: PropTypes14.node,
1985
- /** ClassName tambahan. */
1986
1758
  className: PropTypes14.string
1987
1759
  };
1988
1760
 
1989
1761
  // src/presentation/organisms/TopbarHeader.jsx
1990
1762
  import React15 from "react";
1991
1763
  import PropTypes15 from "prop-types";
1992
- function TopbarHeader({
1993
- title = "",
1994
- actions = null,
1995
- leading = null,
1996
- className = ""
1997
- }) {
1998
- return /* @__PURE__ */ React15.createElement(
1999
- "header",
2000
- {
2001
- 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}`
2002
- },
2003
- /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-3" }, leading, title ? /* @__PURE__ */ React15.createElement(Typography, { variant: "h2", className: "truncate" }, title) : null),
2004
- actions ? /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, actions) : null
2005
- );
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);
2006
1766
  }
2007
1767
  TopbarHeader.propTypes = {
2008
- /** Judul halaman yang ditampilkan. */
2009
1768
  title: PropTypes15.string,
2010
- /** Elemen aksi di sisi kanan (tombol, avatar, dsb.). */
2011
1769
  actions: PropTypes15.node,
2012
- /** Elemen di sisi kiri sebelum judul (misal: hamburger menu). */
2013
1770
  leading: PropTypes15.node,
2014
- /** ClassName tambahan. */
2015
1771
  className: PropTypes15.string
2016
1772
  };
2017
1773
 
2018
1774
  // src/presentation/templates/DashboardLayout.jsx
2019
1775
  import React16 from "react";
2020
1776
  import PropTypes16 from "prop-types";
2021
- function DashboardLayout({
2022
- header = null,
2023
- sidebar = null,
2024
- children,
2025
- className = ""
2026
- }) {
2027
- 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)));
2028
1779
  }
2029
1780
  DashboardLayout.propTypes = {
2030
- /** Konten header atas. */
2031
1781
  header: PropTypes16.node,
2032
- /** Konten sidebar. */
2033
1782
  sidebar: PropTypes16.node,
2034
- /** Konten utama dashboard. */
2035
1783
  children: PropTypes16.node.isRequired,
2036
- /** ClassName tambahan. */
2037
1784
  className: PropTypes16.string
2038
1785
  };
2039
1786
 
@@ -2053,16 +1800,7 @@ function ReusableDashboardView({
2053
1800
  dateLocale,
2054
1801
  liveUpdateEnabled
2055
1802
  }) {
2056
- 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(
2057
- Button,
2058
- {
2059
- variant: "secondary",
2060
- size: "sm",
2061
- onClick: () => onRefresh(),
2062
- title: labels.refresh
2063
- },
2064
- /* @__PURE__ */ React17.createElement(Icon, { name: "RotateCcw", size: 16 })
2065
- )), 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(
2066
1804
  FilterPanel,
2067
1805
  {
2068
1806
  filters,
@@ -2070,16 +1808,17 @@ function ReusableDashboardView({
2070
1808
  onFilterChange,
2071
1809
  onResetFilters
2072
1810
  }
2073
- )))), /* @__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(
2074
1812
  StatCard,
2075
1813
  {
2076
1814
  key: widget.id,
2077
1815
  label: labels[widget.label] || widget.label,
2078
1816
  value: data.stats[widget.valueKey],
2079
1817
  icon: widget.icon,
2080
- format: widget.format
1818
+ format: widget.format,
1819
+ accentColor: widget.accentColor
2081
1820
  }
2082
- ))), /* @__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(
2083
1822
  ChartCard,
2084
1823
  {
2085
1824
  key: widget.id,
@@ -2089,7 +1828,15 @@ function ReusableDashboardView({
2089
1828
  filters,
2090
1829
  chartData: data.charts
2091
1830
  }
2092
- ))), /* @__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(
2093
1840
  DataTable,
2094
1841
  {
2095
1842
  columns: config.widgets.table.columns,
@@ -2101,30 +1848,19 @@ function ReusableDashboardView({
2101
1848
  sortable: true,
2102
1849
  pageSize: 10
2103
1850
  }
2104
- )));
1851
+ ))));
2105
1852
  }
2106
1853
  ReusableDashboardView.propTypes = {
2107
- /** Konfigurasi widget dashboard. */
2108
1854
  config: PropTypes17.object.isRequired,
2109
- /** Objek label i18n. */
2110
1855
  labels: PropTypes17.object.isRequired,
2111
- /** Status loading data. */
2112
1856
  loading: PropTypes17.bool,
2113
- /** Pesan error jika ada. */
2114
1857
  error: PropTypes17.string,
2115
- /** State filter dashboard. */
2116
1858
  filters: PropTypes17.object,
2117
- /** Callback perubahan filter. */
2118
1859
  onFilterChange: PropTypes17.func.isRequired,
2119
- /** Callback reset filter. */
2120
1860
  onResetFilters: PropTypes17.func.isRequired,
2121
- /** Callback refresh data. */
2122
1861
  onRefresh: PropTypes17.func.isRequired,
2123
- /** Data dashboard. */
2124
1862
  data: PropTypes17.object,
2125
- /** Locale untuk format tanggal. */
2126
1863
  dateLocale: PropTypes17.string,
2127
- /** Apakah live update aktif. */
2128
1864
  liveUpdateEnabled: PropTypes17.bool
2129
1865
  };
2130
1866