105 lines
3.8 KiB
Markdown
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).
|