All files / lib useOfflineState.ts

92.64% Statements 63/68
100% Branches 8/8
87.5% Functions 28/32
95.12% Lines 39/41

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                                  899x 9018x 899x 747x     378x 378x 378x 378x 57x 3x 378x 378x 54x 54x 54x   54x 36x 49x   404x 54x 49x 26x     54x   378x 378x 54x 845x   404x   378x 49x 26x         26x     49x 26x     49x 43x 4x   43x     49x    
/**
 * @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 }
}