@edvak99/voice-receptionist-widget 1.1.4 → 1.1.6
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/README.md +37 -0
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-CdrW0He4.mjs +306 -0
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-CdrW0He4.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-ai-receptionist-api.service-48cifVPF.mjs → edvak99-voice-receptionist-widget-ai-receptionist-api.service-DokIb-9r.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-ai-receptionist-api.service-48cifVPF.mjs.map → edvak99-voice-receptionist-widget-ai-receptionist-api.service-DokIb-9r.mjs.map} +1 -1
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-4GK1vsom.mjs +277 -0
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-4GK1vsom.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-Bw_Ade1s.mjs +416 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-Bw_Ade1s.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DnwYnjIE.mjs +193 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DnwYnjIE.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-recording-player.component-Pla2UgeZ.mjs +111 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-recording-player.component-Pla2UgeZ.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-CIMcjGKX.mjs → edvak99-voice-receptionist-widget-call-review.service-Di7GLG9k.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-CIMcjGKX.mjs.map → edvak99-voice-receptionist-widget-call-review.service-Di7GLG9k.mjs.map} +1 -1
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-DVz0romd.mjs +439 -0
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-DVz0romd.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-dashboard-socket.service-BX91r74O.mjs → edvak99-voice-receptionist-widget-dashboard-socket.service-D2pelzu4.mjs} +3 -3
- package/fesm2022/{edvak99-voice-receptionist-widget-dashboard-socket.service-BX91r74O.mjs.map → edvak99-voice-receptionist-widget-dashboard-socket.service-D2pelzu4.mjs.map} +1 -1
- package/fesm2022/{edvak99-voice-receptionist-widget-eds-avatar.component-CWRiJGfv.mjs → edvak99-voice-receptionist-widget-eds-avatar.component-BJs_TH8X.mjs} +5 -5
- package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-BJs_TH8X.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-eds-pulse-dot.component-BPY6xN7C.mjs → edvak99-voice-receptionist-widget-eds-pulse-dot.component-Ig0Eoq1z.mjs} +6 -6
- package/fesm2022/edvak99-voice-receptionist-widget-eds-pulse-dot.component-Ig0Eoq1z.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-Bx7WQ2dg.mjs +81 -0
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-Bx7WQ2dg.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-DWbA5yAG.mjs +1059 -0
- package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-DWbA5yAG.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-modal.component-Cb-FoQ1N.mjs +48 -0
- package/fesm2022/edvak99-voice-receptionist-widget-modal.component-Cb-FoQ1N.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-DhoyrHSe.mjs +267 -0
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-DhoyrHSe.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-CZUDgZ1_.mjs +967 -0
- package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-CZUDgZ1_.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-DyNG2DZZ.mjs +316 -0
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-DyNG2DZZ.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-DjaurkbO.mjs +46 -0
- package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-DjaurkbO.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget.mjs +228 -1
- package/fesm2022/edvak99-voice-receptionist-widget.mjs.map +1 -1
- package/lib/components/call-conversation-panel/call-conversation-panel.component.d.ts +1 -2
- package/lib/components/live-transcript-panel/live-transcript-panel.component.d.ts +0 -3
- package/package.json +1 -1
- package/public-api.d.ts +3 -0
- package/styles/chat-ui.scss +0 -1
- package/styles/eds.scss +0 -10
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-DT9UKvQO.mjs +0 -307
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-DT9UKvQO.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-C5inEHJw.mjs +0 -278
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-C5inEHJw.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-sYhM20CE.mjs +0 -418
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-sYhM20CE.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DpwajEOE.mjs +0 -194
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DpwajEOE.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-recording-player.component-CgkRcFOl.mjs +0 -111
- package/fesm2022/edvak99-voice-receptionist-widget-call-recording-player.component-CgkRcFOl.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-CxbO0eDS.mjs +0 -440
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-CxbO0eDS.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-CWRiJGfv.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-eds-button.component-Bg5T5BN8.mjs +0 -27
- package/fesm2022/edvak99-voice-receptionist-widget-eds-button.component-Bg5T5BN8.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-eds-pulse-dot.component-BPY6xN7C.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-BsBQIS_f.mjs +0 -203
- package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-BsBQIS_f.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-CaSDMxpV.mjs +0 -81
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-CaSDMxpV.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-Cy3ND-c-.mjs +0 -1070
- package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-Cy3ND-c-.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-modal.component-COfi7biN.mjs +0 -48
- package/fesm2022/edvak99-voice-receptionist-widget-modal.component-COfi7biN.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-fHVzpiVk.mjs +0 -268
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-fHVzpiVk.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-Bok64X2q.mjs +0 -968
- package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-Bok64X2q.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-Bb9lPcYi.mjs +0 -317
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-Bb9lPcYi.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-BZVlWLWh.mjs +0 -47
- package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-BZVlWLWh.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-dashboard-page.component-DVz0romd.mjs","sources":["../../../projects/voice-receptionist-widget/src/lib/components/outbound-dial-modal/outbound-dial-modal.component.ts","../../../projects/voice-receptionist-widget/src/lib/components/outbound-dial-modal/outbound-dial-modal.component.html","../../../projects/voice-receptionist-widget/src/lib/pages/dashboard-page/dashboard-page.component.ts","../../../projects/voice-receptionist-widget/src/lib/pages/dashboard-page/dashboard-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 {\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 { EdsButtonComponent } from '../../ui/eds-button.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, EdsButtonComponent, DatePipe],\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 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 patientQuery = '';\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 = 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 = 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 = '';\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 = '';\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 <p class=\"eds-body-sm\">Loading purposes…</p>\n } @else {\n <form class=\"flex flex-col gap-3\" (ngSubmit)=\"submit()\" novalidate autocomplete=\"off\">\n <div class=\"dial-form__field dial-form__typeahead relative flex flex-col gap-1.5\">\n <span class=\"eds-label\">Patient <span class=\"text-[#dc2626]\">*</span></span>\n\n @if (selectedPatient(); as patient) {\n <div class=\"flex items-center justify-between gap-3 rounded-lg border border-[var(--eds-border-subtle,#e4e4e7)] bg-[var(--eds-surface-muted,#f8fafc)] px-3 py-2.5\">\n <div class=\"flex min-w-0 flex-col gap-0.5\">\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 <input\n type=\"search\"\n class=\"dial-form__input rounded-lg border px-2.5 py-2 font-[inherit]\"\n [ngModel]=\"patientQuery\"\n (ngModelChange)=\"onPatientQueryChange($event)\"\n name=\"patientQuery\"\n placeholder=\"Search by name or phone\"\n autocomplete=\"off\"\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-0.5 border-none border-b px-3 py-2.5 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\n @if (selectedPatient()) {\n <div class=\"flex flex-col gap-1.5\">\n <span class=\"eds-label\">Appointment</span>\n @if (loadingAppointments()) {\n <span class=\"dial-form__hint text-xs\">Loading appointments…</span>\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-2.5 border-b px-3 py-2.5 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-2.5 border-b px-3 py-2.5 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-0.5\">\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 <label class=\"flex flex-col gap-1.5\">\n <span class=\"eds-label\">Phone <span class=\"text-[#dc2626]\">*</span></span>\n <input\n type=\"tel\"\n class=\"dial-form__input rounded-lg border px-2.5 py-2 font-[inherit]\"\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 </label>\n\n <label class=\"flex flex-col gap-1.5\">\n <span class=\"eds-label\">Patient name <span class=\"text-[#dc2626]\">*</span></span>\n <input\n type=\"text\"\n class=\"dial-form__input rounded-lg border px-2.5 py-2 font-[inherit]\"\n [(ngModel)]=\"name\"\n (ngModelChange)=\"touchForm()\"\n name=\"name\"\n placeholder=\"Maria Garcia\"\n required\n autocomplete=\"off\"\n />\n </label>\n }\n\n <label class=\"flex flex-col gap-1.5\">\n <span class=\"eds-label\">Purpose <span class=\"text-[#dc2626]\">*</span></span>\n <select\n class=\"dial-form__input rounded-lg border px-2.5 py-2 font-[inherit]\"\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 </label>\n\n @if (showsField('appointmentDate') && !selectedAppointmentId()) {\n <label class=\"flex flex-col gap-1.5\">\n <span class=\"eds-label\">{{ requiredFieldLabel('appointmentDate') }} <span class=\"text-[#dc2626]\">*</span></span>\n <input\n type=\"date\"\n class=\"dial-form__input rounded-lg border px-2.5 py-2 font-[inherit]\"\n [(ngModel)]=\"appointmentDate\"\n (ngModelChange)=\"touchForm()\"\n name=\"appointmentDate\"\n required\n autocomplete=\"off\"\n />\n </label>\n }\n\n @if (showsField('appointmentTime') && !selectedAppointmentId()) {\n <label class=\"flex flex-col gap-1.5\">\n <span class=\"eds-label\">{{ requiredFieldLabel('appointmentTime') }} <span class=\"text-[#dc2626]\">*</span></span>\n <input\n type=\"time\"\n class=\"dial-form__input rounded-lg border px-2.5 py-2 font-[inherit]\"\n [(ngModel)]=\"appointmentTime\"\n (ngModelChange)=\"touchForm()\"\n name=\"appointmentTime\"\n required\n autocomplete=\"off\"\n />\n </label>\n }\n\n @if (showsField('providerName') && !selectedAppointmentId()) {\n <label class=\"flex flex-col gap-1.5\">\n <span class=\"eds-label\">{{ requiredFieldLabel('providerName') }} <span class=\"text-[#dc2626]\">*</span></span>\n <input\n type=\"text\"\n class=\"dial-form__input rounded-lg border px-2.5 py-2 font-[inherit]\"\n [(ngModel)]=\"providerName\"\n (ngModelChange)=\"touchForm()\"\n name=\"providerName\"\n placeholder=\"Dr. Patel\"\n required\n autocomplete=\"off\"\n />\n </label>\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 <label class=\"flex flex-col gap-1.5\">\n <span class=\"eds-label\">\n Custom script\n @if (customScriptRequired()) {\n <span class=\"text-[#dc2626]\">*</span>\n }\n </span>\n <textarea\n class=\"dial-form__textarea resize-y rounded-lg border px-2.5 py-2 font-[inherit]\"\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 </label>\n }\n\n <section class=\"dial-form__preview eds-card rounded-lg p-3 shadow-sm\">\n <h3 class=\"eds-label\">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=\"m-0 text-[13px] text-[#b91c1c]\" role=\"alert\">{{ serverError() }}</p>\n }\n\n <footer class=\"mt-1 flex justify-end gap-2\">\n <eds-button variant=\"secondary\" type=\"button\" (click)=\"close()\" [disabled]=\"submitting()\">\n Cancel\n </eds-button>\n <eds-button variant=\"primary\" type=\"submit\" [disabled]=\"submitting() || !canPlaceCall()\">\n {{ submitting() ? 'Dialing…' : 'Place call' }}\n </eds-button>\n </footer>\n </form>\n }\n </app-modal>\n}\n","import { ChangeDetectionStrategy, Component, inject, OnDestroy, OnInit, signal } from '@angular/core';\nimport { ActivatedRoute, Router, RouterLink } from '@angular/router';\nimport { DatePipe } from '@angular/common';\n\nimport { DashboardData, HistoryCall } from '../../models/ai-receptionist.types';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport { LiveCallsListService } from '../../services/live-calls-list.service';\nimport {\n callerMetaLine,\n historyResolution,\n historyResolutionLabel,\n historyResultChips,\n resultChipIcon,\n} from '../../utils/call-history-display.util';\nimport { isNeedsReview } from '../../utils/call-review.util';\nimport { OutboundDialModalComponent } from '../../components/outbound-dial-modal/outbound-dial-modal.component';\nimport { EdsAvatarComponent } from '../../ui/eds-avatar.component';\nimport { EdsButtonComponent } from '../../ui/eds-button.component';\nimport { EdsIconComponent } from '../../ui/eds-icon.component';\nimport { EdsPulseDotComponent } from '../../ui/eds-pulse-dot.component';\nimport { EdsElapsedPipe } from '../../pipes/eds-elapsed.pipe';\n\n@Component({\n selector: 'eds-dashboard-page',\n imports: [\n RouterLink,\n DatePipe,\n EdsAvatarComponent,\n EdsButtonComponent,\n EdsIconComponent,\n EdsPulseDotComponent,\n EdsElapsedPipe,\n OutboundDialModalComponent,\n ],\n templateUrl: './dashboard-page.component.html',\n styleUrl: './dashboard-page.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DashboardPageComponent implements OnInit, OnDestroy {\n private readonly api = inject(AiReceptionistApiService);\n private readonly liveCallsList = inject(LiveCallsListService);\n private readonly router = inject(Router);\n private readonly route = inject(ActivatedRoute);\n\n protected readonly data = signal<DashboardData | null>(null);\n protected readonly loading = signal(true);\n\n protected readonly liveCalls = this.liveCallsList.calls;\n protected readonly liveCallsLoading = this.liveCallsList.loading;\n protected readonly liveCallsError = this.liveCallsList.error;\n protected readonly outboundOpen = signal(false);\n\n protected readonly statTiles = [\n { key: 'activeCalls' as const, label: 'Live calls', icon: 'graphic_eq' },\n { key: 'answeredToday' as const, label: 'Calls today', icon: 'call' },\n { key: 'tasksCreated' as const, label: 'Open tasks', icon: 'task_alt' },\n {\n key: 'appointmentsBooked' as const,\n label: 'Appointments booked',\n icon: 'event_available',\n },\n { key: 'completionRate' as const, label: 'AI handled', icon: 'trending_up', suffix: '%' },\n ];\n\n protected isNeedsReview = isNeedsReview;\n protected callerMetaLine = callerMetaLine;\n protected resultChipIcon = resultChipIcon;\n protected historyResultChips = historyResultChips;\n protected historyResolutionLabel = historyResolutionLabel;\n\n ngOnInit(): void {\n this.liveCallsList.start();\n this.api.getDashboard().subscribe(({ data }) => {\n this.data.set(data);\n this.loading.set(false);\n });\n }\n\n ngOnDestroy(): void {\n this.liveCallsList.stop();\n }\n\n protected statValue(key: keyof DashboardData['stats']): number {\n if (key === 'activeCalls') {\n return this.liveCallsList.calls().length;\n }\n return this.data()?.stats[key] ?? 0;\n }\n\n protected statusTone(status: string): 'brand' | 'pos' | 'notice' | 'neg' | 'violet' | 'gray' {\n const normalized = status || 'ai';\n const map: Record<string, 'brand' | 'pos' | 'notice' | 'neg' | 'violet'> = {\n ai: 'brand',\n 'needs-help': 'notice',\n 'human-requested': 'violet',\n urgent: 'neg',\n takeover: 'violet',\n transferring: 'notice',\n };\n return map[normalized] ?? 'brand';\n }\n\n protected statusLabel(status: string): string {\n return status || 'ai';\n }\n\n protected historyAvatarTone(row: HistoryCall): 'gray' | 'brand' {\n if (!row.name?.trim() || /^unknown/i.test(row.name) || row.initials === '?') return 'gray';\n return 'brand';\n }\n\n protected resolutionOf(row: HistoryCall) {\n return historyResolution({ outcome: row.outcome, risk: row.risk });\n }\n\n protected formatDuration(sec: number): string {\n const m = Math.floor(sec / 60);\n const s = sec % 60;\n return `${m}:${s.toString().padStart(2, '0')}`;\n }\n\n protected exportCsv(): void {\n // mock export\n }\n\n protected openOutboundDial(): void {\n this.outboundOpen.set(true);\n }\n\n protected closeOutboundDial(): void {\n this.outboundOpen.set(false);\n }\n\n protected openHistoryDetail(id: string): void {\n void this.router.navigate(['../history', id], { relativeTo: this.route });\n }\n\n protected callDetailLink(callId: string): string[] {\n return ['../history', callId];\n }\n\n protected appointmentDetailLink(callId: string | null | undefined): string[] {\n return callId ? ['../history', callId] : ['../history'];\n }\n}\n","<div class=\"eds-page dashboard-page\">\n <header class=\"mb-6 flex items-start justify-between gap-4\">\n <div>\n <h1 class=\"eds-h1\">Dashboard</h1>\n <p class=\"eds-body-sm\">AI receptionist overview and live activity</p>\n </div>\n <div class=\"flex shrink-0 gap-2\">\n <eds-button variant=\"secondary\" (click)=\"exportCsv()\">\n <eds-icon name=\"download\" [size]=\"18\" /> Export\n </eds-button>\n <eds-button variant=\"primary\" (click)=\"openOutboundDial()\">\n <eds-icon name=\"call_made\" [size]=\"18\" /> New outbound call\n </eds-button>\n </div>\n </header>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading dashboard…</p>\n } @else if (data()) {\n <section class=\"mb-5 grid grid-cols-2 gap-3 md:grid-cols-3 lg:grid-cols-5\">\n @for (tile of statTiles; track tile.key) {\n @if (tile.key === 'appointmentsBooked') {\n <a routerLink=\"../appointments\" class=\"eds-card flex cursor-pointer flex-col gap-1.5 p-4 no-underline text-inherit transition-colors hover:border-blue-600 shadow-sm\">\n <eds-icon [name]=\"tile.icon\" [size]=\"22\" />\n <span class=\"eds-label\">{{ tile.label }}</span>\n <span class=\"text-3xl font-semibold tracking-tight text-zinc-900\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </span>\n </a>\n } @else if (tile.key === 'activeCalls') {\n <a routerLink=\"../live\" class=\"eds-card flex cursor-pointer flex-col gap-1.5 p-4 no-underline text-inherit transition-colors hover:border-blue-600 shadow-sm\">\n <eds-icon [name]=\"tile.icon\" [size]=\"22\" />\n <span class=\"eds-label\">{{ tile.label }}</span>\n <span class=\"text-3xl font-semibold tracking-tight text-zinc-900\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </span>\n </a>\n } @else {\n <article class=\"eds-card flex flex-col gap-1.5 p-4 shadow-sm\">\n <eds-icon [name]=\"tile.icon\" [size]=\"22\" />\n <span class=\"eds-label\">{{ tile.label }}</span>\n <span class=\"text-3xl font-semibold tracking-tight text-zinc-900\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </span>\n </article>\n }\n }\n </section>\n\n <section class=\"mb-5 grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <article class=\"eds-card p-4 shadow-sm\">\n <header class=\"mb-3 flex items-center justify-between gap-2\">\n <h2 class=\"eds-h2\">Live calls</h2>\n <eds-pulse-dot />\n </header>\n <ul class=\"m-0 flex list-none flex-col gap-2 p-0\">\n @if (liveCallsError()) {\n <li class=\"eds-body-sm\" role=\"alert\">{{ liveCallsError() }}</li>\n }\n @if (liveCallsLoading() && liveCalls().length === 0) {\n <li class=\"eds-body-sm\">Loading live calls…</li>\n }\n @for (call of liveCalls(); track call.id) {\n <li>\n <a [routerLink]=\"['../live', call.id]\" class=\"grid grid-cols-[auto_1fr_auto] items-center gap-2.5 rounded-md p-2 no-underline text-inherit hover:bg-zinc-50\">\n <eds-avatar [initials]=\"call.initials\" tone=\"brand\" />\n <div class=\"flex min-w-0 flex-col\">\n <span class=\"eds-body-sm-bold\">{{ call.name }}</span>\n <span class=\"eds-body-sm\">{{ call.reason }}</span>\n </div>\n <span class=\"eds-mono-sm\">{{ call.started | edsElapsed }}</span>\n </a>\n </li>\n } @empty {\n @if (!liveCallsLoading()) {\n <li class=\"eds-body-sm\">No active calls right now.</li>\n }\n }\n </ul>\n </article>\n\n <article class=\"eds-card p-4 shadow-sm\">\n <header class=\"mb-3 flex items-center justify-between gap-2\">\n <h2 class=\"eds-h2\">Tasks created</h2>\n <a routerLink=\"../tasks\" class=\"eds-body-sm\">View all</a>\n </header>\n <ul class=\"m-0 flex list-none flex-col gap-2 p-0\">\n @for (task of data()!.latestTasks; track task.id) {\n <li class=\"border-b border-zinc-200 last:border-b-0\">\n <a\n [routerLink]=\"task.callId ? callDetailLink(task.callId) : ['../tasks']\"\n class=\"grid grid-cols-[1fr_auto] items-center gap-2.5 rounded-md p-2 no-underline text-inherit hover:bg-zinc-50\"\n [attr.aria-label]=\"'View task: ' + task.title\"\n >\n <div>\n <span class=\"eds-body-sm-bold\">{{ task.title }}</span>\n <span class=\"eds-body-sm\">\n {{ task.patient }} · {{ task.created | date: 'short' }}\n </span>\n </div>\n <eds-icon name=\"chevron_right\" [size]=\"20\" />\n </a>\n </li>\n } @empty {\n <li class=\"eds-body-sm\">No AI-created tasks yet.</li>\n }\n </ul>\n </article>\n\n <article class=\"eds-card p-4 shadow-sm\">\n <header class=\"mb-3 flex items-center justify-between gap-2\">\n <h2 class=\"eds-h2\">Appointments created</h2>\n <a routerLink=\"../appointments\" class=\"eds-body-sm\">View all</a>\n </header>\n <ul class=\"m-0 flex list-none flex-col gap-2 p-0\">\n @for (appointment of data()!.latestAppointments; track appointment.id) {\n <li class=\"border-b border-zinc-200 last:border-b-0\">\n <a\n [routerLink]=\"appointmentDetailLink(appointment.callId)\"\n class=\"grid grid-cols-[1fr_auto] items-center gap-2.5 rounded-md p-2 no-underline text-inherit hover:bg-zinc-50\"\n [attr.aria-label]=\"'View appointment for ' + appointment.patientName\"\n >\n <div>\n <span class=\"eds-body-sm-bold\">{{ appointment.patientName }}</span>\n <span class=\"eds-body-sm\">\n {{ appointment.providerName ?? 'Provider not assigned' }}\n · {{ appointment.startTime ? (appointment.startTime | date: 'short') : 'Time pending' }}\n </span>\n </div>\n <eds-icon name=\"chevron_right\" [size]=\"20\" />\n </a>\n </li>\n } @empty {\n <li class=\"eds-body-sm\">No AI-booked appointments yet.</li>\n }\n </ul>\n </article>\n\n </section>\n\n <article class=\"eds-card overflow-x-auto p-4 shadow-sm\">\n <header class=\"mb-3 flex items-center justify-between gap-2\">\n <h2 class=\"eds-h2\">Recent Call History</h2>\n <a routerLink=\"../history\" class=\"eds-body-sm\">View all</a>\n </header>\n <div class=\"overflow-x-auto\">\n <table class=\"w-full min-w-5xl border-collapse\">\n <thead>\n <tr>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-3.5 py-3 text-left text-sm font-semibold text-zinc-600\">Time</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-3.5 py-3 text-left text-sm font-semibold text-zinc-600\">Patient/Caller</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-3.5 py-3 text-left text-sm font-semibold text-zinc-600\"></th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-3.5 py-3 text-left text-sm font-semibold text-zinc-600\">Goal Timeline</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-3.5 py-3 text-left text-sm font-semibold text-zinc-600\">Handled By</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-3.5 py-3 text-left text-sm font-semibold text-zinc-600\">Resolution</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-3.5 py-3 text-left text-sm font-semibold text-zinc-600\">Result</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-3.5 py-3 text-left text-sm font-semibold text-zinc-600\">Duration</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-3.5 py-3 text-left text-sm font-semibold text-zinc-600\">Reviewed</th>\n </tr>\n </thead>\n <tbody>\n @for (row of data()!.recentHistory; track row.id) {\n <tr\n class=\"cursor-pointer hover:[&>td]:bg-zinc-50 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-blue-600\"\n [class.dashboard-page__history-row--needs-review]=\"isNeedsReview(row.review)\"\n tabindex=\"0\"\n role=\"link\"\n [attr.aria-label]=\"'View call details for ' + row.name\"\n (click)=\"openHistoryDetail(row.id)\"\n (keyup.enter)=\"openHistoryDetail(row.id)\"\n >\n <td class=\"border-b border-slate-100 px-3.5 py-3.5 align-middle text-sm text-zinc-900\">\n <div class=\"flex flex-col gap-0.5 whitespace-nowrap\">\n @if (row.started) {\n <span class=\"text-sm font-semibold\">{{ row.started | date: 'shortTime' }}</span>\n <span class=\"text-xs text-zinc-500\">{{ row.started | date: 'MMM d, y' }}</span>\n } @else {\n <span class=\"text-sm font-semibold\">{{ row.whenLabel || '—' }}</span>\n }\n </div>\n </td>\n\n <td class=\"border-b border-slate-100 px-3.5 py-3.5 align-middle text-sm text-zinc-900\">\n <div class=\"flex min-w-52 items-center gap-2.5\">\n <eds-avatar\n class=\"dashboard-page__avatar\"\n [initials]=\"row.initials || '?'\"\n [tone]=\"historyAvatarTone(row)\"\n />\n <div class=\"flex min-w-0 flex-col gap-0.5\">\n <span class=\"text-sm font-semibold text-zinc-900\">{{ row.name || 'Unknown Caller' }}</span>\n <span class=\"whitespace-nowrap text-xs text-zinc-500\">\n {{\n callerMetaLine({\n dob: row.dob,\n age: row.age,\n gender: row.gender,\n phone: row.phone,\n name: row.name,\n })\n }}\n </span>\n </div>\n </div>\n </td>\n\n <td class=\"border-b border-slate-100 px-3.5 py-3.5 align-middle text-sm text-zinc-900\">\n <span\n class=\"inline-flex h-7 w-7 items-center justify-center rounded-lg\"\n [class.bg-blue-50]=\"row.direction === 'inbound'\"\n [class.text-blue-600]=\"row.direction === 'inbound'\"\n [class.bg-violet-50]=\"row.direction === 'outbound'\"\n [class.text-violet-600]=\"row.direction === 'outbound'\"\n [attr.title]=\"row.direction === 'inbound' ? 'Inbound' : 'Outbound'\"\n >\n <eds-icon\n [name]=\"row.direction === 'inbound' ? 'call_received' : 'call_made'\"\n [size]=\"16\"\n />\n </span>\n </td>\n\n <td class=\"border-b border-slate-100 px-3.5 py-3.5 align-middle text-sm text-zinc-900\">\n <span class=\"text-sm text-zinc-700\">{{ row.reason || '—' }}</span>\n </td>\n\n <td class=\"border-b border-slate-100 px-3.5 py-3.5 align-middle text-sm text-zinc-900\">\n <span\n class=\"inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium\"\n [class.bg-violet-50]=\"(row.handledBy || 'ai') === 'ai'\"\n [class.text-violet-600]=\"(row.handledBy || 'ai') === 'ai'\"\n [class.bg-blue-50]=\"row.handledBy === 'staff'\"\n [class.text-blue-600]=\"row.handledBy === 'staff'\"\n >\n {{ row.handledBy === 'staff' ? 'STAFF' : 'AI' }}\n </span>\n </td>\n\n <td class=\"border-b border-slate-100 px-3.5 py-3.5 align-middle text-sm text-zinc-900\">\n @if (resolutionOf(row); as resolution) {\n <span\n class=\"inline-flex items-center whitespace-nowrap rounded-full px-2.5 py-1 text-xs font-medium\"\n [class.bg-emerald-50]=\"resolution === 'resolved'\"\n [class.text-emerald-700]=\"resolution === 'resolved'\"\n [class.bg-orange-50]=\"resolution === 'needs_attention'\"\n [class.text-orange-700]=\"resolution === 'needs_attention'\"\n >\n {{ historyResolutionLabel(resolution) }}\n </span>\n }\n </td>\n\n <td class=\"border-b border-slate-100 px-3.5 py-3.5 align-middle text-sm text-zinc-900\">\n <div class=\"flex max-w-64 flex-wrap gap-1.5\">\n @for (chip of historyResultChips(row.results, row.outcome); track chip) {\n <span class=\"inline-flex items-center gap-1 whitespace-nowrap rounded-md bg-zinc-100 px-2 py-1 text-xs font-medium text-zinc-600\">\n <eds-icon [name]=\"resultChipIcon(chip)\" [size]=\"14\" />\n {{ chip }}\n </span>\n } @empty {\n <span class=\"text-sm text-zinc-500\">—</span>\n }\n </div>\n </td>\n\n <td class=\"whitespace-nowrap border-b border-slate-100 px-3.5 py-3.5 align-middle text-sm tabular-nums text-zinc-700\">\n {{ row.durationLabel ?? formatDuration(row.duration) }}\n </td>\n\n <td class=\"w-14 border-b border-slate-100 px-3.5 py-3.5 text-center align-middle text-sm text-zinc-900\">\n @if (row.review === 'reviewed') {\n <span class=\"inline-flex text-green-600\" title=\"Reviewed\">\n <eds-icon name=\"check_circle\" [size]=\"20\" />\n </span>\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td colspan=\"9\" class=\"px-3.5 py-3.5 text-sm text-zinc-500\">No recent calls.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </article>\n }\n</div>\n\n<eds-outbound-dial-modal\n [open]=\"outboundOpen()\"\n (closed)=\"closeOutboundDial()\"\n (dialed)=\"closeOutboundDial()\"\n/>\n"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, signal, Injectable } from '@angular/core';
|
|
3
3
|
import { io } from 'socket.io-client';
|
|
4
|
-
import { j as createVoiceReceptionistPaths } from './edvak99-voice-receptionist-widget-ai-receptionist-api.service-
|
|
5
|
-
import {
|
|
4
|
+
import { j as createVoiceReceptionistPaths } from './edvak99-voice-receptionist-widget-ai-receptionist-api.service-DokIb-9r.mjs';
|
|
5
|
+
import { VOICE_RECEPTIONIST_CONFIG } from './edvak99-voice-receptionist-widget.mjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Single shared Socket.io connection to the `/dashboard` namespace.
|
|
@@ -117,4 +117,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
|
|
|
117
117
|
}] });
|
|
118
118
|
|
|
119
119
|
export { DashboardSocketService as D };
|
|
120
|
-
//# sourceMappingURL=edvak99-voice-receptionist-widget-dashboard-socket.service-
|
|
120
|
+
//# sourceMappingURL=edvak99-voice-receptionist-widget-dashboard-socket.service-D2pelzu4.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edvak99-voice-receptionist-widget-dashboard-socket.service-
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-dashboard-socket.service-D2pelzu4.mjs","sources":["../../../projects/voice-receptionist-widget/src/lib/services/dashboard-socket.service.ts"],"sourcesContent":["import { Injectable, inject, signal } from '@angular/core';\nimport { io, Socket } from 'socket.io-client';\n\nimport { createVoiceReceptionistPaths } from '../api-paths';\nimport { VOICE_RECEPTIONIST_CONFIG } from '../config';\n\ntype SocketHandler = (...args: unknown[]) => void;\n\n/**\n * Single shared Socket.io connection to the `/dashboard` namespace.\n * Used by live calls list + per-call transcript stream.\n */\n@Injectable({ providedIn: 'root' })\nexport class DashboardSocketService {\n private readonly config = inject(VOICE_RECEPTIONIST_CONFIG);\n private readonly paths = createVoiceReceptionistPaths(this.config);\n\n readonly connected = signal(false);\n\n private socket: Socket | null = null;\n private refCount = 0;\n private readonly handlers = new Map<string, Map<SocketHandler, SocketHandler>>();\n private readonly connectCallbacks = new Set<() => void>();\n\n acquire(): void {\n this.refCount++;\n if (this.refCount === 1) {\n this.connect();\n }\n }\n\n release(): void {\n this.refCount = Math.max(0, this.refCount - 1);\n if (this.refCount === 0) {\n this.disconnect();\n }\n }\n\n onConnect(callback: () => void): void {\n this.connectCallbacks.add(callback);\n if (this.connected()) {\n callback();\n }\n }\n\n offConnect(callback: () => void): void {\n this.connectCallbacks.delete(callback);\n }\n\n on<T>(event: string, handler: (payload: T) => void): void {\n const wrapped: SocketHandler = (payload) => handler(payload as T);\n if (!this.handlers.has(event)) {\n this.handlers.set(event, new Map());\n }\n this.handlers.get(event)!.set(handler as SocketHandler, wrapped);\n\n if (this.socket) {\n this.socket.on(event, wrapped);\n }\n }\n\n off<T>(event: string, handler: (payload: T) => void): void {\n const eventHandlers = this.handlers.get(event);\n const wrapped = eventHandlers?.get(handler as SocketHandler);\n if (wrapped) {\n this.socket?.off(event, wrapped);\n eventHandlers!.delete(handler as SocketHandler);\n }\n }\n\n emit(event: string, payload?: unknown): void {\n this.socket?.emit(event, payload);\n }\n\n private connect(): void {\n if (this.config.useMockApi) {\n this.connected.set(true);\n this.fireConnectCallbacks();\n return;\n }\n\n this.socket = io(`${this.paths.apiRoot()}/dashboard`, {\n path: '/socket.io',\n transports: ['websocket'],\n autoConnect: false,\n });\n\n this.socket.on('connect', () => {\n this.connected.set(true);\n this.fireConnectCallbacks();\n });\n\n this.socket.on('disconnect', () => {\n this.connected.set(false);\n });\n\n this.socket.io.on('reconnect', () => {\n this.fireConnectCallbacks();\n });\n\n for (const [event, handlerMap] of this.handlers) {\n for (const wrapped of handlerMap.values()) {\n this.socket.on(event, wrapped);\n }\n }\n\n this.socket.connect();\n }\n\n private disconnect(): void {\n if (!this.socket) {\n this.connected.set(false);\n return;\n }\n\n for (const [event, handlerMap] of this.handlers) {\n for (const wrapped of handlerMap.values()) {\n this.socket.off(event, wrapped);\n }\n }\n\n this.socket.removeAllListeners();\n this.socket.disconnect();\n this.socket = null;\n this.connected.set(false);\n }\n\n private fireConnectCallbacks(): void {\n for (const cb of this.connectCallbacks) {\n cb();\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -11,28 +11,28 @@ class EdsAvatarComponent {
|
|
|
11
11
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsAvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
12
12
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.17", type: EdsAvatarComponent, isStandalone: true, selector: "eds-avatar", inputs: { initials: { classPropertyName: "initials", publicName: "initials", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, gradient: { classPropertyName: "gradient", publicName: "gradient", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
13
13
|
<span
|
|
14
|
-
class="eds-avatar"
|
|
14
|
+
class="eds-avatar inline-flex h-9 w-9 shrink-0 items-center justify-center"
|
|
15
15
|
[attr.data-tone]="tone()"
|
|
16
16
|
[attr.data-shape]="shape()"
|
|
17
17
|
[class.eds-avatar--gradient]="gradient()"
|
|
18
18
|
>
|
|
19
19
|
{{ initials() }}
|
|
20
20
|
</span>
|
|
21
|
-
`, isInline: true, styles: [".eds-avatar{
|
|
21
|
+
`, isInline: true, styles: [".eds-avatar{border-radius:50%;font-family:var(--eds-font-inter);font-size:12px;font-weight:600}.eds-avatar[data-shape=box]{border-radius:10px}.eds-avatar[data-tone=gray]{background:var(--eds-gray-200);color:var(--eds-gray-600)}.eds-avatar[data-tone=brand]{background:#3681f426;color:var(--eds-brand-600)}.eds-avatar[data-tone=green]{background:#dcfce7;color:#15803d}.eds-avatar[data-tone=amber]{background:#ffedd5;color:#c2410c}.eds-avatar[data-tone=violet]{background:#ede9fe;color:#6d28d9}.eds-avatar[data-tone=rose]{background:#ffe4e6;color:#be123c}.eds-avatar--gradient{background:linear-gradient(135deg,var(--eds-brand-500),var(--eds-violet-500));color:#fff}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
22
22
|
}
|
|
23
23
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsAvatarComponent, decorators: [{
|
|
24
24
|
type: Component,
|
|
25
25
|
args: [{ selector: 'eds-avatar', standalone: true, template: `
|
|
26
26
|
<span
|
|
27
|
-
class="eds-avatar"
|
|
27
|
+
class="eds-avatar inline-flex h-9 w-9 shrink-0 items-center justify-center"
|
|
28
28
|
[attr.data-tone]="tone()"
|
|
29
29
|
[attr.data-shape]="shape()"
|
|
30
30
|
[class.eds-avatar--gradient]="gradient()"
|
|
31
31
|
>
|
|
32
32
|
{{ initials() }}
|
|
33
33
|
</span>
|
|
34
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".eds-avatar{
|
|
34
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".eds-avatar{border-radius:50%;font-family:var(--eds-font-inter);font-size:12px;font-weight:600}.eds-avatar[data-shape=box]{border-radius:10px}.eds-avatar[data-tone=gray]{background:var(--eds-gray-200);color:var(--eds-gray-600)}.eds-avatar[data-tone=brand]{background:#3681f426;color:var(--eds-brand-600)}.eds-avatar[data-tone=green]{background:#dcfce7;color:#15803d}.eds-avatar[data-tone=amber]{background:#ffedd5;color:#c2410c}.eds-avatar[data-tone=violet]{background:#ede9fe;color:#6d28d9}.eds-avatar[data-tone=rose]{background:#ffe4e6;color:#be123c}.eds-avatar--gradient{background:linear-gradient(135deg,var(--eds-brand-500),var(--eds-violet-500));color:#fff}\n"] }]
|
|
35
35
|
}] });
|
|
36
36
|
|
|
37
37
|
export { EdsAvatarComponent as E };
|
|
38
|
-
//# sourceMappingURL=edvak99-voice-receptionist-widget-eds-avatar.component-
|
|
38
|
+
//# sourceMappingURL=edvak99-voice-receptionist-widget-eds-avatar.component-BJs_TH8X.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-eds-avatar.component-BJs_TH8X.mjs","sources":["../../../projects/voice-receptionist-widget/src/lib/ui/eds-avatar.component.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, input } from '@angular/core';\n\nexport type EdsAvatarTone = 'gray' | 'brand' | 'green' | 'amber' | 'violet' | 'rose';\nexport type EdsAvatarShape = 'circle' | 'box';\n\n@Component({\n selector: 'eds-avatar',\n standalone: true,\n template: `\n <span\n class=\"eds-avatar inline-flex h-9 w-9 shrink-0 items-center justify-center\"\n [attr.data-tone]=\"tone()\"\n [attr.data-shape]=\"shape()\"\n [class.eds-avatar--gradient]=\"gradient()\"\n >\n {{ initials() }}\n </span>\n `,\n styleUrl: './eds-avatar.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class EdsAvatarComponent {\n readonly initials = input('?');\n readonly tone = input<EdsAvatarTone | string>('gray');\n readonly shape = input<EdsAvatarShape>('circle');\n readonly gradient = input(false);\n}\n"],"names":[],"mappings":";;;MAqBa,kBAAkB,CAAA;AAhB/B,IAAA,WAAA,GAAA;AAiBW,QAAA,IAAA,CAAA,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC;AACrB,QAAA,IAAA,CAAA,IAAI,GAAG,KAAK,CAAyB,MAAM,CAAC;AAC5C,QAAA,IAAA,CAAA,KAAK,GAAG,KAAK,CAAiB,QAAQ,CAAC;AACvC,QAAA,IAAA,CAAA,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC;AACjC,IAAA;+GALY,kBAAkB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;AAAlB,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,kBAAkB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,YAAA,EAAA,MAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAbnB;;;;;;;;;AAST,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,2pBAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA,CAAA;;4FAIU,kBAAkB,EAAA,UAAA,EAAA,CAAA;kBAhB9B,SAAS;+BACE,YAAY,EAAA,UAAA,EACV,IAAI,EAAA,QAAA,EACN;;;;;;;;;GAST,EAAA,eAAA,EAEgB,uBAAuB,CAAC,MAAM,EAAA,MAAA,EAAA,CAAA,2pBAAA,CAAA,EAAA;;;;;"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, signal, Injectable, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
3
|
import { interval, filter, switchMap, catchError, of } from 'rxjs';
|
|
4
|
-
import {
|
|
5
|
-
import { A as AiReceptionistApiService } from './edvak99-voice-receptionist-widget-ai-receptionist-api.service-
|
|
6
|
-
import { D as DashboardSocketService } from './edvak99-voice-receptionist-widget-dashboard-socket.service-
|
|
4
|
+
import { VOICE_RECEPTIONIST_CONFIG } from './edvak99-voice-receptionist-widget.mjs';
|
|
5
|
+
import { A as AiReceptionistApiService } from './edvak99-voice-receptionist-widget-ai-receptionist-api.service-DokIb-9r.mjs';
|
|
6
|
+
import { D as DashboardSocketService } from './edvak99-voice-receptionist-widget-dashboard-socket.service-D2pelzu4.mjs';
|
|
7
7
|
|
|
8
8
|
const DEFAULT_POLL_MS = 12_000;
|
|
9
9
|
/**
|
|
@@ -119,12 +119,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
|
|
|
119
119
|
|
|
120
120
|
class EdsPulseDotComponent {
|
|
121
121
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsPulseDotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
122
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: EdsPulseDotComponent, isStandalone: true, selector: "eds-pulse-dot", ngImport: i0, template: `<span class="eds-pulse-dot"><span class="eds-pulse-dot__core"></span></span>`, isInline: true, styles: [".eds-pulse-
|
|
122
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: EdsPulseDotComponent, isStandalone: true, selector: "eds-pulse-dot", ngImport: i0, template: `<span class="eds-pulse-dot relative inline-flex h-2 w-2"><span class="eds-pulse-dot__core h-2 w-2 rounded-full"></span></span>`, isInline: true, styles: [".eds-pulse-dot__core{background:var(--eds-green-500)}.eds-pulse-dot__core:after{content:\"\";position:absolute;inset:0;border-radius:50%;background:var(--eds-green-500);animation:eds-ping 1.4s cubic-bezier(0,0,.2,1) infinite}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
123
123
|
}
|
|
124
124
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsPulseDotComponent, decorators: [{
|
|
125
125
|
type: Component,
|
|
126
|
-
args: [{ selector: 'eds-pulse-dot', standalone: true, template: `<span class="eds-pulse-dot"><span class="eds-pulse-dot__core"></span></span>`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".eds-pulse-
|
|
126
|
+
args: [{ selector: 'eds-pulse-dot', standalone: true, template: `<span class="eds-pulse-dot relative inline-flex h-2 w-2"><span class="eds-pulse-dot__core h-2 w-2 rounded-full"></span></span>`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".eds-pulse-dot__core{background:var(--eds-green-500)}.eds-pulse-dot__core:after{content:\"\";position:absolute;inset:0;border-radius:50%;background:var(--eds-green-500);animation:eds-ping 1.4s cubic-bezier(0,0,.2,1) infinite}\n"] }]
|
|
127
127
|
}] });
|
|
128
128
|
|
|
129
129
|
export { EdsPulseDotComponent as E, LiveCallsListService as L };
|
|
130
|
-
//# sourceMappingURL=edvak99-voice-receptionist-widget-eds-pulse-dot.component-
|
|
130
|
+
//# sourceMappingURL=edvak99-voice-receptionist-widget-eds-pulse-dot.component-Ig0Eoq1z.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-eds-pulse-dot.component-Ig0Eoq1z.mjs","sources":["../../../projects/voice-receptionist-widget/src/lib/services/live-calls-list.service.ts","../../../projects/voice-receptionist-widget/src/lib/ui/eds-pulse-dot.component.ts"],"sourcesContent":["import { Injectable, inject, signal } from '@angular/core';\nimport { Subscription, catchError, filter, interval, of, switchMap } from 'rxjs';\n\nimport { VOICE_RECEPTIONIST_CONFIG } from '../config';\nimport { LiveCall } from '../models/ai-receptionist.types';\nimport { AiReceptionistApiService } from './ai-receptionist-api.service';\nimport { DashboardSocketService } from './dashboard-socket.service';\n\nconst DEFAULT_POLL_MS = 12_000;\n\n/**\n * Shared live calls list — REST + `live_calls_updated` socket + fallback poll.\n */\n@Injectable({ providedIn: 'root' })\nexport class LiveCallsListService {\n private readonly api = inject(AiReceptionistApiService);\n private readonly socket = inject(DashboardSocketService);\n private readonly config = inject(VOICE_RECEPTIONIST_CONFIG);\n\n readonly calls = signal<LiveCall[]>([]);\n readonly loading = signal(false);\n readonly error = signal<string | null>(null);\n readonly lastUpdated = signal<number | null>(null);\n\n private subscriberCount = 0;\n private pollSub?: Subscription;\n private visibilityHandler?: () => void;\n private mockPollTimer: ReturnType<typeof setInterval> | null = null;\n\n private readonly onLiveCallsUpdated = () => this.refresh(false);\n private readonly onSocketConnect = () => this.refresh(false);\n\n start(): void {\n this.subscriberCount++;\n if (this.subscriberCount > 1) {\n return;\n }\n\n this.socket.acquire();\n this.socket.on('live_calls_updated', this.onLiveCallsUpdated);\n this.socket.onConnect(this.onSocketConnect);\n this.bindVisibility();\n this.refresh(true);\n this.startFallbackPoll();\n }\n\n stop(): void {\n this.subscriberCount = Math.max(0, this.subscriberCount - 1);\n if (this.subscriberCount > 0) {\n return;\n }\n\n this.stopFallbackPoll();\n this.unbindVisibility();\n this.socket.off('live_calls_updated', this.onLiveCallsUpdated);\n this.socket.offConnect(this.onSocketConnect);\n this.socket.release();\n }\n\n refresh(showLoading = true): void {\n if (showLoading && this.calls().length === 0) {\n this.loading.set(true);\n }\n\n this.api.getLiveCalls().subscribe({\n next: ({ data }) => {\n this.calls.set(data);\n this.lastUpdated.set(Date.now());\n this.loading.set(false);\n this.error.set(null);\n },\n error: (err: unknown) => {\n this.loading.set(false);\n const message =\n err instanceof Error ? err.message : 'Could not load live calls.';\n this.error.set(message);\n },\n });\n }\n\n private startFallbackPoll(): void {\n const pollMs = this.config.liveCallsPollIntervalMs ?? DEFAULT_POLL_MS;\n\n if (this.config.useMockApi) {\n this.mockPollTimer = setInterval(() => {\n if (!document.hidden) {\n this.refresh(false);\n }\n }, pollMs);\n return;\n }\n\n this.pollSub = interval(pollMs)\n .pipe(\n filter(() => !document.hidden),\n switchMap(() =>\n this.api.getLiveCalls().pipe(\n catchError(() => {\n this.error.set('Live calls refresh failed.');\n return of(null);\n }),\n ),\n ),\n )\n .subscribe((result) => {\n if (!result) return;\n this.calls.set(result.data);\n this.lastUpdated.set(Date.now());\n this.error.set(null);\n });\n }\n\n private stopFallbackPoll(): void {\n this.pollSub?.unsubscribe();\n this.pollSub = undefined;\n if (this.mockPollTimer) {\n clearInterval(this.mockPollTimer);\n this.mockPollTimer = null;\n }\n }\n\n private bindVisibility(): void {\n this.visibilityHandler = () => {\n if (!document.hidden) {\n this.refresh(false);\n }\n };\n document.addEventListener('visibilitychange', this.visibilityHandler);\n }\n\n private unbindVisibility(): void {\n if (this.visibilityHandler) {\n document.removeEventListener('visibilitychange', this.visibilityHandler);\n this.visibilityHandler = undefined;\n }\n }\n}\n","import { ChangeDetectionStrategy, Component } from '@angular/core';\n\n@Component({\n selector: 'eds-pulse-dot',\n standalone: true,\n template: `<span class=\"eds-pulse-dot relative inline-flex h-2 w-2\"><span class=\"eds-pulse-dot__core h-2 w-2 rounded-full\"></span></span>`,\n styleUrl: './eds-pulse-dot.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class EdsPulseDotComponent {}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { Router, ActivatedRoute } from '@angular/router';
|
|
4
|
+
import { L as LiveCallsListService, E as EdsPulseDotComponent } from './edvak99-voice-receptionist-widget-eds-pulse-dot.component-Ig0Eoq1z.mjs';
|
|
5
|
+
import { c as callerMetaLine, d as ageFromDob, i as isUnknownCaller } from './edvak99-voice-receptionist-widget-ai-receptionist-api.service-DokIb-9r.mjs';
|
|
6
|
+
import { E as EdsAvatarComponent } from './edvak99-voice-receptionist-widget-eds-avatar.component-BJs_TH8X.mjs';
|
|
7
|
+
import { EdsIconComponent } from './edvak99-voice-receptionist-widget.mjs';
|
|
8
|
+
|
|
9
|
+
class LiveCallsPageComponent {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.router = inject(Router);
|
|
12
|
+
this.route = inject(ActivatedRoute);
|
|
13
|
+
this.liveCallsList = inject(LiveCallsListService);
|
|
14
|
+
this.calls = this.liveCallsList.calls;
|
|
15
|
+
this.loading = this.liveCallsList.loading;
|
|
16
|
+
this.error = this.liveCallsList.error;
|
|
17
|
+
this.emptyState = computed(() => {
|
|
18
|
+
if (this.calls().length > 0)
|
|
19
|
+
return null;
|
|
20
|
+
return {
|
|
21
|
+
title: 'No active calls right now',
|
|
22
|
+
detail: 'When a patient calls your practice, their conversation will appear here for real-time monitoring.',
|
|
23
|
+
icon: 'phone_disabled',
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
ngOnInit() {
|
|
28
|
+
this.liveCallsList.start();
|
|
29
|
+
}
|
|
30
|
+
ngOnDestroy() {
|
|
31
|
+
this.liveCallsList.stop();
|
|
32
|
+
}
|
|
33
|
+
openMonitor(id) {
|
|
34
|
+
void this.router.navigate(['../live', id], { relativeTo: this.route });
|
|
35
|
+
}
|
|
36
|
+
displayName(call) {
|
|
37
|
+
return this.isUnknown(call) ? 'Unknown Caller' : call.name.trim();
|
|
38
|
+
}
|
|
39
|
+
displayMeta(call) {
|
|
40
|
+
return callerMetaLine({
|
|
41
|
+
name: call.name,
|
|
42
|
+
matched: call.matched,
|
|
43
|
+
phone: call.phone,
|
|
44
|
+
dob: call.dob,
|
|
45
|
+
age: call.age ?? ageFromDob(call.dob),
|
|
46
|
+
gender: call.gender,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
isUnknown(call) {
|
|
50
|
+
return isUnknownCaller(call.name, call.matched);
|
|
51
|
+
}
|
|
52
|
+
sentimentLabel(sentiment) {
|
|
53
|
+
const key = String(sentiment ?? 'neutral').toLowerCase();
|
|
54
|
+
switch (key) {
|
|
55
|
+
case 'calm':
|
|
56
|
+
return 'Calm';
|
|
57
|
+
case 'frustrated':
|
|
58
|
+
return 'Frustrated';
|
|
59
|
+
case 'urgent':
|
|
60
|
+
return 'Urgent';
|
|
61
|
+
case 'neutral':
|
|
62
|
+
default:
|
|
63
|
+
return 'Neutral';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
avatarInitials(call) {
|
|
67
|
+
return this.isUnknown(call) ? '?' : call.initials || '?';
|
|
68
|
+
}
|
|
69
|
+
avatarTone(call) {
|
|
70
|
+
return this.isUnknown(call) ? 'gray' : 'brand';
|
|
71
|
+
}
|
|
72
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: LiveCallsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
73
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: LiveCallsPageComponent, isStandalone: true, selector: "eds-live-calls-page", ngImport: i0, template: "<div class=\"eds-page live-calls-page\">\n <header class=\"mb-5 flex items-center justify-between\">\n <div>\n <h1 class=\"eds-h1\">Live Calls</h1>\n <p class=\"eds-body-sm\">Monitor active conversations in real time</p>\n </div>\n <eds-pulse-dot />\n </header>\n\n @if (error()) {\n <p class=\"eds-body-sm mb-3 text-sm text-red-600\" role=\"alert\">{{ error() }}</p>\n }\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading live calls\u2026</p>\n } @else {\n <div class=\"flex w-1/2 min-w-80 max-w-1/2 flex-col gap-2.5\">\n @for (call of calls(); track call.id) {\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-xl border border-zinc-200 bg-white px-4 py-3.5 text-left transition hover:border-slate-300 hover:shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2\"\n (click)=\"openMonitor(call.id)\"\n >\n <eds-avatar\n class=\"live-calls-page__avatar shrink-0\"\n [initials]=\"avatarInitials(call)\"\n [tone]=\"avatarTone(call)\"\n />\n <div class=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span class=\"text-sm font-bold text-zinc-700\">{{ displayName(call) }}</span>\n <span class=\"text-xs text-zinc-600\">{{ displayMeta(call) }}</span>\n </div>\n <span\n class=\"live-calls-page__sentiment shrink-0 text-xs font-semibold capitalize whitespace-nowrap text-zinc-600\"\n [attr.data-sentiment]=\"call.sentiment\"\n >\n {{ sentimentLabel(call.sentiment) }}\n </span>\n </button>\n } @empty {\n @if (emptyState(); as state) {\n <div class=\"eds-card flex flex-col items-center gap-2 px-6 py-12 text-center text-zinc-500 shadow-sm\" role=\"status\">\n <eds-icon [name]=\"state.icon\" [size]=\"32\" />\n <p class=\"eds-h2 mb-2 text-zinc-800\">{{ state.title }}</p>\n <p class=\"eds-body-sm m-0 max-w-md\">{{ state.detail }}</p>\n </div>\n }\n }\n </div>\n }\n</div>\n", styles: [".live-calls-page__avatar ::ng-deep .eds-avatar{width:40px;height:40px;border-radius:10px;font-size:13px}.live-calls-page__avatar ::ng-deep .eds-avatar[data-tone=brand]{background:#fef3c7;color:#92400e}.live-calls-page__sentiment[data-sentiment=calm]{color:#16a34a}.live-calls-page__sentiment[data-sentiment=neutral]{color:#71717a}.live-calls-page__sentiment[data-sentiment=frustrated]{color:#d97706}.live-calls-page__sentiment[data-sentiment=urgent]{color:#dc2626}\n"], dependencies: [{ kind: "component", type: EdsAvatarComponent, selector: "eds-avatar", inputs: ["initials", "tone", "shape", "gradient"] }, { kind: "component", type: EdsIconComponent, selector: "eds-icon", inputs: ["name", "size", "fill", "weight"] }, { kind: "component", type: EdsPulseDotComponent, selector: "eds-pulse-dot" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
74
|
+
}
|
|
75
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: LiveCallsPageComponent, decorators: [{
|
|
76
|
+
type: Component,
|
|
77
|
+
args: [{ selector: 'eds-live-calls-page', imports: [EdsAvatarComponent, EdsIconComponent, EdsPulseDotComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"eds-page live-calls-page\">\n <header class=\"mb-5 flex items-center justify-between\">\n <div>\n <h1 class=\"eds-h1\">Live Calls</h1>\n <p class=\"eds-body-sm\">Monitor active conversations in real time</p>\n </div>\n <eds-pulse-dot />\n </header>\n\n @if (error()) {\n <p class=\"eds-body-sm mb-3 text-sm text-red-600\" role=\"alert\">{{ error() }}</p>\n }\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading live calls\u2026</p>\n } @else {\n <div class=\"flex w-1/2 min-w-80 max-w-1/2 flex-col gap-2.5\">\n @for (call of calls(); track call.id) {\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-xl border border-zinc-200 bg-white px-4 py-3.5 text-left transition hover:border-slate-300 hover:shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2\"\n (click)=\"openMonitor(call.id)\"\n >\n <eds-avatar\n class=\"live-calls-page__avatar shrink-0\"\n [initials]=\"avatarInitials(call)\"\n [tone]=\"avatarTone(call)\"\n />\n <div class=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span class=\"text-sm font-bold text-zinc-700\">{{ displayName(call) }}</span>\n <span class=\"text-xs text-zinc-600\">{{ displayMeta(call) }}</span>\n </div>\n <span\n class=\"live-calls-page__sentiment shrink-0 text-xs font-semibold capitalize whitespace-nowrap text-zinc-600\"\n [attr.data-sentiment]=\"call.sentiment\"\n >\n {{ sentimentLabel(call.sentiment) }}\n </span>\n </button>\n } @empty {\n @if (emptyState(); as state) {\n <div class=\"eds-card flex flex-col items-center gap-2 px-6 py-12 text-center text-zinc-500 shadow-sm\" role=\"status\">\n <eds-icon [name]=\"state.icon\" [size]=\"32\" />\n <p class=\"eds-h2 mb-2 text-zinc-800\">{{ state.title }}</p>\n <p class=\"eds-body-sm m-0 max-w-md\">{{ state.detail }}</p>\n </div>\n }\n }\n </div>\n }\n</div>\n", styles: [".live-calls-page__avatar ::ng-deep .eds-avatar{width:40px;height:40px;border-radius:10px;font-size:13px}.live-calls-page__avatar ::ng-deep .eds-avatar[data-tone=brand]{background:#fef3c7;color:#92400e}.live-calls-page__sentiment[data-sentiment=calm]{color:#16a34a}.live-calls-page__sentiment[data-sentiment=neutral]{color:#71717a}.live-calls-page__sentiment[data-sentiment=frustrated]{color:#d97706}.live-calls-page__sentiment[data-sentiment=urgent]{color:#dc2626}\n"] }]
|
|
78
|
+
}] });
|
|
79
|
+
|
|
80
|
+
export { LiveCallsPageComponent };
|
|
81
|
+
//# sourceMappingURL=edvak99-voice-receptionist-widget-live-calls-page.component-Bx7WQ2dg.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-live-calls-page.component-Bx7WQ2dg.mjs","sources":["../../../projects/voice-receptionist-widget/src/lib/pages/live-calls-page/live-calls-page.component.ts","../../../projects/voice-receptionist-widget/src/lib/pages/live-calls-page/live-calls-page.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n inject,\n OnDestroy,\n OnInit,\n} from '@angular/core';\nimport { ActivatedRoute, Router } from '@angular/router';\n\nimport { LiveCall, Sentiment } from '../../models/ai-receptionist.types';\nimport { LiveCallsListService } from '../../services/live-calls-list.service';\nimport {\n ageFromDob,\n callerMetaLine,\n isUnknownCaller,\n} from '../../utils/call-history-display.util';\nimport { EdsAvatarComponent } from '../../ui/eds-avatar.component';\nimport { EdsIconComponent } from '../../ui/eds-icon.component';\nimport { EdsPulseDotComponent } from '../../ui/eds-pulse-dot.component';\n\n@Component({\n selector: 'eds-live-calls-page',\n imports: [EdsAvatarComponent, EdsIconComponent, EdsPulseDotComponent],\n templateUrl: './live-calls-page.component.html',\n styleUrl: './live-calls-page.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class LiveCallsPageComponent implements OnInit, OnDestroy {\n private readonly router = inject(Router);\n private readonly route = inject(ActivatedRoute);\n private readonly liveCallsList = inject(LiveCallsListService);\n\n protected readonly calls = this.liveCallsList.calls;\n protected readonly loading = this.liveCallsList.loading;\n protected readonly error = this.liveCallsList.error;\n\n protected readonly emptyState = computed(() => {\n if (this.calls().length > 0) return null;\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_disabled',\n };\n });\n\n ngOnInit(): void {\n this.liveCallsList.start();\n }\n\n ngOnDestroy(): void {\n this.liveCallsList.stop();\n }\n\n protected openMonitor(id: string): void {\n void this.router.navigate(['../live', id], { relativeTo: this.route });\n }\n\n protected displayName(call: LiveCall): string {\n return this.isUnknown(call) ? 'Unknown Caller' : call.name.trim();\n }\n\n protected displayMeta(call: LiveCall): string {\n return 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 }\n\n protected isUnknown(call: LiveCall): boolean {\n return isUnknownCaller(call.name, call.matched);\n }\n\n protected sentimentLabel(sentiment: Sentiment | string | null | undefined): string {\n const key = String(sentiment ?? 'neutral').toLowerCase();\n switch (key) {\n case 'calm':\n return 'Calm';\n case 'frustrated':\n return 'Frustrated';\n case 'urgent':\n return 'Urgent';\n case 'neutral':\n default:\n return 'Neutral';\n }\n }\n\n protected avatarInitials(call: LiveCall): string {\n return this.isUnknown(call) ? '?' : call.initials || '?';\n }\n\n protected avatarTone(call: LiveCall): 'gray' | 'brand' {\n return this.isUnknown(call) ? 'gray' : 'brand';\n }\n}\n","<div class=\"eds-page live-calls-page\">\n <header class=\"mb-5 flex items-center justify-between\">\n <div>\n <h1 class=\"eds-h1\">Live Calls</h1>\n <p class=\"eds-body-sm\">Monitor active conversations in real time</p>\n </div>\n <eds-pulse-dot />\n </header>\n\n @if (error()) {\n <p class=\"eds-body-sm mb-3 text-sm text-red-600\" role=\"alert\">{{ error() }}</p>\n }\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading live calls…</p>\n } @else {\n <div class=\"flex w-1/2 min-w-80 max-w-1/2 flex-col gap-2.5\">\n @for (call of calls(); track call.id) {\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-xl border border-zinc-200 bg-white px-4 py-3.5 text-left transition hover:border-slate-300 hover:shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2\"\n (click)=\"openMonitor(call.id)\"\n >\n <eds-avatar\n class=\"live-calls-page__avatar shrink-0\"\n [initials]=\"avatarInitials(call)\"\n [tone]=\"avatarTone(call)\"\n />\n <div class=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span class=\"text-sm font-bold text-zinc-700\">{{ displayName(call) }}</span>\n <span class=\"text-xs text-zinc-600\">{{ displayMeta(call) }}</span>\n </div>\n <span\n class=\"live-calls-page__sentiment shrink-0 text-xs font-semibold capitalize whitespace-nowrap text-zinc-600\"\n [attr.data-sentiment]=\"call.sentiment\"\n >\n {{ sentimentLabel(call.sentiment) }}\n </span>\n </button>\n } @empty {\n @if (emptyState(); as state) {\n <div class=\"eds-card flex flex-col items-center gap-2 px-6 py-12 text-center text-zinc-500 shadow-sm\" role=\"status\">\n <eds-icon [name]=\"state.icon\" [size]=\"32\" />\n <p class=\"eds-h2 mb-2 text-zinc-800\">{{ state.title }}</p>\n <p class=\"eds-body-sm m-0 max-w-md\">{{ state.detail }}</p>\n </div>\n }\n }\n </div>\n }\n</div>\n"],"names":[],"mappings":"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"}
|