All files / lib useOfflineState.ts

91.17% Statements 31/34
100% Branches 4/4
81.25% Functions 13/16
92.59% Lines 25/27

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78                                  893x 8943x 893x 753x     382x 382x 382x 382x 54x   382x 382x 54x 54x 54x   54x 37x     382x 54x         54x   382x 382x 54x 839x   54x   382x                                              
/**
 * @see frontend/docs/UX/layer-contracts/L1-data-layer.md §9.6
 * @see frontend/src/lib/onlineDetector.ts
 *
 * Offline-tilan seuranta hook. Kaikki arvot johdetaan automaattisesti:
 * - isOnline OnlineDetector-palvelusta (lazy-luku navigator.onLine:sta
 *   + window online/offline -eventtien kuuntelu)
 * - pendingCount mutationOutboxista
 * - lastSynced queryClientin tuoreimmasta dataUpdatedAt:stä
 *
 * Ei placeholder-proppeja — kaikki johdetaan automaattisesti.
 */
 
import { useState, useEffect } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import { subscribeOutbox, getOutboxEntries } from '@/lib/mutationOutbox'
import { getOnlineDetector } from '@/lib/onlineDetector'
 
export interface OfflineStateResult {
  isOnline: boolean
  pendingCount: number
  lastSynced: Date | null
}
 
function computeLastSynced(queryClient: ReturnType<typeof useQueryClient>): Date | null {
  const queries = queryClient.getQueryCache().getAll()
  const timestamps = queries
    .map((q) => q.state.dataUpdatedAt)
    .filter((t): t is number => t > 0)
 
  if (timestamps.length === 0) return null
  return new Date(Math.max(...timestamps))
}
 
export function useOfflineState(): OfflineStateResult {
  const queryClient = useQueryClient()
 
  const detector = getOnlineDetector()
 
  const [isOnline, setIsOnline] = useState(() => detector.isOnline())

  useEffect(() => {
    return detector.subscribe(() => setIsOnline(detector.isOnline()))
  }, [detector])
 
  const [pendingCount, setPendingCount] = useState(0)
 
  useEffect(() => {
    let cancelled = false
    getOutboxEntries().then((entries) => {
      if (!cancelled) setPendingCount(entries.length)
    })
    return () => { cancelled = true }
  }, [])
 
  useEffect(() => {
    const unsubscribe = subscribeOutbox(() => {
      getOutboxEntries().then((entries) => {
        setPendingCount(entries.length)
      })
    })
    return unsubscribe
  }, [])
 
  const [lastSynced, setLastSynced] = useState<Date | null>(() =>
    computeLastSynced(queryClient),
  )
 
  useEffect(() => {
    const unsubscribe = queryClient.getQueryCache().subscribe(() => {
      setLastSynced(computeLastSynced(queryClient))
    })
    return unsubscribe
  }, [queryClient])
 
  return { isOnline, pendingCount, lastSynced }
}