v2 Admin Spec — Student Management
Student Management
Domain 2 of the Enhanced Candidate A navigation structure. Contains: Students list, Student Detail Page (7 tabs), Submissions, Promoted Students, Failed Sign Ups. Primary workflows served: WF2 (Student Support), WF3 contribution (Semester End -- promotion tracking)
1. Domain Overview
Student Management is the most-used domain. It groups everything that affects an individual student -- profile, academic progress, billing, appointments, and lifecycle actions. Moving Submissions, Promoted Students, and Failed Sign Ups here (from Program and Reports respectively) means all student-centric data is accessible under one domain.
Key design decisions:
- Submissions moved here because they are student activity, not passive content
- Promoted Students and Failed Sign Ups moved here because they are student lifecycle reports
- The Student Detail Page consolidates 5+ screens into a single multi-tab view, reducing WF2 (Student Support) from 5-7 screens to 1-2 screens
Sidebar items:
Student Management
Students <- from People > Students
Submissions (Assessments) <- from Program > Submissions
Promoted Students <- from Reports > Promoted Students
Failed Sign Ups <- from Reports > Failed Sign Ups
Data tables referenced: users, user_profile, user_link_level_tag, student_submitted_assessment, appointments, subscriptions, level_tag, semester, group_members, group_name, assessment
2. Screen: Students List
2.1 Purpose
View, filter, and manage all student accounts. Primary entry point to individual student profiles.
2.2 Entry Points
- Sidebar: Student Management > Students
- Dashboard: alert tile clicks (e.g., "Students Falling Behind" -> filtered list)
- Global search: student result click (goes to Student Detail, not list)
- End Checklist Step 2 (Pass/Fail) deep-links here with
?from=end-checklist&semester=[id]&level=1,2,3,4&final_status=null— see04-semester-management.md §3.8. When?from=end-checklistis present, the screen shows a persistent "← Return to End Checklist for [Semester Name]" banner at top; clicking returns to the End Checklist tab with scroll position preserved.
2.3 Layout
Filter bar + data table + pagination
Breadcrumb: Student Management > Students
2.4 Data Displayed
| Column | Data Type | Source | Sortable | Filterable | Notes |
|---|---|---|---|---|---|
| Name | String | user_profile.first_name + last_name |
Yes | Yes (search) | Row click -> Student Detail Page |
| String | users.email |
Yes | Yes (search) | ||
| Gender | String | user_profile.gender |
Yes | Yes (dropdown: Male/Female) | Display "Male" / "Female" |
| Level | Badge | level_tag.name via user_link_level_tag.level_tag_id |
Yes | Yes (dropdown: Level 0-4, Year 2) | |
| Semester | String | semester.name via user_link_level_tag.semester_id |
Yes | Yes (dropdown) | Shows current/latest semester |
| Enrollment Type | Badge | user_link_level_tag.enrollment_type (new field) |
Yes | Yes (dropdown: Any / Continuing / New / Repeat / Year 2) | Badge: Continuing (blue) / New (green) / Repeat (amber) / Year 2 (purple). Describes why the student is enrolled this semester, distinct from user_semester_status (which is how they finish). Added per STAKEHOLDER-ANSWERS-2026-04-22 §C (Q1). |
| TA | String | Derived from group_members -> group_name.teacher_id -> users |
Yes | Yes (dropdown) | Assigned teaching assistant |
| Status | Badge | users.user_activation_status |
Yes | Yes (dropdown: Active/Inactive) | Green=Active, Gray=Inactive |
| Subscription | Badge | Stripe API or cached | No | Yes (dropdown: Active/Past Due/Cancelled/None) | Shows Stripe subscription status |
| Submissions | Number | Count of student_submitted_assessment for current enrollment |
Yes | No | Total submissions this semester |
| Last Submission | Date | Max student_submitted_assessment.reviewed_date or created date |
Yes | No | Most recent submission date |
| Appointments | Number | Count of appointments for this user_id where apt_time falls in current semester date range |
Yes | No | For mastery students only: scheduled + completed appointments. For non-mastery students: shows "--". Format: "3 / 2" (scheduled / completed) on hover; aggregated count in cell. |
| Final Status | Badge | user_link_level_tag.final_status for current semester |
Yes (sort) | Yes (dropdown: Any / Pass / Fail / Mastery Passed / Not Set) | Badge: Pass (green) / Fail (red) / Mastery Passed (blue) / — (gray) |
2.5 Actions
| Action | Trigger | Behavior | Permission | Navigation | Confirmation |
|---|---|---|---|---|---|
| View Student | Row click | Navigate to Student Detail Page | All roles | -> Student Detail Page (Profile tab) | No |
| Add Student | "Add Student" button (top-right) | Opens create student modal | Admin only | Modal -> refreshes list on save | No |
| Bulk Export | "Export" button + checkbox selection or "Export All" | CSV download of selected/all students | Admin, View-Only | Downloads file, stays on page | No |
| Bulk Deactivate | Checkbox selection + "Deactivate Selected" | Deactivates selected students | Admin only | Stays on page, refreshes | Destructive confirmation |
| Set Pass/Fail | Row action "..." menu → "Set Pass/Fail" | Opens selector modal (Pass / Fail). On save: writes final_status on the student's current user_link_level_tag record. Toast: "[Student name] marked [pass/fail]." Reversible by reopening selector. |
Admin (any student); TA (own assigned students only) | Stays on page, refreshes row | No |
| Set Pass/Fail for Selected | Checkbox selection + "Set Pass/Fail" button in bulk action bar | Bulk selector modal (Pass / Fail). Applies to all selected. Toast: "[N] students marked [pass/fail]." | Admin (any); TA (selection limited to own students) | Stays on page, refreshes | Yes — "Set [N] students to [Pass/Fail]?" |
Add Student modal fields:
| Field | Source Column | Required | Validation |
|---|---|---|---|
| First Name | user_profile.first_name |
Yes | Non-empty |
| Last Name | user_profile.last_name |
Yes | Non-empty |
users.email |
Yes | Valid email, unique | |
| Gender | user_profile.gender |
Yes | Dropdown: Male/Female |
| Age | user_profile.age |
No | Positive integer |
| Phone | user_profile.contact |
No | Numeric with contact_dialcode dropdown |
| Timezone | users.timezone |
No | Timezone dropdown |
| Level | user_link_level_tag.level_tag_id |
Yes | Dropdown: Level 0-4, Year 2 |
| Semester | user_link_level_tag.semester_id |
Yes | Dropdown: available semesters |
On save: creates users record (user_type=3, user_activation_status=1), creates user_profile record, creates user_link_level_tag enrollment record (user_semester_status=1), generates temp_pass (4 chars, lowercase), sends registration email.
2.6 States
| State | Description | Display |
|---|---|---|
| Loaded | Students exist | Data table with results |
| Empty (no students) | No students in system | "No students registered yet." + "Add Student" button |
| Empty (filtered) | Filters return no results | "No students match your filters." + "Clear filters" link |
| Loading | Initial load | Skeleton table (gray bars matching column layout) |
| Error | API failure | "Unable to load students. Please try again." + "Retry" button |
2.7 Role Visibility
- Admin: Full table + all actions
- TA: Filtered to assigned students only (via
group_members->group_name.teacher_idmatching logged-in TAuser_id). No Add Student, no Bulk Deactivate, no Bulk Export. Can use Set Pass/Fail and bulk Set Pass/Fail for Selected, but only for their own assigned students. - View-Only: Full table, no action buttons
- Support Staff: Full table, no action buttons
3. Screen: Student Detail Page (Rich Entity Page)
This is the single most important new screen in the redesign. It consolidates data from Students, Submissions, Payments (Stripe), Appointments, and enrollment records into a single multi-tab view.
3.1 Purpose
Provide a complete view of a student -- their profile, academic progress, billing, appointments, semester history, admin notes, and available actions -- without ever leaving the page. Directly addresses WF2 (Student Support): current state requires 5-7 screens across 3 external tools; Enhanced A requires 1 screen with all data on tabs.
3.2 Entry Points
- Students list: row click
- Global search: student result click
- Dashboard: alert tile link (e.g., failed payment -> Student Detail > Payments tab)
- Promoted Students: student name click
- Failed Sign Ups: student name click
- Submissions list: student name click
- Semester Hub > Enrollment tab: student name click
- Billing > Payment Overview: student name click in alert list
- Billing > Family Plans: member name click
- Direct URL:
/students/{user_id}or/students/{user_id}#payments
3.3 Entity Header (persistent across all tabs)
Data displayed:
| Field | Source | Format |
|---|---|---|
| Full Name | user_profile.first_name + last_name |
Large text |
users.email |
Text with copy icon | |
| Level | level_tag.name via current user_link_level_tag.level_tag_id |
Badge (Level 0-4 / Year 2) |
| Semester | semester.name via current user_link_level_tag.semester_id |
Text |
| TA | Derived from group_members -> group_name.teacher_id -> user_profile |
Text (clickable -> TA Detail Page) |
| Status | users.user_activation_status (0=Inactive, 1=Active) |
Badge (Active=green / Inactive=gray) |
| Subscription Status | Stripe subscription.status via users.stripe_customer_id |
Badge (Active=green / Past Due=orange / Cancelled=gray / Paused=blue / Incomplete=yellow / Unpaid=red / None=gray). See 01-global-patterns.md Section 4.3 for full badge definitions. |
Always-visible actions:
| Action | Trigger | Permission |
|---|---|---|
| Edit Profile | "Edit" button | Admin only |
| View in Stripe | "Stripe ↗" link | Admin only (deep link to https://dashboard.stripe.com/customers/{stripe_customer_id}) |
Breadcrumb: Student Management > Students > [Student Name]
3.4 Tab Structure
| Tab | Label | Badge | Default |
|---|---|---|---|
| 1 | Profile | -- | Yes (default tab) |
| 2 | Submissions | Count of student_submitted_assessment for current enrollment user_link_level_tag_id |
No |
| 3 | Payments | Warning icon if Stripe subscription.status = past_due |
No |
| 4 | Appointments | Count of appointments where apt_time > NOW() |
No |
| 5 | Semester History | Count of distinct user_link_level_tag records for this user_id |
No |
| 6 | Admin Notes | Count of non-deleted notes for this student (hidden for TA role) | No |
| 7 | Actions | -- | No |
Tab behavior follows global pattern (see 01-global-patterns.md section 4.11): lazy-load on first activation, state preserved when switching, URL updates with tab name, tab bar sticky on scroll.
Tab-level loading and error states (applies to ALL tabs unless overridden per-tab):
- Loading: Each tab shows a skeleton layout matching its content structure on first activation. For the Payments tab (which fetches from Stripe), the loading state shows "Loading billing data..." with a spinner; if slow (>3s), updates to "Fetching from Stripe..." per 01-global-patterns.md Section 4.9.
- Error: Each tab shows an inline error message with a "Retry" button if its data fails to load. For the Payments tab specifically, see the Stripe API error state in Section 3.7. For local-data tabs (Profile, Submissions, Appointments, Semester History, Admin Notes, Actions), errors show: "Unable to load [tab name] data. Please try again." with a Retry button.
3.5 Tab: Profile
Data Displayed
Personal Information section:
| Field | Source | Editable | Format |
|---|---|---|---|
| First Name | user_profile.first_name |
Yes (via Edit Profile modal) | Text |
| Last Name | user_profile.last_name |
Yes | Text |
users.email |
Yes | Email with copy icon | |
| Gender | user_profile.gender |
Yes | Male / Female |
| Age | user_profile.age |
Yes | Number |
| Phone | user_profile.contact_dialcode + user_profile.contact |
Yes | "+1 (555) 123-4567" |
| Timezone | users.timezone |
Yes | IANA timezone string (e.g., "America/New_York") |
| Profile Picture | user_profile.profile_picture |
Yes | Thumbnail with upload |
| Device Type | users.device_type |
Read-only | "iOS" / "Android" / "Web" |
Enrollment section:
| Field | Source | Format |
|---|---|---|
| Current Level | level_tag.name via current user_link_level_tag.level_tag_id |
Badge |
| Current Semester | semester.name via current user_link_level_tag.semester_id |
Text |
| Enrollment Type | user_link_level_tag.enrollment_type (new field) |
Badge: Continuing (blue) / New (green) / Repeat (amber) / Year 2 (purple). Orthogonal to Enrollment Status — describes why, not how. |
| Enrollment Status | user_link_level_tag.user_semester_status |
Badge: Enrolled (1=blue), Pass (2=green), Fail (0=red) |
| Elective (Year 2 only) | user_link_level_tag.elective_id -> elective name |
Text, only shown for Year 2 students |
| Is Core Enrollment | user_link_level_tag.is_core_enrollment |
Yes / No |
| Enrollment ID | user_link_level_tag.user_link_level_tag_id |
System reference, small text |
Tags section:
| Field | Source | Format |
|---|---|---|
| Assigned Tags | Tags linked to user via tagging system | Comma-separated pill badges |
System section (collapsed by default):
| Field | Source | Format | Visibility |
|---|---|---|---|
| User ID | users.user_id |
Number | All roles |
| Username | users.username |
Text | All roles |
| User Type | users.user_type |
"Student" (3) | All roles |
| Registration Date | users.created_at (if available) |
Date | All roles |
| Temporary Password | users.temp_pass |
Masked (****), reveal on click | Admin only |
| Push Notification Token | users.gcm_regid |
Truncated text, copy icon | Admin only (debugging) |
Actions
| Action | Trigger | Permission | Behavior |
|---|---|---|---|
| Edit Profile | "Edit" button on Personal Information section header | Admin | Opens edit modal with all editable personal information fields pre-populated |
| Edit Enrollment | "Edit" button on Enrollment section header | Admin | Opens edit modal to change level, semester, enrollment status |
| Manage Tags | "Manage Tags" link in Tags section | Admin | Opens tag assignment modal (multi-select from available tags) |
States
- Loaded: All sections populated with data
- Minimal profile: Some fields empty (e.g., new student, no picture, no phone) -- show dashes ("--") for empty optional fields
- Inactive student: Status badge shows Inactive in header; profile data still fully visible
3.6 Tab: Submissions
Data Displayed
Table of all submissions across all semesters (default filtered to current semester):
| Column | Source | Sortable | Format |
|---|---|---|---|
| Select | Checkbox | -- | Per-row checkbox + header "select all" checkbox for bulk actions |
| Assessment Name | assessment.title via student_submitted_assessment.assessment_id |
Yes | Text |
| Week | student_submitted_assessment.assessment_week |
Yes | "Week 3" |
| Date Submitted | student_submitted_assessment creation timestamp |
Yes | "Mar 1, 2026" |
| Status | student_submitted_assessment.assessment_status |
Yes | Badge: Under Review (0=yellow), Reviewed (1=green), Rejected (2=red) |
| Grade | Review grade field (if reviewed) | Yes | Number or "--" if not yet reviewed |
| Reviewed By | user_profile.first_name + last_name via student_submitted_assessment.reviewed_by |
Yes | Text or "--" |
| Review Date | student_submitted_assessment.reviewed_date |
Yes | "Mar 5, 2026" or "--" |
| Rejection Reason | student_submitted_assessment.rejection_reason |
No | Text (shown in expandable row or tooltip), empty if not rejected |
Filter: Semester dropdown populated from distinct semester.name values across this student's user_link_level_tag records. Default: current semester. Option: "All Semesters".
Summary bar (above table): "X submissions this semester | Y reviewed | Z under review | R rejected | Average grade: N.N"
- X = count of
student_submitted_assessmentfor currentuser_link_level_tag_id - Y = count where
assessment_status = 1 - Z = count where
assessment_status = 0 - R = count where
assessment_status = 2 - Average grade = mean of reviewed submission grades
Per-Week Submission Exception section (above table, collapsible panel labeled "Weekly Submission Overrides"):
Displays the allowed number of submissions per week for this student for the current semester, plus any per-week overrides.
| Field | Source | Format |
|---|---|---|
| Default Weekly Limit | Semester-level setting (e.g., semester.default_submissions_per_week) |
Read-only, e.g., "3 per week" |
| Week Overrides | student_submission_exceptions table (NEW): user_id, semester_id, assessment_week, allowed_submissions, reason, created_by, created_at |
Table: Week |
| Add Exception | Button | Opens modal: Week selector (dropdown of semester weeks), Allowed submissions (integer), Reason (text) |
Per-week override table:
| Column | Source | Format |
|---|---|---|
| Week | assessment_week |
"Week 3" |
| Allowed | allowed_submissions |
Number (e.g., "5") |
| Reason | reason |
Text (truncated, full on hover) |
| Set By | Admin's first+last name via created_by |
Text |
| Set Date | created_at |
"Mar 1, 2026" |
| Actions | -- | Edit / Remove icons |
Actions
| Action | Trigger | Permission | Behavior | Confirmation |
|---|---|---|---|---|
| Play Recording | Play icon on row | All roles | Opens audio player inline or in modal | No |
| View Details | Row click | All roles | Expands row to show full submission details (rejection reason, audio player, review notes) | No |
| Copy Recording Link | "Copy Link" icon on row | Admin, TA | Copies the recording URL to clipboard. Toast: "Recording link copied." | No |
| Download Recording | "Download" icon on row | Admin, TA | Downloads the recording file to the admin's device | No |
| Send Recording Link | "Send Link" icon on row | Admin | Opens modal: pre-populated with student's email and a shareable recording URL; confirm-to-send | Standard: "Send recording link to [student email]?" |
| Reject Under Review | "Reject" icon on row (visible only when assessment_status = 0) |
Admin, TA | Opens modal: rejection reason (required text field). Sets assessment_status = 2, stores rejection_reason, sets reviewed_by = current user, reviewed_date = NOW(). Rejected submissions do NOT count toward the student's weekly submission count. |
Standard: "Reject this submission? The student will be notified and asked to resubmit." |
| Delete Submission | Delete icon on row | Admin only | Removes student_submitted_assessment record |
Destructive: "Are you sure? This will permanently delete this submission. This cannot be undone." |
| Bulk Delete | Checkbox selection + "Delete Selected" button (appears when ≥1 row selected) | Admin only | Deletes all selected student_submitted_assessment records |
Destructive: "Delete [N] selected submissions for [student name]? This cannot be undone." |
| Filter by Semester | Semester dropdown | All roles | Filters table to selected semester's user_link_level_tag_id |
No |
| Add Weekly Exception | "Add Exception" button in Weekly Submission Overrides panel | Admin | Opens modal: Week (dropdown), Allowed submissions (integer), Reason (text). Creates student_submission_exceptions record. |
No |
| Edit Weekly Exception | Edit icon on override row | Admin | Opens modal pre-populated with current values | No |
| Remove Weekly Exception | Remove icon on override row | Admin | Deletes student_submission_exceptions record |
Standard: "Remove the Week [N] exception? This student will revert to the default weekly limit." |
States
- Has submissions: Table populated, summary bar shows counts
- No submissions this semester: "No submissions for [Semester Name]." + "View All Semesters" link (changes filter to "All Semesters")
- No submissions ever: "This student has not submitted any recordings yet."
- No weekly exceptions set: Weekly Submission Overrides panel shows "No week-level exceptions. Student follows the semester default of [N] submissions per week." + "Add Exception" button
- Bulk-select mode: When ≥1 row selected, a sticky action bar appears at the top of the table: "[N] selected" + "Delete Selected" button + "Clear selection" link
3.7 Tab: Payments (Cornerstone billing integration)
This tab eliminates the need to visit Stripe Dashboard or Google Sheets for individual student billing. Addresses workarounds W1 (Stripe dependency), W5 (support staff access), W8 (linked deactivation).
Data fetching: Real-time Stripe API call on tab activation (see 01-global-patterns.md section 5.1). Customer resolved via users.stripe_customer_id -> Stripe Customer API, following the resolution chain in 01-global-patterns.md section 5.2.
Data Displayed
Subscription Status section (source: Stripe GET /v1/subscriptions?customer={stripe_customer_id}&limit=1&status=all):
| Field | Source | Format |
|---|---|---|
| Status | subscription.status |
Badge: Active (green), Past Due (orange), Cancelled (gray), Paused (blue), Incomplete (yellow), Unpaid (red) |
| Plan | subscription.items.data[0].price -> price.nickname or constructed from price.unit_amount + price.recurring.interval |
"Monthly - $15/mo" or "Annual - $120/yr" |
| Current Period | subscription.current_period_start / subscription.current_period_end |
"Mar 1, 2026 -- Mar 31, 2026" |
| Cancel at Period End | subscription.cancel_at_period_end |
"Yes -- cancels on [cancel_at date]" or hidden if false |
| Trial | subscription.trial_start / subscription.trial_end |
"Trial: Jan 1 -- Jan 31, 2026" or hidden if no trial |
| Stripe Subscription ID | subscription.id |
sub_xxxxx (small text, copy icon) |
Payment Method section (source: Stripe GET /v1/customers/{id}/payment_methods?type=card&limit=1):
| Field | Source | Format |
|---|---|---|
| Card | payment_method.card.brand + payment_method.card.last4 |
"Visa ending in 4242" |
| Expiry | payment_method.card.exp_month / payment_method.card.exp_year |
"03/2028" |
Payment History section (source: Stripe GET /v1/invoices?customer={stripe_customer_id}&limit=12):
| Column | Source | Format |
|---|---|---|
| Date | invoice.created (Unix timestamp -> formatted) |
"Mar 1, 2026" |
| Amount | invoice.amount_paid (cents -> dollars) |
"$15.00" |
| Status | invoice.status |
Badge: Paid (green), Open (yellow), Void (gray), Uncollectible (red) |
| Invoice | invoice.hosted_invoice_url |
"View Invoice ↗" link (opens in new tab) |
Pagination: "Show More" button loads next 12 invoices (starting_after cursor).
Special Flags section (source: local DB tables):
| Flag | Source | Display |
|---|---|---|
| Scholarship | student_scholarships table: user_id, scholarship_program_id, tier |
"50% Annual Scholarship" or "--" |
| Family Plan | family_members table: user_id, family_group_id -> family_groups.name |
"Ahmed Family (3 members)" with link to Billing > Family Plans screen, or "--" |
| Deferment | student_deferments table or Stripe subscription.pause_collection |
"Paused until Jun 1, 2026" or "--" |
Upcoming Payment section (source: Stripe GET /v1/invoices/upcoming?customer={stripe_customer_id}):
| Field | Source | Format |
|---|---|---|
| Next Payment Date | upcoming_invoice.next_payment_attempt (Unix timestamp) |
"Apr 1, 2026" |
| Amount | upcoming_invoice.amount_due (cents -> dollars) |
"$15.00" |
| Proration | upcoming_invoice.lines detail if proration applies |
"Includes $5.00 proration credit" or hidden |
Actions
| Action | Trigger | Permission | Behavior | Confirmation |
|---|---|---|---|---|
| Cancel Subscription | "Cancel" button | Admin only | Calls Stripe POST /v1/subscriptions/{id} with cancel_at_period_end=true |
Destructive: "This will cancel billing at the end of the current period ([current_period_end formatted]). The student will retain access until then." |
| Cancel + Deactivate (Linked) | "Cancel & Deactivate" button | Admin only | Cancels Stripe subscription (cancel_at_period_end=true) + sets users.user_activation_status=0 in single transaction |
Destructive: "This will cancel [student name]'s Stripe subscription at the end of the current billing period and deactivate their account. They will lose access to all content immediately." |
| Pause Billing | "Pause Billing" button | Admin only | Sets subscription.pause_collection via Stripe POST /v1/subscriptions/{id} with pause_collection[behavior]=void and resume date |
Standard: "Billing will be paused. Set a resume date:" + date picker |
| Resume Billing | "Resume Billing" button (visible only when paused) | Admin only | Clears pause_collection via Stripe POST /v1/subscriptions/{id} with pause_collection="" |
Standard: "Resume billing immediately?" |
| Apply Discount | "Apply Discount" button | Admin only | Shows coupon selector (from Billing > Coupons list), applies via Stripe POST /v1/subscriptions/{id} with coupon={coupon_id} |
Standard: shows selected coupon details and resulting price |
| Mark as Scholarship | "Assign Scholarship" button | Admin only | Opens scholarship program selector (from Billing > Scholarships list), creates student_scholarships record + applies corresponding Stripe coupon |
Standard: select program and tier from list |
| Add to Family Plan | "Link Family" button | Admin only | Opens family group selector (from Billing > Family Plans) or "Create Family" flow. Creates family_members record, updates Stripe subscription with family discount coupon |
Standard |
| View in Stripe | "View in Stripe ↗" link | Admin only | Opens https://dashboard.stripe.com/customers/{stripe_customer_id} in new tab |
No |
| Send Payment Link | "Send Payment Link" button | Admin only | Generates Stripe Payment Link via POST /v1/payment_links, shows URL to copy or sends email to users.email |
Standard: confirm email address |
| Retry Payment | "Retry Payment" button (visible only when past_due) | Admin only | Calls Stripe POST /v1/invoices/{latest_invoice_id}/pay |
Standard: "Attempt to charge the card on file?" |
States
| State | Description | Display |
|---|---|---|
| Active subscription | Normal state | All sections populated |
| No subscription | Student enrolled but no Stripe record (no stripe_customer_id or no subscription found) |
"No subscription found for this student." + "Search Stripe" button (searches by email) + "Create Subscription" button |
| Past due | subscription.status = past_due |
Red banner: "Payment is past due since [latest_invoice.due_date]. Last attempt failed: [latest_invoice.last_finalization_error or charge.failure_message]." + "Retry Payment" and "Cancel" action buttons |
| Cancelled | subscription.status = canceled |
Grayed subscription section. Shows "Cancelled on [canceled_at date]". Payment history section remains visible with full history. |
| Paused/Deferred | subscription.pause_collection is set |
Blue banner: "Billing paused until [pause_collection.resumes_at]." + "Resume Now" and "Extend Deferment" action buttons |
| Stripe API error | Cannot reach Stripe API | Warning banner: "Unable to load billing data from Stripe. Showing cached data." with "Retry" button. Shows local subscriptions table data as fallback: subscriptions.status, subscriptions.start_date, subscriptions.end_date, subscriptions.next_billing_date, subscriptions.cycles_remaining. |
| IAP subscription | Student subscribed via Google Play (detected by users.device_type = "android" + no Stripe subscription + IAP flag) |
Info banner: "This student has a Google Play subscription. Billing is managed through the Google Play Console." Read-only display of IAP status from local user fields. No billing actions available. |
Support Staff View
- All data sections visible (subscription status, payment method [last 4 only], payment history, special flags, upcoming payment)
- NO action buttons visible (Cancel, Pause, Resume, Apply Discount, Assign Scholarship, Link Family, Send Payment Link, Retry Payment -- all hidden)
- "View in Stripe" link hidden (assumption per OQ9: support staff do not have Stripe access)
3.8 Tab: Appointments
Data Displayed
Table of all appointments (past and upcoming) for this student:
| Column | Source | Sortable | Format |
|---|---|---|---|
| Date/Time | appointments.apt_time |
Yes (default: upcoming first) | "Mar 15, 2026 at 3:00 PM" with student's timezone (users.timezone) |
| TA | user_profile.first_name + last_name via appointments.ta_id -> users -> user_profile |
Yes | Text (clickable -> TA Detail Page) |
| Group | group_name.name via appointments.group_id -> group_name |
No | Text |
| Status | Derived from appointment data | Yes | Badge (see below) |
| Rescheduled To | appointments.new_apt_time (if appointments.re_schedule = 1) |
No | "Rescheduled to Mar 20, 2026 at 3:00 PM" or "--" |
Status derivation logic:
| Condition | Status | Badge Color |
|---|---|---|
apt_time > NOW() and re_schedule = 0 |
Upcoming | Blue |
apt_time > NOW() and re_schedule = 1 |
Rescheduled | Yellow |
apt_time < NOW() and attendance confirmed |
Completed | Green |
apt_time < NOW() and no attendance |
No-Show | Red |
| Manually cancelled | Cancelled | Gray |
Filters:
- Time window: upcoming / past / all (default: upcoming)
- Semester: dropdown populated from distinct
semester.namevalues across this student'suser_link_level_tagrecords, plus "All Semesters". Default: current semester. Filters appointments to those falling within the selected semester's date range.
Summary: "X upcoming appointments | Y completed this semester"
- X = count where
apt_time > NOW()andstudent_id = user_id - Y = count where
apt_time < NOW()and completed, filtered to current semester date range
Actions
| Action | Trigger | Permission | Behavior | Confirmation |
|---|---|---|---|---|
| Schedule Appointment | "Schedule" button | Admin | Opens create appointment modal: select group (from student's group_members), date/time, TA (pre-filled from group's teacher_id) |
No |
| Reschedule | Reschedule icon on row | Admin | Opens reschedule modal: new date/time picker. Sets appointments.re_schedule = 1 and appointments.new_apt_time |
Standard: "Reschedule from [old time] to [new time]?" |
| Cancel Appointment | Cancel icon on row | Admin | Sets appointment to cancelled status | Destructive: "Cancel this appointment on [date] with [TA name]?" |
| View in Calendar | "Calendar View" link | All roles | Navigates to Scheduling > Calendar View filtered to this student's TA | No |
| Filter by Semester | Semester dropdown | All roles | Filters table to appointments within the selected semester's date range | No |
States
- Has upcoming: Table with upcoming appointments highlighted (blue left border on rows)
- No upcoming: "No upcoming appointments." + "Schedule Appointment" button (Admin only)
- No appointments ever: "No appointment history for this student."
- No appointments in selected semester: "No appointments for [Semester Name]." + "View All Semesters" link (changes semester filter to "All Semesters")
3.9 Tab: Semester History
Data Displayed
Table of all semesters the student has been enrolled in, derived from all user_link_level_tag records for this user_id:
| Column | Source | Sortable | Format |
|---|---|---|---|
| Semester | semester.name via user_link_level_tag.semester_id |
Yes (chronological default, most recent first) | Text (clickable -> Semester Hub Page) |
| Level | level_tag.name via user_link_level_tag.level_tag_id |
Yes | Badge |
| Status | user_link_level_tag.user_semester_status |
Yes | Badge: Fail (0=red), Enrolled (1=blue), Pass (2=green) |
| Elective | Elective name via user_link_level_tag.elective_id |
No | Text (Year 2 only, "--" for others) |
| TA | Derived from group_members for that enrollment's semester + level |
No | Text |
| Submissions | Count of student_submitted_assessment where user_link_level_tag_id matches |
Yes | Number |
| Average Grade | Computed average of reviewed submission grades for that user_link_level_tag_id |
Yes | "3.8" or "--" if no reviewed submissions |
Summary: "Enrolled in X semesters | Current level: [Level] | Student since: [earliest semester name]"
Actions
| Action | Trigger | Permission | Behavior |
|---|---|---|---|
| View Semester | Semester name click | All roles | Navigates to Semester Management > Semesters > [Semester Hub Page] for that semester |
| View Submissions | Submissions count click | All roles | Switches to Submissions tab with semester filter set to clicked semester |
| Filter by Status | Status dropdown (Pass / Fail / Enrolled / All) | All roles | Filters table |
States
- Multi-semester student: Full table with history, summary bar shows total count
- First-semester student: Single row. Message below table: "This is [first_name]'s first semester."
- No completed semesters: Shows current enrollment row only (status = Enrolled)
3.9.5 Tab: Admin Notes
Purpose
Admin-only private notes about a student. Used for tracking special circumstances, support ticket references, communication history outside the app, family context, academic accommodations, or any context that helps admins manage the student's account. Notes are never visible to the student and are never visible to TAs.
Layout
Vertical feed of note cards, newest first. Each card shows:
- Note body (rich text or plain text with preserved line breaks)
- Author name and avatar
- Timestamp (creation date + last edited date if edited)
- Action menu (Edit / Delete) visible only on notes authored by the current user
Above the feed: "Add Note" button (prominent, top-right) + search/filter controls.
Data Displayed
Feed of admin_notes records (NEW table) filtered to this user_id and not soft-deleted, sorted by created_at descending.
Per-note fields:
| Field | Source | Format |
|---|---|---|
| Body | admin_notes.body |
Multi-line text; plain-text rendering with preserved line breaks. Max 4000 characters. |
| Author | user_profile.first_name + last_name via admin_notes.created_by -> users |
Text + small avatar |
| Created | admin_notes.created_at |
"Mar 1, 2026 at 2:34 PM" |
| Last Edited | admin_notes.updated_at (shown only if ≠ created_at) |
"Edited Mar 2, 2026 at 9:10 AM" |
| Edit History | admin_note_revisions table (NEW): note_id, body, edited_by, edited_at |
"View history (N revisions)" expandable link, visible only when revisions exist |
Data model (NEW tables):
| Table | Columns |
|---|---|
admin_notes |
admin_note_id, user_id (student), body, created_by (admin user_id), created_at, updated_at, deleted_at (soft delete) |
admin_note_revisions |
revision_id, admin_note_id, body (pre-edit snapshot), edited_by, edited_at |
Search / Filter controls (above feed):
- Text search: free-text match against note body (case-insensitive)
- Author filter: dropdown of admins who have authored notes on this student; default "All authors"
- Date range filter: created-date range picker
Summary bar: "N notes | Last updated [date]" -- or "No notes" if feed is empty.
Actions
| Action | Trigger | Permission | Behavior | Confirmation |
|---|---|---|---|---|
| Add Note | "Add Note" button | Admin | Opens modal with multi-line text input (max 4000 chars) and "Save" button. On save, creates admin_notes record with created_by = current user, created_at = NOW(). Feed refreshes. |
No |
| Edit Own Note | "Edit" menu item on own note card | Admin (author only) | Opens modal pre-populated with note body. On save, snapshots the pre-edit body into admin_note_revisions, then updates admin_notes.body and admin_notes.updated_at. |
No |
| Delete Own Note | "Delete" menu item on own note card | Admin (author only) | Soft-deletes the note (sets admin_notes.deleted_at = NOW()). Removes from feed. |
Destructive: "Delete this note? It will be removed from the feed. This cannot be undone." |
| View Edit History | "View history" link on note card | Admin | Expands to show chronological list of prior revisions with edited_by and edited_at for each |
No |
| Search Notes | Search input | Admin, Support Staff | Filters feed to notes whose body matches the query | No |
| Filter by Author | Author dropdown | Admin, Support Staff | Filters feed to notes created by selected admin | No |
| Filter by Date | Date range picker | Admin, Support Staff | Filters feed to notes created within the selected range | No |
Edit/Delete constraints:
- An admin can edit or delete only notes they authored. A note's action menu is hidden entirely on notes authored by other admins.
- Deleted notes are soft-deleted — retained in
admin_noteswithdeleted_atset, but not displayed in the feed. No UI to restore a deleted note in v2.
States
| State | Description | Display |
|---|---|---|
| Has notes | Feed populated | Vertical feed of note cards, summary bar shows count |
| Empty | No notes for this student | "No admin notes for [first_name] yet." + "Add Note" button (Admin only) |
| Empty (filtered) | Filters return no results | "No notes match your filters." + "Clear filters" link |
| Loading | First tab activation | Skeleton cards (3 placeholders) |
| Error | API failure | "Unable to load notes. Please try again." + "Retry" button |
| Save error (add/edit) | Save API call fails | Inline error inside modal: "Unable to save note. Please try again." — modal stays open with body preserved |
Role Visibility
| Role | Tab visible? | Read notes? | Add note? | Edit own note? | Delete own note? |
|---|---|---|---|---|---|
| Admin | Yes | Yes | Yes | Yes | Yes |
| View-Only | Yes | Yes | No (button hidden) | No | No |
| Support Staff | Yes | Yes | No (button hidden) | No | No |
| TA | No -- tab is hidden from the tab bar entirely | -- | -- | -- | -- |
3.10 Tab: Actions
Contains lifecycle actions that affect the student's account or academic status. These are separated into their own tab because they are infrequent, consequential, and need clear visibility.
Layout
Actions displayed as a vertical list of action cards, each with:
- Action name (bold)
- Description (1-2 sentences explaining what the action does)
- Button (right-aligned)
- Availability indicator (enabled/disabled with reason)
Actions grouped into two sections: Academic Actions and Account Actions.
Academic Actions
| Action | Permission | Behavior | Confirmation | Availability |
|---|---|---|---|---|
| Promote to Next Level | Admin | Select target level from dropdown (auto-populated with next level in sequence: Level 0->1->2->3->4->Year 2). Creates new user_link_level_tag record with new level_tag_id, sets previous enrollment user_semester_status=2 (Pass). |
Standard: "Promote [first_name last_name] from [current level] to [target level]?" | Enabled when user_link_level_tag.user_semester_status = 2 (Pass). Disabled with message "Student must have Pass status to promote" otherwise. Shows warning if active Stripe subscription exists: "This student has an active subscription. Ensure the payment plan is updated after promotion." |
| Revert Promotion | Admin | Undoes last promotion: deletes newest user_link_level_tag record, reverts previous enrollment user_semester_status from 2 back to 1 (Enrolled). |
Standard: "Revert [first_name last_name] from [current level] back to [previous level]?" | Enabled only when a promotion has been executed (detected by multiple user_link_level_tag records in the same semester or a promotion audit log). Disabled with "No recent promotion to revert" otherwise. |
| Register for Year 2 | Admin | Creates new user_link_level_tag record with Year 2 level_tag_id, prompts for elective_id selection. |
Standard: "Register [first_name last_name] for Year 2? Select an elective:" + elective dropdown | Only visible when current level_tag is Level 4. Hidden for all other levels. |
| Enroll in Mastery Course | Admin | Opens mastery-course selector (dropdown populated from active mastery courses). Creates a mastery enrollment record linking user_id to the selected mastery course. Does NOT modify the student's standard user_link_level_tag — mastery enrollment runs in parallel to level enrollment. |
Standard: "Enroll [first_name last_name] in [mastery course name]? This will add them to the mastery course roster and enable one-to-one appointments." | Always enabled when student is Active. Hidden when student is Inactive. If the student is already enrolled in the selected mastery course, the dropdown omits it; if no mastery courses exist, button is disabled with message "No mastery courses available." |
| Reset Progress | Admin | Resets all lesson progress and deletes student_submitted_assessment records for current user_link_level_tag_id. |
Destructive with typed confirmation: "This resets all lesson progress and assessment history for [first_name last_name] in [current semester]. Type RESET to confirm." + text input that must match "RESET" | Always enabled. |
| Revert Progress Reset | Admin | Undoes last progress reset by restoring deleted records from audit log / soft-delete table. | Standard: "Restore [first_name last_name]'s progress data from before the reset on [reset date]?" | Enabled only after a Reset Progress action has been performed. Disabled with "No recent reset to revert" otherwise. |
| Reset to Level 1 | Admin | Sets student's enrollment to Level 1: creates new user_link_level_tag record with Level 1 level_tag_id for current semester. |
Destructive: "This will reset [first_name last_name] to Level 1. All current enrollment data will be affected. This action is typically used for students who need to restart the program." | Always enabled. |
| Mark as Repeat-Eligible | Admin | Adds the student to the repeat-eligible list for a selected upcoming semester (see 08-billing-payments.md §2.10). Once added, the student can complete the repeat-discount checkout if they click through their own opt-in link. Modal: semester dropdown (upcoming semesters only) + confirm. On save: writes a semester_repeat_eligibility record linking user_id to semester_id. Toast: "[first_name last_name] added to repeat-eligible list for [semester name]." |
Standard: "Mark [first_name last_name] as repeat-eligible for [semester name]?" | Enabled for Level 1–4 students (current or historical). Hidden for Level 0 and Year 2. Disabled with tooltip "Student is already on the repeat-eligible list for [semester name]" if already present. |
Account Actions
| Action | Permission | Behavior | Confirmation | Availability |
|---|---|---|---|---|
| Deactivate Account | Admin | Sets users.user_activation_status = 0. If active Stripe subscription exists, shows dual-option confirmation (see below). |
Destructive, context-dependent (see below) | Enabled when user_activation_status = 1 (Active). Hidden when already Inactive. |
| Reactivate Account | Admin | Sets users.user_activation_status = 1. |
Standard: "Reactivate [first_name last_name]'s account?" | Enabled when user_activation_status = 0 (Inactive). Hidden when already Active. |
| Set Temporary Password | Admin | Generates 4-character lowercase password, sets users.temp_pass. |
Shows generated password on success: "Temporary password set: [abcd]. Share this with the student securely." with copy button | Always enabled. |
| Revert Temporary Password | Admin | Clears users.temp_pass (sets to NULL). |
Standard: "Clear the temporary password for [first_name last_name]?" | Enabled when users.temp_pass is not NULL. Disabled with "No temporary password set" otherwise. |
| Resend Registration Email | Admin | Triggers registration email to users.email. |
Standard: "Resend registration email to [email]?" | Always enabled. |
Deactivate Account -- context-dependent confirmation:
- No active subscription: Simple destructive confirmation: "Deactivate [first_name last_name]'s account? They will lose access to all content."
- Active subscription exists: Dual-option confirmation dialog:
- Title: "This student has an active subscription"
- Body: "[first_name last_name] has an active [plan name] subscription (next billing: [date])."
- Option 1 button: "Deactivate Only" -- sets
user_activation_status = 0, leaves subscription active (student stops accessing content but billing continues until separate cancellation) - Option 2 button (recommended): "Cancel & Deactivate" -- cancels Stripe subscription (
cancel_at_period_end=true) + setsuser_activation_status = 0in single transaction (linked deactivation per business rule #11) - Cancel button: "Never mind"
States
- Active student: All applicable actions shown based on availability rules
- Inactive student: Only "Reactivate Account" and "Resend Registration Email" shown in Account Actions. Academic Actions section hidden (cannot modify academic status while inactive).
- View-Only / Support Staff: Tab hidden entirely (no actions available for these roles, so tab is not rendered in the tab bar)
3.11 Cross-Tab Behaviors
When an action on one tab changes data that is visible on other tabs or the entity header, the UI must update without requiring a full page reload.
| Action (on Tab) | Effect on Other Tabs / Header |
|---|---|
| Cancel Subscription (Payments) | Entity header subscription badge -> Cancelled. Actions tab: "Deactivate" no longer shows subscription warning in confirmation dialog. |
| Cancel + Deactivate (Payments) | Entity header status badge -> Inactive, subscription badge -> Cancelled. Payments tab shows cancelled state. Appointments tab hides "Schedule" button. Actions tab switches to inactive state (only Reactivate + Resend visible). |
| Deactivate + Cancel (Actions) | Entity header status badge -> Inactive, subscription badge -> Cancelled. Payments tab shows cancelled state. Appointments tab hides "Schedule" button. |
| Deactivate Only (Actions) | Entity header status badge -> Inactive. Subscription badge unchanged. Appointments tab hides "Schedule" button. |
| Promote (Actions) | Entity header level badge updates to new level. Semester History tab adds new enrollment row. Profile tab enrollment section shows new level/semester. Submissions tab resets summary bar (new enrollment has 0 submissions). |
| Reset Progress (Actions) | Submissions tab shows empty state for current semester (submissions deleted). Submissions badge count resets to 0. Semester History tab submissions count for current enrollment resets to 0. |
| Delete Submission (Submissions) | Submissions badge count decrements. Summary bar updates counts and average grade. Semester History tab submissions count for that enrollment decrements. |
| Pause Billing (Payments) | Entity header subscription badge -> Paused. |
| Resume Billing (Payments) | Entity header subscription badge -> Active. |
| Edit Profile (Profile) | Entity header name updates if name changed. |
| Edit Enrollment (Profile) | Entity header level and semester badges update. Semester History tab reflects changes. |
Implementation note: Cross-tab updates should use local state management (e.g., React context or Redux) to propagate changes without re-fetching all tab data. Stripe-dependent data (Payments tab) should re-fetch from Stripe API when the tab is re-activated after a billing action.
4. Screen: Submissions (Assessments)
4.1 Purpose
Cross-student view of all submissions (assessments). This is the assessment management screen -- creating/editing the recording prompts that students respond to, plus viewing all submitted recordings across all students.
Note: This differs from the Student Detail Page's Submissions tab, which shows one student's submissions. This screen shows ALL submissions across ALL students, and also manages the assessment definitions themselves.
4.2 Entry Points
- Sidebar: Student Management > Submissions
- Dashboard: via alert tile (e.g., "12 Submissions Under Review")
- Semester Hub > Setup Checklist: deep link to create assessments for new semester
- Semester Hub > End Checklist > Step 1 ("All Submissions Reviewed"): deep link to Submissions view filtered to this semester with
assessment_status = 0(pending); URL format?from=end-checklist&semester=[id]&status=pending. When?from=end-checklistis present, the screen shows a persistent "← Return to End Checklist for [Semester Name]" banner at top; clicking returns to the End Checklist tab with scroll position preserved.
4.3 Layout
Two views toggled via segmented control at top of page:
- Assessments view (default): List of assessment definitions (prompts)
- Submissions view: List of student-submitted recordings
Breadcrumb: Student Management > Submissions
4.4 Data Displayed -- Assessments View
| Column | Source | Sortable | Format |
|---|---|---|---|
| Title | assessment.title |
Yes | Text |
| Description | assessment.description |
No | Truncated text (expand on hover/click) |
| Level | level_tag.name via assessment's level association |
Yes | Badge |
| Semester | semester.name via assessment's semester association |
Yes | Text |
| Questions | Count of attached questions | Yes | Number |
| Audio Files | Count of attached audio files | Yes | Number |
| Submission Count | Count of student_submitted_assessment for this assessment_id |
Yes | Number |
| Under Review | Count of student_submitted_assessment where assessment_status = 0 for this assessment_id |
Yes | Number (highlighted if > 0) |
Filters: Semester (dropdown), Level (dropdown)
4.5 Data Displayed -- Submissions View
Submissions Close Date control (page-level bar, above the table, visible to Admin only):
| Field | Source | Format |
|---|---|---|
| Submissions Close Date | semester.submissions_close_date (NEW column) -- defaults to computed value from semester end date |
Date picker pre-populated with current value |
| Default Source | Indicator showing whether the current value is the automatic default (derived from semester end date) or a manual override | "Auto (from semester end)" badge or "Manual override" badge |
| Save Override | Button, visible when the admin changes the picker value | On click: saves new submissions_close_date for the currently-filtered semester, toast "Close date updated for [semester name]." |
| Revert to Auto | Button, visible when a manual override is active | On click: clears the manual value; close date reverts to computed default. Confirmation: "Revert to the automatic close date computed from semester end?" |
Caption text below: "Students cannot submit new recordings after this date. Default is computed from the semester end date but can be overridden."
Student Name search (filter bar): Free-text search input at top of filter bar. Matches against user_profile.first_name + last_name via student_submitted_assessment.owner_id. Case-insensitive, partial match. Placeholder: "Search by student name..."
| Column | Source | Sortable | Format |
|---|---|---|---|
| Select | Checkbox | -- | Per-row checkbox + header "select all" checkbox for bulk actions |
| Student | user_profile.first_name + last_name via student_submitted_assessment.owner_id |
Yes | Text (clickable -> Student Detail Page > Submissions tab) |
| Gender | user_profile.gender via student_submitted_assessment.owner_id -> users -> user_profile |
Yes | "Male" / "Female" |
| Assessment | assessment.title via student_submitted_assessment.assessment_id |
Yes | Text |
| Week | student_submitted_assessment.assessment_week |
Yes | "Week 3" |
| Level | level_tag.name via student_submitted_assessment.level_tag_id |
Yes | Badge |
| Status | student_submitted_assessment.assessment_status |
Yes | Badge: Under Review (0=yellow), Reviewed (1=green), Rejected (2=red). If under review AND ≥48 working hours have elapsed since submission date without feedback, an additional "Late Response" badge (red) is rendered next to the status badge. |
| Reviewed By | user_profile.first_name + last_name via student_submitted_assessment.reviewed_by |
Yes | Text or "--" |
| Date Submitted | student_submitted_assessment creation timestamp |
Yes | "Mar 1, 2026" |
| Feedback Received | student_submitted_assessment.reviewed_date |
Yes | "Mar 5, 2026" or "--" if not yet reviewed |
Filters: Semester (dropdown), Level (dropdown), Status (dropdown: Under Review / Reviewed / Rejected / Late Response / All), TA (dropdown: filters to submissions from students assigned to selected TA), Gender (dropdown: Male / Female / All)
4.6 Actions
Assessments view actions:
| Action | Trigger | Permission | Behavior | Confirmation |
|---|---|---|---|---|
| Create Assessment | "Create Assessment" button (top-right) | Admin | Opens create assessment form: title, description, level, semester, attach questions (text fields), attach audio files (file upload). Saves to assessment table. |
No |
| Edit Assessment | Edit icon on row | Admin | Opens edit form pre-populated with current data | No |
| Delete Assessment | Delete icon on row | Admin | Deletes assessment definition. If submissions exist, shows warning. | Destructive: "Delete this assessment? [N] student submissions reference this assessment." or "Delete this assessment?" if no submissions |
| View Details | Row click | All roles | Opens assessment detail view showing questions, audio files, and linked submissions count | No |
Submissions view actions:
| Action | Trigger | Permission | Behavior | Confirmation |
|---|---|---|---|---|
| View Student | Student name click | All roles | -> Student Detail Page > Submissions tab | No |
| Play Recording | Play icon on row | All roles | Opens audio player inline | No |
| Review Submission | "Review" button on row (Under Review only) | Admin, TA | Opens review modal: grade input, feedback text, approve/reject. Updates assessment_status, reviewed_by, reviewed_date. If rejected, sets rejection_reason. |
No |
| Reassign Submission | "Reassign" button on row (Under Review only) | Admin | Opens teacher picker (dropdown of active TAs). Updates student_submitted_assessment.assigned_reviewer_id (NEW column) to the selected TA. The assigned reviewer sees the submission in their review queue. Original assignment logic (derived from student's TA) is overridden by explicit assignment. |
Standard: "Reassign this submission from [current TA] to [new TA]?" |
| Bulk Delete | Checkbox selection + "Delete Selected" | Admin | Deletes selected student_submitted_assessment records |
Destructive: "Delete [N] selected submissions? This cannot be undone." |
| Set Submissions Close Date | Date picker + "Save Override" button (page-level bar) | Admin | Updates semester.submissions_close_date for the currently selected semester |
No (toast on save) |
| Revert Close Date to Auto | "Revert to Auto" button (page-level bar) | Admin | Clears manual override | Standard: "Revert to the automatic close date?" |
4.7 States
- Assessments view loaded: Assessment list with data
- Assessments view empty: "No assessments created for this semester." + "Create Assessment" button
- Submissions view loaded: Submissions list with data
- Submissions view empty: "No submissions yet for this semester."
- Submissions view empty (filtered by student name): "No submissions match "[query]"." + "Clear search" link
- Late Response filter active: Submissions view filtered to only late-response entries. Empty sub-state: "No late-response submissions. All under-review submissions are within the 48 working-hour window."
- Loading: Skeleton table (see 01-global-patterns.md section 4.9)
- Error: "Unable to load data. Please try again." + "Retry" button
4.8 Role Visibility
- Admin: Full CRUD on both views (create/edit/delete assessments, review/reassign/delete submissions), Submissions Close Date control visible
- TA: Submissions view only, filtered to their assigned students. Can review submissions (set grade, approve/reject). Cannot create/edit/delete assessments, cannot reassign. Cannot access Assessments view. Submissions Close Date control hidden.
- View-Only: Read-only on both views, no action buttons. Submissions Close Date control visible as read-only.
5. Screen: Promoted Students
5.1 Purpose
View students who have been promoted to the next level. Used during and after semester end (WF3) to track promotion outcomes and identify repeat signups.
5.2 Entry Points
- Sidebar: Student Management > Promoted Students
- Semester Hub > End Checklist > Step 2 (Pass/Fail): after passing students are marked, their promotions surface here. Admins can review the resulting promoted-student list filtered to the closing semester.
5.3 Layout
Filter bar + data table
Breadcrumb: Student Management > Promoted Students
5.4 Data Displayed
| Column | Source | Sortable | Format |
|---|---|---|---|
| Student Name | user_profile.first_name + last_name |
Yes | Text (clickable -> Student Detail Page) |
users.email |
Yes | Text | |
| Previous Level | Previous level_tag.name (from prior user_link_level_tag record) |
Yes | Badge |
| New Level | Current level_tag.name (from latest user_link_level_tag record) |
Yes | Badge |
| Semester | semester.name via promotion enrollment's semester_id |
Yes | Text |
| Promotion Date | Derived from user_link_level_tag creation timestamp for the new enrollment |
Yes | "Mar 1, 2026" |
| Is Repeat Signup | Whether student has a previous user_link_level_tag at the same level_tag_id (same level attempted before) |
Yes | Badge: Yes (yellow) / No (hidden) |
Filters: Semester (dropdown, default: current semester), Level From (dropdown), Level To (dropdown)
Summary bar: "X students promoted this semester | Y repeat signups"
5.5 Actions
| Action | Trigger | Permission | Behavior |
|---|---|---|---|
| View Student | Name click | All roles | -> Student Detail Page (Profile tab) |
| View Repeat Signups | "Repeat Signups Only" toggle/filter | All roles | Filters table to Is Repeat Signup = Yes |
| Export | "Export" button | Admin, View-Only | CSV download with all columns |
5.6 States
- Has promotions: Table populated, summary bar shows counts
- No promotions: "No students have been promoted for [semester name]." (expected state before End Checklist Step 2 (Pass/Fail) has been completed for this semester)
- Loading: Skeleton table
- Error: Standard error pattern
5.7 Role Visibility
- Admin: Full view + export
- View-Only: Full view + export
- TA / Support Staff: Hidden from sidebar (not in their navigation per role matrix)
6. Screen: Failed Sign Ups
6.1 Purpose
View incomplete student registrations (users.user_type = 4) for troubleshooting and follow-up. These are accounts where the registration process started but was not completed.
6.2 Entry Points
- Sidebar: Student Management > Failed Sign Ups
6.3 Layout
Filter bar + data table
Breadcrumb: Student Management > Failed Sign Ups
6.4 Data Displayed
| Column | Source | Sortable | Format |
|---|---|---|---|
| Name | user_profile.first_name + last_name (may be partial or empty) |
Yes | Text, or "No name provided" in gray italic if empty |
users.email |
Yes | Text | |
| Registration Date | users creation timestamp |
Yes | "Mar 1, 2026" |
| Status | users.user_type = 4 |
No | Badge: "Failed Signup" (gray) -- always the same |
| Device | users.device_type |
Yes | "iOS" / "Android" / "Web" / "--" |
| Phone | user_profile.contact_dialcode + user_profile.contact |
No | Phone number or "--" |
Filters: Date range (registration date), Device type (dropdown)
6.5 Actions
| Action | Trigger | Permission | Behavior | Confirmation |
|---|---|---|---|---|
| View Details | Row click | All roles | Opens detail modal showing all available data: name, email, device, phone, registration timestamp, users.username, any partially filled profile fields |
No |
| Activate Account | "Activate Account" button on row or in detail modal | Admin | Opens completion modal: pre-populated with any existing profile data; required fields for a standard enrollment (First Name, Last Name, Email, Gender, Level, Semester) -- same shape as the Add Student modal (§2.5). On save: sets users.user_type = 3 (Student), users.user_activation_status = 1 (Active), fills missing user_profile fields, creates user_link_level_tag enrollment record (user_semester_status = 1), generates temp_pass (4 chars, lowercase), sends registration email. Record is removed from Failed Sign-ups list (no longer matches user_type = 4). |
Standard: "Activate [email]'s account? This will complete their registration as an active student." |
| Resend Registration | "Resend" button on row or in detail modal | Admin | Resends registration email to users.email |
Standard: "Resend registration email to [email]?" |
| Delete Record | Delete icon on row | Admin | Deletes users and user_profile records |
Destructive: "Delete this failed sign up record for [email]? This cannot be undone." |
| Bulk Delete | Checkbox selection + "Delete Selected" | Admin | Deletes selected records | Destructive: "Delete [N] failed sign up records? This cannot be undone." |
| Export | "Export" button | Admin, View-Only | CSV download | No |
6.6 States
- Has records: Table populated
- Empty: "No failed sign ups recorded."
- Loading: Skeleton table
- Error: Standard error pattern
6.7 Role Visibility
- Admin: Full view + all actions (including Activate Account)
- View-Only: View only, no action buttons
- TA / Support Staff: Hidden from sidebar
7. Cross-Domain Navigation Summary
This section documents how Student Management screens link to and from other domains.
7.1 Outbound Links (from Student Management to other domains)
| Source | Target | Trigger |
|---|---|---|
| Student Detail > Entity Header > TA name | Teacher Management > TA Detail Page | Click TA name |
| Student Detail > Payments > "View in Stripe" | External: Stripe Dashboard | Click link (new tab) |
| Student Detail > Payments > Family Plan link | Billing & Payments > Family Plans | Click family group name |
| Student Detail > Appointments > "Calendar View" | Scheduling > Calendar View (filtered to student's TA) | Click link |
| Student Detail > Semester History > Semester name | Semester Management > Semester Hub Page | Click semester name |
| Students List > Dashboard alert tile origin | Dashboard | Browser back |
7.2 Inbound Links (from other domains to Student Management)
| Source | Target | Trigger |
|---|---|---|
| Dashboard > alert tile (student-related) | Students List (filtered) or Student Detail Page | Click tile |
| Semester Hub > Enrollment tab > student name | Student Detail Page | Click name |
| Semester Hub > End Checklist > Step 2 (Pass/Fail) deep link | Students List (filtered: current semester, Level 1–4, final_status IS NULL) |
Click "Open" on Step 2 |
| Semester Hub > End Checklist > Step 2 (Pass/Fail) — post-completion | Promoted Students (filtered to that semester) | After Step 2 completes, promoted students from the closing semester surface here. |
| Billing > Payment Overview > student in alert list | Student Detail Page > Payments tab | Click name |
| Billing > Family Plans > member name | Student Detail Page | Click name |
| Teacher Management > TA Detail > Students tab > student name | Student Detail Page | Click name |
| Scheduling > Calendar View > appointment > student name | Student Detail Page > Appointments tab | Click name |
| Reporting > Student Report > student name | Student Detail Page | Click name |
| Global Search > student result | Student Detail Page (Profile tab) | Click result |
8. Data Dependencies and API Calls
Summary of data sources required for each screen in this domain.
| Screen / Tab | Local DB Tables | Stripe API Calls | Notes |
|---|---|---|---|
| Students List | users, user_profile, user_link_level_tag, level_tag, semester, group_members, group_name, student_submitted_assessment, appointments |
None (subscription badge from cached data) | Subscription status cached via webhooks or periodic sync to avoid Stripe calls on list view. Appointment count derived from appointments filtered by current semester date range. |
| Student Detail > Profile | users, user_profile, user_link_level_tag, level_tag, semester, group_members, group_name |
None | |
| Student Detail > Submissions | student_submitted_assessment, assessment, users (for reviewed_by), user_link_level_tag, student_submission_exceptions (NEW) |
None | Weekly exception overrides read from student_submission_exceptions. |
| Student Detail > Payments | users (stripe_customer_id), subscriptions (fallback), student_scholarships, family_members, family_groups, student_deferments |
GET /v1/subscriptions, GET /v1/customers/{id}/payment_methods, GET /v1/invoices, GET /v1/invoices/upcoming |
Real-time Stripe calls on tab activation. Local tables used for special flags and as API fallback. |
| Student Detail > Appointments | appointments, users, user_profile, group_name, semester (for filter dates) |
None | Semester filter narrows results to the selected semester's date range. |
| Student Detail > Semester History | user_link_level_tag, level_tag, semester, group_members, group_name, student_submitted_assessment |
None | |
| Student Detail > Admin Notes | admin_notes (NEW), admin_note_revisions (NEW), users, user_profile (for author names) |
None | Tab hidden for TA role. |
| Student Detail > Actions | users, user_link_level_tag, level_tag, mastery_courses (NEW), mastery_enrollments (NEW) |
GET /v1/subscriptions (to check active subscription for deactivation warning) |
Stripe call only needed to determine if subscription warning should appear on Deactivate action. Mastery enrollment lookup for "Enroll in Mastery Course" action. |
| Submissions | assessment, student_submitted_assessment (+ late_response_flag computed column), users, user_profile, level_tag, semester (+ submissions_close_date column) |
None | 48-hour working-hour late-response flag computed by backend job; surfaced as boolean on each submission. |
| Promoted Students | users, user_profile, user_link_level_tag, level_tag, semester |
None | |
| Failed Sign Ups | users (where user_type = 4), user_profile |
None | Activate Account writes back to users.user_type, users.user_activation_status, creates user_link_level_tag record. |