geocrop-platform./apps/nextgen/.harness/changelogs/2026-07-17-p1-ux-7-class-ro...

3.8 KiB

2026-07-17 — Class Room page (P1-UX-7 / PDF §9) + VC/VM buttons

Branch: feat/p1-ux-7-course-scheme Worktree: .worktrees/feat-p1-ux-7-course-scheme

Why

PDF §9 (Academic Structure / Course Scheme/Plan) calls for a teacher/admin landing page with three sub-tabs and a "Virtual Class Room" button on every lesson row. PDF §3 (Virtual Classroom Integration) requires the same lesson to expose Google Meet / Google Classroom links.

The two are tightly coupled: the VC/VM button reads meet_url / classroom_url, columns added in P1-UX-3 (feat/p1-ux-3-meet-classroom, commit 64d6389). This PR lands the front-end slice plus a small backend endpoint and a forward-port of the P1-UX-3 migration so dev picks the columns up too.

Changes

server/src/database/migrations/knex/2026071700000009_lesson_plan_virtual_classroom.js (new)

Identical schema work as …0008_… (committed on feat/p1-ux-3-meet-classroom), but forwarded onto dev so the endpoint can query the columns today. Idempotent; same try/catch swallow as 0008.

server/src/controllers/class-room.controller.js (new)

GET /api/class-room returns an aggregated payload the UI can render without further joining:

{
  "lesson_plans": [...],
  "total": 12,
  "with_live_link": 4,
  "upcoming": 2
}

Visibility: privileged (admin / principal / systems_admin) sees every row; teachers see rows they authored; student / parent return an empty list with a "not yet exposed" note (the student-facing scope is a follow-up that needs the parent's parent_students join — not in this slice).

server/src/index.js (modified)

Mounts the new controller at /api/class-room.

client/src/pages/teacher/ClassRoom.tsx (new)

Three sub-tabs per PDF §9:

  • Learning Material — every lesson plan with date / subject / teacher. Renders a "VC" button (Google Meet) if meet_url is set, a "VM" button (Google Classroom) if classroom_url is set. Color-coded matching the action.
  • Export SchemeExport Scheme button (was Export Intel) downloads a CSV of all rows in the active filter.
  • Learner — placeholder summary pointing to the parent-side AcademicProgress page for the full subject-scoped rollup.

Header shows three stat cards: Total / With Live Link / Upcoming.

client/src/App.tsx (modified)

Adds /class-room route under both principal and teacher arms of the role-aware getRoutes() map. Gated via <ProtectedRoute allowedRoles={…}>.

client/src/components/Nav.tsx (modified)

Adds a "Class Room" entry to both the teacher and principal nav entries (label is terminology-aware via the new T.PAGE_TITLE_COURSE_SCHEME key in lib/terminology.ts).

server/scripts/smoke-class-room.js (new)

Smoke-test script that boots the server, calls /api/class-room as admin and as student, and prints status + body. Run via node scripts/smoke-class-room.js.

Out of scope (follow-ups)

  • Student-side "Join Class" surface (/api/class-room is gated to teacher/principal for this slice). The map of student → subjects already exists via enrollments; surfacing it is a small follow-up.
  • Per-learner rollup (the "Learner" tab content). Currently a placeholder pointing to AcademicProgress.tsx.
  • Tier-aware switch (PDF §5). ACTIVE_TIER in lib/terminology.ts is hard-coded tertiary; tier wiring is P1-UX-5b's other half.

Verification

  • node src/database/init.js against a fresh DB → 8 + 1 migrations applied, demo users seeded, both meet_url and classroom_url columns present.
  • node scripts/smoke-class-room.js → admin 200 (empty rows), student 200 (note returned).
  • npx vitest run → 2 / 3 spec files green; 1 pre-existing failure in paynow-webhook.test.js ("paid_amount undefined" — same failure as on dev, not caused by this PR).