@stoker-platform/web-app 0.5.221 → 0.5.223

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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # @stoker-platform/web-app
2
2
 
3
+ ## 0.5.223
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @stoker-platform/node-client@0.5.86
9
+ - @stoker-platform/web-client@0.5.92
10
+ - @stoker-platform/utils@0.5.77
11
+
12
+ ## 0.5.222
13
+
14
+ ### Patch Changes
15
+
16
+ - feat: improve calendar data loading
17
+
3
18
  ## 0.5.221
4
19
 
5
20
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoker-platform/web-app",
3
- "version": "0.5.221",
3
+ "version": "0.5.223",
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.56.0",
54
- "@stoker-platform/node-client": "0.5.85",
55
- "@stoker-platform/utils": "0.5.76",
56
- "@stoker-platform/web-client": "0.5.91",
54
+ "@stoker-platform/node-client": "0.5.86",
55
+ "@stoker-platform/utils": "0.5.77",
56
+ "@stoker-platform/web-client": "0.5.92",
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/Calendar.tsx CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  CollectionMeta,
4
4
  CollectionSchema,
5
5
  Filter,
6
+ PreloadCacheRange,
6
7
  RangeFilter,
7
8
  StokerCollection,
8
9
  StokerPermissions,
@@ -37,12 +38,13 @@ import {
37
38
  tryFunction,
38
39
  } from "@stoker-platform/utils"
39
40
  import cloneDeep from "lodash/cloneDeep.js"
40
- import { useGlobalLoading } from "./providers/LoadingProvider"
41
+ import { useGlobalLoading, useRouteLoading } from "./providers/LoadingProvider"
41
42
  import { useToast } from "./hooks/use-toast"
42
43
  import { QueryConstraint, Timestamp, where } from "firebase/firestore"
43
44
  import { Table, TableBody, TableCell, TableRow } from "./components/ui/table"
44
45
  import { Grip } from "lucide-react"
45
46
  import { useDrag } from "react-dnd"
47
+ import { useLocation } from "react-router"
46
48
 
