feat(frontend): refactor Upload page to use useParse hook

This commit is contained in:
Tyler
2026-06-19 19:46:12 -06:00
parent 7333f0f50e
commit 5c26d296be
+39 -34
View File
@@ -28,6 +28,7 @@ import {
import { api, type ParseProgress } from "@/lib/api"; import { api, type ParseProgress } from "@/lib/api";
import { fmt, toNum } from "@/lib/format"; import { fmt, toNum } from "@/lib/format";
import { useAppStore } from "@/store"; import { useAppStore } from "@/store";
import { useParse } from "@/hooks/useParse";
import type { import type {
ClaimOutput, ClaimOutput,
ClaimPayment, ClaimPayment,
@@ -465,7 +466,6 @@ export function Upload() {
const [file, setFile] = useState<File | null>(null); const [file, setFile] = useState<File | null>(null);
const [kind, setKind] = useState<ParsedBatchKind>("837p"); const [kind, setKind] = useState<ParsedBatchKind>("837p");
const [payer, setPayer] = useState<string>(PAYERS_837[0]!.value); const [payer, setPayer] = useState<string>(PAYERS_837[0]!.value);
const [running, setRunning] = useState(false);
const [stream, setStream] = useState<StreamState>({ const [stream, setStream] = useState<StreamState>({
items: [], items: [],
expectedTotal: null, expectedTotal: null,
@@ -474,11 +474,16 @@ export function Upload() {
}); });
const addParsedBatch = useAppStore((s) => s.addParsedBatch); const addParsedBatch = useAppStore((s) => s.addParsedBatch);
const parsedBatches = useAppStore((s) => s.parsedBatches); const parsedBatches = useAppStore((s) => s.parsedBatches);
// useParse wires the mutation; on success it invalidates every list query
// (batches, claims, remittances, providers, activity) so the other pages
// see the new data without manual refetching.
const parseMutation = useParse(kind);
const payerOptions = kind === "837p" ? PAYERS_837 : PAYERS_835; const payerOptions = kind === "837p" ? PAYERS_837 : PAYERS_835;
const expectedTotal = stream.expectedTotal; const expectedTotal = stream.expectedTotal;
const totalSoFar = stream.items.length; const totalSoFar = stream.items.length;
const running = parseMutation.isPending;
const progressPct = useMemo(() => { const progressPct = useMemo(() => {
if (!expectedTotal || expectedTotal <= 0) return 0; if (!expectedTotal || expectedTotal <= 0) return 0;
return Math.min(100, Math.round((totalSoFar / expectedTotal) * 100)); return Math.min(100, Math.round((totalSoFar / expectedTotal) * 100));
@@ -497,39 +502,41 @@ export function Upload() {
); );
return; return;
} }
setRunning(true);
setStream({ items: [], expectedTotal: null, passed: 0, failed: 0 }); setStream({ items: [], expectedTotal: null, passed: 0, failed: 0 });
const startedAt = new Date().toISOString();
const items: StreamedClaim[] = []; const items: StreamedClaim[] = [];
try { const onProgress: ParseProgress = (event) => {
const onProgress: ParseProgress = (event) => { if (event.type === "claim") {
if (event.type === "claim") { items.push({ kind: "837p", data: event.data });
items.push({ kind: "837p", data: event.data }); setStream((s) => ({
setStream((s) => ({ ...s,
...s, items: [...items],
items: [...items], }));
})); } else if (event.type === "claim_payment") {
} else if (event.type === "claim_payment") { items.push({ kind: "835", data: event.data });
items.push({ kind: "835", data: event.data }); setStream((s) => ({
setStream((s) => ({ ...s,
...s, items: [...items],
items: [...items], }));
})); } else if (event.type === "summary") {
} else if (event.type === "summary") { setStream((s) => ({
setStream((s) => ({ ...s,
...s, expectedTotal: event.data.total_claims,
expectedTotal: event.data.total_claims, passed: event.data.passed,
passed: event.data.passed, failed: event.data.failed,
failed: event.data.failed, }));
})); }
} // financial_info / trace / payer / payee are accepted silently;
// financial_info / trace / payer / payee are accepted silently; // the page summary at the end reports the real pass/fail counts.
// the page summary at the end reports the real pass/fail counts. };
};
const summary = await (kind === "837p" try {
? api.parse837(file, { payer, onProgress }) // mutateAsync triggers the mutation; the streaming callbacks above
: api.parse835(file, { payer, onProgress })); // populate `stream` as claims arrive, and the resolver returns the
// final BatchSummary from the "summary" NDJSON line.
const summary = await parseMutation.mutateAsync({
file,
options: { payer, onProgress },
});
// The final return value is a BatchSummary in the streaming case. // The final return value is a BatchSummary in the streaming case.
const finalSummary = const finalSummary =
@@ -548,7 +555,7 @@ export function Upload() {
id, id,
kind, kind,
inputFilename: file.name, inputFilename: file.name,
parsedAt: startedAt, parsedAt: new Date().toISOString(),
claimCount: finalSummary.total_claims, claimCount: finalSummary.total_claims,
passed: finalSummary.passed, passed: finalSummary.passed,
failed: finalSummary.failed, failed: finalSummary.failed,
@@ -567,8 +574,6 @@ export function Upload() {
toast.error( toast.error(
err instanceof Error ? err.message : "Failed to parse file" err instanceof Error ? err.message : "Failed to parse file"
); );
} finally {
setRunning(false);
} }
} }