@stoker-platform/web-app 0.5.137 → 0.5.139
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/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/List.tsx +159 -174
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/src/List.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
Chart,
|
|
5
5
|
CollectionMeta,
|
|
6
6
|
CollectionSchema,
|
|
7
|
+
Filter,
|
|
7
8
|
FormList,
|
|
8
9
|
Metric,
|
|
9
10
|
RelationList,
|
|
@@ -100,6 +101,96 @@ import { getSortingValue } from "./utils/getSortingValue"
|
|
|
100
101
|
|
|
101
102
|
export const description = "A list of records in a table. The content area has a search bar in the header."
|
|
102
103
|
|
|
104
|
+
type AreaChartData = { date: string; metric1: number; metric2?: number }
|
|
105
|
+
|
|
106
|
+
interface AreaMetricChartProps {
|
|
107
|
+
chartConfig: ChartConfig
|
|
108
|
+
chartData: AreaChartData[]
|
|
109
|
+
timeRange: string | undefined
|
|
110
|
+
timezone: string
|
|
111
|
+
showMetric2: boolean
|
|
112
|
+
showLegend: boolean
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const AreaMetricChart = ({
|
|
116
|
+
chartConfig,
|
|
117
|
+
chartData,
|
|
118
|
+
timeRange,
|
|
119
|
+
timezone,
|
|
120
|
+
showMetric2,
|
|
121
|
+
showLegend,
|
|
122
|
+
}: AreaMetricChartProps) => {
|
|
123
|
+
const filteredData = useMemo(() => {
|
|
124
|
+
return chartData?.filter((item) => {
|
|
125
|
+
const date = new Date(item.date)
|
|
126
|
+
let daysToSubtract = 90
|
|
127
|
+
if (timeRange === "30d") {
|
|
128
|
+
daysToSubtract = 30
|
|
129
|
+
} else if (timeRange === "7d") {
|
|
130
|
+
daysToSubtract = 7
|
|
131
|
+
}
|
|
132
|
+
const startDate = DateTime.now().setZone(timezone).toJSDate()
|
|
133
|
+
const endDate = DateTime.now().setZone(timezone).toJSDate()
|
|
134
|
+
startDate.setDate(startDate.getDate() - daysToSubtract)
|
|
135
|
+
return date >= startDate && date <= endDate
|
|
136
|
+
})
|
|
137
|
+
}, [chartData, timeRange, timezone])
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<ChartContainer config={chartConfig} className="aspect-auto h-[173px] w-full">
|
|
141
|
+
<AreaChart data={filteredData}>
|
|
142
|
+
<defs>
|
|
143
|
+
<linearGradient id="fill1" x1="0" y1="0" x2="0" y2="1">
|
|
144
|
+
<stop offset="5%" stopColor="var(--chart-dark)" stopOpacity={0.8} />
|
|
145
|
+
<stop offset="95%" stopColor="var(--chart-light)" stopOpacity={0.1} />
|
|
146
|
+
</linearGradient>
|
|
147
|
+
<linearGradient id="fill2" x1="0" y1="0" x2="0" y2="1">
|
|
148
|
+
<stop offset="5%" stopColor="var(--chart-dark)" stopOpacity={0.8} />
|
|
149
|
+
<stop offset="95%" stopColor="var(--chart-light)" stopOpacity={0.1} />
|
|
150
|
+
</linearGradient>
|
|
151
|
+
</defs>
|
|
152
|
+
<CartesianGrid vertical={false} className="last:opacity-0" />
|
|
153
|
+
<XAxis
|
|
154
|
+
dataKey="date"
|
|
155
|
+
tickLine={false}
|
|
156
|
+
axisLine={false}
|
|
157
|
+
tickMargin={8}
|
|
158
|
+
minTickGap={32}
|
|
159
|
+
tickFormatter={(value) => {
|
|
160
|
+
const date = new Date(value)
|
|
161
|
+
return date.toLocaleDateString("en-US", {
|
|
162
|
+
month: "short",
|
|
163
|
+
day: "numeric",
|
|
164
|
+
})
|
|
165
|
+
}}
|
|
166
|
+
/>
|
|
167
|
+
<YAxis hide padding={{ top: 16 }} />
|
|
168
|
+
<ChartTooltip
|
|
169
|
+
cursor={false}
|
|
170
|
+
content={
|
|
171
|
+
<ChartTooltipContent
|
|
172
|
+
labelFormatter={(value) => {
|
|
173
|
+
return new Date(value).toLocaleDateString("en-US", {
|
|
174
|
+
month: "short",
|
|
175
|
+
day: "numeric",
|
|
176
|
+
})
|
|
177
|
+
}}
|
|
178
|
+
indicator="dot"
|
|
179
|
+
/>
|
|
180
|
+
}
|
|
181
|
+
/>
|
|
182
|
+
<Area dataKey="metric1" type="natural" fill="url(#fill1)" stroke="var(--chart-dark)" stackId="a" />
|
|
183
|
+
{showMetric2 && (
|
|
184
|
+
<Area dataKey="metric2" type="natural" fill="url(#fill2)" stroke="var(--chart-light)" stackId="a" />
|
|
185
|
+
)}
|
|
186
|
+
{showLegend && <ChartLegend className="pb-3" content={<ChartLegendContent />} />}
|
|
187
|
+
</AreaChart>
|
|
188
|
+
</ChartContainer>
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const AreaMetricChartMemo = memo(AreaMetricChart)
|
|
193
|
+
|
|
103
194
|
interface ListProps {
|
|
104
195
|
collection: CollectionSchema
|
|
105
196
|
list: StokerRecord[] | undefined
|
|
@@ -552,9 +643,18 @@ export function List({
|
|
|
552
643
|
return list || []
|
|
553
644
|
}, [isPreloadCacheEnabled, isServerReadOnly, list, search])
|
|
554
645
|
|
|
646
|
+
const selectedRecords = useMemo(() => {
|
|
647
|
+
const selectedIds = Object.keys(rowSelection)
|
|
648
|
+
if (selectedIds.length === 0) return []
|
|
649
|
+
return selectedIds
|
|
650
|
+
.map((id) => searchList.find((record) => record.id === id))
|
|
651
|
+
.filter((record): record is StokerRecord => record !== undefined)
|
|
652
|
+
}, [rowSelection, searchList])
|
|
653
|
+
|
|
555
654
|
const table = useReactTable<StokerRecord>({
|
|
556
655
|
data: searchList,
|
|
557
656
|
columns,
|
|
657
|
+
getRowId: (row) => row.id,
|
|
558
658
|
getCoreRowModel: getCoreRowModel(),
|
|
559
659
|
getPaginationRowModel: getPaginationRowModel(),
|
|
560
660
|
onSortingChange: (sortingUpdater) => {
|
|
@@ -1080,11 +1180,7 @@ export function List({
|
|
|
1080
1180
|
const titles = await getCachedConfigValue(customization, ["collections", labels.collection, "admin", "titles"])
|
|
1081
1181
|
const recordTitle = titles?.record || labels.record
|
|
1082
1182
|
|
|
1083
|
-
|
|
1084
|
-
const key = row as unknown as number
|
|
1085
|
-
if (!list) return
|
|
1086
|
-
// eslint-disable-next-line security/detect-object-injection
|
|
1087
|
-
const record = list[key]
|
|
1183
|
+
selectedRecords.forEach((record) => {
|
|
1088
1184
|
if (isGlobalLoading.get(record.id)?.server) {
|
|
1089
1185
|
alert(
|
|
1090
1186
|
`Record ${record.id} is currently being written to the server. Please wait for it to finish before deleting.`,
|
|
@@ -1121,10 +1217,19 @@ export function List({
|
|
|
1121
1217
|
removeOptimisticDelete(labels.collection, record.id)
|
|
1122
1218
|
}
|
|
1123
1219
|
})
|
|
1220
|
+
setRowSelection({})
|
|
1124
1221
|
toast({
|
|
1125
|
-
description: `Deleting ${
|
|
1222
|
+
description: `Deleting ${selectedRecords.length} ${selectedRecords.length > 1 ? "records" : "record"}.`,
|
|
1126
1223
|
})
|
|
1127
|
-
}, [
|
|
1224
|
+
}, [
|
|
1225
|
+
collection,
|
|
1226
|
+
selectedRecords,
|
|
1227
|
+
isGlobalLoading,
|
|
1228
|
+
softDeleteField,
|
|
1229
|
+
softDeleteTimestampField,
|
|
1230
|
+
recordTitleField,
|
|
1231
|
+
isServerReadOnly,
|
|
1232
|
+
])
|
|
1128
1233
|
|
|
1129
1234
|
const sortingField = getField(fields, sorting[0]?.id)
|
|
1130
1235
|
|
|
@@ -1318,6 +1423,10 @@ export function List({
|
|
|
1318
1423
|
)
|
|
1319
1424
|
}, [metrics])
|
|
1320
1425
|
|
|
1426
|
+
const statusFilter = useMemo(() => {
|
|
1427
|
+
return filters?.find((filter: Filter) => filter.type === "status")
|
|
1428
|
+
}, [filters])
|
|
1429
|
+
|
|
1321
1430
|
return (
|
|
1322
1431
|
<>
|
|
1323
1432
|
{!formList && (meta?.title || collectionTitle) && (
|
|
@@ -1408,11 +1517,7 @@ export function List({
|
|
|
1408
1517
|
|
|
1409
1518
|
const chartData =
|
|
1410
1519
|
// eslint-disable-next-line security/detect-object-injection
|
|
1411
|
-
(metricsValues[index] as
|
|
1412
|
-
date: string
|
|
1413
|
-
metric1: number
|
|
1414
|
-
metric2?: number
|
|
1415
|
-
}[]) || []
|
|
1520
|
+
(metricsValues[index] as AreaChartData[]) || []
|
|
1416
1521
|
|
|
1417
1522
|
const chartConfig = {
|
|
1418
1523
|
visitors: {
|
|
@@ -1428,22 +1533,6 @@ export function List({
|
|
|
1428
1533
|
},
|
|
1429
1534
|
} satisfies ChartConfig
|
|
1430
1535
|
|
|
1431
|
-
const filteredData = chartData?.filter((item) => {
|
|
1432
|
-
const date = new Date(item.date)
|
|
1433
|
-
let daysToSubtract = 90
|
|
1434
|
-
// eslint-disable-next-line security/detect-object-injection
|
|
1435
|
-
if (timeRange[metricTitle] === "30d") {
|
|
1436
|
-
daysToSubtract = 30
|
|
1437
|
-
// eslint-disable-next-line security/detect-object-injection
|
|
1438
|
-
} else if (timeRange[metricTitle] === "7d") {
|
|
1439
|
-
daysToSubtract = 7
|
|
1440
|
-
}
|
|
1441
|
-
const startDate = DateTime.now().setZone(timezone).toJSDate()
|
|
1442
|
-
const endDate = DateTime.now().setZone(timezone).toJSDate()
|
|
1443
|
-
startDate.setDate(startDate.getDate() - daysToSubtract)
|
|
1444
|
-
return date >= startDate && date <= endDate
|
|
1445
|
-
})
|
|
1446
|
-
|
|
1447
1536
|
return (
|
|
1448
1537
|
<div key={`metric-${index}`} className="grid gap-3 flex-1 min-w-0">
|
|
1449
1538
|
<Card className="pt-0 w-full" key={`metric-${index}`}>
|
|
@@ -1485,111 +1574,15 @@ export function List({
|
|
|
1485
1574
|
</Select>
|
|
1486
1575
|
</CardHeader>
|
|
1487
1576
|
<CardContent className="flex-1 px-2 sm:px-6 pb-0">
|
|
1488
|
-
<
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
y1="0"
|
|
1498
|
-
x2="0"
|
|
1499
|
-
y2="1"
|
|
1500
|
-
>
|
|
1501
|
-
<stop
|
|
1502
|
-
offset="5%"
|
|
1503
|
-
stopColor="var(--chart-dark)"
|
|
1504
|
-
stopOpacity={0.8}
|
|
1505
|
-
/>
|
|
1506
|
-
<stop
|
|
1507
|
-
offset="95%"
|
|
1508
|
-
stopColor="var(--chart-light)"
|
|
1509
|
-
stopOpacity={0.1}
|
|
1510
|
-
/>
|
|
1511
|
-
</linearGradient>
|
|
1512
|
-
<linearGradient
|
|
1513
|
-
id="fill2"
|
|
1514
|
-
x1="0"
|
|
1515
|
-
y1="0"
|
|
1516
|
-
x2="0"
|
|
1517
|
-
y2="1"
|
|
1518
|
-
>
|
|
1519
|
-
<stop
|
|
1520
|
-
offset="5%"
|
|
1521
|
-
stopColor="var(--chart-dark)"
|
|
1522
|
-
stopOpacity={0.8}
|
|
1523
|
-
/>
|
|
1524
|
-
<stop
|
|
1525
|
-
offset="95%"
|
|
1526
|
-
stopColor="var(--chart-light)"
|
|
1527
|
-
stopOpacity={0.1}
|
|
1528
|
-
/>
|
|
1529
|
-
</linearGradient>
|
|
1530
|
-
</defs>
|
|
1531
|
-
<CartesianGrid
|
|
1532
|
-
vertical={false}
|
|
1533
|
-
className="last:opacity-0"
|
|
1534
|
-
/>
|
|
1535
|
-
<XAxis
|
|
1536
|
-
dataKey="date"
|
|
1537
|
-
tickLine={false}
|
|
1538
|
-
axisLine={false}
|
|
1539
|
-
tickMargin={8}
|
|
1540
|
-
minTickGap={32}
|
|
1541
|
-
tickFormatter={(value) => {
|
|
1542
|
-
const date = new Date(value)
|
|
1543
|
-
return date.toLocaleDateString(
|
|
1544
|
-
"en-US",
|
|
1545
|
-
{
|
|
1546
|
-
month: "short",
|
|
1547
|
-
day: "numeric",
|
|
1548
|
-
},
|
|
1549
|
-
)
|
|
1550
|
-
}}
|
|
1551
|
-
/>
|
|
1552
|
-
<YAxis hide padding={{ top: 16 }} />
|
|
1553
|
-
<ChartTooltip
|
|
1554
|
-
cursor={false}
|
|
1555
|
-
content={
|
|
1556
|
-
<ChartTooltipContent
|
|
1557
|
-
labelFormatter={(value) => {
|
|
1558
|
-
return new Date(
|
|
1559
|
-
value,
|
|
1560
|
-
).toLocaleDateString("en-US", {
|
|
1561
|
-
month: "short",
|
|
1562
|
-
day: "numeric",
|
|
1563
|
-
})
|
|
1564
|
-
}}
|
|
1565
|
-
indicator="dot"
|
|
1566
|
-
/>
|
|
1567
|
-
}
|
|
1568
|
-
/>
|
|
1569
|
-
<Area
|
|
1570
|
-
dataKey="metric1"
|
|
1571
|
-
type="natural"
|
|
1572
|
-
fill="url(#fill1)"
|
|
1573
|
-
stroke="var(--chart-dark)"
|
|
1574
|
-
stackId="a"
|
|
1575
|
-
/>
|
|
1576
|
-
{metricField2 && (
|
|
1577
|
-
<Area
|
|
1578
|
-
dataKey="metric2"
|
|
1579
|
-
type="natural"
|
|
1580
|
-
fill="url(#fill2)"
|
|
1581
|
-
stroke="var(--chart-light)"
|
|
1582
|
-
stackId="a"
|
|
1583
|
-
/>
|
|
1584
|
-
)}
|
|
1585
|
-
{metricField1 && metricField2 && (
|
|
1586
|
-
<ChartLegend
|
|
1587
|
-
className="pb-3"
|
|
1588
|
-
content={<ChartLegendContent />}
|
|
1589
|
-
/>
|
|
1590
|
-
)}
|
|
1591
|
-
</AreaChart>
|
|
1592
|
-
</ChartContainer>
|
|
1577
|
+
<AreaMetricChartMemo
|
|
1578
|
+
chartConfig={chartConfig}
|
|
1579
|
+
chartData={chartData}
|
|
1580
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
1581
|
+
timeRange={timeRange[metricTitle]}
|
|
1582
|
+
timezone={timezone}
|
|
1583
|
+
showMetric2={!!metricField2}
|
|
1584
|
+
showLegend={!!(metricField1 && metricField2)}
|
|
1585
|
+
/>
|
|
1593
1586
|
</CardContent>
|
|
1594
1587
|
</div>
|
|
1595
1588
|
</Card>
|
|
@@ -1662,6 +1655,7 @@ export function List({
|
|
|
1662
1655
|
path={[labels.collection]}
|
|
1663
1656
|
onSuccess={() => {
|
|
1664
1657
|
setIsUpdateDialogOpen(false)
|
|
1658
|
+
setRowSelection({})
|
|
1665
1659
|
setTimeout(() => {
|
|
1666
1660
|
updateButtonRef.current?.focus()
|
|
1667
1661
|
}, 0)
|
|
@@ -1672,17 +1666,7 @@ export function List({
|
|
|
1672
1666
|
onSaveRecord={() => {
|
|
1673
1667
|
setOptimisticList()
|
|
1674
1668
|
}}
|
|
1675
|
-
rowSelection={
|
|
1676
|
-
.map((row) => {
|
|
1677
|
-
const key = row as unknown as number
|
|
1678
|
-
if (!list) return undefined
|
|
1679
|
-
// eslint-disable-next-line security/detect-object-injection
|
|
1680
|
-
return list[key]
|
|
1681
|
-
})
|
|
1682
|
-
.filter(
|
|
1683
|
-
(record): record is StokerRecord =>
|
|
1684
|
-
record !== undefined,
|
|
1685
|
-
)}
|
|
1669
|
+
rowSelection={selectedRecords}
|
|
1686
1670
|
/>
|
|
1687
1671
|
</div>
|
|
1688
1672
|
</div>
|
|
@@ -1690,36 +1674,37 @@ export function List({
|
|
|
1690
1674
|
</div>,
|
|
1691
1675
|
document.body,
|
|
1692
1676
|
)}
|
|
1693
|
-
{collectionAccess("Delete", collectionPermissions) &&
|
|
1694
|
-
|
|
1695
|
-
<
|
|
1696
|
-
<
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
<
|
|
1709
|
-
<
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
<
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1677
|
+
{collectionAccess("Delete", collectionPermissions) &&
|
|
1678
|
+
!(statusFilter?.value === "trash") && (
|
|
1679
|
+
<AlertDialog>
|
|
1680
|
+
<AlertDialogTrigger asChild>
|
|
1681
|
+
<Button
|
|
1682
|
+
type="button"
|
|
1683
|
+
variant="destructive"
|
|
1684
|
+
disabled={
|
|
1685
|
+
connectionStatus === "offline" &&
|
|
1686
|
+
(disableOfflineDelete || serverWriteOnly || collection.auth)
|
|
1687
|
+
}
|
|
1688
|
+
>
|
|
1689
|
+
Delete Selected
|
|
1690
|
+
</Button>
|
|
1691
|
+
</AlertDialogTrigger>
|
|
1692
|
+
<AlertDialogContent>
|
|
1693
|
+
<AlertDialogHeader>
|
|
1694
|
+
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
|
1695
|
+
<AlertDialogDescription className="hidden">
|
|
1696
|
+
This action delete the selected records.
|
|
1697
|
+
</AlertDialogDescription>
|
|
1698
|
+
</AlertDialogHeader>
|
|
1699
|
+
<AlertDialogFooter>
|
|
1700
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
1701
|
+
<AlertDialogAction onClick={handleDelete}>
|
|
1702
|
+
Delete selected
|
|
1703
|
+
</AlertDialogAction>
|
|
1704
|
+
</AlertDialogFooter>
|
|
1705
|
+
</AlertDialogContent>
|
|
1706
|
+
</AlertDialog>
|
|
1707
|
+
)}
|
|
1723
1708
|
</div>
|
|
1724
1709
|
)}
|
|
1725
1710
|
{pagesLoaded && list && (
|