Admin Spec — Student Management
Student Management
Domain 2 of the Enhanced Candidate A navigation structure. Contains: Students list, Student Detail Page (6 tabs), Submissions, Promoted Students, Failed Sign Ups. Primary workflows served: WF2 (Student Support), WF3 contribution (Semester Close -- 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)
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) | ||
| 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 |
| 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 (NEW) | 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 |
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 |
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 - 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, 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 (NEW) | 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 | 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, 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 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 |
|---|---|---|---|
| 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) |
| 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 | 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 - Average grade = mean of reviewed submission grades
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 |
| Delete Submission (NEW) | 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." |
| Filter by Semester | Semester dropdown | All roles | Filters table to selected semester's user_link_level_tag_id |
No |
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."
3.7 Tab: Payments (NEW -- 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 -- NEW):
| Flag | Source | Display |
|---|---|---|
| Scholarship | student_scholarships table (NEW): user_id, scholarship_program_id, tier |
"50% Annual Scholarship" or "--" |
| Family Plan | family_members table (NEW): user_id, family_group_id -> family_groups.name |
"Ahmed Family (3 members)" with link to Billing > Family Plans screen, or "--" |
| Deferment | student_deferments table (NEW) 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 |
Filter: upcoming / past / all (default: upcoming)
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 |
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."
3.9 Tab: Semester History (NEW)
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.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. |
| 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. |
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
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
| Column | Source | Sortable | Format |
|---|---|---|---|
| Student | user_profile.first_name + last_name via student_submitted_assessment.owner_id |
Yes | Text (clickable -> Student Detail Page > Submissions tab) |
| 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) |
| Reviewed By | user_profile.first_name + last_name via student_submitted_assessment.reviewed_by |
Yes | Text or "--" |
| Date | student_submitted_assessment creation timestamp |
Yes | "Mar 1, 2026" |
Filters: Semester (dropdown), Level (dropdown), Status (dropdown: Under Review / Reviewed / All), TA (dropdown: filters to submissions from students assigned to selected TA)
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 |
| Bulk Delete | Checkbox selection + "Delete Selected" | Admin | Deletes selected student_submitted_assessment records |
Destructive: "Delete [N] selected submissions? This cannot be undone." |
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."
- 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/delete submissions)
- TA: Submissions view only, filtered to their assigned students. Can review submissions (set grade, approve/reject). Cannot create/edit/delete assessments. Cannot access Assessments view.
- View-Only: Read-only on both views, no action buttons
5. Screen: Promoted Students
5.1 Purpose
View students who have been promoted to the next level. Used during and after semester close (WF3) to track promotion outcomes and identify repeat signups.
5.2 Entry Points
- Sidebar: Student Management > Promoted Students
- Semester Hub > Close Workflow > Step 2 (Confirm Promotions): link to this screen filtered to that 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 semester close workflow runs)
- 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 |
| 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
- 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 |
| 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 |
None (subscription badge from cached data) | Subscription status cached via webhooks or periodic sync to avoid Stripe calls on list view |
| 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 |
None | |
| 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 |
None | |
| Student Detail > Semester History | user_link_level_tag, level_tag, semester, group_members, group_name, student_submitted_assessment |
None | |
| Student Detail > Actions | users, user_link_level_tag, level_tag |
GET /v1/subscriptions (to check active subscription for deactivation warning) |
Stripe call only needed to determine if subscription warning should appear on Deactivate action |
| Submissions | assessment, student_submitted_assessment, users, user_profile, level_tag, semester |
None | |
| Promoted Students | users, user_profile, user_link_level_tag, level_tag, semester |
None | |
| Failed Sign Ups | users (where user_type = 4), user_profile |
None |