@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/README.md +64 -473
- package/dist/index.cjs +200 -479
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +742 -0
- package/dist/index.css.map +1 -0
- package/dist/index.js +200 -479
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
package/dist/index.js
CHANGED
|
@@ -1027,42 +1027,20 @@ function Button({
|
|
|
1027
1027
|
children,
|
|
1028
1028
|
...rest
|
|
1029
1029
|
}) {
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
1174
|
-
h1:
|
|
1175
|
-
h2:
|
|
1176
|
-
h3:
|
|
1177
|
-
subheading:
|
|
1178
|
-
body:
|
|
1179
|
-
caption:
|
|
1180
|
-
metric:
|
|
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
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
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
|
|
1206
|
-
confirmed: "
|
|
1207
|
-
pending: "
|
|
1208
|
-
cancelled: "
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
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
|
|
1215
|
-
|
|
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:
|
|
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
|
-
|
|
1242
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1228
|
+
style: { color: trend === "up" ? "#10b981" : "#ef4444" }
|
|
1273
1229
|
}
|
|
1274
|
-
) : null), /* @__PURE__ */ React7.createElement(
|
|
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
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
);
|
|
1314
|
-
|
|
1315
|
-
|
|
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: "
|
|
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
|
|
1369
|
-
for (let
|
|
1370
|
-
|
|
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
|
|
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
|
|
1313
|
+
const p = parseDate(value);
|
|
1392
1314
|
const years = getYears();
|
|
1393
|
-
const maxDay = getDaysInMonth(
|
|
1394
|
-
const handleChange = useCallback3(
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
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:
|
|
1408
|
-
|
|
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:
|
|
1418
|
-
|
|
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:
|
|
1428
|
-
|
|
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
|
|
1452
|
-
|
|
1453
|
-
|
|
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
|
|
1411
|
+
const q = searchQuery.toLowerCase();
|
|
1531
1412
|
return data.filter(
|
|
1532
1413
|
(row) => columns.some((col) => {
|
|
1533
|
-
const
|
|
1534
|
-
return
|
|
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
|
-
|
|
1541
|
-
const
|
|
1542
|
-
const
|
|
1543
|
-
if (typeof
|
|
1544
|
-
return sortDirection === "asc" ?
|
|
1545
|
-
}
|
|
1546
|
-
|
|
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((
|
|
1560
|
-
setSearchQuery(
|
|
1436
|
+
const handleSearch = useCallback4((v) => {
|
|
1437
|
+
setSearchQuery(v);
|
|
1561
1438
|
setCurrentPage(1);
|
|
1562
1439
|
}, []);
|
|
1563
|
-
const handleSort = useCallback4(
|
|
1564
|
-
(
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
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
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
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
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
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
|
-
|
|
1656
|
-
|
|
1657
|
-
PropTypes11.
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
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
|
-
|
|
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: "
|
|
1657
|
+
className: "rdb-select",
|
|
1860
1658
|
value: filters.statusScope,
|
|
1861
|
-
onChange: (
|
|
1659
|
+
onChange: (e) => onFilterChange("statusScope", e.target.value)
|
|
1862
1660
|
},
|
|
1863
|
-
STATUS_OPTIONS.map((
|
|
1864
|
-
), /* @__PURE__ */ React13.createElement("label", { className: "
|
|
1865
|
-
|
|
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: (
|
|
1667
|
+
onChange: (e) => onFilterChange("includePendingOverlay", e.target.checked)
|
|
1870
1668
|
}
|
|
1871
|
-
), /* @__PURE__ */ React13.createElement(
|
|
1669
|
+
), /* @__PURE__ */ React13.createElement("span", { className: "rdb-body" }, labels.showPendingOverlay)), /* @__PURE__ */ React13.createElement(
|
|
1872
1670
|
"select",
|
|
1873
1671
|
{
|
|
1874
|
-
className: "
|
|
1672
|
+
className: "rdb-select",
|
|
1875
1673
|
value: filters.audience,
|
|
1876
|
-
onChange: (
|
|
1674
|
+
onChange: (e) => onFilterChange("audience", e.target.value)
|
|
1877
1675
|
},
|
|
1878
|
-
AUDIENCE_OPTIONS.map((
|
|
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: "
|
|
1680
|
+
className: "rdb-select",
|
|
1883
1681
|
value: String(filters.daysPreset),
|
|
1884
|
-
onChange: (
|
|
1682
|
+
onChange: (e) => onFilterChange("daysPreset", Number(e.target.value))
|
|
1885
1683
|
},
|
|
1886
|
-
DAY_PRESETS.map((
|
|
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: "
|
|
1695
|
+
className: "rdb-filter-date"
|
|
1898
1696
|
}
|
|
1899
|
-
)), /* @__PURE__ */ React13.createElement("div", { className: "
|
|
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: "
|
|
1700
|
+
className: "rdb-select",
|
|
1701
|
+
style: { width: "auto" },
|
|
1903
1702
|
value: filters.sortPkgBy,
|
|
1904
|
-
onChange: (
|
|
1703
|
+
onChange: (e) => onFilterChange("sortPkgBy", e.target.value)
|
|
1905
1704
|
},
|
|
1906
|
-
SORT_BY_OPTIONS.map((
|
|
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: "
|
|
1709
|
+
className: "rdb-select",
|
|
1710
|
+
style: { width: "auto" },
|
|
1911
1711
|
value: filters.sortPkgDir,
|
|
1912
|
-
onChange: (
|
|
1712
|
+
onChange: (e) => onFilterChange("sortPkgDir", e.target.value)
|
|
1913
1713
|
},
|
|
1914
|
-
SORT_DIR_OPTIONS.map((
|
|
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
|
-
|
|
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
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
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
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
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
|
-
|
|
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
|
|
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: "
|
|
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
|
-
))
|
|
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: "
|
|
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
|
|
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
|
|