Central Station / YPM-ARCHIVE-TEACHER-MY-CLASSES-DASHBOARD-CLEANUP

Teacher dashboard cleanup

archive/teacher-my-classes-dashboard-cleanup.md · Updated 2026-05-24
GET /api/tickets/YPM-ARCHIVE-TEACHER-MY-CLASSES-DASHBOARD-CLEANUP

Summary

PR #108 draft is open but conflicting; serial create first unless proven slow

0Questions 0Links 0Comments 2PRs
Spec body Markdown
# Teacher dashboard cleanup

This spec describes what shipped in **PR #108** in `yawp-2.0`, plus the open questions that came out of reviewing that PR. The intent is to reorganize the teacher-facing UI so the right content lives in the right place:

- **Dashboard** (`/app`) — the first item in the side nav and the default landing page after teacher sign-in — shows Assignment Types, a Create Assignment button, and Classes at a Glance.
- **My Classes** (`/app/my-classes`) — stays simple — the teacher's classes listed as full-width cards, one per row.

Welcome header copy on the Dashboard: "Welcome, {first name}!" with subtitle "Your assignment types and class progress, all in one place."

## Current status

`yawp-2.0` PR #108 is open as a draft prototype and currently conflicts with `main`. The product direction from the May email threads is stable:

- Dashboard (`/app`) owns AssignmentTypes, Create Assignment, and Classes at a Glance.
- My Classes stays a simple class grid; this spec is not replacing that page.
- Course view PR #101 owns the inside-a-single-class simplification.
- Create Assignment should start with serial fan-out to existing per-class creation unless real performance evidence forces a bulk endpoint.
- Visibility must defer to [AssignmentType visibility](assignment-type-visibility.md), not the old per-class allow-list.

## Problem

The existing Dashboard showed only a generic welcome header. Assignment types and cross-class status had no home. My Classes was the only teacher-facing surface, but mixing navigation-level class management with deeper assignment workflows made it feel cluttered.

## What was built (PR #108)

### Dashboard (`/app`) — teacher view

The teacher landing page now has two sections, plus a dashboard-level Create Assignment entry point.

**Assignment Types** — displays all `StudentCourse` records visible to the teacher as image cards (same card style as the student Courses view). Each card links to `/app/courses/:id` where the teacher can preview the course and create a document or assignment.

- Cards show the course image (real image from `/api/image/course/:imageId`) or a gradient placeholder if no image is set.
- The "Assignment Types" heading has an orange **Create Assignment** button on the right. (See the Create Assignment Sheet section below.)
- No add/delete controls on the cards themselves, and no "+ Create new assignment type" CTA on the dashboard. Authoring lives inline in the AssignmentType picker, per [AssignmentType visibility](assignment-type-visibility.md).
- **Visibility scopes to AssignmentTypes the teacher has actually used** — i.e. has at least one `Assignment` instance for in a class they teach. This replaces the per-class allow-list (`StudentCourse.allowedInClasses`) the PR #108 prototype currently filters by. Visibility model owner is [AssignmentType visibility](assignment-type-visibility.md); this spec defers to it.

**Classes at a Glance** — a table with one row per class and four status columns:

| Class | In Progress | Submitted | Graded | Released |
|-------|------------|-----------|--------|----------|

- **In Progress** — documents in the class with no active submission yet.
- **Submitted** — submissions that exist but have not been graded.
- **Graded** — submissions with a meaningful grade but not yet released to the student.
- **Released** — submissions where `releasedAt` is set.
- Non-zero counts are clickable badges; zero counts show a dimmed, non-clickable badge.
- Class names link to the class detail view.

> **Note on badge / class-name destinations.** The intended destination is the **new class view** that ships alongside `assignments-unification` — not the current `app.my-classes.$classId` page. The current page has a different (and incompatible) tab contract; PR #108 links to it as a stop-gap, and these links will need to be re-pointed once the new class view exists. The badge contract the new class view must support is in open question 8 below.

Layout sketch:

```
+---------------------------------------------------------------------------+
| Welcome, Kevin Gregorio!                                                  |
| Your assignment types and class progress, all in one place.               |
+---------------------------------------------------------------------------+
| Assignment Types                                  [Create Assignment]     |
+---------------------------------------------------------------------------+
| [Thesis-Driven Essay] [Daily Pages] [5-Paragraph Essay] ...               |
+---------------------------------------------------------------------------+
| Classes at a Glance                                                       |
+----------------------+-------------+-----------+----------+--------------+
| Class                | In Progress | Submitted | Graded   | Released     |
+----------------------+-------------+-----------+----------+--------------+
| Grade 11 — Period 3  |    [20]     |  [1427]   |   [0]    |    [95]      |
+----------------------+-------------+-----------+----------+--------------+
```

### Create Assignment Sheet (Dashboard-level)

