-
+
-
- Nombre
-
-
-
+
+
+
+
+
+
+
+
+ );
-
- Teléfono
-
-
-
+ const urlCard = (
+
+
+
+
+
+
+
+ );
- {createManualBookingMutation.isError && (
-
- {extractMessage(
- createManualBookingMutation.error,
- 'No pudimos crear la reserva manual.'
- )}
-
- )}
-
+ const headerPills = (
+
+
+ {dashboardStats.today} hoy
+
+
+ {dashboardStats.confirmed} confirmadas
+
+
+ {dashboardStats.completed} cumplidas
+
+
+ );
-
-
-
-
-
-
-
-
+ const dateSelector = (
+
+
+ Desde
+ {
+ const isoDate = date ? toIsoDateLocal(date) : todayIso;
+ setFromDate(isoDate);
+ }}
+ disabled={(date) => {
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ const checkDate = new Date(date);
+ checkDate.setHours(0, 0, 0, 0);
+ return checkDate < today;
+ }}
+ placeholder="Selecciona"
+ />
+
+
+ );
+
+ return (
+
+
+
+
+
+
+
+ {selectedComplex.complexSlug}
+
+
+ Panel de reservas
+
+
+ Gestioná turnos de {selectedComplex.complexName}
+
+
+ {headerPills}
-
-
-
- Mostrar desde
- {
- const isoDate = date ? toIsoDateLocal(date) : todayIso;
- setFromDate(isoDate);
- }}
- disabled={(date) => {
- const today = new Date();
- today.setHours(0, 0, 0, 0);
- const checkDate = new Date(date);
- checkDate.setHours(0, 0, 0, 0);
- return checkDate < today;
- }}
- placeholder="Selecciona una fecha"
- />
-
-
+
+ {urlCard}
+ {manualBookingDialog}
+
+
- {bookingsQuery.isLoading && (
-
Cargando reservas...
- )}
+
+ {dateSelector}
- {bookingsQuery.isError && (
-
- {extractMessage(bookingsQuery.error, 'No pudimos cargar las reservas.')}
-
- )}
+ {bookingsQuery.isLoading && (
+ Cargando reservas...
+ )}
- {!bookingsQuery.isLoading && !bookingsQuery.isError && groupedBookings.length === 0 && (
-
- No hay reservas para la fecha seleccionada en adelante.
-
- )}
+ {bookingsQuery.isError && (
+
+ {extractMessage(bookingsQuery.error, 'No pudimos cargar las reservas.')}
+
+ )}
-
- {groupedBookings.map(([date, bookings]) => (
-
-
-
- {formatDateLabel(date, todayIso)}
-
-
- {bookings.length} reservas
-
-
-
- {bookings.map((booking) => {
+ {!bookingsQuery.isLoading && !bookingsQuery.isError && groupedBookings.length === 0 && (
+
+ No hay reservas para la fecha seleccionada.
+
+ )}
+
+
+ {groupedBookings.map(([date, bookings]) => (
+
+
+
+ {formatDateLabel(date, todayIso)}
+
+
+
+ {bookings.length}
+
+
+
+
+ {bookings
+ .slice()
+ .sort((a, b) => a.startTime.localeCompare(b.startTime))
+ .map((booking) => {
const effectiveStatus = getEffectiveStatus(booking);
const canManage =
booking.status === 'CONFIRMED' && effectiveStatus === 'CONFIRMED';
const isMutating = updateStatusMutation.isPending;
+ const now = new Date();
+ const bookingStart = new Date(`${date}T${booking.startTime}:00`);
+ const bookingEnd = new Date(`${date}T${booking.endTime}:00`);
+ const isPast = bookingEnd < now;
+ const isNow = date === todayIso && bookingStart <= now && bookingEnd > now;
+
return (
-
-
-
- {booking.startTime} - {booking.endTime} · {booking.courtName}
-
-
+
+
+
+ {booking.startTime}
+
+
+ {booking.endTime}
+
+
+
+
+
+
{booking.courtName}
+
+ {booking.customerName}{' '}
+
+ · {booking.customerPhone}
+
+
+
-
- {booking.customerName} ({booking.customerPhone}) · Código{' '}
- {booking.bookingCode}
-
-
- {canManage && (
- <>
-
-
- >
- )}
+
+
+
+ {canManage && !isMutating && (
+ <>
+
+
+ >
+ )}
+
);
})}
-
-
- ))}
-
+
+
+ ))}
+
- {updateStatusMutation.isError && (
-
- {extractMessage(updateStatusMutation.error, 'No pudimos actualizar la reserva.')}
-
- )}
-
+ {updateStatusMutation.isError && (
+
+ {extractMessage(updateStatusMutation.error, 'No pudimos actualizar la reserva.')}
+
+ )}
-
-
{bookingToCancel && (
diff --git a/apps/frontend/src/features/public-booking/public-booking-page.tsx b/apps/frontend/src/features/public-booking/public-booking-page.tsx
index 53830cb..9f35935 100644
--- a/apps/frontend/src/features/public-booking/public-booking-page.tsx
+++ b/apps/frontend/src/features/public-booking/public-booking-page.tsx
@@ -1,3 +1,4 @@
+import AvailabilityHeatMap from '@/components/ui/availability-heatmap';
import { Button } from '@/components/ui/button';
import { Field, FieldError, FieldLabel } from '@/components/ui/field';
import { Input } from '@/components/ui/input';
@@ -645,60 +646,11 @@ export function PublicBookingPage({ complexSlug }: PublicBookingPageProps) {
)}
-
- {visibleCourts.map((court) => (
-
-
-
-
- {court.courtName}
-
-
- {court.sport.name} · Turnos de {court.slotDurationMinutes} min
-
-
-
- {court.availableSlots.length} horarios
-
-
-
-
- {court.availableSlots.map((slot) => {
- const isSelected =
- selectedSlot?.courtId === court.courtId &&
- selectedSlot.startTime === slot.startTime;
-
- return (
-
- );
- })}
-
-
- ))}
-
+