@edvak99/voice-receptionist-widget 1.1.6 → 1.1.8
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-CtaPoY1k.mjs +344 -0
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-CtaPoY1k.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-ai-receptionist-api.service-DokIb-9r.mjs → edvak99-voice-receptionist-widget-ai-receptionist-api.service-CK6tM862.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-ai-receptionist-api.service-DokIb-9r.mjs.map → edvak99-voice-receptionist-widget-ai-receptionist-api.service-CK6tM862.mjs.map} +1 -1
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-CM5-vgzu.mjs +277 -0
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-CM5-vgzu.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-BuNyPmhn.mjs +419 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-BuNyPmhn.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-B-99E9Rs.mjs +193 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-B-99E9Rs.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-Pla2UgeZ.mjs → edvak99-voice-receptionist-widget-call-recording-player.component-Cy9DGRci.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-Pla2UgeZ.mjs.map → edvak99-voice-receptionist-widget-call-recording-player.component-Cy9DGRci.mjs.map} +1 -1
- package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-Di7GLG9k.mjs → edvak99-voice-receptionist-widget-call-review.service-BAjx3n3v.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-Di7GLG9k.mjs.map → edvak99-voice-receptionist-widget-call-review.service-BAjx3n3v.mjs.map} +1 -1
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-9ao09dEd.mjs +113 -0
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-9ao09dEd.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-dashboard-socket.service-D2pelzu4.mjs → edvak99-voice-receptionist-widget-dashboard-socket.service-BuFQz1lN.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-dashboard-socket.service-D2pelzu4.mjs.map → edvak99-voice-receptionist-widget-dashboard-socket.service-BuFQz1lN.mjs.map} +1 -1
- package/fesm2022/{edvak99-voice-receptionist-widget-eds-pulse-dot.component-Ig0Eoq1z.mjs → edvak99-voice-receptionist-widget-eds-pulse-dot.component-CQIqnrcs.mjs} +3 -3
- package/fesm2022/{edvak99-voice-receptionist-widget-eds-pulse-dot.component-Ig0Eoq1z.mjs.map → edvak99-voice-receptionist-widget-eds-pulse-dot.component-CQIqnrcs.mjs.map} +1 -1
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-4LJuMn28.mjs +81 -0
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-4LJuMn28.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-live-monitor-page.component-DWbA5yAG.mjs → edvak99-voice-receptionist-widget-live-monitor-page.component-CJ7N-Jq6.mjs} +8 -8
- package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-CJ7N-Jq6.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-modal.component-uYApl1KF.mjs +48 -0
- package/fesm2022/edvak99-voice-receptionist-widget-modal.component-uYApl1KF.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-HJmMq0b6.mjs +391 -0
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-HJmMq0b6.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-CspheAr8.mjs +992 -0
- package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-CspheAr8.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-tasks-page.component-DyNG2DZZ.mjs → edvak99-voice-receptionist-widget-tasks-page.component-Dfz-_0Jz.mjs} +6 -6
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-Dfz-_0Jz.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-BD4Zhp0Y.mjs +46 -0
- package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-BD4Zhp0Y.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget.mjs +20 -20
- package/fesm2022/edvak99-voice-receptionist-widget.mjs.map +1 -1
- package/lib/pages/activities-page/activities-page.component.d.ts +2 -0
- package/lib/pages/call-history-detail-page/call-history-detail-page.component.d.ts +4 -0
- package/lib/pages/dashboard-page/dashboard-page.component.d.ts +0 -3
- package/lib/pages/settings-page/settings-page.component.d.ts +4 -0
- package/package.json +1 -1
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-CdrW0He4.mjs +0 -306
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-CdrW0He4.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-4GK1vsom.mjs +0 -277
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-4GK1vsom.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-Bw_Ade1s.mjs +0 -416
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-Bw_Ade1s.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DnwYnjIE.mjs +0 -193
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DnwYnjIE.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-DVz0romd.mjs +0 -439
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-DVz0romd.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-Bx7WQ2dg.mjs +0 -81
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-Bx7WQ2dg.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-DWbA5yAG.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-modal.component-Cb-FoQ1N.mjs +0 -48
- package/fesm2022/edvak99-voice-receptionist-widget-modal.component-Cb-FoQ1N.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-Bdh4QfFe.mjs +0 -127
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-Bdh4QfFe.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-DhoyrHSe.mjs +0 -267
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-DhoyrHSe.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-CZUDgZ1_.mjs +0 -967
- package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-CZUDgZ1_.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-DyNG2DZZ.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-DjaurkbO.mjs +0 -46
- package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-DjaurkbO.mjs.map +0 -1
- package/lib/components/outbound-dial-modal/outbound-dial-modal.component.d.ts +0 -63
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, signal, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { Router, ActivatedRoute, RouterLink } from '@angular/router';
|
|
4
|
+
import { DatePipe } from '@angular/common';
|
|
5
|
+
import { A as AiReceptionistApiService, c as callerMetaLine, r as resultChipIcon, h as historyResultChips, a as historyResolutionLabel, b as historyResolution } from './edvak99-voice-receptionist-widget-ai-receptionist-api.service-CK6tM862.mjs';
|
|
6
|
+
import { L as LiveCallsListService, E as EdsPulseDotComponent } from './edvak99-voice-receptionist-widget-eds-pulse-dot.component-CQIqnrcs.mjs';
|
|
7
|
+
import { i as isNeedsReview } from './edvak99-voice-receptionist-widget-call-review.util-Deh3jPgo.mjs';
|
|
8
|
+
import { E as EdsAvatarComponent } from './edvak99-voice-receptionist-widget-eds-avatar.component-BJs_TH8X.mjs';
|
|
9
|
+
import { EdsButtonComponent, EdsIconComponent } from './edvak99-voice-receptionist-widget.mjs';
|
|
10
|
+
import { E as EdsElapsedPipe } from './edvak99-voice-receptionist-widget-eds-elapsed.pipe-BFCiJOT3.mjs';
|
|
11
|
+
|
|
12
|
+
class DashboardPageComponent {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.api = inject(AiReceptionistApiService);
|
|
15
|
+
this.liveCallsList = inject(LiveCallsListService);
|
|
16
|
+
this.router = inject(Router);
|
|
17
|
+
this.route = inject(ActivatedRoute);
|
|
18
|
+
this.data = signal(null);
|
|
19
|
+
this.loading = signal(true);
|
|
20
|
+
this.liveCalls = this.liveCallsList.calls;
|
|
21
|
+
this.liveCallsLoading = this.liveCallsList.loading;
|
|
22
|
+
this.liveCallsError = this.liveCallsList.error;
|
|
23
|
+
this.statTiles = [
|
|
24
|
+
{ key: 'activeCalls', label: 'Live calls', icon: 'graphic_eq' },
|
|
25
|
+
{ key: 'answeredToday', label: 'Calls today', icon: 'call' },
|
|
26
|
+
{ key: 'tasksCreated', label: 'Open tasks', icon: 'task_alt' },
|
|
27
|
+
{
|
|
28
|
+
key: 'appointmentsBooked',
|
|
29
|
+
label: 'Appointments booked',
|
|
30
|
+
icon: 'event_available',
|
|
31
|
+
},
|
|
32
|
+
{ key: 'completionRate', label: 'AI handled', icon: 'trending_up', suffix: '%' },
|
|
33
|
+
];
|
|
34
|
+
this.isNeedsReview = isNeedsReview;
|
|
35
|
+
this.callerMetaLine = callerMetaLine;
|
|
36
|
+
this.resultChipIcon = resultChipIcon;
|
|
37
|
+
this.historyResultChips = historyResultChips;
|
|
38
|
+
this.historyResolutionLabel = historyResolutionLabel;
|
|
39
|
+
}
|
|
40
|
+
ngOnInit() {
|
|
41
|
+
this.liveCallsList.start();
|
|
42
|
+
this.api.getDashboard().subscribe(({ data }) => {
|
|
43
|
+
this.data.set(data);
|
|
44
|
+
this.loading.set(false);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
ngOnDestroy() {
|
|
48
|
+
this.liveCallsList.stop();
|
|
49
|
+
}
|
|
50
|
+
statValue(key) {
|
|
51
|
+
if (key === 'activeCalls') {
|
|
52
|
+
return this.liveCallsList.calls().length;
|
|
53
|
+
}
|
|
54
|
+
return this.data()?.stats[key] ?? 0;
|
|
55
|
+
}
|
|
56
|
+
statusTone(status) {
|
|
57
|
+
const normalized = status || 'ai';
|
|
58
|
+
const map = {
|
|
59
|
+
ai: 'brand',
|
|
60
|
+
'needs-help': 'notice',
|
|
61
|
+
'human-requested': 'violet',
|
|
62
|
+
urgent: 'neg',
|
|
63
|
+
takeover: 'violet',
|
|
64
|
+
transferring: 'notice',
|
|
65
|
+
};
|
|
66
|
+
return map[normalized] ?? 'brand';
|
|
67
|
+
}
|
|
68
|
+
statusLabel(status) {
|
|
69
|
+
return status || 'ai';
|
|
70
|
+
}
|
|
71
|
+
historyAvatarTone(row) {
|
|
72
|
+
if (!row.name?.trim() || /^unknown/i.test(row.name) || row.initials === '?')
|
|
73
|
+
return 'gray';
|
|
74
|
+
return 'brand';
|
|
75
|
+
}
|
|
76
|
+
resolutionOf(row) {
|
|
77
|
+
return historyResolution({ outcome: row.outcome, risk: row.risk });
|
|
78
|
+
}
|
|
79
|
+
formatDuration(sec) {
|
|
80
|
+
const m = Math.floor(sec / 60);
|
|
81
|
+
const s = sec % 60;
|
|
82
|
+
return `${m}:${s.toString().padStart(2, '0')}`;
|
|
83
|
+
}
|
|
84
|
+
exportCsv() {
|
|
85
|
+
// mock export
|
|
86
|
+
}
|
|
87
|
+
openHistoryDetail(id) {
|
|
88
|
+
void this.router.navigate(['../history', id], { relativeTo: this.route });
|
|
89
|
+
}
|
|
90
|
+
callDetailLink(callId) {
|
|
91
|
+
return ['../history', callId];
|
|
92
|
+
}
|
|
93
|
+
appointmentDetailLink(callId) {
|
|
94
|
+
return callId ? ['../history', callId] : ['../history'];
|
|
95
|
+
}
|
|
96
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DashboardPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
97
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: DashboardPageComponent, isStandalone: true, selector: "eds-dashboard-page", ngImport: i0, template: "<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\">Home</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 </div>\n </header>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading dashboard\u2026</p>\n } @else if (data()) {\n <section class=\"mb-5 grid grid-cols-2 gap-3 md:grid-cols-3 lg:grid-cols-5\" aria-label=\"Overview KPIs\">\n @for (tile of statTiles; track tile.key) {\n @if (tile.key === 'appointmentsBooked') {\n <a\n routerLink=\"../appointments\"\n class=\"flex cursor-pointer flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4 no-underline text-inherit transition-colors hover:border-blue-500\"\n >\n <span class=\"eds-label\">{{ tile.label }}</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </strong>\n </a>\n } @else if (tile.key === 'activeCalls') {\n <a\n routerLink=\"../live\"\n class=\"flex cursor-pointer flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4 no-underline text-inherit transition-colors hover:border-blue-500\"\n >\n <span class=\"eds-label\">{{ tile.label }}</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </strong>\n </a>\n } @else {\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">{{ tile.label }}</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </strong>\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\u2026</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-3 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=\"../activities\" [queryParams]=\"{ tab: '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) : ['../activities']\"\n [queryParams]=\"task.callId ? null : { tab: '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 }} \u00B7 {{ 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-3 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 \u00B7 {{ 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-4 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-4 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-4 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-4 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-4 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-4 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-4 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-4 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-4 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-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex flex-col gap-1 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 || '\u2014' }}</span>\n }\n </div>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex min-w-52 items-center gap-3\">\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-1\">\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-4 py-4 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-4 py-4 align-middle text-sm text-zinc-900\">\n <span class=\"text-sm text-zinc-700\">{{ row.reason || '\u2014' }}</span>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <span\n class=\"inline-flex items-center rounded-md px-2 py-1 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-4 py-4 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-3 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-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex max-w-64 flex-wrap gap-2\">\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\">\u2014</span>\n }\n </div>\n </td>\n\n <td class=\"whitespace-nowrap border-b border-slate-100 px-4 py-4 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-4 py-4 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-4 py-4 text-sm text-zinc-500\">No recent calls.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </article>\n }\n</div>\n", styles: [".dashboard-page__history-row--needs-review td:first-child{box-shadow:inset 3px 0 #f59e0b}.dashboard-page__avatar ::ng-deep .eds-avatar{width:36px;height:36px;border-radius:10px;font-size:12px}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "component", type: EdsAvatarComponent, selector: "eds-avatar", inputs: ["initials", "tone", "shape", "gradient"] }, { kind: "component", type: EdsButtonComponent, selector: "eds-button", inputs: ["variant", "type", "disabled"] }, { kind: "component", type: EdsIconComponent, selector: "eds-icon", inputs: ["name", "size", "fill", "weight"] }, { kind: "component", type: EdsPulseDotComponent, selector: "eds-pulse-dot" }, { kind: "pipe", type: EdsElapsedPipe, name: "edsElapsed" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
98
|
+
}
|
|
99
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DashboardPageComponent, decorators: [{
|
|
100
|
+
type: Component,
|
|
101
|
+
args: [{ selector: 'eds-dashboard-page', imports: [
|
|
102
|
+
RouterLink,
|
|
103
|
+
DatePipe,
|
|
104
|
+
EdsAvatarComponent,
|
|
105
|
+
EdsButtonComponent,
|
|
106
|
+
EdsIconComponent,
|
|
107
|
+
EdsPulseDotComponent,
|
|
108
|
+
EdsElapsedPipe,
|
|
109
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<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\">Home</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 </div>\n </header>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading dashboard\u2026</p>\n } @else if (data()) {\n <section class=\"mb-5 grid grid-cols-2 gap-3 md:grid-cols-3 lg:grid-cols-5\" aria-label=\"Overview KPIs\">\n @for (tile of statTiles; track tile.key) {\n @if (tile.key === 'appointmentsBooked') {\n <a\n routerLink=\"../appointments\"\n class=\"flex cursor-pointer flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4 no-underline text-inherit transition-colors hover:border-blue-500\"\n >\n <span class=\"eds-label\">{{ tile.label }}</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </strong>\n </a>\n } @else if (tile.key === 'activeCalls') {\n <a\n routerLink=\"../live\"\n class=\"flex cursor-pointer flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4 no-underline text-inherit transition-colors hover:border-blue-500\"\n >\n <span class=\"eds-label\">{{ tile.label }}</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </strong>\n </a>\n } @else {\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">{{ tile.label }}</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </strong>\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\u2026</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-3 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=\"../activities\" [queryParams]=\"{ tab: '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) : ['../activities']\"\n [queryParams]=\"task.callId ? null : { tab: '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 }} \u00B7 {{ 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-3 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 \u00B7 {{ 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-4 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-4 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-4 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-4 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-4 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-4 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-4 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-4 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-4 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-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex flex-col gap-1 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 || '\u2014' }}</span>\n }\n </div>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex min-w-52 items-center gap-3\">\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-1\">\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-4 py-4 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-4 py-4 align-middle text-sm text-zinc-900\">\n <span class=\"text-sm text-zinc-700\">{{ row.reason || '\u2014' }}</span>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <span\n class=\"inline-flex items-center rounded-md px-2 py-1 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-4 py-4 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-3 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-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex max-w-64 flex-wrap gap-2\">\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\">\u2014</span>\n }\n </div>\n </td>\n\n <td class=\"whitespace-nowrap border-b border-slate-100 px-4 py-4 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-4 py-4 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-4 py-4 text-sm text-zinc-500\">No recent calls.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </article>\n }\n</div>\n", styles: [".dashboard-page__history-row--needs-review td:first-child{box-shadow:inset 3px 0 #f59e0b}.dashboard-page__avatar ::ng-deep .eds-avatar{width:36px;height:36px;border-radius:10px;font-size:12px}\n"] }]
|
|
110
|
+
}] });
|
|
111
|
+
|
|
112
|
+
export { DashboardPageComponent };
|
|
113
|
+
//# sourceMappingURL=edvak99-voice-receptionist-widget-dashboard-page.component-9ao09dEd.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-dashboard-page.component-9ao09dEd.mjs","sources":["../../../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 { 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 { 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 ],\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\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 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\">Home</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 </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\" aria-label=\"Overview KPIs\">\n @for (tile of statTiles; track tile.key) {\n @if (tile.key === 'appointmentsBooked') {\n <a\n routerLink=\"../appointments\"\n class=\"flex cursor-pointer flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4 no-underline text-inherit transition-colors hover:border-blue-500\"\n >\n <span class=\"eds-label\">{{ tile.label }}</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </strong>\n </a>\n } @else if (tile.key === 'activeCalls') {\n <a\n routerLink=\"../live\"\n class=\"flex cursor-pointer flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4 no-underline text-inherit transition-colors hover:border-blue-500\"\n >\n <span class=\"eds-label\">{{ tile.label }}</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </strong>\n </a>\n } @else {\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">{{ tile.label }}</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </strong>\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-3 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=\"../activities\" [queryParams]=\"{ tab: '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) : ['../activities']\"\n [queryParams]=\"task.callId ? null : { tab: '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-3 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-4 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-4 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-4 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-4 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-4 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-4 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-4 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-4 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-4 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-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex flex-col gap-1 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-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex min-w-52 items-center gap-3\">\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-1\">\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-4 py-4 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-4 py-4 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-4 py-4 align-middle text-sm text-zinc-900\">\n <span\n class=\"inline-flex items-center rounded-md px-2 py-1 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-4 py-4 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-3 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-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex max-w-64 flex-wrap gap-2\">\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-4 py-4 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-4 py-4 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-4 py-4 text-sm text-zinc-500\">No recent calls.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </article>\n }\n</div>\n"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
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 {
|
|
4
|
+
import { g as createVoiceReceptionistPaths } from './edvak99-voice-receptionist-widget-ai-receptionist-api.service-CK6tM862.mjs';
|
|
5
5
|
import { VOICE_RECEPTIONIST_CONFIG } from './edvak99-voice-receptionist-widget.mjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -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-BuFQz1lN.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-BuFQz1lN.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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"}
|
|
@@ -2,8 +2,8 @@ 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
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-
|
|
6
|
-
import { D as DashboardSocketService } from './edvak99-voice-receptionist-widget-dashboard-socket.service-
|
|
5
|
+
import { A as AiReceptionistApiService } from './edvak99-voice-receptionist-widget-ai-receptionist-api.service-CK6tM862.mjs';
|
|
6
|
+
import { D as DashboardSocketService } from './edvak99-voice-receptionist-widget-dashboard-socket.service-BuFQz1lN.mjs';
|
|
7
7
|
|
|
8
8
|
const DEFAULT_POLL_MS = 12_000;
|
|
9
9
|
/**
|
|
@@ -127,4 +127,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
|
|
|
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-CQIqnrcs.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edvak99-voice-receptionist-widget-eds-pulse-dot.component-
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-eds-pulse-dot.component-CQIqnrcs.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":";;;;;;;AAQA,MAAM,eAAe,GAAG,MAAM;AAE9B;;AAEG;MAEU,oBAAoB,CAAA;AADjC,IAAA,WAAA,GAAA;AAEmB,QAAA,IAAA,CAAA,GAAG,GAAG,MAAM,CAAC,wBAAwB,CAAC;AACtC,QAAA,IAAA,CAAA,MAAM,GAAG,MAAM,CAAC,sBAAsB,CAAC;AACvC,QAAA,IAAA,CAAA,MAAM,GAAG,MAAM,CAAC,yBAAyB,CAAC;AAElD,QAAA,IAAA,CAAA,KAAK,GAAG,MAAM,CAAa,EAAE,CAAC;AAC9B,QAAA,IAAA,CAAA,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC;AACvB,QAAA,IAAA,CAAA,KAAK,GAAG,MAAM,CAAgB,IAAI,CAAC;AACnC,QAAA,IAAA,CAAA,WAAW,GAAG,MAAM,CAAgB,IAAI,CAAC;QAE1C,IAAA,CAAA,eAAe,GAAG,CAAC;QAGnB,IAAA,CAAA,aAAa,GAA0C,IAAI;QAElD,IAAA,CAAA,kBAAkB,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAC9C,IAAA,CAAA,eAAe,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;AA0G7D,IAAA;IAxGC,KAAK,GAAA;QACH,IAAI,CAAC,eAAe,EAAE;AACtB,QAAA,IAAI,IAAI,CAAC,eAAe,GAAG,CAAC,EAAE;YAC5B;QACF;AAEA,QAAA,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE;QACrB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,oBAAoB,EAAE,IAAI,CAAC,kBAAkB,CAAC;QAC7D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,eAAe,CAAC;QAC3C,IAAI,CAAC,cAAc,EAAE;AACrB,QAAA,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;QAClB,IAAI,CAAC,iBAAiB,EAAE;IAC1B;IAEA,IAAI,GAAA;AACF,QAAA,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;AAC5D,QAAA,IAAI,IAAI,CAAC,eAAe,GAAG,CAAC,EAAE;YAC5B;QACF;QAEA,IAAI,CAAC,gBAAgB,EAAE;QACvB,IAAI,CAAC,gBAAgB,EAAE;QACvB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,oBAAoB,EAAE,IAAI,CAAC,kBAAkB,CAAC;QAC9D,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,eAAe,CAAC;AAC5C,QAAA,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE;IACvB;IAEA,OAAO,CAAC,WAAW,GAAG,IAAI,EAAA;QACxB,IAAI,WAAW,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE;AAC5C,YAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;QACxB;AAEA,QAAA,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,SAAS,CAAC;AAChC,YAAA,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,KAAI;AACjB,gBAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;gBACpB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;AAChC,gBAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;AACvB,gBAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;YACtB,CAAC;AACD,YAAA,KAAK,EAAE,CAAC,GAAY,KAAI;AACtB,gBAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;AACvB,gBAAA,MAAM,OAAO,GACX,GAAG,YAAY,KAAK,GAAG,GAAG,CAAC,OAAO,GAAG,4BAA4B;AACnE,gBAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;YACzB,CAAC;AACF,SAAA,CAAC;IACJ;IAEQ,iBAAiB,GAAA;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,uBAAuB,IAAI,eAAe;AAErE,QAAA,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE;AAC1B,YAAA,IAAI,CAAC,aAAa,GAAG,WAAW,CAAC,MAAK;AACpC,gBAAA,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;AACpB,oBAAA,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;gBACrB;YACF,CAAC,EAAE,MAAM,CAAC;YACV;QACF;AAEA,QAAA,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC,MAAM;AAC3B,aAAA,IAAI,CACH,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAC9B,SAAS,CAAC,MACR,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,IAAI,CAC1B,UAAU,CAAC,MAAK;AACd,YAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,4BAA4B,CAAC;AAC5C,YAAA,OAAO,EAAE,CAAC,IAAI,CAAC;QACjB,CAAC,CAAC,CACH,CACF;AAEF,aAAA,SAAS,CAAC,CAAC,MAAM,KAAI;AACpB,YAAA,IAAI,CAAC,MAAM;gBAAE;YACb,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;YAC3B,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;AAChC,YAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;AACtB,QAAA,CAAC,CAAC;IACN;IAEQ,gBAAgB,GAAA;AACtB,QAAA,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE;AAC3B,QAAA,IAAI,CAAC,OAAO,GAAG,SAAS;AACxB,QAAA,IAAI,IAAI,CAAC,aAAa,EAAE;AACtB,YAAA,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC;AACjC,YAAA,IAAI,CAAC,aAAa,GAAG,IAAI;QAC3B;IACF;IAEQ,cAAc,GAAA;AACpB,QAAA,IAAI,CAAC,iBAAiB,GAAG,MAAK;AAC5B,YAAA,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;AACpB,gBAAA,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;YACrB;AACF,QAAA,CAAC;QACD,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,IAAI,CAAC,iBAAiB,CAAC;IACvE;IAEQ,gBAAgB,GAAA;AACtB,QAAA,IAAI,IAAI,CAAC,iBAAiB,EAAE;YAC1B,QAAQ,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,IAAI,CAAC,iBAAiB,CAAC;AACxE,YAAA,IAAI,CAAC,iBAAiB,GAAG,SAAS;QACpC;IACF;+GAzHW,oBAAoB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA,CAAA;AAApB,IAAA,SAAA,IAAA,CAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,oBAAoB,cADP,MAAM,EAAA,CAAA,CAAA;;4FACnB,oBAAoB,EAAA,UAAA,EAAA,CAAA;kBADhC,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;;MCJrB,oBAAoB,CAAA;+GAApB,oBAAoB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;AAApB,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,oBAAoB,yEAJrB,CAAA,8HAAA,CAAgI,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,qOAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA,CAAA;;4FAI/H,oBAAoB,EAAA,UAAA,EAAA,CAAA;kBAPhC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,eAAe,cACb,IAAI,EAAA,QAAA,EACN,gIAAgI,EAAA,eAAA,EAEzH,uBAAuB,CAAC,MAAM,EAAA,MAAA,EAAA,CAAA,qOAAA,CAAA,EAAA;;;;;"}
|
|
@@ -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-CQIqnrcs.mjs';
|
|
5
|
+
import { c as callerMetaLine, d as ageFromDob, i as isUnknownCaller } from './edvak99-voice-receptionist-widget-ai-receptionist-api.service-CK6tM862.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-3\">\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-4 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-1\">\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-3\">\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-4 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-1\">\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-4LJuMn28.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-live-calls-page.component-4LJuMn28.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-3\">\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-4 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-1\">\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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"}
|