@rozaqi02/reusable-dashboard 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -473
- package/dist/index.cjs +220 -484
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +742 -0
- package/dist/index.css.map +1 -0
- package/dist/index.js +220 -484
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
package/dist/index.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
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
1166
|
-
h1:
|
|
1167
|
-
h2:
|
|
1168
|
-
h3:
|
|
1169
|
-
subheading:
|
|
1170
|
-
body:
|
|
1171
|
-
caption:
|
|
1172
|
-
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"
|
|
1173
1145
|
};
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
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
|
|
1198
|
-
confirmed: "
|
|
1199
|
-
pending: "
|
|
1200
|
-
cancelled: "
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
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
|
|
1207
|
-
|
|
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:
|
|
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
|
-
|
|
1234
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1228
|
+
style: { color: trend === "up" ? "#10b981" : "#ef4444" }
|
|
1255
1229
|
}
|
|
1256
|
-
) : null), /* @__PURE__ */ React7.createElement(
|
|
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
|
-
|
|
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
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
);
|
|
1300
|
-
|
|
1301
|
-
|
|
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: "
|
|
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
|
|
1355
|
-
for (let
|
|
1356
|
-
|
|
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
|
|
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
|
|
1313
|
+
const p = parseDate(value);
|
|
1378
1314
|
const years = getYears();
|
|
1379
|
-
const maxDay = getDaysInMonth(
|
|
1380
|
-
const handleChange = useCallback3(
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
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:
|
|
1394
|
-
|
|
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:
|
|
1404
|
-
|
|
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:
|
|
1414
|
-
|
|
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
|
|
1438
|
-
|
|
1439
|
-
|
|
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
|
|
1411
|
+
const q = searchQuery.toLowerCase();
|
|
1517
1412
|
return data.filter(
|
|
1518
1413
|
(row) => columns.some((col) => {
|
|
1519
|
-
const
|
|
1520
|
-
return
|
|
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
|
-
|
|
1527
|
-
const
|
|
1528
|
-
const
|
|
1529
|
-
if (typeof
|
|
1530
|
-
return sortDirection === "asc" ?
|
|
1531
|
-
}
|
|
1532
|
-
|
|
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((
|
|
1546
|
-
setSearchQuery(
|
|
1436
|
+
const handleSearch = useCallback4((v) => {
|
|
1437
|
+
setSearchQuery(v);
|
|
1547
1438
|
setCurrentPage(1);
|
|
1548
1439
|
}, []);
|
|
1549
|
-
const handleSort = useCallback4(
|
|
1550
|
-
(
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
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
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
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
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
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
|
-
|
|
1642
|
-
|
|
1643
|
-
PropTypes11.
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
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
|
-
|
|
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: "
|
|
1657
|
+
className: "rdb-select",
|
|
1846
1658
|
value: filters.statusScope,
|
|
1847
|
-
onChange: (
|
|
1659
|
+
onChange: (e) => onFilterChange("statusScope", e.target.value)
|
|
1848
1660
|
},
|
|
1849
|
-
STATUS_OPTIONS.map((
|
|
1850
|
-
), /* @__PURE__ */ React13.createElement("label", { className: "
|
|
1851
|
-
|
|
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: (
|
|
1667
|
+
onChange: (e) => onFilterChange("includePendingOverlay", e.target.checked)
|
|
1856
1668
|
}
|
|
1857
|
-
), /* @__PURE__ */ React13.createElement(
|
|
1669
|
+
), /* @__PURE__ */ React13.createElement("span", { className: "rdb-body" }, labels.showPendingOverlay)), /* @__PURE__ */ React13.createElement(
|
|
1858
1670
|
"select",
|
|
1859
1671
|
{
|
|
1860
|
-
className: "
|
|
1672
|
+
className: "rdb-select",
|
|
1861
1673
|
value: filters.audience,
|
|
1862
|
-
onChange: (
|
|
1674
|
+
onChange: (e) => onFilterChange("audience", e.target.value)
|
|
1863
1675
|
},
|
|
1864
|
-
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))
|
|
1865
1677
|
), /* @__PURE__ */ React13.createElement(
|
|
1866
1678
|
"select",
|
|
1867
1679
|
{
|
|
1868
|
-
className: "
|
|
1680
|
+
className: "rdb-select",
|
|
1869
1681
|
value: String(filters.daysPreset),
|
|
1870
|
-
onChange: (
|
|
1682
|
+
onChange: (e) => onFilterChange("daysPreset", Number(e.target.value))
|
|
1871
1683
|
},
|
|
1872
|
-
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)))
|
|
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: "
|
|
1695
|
+
className: "rdb-filter-date"
|
|
1884
1696
|
}
|
|
1885
|
-
)), /* @__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(
|
|
1886
1698
|
"select",
|
|
1887
1699
|
{
|
|
1888
|
-
className: "
|
|
1700
|
+
className: "rdb-select",
|
|
1701
|
+
style: { width: "auto" },
|
|
1889
1702
|
value: filters.sortPkgBy,
|
|
1890
|
-
onChange: (
|
|
1703
|
+
onChange: (e) => onFilterChange("sortPkgBy", e.target.value)
|
|
1891
1704
|
},
|
|
1892
|
-
SORT_BY_OPTIONS.map((
|
|
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: "
|
|
1709
|
+
className: "rdb-select",
|
|
1710
|
+
style: { width: "auto" },
|
|
1897
1711
|
value: filters.sortPkgDir,
|
|
1898
|
-
onChange: (
|
|
1712
|
+
onChange: (e) => onFilterChange("sortPkgDir", e.target.value)
|
|
1899
1713
|
},
|
|
1900
|
-
SORT_DIR_OPTIONS.map((
|
|
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
|
-
|
|
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
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
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
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
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
|
-
|
|
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
|
|
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: "
|
|
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
|
-
))
|
|
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: "
|
|
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
|
|
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
|
|