import { useCallback, useEffect, useRef, useState } from 'react'; import { toast } from 'sonner'; import { Button } from '../../../components/ui/button'; import { describeWriteFailure } from '../../../utils/fetchFailureMessage'; import { useAuthServiceContext, useDocumentReferenceServiceContext } from '../../../services'; import { filingOwnerOf, forgetFiling, rememberedFilings, type FilingApi, type FilingState } from '../../../services/DocumentFilingService'; import { useSaveScope } from '../../../utils/saveOperation'; // Only an answered false proves no upload is held; anything else keeps the filing's identity. const settled = (row: FilingState) => row.state === 'filed' && row.evidence === 'settled' && row.uploadCustody === false; const uploadedCount = (row: FilingState) => row.uploads.filter((upload) => upload.observed === 'uploaded').length; const describe = (row: FilingState): string => { if (row.state === 'superseded') return 'This edit ended because the document changed. Its uploads remain available to Check.'; if (row.state === 'abandoned') return 'Discarded without saving. An upload sent earlier may still arrive; Check keeps track of it.'; if (row.state === 'filed' && row.evidence === 'settled') return 'Saved. Other uploads for this filing remain in custody.'; if (row.state === 'filed') return 'Saved. Its record of who saved it is still being sent.'; if (row.state === 'sealed') return 'Saving stopped before it finished. Resume finishes this save; it never saves twice.'; const uploaded = uploadedCount(row); return uploaded ? `Not saved. ${uploaded} ${uploaded === 1 ? 'file was' : 'files were'} uploaded for it.` : 'Not saved. No file for it has been seen yet; one sent earlier may still arrive.'; }; /** * THE VIEWER'S UNFINISHED DOCUMENT SAVES AT THIS PRACTICE, from the server's list and the filings this browser kept. * Check only reads; Resume finishes a save that was sealed; Discard stops one never saved, keeping any uploaded file. */ export const OutstandingFilings = ({ refreshKey, onFiled }: { refreshKey?: unknown; onFiled?: () => void }) => { const api = useDocumentReferenceServiceContext().filings as FilingApi | undefined; const { viewer, practice } = filingOwnerOf(useAuthServiceContext()); // Every rendered owner transition has a distinct identity, including A -> B -> A before effects run. const ownerRef = useRef({ viewer, practice, serial: 0 }); if (ownerRef.current.viewer !== viewer || ownerRef.current.practice !== practice) ownerRef.current = { viewer, practice, serial: ownerRef.current.serial + 1 }; const owner = ownerRef.current; const scope = useSaveScope(`${viewer}|${practice}|${owner.serial}`); const [panel, setPanel] = useState<{ owner: typeof owner; rows: FilingState[]; busy?: string; unavailable?: boolean }>(); const shown = panel?.owner === owner ? panel : undefined; const rows = shown?.rows ?? []; const busy = shown?.busy; const load = useCallback(async () => { scope.renew(); if (!api || !viewer || !practice) return setPanel({ owner, rows: [] }); const operation = scope.start(); const belongs = () => operation.belongs() && ownerRef.current === owner; setPanel({ owner, rows: [], busy: 'load' }); try { const listed = await operation.step(() => api.list(operation.transport)); const kept = rememberedFilings(viewer, practice).filter((id) => !listed.some((row) => row.filingId === id)); const read = await operation.step(() => Promise.all(kept.map((id) => api.check(id, operation.transport)))); if (!belongs()) return; const all = [...listed, ...read]; for (const row of all) if (settled(row)) forgetFiling(row.filingId); setPanel({ owner, rows: all.filter((row) => !settled(row)) }); } catch { if (belongs()) setPanel({ owner, rows: [], unavailable: true }); } finally { operation.retire(); } }, [api, owner, practice, viewer, scope]); useEffect(() => { void load(); }, [load, refreshKey]); const act = async (row: FilingState, action: 'check' | 'resume' | 'abandon') => { if (!api || panel?.owner !== owner || busy || !rows.includes(row)) return; scope.renew(); const operation = scope.start(); const belongs = () => operation.belongs() && ownerRef.current === owner; setPanel({ owner, rows, busy: row.filingId }); try { const next = await operation.step(() => api[action](row.filingId, operation.transport), action !== 'check'); if (!belongs()) return; if (action === 'resume' && next.state === 'filed') onFiled?.(); if (!belongs()) return; if (settled(next)) forgetFiling(row.filingId); setPanel({ owner, rows: rows.flatMap((each) => each.filingId !== row.filingId ? [each] : settled(next) ? [] : [next]) }); } catch (err) { if (!belongs()) return; if (action === 'resume' && (err as { body?: { code?: string } })?.body?.code === 'FILING_SUPERSEDED') { try { const ended = await operation.step(() => api.check(row.filingId, operation.transport)); if (!belongs()) return; if (ended.state === 'superseded') { setPanel({ owner, rows: rows.map((each) => each.filingId === row.filingId ? ended : each) }); return; } } catch { if (!belongs()) return; } } toast.error(describeWriteFailure(err, 'This save could not be reached just now')); setPanel({ owner, rows, unavailable: true }); } finally { if (belongs()) setPanel((current) => current?.owner === owner ? { ...current, busy: undefined } : current); operation.retire(); } }; if (!rows.length && !shown?.unavailable) return null; return (

Unfinished document saves

{shown?.unavailable &&

Document saves could not be read. Their recovery identities have been kept.

}
); };