@stoker-platform/web-app 0.5.52 → 0.5.54
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 +18 -0
- package/package.json +4 -4
- package/src/Collection.tsx +86 -22
- package/src/DashboardChart.tsx +108 -71
- package/src/DashboardMetric.tsx +1 -2
- package/src/DashboardReminder.tsx +1 -1
- package/src/List.tsx +13 -2
- package/src/components/ui/chart.tsx +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @stoker-platform/web-app
|
|
2
2
|
|
|
3
|
+
## 0.5.54
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- feat: improve charts
|
|
8
|
+
- @stoker-platform/node-client@0.5.36
|
|
9
|
+
- @stoker-platform/utils@0.5.30
|
|
10
|
+
- @stoker-platform/web-client@0.5.32
|
|
11
|
+
|
|
12
|
+
## 0.5.53
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- feat: add custom list actions
|
|
17
|
+
- @stoker-platform/node-client@0.5.35
|
|
18
|
+
- @stoker-platform/utils@0.5.29
|
|
19
|
+
- @stoker-platform/web-client@0.5.31
|
|
20
|
+
|
|
3
21
|
## 0.5.52
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stoker-platform/web-app",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.54",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"scripts": {
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
52
52
|
"@react-google-maps/api": "^2.20.8",
|
|
53
53
|
"@sentry/react": "^10.43.0",
|
|
54
|
-
"@stoker-platform/node-client": "0.5.
|
|
55
|
-
"@stoker-platform/utils": "0.5.
|
|
56
|
-
"@stoker-platform/web-client": "0.5.
|
|
54
|
+
"@stoker-platform/node-client": "0.5.36",
|
|
55
|
+
"@stoker-platform/utils": "0.5.30",
|
|
56
|
+
"@stoker-platform/web-client": "0.5.32",
|
|
57
57
|
"@tanstack/react-table": "^8.21.3",
|
|
58
58
|
"@types/react": "18.3.13",
|
|
59
59
|
"@types/react-dom": "18.3.1",
|
package/src/Collection.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
CardsConfig,
|
|
5
5
|
CollectionField,
|
|
6
6
|
CollectionSchema,
|
|
7
|
+
CustomListAction,
|
|
7
8
|
Filter,
|
|
8
9
|
FormList,
|
|
9
10
|
ImagesConfig,
|
|
@@ -206,6 +207,7 @@ function Collection({
|
|
|
206
207
|
const [isOfflineDisabled, setIsOfflineDisabled] = useState<boolean | undefined>(undefined)
|
|
207
208
|
const [restrictExport, setRestrictExport] = useState<StokerRole[] | undefined>(undefined)
|
|
208
209
|
const [disableCreate, setDisableCreate] = useState<boolean>(false)
|
|
210
|
+
const [customListActions, setCustomListActions] = useState<CustomListAction[] | undefined>(undefined)
|
|
209
211
|
|
|
210
212
|
const [table, setTable] = useState<Table<StokerRecord> | undefined>(undefined)
|
|
211
213
|
const [listConfig, setListConfig] = useState<ListConfig | undefined>(undefined)
|
|
@@ -840,6 +842,9 @@ function Collection({
|
|
|
840
842
|
)
|
|
841
843
|
setDisableCreate(!!disableCreate)
|
|
842
844
|
const filters = (await getCachedConfigValue(customization, [...collectionAdminPath, "filters"])) || []
|
|
845
|
+
const customListActions =
|
|
846
|
+
(await getCachedConfigValue(customization, [...collectionAdminPath, "customListActions"])) || []
|
|
847
|
+
setCustomListActions(customListActions)
|
|
843
848
|
|
|
844
849
|
const statusField = await getCachedConfigValue(customization, [...collectionAdminPath, "statusField"])
|
|
845
850
|
setStatusField(statusField)
|
|
@@ -1883,26 +1888,84 @@ function Collection({
|
|
|
1883
1888
|
)}
|
|
1884
1889
|
</ToggleGroup>
|
|
1885
1890
|
)}
|
|
1886
|
-
{!formList &&
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1891
|
+
{!formList && tab === "list" && (
|
|
1892
|
+
<>
|
|
1893
|
+
{customListActions &&
|
|
1894
|
+
customListActions.some(
|
|
1895
|
+
(action) => !action.condition || action.condition(),
|
|
1896
|
+
) ? (
|
|
1897
|
+
<DropdownMenu>
|
|
1898
|
+
<DropdownMenuTrigger>
|
|
1899
|
+
<Button
|
|
1900
|
+
type="button"
|
|
1901
|
+
size="sm"
|
|
1902
|
+
variant="outline"
|
|
1903
|
+
className="hidden sm:flex h-7 gap-1"
|
|
1904
|
+
>
|
|
1905
|
+
<span className="sr-only sm:not-sr-only sm:whitespace-nowrap">
|
|
1906
|
+
Actions
|
|
1907
|
+
</span>
|
|
1908
|
+
<ChevronsUpDown className="h-3.5 w-3.5" />
|
|
1909
|
+
</Button>
|
|
1910
|
+
</DropdownMenuTrigger>
|
|
1911
|
+
<DropdownMenuContent>
|
|
1912
|
+
{(!restrictExport ||
|
|
1913
|
+
restrictExport.includes(permissions.Role)) && (
|
|
1914
|
+
<DropdownMenuItem
|
|
1915
|
+
key="export"
|
|
1916
|
+
onClick={handleExport}
|
|
1917
|
+
disabled={
|
|
1918
|
+
!list.default?.length ||
|
|
1919
|
+
isRouteLoading.has(location.pathname)
|
|
1920
|
+
}
|
|
1921
|
+
>
|
|
1922
|
+
<File className="h-3.5 w-3.5 shrink-0 mr-1" />
|
|
1923
|
+
<span className="sr-only sm:not-sr-only sm:whitespace-nowrap">
|
|
1924
|
+
Export
|
|
1925
|
+
</span>
|
|
1926
|
+
</DropdownMenuItem>
|
|
1927
|
+
)}
|
|
1928
|
+
{customListActions
|
|
1929
|
+
.filter(
|
|
1930
|
+
(action) => !action.condition || action.condition(),
|
|
1931
|
+
)
|
|
1932
|
+
.map((action) => (
|
|
1933
|
+
<DropdownMenuItem
|
|
1934
|
+
key={action.title}
|
|
1935
|
+
onClick={action.action}
|
|
1936
|
+
>
|
|
1937
|
+
{action.icon &&
|
|
1938
|
+
createElement(action.icon, {
|
|
1939
|
+
className: "h-3.5 w-3.5 shrink-0 mr-1",
|
|
1940
|
+
})}
|
|
1941
|
+
<span className="sr-only sm:not-sr-only sm:whitespace-nowrap">
|
|
1942
|
+
{action.title}
|
|
1943
|
+
</span>
|
|
1944
|
+
</DropdownMenuItem>
|
|
1945
|
+
))}
|
|
1946
|
+
</DropdownMenuContent>
|
|
1947
|
+
</DropdownMenu>
|
|
1948
|
+
) : (
|
|
1949
|
+
(!restrictExport || restrictExport.includes(permissions.Role)) && (
|
|
1950
|
+
<Button
|
|
1951
|
+
type="button"
|
|
1952
|
+
size="sm"
|
|
1953
|
+
variant="outline"
|
|
1954
|
+
disabled={
|
|
1955
|
+
!list.default?.length ||
|
|
1956
|
+
isRouteLoading.has(location.pathname)
|
|
1957
|
+
}
|
|
1958
|
+
className="hidden sm:flex h-7 gap-1"
|
|
1959
|
+
onClick={handleExport}
|
|
1960
|
+
>
|
|
1961
|
+
<File className="h-3.5 w-3.5" />
|
|
1962
|
+
<span className="sr-only sm:not-sr-only sm:whitespace-nowrap">
|
|
1963
|
+
Export
|
|
1964
|
+
</span>
|
|
1965
|
+
</Button>
|
|
1966
|
+
)
|
|
1967
|
+
)}
|
|
1968
|
+
{(!restrictExport || restrictExport.includes(permissions.Role)) && (
|
|
1906
1969
|
<CSVLink
|
|
1907
1970
|
ref={csvLinkRef}
|
|
1908
1971
|
className="hidden"
|
|
@@ -1911,8 +1974,9 @@ function Collection({
|
|
|
1911
1974
|
filename={`${collectionTitle}.csv`}
|
|
1912
1975
|
target="_blank"
|
|
1913
1976
|
/>
|
|
1914
|
-
|
|
1915
|
-
|
|
1977
|
+
)}
|
|
1978
|
+
</>
|
|
1979
|
+
)}
|
|
1916
1980
|
{(tab === "cards" || tab === "images") && (
|
|
1917
1981
|
<DropdownMenu>
|
|
1918
1982
|
<DropdownMenuTrigger asChild>
|
package/src/DashboardChart.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "./components/ui/card"
|
|
|
3
3
|
import { useCallback, useEffect, useMemo, useState, useRef } from "react"
|
|
4
4
|
import { getField, getFieldCustomization, tryFunction } from "@stoker-platform/utils"
|
|
5
5
|
import { getCollectionConfigModule, getLoadingState, getSchema, getTimezone } from "@stoker-platform/web-client"
|
|
6
|
-
import { Timestamp, Unsubscribe
|
|
6
|
+
import { Timestamp, Unsubscribe } from "firebase/firestore"
|
|
7
7
|
import { DateTime } from "luxon"
|
|
8
8
|
import {
|
|
9
9
|
ChartConfig,
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
ChartTooltip,
|
|
14
14
|
ChartTooltipContent,
|
|
15
15
|
} from "./components/ui/chart"
|
|
16
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./components/ui/select"
|
|
17
16
|
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
|
18
17
|
import { getData } from "./utils/getData"
|
|
19
18
|
import { preloadCacheEnabled } from "./utils/preloadCacheEnabled"
|
|
@@ -63,7 +62,7 @@ export const DashboardChart = ({ chart, title, collection }: DashboardChartProps
|
|
|
63
62
|
}, [])
|
|
64
63
|
|
|
65
64
|
const constraints = useMemo(() => {
|
|
66
|
-
const existingConstraints
|
|
65
|
+
const existingConstraints = [...(chart.constraints || [])]
|
|
67
66
|
if (softDelete) {
|
|
68
67
|
existingConstraints.push(["Archived", "==", false])
|
|
69
68
|
}
|
|
@@ -108,23 +107,59 @@ export const DashboardChart = ({ chart, title, collection }: DashboardChartProps
|
|
|
108
107
|
|
|
109
108
|
type ChartData = { date: string; metric1: number; metric2?: number }[]
|
|
110
109
|
|
|
111
|
-
const [timeRange, setTimeRange] = useState<string | undefined>(undefined)
|
|
112
|
-
|
|
113
|
-
useEffect(() => {
|
|
114
|
-
if (chart.type === "area") {
|
|
115
|
-
setTimeRange(chart.defaultRange || "30d")
|
|
116
|
-
}
|
|
117
|
-
}, [])
|
|
118
|
-
|
|
119
110
|
const chartData: ChartData = useMemo(() => {
|
|
120
111
|
if (!results) return []
|
|
121
112
|
const chartData: ChartData = []
|
|
122
|
-
|
|
113
|
+
const interval = chart.interval || "day"
|
|
114
|
+
let numberOfIntervals = chart.numberOfIntervals
|
|
115
|
+
if (!numberOfIntervals) {
|
|
116
|
+
if (interval === "day") {
|
|
117
|
+
numberOfIntervals = 90
|
|
118
|
+
} else if (interval === "month") {
|
|
119
|
+
numberOfIntervals = 6
|
|
120
|
+
} else if (interval === "year") {
|
|
121
|
+
numberOfIntervals = 3
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const offset = chart.offset || 0
|
|
125
|
+
if (chart.formula1) {
|
|
126
|
+
results?.forEach((record: StokerRecord) => {
|
|
127
|
+
if (!record[chart.dateField] || !chart.formula1) return
|
|
128
|
+
const date = DateTime.fromJSDate((record[chart.dateField] as Timestamp).toDate(), {
|
|
129
|
+
zone: timezone,
|
|
130
|
+
})
|
|
131
|
+
.startOf(interval)
|
|
132
|
+
.toISO()
|
|
133
|
+
?.split("T")[0]
|
|
134
|
+
const metric1 = chart.formula1(record)
|
|
135
|
+
let metric2
|
|
136
|
+
if (chart.formula2) {
|
|
137
|
+
metric2 = chart.formula2(record)
|
|
138
|
+
}
|
|
139
|
+
if (date && (metric1 || metric2)) {
|
|
140
|
+
const existingInterval = chartData.find((item) => item.date === date)
|
|
141
|
+
if (existingInterval) {
|
|
142
|
+
existingInterval.metric1 += metric1
|
|
143
|
+
if (existingInterval.metric2 && metric2) {
|
|
144
|
+
existingInterval.metric2 += metric2
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
chartData.push({ date, metric1, metric2 })
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
chartData.sort((a, b) => {
|
|
152
|
+
if (a.date < b.date) return -1
|
|
153
|
+
if (a.date > b.date) return 1
|
|
154
|
+
return 0
|
|
155
|
+
})
|
|
156
|
+
} else if (chart.metricField1) {
|
|
123
157
|
results?.forEach((record: StokerRecord) => {
|
|
124
158
|
if (!record[chart.dateField] || !chart.metricField1) return
|
|
125
159
|
const date = DateTime.fromJSDate((record[chart.dateField] as Timestamp).toDate(), {
|
|
126
160
|
zone: timezone,
|
|
127
161
|
})
|
|
162
|
+
.startOf(interval)
|
|
128
163
|
.toISO()
|
|
129
164
|
?.split("T")[0]
|
|
130
165
|
const metric1 = record[chart.metricField1]
|
|
@@ -133,7 +168,15 @@ export const DashboardChart = ({ chart, title, collection }: DashboardChartProps
|
|
|
133
168
|
metric2 = record[chart.metricField2]
|
|
134
169
|
}
|
|
135
170
|
if (date && (metric1 || metric2)) {
|
|
136
|
-
chartData.
|
|
171
|
+
const existingInterval = chartData.find((item) => item.date === date)
|
|
172
|
+
if (existingInterval) {
|
|
173
|
+
existingInterval.metric1 += metric1
|
|
174
|
+
if (existingInterval.metric2 && metric2) {
|
|
175
|
+
existingInterval.metric2 += metric2
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
chartData.push({ date, metric1, metric2 })
|
|
179
|
+
}
|
|
137
180
|
}
|
|
138
181
|
})
|
|
139
182
|
chartData.sort((a, b) => {
|
|
@@ -149,6 +192,7 @@ export const DashboardChart = ({ chart, title, collection }: DashboardChartProps
|
|
|
149
192
|
const date = DateTime.fromJSDate((record[chart.dateField] as Timestamp).toDate(), {
|
|
150
193
|
zone: timezone,
|
|
151
194
|
})
|
|
195
|
+
.startOf(interval)
|
|
152
196
|
.toISO()
|
|
153
197
|
?.split("T")[0]
|
|
154
198
|
if (date) {
|
|
@@ -172,27 +216,35 @@ export const DashboardChart = ({ chart, title, collection }: DashboardChartProps
|
|
|
172
216
|
}
|
|
173
217
|
|
|
174
218
|
return chartData?.filter((item) => {
|
|
175
|
-
const date =
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return date >= startDate
|
|
219
|
+
const date = DateTime.fromISO(item.date).setZone(timezone).startOf(interval).toJSDate()
|
|
220
|
+
const startDate = DateTime.now()
|
|
221
|
+
.setZone(timezone)
|
|
222
|
+
.startOf(interval)
|
|
223
|
+
.plus({ [`${interval}s`]: offset })
|
|
224
|
+
.toJSDate()
|
|
225
|
+
const endDate = DateTime.now()
|
|
226
|
+
.setZone(timezone)
|
|
227
|
+
.endOf(interval)
|
|
228
|
+
.plus({ [`${interval}s`]: (numberOfIntervals || 0) + offset })
|
|
229
|
+
.toJSDate()
|
|
230
|
+
return date >= startDate && date < endDate
|
|
187
231
|
})
|
|
188
|
-
}, [results
|
|
232
|
+
}, [results])
|
|
189
233
|
|
|
190
234
|
const metricField1 = chart.metricField1 ? getField(fields, chart.metricField1) : undefined
|
|
191
235
|
const metricField1Customization = metricField1 ? getFieldCustomization(metricField1, customization) : undefined
|
|
192
|
-
const metricField1Title =
|
|
236
|
+
const metricField1Title =
|
|
237
|
+
tryFunction(chart.label1) ||
|
|
238
|
+
tryFunction(metricField1Customization?.admin?.label) ||
|
|
239
|
+
metricField1?.name ||
|
|
240
|
+
"Total"
|
|
193
241
|
const metricField2 = chart.metricField2 ? getField(fields, chart.metricField2) : undefined
|
|
194
242
|
const metricField2Customization = metricField2 ? getFieldCustomization(metricField2, customization) : undefined
|
|
195
|
-
const metricField2Title =
|
|
243
|
+
const metricField2Title =
|
|
244
|
+
tryFunction(chart.label2) ||
|
|
245
|
+
tryFunction(metricField2Customization?.admin?.label) ||
|
|
246
|
+
metricField2?.name ||
|
|
247
|
+
"Total"
|
|
196
248
|
|
|
197
249
|
const chartConfig = {
|
|
198
250
|
visitors: {
|
|
@@ -211,48 +263,20 @@ export const DashboardChart = ({ chart, title, collection }: DashboardChartProps
|
|
|
211
263
|
return (
|
|
212
264
|
<div className="grid gap-3 flex-1 min-w-0 h-full w-full">
|
|
213
265
|
<Card className="pt-0 w-full h-full">
|
|
214
|
-
<div className="grid
|
|
215
|
-
<CardHeader className="flex flex-col justify-center gap-2 space-y-0 2xl:border-r pb-0 2xl:pb-5 pt-5 w-[200px]">
|
|
266
|
+
<div className="grid h-full">
|
|
267
|
+
<CardHeader className="flex flex-col justify-center gap-2 space-y-0 2xl:border-r pb-0 2xl:pb-5 pt-5 w-[200px] h-[52px]">
|
|
216
268
|
<div className="grid flex-1 gap-1">
|
|
217
269
|
<CardTitle>{metricTitle}</CardTitle>
|
|
218
270
|
</div>
|
|
219
|
-
{!isLoading && timeRange && !(isPreloadCacheEnabled && isCacheLoading) && (
|
|
220
|
-
<Select
|
|
221
|
-
// eslint-disable-next-line security/detect-object-injection
|
|
222
|
-
value={timeRange}
|
|
223
|
-
onValueChange={(value) =>
|
|
224
|
-
// eslint-disable-next-line security/detect-object-injection
|
|
225
|
-
setTimeRange(value)
|
|
226
|
-
}
|
|
227
|
-
>
|
|
228
|
-
<SelectTrigger className="w-[160px] rounded-lg" aria-label="Select a value">
|
|
229
|
-
<SelectValue placeholder="Last 3 months" />
|
|
230
|
-
</SelectTrigger>
|
|
231
|
-
<SelectContent className="rounded-xl">
|
|
232
|
-
<SelectItem value="90d" className="rounded-lg">
|
|
233
|
-
Last 3 months
|
|
234
|
-
</SelectItem>
|
|
235
|
-
<SelectItem value="30d" className="rounded-lg">
|
|
236
|
-
Last 30 days
|
|
237
|
-
</SelectItem>
|
|
238
|
-
<SelectItem value="7d" className="rounded-lg">
|
|
239
|
-
Last 7 days
|
|
240
|
-
</SelectItem>
|
|
241
|
-
</SelectContent>
|
|
242
|
-
</Select>
|
|
243
|
-
)}
|
|
244
271
|
</CardHeader>
|
|
245
272
|
<CardContent className="flex-1 px-2 sm:px-6 pb-0">
|
|
246
273
|
{connectionStatus === "online" || isPreloadCacheEnabled ? (
|
|
247
274
|
isLoading || (isPreloadCacheEnabled && isCacheLoading) ? (
|
|
248
|
-
<div className="flex items-center justify-center h-[
|
|
275
|
+
<div className="flex items-center justify-center h-[271px]">
|
|
249
276
|
<LoadingSpinner size={7} className="relative bottom-6" />
|
|
250
277
|
</div>
|
|
251
278
|
) : (
|
|
252
|
-
<ChartContainer
|
|
253
|
-
config={chartConfig}
|
|
254
|
-
className="aspect-auto w-full h-[250px] md:h-[225px] 2xl:h-[325px]"
|
|
255
|
-
>
|
|
279
|
+
<ChartContainer config={chartConfig} className="aspect-auto w-full h-[271px]">
|
|
256
280
|
<AreaChart data={chartData}>
|
|
257
281
|
<defs>
|
|
258
282
|
<linearGradient id="fill1" x1="0" y1="0" x2="0" y2="1">
|
|
@@ -273,22 +297,35 @@ export const DashboardChart = ({ chart, title, collection }: DashboardChartProps
|
|
|
273
297
|
minTickGap={32}
|
|
274
298
|
tickFormatter={(value) => {
|
|
275
299
|
const date = new Date(value)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
300
|
+
if (chart.interval === "month" || chart.interval === "year") {
|
|
301
|
+
return date.toLocaleDateString("en-US", {
|
|
302
|
+
month: "short",
|
|
303
|
+
})
|
|
304
|
+
} else {
|
|
305
|
+
return date.toLocaleDateString("en-US", {
|
|
306
|
+
month: "short",
|
|
307
|
+
day: "numeric",
|
|
308
|
+
})
|
|
309
|
+
}
|
|
280
310
|
}}
|
|
281
311
|
/>
|
|
282
|
-
<YAxis hide padding={{ top: 16 }} />
|
|
312
|
+
<YAxis hide={!tryFunction(chart.yAxis?.show)} padding={{ top: 16 }} />
|
|
283
313
|
<ChartTooltip
|
|
284
314
|
cursor={false}
|
|
285
315
|
content={
|
|
286
316
|
<ChartTooltipContent
|
|
287
317
|
labelFormatter={(value) => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
318
|
+
const date = new Date(value)
|
|
319
|
+
if (chart.interval === "month" || chart.interval === "year") {
|
|
320
|
+
return date.toLocaleDateString("en-US", {
|
|
321
|
+
month: "short",
|
|
322
|
+
})
|
|
323
|
+
} else {
|
|
324
|
+
return date.toLocaleDateString("en-US", {
|
|
325
|
+
month: "short",
|
|
326
|
+
day: "numeric",
|
|
327
|
+
})
|
|
328
|
+
}
|
|
292
329
|
}}
|
|
293
330
|
indicator="dot"
|
|
294
331
|
/>
|
|
@@ -301,7 +338,7 @@ export const DashboardChart = ({ chart, title, collection }: DashboardChartProps
|
|
|
301
338
|
stroke="var(--chart-dark)"
|
|
302
339
|
stackId="a"
|
|
303
340
|
/>
|
|
304
|
-
{metricField2 && (
|
|
341
|
+
{(metricField2 || chart.formula2) && (
|
|
305
342
|
<Area
|
|
306
343
|
dataKey="metric2"
|
|
307
344
|
type="natural"
|
|
@@ -310,7 +347,7 @@ export const DashboardChart = ({ chart, title, collection }: DashboardChartProps
|
|
|
310
347
|
stackId="a"
|
|
311
348
|
/>
|
|
312
349
|
)}
|
|
313
|
-
{metricField1 && (
|
|
350
|
+
{(metricField1 || chart.formula1) && (
|
|
314
351
|
<ChartLegend className="pb-3" content={<ChartLegendContent />} />
|
|
315
352
|
)}
|
|
316
353
|
</AreaChart>
|
package/src/DashboardMetric.tsx
CHANGED
|
@@ -9,7 +9,6 @@ import { getData } from "./utils/getData"
|
|
|
9
9
|
import { preloadCacheEnabled } from "./utils/preloadCacheEnabled"
|
|
10
10
|
import { LoadingSpinner } from "./components/ui/loading-spinner"
|
|
11
11
|
import { useConnection } from "./providers/ConnectionProvider"
|
|
12
|
-
import { WhereFilterOp } from "firebase/firestore"
|
|
13
12
|
|
|
14
13
|
interface DashboardMetricProps {
|
|
15
14
|
metric: Metric
|
|
@@ -53,7 +52,7 @@ export const DashboardMetric = ({ metric, title, collection }: DashboardMetricPr
|
|
|
53
52
|
}, [])
|
|
54
53
|
|
|
55
54
|
const constraints = useMemo(() => {
|
|
56
|
-
const existingConstraints
|
|
55
|
+
const existingConstraints = [...(metric.constraints || [])]
|
|
57
56
|
if (softDelete) {
|
|
58
57
|
existingConstraints.push(["Archived", "==", false])
|
|
59
58
|
}
|
|
@@ -259,7 +259,7 @@ export const DashboardReminder = ({ reminder, title, collection }: DashboardRemi
|
|
|
259
259
|
colSpan={reminder.columns?.length || 1}
|
|
260
260
|
className="text-center h-10 text-primary/50"
|
|
261
261
|
>
|
|
262
|
-
<span>{collectionTitle}
|
|
262
|
+
<span>{collectionTitle} not available in offline mode.</span>
|
|
263
263
|
</TableCell>
|
|
264
264
|
</TableRow>
|
|
265
265
|
</TableBody>
|
package/src/List.tsx
CHANGED
|
@@ -1219,6 +1219,7 @@ export function List({
|
|
|
1219
1219
|
const date = DateTime.fromJSDate((record[metric.dateField] as Timestamp).toDate(), {
|
|
1220
1220
|
zone: timezone,
|
|
1221
1221
|
})
|
|
1222
|
+
.startOf("day")
|
|
1222
1223
|
.toISO()
|
|
1223
1224
|
?.split("T")[0]
|
|
1224
1225
|
const metric1 = record[metric.metricField1]
|
|
@@ -1227,7 +1228,15 @@ export function List({
|
|
|
1227
1228
|
metric2 = record[metric.metricField2]
|
|
1228
1229
|
}
|
|
1229
1230
|
if (date && (metric1 || metric2)) {
|
|
1230
|
-
chartData.
|
|
1231
|
+
const existingInterval = chartData.find((item) => item.date === date)
|
|
1232
|
+
if (existingInterval) {
|
|
1233
|
+
existingInterval.metric1 += metric1
|
|
1234
|
+
if (existingInterval.metric2 && metric2) {
|
|
1235
|
+
existingInterval.metric2 += metric2
|
|
1236
|
+
}
|
|
1237
|
+
} else {
|
|
1238
|
+
chartData.push({ date, metric1, metric2 })
|
|
1239
|
+
}
|
|
1231
1240
|
}
|
|
1232
1241
|
})
|
|
1233
1242
|
chartData.sort((a, b) => {
|
|
@@ -1246,6 +1255,7 @@ export function List({
|
|
|
1246
1255
|
const date = DateTime.fromJSDate((record[metric.dateField] as Timestamp).toDate(), {
|
|
1247
1256
|
zone: timezone,
|
|
1248
1257
|
})
|
|
1258
|
+
.startOf("day")
|
|
1249
1259
|
.toISO()
|
|
1250
1260
|
?.split("T")[0]
|
|
1251
1261
|
|
|
@@ -1413,8 +1423,9 @@ export function List({
|
|
|
1413
1423
|
daysToSubtract = 7
|
|
1414
1424
|
}
|
|
1415
1425
|
const startDate = DateTime.now().setZone(timezone).toJSDate()
|
|
1426
|
+
const endDate = DateTime.now().setZone(timezone).toJSDate()
|
|
1416
1427
|
startDate.setDate(startDate.getDate() - daysToSubtract)
|
|
1417
|
-
return date >= startDate
|
|
1428
|
+
return date >= startDate && date <= endDate
|
|
1418
1429
|
})
|
|
1419
1430
|
|
|
1420
1431
|
return (
|