Files
cyclone/src/hooks/useRemitDetail.ts
T

104 lines
3.2 KiB
TypeScript

import { useQuery } from "@tanstack/react-query";
import { api, ApiError } from "@/lib/api";
import type { Remittance } from "@/types";
/**
* UI-facing remittance detail shape returned by `GET /api/remittances/{id}`.
*
* The backend mapper (`cyclone.store.to_ui_remittance_with_adjustments`)
* returns the standard `Remittance` fields plus the labeled `adjustments`
* array. We extend the base type locally with the additional 835-derived
* fields the backend emits — `payerClaimControlNumber` and `totalCharge`
* (which aren't part of the v1 `Remittance` interface but ARE part of the
* backend's `/api/remittances/{id}` payload). All newly-added fields are
* optional so the component degrades gracefully when the backend hasn't
* populated them (e.g. earlier rows without those columns populated).
*
* `payer` and `payee` blocks are also optional — the current detail
* endpoint does not surface these yet, but the PartiesGrid renders them
* when present so a future backend version lights up the cards without a
* frontend change.
*/
export interface RemitDetail extends Remittance {
payerClaimControlNumber?: string;
totalCharge?: number;
paymentMethod?: string | null;
paymentDate?: string | null;
payer?: {
name?: string | null;
id?: string | null;
address?: {
line1?: string;
line2?: string | null;
city?: string;
state?: string;
zip?: string;
} | null;
} | null;
payee?: {
name?: string | null;
npi?: string | null;
address?: {
line1?: string;
line2?: string | null;
city?: string;
state?: string;
zip?: string;
} | null;
} | null;
}
/**
* Per-remittance detail drawer query (RemitDrawer).
*
* Mirror of `useClaimDetail` — same return shape, same retry semantics.
* Returns `{ data, isLoading, isError, error, refetch }`:
* - `id === null` (drawer closed): the query is disabled and the hook
* short-circuits to the empty drawer state so a closed drawer doesn't
* burn a network request or a TanStack cache slot.
* - `id` is set: fetches `GET /api/remittances/{id}` via
* `api.getRemittance`. Cached 30 s so j/k navigation across remits
* reuses prior fetches within the drawer session.
* - On 404: the hook's retry predicate short-circuits (no retries) so
* the drawer's not-found state appears immediately rather than being
* masked by three back-to-back retry attempts.
*/
export function useRemitDetail(id: string | null): {
data: RemitDetail | null;
isLoading: boolean;
isError: boolean;
error: Error | null;
refetch: () => void;
} {
const q = useQuery<RemitDetail>({
queryKey: ["remit-detail", id],
queryFn: () => api.getRemittance<RemitDetail>(id as string),
enabled: id !== null,
staleTime: 30 * 1000,
retry: (failureCount, error) => {
if (error instanceof ApiError && error.status === 404) return false;
return failureCount < 3;
},
});
if (id === null) {
return {
data: null,
isLoading: false,
isError: false,
error: null,
refetch: () => {},
};
}
return {
data: q.data ?? null,
isLoading: q.isLoading,
isError: q.isError,
error: q.error,
refetch: () => {
void q.refetch();
},
};
}