From f6c06ec3e12f84598f89b64a100b59bcf6ff4b99 Mon Sep 17 00:00:00 2001 From: Naser Mansour Date: Sun, 21 Dec 2025 00:20:35 +0200 Subject: [PATCH] reviewd epic 7 stories --- .../story-7.1-client-dashboard-overview.md | 272 +++++++++++++-- .../story-7.2-my-consultations-view.md | 125 ++++++- .../story-7.3-my-cases-timelines-view.md | 274 ++++++++++++--- docs/stories/story-7.4-my-profile-view.md | 319 ++++++++++++++++-- .../story-7.6-booking-limit-indicator.md | 200 ++++++++--- 5 files changed, 1037 insertions(+), 153 deletions(-) diff --git a/docs/stories/story-7.1-client-dashboard-overview.md b/docs/stories/story-7.1-client-dashboard-overview.md index 336ad21..c4805ed 100644 --- a/docs/stories/story-7.1-client-dashboard-overview.md +++ b/docs/stories/story-7.1-client-dashboard-overview.md @@ -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 + $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(), ]; } -}; +}; ?> + +
+ {{-- Welcome Section --}} + {{-- Widgets Grid --}} +
``` +### Flux UI Components to Use +- `` - Widget containers +- `` - Section titles +- `` - Status/type indicators +- `` - CTAs +- `` - Body content +- `` - 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 diff --git a/docs/stories/story-7.2-my-consultations-view.md b/docs/stories/story-7.2-my-consultations-view.md index 72480cb..2a164a2 100644 --- a/docs/stories/story-7.2-my-consultations-view.md +++ b/docs/stories/story-7.2-my-consultations-view.md @@ -8,19 +8,24 @@ As a **client**, I want **to view all my consultations**, So that **I can track upcoming appointments and review past sessions**. +## Dependencies +- **Story 7.1:** Client Dashboard Overview (navigation context) +- **Epic 3:** Consultation model with scopes (`approved()`, `pending()`) +- **Story 3.6:** Calendar file generation (.ics) for download functionality + ## Acceptance Criteria ### Upcoming Consultations Section -- [ ] Date and time +- [ ] Date and time (formatted per locale) - [ ] Consultation type (free/paid) - [ ] Status (approved/pending) -- [ ] Payment status (for paid) +- [ ] Payment status (for paid consultations) - [ ] Download .ics calendar file button ### Pending Requests Section - [ ] Submitted bookings awaiting approval - [ ] Submission date -- [ ] Problem summary preview +- [ ] Problem summary preview (truncated) - [ ] Status: "Pending Review" ### Past Consultations Section @@ -29,14 +34,38 @@ So that **I can track upcoming appointments and review past sessions**. - [ ] Date and type ### Features -- [ ] Visual status indicators +- [ ] Visual status indicators (badges with colors) - [ ] Sort by date (default: newest first for past) -- [ ] Pagination if many consultations +- [ ] Pagination if many consultations (10 per page) - [ ] No edit/cancel capabilities (read-only) +### Empty States +- [ ] "No upcoming consultations" message with link to book +- [ ] "No pending requests" message +- [ ] "No past consultations" message + ## Technical Notes +### Files to Create/Modify +- `resources/views/livewire/client/consultations.blade.php` - Main Volt component +- `routes/web.php` - Add route within client middleware group + +### Route Definition ```php +// In routes/web.php, within authenticated client routes +Route::get('/client/consultations', function () { + return view('livewire.client.consultations'); +})->name('client.consultations'); +``` + +### Volt Component Structure +```php +get(), 'past' => $user->consultations() ->whereIn('status', ['completed', 'cancelled', 'no_show']) - ->orWhere(fn($q) => $q->approved()->where('scheduled_date', '<', today())) + ->orWhere(fn($q) => $q + ->where('user_id', $user->id) + ->approved() + ->where('scheduled_date', '<', today())) ->latest('scheduled_date') ->paginate(10), ]; } -}; + + public function downloadCalendar(Consultation $consultation): \Symfony\Component\HttpFoundation\StreamedResponse + { + // Reuse .ics generation from Story 3.6 + return $consultation->generateIcsDownload(); + } +}; ?> ``` +### Model Scopes Required (from Epic 3) +The Consultation model should have these scopes: +- `scopeApproved($query)` - `where('status', 'approved')` +- `scopePending($query)` - `where('status', 'pending')` + +### Flux UI Components to Use +- `` - Status indicators (approved=green, pending=yellow, completed=gray, cancelled=red) +- `` - Download calendar button +- `` - Section headings +- `` - Consultation details + +### Status Badge Colors +| Status | Color | Variant | +|--------|-------|---------| +| approved | green | `variant="success"` | +| pending | yellow | `variant="warning"` | +| completed | gray | `variant="subtle"` | +| cancelled | red | `variant="danger"` | +| no_show | red | `variant="danger"` | + +### Payment Status Display +- For paid consultations, show payment status: + - "Payment Pending" (yellow badge) + - "Payment Received" (green badge) +- For free consultations, show "Free" (blue badge) + +## References +- `docs/epics/epic-3-booking-consultation.md` - Consultation model, statuses, payment handling +- `docs/epics/epic-3-booking-consultation.md#story-36` - .ics calendar file generation +- `docs/stories/story-7.1-client-dashboard-overview.md` - Dashboard navigation context + +## Test Scenarios + +### Access Control +- [ ] Unauthenticated users redirected to login +- [ ] User sees only their own consultations (not other users') + +### Upcoming Section +- [ ] Shows approved consultations with `scheduled_date >= today` +- [ ] Sorted by date ascending (soonest first) +- [ ] Displays type, status, payment status correctly +- [ ] Calendar download button triggers .ics download + +### Pending Section +- [ ] Shows consultations with status='pending' +- [ ] Displays submission date and problem summary +- [ ] Summary truncated if too long + +### Past Section +- [ ] Shows completed, cancelled, no_show consultations +- [ ] Shows approved consultations with `scheduled_date < today` +- [ ] Sorted by date descending (newest first) +- [ ] Pagination works correctly (10 per page) + +### Empty States +- [ ] Empty upcoming shows appropriate message +- [ ] Empty pending shows appropriate message +- [ ] Empty past shows appropriate message + +### Calendar Download +- [ ] Download generates valid .ics file +- [ ] File contains correct consultation details +- [ ] Only available for approved upcoming consultations + ## Definition of Done - [ ] All sections display correctly - [ ] Calendar download works -- [ ] Status indicators clear -- [ ] Read-only (no actions) -- [ ] Pagination works +- [ ] Status indicators clear and color-coded +- [ ] Read-only (no actions except download) +- [ ] Pagination works for past consultations +- [ ] Empty states display appropriately +- [ ] Mobile responsive +- [ ] Bilingual support - [ ] Tests pass ## Estimation diff --git a/docs/stories/story-7.3-my-cases-timelines-view.md b/docs/stories/story-7.3-my-cases-timelines-view.md index 77b15db..a2fa813 100644 --- a/docs/stories/story-7.3-my-cases-timelines-view.md +++ b/docs/stories/story-7.3-my-cases-timelines-view.md @@ -1,73 +1,245 @@ -# Story 7.3: My Cases/Timelines View +# Story 7.3: My Cases/Timelines View (Dashboard Integration) ## Epic Reference **Epic 7:** Client Dashboard ## User Story As a **client**, -I want **to view my case timelines and their updates**, -So that **I can track the progress of my legal matters**. +I want **to access my case timelines from the dashboard navigation**, +So that **I can easily track the progress of my legal matters from one central location**. + +## Story Context + +### Relationship to Story 4.5 +Story 4.5 (`docs/stories/story-4.5-client-timeline-view.md`) already implements the **complete timeline viewing functionality**: +- Routes: `client.timelines.index` and `client.timelines.show` +- Components: `pages/client/timelines/index.blade.php` and `show.blade.php` +- Active/archived separation with visual distinction +- Individual timeline detail view with chronological updates +- Authorization, tests, and translations + +**This story (7.3) focuses solely on dashboard navigation integration** - ensuring clients can access the existing timeline views from the Epic 7 client dashboard structure. + +### Prerequisites +- **Story 4.5:** Client Timeline View - MUST be complete (provides all timeline components) +- **Story 7.1:** Client Dashboard Overview - MUST be complete (provides dashboard layout and navigation) + +### What This Story Does NOT Do +- Does NOT recreate timeline list or detail views (use Story 4.5's components) +- Does NOT add new timeline functionality +- Does NOT modify existing timeline components ## Acceptance Criteria -### Active Cases Section -- [ ] List of active timelines -- [ ] Case name and reference -- [ ] Last update date -- [ ] Update count -- [ ] "View" button +### Navigation Integration +- [ ] "My Cases" navigation item added to client dashboard sidebar/nav +- [ ] Navigation links to `route('client.timelines.index')` +- [ ] Active state shown when on timeline routes +- [ ] Icon: folder or briefcase icon for cases -### Archived Cases Section -- [ ] Clearly separated from active -- [ ] Different visual styling (muted) -- [ ] Still accessible for viewing +### Dashboard Widget (on Story 7.1's dashboard) +- [ ] "My Cases" widget card displays: + - Count of active cases + - Latest update preview (case name + date) + - "View All" link to timeline index +- [ ] Widget shows empty state if no cases exist -### Individual Timeline View -- [ ] Case name and reference -- [ ] Status badge (active/archived) -- [ ] All updates in chronological order -- [ ] Each update shows: - - Date and time - - Update content -- [ ] Read-only (no interactions) +### Layout Consistency +- [ ] Timeline pages use client dashboard layout (consistent header/nav) +- [ ] Breadcrumbs: Dashboard > My Cases (on index) +- [ ] Breadcrumbs: Dashboard > My Cases > [Case Name] (on show) -### Navigation -- [ ] Back to cases list -- [ ] Responsive layout +### Bilingual Support +- [ ] Navigation label translated (AR/EN) +- [ ] Widget content translated (AR/EN) ## Technical Notes -Reuse components from Story 4.5. +### File Structure +``` +Files to Modify: + resources/views/components/layouts/client.blade.php (add nav item) + OR resources/views/livewire/pages/client/dashboard.blade.php (add widget) + +Files from Story 4.5 (DO NOT MODIFY - just ensure they exist): + resources/views/livewire/pages/client/timelines/index.blade.php + resources/views/livewire/pages/client/timelines/show.blade.php + +Tests to Create: + tests/Feature/Client/DashboardTimelineIntegrationTest.php +``` + +### Navigation Item Addition +Add to client dashboard navigation (location depends on Story 7.1's implementation): ```php -new class extends Component { - public function with(): array - { - return [ - 'activeTimelines' => auth()->user() - ->timelines() - ->active() - ->withCount('updates') - ->latest('updated_at') - ->get(), - 'archivedTimelines' => auth()->user() - ->timelines() - ->archived() - ->withCount('updates') - ->latest('updated_at') - ->get(), - ]; - } -}; +{{-- In client layout/navigation component --}} + + {{ __('client.my_cases') }} + +``` + +### Dashboard Widget Component +Add to Story 7.1's dashboard view: + +```php +{{-- My Cases Widget --}} +
+
+

