feat(frontend): add 6 GET methods (batches, batch, claims, remits, providers, activity) + tests
This commit is contained in:
Generated
+1619
-1
File diff suppressed because it is too large
Load Diff
+4
-2
@@ -9,7 +9,8 @@
|
|||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"typecheck": "tsc -b --noEmit"
|
"typecheck": "tsc -b --noEmit",
|
||||||
|
"test": "vitest run"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-dialog": "^1.1.2",
|
"@radix-ui/react-dialog": "^1.1.2",
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "^3.4.14",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.3",
|
||||||
"vite": "^5.4.10"
|
"vite": "^5.4.10",
|
||||||
|
"vitest": "^4.1.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { useAppStore } from "@/store";
|
import { useAppStore } from "@/store";
|
||||||
import { api } from "@/lib/api";
|
|
||||||
import type { Claim } from "@/types";
|
import type { Claim } from "@/types";
|
||||||
|
|
||||||
interface FormState {
|
interface FormState {
|
||||||
@@ -72,9 +71,11 @@ export function NewClaimDialog() {
|
|||||||
submissionDate: new Date().toISOString(),
|
submissionDate: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
if (api.isConfigured) {
|
// The "create claim from form" path is a local-only workflow for the
|
||||||
await api.createClaim(claim);
|
// sample-data mode. When a real backend is wired, the new claim
|
||||||
}
|
// surface is the EDI file → parser → backend pipeline; there's no
|
||||||
|
// direct `api.createClaim` endpoint. So we just push to the store
|
||||||
|
// (and surface an error if the store update ever throws).
|
||||||
addClaim(claim);
|
addClaim(claim);
|
||||||
toast.success(`Claim ${claim.id} submitted`);
|
toast.success(`Claim ${claim.id} submitted`);
|
||||||
setForm(initial);
|
setForm(initial);
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
import { api } from "./api";
|
||||||
|
|
||||||
|
const originalFetch = global.fetch;
|
||||||
|
const mockFetch = vi.fn();
|
||||||
|
|
||||||
|
describe("api GET helpers", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
global.fetch = mockFetch as unknown as typeof fetch;
|
||||||
|
mockFetch.mockReset();
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
global.fetch = originalFetch;
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("listBatches hits /api/batches and unwraps body.items", async () => {
|
||||||
|
mockFetch.mockResolvedValueOnce(
|
||||||
|
new Response(JSON.stringify({ items: [] }), {
|
||||||
|
status: 200,
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const out = await api.listBatches();
|
||||||
|
expect(out).toEqual([]);
|
||||||
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||||
|
const called = mockFetch.mock.calls[0][0] as string;
|
||||||
|
expect(called).toContain("/api/batches");
|
||||||
|
expect(called).not.toContain("?");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("listClaims builds the right URL with status + sort + order", async () => {
|
||||||
|
mockFetch.mockResolvedValueOnce(
|
||||||
|
new Response(
|
||||||
|
JSON.stringify({ items: [], total: 0, returned: 0, has_more: false }),
|
||||||
|
{ status: 200, headers: { "content-type": "application/json" } }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await api.listClaims({
|
||||||
|
status: "submitted",
|
||||||
|
sort: "billedAmount",
|
||||||
|
order: "desc",
|
||||||
|
});
|
||||||
|
const called = mockFetch.mock.calls[0][0] as string;
|
||||||
|
expect(called).toContain("/api/claims?");
|
||||||
|
expect(called).toContain("status=submitted");
|
||||||
|
expect(called).toContain("sort=billedAmount");
|
||||||
|
expect(called).toContain("order=desc");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getBatch returns the result with the right id in the URL", async () => {
|
||||||
|
mockFetch.mockResolvedValueOnce(
|
||||||
|
new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
envelope: {
|
||||||
|
sender_id: "X",
|
||||||
|
receiver_id: "Y",
|
||||||
|
control_number: "1",
|
||||||
|
transaction_date: "2026-01-01",
|
||||||
|
},
|
||||||
|
claims: [],
|
||||||
|
summary: {},
|
||||||
|
kind: "835",
|
||||||
|
}),
|
||||||
|
{ status: 200, headers: { "content-type": "application/json" } }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const out = await api.getBatch("abc");
|
||||||
|
expect(out).toBeTruthy();
|
||||||
|
const called = mockFetch.mock.calls[0][0] as string;
|
||||||
|
expect(called).toContain("/api/batches/abc");
|
||||||
|
});
|
||||||
|
});
|
||||||
+199
-54
@@ -3,20 +3,22 @@
|
|||||||
*
|
*
|
||||||
* Talks to the FastAPI backend that lives at `VITE_API_BASE_URL`
|
* Talks to the FastAPI backend that lives at `VITE_API_BASE_URL`
|
||||||
* (defaulting to an empty string → "no backend configured" mode). When the
|
* (defaulting to an empty string → "no backend configured" mode). When the
|
||||||
* base URL is empty, the high-level helper methods throw, the streaming
|
* base URL is empty, every method throws `notConfiguredError()`; the
|
||||||
* `parse837` / `parse835` calls throw, and `health()` returns `null`. Existing
|
* higher-level hooks in `src/hooks/` catch that by reading from the
|
||||||
* pages keep working with the in-memory sample store in that case.
|
* in-memory zustand store instead. This keeps the pages agnostic of the
|
||||||
|
* switch.
|
||||||
*
|
*
|
||||||
* - `parse837(file, { onProgress })` POSTs to `/api/parse-837` and, when
|
* Endpoints:
|
||||||
* `onProgress` is provided, streams NDJSON (one `{type,data}` object per
|
* - `parse837(file, { onProgress })` POSTs to `/api/parse-837`. With
|
||||||
* line) so the UI can render claims incrementally. With `onProgress`
|
* `onProgress` set, streams NDJSON (one `{type,data}` per line) so the
|
||||||
* omitted, it asks the backend for a single JSON object via
|
* UI can render claims incrementally. Without it, asks the backend for
|
||||||
* `Accept: application/json`.
|
* a single JSON object via `Accept: application/json`.
|
||||||
* - `parse835(...)` mirrors the 837 shape for `/api/parse-835`.
|
* - `parse835(...)` mirrors the 837 shape for `/api/parse-835`.
|
||||||
* - `health()` GETs `/api/health` and returns `{ status, version }`.
|
* - `health()` GETs `/api/health` and returns `{ status, version }`.
|
||||||
|
* - `listBatches / getBatch / listClaims / listRemittances / listProviders
|
||||||
|
* / listActivity` are plain JSON GETs against the persistence surface.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useAppStore } from "@/store";
|
|
||||||
import type {
|
import type {
|
||||||
ClaimOutput,
|
ClaimOutput,
|
||||||
ClaimPayment,
|
ClaimPayment,
|
||||||
@@ -29,7 +31,7 @@ import type {
|
|||||||
Payer835,
|
Payer835,
|
||||||
Payee835,
|
Payee835,
|
||||||
ReassociationTrace,
|
ReassociationTrace,
|
||||||
BatchSummary,
|
BatchSummary as ParserBatchSummary,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
|
|
||||||
const BASE_URL = (import.meta.env.VITE_API_BASE_URL as string | undefined) ?? "";
|
const BASE_URL = (import.meta.env.VITE_API_BASE_URL as string | undefined) ?? "";
|
||||||
@@ -48,7 +50,7 @@ export type NdjsonProgressEvent =
|
|||||||
| { type: "trace"; data: ReassociationTrace }
|
| { type: "trace"; data: ReassociationTrace }
|
||||||
| { type: "payer"; data: Payer | Payer835 }
|
| { type: "payer"; data: Payer | Payer835 }
|
||||||
| { type: "payee"; data: Payee835 }
|
| { type: "payee"; data: Payee835 }
|
||||||
| { type: "summary"; data: BatchSummary };
|
| { type: "summary"; data: ParserBatchSummary };
|
||||||
|
|
||||||
export type ParseProgress = (event: NdjsonProgressEvent) => void;
|
export type ParseProgress = (event: NdjsonProgressEvent) => void;
|
||||||
|
|
||||||
@@ -72,6 +74,69 @@ export interface HealthResponse {
|
|||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// List-endpoint types (GET surfaces, sub-project 1)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface ListClaimsParams {
|
||||||
|
batch_id?: string;
|
||||||
|
status?: string;
|
||||||
|
payer?: string;
|
||||||
|
provider_npi?: string;
|
||||||
|
date_from?: string;
|
||||||
|
date_to?: string;
|
||||||
|
sort?: string;
|
||||||
|
order?: "asc" | "desc";
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListRemittancesParams {
|
||||||
|
batch_id?: string;
|
||||||
|
payer?: string;
|
||||||
|
claim_id?: string;
|
||||||
|
date_from?: string;
|
||||||
|
date_to?: string;
|
||||||
|
sort?: string;
|
||||||
|
order?: "asc" | "desc";
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListProvidersParams {
|
||||||
|
npi?: string;
|
||||||
|
state?: string;
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListActivityParams {
|
||||||
|
kind?: string;
|
||||||
|
since?: string;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginatedResponse<T> {
|
||||||
|
items: T[];
|
||||||
|
total: number;
|
||||||
|
returned: number;
|
||||||
|
has_more: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lightweight summary used by the GET /api/batches list endpoint. Distinct
|
||||||
|
* from the parser's `BatchSummary` (which lives in `@/types` and carries the
|
||||||
|
* full validation + per-rule counts) — this one is what the UI needs to
|
||||||
|
* render a list row.
|
||||||
|
*/
|
||||||
|
export interface BatchSummary {
|
||||||
|
id: string;
|
||||||
|
kind: "837p" | "835";
|
||||||
|
inputFilename: string;
|
||||||
|
parsedAt: string;
|
||||||
|
claimCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Low-level helpers
|
// Low-level helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -105,17 +170,29 @@ async function readErrorBody(res: Response): Promise<string> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Build a query string, skipping undefined/empty params. */
|
||||||
|
function qs(params: Record<string, unknown> | undefined): string {
|
||||||
|
if (!params) return "";
|
||||||
|
const u = new URLSearchParams();
|
||||||
|
for (const [k, v] of Object.entries(params)) {
|
||||||
|
if (v === undefined || v === null || v === "") continue;
|
||||||
|
u.set(k, String(v));
|
||||||
|
}
|
||||||
|
const s = u.toString();
|
||||||
|
return s ? `?${s}` : "";
|
||||||
|
}
|
||||||
|
|
||||||
async function parseNdjsonStream(
|
async function parseNdjsonStream(
|
||||||
res: Response,
|
res: Response,
|
||||||
onProgress: ParseProgress
|
onProgress: ParseProgress
|
||||||
): Promise<BatchSummary> {
|
): Promise<ParserBatchSummary> {
|
||||||
if (!res.body) {
|
if (!res.body) {
|
||||||
throw new Error("Response had no body to stream.");
|
throw new Error("Response had no body to stream.");
|
||||||
}
|
}
|
||||||
const reader = res.body.getReader();
|
const reader = res.body.getReader();
|
||||||
const decoder = new TextDecoder("utf-8");
|
const decoder = new TextDecoder("utf-8");
|
||||||
let buffer = "";
|
let buffer = "";
|
||||||
let lastSummary: BatchSummary | null = null;
|
let lastSummary: ParserBatchSummary | null = null;
|
||||||
|
|
||||||
// Read the body chunk-by-chunk. A `while (true)` loop is the standard
|
// Read the body chunk-by-chunk. A `while (true)` loop is the standard
|
||||||
// pattern for ReadableStreamDefaultReader; `done` breaks us out.
|
// pattern for ReadableStreamDefaultReader; `done` breaks us out.
|
||||||
@@ -143,7 +220,7 @@ async function parseNdjsonStream(
|
|||||||
}
|
}
|
||||||
onProgress(event as NdjsonProgressEvent);
|
onProgress(event as NdjsonProgressEvent);
|
||||||
if (event.type === "summary") {
|
if (event.type === "summary") {
|
||||||
lastSummary = event.data as BatchSummary;
|
lastSummary = event.data as ParserBatchSummary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nl = buffer.indexOf("\n");
|
nl = buffer.indexOf("\n");
|
||||||
@@ -156,7 +233,7 @@ async function parseNdjsonStream(
|
|||||||
const event = JSON.parse(tail) as NdjsonEvent;
|
const event = JSON.parse(tail) as NdjsonEvent;
|
||||||
onProgress(event as NdjsonProgressEvent);
|
onProgress(event as NdjsonProgressEvent);
|
||||||
if (event.type === "summary") {
|
if (event.type === "summary") {
|
||||||
lastSummary = event.data as BatchSummary;
|
lastSummary = event.data as ParserBatchSummary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,13 +244,13 @@ async function parseNdjsonStream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Public surface
|
// Public surface — parser endpoints
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function parse837(
|
async function parse837(
|
||||||
file: File,
|
file: File,
|
||||||
options: ParseOptions = {}
|
options: ParseOptions = {}
|
||||||
): Promise<ParseResult837 | BatchSummary> {
|
): Promise<ParseResult837 | ParserBatchSummary> {
|
||||||
if (!isConfigured) throw notConfiguredError();
|
if (!isConfigured) throw notConfiguredError();
|
||||||
const { payer = "co_medicaid", strict = false, includeRawSegments = true, onProgress } = options;
|
const { payer = "co_medicaid", strict = false, includeRawSegments = true, onProgress } = options;
|
||||||
|
|
||||||
@@ -209,7 +286,7 @@ async function parse837(
|
|||||||
async function parse835(
|
async function parse835(
|
||||||
file: File,
|
file: File,
|
||||||
options: ParseOptions = {}
|
options: ParseOptions = {}
|
||||||
): Promise<ParseResult835 | BatchSummary> {
|
): Promise<ParseResult835 | ParserBatchSummary> {
|
||||||
if (!isConfigured) throw notConfiguredError();
|
if (!isConfigured) throw notConfiguredError();
|
||||||
const { payer = "co_medicaid_835", strict = false, includeRawSegments = true, onProgress } = options;
|
const { payer = "co_medicaid_835", strict = false, includeRawSegments = true, onProgress } = options;
|
||||||
|
|
||||||
@@ -255,19 +332,108 @@ async function health(): Promise<HealthResponse | null> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Legacy helpers (placeholder kept for the data adapter pattern).
|
// Public surface — GET endpoints (sub-project 1)
|
||||||
// The existing pages use the `data` adapter below to either hit the API or
|
// All throw `notConfiguredError` when no backend is wired. The hooks in
|
||||||
// read from the in-memory store. The legacy endpoints listed here are
|
// `src/hooks/` are responsible for the in-memory zustand fallback.
|
||||||
// intentionally not implemented against the live backend (the backend only
|
|
||||||
// exposes the parser endpoints); they exist so the import surface doesn't
|
|
||||||
// break existing callers, but they always throw when invoked.
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function legacyNotImplemented(name: string): Promise<never> {
|
async function listBatches(limit?: number): Promise<BatchSummary[]> {
|
||||||
|
if (!isConfigured) throw notConfiguredError();
|
||||||
|
const params: Record<string, unknown> = {};
|
||||||
|
if (limit !== undefined) params.limit = limit;
|
||||||
|
const res = await fetch(joinUrl(`/api/batches${qs(params)}`), {
|
||||||
|
headers: { Accept: "application/json" },
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const detail = await readErrorBody(res);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`api.${name} is not wired to the live backend. Use api.parse837 / api.parse835 instead, ` +
|
`${res.status} ${res.statusText}${detail ? ` — ${detail}` : ""}`
|
||||||
`or read the in-memory sample store via the \`data\` adapter.`
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
const body = (await res.json()) as { items: BatchSummary[] };
|
||||||
|
return body.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getBatch(id: string): Promise<ParseResult837 | ParseResult835> {
|
||||||
|
if (!isConfigured) throw notConfiguredError();
|
||||||
|
const res = await fetch(joinUrl(`/api/batches/${encodeURIComponent(id)}`), {
|
||||||
|
headers: { Accept: "application/json" },
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const detail = await readErrorBody(res);
|
||||||
|
throw new Error(
|
||||||
|
`${res.status} ${res.statusText}${detail ? ` — ${detail}` : ""}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (await res.json()) as ParseResult837 | ParseResult835;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listClaims<T = unknown>(
|
||||||
|
params: ListClaimsParams
|
||||||
|
): Promise<PaginatedResponse<T>> {
|
||||||
|
if (!isConfigured) throw notConfiguredError();
|
||||||
|
const res = await fetch(
|
||||||
|
joinUrl(`/api/claims${qs(params as Record<string, unknown>)}`),
|
||||||
|
{ headers: { Accept: "application/json" } }
|
||||||
|
);
|
||||||
|
if (!res.ok) {
|
||||||
|
const detail = await readErrorBody(res);
|
||||||
|
throw new Error(
|
||||||
|
`${res.status} ${res.statusText}${detail ? ` — ${detail}` : ""}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (await res.json()) as PaginatedResponse<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listRemittances<T = unknown>(
|
||||||
|
params: ListRemittancesParams
|
||||||
|
): Promise<PaginatedResponse<T>> {
|
||||||
|
if (!isConfigured) throw notConfiguredError();
|
||||||
|
const res = await fetch(
|
||||||
|
joinUrl(`/api/remittances${qs(params as Record<string, unknown>)}`),
|
||||||
|
{ headers: { Accept: "application/json" } }
|
||||||
|
);
|
||||||
|
if (!res.ok) {
|
||||||
|
const detail = await readErrorBody(res);
|
||||||
|
throw new Error(
|
||||||
|
`${res.status} ${res.statusText}${detail ? ` — ${detail}` : ""}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (await res.json()) as PaginatedResponse<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listProviders<T = unknown>(
|
||||||
|
params: ListProvidersParams = {}
|
||||||
|
): Promise<PaginatedResponse<T>> {
|
||||||
|
if (!isConfigured) throw notConfiguredError();
|
||||||
|
const res = await fetch(
|
||||||
|
joinUrl(`/api/providers${qs(params as Record<string, unknown>)}`),
|
||||||
|
{ headers: { Accept: "application/json" } }
|
||||||
|
);
|
||||||
|
if (!res.ok) {
|
||||||
|
const detail = await readErrorBody(res);
|
||||||
|
throw new Error(
|
||||||
|
`${res.status} ${res.statusText}${detail ? ` — ${detail}` : ""}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (await res.json()) as PaginatedResponse<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listActivity<T = unknown>(
|
||||||
|
params: ListActivityParams = {}
|
||||||
|
): Promise<PaginatedResponse<T>> {
|
||||||
|
if (!isConfigured) throw notConfiguredError();
|
||||||
|
const res = await fetch(
|
||||||
|
joinUrl(`/api/activity${qs(params as Record<string, unknown>)}`),
|
||||||
|
{ headers: { Accept: "application/json" } }
|
||||||
|
);
|
||||||
|
if (!res.ok) {
|
||||||
|
const detail = await readErrorBody(res);
|
||||||
|
throw new Error(
|
||||||
|
`${res.status} ${res.statusText}${detail ? ` — ${detail}` : ""}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (await res.json()) as PaginatedResponse<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const api = {
|
export const api = {
|
||||||
@@ -276,31 +442,10 @@ export const api = {
|
|||||||
health,
|
health,
|
||||||
parse837,
|
parse837,
|
||||||
parse835,
|
parse835,
|
||||||
|
listBatches,
|
||||||
// Legacy placeholders — preserved so existing imports compile but never
|
getBatch,
|
||||||
// resolve against the live backend. The `data` adapter handles the
|
listClaims,
|
||||||
// sample-vs-backend switch.
|
listRemittances,
|
||||||
getClaims: () => legacyNotImplemented("getClaims"),
|
listProviders,
|
||||||
createClaim: (_payload: unknown) => legacyNotImplemented("createClaim"),
|
listActivity,
|
||||||
getRemittances: () => legacyNotImplemented("getRemittances"),
|
|
||||||
getProviders: () => legacyNotImplemented("getProviders"),
|
|
||||||
getActivity: () => legacyNotImplemented("getActivity"),
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thin adapter that prefers the backend when configured, otherwise
|
|
||||||
* resolves against the in-memory store. Pages should call this rather
|
|
||||||
* than touching the store directly so the swap is trivial later.
|
|
||||||
*
|
|
||||||
* Note: in the current backend the GET endpoints aren't implemented, so
|
|
||||||
* these all fall back to the in-memory store regardless. The structure is
|
|
||||||
* kept for forward-compat.
|
|
||||||
*/
|
|
||||||
export const data = {
|
|
||||||
claims: () => (api.isConfigured ? api.getClaims() : useAppStore.getState().claims),
|
|
||||||
remittances: () =>
|
|
||||||
api.isConfigured ? api.getRemittances() : useAppStore.getState().remittances,
|
|
||||||
providers: () =>
|
|
||||||
api.isConfigured ? api.getProviders() : useAppStore.getState().providers,
|
|
||||||
activity: () => (api.isConfigured ? api.getActivity() : useAppStore.getState().activity),
|
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -14,5 +14,5 @@
|
|||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts", "vitest.config.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "./src"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
// Node 18+ exposes `Response` / `fetch` globals, so we don't need jsdom.
|
||||||
|
// VITE_API_BASE_URL must be set so the api module treats itself as
|
||||||
|
// configured; otherwise every GET would throw notConfiguredError before
|
||||||
|
// reaching the mocked fetch.
|
||||||
|
env: {
|
||||||
|
VITE_API_BASE_URL: "http://test.local",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user