@agilo/medusa-analytics-plugin 1.1.0 → 1.2.0
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.
|
@@ -972,23 +972,23 @@ const PieChart = ({ data, dataKey }) => {
|
|
|
972
972
|
)
|
|
973
973
|
] }) });
|
|
974
974
|
};
|
|
975
|
-
const columnHelper$
|
|
976
|
-
const columns$
|
|
977
|
-
columnHelper$
|
|
975
|
+
const columnHelper$3 = ui.createDataTableColumnHelper();
|
|
976
|
+
const columns$2 = [
|
|
977
|
+
columnHelper$3.accessor("sku", {
|
|
978
978
|
header: "SKU",
|
|
979
979
|
enableSorting: true,
|
|
980
980
|
sortLabel: "SKU",
|
|
981
981
|
sortAscLabel: "A-Z",
|
|
982
982
|
sortDescLabel: "Z-A"
|
|
983
983
|
}),
|
|
984
|
-
columnHelper$
|
|
984
|
+
columnHelper$3.accessor("variantName", {
|
|
985
985
|
header: "Variant Name",
|
|
986
986
|
enableSorting: true,
|
|
987
987
|
sortLabel: "Variant Name",
|
|
988
988
|
sortAscLabel: "A-Z",
|
|
989
989
|
sortDescLabel: "Z-A"
|
|
990
990
|
}),
|
|
991
|
-
columnHelper$
|
|
991
|
+
columnHelper$3.accessor("inventoryQuantity", {
|
|
992
992
|
header: "Inventory",
|
|
993
993
|
enableSorting: true,
|
|
994
994
|
sortLabel: "Inventory",
|
|
@@ -1000,10 +1000,10 @@ const columns$1 = [
|
|
|
1000
1000
|
}
|
|
1001
1001
|
})
|
|
1002
1002
|
];
|
|
1003
|
-
const PAGE_SIZE = 10;
|
|
1003
|
+
const PAGE_SIZE$1 = 10;
|
|
1004
1004
|
const ProductsTable = ({ products }) => {
|
|
1005
1005
|
const [pagination, setPagination] = React__namespace.useState({
|
|
1006
|
-
pageSize: PAGE_SIZE,
|
|
1006
|
+
pageSize: PAGE_SIZE$1,
|
|
1007
1007
|
pageIndex: 0
|
|
1008
1008
|
});
|
|
1009
1009
|
const [search, setSearch] = React__namespace.useState("");
|
|
@@ -1034,7 +1034,7 @@ const ProductsTable = ({ products }) => {
|
|
|
1034
1034
|
);
|
|
1035
1035
|
}, [products, search, sorting, pagination]);
|
|
1036
1036
|
const table = ui.useDataTable({
|
|
1037
|
-
columns: columns$
|
|
1037
|
+
columns: columns$2,
|
|
1038
1038
|
data: shownProducts,
|
|
1039
1039
|
getRowId: (product) => product.sku,
|
|
1040
1040
|
rowCount: products.length,
|
|
@@ -1069,10 +1069,10 @@ const ProductsTable = ({ products }) => {
|
|
|
1069
1069
|
}
|
|
1070
1070
|
}
|
|
1071
1071
|
),
|
|
1072
|
-
products.length > PAGE_SIZE && /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Pagination, {})
|
|
1072
|
+
products.length > PAGE_SIZE$1 && /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Pagination, {})
|
|
1073
1073
|
] });
|
|
1074
1074
|
};
|
|
1075
|
-
const BACKEND_URL$
|
|
1075
|
+
const BACKEND_URL$2 = __BACKEND_URL__ === "/" ? "" : __BACKEND_URL__;
|
|
1076
1076
|
async function retrieveProductAnalytics(date) {
|
|
1077
1077
|
if (!date || !date.from || !(date == null ? void 0 : date.to)) {
|
|
1078
1078
|
return void 0;
|
|
@@ -1080,7 +1080,7 @@ async function retrieveProductAnalytics(date) {
|
|
|
1080
1080
|
const dateFrom = dateFns.format(date.from, "yyyy-MM-dd");
|
|
1081
1081
|
const dateTo = dateFns.format(date.to, "yyyy-MM-dd");
|
|
1082
1082
|
const data = await fetch(
|
|
1083
|
-
`${BACKEND_URL$
|
|
1083
|
+
`${BACKEND_URL$2}/admin/agilo-analytics/products?date_from=${dateFrom}&date_to=${dateTo}`
|
|
1084
1084
|
);
|
|
1085
1085
|
const productAnalytics = await data.json();
|
|
1086
1086
|
return productAnalytics;
|
|
@@ -1095,11 +1095,11 @@ const useProductAnalytics = (query, options) => {
|
|
|
1095
1095
|
...options
|
|
1096
1096
|
});
|
|
1097
1097
|
};
|
|
1098
|
-
const BACKEND_URL = __BACKEND_URL__ === "/" ? "" : __BACKEND_URL__;
|
|
1098
|
+
const BACKEND_URL$1 = __BACKEND_URL__ === "/" ? "" : __BACKEND_URL__;
|
|
1099
1099
|
async function retrieveOrderAnalytics(preset, date) {
|
|
1100
1100
|
if (!date || !date.from || !(date == null ? void 0 : date.to)) {
|
|
1101
1101
|
const data2 = await fetch(
|
|
1102
|
-
`${BACKEND_URL}/admin/agilo-analytics/orders?preset=${preset}`
|
|
1102
|
+
`${BACKEND_URL$1}/admin/agilo-analytics/orders?preset=${preset}`
|
|
1103
1103
|
);
|
|
1104
1104
|
const orderAnalytics2 = await data2.json();
|
|
1105
1105
|
return orderAnalytics2;
|
|
@@ -1107,7 +1107,7 @@ async function retrieveOrderAnalytics(preset, date) {
|
|
|
1107
1107
|
const dateFrom = dateFns.format(date.from, "yyyy-MM-dd");
|
|
1108
1108
|
const dateTo = dateFns.format(date.to, "yyyy-MM-dd");
|
|
1109
1109
|
const data = await fetch(
|
|
1110
|
-
`${BACKEND_URL}/admin/agilo-analytics/orders?date_from=${dateFrom}&date_to=${dateTo}&preset=${preset}`
|
|
1110
|
+
`${BACKEND_URL$1}/admin/agilo-analytics/orders?date_from=${dateFrom}&date_to=${dateTo}&preset=${preset}`
|
|
1111
1111
|
);
|
|
1112
1112
|
const orderAnalytics = await data.json();
|
|
1113
1113
|
return orderAnalytics;
|
|
@@ -1148,7 +1148,7 @@ const BarChartSkeleton = () => {
|
|
|
1148
1148
|
const PieChartSkeleton = () => {
|
|
1149
1149
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full aspect-square max-h-[400px]", children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "w-full h-full" }) });
|
|
1150
1150
|
};
|
|
1151
|
-
const dummyData = [
|
|
1151
|
+
const dummyData$1 = [
|
|
1152
1152
|
{
|
|
1153
1153
|
a: "a",
|
|
1154
1154
|
b: "b",
|
|
@@ -1180,27 +1180,209 @@ const dummyData = [
|
|
|
1180
1180
|
c: 0
|
|
1181
1181
|
}
|
|
1182
1182
|
];
|
|
1183
|
-
const columnHelper = ui.createDataTableColumnHelper();
|
|
1184
|
-
const columns = [
|
|
1185
|
-
columnHelper.accessor("a", {
|
|
1183
|
+
const columnHelper$2 = ui.createDataTableColumnHelper();
|
|
1184
|
+
const columns$1 = [
|
|
1185
|
+
columnHelper$2.accessor("a", {
|
|
1186
1186
|
header: () => null,
|
|
1187
1187
|
cell: () => /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "w-full h-5" })
|
|
1188
1188
|
}),
|
|
1189
|
-
columnHelper.accessor("b", {
|
|
1189
|
+
columnHelper$2.accessor("b", {
|
|
1190
1190
|
header: () => null,
|
|
1191
1191
|
cell: () => /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "w-full h-5" })
|
|
1192
1192
|
}),
|
|
1193
|
-
columnHelper.accessor("c", {
|
|
1193
|
+
columnHelper$2.accessor("c", {
|
|
1194
1194
|
header: () => null,
|
|
1195
1195
|
cell: () => /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "w-full h-5" })
|
|
1196
1196
|
})
|
|
1197
1197
|
];
|
|
1198
1198
|
const ProductsTableSkeleton = () => {
|
|
1199
|
+
const [search, setSearch] = React__namespace.useState("");
|
|
1200
|
+
const table = ui.useDataTable({
|
|
1201
|
+
columns: columns$1,
|
|
1202
|
+
data: dummyData$1,
|
|
1203
|
+
getRowId: (product) => product.a,
|
|
1204
|
+
rowCount: dummyData$1.length,
|
|
1205
|
+
search: {
|
|
1206
|
+
state: search,
|
|
1207
|
+
onSearchChange: setSearch
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.DataTable, { instance: table, children: [
|
|
1211
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Toolbar, { className: "px-0 pt-0", children: /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Search, { placeholder: "Search..." }) }),
|
|
1212
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Table, {})
|
|
1213
|
+
] });
|
|
1214
|
+
};
|
|
1215
|
+
const BACKEND_URL = __BACKEND_URL__ === "/" ? "" : __BACKEND_URL__;
|
|
1216
|
+
async function retrieveCustomersAnalytics(date) {
|
|
1217
|
+
if (!date || !date.from || !(date == null ? void 0 : date.to)) {
|
|
1218
|
+
return void 0;
|
|
1219
|
+
}
|
|
1220
|
+
const dateFrom = dateFns.format(date.from, "yyyy-MM-dd");
|
|
1221
|
+
const dateTo = dateFns.format(date.to, "yyyy-MM-dd");
|
|
1222
|
+
const data = await fetch(
|
|
1223
|
+
`${BACKEND_URL}/admin/agilo-analytics/customers?date_from=${dateFrom}&date_to=${dateTo}`
|
|
1224
|
+
);
|
|
1225
|
+
const customersAnalytics = await data.json();
|
|
1226
|
+
return customersAnalytics;
|
|
1227
|
+
}
|
|
1228
|
+
const useCustomerAnalytics = (query, options) => {
|
|
1229
|
+
return reactQuery.useQuery({
|
|
1230
|
+
queryKey: ["customer-analytics", query == null ? void 0 : query.from, query == null ? void 0 : query.to],
|
|
1231
|
+
queryFn: async () => {
|
|
1232
|
+
const data = await retrieveCustomersAnalytics(query);
|
|
1233
|
+
return data;
|
|
1234
|
+
},
|
|
1235
|
+
...options
|
|
1236
|
+
});
|
|
1237
|
+
};
|
|
1238
|
+
const StackedBarChart = ({
|
|
1239
|
+
data,
|
|
1240
|
+
xAxisDataKey,
|
|
1241
|
+
yAxisTickFormatter,
|
|
1242
|
+
useStableColors = false,
|
|
1243
|
+
colorKeyField,
|
|
1244
|
+
dataKeys
|
|
1245
|
+
}) => {
|
|
1246
|
+
const isDark = useDarkMode();
|
|
1247
|
+
const colors = React__namespace.default.useMemo(() => {
|
|
1248
|
+
if (!useStableColors || !data || !colorKeyField) {
|
|
1249
|
+
return [];
|
|
1250
|
+
}
|
|
1251
|
+
return generateColorsForData(data, colorKeyField, 70, isDark ? 60 : 50);
|
|
1252
|
+
}, [data, useStableColors, colorKeyField, isDark]);
|
|
1253
|
+
return /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { aspect: 16 / 9, children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.BarChart, { data, margin: { left: 20 }, children: [
|
|
1254
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1255
|
+
recharts.CartesianGrid,
|
|
1256
|
+
{
|
|
1257
|
+
strokeDasharray: "3 3",
|
|
1258
|
+
stroke: isDark ? "#374151" : "#E5E7EB"
|
|
1259
|
+
}
|
|
1260
|
+
),
|
|
1261
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1262
|
+
recharts.XAxis,
|
|
1263
|
+
{
|
|
1264
|
+
dataKey: String(xAxisDataKey),
|
|
1265
|
+
tick: { fill: isDark ? "#D1D5DB" : "#6B7280" },
|
|
1266
|
+
axisLine: { stroke: isDark ? "#4B5563" : "#D1D5DB" },
|
|
1267
|
+
tickLine: { stroke: isDark ? "#4B5563" : "#D1D5DB" },
|
|
1268
|
+
tickMargin: 10
|
|
1269
|
+
}
|
|
1270
|
+
),
|
|
1271
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1272
|
+
recharts.YAxis,
|
|
1273
|
+
{
|
|
1274
|
+
tickFormatter: yAxisTickFormatter,
|
|
1275
|
+
allowDecimals: false,
|
|
1276
|
+
tick: { fill: isDark ? "#D1D5DB" : "#6B7280" },
|
|
1277
|
+
axisLine: { stroke: isDark ? "#4B5563" : "#D1D5DB" },
|
|
1278
|
+
tickLine: { stroke: isDark ? "#4B5563" : "#D1D5DB" }
|
|
1279
|
+
}
|
|
1280
|
+
),
|
|
1281
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1282
|
+
recharts.Tooltip,
|
|
1283
|
+
{
|
|
1284
|
+
cursor: {
|
|
1285
|
+
fill: isDark ? "rgba(55, 65, 81, 0.2)" : "rgba(243, 244, 246, 0.5)"
|
|
1286
|
+
},
|
|
1287
|
+
formatter: (value) => yAxisTickFormatter ? yAxisTickFormatter(value) : value,
|
|
1288
|
+
contentStyle: {
|
|
1289
|
+
backgroundColor: isDark ? "#1F2937" : "#FFFFFF",
|
|
1290
|
+
border: `1px solid ${isDark ? "#374151" : "#E5E7EB"}`,
|
|
1291
|
+
borderRadius: "0.5rem",
|
|
1292
|
+
color: isDark ? "#F9FAFB" : "#111827",
|
|
1293
|
+
boxShadow: isDark ? "0 4px 6px -1px rgba(0, 0, 0, 0.3)" : "0 4px 6px -1px rgba(0, 0, 0, 0.1)"
|
|
1294
|
+
},
|
|
1295
|
+
labelStyle: {
|
|
1296
|
+
color: isDark ? "#F9FAFB" : "#111827",
|
|
1297
|
+
fontWeight: "500",
|
|
1298
|
+
marginBottom: "4px"
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
),
|
|
1302
|
+
dataKeys == null ? void 0 : dataKeys.map((key, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1303
|
+
recharts.Bar,
|
|
1304
|
+
{
|
|
1305
|
+
dataKey: String(key),
|
|
1306
|
+
stackId: "a",
|
|
1307
|
+
fill: useStableColors && colors.length > 0 ? colors[index] : "#3B82F6"
|
|
1308
|
+
},
|
|
1309
|
+
String(key)
|
|
1310
|
+
))
|
|
1311
|
+
] }) });
|
|
1312
|
+
};
|
|
1313
|
+
const dummyData = [
|
|
1314
|
+
{
|
|
1315
|
+
a: "a",
|
|
1316
|
+
b: "b",
|
|
1317
|
+
c: 0,
|
|
1318
|
+
d: 0,
|
|
1319
|
+
e: /* @__PURE__ */ new Date()
|
|
1320
|
+
},
|
|
1321
|
+
{
|
|
1322
|
+
a: "a",
|
|
1323
|
+
b: "b",
|
|
1324
|
+
c: 0,
|
|
1325
|
+
d: 0,
|
|
1326
|
+
e: /* @__PURE__ */ new Date()
|
|
1327
|
+
},
|
|
1328
|
+
{
|
|
1329
|
+
a: "a",
|
|
1330
|
+
b: "b",
|
|
1331
|
+
c: 0,
|
|
1332
|
+
d: 0,
|
|
1333
|
+
e: /* @__PURE__ */ new Date()
|
|
1334
|
+
},
|
|
1335
|
+
{
|
|
1336
|
+
a: "a",
|
|
1337
|
+
b: "b",
|
|
1338
|
+
c: 0,
|
|
1339
|
+
d: 0,
|
|
1340
|
+
e: /* @__PURE__ */ new Date()
|
|
1341
|
+
},
|
|
1342
|
+
{
|
|
1343
|
+
a: "a",
|
|
1344
|
+
b: "b",
|
|
1345
|
+
c: 0,
|
|
1346
|
+
d: 0,
|
|
1347
|
+
e: /* @__PURE__ */ new Date()
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
a: "a",
|
|
1351
|
+
b: "b",
|
|
1352
|
+
c: 0,
|
|
1353
|
+
d: 0,
|
|
1354
|
+
e: /* @__PURE__ */ new Date()
|
|
1355
|
+
}
|
|
1356
|
+
];
|
|
1357
|
+
const columnHelper$1 = ui.createDataTableColumnHelper();
|
|
1358
|
+
const columns = [
|
|
1359
|
+
columnHelper$1.accessor("a", {
|
|
1360
|
+
header: () => null,
|
|
1361
|
+
cell: () => /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "w-full h-5" })
|
|
1362
|
+
}),
|
|
1363
|
+
columnHelper$1.accessor("b", {
|
|
1364
|
+
header: () => null,
|
|
1365
|
+
cell: () => /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "w-full h-5" })
|
|
1366
|
+
}),
|
|
1367
|
+
columnHelper$1.accessor("c", {
|
|
1368
|
+
header: () => null,
|
|
1369
|
+
cell: () => /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "w-full h-5" })
|
|
1370
|
+
}),
|
|
1371
|
+
columnHelper$1.accessor("d", {
|
|
1372
|
+
header: () => null,
|
|
1373
|
+
cell: () => /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "w-full h-5" })
|
|
1374
|
+
}),
|
|
1375
|
+
columnHelper$1.accessor("e", {
|
|
1376
|
+
header: () => null,
|
|
1377
|
+
cell: () => /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "w-full h-5" })
|
|
1378
|
+
})
|
|
1379
|
+
];
|
|
1380
|
+
const CustomersTableSkeleton = () => {
|
|
1199
1381
|
const [search, setSearch] = React__namespace.useState("");
|
|
1200
1382
|
const table = ui.useDataTable({
|
|
1201
1383
|
columns,
|
|
1202
1384
|
data: dummyData,
|
|
1203
|
-
getRowId: (
|
|
1385
|
+
getRowId: (customer) => customer.a,
|
|
1204
1386
|
rowCount: dummyData.length,
|
|
1205
1387
|
search: {
|
|
1206
1388
|
state: search,
|
|
@@ -1212,6 +1394,139 @@ const ProductsTableSkeleton = () => {
|
|
|
1212
1394
|
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Table, {})
|
|
1213
1395
|
] });
|
|
1214
1396
|
};
|
|
1397
|
+
const columnHelper = ui.createDataTableColumnHelper();
|
|
1398
|
+
const PAGE_SIZE = 10;
|
|
1399
|
+
const CustomersTable = ({
|
|
1400
|
+
customers,
|
|
1401
|
+
currencyCode
|
|
1402
|
+
}) => {
|
|
1403
|
+
const [pagination, setPagination] = React__namespace.useState({
|
|
1404
|
+
pageSize: PAGE_SIZE,
|
|
1405
|
+
pageIndex: 0
|
|
1406
|
+
});
|
|
1407
|
+
const [search, setSearch] = React__namespace.useState("");
|
|
1408
|
+
const [sorting, setSorting] = React__namespace.useState(
|
|
1409
|
+
null
|
|
1410
|
+
);
|
|
1411
|
+
const navigate = reactRouterDom.useNavigate();
|
|
1412
|
+
const shownCustomers = React__namespace.useMemo(() => {
|
|
1413
|
+
let filtered = customers.filter(
|
|
1414
|
+
(customer) => customer.name.toLowerCase().includes(search.toLowerCase()) || customer.email.toLowerCase().includes(search.toLowerCase())
|
|
1415
|
+
);
|
|
1416
|
+
if (sorting && sorting.id) {
|
|
1417
|
+
filtered = filtered.slice().sort((a, b) => {
|
|
1418
|
+
const aVal = a[sorting.id];
|
|
1419
|
+
const bVal = b[sorting.id];
|
|
1420
|
+
if (!aVal && !bVal) return 0;
|
|
1421
|
+
if (!aVal) return sorting.desc ? 1 : -1;
|
|
1422
|
+
if (!bVal) return sorting.desc ? -1 : 1;
|
|
1423
|
+
if (aVal < bVal) return sorting.desc ? 1 : -1;
|
|
1424
|
+
if (aVal > bVal) return sorting.desc ? -1 : 1;
|
|
1425
|
+
return 0;
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
return filtered.slice(
|
|
1429
|
+
pagination.pageIndex * pagination.pageSize,
|
|
1430
|
+
(pagination.pageIndex + 1) * pagination.pageSize
|
|
1431
|
+
);
|
|
1432
|
+
}, [customers, search, sorting, pagination]);
|
|
1433
|
+
const columns2 = React__namespace.useMemo(
|
|
1434
|
+
() => [
|
|
1435
|
+
columnHelper.accessor("name", {
|
|
1436
|
+
header: "Name",
|
|
1437
|
+
enableSorting: true,
|
|
1438
|
+
sortLabel: "Name",
|
|
1439
|
+
sortAscLabel: "A-Z",
|
|
1440
|
+
sortDescLabel: "Z-A"
|
|
1441
|
+
}),
|
|
1442
|
+
columnHelper.accessor("email", {
|
|
1443
|
+
header: "Email",
|
|
1444
|
+
enableSorting: true,
|
|
1445
|
+
sortLabel: "Email",
|
|
1446
|
+
sortAscLabel: "A-Z",
|
|
1447
|
+
sortDescLabel: "Z-A"
|
|
1448
|
+
}),
|
|
1449
|
+
columnHelper.accessor("order_count", {
|
|
1450
|
+
header: "Order Count",
|
|
1451
|
+
enableSorting: true,
|
|
1452
|
+
sortLabel: "Order Count",
|
|
1453
|
+
sortAscLabel: "Low to High",
|
|
1454
|
+
sortDescLabel: "High to Low"
|
|
1455
|
+
}),
|
|
1456
|
+
columnHelper.accessor("sales", {
|
|
1457
|
+
header: "Total Sales",
|
|
1458
|
+
enableSorting: true,
|
|
1459
|
+
sortLabel: "Total Sales",
|
|
1460
|
+
sortAscLabel: "Low to High",
|
|
1461
|
+
sortDescLabel: "High to Low",
|
|
1462
|
+
cell: ({ getValue }) => {
|
|
1463
|
+
const sales = getValue();
|
|
1464
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { children: new Intl.NumberFormat("en-US", {
|
|
1465
|
+
style: "currency",
|
|
1466
|
+
currency: currencyCode || "EUR"
|
|
1467
|
+
}).format(sales || 0) });
|
|
1468
|
+
}
|
|
1469
|
+
}),
|
|
1470
|
+
columnHelper.accessor("groups", {
|
|
1471
|
+
header: "Groups",
|
|
1472
|
+
cell: ({ getValue }) => {
|
|
1473
|
+
const groups = getValue();
|
|
1474
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { children: groups.length ? groups.join(", ") : "No Group" });
|
|
1475
|
+
}
|
|
1476
|
+
}),
|
|
1477
|
+
columnHelper.accessor("last_order", {
|
|
1478
|
+
header: "Last Order",
|
|
1479
|
+
enableSorting: true,
|
|
1480
|
+
sortLabel: "Last Order",
|
|
1481
|
+
sortAscLabel: "Oldest to Newest",
|
|
1482
|
+
sortDescLabel: "Newest to Oldest",
|
|
1483
|
+
cell: ({ getValue }) => {
|
|
1484
|
+
const date = getValue();
|
|
1485
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { children: date ? dateFns.format(new Date(date), "MMM dd, yyyy") : "No orders yet" });
|
|
1486
|
+
}
|
|
1487
|
+
})
|
|
1488
|
+
],
|
|
1489
|
+
[currencyCode]
|
|
1490
|
+
);
|
|
1491
|
+
const table = ui.useDataTable({
|
|
1492
|
+
columns: columns2,
|
|
1493
|
+
data: shownCustomers,
|
|
1494
|
+
getRowId: (customer) => customer.customer_id,
|
|
1495
|
+
rowCount: customers.length,
|
|
1496
|
+
search: {
|
|
1497
|
+
state: search,
|
|
1498
|
+
onSearchChange: setSearch
|
|
1499
|
+
},
|
|
1500
|
+
pagination: {
|
|
1501
|
+
state: pagination,
|
|
1502
|
+
onPaginationChange: setPagination
|
|
1503
|
+
},
|
|
1504
|
+
sorting: {
|
|
1505
|
+
state: sorting,
|
|
1506
|
+
onSortingChange: setSorting
|
|
1507
|
+
},
|
|
1508
|
+
onRowClick: (_, row) => {
|
|
1509
|
+
navigate(`/customers/${row.original.customer_id}`);
|
|
1510
|
+
}
|
|
1511
|
+
});
|
|
1512
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.DataTable, { instance: table, children: [
|
|
1513
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Toolbar, { className: "px-0 pt-0", children: /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Search, { placeholder: "Search..." }) }),
|
|
1514
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1515
|
+
ui.DataTable.Table,
|
|
1516
|
+
{
|
|
1517
|
+
emptyState: {
|
|
1518
|
+
filtered: {
|
|
1519
|
+
heading: "No customers found"
|
|
1520
|
+
},
|
|
1521
|
+
empty: {
|
|
1522
|
+
heading: "No customers available"
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
),
|
|
1527
|
+
customers.length > PAGE_SIZE && /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Pagination, {})
|
|
1528
|
+
] });
|
|
1529
|
+
};
|
|
1215
1530
|
function dateToCalendarDate(date) {
|
|
1216
1531
|
return new $35ea8db9cb2ccb90$export$99faa760c7908e4f(
|
|
1217
1532
|
date.getFullYear(),
|
|
@@ -1254,7 +1569,7 @@ function presetToDateRange(preset) {
|
|
|
1254
1569
|
}
|
|
1255
1570
|
const DATE_RANGE_REGEX = /^(\d{4}-\d{2}-\d{2})-(\d{4}-\d{2}-\d{2})$/;
|
|
1256
1571
|
const AnalyticsPage = () => {
|
|
1257
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
1572
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
1258
1573
|
const [searchParams, setSearchParams] = reactRouterDom.useSearchParams();
|
|
1259
1574
|
const rangeParam = searchParams.get("range") || "this-month";
|
|
1260
1575
|
const date = React__namespace.useMemo(() => {
|
|
@@ -1270,6 +1585,7 @@ const AnalyticsPage = () => {
|
|
|
1270
1585
|
return void 0;
|
|
1271
1586
|
}, [rangeParam]);
|
|
1272
1587
|
const { data: products, isLoading: isLoadingProducts } = useProductAnalytics(date);
|
|
1588
|
+
const { data: customers, isLoading: isLoadingCustomers } = useCustomerAnalytics(date);
|
|
1273
1589
|
const { data: orders, isLoading: isLoadingOrders } = useOrderAnalytics(
|
|
1274
1590
|
["this-month", "last-month", "last-3-months"].includes(rangeParam) ? rangeParam : "custom",
|
|
1275
1591
|
date
|
|
@@ -1281,6 +1597,9 @@ const AnalyticsPage = () => {
|
|
|
1281
1597
|
(item) => item.sales > 0
|
|
1282
1598
|
);
|
|
1283
1599
|
const someTopSellingProductsGreaterThanZero = (_c = products == null ? void 0 : products.variantQuantitySold) == null ? void 0 : _c.some((item) => item.quantity > 0);
|
|
1600
|
+
const someCustomerCountsGreaterThanZero = (_d = customers == null ? void 0 : customers.customer_count) == null ? void 0 : _d.some(
|
|
1601
|
+
(item) => (item.new_customers || 0) > 0 || (item.returning_customers || 0) > 0
|
|
1602
|
+
);
|
|
1284
1603
|
const updateDatePreset = React__namespace.useCallback(
|
|
1285
1604
|
(preset) => {
|
|
1286
1605
|
const params = new URLSearchParams(searchParams.toString());
|
|
@@ -1468,6 +1787,14 @@ const AnalyticsPage = () => {
|
|
|
1468
1787
|
disabled: isLoadingOrders || isLoadingProducts,
|
|
1469
1788
|
children: "Products"
|
|
1470
1789
|
}
|
|
1790
|
+
),
|
|
1791
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1792
|
+
ui.Tabs.Trigger,
|
|
1793
|
+
{
|
|
1794
|
+
value: "customers",
|
|
1795
|
+
disabled: isLoadingOrders || isLoadingProducts,
|
|
1796
|
+
children: "Customers"
|
|
1797
|
+
}
|
|
1471
1798
|
)
|
|
1472
1799
|
] }),
|
|
1473
1800
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8", children: [
|
|
@@ -1489,7 +1816,7 @@ const AnalyticsPage = () => {
|
|
|
1489
1816
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "min-h-[9.375rem]", children: [
|
|
1490
1817
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xlarge", weight: "plus", children: "Orders Over Time" }),
|
|
1491
1818
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "mb-8 text-ui-fg-muted", children: "Total number of orders in the selected period" }),
|
|
1492
|
-
isLoadingOrders ? /* @__PURE__ */ jsxRuntime.jsx(LineChartSkeleton, {}) : (orders == null ? void 0 : orders.order_count) && ((
|
|
1819
|
+
isLoadingOrders ? /* @__PURE__ */ jsxRuntime.jsx(LineChartSkeleton, {}) : (orders == null ? void 0 : orders.order_count) && ((_e = orders == null ? void 0 : orders.order_count) == null ? void 0 : _e.length) > 0 && someOrderCountsGreaterThanZero ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full", style: { aspectRatio: "16/9" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1493
1820
|
LineChart,
|
|
1494
1821
|
{
|
|
1495
1822
|
data: orders == null ? void 0 : orders.order_count,
|
|
@@ -1529,7 +1856,7 @@ const AnalyticsPage = () => {
|
|
|
1529
1856
|
orders == null ? void 0 : orders.currency_code,
|
|
1530
1857
|
")"
|
|
1531
1858
|
] }),
|
|
1532
|
-
isLoadingOrders ? /* @__PURE__ */ jsxRuntime.jsx(LineChartSkeleton, {}) : (orders == null ? void 0 : orders.order_sales) && ((
|
|
1859
|
+
isLoadingOrders ? /* @__PURE__ */ jsxRuntime.jsx(LineChartSkeleton, {}) : (orders == null ? void 0 : orders.order_sales) && ((_f = orders == null ? void 0 : orders.order_sales) == null ? void 0 : _f.length) > 0 && someOrderSalesGreaterThanZero ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full", style: { aspectRatio: "16/9" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1533
1860
|
LineChart,
|
|
1534
1861
|
{
|
|
1535
1862
|
data: orders.order_sales,
|
|
@@ -1556,7 +1883,7 @@ const AnalyticsPage = () => {
|
|
|
1556
1883
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "min-h-[9.375rem]", children: [
|
|
1557
1884
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xlarge", weight: "plus", children: "Top Regions by Sales" }),
|
|
1558
1885
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "mb-8 text-ui-fg-muted", children: "Sales breakdown by region in the selected period" }),
|
|
1559
|
-
isLoadingOrders ? /* @__PURE__ */ jsxRuntime.jsx(BarChartSkeleton, {}) : (orders == null ? void 0 : orders.regions) && ((
|
|
1886
|
+
isLoadingOrders ? /* @__PURE__ */ jsxRuntime.jsx(BarChartSkeleton, {}) : (orders == null ? void 0 : orders.regions) && ((_g = orders == null ? void 0 : orders.regions) == null ? void 0 : _g.length) > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full", style: { aspectRatio: "16/9" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1560
1887
|
BarChart,
|
|
1561
1888
|
{
|
|
1562
1889
|
data: orders.regions,
|
|
@@ -1582,7 +1909,7 @@ const AnalyticsPage = () => {
|
|
|
1582
1909
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "min-h-[9.375rem]", children: [
|
|
1583
1910
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xlarge", weight: "plus", children: "Order Status Breakdown" }),
|
|
1584
1911
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "mb-8 text-ui-fg-muted", children: "Distribution of orders by status in the selected period" }),
|
|
1585
|
-
isLoadingOrders ? /* @__PURE__ */ jsxRuntime.jsx(PieChartSkeleton, {}) : (orders == null ? void 0 : orders.statuses) && ((
|
|
1912
|
+
isLoadingOrders ? /* @__PURE__ */ jsxRuntime.jsx(PieChartSkeleton, {}) : (orders == null ? void 0 : orders.statuses) && ((_h = orders == null ? void 0 : orders.statuses) == null ? void 0 : _h.length) > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full", style: { aspectRatio: "16/9" }, children: /* @__PURE__ */ jsxRuntime.jsx(PieChart, { data: orders == null ? void 0 : orders.statuses, dataKey: "count" }) }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1586
1913
|
ui.Text,
|
|
1587
1914
|
{
|
|
1588
1915
|
size: "small",
|
|
@@ -1616,7 +1943,7 @@ const AnalyticsPage = () => {
|
|
|
1616
1943
|
isLoadingProducts ? /* @__PURE__ */ jsxRuntime.jsx(ProductsTableSkeleton, {}) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1617
1944
|
ProductsTable,
|
|
1618
1945
|
{
|
|
1619
|
-
products: ((
|
|
1946
|
+
products: ((_i = products == null ? void 0 : products.lowStockVariants) == null ? void 0 : _i.filter(
|
|
1620
1947
|
(product) => product.inventoryQuantity === 0
|
|
1621
1948
|
)) || []
|
|
1622
1949
|
}
|
|
@@ -1628,13 +1955,107 @@ const AnalyticsPage = () => {
|
|
|
1628
1955
|
isLoadingProducts ? /* @__PURE__ */ jsxRuntime.jsx(ProductsTableSkeleton, {}) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1629
1956
|
ProductsTable,
|
|
1630
1957
|
{
|
|
1631
|
-
products: ((
|
|
1958
|
+
products: ((_j = products == null ? void 0 : products.lowStockVariants) == null ? void 0 : _j.filter(
|
|
1632
1959
|
(product) => product.inventoryQuantity > 0
|
|
1633
1960
|
)) || []
|
|
1634
1961
|
}
|
|
1635
1962
|
)
|
|
1636
1963
|
] })
|
|
1637
1964
|
] })
|
|
1965
|
+
] }),
|
|
1966
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.Content, { value: "customers", children: [
|
|
1967
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex max-md:flex-col gap-4 mb-4", children: [
|
|
1968
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4 flex-1", children: [
|
|
1969
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "relative", children: [
|
|
1970
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.User, { className: "absolute right-6 top-4 text-ui-fg-muted" }),
|
|
1971
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: "Total Customers" }),
|
|
1972
|
+
isLoadingCustomers ? /* @__PURE__ */ jsxRuntime.jsx(SmallCardSkeleton, {}) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xlarge", weight: "plus", children: (customers == null ? void 0 : customers.total_customers) || 0 }) })
|
|
1973
|
+
] }),
|
|
1974
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "relative", children: [
|
|
1975
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.User, { className: "absolute right-6 top-4 text-ui-fg-muted" }),
|
|
1976
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: "New Customers" }),
|
|
1977
|
+
isLoadingCustomers ? /* @__PURE__ */ jsxRuntime.jsx(SmallCardSkeleton, {}) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xlarge", weight: "plus", children: (customers == null ? void 0 : customers.new_customers) || 0 }) })
|
|
1978
|
+
] })
|
|
1979
|
+
] }),
|
|
1980
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4 flex-1", children: [
|
|
1981
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "relative", children: [
|
|
1982
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.User, { className: "absolute right-6 text-ui-fg-muted top-4 size-[15px]" }),
|
|
1983
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: "Returning Customers" }),
|
|
1984
|
+
isLoadingCustomers ? /* @__PURE__ */ jsxRuntime.jsx(SmallCardSkeleton, {}) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xlarge", weight: "plus", children: (customers == null ? void 0 : customers.returning_customers) || 0 }) })
|
|
1985
|
+
] }),
|
|
1986
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "relative", children: [
|
|
1987
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChartNoAxesCombined, { className: "absolute right-6 top-4 text-ui-fg-muted" }),
|
|
1988
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: "Average Sales per Customer" }),
|
|
1989
|
+
isLoadingCustomers || isLoadingOrders ? /* @__PURE__ */ jsxRuntime.jsx(SmallCardSkeleton, {}) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xlarge", weight: "plus", children: new Intl.NumberFormat("en-US", {
|
|
1990
|
+
currency: (customers == null ? void 0 : customers.currency_code) || "EUR",
|
|
1991
|
+
style: "currency"
|
|
1992
|
+
}).format(
|
|
1993
|
+
(customers == null ? void 0 : customers.total_customers) && customers.total_customers > 0 ? ((orders == null ? void 0 : orders.total_sales) || 0) / customers.total_customers : 0
|
|
1994
|
+
) }) })
|
|
1995
|
+
] })
|
|
1996
|
+
] })
|
|
1997
|
+
] }),
|
|
1998
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex max-md:flex-col gap-4 mb-4", children: [
|
|
1999
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "min-h-[9.375rem]", children: [
|
|
2000
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xlarge", weight: "plus", children: "New vs. Returning Customers" }),
|
|
2001
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "mb-8 text-ui-fg-muted", children: "Distribution of new and returning customers in the selected period" }),
|
|
2002
|
+
isLoadingCustomers ? /* @__PURE__ */ jsxRuntime.jsx(BarChartSkeleton, {}) : (customers == null ? void 0 : customers.customer_count) && customers.customer_count.length > 0 && someCustomerCountsGreaterThanZero ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full", style: { aspectRatio: "16/9" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2003
|
+
StackedBarChart,
|
|
2004
|
+
{
|
|
2005
|
+
data: customers.customer_count,
|
|
2006
|
+
xAxisDataKey: "name",
|
|
2007
|
+
lineColor: "#82ca9d",
|
|
2008
|
+
useStableColors: true,
|
|
2009
|
+
colorKeyField: "returning_customers",
|
|
2010
|
+
dataKeys: ["new_customers", "returning_customers"]
|
|
2011
|
+
}
|
|
2012
|
+
) }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2013
|
+
ui.Text,
|
|
2014
|
+
{
|
|
2015
|
+
size: "small",
|
|
2016
|
+
className: "text-ui-fg-muted text-center",
|
|
2017
|
+
children: "No data available for the selected period."
|
|
2018
|
+
}
|
|
2019
|
+
)
|
|
2020
|
+
] }) }),
|
|
2021
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "min-h-[9.375rem]", children: [
|
|
2022
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xlarge", weight: "plus", children: "Top Customer Groups by Sales" }),
|
|
2023
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "mb-8 text-ui-fg-muted", children: "Sales breakdown by customer group in the selected period" }),
|
|
2024
|
+
isLoadingCustomers ? /* @__PURE__ */ jsxRuntime.jsx(BarChartSkeleton, {}) : (customers == null ? void 0 : customers.customer_group) && customers.customer_group.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full", style: { aspectRatio: "16/9" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2025
|
+
BarChart,
|
|
2026
|
+
{
|
|
2027
|
+
data: customers.customer_group,
|
|
2028
|
+
xAxisDataKey: "name",
|
|
2029
|
+
lineColor: "#82ca9d",
|
|
2030
|
+
useStableColors: true,
|
|
2031
|
+
colorKeyField: "name",
|
|
2032
|
+
yAxisDataKey: "total",
|
|
2033
|
+
yAxisTickFormatter: (value) => new Intl.NumberFormat("en-US", {
|
|
2034
|
+
currency: customers.currency_code || "EUR",
|
|
2035
|
+
maximumFractionDigits: 0
|
|
2036
|
+
}).format(value)
|
|
2037
|
+
}
|
|
2038
|
+
) }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2039
|
+
ui.Text,
|
|
2040
|
+
{
|
|
2041
|
+
size: "small",
|
|
2042
|
+
className: "text-ui-fg-muted text-center",
|
|
2043
|
+
children: "No data available for the selected period."
|
|
2044
|
+
}
|
|
2045
|
+
)
|
|
2046
|
+
] }) })
|
|
2047
|
+
] }),
|
|
2048
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-4 max-xl:flex-col", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
|
|
2049
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xlarge", weight: "plus", children: "Top Customers by Sales" }),
|
|
2050
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "mb-8 text-ui-fg-muted", children: "Customers by sales in the selected period" }),
|
|
2051
|
+
isLoadingCustomers ? /* @__PURE__ */ jsxRuntime.jsx(CustomersTableSkeleton, {}) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2052
|
+
CustomersTable,
|
|
2053
|
+
{
|
|
2054
|
+
customers: (customers == null ? void 0 : customers.customer_sales) || [],
|
|
2055
|
+
currencyCode: (customers == null ? void 0 : customers.currency_code) || "EUR"
|
|
2056
|
+
}
|
|
2057
|
+
)
|
|
2058
|
+
] }) })
|
|
1638
2059
|
] })
|
|
1639
2060
|
] })
|
|
1640
2061
|
]
|