{{ __('client.my_cases') }}

+ {{ $activeTimelinesCount }} {{ __('client.active') }} +
+ + @if($latestTimelineUpdate) +
+

{{ $latestTimelineUpdate->timeline->case_name }}

+

{{ __('client.last_update') }}: {{ $latestTimelineUpdate->created_at->diffForHumans() }}

+
+ @else +

{{ __('client.no_cases_yet') }}

+ @endif + + + {{ __('client.view_all_cases') }} + +
+``` + +### Data for Widget (add to Story 7.1's dashboard component) +```php +// In dashboard component's with() method +'activeTimelinesCount' => auth()->user()->timelines()->active()->count(), +'latestTimelineUpdate' => TimelineUpdate::whereHas('timeline', + fn($q) => $q->where('user_id', auth()->id())->active() +) + ->with('timeline:id,case_name') + ->latest() + ->first(), +``` + +### Required Translation Keys +```php +// Add to resources/lang/en/client.php (if not already from 4.5) +'view_all_cases' => 'View All Cases', + +// Add to resources/lang/ar/client.php +'view_all_cases' => 'عرض جميع القضايا', +``` + +## Test Scenarios + +```php +create(['user_type' => 'individual']); + + $this->actingAs($client) + ->get(route('client.dashboard')) + ->assertOk() + ->assertSee(__('client.my_cases')) + ->assertSee(route('client.timelines.index')); +}); + +test('client dashboard shows active cases count in widget', function () { + $client = User::factory()->create(['user_type' => 'individual']); + Timeline::factory()->count(3)->create([ + 'user_id' => $client->id, + 'status' => 'active', + ]); + Timeline::factory()->create([ + 'user_id' => $client->id, + 'status' => 'archived', + ]); + + $this->actingAs($client) + ->get(route('client.dashboard')) + ->assertSee('3'); // Only active cases counted +}); + +test('client dashboard shows latest timeline update', function () { + $client = User::factory()->create(['user_type' => 'individual']); + $timeline = Timeline::factory()->create([ + 'user_id' => $client->id, + 'case_name' => 'Property Dispute Case', + ]); + TimelineUpdate::factory()->create([ + 'timeline_id' => $timeline->id, + 'created_at' => now(), + ]); + + $this->actingAs($client) + ->get(route('client.dashboard')) + ->assertSee('Property Dispute Case'); +}); + +test('client dashboard shows empty state when no cases', function () { + $client = User::factory()->create(['user_type' => 'individual']); + + $this->actingAs($client) + ->get(route('client.dashboard')) + ->assertSee(__('client.no_cases_yet')); +}); + +test('my cases navigation is active on timeline routes', function () { + $client = User::factory()->create(['user_type' => 'individual']); + $timeline = Timeline::factory()->create(['user_id' => $client->id]); + + // Test on index + $this->actingAs($client) + ->get(route('client.timelines.index')) + ->assertOk(); + + // Test on show + $this->actingAs($client) + ->get(route('client.timelines.show', $timeline)) + ->assertOk(); +}); + +test('timeline pages use client dashboard layout', function () { + $client = User::factory()->create(['user_type' => 'individual']); + + $this->actingAs($client) + ->get(route('client.timelines.index')) + ->assertSee(__('client.my_cases')); // Nav item visible = layout applied +}); ``` ## Definition of Done -- [ ] Active cases display correctly -- [ ] Archived cases separated -- [ ] Timeline detail view works -- [ ] Updates display chronologically -- [ ] Read-only enforced -- [ ] Tests pass +- [ ] "My Cases" navigation item added to client dashboard +- [ ] Navigation links to existing `client.timelines.index` route +- [ ] Active state shows on timeline routes +- [ ] Dashboard widget displays active case count +- [ ] Dashboard widget shows latest update preview +- [ ] Dashboard widget links to timeline index +- [ ] Empty state handled in widget +- [ ] Translation keys added for new strings +- [ ] Timeline pages render within client dashboard layout +- [ ] All tests pass +- [ ] Code formatted with Pint + +## Dependencies + +- **Story 4.5:** Client Timeline View (REQUIRED - provides timeline components and routes) +- **Story 7.1:** Client Dashboard Overview (REQUIRED - provides dashboard layout) + +## Notes + +This story is intentionally minimal because the heavy lifting was done in Story 4.5. The developer should: +1. Verify Story 4.5 is complete and routes work +2. Add navigation item to client layout +3. Add widget to dashboard +4. Ensure layout consistency +5. Write integration tests + +Do NOT duplicate or recreate the timeline components from Story 4.5. ## Estimation -**Complexity:** Medium | **Effort:** 3-4 hours +**Complexity:** Low | **Effort:** 1-2 hours diff --git a/docs/stories/story-7.4-my-profile-view.md b/docs/stories/story-7.4-my-profile-view.md index 1185a52..f9cac96 100644 --- a/docs/stories/story-7.4-my-profile-view.md +++ b/docs/stories/story-7.4-my-profile-view.md @@ -3,40 +3,79 @@ ## Epic Reference **Epic 7:** Client Dashboard +## Dependencies +- **Epic 2:** User Management (user model with all client profile fields must exist) +- **Story 7.1:** Client Dashboard Overview (layout and navigation context) + ## User Story As a **client**, I want **to view my profile information**, So that **I can verify my account details are correct**. +## Prerequisites + +### Required User Model Fields +The `users` table must include these columns (from Epic 2): + +| Column | Type | Description | +|--------|------|-------------| +| `user_type` | enum('individual','company') | Distinguishes client type | +| `name` | string | Full name (individual) or company name | +| `national_id` | string | National ID for individuals | +| `email` | string | Email address | +| `phone` | string | Phone number | +| `preferred_language` | enum('ar','en') | User's language preference | +| `company_name` | string, nullable | Company name (company clients) | +| `company_cert_number` | string, nullable | Company registration number | +| `contact_person_name` | string, nullable | Contact person (company clients) | +| `contact_person_id` | string, nullable | Contact person's ID | +| `created_at` | timestamp | Account creation date | + ## Acceptance Criteria ### Individual Client Profile -- [ ] Full name -- [ ] National ID -- [ ] Email address -- [ ] Phone number -- [ ] Preferred language -- [ ] Account created date +- [ ] Full name displayed +- [ ] National ID displayed +- [ ] Email address displayed +- [ ] Phone number displayed +- [ ] Preferred language displayed +- [ ] Account created date displayed ### Company Client Profile -- [ ] Company name -- [ ] Registration number -- [ ] Contact person name -- [ ] Contact person ID -- [ ] Email address -- [ ] Phone number -- [ ] Preferred language -- [ ] Account created date +- [ ] Company name displayed +- [ ] Company certificate/registration number displayed +- [ ] Contact person name displayed +- [ ] Contact person ID displayed +- [ ] Email address displayed +- [ ] Phone number displayed +- [ ] Preferred language displayed +- [ ] Account created date displayed ### Features -- [ ] Account type indicator -- [ ] No edit capabilities (read-only) +- [ ] Account type indicator (Individual/Company badge) +- [ ] No edit capabilities (read-only view) - [ ] Message: "Contact admin to update your information" -- [ ] Logout button +- [ ] Logout button with confirmation redirect ## Technical Notes +### File Location +``` +resources/views/livewire/client/profile.blade.php +``` + +### Route ```php +// In routes/web.php (client authenticated routes) +Route::get('/client/profile', \Livewire\Volt\Volt::route('client.profile'))->name('client.profile'); +``` + +### Component Implementation +```php + {{ __('client.my_profile') }} + + + {{ $user->user_type === 'individual' ? __('profile.individual_account') : __('profile.company_account') }} + +
@if($user->user_type === 'individual')
@@ -69,10 +113,58 @@ new class extends Component {
{{ __('profile.national_id') }}
{{ $user->national_id }}
- +
+
{{ __('profile.email') }}
+
{{ $user->email }}
+
+
+
{{ __('profile.phone') }}
+
{{ $user->phone }}
+
+
+
{{ __('profile.preferred_language') }}
+
{{ $user->preferred_language === 'ar' ? __('profile.arabic') : __('profile.english') }}
+
+
+
{{ __('profile.member_since') }}
+
{{ $user->created_at->translatedFormat('F j, Y') }}
+
@else - +
+
+
{{ __('profile.company_name') }}
+
{{ $user->company_name }}
+
+
+
{{ __('profile.registration_number') }}
+
{{ $user->company_cert_number }}
+
+
+
{{ __('profile.contact_person') }}
+
{{ $user->contact_person_name }}
+
+
+
{{ __('profile.contact_person_id') }}
+
{{ $user->contact_person_id }}
+
+
+
{{ __('profile.email') }}
+
{{ $user->email }}
+
+
+
{{ __('profile.phone') }}
+
{{ $user->phone }}
+
+
+
{{ __('profile.preferred_language') }}
+
{{ $user->preferred_language === 'ar' ? __('profile.arabic') : __('profile.english') }}
+
+
+
{{ __('profile.member_since') }}
+
{{ $user->created_at->translatedFormat('F j, Y') }}
+
+
@endif @@ -86,13 +178,188 @@ new class extends Component { ``` +### Required Translation Keys + +Add to `lang/en/client.php`: +```php +'my_profile' => 'My Profile', +'contact_admin_to_update' => 'Contact admin to update your information', +``` + +Add to `lang/en/profile.php`: +```php +'full_name' => 'Full Name', +'national_id' => 'National ID', +'email' => 'Email Address', +'phone' => 'Phone Number', +'preferred_language' => 'Preferred Language', +'member_since' => 'Member Since', +'company_name' => 'Company Name', +'registration_number' => 'Registration Number', +'contact_person' => 'Contact Person', +'contact_person_id' => 'Contact Person ID', +'individual_account' => 'Individual Account', +'company_account' => 'Company Account', +'arabic' => 'Arabic', +'english' => 'English', +``` + +Add to `lang/en/auth.php`: +```php +'logout' => 'Logout', +``` + +Create corresponding Arabic translations in `lang/ar/` files. + +## Test Scenarios + +### Unit/Feature Tests +Create `tests/Feature/Client/ProfileTest.php`: + +```php +use App\Models\User; +use Livewire\Volt\Volt; + +test('client can view individual profile page', function () { + $user = User::factory()->individual()->create(); + + $this->actingAs($user) + ->get(route('client.profile')) + ->assertOk() + ->assertSeeLivewire('client.profile'); +}); + +test('individual profile displays all required fields', function () { + $user = User::factory()->individual()->create([ + 'name' => 'Test User', + 'national_id' => '123456789', + 'email' => 'test@example.com', + 'phone' => '+970599999999', + 'preferred_language' => 'en', + ]); + + Volt::test('client.profile') + ->actingAs($user) + ->assertSee('Test User') + ->assertSee('123456789') + ->assertSee('test@example.com') + ->assertSee('+970599999999') + ->assertSee('English'); +}); + +test('company profile displays all required fields', function () { + $user = User::factory()->company()->create([ + 'company_name' => 'Test Company', + 'company_cert_number' => 'REG-12345', + 'contact_person_name' => 'John Doe', + 'contact_person_id' => '987654321', + 'email' => 'company@example.com', + 'phone' => '+970599888888', + 'preferred_language' => 'ar', + ]); + + Volt::test('client.profile') + ->actingAs($user) + ->assertSee('Test Company') + ->assertSee('REG-12345') + ->assertSee('John Doe') + ->assertSee('987654321') + ->assertSee('company@example.com'); +}); + +test('profile page shows correct account type badge', function () { + $individual = User::factory()->individual()->create(); + $company = User::factory()->company()->create(); + + Volt::test('client.profile') + ->actingAs($individual) + ->assertSee(__('profile.individual_account')); + + Volt::test('client.profile') + ->actingAs($company) + ->assertSee(__('profile.company_account')); +}); + +test('profile page has no edit functionality', function () { + $user = User::factory()->individual()->create(); + + Volt::test('client.profile') + ->actingAs($user) + ->assertDontSee('Edit') + ->assertDontSee('Update') + ->assertDontSee('wire:model'); +}); + +test('profile page shows contact admin message', function () { + $user = User::factory()->individual()->create(); + + Volt::test('client.profile') + ->actingAs($user) + ->assertSee(__('client.contact_admin_to_update')); +}); + +test('logout button logs out user and redirects to login', function () { + $user = User::factory()->individual()->create(); + + Volt::test('client.profile') + ->actingAs($user) + ->call('logout') + ->assertRedirect(route('login')); + + $this->assertGuest(); +}); + +test('unauthenticated users cannot access profile', function () { + $this->get(route('client.profile')) + ->assertRedirect(route('login')); +}); +``` + +### User Factory States Required +Ensure `database/factories/UserFactory.php` has these states: + +```php +public function individual(): static +{ + return $this->state(fn (array $attributes) => [ + 'user_type' => 'individual', + 'national_id' => fake()->numerify('#########'), + 'company_name' => null, + 'company_cert_number' => null, + 'contact_person_name' => null, + 'contact_person_id' => null, + ]); +} + +public function company(): static +{ + return $this->state(fn (array $attributes) => [ + 'user_type' => 'company', + 'company_name' => fake()->company(), + 'company_cert_number' => fake()->numerify('REG-#####'), + 'contact_person_name' => fake()->name(), + 'contact_person_id' => fake()->numerify('#########'), + 'national_id' => null, + ]); +} +``` + ## Definition of Done -- [ ] Individual profile displays correctly -- [ ] Company profile displays correctly -- [ ] No edit functionality -- [ ] Contact admin message shown -- [ ] Logout works -- [ ] Tests pass +- [ ] Individual profile displays all fields correctly +- [ ] Company profile displays all fields correctly +- [ ] Account type badge shows correctly for both types +- [ ] No edit functionality present (read-only) +- [ ] Contact admin message displayed +- [ ] Logout button works and redirects to login +- [ ] All test scenarios pass +- [ ] Bilingual support (AR/EN) working +- [ ] Responsive design on mobile +- [ ] Code formatted with Pint ## Estimation -**Complexity:** Low | **Effort:** 2 hours +**Complexity:** Low | **Effort:** 2-3 hours + +## Notes +- Date formatting uses `translatedFormat()` for locale-aware display +- Ensure the User model has `$casts` for `created_at` as datetime +- The `bg-cream` and `text-charcoal` classes should be defined in Tailwind config per project design system diff --git a/docs/stories/story-7.6-booking-limit-indicator.md b/docs/stories/story-7.6-booking-limit-indicator.md index a0d4cad..b701e6a 100644 --- a/docs/stories/story-7.6-booking-limit-indicator.md +++ b/docs/stories/story-7.6-booking-limit-indicator.md @@ -3,6 +3,10 @@ ## Epic Reference **Epic 7:** Client Dashboard +## Dependencies +- **Story 7.5:** New Booking Interface (provides booking page where indicator displays) +- **Story 3.3:** Availability Calendar (calendar component to integrate with) + ## User Story As a **client**, I want **to see my booking status and limits clearly**, @@ -11,25 +15,46 @@ So that **I understand when I can book consultations**. ## Acceptance Criteria ### Display Locations -- [ ] Dashboard widget -- [ ] Booking page +- [ ] Dashboard widget showing booking status +- [ ] Booking page status banner ### Status Messages -- [ ] "You can book a consultation today" -- [ ] "You already have a booking for today" -- [ ] "You have a pending request for [date]" +- [ ] "You can book a consultation today" (when no booking exists for today) +- [ ] "You already have a booking for today" (when pending/approved booking exists for today) +- [ ] "You have a pending request for [date]" (shows first pending request date) ### Calendar Integration -- [ ] Calendar shows booked days as unavailable -- [ ] Visual indicator for user's booked dates +- [ ] Pass `bookedDates` array to availability calendar component +- [ ] Calendar marks user's booked dates as unavailable (distinct styling) +- [ ] Visual indicator differentiates "user already booked" from "no slots available" ### Information - [ ] Clear messaging about 1-per-day limit -- [ ] Bilingual messages +- [ ] Bilingual messages (Arabic/English) + +### Edge Cases +- [ ] Handle multiple pending requests (show count or list) +- [ ] Handle cancelled bookings (should not block new booking) +- [ ] Loading state while fetching booking status ## Technical Notes +### Files to Create +- `resources/views/livewire/client/booking-status.blade.php` - Reusable status component + +### Files to Modify +- `resources/views/livewire/client/dashboard.blade.php` - Add booking status widget +- `resources/views/livewire/client/booking.blade.php` - Add status banner (from Story 7.5) +- `resources/lang/en/booking.php` - Add translation keys +- `resources/lang/ar/booking.php` - Add translation keys + +### Component Implementation + ```php +first(); $pendingRequests = $user->consultations() - ->pending() + ->where('status', 'pending') + ->where('scheduled_date', '>=', today()) + ->orderBy('scheduled_date') ->get(); $upcomingApproved = $user->consultations() - ->approved() + ->where('status', 'approved') ->where('scheduled_date', '>=', today()) ->get(); @@ -62,43 +89,136 @@ new class extends Component { ->toArray(), ]; } -}; -``` -### Template -```blade -
- @if($canBookToday) -
- - {{ __('booking.can_book_today') }} -
- @else -
- - {{ __('booking.already_booked_today') }} -
- @endif + public function with(): array + { + return $this->getBookingStatus(); + } +}; ?> - @if($pendingRequests->isNotEmpty()) -

