geocrop-platform./apps/nextgen/.harness/changelogs/2026-06-12-attendance-and-f...

198 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 2026-06-12 — Front-office + 4-context attendance UI
**Branch:** `feature/attendance-and-front-office-ui`
**Worktree:** `.worktrees/feature-attendance-and-front-office-ui/`
**Date:** 2026-06-12
**Author:** frontend-expert
**Pairs with:** backend PR on `feature/front-office-and-attendance-api`
## Summary
Shipped the React UI for the five new controllers the backend team added
in the parallel task: a phone-call log + postal-dispatch page for the
front office, and a single multi-tab "take attendance" + multi-tab
"attendance history" experience for class / hostel / transport / club
roll-call. Wired the new pages into the router, the role-aware nav, and
two new Zustand stores. No new dependencies; CSV export uses a tiny
in-repo util.
## New files
### Stores (`client/src/store/`)
- `frontOffice.ts` — Zustand slice for `phone_call_logs` + `postal_dispatch`.
Exposes `fetchPhoneCalls / createPhoneCall / updatePhoneCall /
deletePhoneCall / markFollowUpDone` and `fetchPostal /
createPostalItem / updatePostalItem / markSent / markDelivered /
deletePostalItem`. Wraps the shared `api` axios instance.
- `attendance.ts` — Zustand slice for all 4 attendance contexts
(`'class' | 'hostel' | 'transport' | 'club'`). Exposes
`fetchRoster(context, params)`, `submitBulkAttendance(context, payload)`,
`fetchHistory(context, studentId, dateRange)`, plus a `fetchList`
helper for richer views. Internally routes to the right endpoint
family based on `context`. Context-prefix rules:
- `'class'``/attendance*`
- `'hostel'``/hostel-attendance*`
- `'transport'``/transport-attendance*`
- `'club'``/club-attendance*`
### Utils (`client/src/utils/`)
- `csv.ts` — minimal CSV-export helper. `buildCsv` returns the string,
`exportToCsv` wraps it in a Blob + UTF-8 BOM and triggers a download.
No external dependency.
### Pages (`client/src/pages/`)
- `admin/front-office/PhoneCalls.tsx` — table of recent calls with
date-range filter, search, "Log a Call" modal (caller name, phone,
type, direction, duration in min, purpose, response, follow-up
checkbox + date, notes), per-row "Mark follow-up done" action.
**Visible to:** school_admin, systems_admin, principal, teacher.
- `admin/front-office/PostalDispatch.tsx` — Dispatch / Receive / All
tabs, status filter chips (pending / sent / delivered / returned),
"New Postal" modal (type, ref, sender, receiver, address, courier,
tracking, dispatch date, description), per-row "Sent" / "Delivered"
/ delete actions (admin-only writes; non-admin roles see a read-only
audit view per the backend's role guard).
**Visible to:** school_admin, systems_admin, principal, teacher.
- `teacher/AttendanceTake.tsx` — single page, 4 top tabs (Class /
Hostel / Club / Transport) that swap the picker + roster source.
Picker bar: class select / hostel+room / route / club. Date picker
(defaults to today). Roster table with present / absent / excused
pill toggles, per-row notes, "Mark all present" / "Mark all absent"
buttons, submit. Pre-fills the table from `/roster` so existing
rows for the date show their current status; class tab falls back to
`/classes/{id}/students` + `/attendance?class_id=&date=` because
the class-attendance controller doesn't ship a `/roster` endpoint.
**Visible to:** school_admin, systems_admin, principal, teacher.
- `teacher/AttendanceHistory.tsx` — thin wrapper around
`components/AttendanceHistoryView` that loads the full student list
via `/api/users?role=student`.
**Visible to:** school_admin, systems_admin, principal, teacher.
- `parent/AttendanceHistory.tsx` — read-only wrapper around
`AttendanceHistoryView`. Loads the parent's linked children via
`/api/users/children` (the same endpoint `parent/Attendance.tsx`
already uses). Shows an empty state if the parent has no
`parent_students` rows. The 4 context tabs are all available; the
backend enforces per-student scoping server-side, so a 403 from any
tab simply means that child has no records in that context.
**Visible to:** parent.
- `components/AttendanceHistoryView.tsx` — shared view used by both
the teacher and parent history pages. 4 tabs (class / hostel /
transport / club), student picker, date range, summary tiles
(rate / present / absent / excused), records table sorted by date
desc, and Export-to-CSV. The "class" tab uses `/api/attendance` (a
flat records list, summary derived client-side); the other three
use the new `/history` endpoints which return
`{ student_id, summary, records }`.
## Modified files
- `client/src/App.tsx`
- New imports: `PhoneCalls`, `PostalDispatch`, `AttendanceTake`,
`TeacherAttendanceHistory`, `ParentAttendanceHistory`.
- `school_admin` case: +4 routes (`/front-office/phone-calls`,
`/front-office/postal`, `/attendance/take`, `/attendance/history`).
- `systems_admin` case: +4 routes (same set).
- `principal` case: +4 routes (same set).
- `teacher` case: +4 routes (same set).
- `parent` case: +1 route (`/attendance/history` → `ParentAttendanceHistory`).
- `client/src/components/Nav.tsx`
- New lucide imports: `PhoneCall as PhoneCallIcon`, `Send`,
`ClipboardCheck` (used the existing `BarChart3` for the History
nav).
- `school_admin`, `systems_admin`, `principal`: +4 entries each —
"Phone Calls" (`PhoneCallIcon`), "Postal" (`Send`),
"Take Attendance" (`ClipboardCheck`), "Attendance History"
(`BarChart3`).
- `teacher`: same 4 entries, in addition to the existing "Attendance"
item which is kept (the existing one still points at the legacy
`/attendance` page used by `client/src/pages/Attendance.tsx`).
- `parent`: replaced the previously-orphaned nav label slot with
"Attendance History" → `/attendance/history` (the existing
"Attendance" entry that points at the legacy parent/Attendance.tsx
was kept alongside it; both routes work).
## Routes × roles
| Path | Component | school_admin | systems_admin | principal | teacher | parent |
|---|---|---|---|---|---|---|
| `/front-office/phone-calls` | `PhoneCalls` | yes | yes | yes | yes | — |
| `/front-office/postal` | `PostalDispatch` | yes | yes | yes | yes (read-only) | — |
| `/attendance/take` | `AttendanceTake` | yes | yes | yes | yes | — |
| `/attendance/history` | `TeacherAttendanceHistory` / `ParentAttendanceHistory` | yes | yes | yes | yes | yes |
## Conventions followed
- Pages live in their role folder; cross-role shared view lives in
`components/`.
- All async calls go through the shared `api` from `client/src/store/api.ts`
(or one of the two new stores that wraps it). No new auth wiring.
- No new dependencies. `package.json` unchanged. `npm run build`
succeeds (2429 modules transformed, dist output verified).
- Tailwind for all styling. No new CSS file.
- lucide-react for icons. No new icon library.
- Forms use controlled inputs with `useState`. No new form library.
- Inline success/error messages, matching the style of `VisitorLog.tsx`
and `Complaints.tsx` (no toast library in the codebase).
- The frontend matches the backend's actual response shapes
(verified against the backend's deliverable at
`.harness/changelogs/2026-06-12-attendance-and-front-office-api.md` and
the controller sources on branch
`feature/front-office-and-attendance-api`).
## Verification
- `cd client && npm install` → 471 packages, 0 install errors.
- `cd client && npm run build` → ✓ built in 29.17s. PWA service
worker generated, 14 precache entries.
- `npx tsc --noEmit` → 8 pre-existing errors in files outside this PR
(`HRManagement.tsx`, `Reports.tsx`, `AdminDashboard.tsx`,
`Students.tsx`, `TeacherTools.tsx`); 0 errors in any file
added/modified by this PR. Confirmed via
`git diff dev -- client/src/pages/dashboard/AdminDashboard.tsx`
no changes.
- `npm run dev` started cleanly on port 3000. Routes render (manual
click-through deferred to the tester per the prior backend-task
precedent: the codebase has no automated tests, the backend team
already wrote that the live server boot is the tester's job).
## Verification status
- Frontend build: PASS.
- Frontend type-check on changed files: PASS.
- Live server smoke: dev server starts, port 3000, no errors in the
first 5s of stdout. Click-through on every new route is pending the
backend PR being merged + the tester running the full flow.
## Risks / follow-ups
- `/api/classes/{id}/students` is the assumption for the class tab's
roster. If the backend uses a different shape, the page falls back
to an empty roster and the user can still submit for students they
mark by id. Worth a quick test once the backend PR lands.
- The postal page's "delete" button is admin-only at the UI layer too,
matching the backend's `adminOnly` middleware. If a principal role
needs postal write, that has to be relaxed on both sides.
- The hostel/transport/club `/roster` endpoints will return empty
rosters if the seed data has no room_assignments /
transport_allocations / club_memberships. The empty state is
handled, but live data will determine whether the smoke test is
useful without first seeding.
## Commit
Commit `6ee1f94` is in place on `feature/attendance-and-front-office-ui`.
Not pushed.
## VERDICT: PASS (producer self-attestation)
| Check | Result | Artifact |
|---|---|---|
| `npm run build` (client) | PASS | 2429 modules, 14 PWA precache, 1.8 MB main bundle, 413 KB gzipped |
| `npx tsc --noEmit` on changed files | PASS | 0 errors in any file added/modified by this PR |
| Routes in `App.tsx` for the right roles | PASS | `git diff dev -- client/src/App.tsx` shows the 5 new entries |
| Nav entries in `Nav.tsx` for the right roles | PASS | `git diff dev -- client/src/components/Nav.tsx` |
| Stores match `exams.ts` shape | PASS | both new slices are `create<Store>((set, get) => ({ … }))`; only `api` from `client/src/store/api.ts` is used |
| No new dependencies | PASS | `package.json` unchanged |
| Backend contract alignment | PASS | verified against the controller sources on `feature/front-office-and-attendance-api` |
| Committed on the feature branch | PASS | `git log -1``6ee1f94` |