5.7 KiB
5.7 KiB
Track B — Fees & Hardcoded Copy
Owner: Person B
Branch: fix/stubs-fees-and-copy (worktree at .worktrees/stubs-fees-and-copy/)
Files in scope: client/src/pages/Fees.tsx, client/src/pages/teacher/MyCourses.tsx, client/src/pages/dashboard/AdminDashboard.tsx (limited slices per task).
No backend changes expected.
Conventions (from AGENTS.md)
- Work in a worktree branched from
dev. Do not edit the main checkout. - Use the shared axios instance at
client/src/store/api.ts. - Drop unused lucide-react imports when removing a button.
- PWA service worker is only built by
npm run build. - Do not commit or push without an explicit "go" from the reviewer.
Tasks
B1. Fees — Fee-group "More actions" button
- File:
client/src/pages/Fees.tsx:124-127 - Current: The
<MoreVertical />icon on each fee group hasonClick={() => { console.warn('MoreVertical on fee group list — not yet implemented'); }}. The TODO comment in source confirms it. - Desired: A real dropdown menu with Edit, Archive, and Duplicate (matches what
Classes.tsxalready offers on class subjects). Edit opens a modal pre-filled with the row'sname,description,amount,due_date. Archive callsDELETE /fees/groups/:id(orPATCHwithis_deleted=1— match what backend exposes) and reloads. - Acceptance: Clicking the icon opens a menu. Edit → modal saves. Archive → row disappears after reload. No
console.warnfrom this code path.
B2. Fees — Fee ledger status filter
- File:
client/src/pages/Fees.tsx:179-187 - Current: Filter button does
window.prompt('Filter by status …'). The free-text answer is logged but never applied.loadData()re-fetches the full set regardless. - Desired: Replace prompt with a
<select>(all / paid / partial / unpaid) bound tofeeStatusFilterstate, applied client-side tostudentFees. Use the same visual treatment as the existing status pills in the table headers. - Acceptance: Picking a status narrows the table live. No
window.prompt.
B3. Fees — Hardcoded stat copy and "Grade 10-B" pill
- File:
client/src/pages/Fees.tsx:95, 105, 115, 238 - Current (all literal strings lying to the user):
:95<span>+12% from last month</span>— should derive from comparing this period's collected to last month's.:105<span>82% Target achieved</span>— should derive fromtotalCollected / (totalCollected + totalOutstanding), or drop "Target achieved" entirely if there is no configured target.:115<span>Review required</span>— remove if no real review queue exists, or replace withpartialCasescount:\{partialCases}{partialCases === 1 ? 'case' : 'cases'} awaiting review``.:238<p ...>Grade 10-B</p>— this is the per-row class label; it ignoressf.class_name. Rendersf.class_name || '—'.
- Acceptance: Every one of the four sentences matches the actual data on screen. No literal class name "Grade 10-B" appears in the row.
B4. teacher/MyCourses — Mock class fallback on API failure
- File:
client/src/pages/teacher/MyCourses.tsx:88-92 - Current:
catchblock seedssetClasses([{Grade 10-B}, {Grade 11-A}, {Advanced Placement}]). The mock leaks into the "Create New Course" modal's<select>. - Desired: Replace the mock with
setClasses([])(or whatever the canonical empty-state in the codebase looks like — checkEvents.tsx:57,Students.tsx:74,Attendance.tsx:90for the pattern). The Create-Course modal's<select>should render a single disabled<option>No classes available</option>when empty. - Acceptance: Kill the API server mid-test (
docker-compose stop serveror just pointapi.getat a dead host). The Create modal does not list fake classes; it shows an empty/disabled state.
B5. dashboard/AdminDashboard — Fake avatar pile
- File:
client/src/pages/dashboard/AdminDashboard.tsx:153-156 - Current:
[1, 2, 3, 4].map(i => <Avatar firstName="Student" />)— every avatar labelled "Student". Decorative. - Desired: Pull the top N actual students by outstanding balance. If a backend sort isn't available, fetch
users?role=student&limit=4and render instats.pendingFees-descending order using the existing Avatar component + initials. If fewer than 4 students exist, render only those that do, then the+Nchip. If zero, only the+Nchip (or hide the row). - Acceptance: Avatars show real student names/initials. No "Student" hardcoded label.
Cross-cutting checks
grep -n "82% Target achieved\|+12% from last month\|Review required\|Grade 10-B" client/src/pages/Fees.tsxreturns no hardcoded marketing-copy hits (real data values still render).grep -n "Grade 10-B\|General Section" client/src/pages/teacher/MyCourses.tsx— only the legitimate fallback'General Section'at line 65 remains if you keep it as thesubject.class_name || 'General Section'default; remove the mock-array literal.npm run buildis green in your worktree.
Manual test
- As
bursar@school.com(or any fee admin), open/fees. Filter the table by status (B2). Edit a fee group (B1). Verify the four stat cards and the row labels match the data (B3). - As a teacher, open
/teacher/coursesand click "Create New Course". Kill or block the backend first if you can — see the modal show an empty class list, not fake classes (B4). - As
school_admin, open/admin/dashboard. The pending-fees widget shows real students in the avatar pile (B5).
Out of scope here (other tracks)
- Login, Profile, password resets — see Track A.
ExamEditor.tsxandSettings.tsxalert-soundness work — see Track C.- General "swap all
alert()to toast" sweep across the rest of the app — Track C's C3 lays the foundation.