import PlayzerIcon from '@/assets/playzer-favicon-512-transparent.png'; import { Button } from '@/components/ui/button'; import { DatePicker } from '@/components/ui/date-picker'; import { Input } from '@/components/ui/input'; import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; import { UserAvatar } from '@/components/user-avatar'; import { apiClient } from '@/lib/api-client'; import { useAuth } from '@/lib/auth'; import { useCurrentComplexStore } from '@/lib/stores/current-complex-store'; import { useTheme } from '@/lib/theme'; import { cn } from '@/lib/utils'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useNavigate } from '@tanstack/react-router'; import { CalendarDays, Check, ChevronLeft, ChevronRight, CircleDot, Dumbbell, Grid2X2, Laptop, ListChecks, LogOut, Moon, MoreHorizontal, Plus, Search, Settings, Sun, User, Users, Wrench, } from 'lucide-react'; import type React from 'react'; import { useMemo, useState } from 'react'; import { useBooking } from '../booking-provider'; import type { BookingCourtSchedule, BookingTimelineSegment } from '../booking.types'; import { fromIsoDateLocal, toIsoDateLocal } from '../lib/booking-time'; export function BookingMobile() { const { schedules, isLoading, isError, errorMessage } = useBooking(); const [selectedCourtId, setSelectedCourtId] = useState(null); const [search, setSearch] = useState(''); const selectedSchedule = useMemo(() => { if (!selectedCourtId) return null; return schedules.find((schedule) => schedule.court.id === selectedCourtId) ?? null; }, [schedules, selectedCourtId]); const filteredSchedules = useMemo(() => { const query = search.trim().toLowerCase(); if (!query) return schedules; return schedules.filter((schedule) => { return ( schedule.court.name.toLowerCase().includes(query) || schedule.court.sport.name.toLowerCase().includes(query) ); }); }, [schedules, search]); if (selectedSchedule) { return setSelectedCourtId(null)} />; } return (

Canchas

setSearch(event.target.value)} />
{isLoading && (
Cargando reservas...
)} {isError && !isLoading && (
{errorMessage}
)} {!isLoading && !isError && filteredSchedules.length === 0 && (
No hay canchas para los filtros seleccionados.
)} {!isLoading && !isError && filteredSchedules.map((schedule) => ( setSelectedCourtId(schedule.court.id)} /> ))}
); } function MobileTopBar() { const navigate = useNavigate(); const queryClient = useQueryClient(); const { user, isAuthenticated, displayName, avatarUrl, signOut } = useAuth(); const { currentComplexSlug, setCurrentComplex } = useCurrentComplexStore(); const { theme, setTheme } = useTheme(); const { openCreateBooking } = useBooking(); const [open, setOpen] = useState(false); const myComplexesQuery = useQuery({ queryKey: ['my-complexes'], enabled: isAuthenticated, queryFn: () => apiClient.complexes.listMine(), }); const complexSlug = currentComplexSlug || myComplexesQuery.data?.[0]?.complexSlug; const currentComplexName = useMemo(() => { const complexes = myComplexesQuery.data ?? []; if (complexes.length === 0) return 'Mi complejo'; const selected = complexes.find((complex) => complex.complexSlug === complexSlug); return selected?.complexName ?? complexes[0]?.complexName ?? 'Mi complejo'; }, [complexSlug, myComplexesQuery.data]); const handleSelectComplex = async (complexId: string, nextSlug: string) => { await apiClient.complexes.select({ complexId }); setCurrentComplex(nextSlug); await queryClient.invalidateQueries({ queryKey: ['current-complex'] }); await queryClient.invalidateQueries({ queryKey: ['admin-bookings'] }); }; const handleSignOut = async () => { setOpen(false); await signOut(); await navigate({ to: '/login' }); }; return (
Playzer Playzer

{displayName}

{user?.email}

{currentComplexName}

Cuenta

); } function MobilePanelHeader() { const { complex, openCreateBooking } = useBooking(); return (

Panel de Reservas

Gestiona las canchas de {complex.complexName}

); } function MobileFilters() { const { selectedDate, setSelectedDate } = useBooking(); return (
setSelectedDate(date ? toIsoDateLocal(date) : toIsoDateLocal(new Date())) } className="h-12 w-full justify-center rounded-lg border-border/70 bg-card/75 px-4 text-sm font-medium" placeholder="Fecha" />
); } function MobileSummary() { const { summary } = useBooking(); return (

Resumen del día

); } function SummaryPill({ value, label, className, }: { value: number; label: string; className: string; }) { return (

{value}

{label}

); } function MobileCourtCard({ schedule, onOpen, }: { schedule: BookingCourtSchedule; onOpen: () => void; }) { const nextSegments = schedule.segments.slice(0, 5); return (

{schedule.court.name}

{schedule.court.sport.name}

Próximos horarios

{nextSegments.map((segment) => ( ))}
); } function MobileCourtDay({ schedule, onBack, }: { schedule: BookingCourtSchedule; onBack: () => void }) { const { selectedDate, moveSelectedDate, setSelectedDate, openCreateBooking } = useBooking(); return (

{schedule.court.name}

{schedule.court.sport.name}

setSelectedDate(date ? toIsoDateLocal(date) : toIsoDateLocal(new Date())) } className="h-12 rounded-lg border-border/70 bg-card/75 text-sm" />
{schedule.segments.map((segment) => ( ))}
); } function MobileSegmentRow({ schedule, segment, }: { schedule: BookingCourtSchedule; segment: BookingTimelineSegment; }) { const { openCreateBooking, openBookingTools } = useBooking(); const isFree = segment.status === 'free'; return ( ); } function SegmentStatusLine({ segment }: { segment: BookingTimelineSegment }) { const status = segment.status; const label = status === 'free' ? 'Libre' : status === 'reserved' ? 'Reservado' : 'Mantenimiento'; return (

{label}

); } function StatusLegend() { return (
); } function LegendItem({ label, className }: { label: string; className: string }) { return ( {label} ); } function MiniSlot({ schedule, segment, }: { schedule: BookingCourtSchedule; segment: BookingTimelineSegment; }) { const { openCreateBooking, openBookingTools } = useBooking(); const label = segment.status === 'free' ? 'Libre' : segment.status === 'reserved' ? 'Reservado' : 'Manten.'; const isActionable = segment.status === 'free' || Boolean(segment.booking); return ( ); } function SportIcon({ sportName }: { sportName: string }) { const isPadel = sportName.toLowerCase().includes('padel'); return (
{isPadel ? : }
); } function MetricDots({ schedule }: { schedule: BookingCourtSchedule }) { return (
{schedule.metrics.free} {schedule.metrics.reserved} {schedule.metrics.maintenance}
); } function MobileBottomNav({ active }: { active: 'panel' | 'status' | 'bookings' | 'more' }) { return ( ); } function BottomNavItem({ active, icon, label, }: { active: boolean; icon: React.ReactNode; label: string }) { return ( ); }