Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

06 — Screen Specifications (Bulk Module)

Detailed specification of every bulk screen: layout, content, states, interactions, error handling, exact copy and backend citations. The biggest document in this package. Components in 07, forms/CSV contract in 08, motion in 00-shared/08, QA in 14. Forward-looking client.


Legend

  • SSn = screen id (see 05). Routes are (forward-looking).
  • States: idle / loading / success / empty / error / disabled / permission.
  • "Server 400/404" notes cite exact backend behaviour (bulk-import.service.ts, bulk.controller.ts).

SS1 — Bulk Home / Upload

1.1 Layout (desktop ≥ 1280 dp)

┌──────────────────────────────────────────────────────────────┐
│ Breadcrumb: Admin › Import & Export                     [Export →]│
├──────────────────────────────────────────────────────────────┤
│  Card: Import data (width ≤ 720, centered)                    │
│  ┌────────────────────────────────────────────────────────┐  │
│  │ Entity            [ Students ▾ ]   (only option; others │  │
│  │                                                         │  │
│  │ File               ╔══════════════════════════════════╗ │  │
│  │                     ║  Drop students.csv here or      ║ │  │
│  │                     ║  [ Browse files ]               ║ │  │
│  │                     ║  .csv only · ≤ 1000 rows · ≤2 MB║ │  │
│  │                     ╚══════════════════════════════════╝ │  │
│  │  [ Expected columns ] ▾  (08 §1 contract summary)         │  │
│  │                                                           │  │
│  │                                [ Back ]  [ Next → ]       │  │
│  └────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────┘

1.2 Behaviour

  1. Entity selector: hard-coded ['students'] — the only adapter is students (students-import.adapter.ts:15); others (planned) (IMPLEMENTATION_PLAN.md:172). Selector present anyway to future-proof; disabled-looking when single option.
  2. File pick: .csv extension filter; MIME not trusted (server reads bytes, bulk.controller.ts:47). On selection the client immediately parses the file in an isolate (15 §4) mirroring server options columns:true, skip_empty_lines:true, trim:true (bulk-import.service.ts:26-30).
  3. Parse outcomes:
    • Success → file card shows name, row count (data rows = lines − header), detected headers; Next enabled.
    • Unparsable (unclosed quote, binary, garbage) → inline error "This file could not be read as CSV." (client-side mirror of server 400 Malformed CSV: could not parse file.bulk-import.service.ts:31-33).
    • Zero data rows → "CSV must include a header row and data." (mirror of :34-35); Next stays disabled.
    • Encoding/BOM: strip UTF-8 BOM before parse (14 §2); non-UTF-8 (e.g. Windows-1252 accents) → warning card with "encode as UTF-8" guidance — the server decodes UTF-8 only (bulk.controller.ts:47).
  4. Next → SS2. Back disabled on SS1 (it is the flow root).

1.3 States

StateVisualNotes
idlecard, Next disabled
parsingfile card skeleton, spinnerisolate parse is ms-level for ≤ 1000 rows
errorAppAlert error under file zoneper 14 §2 encodings
permissionempty state "You don't have import permission"client gate only; server RBAC (planned) (permissions.constants.ts:10)
offlineAppOfflineBanner; pick still allowed, Next blockedno offline import (00-shared/06 §3.5)

1.4 Analytics (proposed)

bulk.upload.pick(entity, rows), bulk.upload.parse_fail(reason).


SS2 — CSV Preview

2.1 Layout

┌──────────────────────────────────────────────────────────────┐
│ Import › Preview                       file: students.csv    │
│ [ImportStepper: Upload ✓ · Preview ● · Confirm · Result]     │
├──────────────────────────────────────────────────────────────┤
│ Header map (chips per column)                                │
│  firstName [required] · lastName [required] · email [required]│
│  admissionNumber [required] · grade [required]               │
│  section [required] · academicYear [required]                │
│  rollNumber [optional] · phone [unknown!]                    │
│  ⚠ 1 unknown column ("phone") will be ignored                │
│  ⚠ blocking: missing required column "middleName"? no —      │
│    list ACTUAL missing: e.g. missing "grade"                 │
├──────────────────────────────────────────────────────────────┤
│  #  firstName  lastName  email            admissionNumber … │
│  2  Amara      Osei      amara.o@school.x   A-2026-001     … │
│  3  Bilal      Khan      bilal.k@school.x   A-2026-002     … │
│  … (first 10 rows; "showing 10 of 413")                      │
│                                       [Back]  [Import 413 →] │
└──────────────────────────────────────────────────────────────┘

2.2 Header contract logic (client-side, exact to server)

Compare file headers (as read, no case normalization — the server matches exact names: students-import.adapter.ts:17-26) against:

StateClassificationBlocking?
header ∈ required set[required] chip
header ∈ optional set (rollNumber)[optional] chip
header not in contract[unknown] chip + warning "will be ignored"no
required column absentAppAlert error listing column name(s)yes (Next disabled)

Trim nuance: the server trims cell values (trim:true, bulk-import.service.ts:29) and the adapter trims values again (students-import.adapter.ts:41-44) — but headers are not normalized. A header " firstName" will produce rows keyed " firstName" and every required check will fail with Missing required column "firstName". (:43). The client must surface whitespace in header chips ("leading space detected") as blocking — this is the #1 real-world import failure (14 §2).

2.3 Preview table

  • Shows first 10 data rows; row numbers shown are physical line numbers (header = 1) so they match server rowNumber in errors (bulk-import.service.ts:46).
  • Empty after parse (0 data rows) → blocking state with server-exact copy CSV must include a header row and data. (:34-35).
  • Duplicate emails/admissionNumbers within the file: client can warn ((forward-looking); server only checks against DB, students-import.adapter.ts:49-56) — flagged as enhancement, not required.

2.4 CTA

Import N rows → SS3. Disabled when any blocking warning exists.


SS3 — Confirm

3.1 Content (exact, honest copy)

Import 413 rows as students

• 413 rows will create 413 user accounts (firstName, lastName, email)
  and 413 student records with auto-enrollment into their grade/section
  (student.service.ts:71-78).
• Rows whose email or admissionNumber already exist will be skipped and
  listed as errors — they are not overwritten (students-import.adapter.ts:49-56).
• Grade/section/academicYear must already exist; unmatched values are
  skipped with "not found" errors (students-import.adapter.ts:113-125).
• Re-uploading a file that already succeeded is safe: completed rows fail
  as duplicates, new rows import.
• This import runs in one request — keep this tab open until the result
  appears (bulk-import.service.ts:22-65).   ← large-file caveat
                              [Back]        [Start import]

3.2 Rules

  • No new data entry on this screen; pure review.
  • Start import triggers the synchronous POST /bulk/import/students (bulk.controller.ts:35-48) and navigates to SS4 in-flight state immediately — the request must not be awaited before navigating (progress UI lives on SS4; 10 §6).
  • Double-tap protection: CTA disabled once fired (idempotent server, but double-fire wastes the 100/min api budget — 00-shared/07 §4).

SS4 — Result & Error Review

4.1 States

a) In-flight

