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({
)}
+
+
)
}
@@ -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() {