feat: implement booking cancellation feature in HomePage

This commit is contained in:
Jose Selesan
2026-04-24 15:44:46 -03:00
parent 82dbc8ad99
commit d6e32f3e84
5 changed files with 59 additions and 7 deletions

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "court_bookings" ADD COLUMN "price" DECIMAL(19,2);

View File

@@ -129,6 +129,7 @@ export function HomePage() {
const [selectedStartTime, setSelectedStartTime] = useState<string>(''); const [selectedStartTime, setSelectedStartTime] = useState<string>('');
const [isManualBookingOpen, setIsManualBookingOpen] = useState(false); const [isManualBookingOpen, setIsManualBookingOpen] = useState(false);
const [urlCopied, setUrlCopied] = useState(false); const [urlCopied, setUrlCopied] = useState(false);
const [bookingToCancel, setBookingToCancel] = useState<AdminBooking | null>(null);
const currentComplexQuery = useQuery({ const currentComplexQuery = useQuery({
queryKey: ['current-complex'], queryKey: ['current-complex'],
@@ -361,6 +362,7 @@ export function HomePage() {
mutationFn: (payload: { bookingId: string; status: 'CANCELLED' | 'COMPLETED' }) => mutationFn: (payload: { bookingId: string; status: 'CANCELLED' | 'COMPLETED' }) =>
apiClient.adminBookings.updateStatus(payload.bookingId, { status: payload.status }), apiClient.adminBookings.updateStatus(payload.bookingId, { status: payload.status }),
onSuccess: () => { onSuccess: () => {
setBookingToCancel(null);
void queryClient.invalidateQueries({ queryKey: ['admin-bookings', selectedComplex?.id] }); void queryClient.invalidateQueries({ queryKey: ['admin-bookings', selectedComplex?.id] });
void queryClient.invalidateQueries({ void queryClient.invalidateQueries({
queryKey: ['manual-booking-availability', selectedComplex?.complexSlug], queryKey: ['manual-booking-availability', selectedComplex?.complexSlug],
@@ -368,6 +370,18 @@ export function HomePage() {
}, },
}); });
function handleCancelBooking(booking: AdminBooking) {
setBookingToCancel(booking);
}
function confirmCancelBooking() {
if (!bookingToCancel) return;
updateStatusMutation.mutate({
bookingId: bookingToCancel.id,
status: 'CANCELLED',
});
}
const { const {
register, register,
handleSubmit, handleSubmit,
@@ -817,10 +831,7 @@ export function HomePage() {
variant="destructive" variant="destructive"
disabled={isMutating} disabled={isMutating}
onClick={() => { onClick={() => {
updateStatusMutation.mutate({ handleCancelBooking(booking);
bookingId: booking.id,
status: 'CANCELLED',
});
}} }}
> >
Cancelar Cancelar
@@ -874,6 +885,46 @@ export function HomePage() {
</section> </section>
</aside> </aside>
</div> </div>
{bookingToCancel && (
<ResponsiveDialog
open={Boolean(bookingToCancel)}
onOpenChange={() => setBookingToCancel(null)}
>
<ResponsiveDialogContent>
<ResponsiveDialogHeader>
<ResponsiveDialogTitle>Cancelar reserva</ResponsiveDialogTitle>
<ResponsiveDialogDescription>
¿Estás seguro de que quieres cancelar esta reserva? Esta acción no se puede
deshacer.
</ResponsiveDialogDescription>
</ResponsiveDialogHeader>
<div className="mt-4 rounded-2xl border border-border/70 bg-background p-4">
<p className="font-medium">
{bookingToCancel.startTime} - {bookingToCancel.endTime} ·{' '}
{bookingToCancel.courtName}
</p>
<p className="mt-1 text-sm text-muted-foreground">
{bookingToCancel.customerName} ({bookingToCancel.customerPhone})
</p>
</div>
<ResponsiveDialogFooter>
<ResponsiveDialogClose asChild>
<Button variant="outline" disabled={updateStatusMutation.isPending}>
Mantener
</Button>
</ResponsiveDialogClose>
<Button
variant="destructive"
onClick={confirmCancelBooking}
disabled={updateStatusMutation.isPending}
>
{updateStatusMutation.isPending ? 'Cancelando...' : 'Sí, cancelar reserva'}
</Button>
</ResponsiveDialogFooter>
</ResponsiveDialogContent>
</ResponsiveDialog>
)}
</main> </main>
); );
} }

View File

@@ -142,7 +142,6 @@ export function RootLayout() {
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<ThemeSwitcher /> <ThemeSwitcher />
{!isAuthenticated && ( {!isAuthenticated && (

View File

@@ -93,7 +93,7 @@ export function PublicBookingConfirmationPage({
{confirmationQuery.data.price > 0 && ( {confirmationQuery.data.price > 0 && (
<> <>
<p className="mt-4 text-sm text-muted-foreground">Precio</p> <p className="mt-4 text-sm text-muted-foreground">Precio</p>
<p className="mt-1 text-base font-medium sm:text-lg"> <p className="mt-1 text-xl font-bold sm:text-2xl">
${confirmationQuery.data.price.toLocaleString('es-AR')} ${confirmationQuery.data.price.toLocaleString('es-AR')}
</p> </p>
</> </>

View File

@@ -737,7 +737,7 @@ export function PublicBookingPage({ complexSlug }: PublicBookingPageProps) {
<p className="text-xs uppercase tracking-[0.24em] text-muted-foreground"> <p className="text-xs uppercase tracking-[0.24em] text-muted-foreground">
Precio Precio
</p> </p>
<p className="mt-1 text-sm font-medium text-foreground"> <p className="mt-1 text-base font-bold text-foreground">
${selectedSlot.price.min.toLocaleString('es-AR')} ${selectedSlot.price.min.toLocaleString('es-AR')}
{selectedSlot.price.min !== selectedSlot.price.max && {selectedSlot.price.min !== selectedSlot.price.max &&
` - $${selectedSlot.price.max.toLocaleString('es-AR')}`} ` - $${selectedSlot.price.max.toLocaleString('es-AR')}`}