From 77004b14b0ce753263e199f569aba0ff31dbbd00 Mon Sep 17 00:00:00 2001 From: Jose Selesan Date: Fri, 10 Apr 2026 09:05:10 -0300 Subject: [PATCH] Improved booking panel --- apps/frontend/package.json | 3 + apps/frontend/src/components/ui/calendar.tsx | 64 +++++++++++++++ .../src/components/ui/date-picker.tsx | 79 +++++++++++++++++++ apps/frontend/src/components/ui/popover.tsx | 29 +++++++ apps/frontend/src/features/home/home-page.tsx | 41 ++++++---- 5 files changed, 202 insertions(+), 14 deletions(-) create mode 100644 apps/frontend/src/components/ui/calendar.tsx create mode 100644 apps/frontend/src/components/ui/date-picker.tsx create mode 100644 apps/frontend/src/components/ui/popover.tsx diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 83709d9..dab34ee 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -13,6 +13,7 @@ "@fontsource-variable/geist": "^5.2.8", "@hookform/resolvers": "^5.2.2", "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-slot": "^1.2.4", "@repo/api-contract": "workspace:*", "@supabase/supabase-js": "^2.101.1", @@ -23,10 +24,12 @@ "axios": "^1.14.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "date-fns": "^4.1.0", "input-otp": "^1.4.2", "lucide-react": "^1.7.0", "radix-ui": "^1.4.3", "react": "^19.2.4", + "react-day-picker": "^8.10.0", "react-dom": "^19.2.4", "react-hook-form": "^7.72.0", "shadcn": "^4.1.2", diff --git a/apps/frontend/src/components/ui/calendar.tsx b/apps/frontend/src/components/ui/calendar.tsx new file mode 100644 index 0000000..fc70708 --- /dev/null +++ b/apps/frontend/src/components/ui/calendar.tsx @@ -0,0 +1,64 @@ +import * as React from "react" +import { DayPicker } from "react-day-picker" +import { ChevronLeft, ChevronRight } from "lucide-react" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +export type CalendarProps = React.ComponentProps + +function Calendar({ + className, + classNames, + showOutsideDays = true, + ...props +}: CalendarProps) { + return ( + , + IconRight: () => , + }} + {...props} + /> + ) +} +Calendar.displayName = "Calendar" + +export { Calendar } diff --git a/apps/frontend/src/components/ui/date-picker.tsx b/apps/frontend/src/components/ui/date-picker.tsx new file mode 100644 index 0000000..c218e48 --- /dev/null +++ b/apps/frontend/src/components/ui/date-picker.tsx @@ -0,0 +1,79 @@ +"use client" + +import * as React from "react" +import { format } from "date-fns" +import { es } from "date-fns/locale" +import { Calendar as CalendarIcon } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Calendar } from "@/components/ui/calendar" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" + +interface DatePickerProps { + value?: Date + onChange?: (date: Date | undefined) => void + disabled?: (date: Date) => boolean + className?: string + placeholder?: string + minDate?: Date +} + +export function DatePicker({ + value, + onChange, + disabled, + className, + placeholder = "Selecciona una fecha", + minDate, +}: DatePickerProps) { + const [open, setOpen] = React.useState(false) + + const isDateDisabled = React.useCallback( + (date: Date) => { + if (minDate && date < minDate) return true + if (disabled) return disabled(date) + return false + }, + [minDate, disabled] + ) + + return ( + + + + + + { + onChange?.(date) + setOpen(false) + }} + disabled={isDateDisabled} + initialFocus + /> + + + ) +} diff --git a/apps/frontend/src/components/ui/popover.tsx b/apps/frontend/src/components/ui/popover.tsx new file mode 100644 index 0000000..bbba7e0 --- /dev/null +++ b/apps/frontend/src/components/ui/popover.tsx @@ -0,0 +1,29 @@ +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent } diff --git a/apps/frontend/src/features/home/home-page.tsx b/apps/frontend/src/features/home/home-page.tsx index 31b18c4..d727bd1 100644 --- a/apps/frontend/src/features/home/home-page.tsx +++ b/apps/frontend/src/features/home/home-page.tsx @@ -5,6 +5,7 @@ import { useForm } from 'react-hook-form' import { z } from 'zod' import type { AdminBooking } from '@repo/api-contract' import { Button } from '@/components/ui/button' +import { DatePicker } from '@/components/ui/date-picker' import { Field, FieldError, FieldLabel } from '@/components/ui/field' import { Input } from '@/components/ui/input' import { @@ -42,6 +43,12 @@ function toIsoDateLocal(date: Date) { return `${year}-${month}-${day}` } +function fromIsoDateLocal(dateIso: string): Date | undefined { + const [year, month, day] = dateIso.split('-').map(Number) + if (!year || !month || !day) return undefined + return new Date(year, month - 1, day) +} + function extractMessage(error: unknown, fallback: string) { if (error instanceof ApiClientError) { return error.message || fallback @@ -317,14 +324,20 @@ export function HomePage() {
Mostrar desde - { - setFromDate(event.target.value) + { + 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" />
@@ -435,16 +448,16 @@ export function HomePage() {
Fecha - { - setManualDate(event.target.value) + { + const isoDate = date ? toIsoDateLocal(date) : todayIso + setManualDate(isoDate) setSelectedCourtId('') setSelectedStartTime('') }} + minDate={new Date()} + placeholder="Selecciona una fecha" />