From c278c78e3d79aa12e30b4a6864cfc5242637d22f Mon Sep 17 00:00:00 2001 From: Jose Selesan Date: Mon, 8 Jun 2026 23:21:35 -0300 Subject: [PATCH] feat(booking): replace filter toggle with dedicated list view for today's bookings --- .../frontend/src/features/booking/booking.tsx | 43 +-- .../src/features/booking/booking.types.ts | 2 +- .../components/booking-day-summary.tsx | 11 +- .../booking/components/booking-list-view.tsx | 249 ++++++++++++++++++ 4 files changed, 282 insertions(+), 23 deletions(-) create mode 100644 apps/frontend/src/features/booking/components/booking-list-view.tsx diff --git a/apps/frontend/src/features/booking/booking.tsx b/apps/frontend/src/features/booking/booking.tsx index 72b7c4b..301dfc6 100644 --- a/apps/frontend/src/features/booking/booking.tsx +++ b/apps/frontend/src/features/booking/booking.tsx @@ -3,10 +3,11 @@ import { authClient } from '@/lib/api-client'; import { useAuth } from '@/lib/auth'; import type { ComplexWithRole } from '@repo/api-contract'; import { useState } from 'react'; -import { BookingProvider } from './booking-provider'; +import { BookingProvider, useBooking } from './booking-provider'; import { BookingCreateDialog } from './components/booking-create-dialog'; import { BookingDaySummary, BookingQuickActions } from './components/booking-day-summary'; import { BookingHeader } from './components/booking-header'; +import { BookingListView } from './components/booking-list-view'; import { BookingMobile } from './components/booking-mobile'; import { BookingTimeline } from './components/booking-timeline'; import { BookingToolbar } from './components/booking-toolbar'; @@ -17,7 +18,6 @@ interface BookingProps { } export function Booking({ complex }: BookingProps) { - const isMobile = useIsMobile(); const { user } = useAuth(); const [sending, setSending] = useState(false); const [emailSent, setEmailSent] = useState(false); @@ -56,21 +56,34 @@ export function Booking({ complex }: BookingProps) { )} - {isMobile ? ( - - ) : ( -
- - - -
- - -
-
- )} + ); } + +function BookingInner() { + const { viewMode } = useBooking(); + const isMobile = useIsMobile(); + + if (viewMode === 'list') { + return ; + } + + if (isMobile) { + return ; + } + + return ( +
+ + + +
+ + +
+
+ ); +} diff --git a/apps/frontend/src/features/booking/booking.types.ts b/apps/frontend/src/features/booking/booking.types.ts index 9942861..775dca3 100644 --- a/apps/frontend/src/features/booking/booking.types.ts +++ b/apps/frontend/src/features/booking/booking.types.ts @@ -1,6 +1,6 @@ import type { AdminBooking, Court } from '@repo/api-contract'; -export type BookingViewMode = 'panel' | 'status'; +export type BookingViewMode = 'panel' | 'status' | 'list'; export type BookingStatusFilter = 'all' | 'free' | 'reserved'; export type BookingSegmentStatus = 'free' | 'reserved'; diff --git a/apps/frontend/src/features/booking/components/booking-day-summary.tsx b/apps/frontend/src/features/booking/components/booking-day-summary.tsx index b153a6e..31a07c6 100644 --- a/apps/frontend/src/features/booking/components/booking-day-summary.tsx +++ b/apps/frontend/src/features/booking/components/booking-day-summary.tsx @@ -66,10 +66,8 @@ function SummaryCard({ label, value, detail, icon: Icon, className }: SummaryCar } export function BookingQuickActions() { - const { selectedDate, selectedStatus, setSelectedDate, setSelectedStatus, exportDayReport } = - useBooking(); + const { setSelectedDate, setViewMode, exportDayReport } = useBooking(); const todayIso = toIsoDateLocal(new Date()); - const isViewingTodayReservations = selectedDate === todayIso && selectedStatus === 'reserved'; return (
@@ -79,13 +77,12 @@ export function BookingQuickActions() { type="button" onClick={() => { setSelectedDate(todayIso); - setSelectedStatus(isViewingTodayReservations ? 'all' : 'reserved'); + setViewMode('list'); }} - aria-pressed={isViewingTodayReservations} - className="flex h-10 items-center gap-3 rounded-md border bg-background/45 px-3 text-left text-sm transition-colors hover:bg-muted aria-pressed:border-reserved/50 aria-pressed:bg-reserved/10" + className="flex h-10 items-center gap-3 rounded-md border bg-background/45 px-3 text-left text-sm transition-colors hover:bg-muted" > - {isViewingTodayReservations ? 'Ver todos los estados' : 'Ver reservas de hoy'} + Ver reservas de hoy +
+

Reservas del día

+

+ {formatBookingDate(selectedDate, { year: 'numeric' })} +

+
+ + +
+ + + {reservedSegments.length} {reservedSegments.length === 1 ? 'reserva' : 'reservas'} + +
+ + + {isLoading && ( +
Cargando reservas...
+ )} + + {isError && !isLoading && ( +
{errorMessage}
+ )} + + {!isLoading && !isError && reservedSegments.length === 0 && ( +
+ +

No hay reservas para este día.

+
+ )} + + {!isLoading && !isError && reservedSegments.length > 0 && ( +
+ {/* Desktop table */} + + + + + + + + + + + + {reservedSegments.map(({ schedule, segment }) => ( + openBookingTools(segment)} + /> + ))} + +
HorarioCanchaClienteTeléfonoEstado
+ + {/* Mobile cards */} +
+ {reservedSegments.map(({ schedule, segment }) => ( + + ))} +
+
+ )} +
+ ); +} + +interface BookingRowProps { + segment: BookingTimelineSegment; + courtName: string; + sportName: string; + onClick: () => void; +} + +function BookingRow({ segment, courtName, sportName, onClick }: BookingRowProps) { + const booking = segment.booking; + + return ( + { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onClick(); + } + }} + tabIndex={0} + role="button" + > + +
+ + + {segment.startTime} - {segment.endTime} + +
+ + +
+ +
+

{courtName}

+

{sportName}

+
+
+ + +
+ + {booking?.customerName ?? 'Sin información'} +
+ + +
+ + {booking?.customerPhone ?? '-'} +
+ + + + + + ); +} + +function StatusBadge({ status }: { status: string }) { + const config = statusConfig[status] ?? { + label: status, + className: 'bg-slate-500/15 text-slate-600 dark:text-slate-400 border-slate-500/30', + }; + const Icon = statusIconConfig[status] ?? ShieldCheck; + + return ( + + + {config.label} + + ); +}