# Story 6.1: Dashboard Overview & Statistics > **Note:** The color values in this story were implemented with the original Navy+Gold palette. > These colors were updated in Epic 10 (Brand Color Refresh) to the new Charcoal+Warm Gray palette. > See `docs/brand.md` for current color specifications. ## Epic Reference **Epic 6:** Admin Dashboard ## User Story As an **admin**, I want **to see real-time metrics and key statistics at a glance**, So that **I can understand the current state of my practice**. ## Prerequisites / Dependencies This story requires the following to be completed first: | Dependency | Required From | What's Needed | |------------|---------------|---------------| | User Model | Epic 2 | `status` (active/deactivated), `user_type` (individual/company) fields | | Consultation Model | Epic 3 | `consultations` table with `status`, `consultation_type`, `scheduled_date` | | Timeline Model | Epic 4 | `timelines` table with `status` (active/archived) | | Timeline Updates | Epic 4 | `timeline_updates` table with `created_at` | | Post Model | Epic 5 | `posts` table with `status` (published/draft), `created_at` | | Admin Layout | Epic 1 | Admin authenticated layout with navigation | **References:** - Epic 6 details: `docs/epics/epic-6-admin-dashboard.md` - PRD Dashboard Section: `docs/prd.md` Section 5.7 (Admin Dashboard) - Database Schema: `docs/prd.md` Section 16.1 ## Acceptance Criteria ### User Metrics Card - [x] Total active clients (individual + company with status = active) - [x] Individual vs company breakdown (count each type) - [x] Deactivated clients count - [x] New clients this month (created_at in current month) ### Booking Metrics Card - [x] Pending requests count (highlighted with warning color) - [x] Today's consultations (scheduled for today, approved status) - [x] This week's consultations - [x] This month's consultations - [x] Free vs paid breakdown (consultation_type field) - [x] No-show rate percentage (no-show / total completed * 100) ### Timeline Metrics Card - [x] Active case timelines (status = active) - [x] Archived timelines (status = archived) - [x] Updates added this week (timeline_updates created in last 7 days) ### Posts Metrics Card - [x] Total published posts (status = published) - [x] Posts published this month ### Design - [x] Clean card-based layout using Flux UI components - [x] Color-coded status indicators (gold for highlights, success green, warning colors) - [x] Responsive grid (2 columns on tablet, 1 on mobile, 4 on desktop) - [x] Navy blue and gold color scheme per PRD Section 7.1 ## Technical Implementation ### Files to Create/Modify | File | Purpose | |------|---------| | `resources/views/livewire/admin/dashboard.blade.php` | Main Volt component | | `routes/web.php` | Add admin dashboard route | ### Route Definition ```php Route::middleware(['auth', 'admin'])->prefix('admin')->group(function () { Route::get('/dashboard', function () { return view('livewire.admin.dashboard'); })->name('admin.dashboard'); }); ``` ### Component Structure (Volt Class-Based) ```php $this->getUserMetrics(), 'bookingMetrics' => $this->getBookingMetrics(), 'timelineMetrics' => $this->getTimelineMetrics(), 'postMetrics' => $this->getPostMetrics(), ]; } private function getUserMetrics(): array { return Cache::remember('admin.metrics.users', 300, fn() => [ 'total_active' => User::where('status', 'active') ->whereIn('user_type', ['individual', 'company'])->count(), 'individual' => User::where('user_type', 'individual') ->where('status', 'active')->count(), 'company' => User::where('user_type', 'company') ->where('status', 'active')->count(), 'deactivated' => User::where('status', 'deactivated')->count(), 'new_this_month' => User::whereMonth('created_at', now()->month) ->whereYear('created_at', now()->year)->count(), ]); } private function getBookingMetrics(): array { return Cache::remember('admin.metrics.bookings', 300, function () { $total = Consultation::whereIn('status', ['completed', 'no-show'])->count(); $noShows = Consultation::where('status', 'no-show')->count(); return [ 'pending' => Consultation::where('status', 'pending')->count(), 'today' => Consultation::whereDate('scheduled_date', today()) ->where('status', 'approved')->count(), 'this_week' => Consultation::whereBetween('scheduled_date', [ now()->startOfWeek(), now()->endOfWeek() ])->count(), 'this_month' => Consultation::whereMonth('scheduled_date', now()->month) ->whereYear('scheduled_date', now()->year)->count(), 'free' => Consultation::where('consultation_type', 'free')->count(), 'paid' => Consultation::where('consultation_type', 'paid')->count(), 'no_show_rate' => $total > 0 ? round(($noShows / $total) * 100, 1) : 0, ]; }); } private function getTimelineMetrics(): array { return Cache::remember('admin.metrics.timelines', 300, fn() => [ 'active' => Timeline::where('status', 'active')->count(), 'archived' => Timeline::where('status', 'archived')->count(), 'updates_this_week' => TimelineUpdate::where('created_at', '>=', now()->subWeek())->count(), ]); } private function getPostMetrics(): array { return Cache::remember('admin.metrics.posts', 300, fn() => [ 'total_published' => Post::where('status', 'published')->count(), 'this_month' => Post::where('status', 'published') ->whereMonth('created_at', now()->month) ->whereYear('created_at', now()->year)->count(), ]); } }; ?>
{{-- Dashboard content with Flux UI cards --}}
``` ### Flux UI Components to Use - `` - Page title - `` or custom card component - Metric cards (if available, otherwise Tailwind) - `` - Status indicators - `` - Metric labels and values ### Cache Strategy - **TTL:** 300 seconds (5 minutes) for all metrics - **Keys:** `admin.metrics.users`, `admin.metrics.bookings`, `admin.metrics.timelines`, `admin.metrics.posts` - **Invalidation:** Consider cache clearing when data changes (optional enhancement) ## Edge Cases & Error Handling | Scenario | Expected Behavior | |----------|-------------------| | Empty database (0 clients) | All metrics display "0" - no errors | | No consultations exist | No-show rate displays "0%" (not "N/A" or error) | | New month (1st day) | "This month" metrics show 0 | | Cache failure | Queries execute directly without caching (graceful degradation) | | Division by zero (no-show rate) | Return 0 when total consultations is 0 | ## Testing Requirements ### Test File `tests/Feature/Admin/DashboardTest.php` ### Test Scenarios ```php // 1. Dashboard loads successfully for admin test('admin can view dashboard', function () { $admin = User::factory()->admin()->create(); $this->actingAs($admin) ->get(route('admin.dashboard')) ->assertSuccessful() ->assertSee('Dashboard'); }); // 2. Metrics display correctly with sample data test('dashboard displays correct user metrics', function () { User::factory()->count(5)->create(['status' => 'active', 'user_type' => 'individual']); User::factory()->count(3)->create(['status' => 'active', 'user_type' => 'company']); User::factory()->count(2)->create(['status' => 'deactivated']); // Assert metrics show 8 active, 5 individual, 3 company, 2 deactivated }); // 3. Empty state handling test('dashboard handles empty database gracefully', function () { $admin = User::factory()->admin()->create(); $this->actingAs($admin) ->get(route('admin.dashboard')) ->assertSuccessful() ->assertSee('0'); // Should show zeros, not errors }); // 4. No-show rate calculation test('no-show rate calculates correctly', function () { // Create 10 completed, 2 no-shows = 20% rate }); // 5. Cache behavior test('metrics are cached for 5 minutes', function () { // Verify cache key exists after first load }); // 6. Non-admin cannot access test('non-admin cannot access dashboard', function () { $client = User::factory()->client()->create(); $this->actingAs($client) ->get(route('admin.dashboard')) ->assertForbidden(); }); ``` ### Manual Testing Checklist - [ ] Verify responsive layout on mobile (375px) - [ ] Verify responsive layout on tablet (768px) - [ ] Verify responsive layout on desktop (1200px+) - [ ] Verify pending count is highlighted/prominent - [ ] Verify color scheme matches PRD (navy blue, gold) - [ ] Verify RTL layout works correctly (Arabic) ## Definition of Done - [ ] All metric cards display correctly with accurate data - [ ] Data is cached with 5-minute TTL - [ ] Empty states handled gracefully (show 0, no errors) - [ ] No-show rate handles division by zero - [ ] Responsive layout works on mobile, tablet, desktop - [ ] Color scheme matches brand guidelines - [ ] All tests pass - [ ] Admin-only access enforced - [ ] Code formatted with Pint ## Estimation **Complexity:** Medium | **Effort:** 4-5 hours ## Out of Scope - Real-time updates (polling/websockets) - covered in Story 6.3 - Charts and visualizations - covered in Story 6.2 - Quick action buttons - covered in Story 6.3 --- ## Dev Agent Record ### Status Ready for Review ### Agent Model Used Claude Opus 4.5 ### File List | File | Action | |------|--------| | `routes/web.php` | Modified - Updated admin dashboard route to use Volt | | `resources/views/livewire/admin/dashboard.blade.php` | Created - Main Volt dashboard component | | `resources/views/livewire/admin/dashboard-placeholder.blade.php` | Deleted - Removed old placeholder | | `lang/en/admin_metrics.php` | Created - English translations for dashboard | | `lang/ar/admin_metrics.php` | Created - Arabic translations for dashboard | | `tests/Feature/Admin/DashboardTest.php` | Created - 21 tests covering all functionality | ### Completion Notes - Implemented all 4 metric cards (Users, Bookings, Timelines, Posts) - Used enums for status comparisons (UserStatus, ConsultationStatus, etc.) - Implemented 5-minute cache TTL for all metrics - Added responsive grid layout (1 col mobile, 2 tablet, 4 desktop) - Used navy blue (#0A1F44) and gold (#D4AF37) color scheme - Pending requests highlighted with amber badge - Named translation file `admin_metrics.php` to avoid collision with `__('Dashboard')` in sidebar - All 21 tests pass including edge cases (empty database, division by zero) ### Change Log | Date | Change | |------|--------| | 2025-12-27 | Initial implementation of Story 6.1 | ## QA Results ### Review Date: 2025-12-27 ### Reviewed By: Quinn (Test Architect) ### Code Quality Assessment **Overall: Excellent** - The implementation is clean, well-structured, and follows Laravel/Livewire best practices. The code correctly uses: - Enums for status comparisons (UserStatus, ConsultationStatus, etc.) - Eloquent `query()` method for building queries - Cache::remember for 5-minute TTL as specified - Proper separation of concerns with private methods for each metric group - Bilingual translations (Arabic/English) - Flux UI components appropriately - Responsive grid layout per specifications ### Refactoring Performed - **File**: `resources/views/livewire/admin/dashboard.blade.php` - **Change**: Fixed braces_position coding standard violation (line 17) - **Why**: Pint linter reported `braces_position` style issue - **How**: Changed `new class extends Component {` to `new class extends Component` with brace on next line ### Compliance Check - Coding Standards: ✓ Passes Pint after fix - Project Structure: ✓ Follows existing admin component patterns - Testing Strategy: ✓ 21 Pest tests covering all functionality - All ACs Met: ✓ All acceptance criteria fully implemented ### Improvements Checklist - [x] Fixed Pint formatting issue (braces_position) - [ ] Consider adding `@php /** @var array $userMetrics */ @endphp` for IDE type hints (optional enhancement) - [ ] Consider extracting card component for reusability across future dashboard stories (future enhancement) ### Security Review No security concerns identified. The implementation: - Uses admin middleware for access control - Does not expose sensitive data - Uses safe Eloquent queries (no raw SQL) - Properly scopes metrics to exclude admin users from client counts ### Performance Considerations **Well-optimized:** - Cache TTL of 300 seconds (5 minutes) reduces database load - Uses efficient `count()` queries instead of loading models - No N+1 query issues (no relationships loaded) **Minor observation:** There are 13 separate count queries when cache is cold. For high-traffic scenarios, these could be consolidated using raw queries, but for an admin dashboard this is perfectly acceptable. ### Files Modified During Review | File | Change | |------|--------| | `resources/views/livewire/admin/dashboard.blade.php` | Fixed braces_position Pint style | ### Gate Status Gate: **PASS** → `docs/qa/gates/6.1-dashboard-overview-statistics.yml` ### Recommended Status ✓ **Ready for Done** - All acceptance criteria met, tests passing, code quality excellent.