@edvak99/voice-receptionist-widget 1.1.21 → 1.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-dcDXT2KG.mjs +533 -0
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-dcDXT2KG.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-analytics-page.component-CmImX5FN.mjs → edvak99-voice-receptionist-widget-analytics-page.component-B25yZJZz.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-analytics-page.component-CmImX5FN.mjs.map → edvak99-voice-receptionist-widget-analytics-page.component-B25yZJZz.mjs.map} +1 -1
- package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-Df-dtgR3.mjs +96 -0
- package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-Df-dtgR3.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-call-history-detail-page.component-BT4clUO-.mjs → edvak99-voice-receptionist-widget-call-history-detail-page.component-BJGzgVxG.mjs} +21 -10
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-BJGzgVxG.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-CcwwO5SO.mjs +254 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-CcwwO5SO.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-4fL-zUWy.mjs → edvak99-voice-receptionist-widget-call-recording-player.component-BepxNLNQ.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-4fL-zUWy.mjs.map → edvak99-voice-receptionist-widget-call-recording-player.component-BepxNLNQ.mjs.map} +1 -1
- package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-BhbCmB1S.mjs → edvak99-voice-receptionist-widget-call-review.service-nFqc30t3.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-BhbCmB1S.mjs.map → edvak99-voice-receptionist-widget-call-review.service-nFqc30t3.mjs.map} +1 -1
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-CMgOu0aw.mjs +311 -0
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-CMgOu0aw.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DDS7VdnD.mjs → edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-CJ1L6QeE.mjs} +54 -25
- package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-CJ1L6QeE.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-live-calls-page.component-4LtgT4lS.mjs → edvak99-voice-receptionist-widget-live-calls-page.component-MnBMu8iA.mjs} +4 -4
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-MnBMu8iA.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-live-monitor-page.component-B1lKfw-j.mjs → edvak99-voice-receptionist-widget-live-monitor-page.component-BzvMkbC4.mjs} +10 -7
- package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-BzvMkbC4.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-Aws6TaaE.mjs +273 -0
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-Aws6TaaE.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-task-detail-drawer.component-aDTKTV79.mjs → edvak99-voice-receptionist-widget-task-detail-drawer.component-DVWdArqj.mjs} +14 -6
- package/fesm2022/edvak99-voice-receptionist-widget-task-detail-drawer.component-DVWdArqj.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-tasks-page.component-Dj1nWx6V.mjs → edvak99-voice-receptionist-widget-tasks-page.component-CBDmGLol.mjs} +11 -7
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-CBDmGLol.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-wizard-page.component-CQttbEzW.mjs → edvak99-voice-receptionist-widget-wizard-page.component-C-hdBQIV.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-wizard-page.component-CQttbEzW.mjs.map → edvak99-voice-receptionist-widget-wizard-page.component-C-hdBQIV.mjs.map} +1 -1
- package/fesm2022/edvak99-voice-receptionist-widget.mjs +1 -1
- package/lib/components/call-tasks-panel/call-tasks-panel.component.d.ts +3 -0
- package/lib/components/patient-profile-drawer/patient-profile-drawer.component.d.ts +3 -0
- package/lib/components/task-detail-drawer/task-detail-drawer.component.d.ts +3 -0
- package/lib/pages/activities-page/activities-page.component.d.ts +5 -2
- package/lib/pages/audit-page/audit-page.component.d.ts +3 -0
- package/lib/pages/call-history-detail-page/call-history-detail-page.component.d.ts +2 -0
- package/lib/pages/call-history-page/call-history-page.component.d.ts +1 -0
- package/lib/pages/dashboard-page/dashboard-page.component.d.ts +4 -0
- package/lib/pages/live-monitor-page/live-monitor-page.component.d.ts +2 -0
- package/lib/pages/outbound-purposes-page/outbound-purposes-page.component.d.ts +3 -0
- package/lib/pages/settings-page/settings-page.component.d.ts +1 -0
- package/lib/pages/tasks-page/tasks-page.component.d.ts +2 -0
- package/lib/services/ai-receptionist-api.service.d.ts +1 -0
- package/package.json +1 -1
- package/src/assets/css/_tables.scss +12 -0
- package/styles/embed.scss +27 -0
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-CpXa1J9-.mjs +0 -510
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-CpXa1J9-.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-DTAB2WTX.mjs +0 -78
- package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-DTAB2WTX.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-BT4clUO-.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-BACON4uq.mjs +0 -247
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-BACON4uq.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-CxNYLBbg.mjs +0 -291
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-CxNYLBbg.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DDS7VdnD.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-4LtgT4lS.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-B1lKfw-j.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-BkIHblSu.mjs +0 -269
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-BkIHblSu.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-task-detail-drawer.component-aDTKTV79.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-Dj1nWx6V.mjs.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, DestroyRef, EventEmitter, signal, Output, Input, ChangeDetectionStrategy, Component, computed, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
3
3
|
import { Router, ActivatedRoute } from '@angular/router';
|
|
4
|
-
import { A as AiReceptionistApiService, T as ToastService,
|
|
4
|
+
import { A as AiReceptionistApiService, T as ToastService, j as toE164Phone, M as ModalComponent, f as EdButtonComponent, g as EdSkeletonComponent, L as LiveCallsListService, a as AgentOnlineService, i as isUnknownCaller, c as callerMetaLine, b as ageFromDob, E as EdAvatarComponent, I as IconComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-CJ1L6QeE.mjs';
|
|
5
5
|
import { s as sentimentIconSrc, a as sentimentLabel, E as EdsElapsedPipe } from './edvak99-voice-receptionist-widget-eds-elapsed.pipe-Dra7e7xN.mjs';
|
|
6
6
|
import { DatePipe } from '@angular/common';
|
|
7
7
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
@@ -314,11 +314,11 @@ class OutboundDialModalComponent {
|
|
|
314
314
|
});
|
|
315
315
|
}
|
|
316
316
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: OutboundDialModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
317
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: OutboundDialModalComponent, isStandalone: true, selector: "eds-outbound-dial-modal", inputs: { open: "open" }, outputs: { closed: "closed", dialed: "dialed" }, usesOnChanges: true, ngImport: i0, template: "@if (open) {\n <app-modal\n title=\"Outbound call\"\n subtitle=\"Search a patient, pick an appointment, then dial\"\n maxWidth=\"600px\"\n (closed)=\"close()\"\n >\n @if (!loading()) {\n <div modalStickyTop class=\"border-b border-base bg-white px-6 pb-3 pt-5\">\n <div class=\"dial-form__field dial-form__typeahead relative\">\n <label>Patient<span required>*</span></label>\n\n @if (selectedPatient(); as patient) {\n <div class=\"flex items-center justify-between gap-3 rounded-lg border border-base bg-lighter px-3 py-3\">\n <div class=\"flex min-w-0 flex-col gap-1\">\n <strong>{{ patient.fullName }}</strong>\n <span class=\"dial-form__chip-meta text-xs\">\n @if (patient.phone) {\n {{ patient.phone }}\n }\n @if (patient.dob) {\n \u00B7 DOB {{ patient.dob }}\n }\n </span>\n </div>\n <button type=\"button\" class=\"dial-form__chip-clear shrink-0 cursor-pointer border-none bg-transparent p-1 text-[13px] font-[inherit]\" (click)=\"clearPatient()\">\n Change\n </button>\n </div>\n } @else {\n <eds-search-field\n inputClass=\"form-input\"\n [value]=\"patientQuery()\"\n (valueChange)=\"onPatientQueryChange($event)\"\n placeholder=\"Search by name or phone\"\n />\n @if (searchingPatients()) {\n <span class=\"dial-form__hint text-xs\">Searching\u2026</span>\n }\n @if (patientHits().length) {\n <ul class=\"dial-form__hits absolute left-0 right-0 top-[calc(100%+4px)] z-[5] max-h-[220px] overflow-hidden overflow-y-auto rounded-lg border\" role=\"listbox\">\n @for (hit of patientHits(); track hit.patientId) {\n <li>\n <button\n type=\"button\"\n class=\"dial-form__hit flex w-full cursor-pointer flex-col items-start gap-1 border-none border-b px-3 py-3 text-left font-[inherit] last:border-b-0\"\n role=\"option\"\n (click)=\"selectPatient(hit)\"\n >\n <span class=\"font-semibold\">{{ hit.fullName }}</span>\n <span class=\"dial-form__hit-meta text-xs\">\n {{ hit.phone || 'No phone' }}\n @if (hit.dob) {\n \u00B7 {{ hit.dob }}\n }\n </span>\n </button>\n </li>\n }\n </ul>\n }\n }\n </div>\n </div>\n }\n\n @if (loading()) {\n <div class=\"flex flex-col gap-3\" aria-busy=\"true\">\n @for (_ of formSkeletonRows; track _) {\n <div class=\"grid gap-2xs\">\n <ed-skeleton class=\"h-3 w-20\"></ed-skeleton>\n <ed-skeleton class=\"h-10 w-full !rounded-md\"></ed-skeleton>\n </div>\n }\n </div>\n } @else {\n <form\n id=\"outbound-dial-form\"\n class=\"flex flex-col gap-3\"\n (ngSubmit)=\"submit()\"\n novalidate\n autocomplete=\"off\"\n >\n @if (selectedPatient()) {\n <div class=\"mb-sm\">\n <label>Appointment</label>\n @if (loadingAppointments()) {\n <ul class=\"max-h-[220px] overflow-hidden overflow-y-auto rounded-lg border\" aria-busy=\"true\">\n @for (_ of appointmentSkeletonRows; track _) {\n <li class=\"flex items-start gap-3 border-b px-3 py-3 last:border-b-0\">\n <ed-skeleton class=\"mt-0.5 h-4 w-4 !rounded-full\"></ed-skeleton>\n <div class=\"grid flex-1 gap-2xs\">\n <ed-skeleton class=\"h-3 w-40\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-56\"></ed-skeleton>\n </div>\n </li>\n }\n </ul>\n } @else if (!appointments().length) {\n <span class=\"dial-form__hint text-xs\">No upcoming appointments \u2014 dial without one, or enter details below.</span>\n } @else {\n <ul class=\"max-h-[220px] overflow-hidden overflow-y-auto rounded-lg border\">\n <li>\n <label class=\"dial-form__appt flex cursor-pointer items-start gap-3 border-b px-3 py-3 last:border-b-0\">\n <input\n type=\"radio\"\n name=\"appointmentId\"\n [checked]=\"!selectedAppointmentId()\"\n (change)=\"selectAppointment(null)\"\n autocomplete=\"off\"\n />\n <span>No appointment (purpose-only)</span>\n </label>\n </li>\n @for (appt of appointments(); track appt.appointmentId) {\n <li>\n <label class=\"dial-form__appt flex cursor-pointer items-start gap-3 border-b px-3 py-3 last:border-b-0\">\n <input\n type=\"radio\"\n name=\"appointmentId\"\n [checked]=\"selectedAppointmentId() === appt.appointmentId\"\n (change)=\"selectAppointment(appt)\"\n autocomplete=\"off\"\n />\n <span class=\"flex flex-col gap-1\">\n <strong>\n {{ appt.startTime ? (appt.startTime | date: 'medium') : 'TBD' }}\n </strong>\n <span class=\"dial-form__hit-meta text-xs\">\n {{ appt.providerName || 'Provider TBD' }}\n @if (appt.locationName) {\n \u00B7 {{ appt.locationName }}\n }\n @if (appt.visitType) {\n \u00B7 {{ appt.visitType }}\n }\n @if (appt.status) {\n \u00B7 {{ appt.status }}\n }\n </span>\n </span>\n </label>\n </li>\n }\n </ul>\n }\n </div>\n }\n\n <button\n type=\"button\"\n class=\"dial-form__toggle self-start cursor-pointer border-none bg-transparent p-0 text-[13px] font-[inherit]\"\n (click)=\"showManualEntry.set(!showManualEntry())\"\n >\n {{ showManualEntry() ? 'Hide' : 'Show' }} phone / name\n @if (!selectedPatient()) {\n (required if no patient selected)\n }\n </button>\n\n @if (showManualEntry() || !selectedPatient()) {\n <div class=\"mb-sm\">\n <label>Phone<span required>*</span></label>\n <input\n class=\"form-input\"\n type=\"tel\"\n [(ngModel)]=\"phone\"\n (ngModelChange)=\"touchForm()\"\n name=\"phone\"\n placeholder=\"+14155550100\"\n autocomplete=\"off\"\n required\n />\n <span class=\"dial-form__hint text-xs\">E.164 format, e.g. +14155550100</span>\n </div>\n\n <div class=\"mb-sm\">\n <label>Patient name<span required>*</span></label>\n <input\n class=\"form-input\"\n type=\"text\"\n [(ngModel)]=\"name\"\n (ngModelChange)=\"touchForm()\"\n name=\"name\"\n placeholder=\"Maria Garcia\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n <div class=\"mb-sm\">\n <label>Purpose<span required>*</span></label>\n <select\n class=\"form-select\"\n [(ngModel)]=\"purposeId\"\n name=\"purposeId\"\n (ngModelChange)=\"onPurposeChange()\"\n required\n autocomplete=\"off\"\n >\n @for (p of purposes(); track p.purposeId) {\n <option [value]=\"p.purposeId\">{{ p.label }}</option>\n }\n </select>\n </div>\n\n @if (showsField('appointmentDate') && !selectedAppointmentId()) {\n <div class=\"mb-sm\">\n <label>{{ requiredFieldLabel('appointmentDate') }}<span required>*</span></label>\n <input\n class=\"form-input\"\n type=\"date\"\n [(ngModel)]=\"appointmentDate\"\n (ngModelChange)=\"touchForm()\"\n name=\"appointmentDate\"\n placeholder=\"Appointment Date\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n @if (showsField('appointmentTime') && !selectedAppointmentId()) {\n <div class=\"mb-sm\">\n <label>{{ requiredFieldLabel('appointmentTime') }}<span required>*</span></label>\n <input\n class=\"form-input\"\n type=\"time\"\n [(ngModel)]=\"appointmentTime\"\n (ngModelChange)=\"touchForm()\"\n name=\"appointmentTime\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n @if (showsField('providerName') && !selectedAppointmentId()) {\n <div class=\"mb-sm\">\n <label>{{ requiredFieldLabel('providerName') }}<span required>*</span></label>\n <input\n class=\"form-input\"\n type=\"text\"\n [(ngModel)]=\"providerName\"\n (ngModelChange)=\"touchForm()\"\n name=\"providerName\"\n placeholder=\"Dr. Patel\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n @if (selectedAppointmentId() && (showsField('appointmentDate') || showsField('appointmentTime') || showsField('providerName'))) {\n <p class=\"dial-form__hint text-xs\">\n Date, time, and provider are taken from the selected appointment\n @if (appointmentDate || appointmentTime || providerName) {\n ({{ appointmentDate }}\n @if (appointmentTime) {\n {{ appointmentTime }}\n }\n @if (providerName) {\n \u00B7 {{ providerName }}\n })\n }.\n </p>\n }\n\n @if (showsCustomScript()) {\n <div class=\"mb-sm\">\n <label>\n Custom script\n @if (customScriptRequired()) {\n <span required>*</span>\n }\n </label>\n <textarea\n class=\"form-textarea\"\n rows=\"4\"\n [(ngModel)]=\"customScript\"\n (ngModelChange)=\"touchForm()\"\n name=\"customScript\"\n [required]=\"customScriptRequired()\"\n placeholder=\"What should the agent say or do?\"\n autocomplete=\"off\"\n ></textarea>\n </div>\n }\n </form>\n }\n\n @if (!loading()) {\n <div modalFooter class=\"border-t border-base bg-white px-6 py-4\">\n <section class=\"dial-form__preview rounded-lg border border-base bg-white p-3 shadow-sm\">\n <h3 class=\"fs-sm font-semibold text-light\">Opening preview</h3>\n <p class=\"dial-form__preview-text mt-2 whitespace-pre-wrap text-sm leading-normal\">{{ previewText }}</p>\n @if (selectedAppointmentId()) {\n <p class=\"dial-form__hint text-xs\">\n Filled from the selected appointment and purpose template. Switch purpose to change the script.\n </p>\n } @else {\n <p class=\"dial-form__hint text-xs\">\n Select an appointment to fill date, time, and provider into this message.\n </p>\n }\n </section>\n\n @if (serverError()) {\n <p class=\"mt-3 m-0 text-[13px] text-[#b91c1c]\" role=\"alert\">{{ serverError() }}</p>\n }\n\n <footer class=\"mt-3 flex justify-end gap-2\">\n <ed-button variant=\"secondary\" outline size=\"sm\" (click$)=\"close()\" [disabled]=\"submitting()\">\n Cancel\n </ed-button>\n <ed-button\n type=\"button\"\n variant=\"primary\"\n fill\n size=\"sm\"\n [disabled]=\"submitting() || !canPlaceCall()\"\n (click$)=\"submit()\"\n >\n {{ submitting() ? 'Dialing\u2026' : 'Place call' }}\n </ed-button>\n </footer>\n </div>\n }\n </app-modal>\n}\n", styles: [".dial-form__hint{color:var(--eds-text-secondary)}.dial-form__chip-clear{color:var(--eds-primary, #2563eb)}.dial-form__chip-meta,.dial-form__hit-meta{color:var(--eds-text-secondary)}.dial-form__hits,.dial-form__appts{list-style:none;margin:0;padding:0;border-color:var(--eds-border-subtle, #e4e4e7)}.dial-form__hits{background:var(--eds-surface, #fff);box-shadow:0 8px 24px #0f172a1f}.dial-form__hit{background:transparent;border-bottom-color:var(--eds-border-subtle, #f1f5f9)}.dial-form__hit:hover{background:var(--eds-surface-muted, #f8fafc)}.dial-form__appt{border-bottom-color:var(--eds-border-subtle, #f1f5f9)}.dial-form__toggle{color:var(--eds-primary, #2563eb)}.dial-form__preview{background:var(--eds-surface-muted, #f8fafc)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: ModalComponent, selector: "app-modal", inputs: ["title", "subtitle", "maxWidth"], outputs: ["closed"] }, { kind: "component", type: EdsSearchFieldComponent, selector: "eds-search-field", inputs: ["value", "inputId", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange", "enter"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "component", type: EdButtonComponent, selector: "ed-button", inputs: ["variant", "size", "disabled", "ghost", "outline", "subtle", "fill", "loading", "block", "elevated", "shape", "type", "href", "routerLink", "rel", "target", "download", "ariaLabel"], outputs: ["click$"] }, { kind: "component", type: EdSkeletonComponent, selector: "ed-skeleton", inputs: ["class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
317
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: OutboundDialModalComponent, isStandalone: true, selector: "eds-outbound-dial-modal", inputs: { open: "open" }, outputs: { closed: "closed", dialed: "dialed" }, usesOnChanges: true, ngImport: i0, template: "@if (open) {\n <app-modal\n title=\"Outbound call\"\n subtitle=\"Search a patient, pick an appointment, then dial\"\n maxWidth=\"600px\"\n (closed)=\"close()\"\n >\n @if (!loading()) {\n <div modalStickyTop class=\"border-b border-base bg-white px-6 pb-3 pt-5\">\n <div class=\"dial-form__field dial-form__typeahead relative\">\n <label>Patient<span required></span></label>\n\n @if (selectedPatient(); as patient) {\n <div class=\"flex items-center justify-between gap-3 rounded-lg border border-base bg-lighter px-3 py-3\">\n <div class=\"flex min-w-0 flex-col gap-1\">\n <strong>{{ patient.fullName }}</strong>\n <span class=\"dial-form__chip-meta text-xs\">\n @if (patient.phone) {\n {{ patient.phone }}\n }\n @if (patient.dob) {\n \u00B7 DOB {{ patient.dob }}\n }\n </span>\n </div>\n <button type=\"button\" class=\"dial-form__chip-clear shrink-0 cursor-pointer border-none bg-transparent p-1 text-[13px] font-[inherit]\" (click)=\"clearPatient()\">\n Change\n </button>\n </div>\n } @else {\n <eds-search-field\n inputClass=\"form-input\"\n [value]=\"patientQuery()\"\n (valueChange)=\"onPatientQueryChange($event)\"\n placeholder=\"Search by name or phone\"\n />\n @if (searchingPatients()) {\n <span class=\"dial-form__hint text-xs\">Searching\u2026</span>\n }\n @if (patientHits().length) {\n <ul class=\"dial-form__hits absolute left-0 right-0 top-[calc(100%+4px)] z-[5] max-h-[220px] overflow-hidden overflow-y-auto rounded-lg border\" role=\"listbox\">\n @for (hit of patientHits(); track hit.patientId) {\n <li>\n <button\n type=\"button\"\n class=\"dial-form__hit flex w-full cursor-pointer flex-col items-start gap-1 border-none border-b px-3 py-3 text-left font-[inherit] last:border-b-0\"\n role=\"option\"\n (click)=\"selectPatient(hit)\"\n >\n <span class=\"font-semibold\">{{ hit.fullName }}</span>\n <span class=\"dial-form__hit-meta text-xs\">\n {{ hit.phone || 'No phone' }}\n @if (hit.dob) {\n \u00B7 {{ hit.dob }}\n }\n </span>\n </button>\n </li>\n }\n </ul>\n }\n }\n </div>\n </div>\n }\n\n @if (loading()) {\n <div class=\"flex flex-col gap-3\" aria-busy=\"true\">\n @for (_ of formSkeletonRows; track _) {\n <div class=\"grid gap-2xs\">\n <ed-skeleton class=\"h-3 w-20\"></ed-skeleton>\n <ed-skeleton class=\"h-10 w-full !rounded-md\"></ed-skeleton>\n </div>\n }\n </div>\n } @else {\n <form\n id=\"outbound-dial-form\"\n class=\"flex flex-col gap-3\"\n (ngSubmit)=\"submit()\"\n novalidate\n autocomplete=\"off\"\n >\n @if (selectedPatient()) {\n <div class=\"mb-sm\">\n <label>Appointment</label>\n @if (loadingAppointments()) {\n <ul class=\"max-h-[220px] overflow-hidden overflow-y-auto rounded-lg border\" aria-busy=\"true\">\n @for (_ of appointmentSkeletonRows; track _) {\n <li class=\"flex items-start gap-3 border-b px-3 py-3 last:border-b-0\">\n <ed-skeleton class=\"mt-0.5 h-4 w-4 !rounded-full\"></ed-skeleton>\n <div class=\"grid flex-1 gap-2xs\">\n <ed-skeleton class=\"h-3 w-40\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-56\"></ed-skeleton>\n </div>\n </li>\n }\n </ul>\n } @else if (!appointments().length) {\n <span class=\"dial-form__hint text-xs\">No upcoming appointments \u2014 dial without one, or enter details below.</span>\n } @else {\n <ul class=\"max-h-[220px] overflow-hidden overflow-y-auto rounded-lg border\">\n <li>\n <label class=\"dial-form__appt flex cursor-pointer items-start gap-3 border-b px-3 py-3 last:border-b-0\">\n <input\n type=\"radio\"\n name=\"appointmentId\"\n [checked]=\"!selectedAppointmentId()\"\n (change)=\"selectAppointment(null)\"\n autocomplete=\"off\"\n />\n <span>No appointment (purpose-only)</span>\n </label>\n </li>\n @for (appt of appointments(); track appt.appointmentId) {\n <li>\n <label class=\"dial-form__appt flex cursor-pointer items-start gap-3 border-b px-3 py-3 last:border-b-0\">\n <input\n type=\"radio\"\n name=\"appointmentId\"\n [checked]=\"selectedAppointmentId() === appt.appointmentId\"\n (change)=\"selectAppointment(appt)\"\n autocomplete=\"off\"\n />\n <span class=\"flex flex-col gap-1\">\n <strong>\n {{ appt.startTime ? (appt.startTime | date: 'medium') : 'TBD' }}\n </strong>\n <span class=\"dial-form__hit-meta text-xs\">\n {{ appt.providerName || 'Provider TBD' }}\n @if (appt.locationName) {\n \u00B7 {{ appt.locationName }}\n }\n @if (appt.visitType) {\n \u00B7 {{ appt.visitType }}\n }\n @if (appt.status) {\n \u00B7 {{ appt.status }}\n }\n </span>\n </span>\n </label>\n </li>\n }\n </ul>\n }\n </div>\n }\n\n <button\n type=\"button\"\n class=\"dial-form__toggle self-start cursor-pointer border-none bg-transparent p-0 text-[13px] font-[inherit]\"\n (click)=\"showManualEntry.set(!showManualEntry())\"\n >\n {{ showManualEntry() ? 'Hide' : 'Show' }} phone / name\n @if (!selectedPatient()) {\n (required if no patient selected)\n }\n </button>\n\n @if (showManualEntry() || !selectedPatient()) {\n <div class=\"mb-sm\">\n <label>Phone<span required></span></label>\n <input\n class=\"form-input\"\n type=\"tel\"\n [(ngModel)]=\"phone\"\n (ngModelChange)=\"touchForm()\"\n name=\"phone\"\n placeholder=\"+14155550100\"\n autocomplete=\"off\"\n required\n />\n <span class=\"dial-form__hint text-xs\">E.164 format, e.g. +14155550100</span>\n </div>\n\n <div class=\"mb-sm\">\n <label>Patient name<span required></span></label>\n <input\n class=\"form-input\"\n type=\"text\"\n [(ngModel)]=\"name\"\n (ngModelChange)=\"touchForm()\"\n name=\"name\"\n placeholder=\"Maria Garcia\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n <div class=\"mb-sm\">\n <label>Purpose<span required></span></label>\n <select\n class=\"form-select\"\n [(ngModel)]=\"purposeId\"\n name=\"purposeId\"\n (ngModelChange)=\"onPurposeChange()\"\n required\n autocomplete=\"off\"\n >\n @for (p of purposes(); track p.purposeId) {\n <option [value]=\"p.purposeId\">{{ p.label }}</option>\n }\n </select>\n </div>\n\n @if (showsField('appointmentDate') && !selectedAppointmentId()) {\n <div class=\"mb-sm\">\n <label>{{ requiredFieldLabel('appointmentDate') }}<span required></span></label>\n <input\n class=\"form-input\"\n type=\"date\"\n [(ngModel)]=\"appointmentDate\"\n (ngModelChange)=\"touchForm()\"\n name=\"appointmentDate\"\n placeholder=\"Appointment Date\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n @if (showsField('appointmentTime') && !selectedAppointmentId()) {\n <div class=\"mb-sm\">\n <label>{{ requiredFieldLabel('appointmentTime') }}<span required></span></label>\n <input\n class=\"form-input\"\n type=\"time\"\n [(ngModel)]=\"appointmentTime\"\n (ngModelChange)=\"touchForm()\"\n name=\"appointmentTime\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n @if (showsField('providerName') && !selectedAppointmentId()) {\n <div class=\"mb-sm\">\n <label>{{ requiredFieldLabel('providerName') }}<span required></span></label>\n <input\n class=\"form-input\"\n type=\"text\"\n [(ngModel)]=\"providerName\"\n (ngModelChange)=\"touchForm()\"\n name=\"providerName\"\n placeholder=\"Dr. Patel\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n @if (selectedAppointmentId() && (showsField('appointmentDate') || showsField('appointmentTime') || showsField('providerName'))) {\n <p class=\"dial-form__hint text-xs\">\n Date, time, and provider are taken from the selected appointment\n @if (appointmentDate || appointmentTime || providerName) {\n ({{ appointmentDate }}\n @if (appointmentTime) {\n {{ appointmentTime }}\n }\n @if (providerName) {\n \u00B7 {{ providerName }}\n })\n }.\n </p>\n }\n\n @if (showsCustomScript()) {\n <div class=\"mb-sm\">\n <label>\n Custom script\n @if (customScriptRequired()) {\n <span required></span>\n }\n </label>\n <textarea\n class=\"form-textarea\"\n rows=\"4\"\n [(ngModel)]=\"customScript\"\n (ngModelChange)=\"touchForm()\"\n name=\"customScript\"\n [required]=\"customScriptRequired()\"\n placeholder=\"What should the agent say or do?\"\n autocomplete=\"off\"\n ></textarea>\n </div>\n }\n </form>\n }\n\n @if (!loading()) {\n <div modalFooter class=\"border-t border-base bg-white px-6 py-4\">\n <section class=\"dial-form__preview rounded-lg border border-base bg-white p-3 shadow-sm\">\n <h3 class=\"fs-sm font-semibold text-light\">Opening preview</h3>\n <p class=\"dial-form__preview-text mt-2 whitespace-pre-wrap text-sm leading-normal\">{{ previewText }}</p>\n @if (selectedAppointmentId()) {\n <p class=\"dial-form__hint text-xs\">\n Filled from the selected appointment and purpose template. Switch purpose to change the script.\n </p>\n } @else {\n <p class=\"dial-form__hint text-xs\">\n Select an appointment to fill date, time, and provider into this message.\n </p>\n }\n </section>\n\n @if (serverError()) {\n <p class=\"mt-3 m-0 text-[13px] text-[#b91c1c]\" role=\"alert\">{{ serverError() }}</p>\n }\n\n <footer class=\"mt-3 flex justify-end gap-2\">\n <ed-button variant=\"secondary\" outline size=\"sm\" (click$)=\"close()\" [disabled]=\"submitting()\">\n Cancel\n </ed-button>\n <ed-button\n type=\"button\"\n variant=\"primary\"\n fill\n size=\"sm\"\n [disabled]=\"submitting() || !canPlaceCall()\"\n (click$)=\"submit()\"\n >\n {{ submitting() ? 'Dialing\u2026' : 'Place call' }}\n </ed-button>\n </footer>\n </div>\n }\n </app-modal>\n}\n", styles: [".dial-form__hint{color:var(--eds-text-secondary)}.dial-form__chip-clear{color:var(--eds-primary, #2563eb)}.dial-form__chip-meta,.dial-form__hit-meta{color:var(--eds-text-secondary)}.dial-form__hits,.dial-form__appts{list-style:none;margin:0;padding:0;border-color:var(--eds-border-subtle, #e4e4e7)}.dial-form__hits{background:var(--eds-surface, #fff);box-shadow:0 8px 24px #0f172a1f}.dial-form__hit{background:transparent;border-bottom-color:var(--eds-border-subtle, #f1f5f9)}.dial-form__hit:hover{background:var(--eds-surface-muted, #f8fafc)}.dial-form__appt{border-bottom-color:var(--eds-border-subtle, #f1f5f9)}.dial-form__toggle{color:var(--eds-primary, #2563eb)}.dial-form__preview{background:var(--eds-surface-muted, #f8fafc)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: ModalComponent, selector: "app-modal", inputs: ["title", "subtitle", "maxWidth"], outputs: ["closed"] }, { kind: "component", type: EdsSearchFieldComponent, selector: "eds-search-field", inputs: ["value", "inputId", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange", "enter"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "component", type: EdButtonComponent, selector: "ed-button", inputs: ["variant", "size", "disabled", "ghost", "outline", "subtle", "fill", "loading", "block", "elevated", "shape", "type", "href", "routerLink", "rel", "target", "download", "ariaLabel"], outputs: ["click$"] }, { kind: "component", type: EdSkeletonComponent, selector: "ed-skeleton", inputs: ["class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
318
318
|
}
|
|
319
319
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: OutboundDialModalComponent, decorators: [{
|
|
320
320
|
type: Component,
|
|
321
|
-
args: [{ selector: 'eds-outbound-dial-modal', imports: [FormsModule, ModalComponent, EdsSearchFieldComponent, DatePipe, EdButtonComponent, EdSkeletonComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (open) {\n <app-modal\n title=\"Outbound call\"\n subtitle=\"Search a patient, pick an appointment, then dial\"\n maxWidth=\"600px\"\n (closed)=\"close()\"\n >\n @if (!loading()) {\n <div modalStickyTop class=\"border-b border-base bg-white px-6 pb-3 pt-5\">\n <div class=\"dial-form__field dial-form__typeahead relative\">\n <label>Patient<span required>*</span></label>\n\n @if (selectedPatient(); as patient) {\n <div class=\"flex items-center justify-between gap-3 rounded-lg border border-base bg-lighter px-3 py-3\">\n <div class=\"flex min-w-0 flex-col gap-1\">\n <strong>{{ patient.fullName }}</strong>\n <span class=\"dial-form__chip-meta text-xs\">\n @if (patient.phone) {\n {{ patient.phone }}\n }\n @if (patient.dob) {\n \u00B7 DOB {{ patient.dob }}\n }\n </span>\n </div>\n <button type=\"button\" class=\"dial-form__chip-clear shrink-0 cursor-pointer border-none bg-transparent p-1 text-[13px] font-[inherit]\" (click)=\"clearPatient()\">\n Change\n </button>\n </div>\n } @else {\n <eds-search-field\n inputClass=\"form-input\"\n [value]=\"patientQuery()\"\n (valueChange)=\"onPatientQueryChange($event)\"\n placeholder=\"Search by name or phone\"\n />\n @if (searchingPatients()) {\n <span class=\"dial-form__hint text-xs\">Searching\u2026</span>\n }\n @if (patientHits().length) {\n <ul class=\"dial-form__hits absolute left-0 right-0 top-[calc(100%+4px)] z-[5] max-h-[220px] overflow-hidden overflow-y-auto rounded-lg border\" role=\"listbox\">\n @for (hit of patientHits(); track hit.patientId) {\n <li>\n <button\n type=\"button\"\n class=\"dial-form__hit flex w-full cursor-pointer flex-col items-start gap-1 border-none border-b px-3 py-3 text-left font-[inherit] last:border-b-0\"\n role=\"option\"\n (click)=\"selectPatient(hit)\"\n >\n <span class=\"font-semibold\">{{ hit.fullName }}</span>\n <span class=\"dial-form__hit-meta text-xs\">\n {{ hit.phone || 'No phone' }}\n @if (hit.dob) {\n \u00B7 {{ hit.dob }}\n }\n </span>\n </button>\n </li>\n }\n </ul>\n }\n }\n </div>\n </div>\n }\n\n @if (loading()) {\n <div class=\"flex flex-col gap-3\" aria-busy=\"true\">\n @for (_ of formSkeletonRows; track _) {\n <div class=\"grid gap-2xs\">\n <ed-skeleton class=\"h-3 w-20\"></ed-skeleton>\n <ed-skeleton class=\"h-10 w-full !rounded-md\"></ed-skeleton>\n </div>\n }\n </div>\n } @else {\n <form\n id=\"outbound-dial-form\"\n class=\"flex flex-col gap-3\"\n (ngSubmit)=\"submit()\"\n novalidate\n autocomplete=\"off\"\n >\n @if (selectedPatient()) {\n <div class=\"mb-sm\">\n <label>Appointment</label>\n @if (loadingAppointments()) {\n <ul class=\"max-h-[220px] overflow-hidden overflow-y-auto rounded-lg border\" aria-busy=\"true\">\n @for (_ of appointmentSkeletonRows; track _) {\n <li class=\"flex items-start gap-3 border-b px-3 py-3 last:border-b-0\">\n <ed-skeleton class=\"mt-0.5 h-4 w-4 !rounded-full\"></ed-skeleton>\n <div class=\"grid flex-1 gap-2xs\">\n <ed-skeleton class=\"h-3 w-40\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-56\"></ed-skeleton>\n </div>\n </li>\n }\n </ul>\n } @else if (!appointments().length) {\n <span class=\"dial-form__hint text-xs\">No upcoming appointments \u2014 dial without one, or enter details below.</span>\n } @else {\n <ul class=\"max-h-[220px] overflow-hidden overflow-y-auto rounded-lg border\">\n <li>\n <label class=\"dial-form__appt flex cursor-pointer items-start gap-3 border-b px-3 py-3 last:border-b-0\">\n <input\n type=\"radio\"\n name=\"appointmentId\"\n [checked]=\"!selectedAppointmentId()\"\n (change)=\"selectAppointment(null)\"\n autocomplete=\"off\"\n />\n <span>No appointment (purpose-only)</span>\n </label>\n </li>\n @for (appt of appointments(); track appt.appointmentId) {\n <li>\n <label class=\"dial-form__appt flex cursor-pointer items-start gap-3 border-b px-3 py-3 last:border-b-0\">\n <input\n type=\"radio\"\n name=\"appointmentId\"\n [checked]=\"selectedAppointmentId() === appt.appointmentId\"\n (change)=\"selectAppointment(appt)\"\n autocomplete=\"off\"\n />\n <span class=\"flex flex-col gap-1\">\n <strong>\n {{ appt.startTime ? (appt.startTime | date: 'medium') : 'TBD' }}\n </strong>\n <span class=\"dial-form__hit-meta text-xs\">\n {{ appt.providerName || 'Provider TBD' }}\n @if (appt.locationName) {\n \u00B7 {{ appt.locationName }}\n }\n @if (appt.visitType) {\n \u00B7 {{ appt.visitType }}\n }\n @if (appt.status) {\n \u00B7 {{ appt.status }}\n }\n </span>\n </span>\n </label>\n </li>\n }\n </ul>\n }\n </div>\n }\n\n <button\n type=\"button\"\n class=\"dial-form__toggle self-start cursor-pointer border-none bg-transparent p-0 text-[13px] font-[inherit]\"\n (click)=\"showManualEntry.set(!showManualEntry())\"\n >\n {{ showManualEntry() ? 'Hide' : 'Show' }} phone / name\n @if (!selectedPatient()) {\n (required if no patient selected)\n }\n </button>\n\n @if (showManualEntry() || !selectedPatient()) {\n <div class=\"mb-sm\">\n <label>Phone<span required>*</span></label>\n <input\n class=\"form-input\"\n type=\"tel\"\n [(ngModel)]=\"phone\"\n (ngModelChange)=\"touchForm()\"\n name=\"phone\"\n placeholder=\"+14155550100\"\n autocomplete=\"off\"\n required\n />\n <span class=\"dial-form__hint text-xs\">E.164 format, e.g. +14155550100</span>\n </div>\n\n <div class=\"mb-sm\">\n <label>Patient name<span required>*</span></label>\n <input\n class=\"form-input\"\n type=\"text\"\n [(ngModel)]=\"name\"\n (ngModelChange)=\"touchForm()\"\n name=\"name\"\n placeholder=\"Maria Garcia\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n <div class=\"mb-sm\">\n <label>Purpose<span required>*</span></label>\n <select\n class=\"form-select\"\n [(ngModel)]=\"purposeId\"\n name=\"purposeId\"\n (ngModelChange)=\"onPurposeChange()\"\n required\n autocomplete=\"off\"\n >\n @for (p of purposes(); track p.purposeId) {\n <option [value]=\"p.purposeId\">{{ p.label }}</option>\n }\n </select>\n </div>\n\n @if (showsField('appointmentDate') && !selectedAppointmentId()) {\n <div class=\"mb-sm\">\n <label>{{ requiredFieldLabel('appointmentDate') }}<span required>*</span></label>\n <input\n class=\"form-input\"\n type=\"date\"\n [(ngModel)]=\"appointmentDate\"\n (ngModelChange)=\"touchForm()\"\n name=\"appointmentDate\"\n placeholder=\"Appointment Date\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n @if (showsField('appointmentTime') && !selectedAppointmentId()) {\n <div class=\"mb-sm\">\n <label>{{ requiredFieldLabel('appointmentTime') }}<span required>*</span></label>\n <input\n class=\"form-input\"\n type=\"time\"\n [(ngModel)]=\"appointmentTime\"\n (ngModelChange)=\"touchForm()\"\n name=\"appointmentTime\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n @if (showsField('providerName') && !selectedAppointmentId()) {\n <div class=\"mb-sm\">\n <label>{{ requiredFieldLabel('providerName') }}<span required>*</span></label>\n <input\n class=\"form-input\"\n type=\"text\"\n [(ngModel)]=\"providerName\"\n (ngModelChange)=\"touchForm()\"\n name=\"providerName\"\n placeholder=\"Dr. Patel\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n @if (selectedAppointmentId() && (showsField('appointmentDate') || showsField('appointmentTime') || showsField('providerName'))) {\n <p class=\"dial-form__hint text-xs\">\n Date, time, and provider are taken from the selected appointment\n @if (appointmentDate || appointmentTime || providerName) {\n ({{ appointmentDate }}\n @if (appointmentTime) {\n {{ appointmentTime }}\n }\n @if (providerName) {\n \u00B7 {{ providerName }}\n })\n }.\n </p>\n }\n\n @if (showsCustomScript()) {\n <div class=\"mb-sm\">\n <label>\n Custom script\n @if (customScriptRequired()) {\n <span required>*</span>\n }\n </label>\n <textarea\n class=\"form-textarea\"\n rows=\"4\"\n [(ngModel)]=\"customScript\"\n (ngModelChange)=\"touchForm()\"\n name=\"customScript\"\n [required]=\"customScriptRequired()\"\n placeholder=\"What should the agent say or do?\"\n autocomplete=\"off\"\n ></textarea>\n </div>\n }\n </form>\n }\n\n @if (!loading()) {\n <div modalFooter class=\"border-t border-base bg-white px-6 py-4\">\n <section class=\"dial-form__preview rounded-lg border border-base bg-white p-3 shadow-sm\">\n <h3 class=\"fs-sm font-semibold text-light\">Opening preview</h3>\n <p class=\"dial-form__preview-text mt-2 whitespace-pre-wrap text-sm leading-normal\">{{ previewText }}</p>\n @if (selectedAppointmentId()) {\n <p class=\"dial-form__hint text-xs\">\n Filled from the selected appointment and purpose template. Switch purpose to change the script.\n </p>\n } @else {\n <p class=\"dial-form__hint text-xs\">\n Select an appointment to fill date, time, and provider into this message.\n </p>\n }\n </section>\n\n @if (serverError()) {\n <p class=\"mt-3 m-0 text-[13px] text-[#b91c1c]\" role=\"alert\">{{ serverError() }}</p>\n }\n\n <footer class=\"mt-3 flex justify-end gap-2\">\n <ed-button variant=\"secondary\" outline size=\"sm\" (click$)=\"close()\" [disabled]=\"submitting()\">\n Cancel\n </ed-button>\n <ed-button\n type=\"button\"\n variant=\"primary\"\n fill\n size=\"sm\"\n [disabled]=\"submitting() || !canPlaceCall()\"\n (click$)=\"submit()\"\n >\n {{ submitting() ? 'Dialing\u2026' : 'Place call' }}\n </ed-button>\n </footer>\n </div>\n }\n </app-modal>\n}\n", styles: [".dial-form__hint{color:var(--eds-text-secondary)}.dial-form__chip-clear{color:var(--eds-primary, #2563eb)}.dial-form__chip-meta,.dial-form__hit-meta{color:var(--eds-text-secondary)}.dial-form__hits,.dial-form__appts{list-style:none;margin:0;padding:0;border-color:var(--eds-border-subtle, #e4e4e7)}.dial-form__hits{background:var(--eds-surface, #fff);box-shadow:0 8px 24px #0f172a1f}.dial-form__hit{background:transparent;border-bottom-color:var(--eds-border-subtle, #f1f5f9)}.dial-form__hit:hover{background:var(--eds-surface-muted, #f8fafc)}.dial-form__appt{border-bottom-color:var(--eds-border-subtle, #f1f5f9)}.dial-form__toggle{color:var(--eds-primary, #2563eb)}.dial-form__preview{background:var(--eds-surface-muted, #f8fafc)}\n"] }]
|
|
321
|
+
args: [{ selector: 'eds-outbound-dial-modal', imports: [FormsModule, ModalComponent, EdsSearchFieldComponent, DatePipe, EdButtonComponent, EdSkeletonComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (open) {\n <app-modal\n title=\"Outbound call\"\n subtitle=\"Search a patient, pick an appointment, then dial\"\n maxWidth=\"600px\"\n (closed)=\"close()\"\n >\n @if (!loading()) {\n <div modalStickyTop class=\"border-b border-base bg-white px-6 pb-3 pt-5\">\n <div class=\"dial-form__field dial-form__typeahead relative\">\n <label>Patient<span required></span></label>\n\n @if (selectedPatient(); as patient) {\n <div class=\"flex items-center justify-between gap-3 rounded-lg border border-base bg-lighter px-3 py-3\">\n <div class=\"flex min-w-0 flex-col gap-1\">\n <strong>{{ patient.fullName }}</strong>\n <span class=\"dial-form__chip-meta text-xs\">\n @if (patient.phone) {\n {{ patient.phone }}\n }\n @if (patient.dob) {\n \u00B7 DOB {{ patient.dob }}\n }\n </span>\n </div>\n <button type=\"button\" class=\"dial-form__chip-clear shrink-0 cursor-pointer border-none bg-transparent p-1 text-[13px] font-[inherit]\" (click)=\"clearPatient()\">\n Change\n </button>\n </div>\n } @else {\n <eds-search-field\n inputClass=\"form-input\"\n [value]=\"patientQuery()\"\n (valueChange)=\"onPatientQueryChange($event)\"\n placeholder=\"Search by name or phone\"\n />\n @if (searchingPatients()) {\n <span class=\"dial-form__hint text-xs\">Searching\u2026</span>\n }\n @if (patientHits().length) {\n <ul class=\"dial-form__hits absolute left-0 right-0 top-[calc(100%+4px)] z-[5] max-h-[220px] overflow-hidden overflow-y-auto rounded-lg border\" role=\"listbox\">\n @for (hit of patientHits(); track hit.patientId) {\n <li>\n <button\n type=\"button\"\n class=\"dial-form__hit flex w-full cursor-pointer flex-col items-start gap-1 border-none border-b px-3 py-3 text-left font-[inherit] last:border-b-0\"\n role=\"option\"\n (click)=\"selectPatient(hit)\"\n >\n <span class=\"font-semibold\">{{ hit.fullName }}</span>\n <span class=\"dial-form__hit-meta text-xs\">\n {{ hit.phone || 'No phone' }}\n @if (hit.dob) {\n \u00B7 {{ hit.dob }}\n }\n </span>\n </button>\n </li>\n }\n </ul>\n }\n }\n </div>\n </div>\n }\n\n @if (loading()) {\n <div class=\"flex flex-col gap-3\" aria-busy=\"true\">\n @for (_ of formSkeletonRows; track _) {\n <div class=\"grid gap-2xs\">\n <ed-skeleton class=\"h-3 w-20\"></ed-skeleton>\n <ed-skeleton class=\"h-10 w-full !rounded-md\"></ed-skeleton>\n </div>\n }\n </div>\n } @else {\n <form\n id=\"outbound-dial-form\"\n class=\"flex flex-col gap-3\"\n (ngSubmit)=\"submit()\"\n novalidate\n autocomplete=\"off\"\n >\n @if (selectedPatient()) {\n <div class=\"mb-sm\">\n <label>Appointment</label>\n @if (loadingAppointments()) {\n <ul class=\"max-h-[220px] overflow-hidden overflow-y-auto rounded-lg border\" aria-busy=\"true\">\n @for (_ of appointmentSkeletonRows; track _) {\n <li class=\"flex items-start gap-3 border-b px-3 py-3 last:border-b-0\">\n <ed-skeleton class=\"mt-0.5 h-4 w-4 !rounded-full\"></ed-skeleton>\n <div class=\"grid flex-1 gap-2xs\">\n <ed-skeleton class=\"h-3 w-40\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-56\"></ed-skeleton>\n </div>\n </li>\n }\n </ul>\n } @else if (!appointments().length) {\n <span class=\"dial-form__hint text-xs\">No upcoming appointments \u2014 dial without one, or enter details below.</span>\n } @else {\n <ul class=\"max-h-[220px] overflow-hidden overflow-y-auto rounded-lg border\">\n <li>\n <label class=\"dial-form__appt flex cursor-pointer items-start gap-3 border-b px-3 py-3 last:border-b-0\">\n <input\n type=\"radio\"\n name=\"appointmentId\"\n [checked]=\"!selectedAppointmentId()\"\n (change)=\"selectAppointment(null)\"\n autocomplete=\"off\"\n />\n <span>No appointment (purpose-only)</span>\n </label>\n </li>\n @for (appt of appointments(); track appt.appointmentId) {\n <li>\n <label class=\"dial-form__appt flex cursor-pointer items-start gap-3 border-b px-3 py-3 last:border-b-0\">\n <input\n type=\"radio\"\n name=\"appointmentId\"\n [checked]=\"selectedAppointmentId() === appt.appointmentId\"\n (change)=\"selectAppointment(appt)\"\n autocomplete=\"off\"\n />\n <span class=\"flex flex-col gap-1\">\n <strong>\n {{ appt.startTime ? (appt.startTime | date: 'medium') : 'TBD' }}\n </strong>\n <span class=\"dial-form__hit-meta text-xs\">\n {{ appt.providerName || 'Provider TBD' }}\n @if (appt.locationName) {\n \u00B7 {{ appt.locationName }}\n }\n @if (appt.visitType) {\n \u00B7 {{ appt.visitType }}\n }\n @if (appt.status) {\n \u00B7 {{ appt.status }}\n }\n </span>\n </span>\n </label>\n </li>\n }\n </ul>\n }\n </div>\n }\n\n <button\n type=\"button\"\n class=\"dial-form__toggle self-start cursor-pointer border-none bg-transparent p-0 text-[13px] font-[inherit]\"\n (click)=\"showManualEntry.set(!showManualEntry())\"\n >\n {{ showManualEntry() ? 'Hide' : 'Show' }} phone / name\n @if (!selectedPatient()) {\n (required if no patient selected)\n }\n </button>\n\n @if (showManualEntry() || !selectedPatient()) {\n <div class=\"mb-sm\">\n <label>Phone<span required></span></label>\n <input\n class=\"form-input\"\n type=\"tel\"\n [(ngModel)]=\"phone\"\n (ngModelChange)=\"touchForm()\"\n name=\"phone\"\n placeholder=\"+14155550100\"\n autocomplete=\"off\"\n required\n />\n <span class=\"dial-form__hint text-xs\">E.164 format, e.g. +14155550100</span>\n </div>\n\n <div class=\"mb-sm\">\n <label>Patient name<span required></span></label>\n <input\n class=\"form-input\"\n type=\"text\"\n [(ngModel)]=\"name\"\n (ngModelChange)=\"touchForm()\"\n name=\"name\"\n placeholder=\"Maria Garcia\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n <div class=\"mb-sm\">\n <label>Purpose<span required></span></label>\n <select\n class=\"form-select\"\n [(ngModel)]=\"purposeId\"\n name=\"purposeId\"\n (ngModelChange)=\"onPurposeChange()\"\n required\n autocomplete=\"off\"\n >\n @for (p of purposes(); track p.purposeId) {\n <option [value]=\"p.purposeId\">{{ p.label }}</option>\n }\n </select>\n </div>\n\n @if (showsField('appointmentDate') && !selectedAppointmentId()) {\n <div class=\"mb-sm\">\n <label>{{ requiredFieldLabel('appointmentDate') }}<span required></span></label>\n <input\n class=\"form-input\"\n type=\"date\"\n [(ngModel)]=\"appointmentDate\"\n (ngModelChange)=\"touchForm()\"\n name=\"appointmentDate\"\n placeholder=\"Appointment Date\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n @if (showsField('appointmentTime') && !selectedAppointmentId()) {\n <div class=\"mb-sm\">\n <label>{{ requiredFieldLabel('appointmentTime') }}<span required></span></label>\n <input\n class=\"form-input\"\n type=\"time\"\n [(ngModel)]=\"appointmentTime\"\n (ngModelChange)=\"touchForm()\"\n name=\"appointmentTime\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n @if (showsField('providerName') && !selectedAppointmentId()) {\n <div class=\"mb-sm\">\n <label>{{ requiredFieldLabel('providerName') }}<span required></span></label>\n <input\n class=\"form-input\"\n type=\"text\"\n [(ngModel)]=\"providerName\"\n (ngModelChange)=\"touchForm()\"\n name=\"providerName\"\n placeholder=\"Dr. Patel\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n @if (selectedAppointmentId() && (showsField('appointmentDate') || showsField('appointmentTime') || showsField('providerName'))) {\n <p class=\"dial-form__hint text-xs\">\n Date, time, and provider are taken from the selected appointment\n @if (appointmentDate || appointmentTime || providerName) {\n ({{ appointmentDate }}\n @if (appointmentTime) {\n {{ appointmentTime }}\n }\n @if (providerName) {\n \u00B7 {{ providerName }}\n })\n }.\n </p>\n }\n\n @if (showsCustomScript()) {\n <div class=\"mb-sm\">\n <label>\n Custom script\n @if (customScriptRequired()) {\n <span required></span>\n }\n </label>\n <textarea\n class=\"form-textarea\"\n rows=\"4\"\n [(ngModel)]=\"customScript\"\n (ngModelChange)=\"touchForm()\"\n name=\"customScript\"\n [required]=\"customScriptRequired()\"\n placeholder=\"What should the agent say or do?\"\n autocomplete=\"off\"\n ></textarea>\n </div>\n }\n </form>\n }\n\n @if (!loading()) {\n <div modalFooter class=\"border-t border-base bg-white px-6 py-4\">\n <section class=\"dial-form__preview rounded-lg border border-base bg-white p-3 shadow-sm\">\n <h3 class=\"fs-sm font-semibold text-light\">Opening preview</h3>\n <p class=\"dial-form__preview-text mt-2 whitespace-pre-wrap text-sm leading-normal\">{{ previewText }}</p>\n @if (selectedAppointmentId()) {\n <p class=\"dial-form__hint text-xs\">\n Filled from the selected appointment and purpose template. Switch purpose to change the script.\n </p>\n } @else {\n <p class=\"dial-form__hint text-xs\">\n Select an appointment to fill date, time, and provider into this message.\n </p>\n }\n </section>\n\n @if (serverError()) {\n <p class=\"mt-3 m-0 text-[13px] text-[#b91c1c]\" role=\"alert\">{{ serverError() }}</p>\n }\n\n <footer class=\"mt-3 flex justify-end gap-2\">\n <ed-button variant=\"secondary\" outline size=\"sm\" (click$)=\"close()\" [disabled]=\"submitting()\">\n Cancel\n </ed-button>\n <ed-button\n type=\"button\"\n variant=\"primary\"\n fill\n size=\"sm\"\n [disabled]=\"submitting() || !canPlaceCall()\"\n (click$)=\"submit()\"\n >\n {{ submitting() ? 'Dialing\u2026' : 'Place call' }}\n </ed-button>\n </footer>\n </div>\n }\n </app-modal>\n}\n", styles: [".dial-form__hint{color:var(--eds-text-secondary)}.dial-form__chip-clear{color:var(--eds-primary, #2563eb)}.dial-form__chip-meta,.dial-form__hit-meta{color:var(--eds-text-secondary)}.dial-form__hits,.dial-form__appts{list-style:none;margin:0;padding:0;border-color:var(--eds-border-subtle, #e4e4e7)}.dial-form__hits{background:var(--eds-surface, #fff);box-shadow:0 8px 24px #0f172a1f}.dial-form__hit{background:transparent;border-bottom-color:var(--eds-border-subtle, #f1f5f9)}.dial-form__hit:hover{background:var(--eds-surface-muted, #f8fafc)}.dial-form__appt{border-bottom-color:var(--eds-border-subtle, #f1f5f9)}.dial-form__toggle{color:var(--eds-primary, #2563eb)}.dial-form__preview{background:var(--eds-surface-muted, #f8fafc)}\n"] }]
|
|
322
322
|
}], ctorParameters: () => [], propDecorators: { open: [{
|
|
323
323
|
type: Input
|
|
324
324
|
}], closed: [{
|
|
@@ -434,4 +434,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
|
|
|
434
434
|
}] });
|
|
435
435
|
|
|
436
436
|
export { LiveCallsPageComponent };
|
|
437
|
-
//# sourceMappingURL=edvak99-voice-receptionist-widget-live-calls-page.component-
|
|
437
|
+
//# sourceMappingURL=edvak99-voice-receptionist-widget-live-calls-page.component-MnBMu8iA.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-live-calls-page.component-MnBMu8iA.mjs","sources":["../../../src/lib/components/outbound-dial-modal/outbound-dial-modal.component.ts","../../../src/lib/components/outbound-dial-modal/outbound-dial-modal.component.html","../../../src/lib/pages/live-calls-page/live-calls-page.component.ts","../../../src/lib/pages/live-calls-page/live-calls-page.component.html"],"sourcesContent":["import { DatePipe } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n EventEmitter,\n Input,\n OnChanges,\n Output,\n SimpleChanges,\n inject,\n signal,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { FormsModule } from '@angular/forms';\nimport { Subject, debounceTime, distinctUntilChanged, switchMap, of, catchError } from 'rxjs';\n\nimport { EdButtonComponent } from '../../ui/ed-button.component';\nimport { EdSkeletonComponent } from '../../ui/ed-skeleton.component';\n\nimport {\n OutboundAppointmentHit,\n OutboundPatientHit,\n toE164Phone,\n} from '../../models/outbound-patient.types';\nimport { OutboundPurpose } from '../../models/outbound-purpose.types';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport {\n formatOpeningDate,\n formatOpeningTime,\n isCustomScriptRequired,\n isPurposeFormValid,\n purposeShowsCustomScript,\n renderLiveOpeningPreview,\n requiredFieldLabel,\n} from '../../utils/outbound-purpose.util';\nimport { EdsSearchFieldComponent } from '../../ui/eds-search-field.component';\nimport { ModalComponent } from '../../shared/modal.component';\nimport { ToastService } from '../../shared/toast.service';\n\nconst E164_PATTERN = /^\\+[1-9]\\d{6,14}$/;\n\n@Component({\n selector: 'eds-outbound-dial-modal',\n imports: [FormsModule, ModalComponent, EdsSearchFieldComponent, DatePipe, EdButtonComponent, EdSkeletonComponent],\n templateUrl: './outbound-dial-modal.component.html',\n styleUrl: './outbound-dial-modal.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class OutboundDialModalComponent implements OnChanges {\n private readonly api = inject(AiReceptionistApiService);\n private readonly toast = inject(ToastService);\n private readonly destroyRef = inject(DestroyRef);\n\n @Input() open = false;\n @Output() readonly closed = new EventEmitter<void>();\n @Output() readonly dialed = new EventEmitter<void>();\n\n protected readonly purposes = signal<OutboundPurpose[]>([]);\n protected readonly loading = signal(true);\n protected readonly formSkeletonRows = [0, 1, 2, 3] as const;\n protected readonly appointmentSkeletonRows = [0, 1, 2] as const;\n protected readonly submitting = signal(false);\n protected readonly serverError = signal<string | null>(null);\n\n protected readonly patientHits = signal<OutboundPatientHit[]>([]);\n protected readonly searchingPatients = signal(false);\n protected readonly selectedPatient = signal<OutboundPatientHit | null>(null);\n protected readonly appointments = signal<OutboundAppointmentHit[]>([]);\n protected readonly loadingAppointments = signal(false);\n protected readonly selectedAppointmentId = signal<string | null>(null);\n protected readonly showManualEntry = signal(false);\n protected readonly practiceName = signal('');\n /** Bumps OnPush re-render when plain ngModel fields change. */\n protected readonly formEpoch = signal(0);\n\n protected readonly patientQuery = signal('');\n protected phone = '';\n protected name = '';\n protected purposeId = '';\n protected appointmentDate = '';\n protected appointmentTime = '';\n protected providerName = '';\n protected customScript = '';\n\n protected requiredFieldLabel = requiredFieldLabel;\n\n private readonly search$ = new Subject<string>();\n\n constructor() {\n this.search$\n .pipe(\n debounceTime(280),\n distinctUntilChanged(),\n switchMap((q) => {\n const query = q.trim();\n if (query.length < 2) {\n this.searchingPatients.set(false);\n this.patientHits.set([]);\n return of([] as OutboundPatientHit[]);\n }\n this.searchingPatients.set(true);\n return this.api.searchPatientsForOutbound(query, 10).pipe(\n catchError(() => of([] as OutboundPatientHit[])),\n );\n }),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe((hits) => {\n this.searchingPatients.set(false);\n if (!this.selectedPatient()) {\n this.patientHits.set(hits);\n }\n });\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['open']?.currentValue) {\n this.resetForm();\n this.loadPurposes();\n this.loadPracticeName();\n }\n }\n\n protected get selectedPurpose(): OutboundPurpose | null {\n return this.purposes().find((p) => p.purposeId === this.purposeId) ?? null;\n }\n\n protected get selectedAppointment(): OutboundAppointmentHit | null {\n const id = this.selectedAppointmentId();\n if (!id) return null;\n return this.appointments().find((a) => a.appointmentId === id) ?? null;\n }\n\n protected get previewText(): string {\n this.formEpoch(); // depend for CD when form fields change\n const p = this.selectedPurpose;\n if (!p) return '—';\n const appt = this.selectedAppointment;\n const dateSource = appt?.startTime || this.appointmentDate;\n const timeSource = appt?.startTime || this.appointmentTime;\n return renderLiveOpeningPreview(p, {\n patientName: this.name.trim() || this.selectedPatient()?.fullName || '',\n appointmentDate: formatOpeningDate(dateSource),\n appointmentTime: formatOpeningTime(this.appointmentTime || timeSource, appt?.startTime),\n providerName: this.providerName.trim() || appt?.providerName || '',\n practiceName: this.practiceName(),\n locationName: appt?.locationName || '',\n visitType: appt?.visitType || '',\n customScript: this.customScript.trim(),\n });\n }\n\n protected showsField(field: 'appointmentDate' | 'appointmentTime' | 'providerName'): boolean {\n return this.selectedPurpose?.requiredFields.includes(field) ?? false;\n }\n\n protected showsCustomScript(): boolean {\n const p = this.selectedPurpose;\n return p ? purposeShowsCustomScript(p) : false;\n }\n\n protected customScriptRequired(): boolean {\n const p = this.selectedPurpose;\n return p ? isCustomScriptRequired(p) : false;\n }\n\n protected canPlaceCall(): boolean {\n if (!E164_PATTERN.test(this.phone.trim())) return false;\n return isPurposeFormValid(this.selectedPurpose, {\n phone: this.phone,\n name: this.name,\n appointmentDate: this.appointmentDate,\n appointmentTime: this.appointmentTime,\n providerName: this.providerName,\n customScript: this.customScript,\n });\n }\n\n protected touchForm(): void {\n this.formEpoch.update((n) => n + 1);\n }\n\n protected onPatientQueryChange(value: string): void {\n this.patientQuery.set(value);\n if (this.selectedPatient()) return;\n this.search$.next(value);\n }\n\n protected selectPatient(patient: OutboundPatientHit): void {\n this.selectedPatient.set(patient);\n this.patientHits.set([]);\n this.patientQuery.set(patient.fullName);\n this.name = patient.fullName;\n this.phone = toE164Phone(patient.phone) || '';\n this.showManualEntry.set(!this.phone);\n this.selectedAppointmentId.set(null);\n this.clearApptDerivedFields();\n this.touchForm();\n this.loadAppointments(patient.patientId);\n }\n\n protected clearPatient(): void {\n this.selectedPatient.set(null);\n this.patientQuery.set('');\n this.patientHits.set([]);\n this.appointments.set([]);\n this.selectedAppointmentId.set(null);\n this.name = '';\n this.phone = '';\n this.clearApptDerivedFields();\n this.touchForm();\n }\n\n protected selectAppointment(appt: OutboundAppointmentHit | null): void {\n this.selectedAppointmentId.set(appt?.appointmentId ?? null);\n if (!appt) {\n this.clearApptDerivedFields();\n this.touchForm();\n return;\n }\n this.applyAppointmentFields(appt);\n this.preferAppointmentPurpose();\n this.touchForm();\n }\n\n protected onPurposeChange(): void {\n const appt = this.selectedAppointment;\n if (appt) {\n this.applyAppointmentFields(appt);\n } else {\n this.clearApptDerivedFields();\n }\n this.customScript = '';\n this.serverError.set(null);\n this.touchForm();\n }\n\n protected close(): void {\n this.closed.emit();\n }\n\n protected submit(): void {\n this.serverError.set(null);\n if (!this.canPlaceCall() || !this.selectedPurpose) return;\n\n this.submitting.set(true);\n const patient = this.selectedPatient();\n const apptId = this.selectedAppointmentId();\n const body = {\n phone: this.phone.trim(),\n name: this.name.trim(),\n purpose: this.purposeId,\n ...(this.appointmentDate ? { appointmentDate: this.appointmentDate } : {}),\n ...(this.appointmentTime ? { appointmentTime: this.appointmentTime } : {}),\n ...(this.providerName ? { providerName: this.providerName.trim() } : {}),\n ...(this.customScript ? { customScript: this.customScript.trim() } : {}),\n ...(patient?.patientId ? { patientId: patient.patientId } : {}),\n ...(apptId ? { appointmentId: apptId } : {}),\n };\n\n this.api.placeOutboundCall(body).subscribe({\n next: (res) => {\n this.submitting.set(false);\n const msg = res.message ?? 'Outbound call started.';\n this.toast.show(msg, 'success', 5000);\n this.dialed.emit();\n this.close();\n },\n error: (err: { message?: string }) => {\n this.submitting.set(false);\n const msg = err?.message ?? 'Failed to place outbound call.';\n this.serverError.set(msg);\n this.toast.show(msg, 'error', 5000);\n },\n });\n }\n\n private preferAppointmentPurpose(): void {\n const current = this.selectedPurpose;\n const needsAppt =\n !!current &&\n (current.requiredFields.includes('appointmentDate') ||\n current.requiredFields.includes('appointmentTime'));\n if (needsAppt) return;\n\n const confirm =\n this.purposes().find(\n (p) =>\n p.isActive &&\n p.requiredFields.includes('appointmentDate') &&\n p.requiredFields.includes('appointmentTime'),\n ) ??\n this.purposes().find((p) => p.isActive && /confirm/i.test(p.label + p.purposeId));\n\n if (confirm) {\n this.purposeId = confirm.purposeId;\n }\n }\n\n private applyAppointmentFields(appt: OutboundAppointmentHit): void {\n const start = appt.startTime ? new Date(appt.startTime) : null;\n if (start && !Number.isNaN(start.getTime())) {\n const y = start.getFullYear();\n const m = String(start.getMonth() + 1).padStart(2, '0');\n const d = String(start.getDate()).padStart(2, '0');\n const hh = String(start.getHours()).padStart(2, '0');\n const mm = String(start.getMinutes()).padStart(2, '0');\n this.appointmentDate = `${y}-${m}-${d}`;\n this.appointmentTime = `${hh}:${mm}`;\n }\n if (appt.providerName) {\n this.providerName = appt.providerName;\n }\n }\n\n private clearApptDerivedFields(): void {\n this.appointmentDate = '';\n this.appointmentTime = '';\n this.providerName = '';\n }\n\n private loadAppointments(patientId: string): void {\n this.loadingAppointments.set(true);\n this.appointments.set([]);\n this.api.listPatientAppointmentsForOutbound(patientId, 20).subscribe({\n next: (rows) => {\n this.appointments.set(rows);\n this.loadingAppointments.set(false);\n if (rows.length === 1) {\n this.selectAppointment(rows[0]);\n }\n },\n error: () => {\n this.loadingAppointments.set(false);\n this.appointments.set([]);\n },\n });\n }\n\n private loadPracticeName(): void {\n this.api.getSettings().subscribe({\n next: ({ data }) => {\n const name = String(data.general?.['practiceName'] ?? '').trim();\n this.practiceName.set(name || 'our office');\n },\n error: () => this.practiceName.set('our office'),\n });\n }\n\n private resetForm(): void {\n this.patientQuery.set('');\n this.phone = '';\n this.name = '';\n this.purposeId = '';\n this.appointmentDate = '';\n this.appointmentTime = '';\n this.providerName = '';\n this.customScript = '';\n this.patientHits.set([]);\n this.selectedPatient.set(null);\n this.appointments.set([]);\n this.selectedAppointmentId.set(null);\n this.searchingPatients.set(false);\n this.loadingAppointments.set(false);\n this.showManualEntry.set(false);\n this.serverError.set(null);\n this.submitting.set(false);\n this.formEpoch.set(0);\n }\n\n private loadPurposes(): void {\n this.loading.set(true);\n this.api.getOutboundPurposes(false).subscribe({\n next: ({ purposes }) => {\n this.purposes.set(purposes);\n if (purposes.length && !this.purposeId) {\n this.purposeId = purposes[0].purposeId;\n }\n this.loading.set(false);\n },\n error: () => {\n this.loading.set(false);\n this.serverError.set('Failed to load outbound purposes.');\n },\n });\n }\n}\n","@if (open) {\n <app-modal\n title=\"Outbound call\"\n subtitle=\"Search a patient, pick an appointment, then dial\"\n maxWidth=\"600px\"\n (closed)=\"close()\"\n >\n @if (!loading()) {\n <div modalStickyTop class=\"border-b border-base bg-white px-6 pb-3 pt-5\">\n <div class=\"dial-form__field dial-form__typeahead relative\">\n <label>Patient<span required></span></label>\n\n @if (selectedPatient(); as patient) {\n <div class=\"flex items-center justify-between gap-3 rounded-lg border border-base bg-lighter px-3 py-3\">\n <div class=\"flex min-w-0 flex-col gap-1\">\n <strong>{{ patient.fullName }}</strong>\n <span class=\"dial-form__chip-meta text-xs\">\n @if (patient.phone) {\n {{ patient.phone }}\n }\n @if (patient.dob) {\n · DOB {{ patient.dob }}\n }\n </span>\n </div>\n <button type=\"button\" class=\"dial-form__chip-clear shrink-0 cursor-pointer border-none bg-transparent p-1 text-[13px] font-[inherit]\" (click)=\"clearPatient()\">\n Change\n </button>\n </div>\n } @else {\n <eds-search-field\n inputClass=\"form-input\"\n [value]=\"patientQuery()\"\n (valueChange)=\"onPatientQueryChange($event)\"\n placeholder=\"Search by name or phone\"\n />\n @if (searchingPatients()) {\n <span class=\"dial-form__hint text-xs\">Searching…</span>\n }\n @if (patientHits().length) {\n <ul class=\"dial-form__hits absolute left-0 right-0 top-[calc(100%+4px)] z-[5] max-h-[220px] overflow-hidden overflow-y-auto rounded-lg border\" role=\"listbox\">\n @for (hit of patientHits(); track hit.patientId) {\n <li>\n <button\n type=\"button\"\n class=\"dial-form__hit flex w-full cursor-pointer flex-col items-start gap-1 border-none border-b px-3 py-3 text-left font-[inherit] last:border-b-0\"\n role=\"option\"\n (click)=\"selectPatient(hit)\"\n >\n <span class=\"font-semibold\">{{ hit.fullName }}</span>\n <span class=\"dial-form__hit-meta text-xs\">\n {{ hit.phone || 'No phone' }}\n @if (hit.dob) {\n · {{ hit.dob }}\n }\n </span>\n </button>\n </li>\n }\n </ul>\n }\n }\n </div>\n </div>\n }\n\n @if (loading()) {\n <div class=\"flex flex-col gap-3\" aria-busy=\"true\">\n @for (_ of formSkeletonRows; track _) {\n <div class=\"grid gap-2xs\">\n <ed-skeleton class=\"h-3 w-20\"></ed-skeleton>\n <ed-skeleton class=\"h-10 w-full !rounded-md\"></ed-skeleton>\n </div>\n }\n </div>\n } @else {\n <form\n id=\"outbound-dial-form\"\n class=\"flex flex-col gap-3\"\n (ngSubmit)=\"submit()\"\n novalidate\n autocomplete=\"off\"\n >\n @if (selectedPatient()) {\n <div class=\"mb-sm\">\n <label>Appointment</label>\n @if (loadingAppointments()) {\n <ul class=\"max-h-[220px] overflow-hidden overflow-y-auto rounded-lg border\" aria-busy=\"true\">\n @for (_ of appointmentSkeletonRows; track _) {\n <li class=\"flex items-start gap-3 border-b px-3 py-3 last:border-b-0\">\n <ed-skeleton class=\"mt-0.5 h-4 w-4 !rounded-full\"></ed-skeleton>\n <div class=\"grid flex-1 gap-2xs\">\n <ed-skeleton class=\"h-3 w-40\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-56\"></ed-skeleton>\n </div>\n </li>\n }\n </ul>\n } @else if (!appointments().length) {\n <span class=\"dial-form__hint text-xs\">No upcoming appointments — dial without one, or enter details below.</span>\n } @else {\n <ul class=\"max-h-[220px] overflow-hidden overflow-y-auto rounded-lg border\">\n <li>\n <label class=\"dial-form__appt flex cursor-pointer items-start gap-3 border-b px-3 py-3 last:border-b-0\">\n <input\n type=\"radio\"\n name=\"appointmentId\"\n [checked]=\"!selectedAppointmentId()\"\n (change)=\"selectAppointment(null)\"\n autocomplete=\"off\"\n />\n <span>No appointment (purpose-only)</span>\n </label>\n </li>\n @for (appt of appointments(); track appt.appointmentId) {\n <li>\n <label class=\"dial-form__appt flex cursor-pointer items-start gap-3 border-b px-3 py-3 last:border-b-0\">\n <input\n type=\"radio\"\n name=\"appointmentId\"\n [checked]=\"selectedAppointmentId() === appt.appointmentId\"\n (change)=\"selectAppointment(appt)\"\n autocomplete=\"off\"\n />\n <span class=\"flex flex-col gap-1\">\n <strong>\n {{ appt.startTime ? (appt.startTime | date: 'medium') : 'TBD' }}\n </strong>\n <span class=\"dial-form__hit-meta text-xs\">\n {{ appt.providerName || 'Provider TBD' }}\n @if (appt.locationName) {\n · {{ appt.locationName }}\n }\n @if (appt.visitType) {\n · {{ appt.visitType }}\n }\n @if (appt.status) {\n · {{ appt.status }}\n }\n </span>\n </span>\n </label>\n </li>\n }\n </ul>\n }\n </div>\n }\n\n <button\n type=\"button\"\n class=\"dial-form__toggle self-start cursor-pointer border-none bg-transparent p-0 text-[13px] font-[inherit]\"\n (click)=\"showManualEntry.set(!showManualEntry())\"\n >\n {{ showManualEntry() ? 'Hide' : 'Show' }} phone / name\n @if (!selectedPatient()) {\n (required if no patient selected)\n }\n </button>\n\n @if (showManualEntry() || !selectedPatient()) {\n <div class=\"mb-sm\">\n <label>Phone<span required></span></label>\n <input\n class=\"form-input\"\n type=\"tel\"\n [(ngModel)]=\"phone\"\n (ngModelChange)=\"touchForm()\"\n name=\"phone\"\n placeholder=\"+14155550100\"\n autocomplete=\"off\"\n required\n />\n <span class=\"dial-form__hint text-xs\">E.164 format, e.g. +14155550100</span>\n </div>\n\n <div class=\"mb-sm\">\n <label>Patient name<span required></span></label>\n <input\n class=\"form-input\"\n type=\"text\"\n [(ngModel)]=\"name\"\n (ngModelChange)=\"touchForm()\"\n name=\"name\"\n placeholder=\"Maria Garcia\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n <div class=\"mb-sm\">\n <label>Purpose<span required></span></label>\n <select\n class=\"form-select\"\n [(ngModel)]=\"purposeId\"\n name=\"purposeId\"\n (ngModelChange)=\"onPurposeChange()\"\n required\n autocomplete=\"off\"\n >\n @for (p of purposes(); track p.purposeId) {\n <option [value]=\"p.purposeId\">{{ p.label }}</option>\n }\n </select>\n </div>\n\n @if (showsField('appointmentDate') && !selectedAppointmentId()) {\n <div class=\"mb-sm\">\n <label>{{ requiredFieldLabel('appointmentDate') }}<span required></span></label>\n <input\n class=\"form-input\"\n type=\"date\"\n [(ngModel)]=\"appointmentDate\"\n (ngModelChange)=\"touchForm()\"\n name=\"appointmentDate\"\n placeholder=\"Appointment Date\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n @if (showsField('appointmentTime') && !selectedAppointmentId()) {\n <div class=\"mb-sm\">\n <label>{{ requiredFieldLabel('appointmentTime') }}<span required></span></label>\n <input\n class=\"form-input\"\n type=\"time\"\n [(ngModel)]=\"appointmentTime\"\n (ngModelChange)=\"touchForm()\"\n name=\"appointmentTime\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n @if (showsField('providerName') && !selectedAppointmentId()) {\n <div class=\"mb-sm\">\n <label>{{ requiredFieldLabel('providerName') }}<span required></span></label>\n <input\n class=\"form-input\"\n type=\"text\"\n [(ngModel)]=\"providerName\"\n (ngModelChange)=\"touchForm()\"\n name=\"providerName\"\n placeholder=\"Dr. Patel\"\n required\n autocomplete=\"off\"\n />\n </div>\n }\n\n @if (selectedAppointmentId() && (showsField('appointmentDate') || showsField('appointmentTime') || showsField('providerName'))) {\n <p class=\"dial-form__hint text-xs\">\n Date, time, and provider are taken from the selected appointment\n @if (appointmentDate || appointmentTime || providerName) {\n ({{ appointmentDate }}\n @if (appointmentTime) {\n {{ appointmentTime }}\n }\n @if (providerName) {\n · {{ providerName }}\n })\n }.\n </p>\n }\n\n @if (showsCustomScript()) {\n <div class=\"mb-sm\">\n <label>\n Custom script\n @if (customScriptRequired()) {\n <span required></span>\n }\n </label>\n <textarea\n class=\"form-textarea\"\n rows=\"4\"\n [(ngModel)]=\"customScript\"\n (ngModelChange)=\"touchForm()\"\n name=\"customScript\"\n [required]=\"customScriptRequired()\"\n placeholder=\"What should the agent say or do?\"\n autocomplete=\"off\"\n ></textarea>\n </div>\n }\n </form>\n }\n\n @if (!loading()) {\n <div modalFooter class=\"border-t border-base bg-white px-6 py-4\">\n <section class=\"dial-form__preview rounded-lg border border-base bg-white p-3 shadow-sm\">\n <h3 class=\"fs-sm font-semibold text-light\">Opening preview</h3>\n <p class=\"dial-form__preview-text mt-2 whitespace-pre-wrap text-sm leading-normal\">{{ previewText }}</p>\n @if (selectedAppointmentId()) {\n <p class=\"dial-form__hint text-xs\">\n Filled from the selected appointment and purpose template. Switch purpose to change the script.\n </p>\n } @else {\n <p class=\"dial-form__hint text-xs\">\n Select an appointment to fill date, time, and provider into this message.\n </p>\n }\n </section>\n\n @if (serverError()) {\n <p class=\"mt-3 m-0 text-[13px] text-[#b91c1c]\" role=\"alert\">{{ serverError() }}</p>\n }\n\n <footer class=\"mt-3 flex justify-end gap-2\">\n <ed-button variant=\"secondary\" outline size=\"sm\" (click$)=\"close()\" [disabled]=\"submitting()\">\n Cancel\n </ed-button>\n <ed-button\n type=\"button\"\n variant=\"primary\"\n fill\n size=\"sm\"\n [disabled]=\"submitting() || !canPlaceCall()\"\n (click$)=\"submit()\"\n >\n {{ submitting() ? 'Dialing…' : 'Place call' }}\n </ed-button>\n </footer>\n </div>\n }\n </app-modal>\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n CUSTOM_ELEMENTS_SCHEMA,\n inject,\n signal,\n} from '@angular/core';\nimport { ActivatedRoute, Router } from '@angular/router';\n\nimport { EdButtonComponent } from '../../ui/ed-button.component';\nimport { EdSkeletonComponent } from '../../ui/ed-skeleton.component';\n\nimport { CallStatus, LiveCall, Sentiment } from '../../models/ai-receptionist.types';\nimport { LiveCallsListService } from '../../services/live-calls-list.service';\nimport { AgentOnlineService } from '../../services/agent-online.service';\nimport {\n ageFromDob,\n callerMetaLine,\n isUnknownCaller,\n} from '../../utils/call-history-display.util';\nimport { sentimentIconSrc, sentimentLabel } from '../../utils/sentiment-display.util';\nimport { OutboundDialModalComponent } from '../../components/outbound-dial-modal/outbound-dial-modal.component';\nimport { SmartTooltipDirective } from '../../shared/smart-tooltip.directive';\nimport { EdAvatarComponent } from '../../ui/ed-avatar.component';\nimport { IconComponent } from '../../ui/ed-icon.component';\nimport { EdsPulseDotComponent } from '../../ui/eds-pulse-dot.component';\nimport { EdsElapsedPipe } from '../../pipes/eds-elapsed.pipe';\n\ninterface LiveCallRowVm {\n id: string;\n name: string;\n initials: string;\n avatarTone: 'gray' | 'brand' | 'warm' | 'violet' | 'rose' | 'green';\n metaLine: string;\n sentiment: Sentiment;\n sentimentLabel: string;\n sentimentIconSrc: string;\n direction: 'inbound' | 'outbound';\n directionIcon: string;\n directionTitle: string;\n goal: string;\n statusSubtitle: string;\n handlingLabel: string;\n handlingVariant: 'opened' | 'danger' | 'primary';\n needsAssistance: boolean;\n started: number;\n}\n\n@Component({\n selector: 'eds-live-calls-page',\n imports: [\n EdAvatarComponent,\n IconComponent,\n EdsPulseDotComponent,\n EdsElapsedPipe,\n OutboundDialModalComponent,\n EdSkeletonComponent,\n EdButtonComponent,\n SmartTooltipDirective,\n ],\n templateUrl: './live-calls-page.component.html',\n styleUrl: './live-calls-page.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n schemas: [CUSTOM_ELEMENTS_SCHEMA],\n host: { class: 'block h-full min-h-0' },\n})\nexport class LiveCallsPageComponent {\n private readonly router = inject(Router);\n private readonly route = inject(ActivatedRoute);\n private readonly liveCallsList = inject(LiveCallsListService);\n private readonly agentOnline = inject(AgentOnlineService);\n\n protected readonly calls = this.liveCallsList.calls;\n protected readonly loading = this.liveCallsList.loading;\n protected readonly error = this.liveCallsList.error;\n protected readonly agentOffline = computed(() => this.agentOnline.online() === false);\n protected readonly listSkeletonRows = [0, 1, 2, 3] as const;\n\n protected readonly outboundOpen = signal(false);\n\n protected readonly callRows = computed(() => this.calls().map((call) => this.toRow(call)));\n\n protected readonly emptyState = computed(() => {\n if (this.calls().length > 0) return null;\n if (this.agentOnline.online() === false) {\n return {\n title: 'Agent is offline',\n detail:\n 'The AI receptionist is not answering calls. Turn it on in Agent Setting → Advanced.',\n icon: 'phone_disabled',\n };\n }\n return {\n title: 'No active calls right now',\n detail:\n 'When a patient calls your practice, their conversation will appear here for real-time monitoring.',\n icon: 'phone_callback',\n };\n });\n\n protected openMonitor(id: string): void {\n void this.router.navigate([id], { relativeTo: this.route.parent ?? this.route });\n }\n\n protected openOutbound(): void {\n this.outboundOpen.set(true);\n }\n\n protected closeOutbound(): void {\n this.outboundOpen.set(false);\n }\n\n protected openTestCall(): void {\n void this.router.navigate(['../settings'], { relativeTo: this.route.parent ?? this.route });\n }\n\n private toRow(call: LiveCall): LiveCallRowVm {\n const unknown = isUnknownCaller(call.name, call.matched);\n const needsAssistance = this.isAssistanceStatus(call.status);\n const staffHandling = call.status === 'takeover' || call.controlMode === 'takeover';\n const lastTool = call.tools[call.tools.length - 1];\n\n return {\n id: call.id,\n name: unknown ? 'Unknown Caller' : call.name.trim() || 'Unknown Caller',\n initials: unknown ? '?' : call.initials || '?',\n avatarTone: unknown ? 'gray' : this.avatarToneFor(call),\n metaLine: callerMetaLine({\n name: call.name,\n matched: call.matched,\n phone: call.phone,\n dob: call.dob,\n age: call.age ?? ageFromDob(call.dob),\n gender: call.gender,\n }),\n sentiment: call.sentiment,\n sentimentLabel: sentimentLabel(call.sentiment),\n sentimentIconSrc: sentimentIconSrc(call.sentiment),\n direction: call.direction,\n directionIcon: call.direction === 'inbound' ? 'call_received' : 'call_made',\n directionTitle: call.direction === 'inbound' ? 'Incoming call' : 'Outgoing call',\n goal: call.reason || call.goal || '—',\n statusSubtitle: lastTool?.result || call.goal || 'In progress…',\n handlingLabel: needsAssistance\n ? 'NEED ASSISTANCE'\n : staffHandling\n ? 'STAFF HANDLING'\n : 'AI HANDLING',\n handlingVariant: needsAssistance ? 'danger' : staffHandling ? 'primary' : 'opened',\n needsAssistance,\n started: call.started,\n };\n }\n\n private isAssistanceStatus(status: CallStatus): boolean {\n return status === 'needs-help' || status === 'human-requested' || status === 'urgent';\n }\n\n private avatarToneFor(call: LiveCall): 'brand' | 'warm' | 'violet' | 'rose' | 'green' {\n const tones = ['brand', 'warm', 'violet', 'rose', 'green'] as const;\n const key = call.id || call.name;\n let hash = 0;\n for (let i = 0; i < key.length; i++) hash = (hash + key.charCodeAt(i) * (i + 1)) % tones.length;\n return tones[hash];\n }\n}\n","<div class=\"flex h-full min-h-0 flex-col overflow-hidden bg-lighter p-base live-calls-page\">\n <header class=\"mb-md flex shrink-0 items-start justify-between gap-base\">\n <div class=\"grid gap-2xs\">\n <div class=\"flex items-center gap-sm\">\n <h4>Live Calls</h4>\n @if (agentOffline()) {\n <span class=\"inline-flex items-center gap-2xs fs-sm font-semibold text-danger\">\n <ed-icon name=\"phone_disabled\" [size]=\"18\" />\n Offline\n </span>\n } @else {\n <eds-pulse-dot />\n }\n </div>\n <p class=\"fs-sm text-light\">Monitor active conversations and step in when needed</p>\n </div>\n <div class=\"flex shrink-0 items-center gap-sm hidden\">\n <ed-button\n variant=\"secondary\"\n outline\n size=\"sm\"\n (click$)=\"openTestCall()\"\n >\n <ed-icon slot=\"prefix\" name=\"phone_call\" [size]=\"16\" />\n Test Call\n </ed-button>\n <ed-button\n variant=\"primary\"\n fill\n size=\"sm\"\n (click$)=\"openOutbound()\"\n >\n <ed-icon slot=\"prefix\" name=\"call_made\" [size]=\"16\" />\n Outbound Call\n </ed-button>\n </div>\n </header>\n\n <div class=\"flex min-h-0 flex-1 flex-col gap-md overflow-y-auto\">\n @if (error()) {\n <p class=\"fs-sm text-danger\" role=\"alert\">{{ error() }}</p>\n }\n\n @if (loading() && callRows().length === 0) {\n <article class=\"grid gap-base overflow-hidden rounded-lg border border-light bg-white p-base\" aria-busy=\"true\">\n <ul class=\"m-0 grid list-none content-start gap-xs p-0\">\n @for (_ of listSkeletonRows; track _) {\n <li>\n <div class=\"flex w-full items-center gap-base rounded-md border border-light bg-white p-sm\">\n <div class=\"flex min-w-0 shrink-0 items-center gap-xs md:w-72\">\n <ed-skeleton class=\"h-9 w-9 shrink-0 !rounded-md\"></ed-skeleton>\n <div class=\"grid min-w-0 flex-1 gap-2xs\">\n <ed-skeleton class=\"h-3 w-32\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-40\"></ed-skeleton>\n </div>\n </div>\n <div class=\"hidden min-w-0 items-center gap-xs md:flex\">\n <ed-skeleton class=\"h-9 w-9 shrink-0 !rounded-md\"></ed-skeleton>\n <div class=\"grid min-w-0 gap-2xs\">\n <ed-skeleton class=\"h-3 w-28\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-24\"></ed-skeleton>\n </div>\n </div>\n <div class=\"ml-auto grid shrink-0 justify-items-end gap-2xs self-stretch\">\n <ed-skeleton class=\"h-5 w-24 !rounded-full\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-12\"></ed-skeleton>\n </div>\n </div>\n </li>\n }\n </ul>\n </article>\n } @else {\n <article class=\"grid gap-base overflow-hidden rounded-lg border border-light bg-white p-base\">\n <ul class=\"m-0 grid list-none content-start gap-xs p-0\">\n @for (call of callRows(); track call.id) {\n <li>\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center gap-base rounded-md border border-light bg-white p-sm text-left transition-colors hover:bg-lighter hover:shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary\"\n [class.border-danger]=\"call.needsAssistance\"\n [class.bg-danger-50]=\"call.needsAssistance\"\n (click)=\"openMonitor(call.id)\"\n >\n <div class=\"flex min-w-0 shrink-0 items-center gap-xs md:w-72\">\n <ed-avatar\n shape=\"box\"\n size=\"base\"\n [initials]=\"call.initials\"\n [tone]=\"call.avatarTone\"\n />\n <div class=\"grid min-w-0 flex-1 gap-2xs\">\n <div class=\"flex min-w-0 flex-wrap items-center gap-2xs\">\n <span class=\"fs-sm font-semibold text-dark\">{{ call.name }}</span>\n <!-- <span class=\"inline-flex text-light\">\n <ed-icon name=\"TagSimple\" [size]=\"14\" color=\"currentColor\" />\n </span> -->\n <span class=\"vr-sentiment\" [attr.data-sentiment]=\"call.sentiment\">\n <span class=\"vr-sentiment__icon\" aria-hidden=\"true\">\n <img [src]=\"call.sentimentIconSrc\" alt=\"\" width=\"16\" height=\"16\" />\n </span>\n <span>{{ call.sentimentLabel }}</span>\n </span>\n </div>\n <span class=\"fs-xs whitespace-nowrap text-light\">{{ call.metaLine }}</span>\n </div>\n </div>\n\n <div class=\"hidden min-w-0 items-center gap-xs md:flex\">\n <span\n class=\"inline-flex size-9 shrink-0 items-center justify-center rounded-md\"\n [class.bg-success-50]=\"call.direction === 'inbound'\"\n [class.text-success]=\"call.direction === 'inbound'\"\n [class.bg-primary-50]=\"call.direction === 'outbound'\"\n [class.text-primary]=\"call.direction === 'outbound'\"\n [smartTooltip]=\"call.directionTitle\"\n >\n <ed-icon [name]=\"call.directionIcon\" [size]=\"18\" color=\"currentColor\" />\n </span>\n <div class=\"grid min-w-0 gap-2xs whitespace-nowrap\">\n <span class=\"fs-sm font-medium text-dark\">{{ call.goal }}</span>\n <span class=\"fs-xs text-light\">{{ call.statusSubtitle }}</span>\n </div>\n </div>\n\n <div class=\"ml-auto grid shrink-0 justify-items-end gap-2xs self-stretch\">\n <ed-badge-2 [attr.variant]=\"call.handlingVariant\" pill uppercase>\n {{ call.handlingLabel }}\n </ed-badge-2>\n <strong\n class=\"inline-flex items-center gap-2xs text-primary fs-2xs\"\n [class.text-danger]=\"call.needsAssistance\"\n [class.text-light]=\"!call.needsAssistance\"\n >\n @if (call.needsAssistance) {\n <ed-icon name=\"schedule\" [size]=\"12\" color=\"currentColor\" />\n } @else {\n <eds-pulse-dot />\n }\n {{ call.started | edsElapsed }}\n </strong>\n </div>\n </button>\n </li>\n } @empty {\n @if (emptyState(); as state) {\n <li\n class=\"grid justify-items-center gap-xs rounded-md border border-light px-base py-xl text-center\"\n role=\"status\"\n >\n <ed-icon [name]=\"state.icon\" [size]=\"32\" />\n <p class=\"m-0 font-semibold text-dark\">{{ state.title }}</p>\n <p class=\"m-0 max-w-md fs-sm text-light\">{{ state.detail }}</p>\n </li>\n }\n }\n </ul>\n </article>\n }\n </div>\n</div>\n\n<eds-outbound-dial-modal\n [open]=\"outboundOpen()\"\n (closed)=\"closeOutbound()\"\n (dialed)=\"closeOutbound()\"\n/>\n"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, signal, Injectable, input, output, computed, effect, ViewChild, ChangeDetectionStrategy, Component, DestroyRef, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
3
3
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
|
-
import { ActivatedRoute,
|
|
4
|
+
import { ActivatedRoute, Router } from '@angular/router';
|
|
5
5
|
import * as i1 from '@angular/forms';
|
|
6
6
|
import { FormsModule } from '@angular/forms';
|
|
7
7
|
import { firstValueFrom } from 'rxjs';
|
|
8
|
-
import {
|
|
8
|
+
import { k as resolveToolKey, l as resolveToolDisplayName, D as DashboardSocketService, V as VOICE_RECEPTIONIST_CONFIG, i as isUnknownCaller, I as IconComponent, A as AiReceptionistApiService, T as ToastService, m as toolEventTrackKey, c as callerMetaLine, b as ageFromDob, E as EdAvatarComponent, f as EdButtonComponent, g as EdSkeletonComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-CJ1L6QeE.mjs';
|
|
9
9
|
import { E as EdsElapsedPipe, a as sentimentLabel, s as sentimentIconSrc } from './edvak99-voice-receptionist-widget-eds-elapsed.pipe-Dra7e7xN.mjs';
|
|
10
|
-
import { C as CallRecordingPlayerComponent } from './edvak99-voice-receptionist-widget-call-recording-player.component-
|
|
10
|
+
import { C as CallRecordingPlayerComponent } from './edvak99-voice-receptionist-widget-call-recording-player.component-BepxNLNQ.mjs';
|
|
11
11
|
import { Track, Room, RoomEvent, ConnectionState } from 'livekit-client';
|
|
12
12
|
|
|
13
13
|
function mapChunkToLine(chunk) {
|
|
@@ -703,6 +703,7 @@ class LiveMonitorPageComponent {
|
|
|
703
703
|
constructor() {
|
|
704
704
|
this.api = inject(AiReceptionistApiService);
|
|
705
705
|
this.route = inject(ActivatedRoute);
|
|
706
|
+
this.router = inject(Router);
|
|
706
707
|
this.stream = inject(LiveTranscriptService);
|
|
707
708
|
this.staffMedia = inject(StaffMediaService);
|
|
708
709
|
this.socket = inject(DashboardSocketService);
|
|
@@ -783,6 +784,9 @@ class LiveMonitorPageComponent {
|
|
|
783
784
|
}
|
|
784
785
|
});
|
|
785
786
|
}
|
|
787
|
+
goToLiveCalls() {
|
|
788
|
+
void this.router.navigate(['..'], { relativeTo: this.route });
|
|
789
|
+
}
|
|
786
790
|
ngOnInit() {
|
|
787
791
|
this.socket.acquire();
|
|
788
792
|
this.socket.on('call_control_changed', this.onControlChanged);
|
|
@@ -1093,12 +1097,11 @@ class LiveMonitorPageComponent {
|
|
|
1093
1097
|
});
|
|
1094
1098
|
}
|
|
1095
1099
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: LiveMonitorPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1096
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: LiveMonitorPageComponent, isStandalone: true, selector: "eds-live-monitor-page", host: { classAttribute: "block h-full min-h-0" }, ngImport: i0, template: "<div class=\"live-monitor-page box-border flex h-full min-h-0 flex-col overflow-hidden bg-lighter p-base text-zinc-900\">\n @if (loading()) {\n <div class=\"flex min-h-0 flex-1 flex-col gap-4\" aria-busy=\"true\">\n <header class=\"flex shrink-0 items-center justify-between gap-3 rounded-lg bg-white p-3\">\n <div class=\"flex min-w-0 flex-1 items-center gap-4\">\n <ed-skeleton class=\"h-5 w-5 !rounded-sm\"></ed-skeleton>\n <div class=\"flex min-w-0 items-center gap-2\">\n <ed-skeleton class=\"h-9 w-9 !rounded-md\"></ed-skeleton>\n <div class=\"grid min-w-0 gap-2xs\">\n <ed-skeleton class=\"h-4 w-40\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-32\"></ed-skeleton>\n </div>\n </div>\n </div>\n <div class=\"flex shrink-0 items-center gap-3\">\n <ed-skeleton class=\"h-8 w-24 !rounded-md\"></ed-skeleton>\n <ed-skeleton class=\"h-8 w-28 !rounded-md\"></ed-skeleton>\n </div>\n </header>\n <div class=\"grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_minmax(280px,447px)] items-stretch gap-4 max-[1100px]:grid-cols-1\">\n <div class=\"flex min-h-0 flex-col rounded-xl bg-white p-4\">\n <ed-skeleton class=\"mb-4 h-5 w-36\"></ed-skeleton>\n @for (_ of transcriptSkeletonRows; track _) {\n <div class=\"mb-4 flex flex-col gap-2\">\n <ed-skeleton class=\"h-3 w-24\"></ed-skeleton>\n <ed-skeleton class=\"h-16 w-full max-w-xl !rounded-lg\"></ed-skeleton>\n </div>\n }\n </div>\n <aside class=\"flex min-h-0 flex-col gap-4\">\n <ed-skeleton class=\"h-40 w-full !rounded-lg\"></ed-skeleton>\n <ed-skeleton class=\"min-h-48 w-full flex-1 !rounded-lg\"></ed-skeleton>\n <ed-skeleton class=\"h-48 w-full !rounded-lg\"></ed-skeleton>\n </aside>\n </div>\n </div>\n } @else {\n @if (call(); as c) {\n <header class=\"live-monitor-page__header mb-4 flex shrink-0 flex-wrap items-center justify-between gap-3 rounded-lg bg-white p-3\">\n <div class=\"flex min-w-0 flex-1 items-center gap-4\">\n <a\n routerLink=\"..\"\n class=\"live-monitor-page__back inline-flex h-5 w-5 shrink-0 items-center justify-center text-zinc-600 no-underline\"\n aria-label=\"Back to live calls\"\n >\n <ed-icon name=\"arrow_back\" [size]=\"14\" />\n </a>\n\n <div class=\"live-monitor-page__profile flex min-w-0 items-center gap-2\">\n <ed-avatar\n class=\"shrink-0\"\n shape=\"box\"\n size=\"md\"\n [initials]=\"profileInitials(c)\"\n [tone]=\"isUnknownCaller(c) ? 'gray' : 'warm'\"\n />\n\n <div class=\"min-w-0\">\n <div class=\"flex flex-wrap items-center gap-3\">\n <h4 class=\"m-0\">{{ displayName(c) }}</h4>\n @if (isUnknownCaller(c)) {\n <ed-badge-2 variant=\"primary\" pill uppercase>Unknown</ed-badge-2>\n } @else {\n @if (goalBadge(c); as badge) {\n <ed-badge-2 variant=\"primary\" pill uppercase>{{ badge }}</ed-badge-2>\n }\n }\n </div>\n <p class=\"m-0 mt-0.5 text-zinc-500\">{{ c.phone || profileMeta(c) }}</p>\n </div>\n </div>\n </div>\n\n <div class=\"live-monitor-page__header-actions flex flex-wrap items-center gap-3\">\n @if (!callEnded()) {\n @if (mediaState() === 'connected' && isMediaForThisCall() && !audioPlaybackAllowed()) {\n <ed-button variant=\"secondary\" outline size=\"sm\" (click$)=\"enableSound()\">\n <ed-icon slot=\"prefix\" name=\"mic\" [size]=\"16\" />\n Enable sound\n </ed-button>\n }\n @if (isMediaForThisCall() && (mediaState() === 'connected' || mediaState() === 'connecting')) {\n <span\n class=\"live-monitor-page__media-pill inline-flex h-7 items-center rounded-full border border-zinc-200 bg-zinc-50 px-3 text-zinc-600\"\n [attr.data-mode]=\"mediaMode() || 'idle'\"\n [attr.data-state]=\"mediaState()\"\n >\n @if (mediaState() === 'connecting') {\n Connecting\u2026\n } @else if (mediaMode() === 'takeover') {\n {{ micEnabled() ? 'On call \u00B7 mic on' : 'On call \u00B7 mic off' }}\n } @else {\n Joined\n }\n </span>\n }\n <ed-button\n variant=\"danger\"\n outline\n size=\"sm\"\n [disabled]=\"mediaBusy()\"\n (click$)=\"endCall()\"\n >\n <ed-icon slot=\"prefix\" name=\"call_end\" [size]=\"16\" />\n End Call\n </ed-button>\n @if (isTakeover()) {\n @if (mediaState() === 'connected' && isMediaForThisCall()) {\n <ed-button variant=\"secondary\" outline size=\"sm\" (click$)=\"toggleMic()\">\n <ed-icon slot=\"prefix\" [name]=\"micEnabled() ? 'mic' : 'mic_off'\" [size]=\"16\" />\n {{ micEnabled() ? 'Mute' : 'Unmute' }}\n </ed-button>\n }\n } @else {\n <ed-button\n variant=\"primary\"\n fill\n size=\"sm\"\n [disabled]=\"mediaBusy()\"\n (click$)=\"takeover()\"\n >\n <ed-icon slot=\"prefix\" name=\"phone_call\" [size]=\"16\" />\n Take Over\n </ed-button>\n }\n } @else {\n <span class=\"inline-flex items-center gap-2 font-mono text-zinc-900\">\n <span class=\"h-2 w-2 rounded-full bg-green-500 ring-4 ring-green-500/15\" aria-hidden=\"true\"></span>\n {{ c.started | edsElapsed }}\n </span>\n }\n </div>\n </header>\n\n @if (callEnded()) {\n <div class=\"mb-4 flex items-center gap-2 rounded-lg bg-green-50 px-4 py-3 text-green-700\" role=\"status\">\n <ed-icon name=\"check_circle\" [size]=\"18\" />\n Call ended\n </div>\n }\n\n <div\n class=\"live-monitor-page__grid grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_360px] items-stretch gap-4 max-[1100px]:grid-cols-1 max-[1100px]:overflow-auto\"\n >\n <div class=\"live-monitor-page__main flex h-full min-h-0 flex-col overflow-hidden\">\n <eds-live-transcript-panel\n [live]=\"isLive()\"\n [paused]=\"isPaused()\"\n [humanTakeover]=\"isTakeover()\"\n [callStarted]=\"c.started\"\n [callerName]=\"c.name\"\n [callerMatched]=\"c.matched\"\n />\n\n @if (callEnded()) {\n <section class=\"live-monitor-page__post-call flex flex-col gap-4\">\n <div class=\"rounded-lg border border-zinc-200 bg-white p-4 text-zinc-600\">\n <h4 class=\"mb-3 m-0\">Call summary</h4>\n @if (summaryLoading()) {\n <div class=\"grid gap-2xs\" aria-busy=\"true\">\n <ed-skeleton class=\"h-3 w-full\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-5/6\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-3/4\"></ed-skeleton>\n </div>\n } @else if (summary()) {\n <p class=\"m-0\">{{ summary() }}</p>\n } @else {\n <p class=\"m-0 text-zinc-500\">Summary will appear when processing completes.</p>\n }\n </div>\n <eds-call-recording-player [callId]=\"callId()\" />\n </section>\n }\n </div>\n\n <aside class=\"live-monitor-page__sidebar flex h-full min-h-0 flex-col gap-4 overflow-hidden\">\n @if (!callEnded()) {\n <section class=\"flex shrink-0 flex-col gap-4 rounded-lg bg-white p-4\" aria-labelledby=\"live-call-state-title\">\n <header class=\"flex items-center gap-3\">\n <span class=\"inline-flex items-center justify-center rounded-md bg-fuchsia-500/10 p-1 text-fuchsia-600\">\n <ed-icon name=\"phone_call\" [size]=\"16\" />\n </span>\n <h4 id=\"live-call-state-title\" class=\"m-0\">Call State</h4>\n </header>\n\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex items-center justify-between gap-2 text-zinc-600\">\n <span class=\"live-monitor-page__eyebrow\">Agent confidence</span>\n <span class=\"live-monitor-page__eyebrow\">{{ c.confidence }}%</span>\n </div>\n <div class=\"live-monitor-page__confidence-track\">\n <span\n class=\"live-monitor-page__confidence-fill\"\n [style.width.%]=\"c.confidence\"\n [attr.data-level]=\"confidenceLevel(c.confidence)\"\n ></span>\n </div>\n <div class=\"flex items-center gap-2 text-zinc-600\">\n <ed-icon\n [name]=\"confidenceLevel(c.confidence) === 'low' ? 'trend_down' : 'info'\"\n [size]=\"16\"\n />\n <span>{{ confidenceHint(c.confidence) }}</span>\n </div>\n </div>\n\n <div class=\"flex items-center justify-between gap-3\">\n <span class=\"live-monitor-page__eyebrow\">Patient sentiment</span>\n <span class=\"vr-sentiment\" [attr.data-sentiment]=\"c.sentiment\">\n <span class=\"vr-sentiment__icon\" aria-hidden=\"true\">\n <img [src]=\"sentimentIcon(c.sentiment)\" alt=\"\" width=\"16\" height=\"16\" />\n </span>\n <span>{{ sentimentLabel(c.sentiment) }}</span>\n </span>\n </div>\n </section>\n }\n\n <section\n class=\"live-monitor-page__sidebar-block flex min-h-[16rem] flex-1 flex-col gap-4 overflow-hidden rounded-lg bg-white p-4\"\n aria-labelledby=\"live-goal-timeline-title\"\n >\n <header class=\"flex items-center gap-3\">\n <span class=\"inline-flex items-center justify-center rounded-md bg-blue-500/10 p-1 text-blue-600\">\n <ed-icon name=\"flag\" [size]=\"16\" />\n </span>\n <h4 id=\"live-goal-timeline-title\" class=\"m-0\">Goal Timeline</h4>\n </header>\n\n <div class=\"live-monitor-page__goal-panel flex min-h-0 flex-1 flex-col rounded-lg bg-zinc-50 p-2\">\n @if (c.goal) {\n <p class=\"live-monitor-page__eyebrow mb-3 m-0 text-zinc-900\">{{ c.goal }}</p>\n } @else {\n <p class=\"live-monitor-page__eyebrow mb-3 m-0 text-zinc-900\">No Goal identified yet</p>\n }\n\n <ul class=\"live-monitor-page__tools m-0 flex min-h-0 flex-1 list-none flex-col gap-4 overflow-auto p-0\">\n @for (tool of liveTools(); track toolTrackKey(tool, $index)) {\n <li class=\"flex flex-col gap-1\" [class]=\"toolStateClass(tool.state)\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <span class=\"text-zinc-900\">{{ tool.name }}</span>\n <ed-badge-2 [attr.variant]=\"toolBadgeTone(tool.state)\" pill uppercase>\n {{ toolBadgeLabel(tool.state) }}\n </ed-badge-2>\n </div>\n @if (tool.result) {\n <p class=\"m-0 text-zinc-500\">{{ tool.result }}</p>\n }\n </li>\n } @empty {\n @for (step of goalPlaceholderSteps; track step.title) {\n <li class=\"flex flex-col gap-1\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <span class=\"text-zinc-900\">{{ step.title }}</span>\n <ed-badge-2 [attr.variant]=\"step.tone\" pill uppercase>{{ step.label }}</ed-badge-2>\n </div>\n <p class=\"m-0 text-zinc-500\">{{ step.detail }}</p>\n </li>\n }\n }\n </ul>\n </div>\n </section>\n </aside>\n </div>\n } @else {\n <div class=\"flex flex-col items-start gap-3\">\n <a\n routerLink=\"..\"\n class=\"live-monitor-page__back inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md border border-zinc-200 bg-white text-zinc-600 no-underline\"\n aria-label=\"Back to live calls\"\n >\n <ed-icon name=\"arrow_back\" [size]=\"19\" />\n </a>\n <p class=\"m-0 text-zinc-500\">Call ended or not found.</p>\n </div>\n }\n }\n</div>\n", styles: [".vr-sentiment{display:inline-flex;align-items:center;justify-content:center;gap:4px;height:20px;padding:2px 4px;border-radius:6px;font-size:11px;font-weight:500;line-height:16px;white-space:nowrap}.vr-sentiment__icon{display:block;width:16px;height:16px;flex-shrink:0;overflow:hidden}.vr-sentiment__icon img{display:block;width:100%;height:100%;max-width:none}.vr-sentiment[data-sentiment=calm]{background:#3681f41a;color:#2162f0}.vr-sentiment[data-sentiment=neutral]{background:#f0f2f5;color:#41464c}.vr-sentiment[data-sentiment=urgent]{background:#f26b0d1a;color:#e94e0c}.vr-sentiment[data-sentiment=frustrated]{background:#f037401a;color:#db0c15}.live-monitor-page__eyebrow{font-size:10px;font-weight:600;letter-spacing:1px;line-height:1;text-transform:uppercase}.live-monitor-page__confidence-track{height:8px;overflow:hidden;border-radius:9999px;background:linear-gradient(90deg,#7d34fb1a,#e76b171a)}.live-monitor-page__confidence-fill{display:block;height:100%;border-radius:9999px;background:linear-gradient(90deg,#7d34fb,#e76b17)}.live-monitor-page__sidebar-block{min-height:16rem}.live-monitor-page__tools li{position:relative;padding-left:1.25rem}.live-monitor-page__tools li:before{content:\"\";position:absolute;top:.35rem;left:0;width:.5rem;height:.5rem;border-radius:9999px;background:#a2a4a6}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: EdAvatarComponent, selector: "ed-avatar", inputs: ["initials", "tone", "shape", "size", "gradient"] }, { kind: "component", type: IconComponent, selector: "ed-icon", inputs: ["name", "size", "color", "cssClass", "decorative", "ariaHidden", "ariaLabel", "title", "desc", "focusable", "tabIndex"] }, { kind: "component", type: EdButtonComponent, selector: "ed-button", inputs: ["variant", "size", "disabled", "ghost", "outline", "subtle", "fill", "loading", "block", "elevated", "shape", "type", "href", "routerLink", "rel", "target", "download", "ariaLabel"], outputs: ["click$"] }, { kind: "pipe", type: EdsElapsedPipe, name: "edsElapsed" }, { kind: "component", type: LiveTranscriptPanelComponent, selector: "eds-live-transcript-panel", inputs: ["live", "paused", "humanTakeover", "showStaffInput", "callStarted", "staffMessage", "callerName", "callerMatched"], outputs: ["staffMessageChange", "sendMessage"] }, { kind: "component", type: CallRecordingPlayerComponent, selector: "eds-call-recording-player", inputs: ["callId"], outputs: ["currentTimeChange", "durationChange"] }, { kind: "component", type: EdSkeletonComponent, selector: "ed-skeleton", inputs: ["class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1100
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: LiveMonitorPageComponent, isStandalone: true, selector: "eds-live-monitor-page", host: { classAttribute: "block h-full min-h-0" }, ngImport: i0, template: "<div class=\"live-monitor-page box-border flex h-full min-h-0 flex-col overflow-hidden bg-lighter p-base text-zinc-900\">\n @if (loading()) {\n <div class=\"flex min-h-0 flex-1 flex-col gap-4\" aria-busy=\"true\">\n <header class=\"flex shrink-0 items-center justify-between gap-3 rounded-lg bg-white p-3\">\n <div class=\"flex min-w-0 flex-1 items-center gap-4\">\n <ed-skeleton class=\"h-5 w-5 !rounded-sm\"></ed-skeleton>\n <div class=\"flex min-w-0 items-center gap-2\">\n <ed-skeleton class=\"h-9 w-9 !rounded-md\"></ed-skeleton>\n <div class=\"grid min-w-0 gap-2xs\">\n <ed-skeleton class=\"h-4 w-40\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-32\"></ed-skeleton>\n </div>\n </div>\n </div>\n <div class=\"flex shrink-0 items-center gap-3\">\n <ed-skeleton class=\"h-8 w-24 !rounded-md\"></ed-skeleton>\n <ed-skeleton class=\"h-8 w-28 !rounded-md\"></ed-skeleton>\n </div>\n </header>\n <div class=\"grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_minmax(280px,447px)] items-stretch gap-4 max-[1100px]:grid-cols-1\">\n <div class=\"flex min-h-0 flex-col rounded-xl bg-white p-4\">\n <ed-skeleton class=\"mb-4 h-5 w-36\"></ed-skeleton>\n @for (_ of transcriptSkeletonRows; track _) {\n <div class=\"mb-4 flex flex-col gap-2\">\n <ed-skeleton class=\"h-3 w-24\"></ed-skeleton>\n <ed-skeleton class=\"h-16 w-full max-w-xl !rounded-lg\"></ed-skeleton>\n </div>\n }\n </div>\n <aside class=\"flex min-h-0 flex-col gap-4\">\n <ed-skeleton class=\"h-40 w-full !rounded-lg\"></ed-skeleton>\n <ed-skeleton class=\"min-h-48 w-full flex-1 !rounded-lg\"></ed-skeleton>\n <ed-skeleton class=\"h-48 w-full !rounded-lg\"></ed-skeleton>\n </aside>\n </div>\n </div>\n } @else {\n @if (call(); as c) {\n <header class=\"live-monitor-page__header mb-4 flex shrink-0 flex-wrap items-center justify-between gap-3 rounded-lg bg-white p-3\">\n <div class=\"flex min-w-0 flex-1 items-center gap-4\">\n <button\n type=\"button\"\n class=\"eds-plain-nav live-monitor-page__back inline-flex h-5 w-5 shrink-0 items-center justify-center text-zinc-600\"\n aria-label=\"Back to live calls\"\n (click)=\"goToLiveCalls()\"\n >\n <ed-icon name=\"arrow_back\" [size]=\"14\" />\n </button>\n\n <div class=\"live-monitor-page__profile flex min-w-0 items-center gap-2\">\n <ed-avatar\n class=\"shrink-0\"\n shape=\"box\"\n size=\"md\"\n [initials]=\"profileInitials(c)\"\n [tone]=\"isUnknownCaller(c) ? 'gray' : 'warm'\"\n />\n\n <div class=\"min-w-0\">\n <div class=\"flex flex-wrap items-center gap-3\">\n <h4 class=\"m-0\">{{ displayName(c) }}</h4>\n @if (isUnknownCaller(c)) {\n <ed-badge-2 variant=\"primary\" pill uppercase>Unknown</ed-badge-2>\n } @else {\n @if (goalBadge(c); as badge) {\n <ed-badge-2 variant=\"primary\" pill uppercase>{{ badge }}</ed-badge-2>\n }\n }\n </div>\n <p class=\"m-0 mt-0.5 text-zinc-500\">{{ c.phone || profileMeta(c) }}</p>\n </div>\n </div>\n </div>\n\n <div class=\"live-monitor-page__header-actions flex flex-wrap items-center gap-3\">\n @if (!callEnded()) {\n @if (mediaState() === 'connected' && isMediaForThisCall() && !audioPlaybackAllowed()) {\n <ed-button variant=\"secondary\" outline size=\"sm\" (click$)=\"enableSound()\">\n <ed-icon slot=\"prefix\" name=\"mic\" [size]=\"16\" />\n Enable sound\n </ed-button>\n }\n @if (isMediaForThisCall() && (mediaState() === 'connected' || mediaState() === 'connecting')) {\n <span\n class=\"live-monitor-page__media-pill inline-flex h-7 items-center rounded-full border border-zinc-200 bg-zinc-50 px-3 text-zinc-600\"\n [attr.data-mode]=\"mediaMode() || 'idle'\"\n [attr.data-state]=\"mediaState()\"\n >\n @if (mediaState() === 'connecting') {\n Connecting\u2026\n } @else if (mediaMode() === 'takeover') {\n {{ micEnabled() ? 'On call \u00B7 mic on' : 'On call \u00B7 mic off' }}\n } @else {\n Joined\n }\n </span>\n }\n <ed-button\n variant=\"danger\"\n outline\n size=\"sm\"\n [disabled]=\"mediaBusy()\"\n (click$)=\"endCall()\"\n >\n <ed-icon slot=\"prefix\" name=\"call_end\" [size]=\"16\" />\n End Call\n </ed-button>\n @if (isTakeover()) {\n @if (mediaState() === 'connected' && isMediaForThisCall()) {\n <ed-button variant=\"secondary\" outline size=\"sm\" (click$)=\"toggleMic()\">\n <ed-icon slot=\"prefix\" [name]=\"micEnabled() ? 'mic' : 'mic_off'\" [size]=\"16\" />\n {{ micEnabled() ? 'Mute' : 'Unmute' }}\n </ed-button>\n }\n } @else {\n <ed-button\n variant=\"primary\"\n fill\n size=\"sm\"\n [disabled]=\"mediaBusy()\"\n (click$)=\"takeover()\"\n >\n <ed-icon slot=\"prefix\" name=\"phone_call\" [size]=\"16\" />\n Take Over\n </ed-button>\n }\n } @else {\n <span class=\"inline-flex items-center gap-2 font-mono text-zinc-900\">\n <span class=\"h-2 w-2 rounded-full bg-green-500 ring-4 ring-green-500/15\" aria-hidden=\"true\"></span>\n {{ c.started | edsElapsed }}\n </span>\n }\n </div>\n </header>\n\n @if (callEnded()) {\n <div class=\"mb-4 flex items-center gap-2 rounded-lg bg-green-50 px-4 py-3 text-green-700\" role=\"status\">\n <ed-icon name=\"check_circle\" [size]=\"18\" />\n Call ended\n </div>\n }\n\n <div\n class=\"live-monitor-page__grid grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_360px] items-stretch gap-4 max-[1100px]:grid-cols-1 max-[1100px]:overflow-auto\"\n >\n <div class=\"live-monitor-page__main flex h-full min-h-0 flex-col overflow-hidden\">\n <eds-live-transcript-panel\n [live]=\"isLive()\"\n [paused]=\"isPaused()\"\n [humanTakeover]=\"isTakeover()\"\n [callStarted]=\"c.started\"\n [callerName]=\"c.name\"\n [callerMatched]=\"c.matched\"\n />\n\n @if (callEnded()) {\n <section class=\"live-monitor-page__post-call flex flex-col gap-4\">\n <div class=\"rounded-lg border border-zinc-200 bg-white p-4 text-zinc-600\">\n <h4 class=\"mb-3 m-0\">Call summary</h4>\n @if (summaryLoading()) {\n <div class=\"grid gap-2xs\" aria-busy=\"true\">\n <ed-skeleton class=\"h-3 w-full\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-5/6\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-3/4\"></ed-skeleton>\n </div>\n } @else if (summary()) {\n <p class=\"m-0\">{{ summary() }}</p>\n } @else {\n <p class=\"m-0 text-zinc-500\">Summary will appear when processing completes.</p>\n }\n </div>\n <eds-call-recording-player [callId]=\"callId()\" />\n </section>\n }\n </div>\n\n <aside class=\"live-monitor-page__sidebar flex h-full min-h-0 flex-col gap-4 overflow-hidden\">\n @if (!callEnded()) {\n <section class=\"flex shrink-0 flex-col gap-4 rounded-lg bg-white p-4\" aria-labelledby=\"live-call-state-title\">\n <header class=\"flex items-center gap-3\">\n <span class=\"inline-flex items-center justify-center rounded-md bg-fuchsia-500/10 p-1 text-fuchsia-600\">\n <ed-icon name=\"phone_call\" [size]=\"16\" />\n </span>\n <h4 id=\"live-call-state-title\" class=\"m-0\">Call State</h4>\n </header>\n\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex items-center justify-between gap-2 text-zinc-600\">\n <span class=\"live-monitor-page__eyebrow\">Agent confidence</span>\n <span class=\"live-monitor-page__eyebrow\">{{ c.confidence }}%</span>\n </div>\n <div class=\"live-monitor-page__confidence-track\">\n <span\n class=\"live-monitor-page__confidence-fill\"\n [style.width.%]=\"c.confidence\"\n [attr.data-level]=\"confidenceLevel(c.confidence)\"\n ></span>\n </div>\n <div class=\"flex items-center gap-2 text-zinc-600\">\n <ed-icon\n [name]=\"confidenceLevel(c.confidence) === 'low' ? 'trend_down' : 'info'\"\n [size]=\"16\"\n />\n <span>{{ confidenceHint(c.confidence) }}</span>\n </div>\n </div>\n\n <div class=\"flex items-center justify-between gap-3\">\n <span class=\"live-monitor-page__eyebrow\">Patient sentiment</span>\n <span class=\"vr-sentiment\" [attr.data-sentiment]=\"c.sentiment\">\n <span class=\"vr-sentiment__icon\" aria-hidden=\"true\">\n <img [src]=\"sentimentIcon(c.sentiment)\" alt=\"\" width=\"16\" height=\"16\" />\n </span>\n <span>{{ sentimentLabel(c.sentiment) }}</span>\n </span>\n </div>\n </section>\n }\n\n <section\n class=\"live-monitor-page__sidebar-block flex min-h-[16rem] flex-1 flex-col gap-4 overflow-hidden rounded-lg bg-white p-4\"\n aria-labelledby=\"live-goal-timeline-title\"\n >\n <header class=\"flex items-center gap-3\">\n <span class=\"inline-flex items-center justify-center rounded-md bg-blue-500/10 p-1 text-blue-600\">\n <ed-icon name=\"flag\" [size]=\"16\" />\n </span>\n <h4 id=\"live-goal-timeline-title\" class=\"m-0\">Goal Timeline</h4>\n </header>\n\n <div class=\"live-monitor-page__goal-panel flex min-h-0 flex-1 flex-col rounded-lg bg-zinc-50 p-2\">\n @if (c.goal) {\n <p class=\"live-monitor-page__eyebrow mb-3 m-0 text-zinc-900\">{{ c.goal }}</p>\n } @else {\n <p class=\"live-monitor-page__eyebrow mb-3 m-0 text-zinc-900\">No Goal identified yet</p>\n }\n\n <ul class=\"live-monitor-page__tools m-0 flex min-h-0 flex-1 list-none flex-col gap-4 overflow-auto p-0\">\n @for (tool of liveTools(); track toolTrackKey(tool, $index)) {\n <li class=\"flex flex-col gap-1\" [class]=\"toolStateClass(tool.state)\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <span class=\"text-zinc-900\">{{ tool.name }}</span>\n <ed-badge-2 [attr.variant]=\"toolBadgeTone(tool.state)\" pill uppercase>\n {{ toolBadgeLabel(tool.state) }}\n </ed-badge-2>\n </div>\n @if (tool.result) {\n <p class=\"m-0 text-zinc-500\">{{ tool.result }}</p>\n }\n </li>\n } @empty {\n @for (step of goalPlaceholderSteps; track step.title) {\n <li class=\"flex flex-col gap-1\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <span class=\"text-zinc-900\">{{ step.title }}</span>\n <ed-badge-2 [attr.variant]=\"step.tone\" pill uppercase>{{ step.label }}</ed-badge-2>\n </div>\n <p class=\"m-0 text-zinc-500\">{{ step.detail }}</p>\n </li>\n }\n }\n </ul>\n </div>\n </section>\n </aside>\n </div>\n } @else {\n <div class=\"flex flex-col items-start gap-3\">\n <button\n type=\"button\"\n class=\"live-monitor-page__back inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md border border-zinc-200 bg-white text-zinc-600\"\n aria-label=\"Back to live calls\"\n (click)=\"goToLiveCalls()\"\n >\n <ed-icon name=\"arrow_back\" [size]=\"19\" />\n </button>\n <p class=\"m-0 text-zinc-500\">Call ended or not found.</p>\n </div>\n }\n }\n</div>\n", styles: [".vr-sentiment{display:inline-flex;align-items:center;justify-content:center;gap:4px;height:20px;padding:2px 4px;border-radius:6px;font-size:11px;font-weight:500;line-height:16px;white-space:nowrap}.vr-sentiment__icon{display:block;width:16px;height:16px;flex-shrink:0;overflow:hidden}.vr-sentiment__icon img{display:block;width:100%;height:100%;max-width:none}.vr-sentiment[data-sentiment=calm]{background:#3681f41a;color:#2162f0}.vr-sentiment[data-sentiment=neutral]{background:#f0f2f5;color:#41464c}.vr-sentiment[data-sentiment=urgent]{background:#f26b0d1a;color:#e94e0c}.vr-sentiment[data-sentiment=frustrated]{background:#f037401a;color:#db0c15}.live-monitor-page__eyebrow{font-size:10px;font-weight:600;letter-spacing:1px;line-height:1;text-transform:uppercase}.live-monitor-page__confidence-track{height:8px;overflow:hidden;border-radius:9999px;background:linear-gradient(90deg,#7d34fb1a,#e76b171a)}.live-monitor-page__confidence-fill{display:block;height:100%;border-radius:9999px;background:linear-gradient(90deg,#7d34fb,#e76b17)}.live-monitor-page__sidebar-block{min-height:16rem}.live-monitor-page__tools li{position:relative;padding-left:1.25rem}.live-monitor-page__tools li:before{content:\"\";position:absolute;top:.35rem;left:0;width:.5rem;height:.5rem;border-radius:9999px;background:#a2a4a6}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: EdAvatarComponent, selector: "ed-avatar", inputs: ["initials", "tone", "shape", "size", "gradient"] }, { kind: "component", type: IconComponent, selector: "ed-icon", inputs: ["name", "size", "color", "cssClass", "decorative", "ariaHidden", "ariaLabel", "title", "desc", "focusable", "tabIndex"] }, { kind: "component", type: EdButtonComponent, selector: "ed-button", inputs: ["variant", "size", "disabled", "ghost", "outline", "subtle", "fill", "loading", "block", "elevated", "shape", "type", "href", "routerLink", "rel", "target", "download", "ariaLabel"], outputs: ["click$"] }, { kind: "pipe", type: EdsElapsedPipe, name: "edsElapsed" }, { kind: "component", type: LiveTranscriptPanelComponent, selector: "eds-live-transcript-panel", inputs: ["live", "paused", "humanTakeover", "showStaffInput", "callStarted", "staffMessage", "callerName", "callerMatched"], outputs: ["staffMessageChange", "sendMessage"] }, { kind: "component", type: CallRecordingPlayerComponent, selector: "eds-call-recording-player", inputs: ["callId"], outputs: ["currentTimeChange", "durationChange"] }, { kind: "component", type: EdSkeletonComponent, selector: "ed-skeleton", inputs: ["class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1097
1101
|
}
|
|
1098
1102
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: LiveMonitorPageComponent, decorators: [{
|
|
1099
1103
|
type: Component,
|
|
1100
1104
|
args: [{ selector: 'eds-live-monitor-page', imports: [
|
|
1101
|
-
RouterLink,
|
|
1102
1105
|
FormsModule,
|
|
1103
1106
|
EdAvatarComponent,
|
|
1104
1107
|
IconComponent,
|
|
@@ -1107,8 +1110,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
|
|
|
1107
1110
|
LiveTranscriptPanelComponent,
|
|
1108
1111
|
CallRecordingPlayerComponent,
|
|
1109
1112
|
EdSkeletonComponent,
|
|
1110
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, schemas: [CUSTOM_ELEMENTS_SCHEMA], host: { class: 'block h-full min-h-0' }, template: "<div class=\"live-monitor-page box-border flex h-full min-h-0 flex-col overflow-hidden bg-lighter p-base text-zinc-900\">\n @if (loading()) {\n <div class=\"flex min-h-0 flex-1 flex-col gap-4\" aria-busy=\"true\">\n <header class=\"flex shrink-0 items-center justify-between gap-3 rounded-lg bg-white p-3\">\n <div class=\"flex min-w-0 flex-1 items-center gap-4\">\n <ed-skeleton class=\"h-5 w-5 !rounded-sm\"></ed-skeleton>\n <div class=\"flex min-w-0 items-center gap-2\">\n <ed-skeleton class=\"h-9 w-9 !rounded-md\"></ed-skeleton>\n <div class=\"grid min-w-0 gap-2xs\">\n <ed-skeleton class=\"h-4 w-40\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-32\"></ed-skeleton>\n </div>\n </div>\n </div>\n <div class=\"flex shrink-0 items-center gap-3\">\n <ed-skeleton class=\"h-8 w-24 !rounded-md\"></ed-skeleton>\n <ed-skeleton class=\"h-8 w-28 !rounded-md\"></ed-skeleton>\n </div>\n </header>\n <div class=\"grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_minmax(280px,447px)] items-stretch gap-4 max-[1100px]:grid-cols-1\">\n <div class=\"flex min-h-0 flex-col rounded-xl bg-white p-4\">\n <ed-skeleton class=\"mb-4 h-5 w-36\"></ed-skeleton>\n @for (_ of transcriptSkeletonRows; track _) {\n <div class=\"mb-4 flex flex-col gap-2\">\n <ed-skeleton class=\"h-3 w-24\"></ed-skeleton>\n <ed-skeleton class=\"h-16 w-full max-w-xl !rounded-lg\"></ed-skeleton>\n </div>\n }\n </div>\n <aside class=\"flex min-h-0 flex-col gap-4\">\n <ed-skeleton class=\"h-40 w-full !rounded-lg\"></ed-skeleton>\n <ed-skeleton class=\"min-h-48 w-full flex-1 !rounded-lg\"></ed-skeleton>\n <ed-skeleton class=\"h-48 w-full !rounded-lg\"></ed-skeleton>\n </aside>\n </div>\n </div>\n } @else {\n @if (call(); as c) {\n <header class=\"live-monitor-page__header mb-4 flex shrink-0 flex-wrap items-center justify-between gap-3 rounded-lg bg-white p-3\">\n <div class=\"flex min-w-0 flex-1 items-center gap-4\">\n <a\n routerLink=\"..\"\n class=\"live-monitor-page__back inline-flex h-5 w-5 shrink-0 items-center justify-center text-zinc-600 no-underline\"\n aria-label=\"Back to live calls\"\n >\n <ed-icon name=\"arrow_back\" [size]=\"14\" />\n </a>\n\n <div class=\"live-monitor-page__profile flex min-w-0 items-center gap-2\">\n <ed-avatar\n class=\"shrink-0\"\n shape=\"box\"\n size=\"md\"\n [initials]=\"profileInitials(c)\"\n [tone]=\"isUnknownCaller(c) ? 'gray' : 'warm'\"\n />\n\n <div class=\"min-w-0\">\n <div class=\"flex flex-wrap items-center gap-3\">\n <h4 class=\"m-0\">{{ displayName(c) }}</h4>\n @if (isUnknownCaller(c)) {\n <ed-badge-2 variant=\"primary\" pill uppercase>Unknown</ed-badge-2>\n } @else {\n @if (goalBadge(c); as badge) {\n <ed-badge-2 variant=\"primary\" pill uppercase>{{ badge }}</ed-badge-2>\n }\n }\n </div>\n <p class=\"m-0 mt-0.5 text-zinc-500\">{{ c.phone || profileMeta(c) }}</p>\n </div>\n </div>\n </div>\n\n <div class=\"live-monitor-page__header-actions flex flex-wrap items-center gap-3\">\n @if (!callEnded()) {\n @if (mediaState() === 'connected' && isMediaForThisCall() && !audioPlaybackAllowed()) {\n <ed-button variant=\"secondary\" outline size=\"sm\" (click$)=\"enableSound()\">\n <ed-icon slot=\"prefix\" name=\"mic\" [size]=\"16\" />\n Enable sound\n </ed-button>\n }\n @if (isMediaForThisCall() && (mediaState() === 'connected' || mediaState() === 'connecting')) {\n <span\n class=\"live-monitor-page__media-pill inline-flex h-7 items-center rounded-full border border-zinc-200 bg-zinc-50 px-3 text-zinc-600\"\n [attr.data-mode]=\"mediaMode() || 'idle'\"\n [attr.data-state]=\"mediaState()\"\n >\n @if (mediaState() === 'connecting') {\n Connecting\u2026\n } @else if (mediaMode() === 'takeover') {\n {{ micEnabled() ? 'On call \u00B7 mic on' : 'On call \u00B7 mic off' }}\n } @else {\n Joined\n }\n </span>\n }\n <ed-button\n variant=\"danger\"\n outline\n size=\"sm\"\n [disabled]=\"mediaBusy()\"\n (click$)=\"endCall()\"\n >\n <ed-icon slot=\"prefix\" name=\"call_end\" [size]=\"16\" />\n End Call\n </ed-button>\n @if (isTakeover()) {\n @if (mediaState() === 'connected' && isMediaForThisCall()) {\n <ed-button variant=\"secondary\" outline size=\"sm\" (click$)=\"toggleMic()\">\n <ed-icon slot=\"prefix\" [name]=\"micEnabled() ? 'mic' : 'mic_off'\" [size]=\"16\" />\n {{ micEnabled() ? 'Mute' : 'Unmute' }}\n </ed-button>\n }\n } @else {\n <ed-button\n variant=\"primary\"\n fill\n size=\"sm\"\n [disabled]=\"mediaBusy()\"\n (click$)=\"takeover()\"\n >\n <ed-icon slot=\"prefix\" name=\"phone_call\" [size]=\"16\" />\n Take Over\n </ed-button>\n }\n } @else {\n <span class=\"inline-flex items-center gap-2 font-mono text-zinc-900\">\n <span class=\"h-2 w-2 rounded-full bg-green-500 ring-4 ring-green-500/15\" aria-hidden=\"true\"></span>\n {{ c.started | edsElapsed }}\n </span>\n }\n </div>\n </header>\n\n @if (callEnded()) {\n <div class=\"mb-4 flex items-center gap-2 rounded-lg bg-green-50 px-4 py-3 text-green-700\" role=\"status\">\n <ed-icon name=\"check_circle\" [size]=\"18\" />\n Call ended\n </div>\n }\n\n <div\n class=\"live-monitor-page__grid grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_360px] items-stretch gap-4 max-[1100px]:grid-cols-1 max-[1100px]:overflow-auto\"\n >\n <div class=\"live-monitor-page__main flex h-full min-h-0 flex-col overflow-hidden\">\n <eds-live-transcript-panel\n [live]=\"isLive()\"\n [paused]=\"isPaused()\"\n [humanTakeover]=\"isTakeover()\"\n [callStarted]=\"c.started\"\n [callerName]=\"c.name\"\n [callerMatched]=\"c.matched\"\n />\n\n @if (callEnded()) {\n <section class=\"live-monitor-page__post-call flex flex-col gap-4\">\n <div class=\"rounded-lg border border-zinc-200 bg-white p-4 text-zinc-600\">\n <h4 class=\"mb-3 m-0\">Call summary</h4>\n @if (summaryLoading()) {\n <div class=\"grid gap-2xs\" aria-busy=\"true\">\n <ed-skeleton class=\"h-3 w-full\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-5/6\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-3/4\"></ed-skeleton>\n </div>\n } @else if (summary()) {\n <p class=\"m-0\">{{ summary() }}</p>\n } @else {\n <p class=\"m-0 text-zinc-500\">Summary will appear when processing completes.</p>\n }\n </div>\n <eds-call-recording-player [callId]=\"callId()\" />\n </section>\n }\n </div>\n\n <aside class=\"live-monitor-page__sidebar flex h-full min-h-0 flex-col gap-4 overflow-hidden\">\n @if (!callEnded()) {\n <section class=\"flex shrink-0 flex-col gap-4 rounded-lg bg-white p-4\" aria-labelledby=\"live-call-state-title\">\n <header class=\"flex items-center gap-3\">\n <span class=\"inline-flex items-center justify-center rounded-md bg-fuchsia-500/10 p-1 text-fuchsia-600\">\n <ed-icon name=\"phone_call\" [size]=\"16\" />\n </span>\n <h4 id=\"live-call-state-title\" class=\"m-0\">Call State</h4>\n </header>\n\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex items-center justify-between gap-2 text-zinc-600\">\n <span class=\"live-monitor-page__eyebrow\">Agent confidence</span>\n <span class=\"live-monitor-page__eyebrow\">{{ c.confidence }}%</span>\n </div>\n <div class=\"live-monitor-page__confidence-track\">\n <span\n class=\"live-monitor-page__confidence-fill\"\n [style.width.%]=\"c.confidence\"\n [attr.data-level]=\"confidenceLevel(c.confidence)\"\n ></span>\n </div>\n <div class=\"flex items-center gap-2 text-zinc-600\">\n <ed-icon\n [name]=\"confidenceLevel(c.confidence) === 'low' ? 'trend_down' : 'info'\"\n [size]=\"16\"\n />\n <span>{{ confidenceHint(c.confidence) }}</span>\n </div>\n </div>\n\n <div class=\"flex items-center justify-between gap-3\">\n <span class=\"live-monitor-page__eyebrow\">Patient sentiment</span>\n <span class=\"vr-sentiment\" [attr.data-sentiment]=\"c.sentiment\">\n <span class=\"vr-sentiment__icon\" aria-hidden=\"true\">\n <img [src]=\"sentimentIcon(c.sentiment)\" alt=\"\" width=\"16\" height=\"16\" />\n </span>\n <span>{{ sentimentLabel(c.sentiment) }}</span>\n </span>\n </div>\n </section>\n }\n\n <section\n class=\"live-monitor-page__sidebar-block flex min-h-[16rem] flex-1 flex-col gap-4 overflow-hidden rounded-lg bg-white p-4\"\n aria-labelledby=\"live-goal-timeline-title\"\n >\n <header class=\"flex items-center gap-3\">\n <span class=\"inline-flex items-center justify-center rounded-md bg-blue-500/10 p-1 text-blue-600\">\n <ed-icon name=\"flag\" [size]=\"16\" />\n </span>\n <h4 id=\"live-goal-timeline-title\" class=\"m-0\">Goal Timeline</h4>\n </header>\n\n <div class=\"live-monitor-page__goal-panel flex min-h-0 flex-1 flex-col rounded-lg bg-zinc-50 p-2\">\n @if (c.goal) {\n <p class=\"live-monitor-page__eyebrow mb-3 m-0 text-zinc-900\">{{ c.goal }}</p>\n } @else {\n <p class=\"live-monitor-page__eyebrow mb-3 m-0 text-zinc-900\">No Goal identified yet</p>\n }\n\n <ul class=\"live-monitor-page__tools m-0 flex min-h-0 flex-1 list-none flex-col gap-4 overflow-auto p-0\">\n @for (tool of liveTools(); track toolTrackKey(tool, $index)) {\n <li class=\"flex flex-col gap-1\" [class]=\"toolStateClass(tool.state)\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <span class=\"text-zinc-900\">{{ tool.name }}</span>\n <ed-badge-2 [attr.variant]=\"toolBadgeTone(tool.state)\" pill uppercase>\n {{ toolBadgeLabel(tool.state) }}\n </ed-badge-2>\n </div>\n @if (tool.result) {\n <p class=\"m-0 text-zinc-500\">{{ tool.result }}</p>\n }\n </li>\n } @empty {\n @for (step of goalPlaceholderSteps; track step.title) {\n <li class=\"flex flex-col gap-1\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <span class=\"text-zinc-900\">{{ step.title }}</span>\n <ed-badge-2 [attr.variant]=\"step.tone\" pill uppercase>{{ step.label }}</ed-badge-2>\n </div>\n <p class=\"m-0 text-zinc-500\">{{ step.detail }}</p>\n </li>\n }\n }\n </ul>\n </div>\n </section>\n </aside>\n </div>\n } @else {\n <div class=\"flex flex-col items-start gap-3\">\n <a\n routerLink=\"..\"\n class=\"live-monitor-page__back inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md border border-zinc-200 bg-white text-zinc-600 no-underline\"\n aria-label=\"Back to live calls\"\n >\n <ed-icon name=\"arrow_back\" [size]=\"19\" />\n </a>\n <p class=\"m-0 text-zinc-500\">Call ended or not found.</p>\n </div>\n }\n }\n</div>\n", styles: [".vr-sentiment{display:inline-flex;align-items:center;justify-content:center;gap:4px;height:20px;padding:2px 4px;border-radius:6px;font-size:11px;font-weight:500;line-height:16px;white-space:nowrap}.vr-sentiment__icon{display:block;width:16px;height:16px;flex-shrink:0;overflow:hidden}.vr-sentiment__icon img{display:block;width:100%;height:100%;max-width:none}.vr-sentiment[data-sentiment=calm]{background:#3681f41a;color:#2162f0}.vr-sentiment[data-sentiment=neutral]{background:#f0f2f5;color:#41464c}.vr-sentiment[data-sentiment=urgent]{background:#f26b0d1a;color:#e94e0c}.vr-sentiment[data-sentiment=frustrated]{background:#f037401a;color:#db0c15}.live-monitor-page__eyebrow{font-size:10px;font-weight:600;letter-spacing:1px;line-height:1;text-transform:uppercase}.live-monitor-page__confidence-track{height:8px;overflow:hidden;border-radius:9999px;background:linear-gradient(90deg,#7d34fb1a,#e76b171a)}.live-monitor-page__confidence-fill{display:block;height:100%;border-radius:9999px;background:linear-gradient(90deg,#7d34fb,#e76b17)}.live-monitor-page__sidebar-block{min-height:16rem}.live-monitor-page__tools li{position:relative;padding-left:1.25rem}.live-monitor-page__tools li:before{content:\"\";position:absolute;top:.35rem;left:0;width:.5rem;height:.5rem;border-radius:9999px;background:#a2a4a6}\n"] }]
|
|
1113
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, schemas: [CUSTOM_ELEMENTS_SCHEMA], host: { class: 'block h-full min-h-0' }, template: "<div class=\"live-monitor-page box-border flex h-full min-h-0 flex-col overflow-hidden bg-lighter p-base text-zinc-900\">\n @if (loading()) {\n <div class=\"flex min-h-0 flex-1 flex-col gap-4\" aria-busy=\"true\">\n <header class=\"flex shrink-0 items-center justify-between gap-3 rounded-lg bg-white p-3\">\n <div class=\"flex min-w-0 flex-1 items-center gap-4\">\n <ed-skeleton class=\"h-5 w-5 !rounded-sm\"></ed-skeleton>\n <div class=\"flex min-w-0 items-center gap-2\">\n <ed-skeleton class=\"h-9 w-9 !rounded-md\"></ed-skeleton>\n <div class=\"grid min-w-0 gap-2xs\">\n <ed-skeleton class=\"h-4 w-40\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-32\"></ed-skeleton>\n </div>\n </div>\n </div>\n <div class=\"flex shrink-0 items-center gap-3\">\n <ed-skeleton class=\"h-8 w-24 !rounded-md\"></ed-skeleton>\n <ed-skeleton class=\"h-8 w-28 !rounded-md\"></ed-skeleton>\n </div>\n </header>\n <div class=\"grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_minmax(280px,447px)] items-stretch gap-4 max-[1100px]:grid-cols-1\">\n <div class=\"flex min-h-0 flex-col rounded-xl bg-white p-4\">\n <ed-skeleton class=\"mb-4 h-5 w-36\"></ed-skeleton>\n @for (_ of transcriptSkeletonRows; track _) {\n <div class=\"mb-4 flex flex-col gap-2\">\n <ed-skeleton class=\"h-3 w-24\"></ed-skeleton>\n <ed-skeleton class=\"h-16 w-full max-w-xl !rounded-lg\"></ed-skeleton>\n </div>\n }\n </div>\n <aside class=\"flex min-h-0 flex-col gap-4\">\n <ed-skeleton class=\"h-40 w-full !rounded-lg\"></ed-skeleton>\n <ed-skeleton class=\"min-h-48 w-full flex-1 !rounded-lg\"></ed-skeleton>\n <ed-skeleton class=\"h-48 w-full !rounded-lg\"></ed-skeleton>\n </aside>\n </div>\n </div>\n } @else {\n @if (call(); as c) {\n <header class=\"live-monitor-page__header mb-4 flex shrink-0 flex-wrap items-center justify-between gap-3 rounded-lg bg-white p-3\">\n <div class=\"flex min-w-0 flex-1 items-center gap-4\">\n <button\n type=\"button\"\n class=\"eds-plain-nav live-monitor-page__back inline-flex h-5 w-5 shrink-0 items-center justify-center text-zinc-600\"\n aria-label=\"Back to live calls\"\n (click)=\"goToLiveCalls()\"\n >\n <ed-icon name=\"arrow_back\" [size]=\"14\" />\n </button>\n\n <div class=\"live-monitor-page__profile flex min-w-0 items-center gap-2\">\n <ed-avatar\n class=\"shrink-0\"\n shape=\"box\"\n size=\"md\"\n [initials]=\"profileInitials(c)\"\n [tone]=\"isUnknownCaller(c) ? 'gray' : 'warm'\"\n />\n\n <div class=\"min-w-0\">\n <div class=\"flex flex-wrap items-center gap-3\">\n <h4 class=\"m-0\">{{ displayName(c) }}</h4>\n @if (isUnknownCaller(c)) {\n <ed-badge-2 variant=\"primary\" pill uppercase>Unknown</ed-badge-2>\n } @else {\n @if (goalBadge(c); as badge) {\n <ed-badge-2 variant=\"primary\" pill uppercase>{{ badge }}</ed-badge-2>\n }\n }\n </div>\n <p class=\"m-0 mt-0.5 text-zinc-500\">{{ c.phone || profileMeta(c) }}</p>\n </div>\n </div>\n </div>\n\n <div class=\"live-monitor-page__header-actions flex flex-wrap items-center gap-3\">\n @if (!callEnded()) {\n @if (mediaState() === 'connected' && isMediaForThisCall() && !audioPlaybackAllowed()) {\n <ed-button variant=\"secondary\" outline size=\"sm\" (click$)=\"enableSound()\">\n <ed-icon slot=\"prefix\" name=\"mic\" [size]=\"16\" />\n Enable sound\n </ed-button>\n }\n @if (isMediaForThisCall() && (mediaState() === 'connected' || mediaState() === 'connecting')) {\n <span\n class=\"live-monitor-page__media-pill inline-flex h-7 items-center rounded-full border border-zinc-200 bg-zinc-50 px-3 text-zinc-600\"\n [attr.data-mode]=\"mediaMode() || 'idle'\"\n [attr.data-state]=\"mediaState()\"\n >\n @if (mediaState() === 'connecting') {\n Connecting\u2026\n } @else if (mediaMode() === 'takeover') {\n {{ micEnabled() ? 'On call \u00B7 mic on' : 'On call \u00B7 mic off' }}\n } @else {\n Joined\n }\n </span>\n }\n <ed-button\n variant=\"danger\"\n outline\n size=\"sm\"\n [disabled]=\"mediaBusy()\"\n (click$)=\"endCall()\"\n >\n <ed-icon slot=\"prefix\" name=\"call_end\" [size]=\"16\" />\n End Call\n </ed-button>\n @if (isTakeover()) {\n @if (mediaState() === 'connected' && isMediaForThisCall()) {\n <ed-button variant=\"secondary\" outline size=\"sm\" (click$)=\"toggleMic()\">\n <ed-icon slot=\"prefix\" [name]=\"micEnabled() ? 'mic' : 'mic_off'\" [size]=\"16\" />\n {{ micEnabled() ? 'Mute' : 'Unmute' }}\n </ed-button>\n }\n } @else {\n <ed-button\n variant=\"primary\"\n fill\n size=\"sm\"\n [disabled]=\"mediaBusy()\"\n (click$)=\"takeover()\"\n >\n <ed-icon slot=\"prefix\" name=\"phone_call\" [size]=\"16\" />\n Take Over\n </ed-button>\n }\n } @else {\n <span class=\"inline-flex items-center gap-2 font-mono text-zinc-900\">\n <span class=\"h-2 w-2 rounded-full bg-green-500 ring-4 ring-green-500/15\" aria-hidden=\"true\"></span>\n {{ c.started | edsElapsed }}\n </span>\n }\n </div>\n </header>\n\n @if (callEnded()) {\n <div class=\"mb-4 flex items-center gap-2 rounded-lg bg-green-50 px-4 py-3 text-green-700\" role=\"status\">\n <ed-icon name=\"check_circle\" [size]=\"18\" />\n Call ended\n </div>\n }\n\n <div\n class=\"live-monitor-page__grid grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_360px] items-stretch gap-4 max-[1100px]:grid-cols-1 max-[1100px]:overflow-auto\"\n >\n <div class=\"live-monitor-page__main flex h-full min-h-0 flex-col overflow-hidden\">\n <eds-live-transcript-panel\n [live]=\"isLive()\"\n [paused]=\"isPaused()\"\n [humanTakeover]=\"isTakeover()\"\n [callStarted]=\"c.started\"\n [callerName]=\"c.name\"\n [callerMatched]=\"c.matched\"\n />\n\n @if (callEnded()) {\n <section class=\"live-monitor-page__post-call flex flex-col gap-4\">\n <div class=\"rounded-lg border border-zinc-200 bg-white p-4 text-zinc-600\">\n <h4 class=\"mb-3 m-0\">Call summary</h4>\n @if (summaryLoading()) {\n <div class=\"grid gap-2xs\" aria-busy=\"true\">\n <ed-skeleton class=\"h-3 w-full\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-5/6\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-3/4\"></ed-skeleton>\n </div>\n } @else if (summary()) {\n <p class=\"m-0\">{{ summary() }}</p>\n } @else {\n <p class=\"m-0 text-zinc-500\">Summary will appear when processing completes.</p>\n }\n </div>\n <eds-call-recording-player [callId]=\"callId()\" />\n </section>\n }\n </div>\n\n <aside class=\"live-monitor-page__sidebar flex h-full min-h-0 flex-col gap-4 overflow-hidden\">\n @if (!callEnded()) {\n <section class=\"flex shrink-0 flex-col gap-4 rounded-lg bg-white p-4\" aria-labelledby=\"live-call-state-title\">\n <header class=\"flex items-center gap-3\">\n <span class=\"inline-flex items-center justify-center rounded-md bg-fuchsia-500/10 p-1 text-fuchsia-600\">\n <ed-icon name=\"phone_call\" [size]=\"16\" />\n </span>\n <h4 id=\"live-call-state-title\" class=\"m-0\">Call State</h4>\n </header>\n\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex items-center justify-between gap-2 text-zinc-600\">\n <span class=\"live-monitor-page__eyebrow\">Agent confidence</span>\n <span class=\"live-monitor-page__eyebrow\">{{ c.confidence }}%</span>\n </div>\n <div class=\"live-monitor-page__confidence-track\">\n <span\n class=\"live-monitor-page__confidence-fill\"\n [style.width.%]=\"c.confidence\"\n [attr.data-level]=\"confidenceLevel(c.confidence)\"\n ></span>\n </div>\n <div class=\"flex items-center gap-2 text-zinc-600\">\n <ed-icon\n [name]=\"confidenceLevel(c.confidence) === 'low' ? 'trend_down' : 'info'\"\n [size]=\"16\"\n />\n <span>{{ confidenceHint(c.confidence) }}</span>\n </div>\n </div>\n\n <div class=\"flex items-center justify-between gap-3\">\n <span class=\"live-monitor-page__eyebrow\">Patient sentiment</span>\n <span class=\"vr-sentiment\" [attr.data-sentiment]=\"c.sentiment\">\n <span class=\"vr-sentiment__icon\" aria-hidden=\"true\">\n <img [src]=\"sentimentIcon(c.sentiment)\" alt=\"\" width=\"16\" height=\"16\" />\n </span>\n <span>{{ sentimentLabel(c.sentiment) }}</span>\n </span>\n </div>\n </section>\n }\n\n <section\n class=\"live-monitor-page__sidebar-block flex min-h-[16rem] flex-1 flex-col gap-4 overflow-hidden rounded-lg bg-white p-4\"\n aria-labelledby=\"live-goal-timeline-title\"\n >\n <header class=\"flex items-center gap-3\">\n <span class=\"inline-flex items-center justify-center rounded-md bg-blue-500/10 p-1 text-blue-600\">\n <ed-icon name=\"flag\" [size]=\"16\" />\n </span>\n <h4 id=\"live-goal-timeline-title\" class=\"m-0\">Goal Timeline</h4>\n </header>\n\n <div class=\"live-monitor-page__goal-panel flex min-h-0 flex-1 flex-col rounded-lg bg-zinc-50 p-2\">\n @if (c.goal) {\n <p class=\"live-monitor-page__eyebrow mb-3 m-0 text-zinc-900\">{{ c.goal }}</p>\n } @else {\n <p class=\"live-monitor-page__eyebrow mb-3 m-0 text-zinc-900\">No Goal identified yet</p>\n }\n\n <ul class=\"live-monitor-page__tools m-0 flex min-h-0 flex-1 list-none flex-col gap-4 overflow-auto p-0\">\n @for (tool of liveTools(); track toolTrackKey(tool, $index)) {\n <li class=\"flex flex-col gap-1\" [class]=\"toolStateClass(tool.state)\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <span class=\"text-zinc-900\">{{ tool.name }}</span>\n <ed-badge-2 [attr.variant]=\"toolBadgeTone(tool.state)\" pill uppercase>\n {{ toolBadgeLabel(tool.state) }}\n </ed-badge-2>\n </div>\n @if (tool.result) {\n <p class=\"m-0 text-zinc-500\">{{ tool.result }}</p>\n }\n </li>\n } @empty {\n @for (step of goalPlaceholderSteps; track step.title) {\n <li class=\"flex flex-col gap-1\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <span class=\"text-zinc-900\">{{ step.title }}</span>\n <ed-badge-2 [attr.variant]=\"step.tone\" pill uppercase>{{ step.label }}</ed-badge-2>\n </div>\n <p class=\"m-0 text-zinc-500\">{{ step.detail }}</p>\n </li>\n }\n }\n </ul>\n </div>\n </section>\n </aside>\n </div>\n } @else {\n <div class=\"flex flex-col items-start gap-3\">\n <button\n type=\"button\"\n class=\"live-monitor-page__back inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md border border-zinc-200 bg-white text-zinc-600\"\n aria-label=\"Back to live calls\"\n (click)=\"goToLiveCalls()\"\n >\n <ed-icon name=\"arrow_back\" [size]=\"19\" />\n </button>\n <p class=\"m-0 text-zinc-500\">Call ended or not found.</p>\n </div>\n }\n }\n</div>\n", styles: [".vr-sentiment{display:inline-flex;align-items:center;justify-content:center;gap:4px;height:20px;padding:2px 4px;border-radius:6px;font-size:11px;font-weight:500;line-height:16px;white-space:nowrap}.vr-sentiment__icon{display:block;width:16px;height:16px;flex-shrink:0;overflow:hidden}.vr-sentiment__icon img{display:block;width:100%;height:100%;max-width:none}.vr-sentiment[data-sentiment=calm]{background:#3681f41a;color:#2162f0}.vr-sentiment[data-sentiment=neutral]{background:#f0f2f5;color:#41464c}.vr-sentiment[data-sentiment=urgent]{background:#f26b0d1a;color:#e94e0c}.vr-sentiment[data-sentiment=frustrated]{background:#f037401a;color:#db0c15}.live-monitor-page__eyebrow{font-size:10px;font-weight:600;letter-spacing:1px;line-height:1;text-transform:uppercase}.live-monitor-page__confidence-track{height:8px;overflow:hidden;border-radius:9999px;background:linear-gradient(90deg,#7d34fb1a,#e76b171a)}.live-monitor-page__confidence-fill{display:block;height:100%;border-radius:9999px;background:linear-gradient(90deg,#7d34fb,#e76b17)}.live-monitor-page__sidebar-block{min-height:16rem}.live-monitor-page__tools li{position:relative;padding-left:1.25rem}.live-monitor-page__tools li:before{content:\"\";position:absolute;top:.35rem;left:0;width:.5rem;height:.5rem;border-radius:9999px;background:#a2a4a6}\n"] }]
|
|
1111
1114
|
}], ctorParameters: () => [] });
|
|
1112
1115
|
|
|
1113
1116
|
export { LiveMonitorPageComponent };
|
|
1114
|
-
//# sourceMappingURL=edvak99-voice-receptionist-widget-live-monitor-page.component-
|
|
1117
|
+
//# sourceMappingURL=edvak99-voice-receptionist-widget-live-monitor-page.component-BzvMkbC4.mjs.map
|