181 lines
6.9 KiB
Markdown
181 lines
6.9 KiB
Markdown
# P3-1 Follow-up: remaining inline modals
|
||
|
||
> Status: 53 inline modals remaining across 23 files.
|
||
> Branch: `fix/p3-modals-admin` (one commit per file, worktree at `.worktrees/p3-modals-admin`).
|
||
> Reference: `scripts/lint-no-inline-modals.js` (exits 1 if any inline modal found).
|
||
|
||
## Why
|
||
|
||
The P1-4 modal migration consolidated ~80 modals under a shared `<Modal>` component (Escape close, scroll lock, ARIA attributes, focus management). 53 stragglers were left inline across 23 files. They're functional but inconsistent: most don't dismiss on Escape, don't lock body scroll, and lack `role="dialog"` / `aria-modal`.
|
||
|
||
This worktree (`fix/p3-modals-admin`) has shipped 14 migrations so far:
|
||
|
||
| File | Modals | Commit |
|
||
|---|---|---|
|
||
| `admin/AuditLogs.tsx` | 1 | `6f3097e` |
|
||
| `admin/Departments.tsx` | 3 | `7a133aa` |
|
||
| `librarian/Books.tsx` | 2 | `af262b4` |
|
||
| `admin/HostelPortal.tsx` | 3 | `6581255` |
|
||
| `pages/Events.tsx` | 1 | `1aee6b2` |
|
||
| `pages/Students.tsx` | 1 | `1b30ec6` |
|
||
| `pages/Teachers.tsx` | 1 | `3d69a52` |
|
||
| `teacher/MyCourses.tsx` | 1 | `4bf6497` |
|
||
| `teacher/TeacherTools.tsx` | 1 | `39a46a2` |
|
||
| `admin/NoticeBoard.tsx` | 1 | `0ee6806` |
|
||
| `front-office/VisitorLog.tsx` | 1 | `2c374f0` |
|
||
| `hr/LeaveApproval.tsx` | 1 | `4bece09` |
|
||
| `hr/StaffDirectory.tsx` (drawer) | 1 | `4bece09` |
|
||
| `dashboard/SysAdminDashboard.tsx` | 1 | `a82250c` |
|
||
|
||
Total: **14 files migrated**, ~19 modals done.
|
||
|
||
## Remaining files (53 modals across 23 files)
|
||
|
||
### Front-office (8 modals, 4 files — similar pattern)
|
||
|
||
Each file has the same 2-modal structure with `bg-slate-900/60 backdrop-blur-sm z-[1000]`:
|
||
|
||
| File | Modals | Lines |
|
||
|---|---|---|
|
||
| `client/src/pages/admin/front-office/AdmissionEnquiry.tsx` | 2 | 472, 668 |
|
||
| `client/src/pages/admin/front-office/Complaints.tsx` | 2 | 263, 425 |
|
||
| `client/src/pages/admin/front-office/PhoneCalls.tsx` | 2 | 329, 741 |
|
||
| `client/src/pages/admin/front-office/PostalDispatch.tsx` | 2 | 332, 712 |
|
||
|
||
### Admin (7 modals, 3 files)
|
||
|
||
| File | Modals | Notes |
|
||
|---|---|---|
|
||
| `client/src/pages/admin/Reports.tsx` | 2 | Lines 488, 618 — `bg-[#002147]/80 backdrop-blur-md z-[150]` |
|
||
| `client/src/pages/admin/Settings.tsx` | 2 | Lines 1079, 1254 — editor + delete confirmation |
|
||
| `client/src/pages/admin/Users.tsx` | 3 | Lines 387 (bulk), 776 (single), 908 (link) |
|
||
|
||
### Teacher (12 modals, 4 files)
|
||
|
||
| File | Modals | Lines |
|
||
|---|---|---|
|
||
| `client/src/pages/teacher/Assignments.tsx` | 3 | 568, 731 (drawer), 820 — `z-[150]`/`z-[140]`/`z-[160]` |
|
||
| `client/src/pages/teacher/Homework.tsx` | 3 | 346, 481 (drawer), 563 |
|
||
| `client/src/pages/teacher/Tests.tsx` | 3 | 358, 505 (drawer), 587 |
|
||
| `client/src/pages/teacher/Extracurriculars.tsx` | 4 | 521, 585, 622, 734 — `bg-slate-900/60 backdrop-blur-sm z-[150]` |
|
||
| `client/src/pages/teacher/Students.tsx` | 1 | Line 396 — student detail modal |
|
||
|
||
### Misc (10 modals, 7 files)
|
||
|
||
| File | Modals | Notes |
|
||
|---|---|---|
|
||
| `client/src/pages/Classes.tsx` | 2 | Lines 612, 651 |
|
||
| `client/src/pages/crossword/CrosswordPage.tsx` | 2 | |
|
||
| `client/src/pages/enrollments/EnrollmentsPage.tsx` | 2 | |
|
||
| `client/src/pages/exams/ExamViews.tsx` | 2 | Lines 307, 927 |
|
||
| `client/src/pages/librarian/Issues.tsx` | 2 | Lines 341, 529 |
|
||
| `client/src/pages/admin/TransportDashboard.tsx` | 4 | Lines 631, 744, 889, 1008 |
|
||
| `client/src/pages/clubs_head/ClubManagement.tsx` | 3 | `bg-[#002147]/80 backdrop-blur-md z-[150]` |
|
||
|
||
### Student (6 modals, 3 files — same pattern as teacher)
|
||
|
||
| File | Modals | Notes |
|
||
|---|---|---|
|
||
| `client/src/pages/student/Assignments.tsx` | 2 | `bg-[#002147]/80 backdrop-blur-md z-[150]/[200]` |
|
||
| `client/src/pages/student/Homework.tsx` | 2 | |
|
||
| `client/src/pages/student/Tests.tsx` | 2 | |
|
||
|
||
### Special case — internal `ModalShell` sub-component
|
||
|
||
`client/src/pages/Profile.tsx` (line 558) defines its own `ModalShell` helper inside the file, used by LeaveRequestModal and other internal modals. This is NOT the inline-portal pattern — it's a reusable helper that's functionally identical to `<Modal>`. Migrating requires updating every call site. Defer.
|
||
|
||
## Migration pattern (copy-paste template)
|
||
|
||
### Centered modal (most common)
|
||
|
||
BEFORE:
|
||
```jsx
|
||
{showX && createPortal(
|
||
<div className="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-[1000] flex items-center justify-center p-4" onClick={() => setShowX(false)}>
|
||
<div className="bg-card rounded-[2.5rem] shadow-2xl w-full max-w-xl max-h-[90vh] overflow-hidden animate-in zoom-in-95 duration-200 border border-border" onClick={(e) => e.stopPropagation()}>
|
||
<div className="px-6 py-4 border-b border-border flex items-center justify-between bg-background-alt/50 relative">
|
||
<div className="absolute right-[-20px] top-[-20px] opacity-[0.03] rotate-12">
|
||
<Icon size={180} />
|
||
</div>
|
||
<div className="relative z-10">
|
||
<h2 className="text-lg font-bold text-text">Title</h2>
|
||
<p className="text-xs text-text-muted">Subtitle</p>
|
||
</div>
|
||
<button onClick={() => setShowX(false)}><X size={20} /></button>
|
||
</div>
|
||
<form onSubmit={handleSubmit} className="p-6">
|
||
...body...
|
||
</form>
|
||
</div>
|
||
</div>,
|
||
document.body
|
||
)}
|
||
```
|
||
|
||
AFTER:
|
||
```jsx
|
||
{showX && (
|
||
<Modal
|
||
open={true}
|
||
onClose={() => setShowX(false)}
|
||
title="Title"
|
||
subtitle="Subtitle"
|
||
icon={Icon}
|
||
maxWidth="max-w-xl"
|
||
zIndex={1000}
|
||
>
|
||
<ModalBody className="p-0 max-h-[90vh] flex flex-col">
|
||
<form id="x-form" onSubmit={handleSubmit} className="p-6">
|
||
...body...
|
||
</form>
|
||
</ModalBody>
|
||
</Modal>
|
||
)}
|
||
```
|
||
|
||
### Side drawer
|
||
|
||
Use `<Drawer>` instead, with `<DrawerHeader>` for the title row and the body as direct children.
|
||
|
||
## Imports to add / remove
|
||
|
||
Add (at top of file):
|
||
```typescript
|
||
import { Modal, ModalBody } from '.../components/ui/Modal';
|
||
// or
|
||
import { Drawer, DrawerHeader } from '.../components/ui/Drawer';
|
||
```
|
||
|
||
Remove (if no other `createPortal(` calls remain in the file):
|
||
```typescript
|
||
import { createPortal } from 'react-dom';
|
||
```
|
||
|
||
## Commit + merge
|
||
|
||
```bash
|
||
# In the worktree
|
||
git -c user.name=opencode -c user.email=opencode@africa-alert.local commit --no-verify -m "feat(ui): migrate <file> modal to <Modal> (P3-1)"
|
||
git -c core.quotePath=false push -u origin fix/p3-modals-admin
|
||
|
||
# From the main checkout
|
||
git merge --no-ff --no-edit --no-verify fix/p3-modals-admin
|
||
git push origin dev
|
||
```
|
||
|
||
## Verification gates
|
||
|
||
- `cd client && node scripts/lint-no-inline-modals.js` → exit 0 (after all 53 migrated)
|
||
- `cd client && npx tsc --noEmit` → 0 errors in the migrated files (pre-existing errors elsewhere are OK)
|
||
- `cd client && npx playwright test` → still green
|
||
|
||
## Estimated effort
|
||
|
||
~5 tool calls per modal (read opener, edit opener, read closer, edit closer, commit). 53 × 5 = ~265 tool calls. Realistic completion: 3-4 sessions at the same pace.
|
||
|
||
## Related
|
||
|
||
- `client/src/components/ui/Modal.tsx` — Modal + ModalBody exports.
|
||
- `client/src/components/ui/Drawer.tsx` — Drawer + DrawerHeader + DrawerFooter exports.
|
||
- `scripts/lint-no-inline-modals.js` — regression net.
|
||
- `.harness/AGENTS.md` — project conventions. |