diff --git a/src/features/officer/IntegrationPage.tsx b/src/features/officer/IntegrationPage.tsx index 6985bd8..b7d9259 100644 --- a/src/features/officer/IntegrationPage.tsx +++ b/src/features/officer/IntegrationPage.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { errMsg, get, post, put } from '../../lib/api' +import { del, errMsg, get, post, put } from '../../lib/api' import type { CanonicalColumns, ConnectorCatalogEntry, @@ -8,6 +8,8 @@ import type { ConnectorSyncResponse, ConnectorSyncRun, ConnectorTestResult, + LoanHistoryCoverage, + LoanHistoryGenerateResult, } from '../../lib/api' import { Card, @@ -20,7 +22,7 @@ import { StatusPill, inputCls, } from '../../components/ui/bits' -import { CANONICAL, demoCatalog, demoProfiles, demoRuns } from '../../lib/demo/connectors' +import { CANONICAL, demoCatalog, demoCoverage, demoProfiles, demoRuns } from '../../lib/demo/connectors' /** * Core banking / LMS integration. @@ -41,7 +43,7 @@ import { CANONICAL, demoCatalog, demoProfiles, demoRuns } from '../../lib/demo/c * waits for a person. */ -type Tab = 'connections' | 'mapping' | 'history' +type Tab = 'connections' | 'mapping' | 'history' | 'loanhistory' /** * Whether a column name carries borrower identity — the same test the backend @@ -131,6 +133,52 @@ export function IntegrationPage() { onError: (e) => setError(errMsg(e)), }) + const coverageQ = useQuery({ + queryKey: ['loan-history-coverage'], + queryFn: () => get('/admin/loan-history/coverage'), + }) + const coverage = coverageQ.data ?? demoCoverage() + + async function refreshHistory() { + await queryClient.invalidateQueries({ queryKey: ['loan-history-coverage'] }) + await queryClient.invalidateQueries({ queryKey: ['connector-runs'] }) + } + + const generateM = useMutation({ + mutationFn: (months: number) => + post(`/admin/loan-history/generate?months=${months}`), + onSuccess: async (data) => { + setError(null) + setNotice(`${data.snapshotsWritten} snapshots written across ${data.loans} loans — ${data.note}`) + await refreshHistory() + }, + onError: (e) => setError(errMsg(e)), + }) + + const purgeM = useMutation({ + mutationFn: () => del('/admin/loan-history/generated'), + onSuccess: async (removed) => { + setError(null) + setNotice(`${removed} generated snapshots removed. Real extracts were left untouched.`) + await refreshHistory() + }, + onError: (e) => setError(errMsg(e)), + }) + + const backfillM = useMutation({ + mutationFn: (vars: { code: string; from: string; to: string }) => + post( + `/admin/connectors/profiles/${vars.code}/backfill?from=${vars.from}&to=${vars.to}`, + ), + onSuccess: async (data) => { + setSyncResult(data) + setError(null) + setNotice(data.run.message) + await refreshHistory() + }, + onError: (e) => setError(errMsg(e)), + }) + const connected = profiles.filter((p) => p.active).length const live = profiles.filter((p) => p.active && !p.simulated).length const failing = profiles.filter((p) => p.lastStatus === 'FAILED').length @@ -175,6 +223,7 @@ export function IntegrationPage() { ['connections', 'Connections'], ['mapping', 'Field mapping'], ['history', 'Sync history'], + ['loanhistory', 'DPD history'], ] as [Tab, string][] ).map(([key, label]) => (