diff --git a/src/app/(staff)/staff/appointments/[id]/page.tsx b/src/app/(staff)/staff/appointments/[id]/page.tsx
index ec725ba..812e2b2 100644
--- a/src/app/(staff)/staff/appointments/[id]/page.tsx
+++ b/src/app/(staff)/staff/appointments/[id]/page.tsx
@@ -1,5 +1,5 @@
import type { Metadata } from "next";
-import { CalendarPlus, Mail, Phone } from "lucide-react";
+import { Mail, Phone } from "lucide-react";
import Link from "next/link";
import { notFound, redirect } from "next/navigation";
import { Card } from "@/components/ui/card";
@@ -213,15 +213,20 @@ export default async function StaffAppointmentDetailsPage({
{loyalty?.isEnabled && (
-
- Loyalty
-
-
- {loyalty.currentStamps}/{resolveRewardThreshold(loyalty)} stamps
- {loyalty.availableReward && (
- · Reward available
- )}
-
+
+
+
+ Loyalty
+
+
+ {loyalty.currentStamps}/{resolveRewardThreshold(loyalty)} stamps
+ {loyalty.availableReward && (
+ · Reward available
+ )}
+
+
+
+
{loyaltyTransactions.length > 0 && (
{loyaltyTransactions.map((t) => (
@@ -255,41 +260,29 @@ export default async function StaffAppointmentDetailsPage({
)}
-
-
- Add to Calendar
-
- {RESCHEDULABLE.has(appointment.status) && (
-
- Reschedule
-
- )}
- {appointment.status === "COMPLETED" && (
-
- )}
-
+ {appointment.status === "COMPLETED" && (
+
+ )}
-
+
-
- {loyalty?.isEnabled && (
-
- )}
- {canRedeemReward && loyalty?.availableReward && (
-
- )}
-
+ {canRedeemReward && loyalty?.availableReward && (
+
+ )}
);
diff --git a/src/features/staff/components/add-stamp-button.tsx b/src/features/staff/components/add-stamp-button.tsx
index dd2b88c..8c87b53 100644
--- a/src/features/staff/components/add-stamp-button.tsx
+++ b/src/features/staff/components/add-stamp-button.tsx
@@ -14,11 +14,14 @@ export function AddStampButton({
appointmentId,
className,
size = "sm",
+ iconOnly = false,
}: {
customerId: string;
appointmentId?: string;
className?: string;
size?: "sm" | "md" | "lg";
+ /** Renders just the stamp icon (e.g. opposite a "X/Y stamps" line) instead of icon + label — same action, just compact enough to sit inline with other text. */
+ iconOnly?: boolean;
}) {
const router = useRouter();
const [open, setOpen] = useState(false);
@@ -64,12 +67,13 @@ export function AddStampButton({
<>
{result ? (
diff --git a/src/features/staff/components/appointment-status-actions.tsx b/src/features/staff/components/appointment-status-actions.tsx
index 2c06c2e..0f59bf1 100644
--- a/src/features/staff/components/appointment-status-actions.tsx
+++ b/src/features/staff/components/appointment-status-actions.tsx
@@ -1,5 +1,6 @@
"use client";
+import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState, useTransition } from "react";
import { Button } from "@/components/ui/button";
@@ -55,12 +56,15 @@ export function AppointmentStatusActions({
status,
actions = DEFAULT_ACTIONS,
layout = "wrap",
+ rescheduleUrl,
}: {
appointmentId: string;
status: RawStatus;
actions?: AppointmentActionSet;
- /** "stack" renders one full-width button per row (Current Appointment); "wrap" (default, used by Appointment Details) keeps the original inline small buttons. */
+ /** "stack" renders one full-width button per row (used by both Current Appointment and Appointment Details); "wrap" (default) keeps the original inline small buttons. */
layout?: "wrap" | "stack";
+ /** When given, renders a "Reschedule" link right after the primary status button and before Mark No-Show/Cancel — the caller (Appointment Details) decides whether the status is actually reschedulable, this component just renders what it's given. */
+ rescheduleUrl?: string;
}) {
const router = useRouter();
const [isPending, startTransition] = useTransition();
@@ -169,6 +173,11 @@ export function AppointmentStatusActions({
Complete Appointment
)}
+ {rescheduleUrl && (
+
+ )}
{status === "CONFIRMED" && (