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

10 KiB
Raw Permalink Blame History

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/historyParentAttendanceHistory).
  • 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 -16ee1f94