Better court configuration

This commit is contained in:
Jose Selesan
2026-04-09 09:38:21 -03:00
parent 8a01606c80
commit 30ac0d569a
4 changed files with 350 additions and 44 deletions

View File

@@ -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<string | null>(null)
const [isWeekModalOpen, setIsWeekModalOpen] = useState(false)
const [weekDefaultStartTime, setWeekDefaultStartTime] = useState('08:00')
const [weekDefaultEndTime, setWeekDefaultEndTime] = useState('22:00')
const [weekModalError, setWeekModalError] = useState<string | null>(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({
)}
</div>
</form>
<Dialog
open={isWeekModalOpen}
onOpenChange={(nextOpen) => {
setIsWeekModalOpen(nextOpen)
if (!nextOpen) {
setWeekModalError(null)
}
}}
>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>Horario por defecto</DialogTitle>
<DialogDescription>
Ingresa el horario para completar los días faltantes de la semana.
</DialogDescription>
</DialogHeader>
<div className="grid gap-3 sm:grid-cols-2">
<Field>
<FieldLabel htmlFor="week-start-time">Desde</FieldLabel>
<Input
id="week-start-time"
type="time"
value={weekDefaultStartTime}
onChange={(event) => {
setWeekDefaultStartTime(event.target.value)
setWeekModalError(null)
}}
/>
</Field>
<Field>
<FieldLabel htmlFor="week-end-time">Hasta</FieldLabel>
<Input
id="week-end-time"
type="time"
value={weekDefaultEndTime}
onChange={(event) => {
setWeekDefaultEndTime(event.target.value)
setWeekModalError(null)
}}
/>
</Field>
</div>
{weekModalError && <p className="text-sm text-destructive">{weekModalError}</p>}
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => {
setIsWeekModalOpen(false)
setWeekModalError(null)
}}
>
Cancelar
</Button>
<Button
type="button"
onClick={() => {
if (weekDefaultStartTime >= weekDefaultEndTime) {
setWeekModalError(
'El horario de inicio debe ser menor al horario de fin.',
)
return
}
const availability = form.getValues('availability')
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: weekDefaultStartTime,
endTime: weekDefaultEndTime,
}))
if (missingDays.length > 0) {
availabilityFieldArray.append(missingDays)
}
setIsWeekModalOpen(false)
setWeekModalError(null)
}}
>
Agregar días faltantes
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</section>
)
}
@@ -452,6 +553,7 @@ type ComplexCourtsPageProps = {
export function ComplexCourtsPage({ complexSlug }: ComplexCourtsPageProps) {
const [editingCourt, setEditingCourt] = useState<Court | null>(null)
const [initialDraft, setInitialDraft] = useState<CreateCourtInput | null>(null)
useEffect(() => {
setCurrentComplexSlug(complexSlug)
@@ -484,6 +586,7 @@ export function ComplexCourtsPage({ complexSlug }: ComplexCourtsPageProps) {
<CourtFormSection
complexId={complexId}
editingCourt={editingCourt}
initialDraft={initialDraft}
onCancelEdit={() => {
setEditingCourt(null)
}}
@@ -519,16 +622,42 @@ export function ComplexCourtsPage({ complexSlug }: ComplexCourtsPageProps) {
{formatCurrency(court.basePrice)}
</p>
</div>
<Button
variant="outline"
className="w-full sm:w-auto"
onClick={() => {
setEditingCourt(court)
window.scrollTo({ top: 0, behavior: 'smooth' })
}}
>
Editar
</Button>
<div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row">
<Button
type="button"
variant="outline"
className="w-full sm:w-auto"
onClick={() => {
setEditingCourt(null)
setInitialDraft({
name: `${court.name} - Copy`,
sportId: court.sportId,
slotDurationMinutes: court.slotDurationMinutes,
basePrice: court.basePrice,
availability: court.availability.map((slot) => ({
dayOfWeek: slot.dayOfWeek,
startTime: slot.startTime,
endTime: slot.endTime,
})),
})
window.scrollTo({ top: 0, behavior: 'smooth' })
}}
>
Duplicar cancha
</Button>
<Button
type="button"
variant="outline"
className="w-full sm:w-auto"
onClick={() => {
setInitialDraft(null)
setEditingCourt(court)
window.scrollTo({ top: 0, behavior: 'smooth' })
}}
>
Editar
</Button>
</div>
</div>
<div className="mt-3 flex flex-wrap gap-2 text-xs text-muted-foreground">