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 { fmt, toNum } from "@/lib/format";
import { useAppStore } from "@/store";
import { useParse } from "@/hooks/useParse";
import type {
ClaimOutput,
ClaimPayment,
@@ -465,7 +466,6 @@ export function Upload() {
const [file, setFile] = useState<File | null>(null);
const [kind, setKind] = useState<ParsedBatchKind>("837p");
const [payer, setPayer] = useState<string>(PAYERS_837[0]!.value);
const [running, setRunning] = useState(false);
const [stream, setStream] = useState<StreamState>({
items: [],
expectedTotal: null,
@@ -474,11 +474,16 @@ export function Upload() {
});
const addParsedBatch = useAppStore((s) => s.addParsedBatch);
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 expectedTotal = stream.expectedTotal;
const totalSoFar = stream.items.length;
const running = parseMutation.isPending;
const progressPct = useMemo(() => {
if (!expectedTotal || expectedTotal <= 0) return 0;
return Math.min(100, Math.round((totalSoFar / expectedTotal) * 100));
@@ -497,39 +502,41 @@ export function Upload() {
);
return;
}
setRunning(true);
setStream({ items: [], expectedTotal: null, passed: 0, failed: 0 });
const startedAt = new Date().toISOString();
const items: StreamedClaim[] = [];
try {
const onProgress: ParseProgress = (event) => {
if (event.type === "claim") {
items.push({ kind: "837p", data: event.data });
setStream((s) => ({
...s,
items: [...items],
}));
} else if (event.type === "claim_payment") {
items.push({ kind: "835", data: event.data });
setStream((s) => ({
...s,
items: [...items],
}));
} else if (event.type === "summary") {
setStream((s) => ({
...s,
expectedTotal: event.data.total_claims,
passed: event.data.passed,
failed: event.data.failed,
}));
}
// financial_info / trace / payer / payee are accepted silently;
// the page summary at the end reports the real pass/fail counts.
};
const onProgress: ParseProgress = (event) => {
if (event.type === "claim") {
items.push({ kind: "837p", data: event.data });
setStream((s) => ({
...s,
items: [...items],
}));
} else if (event.type === "claim_payment") {
items.push({ kind: "835", data: event.data });
setStream((s) => ({
...s,
items: [...items],
}));
} else if (event.type === "summary") {
setStream((s) => ({
...s,
expectedTotal: event.data.total_claims,
passed: event.data.passed,
failed: event.data.failed,
}));
}
// financial_info / trace / payer / payee are accepted silently;
// the page summary at the end reports the real pass/fail counts.
};
const summary = await (kind === "837p"
? api.parse837(file, { payer, onProgress })
: api.parse835(file, { payer, onProgress }));
try {
// mutateAsync triggers the mutation; the streaming callbacks above
// 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.
const finalSummary =
@@ -548,7 +555,7 @@ export function Upload() {
id,
kind,
inputFilename: file.name,
parsedAt: startedAt,
parsedAt: new Date().toISOString(),
claimCount: finalSummary.total_claims,
passed: finalSummary.passed,
failed: finalSummary.failed,
@@ -567,8 +574,6 @@ export function Upload() {
toast.error(
err instanceof Error ? err.message : "Failed to parse file"
);
} finally {
setRunning(false);
}
}