mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
feat(pwa): implement real offline mode with IndexedDB sync
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
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
Reference in New Issue
Block a user