Admin Spec — Global Patterns
Global Patterns
Cross-cutting patterns and conventions used throughout the admin backend redesign. Domain-specific spec documents reference these patterns by name rather than re-specifying them.
1. Navigation Shell
1.1 Sidebar Structure
Layout: Fixed left sidebar, always visible. Collapsible to icon-only mode.
Sections (top to bottom):
- Logo/brand area (top)
- Global search bar
- Dashboard (standalone item, not grouped)
- 8 domain sections, each with an icon, label, and expandable sub-items:
- Student Management (icon: users)
- Semester Management (icon: calendar-check)
- Content (icon: play-circle)
- Scheduling (icon: clock)
- Teacher Management (icon: user-check)
- Billing & Payments (icon: credit-card)
- Reporting (icon: bar-chart)
- Admin & System (icon: settings)
Expand/collapse behavior:
- Clicking a section header expands/collapses its sub-items
- Only one section expanded at a time (accordion pattern)
- Active section auto-expands on page load
- Collapsed sidebar shows only icons; hovering expands temporarily
Active state: Current section header highlighted, current sub-item highlighted with left border accent
1.2 Breadcrumb Pattern
Format: Domain > Screen > [Entity Name] Examples:
- Student Management > Students > Sarah Ahmed
- Semester Management > Semesters > Spring 2026 > Setup Checklist
- Billing & Payments > Family Plans
Behavior: Each segment is clickable and navigates to that level. Entity names link to the entity page's default tab (Profile).
1.3 Page Header Pattern
Every page has:
- Title: Screen name (e.g., "Students", "Payment Overview")
- Subtitle (optional): Brief description or context
- Primary action button (if applicable): Top-right, e.g., "Add Student", "Create Semester"
- Breadcrumb: Below title
2. Global Search
From Candidate C enhancement.
2.1 Placement and Access
- Search bar at top of sidebar, always visible
- Keyboard shortcut:
Cmd+K(Mac) /Ctrl+K(Windows) - Focus: opens expanded search overlay with recent searches
2.2 Searchable Entities
| Entity | Searchable Fields | Result Format |
|---|---|---|
| Student | Name, email | Name · Level · Semester · Status badge |
| TA | Name, email | Name · "Teaching Assistant" · Student count |
| Semester | Name | Name · Date range · Status badge |
2.3 Result Behavior
- Results appear as you type (debounced, 300ms)
- Maximum 5 results per entity type, grouped by type
- Clicking a result navigates to the entity's detail page (Student Detail, Semester Hub, or TA Detail), default tab
- No-result state: "No results found for '[query]'" with suggestion: "Try searching by name or email"
2.4 Recent Searches
- Last 5 searches persisted per session
- Shown when search bar is focused with no input
3. Role-Based Access
3.1 Role Definitions
| Role | Code | Sidebar View | Action Permissions |
|---|---|---|---|
| Admin | user_type=1 | Full 8-domain sidebar | Full CRUD + bulk operations |
| Teaching Assistant (TA) | user_type=2 | 4 items: My Students, My Schedule, My Groups, Recordings | View assigned students, review submissions, manage own schedule |
| Admin (View Only) | user_type=5, role_flag=view_only | Full 8-domain sidebar | View all, no create/edit/delete/bulk actions |
| Support Staff | user_type=5, role_flag=support | Full 8-domain sidebar | View all including billing data, no billing actions |
Implementation note: View-Only and Support Staff share user_type=5. A new role_flag column (or equivalent permission attribute) on the users table is required to differentiate them. The only behavioral difference is that Support Staff sees the Student Detail > Payments tab data (read-only) and billing domain screens, while View-Only also sees these but without the billing-specific context that Support Staff needs for student inquiries. In practice, both roles have identical view-only access; the distinction exists primarily for audit/logging purposes and potential future permission divergence.
3.2 Master Access Matrix
| Domain | Admin | TA | View-Only | Support Staff |
|---|---|---|---|---|
| Dashboard | Full | Reduced (own alerts) | View only | View only |
| Student Management | Full | Own students only | View only | View only |
| Semester Management | Full | Hidden | View only | View only |
| Content | Full | Recordings only | View only | View only |
| Scheduling | Full | Own schedule only | View only | View only |
| Teacher Management | Full | Hidden | View only | View only |
| Billing & Payments | Full | Hidden | View only | View only (no actions) |
| Reporting | Full | Hidden | View only | View only |
| Admin & System | Full | Hidden | Hidden | Hidden |
3.3 TA Sidebar
TAs see a simplified sidebar:
├── My Students (→ Students list filtered to assigned students)
├── My Schedule (→ TA Schedules filtered to own schedule)
├── My Groups (→ Student Groups filtered to own groups)
└── Recordings (→ Content > Recordings)
3.4 Action Visibility Rules
- View-only users: See all data, see all filter/sort controls, but action buttons (Create, Edit, Delete, Promote, Deactivate, etc.) are hidden — not grayed out, hidden entirely
- Support staff on billing: See subscription status, payment history, special flags on Student Detail Payments tab. NO action buttons (Cancel, Pause, Apply Discount, etc.)
- Per-action overrides: Some actions are admin-only even within accessible domains. These are noted in domain specs with
[Admin only]
4. Component Patterns
4.1 Data Table
Standard layout:
- Filter bar above table
- Column headers with sort indicators (▲/▼)
- Row hover highlight
- Row click navigates to detail (for entity tables) or opens modal (for action tables)
- Bulk selection: checkbox column (leftmost), "Select All" in header, bulk action bar appears above table
- Pagination: bottom-right, showing "1-25 of 342" with page navigation
Column types:
- Text: left-aligned
- Number: right-aligned
- Status: badge (see 4.3)
- Date: formatted as "Mar 10, 2026"
- Actions: icon buttons or "..." menu (rightmost column)
4.2 Filter Bar
Layout: Horizontal bar above data table with:
- Search input (leftmost): filters by name/email
- Dropdown filters: semester, level, status, TA, etc. (screen-specific)
- Active filter chips: appear below filter bar when filters are applied, each with "×" to remove
- "Clear all" link: appears when any filter is active
Behavior:
- Filters apply immediately (no "Apply" button)
- URL updates with filter state (shareable filtered views)
- Filter state persists within session
4.3 Status Badges
Color scheme by domain:
| Status | Color | Used For |
|---|---|---|
| Active | Green | Student status, subscription status, semester status |
| Inactive / Blocked | Gray | Deactivated student |
| Enrolled | Blue | Student semester status |
| Pass | Green | Student promotion status |
| Fail | Red | Student promotion status |
| Under Review | Yellow/Amber | Submission status |
| Reviewed | Green | Submission status |
| Past Due | Orange | Subscription payment status |
| Cancelled | Gray | Subscription status |
| Paused / Deferred | Blue | Subscription pause or deferment status |
| Incomplete | Yellow/Amber | Stripe subscription incomplete (initial payment failed) |
| Unpaid | Red | Stripe subscription unpaid (renewal payment failed, exhausted retries) |
| None | Gray | No Stripe subscription found for student |
| Pending | Yellow/Amber | Awaiting action (close workflow steps) |
| Complete | Green | Workflow step completed |
| Draft | Blue | Semester not yet active (distinguishes from Inactive gray) |
Invoice-level statuses (used in Student Detail > Payments tab payment history, sourced from Stripe invoice.status):
| Status | Color | Context |
|---|---|---|
| Paid | Green | Invoice fully paid |
| Open | Yellow/Amber | Invoice issued, awaiting payment |
| Void | Gray | Invoice voided (no longer collectible) |
| Uncollectible | Red | Invoice marked uncollectible (all retry attempts exhausted) |
Payment transaction statuses (used in Billing & Payments > Payments screen, sourced from Stripe charge/payment intent status):
| Status | Color | Context |
|---|---|---|
| Succeeded | Green | Payment charge succeeded |
| Failed | Red | Payment charge failed |
| Refunded | Gray | Payment refunded |
| Pending | Yellow/Amber | Payment processing |
Note: Invoice statuses and payment transaction statuses are related but distinct. An invoice can be "Open" while its latest payment attempt "Failed." The Student Detail Payments tab shows invoice-level status (Stripe's canonical invoice lifecycle). The Billing > Payments screen shows transaction-level status (individual charge outcomes). Both are Stripe-sourced.
Badge format: Rounded pill with background color, white/dark text. Consistent across all screens.
4.4 Alert Tiles (Dashboard)
Layout: Card with:
- Icon (left)
- Count (large number, center)
- Label (below count)
- Trend indicator (optional: ↑/↓ vs. last week)
- Click: navigates to resolution context (specific screen with pre-applied filters)
Conditional display: Tiles only appear when count > 0 (except semester phase prompt, which is always visible)
Color coding:
- Red border: requires immediate action (failed payments, past due)
- Yellow border: awareness needed (students behind, TA response time)
- Blue border: informational (semester phase, appointment utilization)
4.5 Metric Cards
Layout: Small card with:
- Label (top, small text)
- Value (large number or formatted amount)
- Optional trend (vs. previous period)
Used on: Payment Overview metrics bar, Dashboard quick stats
4.6 Confirmation Dialogs
Standard (non-destructive):
- Title: action description
- Body: what will happen
- Buttons: Cancel (left, secondary) | Confirm (right, primary)
Destructive:
- Title: "Are you sure?" or action-specific
- Body: warning text explaining consequences, in red
- Buttons: Cancel (left, secondary) | [Action Name] (right, red/destructive)
- Extra: typed confirmation for high-risk actions (e.g., "Type DELETE to confirm")
Used for: Delete, Deactivate, Reset Progress, Cancel Subscription, Linked Deactivation
4.7 Toast Notifications
Types:
- Success (green): "Student deactivated successfully"
- Error (red): "Failed to cancel subscription. Please try again."
- Info (blue): "Semester cloned. Review Setup Checklist."
Behavior: Auto-dismiss after 5 seconds. Appears top-right. Stacks if multiple.
4.8 Empty States
Layout:
- Icon or illustration (centered)
- Message: what the empty state means (e.g., "No students match your filters")
- Suggested action: button or link (e.g., "Clear filters" or "Add your first student")
Every screen must define its empty state message and suggested action in its spec.
4.9 Loading States
- Initial page load: Skeleton screen (gray blocks matching layout)
- Data refresh: Inline spinner next to the data section being refreshed
- Action processing: Button shows loading spinner, disabled until complete
- Stripe data: Shows "Loading billing data..." with spinner; if slow (>3s), shows "Fetching from Stripe..."
4.10 Modals
Standard layout:
- Overlay with backdrop
- Header: title + close (×) button
- Body: form fields or content
- Footer: action buttons (Cancel | Primary Action)
- Max width: 600px for forms, 800px for previews
- Close on: × button, Cancel button, backdrop click, Escape key
Used for: Create forms, Edit forms, Preview content, Confirmation dialogs
4.11 Tabs (Entity Pages)
Layout:
- Horizontal tab bar below entity header
- Active tab: underline accent + bold text
- Tab labels may include badge count (e.g., "Submissions (12)")
- Default tab: Profile for Students/TAs, Overview for Semesters
Behavior:
- Tabs lazy-load content when first activated
- Tab state preserved when switching between tabs (scroll position, filters)
- URL updates with tab name (shareable deep links, e.g.,
/students/123#payments) - Tab bar is sticky when scrolling within a tab
4.12 Section Headers
Within a tab or screen:
- Section title (bold, larger text)
- Optional action link (right-aligned, e.g., "View all" or "Edit")
- Optional count (e.g., "Payment History (12)")
- Optional collapse/expand toggle
5. Data Patterns
5.1 Stripe Data Fetching
Two strategies depending on context:
| Context | Strategy | Latency | Staleness |
|---|---|---|---|
| Student Detail > Payments tab | Real-time Stripe API call | 1-3 seconds | Fresh |
| Dashboard alert tiles | Webhook-cached in local billing_alerts table |
<100ms | Up to event delay |
| Payment Overview metrics | Webhook-cached + periodic sync | <100ms | Up to 15 min |
5.2 Customer ID Resolution
When looking up a student's Stripe data, follow this chain:
- Check
users.stripe_customer_id(populated for admin-created subscriptions) - If empty, look up
subscription_signups.pp_customer_idin FE database byuser_id - If neither exists, search Stripe by email via
Customer::search() - If found, backfill
users.stripe_customer_idfor future lookups
5.3 Data Staleness Indicators
When displaying Stripe data fetched in real-time:
- Show "Updated just now" on successful fetch
- Show "Last updated: [time ago]" for cached data
- Show warning icon + "Could not reach Stripe — showing cached data" on API failure with fallback
5.4 Error Handling for External APIs
- Stripe timeout: Show cached data (if available) with warning banner. If no cached data, show error state: "Unable to load billing data. Stripe may be temporarily unavailable." with "Retry" button.
- Partial data: If subscription loads but invoices fail, show subscription data with "Payment history unavailable" message in that section.
- Permission denied: Show "You don't have permission to view this data" (should not occur based on role matrix, but handle defensively).
6. Inline Documentation Pattern
Addresses interview finding: "Navigation is unintuitive and undocumented" and admin's request for "clear inline documentation explaining what each feature does."
6.1 Tooltips
- Trigger: hover over info icon (ⓘ) next to field labels
- Content: 1-2 sentence explanation of what the field does and what it affects
- Example: Registration Status ⓘ → "When enabled, students can register for this semester. Does not make the semester active — use 'Active Semester' toggle for that."
6.2 Field-Level Help Text
- Small gray text below input fields in forms
- Explains format, constraints, or downstream effects
- Example: below "Content Availability Date" input → "Students will see this semester's content starting on this date, even if the semester start date is later."
6.3 Section Documentation Blocks
- Collapsible "How does this work?" block at top of complex screens
- Contains 2-3 sentences explaining the screen's purpose and typical workflow
- Collapsed by default after first visit (user preference stored)
- Example on Close Workflow tab: "This workflow guides you through end-of-semester tasks in order. Each step must be completed before the next becomes available. You can override gates if needed."
6.4 Transition Hints (First 30 Days)
- For the first 30 days after launch, relocated screens show a subtle hint: "Formerly under [Previous Section]"
- Example: On Tags page → "Formerly under Settings"
- Dismissable per user; auto-expires after 30 days
7. Key Business Rules (Reference)
Preserved from the current system. Domain specs must enforce these:
- Semester protection: Cannot delete a semester if students are enrolled
- Level progression: Level 0 → 1 → 2 → 3 → 4 → Year 2 (no skipping)
- Year 2 enrollment: Level 4 students must explicitly sign up for Year 2
- Passing threshold: 3.5 marks minimum to pass
- Submission flow: Under Review (0) → Reviewed (1) by TA
- Content scoping: All content is scoped to a semester
- Registration vs Active: Registration can be open before semester becomes active
- Active semester:
is_current = 1determines which semester's content is served - TA assignment: Can be automated based on Teacher Assignment Criteria rules
- Password generation: Auto-generated, 4 characters, lowercase only
- Linked deactivation (NEW): Deactivating a student with an active subscription must cancel the Stripe subscription in the same operation