9.5 KiB
9.5 KiB
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.mdSection 5.7 (Admin Dashboard) - Database Schema:
docs/prd.mdSection 16.1
Acceptance Criteria
User Metrics Card
- Total active clients (individual + company with status = active)
- Individual vs company breakdown (count each type)
- Deactivated clients count
- New clients this month (created_at in current month)
Booking Metrics Card
- Pending requests count (highlighted with warning color)
- Today's consultations (scheduled for today, approved status)
- This week's consultations
- This month's consultations
- Free vs paid breakdown (consultation_type field)
- No-show rate percentage (no-show / total completed * 100)
Timeline Metrics Card
- Active case timelines (status = active)
- Archived timelines (status = archived)
- Updates added this week (timeline_updates created in last 7 days)
Posts Metrics Card
- Total published posts (status = published)
- Posts published this month
Design
- Clean card-based layout using Flux UI components
- Color-coded status indicators (gold for highlights, success green, warning colors)
- Responsive grid (2 columns on tablet, 1 on mobile, 4 on desktop)
- 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
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
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
// 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