import { useCallback, createContext, useContext, ReactNode } from 'react'; import { environment } from '../environments/environment.ts'; import { HttpError, useHttpService } from './HttpService.tsx'; import type { SaveTransport } from '../utils/saveOperation'; import { buildBillingExportQuery, exportFilename, type BillingExportFilters } from '../pages/DashboardPage/BillingPage/billingExportQuery'; // (Billing-Aware Export), Task 9 - the CLIENT half of the export flow. Mirrors // PaymentService.tsx: same fhirBaseApi base (the org-scoped FHIR-adjacent API, NOT apiUrl) and the // same hook + Provider/Context pair shape. const API_URL = environment.fhirBaseApi; // Result shape the download resolves to - the caller (an export dialog) turns this into a browser // download via a Blob URL + . export type BillingExportResult = { blob: Blob; filename: string }; export type BillingExportTransport = SaveTransport & { organizationId?: string }; export const BILLING_EXPORT_UNAVAILABLE = 'The billing export could not be fully loaded. Please try again.'; export const useBillingExportService = () => { const { fetchWithAuth } = useHttpService(); // GET {fhirBaseApi}/BillingExport?start=...&end=...&format=csv|json[&provider=...][&encounterType=...] // Deliberately uses fetchWithAuth (not the `get()` helper) because `get()` unconditionally calls // response.json() - that would corrupt a CSV body (and even for JSON, this endpoint returns a // downloadable file, not a typed resource). fetchWithAuth already attaches Authorization and the // X-Organization-ID/X-Organization-Context headers, so no org/issuer query param is sent here - // the server org-scopes off the trusted header, not a client-supplied value. const downloadBillingExport = useCallback( async (f: BillingExportFilters, transport?: BillingExportTransport): Promise => { transport?.admit(); const filters = { ...f }; const accept = filters.format === 'csv' ? 'text/csv' : 'application/json'; const res = await fetchWithAuth(`${API_URL}/BillingExport?${buildBillingExportQuery(filters)}`, { headers: { Accept: accept }, admit: transport?.admit, signal: transport?.signal, organizationId: transport?.organizationId, }); transport?.admit(); if (!res.ok) { const message = res.status === 401 || res.status === 403 ? 'You do not have access to this billing export.' : BILLING_EXPORT_UNAVAILABLE; throw new HttpError(res.status, message, { message }); } const contentType = res.headers.get('Content-Type')?.split(';')[0].trim().toLowerCase(); if (contentType !== accept) throw new Error(BILLING_EXPORT_UNAVAILABLE); const blob = await res.blob(); transport?.admit(); if (blob.size === 0) throw new Error(BILLING_EXPORT_UNAVAILABLE); return { blob, filename: exportFilename(filters, filters.format) }; }, [fetchWithAuth], ); return { downloadBillingExport }; }; const BillingExportServiceContext = createContext | undefined>(undefined); export const BillingExportServiceProvider = ({ children }: { children: ReactNode }) => { const billingExportService = useBillingExportService(); return ( {children} ); }; export const useBillingExportServiceContext = () => { const context = useContext(BillingExportServiceContext); if (context === undefined) { throw new Error('useBillingExportServiceContext must be used within a BillingExportServiceProvider'); } return context; };