┌────────────────────────────────────────────────┐
│ Importing 413 rows…                            │
│ [Indeterminate progress bar]                   │
│ • All rows are validated and created server-   │
│   side in this single request.                 │
│ • Do not close this tab; you will see the      │
│   report when it finishes.                     │
└────────────────────────────────────────────────┘
  • No progress events exist (synchronous service, bulk-import.service.ts:22-65); the UI is honest: indeterminate bar + explanation. When async workers land ((planned), IMPLEMENTATION_PLAN.md:172), this becomes a determinate progress + polling seam (13 §4).
  • Offline/timeout: on network failure the request may have partially completed server-side (rows already created). Copy: "The connection was lost. Check your students list, then re-upload — completed rows will be reported as duplicates, not recreated." (idempotency by design, students-import.adapter.ts:49-56).

b) Success (imported === totalRows)

┌────────────────────────────────────────────────┐
│ ✓ 413 of 413 rows imported                     │
│ • 413 users created (in-app notifications      │
│   sent via UserCreated → in-app queue          │
│   user-created-notification, event-queue-map.ts:10) │
│ • 413 students enrolled (StudentCreated →      │
│   in-app student-enrolled, event-queue-map.ts:28)   │
│        [View students]   [Download report]     │
└────────────────────────────────────────────────┘

