# Maintenance Team Leader (Web) — Per-Screen Spec

> Acceptance detail per screen (Given/When/Then where useful). RBAC: the MTL has **own-Game-Zone scope** (`gameZoneScope = own`, `FOUNDATION_SPEC §5`) — every gate is **resolved at runtime from `RolePermission`**, never hardcoded. The MTL sits on the **maintenance branch** of the cascade (level 3, reporting to the SM alongside the operations TL): it receives routed A-item work-orders, assigns them, reviews results, closes internal fixes, and escalates **only** outsource cases to the SM.
> References: `FOUNDATION_SPEC.md` (§4 cascade + single-track hold, §4a Work-Order flow + endpoints, §5 MTL scoping, WorkOrder/WorkOrderEvent/WorkOrderPhoto models), `DESIGN_SYSTEM.md` (§2.4 `wo-*` state colors + `st-held`, §6.2 Sidebar, §6.3 Topbar, §6.6 DataTable, §6.7 Modal/Drawer, §6.11 StatusBadge, §6.12 PhotoUpload viewer, §6.14 ApprovalTimeline **workorder variant**, §6.22 WorkOrderCard/Detail, §6.24 HeldBanner, §7 MTL sidebar, §8.1 web shell), `CHANGE_LIST_v1.1.md` (CL-2), `SCREEN_LIST.md` (MTL block).

---

## Global rules (apply to every screen)