- {{ __('booking.pending_for_date', ['date' => $pendingRequests->first()->scheduled_date->format('d/m/Y')]) }} -

- @endif - -

- {{ __('booking.limit_message') }} -

+
+
``` +### Template + +```blade +
+ {{-- Loading State --}} +
+
+
+ +
+ @if($canBookToday) +
+ + {{ __('booking.can_book_today') }} +
+ @else +
+ + {{ __('booking.already_booked_today') }} +
+ @endif + + @if($pendingRequests->isNotEmpty()) +
+ @if($pendingRequests->count() === 1) +

{{ __('booking.pending_for_date', ['date' => $pendingRequests->first()->scheduled_date->format('d/m/Y')]) }}

+ @else +

{{ __('booking.pending_count', ['count' => $pendingRequests->count()]) }}

+
    + @foreach($pendingRequests->take(3) as $request) +
  • {{ $request->scheduled_date->format('d/m/Y') }}
  • + @endforeach +
+ @endif +
+ @endif + +

+ {{ __('booking.limit_message') }} +

+
+
+``` + +### Translation Keys + +```php +// resources/lang/en/booking.php +return [ + 'can_book_today' => 'You can book a consultation today', + 'already_booked_today' => 'You already have a booking for today', + 'pending_for_date' => 'You have a pending request for :date', + 'pending_count' => 'You have :count pending requests', + 'limit_message' => 'Note: You can book a maximum of 1 consultation per day.', +]; + +// resources/lang/ar/booking.php +return [ + 'can_book_today' => 'يمكنك حجز استشارة اليوم', + 'already_booked_today' => 'لديك حجز بالفعل لهذا اليوم', + 'pending_for_date' => 'لديك طلب معلق بتاريخ :date', + 'pending_count' => 'لديك :count طلبات معلقة', + 'limit_message' => 'ملاحظة: يمكنك حجز استشارة واحدة كحد أقصى في اليوم.', +]; +``` + +### Calendar Integration + +Pass `bookedDates` to the availability calendar from Story 3.3: + +```blade +{{-- In booking page --}} + +``` + +The calendar should style user's booked dates differently (e.g., with a badge or distinct color) from dates with no available slots. + +## Test Scenarios + +### Unit Tests +- `getBookingStatus()` returns correct structure +- `canBookToday` is true when no booking exists for today +- `canBookToday` is false when pending booking exists for today +- `canBookToday` is false when approved booking exists for today +- Cancelled bookings do not affect `canBookToday` +- `bookedDates` only includes pending and approved bookings + +### Feature Tests +- Status displays "can book today" message for new users +- Status displays "already booked" when user has today's booking +- Status displays pending request date correctly +- Multiple pending requests display count and dates +- Component renders on dashboard page +- Component renders on booking page +- Messages display correctly in Arabic locale +- Messages display correctly in English locale + +### Browser Tests (optional) +- Calendar visually shows user's booked dates as unavailable +- Status updates after successful booking submission + +## References +- **PRD Section 5.8:** Client Dashboard - Booking limit status requirement +- **PRD Section 5.4:** "Maximum 1 consultation per client per day" +- **Epic 7 Success Criteria:** "Booking limit enforcement (1 per day)" + ## Definition of Done +- [ ] Booking status component created - [ ] Status displays on dashboard - [ ] Status displays on booking page -- [ ] Calendar highlights booked dates -- [ ] Messages are accurate -- [ ] Bilingual support -- [ ] Tests pass +- [ ] Calendar highlights user's booked dates +- [ ] Messages are accurate for all states +- [ ] Bilingual support (AR/EN) +- [ ] Loading state implemented +- [ ] Edge cases handled (multiple pending, cancelled) +- [ ] Unit tests pass +- [ ] Feature tests pass +- [ ] Code formatted with Pint ## Estimation -**Complexity:** Low | **Effort:** 2 hours +**Complexity:** Low | **Effort:** 2-3 hours