import type { Metadata } from "next"; import { notFound, redirect } from "next/navigation"; import { auth } from "@/auth"; import { Card } from "@/components/ui/card"; import { TopNav } from "@/components/navigation/top-nav"; import { prisma } from "@/lib/db/prisma"; import { hasPermission } from "@/lib/auth/permissions"; import { Permission } from "@/generated/prisma/enums"; import { getAppointmentById } from "@/services/scheduling/appointment.service"; import { assertAppointmentInBusiness, StaffPermissionDeniedError } from "@/features/staff/authorization"; import { ownerRescheduleAppointmentAction } from "@/features/owner/actions/appointments"; import { BarberDateTimePicker } from "@/features/booking/components/barber-date-time-picker"; export const metadata: Metadata = { title: "Reschedule / Reassign" }; export const dynamic = "force-dynamic"; const NON_RESCHEDULABLE = new Set(["COMPLETED", "CANCELLED", "NO_SHOW", "IN_PROGRESS"]); /** * Also the owner's appointment-reassignment surface — BarberDateTimePicker * already lets the caller choose a different eligible barber as part of * picking a new slot, so "reassign to another barber" and "reschedule to a * new time" are the same underlying operation here, not two separate * flows. See docs/phase-6-summary.md, "Appointment reassignment approach". */ export default async function OwnerRescheduleAppointmentPage({ params, }: { params: Promise<{ id: string }>; }) { const session = await auth(); if (!session?.user) redirect("/sign-in"); const canManage = await hasPermission(session.user.id, Permission.MANAGE_ALL_APPOINTMENTS); if (!canManage) redirect("/staff"); const owner = await prisma.staffProfile.findUnique({ where: { userId: session.user.id }, include: { business: { select: { id: true, timezone: true } } }, }); if (!owner) redirect("/staff"); const { id } = await params; const appointment = await getAppointmentById(prisma, id); if (!appointment) notFound(); try { assertAppointmentInBusiness(appointment, { id: owner.id, businessId: owner.businessId }); } catch (error) { if (error instanceof StaffPermissionDeniedError) notFound(); throw error; } if (NON_RESCHEDULABLE.has(appointment.status)) { redirect(`/owner/appointments/${id}`); } async function handleContinue(selection: { staffId?: string; date: string; startAt: string }) { "use server"; const result = await ownerRescheduleAppointmentAction({ appointmentId: id, staffId: selection.staffId, startAt: selection.startAt, }); if (result.success) { redirect(`/owner/appointments/${id}`); } return result; } return (
{appointment.services.map((s) => s.service.name).join(" + ")} for {appointment.customer.firstName}{" "} {appointment.customer.lastName}
Currently with {appointment.staff.user.name} —{" "} {appointment.startAt.toLocaleDateString("en-GB", { weekday: "short", day: "numeric", month: "short", timeZone: owner.business.timezone, })}{" "} ·{" "} {appointment.startAt.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", timeZone: owner.business.timezone, })}