feat(frontend): add 6 query/mutation hooks (batches, claims, remits, providers, activity, parse)
This commit is contained in:
@@ -0,0 +1,41 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { useSyncExternalStore } from "react";
|
||||||
|
import { api, type ListActivityParams, type PaginatedResponse } from "@/lib/api";
|
||||||
|
import { useAppStore } from "@/store";
|
||||||
|
import type { Activity } from "@/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists activity. Polls every 30s when a backend is configured (so the
|
||||||
|
* Activity Log page reflects new events without a manual refresh); in
|
||||||
|
* sample-data mode it returns the in-memory store directly.
|
||||||
|
*/
|
||||||
|
export function useActivity(params: ListActivityParams = {}) {
|
||||||
|
const fallback = useSyncExternalStore(
|
||||||
|
(cb) => useAppStore.subscribe(cb),
|
||||||
|
() => useAppStore.getState().activity,
|
||||||
|
() => useAppStore.getState().activity
|
||||||
|
);
|
||||||
|
|
||||||
|
const q = useQuery<PaginatedResponse<Activity>>({
|
||||||
|
queryKey: ["activity", params],
|
||||||
|
queryFn: () => api.listActivity<Activity>(params),
|
||||||
|
enabled: api.isConfigured,
|
||||||
|
refetchInterval: api.isConfigured ? 30_000 : false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!api.isConfigured) {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
items: fallback,
|
||||||
|
total: fallback.length,
|
||||||
|
returned: fallback.length,
|
||||||
|
has_more: false,
|
||||||
|
} satisfies PaginatedResponse<Activity>,
|
||||||
|
isLoading: false,
|
||||||
|
isError: false,
|
||||||
|
error: null,
|
||||||
|
refetch: () => Promise.resolve(),
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
return q;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { api, type BatchSummary } from "@/lib/api";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists parsed batches. Batches are only available when a backend is
|
||||||
|
* configured — when running in sample-data mode there's no persisted
|
||||||
|
* history, so we leave the query disabled.
|
||||||
|
*/
|
||||||
|
export function useBatches(limit?: number) {
|
||||||
|
return useQuery<BatchSummary[]>({
|
||||||
|
queryKey: ["batches", limit ?? null],
|
||||||
|
queryFn: () => api.listBatches(limit),
|
||||||
|
enabled: api.isConfigured,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { useSyncExternalStore } from "react";
|
||||||
|
import { api, type ListClaimsParams, type PaginatedResponse } from "@/lib/api";
|
||||||
|
import { useAppStore } from "@/store";
|
||||||
|
import type { Claim } from "@/types";
|
||||||
|
|
||||||
|
export const EMPTY_CLAIMS: PaginatedResponse<Claim> = {
|
||||||
|
items: [],
|
||||||
|
total: 0,
|
||||||
|
returned: 0,
|
||||||
|
has_more: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists claims. Falls back to the in-memory zustand store when no backend
|
||||||
|
* is configured, applying the same `status` / `provider_npi` / `batch_id`
|
||||||
|
* / pagination parameters the live endpoint would accept. Note: the UI
|
||||||
|
* `Claim` shape doesn't carry a `batchId` yet, so the `batch_id` filter is
|
||||||
|
* a no-op in fallback mode — it'll start working once the Claim type
|
||||||
|
* gains that field.
|
||||||
|
*/
|
||||||
|
export function useClaims(params: ListClaimsParams) {
|
||||||
|
const fallback = useSyncExternalStore(
|
||||||
|
(cb) => useAppStore.subscribe(cb),
|
||||||
|
() => useAppStore.getState().claims,
|
||||||
|
() => useAppStore.getState().claims
|
||||||
|
);
|
||||||
|
|
||||||
|
const q = useQuery<PaginatedResponse<Claim>>({
|
||||||
|
queryKey: ["claims", params],
|
||||||
|
queryFn: () => api.listClaims<Claim>(params),
|
||||||
|
enabled: api.isConfigured,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!api.isConfigured) {
|
||||||
|
const offset = params.offset ?? 0;
|
||||||
|
const limit = params.limit ?? 100;
|
||||||
|
const filtered = fallback.filter((c) => {
|
||||||
|
if (params.status && c.status !== params.status) return false;
|
||||||
|
if (params.provider_npi && c.providerNpi !== params.provider_npi) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
const sliced = filtered.slice(offset, offset + limit);
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
items: sliced,
|
||||||
|
total: filtered.length,
|
||||||
|
returned: sliced.length,
|
||||||
|
has_more: filtered.length > offset + limit,
|
||||||
|
} satisfies PaginatedResponse<Claim>,
|
||||||
|
isLoading: false,
|
||||||
|
isError: false,
|
||||||
|
error: null,
|
||||||
|
refetch: () => Promise.resolve(),
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
return q;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { api, type ParseOptions } from "@/lib/api";
|
||||||
|
import type {
|
||||||
|
BatchSummary as ParserBatchSummary,
|
||||||
|
ParseResult837,
|
||||||
|
ParseResult835,
|
||||||
|
} from "@/types";
|
||||||
|
|
||||||
|
type ParseResult = ParseResult837 | ParseResult835 | ParserBatchSummary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutation that runs an EDI file through the parser. On success, invalidates
|
||||||
|
* every list query that might have been affected by the new batch so pages
|
||||||
|
* showing stale data re-fetch.
|
||||||
|
*/
|
||||||
|
export function useParse(kind: "837p" | "835") {
|
||||||
|
const qc = useQueryClient();
|
||||||
|
return useMutation<ParseResult, Error, { file: File; options?: ParseOptions }>({
|
||||||
|
mutationFn: (opts) => {
|
||||||
|
const fn = kind === "837p" ? api.parse837 : api.parse835;
|
||||||
|
return fn(opts.file, opts.options) as Promise<ParseResult>;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
qc.invalidateQueries({ queryKey: ["batches"] });
|
||||||
|
qc.invalidateQueries({ queryKey: ["claims"] });
|
||||||
|
qc.invalidateQueries({ queryKey: ["remittances"] });
|
||||||
|
qc.invalidateQueries({ queryKey: ["providers"] });
|
||||||
|
qc.invalidateQueries({ queryKey: ["activity"] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { useSyncExternalStore } from "react";
|
||||||
|
import { api, type ListProvidersParams, type PaginatedResponse } from "@/lib/api";
|
||||||
|
import { useAppStore } from "@/store";
|
||||||
|
import type { Provider } from "@/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists providers. Falls back to the in-memory zustand store when no
|
||||||
|
* backend is configured; the store carries the full provider directory
|
||||||
|
* so no pagination is applied in fallback mode.
|
||||||
|
*/
|
||||||
|
export function useProviders(params: ListProvidersParams = {}) {
|
||||||
|
const fallback = useSyncExternalStore(
|
||||||
|
(cb) => useAppStore.subscribe(cb),
|
||||||
|
() => useAppStore.getState().providers,
|
||||||
|
() => useAppStore.getState().providers
|
||||||
|
);
|
||||||
|
|
||||||
|
const q = useQuery<PaginatedResponse<Provider>>({
|
||||||
|
queryKey: ["providers", params],
|
||||||
|
queryFn: () => api.listProviders<Provider>(params),
|
||||||
|
enabled: api.isConfigured,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!api.isConfigured) {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
items: fallback,
|
||||||
|
total: fallback.length,
|
||||||
|
returned: fallback.length,
|
||||||
|
has_more: false,
|
||||||
|
} satisfies PaginatedResponse<Provider>,
|
||||||
|
isLoading: false,
|
||||||
|
isError: false,
|
||||||
|
error: null,
|
||||||
|
refetch: () => Promise.resolve(),
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
return q;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { useSyncExternalStore } from "react";
|
||||||
|
import { api, type ListRemittancesParams, type PaginatedResponse } from "@/lib/api";
|
||||||
|
import { useAppStore } from "@/store";
|
||||||
|
import type { Remittance } from "@/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists remittances. Falls back to the in-memory zustand store when no
|
||||||
|
* backend is configured; the fallback honors `offset` / `limit` and
|
||||||
|
* returns the full list otherwise.
|
||||||
|
*/
|
||||||
|
export function useRemittances(params: ListRemittancesParams) {
|
||||||
|
const fallback = useSyncExternalStore(
|
||||||
|
(cb) => useAppStore.subscribe(cb),
|
||||||
|
() => useAppStore.getState().remittances,
|
||||||
|
() => useAppStore.getState().remittances
|
||||||
|
);
|
||||||
|
|
||||||
|
const q = useQuery<PaginatedResponse<Remittance>>({
|
||||||
|
queryKey: ["remittances", params],
|
||||||
|
queryFn: () => api.listRemittances<Remittance>(params),
|
||||||
|
enabled: api.isConfigured,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!api.isConfigured) {
|
||||||
|
const offset = params.offset ?? 0;
|
||||||
|
const limit = params.limit ?? 100;
|
||||||
|
const sliced = fallback.slice(offset, offset + limit);
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
items: sliced,
|
||||||
|
total: fallback.length,
|
||||||
|
returned: sliced.length,
|
||||||
|
has_more: fallback.length > offset + limit,
|
||||||
|
} satisfies PaginatedResponse<Remittance>,
|
||||||
|
isLoading: false,
|
||||||
|
isError: false,
|
||||||
|
error: null,
|
||||||
|
refetch: () => Promise.resolve(),
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
return q;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user