Opened from the **Create Assignment** button next to the Assignment Types heading. The Sheet mirrors the existing per-course `CreateAssignmentSheet` (`app.courses.$id/create-assignment-sheet.tsx`) so teachers see the same form they already know, with two structural additions:

1. **Assignment type** — `Select` dropdown at the top of the form. (The per-course form omits this because it's already scoped to a course; the dashboard form is not scoped, so the teacher picks here.)
2. **Assign to** — a checkbox list of the teacher's non-archived classes, replacing the per-course form's single-class Select.

All other fields are identical to the per-course form: title (optional), prompt (required), tutor context (optional), due date (optional). Due date applies to all selected classes — multi-due-date support is out of scope for this pass.

Submit posts to `/api/assignments/create` with payload `{ studentCourseId, classIds[], title, prompt, tutorContext, dueDate, intent: "create-assignment" }`. See open question 7 for whether this should be a real bulk endpoint or a serial fan-out to the existing per-class handler.

### My Classes (`/app/my-classes`)

Unchanged from the original design — full-width cards, one per row. Each card shows:
- Class name (Grade • Period, with optional title)
- School name and student count
- Orange clipboard badge for ungraded submissions, blue send badge for graded-but-unreleased
- Open, Grade (if ungraded > 0), Release (if graded-unreleased > 0) action buttons

### Course detail page (`/app/courses/:id`) — teacher-specific

When a teacher visits a course detail page (reached by clicking an Assignment Type card on the Dashboard), the New button becomes a dropdown with two options:

- **Document** — creates a test document for the teacher using the existing `createStudentDocumentForCourse` flow, so they can experience the course as a student would.
- **Assignment** — opens the per-course `CreateAssignmentSheet` (single-class scope, no AssignmentType picker), where the teacher fills in title (optional), prompt (required), tutor context (optional), due date (optional) and submits to `/app/my-classes/:classId` with `intent=create-assignment`.

Students still see the original plain New button.

The dashboard-level Create Assignment Sheet and the per-course one coexist as two entry points to the same action — see open question 7 on whether the underlying endpoints should also unify.

## Data sources (real DB queries — no mock data)

- **Assignment Types**: AssignmentTypes the teacher has at least one `Assignment` instance for, in a class they teach. Concretely (per [AssignmentType visibility](assignment-type-visibility.md)):
  ```
  AssignmentType where
    isSystem = false
    AND assignments.some(a => a.class.teachers.some(t => t.id = me.teacherProfileId))
  ```
  Replaces the PR #108 prototype's `prisma.studentCourse.findMany` + per-class allow-list filter.
- **Classes at a Glance**: for each of the teacher's classes, counts submissions by status using the same `hasMeaningfulGrade` helper already used in the My Classes index loader.
- **Teacher classes in assignment sheet**: `prisma.class.findMany` filtered to the teacher's non-archived classes.

## File paths in `yawp-2.0`

### Changed
- `services/web-app/app/routes/app._index/route.tsx` — teacher section replaced; real DB queries for `assignmentTypes` and `coursesGlance`
- `services/web-app/app/routes/app._index/components/assignment-types-list.tsx` — clean card grid using `StudentCourse` image API
- `services/web-app/app/routes/app._index/components/classes-at-a-glance.tsx` — color-coded badge table; badges link to `?tab=` params (placeholder, will re-point to new class view — see open question 8)
- `services/web-app/app/routes/app.my-classes/route.tsx` — reverted to simple `<Outlet />` layout wrapper
- `services/web-app/app/routes/app.my-classes._index/route.tsx` — single-column full-width card layout (original style restored)
- `services/web-app/app/routes/app.courses.$id/route.tsx` — teacher classes added to loader; teacher-specific New dropdown button

### Added
- `services/web-app/app/routes/app._index/components/create-assignment-sheet.tsx` — dashboard-level Sheet with AssignmentType picker + multi-class checklist
- `services/web-app/app/routes/app.courses.$id/create-assignment-sheet.tsx` — per-course slide-over (single-class), submits to existing `create-assignment` action

### Removed
- `services/web-app/app/routes/app._index/components/courses-list.tsx`
- `services/web-app/app/routes/app._index/components/training-list.tsx`
- `services/web-app/app/routes/app.my-classes/components/` — all components moved or deleted

## Decisions

- Assignment Types on the Dashboard are `StudentCourse` records — no new `AssignmentType` model needed for this UI layer. The `assignments-unification` schema work remains a separate effort.
- **Visibility model defers to [AssignmentType visibility](assignment-type-visibility.md)**: org-scoped sharing, no admin allow-list, dashboard scopes to types the teacher has actually used. The PR #108 prototype filters by per-class allow-list as a placeholder; engineering should swap to the visibility-spec query before handoff is considered done.
- **No "+ Create new assignment type" CTA on the dashboard.** The only authoring surface is the inline create-new affordance in the AssignmentType picker (per the visibility spec). The orange **Create Assignment** button on the dashboard creates a new `Assignment` (using an existing AssignmentType), not a new AssignmentType.
- The Dashboard is two sections only — Assignment Types and Classes at a Glance. The earlier Courses and Training tabs were dropped.
- The New dropdown on the course detail page lets teachers create test documents or assignments without leaving the course context, in addition to the dashboard-level Create Assignment button.
- **Badge destination**: clicking a badge (or class name) lands the teacher on the new class view (shipping with `assignments-unification`) for that class, filtered to the relevant state. The class view's natural grouping is by assignment, so e.g. clicking "Submitted: 1427" lands on Period 3's class view showing the per-assignment breakdown of submitted-but-not-graded papers. The four states the class view must support as deep-link targets: in-progress, submitted (to-grade), graded (not-yet-released), released. PR #108 currently links to the old `app.my-classes.$classId` route as a placeholder — re-point during the new class view rollout.

## Non-goals

- Bulk operations on assignment types (clone, archive, share).
- A separate library page for assignment types.
- Student-side dashboard changes.
- Multiple due dates in a single assignment creation form.

## Edge cases

- Teacher has no classes: Classes at a Glance shows empty state; Create Assignment Sheet shows empty checklist.
- StudentCourse has no image: gradient placeholder shown.
- Teacher has no classes when on course detail page: Assignment dropdown item is disabled.
- All papers released: non-released columns show dimmed `0` badge.

## Resolved during PR #108 review (2026-05-06)

Walking through the PR #108 prototype with a real teacher account surfaced a cluster of questions about how AssignmentTypes become visible to a teacher. All resolved below; PR #108's per-class allow-list filter is a placeholder and needs to be swapped for the visibility-spec query before handoff is considered complete.

- **Visibility model, scope, and authoring** — defer to [AssignmentType visibility](assignment-type-visibility.md). Org-scoped sharing, no admin allow-list, dashboard scopes to types the teacher has actually used (has at least one `Assignment` for in a class they teach). Picker shows all org-wide types. Creation happens inline from the picker — no dashboard CTA for creating AssignmentTypes. The old `StudentCourse.allowedInClasses` relation is going away as part of `assignments-unification`; the visibility spec has the migration plan.
- **Badge / class-name destinations** — land on the new class view (shipping with `assignments-unification`) for that class, filtered to the relevant state. The class view groups by assignment, so e.g. "Submitted: 1427" lands on the per-assignment breakdown of submitted-but-not-graded papers in that class. Four states must be deep-linkable: in-progress, submitted (to-grade), graded (not-yet-released), released. Coordinate with whoever owns the new class view spec on the param naming. PR #108's links to the old `app.my-classes.$classId` route are a placeholder — re-point during the new class view rollout.

## Engineering call (deferred to Bryant)

- **Bulk vs. per-class create endpoint.** The dashboard Create Assignment Sheet posts to `/api/assignments/create` with `{ studentCourseId, classIds[], title, prompt, tutorContext, dueDate, intent }`. The existing per-course `CreateAssignmentSheet` posts to `/app/my-classes/{classId}` with `intent=create-assignment`. Two patterns now coexist — pick one:
  - **Option A — Real bulk endpoint** that accepts `classIds[]` and fans out server-side. More correct (atomic), but new code.
  - **Option B — Serial fan-out** from the dashboard form to each `/app/my-classes/{classId}` (reuse the existing handler). Faster to ship, no new endpoint, but no atomicity guarantee — if class 3 of 5 fails, classes 1-2 are already created.
  - **Option C — Hybrid**: single-class checks route through the existing per-class handler; multi-class goes bulk.

## Operational note

- **Demoability.** A demo screenshot of the dashboard with three tiles can't be produced from a real account without first making sure the demo teacher has assigned each of the example AssignmentTypes at least once (under the new visibility model). Either seed test assignments for the demo account, or accept that the demo dashboard reflects the demo teacher's actual usage history.

## Test plan

Manual QA:
- Dashboard shows Assignment Types as image cards with real images.
- Create Assignment button on the Dashboard opens the multi-class Sheet; submitting creates assignments across all checked classes.
- Classes at a Glance table shows correct counts for In Progress / Submitted / Graded / Released.
- Non-zero badge navigates to the class detail at the correct tab; zero badge is not clickable.
- Class name in the table links to the class detail view.
- My Classes shows full-width single-column cards (original style).
- On a course detail page, teachers see `New ▾` dropdown; students see plain `New +` button.
- Document option creates a test doc and redirects to the document editor.
- Per-course Assignment option opens the slide-over; selecting a class and submitting creates the assignment.
Repo sync Not recorded

No repo sync metadata recorded yet.