import React from "react"; import { Badge } from "./badge"; import { Button } from "./button"; import { Avatar, AvatarFallback, AvatarImage } from "./avatar"; import { Separator } from "./separator"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "./dropdown-menu"; import { Calendar, CalendarCheck, ChevronRight, Mail, MoreVertical, Phone, Plus, } from "lucide-react"; import type { AppointmentStatus } from "./appointment-time-slot-picker"; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- /** A single item in the ⋮ overflow DropdownMenu */ export interface AdvisorCardMenuItem { /** Display label */ label: string; /** Called when the item is clicked */ onClick: () => void; /** Visual variant — use "destructive" for delete/remove actions */ variant?: "default" | "destructive"; /** Disables the item without removing it */ disabled?: boolean; } /** Appointment data shown in the strip inside the advisor card */ export interface AdvisorAppointmentStrip { status: AppointmentStatus; /** Human-readable appointment type — e.g. "Financial Review" */ appointmentType?: string; /** Display date string — e.g. "Wed, 23 Apr 2026" */ date: string; /** Display start time — e.g. "2:00 PM" */ timeStart: string; /** Display end time — e.g. "2:30 PM" */ timeEnd: string; } export interface AdvisorCardProps { /** Advisor full name */ name: string; /** Job title / role label */ role: string; /** Phone number string */ phone: string; /** Email address */ email: string; /** Broker company name — shown as text fallback when no logo is provided */ companyName?: string; /** URL of the broker company logo — renders as avatar image */ companyLogoUrl?: string; /** Explicit initials for the avatar fallback; derived from companyName if omitted */ avatarInitials?: string; /** Whether this is the client's primary assigned advisor */ isPrimary?: boolean; /** * Upcoming appointments shown as stacked strips. * - `undefined` — strip section hidden * - `null` or `[]` — empty state: "No upcoming appointments" * - array of items — one coloured strip per appointment */ appointments?: AdvisorAppointmentStrip[] | null; /** Called when "Refer [name] to Others" is clicked */ onRefer?: () => void; /** * Items to render in the ⋮ overflow DropdownMenu. * When provided, the button opens an inline dropdown — `onMoreOptions` is ignored. */ menuItems?: AdvisorCardMenuItem[]; /** @deprecated Pass `menuItems` for an inline DropdownMenu instead */ onMoreOptions?: () => void; /** Called when "Book Appointment" is clicked */ onBookAppointment?: () => void; /** Called when "View →" in an appointment strip is clicked; receives the appointment index */ onViewAppointment?: (index: number) => void; } export interface AdvisorInviteCardProps { /** Called when the "Add Advisor" CTA is clicked */ onInvite?: () => void; } // --------------------------------------------------------------------------- // Status config // --------------------------------------------------------------------------- const STATUS_VARIANT: Record< AppointmentStatus, "warning" | "success" | "destructive" | "info" > = { pending: "warning", confirmed: "success", cancelled: "destructive", rescheduled: "info", }; const STATUS_LABEL: Record = { pending: "Pending", confirmed: "Confirmed", cancelled: "Cancelled", rescheduled: "Rescheduled", }; const STRIP_BG: Record = { pending: "bg-warning/5", confirmed: "bg-success/5", cancelled: "bg-destructive/5", rescheduled: "bg-info/5", }; // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- export function AdvisorCard({ name, role, phone, email, companyName, companyLogoUrl, avatarInitials, isPrimary = false, appointments, onRefer, menuItems, onMoreOptions, onBookAppointment, onViewAppointment, }: AdvisorCardProps) { const hasAppointments = appointments !== undefined; const appointmentList = appointments === null || (Array.isArray(appointments) && appointments.length === 0) ? null : appointments; return (
{/* ── Advisor info ── */}
{companyLogoUrl && ( )} {avatarInitials ?? (companyName ? companyName .split(" ") .map((w) => w[0]) .join("") .slice(0, 2) .toUpperCase() : name.slice(0, 2).toUpperCase())}

{name}

{isPrimary && Primary}

{role}

{phone}
{email}
{/* Overflow menu */}
{menuItems && menuItems.length > 0 ? ( } > {menuItems.map((item, i) => ( {item.label} ))} ) : ( )}
{/* ── Appointment strips ── */} {hasAppointments && ( <> {appointmentList ? ( appointmentList.map((appt, i) => ( {i > 0 && }
{STATUS_LABEL[appt.status]} {appt.appointmentType && ( {appt.appointmentType} )}

{appt.date} · {appt.timeStart}–{appt.timeEnd}

{onViewAppointment && ( )}
)) ) : ( /* Empty state */

No upcoming appointments

)} )} {/* ── Footer actions ── */}
{onBookAppointment && ( )} {onRefer && ( )}
); } // --------------------------------------------------------------------------- // AdvisorInviteCard // --------------------------------------------------------------------------- export function AdvisorInviteCard({ onInvite }: AdvisorInviteCardProps) { return ( ); }