import { Stethoscope } from 'lucide-react'; import type { Practitioner, PractitionerRole } from 'fhir/r4'; import { Card, CardContent, CardHeader, CardTitle } from '../../../components/ui/card'; import { isClinicianEnabled } from '../../../utils/clinicianEnabled'; type PractitionerWithRoles = { practitioner: Practitioner; roles: PractitionerRole[]; }; type ClinicianSeatsCardProps = { practitionersList: PractitionerWithRoles[] | undefined; /** * - WHETHER THE LIST IS AN ANSWER YET. * * Measured at Great River, 2026-08-21: this card read **"0 active clinician seats - No clinician * seats are in use - your first clinician is free"** while the practice had one, and the charge * confirmation quoted **$50** for the next one minutes later on the same screen. * * `practitionersList` initialises to `[]`, so an UNLOADED list and a practice with no clinicians are * the same value. `?? []` then turned "we do not know" into "we know it is none" - and none is the * one answer here that makes a promise about somebody's bill. * The server already refuses to do this: `Billing/entitlements.ts` fails soft to `undefined`, * *"never to 0. A zero would render '0 enabled' and a $0 seat line to a practice with four clinicians - * a confident wrong answer about their own bill."* This is the same rule, on the client. */ loading?: boolean; className?: string; }; export const displayNameFor = (practitioner: Practitioner): string => { const human = practitioner.name?.[0]; const text = human?.text ?? [human?.given?.join(' '), human?.family].filter(Boolean).join(' ').trim(); // Never render a blank row: a seat we cannot name is still a seat the practice is paying for, and an // empty line would make the count and the list silently disagree. return text || 'Unnamed clinician'; }; /** * / 174 - the seats the practice is actually billed for, on the org overview. * * Spencer, 2026-08-06: *"a card section to the org admin overview with a header label saying 'X' active * clinician seats and then list the active clinicians in the body... this will help the org admin see right * away how many seats they're being billed for and what practitioners hold them."* * * THE COUNT IS `seats.length` - THE HEADER AND THE BODY CANNOT DISAGREE. Deriving the number separately * from the list it labels is how a header ends up lying, and here the lie would be about someone's bill. * * COUNTS PEOPLE, NOT ROLES. One human can hold several PractitionerRoles in an org and the model bills * per clinician, so the seat is the PRACTITIONER and a person qualifies if ANY of their roles is enabled. * Counting roles would over-report the bill on exactly the staff most likely to hold more than one. * An INACTIVE role is not a seat - a deactivated person is not billable and must not be listed. */ export const getClinicianSeatHolders = ( practitionersList: PractitionerWithRoles[] | undefined, ): Practitioner[] => (practitionersList ?? []) .filter(({ roles }) => (roles ?? []).some( (role) => role.active !== false && isClinicianEnabled(role), )) .map(({ practitioner }) => practitioner); const ClinicianSeatsCard = ({ practitionersList, loading = false, className }: ClinicianSeatsCardProps) => { const seats = getClinicianSeatHolders(practitionersList); // Not `seats.length === 0 && loading` - the count itself is what must be withheld, not just the // empty state. A header reading "0 active clinician seats" is the claim; the body merely repeats it. const countIsKnown = !loading; return ( // Fixed height with ~5 rows visible and the rest behind an inner scroll (Spencer's spec). The // header sits OUTSIDE the scrolling region so the count stays readable while the list moves.
{countIsKnown ? `${seats.length} active clinician ${seats.length === 1 ? 'seat' : 'seats'}` : 'Clinician seats'} {/* ⚠️ Deliberately NO price. The per-seat figure lives in the billing config and on the Subscription page; a second literal here is the card-124 drift shape, and a stale price on a billing summary is worse than no price at all. */} {/* ⛔ Withheld while the count is unknown. "Your first clinician is free" is only true when there are none yet, so saying it beside a number we have not read promises the next enable is free — which is exactly the sentence a practice would act on immediately before being charged. */} {countIsKnown && (

Billed monthly. Your first clinician is free.

)}
{!countIsKnown ? (
Loading clinician seats…
) : seats.length === 0 ? (
No clinician seats are in use. Give a staff practitioner a clinician seat for clinical visits and patient booking.
) : ( )}
); }; export default ClinicianSeatsCard;