libra/docs/stories/story-7.6-booking-limit-ind...

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 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 (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

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 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 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