@edvak99/voice-receptionist-widget 1.1.2 → 1.1.4
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 +3 -0
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-DT9UKvQO.mjs +307 -0
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-DT9UKvQO.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-CMhknpGf.mjs → edvak99-voice-receptionist-widget-ai-receptionist-api.service-48cifVPF.mjs} +1351 -579
- package/fesm2022/edvak99-voice-receptionist-widget-ai-receptionist-api.service-48cifVPF.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-C5inEHJw.mjs +278 -0
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-C5inEHJw.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-sYhM20CE.mjs +418 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-sYhM20CE.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DpwajEOE.mjs +194 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DpwajEOE.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-AN6GcSDq.mjs → edvak99-voice-receptionist-widget-call-recording-player.component-CgkRcFOl.mjs} +5 -4
- package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-AN6GcSDq.mjs.map → edvak99-voice-receptionist-widget-call-recording-player.component-CgkRcFOl.mjs.map} +1 -1
- package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-Cy_Bf7AO.mjs → edvak99-voice-receptionist-widget-call-review.service-CIMcjGKX.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-Cy_Bf7AO.mjs.map → edvak99-voice-receptionist-widget-call-review.service-CIMcjGKX.mjs.map} +1 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-Deh3jPgo.mjs +21 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-Deh3jPgo.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-CxbO0eDS.mjs +440 -0
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-CxbO0eDS.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-eds-elapsed.pipe-ouRlPpwT.mjs → edvak99-voice-receptionist-widget-dashboard-socket.service-BX91r74O.mjs} +5 -21
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-socket.service-BX91r74O.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-CWRiJGfv.mjs +38 -0
- package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-CWRiJGfv.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-eds-button.component-Bg5T5BN8.mjs +27 -0
- package/fesm2022/edvak99-voice-receptionist-widget-eds-button.component-Bg5T5BN8.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-BFCiJOT3.mjs +22 -0
- package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-BFCiJOT3.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-live-calls-list.service-CDtFRW8w.mjs → edvak99-voice-receptionist-widget-eds-pulse-dot.component-BPY6xN7C.mjs} +15 -5
- package/fesm2022/edvak99-voice-receptionist-widget-eds-pulse-dot.component-BPY6xN7C.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-BsBQIS_f.mjs +203 -0
- package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-BsBQIS_f.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-CaSDMxpV.mjs +81 -0
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-CaSDMxpV.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-Cy3ND-c-.mjs +1070 -0
- package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-Cy3ND-c-.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-modal.component-COfi7biN.mjs +48 -0
- package/fesm2022/edvak99-voice-receptionist-widget-modal.component-COfi7biN.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-Bdh4QfFe.mjs +127 -0
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-Bdh4QfFe.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-fHVzpiVk.mjs +268 -0
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-fHVzpiVk.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-Bok64X2q.mjs +968 -0
- package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-Bok64X2q.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-Bb9lPcYi.mjs +317 -0
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-Bb9lPcYi.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-wizard-page.component-CJLa5pia.mjs → edvak99-voice-receptionist-widget-wizard-page.component-BZVlWLWh.mjs} +6 -4
- package/fesm2022/{edvak99-voice-receptionist-widget-wizard-page.component-CJLa5pia.mjs.map → edvak99-voice-receptionist-widget-wizard-page.component-BZVlWLWh.mjs.map} +1 -1
- package/fesm2022/edvak99-voice-receptionist-widget.mjs +1 -1
- package/lib/components/call-conversation-panel/call-conversation-panel.component.d.ts +9 -6
- package/lib/components/call-tasks-panel/call-tasks-panel.component.d.ts +8 -8
- package/lib/components/knowledge-test-modal/knowledge-test-modal.component.d.ts +28 -0
- package/lib/components/live-transcript-panel/live-transcript-panel.component.d.ts +7 -4
- package/lib/components/outbound-dial-modal/outbound-dial-modal.component.d.ts +26 -0
- package/lib/layout/eds-module-nav.component.d.ts +0 -3
- package/lib/mocks/agent-activity.seed.d.ts +2 -0
- package/lib/models/agent-activity.types.d.ts +54 -0
- package/lib/models/ai-receptionist.types.d.ts +26 -0
- package/lib/models/api-dto.types.d.ts +18 -0
- package/lib/models/live-transcript.types.d.ts +2 -1
- package/lib/models/outbound-patient.types.d.ts +23 -0
- package/lib/models/outbound-purpose.types.d.ts +2 -0
- package/lib/models/pagination.d.ts +11 -0
- package/lib/models/staff-media.types.d.ts +36 -0
- package/lib/pages/activities-page/activities-page.component.d.ts +57 -0
- package/lib/pages/analytics-page/analytics-page.component.d.ts +38 -14
- package/lib/pages/call-history-detail-page/call-history-detail-page.component.d.ts +2 -3
- package/lib/pages/call-history-page/call-history-page.component.d.ts +16 -9
- package/lib/pages/dashboard-page/dashboard-page.component.d.ts +12 -8
- package/lib/pages/live-calls-page/live-calls-page.component.d.ts +14 -24
- package/lib/pages/live-monitor-page/live-monitor-page.component.d.ts +34 -1
- package/lib/pages/settings-page/settings-fields.config.d.ts +16 -0
- package/lib/pages/settings-page/settings-page.component.d.ts +48 -14
- package/lib/pages/tasks-page/tasks-page.component.d.ts +6 -0
- package/lib/services/ai-receptionist-api.mock.d.ts +44 -9
- package/lib/services/ai-receptionist-api.service.d.ts +65 -10
- package/lib/services/live-transcript.service.d.ts +1 -0
- package/lib/services/staff-media.service.d.ts +52 -0
- package/lib/ui/eds-avatar.component.d.ts +5 -2
- package/lib/ui/eds-button.component.d.ts +1 -1
- package/lib/utils/call-history-display.util.d.ts +21 -0
- package/lib/utils/outbound-purpose.util.d.ts +19 -0
- package/lib/utils/transcript-timing.util.d.ts +1 -1
- package/lib/utils/welcome-message.util.d.ts +1 -1
- package/package.json +2 -1
- package/styles/_index.scss +5 -40
- package/styles/chat-ui.scss +18 -18
- package/styles/eds.scss +188 -119
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-CW5OKmik.mjs +0 -113
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-CW5OKmik.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-DL1GfQyF.mjs +0 -35
- package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-DL1GfQyF.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-CTwfUHUf.mjs +0 -316
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-CTwfUHUf.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-Cj4wxd4e.mjs +0 -138
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-Cj4wxd4e.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-DQLIw7xb.mjs +0 -70
- package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-DQLIw7xb.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-DzdIt2A7.mjs +0 -248
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-DzdIt2A7.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs +0 -27
- package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-ouRlPpwT.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-CMhknpGf.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-knowledge-hub.page-cZqBuull.mjs +0 -575
- package/fesm2022/edvak99-voice-receptionist-widget-knowledge-hub.page-cZqBuull.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-list.service-CDtFRW8w.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-DrYzXJQW.mjs +0 -115
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-DrYzXJQW.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-D7dlk0y2.mjs +0 -574
- package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-D7dlk0y2.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-BQECIuJu.mjs +0 -54
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-BQECIuJu.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-CJ8NEjcj.mjs +0 -265
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-CJ8NEjcj.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-Bo3XNK3b.mjs +0 -509
- package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-Bo3XNK3b.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-task.util-DdtI0CR3.mjs +0 -52
- package/fesm2022/edvak99-voice-receptionist-widget-task.util-DdtI0CR3.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-CBwl15NP.mjs +0 -237
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-CBwl15NP.mjs.map +0 -1
- package/lib/knowledge/components/faq-empty-state/faq-empty-state.component.d.ts +0 -22
- package/lib/knowledge/components/faq-form/faq-form.component.d.ts +0 -27
- package/lib/knowledge/components/faq-search-tester/faq-search-tester.component.d.ts +0 -17
- package/lib/knowledge/mocks/practice-faq.seed.d.ts +0 -2
- package/lib/knowledge/models/practice-faq.model.d.ts +0 -47
- package/lib/knowledge/pages/knowledge-hub.page.d.ts +0 -47
- package/lib/knowledge/services/practice-faq-api.mock.d.ts +0 -16
- package/lib/knowledge/services/practice-faq-api.service.d.ts +0 -22
- package/lib/knowledge/utils/faq.util.d.ts +0 -3
- package/lib/layout/eds-topbar.component.d.ts +0 -29
- package/lib/pages/audit-page/audit-page.component.d.ts +0 -14
- package/lib/ui/eds-waveform.component.d.ts +0 -8
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, signal, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { A as AiReceptionistApiService } from './edvak99-voice-receptionist-widget-ai-receptionist-api.service-48cifVPF.mjs';
|
|
4
|
+
import { E as EdsButtonComponent } from './edvak99-voice-receptionist-widget-eds-button.component-Bg5T5BN8.mjs';
|
|
5
|
+
import { E as EdsIconComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-BsBQIS_f.mjs';
|
|
6
|
+
|
|
7
|
+
const REASON_COLORS = ['#8E51FF', '#3681F4', '#F472B6', '#7DD3FC', '#FDA4AF', '#34D399', '#FBBF24'];
|
|
8
|
+
const STUCK_COLORS = ['#8E51FF', '#3681F4', '#7DD3FC', '#FBBF24'];
|
|
9
|
+
class AnalyticsPageComponent {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.api = inject(AiReceptionistApiService);
|
|
12
|
+
this.data = signal(null);
|
|
13
|
+
this.range = signal('30d');
|
|
14
|
+
this.loading = signal(true);
|
|
15
|
+
this.chartsReady = signal(false);
|
|
16
|
+
this.hoveredVolume = signal(null);
|
|
17
|
+
this.ranges = [
|
|
18
|
+
{ id: '7d', label: '7 days' },
|
|
19
|
+
{ id: '30d', label: '30 days' },
|
|
20
|
+
{ id: '90d', label: '90 days' },
|
|
21
|
+
];
|
|
22
|
+
this.impactKpi = computed(() => this.findKpi(/(impact|hours|front-desk|time saved)/i));
|
|
23
|
+
this.resolutionKpi = computed(() => this.findKpi(/(autonomous|resolution)/i));
|
|
24
|
+
this.secondaryKpis = computed(() => {
|
|
25
|
+
const cards = this.data()?.kpiCards ?? [];
|
|
26
|
+
const preferred = [
|
|
27
|
+
/appointment/i,
|
|
28
|
+
/task/i,
|
|
29
|
+
/patient|registr/i,
|
|
30
|
+
/calls?\s*handled|total\s*calls|^calls$/i,
|
|
31
|
+
];
|
|
32
|
+
const picked = [];
|
|
33
|
+
for (const re of preferred) {
|
|
34
|
+
const hit = cards.find((c) => re.test(c.label) && !picked.includes(c));
|
|
35
|
+
if (hit)
|
|
36
|
+
picked.push(hit);
|
|
37
|
+
}
|
|
38
|
+
if (picked.length >= 4)
|
|
39
|
+
return picked.slice(0, 4);
|
|
40
|
+
const rest = cards.filter((c) => !picked.includes(c) &&
|
|
41
|
+
!/(impact|hours|front-desk|autonomous|resolution)/i.test(c.label));
|
|
42
|
+
return [...picked, ...rest].slice(0, 4);
|
|
43
|
+
});
|
|
44
|
+
this.distribution = computed(() => {
|
|
45
|
+
const outcomes = this.data()?.outcomes ?? [];
|
|
46
|
+
const total = outcomes.reduce((s, o) => s + o.value, 0) || 1;
|
|
47
|
+
return outcomes.map((o) => ({
|
|
48
|
+
...o,
|
|
49
|
+
pct: Math.round((o.value / total) * 100),
|
|
50
|
+
width: (o.value / total) * 100,
|
|
51
|
+
}));
|
|
52
|
+
});
|
|
53
|
+
this.totalCallsLabel = computed(() => {
|
|
54
|
+
const handled = this.findKpi(/calls?\s*handled|total\s*calls|^calls$/i);
|
|
55
|
+
if (handled?.value)
|
|
56
|
+
return handled.value.includes('Call') ? handled.value : `${handled.value} Calls`;
|
|
57
|
+
const n = this.data()?.kpis.totalCalls;
|
|
58
|
+
return n ? `${n.toLocaleString()} Calls` : 'Calls';
|
|
59
|
+
});
|
|
60
|
+
this.directionSub = computed(() => {
|
|
61
|
+
const handled = this.findKpi(/calls?\s*handled|total\s*calls|^calls$/i);
|
|
62
|
+
if (handled?.sub && /inbound|outbound/i.test(handled.sub))
|
|
63
|
+
return handled.sub;
|
|
64
|
+
return null;
|
|
65
|
+
});
|
|
66
|
+
this.volumePoints = computed(() => this.data()?.volume.points ?? []);
|
|
67
|
+
/** Human date span for the selected analytics window (month + year included). */
|
|
68
|
+
this.periodLabel = computed(() => {
|
|
69
|
+
const vol = this.data()?.volume;
|
|
70
|
+
const start = this.parseIsoDate(vol?.startDate) ?? this.parseIsoDate(vol?.points?.[0]?.date) ?? null;
|
|
71
|
+
const end = this.parseIsoDate(vol?.endDate) ??
|
|
72
|
+
this.parseIsoDate(vol?.points?.[vol.points.length - 1]?.date) ??
|
|
73
|
+
null;
|
|
74
|
+
if (start && end)
|
|
75
|
+
return this.formatPeriodRange(start, end);
|
|
76
|
+
const days = this.range() === '7d' ? 7 : this.range() === '90d' ? 90 : 30;
|
|
77
|
+
const endD = new Date();
|
|
78
|
+
const startD = new Date();
|
|
79
|
+
startD.setDate(endD.getDate() - (days - 1));
|
|
80
|
+
return this.formatPeriodRange(startD, endD);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
ngOnInit() {
|
|
84
|
+
this.load();
|
|
85
|
+
}
|
|
86
|
+
setRange(id) {
|
|
87
|
+
this.range.set(id);
|
|
88
|
+
this.load();
|
|
89
|
+
}
|
|
90
|
+
maxCount(items) {
|
|
91
|
+
return Math.max(...items.map((i) => i.count), 1);
|
|
92
|
+
}
|
|
93
|
+
barHeightPercent(point, points) {
|
|
94
|
+
if (point.count <= 0)
|
|
95
|
+
return 0;
|
|
96
|
+
return (point.count / this.maxCount(points)) * 100;
|
|
97
|
+
}
|
|
98
|
+
maxValue(items) {
|
|
99
|
+
return Math.max(...items.map((i) => i.value), 1);
|
|
100
|
+
}
|
|
101
|
+
reasonColor(index) {
|
|
102
|
+
return REASON_COLORS[index % REASON_COLORS.length];
|
|
103
|
+
}
|
|
104
|
+
stuckColor(index) {
|
|
105
|
+
return STUCK_COLORS[index % STUCK_COLORS.length];
|
|
106
|
+
}
|
|
107
|
+
peakTooltip(point) {
|
|
108
|
+
return `${point.label}: ${point.count} call${point.count === 1 ? '' : 's'}`;
|
|
109
|
+
}
|
|
110
|
+
volumeTooltip(point) {
|
|
111
|
+
const inbound = point.inbound ?? Math.round(point.count * 0.8);
|
|
112
|
+
const outbound = point.outbound ?? Math.max(0, point.count - inbound);
|
|
113
|
+
const headline = this.volumePointHeadline(point);
|
|
114
|
+
return `${headline}\nTotal ${point.count} · In ${inbound} · Out ${outbound}`;
|
|
115
|
+
}
|
|
116
|
+
hbarTooltip(label, value) {
|
|
117
|
+
return `${label}: ${value}%`;
|
|
118
|
+
}
|
|
119
|
+
distributionTooltip(label, value) {
|
|
120
|
+
const calls = this.estimatedCalls(value);
|
|
121
|
+
return `${label}: ${value}% (${calls.toLocaleString()} calls)`;
|
|
122
|
+
}
|
|
123
|
+
showPeakAxisLabel(index, total) {
|
|
124
|
+
if (total <= 12)
|
|
125
|
+
return true;
|
|
126
|
+
const hour = this.data()?.peakHours.points[index]?.hour;
|
|
127
|
+
if (hour != null && [9, 12, 15, 18, 21].includes(hour))
|
|
128
|
+
return true;
|
|
129
|
+
const step = total <= 24 ? 3 : 4;
|
|
130
|
+
return index % step === 0 || index === total - 1;
|
|
131
|
+
}
|
|
132
|
+
volumePath(series) {
|
|
133
|
+
const points = this.volumePoints();
|
|
134
|
+
if (!points.length)
|
|
135
|
+
return '';
|
|
136
|
+
const max = Math.max(...points.map((p) => Math.max(p.count, p.inbound ?? 0, p.outbound ?? 0)), 1);
|
|
137
|
+
const w = 100;
|
|
138
|
+
const h = 100;
|
|
139
|
+
const step = points.length === 1 ? 0 : w / (points.length - 1);
|
|
140
|
+
return points
|
|
141
|
+
.map((p, i) => {
|
|
142
|
+
const yVal = series === 'inbound'
|
|
143
|
+
? (p.inbound ?? Math.round(p.count * 0.8))
|
|
144
|
+
: series === 'outbound'
|
|
145
|
+
? (p.outbound ?? Math.round(p.count * 0.2))
|
|
146
|
+
: p.count;
|
|
147
|
+
const x = i * step;
|
|
148
|
+
const y = h - (yVal / max) * h;
|
|
149
|
+
return `${i === 0 ? 'M' : 'L'}${x.toFixed(2)},${y.toFixed(2)}`;
|
|
150
|
+
})
|
|
151
|
+
.join(' ');
|
|
152
|
+
}
|
|
153
|
+
volumeAreaPath() {
|
|
154
|
+
const line = this.volumePath('total');
|
|
155
|
+
if (!line)
|
|
156
|
+
return '';
|
|
157
|
+
return `${line} L100,100 L0,100 Z`;
|
|
158
|
+
}
|
|
159
|
+
volumeAxisLabels() {
|
|
160
|
+
const points = this.volumePoints();
|
|
161
|
+
return points.map((p, i) => this.volumeAxisLabel(p, i, points.length));
|
|
162
|
+
}
|
|
163
|
+
volumeAxisLabel(point, index, total) {
|
|
164
|
+
if (/week/i.test(point.day) || /week/i.test(point.label)) {
|
|
165
|
+
const week = point.day || point.label;
|
|
166
|
+
const month = point.date ? this.formatMonthYear(point.date) : '';
|
|
167
|
+
return month ? `${week} · ${month}` : week;
|
|
168
|
+
}
|
|
169
|
+
if (point.date) {
|
|
170
|
+
if (total <= 7)
|
|
171
|
+
return this.formatChartDate(point.date);
|
|
172
|
+
const step = total <= 14 ? 2 : total <= 31 ? 5 : 7;
|
|
173
|
+
if (index % step !== 0 && index !== total - 1)
|
|
174
|
+
return '';
|
|
175
|
+
return this.formatChartDate(point.date);
|
|
176
|
+
}
|
|
177
|
+
return point.day || point.label;
|
|
178
|
+
}
|
|
179
|
+
deltaTone(kpi) {
|
|
180
|
+
if (!kpi?.delta)
|
|
181
|
+
return 'neutral';
|
|
182
|
+
if (kpi.tone === 'pos' || kpi.tone === 'warn' || kpi.tone === 'neg')
|
|
183
|
+
return kpi.tone;
|
|
184
|
+
if (/^\+/.test(kpi.delta.trim()))
|
|
185
|
+
return 'pos';
|
|
186
|
+
if (/^-|−/.test(kpi.delta.trim()))
|
|
187
|
+
return 'warn';
|
|
188
|
+
return 'neutral';
|
|
189
|
+
}
|
|
190
|
+
formatChartDate(date) {
|
|
191
|
+
const iso = /^(\d{4})-(\d{2})-(\d{2})/.exec(date.trim());
|
|
192
|
+
if (iso)
|
|
193
|
+
return `${iso[2]}/${iso[3]}`;
|
|
194
|
+
return date;
|
|
195
|
+
}
|
|
196
|
+
estimatedCalls(pct) {
|
|
197
|
+
const total = this.data()?.kpis.totalCalls ?? 0;
|
|
198
|
+
if (!total)
|
|
199
|
+
return Math.round(pct);
|
|
200
|
+
return Math.round((total * pct) / 100);
|
|
201
|
+
}
|
|
202
|
+
onVolumeEnter(index) {
|
|
203
|
+
this.hoveredVolume.set(index);
|
|
204
|
+
}
|
|
205
|
+
onVolumeLeave() {
|
|
206
|
+
this.hoveredVolume.set(null);
|
|
207
|
+
}
|
|
208
|
+
volumePointHeadline(point) {
|
|
209
|
+
const raw = (point.label || point.day || '').trim();
|
|
210
|
+
if (/week/i.test(raw) && point.date) {
|
|
211
|
+
return `${raw} · ${this.formatMonthYear(point.date)}`;
|
|
212
|
+
}
|
|
213
|
+
if (point.date)
|
|
214
|
+
return this.formatFullChartDate(point.date);
|
|
215
|
+
return raw || 'Period';
|
|
216
|
+
}
|
|
217
|
+
formatMonthYear(date) {
|
|
218
|
+
const d = this.parseIsoDate(date);
|
|
219
|
+
if (!d)
|
|
220
|
+
return '';
|
|
221
|
+
return d.toLocaleDateString(undefined, { month: 'short', year: 'numeric' });
|
|
222
|
+
}
|
|
223
|
+
formatFullChartDate(date) {
|
|
224
|
+
const d = this.parseIsoDate(date);
|
|
225
|
+
if (!d)
|
|
226
|
+
return this.formatChartDate(date);
|
|
227
|
+
return d.toLocaleDateString(undefined, {
|
|
228
|
+
month: 'short',
|
|
229
|
+
day: 'numeric',
|
|
230
|
+
year: 'numeric',
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
formatPeriodRange(start, end) {
|
|
234
|
+
const sameYear = start.getFullYear() === end.getFullYear();
|
|
235
|
+
const sameMonth = sameYear && start.getMonth() === end.getMonth();
|
|
236
|
+
if (sameMonth) {
|
|
237
|
+
return `${start.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} – ${end.getDate()}, ${end.getFullYear()}`;
|
|
238
|
+
}
|
|
239
|
+
if (sameYear) {
|
|
240
|
+
return `${start.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} – ${end.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}`;
|
|
241
|
+
}
|
|
242
|
+
return `${start.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })} – ${end.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}`;
|
|
243
|
+
}
|
|
244
|
+
parseIsoDate(value) {
|
|
245
|
+
if (!value?.trim())
|
|
246
|
+
return null;
|
|
247
|
+
const iso = /^(\d{4})-(\d{2})-(\d{2})/.exec(value.trim());
|
|
248
|
+
if (iso) {
|
|
249
|
+
const d = new Date(Number(iso[1]), Number(iso[2]) - 1, Number(iso[3]));
|
|
250
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
251
|
+
}
|
|
252
|
+
const d = new Date(value);
|
|
253
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
254
|
+
}
|
|
255
|
+
findKpi(re) {
|
|
256
|
+
return this.data()?.kpiCards.find((c) => re.test(c.label)) ?? null;
|
|
257
|
+
}
|
|
258
|
+
load() {
|
|
259
|
+
this.loading.set(true);
|
|
260
|
+
this.chartsReady.set(false);
|
|
261
|
+
this.hoveredVolume.set(null);
|
|
262
|
+
this.api.getAnalytics(this.range()).subscribe(({ data }) => {
|
|
263
|
+
this.data.set(data);
|
|
264
|
+
this.loading.set(false);
|
|
265
|
+
// Next frame so CSS enter animations run after paint.
|
|
266
|
+
requestAnimationFrame(() => this.chartsReady.set(true));
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AnalyticsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
270
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: AnalyticsPageComponent, isStandalone: true, selector: "eds-analytics-page", ngImport: i0, template: "<div class=\"eds-page analytics-page\">\n <header class=\"analytics-page__header\">\n <div>\n <h1 class=\"eds-h1\">Analytics</h1>\n <p class=\"eds-body-sm\">How the AI is doing over time, and what\u2019s worth changing.</p>\n </div>\n <div class=\"analytics-page__actions\">\n <div class=\"analytics-page__range\" role=\"group\" aria-label=\"Date range\">\n @for (r of ranges; track r.id) {\n <button\n type=\"button\"\n class=\"analytics-page__range-btn\"\n [class.analytics-page__range-btn--active]=\"range() === r.id\"\n (click)=\"setRange(r.id)\"\n >\n {{ r.label }}\n </button>\n }\n </div>\n <eds-button variant=\"secondary\" type=\"button\">\n <eds-icon name=\"download\" [size]=\"18\" />\n Export\n </eds-button>\n <button type=\"button\" class=\"analytics-page__icon-btn\" aria-label=\"Filter\">\n <eds-icon name=\"filter_list\" [size]=\"20\" />\n </button>\n </div>\n </header>\n\n @if (loading()) {\n <div class=\"analytics-page__loading\" aria-busy=\"true\" aria-live=\"polite\">\n <div class=\"analytics-page__skeleton analytics-page__skeleton--hero\"></div>\n <div class=\"analytics-page__skeleton-row\">\n <div class=\"analytics-page__skeleton analytics-page__skeleton--stat\"></div>\n <div class=\"analytics-page__skeleton analytics-page__skeleton--stat\"></div>\n <div class=\"analytics-page__skeleton analytics-page__skeleton--stat\"></div>\n <div class=\"analytics-page__skeleton analytics-page__skeleton--stat\"></div>\n </div>\n <div class=\"analytics-page__skeleton-row analytics-page__skeleton-row--charts\">\n <div class=\"analytics-page__skeleton analytics-page__skeleton--chart\"></div>\n <div class=\"analytics-page__skeleton analytics-page__skeleton--chart\"></div>\n </div>\n <p class=\"eds-body-sm analytics-page__loading-label\">Loading analytics\u2026</p>\n </div>\n } @else {\n @if (data(); as d) {\n <div class=\"analytics-page__body\" [class.analytics-page__body--ready]=\"chartsReady()\">\n <section class=\"analytics-page__hero eds-animate-fade-up\" aria-label=\"Key outcomes\">\n <article class=\"analytics-page__impact\">\n <span class=\"analytics-page__impact-label\">\n {{ impactKpi()?.label || 'Estimated Impact This Month' }}\n </span>\n <strong class=\"analytics-page__impact-value\">{{ impactKpi()?.value || '\u2014' }}</strong>\n <p class=\"analytics-page__impact-sub\">\n {{ impactKpi()?.sub || 'of front-desk time handled by the AI' }}\n </p>\n </article>\n\n <article class=\"analytics-page__resolution\">\n <span class=\"eds-label\">{{ resolutionKpi()?.label || 'AI Autonomous Resolution' }}</span>\n <div class=\"analytics-page__resolution-row\">\n <strong class=\"analytics-page__resolution-value\">{{ resolutionKpi()?.value || '\u2014' }}</strong>\n @if (resolutionKpi()?.delta) {\n <span\n class=\"analytics-page__trend\"\n [attr.data-tone]=\"deltaTone(resolutionKpi())\"\n >\n <eds-icon\n [name]=\"deltaTone(resolutionKpi()) === 'pos' ? 'trending_up' : 'trending_down'\"\n [size]=\"16\"\n />\n {{ resolutionKpi()!.delta }}\n </span>\n }\n </div>\n <p class=\"analytics-page__muted\">{{ resolutionKpi()?.sub || 'without staff intervention' }}</p>\n </article>\n\n <article class=\"analytics-page__distribution\">\n <div class=\"analytics-page__distribution-head\">\n <strong class=\"analytics-page__distribution-total\">{{ totalCallsLabel() }}</strong>\n @if (directionSub(); as dir) {\n <span class=\"analytics-page__muted\">{{ dir }}</span>\n }\n </div>\n <div class=\"analytics-page__stack\" role=\"img\" aria-label=\"Call distribution\">\n @for (seg of distribution(); track seg.label) {\n <span\n class=\"analytics-page__stack-seg\"\n [style.width.%]=\"seg.width\"\n [style.background]=\"seg.color\"\n [attr.aria-label]=\"distributionTooltip(seg.label, seg.value)\"\n >\n <span class=\"analytics-page__tooltip\">{{ distributionTooltip(seg.label, seg.value) }}</span>\n </span>\n }\n </div>\n <ul class=\"analytics-page__legend\">\n @for (seg of distribution(); track seg.label) {\n <li>\n <span class=\"analytics-page__swatch\" [style.background]=\"seg.color\"></span>\n <span>\n {{ seg.label }}\n <strong>{{ estimatedCalls(seg.value).toLocaleString() }}</strong>\n calls\n </span>\n <em>{{ seg.value }}%</em>\n </li>\n }\n </ul>\n </article>\n </section>\n\n <section class=\"analytics-page__stats eds-animate-fade-up\" aria-label=\"Activity KPIs\">\n @for (kpi of secondaryKpis(); track kpi.label) {\n <article class=\"analytics-page__stat\">\n <div class=\"analytics-page__stat-top\">\n <span class=\"eds-label\">{{ kpi.label }}</span>\n @if (kpi.delta) {\n <span class=\"analytics-page__trend\" [attr.data-tone]=\"deltaTone(kpi)\">\n <eds-icon\n [name]=\"deltaTone(kpi) === 'pos' ? 'trending_up' : 'trending_down'\"\n [size]=\"14\"\n />\n {{ kpi.delta }}\n </span>\n }\n </div>\n <strong class=\"analytics-page__stat-value\">{{ kpi.value }}</strong>\n @if (kpi.sub) {\n <span class=\"analytics-page__muted\">{{ kpi.sub }}</span>\n }\n </article>\n }\n </section>\n\n <div class=\"analytics-page__charts\">\n <article class=\"eds-card analytics-page__chart eds-animate-fade-up\">\n <div class=\"analytics-page__chart-head\">\n <div class=\"analytics-page__chart-title\">\n <h2 class=\"eds-h2\">Call Volume</h2>\n <span class=\"analytics-page__chart-period\">{{ periodLabel() }}</span>\n </div>\n <ul class=\"analytics-page__line-legend\">\n <li><span class=\"analytics-page__dot analytics-page__dot--total\"></span> Total Calls</li>\n <li><span class=\"analytics-page__dot analytics-page__dot--in\"></span> Inbound Calls</li>\n <li><span class=\"analytics-page__dot analytics-page__dot--out\"></span> Outbound Calls</li>\n </ul>\n </div>\n <div class=\"analytics-page__line-chart\">\n <svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\" aria-hidden=\"true\">\n <path class=\"analytics-page__area\" [attr.d]=\"volumeAreaPath()\" />\n <path class=\"analytics-page__line analytics-page__line--total\" [attr.d]=\"volumePath('total')\" fill=\"none\" />\n <path class=\"analytics-page__line analytics-page__line--in\" [attr.d]=\"volumePath('inbound')\" fill=\"none\" />\n <path class=\"analytics-page__line analytics-page__line--out\" [attr.d]=\"volumePath('outbound')\" fill=\"none\" />\n </svg>\n <div\n class=\"analytics-page__volume-hit\"\n role=\"list\"\n aria-label=\"Call volume by period\"\n (mouseleave)=\"onVolumeLeave()\"\n >\n @for (point of volumePoints(); track point.date || point.label; let i = $index) {\n <button\n type=\"button\"\n class=\"analytics-page__volume-col\"\n role=\"listitem\"\n [class.analytics-page__volume-col--active]=\"hoveredVolume() === i\"\n [attr.aria-label]=\"volumeTooltip(point)\"\n (mouseenter)=\"onVolumeEnter(i)\"\n (focus)=\"onVolumeEnter(i)\"\n (blur)=\"onVolumeLeave()\"\n >\n <span class=\"analytics-page__tooltip analytics-page__tooltip--multiline\">\n {{ volumeTooltip(point) }}\n </span>\n </button>\n }\n </div>\n <div class=\"analytics-page__line-axis\">\n @for (label of volumeAxisLabels(); track $index) {\n <span>{{ label }}</span>\n }\n </div>\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart eds-animate-fade-up\">\n <h2 class=\"eds-h2\">Top Call Reasons</h2>\n <div class=\"analytics-page__hbars\">\n @for (item of d.reasons; track item.label; let i = $index) {\n <div\n class=\"analytics-page__hbar-row\"\n [attr.aria-label]=\"hbarTooltip(item.label, item.value)\"\n >\n <span class=\"analytics-page__hbar-label\">{{ item.label }}</span>\n <div class=\"analytics-page__hbar-track\">\n <span\n class=\"analytics-page__hbar-fill\"\n [style.width.%]=\"(item.value / maxValue(d.reasons)) * 100\"\n [style.background]=\"reasonColor(i)\"\n [style.animation-delay.ms]=\"i * 40\"\n ></span>\n <span class=\"analytics-page__tooltip\">{{ hbarTooltip(item.label, item.value) }}</span>\n </div>\n <span class=\"analytics-page__hbar-pct\">{{ item.value }}%</span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart eds-animate-fade-up\">\n <h2 class=\"eds-h2\">Reasons AI got stuck</h2>\n <div class=\"analytics-page__hbars\">\n @for (item of d.transfers; track item.label; let i = $index) {\n <div\n class=\"analytics-page__hbar-row\"\n [attr.aria-label]=\"hbarTooltip(item.label, item.value)\"\n >\n <span class=\"analytics-page__hbar-label\">{{ item.label }}</span>\n <div class=\"analytics-page__hbar-track\">\n <span\n class=\"analytics-page__hbar-fill\"\n [style.width.%]=\"(item.value / maxValue(d.transfers)) * 100\"\n [style.background]=\"stuckColor(i)\"\n [style.animation-delay.ms]=\"i * 40\"\n ></span>\n <span class=\"analytics-page__tooltip\">{{ hbarTooltip(item.label, item.value) }}</span>\n </div>\n <span class=\"analytics-page__hbar-pct\">{{ item.value }}%</span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart eds-animate-fade-up\">\n <div class=\"analytics-page__chart-head\">\n <h2 class=\"eds-h2\">Peak Hours</h2>\n <span class=\"eds-label\">\n @if (d.peakHours.timezone) {\n {{ d.peakHours.timezone }}\n } @else {\n Calls per hour\n }\n </span>\n </div>\n <div class=\"analytics-page__peak\">\n <div class=\"analytics-page__peak-bars\" role=\"list\" aria-label=\"Call volume by hour\">\n @for (point of d.peakHours.points; track point.hour; let i = $index) {\n <div\n class=\"analytics-page__peak-col\"\n role=\"listitem\"\n [attr.aria-label]=\"peakTooltip(point)\"\n >\n <div class=\"analytics-page__peak-wrap\">\n <div\n class=\"analytics-page__peak-bar\"\n [style.height.%]=\"barHeightPercent(point, d.peakHours.points)\"\n [style.animation-delay.ms]=\"i * 18\"\n ></div>\n <span class=\"analytics-page__tooltip\">{{ peakTooltip(point) }}</span>\n </div>\n </div>\n }\n </div>\n <div class=\"analytics-page__peak-axis\">\n @for (point of d.peakHours.points; track point.hour; let i = $index) {\n <span>\n @if (showPeakAxisLabel(i, d.peakHours.points.length)) {\n {{ point.label }}\n }\n </span>\n }\n </div>\n </div>\n </article>\n </div>\n </div>\n }\n }\n</div>\n", styles: [".analytics-page__header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:20px;flex-wrap:wrap}.analytics-page__actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.analytics-page__period{margin:6px 0 0;font:500 13px/1.3 var(--eds-font-inter);color:var(--eds-text-secondary, #71717a)}.analytics-page__range{display:flex;gap:4px;background:var(--eds-surface-l0);border:1px solid var(--eds-border-default);border-radius:var(--eds-radius-md);padding:4px}.analytics-page__range-btn{padding:6px 12px;border:none;background:none;font-size:13px;border-radius:var(--eds-radius-sm);cursor:pointer;color:var(--eds-text-secondary)}.analytics-page__range-btn--active{background:var(--eds-brand-500);color:#fff}.analytics-page__icon-btn{width:36px;height:36px;display:inline-flex;align-items:center;justify-content:center;border-radius:var(--eds-radius-md);border:1px solid var(--eds-border-default);background:var(--eds-surface-l0);color:var(--eds-text-secondary);cursor:pointer}.analytics-page__icon-btn:hover{color:var(--eds-text-primary);border-color:var(--eds-gray-300)}.analytics-page__hero{display:grid;grid-template-columns:1.1fr .9fr 1.6fr;gap:12px;margin-bottom:12px}.analytics-page__impact{padding:20px;border-radius:16px;background:linear-gradient(145deg,#3b82f6,#2563eb 55%,#1d4ed8);color:#fff;display:flex;flex-direction:column;gap:8px;min-height:140px}.analytics-page__impact-label{font:500 13px/1.3 var(--eds-font-body);opacity:.9}.analytics-page__impact-value{font:700 36px/1.1 var(--eds-font-body);letter-spacing:-.02em}.analytics-page__impact-sub{margin:0;font:400 13px/1.45 var(--eds-font-body);opacity:.9;max-width:28ch}.analytics-page__resolution,.analytics-page__distribution,.analytics-page__stat{padding:16px 18px;border-radius:16px;background:var(--eds-surface-l0);border:1px solid var(--eds-border-default)}.analytics-page__resolution{display:flex;flex-direction:column;gap:8px;justify-content:center}.analytics-page__resolution-row{display:flex;align-items:baseline;gap:10px}.analytics-page__resolution-value{font:700 34px/1 var(--eds-font-body);color:var(--eds-text-primary)}.analytics-page__distribution{display:flex;flex-direction:column;gap:12px}.analytics-page__distribution-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap}.analytics-page__distribution-total{font:700 20px/1.2 var(--eds-font-body);color:var(--eds-text-primary)}.analytics-page__stack{display:flex;height:14px;border-radius:999px;overflow:visible;background:var(--eds-surface-l2)}.analytics-page__stack-seg{position:relative;display:block;height:100%;min-width:2px;cursor:default}.analytics-page__stack-seg:first-child{border-radius:999px 0 0 999px}.analytics-page__stack-seg:last-child{border-radius:0 999px 999px 0}.analytics-page__stack-seg:only-child{border-radius:999px}.analytics-page__stack-seg:hover .analytics-page__tooltip,.analytics-page__stack-seg:focus-within .analytics-page__tooltip{opacity:1;transform:translate(-50%)}.analytics-page__tooltip{position:absolute;bottom:calc(100% + 8px);left:50%;transform:translate(-50%,4px);padding:8px 12px;background:var(--eds-gray-900, #1f2328);color:#fafafa;font:500 12px/1.4 var(--eds-font-inter);border-radius:8px;white-space:nowrap;width:max-content;min-width:168px;opacity:0;pointer-events:none;transition:opacity .15s ease,transform .15s ease;z-index:5;box-shadow:0 4px 12px #0000002e}.analytics-page__tooltip:after{content:\"\";position:absolute;top:100%;left:50%;transform:translate(-50%);border:5px solid transparent;border-top-color:var(--eds-gray-900, #1f2328)}.analytics-page__tooltip--multiline{white-space:pre-line;text-align:left;min-width:190px;max-width:none}.analytics-page__loading{display:flex;flex-direction:column;gap:14px}.analytics-page__loading-label{margin:4px 0 0}.analytics-page__skeleton{border-radius:16px;background:linear-gradient(90deg,var(--eds-surface-l0) 0%,#eef2f4 45%,var(--eds-surface-l0) 90%);background-size:200% 100%;animation:analytics-shimmer 1.2s ease-in-out infinite;border:1px solid var(--eds-border-default)}.analytics-page__skeleton--hero{height:150px}.analytics-page__skeleton--stat{height:96px;flex:1}.analytics-page__skeleton--chart{height:280px;flex:1}.analytics-page__skeleton-row{display:flex;gap:12px}.analytics-page__skeleton-row--charts{flex-wrap:wrap}.analytics-page__body .analytics-page__hbar-fill,.analytics-page__body .analytics-page__peak-bar,.analytics-page__body .analytics-page__line,.analytics-page__body .analytics-page__area,.analytics-page__body .analytics-page__stack-seg{opacity:0}.analytics-page__body--ready .analytics-page__hbar-fill{opacity:1;animation:analytics-hbar-grow .55s ease forwards;transform-origin:left center}.analytics-page__body--ready .analytics-page__peak-bar{opacity:1;animation:eds-bar-grow .55s ease forwards;transform-origin:bottom}.analytics-page__body--ready .analytics-page__line,.analytics-page__body--ready .analytics-page__area,.analytics-page__body--ready .analytics-page__stack-seg{opacity:1;animation:analytics-fade-in .45s ease forwards}.analytics-page__body--ready .analytics-page__line--total{animation:analytics-draw-line .85s ease forwards}.analytics-page__body--ready .analytics-page__line--in{animation:analytics-draw-line .85s ease .08s forwards}.analytics-page__body--ready .analytics-page__line--out{animation:analytics-draw-line .85s ease .16s forwards}.analytics-page__legend{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:8px 16px}.analytics-page__legend li{display:inline-flex;align-items:center;gap:8px;font:400 12px/1.3 var(--eds-font-body);color:var(--eds-text-secondary)}.analytics-page__legend li strong{color:var(--eds-text-primary);font-weight:600}.analytics-page__legend li em{font-style:normal;color:var(--eds-text-secondary);font-weight:600}.analytics-page__swatch{width:8px;height:8px;border-radius:50%;flex-shrink:0}.analytics-page__stats{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px}.analytics-page__stat{display:flex;flex-direction:column;gap:6px}.analytics-page__stat-top{display:flex;align-items:center;justify-content:space-between;gap:8px}.analytics-page__stat-value{font:700 28px/1.1 var(--eds-font-body);color:var(--eds-text-primary)}.analytics-page__trend{display:inline-flex;align-items:center;gap:2px;font:600 12px/1 var(--eds-font-body)}.analytics-page__trend[data-tone=pos]{color:var(--eds-green-600, #16a34a)}.analytics-page__trend[data-tone=warn],.analytics-page__trend[data-tone=neg]{color:var(--eds-red-500, #f03740)}.analytics-page__trend[data-tone=neutral]{color:var(--eds-text-secondary)}.analytics-page__muted{margin:0;font:400 12px/1.4 var(--eds-font-body);color:var(--eds-text-secondary)}.analytics-page__charts{display:grid;grid-template-columns:1fr 1fr;gap:16px}.analytics-page__chart{padding:16px 18px}.analytics-page__chart .eds-h2{margin:0 0 14px}.analytics-page__chart-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:12px;flex-wrap:wrap}.analytics-page__chart-head .eds-h2{margin:0}.analytics-page__chart-title{display:flex;flex-direction:row;align-items:baseline;flex-wrap:wrap;gap:10px;min-width:0}.analytics-page__chart-period{display:inline-flex;align-items:center;padding:3px 10px;border-radius:999px;background:var(--eds-surface-l1, #f7f9fa);border:1px solid var(--eds-border-default, #e5e5e5);font:500 12px/1.3 var(--eds-font-inter);color:var(--eds-text-secondary, #53575c)}.analytics-page__line-legend{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:10px 14px;font:500 11px/1 var(--eds-font-body);color:var(--eds-text-secondary)}.analytics-page__dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px}.analytics-page__dot--total{background:#3681f4}.analytics-page__dot--in{background:#8e51ff}.analytics-page__dot--out{background:#34d399}.analytics-page__line-chart{position:relative;height:220px}.analytics-page__line-chart svg{width:100%;height:calc(100% - 28px);overflow:visible}.analytics-page__volume-hit{position:absolute;inset:0 0 28px;display:flex;z-index:2}.analytics-page__volume-col{flex:1;position:relative;border:none;background:transparent;padding:0;cursor:default;min-width:0}.analytics-page__volume-col:before{content:\"\";position:absolute;inset:0 35%;border-radius:4px;background:transparent;transition:background .15s ease}.analytics-page__volume-col:hover:before,.analytics-page__volume-col--active:before,.analytics-page__volume-col:focus-visible:before{background:#2162f014}.analytics-page__volume-col:hover .analytics-page__tooltip,.analytics-page__volume-col:focus-visible .analytics-page__tooltip,.analytics-page__volume-col--active .analytics-page__tooltip{opacity:1;transform:translate(-50%)}.analytics-page__area{fill:#3681f41f}.analytics-page__line{stroke-width:2.2;vector-effect:non-scaling-stroke;fill:none;stroke-linecap:round;stroke-linejoin:round}.analytics-page__line--total{stroke:#3681f4}.analytics-page__line--in{stroke:#8e51ff}.analytics-page__line--out{stroke:#34d399}.analytics-page__line-axis{display:flex;justify-content:space-between;gap:4px;margin-top:8px}.analytics-page__line-axis span{flex:1;text-align:center;font:500 11px/1.2 var(--eds-font-mono);color:var(--eds-text-secondary);text-transform:uppercase}.analytics-page__hbars{display:flex;flex-direction:column;gap:12px}.analytics-page__hbar-row{display:grid;grid-template-columns:minmax(110px,140px) 1fr 42px;align-items:center;gap:10px}.analytics-page__hbar-label{font:500 13px/1.3 var(--eds-font-body);color:var(--eds-text-primary)}.analytics-page__hbar-track{position:relative;height:10px;background:var(--eds-surface-l2);border-radius:999px;overflow:visible}.analytics-page__hbar-track:hover .analytics-page__tooltip,.analytics-page__hbar-track:focus-within .analytics-page__tooltip{opacity:1;transform:translate(-50%)}.analytics-page__hbar-fill{display:block;height:100%;border-radius:999px;min-width:4px}.analytics-page__hbar-pct{text-align:right;font:600 12px/1 var(--eds-font-mono);color:var(--eds-text-secondary)}.analytics-page__peak{display:flex;flex-direction:column;gap:8px}.analytics-page__peak-bars{display:flex;align-items:flex-end;gap:3px;height:160px}.analytics-page__peak-col{flex:1;min-width:0;height:100%;display:flex;flex-direction:column}.analytics-page__peak-wrap{position:relative;flex:1;display:flex;align-items:flex-end;min-height:0}.analytics-page__peak-wrap:hover .analytics-page__tooltip,.analytics-page__peak-wrap:focus-within .analytics-page__tooltip{opacity:1;transform:translate(-50%)}.analytics-page__peak-bar{width:100%;min-height:2px;border-radius:4px 4px 0 0;background:#93c5fd;transition:background .15s ease}.analytics-page__peak-col:hover .analytics-page__peak-bar{background:#60a5fa}.analytics-page__peak-axis{display:flex;gap:3px}.analytics-page__peak-axis span{flex:1;text-align:center;min-height:14px;font:500 10px/1.2 var(--eds-font-mono);color:var(--eds-text-secondary)}@keyframes analytics-shimmer{0%{background-position:100% 0}to{background-position:-100% 0}}@keyframes analytics-hbar-grow{0%{transform:scaleX(0)}to{transform:scaleX(1)}}@keyframes analytics-fade-in{0%{opacity:0}to{opacity:1}}@keyframes analytics-draw-line{0%{opacity:0;stroke-dasharray:400;stroke-dashoffset:400}to{opacity:1;stroke-dasharray:400;stroke-dashoffset:0}}@media(max-width:1100px){.analytics-page__hero{grid-template-columns:1fr 1fr}.analytics-page__distribution{grid-column:1/-1}.analytics-page__stats{grid-template-columns:repeat(2,1fr)}}@media(max-width:800px){.analytics-page__hero,.analytics-page__charts,.analytics-page__stats{grid-template-columns:1fr}.analytics-page__distribution{grid-column:auto}.analytics-page__hbar-row{grid-template-columns:100px 1fr 36px}}\n"], dependencies: [{ kind: "component", type: EdsIconComponent, selector: "eds-icon", inputs: ["name", "size", "fill", "weight"] }, { kind: "component", type: EdsButtonComponent, selector: "eds-button", inputs: ["variant", "type", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
271
|
+
}
|
|
272
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AnalyticsPageComponent, decorators: [{
|
|
273
|
+
type: Component,
|
|
274
|
+
args: [{ selector: 'eds-analytics-page', imports: [EdsIconComponent, EdsButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"eds-page analytics-page\">\n <header class=\"analytics-page__header\">\n <div>\n <h1 class=\"eds-h1\">Analytics</h1>\n <p class=\"eds-body-sm\">How the AI is doing over time, and what\u2019s worth changing.</p>\n </div>\n <div class=\"analytics-page__actions\">\n <div class=\"analytics-page__range\" role=\"group\" aria-label=\"Date range\">\n @for (r of ranges; track r.id) {\n <button\n type=\"button\"\n class=\"analytics-page__range-btn\"\n [class.analytics-page__range-btn--active]=\"range() === r.id\"\n (click)=\"setRange(r.id)\"\n >\n {{ r.label }}\n </button>\n }\n </div>\n <eds-button variant=\"secondary\" type=\"button\">\n <eds-icon name=\"download\" [size]=\"18\" />\n Export\n </eds-button>\n <button type=\"button\" class=\"analytics-page__icon-btn\" aria-label=\"Filter\">\n <eds-icon name=\"filter_list\" [size]=\"20\" />\n </button>\n </div>\n </header>\n\n @if (loading()) {\n <div class=\"analytics-page__loading\" aria-busy=\"true\" aria-live=\"polite\">\n <div class=\"analytics-page__skeleton analytics-page__skeleton--hero\"></div>\n <div class=\"analytics-page__skeleton-row\">\n <div class=\"analytics-page__skeleton analytics-page__skeleton--stat\"></div>\n <div class=\"analytics-page__skeleton analytics-page__skeleton--stat\"></div>\n <div class=\"analytics-page__skeleton analytics-page__skeleton--stat\"></div>\n <div class=\"analytics-page__skeleton analytics-page__skeleton--stat\"></div>\n </div>\n <div class=\"analytics-page__skeleton-row analytics-page__skeleton-row--charts\">\n <div class=\"analytics-page__skeleton analytics-page__skeleton--chart\"></div>\n <div class=\"analytics-page__skeleton analytics-page__skeleton--chart\"></div>\n </div>\n <p class=\"eds-body-sm analytics-page__loading-label\">Loading analytics\u2026</p>\n </div>\n } @else {\n @if (data(); as d) {\n <div class=\"analytics-page__body\" [class.analytics-page__body--ready]=\"chartsReady()\">\n <section class=\"analytics-page__hero eds-animate-fade-up\" aria-label=\"Key outcomes\">\n <article class=\"analytics-page__impact\">\n <span class=\"analytics-page__impact-label\">\n {{ impactKpi()?.label || 'Estimated Impact This Month' }}\n </span>\n <strong class=\"analytics-page__impact-value\">{{ impactKpi()?.value || '\u2014' }}</strong>\n <p class=\"analytics-page__impact-sub\">\n {{ impactKpi()?.sub || 'of front-desk time handled by the AI' }}\n </p>\n </article>\n\n <article class=\"analytics-page__resolution\">\n <span class=\"eds-label\">{{ resolutionKpi()?.label || 'AI Autonomous Resolution' }}</span>\n <div class=\"analytics-page__resolution-row\">\n <strong class=\"analytics-page__resolution-value\">{{ resolutionKpi()?.value || '\u2014' }}</strong>\n @if (resolutionKpi()?.delta) {\n <span\n class=\"analytics-page__trend\"\n [attr.data-tone]=\"deltaTone(resolutionKpi())\"\n >\n <eds-icon\n [name]=\"deltaTone(resolutionKpi()) === 'pos' ? 'trending_up' : 'trending_down'\"\n [size]=\"16\"\n />\n {{ resolutionKpi()!.delta }}\n </span>\n }\n </div>\n <p class=\"analytics-page__muted\">{{ resolutionKpi()?.sub || 'without staff intervention' }}</p>\n </article>\n\n <article class=\"analytics-page__distribution\">\n <div class=\"analytics-page__distribution-head\">\n <strong class=\"analytics-page__distribution-total\">{{ totalCallsLabel() }}</strong>\n @if (directionSub(); as dir) {\n <span class=\"analytics-page__muted\">{{ dir }}</span>\n }\n </div>\n <div class=\"analytics-page__stack\" role=\"img\" aria-label=\"Call distribution\">\n @for (seg of distribution(); track seg.label) {\n <span\n class=\"analytics-page__stack-seg\"\n [style.width.%]=\"seg.width\"\n [style.background]=\"seg.color\"\n [attr.aria-label]=\"distributionTooltip(seg.label, seg.value)\"\n >\n <span class=\"analytics-page__tooltip\">{{ distributionTooltip(seg.label, seg.value) }}</span>\n </span>\n }\n </div>\n <ul class=\"analytics-page__legend\">\n @for (seg of distribution(); track seg.label) {\n <li>\n <span class=\"analytics-page__swatch\" [style.background]=\"seg.color\"></span>\n <span>\n {{ seg.label }}\n <strong>{{ estimatedCalls(seg.value).toLocaleString() }}</strong>\n calls\n </span>\n <em>{{ seg.value }}%</em>\n </li>\n }\n </ul>\n </article>\n </section>\n\n <section class=\"analytics-page__stats eds-animate-fade-up\" aria-label=\"Activity KPIs\">\n @for (kpi of secondaryKpis(); track kpi.label) {\n <article class=\"analytics-page__stat\">\n <div class=\"analytics-page__stat-top\">\n <span class=\"eds-label\">{{ kpi.label }}</span>\n @if (kpi.delta) {\n <span class=\"analytics-page__trend\" [attr.data-tone]=\"deltaTone(kpi)\">\n <eds-icon\n [name]=\"deltaTone(kpi) === 'pos' ? 'trending_up' : 'trending_down'\"\n [size]=\"14\"\n />\n {{ kpi.delta }}\n </span>\n }\n </div>\n <strong class=\"analytics-page__stat-value\">{{ kpi.value }}</strong>\n @if (kpi.sub) {\n <span class=\"analytics-page__muted\">{{ kpi.sub }}</span>\n }\n </article>\n }\n </section>\n\n <div class=\"analytics-page__charts\">\n <article class=\"eds-card analytics-page__chart eds-animate-fade-up\">\n <div class=\"analytics-page__chart-head\">\n <div class=\"analytics-page__chart-title\">\n <h2 class=\"eds-h2\">Call Volume</h2>\n <span class=\"analytics-page__chart-period\">{{ periodLabel() }}</span>\n </div>\n <ul class=\"analytics-page__line-legend\">\n <li><span class=\"analytics-page__dot analytics-page__dot--total\"></span> Total Calls</li>\n <li><span class=\"analytics-page__dot analytics-page__dot--in\"></span> Inbound Calls</li>\n <li><span class=\"analytics-page__dot analytics-page__dot--out\"></span> Outbound Calls</li>\n </ul>\n </div>\n <div class=\"analytics-page__line-chart\">\n <svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\" aria-hidden=\"true\">\n <path class=\"analytics-page__area\" [attr.d]=\"volumeAreaPath()\" />\n <path class=\"analytics-page__line analytics-page__line--total\" [attr.d]=\"volumePath('total')\" fill=\"none\" />\n <path class=\"analytics-page__line analytics-page__line--in\" [attr.d]=\"volumePath('inbound')\" fill=\"none\" />\n <path class=\"analytics-page__line analytics-page__line--out\" [attr.d]=\"volumePath('outbound')\" fill=\"none\" />\n </svg>\n <div\n class=\"analytics-page__volume-hit\"\n role=\"list\"\n aria-label=\"Call volume by period\"\n (mouseleave)=\"onVolumeLeave()\"\n >\n @for (point of volumePoints(); track point.date || point.label; let i = $index) {\n <button\n type=\"button\"\n class=\"analytics-page__volume-col\"\n role=\"listitem\"\n [class.analytics-page__volume-col--active]=\"hoveredVolume() === i\"\n [attr.aria-label]=\"volumeTooltip(point)\"\n (mouseenter)=\"onVolumeEnter(i)\"\n (focus)=\"onVolumeEnter(i)\"\n (blur)=\"onVolumeLeave()\"\n >\n <span class=\"analytics-page__tooltip analytics-page__tooltip--multiline\">\n {{ volumeTooltip(point) }}\n </span>\n </button>\n }\n </div>\n <div class=\"analytics-page__line-axis\">\n @for (label of volumeAxisLabels(); track $index) {\n <span>{{ label }}</span>\n }\n </div>\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart eds-animate-fade-up\">\n <h2 class=\"eds-h2\">Top Call Reasons</h2>\n <div class=\"analytics-page__hbars\">\n @for (item of d.reasons; track item.label; let i = $index) {\n <div\n class=\"analytics-page__hbar-row\"\n [attr.aria-label]=\"hbarTooltip(item.label, item.value)\"\n >\n <span class=\"analytics-page__hbar-label\">{{ item.label }}</span>\n <div class=\"analytics-page__hbar-track\">\n <span\n class=\"analytics-page__hbar-fill\"\n [style.width.%]=\"(item.value / maxValue(d.reasons)) * 100\"\n [style.background]=\"reasonColor(i)\"\n [style.animation-delay.ms]=\"i * 40\"\n ></span>\n <span class=\"analytics-page__tooltip\">{{ hbarTooltip(item.label, item.value) }}</span>\n </div>\n <span class=\"analytics-page__hbar-pct\">{{ item.value }}%</span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart eds-animate-fade-up\">\n <h2 class=\"eds-h2\">Reasons AI got stuck</h2>\n <div class=\"analytics-page__hbars\">\n @for (item of d.transfers; track item.label; let i = $index) {\n <div\n class=\"analytics-page__hbar-row\"\n [attr.aria-label]=\"hbarTooltip(item.label, item.value)\"\n >\n <span class=\"analytics-page__hbar-label\">{{ item.label }}</span>\n <div class=\"analytics-page__hbar-track\">\n <span\n class=\"analytics-page__hbar-fill\"\n [style.width.%]=\"(item.value / maxValue(d.transfers)) * 100\"\n [style.background]=\"stuckColor(i)\"\n [style.animation-delay.ms]=\"i * 40\"\n ></span>\n <span class=\"analytics-page__tooltip\">{{ hbarTooltip(item.label, item.value) }}</span>\n </div>\n <span class=\"analytics-page__hbar-pct\">{{ item.value }}%</span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart eds-animate-fade-up\">\n <div class=\"analytics-page__chart-head\">\n <h2 class=\"eds-h2\">Peak Hours</h2>\n <span class=\"eds-label\">\n @if (d.peakHours.timezone) {\n {{ d.peakHours.timezone }}\n } @else {\n Calls per hour\n }\n </span>\n </div>\n <div class=\"analytics-page__peak\">\n <div class=\"analytics-page__peak-bars\" role=\"list\" aria-label=\"Call volume by hour\">\n @for (point of d.peakHours.points; track point.hour; let i = $index) {\n <div\n class=\"analytics-page__peak-col\"\n role=\"listitem\"\n [attr.aria-label]=\"peakTooltip(point)\"\n >\n <div class=\"analytics-page__peak-wrap\">\n <div\n class=\"analytics-page__peak-bar\"\n [style.height.%]=\"barHeightPercent(point, d.peakHours.points)\"\n [style.animation-delay.ms]=\"i * 18\"\n ></div>\n <span class=\"analytics-page__tooltip\">{{ peakTooltip(point) }}</span>\n </div>\n </div>\n }\n </div>\n <div class=\"analytics-page__peak-axis\">\n @for (point of d.peakHours.points; track point.hour; let i = $index) {\n <span>\n @if (showPeakAxisLabel(i, d.peakHours.points.length)) {\n {{ point.label }}\n }\n </span>\n }\n </div>\n </div>\n </article>\n </div>\n </div>\n }\n }\n</div>\n", styles: [".analytics-page__header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:20px;flex-wrap:wrap}.analytics-page__actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.analytics-page__period{margin:6px 0 0;font:500 13px/1.3 var(--eds-font-inter);color:var(--eds-text-secondary, #71717a)}.analytics-page__range{display:flex;gap:4px;background:var(--eds-surface-l0);border:1px solid var(--eds-border-default);border-radius:var(--eds-radius-md);padding:4px}.analytics-page__range-btn{padding:6px 12px;border:none;background:none;font-size:13px;border-radius:var(--eds-radius-sm);cursor:pointer;color:var(--eds-text-secondary)}.analytics-page__range-btn--active{background:var(--eds-brand-500);color:#fff}.analytics-page__icon-btn{width:36px;height:36px;display:inline-flex;align-items:center;justify-content:center;border-radius:var(--eds-radius-md);border:1px solid var(--eds-border-default);background:var(--eds-surface-l0);color:var(--eds-text-secondary);cursor:pointer}.analytics-page__icon-btn:hover{color:var(--eds-text-primary);border-color:var(--eds-gray-300)}.analytics-page__hero{display:grid;grid-template-columns:1.1fr .9fr 1.6fr;gap:12px;margin-bottom:12px}.analytics-page__impact{padding:20px;border-radius:16px;background:linear-gradient(145deg,#3b82f6,#2563eb 55%,#1d4ed8);color:#fff;display:flex;flex-direction:column;gap:8px;min-height:140px}.analytics-page__impact-label{font:500 13px/1.3 var(--eds-font-body);opacity:.9}.analytics-page__impact-value{font:700 36px/1.1 var(--eds-font-body);letter-spacing:-.02em}.analytics-page__impact-sub{margin:0;font:400 13px/1.45 var(--eds-font-body);opacity:.9;max-width:28ch}.analytics-page__resolution,.analytics-page__distribution,.analytics-page__stat{padding:16px 18px;border-radius:16px;background:var(--eds-surface-l0);border:1px solid var(--eds-border-default)}.analytics-page__resolution{display:flex;flex-direction:column;gap:8px;justify-content:center}.analytics-page__resolution-row{display:flex;align-items:baseline;gap:10px}.analytics-page__resolution-value{font:700 34px/1 var(--eds-font-body);color:var(--eds-text-primary)}.analytics-page__distribution{display:flex;flex-direction:column;gap:12px}.analytics-page__distribution-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap}.analytics-page__distribution-total{font:700 20px/1.2 var(--eds-font-body);color:var(--eds-text-primary)}.analytics-page__stack{display:flex;height:14px;border-radius:999px;overflow:visible;background:var(--eds-surface-l2)}.analytics-page__stack-seg{position:relative;display:block;height:100%;min-width:2px;cursor:default}.analytics-page__stack-seg:first-child{border-radius:999px 0 0 999px}.analytics-page__stack-seg:last-child{border-radius:0 999px 999px 0}.analytics-page__stack-seg:only-child{border-radius:999px}.analytics-page__stack-seg:hover .analytics-page__tooltip,.analytics-page__stack-seg:focus-within .analytics-page__tooltip{opacity:1;transform:translate(-50%)}.analytics-page__tooltip{position:absolute;bottom:calc(100% + 8px);left:50%;transform:translate(-50%,4px);padding:8px 12px;background:var(--eds-gray-900, #1f2328);color:#fafafa;font:500 12px/1.4 var(--eds-font-inter);border-radius:8px;white-space:nowrap;width:max-content;min-width:168px;opacity:0;pointer-events:none;transition:opacity .15s ease,transform .15s ease;z-index:5;box-shadow:0 4px 12px #0000002e}.analytics-page__tooltip:after{content:\"\";position:absolute;top:100%;left:50%;transform:translate(-50%);border:5px solid transparent;border-top-color:var(--eds-gray-900, #1f2328)}.analytics-page__tooltip--multiline{white-space:pre-line;text-align:left;min-width:190px;max-width:none}.analytics-page__loading{display:flex;flex-direction:column;gap:14px}.analytics-page__loading-label{margin:4px 0 0}.analytics-page__skeleton{border-radius:16px;background:linear-gradient(90deg,var(--eds-surface-l0) 0%,#eef2f4 45%,var(--eds-surface-l0) 90%);background-size:200% 100%;animation:analytics-shimmer 1.2s ease-in-out infinite;border:1px solid var(--eds-border-default)}.analytics-page__skeleton--hero{height:150px}.analytics-page__skeleton--stat{height:96px;flex:1}.analytics-page__skeleton--chart{height:280px;flex:1}.analytics-page__skeleton-row{display:flex;gap:12px}.analytics-page__skeleton-row--charts{flex-wrap:wrap}.analytics-page__body .analytics-page__hbar-fill,.analytics-page__body .analytics-page__peak-bar,.analytics-page__body .analytics-page__line,.analytics-page__body .analytics-page__area,.analytics-page__body .analytics-page__stack-seg{opacity:0}.analytics-page__body--ready .analytics-page__hbar-fill{opacity:1;animation:analytics-hbar-grow .55s ease forwards;transform-origin:left center}.analytics-page__body--ready .analytics-page__peak-bar{opacity:1;animation:eds-bar-grow .55s ease forwards;transform-origin:bottom}.analytics-page__body--ready .analytics-page__line,.analytics-page__body--ready .analytics-page__area,.analytics-page__body--ready .analytics-page__stack-seg{opacity:1;animation:analytics-fade-in .45s ease forwards}.analytics-page__body--ready .analytics-page__line--total{animation:analytics-draw-line .85s ease forwards}.analytics-page__body--ready .analytics-page__line--in{animation:analytics-draw-line .85s ease .08s forwards}.analytics-page__body--ready .analytics-page__line--out{animation:analytics-draw-line .85s ease .16s forwards}.analytics-page__legend{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:8px 16px}.analytics-page__legend li{display:inline-flex;align-items:center;gap:8px;font:400 12px/1.3 var(--eds-font-body);color:var(--eds-text-secondary)}.analytics-page__legend li strong{color:var(--eds-text-primary);font-weight:600}.analytics-page__legend li em{font-style:normal;color:var(--eds-text-secondary);font-weight:600}.analytics-page__swatch{width:8px;height:8px;border-radius:50%;flex-shrink:0}.analytics-page__stats{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px}.analytics-page__stat{display:flex;flex-direction:column;gap:6px}.analytics-page__stat-top{display:flex;align-items:center;justify-content:space-between;gap:8px}.analytics-page__stat-value{font:700 28px/1.1 var(--eds-font-body);color:var(--eds-text-primary)}.analytics-page__trend{display:inline-flex;align-items:center;gap:2px;font:600 12px/1 var(--eds-font-body)}.analytics-page__trend[data-tone=pos]{color:var(--eds-green-600, #16a34a)}.analytics-page__trend[data-tone=warn],.analytics-page__trend[data-tone=neg]{color:var(--eds-red-500, #f03740)}.analytics-page__trend[data-tone=neutral]{color:var(--eds-text-secondary)}.analytics-page__muted{margin:0;font:400 12px/1.4 var(--eds-font-body);color:var(--eds-text-secondary)}.analytics-page__charts{display:grid;grid-template-columns:1fr 1fr;gap:16px}.analytics-page__chart{padding:16px 18px}.analytics-page__chart .eds-h2{margin:0 0 14px}.analytics-page__chart-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:12px;flex-wrap:wrap}.analytics-page__chart-head .eds-h2{margin:0}.analytics-page__chart-title{display:flex;flex-direction:row;align-items:baseline;flex-wrap:wrap;gap:10px;min-width:0}.analytics-page__chart-period{display:inline-flex;align-items:center;padding:3px 10px;border-radius:999px;background:var(--eds-surface-l1, #f7f9fa);border:1px solid var(--eds-border-default, #e5e5e5);font:500 12px/1.3 var(--eds-font-inter);color:var(--eds-text-secondary, #53575c)}.analytics-page__line-legend{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:10px 14px;font:500 11px/1 var(--eds-font-body);color:var(--eds-text-secondary)}.analytics-page__dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px}.analytics-page__dot--total{background:#3681f4}.analytics-page__dot--in{background:#8e51ff}.analytics-page__dot--out{background:#34d399}.analytics-page__line-chart{position:relative;height:220px}.analytics-page__line-chart svg{width:100%;height:calc(100% - 28px);overflow:visible}.analytics-page__volume-hit{position:absolute;inset:0 0 28px;display:flex;z-index:2}.analytics-page__volume-col{flex:1;position:relative;border:none;background:transparent;padding:0;cursor:default;min-width:0}.analytics-page__volume-col:before{content:\"\";position:absolute;inset:0 35%;border-radius:4px;background:transparent;transition:background .15s ease}.analytics-page__volume-col:hover:before,.analytics-page__volume-col--active:before,.analytics-page__volume-col:focus-visible:before{background:#2162f014}.analytics-page__volume-col:hover .analytics-page__tooltip,.analytics-page__volume-col:focus-visible .analytics-page__tooltip,.analytics-page__volume-col--active .analytics-page__tooltip{opacity:1;transform:translate(-50%)}.analytics-page__area{fill:#3681f41f}.analytics-page__line{stroke-width:2.2;vector-effect:non-scaling-stroke;fill:none;stroke-linecap:round;stroke-linejoin:round}.analytics-page__line--total{stroke:#3681f4}.analytics-page__line--in{stroke:#8e51ff}.analytics-page__line--out{stroke:#34d399}.analytics-page__line-axis{display:flex;justify-content:space-between;gap:4px;margin-top:8px}.analytics-page__line-axis span{flex:1;text-align:center;font:500 11px/1.2 var(--eds-font-mono);color:var(--eds-text-secondary);text-transform:uppercase}.analytics-page__hbars{display:flex;flex-direction:column;gap:12px}.analytics-page__hbar-row{display:grid;grid-template-columns:minmax(110px,140px) 1fr 42px;align-items:center;gap:10px}.analytics-page__hbar-label{font:500 13px/1.3 var(--eds-font-body);color:var(--eds-text-primary)}.analytics-page__hbar-track{position:relative;height:10px;background:var(--eds-surface-l2);border-radius:999px;overflow:visible}.analytics-page__hbar-track:hover .analytics-page__tooltip,.analytics-page__hbar-track:focus-within .analytics-page__tooltip{opacity:1;transform:translate(-50%)}.analytics-page__hbar-fill{display:block;height:100%;border-radius:999px;min-width:4px}.analytics-page__hbar-pct{text-align:right;font:600 12px/1 var(--eds-font-mono);color:var(--eds-text-secondary)}.analytics-page__peak{display:flex;flex-direction:column;gap:8px}.analytics-page__peak-bars{display:flex;align-items:flex-end;gap:3px;height:160px}.analytics-page__peak-col{flex:1;min-width:0;height:100%;display:flex;flex-direction:column}.analytics-page__peak-wrap{position:relative;flex:1;display:flex;align-items:flex-end;min-height:0}.analytics-page__peak-wrap:hover .analytics-page__tooltip,.analytics-page__peak-wrap:focus-within .analytics-page__tooltip{opacity:1;transform:translate(-50%)}.analytics-page__peak-bar{width:100%;min-height:2px;border-radius:4px 4px 0 0;background:#93c5fd;transition:background .15s ease}.analytics-page__peak-col:hover .analytics-page__peak-bar{background:#60a5fa}.analytics-page__peak-axis{display:flex;gap:3px}.analytics-page__peak-axis span{flex:1;text-align:center;min-height:14px;font:500 10px/1.2 var(--eds-font-mono);color:var(--eds-text-secondary)}@keyframes analytics-shimmer{0%{background-position:100% 0}to{background-position:-100% 0}}@keyframes analytics-hbar-grow{0%{transform:scaleX(0)}to{transform:scaleX(1)}}@keyframes analytics-fade-in{0%{opacity:0}to{opacity:1}}@keyframes analytics-draw-line{0%{opacity:0;stroke-dasharray:400;stroke-dashoffset:400}to{opacity:1;stroke-dasharray:400;stroke-dashoffset:0}}@media(max-width:1100px){.analytics-page__hero{grid-template-columns:1fr 1fr}.analytics-page__distribution{grid-column:1/-1}.analytics-page__stats{grid-template-columns:repeat(2,1fr)}}@media(max-width:800px){.analytics-page__hero,.analytics-page__charts,.analytics-page__stats{grid-template-columns:1fr}.analytics-page__distribution{grid-column:auto}.analytics-page__hbar-row{grid-template-columns:100px 1fr 36px}}\n"] }]
|
|
275
|
+
}] });
|
|
276
|
+
|
|
277
|
+
export { AnalyticsPageComponent };
|
|
278
|
+
//# sourceMappingURL=edvak99-voice-receptionist-widget-analytics-page.component-C5inEHJw.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-analytics-page.component-C5inEHJw.mjs","sources":["../../../projects/voice-receptionist-widget/src/lib/pages/analytics-page/analytics-page.component.ts","../../../projects/voice-receptionist-widget/src/lib/pages/analytics-page/analytics-page.component.html"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, inject, OnInit, signal } from '@angular/core';\n\nimport {\n AnalyticsData,\n AnalyticsKpiCard,\n AnalyticsPeakPoint,\n AnalyticsVolumePoint,\n} from '../../models/ai-receptionist.types';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport { EdsButtonComponent } from '../../ui/eds-button.component';\nimport { EdsIconComponent } from '../../ui/eds-icon.component';\n\nconst REASON_COLORS = ['#8E51FF', '#3681F4', '#F472B6', '#7DD3FC', '#FDA4AF', '#34D399', '#FBBF24'];\nconst STUCK_COLORS = ['#8E51FF', '#3681F4', '#7DD3FC', '#FBBF24'];\n\n@Component({\n selector: 'eds-analytics-page',\n imports: [EdsIconComponent, EdsButtonComponent],\n templateUrl: './analytics-page.component.html',\n styleUrl: './analytics-page.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AnalyticsPageComponent implements OnInit {\n private readonly api = inject(AiReceptionistApiService);\n\n protected readonly data = signal<AnalyticsData | null>(null);\n protected readonly range = signal('30d');\n protected readonly loading = signal(true);\n protected readonly chartsReady = signal(false);\n protected readonly hoveredVolume = signal<number | null>(null);\n\n protected readonly ranges = [\n { id: '7d', label: '7 days' },\n { id: '30d', label: '30 days' },\n { id: '90d', label: '90 days' },\n ];\n\n protected readonly impactKpi = computed(() =>\n this.findKpi(/(impact|hours|front-desk|time saved)/i),\n );\n protected readonly resolutionKpi = computed(() =>\n this.findKpi(/(autonomous|resolution)/i),\n );\n protected readonly secondaryKpis = computed(() => {\n const cards = this.data()?.kpiCards ?? [];\n const preferred = [\n /appointment/i,\n /task/i,\n /patient|registr/i,\n /calls?\\s*handled|total\\s*calls|^calls$/i,\n ];\n const picked: AnalyticsKpiCard[] = [];\n for (const re of preferred) {\n const hit = cards.find((c) => re.test(c.label) && !picked.includes(c));\n if (hit) picked.push(hit);\n }\n if (picked.length >= 4) return picked.slice(0, 4);\n const rest = cards.filter(\n (c) =>\n !picked.includes(c) &&\n !/(impact|hours|front-desk|autonomous|resolution)/i.test(c.label),\n );\n return [...picked, ...rest].slice(0, 4);\n });\n\n protected readonly distribution = computed(() => {\n const outcomes = this.data()?.outcomes ?? [];\n const total = outcomes.reduce((s, o) => s + o.value, 0) || 1;\n return outcomes.map((o) => ({\n ...o,\n pct: Math.round((o.value / total) * 100),\n width: (o.value / total) * 100,\n }));\n });\n\n protected readonly totalCallsLabel = computed(() => {\n const handled = this.findKpi(/calls?\\s*handled|total\\s*calls|^calls$/i);\n if (handled?.value) return handled.value.includes('Call') ? handled.value : `${handled.value} Calls`;\n const n = this.data()?.kpis.totalCalls;\n return n ? `${n.toLocaleString()} Calls` : 'Calls';\n });\n\n protected readonly directionSub = computed(() => {\n const handled = this.findKpi(/calls?\\s*handled|total\\s*calls|^calls$/i);\n if (handled?.sub && /inbound|outbound/i.test(handled.sub)) return handled.sub;\n return null;\n });\n\n protected readonly volumePoints = computed(() => this.data()?.volume.points ?? []);\n\n /** Human date span for the selected analytics window (month + year included). */\n protected readonly periodLabel = computed(() => {\n const vol = this.data()?.volume;\n const start =\n this.parseIsoDate(vol?.startDate) ?? this.parseIsoDate(vol?.points?.[0]?.date) ?? null;\n const end =\n this.parseIsoDate(vol?.endDate) ??\n this.parseIsoDate(vol?.points?.[vol.points.length - 1]?.date) ??\n null;\n if (start && end) return this.formatPeriodRange(start, end);\n\n const days = this.range() === '7d' ? 7 : this.range() === '90d' ? 90 : 30;\n const endD = new Date();\n const startD = new Date();\n startD.setDate(endD.getDate() - (days - 1));\n return this.formatPeriodRange(startD, endD);\n });\n\n ngOnInit(): void {\n this.load();\n }\n\n protected setRange(id: string): void {\n this.range.set(id);\n this.load();\n }\n\n protected maxCount(items: { count: number }[]): number {\n return Math.max(...items.map((i) => i.count), 1);\n }\n\n protected barHeightPercent(point: { count: number }, points: { count: number }[]): number {\n if (point.count <= 0) return 0;\n return (point.count / this.maxCount(points)) * 100;\n }\n\n protected maxValue(items: { value: number }[]): number {\n return Math.max(...items.map((i) => i.value), 1);\n }\n\n protected reasonColor(index: number): string {\n return REASON_COLORS[index % REASON_COLORS.length];\n }\n\n protected stuckColor(index: number): string {\n return STUCK_COLORS[index % STUCK_COLORS.length];\n }\n\n protected peakTooltip(point: AnalyticsPeakPoint): string {\n return `${point.label}: ${point.count} call${point.count === 1 ? '' : 's'}`;\n }\n\n protected volumeTooltip(point: AnalyticsVolumePoint): string {\n const inbound = point.inbound ?? Math.round(point.count * 0.8);\n const outbound = point.outbound ?? Math.max(0, point.count - inbound);\n const headline = this.volumePointHeadline(point);\n return `${headline}\\nTotal ${point.count} · In ${inbound} · Out ${outbound}`;\n }\n\n protected hbarTooltip(label: string, value: number): string {\n return `${label}: ${value}%`;\n }\n\n protected distributionTooltip(label: string, value: number): string {\n const calls = this.estimatedCalls(value);\n return `${label}: ${value}% (${calls.toLocaleString()} calls)`;\n }\n\n protected showPeakAxisLabel(index: number, total: number): boolean {\n if (total <= 12) return true;\n const hour = this.data()?.peakHours.points[index]?.hour;\n if (hour != null && [9, 12, 15, 18, 21].includes(hour)) return true;\n const step = total <= 24 ? 3 : 4;\n return index % step === 0 || index === total - 1;\n }\n\n protected volumePath(series: 'total' | 'inbound' | 'outbound'): string {\n const points = this.volumePoints();\n if (!points.length) return '';\n const max = Math.max(\n ...points.map((p) => Math.max(p.count, p.inbound ?? 0, p.outbound ?? 0)),\n 1,\n );\n const w = 100;\n const h = 100;\n const step = points.length === 1 ? 0 : w / (points.length - 1);\n return points\n .map((p, i) => {\n const yVal =\n series === 'inbound'\n ? (p.inbound ?? Math.round(p.count * 0.8))\n : series === 'outbound'\n ? (p.outbound ?? Math.round(p.count * 0.2))\n : p.count;\n const x = i * step;\n const y = h - (yVal / max) * h;\n return `${i === 0 ? 'M' : 'L'}${x.toFixed(2)},${y.toFixed(2)}`;\n })\n .join(' ');\n }\n\n protected volumeAreaPath(): string {\n const line = this.volumePath('total');\n if (!line) return '';\n return `${line} L100,100 L0,100 Z`;\n }\n\n protected volumeAxisLabels(): string[] {\n const points = this.volumePoints();\n return points.map((p, i) => this.volumeAxisLabel(p, i, points.length));\n }\n\n protected volumeAxisLabel(point: AnalyticsVolumePoint, index: number, total: number): string {\n if (/week/i.test(point.day) || /week/i.test(point.label)) {\n const week = point.day || point.label;\n const month = point.date ? this.formatMonthYear(point.date) : '';\n return month ? `${week} · ${month}` : week;\n }\n if (point.date) {\n if (total <= 7) return this.formatChartDate(point.date);\n const step = total <= 14 ? 2 : total <= 31 ? 5 : 7;\n if (index % step !== 0 && index !== total - 1) return '';\n return this.formatChartDate(point.date);\n }\n return point.day || point.label;\n }\n\n protected deltaTone(kpi: AnalyticsKpiCard | null | undefined): string {\n if (!kpi?.delta) return 'neutral';\n if (kpi.tone === 'pos' || kpi.tone === 'warn' || kpi.tone === 'neg') return kpi.tone;\n if (/^\\+/.test(kpi.delta.trim())) return 'pos';\n if (/^-|−/.test(kpi.delta.trim())) return 'warn';\n return 'neutral';\n }\n\n protected formatChartDate(date: string): string {\n const iso = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(date.trim());\n if (iso) return `${iso[2]}/${iso[3]}`;\n return date;\n }\n\n protected estimatedCalls(pct: number): number {\n const total = this.data()?.kpis.totalCalls ?? 0;\n if (!total) return Math.round(pct);\n return Math.round((total * pct) / 100);\n }\n\n protected onVolumeEnter(index: number): void {\n this.hoveredVolume.set(index);\n }\n\n protected onVolumeLeave(): void {\n this.hoveredVolume.set(null);\n }\n\n private volumePointHeadline(point: AnalyticsVolumePoint): string {\n const raw = (point.label || point.day || '').trim();\n if (/week/i.test(raw) && point.date) {\n return `${raw} · ${this.formatMonthYear(point.date)}`;\n }\n if (point.date) return this.formatFullChartDate(point.date);\n return raw || 'Period';\n }\n\n private formatMonthYear(date: string): string {\n const d = this.parseIsoDate(date);\n if (!d) return '';\n return d.toLocaleDateString(undefined, { month: 'short', year: 'numeric' });\n }\n\n private formatFullChartDate(date: string): string {\n const d = this.parseIsoDate(date);\n if (!d) return this.formatChartDate(date);\n return d.toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n }\n\n private formatPeriodRange(start: Date, end: Date): string {\n const sameYear = start.getFullYear() === end.getFullYear();\n const sameMonth = sameYear && start.getMonth() === end.getMonth();\n if (sameMonth) {\n return `${start.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} – ${end.getDate()}, ${end.getFullYear()}`;\n }\n if (sameYear) {\n return `${start.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} – ${end.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}`;\n }\n return `${start.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })} – ${end.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}`;\n }\n\n private parseIsoDate(value: string | null | undefined): Date | null {\n if (!value?.trim()) return null;\n const iso = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(value.trim());\n if (iso) {\n const d = new Date(Number(iso[1]), Number(iso[2]) - 1, Number(iso[3]));\n return Number.isNaN(d.getTime()) ? null : d;\n }\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n\n private findKpi(re: RegExp): AnalyticsKpiCard | null {\n return this.data()?.kpiCards.find((c) => re.test(c.label)) ?? null;\n }\n\n private load(): void {\n this.loading.set(true);\n this.chartsReady.set(false);\n this.hoveredVolume.set(null);\n this.api.getAnalytics(this.range()).subscribe(({ data }) => {\n this.data.set(data);\n this.loading.set(false);\n // Next frame so CSS enter animations run after paint.\n requestAnimationFrame(() => this.chartsReady.set(true));\n });\n }\n}\n","<div class=\"eds-page analytics-page\">\n <header class=\"analytics-page__header\">\n <div>\n <h1 class=\"eds-h1\">Analytics</h1>\n <p class=\"eds-body-sm\">How the AI is doing over time, and what’s worth changing.</p>\n </div>\n <div class=\"analytics-page__actions\">\n <div class=\"analytics-page__range\" role=\"group\" aria-label=\"Date range\">\n @for (r of ranges; track r.id) {\n <button\n type=\"button\"\n class=\"analytics-page__range-btn\"\n [class.analytics-page__range-btn--active]=\"range() === r.id\"\n (click)=\"setRange(r.id)\"\n >\n {{ r.label }}\n </button>\n }\n </div>\n <eds-button variant=\"secondary\" type=\"button\">\n <eds-icon name=\"download\" [size]=\"18\" />\n Export\n </eds-button>\n <button type=\"button\" class=\"analytics-page__icon-btn\" aria-label=\"Filter\">\n <eds-icon name=\"filter_list\" [size]=\"20\" />\n </button>\n </div>\n </header>\n\n @if (loading()) {\n <div class=\"analytics-page__loading\" aria-busy=\"true\" aria-live=\"polite\">\n <div class=\"analytics-page__skeleton analytics-page__skeleton--hero\"></div>\n <div class=\"analytics-page__skeleton-row\">\n <div class=\"analytics-page__skeleton analytics-page__skeleton--stat\"></div>\n <div class=\"analytics-page__skeleton analytics-page__skeleton--stat\"></div>\n <div class=\"analytics-page__skeleton analytics-page__skeleton--stat\"></div>\n <div class=\"analytics-page__skeleton analytics-page__skeleton--stat\"></div>\n </div>\n <div class=\"analytics-page__skeleton-row analytics-page__skeleton-row--charts\">\n <div class=\"analytics-page__skeleton analytics-page__skeleton--chart\"></div>\n <div class=\"analytics-page__skeleton analytics-page__skeleton--chart\"></div>\n </div>\n <p class=\"eds-body-sm analytics-page__loading-label\">Loading analytics…</p>\n </div>\n } @else {\n @if (data(); as d) {\n <div class=\"analytics-page__body\" [class.analytics-page__body--ready]=\"chartsReady()\">\n <section class=\"analytics-page__hero eds-animate-fade-up\" aria-label=\"Key outcomes\">\n <article class=\"analytics-page__impact\">\n <span class=\"analytics-page__impact-label\">\n {{ impactKpi()?.label || 'Estimated Impact This Month' }}\n </span>\n <strong class=\"analytics-page__impact-value\">{{ impactKpi()?.value || '—' }}</strong>\n <p class=\"analytics-page__impact-sub\">\n {{ impactKpi()?.sub || 'of front-desk time handled by the AI' }}\n </p>\n </article>\n\n <article class=\"analytics-page__resolution\">\n <span class=\"eds-label\">{{ resolutionKpi()?.label || 'AI Autonomous Resolution' }}</span>\n <div class=\"analytics-page__resolution-row\">\n <strong class=\"analytics-page__resolution-value\">{{ resolutionKpi()?.value || '—' }}</strong>\n @if (resolutionKpi()?.delta) {\n <span\n class=\"analytics-page__trend\"\n [attr.data-tone]=\"deltaTone(resolutionKpi())\"\n >\n <eds-icon\n [name]=\"deltaTone(resolutionKpi()) === 'pos' ? 'trending_up' : 'trending_down'\"\n [size]=\"16\"\n />\n {{ resolutionKpi()!.delta }}\n </span>\n }\n </div>\n <p class=\"analytics-page__muted\">{{ resolutionKpi()?.sub || 'without staff intervention' }}</p>\n </article>\n\n <article class=\"analytics-page__distribution\">\n <div class=\"analytics-page__distribution-head\">\n <strong class=\"analytics-page__distribution-total\">{{ totalCallsLabel() }}</strong>\n @if (directionSub(); as dir) {\n <span class=\"analytics-page__muted\">{{ dir }}</span>\n }\n </div>\n <div class=\"analytics-page__stack\" role=\"img\" aria-label=\"Call distribution\">\n @for (seg of distribution(); track seg.label) {\n <span\n class=\"analytics-page__stack-seg\"\n [style.width.%]=\"seg.width\"\n [style.background]=\"seg.color\"\n [attr.aria-label]=\"distributionTooltip(seg.label, seg.value)\"\n >\n <span class=\"analytics-page__tooltip\">{{ distributionTooltip(seg.label, seg.value) }}</span>\n </span>\n }\n </div>\n <ul class=\"analytics-page__legend\">\n @for (seg of distribution(); track seg.label) {\n <li>\n <span class=\"analytics-page__swatch\" [style.background]=\"seg.color\"></span>\n <span>\n {{ seg.label }}\n <strong>{{ estimatedCalls(seg.value).toLocaleString() }}</strong>\n calls\n </span>\n <em>{{ seg.value }}%</em>\n </li>\n }\n </ul>\n </article>\n </section>\n\n <section class=\"analytics-page__stats eds-animate-fade-up\" aria-label=\"Activity KPIs\">\n @for (kpi of secondaryKpis(); track kpi.label) {\n <article class=\"analytics-page__stat\">\n <div class=\"analytics-page__stat-top\">\n <span class=\"eds-label\">{{ kpi.label }}</span>\n @if (kpi.delta) {\n <span class=\"analytics-page__trend\" [attr.data-tone]=\"deltaTone(kpi)\">\n <eds-icon\n [name]=\"deltaTone(kpi) === 'pos' ? 'trending_up' : 'trending_down'\"\n [size]=\"14\"\n />\n {{ kpi.delta }}\n </span>\n }\n </div>\n <strong class=\"analytics-page__stat-value\">{{ kpi.value }}</strong>\n @if (kpi.sub) {\n <span class=\"analytics-page__muted\">{{ kpi.sub }}</span>\n }\n </article>\n }\n </section>\n\n <div class=\"analytics-page__charts\">\n <article class=\"eds-card analytics-page__chart eds-animate-fade-up\">\n <div class=\"analytics-page__chart-head\">\n <div class=\"analytics-page__chart-title\">\n <h2 class=\"eds-h2\">Call Volume</h2>\n <span class=\"analytics-page__chart-period\">{{ periodLabel() }}</span>\n </div>\n <ul class=\"analytics-page__line-legend\">\n <li><span class=\"analytics-page__dot analytics-page__dot--total\"></span> Total Calls</li>\n <li><span class=\"analytics-page__dot analytics-page__dot--in\"></span> Inbound Calls</li>\n <li><span class=\"analytics-page__dot analytics-page__dot--out\"></span> Outbound Calls</li>\n </ul>\n </div>\n <div class=\"analytics-page__line-chart\">\n <svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\" aria-hidden=\"true\">\n <path class=\"analytics-page__area\" [attr.d]=\"volumeAreaPath()\" />\n <path class=\"analytics-page__line analytics-page__line--total\" [attr.d]=\"volumePath('total')\" fill=\"none\" />\n <path class=\"analytics-page__line analytics-page__line--in\" [attr.d]=\"volumePath('inbound')\" fill=\"none\" />\n <path class=\"analytics-page__line analytics-page__line--out\" [attr.d]=\"volumePath('outbound')\" fill=\"none\" />\n </svg>\n <div\n class=\"analytics-page__volume-hit\"\n role=\"list\"\n aria-label=\"Call volume by period\"\n (mouseleave)=\"onVolumeLeave()\"\n >\n @for (point of volumePoints(); track point.date || point.label; let i = $index) {\n <button\n type=\"button\"\n class=\"analytics-page__volume-col\"\n role=\"listitem\"\n [class.analytics-page__volume-col--active]=\"hoveredVolume() === i\"\n [attr.aria-label]=\"volumeTooltip(point)\"\n (mouseenter)=\"onVolumeEnter(i)\"\n (focus)=\"onVolumeEnter(i)\"\n (blur)=\"onVolumeLeave()\"\n >\n <span class=\"analytics-page__tooltip analytics-page__tooltip--multiline\">\n {{ volumeTooltip(point) }}\n </span>\n </button>\n }\n </div>\n <div class=\"analytics-page__line-axis\">\n @for (label of volumeAxisLabels(); track $index) {\n <span>{{ label }}</span>\n }\n </div>\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart eds-animate-fade-up\">\n <h2 class=\"eds-h2\">Top Call Reasons</h2>\n <div class=\"analytics-page__hbars\">\n @for (item of d.reasons; track item.label; let i = $index) {\n <div\n class=\"analytics-page__hbar-row\"\n [attr.aria-label]=\"hbarTooltip(item.label, item.value)\"\n >\n <span class=\"analytics-page__hbar-label\">{{ item.label }}</span>\n <div class=\"analytics-page__hbar-track\">\n <span\n class=\"analytics-page__hbar-fill\"\n [style.width.%]=\"(item.value / maxValue(d.reasons)) * 100\"\n [style.background]=\"reasonColor(i)\"\n [style.animation-delay.ms]=\"i * 40\"\n ></span>\n <span class=\"analytics-page__tooltip\">{{ hbarTooltip(item.label, item.value) }}</span>\n </div>\n <span class=\"analytics-page__hbar-pct\">{{ item.value }}%</span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart eds-animate-fade-up\">\n <h2 class=\"eds-h2\">Reasons AI got stuck</h2>\n <div class=\"analytics-page__hbars\">\n @for (item of d.transfers; track item.label; let i = $index) {\n <div\n class=\"analytics-page__hbar-row\"\n [attr.aria-label]=\"hbarTooltip(item.label, item.value)\"\n >\n <span class=\"analytics-page__hbar-label\">{{ item.label }}</span>\n <div class=\"analytics-page__hbar-track\">\n <span\n class=\"analytics-page__hbar-fill\"\n [style.width.%]=\"(item.value / maxValue(d.transfers)) * 100\"\n [style.background]=\"stuckColor(i)\"\n [style.animation-delay.ms]=\"i * 40\"\n ></span>\n <span class=\"analytics-page__tooltip\">{{ hbarTooltip(item.label, item.value) }}</span>\n </div>\n <span class=\"analytics-page__hbar-pct\">{{ item.value }}%</span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart eds-animate-fade-up\">\n <div class=\"analytics-page__chart-head\">\n <h2 class=\"eds-h2\">Peak Hours</h2>\n <span class=\"eds-label\">\n @if (d.peakHours.timezone) {\n {{ d.peakHours.timezone }}\n } @else {\n Calls per hour\n }\n </span>\n </div>\n <div class=\"analytics-page__peak\">\n <div class=\"analytics-page__peak-bars\" role=\"list\" aria-label=\"Call volume by hour\">\n @for (point of d.peakHours.points; track point.hour; let i = $index) {\n <div\n class=\"analytics-page__peak-col\"\n role=\"listitem\"\n [attr.aria-label]=\"peakTooltip(point)\"\n >\n <div class=\"analytics-page__peak-wrap\">\n <div\n class=\"analytics-page__peak-bar\"\n [style.height.%]=\"barHeightPercent(point, d.peakHours.points)\"\n [style.animation-delay.ms]=\"i * 18\"\n ></div>\n <span class=\"analytics-page__tooltip\">{{ peakTooltip(point) }}</span>\n </div>\n </div>\n }\n </div>\n <div class=\"analytics-page__peak-axis\">\n @for (point of d.peakHours.points; track point.hour; let i = $index) {\n <span>\n @if (showPeakAxisLabel(i, d.peakHours.points.length)) {\n {{ point.label }}\n }\n </span>\n }\n </div>\n </div>\n </article>\n </div>\n </div>\n }\n }\n</div>\n"],"names":[],"mappings":"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"}
|