/** * useOfflineCache Hook * * React hook providing a reactive interface to the IndexedDB-backed * offline SOAP consultation cache. * * Exposes the full cached consultation list, individual lookup, * cache/remove operations, and online/offline status. */ import { useState, useEffect, useCallback, useRef } from 'react' import type { CachedConsultation } from '../utils/offlineCache' import * as offlineCache from '../utils/offlineCache' // ================================================================ // Hook return type // ================================================================ export interface UseOfflineCacheReturn { /** All cached consultations, newest first */ cachedConsultations: CachedConsultation[] /** True while the initial load from IndexedDB is in progress */ isLoading: boolean /** Cache a consultation record (also refreshes the list) */ cacheConsultation: (data: CachedConsultation) => Promise /** Look up a single cached consultation by ID */ getConsultation: (id: string) => Promise /** Remove a single cached consultation by ID */ removeConsultation: (id: string) => Promise /** Whether the browser is currently offline */ isOffline: boolean } // ================================================================ // Hook implementation // ================================================================ export function useOfflineCache(): UseOfflineCacheReturn { const [cachedConsultations, setCachedConsultations] = useState([]) const [isLoading, setIsLoading] = useState(true) const [isOffline, setIsOffline] = useState(!navigator.onLine) // Track mount state to prevent state updates after unmount const mountedRef = useRef(true) // ----- Load all cached consultations on mount ----- const refreshList = useCallback(async () => { try { const items = await offlineCache.getAllCachedConsultations() if (mountedRef.current) { setCachedConsultations(items) } } catch { // Silently ignore — cache reads failing shouldn't break the UI } }, []) useEffect(() => { mountedRef.current = true const load = async () => { setIsLoading(true) await refreshList() if (mountedRef.current) { setIsLoading(false) } } load() return () => { mountedRef.current = false } }, [refreshList]) // ----- Online/offline detection ----- useEffect(() => { const handleOnline = () => { if (mountedRef.current) { setIsOffline(false) } } const handleOffline = () => { if (mountedRef.current) { setIsOffline(true) } } window.addEventListener('online', handleOnline) window.addEventListener('offline', handleOffline) return () => { window.removeEventListener('online', handleOnline) window.removeEventListener('offline', handleOffline) } }, []) // ----- Cache a consultation ----- const cacheConsultation = useCallback( async (data: CachedConsultation) => { await offlineCache.cacheConsultation(data) await refreshList() }, [refreshList], ) // ----- Look up a single consultation ----- const getConsultation = useCallback( async (id: string): Promise => { return offlineCache.getCachedConsultation(id) }, [], ) // ----- Remove a single consultation ----- const removeConsultation = useCallback( async (id: string) => { await offlineCache.removeConsultation(id) await refreshList() }, [refreshList], ) return { cachedConsultations, isLoading, cacheConsultation, getConsultation, removeConsultation, isOffline, } }