# Story 7.1: Client Dashboard Overview ## Epic Reference **Epic 7:** Client Dashboard ## User Story As a **client**, I want **a dashboard showing my key information at a glance**, So that **I can quickly see upcoming consultations and case updates**. ## Dependencies ### Epic Dependencies | Epic | Dependency | Status | |------|------------|--------| | Epic 1 | Authentication system, base UI layout | Required | | Epic 2 | User model with client data | Required | | Epic 3 | Consultation model and booking system | Required | | Epic 4 | Timeline and TimelineUpdate models | Required | ### Model Prerequisites The following models and relationships must exist before implementation: **User Model** (`app/Models/User.php`): - `consultations()` - HasMany relationship to Consultation - `timelines()` - HasMany relationship to Timeline **Consultation Model** (`app/Models/Consultation.php`): - `approved()` scope - filters `status = 'approved'` - `pending()` scope - filters `status = 'pending'` - `upcoming()` scope - filters `scheduled_date >= today()` - `scheduled_date` column (date) - `scheduled_time` column (time) - `type` column (enum: 'free', 'paid') - `status` column (enum: 'pending', 'approved', 'rejected', 'completed', 'no-show', 'cancelled') **Timeline Model** (`app/Models/Timeline.php`): - `active()` scope - filters `status = 'active'` - `user_id` foreign key - `updates()` - HasMany relationship to TimelineUpdate **TimelineUpdate Model** (`app/Models/TimelineUpdate.php`): - `timeline()` - BelongsTo relationship to Timeline - `update_text` column - `created_at` timestamp ## Acceptance Criteria ### Welcome Section - [ ] Display "Welcome, {client name}" greeting - [ ] Use localized greeting based on user's preferred language - [ ] Show current date in user's locale format ### Upcoming Consultations Widget - [ ] Display next approved consultation (or "No upcoming consultations" if none) - [ ] Show consultation date/time formatted per locale (AR: DD/MM/YYYY, EN: MM/DD/YYYY) - [ ] Show time in 12-hour format (AM/PM) - [ ] Display type badge: "Free" (green) or "Paid" (gold) - [ ] Display status badge with appropriate color - [ ] "View Details" link to consultation details (Story 7.2) ### Active Cases Widget - [ ] Display count of active timelines - [ ] Show preview of most recent update (truncated to ~100 chars) - [ ] "View All Cases" link to timelines list (Story 7.3) - [ ] Empty state: "No active cases" with muted styling ### Recent Updates Widget - [ ] Display last 3 timeline updates across all user's cases - [ ] Each update shows: case name, update date, preview text - [ ] "View Timeline" link for each update - [ ] Empty state: "No recent updates" ### Booking Status Widget - [ ] Display count of pending booking requests - [ ] Show booking limit indicator: - Can book: "You can book a consultation today" (green) - Cannot book: "You already have a booking for today" (amber) - [ ] "Book Consultation" button linking to booking page (Story 7.5) - [ ] Disable button if daily limit reached ### Design Requirements - [ ] Card-based layout using Flux UI components - [ ] Color scheme: Navy (#0A1F44) background sections, Gold (#D4AF37) accents - [ ] Mobile-first responsive (stack cards vertically on mobile) - [ ] All text content bilingual (Arabic RTL / English LTR) - [ ] Use consistent spacing: `gap-6` between cards, `p-6` card padding ### Edge Cases & Empty States - [ ] No consultations: Show empty state with "Book your first consultation" CTA - [ ] No timelines: Show empty state "No cases assigned yet" - [ ] No updates: Show empty state "No recent updates" - [ ] Loading state: Show skeleton loaders while data fetches ## Technical Implementation ### Files to Create/Modify | File | Action | Purpose | |------|--------|---------| | `resources/views/livewire/client/dashboard.blade.php` | Create | Main Volt component | | `routes/web.php` | Modify | Add client dashboard route | | `resources/views/components/layouts/client.blade.php` | Create/Verify | Client layout if not exists | ### Route Configuration ```php // routes/web.php Route::middleware(['auth', 'verified'])->prefix('client')->group(function () { Route::get('/dashboard', function () { return view('livewire.client.dashboard'); })->name('client.dashboard'); }); ``` ### Component Structure ```php user(); return [ 'upcomingConsultation' => $user->consultations() ->approved() ->upcoming() ->orderBy('scheduled_date') ->orderBy('scheduled_time') ->first(), 'activeTimelinesCount' => $user->timelines()->active()->count(), 'recentUpdates' => TimelineUpdate::whereHas('timeline', fn($q) => $q->where('user_id', $user->id)) ->latest() ->take(3) ->with('timeline') ->get(), 'pendingBookingsCount' => $user->consultations()->pending()->count(), 'canBookToday' => !$user->consultations() ->whereDate('scheduled_date', today()) ->whereIn('status', ['pending', 'approved']) ->exists(), ]; } }; ?>
{{-- Welcome Section --}} {{-- Widgets Grid --}}
``` ### Flux UI Components to Use - `` - Widget containers - `` - Section titles - `` - Status/type indicators - `` - CTAs - `` - Body content - `` - Loading states ### Localization - Create/update `resources/lang/en/client.php` and `resources/lang/ar/client.php` - Keys needed: `dashboard.welcome`, `dashboard.upcoming`, `dashboard.cases`, `dashboard.updates`, `dashboard.booking`, empty state messages ## Testing Requirements ### Test File `tests/Feature/Client/DashboardTest.php` ### Test Scenarios ```php use App\Models\User; use App\Models\Consultation; use App\Models\Timeline; use App\Models\TimelineUpdate; use Livewire\Volt\Volt; test('client can view their dashboard', function () { $user = User::factory()->create(); $this->actingAs($user) ->get(route('client.dashboard')) ->assertOk() ->assertSeeLivewire('client.dashboard'); }); test('dashboard shows only authenticated user consultations', function () { $user = User::factory()->create(); $otherUser = User::factory()->create(); $myConsultation = Consultation::factory()->approved()->upcoming()->for($user)->create(); $otherConsultation = Consultation::factory()->approved()->upcoming()->for($otherUser)->create(); Volt::test('client.dashboard') ->actingAs($user) ->assertSee($myConsultation->scheduled_date->format('...')) ->assertDontSee($otherConsultation->scheduled_date->format('...')); }); test('dashboard shows correct active timelines count', function () { $user = User::factory()->create(); Timeline::factory()->active()->count(3)->for($user)->create(); Timeline::factory()->archived()->count(2)->for($user)->create(); Volt::test('client.dashboard') ->actingAs($user) ->assertSee('3'); // Only active count }); test('dashboard shows last 3 timeline updates', function () { $user = User::factory()->create(); $timeline = Timeline::factory()->for($user)->create(); TimelineUpdate::factory()->count(5)->for($timeline)->create(); Volt::test('client.dashboard') ->actingAs($user) ->assertViewHas('recentUpdates', fn($updates) => $updates->count() === 3); }); test('canBookToday is false when user has booking today', function () { $user = User::factory()->create(); Consultation::factory()->approved()->for($user)->create([ 'scheduled_date' => today(), ]); Volt::test('client.dashboard') ->actingAs($user) ->assertSet('canBookToday', false); }); test('canBookToday is true when user has no booking today', function () { $user = User::factory()->create(); Volt::test('client.dashboard') ->actingAs($user) ->assertSet('canBookToday', true); }); test('dashboard handles empty state gracefully', function () { $user = User::factory()->create(); Volt::test('client.dashboard') ->actingAs($user) ->assertSee(__('client.dashboard.no_upcoming')) ->assertSee(__('client.dashboard.no_cases')); }); test('unauthenticated user cannot access dashboard', function () { $this->get(route('client.dashboard')) ->assertRedirect(route('login')); }); ``` ### Factory Requirements Ensure factories exist with states: - `Consultation::factory()->approved()`, `->pending()`, `->upcoming()` - `Timeline::factory()->active()`, `->archived()` ## References ### PRD Sections - **Section 5.8** - Client Dashboard requirements and components - **Section 7.1** - Design requirements (color scheme, typography) - **Section 5.4** - Booking rules (1 per day limit, consultation types) - **Section 5.5** - Timeline system structure ### Design Specifications - Primary: Navy Blue `#0A1F44` - Accent: Gold `#D4AF37` - Card styling: `shadow-sm`, `rounded-lg`, `border border-gray-200` - Spacing scale per PRD Section 7.1 ### Related Stories - Story 7.2: My Consultations View (link target for upcoming consultation) - Story 7.3: My Cases/Timelines View (link target for cases widget) - Story 7.5: New Booking Interface (link target for book button) ## Definition of Done - [ ] Volt component created at `resources/views/livewire/client/dashboard.blade.php` - [ ] Route registered and protected with auth middleware - [ ] All 5 widgets display correctly with real data - [ ] Data strictly scoped to authenticated user (security verified) - [ ] Empty states display appropriately for each widget - [ ] Mobile responsive (tested on 375px viewport) - [ ] Bilingual content working (AR/EN toggle) - [ ] All test scenarios pass - [ ] Code formatted with `vendor/bin/pint --dirty` - [ ] No console errors or warnings ## Estimation **Complexity:** Medium | **Effort:** 4-5 hours ## Out of Scope - Consultation detail view (Story 7.2) - Timeline detail view (Story 7.3) - Booking form functionality (Story 7.5) - Real-time updates via websockets