mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 21:31:46 +00:00
b194e8317d
Add genuine offline read/write capability for trips: - Dexie IndexedDB schema (trips, places, packing, todo, budget, reservations, files, mutationQueue, syncMeta, blobCache) - Repo layer for all domains: offline reads from Dexie, writes optimistically to Dexie and enqueue mutations for later replay - Mutation queue with UUID idempotency keys (X-Idempotency-Key), FIFO flush, temp-ID reconciliation on 2xx, fail-and-continue on 4xx - Trip sync manager: caches all trips with end_date >= today or null, auto-evicts 7d after end_date, fetches bundle endpoint in one request - Map tile prefetcher: bbox from place coords, zooms 10-16, 50MB cap, warms SW cache via fetch - Sync triggers: network online → flush + syncAll; WS reconnect → flush only (rate-limiter safe); visibilitychange/30s → flush only - WS remoteEventHandler writes through to Dexie on every event - Server idempotency middleware + idempotency_keys table (migration 100, 24h TTL nightly cleanup) - GET /api/trips/:id/bundle endpoint for efficient single-request sync - OfflineBanner component: amber (offline) / blue (syncing) / hidden - OfflineTab in Settings: cached trip list, re-sync and clear actions - usePendingMutations hook for per-item pending indicators Closes #505 #541
44 lines
1.2 KiB
TypeScript
44 lines
1.2 KiB
TypeScript
/**
|
|
* usePendingMutations — returns the set of entity IDs that have a pending
|
|
* or syncing mutation for a given trip.
|
|
*
|
|
* Components use this to render a clock/pending indicator on list rows.
|
|
* Polls Dexie every 2 s so the indicator clears automatically once synced.
|
|
*/
|
|
import { useState, useEffect } from 'react'
|
|
import { mutationQueue } from '../sync/mutationQueue'
|
|
|
|
const POLL_MS = 2_000
|
|
|
|
export function usePendingMutations(tripId: number): Set<number> {
|
|
const [pendingIds, setPendingIds] = useState<Set<number>>(new Set())
|
|
|
|
useEffect(() => {
|
|
let cancelled = false
|
|
|
|
async function refresh() {
|
|
const pending = await mutationQueue.pending(tripId)
|
|
if (cancelled) return
|
|
|
|
const ids = new Set<number>()
|
|
for (const m of pending) {
|
|
// Extract entity id from the mutation URL (last numeric segment)
|
|
const match = m.url.match(/\/(\d+)$/)
|
|
if (match) ids.add(Number(match[1]))
|
|
// Also include tempId for offline-created items
|
|
if (m.tempId !== undefined) ids.add(m.tempId)
|
|
}
|
|
setPendingIds(ids)
|
|
}
|
|
|
|
refresh()
|
|
const timer = setInterval(refresh, POLL_MS)
|
|
return () => {
|
|
cancelled = true
|
|
clearInterval(timer)
|
|
}
|
|
}, [tripId])
|
|
|
|
return pendingIds
|
|
}
|