diff --git a/UAT_PLAYBOOK.md b/UAT_PLAYBOOK.md
index 16f797d..3b009ae 100644
--- a/UAT_PLAYBOOK.md
+++ b/UAT_PLAYBOOK.md
@@ -195,6 +195,17 @@ export const { signIn, signOut, useSession, changePassword } = authClient;
| TC-WEB-5.12.10 | RescheduleFlow error state | Mock API failure on availability fetch | "Failed to load time slots" error shown |
| TC-WEB-5.12.11 | RescheduleFlow no slots | Select date with no availability | "No available slots on this date" shown |
+#### 5.12c Waitlist/Booking Status Badges (GRO-1795)
+
+| # | Scenario | Steps | Expected |
+|---|----------|-------|----------|
+| TC-WEB-5.12.12 | Confirmed badge | View appointment card with confirmed status | Green "Confirmed" badge displayed |
+| TC-WEB-5.12.13 | Pending badge | View appointment card with pending status | Amber "Pending" badge displayed |
+| TC-WEB-5.12.14 | Waitlisted badge | View appointment card with waitlisted status | Blue "Waitlisted" badge displayed |
+| TC-WEB-5.12.15 | Badge uses CSS classes | Inspect badge element | Badge uses CSS variable-based classes (e.g., bg-green-100, text-amber-600), not hardcoded colors |
+| TC-WEB-5.12.16 | Badge status from data | Compare badge label to appointment.status field | Badge label matches the API appointment status exactly |
+| TC-WEB-5.12.17 | Unknown status fallback | Render badge with unknown status value | Badge renders with the raw status string as label and fallback CSS class |
+
### 5.13 Reports UI
| # | Scenario | Steps | Expected |
diff --git a/src/__tests__/Appointments.test.tsx b/src/__tests__/Appointments.test.tsx
index 9ba746b..0f6fd76 100644
--- a/src/__tests__/Appointments.test.tsx
+++ b/src/__tests__/Appointments.test.tsx
@@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
-import { parseTimeTo24Hour, isUpcoming, CustomerNotesSection, ConfirmationSection } from "../portal/sections/Appointments.tsx";
+import { parseTimeTo24Hour, isUpcoming, CustomerNotesSection, ConfirmationSection, StatusBadge } from "../portal/sections/Appointments.tsx";
const UPCOMING_APPT = {
id: "appt-1",
@@ -381,6 +381,66 @@ describe("ConfirmationSection", () => {
});
});
+describe("StatusBadge", () => {
+ it("renders Confirmed for confirmed status", () => {
+ render();
+ expect(screen.getByText("Confirmed")).toBeInTheDocument();
+ });
+
+ it("renders Pending for pending status", () => {
+ render();
+ expect(screen.getByText("Pending")).toBeInTheDocument();
+ });
+
+ it("renders Waitlisted for waitlisted status", () => {
+ render();
+ expect(screen.getByText("Waitlisted")).toBeInTheDocument();
+ });
+
+ it("renders Completed for completed status", () => {
+ render();
+ expect(screen.getByText("Completed")).toBeInTheDocument();
+ });
+
+ it("renders Cancelled for cancelled status", () => {
+ render();
+ expect(screen.getByText("Cancelled")).toBeInTheDocument();
+ });
+
+ it("falls back to status string for unknown status", () => {
+ render();
+ expect(screen.getByText("custom-status")).toBeInTheDocument();
+ });
+
+ it("uses correct CSS class for confirmed status", () => {
+ render();
+ const badge = screen.getByText("Confirmed").closest('span');
+ expect(badge?.className).toContain("bg-green-100");
+ expect(badge?.className).toContain("text-green-700");
+ });
+
+ it("uses correct CSS class for waitlisted status", () => {
+ render();
+ const badge = screen.getByText("Waitlisted").closest('span');
+ expect(badge?.className).toContain("bg-blue-100");
+ expect(badge?.className).toContain("text-blue-600");
+ });
+
+ it("uses correct CSS class for pending status", () => {
+ render();
+ const badge = screen.getByText("Pending").closest('span');
+ expect(badge?.className).toContain("bg-amber-100");
+ expect(badge?.className).toContain("text-amber-600");
+ });
+
+ it("uses fallback styling for unknown status", () => {
+ render();
+ const badge = screen.getByText("unknown").closest('span');
+ expect(badge?.className).toContain("bg-stone-100");
+ expect(badge?.className).toContain("text-stone-600");
+ });
+});
+
describe("RescheduleFlow dynamic time slots", () => {
beforeEach(() => {
vi.clearAllMocks();
diff --git a/src/portal/sections/Appointments.tsx b/src/portal/sections/Appointments.tsx
index 04ab1aa..0a86e2f 100644
--- a/src/portal/sections/Appointments.tsx
+++ b/src/portal/sections/Appointments.tsx
@@ -83,14 +83,34 @@ export function isUpcoming(appt: Appointment): boolean {
const STATUS_COLORS: Record = {
confirmed: 'bg-green-100 text-green-700',
- pending: 'bg-amber-100 text-amber-700',
- waitlisted: 'bg-blue-100 text-blue-700',
+ pending: 'bg-amber-100 text-amber-600',
+ waitlisted: 'bg-blue-100 text-blue-600',
completed: 'bg-stone-100 text-stone-600',
cancelled: 'bg-red-100 text-red-600',
'no-show': 'bg-yellow-100 text-yellow-700',
- scheduled: 'bg-blue-100 text-blue-700',
+ scheduled: 'bg-blue-100 text-blue-600',
};
+const STATUS_LABELS: Record = {
+ confirmed: 'Confirmed',
+ pending: 'Pending',
+ waitlisted: 'Waitlisted',
+ completed: 'Completed',
+ cancelled: 'Cancelled',
+ 'no-show': 'No-show',
+ scheduled: 'Scheduled',
+};
+
+export function StatusBadge({ status }: { status: string }) {
+ const label = STATUS_LABELS[status] ?? status;
+ const colorClass = STATUS_COLORS[status] ?? 'bg-stone-100 text-stone-600';
+ return (
+
+ {label}
+
+ );
+}
+
const CONFIRMATION_STATUS_COLORS: Record = {
confirmed: 'bg-green-100 text-green-700',
pending: 'bg-amber-100 text-amber-700',
@@ -298,13 +318,7 @@ function AppointmentCard({
with {appt.groomerName || 'First Available'}
-
- {appt.status}
-
+
{expanded ? (
) : (