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

105 lines
3.8 KiB
Markdown

# 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:
```json
{
"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 Scheme** — `Export 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).