# 2026-07-17 — P1-4 modal migration: Fees.tsx (Track A continuation)

Branch: `feat/p1-4-modals-fees`
Worktree: `.worktrees/feat-p1-4-modals-fees`

## Why

P1-4 from the readiness audit asks for every inline modal to use the
shared `<Modal>` component Track A introduced. BankingAccounts.tsx and
8 finance/* files already migrated. This PR continues the sweep with
the top-level `Fees.tsx` (4 modals).

## Changes

### `client/src/pages/Fees.tsx`

Migrated 4 modals to `<Modal>` + `<ModalBody>`:

- **Create Fee Group** — 2-field form (name, amount)
- **Assign Fees** — 2 dropdowns (student, fee group)
- **Record Payment** — dropdown + amount input
- **Edit Fee Group** — name / description / amount / due date

### Notes

The page's open `<button className="text-2xl font-light">×</button>`
header close button is removed — `<Modal>` injects its own close button
inside `<ModalHeader>` (already part of `<Modal>` when no `icon`
prop is supplied). Same back-button-on-Escape + click-outside-to-close
behaviour comes for free.

Each form uses an explicit `id="…-form"` so the footer-submit button
can use `form="…"` to post outside the form element. This preserves
the exact button placement at the modal footer (matching the
`Footer` slot in Track A's reference).

The `createPortal`-based row context menu (lines ~517 in the source)
is NOT a modal — it's a row-popover — and was left untouched. Lint
script counts it as 0 because it uses neither `fixed inset-0` nor
`backdrop-blur-sm`.

## Out of scope

The remaining 14 inline modals split across:

- `HRManagement.tsx` (7)
- `Inventory.tsx` (4)
- `Library.tsx` (3)
- `parent/AcademicProgress.tsx` (4)
- 6 in `finance/*` (StudentGroups × 3, StudentGroupDetail, and
  side-drawers in Invoices + Payslips needing a sibling `<Drawer>`
  component)

…land in subsequent PRs.

## Verification

- `node scripts/lint-no-inline-modals.js` → `Fees.tsx` count
  dropped 4 → 0.
- `npx tsc --noEmit` clean for `Fees.tsx` (no new TS errors).
