import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faBuilding, faCircle, faCircleCheck, faCopy, faHeadset, faPenToSquare, faPhone, faRobot, faStethoscope, faUser, faUsers, } from '@fortawesome/pro-duotone-svg-icons'; // Reusable right-pane preview components for the onboarding wizard. // Each one is a pure presentation component that takes already-fetched // data as props — the parent step component owns the state + fetches // + refetches after a successful save. Keeping the panes data-only // means the active step can pass the same source of truth to both // the middle (form) pane and this preview without two GraphQL queries // running side by side. // Shared title/empty state primitives so every pane has the same // visual rhythm. const PaneCard = ({ title, count, children, }: { title: string; count?: number; children: React.ReactNode; }) => (

{title}

{typeof count === 'number' && ( {count} )}
{children}
); const EmptyState = ({ message }: { message: string }) => (
{message}
); // --------------------------------------------------------------------------- // Identity step — short "about this step" card. Explains what the // admin is configuring and where it shows up in the staff portal so // the right pane stays useful even when there's nothing to list yet. // --------------------------------------------------------------------------- const IDENTITY_BULLETS: { title: string; body: string }[] = [ { title: 'Hospital name', body: 'Shown on the staff portal sidebar, the login screen, and every patient-facing widget greeting.', }, { title: 'Logo', body: 'Used as the avatar at the top of the staff portal and on the website widget header. Square images work best.', }, { title: 'Brand identity', body: 'Colors, fonts and login copy live on the full Branding page — open it from Settings any time after setup.', }, ]; export const IdentityRightPane = () => (

This is how patients and staff first see your hospital across Helix Engage. Get the basics in now — you can polish branding later.

); // --------------------------------------------------------------------------- // Clinics step — list of clinics created so far. // --------------------------------------------------------------------------- export type ClinicSummary = { id: string; clinicName: string | null; addressCity?: string | null; clinicStatus?: string | null; }; export const ClinicsRightPane = ({ clinics }: { clinics: ClinicSummary[] }) => ( {clinics.length === 0 ? ( ) : ( )} ); // --------------------------------------------------------------------------- // Doctors step — grouped by department, since the user explicitly asked // for "doctors grouped by department" earlier in the design discussion. // --------------------------------------------------------------------------- export type DoctorSummary = { id: string; fullName: { firstName: string | null; lastName: string | null } | null; department?: string | null; specialty?: string | null; }; const doctorDisplayName = (d: DoctorSummary): string => { const first = d.fullName?.firstName?.trim() ?? ''; const last = d.fullName?.lastName?.trim() ?? ''; const full = `${first} ${last}`.trim(); return full.length > 0 ? full : 'Unnamed'; }; export const DoctorsRightPane = ({ doctors }: { doctors: DoctorSummary[] }) => { // Group by department. Doctors with no department land in // "Unassigned" so they're not silently dropped. const grouped: Record = {}; for (const d of doctors) { const key = d.department?.trim() || 'Unassigned'; (grouped[key] ??= []).push(d); } const sortedKeys = Object.keys(grouped).sort(); return ( {doctors.length === 0 ? ( ) : (
{sortedKeys.map((dept) => (

{dept}{' '} ({grouped[dept].length})

    {grouped[dept].map((d) => (
  • {doctorDisplayName(d)}

    {d.specialty && (

    {d.specialty}

    )}
  • ))}
))}
)}
); }; // --------------------------------------------------------------------------- // Team step — list of employees with role + SIP badge. // --------------------------------------------------------------------------- export type TeamMemberSummary = { id: string; userEmail: string; name: { firstName: string | null; lastName: string | null } | null; roleLabel: string | null; sipExtension: string | null; // True if this row represents the currently logged-in admin — // suppresses the edit/copy icons since admins shouldn't edit // themselves from the wizard. isCurrentUser: boolean; // True if the parent has the plaintext temp password in memory // (i.e. this employee was created in the current session). // Drives whether the copy icon shows. canCopyCredentials: boolean; }; const memberDisplayName = (m: TeamMemberSummary): string => { const first = m.name?.firstName?.trim() ?? ''; const last = m.name?.lastName?.trim() ?? ''; const full = `${first} ${last}`.trim(); return full.length > 0 ? full : m.userEmail; }; // Tiny icon button shared between the edit and copy actions on the // employee row. Kept inline since it's only used here and the styling // matches the existing right-pane density. const RowIconButton = ({ icon, title, onClick, }: { icon: typeof faPenToSquare; title: string; onClick: () => void; }) => ( ); export const TeamRightPane = ({ members, onEdit, onCopy, }: { members: TeamMemberSummary[]; onEdit?: (memberId: string) => void; onCopy?: (memberId: string) => void; }) => ( {members.length === 0 ? ( ) : (
    {members.map((m) => (
  • {memberDisplayName(m)}

    {m.userEmail} {m.roleLabel && ` · ${m.roleLabel}`}

    {m.sipExtension && ( {m.sipExtension} )}
    {/* Admin row gets neither button — admins shouldn't edit themselves from here, and their password isn't in our session memory anyway. */} {!m.isCurrentUser && (
    {m.canCopyCredentials && onCopy && ( onCopy(m.id)} /> )} {onEdit && ( onEdit(m.id)} /> )}
    )}
  • ))}
)}
); // --------------------------------------------------------------------------- // Telephony step — live SIP → member mapping. // --------------------------------------------------------------------------- export type SipSeatSummary = { id: string; sipExtension: string | null; ozonetelAgentId: string | null; workspaceMember: { name: { firstName: string | null; lastName: string | null } | null; userEmail: string; } | null; }; const seatMemberLabel = (m: SipSeatSummary['workspaceMember']): string => { if (!m) return 'Unassigned'; const first = m.name?.firstName?.trim() ?? ''; const last = m.name?.lastName?.trim() ?? ''; const full = `${first} ${last}`.trim(); return full.length > 0 ? full : m.userEmail; }; export const TelephonyRightPane = ({ seats }: { seats: SipSeatSummary[] }) => ( {seats.length === 0 ? ( ) : (
    {seats.map((seat) => { const isAssigned = seat.workspaceMember !== null; return (
  • Ext {seat.sipExtension ?? '—'}

    {seatMemberLabel(seat.workspaceMember)}

    {!isAssigned && ( Available )}
  • ); })}
)}
); // --------------------------------------------------------------------------- // AI step — static cards for each configured actor with last-edited info. // Filled in once the backend prompt config refactor lands. // --------------------------------------------------------------------------- export type AiActorSummary = { key: string; label: string; description: string; lastEditedAt: string | null; isCustom: boolean; }; export const AiRightPane = ({ actors }: { actors: AiActorSummary[] }) => ( {actors.length === 0 ? ( ) : (
    {actors.map((a) => (
  • {a.label}

    {a.isCustom ? `Edited ${a.lastEditedAt ? new Date(a.lastEditedAt).toLocaleDateString() : 'recently'}` : 'Default'}

  • ))}
)}
); // Suppress unused-import warnings for icons reserved for future use. void faCircle; void faUsers;