# Story 2.3: Worker Scoped Dashboard Status: review ## Story As a firm worker, I want to see only my assigned declarations and their statuses when I open the app, So that I can quickly identify what I need to work on today without information overload. ## Acceptance Criteria 1. **Given** a Worker is logged in, **When** they navigate to the Dashboard, **Then** the page displays the same KPI card layout as the Owner dashboard but counts reflect only declarations assigned to the Worker 2. **Given** a Worker is logged in, **When** the summary table renders, **Then** it shows only the Worker's assigned declarations sorted by deadline ascending 3. **Given** a Worker is logged in, **When** the priority alerts panel renders, **Then** it shows only alerts for the Worker's assigned declarations 4. **Given** a Worker views the dashboard, **Then** StatCards are clickable and navigate to `/declarations?assignee={user.id}&status={status}` (using the Worker's actual user ID to pre-filter) 5. **Given** a Worker views the dashboard, **Then** the page title or subtitle indicates the scoped view (e.g., "Mes déclarations" or the Worker's name) 6. **Given** a Worker has no assigned declarations, **When** they view the Dashboard, **Then** an EmptyState is displayed: "Aucune déclaration assignée — contactez votre responsable" 7. **Given** the `DashboardController` is invoked for a Worker, **Then** it uses the same code path as Owner/Manager but applies `forUser()` scope, resulting in scoped data for Workers 8. **Given** the Worker dashboard is loaded, **Then** Workers do NOT see team workload distribution or activity from other team members 9. **Given** the Worker dashboard is loaded, **Then** the page renders within 3 seconds (NFR5) ## Tasks / Subtasks - [x] Task 1: Verify and enhance Worker scoping in `DashboardController` (AC: #1, #2, #3, #7) - [x] 1.1 Read the current `DashboardController` and confirm `forUser()` scope is already applied to all queries (KPI counts, urgent declarations, alerts). **It already is** — the `baseQuery` closure applies `forUser($user, $workspaceUser)`. Verify this works correctly for Worker role by running existing tests. - [x] 1.2 Add `assignee={user.id}` param to StatCard `href` URLs when the current user is a Worker, so clicking a KPI card pre-filters the declarations list to the Worker's assignments - [x] 1.3 Add `isWorker` boolean flag to Inertia props to enable frontend conditional rendering - [x] Task 2: Update `Dashboard.vue` for Worker-specific UI (AC: #4, #5, #6, #8) - [x] 2.1 Add a scoped view subtitle/header that displays when `isWorker` is true: "Mes déclarations" or "Tableau de bord — {userName}" - [x] 2.2 Add an EmptyState when `isWorker` is true AND `declarations.length === 0` AND all stat card counts are 0: display "Aucune déclaration assignée — contactez votre responsable" with an appropriate icon (e.g., `FolderOpen` or `ClipboardList`) - [x] 2.3 Hide the "Assigné à" column in the urgent declarations table when `isWorker` is true (Worker always sees their own — showing their own name is redundant) - [x] 2.4 Ensure "Relancer" (Nudge) and "Réassigner" dropdown actions remain disabled/hidden for Workers (they already are disabled — verify they stay that way) - [x] Task 3: Extend StatCard `href` for Worker context (AC: #4) - [x] 3.1 In `DashboardController`, when building `statCards` array, append `'assignee' => 'me'` (or `$user->id`) to the route params when the user is a Worker. This ensures clicking a KPI card navigates to a filtered declarations list scoped to the Worker. - [x] 3.2 Verify the `declarations.index` route reads the `assignee` param and applies filtering (prep for Epic 4 FilterBar — for now, the param exists in the URL for future compatibility) - [x] Task 4: Write Pest feature tests for Worker dashboard (AC: #1, #2, #3, #6, #7, #8) - [x] 4.1 Test Worker sees ONLY their assigned declarations in KPI counts (overdue, dueThisWeek, enAttenteClient, enCours) - [x] 4.2 Test Worker does NOT see declarations assigned to other team members in KPI counts - [x] 4.3 Test Worker sees only their assigned declarations in the urgent declarations table - [x] 4.4 Test Worker sees only their assigned declarations in the priority alerts - [x] 4.5 Test Worker dashboard returns `isWorker: true` in Inertia props - [x] 4.6 Test Worker with no assigned declarations gets zero counts and empty declarations/alerts arrays - [x] 4.7 Test Worker StatCard hrefs include assignee scoping param - [x] 4.8 Test Owner/Manager dashboard returns `isWorker: false` - [x] 4.9 Test cached data is scoped per user (Worker cache key includes user ID — already does: `dashboard:{workspace_id}:{user_id}`) ## Dev Notes ### Architecture Patterns & Constraints - **Workspace resolution:** Always from session `current_workspace_id`, NEVER from URL params - **Authorization:** Use `abort(404)` not `abort(403)` for workspace boundary violations - **Role-scoped queries:** Use existing `Declaration::forUser($user, $workspaceUser)` scope — Workers see only assigned, Owners/Managers see all - **Data shaping:** Manually build arrays in controller, NO API Resources - **URLs as props:** ALL frontend URLs must come from controller via `route()` helper — never hardcode routes in Vue - **Wayfinder routes:** All URLs in Vue MUST use Wayfinder type-safe routes. Check existing Dashboard.vue imports for pattern. - **Cache pattern:** Same `Cache::remember()` with key `dashboard:{workspace_id}:{user_id}` — already user-specific, so Worker data is automatically scoped and cached separately from Owner/Manager data ### CRITICAL: The `forUser()` Scope Already Works The current `DashboardController` already applies `forUser($user, $workspaceUser)` to ALL queries — both the cached KPI counts and the urgent declarations table. The `Declaration::scopeForUser()` method (in `app/Models/Declaration.php:152`) checks if the role is `Worker` and adds `where('assigned_to', $user->id)`. This means: - **KPI counts are already Worker-scoped** — no backend counting changes needed - **Urgent declarations table is already Worker-scoped** — no backend query changes needed - **Priority alerts are already Worker-scoped** — the `buildAlerts()` method uses the same `baseQuery` which includes `forUser()` **What IS needed:** 1. **StatCard URLs** need `assignee` param appended for Workers so clicking navigates to a pre-filtered list 2. **Frontend UI** needs Worker-specific elements: subtitle, empty state, column hiding 3. **`isWorker` prop** added to Inertia response for frontend conditional rendering 4. **Tests** to explicitly verify Worker scoping works correctly end-to-end ### CRITICAL: DB Column Name The architecture docs reference `deadline` but the actual database column is **`due_date`**. Use `due_date` in ALL queries. This was caught in Story 2.1 and confirmed in Story 2.2. ### CRITICAL: Excluded Statuses The dashboard excludes declarations with statuses: `termine`, `mise_en_demeure`, `ferme`. The `DeclarationStatus` enum has 6 values: `created`, `en_cours`, `en_attente_client`, `termine`, `mise_en_demeure`, `ferme`. Only `created`, `en_cours`, and `en_attente_client` should appear in dashboard data. ### Existing Code to Extend (NOT Create Fresh) **`app/Http/Controllers/DashboardController.php`** — Current controller (Story 2.1 + 2.2): - Already has `Cache::remember()` with key `dashboard:{workspace_id}:{user_id}` and 5-min TTL - Already queries with `forUser($user, $workspaceUser)` scope on all queries - Already returns `stats`, `statCards`, `declarations`, `alerts`, `workspaceName`, `roleLabel`, `declarationsUrl`, `clientsUrl`, `viewAllAlertsUrl` - **ADD:** `isWorker` boolean prop to Inertia response - **MODIFY:** StatCard `href` values — append `assignee` param when user is Worker - The `roleLabel` for Worker already returns `'Collaborateur'` — can use this on the frontend **`resources/js/pages/Dashboard.vue`** — Current page (Story 2.1 + 2.2): - Currently has: KPI card grid → PriorityAlertsPanel → urgent declarations table - **ADD:** Worker subtitle/header conditional on `isWorker` prop - **ADD:** EmptyState when Worker has no data - **ADD:** Hide "Assigné à" column when `isWorker` is true - Already has all deadline proximity logic, status badge variants, row actions **`resources/js/types/dashboard.ts`** — Current types (Story 2.1 + 2.2): - Has: `DashboardStats`, `DashboardDeclaration`, `StatCardLink`, `DashboardAlert`, `DashboardProps` - **ADD:** `isWorker: boolean` to `DashboardProps` ### Files to Modify | File | Changes | |------|---------| | `app/Http/Controllers/DashboardController.php` | Add `isWorker` boolean to Inertia props; modify StatCard `href` to include `assignee` for Workers | | `resources/js/pages/Dashboard.vue` | Add Worker subtitle, EmptyState for no assignments, hide assignee column for Workers | | `resources/js/types/dashboard.ts` | Add `isWorker: boolean` to `DashboardProps` | ### New Files to Create | File | Purpose | |------|---------| | `tests/Feature/Dashboard/WorkerDashboardTest.php` | Pest feature tests for Worker-scoped dashboard | ### Component Specifications (from UX Design & Architecture) **Worker Dashboard Layout:** - Same 4-column KPI card grid as Owner/Manager - Same PriorityAlertsPanel component (data is already scoped by backend) - Same urgent declarations table but WITHOUT "Assigné à" column (redundant for Worker) - Section heading: "Mes déclarations" (replaces "Déclarations urgentes" for Worker) or keep "Déclarations urgentes" but add subtitle "Mes déclarations" above the KPI cards - Nudge and Reassign row actions remain disabled (Workers cannot nudge or reassign) **EmptyState (Worker with no assignments):** - Icon: `FolderOpen` or `ClipboardList` from lucide-vue-next (consistent with existing empty state icon) - Title: "Aucune déclaration assignée" - Subtitle: "Contactez votre responsable pour recevoir des déclarations" - No action button (Worker cannot self-assign) - Show this ONLY when all KPI counts are 0 AND declarations array is empty ### Frontend Conditional Rendering Pattern ```vue
Mes déclarations