Passo 12 completo. Build limpo, AC server-side totalmente verificado. O que foi implementado: lib/queue/ — camada de persistência offline: db.ts — Dexie 4 com tabelas pending e deadLetters broadcast.ts — BroadcastChannel helper (mai-call-sync) para comunicar entre tabs sync.ts — loop de sync com retry/backoff: signPhotoUpload → PUT MinIO → create; 409 = sucesso; 4xx = dead-letter; erros de rede = paragem + retry na próxima volta SyncProvider — React Context que: Arranca sync ao reconectar (online event + visibilitychange) Polling de 10s como fallback Regista Background Sync API quando disponível Expõe pendingCount / deadLetterCount via useSyncState() Formulário (/maintenance/new) — refatorado: ao submeter, escreve em IndexedDB e navega imediatamente para /sent sem esperar pelo servidor. O SyncProvider processa a fila em background. Feedback visual: SyncChip na home: "Tudo sincronizado" / "N pedidos por enviar" / erro dead-letter /maintenance/sent: mostra "Em fila" (Clock) ou "Enviado" (CheckCircle2) reactivamente via BroadcastChannel Workbox (@ducanh2912/next-pwa) — app shell precaching ativo, para que o app carregue mesmo sem rede depois da primeira visita.
60 lines
1.8 KiB
TypeScript
60 lines
1.8 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
import Link from 'next/link';
|
|
import { CheckCircle2, Clock } from 'lucide-react';
|
|
import { db } from '@/lib/queue/db';
|
|
import { subscribeBroadcast } from '@/lib/queue/broadcast';
|
|
|
|
export function SentStatus({ cid }: { cid: string }) {
|
|
const [inQueue, setInQueue] = useState<boolean | null>(null); // null = loading
|
|
|
|
useEffect(() => {
|
|
async function check() {
|
|
const item = await db.pending.get(cid);
|
|
setInQueue(!!item);
|
|
}
|
|
check();
|
|
|
|
const unsub = subscribeBroadcast((msg) => {
|
|
if (msg.type === 'synced' && msg.clientRequestId === cid) setInQueue(false);
|
|
if (msg.type === 'dead-letter' && msg.clientRequestId === cid) setInQueue(false);
|
|
});
|
|
|
|
return unsub;
|
|
}, [cid]);
|
|
|
|
const pending = inQueue === true;
|
|
|
|
return (
|
|
<main className="mx-auto flex min-h-dvh max-w-lg flex-col items-center justify-center gap-6 p-6 text-center">
|
|
<div className="flex flex-col items-center gap-3">
|
|
{pending ? (
|
|
<Clock className="h-16 w-16 text-orange-400" />
|
|
) : (
|
|
<CheckCircle2 className="h-16 w-16 text-green-500" />
|
|
)}
|
|
<h1 className="text-2xl font-bold">
|
|
{pending ? 'Pedido em fila' : 'Pedido enviado'}
|
|
</h1>
|
|
{cid && (
|
|
<p className="font-mono text-xs text-muted-foreground" data-testid="request-cid">
|
|
{cid}
|
|
</p>
|
|
)}
|
|
<p className="text-sm text-muted-foreground">
|
|
{pending
|
|
? 'Será enviado assim que a ligação for restabelecida.'
|
|
: 'A equipa de manutenção foi notificada e irá tratar do problema.'}
|
|
</p>
|
|
</div>
|
|
<Link
|
|
href="/"
|
|
className="rounded-xl bg-primary px-8 py-3 font-semibold text-primary-foreground hover:opacity-90"
|
|
>
|
|
Voltar ao início
|
|
</Link>
|
|
</main>
|
|
);
|
|
}
|