reviewd epic 7 stories
This commit is contained in:
@@ -8,40 +8,117 @@ 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
|
||||
- [ ] Welcome message with client name
|
||||
- [ ] 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
|
||||
- [ ] Next consultation date/time
|
||||
- [ ] Type (free/paid)
|
||||
- [ ] Status
|
||||
- [ ] Quick link to details
|
||||
- [ ] 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
|
||||
- [ ] Count of active timelines
|
||||
- [ ] Latest update preview
|
||||
- [ ] Link to full list
|
||||
- [ ] 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
|
||||
- [ ] Last 3 timeline updates (across all cases)
|
||||
- [ ] Case name and date
|
||||
- [ ] Link to full timeline
|
||||
- [ ] 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
|
||||
- [ ] Pending booking requests
|
||||
- [ ] Daily booking limit indicator
|
||||
- [ ] Quick book button
|
||||
- [ ] 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
|
||||
- [ ] Clean, card-based layout
|
||||
- [ ] Mobile-first responsive
|
||||
- [ ] Bilingual content
|
||||
### 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
|
||||
|
||||
## Technical Notes
|
||||
### 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
|
||||
<?php
|
||||
|
||||
use App\Models\TimelineUpdate;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component {
|
||||
public function with(): array
|
||||
{
|
||||
@@ -51,6 +128,8 @@ new class extends Component {
|
||||
'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))
|
||||
@@ -58,22 +137,163 @@ new class extends Component {
|
||||
->take(3)
|
||||
->with('timeline')
|
||||
->get(),
|
||||
'pendingBookings' => $user->consultations()->pending()->count(),
|
||||
'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.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
|
||||
- [ ] All widgets display correctly
|
||||
- [ ] Data scoped to logged-in user
|
||||
- [ ] Mobile responsive
|
||||
- [ ] Bilingual
|
||||
- [ ] Tests pass
|
||||
- [ ] 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
|
||||
|
||||
Reference in New Issue
Block a user