c) Partial (failed > 0)

┌────────────────────────────────────────────────┐
│ ⚠ 398 of 413 rows imported · 15 failed         │
│ [Error groups] Duplicates 9 · References 5 ·   │
│                  Format 1                       │
│ ┌─ ErrorReviewTable ──────────────────────────┐ │
│ │ Row │ Fields                    │ Error     │ │
│ │ 17  │ email, admissionNumber    │ Email "…" │ │
│ │     │                           │ already   │ │
│ │     │                           │ registered│ │
│ │ 88  │ academicYear              │ Academic  │ │
│ │     │                           │ year "…"  │ │
│ │     │                           │ not found │ │
│ │ …                                  (paged 50)│ │
│ └──────────────────────────────────────────────┘ │
│ [Download error CSV]  [Fix errors →]  [View students] │
└────────────────────────────────────────────────┘
  • Error rows show: physical row number (matches file line — server rowNumber = index+2, bulk-import.service.ts:46), affected column(s), full error string(s) — exact server strings in 08 §2.
  • Error CSV is client-synthesized: original row + error column (no server endpoint exists).
  • "Fix errors" → SS1 with failed row numbers pre-queued so the preview can highlight them after the corrected file is picked (03 J3).
  • Duplicate rows are labelled with a "likely already imported" hint on re-upload journeys (03 J3), normalizing the duplicate-noise problem.

d) Failed before any row (server rejects the file)

Server triggerCode/messageSS4 rendering
no file field400 CSV file is required (multipart field "file"). (bulk.controller.ts:43-46)client prevents this (always sends file); show generic + retry
unknown entity404 RESOURCE_NOT_FOUND No import adapter for entity "X". (bulk-import.service.ts:17-20)"Entity not available" + back to SS1
malformed CSV400 VALIDATION_ERROR Malformed CSV: could not parse file. (:31-33)"File could not be parsed" + back to SS1 (preview normally catches this)
header-only CSV400 VALIDATION_ERROR CSV must include a header row and data. (:34-35)same pattern

e) Rate-limited / permission

  • 429 (RATE_LIMITED, api tier 100/min — 00-shared/07 §4): countdown copy Retry in Ns, no auto-retry.
  • 401: session refresh flow (00-shared/06 §3.6).
  • 403: only when RBAC guard lands (planned); render permission empty state.

4.2 Invariants the UI can assert

imported + failed === totalRows (04 §5). If the client detects a mismatch it renders a data-error banner with the raw report for support.

4.3 Realtime

No WS topic exists for import results; notifications to imported users are server-side only. (forward-looking): a bulk.import.completed WS topic once async workers exist.


SS5 — Export

5.1 Layout

Import & Export › Export
Entity  [ Students ▾ ]              (hard-coded list; students only)
Format  CSV (text/csv)              (fixed)
Note: roster snapshot — columns admissionNumber, rollNumber, status,
      admissionDate; sorted by admissionNumber (students-import.adapter.ts:87-98).
      Not an import template (04 §6).
                          [Download CSV]

5.2 Behaviour

  • GET /api/v1/bulk/export/students (bulk.controller.ts:50-60); response text/csv + Content-Disposition: attachment; filename="students.csv" (:52,55-58).
  • Empty roster → header-only CSV (stringify([], {header:true}), bulk-import.service.ts:67-71); UI shows "No rows exported" toast, still downloads the header file.
  • Unknown entity → 404 (:17-20); client prevents via hard-coded list.
  • The file downloads through the browser; on phone a share sheet (forward-looking).

5.3 Analytics (proposed)

bulk.export.download(entity).


Cross-screen requirements

ConcernRule
StepperSS1→SS2→SS3→SS4; SS5 outside wizard (ImportStepper, 07)
Backpreserves wizard state (file + preview) so users can fix without re-picking when possible
Unsaved stateno forms with server writes until SS3; leaving SS3 before firing has no effect
Large fileSS1 cap + warning; SS3 caveat; SS4 in-flight honesty (10 §6)
a11ylive-region on parse errors and result summary; tables with real semantics (00-shared/09)
Motionm-fast micro-feedback, m-base screen transitions (00-shared/08)