import type { Metadata } from "next"; import { CalendarPlus, Mail, Phone } from "lucide-react"; import Link from "next/link"; import { notFound, redirect } from "next/navigation"; import { Card } from "@/components/ui/card"; import { StatusPill } from "@/components/ui/status-pill"; import { TopNav } from "@/components/navigation/top-nav"; import { prisma } from "@/lib/db/prisma"; import { getActingStaff } from "@/lib/staff/get-acting-staff"; import { getLoyaltySummary } from "@/services/loyalty/loyalty.service"; import { getAppointmentById } from "@/services/scheduling/appointment.service"; import { assertOwnAppointment, StaffPermissionDeniedError } from "@/features/staff/authorization"; import { AppointmentStatusActions } from "@/features/staff/components/appointment-status-actions"; import { AddStampButton } from "@/features/staff/components/add-stamp-button"; import { BookFollowUpButton } from "@/features/staff/components/book-follow-up-button"; import { RedeemRewardButton } from "@/features/owner/components/redeem-reward-button"; import type { AppointmentStatus } from "@/types/domain"; export const metadata: Metadata = { title: "Appointment Details" }; export const dynamic = "force-dynamic"; const STATUS_MAP: Record = { PENDING: "pending_approval", CONFIRMED: "confirmed", CHECKED_IN: "arrived", IN_PROGRESS: "in_progress", COMPLETED: "completed", CANCELLED: "cancelled", NO_SHOW: "no_show", }; const RESCHEDULABLE = new Set(["PENDING", "CONFIRMED", "CHECKED_IN"]); export default async function StaffAppointmentDetailsPage({ params, }: { params: Promise<{ id: string }>; }) { const staff = await getActingStaff(); if (!staff) redirect("/home"); const { id } = await params; const appointment = await getAppointmentById(prisma, id); if (!appointment) notFound(); try { assertOwnAppointment(appointment, staff); } catch (error) { if (error instanceof StaffPermissionDeniedError) notFound(); throw error; } const [notes, loyaltyTransactions, loyalty, cancelledByUser] = await Promise.all([ prisma.customerNote.findMany({ where: { customerId: appointment.customerId }, orderBy: { createdAt: "desc" }, take: 5, include: { author: { include: { user: { select: { name: true } } } } }, }), prisma.loyaltyTransaction.findMany({ where: { appointmentId: appointment.id }, orderBy: { createdAt: "desc" }, }), getLoyaltySummary(prisma, { customerId: appointment.customerId, businessId: staff.businessId }), appointment.cancelledByUserId ? prisma.user.findUnique({ where: { id: appointment.cancelledByUserId }, select: { name: true }, }) : null, ]); const tz = staff.business.timezone; const totalDuration = appointment.services.reduce( (sum, s) => sum + s.durationAtBookingMinutes, 0, ); const totalPrice = appointment.services.reduce((sum, s) => sum + Number(s.priceAtBooking), 0); const discount = appointment.discountAmount ? Number(appointment.discountAmount) : 0; const canRedeemReward = loyalty?.availableReward && appointment.discountAmount === null && appointment.status !== "CANCELLED"; return (
Ref: {appointment.id.slice(-8)}
{appointment.customer.firstName} {appointment.customer.lastName}

{appointment.customer.isGuest ? "Guest customer" : "Registered customer"}

{appointment.customer.phone && ( )}
Services

{appointment.services.map((s) => s.service.name).join(" + ")}

Date

{appointment.startAt.toLocaleDateString("en-GB", { weekday: "short", day: "numeric", month: "short", timeZone: tz, })}

Time

{appointment.startAt.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", timeZone: tz, })}{" "} –{" "} {appointment.endAt.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", timeZone: tz, })}

Duration / Price

{totalDuration} min · £{totalPrice.toFixed(2)} {discount > 0 && ( (-£{discount.toFixed(2)} reward) )}

{appointment.notes && (
Booking Notes

{appointment.notes}

)} {notes.length > 0 && (
Customer Notes
{notes.map((n) => (

{n.author.user.name ?? "Staff"}: {n.body}

))}
)} {loyalty?.isEnabled && (
Loyalty

{loyalty.currentStamps}/{loyalty.stampsRequired} stamps {loyalty.availableReward && ( · Reward available )}

{loyaltyTransactions.length > 0 && (
    {loyaltyTransactions.map((t) => (
  • {t.amount > 0 ? "+" : ""} {t.amount} stamp{Math.abs(t.amount) === 1 ? "" : "s"} —{" "} {t.type.replace("_", " ").toLowerCase()} {t.reason ? ` (${t.reason})` : ""}
  • ))}
)}
)} {appointment.status === "CANCELLED" && (
Cancellation

Cancelled {appointment.cancelledAt?.toLocaleString("en-GB", { timeZone: tz })} {cancelledByUser ? ` by ${cancelledByUser.name}` : ""}

{appointment.cancellationReason && (

“{appointment.cancellationReason}”

)}
)}
{RESCHEDULABLE.has(appointment.status) && ( Reschedule )} {appointment.status === "COMPLETED" && ( )}
{loyalty?.isEnabled && ( )} {canRedeemReward && loyalty?.availableReward && ( )}
); }