74 lines
2.6 KiB
TypeScript
74 lines
2.6 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useState, useCallback, useRef } from 'react'
|
|
|
|
export function OfflineSync() {
|
|
const [pendingCount, setPendingCount] = useState(0)
|
|
const [syncing, setSyncing] = useState(false)
|
|
const syncingRef = useRef(false)
|
|
|
|
async function checkPending() {
|
|
const { getPendingCount } = await import('@/lib/offline/db')
|
|
setPendingCount(await getPendingCount())
|
|
}
|
|
|
|
const syncNow = useCallback(async () => {
|
|
if (syncingRef.current) return
|
|
syncingRef.current = true
|
|
setSyncing(true)
|
|
try {
|
|
const { getPendingReports, removePendingReport } = await import('@/lib/offline/db')
|
|
const reports = await getPendingReports()
|
|
for (const report of reports) {
|
|
const fd = new FormData()
|
|
fd.append('zone_token', report.zone_token)
|
|
fd.append('incident_type', report.incident_type)
|
|
fd.append('description', report.description)
|
|
fd.append('injury_involved', String(report.injury_involved))
|
|
fd.append('asset_involved', String(report.asset_involved))
|
|
if (report.medical_status) fd.append('medical_status', report.medical_status)
|
|
if (report.type_details) fd.append('type_details', JSON.stringify(report.type_details))
|
|
|
|
try {
|
|
const res = await fetch('/api/incidents', { method: 'POST', body: fd })
|
|
if (res.ok && report.id != null) {
|
|
await removePendingReport(report.id)
|
|
}
|
|
} catch {
|
|
// Network still unavailable — will retry on next online event
|
|
}
|
|
}
|
|
} finally {
|
|
syncingRef.current = false
|
|
await checkPending()
|
|
setSyncing(false)
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
checkPending()
|
|
const handleOnline = () => { syncNow() }
|
|
window.addEventListener('online', handleOnline)
|
|
return () => window.removeEventListener('online', handleOnline)
|
|
}, [syncNow])
|
|
|
|
if (pendingCount === 0) return null
|
|
|
|
return (
|
|
<div className="fixed bottom-4 left-4 right-4 bg-yellow-50 border border-yellow-300 rounded-lg p-3 flex items-center justify-between shadow-md z-50">
|
|
<span className="text-sm text-yellow-800 font-medium">
|
|
{pendingCount} report{pendingCount > 1 ? 's' : ''} saved offline
|
|
</span>
|
|
<button
|
|
onClick={syncNow}
|
|
disabled={syncing || !navigator.onLine}
|
|
className="text-xs bg-yellow-600 text-white px-3 py-1.5 rounded font-medium
|
|
hover:bg-yellow-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
>
|
|
{syncing ? 'Syncing…' : 'Sync now'}
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|