47
49
  import {
48
50
  CalendarOptions,
@@ -257,6 +259,7 @@ export function Calendar({
257
259
  const customization = getCollectionConfigModule(labels.collection)
258
260
  const permissions = getCurrentUserPermissions()
259
261
  if (!permissions?.Role) throw new Error("PERMISSION_DENIED")
262
+ const location = useLocation()
260
263
  const goToRecord = useGoToRecord()
261
264
  const { toast } = useToast()
262
265
  const [connectionStatus] = useConnection()
@@ -280,6 +283,7 @@ export function Calendar({
280
283
 
281
284
  const { optimisticUpdates, removeOptimisticUpdate, setOptimisticUpdate, removeCacheOptimistic } = useOptimistic()
282
285
  const { isGlobalLoading, setGlobalLoading } = useGlobalLoading()
286
+ const { setIsRouteLoading } = useRouteLoading()
283
287
  const [isInitialized, setIsInitialized] = useState(false)
284
288
  const [mainCollectionLoaded, setMainCollectionLoaded] = useState(false)
285
289
  const [additionalConfigLoaded, setAdditionalConfigLoaded] = useState(false)
@@ -307,6 +311,10 @@ export function Calendar({
307
311
  const preloadRange = preloadRangeAll?.[labels.collection]
308
312
  const isPreloadCacheEnabled = preloadCacheEnabled(collection)
309
313
 
314
+ const pendingPreload = useRef<{ preloadCacheRange: PreloadCacheRange; newRange: DateRange } | null>(null)
315
+ const isPreloading = useRef(false)
316
+ const isPreloadSpinnerShown = useRef(false)
317
+
310
318
  const constraintsWithoutRangeFilter = useCallback(
311
319
  (allFilters: Filter[]) => {
312
320
  const newFilters = cloneDeep(allFilters)
@@ -1259,57 +1267,116 @@ export function Calendar({
1259
1267
  }
1260
1268
  }, [currentViewSmall])
1261
1269
 
1270
+ const runPreload = useCallback(
1271
+ async (preloadCacheRange: PreloadCacheRange, newRange: DateRange) => {
1272
+ const preloadedCollections: StokerCollection[] = [labels.collection]
1273
+ const preloads: Promise<void>[] = [preloadCollection(labels.collection, undefined, preloadCacheRange)]
1274
+ for (const additionalCollection of calendarConfig?.additionalCollections || []) {
1275
+ // eslint-disable-next-line security/detect-object-injection
1276
+ const additionalSchema = schema.collections[additionalCollection]
1277
+ if (!additionalSchema?.preloadCache?.range || !preloadCacheEnabled(additionalSchema)) continue
1278
+ const preloadCacheRangeAdditional = cloneDeep(additionalSchema.preloadCache.range)
1279
+ preloadCacheRangeAdditional.start = preloadCacheRange.start
1280
+ preloadCacheRangeAdditional.end = preloadCacheRange.end
1281
+ preloads.push(preloadCollection(additionalCollection, undefined, preloadCacheRangeAdditional))
1282
+ preloadedCollections.push(additionalCollection)
1283
+ }
1284
+ try {
1285
+ await Promise.all(preloads)
1286
+ } catch (error) {
1287
+ console.error(error)
1288
+ return
1289
+ }
1290
+ setPreloadRange((prev) => {
1291
+ const next = { ...prev }
1292
+ for (const preloadedCollection of preloadedCollections) {
1293
+ // eslint-disable-next-line security/detect-object-injection
1294
+ next[preloadedCollection] = newRange
1295
+ }
1296
+ return next
1297
+ })
1298
+ },
1299
+ [calendarConfig, labels.collection, schema.collections, setPreloadRange],
1300
+ )
1301
+
1302
+ const requestPreload = useCallback(
1303
+ (preloadCacheRange: PreloadCacheRange, newRange: DateRange) => {
1304
+ pendingPreload.current = { preloadCacheRange, newRange }
1305
+ if (isPreloading.current) {
1306
+ if (!isPreloadSpinnerShown.current) {
1307
+ isPreloadSpinnerShown.current = true
1308
+ setIsRouteLoading("+", location.pathname, true)
1309
+ }
1310
+ return
1311
+ }
1312
+ isPreloading.current = true
1313
+ const run = async () => {
1314
+ try {
1315
+ while (pendingPreload.current) {
1316
+ const request = pendingPreload.current
1317
+ pendingPreload.current = null
1318
+ await runPreload(request.preloadCacheRange, request.newRange)
1319
+ }
1320
+ } finally {
1321
+ isPreloading.current = false
1322
+ if (isPreloadSpinnerShown.current) {
1323
+ isPreloadSpinnerShown.current = false
1324
+ setIsRouteLoading("-", location.pathname, true)
1325
+ }
1326
+ }
1327
+ }
1328
+ run()
1329
+ },
1330
+ [runPreload, setIsRouteLoading, location.pathname],
1331
+ )
1332
+
1333
+ useEffect(() => {
1334
+ return () => {
1335
+ if (isPreloadSpinnerShown.current) {
1336
+ isPreloadSpinnerShown.current = false
1337
+ setIsRouteLoading("-", location.pathname, true)
1338
+ }
1339
+ }
1340
+ }, [])
1341
+
1262
1342
  const handleDateChange = useCallback(
1263
- (date: Date) => {
1343
+ (date: Date, viewStart: Date, viewEnd: Date) => {
1264
1344
  if (!isInitialized || (isPreloadCacheEnabled && !preloadRange)) return
1265
1345
  const datetime = DateTime.fromJSDate(date)
1346
+ const requiredStart = DateTime.min(
1347
+ DateTime.fromJSDate(viewStart),
1348
+ datetime.minus(calendarConfig?.dataStartOffset || { months: 1 }),
1349
+ )
1350
+ const requiredEnd = DateTime.max(
1351
+ DateTime.fromJSDate(viewEnd),
1352
+ datetime.plus(calendarConfig?.dataEndOffset || { months: 1 }),
1353
+ )
1354
+ const expandStart = DateTime.min(
1355
+ DateTime.fromJSDate(viewStart),
1356
+ datetime.minus(calendarConfig?.dataStart || { months: 1 }),
1357
+ ).toJSDate()
1358
+ const expandEnd = DateTime.max(
1359
+ DateTime.fromJSDate(viewEnd),
1360
+ datetime.plus(calendarConfig?.dataEnd || { months: 1 }),
1361
+ ).toJSDate()
1266
1362
  const newRange = {} as DateRange
1267
1363
  if (currentField && preloadCache?.range) {
1268
1364
  if (preloadRange?.from && preloadRange?.to) {
1269
1365
  const preloadCacheRange = cloneDeep(preloadCache.range)
1270
- if (
1271
- datetime
1272
- .minus(calendarConfig?.dataStartOffset || { months: 1 })
1273
- .diff(DateTime.fromJSDate(preloadRange?.from)).milliseconds < 0
1274
- ) {
1275
- preloadCacheRange.start = datetime.minus(calendarConfig?.dataStart || { months: 1 }).toJSDate()
1366
+ if (requiredStart.diff(DateTime.fromJSDate(preloadRange?.from)).milliseconds < 0) {
1367
+ preloadCacheRange.start = expandStart
1276
1368
  } else {
1277
1369
  preloadCacheRange.start = preloadRange?.from
1278
1370
  }
1279
- if (
1280
- datetime
1281
- .plus(calendarConfig?.dataEndOffset || { months: 1 })
1282
- .diff(DateTime.fromJSDate(preloadRange?.to)).milliseconds > 0
1283
- ) {
1284
- preloadCacheRange.end = datetime.plus(calendarConfig?.dataEnd || { months: 1 }).toJSDate()
1371
+ if (requiredEnd.diff(DateTime.fromJSDate(preloadRange?.to)).milliseconds > 0) {
1372
+ preloadCacheRange.end = expandEnd
1285
1373
  } else {
1286
1374
  preloadCacheRange.end = preloadRange?.to
1287
1375
  }
1288
1376
  newRange.from = preloadCacheRange.start
1289
1377
  newRange.to = preloadCacheRange.end
1290
1378
  if (!isEqual(newRange, preloadRange)) {
1291
- preloadCollection(labels.collection, undefined, preloadCacheRange)
1292
- for (const additionalCollection of calendarConfig?.additionalCollections || []) {
1293
- // eslint-disable-next-line security/detect-object-injection
1294
- const additionalSchema = schema.collections[additionalCollection]
1295
- if (!additionalSchema?.preloadCache?.range || !preloadCacheEnabled(additionalSchema))
1296
- continue
1297
- const preloadCacheRangeAdditional = cloneDeep(additionalSchema.preloadCache.range)
1298
- preloadCacheRangeAdditional.start = preloadCacheRange.start
1299
- preloadCacheRangeAdditional.end = preloadCacheRange.end
1300
- preloadCollection(additionalCollection, undefined, preloadCacheRangeAdditional)
1301
- }
1302
- setPreloadRange((prev) => {
1303
- const next = {
1304
- ...prev,
1305
- [labels.collection]: newRange,
1306
- }
1307
- for (const additionalCollection of calendarConfig?.additionalCollections || []) {
1308
- // eslint-disable-next-line security/detect-object-injection
1309
- next[additionalCollection] = newRange
1310
- }
1311
- return next
1312
- })
1379
+ requestPreload(preloadCacheRange, newRange)
1313
1380
  }
1314
1381
  }
1315
1382
  } else {
@@ -1319,13 +1386,11 @@ export function Calendar({
1319
1386
  if (
1320
1387
  calendarConfig.dataStart &&
1321
1388
  calendarConfig.dataEnd &&
1322
- (datetime.minus(calendarConfig?.dataStartOffset || { months: 1 }).diff(DateTime.fromISO(range.from))
1323
- .milliseconds < 0 ||
1324
- datetime.plus(calendarConfig?.dataEndOffset || { months: 1 }).diff(DateTime.fromISO(range.to))
1325
- .milliseconds > 0)
1389
+ (requiredStart.diff(DateTime.fromISO(range.from)).milliseconds < 0 ||
1390
+ requiredEnd.diff(DateTime.fromISO(range.to)).milliseconds > 0)
1326
1391
  ) {
1327
- newRange.from = datetime.minus(calendarConfig.dataStart).toJSDate()
1328
- newRange.to = datetime.plus(calendarConfig.dataEnd).toJSDate()
1392
+ newRange.from = expandStart
1393
+ newRange.to = expandEnd
1329
1394
  if (!isEqual(JSON.stringify(newRange), JSON.stringify(range))) {
1330
1395
  const rangeFilterValue = {
1331
1396
  type: "range" as const,
@@ -1337,7 +1402,16 @@ export function Calendar({
1337
1402
  }
1338
1403
  }
1339
1404
  },
1340
- [calendarConfig, rangeFilter, preloadCache, currentField, preloadRange, isInitialized, isPreloadCacheEnabled],
1405
+ [
1406
+ calendarConfig,
1407
+ rangeFilter,
1408
+ preloadCache,
1409
+ currentField,
1410
+ preloadRange,
1411
+ isInitialized,
1412
+ isPreloadCacheEnabled,
1413
+ requestPreload,
1414
+ ],
1341
1415
  )
1342
1416
 
1343
1417
  if (!calendarConfig || !permissions || (isPreloadCacheEnabled && preloadCache?.range && !currentField)) return null
@@ -1448,7 +1522,7 @@ export function Calendar({
1448
1522
  datesSet={(arg: DatesSetArg) => {
1449
1523
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1450
1524
  const date = (arg.view as any).getCurrentData().calendarApi.getDate()
1451
- handleDateChange(date)
1525
+ handleDateChange(date, arg.start, arg.end)
1452
1526
  setState(
1453
1527
  `collection-calendar-large-date-${labels.collection.toLowerCase()}`,
1454
1528
  "calendar-large-date",
@@ -1480,7 +1554,7 @@ export function Calendar({
1480
1554
  datesSet={(arg: DatesSetArg) => {
1481
1555
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1482
1556
  const date = (arg.view as any).getCurrentData().calendarApi.getDate()
1483
- handleDateChange(date)
1557
+ handleDateChange(date, arg.start, arg.end)
1484
1558
  setState(
1485
1559
  `collection-calendar-small-date-${labels.collection.toLowerCase()}`,
1486
1560
  "calendar-small-date",
@@ -875,6 +875,7 @@ function Collection({
875
875
  }
876
876
  }, [location.pathname, setIsRouteLoading])
877
877
  const cacheLoaded = useCallback(() => {
878
+ if (!isCacheLoading.current) return
878
879
  isCacheLoading.current = false
879
880
  setIsRouteLoading("-", location.pathname, true)
880
881
  setCacheLoadingCompleted(true)
@@ -883,14 +884,18 @@ function Collection({
883
884
  const hasRunSingleton = useRef(false)
884
885
 
885
886
  useEffect(() => {
887
+ // Prevent UI flicker
888
+ const cacheLoadedDelayed = () => setTimeout(() => cacheLoaded(), 100)
886
889
  if (isPreloadCacheEnabled) {
887
890
  const isPreloading = getLoadingState()[labels.collection]
888
891
  if (!isPreloading || isPreloading === "Loading") {
889
892
  cacheLoading()
893
+ } else if (isPreloading === "Loaded") {
894
+ cacheLoadingStarted.current = true
895
+ setCacheLoadingCompleted(true)
890
896
  }
891
897
  document.addEventListener(`stoker:loading:${labels.collection}`, cacheLoading)
892
- // Prevent UI flicker
893
- document.addEventListener(`stoker:loaded:${labels.collection}`, () => setTimeout(() => cacheLoaded(), 100))
898
+ document.addEventListener(`stoker:loaded:${labels.collection}`, cacheLoadedDelayed)
894
899
  }
895
900
 
896
901
  const getSingleton = async () => {
@@ -1480,7 +1485,7 @@ function Collection({
1480
1485
  unsubscribe.current = {}
1481
1486
  if (isPreloadCacheEnabled) {
1482
1487
  document.removeEventListener(`stoker:loading:${labels.collection}`, cacheLoading)
1483
- document.removeEventListener(`stoker:loaded:${labels.collection}`, cacheLoaded)
1488
+ document.removeEventListener(`stoker:loaded:${labels.collection}`, cacheLoadedDelayed)
1484
1489
  }
1485
1490
  if (relationList?.loadAll) {
1486
1491
  const unsubscribes = getPreloadListeners()[`${labels.collection}-${relationList.field}`]