From 30ac0d569ad7d0aaa7e3145378c0ba0db66aa1bb Mon Sep 17 00:00:00 2001 From: Jose Selesan Date: Thu, 9 Apr 2026 09:38:21 -0300 Subject: [PATCH] Better court configuration --- apps/frontend/index.html | 2 +- apps/frontend/src/components/ui/dialog.tsx | 166 ++++++++++++++ .../features/complex/complex-courts-page.tsx | 211 ++++++++++++++---- .../src/features/layout/root-layout.tsx | 15 +- 4 files changed, 350 insertions(+), 44 deletions(-) create mode 100644 apps/frontend/src/components/ui/dialog.tsx diff --git a/apps/frontend/index.html b/apps/frontend/index.html index 0fca6f0..3df409b 100644 --- a/apps/frontend/index.html +++ b/apps/frontend/index.html @@ -4,7 +4,7 @@ - frontend + PlayZer
diff --git a/apps/frontend/src/components/ui/dialog.tsx b/apps/frontend/src/components/ui/dialog.tsx new file mode 100644 index 0000000..527af74 --- /dev/null +++ b/apps/frontend/src/components/ui/dialog.tsx @@ -0,0 +1,166 @@ +import * as React from "react" +import { Dialog as DialogPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { XIcon } from "lucide-react" + +function Dialog({ + ...props +}: React.ComponentProps) { + return +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + + + )} + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ + className, + showCloseButton = false, + children, + ...props +}: React.ComponentProps<"div"> & { + showCloseButton?: boolean +}) { + return ( +
+ {children} + {showCloseButton && ( + + + + )} +
+ ) +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/apps/frontend/src/features/complex/complex-courts-page.tsx b/apps/frontend/src/features/complex/complex-courts-page.tsx index 70d696f..9bfad5b 100644 --- a/apps/frontend/src/features/complex/complex-courts-page.tsx +++ b/apps/frontend/src/features/complex/complex-courts-page.tsx @@ -9,6 +9,14 @@ import { } from '@repo/api-contract' import { Controller, useFieldArray, useForm } from 'react-hook-form' import { Button } from '@/components/ui/button' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' import { Field, FieldError, FieldLabel } from '@/components/ui/field' import { Input } from '@/components/ui/input' import { @@ -134,16 +142,22 @@ function createDefaultValues(): CreateCourtInput { type CourtFormSectionProps = { complexId: string | null editingCourt: Court | null + initialDraft: CreateCourtInput | null onCancelEdit: () => void } function CourtFormSection({ complexId, editingCourt, + initialDraft, onCancelEdit, }: CourtFormSectionProps) { const queryClient = useQueryClient() const [formError, setFormError] = useState(null) + const [isWeekModalOpen, setIsWeekModalOpen] = useState(false) + const [weekDefaultStartTime, setWeekDefaultStartTime] = useState('08:00') + const [weekDefaultEndTime, setWeekDefaultEndTime] = useState('22:00') + const [weekModalError, setWeekModalError] = useState(null) const sportsQuery = useQuery({ queryKey: ['sports'], @@ -201,23 +215,28 @@ function CourtFormSection({ } useEffect(() => { - if (!editingCourt) { - form.reset(createDefaultValues()) + if (editingCourt) { + form.reset({ + name: editingCourt.name, + sportId: editingCourt.sportId, + slotDurationMinutes: editingCourt.slotDurationMinutes, + basePrice: editingCourt.basePrice, + availability: editingCourt.availability.map((slot) => ({ + dayOfWeek: slot.dayOfWeek, + startTime: slot.startTime, + endTime: slot.endTime, + })), + }) return } - form.reset({ - name: editingCourt.name, - sportId: editingCourt.sportId, - slotDurationMinutes: editingCourt.slotDurationMinutes, - basePrice: editingCourt.basePrice, - availability: editingCourt.availability.map((slot) => ({ - dayOfWeek: slot.dayOfWeek, - startTime: slot.startTime, - endTime: slot.endTime, - })), - }) - }, [editingCourt, form]) + if (initialDraft) { + form.reset(initialDraft) + return + } + + form.reset(createDefaultValues()) + }, [editingCourt, form, initialDraft]) const title = editingCourt ? 'Editar cancha' : 'Nueva cancha' const buttonText = editingCourt ? 'Guardar cambios' : 'Agregar cancha' @@ -317,23 +336,10 @@ function CourtFormSection({ className="w-full sm:w-auto" onClick={() => { const availability = form.getValues('availability') - const baseStartTime = availability[0]?.startTime ?? '08:00' - const baseEndTime = availability[0]?.endTime ?? '22:00' - const existingDays = new Set( - availability.map((item) => item.dayOfWeek), - ) - - const missingDays = DAY_OPTIONS.filter( - (option) => !existingDays.has(option.value), - ).map((option) => ({ - dayOfWeek: option.value, - startTime: baseStartTime, - endTime: baseEndTime, - })) - - if (missingDays.length > 0) { - availabilityFieldArray.append(missingDays) - } + setWeekDefaultStartTime(availability[0]?.startTime ?? '08:00') + setWeekDefaultEndTime(availability[0]?.endTime ?? '22:00') + setWeekModalError(null) + setIsWeekModalOpen(true) }} > Agregar semana completa @@ -442,6 +448,101 @@ function CourtFormSection({ )}
+ + { + setIsWeekModalOpen(nextOpen) + if (!nextOpen) { + setWeekModalError(null) + } + }} + > + + + Horario por defecto + + Ingresa el horario para completar los días faltantes de la semana. + + + +
+ + Desde + { + setWeekDefaultStartTime(event.target.value) + setWeekModalError(null) + }} + /> + + + + Hasta + { + setWeekDefaultEndTime(event.target.value) + setWeekModalError(null) + }} + /> + +
+ + {weekModalError &&

{weekModalError}

} + + + + + +
+
) } @@ -452,6 +553,7 @@ type ComplexCourtsPageProps = { export function ComplexCourtsPage({ complexSlug }: ComplexCourtsPageProps) { const [editingCourt, setEditingCourt] = useState(null) + const [initialDraft, setInitialDraft] = useState(null) useEffect(() => { setCurrentComplexSlug(complexSlug) @@ -484,6 +586,7 @@ export function ComplexCourtsPage({ complexSlug }: ComplexCourtsPageProps) { { setEditingCourt(null) }} @@ -519,16 +622,42 @@ export function ComplexCourtsPage({ complexSlug }: ComplexCourtsPageProps) { {formatCurrency(court.basePrice)}

- +
+ + +
diff --git a/apps/frontend/src/features/layout/root-layout.tsx b/apps/frontend/src/features/layout/root-layout.tsx index f20c4b6..c9729a6 100644 --- a/apps/frontend/src/features/layout/root-layout.tsx +++ b/apps/frontend/src/features/layout/root-layout.tsx @@ -1,5 +1,5 @@ import { Link, Outlet, useNavigate } from '@tanstack/react-router' -import { useEffect, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { useQuery } from '@tanstack/react-query' import { LogOut, Menu, UserRound } from 'lucide-react' import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' @@ -44,6 +44,17 @@ export function RootLayout() { } }, [currentComplexSlug, myComplexesQuery.data]) + const currentComplexName = useMemo(() => { + const complexes = myComplexesQuery.data ?? [] + if (complexes.length === 0) return 'Mi complejo' + + const selected = complexes.find( + (complex) => complex.complexSlug === currentComplexSlug, + ) + + return selected?.complexName ?? complexes[0]?.complexName ?? 'Mi complejo' + }, [currentComplexSlug, myComplexesQuery.data]) + const handleSignOut = async () => { await signOut() await navigate({ to: '/login' }) @@ -53,7 +64,7 @@ export function RootLayout() {
-

TanStack Router

+

{currentComplexName}