- **Chrome:** navy **240px sidebar** (wordmark lockup → OVERVIEW/OPERATIONS/PEOPLE groups; 5 items; active = 3px orange left bar + orange icon/label, `DESIGN_SYSTEM §6.2`) + **64px sticky topbar** (breadcrumb left · WO# search center · **non-interactive branch badge** `Game Zone · Ahmedabad-1` + bell + profile right). Content well max 1440, 40px top / 32px side padding (`DESIGN_SYSTEM §8.1`).
- **Branch badge (MTL = own scope, like SM/TL):** the topbar badge is a **read-only indicator** (no caret, no dropdown). The MTL never switches branches; the server forces `gameZoneId = me.gameZoneId` on every query (`FOUNDATION_SPEC §5`). Only the OH gets an interactive selector.
- **RBAC:** sidebar + permissions come from `GET /me` (`FOUNDATION_SPEC §2`). The app never hardcodes role logic. Assign/Reassign/Close/Outsource affordances render because the resolver grants those permissions at MTL `own` scope — not because the code checks `role === 'MTL'`.
- **Work-Order state machine (CL-2, `DESIGN_SYSTEM §2.4`):** `OPEN → ROUTED → ASSIGNED → IN_PROGRESS → (DONE | RETURNED)`; `RETURNED → (ASSIGNED | OUTSOURCED)`. Colors: open `#94A3B8` · routed `#7FB2E5` · assigned `#2E6DB4` · in-progress `#C2410C` · returned `#E0860B` · done `#1E9E5A` · outsourced `#16284A`; overdue solid `#ED1C24`. Held source checklist `#C2410C`.
- **Only OUTSOURCED reaches the SM (locked):** `DONE` closes silently at the maintenance level (no SM notification). `OUTSOURCED` is the single escalation channel to `SM-WO-OUTSOURCE`.
- **Single-track hold (CL-2):** the parent inspection checklist is **HELD** while any of its A-item WOs is open. Closing the **last** WO (`DONE`/`OUTSOURCED`) auto-releases it: **HELD → TL_APPROVED**, resuming the TL→SM→OH cascade.
- **Concurrency:** WO mutations carry `If-Match: <workOrder.version>` → **409** if the state moved (e.g. the tech returned it while the drawer was open). The UI refetches the timeline and re-renders.
- **Read-only A-item:** the MTL never edits the source A-item text, its `recordedAt`, or its `initials` — server-stamped at fill, tamper-proof.
- **States:** loading = skeleton tables/cards (never bare spinner); 401 → redirect to login; 403 → no-access page (out-of-GZ / role-denied); error → inline card + retry.

---

## `AUTH-LOGIN` — Login (`screens/01-login.html`)

- **Given** the app is opened logged-out, **When** Vikram enters email/phone + password and clicks Sign In, **Then** the app calls `POST /auth/login`, stores access+refresh tokens, calls `GET /me`, and routes to `MTL-DASH` (role resolved = MTL, scope = own).
- **Given** bad credentials → inline `danger` error; **Given** too many attempts → **locked** state (countdown). Primary = **navy-on-orange** "Sign In" (44px web). Maniax wordmark lockup beside the form.

## `MTL-DASH` — Maintenance Dashboard (`screens/02-dashboard.html`) — CRITICAL

- **Purpose:** work-order health at a glance for own Game Zone.
- **Given** the MTL Dashboard, **Then** show a row of `KpiCard`s keyed to the WO state machine: **New / Routed** (`wo-routed`), **Assigned** (`wo-assigned`), **In progress** (`wo-in-progress`), **Held (source)** (count of source checklists in HELD, `st-held`), **Returned** (`wo-returned`, needs your review), **Closed today** (`wo-done` + `wo-outsourced`), **Overdue WOs** (solid-red). Below: an attention strip (over-SLA / returned / held) + a **WO queue preview** (next-up rows → `MTL-WO-QUEUE`).
- **Given** Vikram clicks a KPI or a preview row, **Then** drill into `MTL-WO-QUEUE` (filtered) or `MTL-WO-DETAIL`.
- Loading = skeleton KPI cards + table; empty (no routed WOs) → all-clear EmptyState.

## `MTL-WO-QUEUE` — Work-Order Queue (`screens/03-wo-queue.html`) — CRITICAL

- **Purpose:** every routed A-item work-order for own GZ in one table; the entry to assign / review.
- **Given** the table (`DataTable`), **Then** columns: **WO#** · **Source checklist · Ride** · **A-item** · **State** (`StatusBadge` using the `wo-*` token set) · **Technician** · **Age / SLA** · actions. **Filter chips** by state (All / Routed / Assigned / In progress / Returned / Done / Outsourced / **Overdue**) + secondary filters (Ride / Priority / Technician).
- **Given** a row is `ROUTED`, **Then** the action is **Assign →** (`MTL-WO-ASSIGN`); a `RETURNED` row → **Review →** (`MTL-WO-REVIEW`); `IN_PROGRESS` / `ASSIGNED` rows are read-only ("tech working" / "not started"); `OUTSOURCED` rows show "with Store Mgr"; `DONE` rows are closed (audit only).
- One row = one WO = one A-item (`WorkOrder @@unique(responseId)`). Loading skeleton; no-results after filter; empty when nothing routed.

## `MTL-WO-DETAIL` — Work-Order Detail (`screens/04-wo-detail.html`) — CRITICAL

- **Purpose:** one WO end-to-end. Opens from `MTL-WO-QUEUE`.
- **Given** a WO, **Then** show: the **source A-item** (text + note + server time + initials, read-only) with its **issue photos** (click → photo-lightbox), the **HELD banner** ("source checklist held — N open A-items, can't reach Done until resolved", `DESIGN_SYSTEM §6.24`), the **reason / return history**, and the **WorkOrder-variant ApprovalTimeline** (`Routed (Ops TL) → Maintenance TL (you) → Technician → Closed/Outsourced`, `DESIGN_SYSTEM §6.14`).
- **Action bar (role-gated):** **Assign to Technician** (orange primary), **Reassign** (when applicable), **Escalate / Outsource to SM** (navy). The available actions follow the WO state.
- The MTL views photos but does **not** upload them (uploads are Technician/filler-only).

## `MTL-WO-ASSIGN` — Assign to Technician (`screens/05-wo-assign.html`)

- **Purpose:** assign / reassign a WO to a Technician on the maintenance team.
- **Given** the technician picker, **Then** list only **technicians on this MTL's team in this GZ** (`reportsToId = me`, `gameZoneId = me.gameZoneId`), each with a **load badge** (their current open WO count) so the MTL can balance load. Search by name.
- **Given** Vikram picks a tech + confirms (assign-technician modal), **Then** `PATCH /work-orders/:id/assign { technicianId }` → `ROUTED → ASSIGNED` (or `RETURNED → ASSIGNED` for a reassign); the tech is notified and the WO appears on their mobile **My Work-Orders**. `If-Match` concurrency.

## `MTL-WO-REVIEW` — Review Returned / Done WO (`screens/06-wo-review.html`) — CRITICAL

- **Purpose:** review a Technician's outcome and decide the next move. Opens from a `RETURNED` (or `DONE`-for-audit) WO.
- **Given** a `RETURNED` WO, **Then** show the **technician's return reason** + optional return photo, the **original A-item** context, and the WorkOrder-variant timeline with the **Returned** node + a "you decide" ring on the MTL node.
- **Decision bar (three options):**
  - **Close internally (resolved)** — **green** → `DONE` (closes at maintenance, SM **not** notified). Use when it's actually fixed / not needed.
  - **Reassign to a technician** — **orange** → reassign-confirm modal → `RETURNED → ASSIGNED` with a fresh tech.
  - **Escalate / Outsource to SM** — **navy** → `MTL-WO-ESCALATE`.
- **Given** another actor moved the WO (stale version), **Then** 409 → refetch + re-render.

## `MTL-WO-ESCALATE` — Escalate / Outsource Report (`screens/07-wo-escalate.html`) — CRITICAL

- **Purpose:** compose the outsource / action-needed report to the **Store Manager** — the **only** WO action that reaches the SM.
- **Given** the compose screen, **Then** it is pre-addressed to **this GZ's Store Manager**; a **required** reason/action-needed textarea; the WO's issue + return photos carried over automatically (+ add more). A banner reminds: "this is the only WO action that reaches the SM; internal fixes close silently."
- **Given** Vikram clicks Send → the **escalate-outsource-confirm modal** → confirm, **Then** `PATCH /work-orders/:id/outsource { note }` → `RETURNED → OUTSOURCED`; the WO surfaces on `SM-WO-OUTSOURCE`. **422** if the note is blank (client + server). If this is the last open WO on the source checklist, the checklist **un-HOLDs** (`HELD → TL_APPROVED`).

## `MTL-TEAM` — Maintenance Team (`screens/08-team.html`)

- **Purpose:** the Technicians reporting to this MTL + their open WO load (read-only).
- **Given** the team view, **Then** show per-tech load cards (Assigned / In-progress / Returned counts, over-SLA flag, available state) + a detailed load table (open WOs, state mix, oldest open, status). Search + filter.
- **No staffing actions here** — onboarding the maintenance team is SM/OH. This is a load-balancing reference; assign / reassign happens from a work-order.

## `SH-PROFILE` (utility) — Profile / Settings (`screens/09-profile.html`)

- **Purpose:** the utility-cluster landing — own profile, role ("Maintenance Team Leader"), reports-to (Store Manager), Game Zone (own), logout. Role/reports-to/GZ are server-resolved + read-only. Logout confirms → clears tokens → `AUTH-LOGIN`. Alerts reachable from here + the top-bar bell.

---

## Acceptance summary (the slot's must-pass behaviours)
- [ ] Login → `/me` role-resolve → MTL Dashboard; sidebar (5 items, 2 groups) + permissions server-resolved; **no hardcoded role logic**; branch badge is read-only (own scope).
- [ ] Dashboard KPIs map to the WO state machine (New/Routed, Assigned, In-progress, Held-source, Returned, Closed today, Overdue) using the `wo-*` colors; preview rows drill into the queue.
- [ ] `MTL-WO-QUEUE` shows every routed A-item WO for own GZ with `wo-*` `StatusBadge`; filter chips by state + Overdue; Routed rows → Assign, Returned rows → Review.
- [ ] `MTL-WO-DETAIL` shows the source A-item + issue photos + HELD banner + WorkOrder-variant ApprovalTimeline; action bar is state- and role-gated.
- [ ] `MTL-WO-ASSIGN` lists only own-team technicians in own GZ with load badges; assign → `ASSIGNED`; `If-Match` → 409 on stale.
- [ ] `MTL-WO-REVIEW`: **Close internally** → `DONE` (SM not notified); **Reassign** → `ASSIGNED`; **Outsource** → `MTL-WO-ESCALATE`.
- [ ] `MTL-WO-ESCALATE`: outsource note **required** (422 if blank) → `OUTSOURCED` → surfaces on `SM-WO-OUTSOURCE`; **only outsource reaches the SM**.
- [ ] Technician **Done is photo-gated** (≥1 `WorkOrderPhoto`) — enforced on the Technician side; the MTL views the fix photo, does not upload.
- [ ] **Single-track hold:** the source checklist stays HELD until every WO closes; closing the last WO un-HOLDs it (`HELD → TL_APPROVED`).
- [ ] Own-GZ scope: every WO read/write is server-forced to `me.gameZoneId`; out-of-GZ access → 403; states (loading/empty/no-results/error/401/403) render and are reachable.
