feat: implement booking cancellation feature in HomePage
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "court_bookings" ADD COLUMN "price" DECIMAL(19,2);
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -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')}`}
|
||||||
|
|||||||
Reference in New Issue
Block a user