7.5 KiB
7.5 KiB
Story 7.6: Booking Limit Indicator
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, So that I understand when I can book consultations.
Acceptance Criteria
Display Locations
- Dashboard widget showing booking status
- Booking page status banner
Status Messages
- "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
- Pass
bookedDatesarray 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 (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 widgetresources/views/livewire/client/booking.blade.php- Add status banner (from Story 7.5)resources/lang/en/booking.php- Add translation keysresources/lang/ar/booking.php- Add translation keys
Component Implementation
<?php
use Livewire\Volt\Component;
new class extends Component {
public function getBookingStatus(): array
{
$user = auth()->user();
$todayBooking = $user->consultations()
->whereDate('scheduled_date', today())
->whereIn('status', ['pending', 'approved'])
->first();
$pendingRequests = $user->consultations()
->where('status', 'pending')
->where('scheduled_date', '>=', today())
->orderBy('scheduled_date')
->get();
$upcomingApproved = $user->consultations()
->where('status', 'approved')
->where('scheduled_date', '>=', today())
->get();
return [
'canBookToday' => is_null($todayBooking),
'todayBooking' => $todayBooking,
'pendingRequests' => $pendingRequests,
'upcomingApproved' => $upcomingApproved,
'bookedDates' => $user->consultations()
->whereIn('status', ['pending', 'approved'])
->where('scheduled_date', '>=', today())
->pluck('scheduled_date')
->map(fn($d) => $d->format('Y-m-d'))
->toArray(),
];
}
public function with(): array
{
return $this->getBookingStatus();
}
}; ?>
<div>
<!-- Template below -->
</div>
Template
<div class="bg-cream rounded-lg p-4">
{{-- Loading State --}}
<div wire:loading class="animate-pulse">
<div class="h-5 bg-charcoal/20 rounded w-3/4"></div>
</div>
<div wire:loading.remove>
@if($canBookToday)
<div class="flex items-center gap-2 text-success">
<flux:icon name="check-circle" class="w-5 h-5" />
<span>{{ __('booking.can_book_today') }}</span>
</div>
@else
<div class="flex items-center gap-2 text-warning">
<flux:icon name="exclamation-circle" class="w-5 h-5" />
<span>{{ __('booking.already_booked_today') }}</span>
</div>
@endif
@if($pendingRequests->isNotEmpty())
<div class="mt-2 text-sm text-charcoal/70">
@if($pendingRequests->count() === 1)
<p>{{ __('booking.pending_for_date', ['date' => $pendingRequests->first()->scheduled_date->format('d/m/Y')]) }}</p>
@else
<p>{{ __('booking.pending_count', ['count' => $pendingRequests->count()]) }}</p>
<ul class="mt-1 list-disc list-inside">
@foreach($pendingRequests->take(3) as $request)
<li>{{ $request->scheduled_date->format('d/m/Y') }}</li>
@endforeach
</ul>
@endif
</div>
@endif
<p class="mt-2 text-sm text-charcoal/70">
{{ __('booking.limit_message') }}
</p>
</div>
</div>
Translation Keys
// 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:
{{-- In booking page --}}
<livewire:availability-calendar :disabled-dates="$bookedDates" />
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 structurecanBookTodayis true when no booking exists for todaycanBookTodayis false when pending booking exists for todaycanBookTodayis false when approved booking exists for today- Cancelled bookings do not affect
canBookToday bookedDatesonly 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 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-3 hours