libra/docs/stories/story-6.1-dashboard-overvie...

371 lines
13 KiB
Markdown

# Story 6.1: Dashboard Overview & Statistics
## 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
<?php
use App\Models\User;
use App\Models\Consultation;
use App\Models\Timeline;
use App\Models\TimelineUpdate;
use App\Models\Post;
use Illuminate\Support\Facades\Cache;
use Livewire\Volt\Component;
new class extends Component {
public function with(): array
{
return [
'userMetrics' => $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(),
]);
}
}; ?>
<div>
{{-- Dashboard content with Flux UI cards --}}
</div>
```
### Flux UI Components to Use
- `<flux:heading>` - Page title
- `<flux:card>` or custom card component - Metric cards (if available, otherwise Tailwind)
- `<flux:badge>` - Status indicators
- `<flux:text>` - 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.