Files
LincPayandClaude Opus 5 0a76a53ceb feat(integration): core banking console at /officer/integration
Three tabs. Connections lists each bank's configured link with its last
status, and lets an operations user test or run a sync. Field mapping is
the screen that carries the argument: one row per loan-book field, one
column per lender's name for it, which is what "every bank's core is
different" actually reduces to. Sync history reads as an audit — a
failed run is a row, because a feed that stopped three weeks ago must be
visible as something other than silence.

Simulated adapters are badged everywhere they appear, including in the
run history, because the failure mode that matters here is a demo
mistaken for a live feed. Unmapped vendor columns are shown being
dropped at the boundary, with identifying ones marked never stored.

The client-side identifying-column check mirrors the backend's, matching
on the whole name and on each part with separators and camelCase split
alike — so CUST_NAME and borrowerMobile are refused next to the field
rather than after a round trip.

Follows the house pattern: useQuery against the real endpoint, in-file
demo fallback, "Demo data" pill when the backend is unreachable. The
demo fixtures deliberately include one connection that has never run and
one that last failed. Build and oxlint clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 18:20:15 +05:30

71 lines
3.7 KiB
Markdown

# UdyamLinc — UI
Three surfaces in one Vite/React app, matching the three institutional tiers. Full project documentation, including the demo path and what is real versus seeded, lives in **`../UdyamLinc-Backend/README.md`**.
```bash
npm install
npm run dev # http://localhost:5182
```
The backend is expected at `http://localhost:8082/api/v1/udyam`. Override with `VITE_UDYAM_API_BASE_URL` in `.env.local`.
---
## The three surfaces
| Route | Tier | Shell |
|---|---|---|
| `/nabard` | NABARD — aggregates only | `features/nabard` |
| `/officer` | Bank console — one institution's book | `features/officer` |
| `/app` | Enterprise PWA — offline, Hindi-first | `features/enterprise` |
Both staff tiers share one sign-in form and land in different portals; `homeFor()` picks the destination from the role. That routing is a **convenience only** — the backend re-derives the tier from the signed JWT and refuses the other surface with a `403`, so a tampered session gains nothing.
The NABARD portal is deliberately a different chrome from the bank console. An apex user and a branch officer are not two ranks of one job, and a portal that looked identical would invite the assumption that the apex user is a bank user with more rights.
---
## The house pattern
Every screen follows the same shape, and new ones should:
```tsx
const { data, isError } = useQuery({ queryKey: [...], queryFn: () => get<T>('/path') })
const demo = isError
const rows = data ?? demoRows() // in-file fallback from lib/demo/
…
{demo && <StatusPill status="Demo data" />}
```
A screen must render with the backend down — field devices are on patchy 2G — but it must **say so**. The "Demo data" pill means that screen's call failed, not that the whole app is a mock.
Demo fallbacks deliberately include failure states: the data-entry fallback contains rejected rows, and the NABARD fallback contains a bank below the suppression threshold. A demo where everything is clean teaches the wrong thing — a reviewer who only ever sees green rows will not know the rejection panel exists.
---
## Screens that carry the argument
- **`/officer/rules`** — every trip-wire with its outcome, including the ones that were *skipped* and why. This is the screen that shows the system refusing to fire on stale data.
- **`/officer/feeds`** — provenance. Mandi rows carry a source: `Agmarknet`, or an amber **Hand** chip for a price somebody typed. By the time a price reaches a feature the two are indistinguishable, so the distinction has to be visible here.
- **`/officer/data-entry`** — upload, preview, commit. Accepted, rejected and overwriting rows stay in three separate groups.
- **`/officer/integration`** — core banking / LMS. The mapping table is the argument: each lender's core names the same eleven fields differently, and a per-bank mapping is what absorbs that. Simulated adapters are badged as simulated on every screen they appear on, and unmapped vendor columns are shown being dropped at the boundary.
- **`/nabard`** — the India choropleth and the zone → state → district drill-down. States with no portfolio are drawn in a neutral grey that is nowhere on the stress ramp, so absence is never read as health.
---
## Checks
```bash
npx tsc -b # types
npx oxlint src # lint
npm run build # production build
```
All three are expected to pass before a commit.
---
## Attribution
State boundaries by the [DataMeet India community](http://datameet.org/) ([datameet/maps](https://github.com/datameet/maps)), used under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). Simplified for display and **not an authoritative depiction of any boundary**. The credit renders on the map itself, as the licence requires.