Menu

Change language

GitHub repo

Telehealth Video Room

blockhealthcare

HIPAA-compliant telehealth video consultation room with live telemetry vitals, clinical SOAP notes, in-call encrypted chat, and prescription generator drawer.

Installation

$npx uipkge-ng@latest add telehealth-video-room
Manifest:https://uipkge.dev/r/angular/telehealth-video-room.jsonInstalls to:src/app/components/blocks/telehealth-video-room/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

PrescriptionItem
interface PrescriptionItem {
  id: string
  drugName: string
  dosage: string
  sig: string
  quantity: string
  refills: number
  pharmacy: string
  timestamp: string
  status: 'transmitted' | 'pending'
  rxNumber: string
}
ChatMessage
interface ChatMessage {
  id: string
  sender: 'doctor' | 'patient'
  senderName: string
  text: string
  timestamp: string
  attachment?: {
    name: string
    size: string
    type: 'pdf' | 'image'
  }
}
DrugOption
interface DrugOption {
  id: string
  name: string
  defaultDosage: string
  defaultQty: string
  refills: string
}

Files installed (2)

  • src/app/components/blocks/telehealth-video-room/telehealth-video-room.component.ts54.3 kB
    // HIPAA-compliant telehealth video consultation room with live telemetry
    // vitals, clinical SOAP notes, in-call encrypted chat, and an e-prescription
    // generator. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, DestroyRef, Input, computed, inject, signal } from '@angular/core'
    import {
      AlertCircle,
      CheckCircle2,
      Clock,
      Download,
      FileCheck,
      FileText,
      Heart,
      HeartPulse,
      Lock,
      LucideAngularModule,
      MessageSquare,
      Mic,
      MicOff,
      PanelRightClose,
      PanelRightOpen,
      Paperclip,
      PhoneOff,
      Pill,
      RefreshCw,
      Send,
      Settings,
      Share2,
      ShieldCheck,
      Thermometer,
      Video,
      VideoOff,
      Wifi,
      X,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiAvatarComponent, UiAvatarFallbackComponent } from '@/ui/avatar/avatar.component'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import {
      UiCardComponent,
      UiCardContentComponent,
      UiCardDescriptionComponent,
      UiCardHeaderComponent,
      UiCardTitleComponent,
    } from '@/ui/card/card.component'
    import { UiInputComponent } from '@/ui/input/input.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    import {
      UiTabsComponent,
      UiTabsContentComponent,
      UiTabsListComponent,
      UiTabsTriggerComponent,
    } from '@/ui/tabs/tabs.component'
    import { UiTextareaComponent } from '@/ui/textarea/textarea.component'
    
    export type TelehealthTab = 'notes' | 'chat' | 'vitals'
    
    export interface PrescriptionItem {
      id: string
      drugName: string
      dosage: string
      sig: string
      quantity: string
      refills: number
      pharmacy: string
      timestamp: string
      status: 'transmitted' | 'pending'
      rxNumber: string
    }
    
    export interface ChatMessage {
      id: string
      sender: 'doctor' | 'patient'
      senderName: string
      text: string
      timestamp: string
      attachment?: {
        name: string
        size: string
        type: 'pdf' | 'image'
      }
    }
    
    interface DrugOption {
      id: string
      name: string
      defaultDosage: string
      defaultQty: string
      refills: string
    }
    
    const drugOptions: DrugOption[] = [
      {
        id: 'metoprolol',
        name: 'Metoprolol Succinate ER',
        defaultDosage: '25mg PO Daily (Morning)',
        defaultQty: '30 tablets',
        refills: '2',
      },
      {
        id: 'propranolol',
        name: 'Propranolol HCl',
        defaultDosage: '10mg PO BID (Twice Daily)',
        defaultQty: '60 tablets',
        refills: '1',
      },
      {
        id: 'diltiazem',
        name: 'Diltiazem HCl Extended Release',
        defaultDosage: '120mg PO Daily',
        defaultQty: '30 capsules',
        refills: '3',
      },
      { id: 'lisinopril', name: 'Lisinopril', defaultDosage: '10mg PO Daily', defaultQty: '30 tablets', refills: '3' },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-telehealth-video-room, [ui-telehealth-video-room]',
      standalone: true,
      // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiAvatarComponent,
        UiAvatarFallbackComponent,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiInputComponent,
        UiSeparatorComponent,
        UiTabsComponent,
        UiTabsContentComponent,
        UiTabsListComponent,
        UiTabsTriggerComponent,
        UiTextareaComponent,
      ],
      template: `
        <div data-slot="telehealth-video-room" [class]="rootClass">
          <!-- Top Room Bar -->
          <header class="bg-card rounded-xl border p-4 shadow-xs sm:px-6 sm:py-3.5">
            <div class="flex flex-wrap items-center justify-between gap-3">
              <!-- Call Metadata & HIPAA Badge -->
              <div class="flex flex-wrap items-center gap-3">
                <div class="flex items-center gap-2">
                  <span class="relative flex size-3">
                    @if (!isCallEnded()) {
                      <span class="bg-success absolute inline-flex h-full w-full rounded-full opacity-75"></span>
                    }
                    <span
                      [class]="'relative inline-flex size-3 rounded-full ' + (isCallEnded() ? 'bg-zinc-400' : 'bg-success')"
                    ></span>
                  </span>
                  <div class="flex items-center gap-1.5 font-mono text-sm font-semibold tracking-tight">
                    <lucide-icon [img]="Clock" class="text-muted-foreground size-3.5" />
                    <span>{{ isCallEnded() ? 'Call Ended' : formattedDuration() }}</span>
                  </div>
                </div>
    
                <div ui-separator orientation="vertical" class="hidden h-5 sm:block"></div>
    
                <!-- HIPAA Encrypted Connection Badge -->
                <span
                  ui-badge
                  variant="outline"
                  class="border-success/30 bg-success/10 text-success gap-1.5 text-xs font-medium"
                >
                  <lucide-icon [img]="Lock" class="text-success size-3" />
                  <span>HIPAA Encrypted · AES-256</span>
                </span>
    
                <div ui-separator orientation="vertical" class="hidden h-5 md:block"></div>
    
                <!-- Doctor & Patient Context Strip -->
                <div class="hidden items-center gap-2 lg:flex">
                  <div class="flex items-center gap-1.5 text-xs">
                    <span class="text-muted-foreground">Provider:</span>
                    <span class="text-foreground font-semibold">Dr. Sarah Jenkins, MD</span>
                    <span class="text-muted-foreground">(Cardiology)</span>
                  </div>
                  <div class="flex items-center gap-1.5 text-xs">
                    <span class="text-muted-foreground">Patient:</span>
                    <span class="text-foreground font-semibold">David Chen</span>
                    <span class="text-muted-foreground font-mono text-xs">(MRN-88412)</span>
                  </div>
                </div>
              </div>
    
              <!-- Header Actions: End Call & Network Status -->
              <div class="flex items-center gap-2.5">
                <div class="text-muted-foreground hidden items-center gap-1.5 text-xs sm:flex">
                  <lucide-icon [img]="Wifi" class="text-success size-3.5" />
                  <span>HD Connection (18ms)</span>
                </div>
    
                @if (!isCallEnded()) {
                  <button
                    ui-button
                    variant="destructive"
                    size="sm"
                    class="h-8 gap-1.5 px-3 text-xs font-semibold shadow-xs"
                    (click)="handleEndCall()"
                  >
                    <lucide-icon [img]="PhoneOff" class="size-3.5" />
                    <span>End Consultation</span>
                  </button>
                } @else {
                  <button
                    ui-button
                    variant="default"
                    size="sm"
                    class="h-8 gap-1.5 px-3 text-xs font-semibold"
                    (click)="handleRestartCall()"
                  >
                    <lucide-icon [img]="RefreshCw" class="size-3.5" />
                    <span>Reconnect Call</span>
                  </button>
                }
              </div>
            </div>
          </header>
    
          <!-- Main Video Room Grid (Video Canvas + Collapsible Workspace) -->
          <div class="grid grid-cols-1 gap-4 lg:grid-cols-12">
            <!-- Video Call Canvas -->
            <section [class]="canvasClass">
              <!-- Overlay Top: Provider Status & Watermark -->
              <div class="z-10 flex flex-wrap items-start justify-between gap-3">
                <div
                  class="flex items-center gap-2.5 rounded-lg border border-zinc-700/60 bg-zinc-900/85 p-2 shadow-md backdrop-blur-md"
                >
                  <ui-avatar class="border-success/50 size-8 border">
                    <ui-avatar-fallback class="bg-success/15 text-success text-xs font-semibold">SJ</ui-avatar-fallback>
                  </ui-avatar>
                  <div>
                    <div class="flex items-center gap-1.5">
                      <span class="text-xs font-semibold text-zinc-100">Dr. Sarah Jenkins, MD</span>
                      <span
                        ui-badge
                        variant="outline"
                        class="border-success/40 bg-success/20 text-success px-1 py-0 text-xs font-normal"
                      >
                        Attending
                      </span>
                    </div>
                    <div class="text-muted-foreground flex items-center gap-2 text-xs">
                      <div class="flex items-center gap-1">
                        <lucide-icon [img]="Mic" class="text-success size-3" />
                        <div class="flex items-center gap-0.5">
                          <span class="bg-success size-1 animate-pulse rounded-full"></span>
                          <span
                            class="bg-success h-2.5 w-0.5 animate-[pulse_0.8s_ease-in-out_infinite] rounded-full"
                          ></span>
                          <span
                            class="bg-success h-3.5 w-0.5 animate-[pulse_1.2s_ease-in-out_infinite] rounded-full"
                          ></span>
                          <span
                            class="bg-success h-1.5 w-0.5 animate-[pulse_0.6s_ease-in-out_infinite] rounded-full"
                          ></span>
                        </div>
                        <span class="text-success text-xs font-medium">Speaking</span>
                      </div>
                      <span>·</span>
                      <span>St. Jude Telehealth Clinic</span>
                    </div>
                  </div>
                </div>
    
                <div
                  class="flex items-center gap-1.5 rounded-lg border border-zinc-800 bg-zinc-900/80 px-2.5 py-1 text-xs text-zinc-300 backdrop-blur-sm"
                >
                  <lucide-icon [img]="ShieldCheck" class="text-success size-3.5" />
                  <span class="font-mono text-xs">1080p 60fps · BAA Protected</span>
                </div>
              </div>
    
              <!-- Main Video Placeholder Canvas -->
              <div class="relative my-auto flex flex-col items-center justify-center pt-12 pb-28 text-center">
                <div
                  class="pointer-events-none absolute inset-0 bg-radial from-emerald-950/20 via-zinc-950/80 to-zinc-950"
                ></div>
    
                <div class="relative z-10 space-y-4">
                  <div class="relative mx-auto size-28 sm:size-36">
                    <div
                      class="absolute -inset-1 rounded-full bg-gradient-to-tr from-emerald-500 to-teal-400 opacity-60 blur-xs"
                    ></div>
                    <div
                      class="border-success/80 relative flex size-full items-center justify-center overflow-hidden rounded-full border-2 bg-zinc-900 shadow-xl"
                    >
                      <ui-avatar class="size-full">
                        <ui-avatar-fallback class="text-success bg-zinc-900 text-xl font-bold sm:text-2xl">
                          SJ
                        </ui-avatar-fallback>
                      </ui-avatar>
                    </div>
                    <span class="bg-success absolute right-2 bottom-2 size-4 rounded-full ring-2 ring-zinc-950"></span>
                  </div>
    
                  <div class="space-y-1">
                    <h3 class="text-base font-semibold tracking-tight text-zinc-100 sm:text-lg">Dr. Sarah Jenkins, MD</h3>
                    <p class="text-muted-foreground text-xs">
                      Department of Cardiovascular Medicine · Clinical Telehealth Encounter
                    </p>
                  </div>
    
                  <div
                    class="text-muted-foreground inline-flex max-w-full items-center gap-2 rounded-full border border-zinc-800 bg-zinc-900/70 px-3 py-1 font-mono text-xs"
                  >
                    <span class="bg-success size-1.5 shrink-0 rounded-full"></span>
                    <span class="truncate">Session: #TH-9021-TX</span>
                    <span class="hidden sm:inline">·</span>
                    <span class="hidden truncate sm:inline">Latency: 24ms (0% loss)</span>
                  </div>
                </div>
              </div>
    
              <!-- Picture-in-Picture Self Video Tile -->
              <div
                class="absolute right-4 bottom-20 z-20 aspect-video w-36 overflow-hidden rounded-xl border-2 border-zinc-700/80 bg-zinc-900 shadow-sm transition-colors sm:right-6 sm:bottom-20 sm:w-48"
              >
                @if (!isCameraOff()) {
                  <div
                    class="relative flex size-full flex-col justify-between bg-gradient-to-b from-zinc-800 to-zinc-950 p-2 text-white"
                  >
                    <div class="flex items-center justify-between">
                      <span
                        ui-badge
                        variant="outline"
                        class="border-zinc-700 bg-zinc-900/80 px-1 py-0 text-xs text-zinc-300"
                      >
                        You (720p)
                      </span>
                      <div
                        [class]="
                          'flex size-4 items-center justify-center rounded-full ' +
                          (isMuted() ? 'bg-destructive text-white' : 'bg-success/80 text-white')
                        "
                      >
                        @if (isMuted()) {
                          <lucide-icon [img]="MicOff" class="size-2.5" />
                        } @else {
                          <lucide-icon [img]="Mic" class="size-2.5" />
                        }
                      </div>
                    </div>
                    <div class="flex items-center gap-1.5">
                      <ui-avatar class="size-5">
                        <ui-avatar-fallback class="bg-zinc-700 text-xs font-bold text-zinc-100">DC</ui-avatar-fallback>
                      </ui-avatar>
                      <span class="truncate text-xs font-medium text-zinc-200">David Chen</span>
                    </div>
                  </div>
                } @else {
                  <div
                    class="text-muted-foreground flex size-full flex-col items-center justify-center gap-1 bg-zinc-950 p-2 text-center"
                  >
                    <lucide-icon [img]="VideoOff" class="text-muted-foreground size-4" />
                    <span class="text-xs font-medium">Camera Off</span>
                  </div>
                }
              </div>
    
              <!-- Floating Call Controls Bar -->
              <div class="z-20 flex items-center justify-center pt-4">
                <div
                  class="flex flex-wrap items-center justify-center gap-1.5 rounded-full border border-zinc-700/70 bg-zinc-900/90 p-2 shadow-sm backdrop-blur-md sm:gap-2 sm:px-4"
                >
                  <button
                    ui-button
                    [variant]="isMuted() ? 'destructive' : 'secondary'"
                    size="icon"
                    class="size-9 rounded-full sm:size-10"
                    [attr.aria-label]="isMuted() ? 'Unmute microphone' : 'Mute microphone'"
                    (click)="isMuted.set(!isMuted())"
                  >
                    @if (isMuted()) {
                      <lucide-icon [img]="MicOff" class="size-4" />
                    } @else {
                      <lucide-icon [img]="Mic" class="text-success size-4" />
                    }
                  </button>
    
                  <button
                    ui-button
                    [variant]="isCameraOff() ? 'destructive' : 'secondary'"
                    size="icon"
                    class="size-9 rounded-full sm:size-10"
                    [attr.aria-label]="isCameraOff() ? 'Turn on camera' : 'Turn off camera'"
                    (click)="isCameraOff.set(!isCameraOff())"
                  >
                    @if (isCameraOff()) {
                      <lucide-icon [img]="VideoOff" class="size-4" />
                    } @else {
                      <lucide-icon [img]="Video" class="text-success size-4" />
                    }
                  </button>
    
                  <button
                    ui-button
                    [variant]="isScreenSharing() ? 'default' : 'secondary'"
                    size="icon"
                    class="size-9 rounded-full sm:size-10"
                    [attr.aria-label]="isScreenSharing() ? 'Stop sharing screen' : 'Share screen'"
                    (click)="isScreenSharing.set(!isScreenSharing())"
                  >
                    <lucide-icon [img]="Share2" class="size-4" />
                  </button>
    
                  <div ui-separator orientation="vertical" class="mx-1 h-6 bg-zinc-700"></div>
    
                  <button
                    ui-button
                    [variant]="isSidebarOpen() && activeTab() === 'notes' ? 'default' : 'secondary'"
                    size="sm"
                    class="h-9 gap-1.5 rounded-full px-3 text-xs sm:h-10 sm:px-3.5"
                    (click)="handleOpenTab('notes')"
                  >
                    <lucide-icon [img]="FileText" class="size-3.5" />
                    <span class="hidden sm:inline">SOAP Notes</span>
                  </button>
    
                  <button
                    ui-button
                    [variant]="isSidebarOpen() && activeTab() === 'chat' ? 'default' : 'secondary'"
                    size="sm"
                    class="relative h-9 gap-1.5 rounded-full px-3 text-xs sm:h-10 sm:px-3.5"
                    (click)="handleOpenTab('chat')"
                  >
                    <lucide-icon [img]="MessageSquare" class="size-3.5" />
                    <span class="hidden sm:inline">In-Call Chat</span>
                  </button>
    
                  <button
                    ui-button
                    [variant]="isSidebarOpen() && activeTab() === 'vitals' ? 'default' : 'secondary'"
                    size="sm"
                    class="h-9 gap-1.5 rounded-full px-3 text-xs sm:h-10 sm:px-3.5"
                    (click)="handleOpenTab('vitals')"
                  >
                    <lucide-icon [img]="HeartPulse" class="text-destructive size-3.5" />
                    <span class="hidden sm:inline">Vitals</span>
                  </button>
    
                  <div ui-separator orientation="vertical" class="mx-1 h-6 bg-zinc-700"></div>
    
                  <button
                    ui-button
                    variant="secondary"
                    size="icon"
                    class="size-9 rounded-full sm:size-10"
                    aria-label="Audio and Video Settings"
                    (click)="showSettingsModal.set(!showSettingsModal())"
                  >
                    <lucide-icon [img]="Settings" class="size-4 text-zinc-300" />
                  </button>
    
                  <button
                    ui-button
                    variant="secondary"
                    size="icon"
                    class="size-9 rounded-full sm:size-10"
                    [attr.aria-label]="isSidebarOpen() ? 'Collapse side workspace' : 'Open side workspace'"
                    (click)="isSidebarOpen.set(!isSidebarOpen())"
                  >
                    @if (isSidebarOpen()) {
                      <lucide-icon [img]="PanelRightClose" class="size-4 text-zinc-300" />
                    } @else {
                      <lucide-icon [img]="PanelRightOpen" class="size-4 text-zinc-300" />
                    }
                  </button>
                </div>
              </div>
    
              <!-- Quick Settings Flyout -->
              @if (showSettingsModal()) {
                <div class="bg-card text-card-foreground absolute top-16 right-4 z-30 w-72 rounded-xl border p-4 shadow-xl">
                  <div class="flex items-center justify-between pb-2">
                    <h4 class="text-muted-foreground text-xs font-bold tracking-wider uppercase">AV Devices & HIPAA</h4>
                    <button
                      ui-button
                      variant="ghost"
                      size="icon"
                      class="size-6"
                      aria-label="Close settings modal"
                      (click)="showSettingsModal.set(false)"
                    >
                      <lucide-icon [img]="X" class="size-3.5" />
                    </button>
                  </div>
                  <div class="space-y-3 pt-1 text-xs">
                    <div>
                      <label class="text-muted-foreground block font-medium">Microphone</label>
                      <div class="text-foreground mt-0.5 rounded border p-1.5 font-mono text-xs">
                        Default - MacBook Pro Mic (CoreAudio)
                      </div>
                    </div>
                    <div>
                      <label class="text-muted-foreground block font-medium">Camera</label>
                      <div class="text-foreground mt-0.5 rounded border p-1.5 font-mono text-xs">
                        FaceTime HD Camera (1080p)
                      </div>
                    </div>
                    <div>
                      <label class="text-muted-foreground block font-medium">Speaker Output</label>
                      <div class="text-foreground mt-0.5 rounded border p-1.5 font-mono text-xs">
                        Studio Display Speakers
                      </div>
                    </div>
                  </div>
                </div>
              }
            </section>
    
            <!-- Collapsible Side Workspace -->
            @if (isSidebarOpen()) {
              <aside class="bg-card flex flex-col overflow-hidden rounded-xl border shadow-xs lg:col-span-4 xl:col-span-4">
                <ui-tabs [value]="activeTab()" (valueChange)="setActiveTab($event)" class="flex h-full flex-col">
                  <div class="bg-muted/30 flex items-center justify-between border-b px-3 py-2.5">
                    <ui-tabs-list class="grid h-8 w-full max-w-[320px] grid-cols-3">
                      <button ui-tabs-trigger value="notes" class="gap-1 text-xs font-medium">
                        <lucide-icon [img]="FileText" class="size-3" />
                        <span>Notes & Rx</span>
                      </button>
                      <button ui-tabs-trigger value="chat" class="gap-1 text-xs font-medium">
                        <lucide-icon [img]="MessageSquare" class="size-3" />
                        <span>Chat</span>
                      </button>
                      <button ui-tabs-trigger value="vitals" class="gap-1 text-xs font-medium">
                        <lucide-icon [img]="HeartPulse" class="text-destructive size-3" />
                        <span>Vitals</span>
                      </button>
                    </ui-tabs-list>
    
                    <button
                      ui-button
                      variant="ghost"
                      size="icon"
                      class="text-muted-foreground hover:text-foreground ml-1 size-8"
                      aria-label="Close sidebar"
                      (click)="isSidebarOpen.set(false)"
                    >
                      <lucide-icon [img]="PanelRightClose" class="size-4" />
                    </button>
                  </div>
    
                  <!-- TAB 1: Clinical Notes & Rx -->
                  <ui-tabs-content value="notes" class="flex-1 space-y-4 overflow-y-auto p-4 focus-visible:outline-none">
                    <div class="bg-muted/40 space-y-2 rounded-lg border p-3">
                      <div class="flex items-center justify-between">
                        <div class="flex items-center gap-2">
                          <ui-avatar class="size-7">
                            <ui-avatar-fallback class="bg-primary/10 text-primary text-xs font-bold">DC</ui-avatar-fallback>
                          </ui-avatar>
                          <div>
                            <span class="text-foreground text-xs font-bold">David Chen</span>
                            <span class="text-muted-foreground ml-1 text-xs">44y · Male</span>
                          </div>
                        </div>
                        <span ui-badge variant="destructive" class="text-xs font-normal"> Allergy: Penicillin </span>
                      </div>
                      <div
                        class="text-muted-foreground flex flex-wrap items-center justify-between gap-1 border-t pt-2 text-xs"
                      >
                        <span>DOB: 05/14/1982</span>
                        <span>Pharmacy: CVS #4021 (Austin)</span>
                      </div>
                    </div>
    
                    <div class="space-y-1.5">
                      <label
                        class="text-muted-foreground flex items-center gap-1.5 text-xs font-bold tracking-wider uppercase"
                      >
                        <lucide-icon [img]="AlertCircle" class="text-warning size-3.5" />
                        Chief Complaint
                      </label>
                      <div class="bg-muted/20 text-foreground rounded-lg border p-2.5 text-xs leading-relaxed">
                        {{ chiefComplaint() }}
                      </div>
                    </div>
    
                    <div class="space-y-3">
                      <div class="flex items-center justify-between">
                        <span class="text-muted-foreground text-xs font-bold tracking-wider uppercase"
                          >SOAP Encounter Note</span
                        >
                        <span class="text-muted-foreground font-mono text-xs">ICD-10: I47.1</span>
                      </div>
    
                      <div class="bg-card space-y-1 rounded-lg border p-3">
                        <div class="text-foreground flex items-center gap-1.5 text-xs font-semibold">
                          <span
                            class="bg-primary text-primary-foreground flex size-4 items-center justify-center rounded-full text-xs"
                            >S</span
                          >
                          <span>Subjective</span>
                        </div>
                        <p class="text-muted-foreground text-xs leading-relaxed">{{ soapSubjective() }}</p>
                      </div>
    
                      <div class="bg-card space-y-1 rounded-lg border p-3">
                        <div class="text-foreground flex items-center gap-1.5 text-xs font-semibold">
                          <span
                            class="bg-primary text-primary-foreground flex size-4 items-center justify-center rounded-full text-xs"
                            >O</span
                          >
                          <span>Objective (Telemetry & Exam)</span>
                        </div>
                        <p class="text-muted-foreground text-xs leading-relaxed">{{ soapObjective() }}</p>
                      </div>
    
                      <div class="bg-card space-y-1 rounded-lg border p-3">
                        <div class="text-foreground flex items-center gap-1.5 text-xs font-semibold">
                          <span
                            class="bg-primary text-primary-foreground flex size-4 items-center justify-center rounded-full text-xs"
                            >A</span
                          >
                          <span>Assessment & Differential</span>
                        </div>
                        <p class="text-muted-foreground text-xs leading-relaxed whitespace-pre-line">
                          {{ soapAssessment() }}
                        </p>
                      </div>
    
                      <div class="bg-card space-y-2 rounded-lg border p-3">
                        <div class="flex items-center justify-between">
                          <div class="text-foreground flex items-center gap-1.5 text-xs font-semibold">
                            <span
                              class="bg-primary text-primary-foreground flex size-4 items-center justify-center rounded-full text-xs"
                              >P</span
                            >
                            <span>Clinical Plan & Orders</span>
                          </div>
                          <span class="text-muted-foreground text-xs">Editable</span>
                        </div>
                        <ui-textarea
                          [value]="soapPlan()"
                          (valueChange)="soapPlan.set($event)"
                          [rows]="3"
                          class="resize-y text-xs leading-relaxed"
                          placeholder="Enter medical orders, follow-up recommendations..."
                        />
                      </div>
                    </div>
    
                    <!-- Quick e-Prescription Generator -->
                    <div ui-card class="border-primary/30 shadow-xs">
                      <div ui-card-header class="px-3.5 pt-3 pb-2">
                        <div class="flex items-center justify-between">
                          <h3 ui-card-title class="text-foreground flex items-center gap-1.5 text-xs font-bold">
                            <lucide-icon [img]="Pill" class="text-primary size-3.5" />
                            Electronic Prescription (e-Rx)
                          </h3>
                          <span ui-badge variant="outline" class="font-mono text-xs">NPI #1894021</span>
                        </div>
                        <p ui-card-description class="text-xs">
                          Generate and electronically sign DEA-compliant prescription
                        </p>
                      </div>
                      <div ui-card-content class="space-y-3 px-3.5 pb-3">
                        <div class="space-y-1.5">
                          <label class="text-muted-foreground text-xs font-medium">Select Medication</label>
                          <div class="grid grid-cols-2 gap-1.5">
                            @for (drug of drugOptions; track drug.id) {
                              <button
                                ui-button
                                type="button"
                                [variant]="selectedDrugPreset() === drug.id ? 'default' : 'outline'"
                                size="sm"
                                class="h-7 justify-start truncate px-2 text-xs"
                                (click)="handleSelectDrugPreset(drug.id)"
                              >
                                {{ drug.name }}
                              </button>
                            }
                          </div>
                        </div>
    
                        <div class="grid grid-cols-2 gap-2">
                          <div class="space-y-1">
                            <label class="text-muted-foreground text-xs">Dosage & Frequency</label>
                            <ui-input
                              [value]="customDosage()"
                              (valueChange)="customDosage.set($event)"
                              class="h-7 text-xs"
                            />
                          </div>
                          <div class="space-y-1">
                            <label class="text-muted-foreground text-xs">Quantity & Refills</label>
                            <ui-input
                              [value]="customQuantity()"
                              (valueChange)="customQuantity.set($event)"
                              class="h-7 text-xs"
                            />
                          </div>
                        </div>
    
                        @if (rxSuccessMessage()) {
                          <div
                            class="border-success/30 bg-success/10 text-success flex items-center gap-1.5 rounded-md border p-2 text-xs font-medium"
                          >
                            <lucide-icon [img]="CheckCircle2" class="size-3.5 shrink-0" />
                            <span>{{ rxSuccessMessage() }}</span>
                          </div>
                        }
    
                        <button
                          ui-button
                          size="sm"
                          class="w-full gap-1.5 text-xs font-semibold"
                          (click)="handleGeneratePrescription()"
                        >
                          <lucide-icon [img]="FileCheck" class="size-3.5" />
                          <span>Sign & Transmit e-Rx to Pharmacy</span>
                        </button>
    
                        @if (prescriptions().length > 0) {
                          <div class="space-y-1.5 border-t pt-1">
                            <span class="text-muted-foreground text-xs font-medium">Active Prescriptions this Visit:</span>
                            @for (rx of prescriptions(); track rx.id) {
                              <div class="bg-muted/30 space-y-1 rounded border p-2 text-xs">
                                <div class="flex items-center justify-between">
                                  <span class="text-foreground font-semibold">{{ rx.drugName }}</span>
                                  <span
                                    ui-badge
                                    variant="outline"
                                    class="border-success/40 bg-success/10 text-success text-xs"
                                  >
                                    {{ rx.status === 'transmitted' ? 'Transmitted' : 'Pending' }}
                                  </span>
                                </div>
                                <p class="text-muted-foreground text-xs">
                                  {{ rx.dosage }} · {{ rx.quantity }} (Refills: {{ rx.refills }})
                                </p>
                                <p class="text-muted-foreground font-mono text-xs">{{ rx.pharmacy }} · {{ rx.rxNumber }}</p>
                              </div>
                            }
                          </div>
                        }
                      </div>
                    </div>
                  </ui-tabs-content>
    
                  <!-- TAB 2: In-Call Chat -->
                  <ui-tabs-content value="chat" class="flex h-[460px] flex-col focus-visible:outline-none">
                    <div class="bg-success/10 text-success flex items-center gap-2 border-b p-2.5 text-xs">
                      <lucide-icon [img]="ShieldCheck" class="text-success size-4 shrink-0" />
                      <span>Encrypted Consultation Chat · Archived in EHR Audit Log</span>
                    </div>
    
                    <div class="flex-1 space-y-3.5 overflow-y-auto p-4">
                      @for (msg of chatMessages(); track msg.id) {
                        <div
                          [class]="
                            'flex flex-col gap-1 text-xs ' + (msg.sender === 'patient' ? 'items-end' : 'items-start')
                          "
                        >
                          <div class="text-muted-foreground flex items-center gap-1.5 px-1 text-xs">
                            <span>{{ msg.senderName }}</span>
                            <span>·</span>
                            <span>{{ msg.timestamp }}</span>
                          </div>
                          <div
                            [class]="
                              'max-w-[85%] rounded-2xl px-3.5 py-2 leading-relaxed shadow-xs ' +
                              (msg.sender === 'patient'
                                ? 'bg-primary text-primary-foreground rounded-br-xs'
                                : 'bg-muted text-foreground rounded-bl-xs')
                            "
                          >
                            <p>{{ msg.text }}</p>
                            @if (msg.attachment) {
                              <div
                                class="mt-2 flex items-center justify-between gap-2 rounded-lg border border-white/20 bg-black/10 p-2 text-xs"
                              >
                                <div class="flex min-w-0 items-center gap-2">
                                  <lucide-icon [img]="FileText" class="size-4 shrink-0" />
                                  <div class="min-w-0">
                                    <p class="truncate font-medium">{{ msg.attachment!.name }}</p>
                                    <p class="text-xs opacity-80">{{ msg.attachment!.size }}</p>
                                  </div>
                                </div>
                                <button
                                  ui-button
                                  variant="ghost"
                                  size="icon"
                                  class="size-6 shrink-0 text-current hover:bg-white/10"
                                  aria-label="Download attachment"
                                >
                                  <lucide-icon [img]="Download" class="size-3.5" />
                                </button>
                              </div>
                            }
                          </div>
                        </div>
                      }
                    </div>
    
                    <div class="bg-muted/20 space-y-2 border-t p-3">
                      <div class="flex items-center gap-1.5">
                        <ui-input
                          [value]="chatInput()"
                          (valueChange)="chatInput.set($event)"
                          placeholder="Type encrypted message to Dr. Jenkins..."
                          class="h-8 text-xs"
                          (keydown.enter)="handleSendMessage()"
                        />
                        <button
                          ui-button
                          variant="ghost"
                          size="icon"
                          class="text-muted-foreground hover:text-foreground size-8 shrink-0"
                          aria-label="Attach clinical file"
                        >
                          <lucide-icon [img]="Paperclip" class="size-4" />
                        </button>
                        <button
                          ui-button
                          size="sm"
                          class="h-8 shrink-0 gap-1 px-3 text-xs"
                          [disabled]="!chatInput().trim()"
                          (click)="handleSendMessage()"
                        >
                          <lucide-icon [img]="Send" class="size-3.5" />
                          <span>Send</span>
                        </button>
                      </div>
                      <div class="text-muted-foreground flex items-center justify-between px-1 text-xs">
                        <span>Press Enter to send</span>
                        <span>BAA Encrypted</span>
                      </div>
                    </div>
                  </ui-tabs-content>
    
                  <!-- TAB 3: Patient Vitals & Telemetry -->
                  <ui-tabs-content value="vitals" class="flex-1 space-y-4 overflow-y-auto p-4 focus-visible:outline-none">
                    <div class="bg-muted/30 flex items-center justify-between rounded-lg border p-3">
                      <div class="flex items-center gap-2">
                        <span class="relative flex size-2.5">
                          <span class="bg-success absolute inline-flex h-full w-full rounded-full opacity-75"></span>
                          <span class="bg-success relative inline-flex size-2.5 rounded-full"></span>
                        </span>
                        <div>
                          <h4 class="text-foreground text-xs font-bold">Live Telemetry Feed</h4>
                          <p class="text-muted-foreground text-xs">BLE Medical Hub · Updated {{ lastVitalsUpdate() }}</p>
                        </div>
                      </div>
                      <button
                        ui-button
                        variant="outline"
                        size="sm"
                        class="h-7 gap-1.5 text-xs"
                        [disabled]="isRefreshingVitals()"
                        (click)="handleRefreshVitals()"
                      >
                        <lucide-icon [img]="RefreshCw" [class]="'size-3' + (isRefreshingVitals() ? ' animate-spin' : '')" />
                        <span>Refresh</span>
                      </button>
                    </div>
    
                    <div class="grid grid-cols-2 gap-3">
                      <!-- Blood Pressure -->
                      <div ui-card class="space-y-2 p-3">
                        <div class="flex items-center justify-between">
                          <span class="text-muted-foreground text-xs font-semibold">Blood Pressure</span>
                          <span ui-badge variant="outline" class="border-success/40 bg-success/10 text-success text-xs">
                            Normal
                          </span>
                        </div>
                        <div>
                          <div class="flex items-baseline gap-1">
                            <span class="text-foreground font-mono text-xl font-bold tracking-tight">128/82</span>
                            <span class="text-muted-foreground text-xs">mmHg</span>
                          </div>
                          <p class="text-muted-foreground mt-0.5 text-xs">Target: &lt;130/85 mmHg</p>
                        </div>
                        <div class="text-muted-foreground flex items-center justify-between border-t pt-1.5 text-xs">
                          <span>Smart Cuff</span>
                          <span class="text-foreground font-medium">MAP: 97</span>
                        </div>
                      </div>
    
                      <!-- Heart Rate -->
                      <div ui-card class="space-y-2 p-3">
                        <div class="flex items-center justify-between">
                          <span class="text-muted-foreground flex items-center gap-1 text-xs font-semibold">
                            <lucide-icon [img]="Heart" class="text-destructive size-3" />
                            Heart Rate
                          </span>
                          <span ui-badge variant="outline" class="border-success/40 bg-success/10 text-success text-xs">
                            NSR
                          </span>
                        </div>
                        <div>
                          <div class="flex items-baseline gap-1">
                            <span class="text-foreground font-mono text-xl font-bold tracking-tight">74</span>
                            <span class="text-muted-foreground text-xs">bpm</span>
                          </div>
                          <p class="text-muted-foreground mt-0.5 text-xs">Resting (Range: 68-88)</p>
                        </div>
                        <div class="text-muted-foreground flex items-center justify-between border-t pt-1.5 text-xs">
                          <span>Rhythm: Sinus</span>
                          <span class="text-success font-medium">Regular</span>
                        </div>
                      </div>
    
                      <!-- Oxygen Saturation (SpO2) -->
                      <div ui-card class="space-y-2 p-3">
                        <div class="flex items-center justify-between">
                          <span class="text-muted-foreground text-xs font-semibold">Oxygen (SpO2)</span>
                          <span ui-badge variant="outline" class="border-success/40 bg-success/10 text-success text-xs">
                            Optimal
                          </span>
                        </div>
                        <div>
                          <div class="flex items-baseline gap-1">
                            <span class="text-foreground font-mono text-xl font-bold tracking-tight">98%</span>
                            <span class="text-muted-foreground text-xs">Ambient</span>
                          </div>
                          <p class="text-muted-foreground mt-0.5 text-xs">Room Air (FiO2 21%)</p>
                        </div>
                        <div class="text-muted-foreground flex items-center justify-between border-t pt-1.5 text-xs">
                          <span>PulseOx Probe</span>
                          <span class="text-foreground font-medium">PI: 4.8%</span>
                        </div>
                      </div>
    
                      <!-- Body Temperature -->
                      <div ui-card class="space-y-2 p-3">
                        <div class="flex items-center justify-between">
                          <span class="text-muted-foreground flex items-center gap-1 text-xs font-semibold">
                            <lucide-icon [img]="Thermometer" class="text-warning size-3" />
                            Temperature
                          </span>
                          <span ui-badge variant="secondary" class="text-xs"> Afebrile </span>
                        </div>
                        <div>
                          <div class="flex items-baseline gap-1">
                            <span class="text-foreground font-mono text-xl font-bold tracking-tight">98.4°</span>
                            <span class="text-muted-foreground text-xs">F</span>
                          </div>
                          <p class="text-muted-foreground mt-0.5 text-xs">36.9°C (Temporal Scan)</p>
                        </div>
                        <div class="text-muted-foreground flex items-center justify-between border-t pt-1.5 text-xs">
                          <span>Scanner Hub</span>
                          <span class="text-foreground font-medium">Normal</span>
                        </div>
                      </div>
    
                      <!-- Respiration Rate -->
                      <div ui-card class="space-y-2 p-3">
                        <div class="flex items-center justify-between">
                          <span class="text-muted-foreground text-xs font-semibold">Respiration</span>
                          <span ui-badge variant="secondary" class="text-xs"> Eupneic </span>
                        </div>
                        <div>
                          <div class="flex items-baseline gap-1">
                            <span class="text-foreground font-mono text-xl font-bold tracking-tight">16</span>
                            <span class="text-muted-foreground text-xs">br/min</span>
                          </div>
                          <p class="text-muted-foreground mt-0.5 text-xs">Range: 12-20 normal</p>
                        </div>
                        <div class="text-muted-foreground flex items-center justify-between border-t pt-1.5 text-xs">
                          <span>Chest Sensor</span>
                          <span class="text-foreground font-medium">Steady</span>
                        </div>
                      </div>
    
                      <!-- Fasting Blood Glucose -->
                      <div ui-card class="space-y-2 p-3">
                        <div class="flex items-center justify-between">
                          <span class="text-muted-foreground text-xs font-semibold">Glucose</span>
                          <span ui-badge variant="outline" class="border-success/40 bg-success/10 text-success text-xs">
                            Normal
                          </span>
                        </div>
                        <div>
                          <div class="flex items-baseline gap-1">
                            <span class="text-foreground font-mono text-xl font-bold tracking-tight">94</span>
                            <span class="text-muted-foreground text-xs">mg/dL</span>
                          </div>
                          <p class="text-muted-foreground mt-0.5 text-xs">Fasting 3.5h post-meal</p>
                        </div>
                        <div class="text-muted-foreground flex items-center justify-between border-t pt-1.5 text-xs">
                          <span>Continuous CGM</span>
                          <span class="text-success font-medium">In Range</span>
                        </div>
                      </div>
                    </div>
    
                    <!-- Physician Vitals Summary -->
                    <div class="bg-muted/20 space-y-1.5 rounded-lg border p-3 text-xs">
                      <div class="text-foreground flex items-center gap-1.5 font-semibold">
                        <lucide-icon [img]="ShieldCheck" class="text-success size-3.5" />
                        <span>Physician Baseline Parameter Assessment</span>
                      </div>
                      <p class="text-muted-foreground leading-relaxed">
                        All live telemetry telemetry values remain within normal hemodynamic thresholds. Patient exhibits no
                        signs of acute decompensation or malignant arrhythmias during the video encounter.
                      </p>
                    </div>
                  </ui-tabs-content>
                </ui-tabs>
              </aside>
            }
          </div>
        </div>
      `,
    })
    export class UiTelehealthVideoRoomComponent {
      protected readonly AlertCircle = AlertCircle
      protected readonly CheckCircle2 = CheckCircle2
      protected readonly Clock = Clock
      protected readonly Download = Download
      protected readonly FileCheck = FileCheck
      protected readonly FileText = FileText
      protected readonly Heart = Heart
      protected readonly HeartPulse = HeartPulse
      protected readonly Lock = Lock
      protected readonly MessageSquare = MessageSquare
      protected readonly Mic = Mic
      protected readonly MicOff = MicOff
      protected readonly PanelRightClose = PanelRightClose
      protected readonly PanelRightOpen = PanelRightOpen
      protected readonly Paperclip = Paperclip
      protected readonly PhoneOff = PhoneOff
      protected readonly Pill = Pill
      protected readonly RefreshCw = RefreshCw
      protected readonly Send = Send
      protected readonly Settings = Settings
      protected readonly Share2 = Share2
      protected readonly ShieldCheck = ShieldCheck
      protected readonly Thermometer = Thermometer
      protected readonly Video = Video
      protected readonly VideoOff = VideoOff
      protected readonly Wifi = Wifi
      protected readonly X = X
    
      protected readonly drugOptions = drugOptions
    
      @Input('class') className?: string
    
      // Interactive State
      readonly isMuted = signal(false)
      readonly isCameraOff = signal(false)
      readonly isScreenSharing = signal(false)
      readonly isSidebarOpen = signal(true)
      readonly activeTab = signal<TelehealthTab>('notes')
      readonly isCallEnded = signal(false)
      readonly showSettingsModal = signal(false)
      readonly callDurationSeconds = signal(868) // 14:28
      readonly unreadChatCount = signal(0)
      readonly lastVitalsUpdate = signal('Just now')
      readonly isRefreshingVitals = signal(false)
    
      // SOAP Notes State
      readonly chiefComplaint = signal(
        'Recurrent palpitations, sudden resting tachycardia (120-140 bpm), and lightheadedness over the past 2 weeks following morning coffee.',
      )
      readonly soapSubjective = signal(
        'Patient reports 4 distinct episodes in past 14 days. Episodes last ~5-10 minutes with abrupt onset and offset. No syncope, mild lightheadedness. Denies chest pressure or radiating pain. Reports high work stress and 3-4 espresso shots daily.',
      )
      readonly soapObjective = signal(
        'In-call vitals: HR 74 bpm regular sinus, BP 128/82 mmHg, SpO2 98%. Telemetry strip transmitted via Smart Hub shows NSR with infrequent PACs. No ST elevation or ischemic changes.',
      )
      readonly soapAssessment = signal(
        '1. Paroxysmal Supraventricular Tachycardia (PSVT) vs. Benign Premature Atrial Contractions (PACs) secondary to high caffeine consumption and sympathetic stress.\n2. Stage 1 Essential Hypertension - well controlled on current regimen.',
      )
      readonly soapPlan = signal(
        '1. Ordered 14-day continuous Holter monitor patch.\n2. Titrate Metoprolol Succinate ER to 25mg PO daily.\n3. Restrict caffeine to ≤1 cup/day; hydration goal 2.5L daily.\n4. Follow-up video consultation in 3 weeks or ASAP if syncope occurs.',
      )
    
      // Prescription Generator State
      readonly selectedDrugPreset = signal('metoprolol')
      readonly customDosage = signal('25mg PO Daily in Morning')
      readonly customQuantity = signal('30 tablets')
      readonly customRefills = signal('2')
      readonly rxSuccessMessage = signal('')
    
      readonly prescriptions = signal<PrescriptionItem[]>([
        {
          id: 'rx-1',
          drugName: 'Metoprolol Succinate ER 25mg',
          dosage: 'Take 1 tablet by mouth daily in the morning',
          sig: '1 tab PO QAM',
          quantity: '30 tablets',
          refills: 2,
          pharmacy: 'CVS Pharmacy #4021 (Austin, TX)',
          timestamp: '10:24 AM',
          status: 'transmitted',
          rxNumber: 'RX-994182',
        },
      ])
    
      // Chat Thread State
      readonly chatInput = signal('')
      readonly chatMessages = signal<ChatMessage[]>([
        {
          id: 'msg-1',
          sender: 'doctor',
          senderName: 'Dr. Sarah Jenkins, MD',
          text: 'Hello David! I have your chart open and we are receiving your continuous telemetry stream. How are you feeling right now?',
          timestamp: '10:15 AM',
        },
        {
          id: 'msg-2',
          sender: 'patient',
          senderName: 'David Chen (You)',
          text: 'Good morning Dr. Jenkins. Feeling better than yesterday, but had another fluttering sensation around 8 AM after my second cup of coffee.',
          timestamp: '10:17 AM',
        },
        {
          id: 'msg-3',
          sender: 'patient',
          senderName: 'David Chen (You)',
          text: 'I captured the rhythm episode on my smart watch and exported the 30-second single lead PDF strip here.',
          timestamp: '10:18 AM',
          attachment: {
            name: 'Apple_Watch_ECG_Lead_I_2026.pdf',
            size: '1.4 MB · PDF ECG Report',
            type: 'pdf',
          },
        },
        {
          id: 'msg-4',
          sender: 'doctor',
          senderName: 'Dr. Sarah Jenkins, MD',
          text: 'Thank you, David! Reviewing the strip now. The baseline is normal sinus rhythm with occasional isolated PACs. This matches our telemetry.',
          timestamp: '10:20 AM',
        },
      ])
    
      private rxTimer: ReturnType<typeof setTimeout> | undefined
      private chatTimer: ReturnType<typeof setTimeout> | undefined
      private vitalsTimer: ReturnType<typeof setTimeout> | undefined
    
      constructor() {
        const destroyRef = inject(DestroyRef)
        const interval = setInterval(() => {
          if (!this.isCallEnded()) this.callDurationSeconds.update((v) => v + 1)
        }, 1000)
        destroyRef.onDestroy(() => {
          clearInterval(interval)
          clearTimeout(this.rxTimer)
          clearTimeout(this.chatTimer)
          clearTimeout(this.vitalsTimer)
        })
      }
    
      readonly formattedDuration = computed(() => {
        const total = this.callDurationSeconds()
        const mins = Math.floor(total / 60)
        const secs = total % 60
        return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
      })
    
      get rootClass(): string {
        return cn('bg-background text-foreground w-full space-y-4', this.className)
      }
    
      get canvasClass(): string {
        return cn(
          'relative flex flex-col justify-between overflow-hidden rounded-xl border border-zinc-800 bg-zinc-950 p-4 text-white shadow-lg transition-colors duration-200',
          this.isSidebarOpen() ? 'min-h-[560px] lg:col-span-8 xl:col-span-8' : 'min-h-[600px] lg:col-span-12',
        )
      }
    
      setActiveTab(value: string): void {
        this.activeTab.set(value as TelehealthTab)
      }
    
      handleOpenTab(tab: TelehealthTab): void {
        this.activeTab.set(tab)
        this.isSidebarOpen.set(true)
      }
    
      handleEndCall(): void {
        this.isCallEnded.set(true)
      }
    
      handleRestartCall(): void {
        this.isCallEnded.set(false)
        this.callDurationSeconds.set(0)
      }
    
      handleRefreshVitals(): void {
        this.isRefreshingVitals.set(true)
        clearTimeout(this.vitalsTimer)
        this.vitalsTimer = setTimeout(() => {
          this.isRefreshingVitals.set(false)
          this.lastVitalsUpdate.set('Just now')
        }, 800)
      }
    
      handleSelectDrugPreset(drugId: string): void {
        this.selectedDrugPreset.set(drugId)
        const found = drugOptions.find((d) => d.id === drugId)
        if (found) {
          this.customDosage.set(found.defaultDosage)
          this.customQuantity.set(found.defaultQty)
          this.customRefills.set(found.refills)
        }
      }
    
      handleGeneratePrescription(): void {
        const current = drugOptions.find((d) => d.id === this.selectedDrugPreset())
        const drugName = current ? current.name : 'Custom Medication'
        const dosage = this.customDosage()
        const newRx: PrescriptionItem = {
          id: `rx-${Date.now()}`,
          drugName: `${drugName} (${dosage})`,
          dosage,
          sig: `Take as directed: ${dosage}`,
          quantity: this.customQuantity(),
          refills: parseInt(this.customRefills(), 10) || 1,
          pharmacy: 'CVS Pharmacy #4021 (Austin, TX)',
          timestamp: 'Just now',
          status: 'transmitted',
          rxNumber: `RX-${Math.floor(100000 + Math.random() * 900000)}`,
        }
        this.prescriptions.update((prev) => [newRx, ...prev])
        this.rxSuccessMessage.set(`e-Rx signed & securely transmitted to CVS Pharmacy #4021 (${newRx.rxNumber})`)
        clearTimeout(this.rxTimer)
        this.rxTimer = setTimeout(() => this.rxSuccessMessage.set(''), 4500)
      }
    
      handleSendMessage(): void {
        const text = this.chatInput().trim()
        if (!text) return
        this.chatMessages.update((prev) => [
          ...prev,
          {
            id: `msg-${Date.now()}`,
            sender: 'patient',
            senderName: 'David Chen (You)',
            text,
            timestamp: 'Just now',
          },
        ])
        this.chatInput.set('')
    
        // Simulated provider automated acknowledgment if consultation active
        clearTimeout(this.chatTimer)
        this.chatTimer = setTimeout(() => {
          if (!this.isCallEnded()) {
            this.chatMessages.update((prev) => [
              ...prev,
              {
                id: `msg-doc-${Date.now()}`,
                sender: 'doctor',
                senderName: 'Dr. Sarah Jenkins, MD',
                text: 'Noted! I have added this observation into your encounter notes and prescription schedule.',
                timestamp: 'Just now',
              },
            ])
          }
        }, 1200)
      }
    }
    
  • src/app/components/blocks/telehealth-video-room/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/telehealth-video-room.json