10 KiB
10 KiB
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 Consultationtimelines()- HasMany relationship to Timeline
Consultation Model (app/Models/Consultation.php):
approved()scope - filtersstatus = 'approved'pending()scope - filtersstatus = 'pending'upcoming()scope - filtersscheduled_date >= today()scheduled_datecolumn (date)scheduled_timecolumn (time)typecolumn (enum: 'free', 'paid')statuscolumn (enum: 'pending', 'approved', 'rejected', 'completed', 'no-show', 'cancelled')
Timeline Model (app/Models/Timeline.php):
active()scope - filtersstatus = 'active'user_idforeign keyupdates()- HasMany relationship to TimelineUpdate
TimelineUpdate Model (app/Models/TimelineUpdate.php):
timeline()- BelongsTo relationship to Timelineupdate_textcolumncreated_attimestamp
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-6between cards,p-6card 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
// 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
use App\Models\TimelineUpdate;
use Livewire\Volt\Component;
new class extends Component {
public function with(): array
{
$user = auth()->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(),
];
}
}; ?>
<div>
{{-- Welcome Section --}}
{{-- Widgets Grid --}}
</div>
Flux UI Components to Use
<flux:card>- Widget containers<flux:heading>- Section titles<flux:badge>- Status/type indicators<flux:button>- CTAs<flux:text>- Body content<flux:skeleton>- Loading states
Localization
- Create/update
resources/lang/en/client.phpandresources/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
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