@edvak99/voice-receptionist-widget 1.1.20 → 1.1.21

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.
Files changed (42) hide show
  1. package/fesm2022/{edvak99-voice-receptionist-widget-activities-page.component-CLpNIrcb.mjs → edvak99-voice-receptionist-widget-activities-page.component-CpXa1J9-.mjs} +3 -3
  2. package/fesm2022/{edvak99-voice-receptionist-widget-activities-page.component-CLpNIrcb.mjs.map → edvak99-voice-receptionist-widget-activities-page.component-CpXa1J9-.mjs.map} +1 -1
  3. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-CmImX5FN.mjs +435 -0
  4. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-CmImX5FN.mjs.map +1 -0
  5. package/fesm2022/{edvak99-voice-receptionist-widget-audit-page.component-DwHRYqNe.mjs → edvak99-voice-receptionist-widget-audit-page.component-DTAB2WTX.mjs} +2 -2
  6. package/fesm2022/{edvak99-voice-receptionist-widget-audit-page.component-DwHRYqNe.mjs.map → edvak99-voice-receptionist-widget-audit-page.component-DTAB2WTX.mjs.map} +1 -1
  7. package/fesm2022/{edvak99-voice-receptionist-widget-call-history-detail-page.component-gdOpQsoM.mjs → edvak99-voice-receptionist-widget-call-history-detail-page.component-BT4clUO-.mjs} +4 -4
  8. package/fesm2022/{edvak99-voice-receptionist-widget-call-history-detail-page.component-gdOpQsoM.mjs.map → edvak99-voice-receptionist-widget-call-history-detail-page.component-BT4clUO-.mjs.map} +1 -1
  9. package/fesm2022/{edvak99-voice-receptionist-widget-call-history-page.component-CDOyFwGI.mjs → edvak99-voice-receptionist-widget-call-history-page.component-BACON4uq.mjs} +3 -3
  10. package/fesm2022/{edvak99-voice-receptionist-widget-call-history-page.component-CDOyFwGI.mjs.map → edvak99-voice-receptionist-widget-call-history-page.component-BACON4uq.mjs.map} +1 -1
  11. package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-o0bttL-m.mjs → edvak99-voice-receptionist-widget-call-recording-player.component-4fL-zUWy.mjs} +2 -2
  12. package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-o0bttL-m.mjs.map → edvak99-voice-receptionist-widget-call-recording-player.component-4fL-zUWy.mjs.map} +1 -1
  13. package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-B3y421d_.mjs → edvak99-voice-receptionist-widget-call-review.service-BhbCmB1S.mjs} +2 -2
  14. package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-B3y421d_.mjs.map → edvak99-voice-receptionist-widget-call-review.service-BhbCmB1S.mjs.map} +1 -1
  15. package/fesm2022/{edvak99-voice-receptionist-widget-dashboard-page.component-CWAsroOR.mjs → edvak99-voice-receptionist-widget-dashboard-page.component-CxNYLBbg.mjs} +2 -2
  16. package/fesm2022/{edvak99-voice-receptionist-widget-dashboard-page.component-CWAsroOR.mjs.map → edvak99-voice-receptionist-widget-dashboard-page.component-CxNYLBbg.mjs.map} +1 -1
  17. package/fesm2022/{edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-BEyM4U02.mjs → edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DDS7VdnD.mjs} +26 -19
  18. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DDS7VdnD.mjs.map +1 -0
  19. package/fesm2022/{edvak99-voice-receptionist-widget-live-calls-page.component-BEhbKM1c.mjs → edvak99-voice-receptionist-widget-live-calls-page.component-4LtgT4lS.mjs} +2 -2
  20. package/fesm2022/{edvak99-voice-receptionist-widget-live-calls-page.component-BEhbKM1c.mjs.map → edvak99-voice-receptionist-widget-live-calls-page.component-4LtgT4lS.mjs.map} +1 -1
  21. package/fesm2022/{edvak99-voice-receptionist-widget-live-monitor-page.component-DrqhaEEw.mjs → edvak99-voice-receptionist-widget-live-monitor-page.component-B1lKfw-j.mjs} +8 -29
  22. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-B1lKfw-j.mjs.map +1 -0
  23. package/fesm2022/{edvak99-voice-receptionist-widget-outbound-purposes-page.component-DTqLwtvb.mjs → edvak99-voice-receptionist-widget-outbound-purposes-page.component-BkIHblSu.mjs} +2 -2
  24. package/fesm2022/{edvak99-voice-receptionist-widget-outbound-purposes-page.component-DTqLwtvb.mjs.map → edvak99-voice-receptionist-widget-outbound-purposes-page.component-BkIHblSu.mjs.map} +1 -1
  25. package/fesm2022/{edvak99-voice-receptionist-widget-task-detail-drawer.component-lW-CQ71m.mjs → edvak99-voice-receptionist-widget-task-detail-drawer.component-aDTKTV79.mjs} +2 -2
  26. package/fesm2022/{edvak99-voice-receptionist-widget-task-detail-drawer.component-lW-CQ71m.mjs.map → edvak99-voice-receptionist-widget-task-detail-drawer.component-aDTKTV79.mjs.map} +1 -1
  27. package/fesm2022/{edvak99-voice-receptionist-widget-tasks-page.component-MBm-mLFO.mjs → edvak99-voice-receptionist-widget-tasks-page.component-Dj1nWx6V.mjs} +3 -3
  28. package/fesm2022/{edvak99-voice-receptionist-widget-tasks-page.component-MBm-mLFO.mjs.map → edvak99-voice-receptionist-widget-tasks-page.component-Dj1nWx6V.mjs.map} +1 -1
  29. package/fesm2022/{edvak99-voice-receptionist-widget-wizard-page.component-CgK05X9O.mjs → edvak99-voice-receptionist-widget-wizard-page.component-CQttbEzW.mjs} +2 -2
  30. package/fesm2022/{edvak99-voice-receptionist-widget-wizard-page.component-CgK05X9O.mjs.map → edvak99-voice-receptionist-widget-wizard-page.component-CQttbEzW.mjs.map} +1 -1
  31. package/fesm2022/edvak99-voice-receptionist-widget.mjs +1 -1
  32. package/lib/components/live-transcript-panel/live-transcript-panel.component.d.ts +1 -5
  33. package/lib/models/ai-receptionist.types.d.ts +2 -0
  34. package/lib/pages/analytics-page/analytics-page.component.d.ts +17 -8
  35. package/lib/pages/live-monitor-page/live-monitor-page.component.d.ts +0 -2
  36. package/lib/services/ai-receptionist-api.service.d.ts +1 -0
  37. package/package.json +1 -1
  38. package/styles/embed.scss +2 -1
  39. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-D5ze9OdJ.mjs +0 -416
  40. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-D5ze9OdJ.mjs.map +0 -1
  41. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-BEyM4U02.mjs.map +0 -1
  42. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-DrqhaEEw.mjs.map +0 -1
@@ -0,0 +1,435 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, signal, computed, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { A as AiReceptionistApiService, T as ToastService, o as triggerBlobDownload, I as IconComponent, f as EdButtonComponent, p as EdIconButtonComponent, g as EdSkeletonComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DDS7VdnD.mjs';
4
+
5
+ /** Figma pastel palette for horizontal reason bars */
6
+ const REASON_COLORS = ['#DAB2FF', '#A3B3FF', '#FFA1AD', '#74D4FF', 'rgba(255,32,86,0.2)', '#BBF451', '#FFD230'];
7
+ /** Figma pastel palette for transfer / stuck bars */
8
+ const STUCK_COLORS = ['#DAB2FF', '#A3B3FF', '#74D4FF', '#FFD230'];
9
+ const OUTCOME_TEXT_COLORS = ['#8200DB', '#2162F0', '#DB0C15'];
10
+ const OUTCOME_FALLBACK_COLORS = ['#DAB2FF', '#A3B3FF', '#FFA1AD'];
11
+ class AnalyticsPageComponent {
12
+ constructor() {
13
+ this.api = inject(AiReceptionistApiService);
14
+ this.toast = inject(ToastService);
15
+ this.data = signal(null);
16
+ this.range = signal('30d');
17
+ this.startDate = signal('');
18
+ this.endDate = signal('');
19
+ this.loading = signal(true);
20
+ this.exporting = signal(false);
21
+ this.kpiSkeletonRows = [0, 1, 2, 3];
22
+ this.legendSkeletonRows = [0, 1, 2];
23
+ this.chartRowSkeletonRows = [0, 1, 2, 3, 4];
24
+ this.peakSkeletonRows = [
25
+ 'h-16',
26
+ 'h-24',
27
+ 'h-20',
28
+ 'h-32',
29
+ 'h-28',
30
+ 'h-36',
31
+ 'h-24',
32
+ 'h-40',
33
+ 'h-28',
34
+ 'h-20',
35
+ 'h-16',
36
+ 'h-12',
37
+ ];
38
+ this.chartsReady = signal(false);
39
+ this.hoveredVolume = signal(null);
40
+ this.filtersOpen = signal(false);
41
+ this.ranges = [
42
+ { id: '7d', label: '7 days' },
43
+ { id: '30d', label: '30 days' },
44
+ { id: '90d', label: '90 days' },
45
+ ];
46
+ this.impactKpi = computed(() => this.findKpi(/(impact|hours|front-desk|time saved)/i));
47
+ this.resolutionKpi = computed(() => this.findKpi(/(autonomous|resolution)/i));
48
+ this.impactSubPrimary = computed(() => {
49
+ const sub = this.impactKpi()?.sub || 'of front-desk time handled by the AI';
50
+ const tilde = sub.indexOf('~');
51
+ if (tilde === -1)
52
+ return sub;
53
+ return sub.slice(0, tilde).trim();
54
+ });
55
+ this.impactSubSecondary = computed(() => {
56
+ const sub = this.impactKpi()?.sub || '';
57
+ const tilde = sub.indexOf('~');
58
+ if (tilde === -1)
59
+ return null;
60
+ return sub.slice(tilde).trim();
61
+ });
62
+ this.resolutionTrend = computed(() => {
63
+ const kpi = this.resolutionKpi();
64
+ if (!kpi?.delta)
65
+ return null;
66
+ const tone = this.deltaTone(kpi);
67
+ return {
68
+ delta: kpi.delta.replace(/^[+\s]+/, ''),
69
+ tone,
70
+ icon: tone === 'pos' ? 'growth_chart_trend_up' : 'growth_chart_trend_down',
71
+ };
72
+ });
73
+ this.secondaryKpis = computed(() => {
74
+ const cards = this.data()?.kpiCards ?? [];
75
+ const preferred = [
76
+ /appointment/i,
77
+ /task/i,
78
+ /patient|registr/i,
79
+ /calls?\s*handled|total\s*calls|^calls$/i,
80
+ ];
81
+ const picked = [];
82
+ for (const re of preferred) {
83
+ const hit = cards.find((c) => re.test(c.label) && !picked.includes(c));
84
+ if (hit)
85
+ picked.push(hit);
86
+ }
87
+ const list = picked.length >= 4
88
+ ? picked.slice(0, 4)
89
+ : [
90
+ ...picked,
91
+ ...cards.filter((c) => !picked.includes(c) &&
92
+ !/(impact|hours|front-desk|autonomous|resolution)/i.test(c.label)),
93
+ ].slice(0, 4);
94
+ return list.map((kpi) => {
95
+ const tone = this.deltaTone(kpi);
96
+ return {
97
+ label: kpi.label,
98
+ value: kpi.value,
99
+ sub: kpi.sub,
100
+ delta: kpi.delta ? kpi.delta.replace(/^[+\s]+/, '') : undefined,
101
+ tone,
102
+ trendIcon: tone === 'pos' ? 'growth_chart_trend_up' : 'growth_chart_trend_down',
103
+ };
104
+ });
105
+ });
106
+ this.distribution = computed(() => {
107
+ const outcomes = this.data()?.outcomes ?? [];
108
+ const total = outcomes.reduce((s, o) => s + o.value, 0) || 1;
109
+ return outcomes.map((o, i) => {
110
+ const width = (o.value / total) * 100;
111
+ const calls = this.estimatedCalls(o.value);
112
+ return {
113
+ ...o,
114
+ color: o.color || OUTCOME_FALLBACK_COLORS[i % OUTCOME_FALLBACK_COLORS.length],
115
+ textColor: OUTCOME_TEXT_COLORS[i % OUTCOME_TEXT_COLORS.length],
116
+ pct: Math.round(width),
117
+ width,
118
+ tooltip: `${o.label}: ${o.value}% (${calls.toLocaleString()} calls)`,
119
+ callsText: `${calls.toLocaleString()} calls`,
120
+ };
121
+ });
122
+ });
123
+ this.totalCallsLabel = computed(() => {
124
+ const handled = this.findKpi(/calls?\s*handled|total\s*calls|^calls$/i);
125
+ if (handled?.value)
126
+ return handled.value.includes('Call') ? handled.value : `${handled.value} Calls`;
127
+ const n = this.data()?.kpis.totalCalls;
128
+ return n ? `${n.toLocaleString()} Calls` : 'Calls';
129
+ });
130
+ this.directionSub = computed(() => {
131
+ const handled = this.findKpi(/calls?\s*handled|total\s*calls|^calls$/i);
132
+ if (handled?.sub && /inbound|outbound/i.test(handled.sub))
133
+ return handled.sub;
134
+ return null;
135
+ });
136
+ this.volumePoints = computed(() => this.data()?.volume.points ?? []);
137
+ this.volumeTotalPath = computed(() => this.buildVolumePath('total'));
138
+ this.volumeInboundPath = computed(() => this.buildVolumePath('inbound'));
139
+ this.volumeOutboundPath = computed(() => this.buildVolumePath('outbound'));
140
+ this.volumeAreaPath = computed(() => {
141
+ const line = this.volumeTotalPath();
142
+ if (!line)
143
+ return '';
144
+ return `${line} L100,100 L0,100 Z`;
145
+ });
146
+ this.volumePointVms = computed(() => this.volumePoints().map((point) => ({
147
+ trackKey: point.date || point.label,
148
+ tooltip: this.volumeTooltip(point),
149
+ })));
150
+ this.reasonRows = computed(() => {
151
+ const items = this.data()?.reasons ?? [];
152
+ const max = Math.max(...items.map((i) => i.value), 1);
153
+ return items.map((item, i) => ({
154
+ label: item.label,
155
+ value: item.value,
156
+ widthPct: (item.value / max) * 100,
157
+ color: REASON_COLORS[i % REASON_COLORS.length],
158
+ tooltip: `${item.label}: ${item.value}%`,
159
+ delayMs: i * 40,
160
+ }));
161
+ });
162
+ this.transferRows = computed(() => {
163
+ const items = this.data()?.transfers ?? [];
164
+ const max = Math.max(...items.map((i) => i.value), 1);
165
+ return items.map((item, i) => ({
166
+ label: item.label,
167
+ value: item.value,
168
+ widthPct: (item.value / max) * 100,
169
+ color: STUCK_COLORS[i % STUCK_COLORS.length],
170
+ tooltip: `${item.label}: ${item.value}%`,
171
+ delayMs: i * 40,
172
+ }));
173
+ });
174
+ this.peakRows = computed(() => {
175
+ const points = this.data()?.peakHours.points ?? [];
176
+ const max = Math.max(...points.map((p) => p.count), 1);
177
+ return points.map((point, i) => ({
178
+ hour: point.hour,
179
+ label: point.label,
180
+ heightPct: point.count <= 0 ? 0 : (point.count / max) * 100,
181
+ tooltip: `${point.label}: ${point.count} call${point.count === 1 ? '' : 's'}`,
182
+ showLabel: this.showPeakAxisLabel(i, points.length),
183
+ delayMs: i * 18,
184
+ }));
185
+ });
186
+ /** Human date span for the selected analytics window (month + year included). */
187
+ this.periodLabel = computed(() => {
188
+ const customStart = this.parseIsoDate(this.startDate());
189
+ const customEnd = this.parseIsoDate(this.endDate());
190
+ if (customStart && customEnd)
191
+ return this.formatPeriodRange(customStart, customEnd);
192
+ const vol = this.data()?.volume;
193
+ const start = this.parseIsoDate(vol?.startDate) ?? this.parseIsoDate(vol?.points?.[0]?.date) ?? null;
194
+ const end = this.parseIsoDate(vol?.endDate) ??
195
+ this.parseIsoDate(vol?.points?.[vol.points.length - 1]?.date) ??
196
+ null;
197
+ if (start && end)
198
+ return this.formatPeriodRange(start, end);
199
+ const days = this.range() === '7d' ? 7 : this.range() === '90d' ? 90 : 30;
200
+ const endD = new Date();
201
+ const startD = new Date();
202
+ startD.setDate(endD.getDate() - (days - 1));
203
+ return this.formatPeriodRange(startD, endD);
204
+ });
205
+ this.volumeAxisLabels = computed(() => {
206
+ const points = this.volumePoints();
207
+ return points.map((p, i) => this.volumeAxisLabel(p, i, points.length));
208
+ });
209
+ }
210
+ ngOnInit() {
211
+ this.applyPresetDates(this.range());
212
+ this.load();
213
+ }
214
+ toggleFilters(event) {
215
+ event?.stopPropagation();
216
+ this.filtersOpen.update((open) => !open);
217
+ }
218
+ closeFilters() {
219
+ this.filtersOpen.set(false);
220
+ }
221
+ onFiltersDocClick(event) {
222
+ const target = event.target;
223
+ if (!target?.closest('[data-analytics-filters]')) {
224
+ this.filtersOpen.set(false);
225
+ }
226
+ }
227
+ setRange(id) {
228
+ this.range.set(id);
229
+ this.applyPresetDates(id);
230
+ }
231
+ onStartDate(value) {
232
+ this.startDate.set(value);
233
+ this.range.set('custom');
234
+ }
235
+ onEndDate(value) {
236
+ this.endDate.set(value);
237
+ this.range.set('custom');
238
+ }
239
+ applyFilters() {
240
+ this.load();
241
+ }
242
+ resetFilters() {
243
+ this.range.set('30d');
244
+ this.applyPresetDates('30d');
245
+ this.load();
246
+ }
247
+ exportCsv() {
248
+ if (this.exporting())
249
+ return;
250
+ this.exporting.set(true);
251
+ this.api.exportAnalyticsCsv(this.activeFilters()).subscribe({
252
+ next: ({ blob, filename }) => {
253
+ triggerBlobDownload(blob, filename);
254
+ this.exporting.set(false);
255
+ this.toast.show('Export downloaded', 'success');
256
+ },
257
+ error: (err) => {
258
+ this.exporting.set(false);
259
+ this.toast.show(err.message ?? 'Failed to export CSV', 'error');
260
+ },
261
+ });
262
+ }
263
+ onVolumeEnter(index) {
264
+ this.hoveredVolume.set(index);
265
+ }
266
+ onVolumeLeave() {
267
+ this.hoveredVolume.set(null);
268
+ }
269
+ buildVolumePath(series) {
270
+ const points = this.volumePoints();
271
+ if (!points.length)
272
+ return '';
273
+ const max = Math.max(...points.map((p) => Math.max(p.count, p.inbound ?? 0, p.outbound ?? 0)), 1);
274
+ const w = 100;
275
+ const h = 100;
276
+ const step = points.length === 1 ? 0 : w / (points.length - 1);
277
+ return points
278
+ .map((p, i) => {
279
+ const yVal = series === 'inbound'
280
+ ? (p.inbound ?? Math.round(p.count * 0.8))
281
+ : series === 'outbound'
282
+ ? (p.outbound ?? Math.round(p.count * 0.2))
283
+ : p.count;
284
+ const x = i * step;
285
+ const y = h - (yVal / max) * h;
286
+ return `${i === 0 ? 'M' : 'L'}${x.toFixed(2)},${y.toFixed(2)}`;
287
+ })
288
+ .join(' ');
289
+ }
290
+ volumeTooltip(point) {
291
+ const inbound = point.inbound ?? Math.round(point.count * 0.8);
292
+ const outbound = point.outbound ?? Math.max(0, point.count - inbound);
293
+ const headline = this.volumePointHeadline(point);
294
+ return `${headline}\nTotal ${point.count} · In ${inbound} · Out ${outbound}`;
295
+ }
296
+ showPeakAxisLabel(index, total) {
297
+ if (total <= 12)
298
+ return true;
299
+ const hour = this.data()?.peakHours.points[index]?.hour;
300
+ if (hour != null && [9, 12, 15, 18, 21].includes(hour))
301
+ return true;
302
+ const step = total <= 24 ? 3 : 4;
303
+ return index % step === 0 || index === total - 1;
304
+ }
305
+ volumeAxisLabel(point, index, total) {
306
+ if (/week/i.test(point.day) || /week/i.test(point.label)) {
307
+ const week = point.day || point.label;
308
+ const month = point.date ? this.formatMonthYear(point.date) : '';
309
+ return month ? `${week} · ${month}` : week;
310
+ }
311
+ if (point.date) {
312
+ if (total <= 7)
313
+ return this.formatChartDate(point.date);
314
+ const step = total <= 14 ? 2 : total <= 31 ? 5 : 7;
315
+ if (index % step !== 0 && index !== total - 1)
316
+ return '';
317
+ return this.formatChartDate(point.date);
318
+ }
319
+ return point.day || point.label;
320
+ }
321
+ deltaTone(kpi) {
322
+ if (!kpi?.delta)
323
+ return 'neutral';
324
+ if (kpi.tone === 'pos' || kpi.tone === 'warn' || kpi.tone === 'neg')
325
+ return kpi.tone;
326
+ if (/^\+/.test(kpi.delta.trim()))
327
+ return 'pos';
328
+ if (/^-|−/.test(kpi.delta.trim()))
329
+ return 'warn';
330
+ return 'neutral';
331
+ }
332
+ formatChartDate(date) {
333
+ const iso = /^(\d{4})-(\d{2})-(\d{2})/.exec(date.trim());
334
+ if (iso)
335
+ return `${iso[2]}/${iso[3]}`;
336
+ return date;
337
+ }
338
+ estimatedCalls(pct) {
339
+ const total = this.data()?.kpis.totalCalls ?? 0;
340
+ if (!total)
341
+ return Math.round(pct);
342
+ return Math.round((total * pct) / 100);
343
+ }
344
+ volumePointHeadline(point) {
345
+ const raw = (point.label || point.day || '').trim();
346
+ if (/week/i.test(raw) && point.date) {
347
+ return `${raw} · ${this.formatMonthYear(point.date)}`;
348
+ }
349
+ if (point.date)
350
+ return this.formatFullChartDate(point.date);
351
+ return raw || 'Period';
352
+ }
353
+ formatMonthYear(date) {
354
+ const d = this.parseIsoDate(date);
355
+ if (!d)
356
+ return '';
357
+ return d.toLocaleDateString(undefined, { month: 'short', year: 'numeric' });
358
+ }
359
+ formatFullChartDate(date) {
360
+ const d = this.parseIsoDate(date);
361
+ if (!d)
362
+ return this.formatChartDate(date);
363
+ return d.toLocaleDateString(undefined, {
364
+ month: 'short',
365
+ day: 'numeric',
366
+ year: 'numeric',
367
+ });
368
+ }
369
+ formatPeriodRange(start, end) {
370
+ const sameYear = start.getFullYear() === end.getFullYear();
371
+ const sameMonth = sameYear && start.getMonth() === end.getMonth();
372
+ if (sameMonth) {
373
+ return `${start.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} – ${end.getDate()}, ${end.getFullYear()}`;
374
+ }
375
+ if (sameYear) {
376
+ return `${start.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} – ${end.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}`;
377
+ }
378
+ return `${start.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })} – ${end.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}`;
379
+ }
380
+ parseIsoDate(value) {
381
+ if (!value?.trim())
382
+ return null;
383
+ const iso = /^(\d{4})-(\d{2})-(\d{2})/.exec(value.trim());
384
+ if (iso) {
385
+ const d = new Date(Number(iso[1]), Number(iso[2]) - 1, Number(iso[3]));
386
+ return Number.isNaN(d.getTime()) ? null : d;
387
+ }
388
+ const d = new Date(value);
389
+ return Number.isNaN(d.getTime()) ? null : d;
390
+ }
391
+ findKpi(re) {
392
+ return this.data()?.kpiCards.find((c) => re.test(c.label)) ?? null;
393
+ }
394
+ activeFilters() {
395
+ return {
396
+ range: this.range(),
397
+ startDate: this.startDate() || undefined,
398
+ endDate: this.endDate() || undefined,
399
+ };
400
+ }
401
+ applyPresetDates(id) {
402
+ const days = id === '7d' ? 7 : id === '90d' ? 90 : 30;
403
+ const end = new Date();
404
+ const start = new Date();
405
+ start.setDate(end.getDate() - (days - 1));
406
+ this.startDate.set(this.toIsoDate(start));
407
+ this.endDate.set(this.toIsoDate(end));
408
+ }
409
+ toIsoDate(d) {
410
+ const y = d.getFullYear();
411
+ const m = String(d.getMonth() + 1).padStart(2, '0');
412
+ const day = String(d.getDate()).padStart(2, '0');
413
+ return `${y}-${m}-${day}`;
414
+ }
415
+ load() {
416
+ this.loading.set(true);
417
+ this.chartsReady.set(false);
418
+ this.hoveredVolume.set(null);
419
+ this.api.getAnalytics(this.activeFilters()).subscribe(({ data }) => {
420
+ this.data.set(data);
421
+ this.loading.set(false);
422
+ // Next frame so CSS enter animations run after paint.
423
+ requestAnimationFrame(() => this.chartsReady.set(true));
424
+ });
425
+ }
426
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AnalyticsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
427
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: AnalyticsPageComponent, isStandalone: true, selector: "eds-analytics-page", host: { classAttribute: "block h-full min-h-0" }, ngImport: i0, template: "<div class=\"flex h-full min-h-0 flex-col overflow-hidden bg-lighter p-base analytics-page\">\n <header class=\"mb-md flex shrink-0 items-start justify-between gap-base\">\n <div class=\"flex flex-col gap-2xs\">\n <h4>Analytics</h4>\n <p class=\"fs-sm text-light\">Call volume, outcomes, and agent performance</p>\n </div>\n <div class=\"flex shrink-0 items-center gap-base\">\n <ed-button\n variant=\"default\"\n outline\n size=\"sm\"\n [disabled]=\"exporting()\"\n (click$)=\"exportCsv()\"\n >\n <ed-icon slot=\"prefix\" name=\"download\" [size]=\"16\" />\n Export\n </ed-button>\n <div\n class=\"relative\"\n data-analytics-filters\n (document:click)=\"onFiltersDocClick($event)\"\n >\n <ed-icon-button\n name=\"filter_alt\"\n label=\"Filters\"\n size=\"md\"\n outline\n allowBubble\n [pressed]=\"filtersOpen()\"\n toggle\n (click$)=\"toggleFilters($event)\"\n />\n @if (filtersOpen()) {\n <div\n class=\"absolute right-0 top-[calc(100%+4px)] z-20 w-[22rem] rounded-md border border-light bg-white shadow-md\"\n role=\"dialog\"\n aria-label=\"Analytics Filter\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"flex items-center justify-between border-b border-light px-base py-sm\">\n <h5 class=\"m-0\">Analytics Filter</h5>\n <ed-icon-button\n name=\"close\"\n size=\"sm\"\n label=\"Close filters\"\n (click$)=\"closeFilters()\"\n />\n </header>\n <section class=\"grid gap-base p-base\">\n <div class=\"grid gap-2xs\">\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-light\">Range</span>\n <div class=\"analytics-page__range-row\" role=\"group\" aria-label=\"Preset 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 </div>\n <div class=\"grid grid-cols-2 gap-sm\">\n <div class=\"grid gap-2xs\">\n <label class=\"fs-sm font-semibold text-light\" for=\"analytics-start-date\">From</label>\n <input\n id=\"analytics-start-date\"\n class=\"form-input is-sm w-full\"\n type=\"date\"\n [value]=\"startDate()\"\n [max]=\"endDate() || undefined\"\n (input)=\"onStartDate($any($event.target).value)\"\n />\n </div>\n <div class=\"grid gap-2xs\">\n <label class=\"fs-sm font-semibold text-light\" for=\"analytics-end-date\">To</label>\n <input\n id=\"analytics-end-date\"\n class=\"form-input is-sm w-full\"\n type=\"date\"\n [value]=\"endDate()\"\n [min]=\"startDate() || undefined\"\n (input)=\"onEndDate($any($event.target).value)\"\n />\n </div>\n </div>\n </section>\n <footer class=\"flex flex-row-reverse flex-wrap items-center gap-sm border-t border-light px-base py-sm\">\n <ed-button\n variant=\"primary\"\n fill\n size=\"sm\"\n (click$)=\"applyFilters(); closeFilters()\"\n >\n Apply\n </ed-button>\n <ed-button\n variant=\"secondary\"\n outline\n size=\"sm\"\n (click$)=\"resetFilters()\"\n >\n Reset\n </ed-button>\n </footer>\n </div>\n }\n </div>\n </div>\n </header>\n\n <div class=\"flex min-h-0 flex-1 flex-col gap-md overflow-y-auto\">\n @if (loading()) {\n <div class=\"flex flex-col gap-base\" aria-busy=\"true\" aria-live=\"polite\">\n <section class=\"flex flex-col gap-base lg:flex-row\" aria-label=\"Key outcomes\">\n <article class=\"flex min-h-36 shrink-0 flex-col gap-sm rounded-md border border-light bg-white p-base lg:w-72\">\n <ed-skeleton class=\"h-3 w-40\"></ed-skeleton>\n <ed-skeleton class=\"h-7 w-28\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-48\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-36\"></ed-skeleton>\n </article>\n <article class=\"flex min-w-0 flex-1 flex-col gap-base overflow-hidden rounded-lg border border-light bg-white p-base lg:flex-row lg:items-center\">\n <div class=\"flex w-full shrink-0 flex-col gap-sm lg:w-52\">\n <ed-skeleton class=\"h-3 w-36\"></ed-skeleton>\n <ed-skeleton class=\"h-7 w-20\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-40\"></ed-skeleton>\n </div>\n <div class=\"flex min-w-0 flex-1 flex-col gap-sm\">\n <div class=\"flex items-center justify-between gap-sm\">\n <ed-skeleton class=\"h-3 w-28\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-24\"></ed-skeleton>\n </div>\n <ed-skeleton class=\"h-8 w-full !rounded-md\"></ed-skeleton>\n <div class=\"flex flex-wrap gap-md\">\n @for (_ of legendSkeletonRows; track _) {\n <div class=\"inline-flex items-center gap-xs\">\n <ed-skeleton class=\"h-4 w-4 !rounded-sm\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-20\"></ed-skeleton>\n </div>\n }\n </div>\n </div>\n </article>\n </section>\n\n <section class=\"grid grid-cols-1 gap-base sm:grid-cols-2 xl:grid-cols-4\" aria-label=\"Activity KPIs\">\n @for (_ of kpiSkeletonRows; track _) {\n <article class=\"flex flex-col gap-2xs rounded-md border border-light bg-white p-base\">\n <ed-skeleton class=\"h-3 w-28\"></ed-skeleton>\n <ed-skeleton class=\"h-6 w-14\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-24\"></ed-skeleton>\n </article>\n }\n </section>\n\n <div class=\"grid grid-cols-1 gap-base md:grid-cols-2\">\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg border border-light bg-white p-base\">\n <div class=\"flex flex-wrap items-center justify-between gap-base\">\n <h3>Call Volume</h3>\n <div class=\"flex flex-wrap gap-md\">\n @for (_ of legendSkeletonRows; track _) {\n <ed-skeleton class=\"h-3 w-20\"></ed-skeleton>\n }\n </div>\n </div>\n <ed-skeleton class=\"h-60 w-full !rounded-md\"></ed-skeleton>\n </article>\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg border border-light bg-white p-base\">\n <h3>Top Call Reasons</h3>\n <div class=\"flex flex-col gap-md\">\n @for (_ of chartRowSkeletonRows; track _) {\n <div class=\"grid grid-cols-[minmax(110px,140px)_1fr_42px] items-center gap-sm\">\n <ed-skeleton class=\"h-3 w-20 justify-self-end\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-full !rounded-r-xl\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-8 justify-self-end\"></ed-skeleton>\n </div>\n }\n </div>\n </article>\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg border border-light bg-white p-base\">\n <h3>Reasons AI got stuck</h3>\n <div class=\"flex flex-col gap-md\">\n @for (_ of chartRowSkeletonRows; track _) {\n <div class=\"grid grid-cols-[minmax(110px,140px)_1fr_42px] items-center gap-sm\">\n <ed-skeleton class=\"h-3 w-20 justify-self-end\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-full !rounded-r-xl\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-8 justify-self-end\"></ed-skeleton>\n </div>\n }\n </div>\n </article>\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg border border-light bg-white p-base\">\n <h3>Peak Hours</h3>\n <div class=\"flex h-40 items-end gap-xs\">\n @for (heightClass of peakSkeletonRows; track $index) {\n <ed-skeleton [class]=\"'min-w-0 flex-1 !rounded-t-full ' + heightClass\"></ed-skeleton>\n }\n </div>\n </article>\n </div>\n </div>\n } @else {\n @if (data(); as d) {\n <div class=\"analytics-page__body flex flex-col gap-base\" [class.analytics-page__body--ready]=\"chartsReady()\">\n <section class=\"flex flex-col gap-base lg:flex-row\" aria-label=\"Key outcomes\">\n <article\n class=\"analytics-page__impact flex min-h-36 shrink-0 flex-col gap-sm rounded-md p-base text-white lg:w-72\"\n >\n <div class=\"flex items-center gap-2xs\">\n <ed-icon name=\"lightning\" [size]=\"12\" color=\"currentColor\" />\n <span class=\"fs-xs font-semibold uppercase tracking-wide opacity-90\">\n {{ impactKpi()?.label || 'Estimated Impact This Month' }}\n </span>\n </div>\n <strong class=\"fs-lg font-semibold leading-tight\">\n {{ impactKpi()?.value || '\u2014' }}\n </strong>\n <div class=\"flex flex-col gap-2xs opacity-90\">\n <p class=\"m-0 fs-sm\">{{ impactSubPrimary() }}</p>\n @if (impactSubSecondary(); as secondary) {\n <p class=\"m-0 fs-xs opacity-90\">{{ secondary }}</p>\n }\n </div>\n </article>\n\n <article\n class=\"analytics-page__card flex min-w-0 flex-1 flex-col gap-base overflow-hidden rounded-lg bg-white p-base lg:flex-row lg:items-center\"\n >\n <div class=\"flex w-full shrink-0 flex-col gap-sm lg:w-52\">\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-light\">\n {{ resolutionKpi()?.label || 'AI Autonomous Resolution' }}\n </span>\n <strong class=\"fs-lg font-semibold text-dark\">\n {{ resolutionKpi()?.value || '\u2014' }}\n </strong>\n <div class=\"flex flex-wrap items-center gap-xs\">\n <span class=\"fs-sm text-body\">\n {{ resolutionKpi()?.sub || 'without staff intervention' }}\n </span>\n @if (resolutionTrend(); as trend) {\n <span class=\"analytics-page__divider\" aria-hidden=\"true\"></span>\n <span\n class=\"analytics-page__trend inline-flex items-center gap-2xs\"\n [attr.data-tone]=\"trend.tone\"\n >\n <ed-icon [name]=\"trend.icon\" [size]=\"12\" color=\"currentColor\" />\n {{ trend.delta }}\n </span>\n }\n </div>\n </div>\n\n <div class=\"analytics-page__v-divider hidden lg:block\" aria-hidden=\"true\"></div>\n\n <div class=\"analytics-page__distribution flex min-w-0 flex-1 flex-col gap-sm\">\n <div class=\"flex flex-wrap items-center justify-between gap-sm\">\n <strong class=\"fs-sm font-semibold text-body\">{{ totalCallsLabel() }}</strong>\n @if (directionSub(); as dir) {\n <span class=\"fs-sm text-body\">{{ dir }}</span>\n }\n </div>\n <div\n class=\"analytics-page__stack flex h-8 gap-2xs overflow-hidden rounded-md\"\n role=\"img\"\n aria-label=\"Call distribution\"\n >\n @for (seg of distribution(); track seg.label) {\n <span\n class=\"analytics-page__stack-seg relative flex min-w-0 items-center justify-center\"\n [style.flex-grow]=\"seg.width\"\n [style.background]=\"seg.color\"\n [attr.aria-label]=\"seg.tooltip\"\n >\n @if (seg.width >= 8) {\n <span\n class=\"analytics-page__stack-pct fs-xs font-semibold uppercase tracking-wide\"\n [style.color]=\"seg.textColor\"\n >\n {{ seg.value }}%\n </span>\n }\n <span class=\"analytics-page__tooltip\">{{ seg.tooltip }}</span>\n </span>\n }\n </div>\n <ul class=\"m-0 flex list-none flex-wrap gap-x-md gap-y-xs p-0\">\n @for (seg of distribution(); track seg.label) {\n <li class=\"inline-flex items-center gap-xs\">\n <span\n class=\"h-4 w-4 shrink-0 rounded-sm\"\n [style.background]=\"seg.color\"\n ></span>\n <span class=\"inline-flex items-center gap-2xs\">\n <strong class=\"fs-sm font-semibold text-body\">{{ seg.label }}</strong>\n <span class=\"fs-xs text-light\">{{ seg.callsText }}</span>\n </span>\n </li>\n }\n </ul>\n </div>\n </article>\n </section>\n\n <section\n class=\"grid grid-cols-1 gap-base sm:grid-cols-2 xl:grid-cols-4\"\n aria-label=\"Activity KPIs\"\n >\n @for (kpi of secondaryKpis(); track kpi.label) {\n <article class=\"analytics-page__card flex flex-col gap-2xs rounded-md bg-white p-base\">\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-light\">{{ kpi.label }}</span>\n <strong class=\"font-semibold text-dark\">{{ kpi.value }}</strong>\n <div class=\"flex flex-wrap items-center gap-xs\">\n @if (kpi.sub) {\n <span class=\"fs-sm text-body\">{{ kpi.sub }}</span>\n }\n @if (kpi.delta) {\n <span class=\"analytics-page__divider\" aria-hidden=\"true\"></span>\n <span\n class=\"analytics-page__trend inline-flex items-center gap-2xs\"\n [attr.data-tone]=\"kpi.tone\"\n >\n <ed-icon [name]=\"kpi.trendIcon\" [size]=\"12\" color=\"currentColor\" />\n {{ kpi.delta }}\n </span>\n }\n </div>\n </article>\n }\n </section>\n\n <div class=\"grid grid-cols-1 gap-base md:grid-cols-2\">\n <article class=\"flex flex-col gap-md overflow-hidden rounded-lg bg-white p-base analytics-page__chart\">\n <div class=\"flex flex-wrap items-center justify-between gap-base\">\n <h3>Call Volume</h3>\n <ul class=\"m-0 flex list-none flex-wrap items-center justify-end gap-md p-0\">\n <li class=\"inline-flex items-center gap-2xs\">\n <span class=\"analytics-page__dot analytics-page__dot--total h-2 w-2 rounded-full\"></span>\n <span class=\"fs-sm font-semibold text-body\">Total Calls</span>\n </li>\n <li class=\"inline-flex items-center gap-2xs\">\n <span class=\"analytics-page__dot analytics-page__dot--in h-2 w-2 rounded-full\"></span>\n <span class=\"fs-sm font-semibold text-body\">Inbound Calls</span>\n </li>\n <li class=\"inline-flex items-center gap-2xs\">\n <span class=\"analytics-page__dot analytics-page__dot--out h-2 w-2 rounded-full\"></span>\n <span class=\"fs-sm font-semibold text-body\">Outbound Calls</span>\n </li>\n </ul>\n </div>\n <div class=\"analytics-page__line-chart relative h-60\">\n <svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\" aria-hidden=\"true\">\n <path class=\"analytics-page__area\" [attr.d]=\"volumeAreaPath()\" />\n <path\n class=\"analytics-page__line analytics-page__line--total\"\n [attr.d]=\"volumeTotalPath()\"\n fill=\"none\"\n />\n <path\n class=\"analytics-page__line analytics-page__line--in\"\n [attr.d]=\"volumeInboundPath()\"\n fill=\"none\"\n />\n <path\n class=\"analytics-page__line analytics-page__line--out\"\n [attr.d]=\"volumeOutboundPath()\"\n fill=\"none\"\n />\n </svg>\n <div\n class=\"analytics-page__volume-hit flex\"\n role=\"list\"\n aria-label=\"Call volume by period\"\n (mouseleave)=\"onVolumeLeave()\"\n >\n @for (point of volumePointVms(); track point.trackKey; let i = $index) {\n <button\n type=\"button\"\n class=\"analytics-page__volume-col relative min-w-0 flex-1 cursor-default border-none bg-transparent p-0\"\n role=\"listitem\"\n [class.analytics-page__volume-col--active]=\"hoveredVolume() === i\"\n [attr.aria-label]=\"point.tooltip\"\n (mouseenter)=\"onVolumeEnter(i)\"\n (focus)=\"onVolumeEnter(i)\"\n (blur)=\"onVolumeLeave()\"\n >\n <span class=\"analytics-page__tooltip analytics-page__tooltip--multiline\">\n {{ point.tooltip }}\n </span>\n </button>\n }\n </div>\n <div class=\"mt-xs flex justify-between gap-2xs\">\n @for (label of volumeAxisLabels(); track $index) {\n <span class=\"flex-1 text-center fs-xs font-semibold uppercase tracking-wide text-light\">\n {{ label }}\n </span>\n }\n </div>\n </div>\n </article>\n\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg bg-white p-base analytics-page__chart\">\n <h3>Top Call Reasons</h3>\n <div class=\"flex flex-col gap-md\">\n @for (item of reasonRows(); track item.label) {\n <div\n class=\"analytics-page__hbar-row grid items-center gap-sm\"\n [attr.aria-label]=\"item.tooltip\"\n >\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-light text-right\">\n {{ item.label }}\n </span>\n <div class=\"analytics-page__hbar-track relative h-3 overflow-hidden rounded-r-xl\">\n <span\n class=\"analytics-page__hbar-fill block h-full min-w-1 rounded-r-xl\"\n [style.width.%]=\"item.widthPct\"\n [style.background]=\"item.color\"\n [style.animation-delay.ms]=\"item.delayMs\"\n ></span>\n <span class=\"analytics-page__tooltip\">{{ item.tooltip }}</span>\n </div>\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-dark text-right\">\n {{ item.value }}%\n </span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg bg-white p-base analytics-page__chart\">\n <h3>Reasons AI got stuck</h3>\n <div class=\"flex flex-col gap-md\">\n @for (item of transferRows(); track item.label) {\n <div\n class=\"analytics-page__hbar-row grid items-center gap-sm\"\n [attr.aria-label]=\"item.tooltip\"\n >\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-light text-right\">\n {{ item.label }}\n </span>\n <div class=\"analytics-page__hbar-track relative h-3 overflow-hidden rounded-r-xl\">\n <span\n class=\"analytics-page__hbar-fill block h-full min-w-1 rounded-r-xl\"\n [style.width.%]=\"item.widthPct\"\n [style.background]=\"item.color\"\n [style.animation-delay.ms]=\"item.delayMs\"\n ></span>\n <span class=\"analytics-page__tooltip\">{{ item.tooltip }}</span>\n </div>\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-dark text-right\">\n {{ item.value }}%\n </span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"flex flex-col gap-md overflow-hidden rounded-lg bg-white p-base analytics-page__chart\">\n <h3>Peak Hours</h3>\n <div class=\"flex flex-col gap-xs\">\n <div class=\"flex h-40 items-end gap-xs\" role=\"list\" aria-label=\"Call volume by hour\">\n @for (point of peakRows(); track point.hour) {\n <div\n class=\"analytics-page__peak-col flex h-full min-w-0 flex-1 flex-col\"\n role=\"listitem\"\n [attr.aria-label]=\"point.tooltip\"\n >\n <div class=\"analytics-page__peak-wrap relative flex min-h-0 flex-1 items-end justify-center\">\n <div\n class=\"analytics-page__peak-bar w-3 min-h-0.5 rounded-t-full\"\n [style.height.%]=\"point.heightPct\"\n [style.animation-delay.ms]=\"point.delayMs\"\n ></div>\n <span class=\"analytics-page__tooltip\">{{ point.tooltip }}</span>\n </div>\n </div>\n }\n </div>\n <div class=\"flex gap-xs\">\n @for (point of peakRows(); track point.hour) {\n <span class=\"min-h-3.5 flex-1 text-center fs-xs font-semibold uppercase tracking-wide text-light\">\n @if (point.showLabel) {\n {{ point.label }}\n }\n </span>\n }\n </div>\n </div>\n </article>\n </div>\n </div>\n }\n }\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";.analytics-page__range-row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.analytics-page__range-btn{min-height:36px;padding:8px 10px;border:1px solid #d9d9de;border-radius:8px;background:#fff;color:#4d5573;font-size:13px;font-weight:500;line-height:1;cursor:pointer}.analytics-page__range-btn--active{border-color:#2162f0;background:#fff;color:#2162f0}.analytics-page__impact{background:linear-gradient(145deg,#2162f0,#214dd9)}.analytics-page__divider{display:inline-block;width:1px;height:1rem;background:var(--eds-border-default, #e5e5e5);flex-shrink:0}.analytics-page__v-divider{width:1px;align-self:stretch;min-height:5.5rem;background:var(--eds-border-default, #e5e5e5);flex-shrink:0}.analytics-page__stack{background:transparent}.analytics-page__stack-seg{flex-basis:0;min-width:.25rem}.analytics-page__stack-seg:hover .analytics-page__tooltip,.analytics-page__stack-seg:focus-within .analytics-page__tooltip{opacity:1;transform:translate(-50%)}.analytics-page__stack-pct{pointer-events:none;z-index:1}.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__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:analytics-peak-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__trend{font:400 11px/16px var(--eds-font-inter)}.analytics-page__trend[data-tone=pos]{color:var(--eds-green-600, #077c37)}.analytics-page__trend[data-tone=warn],.analytics-page__trend[data-tone=neg]{color:var(--eds-red-500, #db0c15)}.analytics-page__trend[data-tone=neutral]{color:var(--eds-text-secondary)}.analytics-page__dot--total{background:#749fff}.analytics-page__dot--in{background:#ca88f9}.analytics-page__dot--out{background:#4dce7d}.analytics-page__line-chart svg{width:100%;height:calc(100% - 28px);overflow:visible}.analytics-page__volume-hit{position:absolute;inset:0 0 28px;z-index:2}.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:#749fff33}.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:#749fff}.analytics-page__line--in{stroke:#ca88f9}.analytics-page__line--out{stroke:#4dce7d}.analytics-page__hbar-row{grid-template-columns:minmax(110px,140px) 1fr 42px}@media(max-width:768px){.analytics-page__hbar-row{grid-template-columns:100px 1fr 36px}}.analytics-page__hbar-track{background:var(--eds-surface-l1, #f7f9fa)}.analytics-page__hbar-track:hover .analytics-page__tooltip,.analytics-page__hbar-track:focus-within .analytics-page__tooltip{opacity:1;transform:translate(-50%)}.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{background:#a3b3ff}.analytics-page__peak-col:hover .analytics-page__peak-bar{background:#8494f0}@keyframes analytics-hbar-grow{0%{transform:scaleX(0)}to{transform:scaleX(1)}}@keyframes analytics-peak-grow{0%{transform:scaleY(0)}to{transform:scaleY(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}}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "ed-icon", inputs: ["name", "size", "color", "cssClass", "decorative", "ariaHidden", "ariaLabel", "title", "desc", "focusable", "tabIndex"] }, { kind: "component", type: EdButtonComponent, selector: "ed-button", inputs: ["variant", "size", "disabled", "ghost", "outline", "subtle", "fill", "loading", "block", "elevated", "shape", "type", "href", "routerLink", "rel", "target", "download", "ariaLabel"], outputs: ["click$"] }, { kind: "component", type: EdIconButtonComponent, selector: "ed-icon-button", inputs: ["label", "name", "size", "variant", "allowBubble", "ghost", "outline", "solid", "subtle", "shape", "type", "disabled", "loading", "toggle", "pressed", "id", "title", "nameAttr", "value", "autofocus", "tabIndex"], outputs: ["click", "focus", "blur", "click$", "focus$", "blur$"] }, { kind: "component", type: EdSkeletonComponent, selector: "ed-skeleton", inputs: ["class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
428
+ }
429
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AnalyticsPageComponent, decorators: [{
430
+ type: Component,
431
+ args: [{ selector: 'eds-analytics-page', imports: [IconComponent, EdButtonComponent, EdIconButtonComponent, EdSkeletonComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'block h-full min-h-0' }, template: "<div class=\"flex h-full min-h-0 flex-col overflow-hidden bg-lighter p-base analytics-page\">\n <header class=\"mb-md flex shrink-0 items-start justify-between gap-base\">\n <div class=\"flex flex-col gap-2xs\">\n <h4>Analytics</h4>\n <p class=\"fs-sm text-light\">Call volume, outcomes, and agent performance</p>\n </div>\n <div class=\"flex shrink-0 items-center gap-base\">\n <ed-button\n variant=\"default\"\n outline\n size=\"sm\"\n [disabled]=\"exporting()\"\n (click$)=\"exportCsv()\"\n >\n <ed-icon slot=\"prefix\" name=\"download\" [size]=\"16\" />\n Export\n </ed-button>\n <div\n class=\"relative\"\n data-analytics-filters\n (document:click)=\"onFiltersDocClick($event)\"\n >\n <ed-icon-button\n name=\"filter_alt\"\n label=\"Filters\"\n size=\"md\"\n outline\n allowBubble\n [pressed]=\"filtersOpen()\"\n toggle\n (click$)=\"toggleFilters($event)\"\n />\n @if (filtersOpen()) {\n <div\n class=\"absolute right-0 top-[calc(100%+4px)] z-20 w-[22rem] rounded-md border border-light bg-white shadow-md\"\n role=\"dialog\"\n aria-label=\"Analytics Filter\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"flex items-center justify-between border-b border-light px-base py-sm\">\n <h5 class=\"m-0\">Analytics Filter</h5>\n <ed-icon-button\n name=\"close\"\n size=\"sm\"\n label=\"Close filters\"\n (click$)=\"closeFilters()\"\n />\n </header>\n <section class=\"grid gap-base p-base\">\n <div class=\"grid gap-2xs\">\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-light\">Range</span>\n <div class=\"analytics-page__range-row\" role=\"group\" aria-label=\"Preset 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 </div>\n <div class=\"grid grid-cols-2 gap-sm\">\n <div class=\"grid gap-2xs\">\n <label class=\"fs-sm font-semibold text-light\" for=\"analytics-start-date\">From</label>\n <input\n id=\"analytics-start-date\"\n class=\"form-input is-sm w-full\"\n type=\"date\"\n [value]=\"startDate()\"\n [max]=\"endDate() || undefined\"\n (input)=\"onStartDate($any($event.target).value)\"\n />\n </div>\n <div class=\"grid gap-2xs\">\n <label class=\"fs-sm font-semibold text-light\" for=\"analytics-end-date\">To</label>\n <input\n id=\"analytics-end-date\"\n class=\"form-input is-sm w-full\"\n type=\"date\"\n [value]=\"endDate()\"\n [min]=\"startDate() || undefined\"\n (input)=\"onEndDate($any($event.target).value)\"\n />\n </div>\n </div>\n </section>\n <footer class=\"flex flex-row-reverse flex-wrap items-center gap-sm border-t border-light px-base py-sm\">\n <ed-button\n variant=\"primary\"\n fill\n size=\"sm\"\n (click$)=\"applyFilters(); closeFilters()\"\n >\n Apply\n </ed-button>\n <ed-button\n variant=\"secondary\"\n outline\n size=\"sm\"\n (click$)=\"resetFilters()\"\n >\n Reset\n </ed-button>\n </footer>\n </div>\n }\n </div>\n </div>\n </header>\n\n <div class=\"flex min-h-0 flex-1 flex-col gap-md overflow-y-auto\">\n @if (loading()) {\n <div class=\"flex flex-col gap-base\" aria-busy=\"true\" aria-live=\"polite\">\n <section class=\"flex flex-col gap-base lg:flex-row\" aria-label=\"Key outcomes\">\n <article class=\"flex min-h-36 shrink-0 flex-col gap-sm rounded-md border border-light bg-white p-base lg:w-72\">\n <ed-skeleton class=\"h-3 w-40\"></ed-skeleton>\n <ed-skeleton class=\"h-7 w-28\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-48\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-36\"></ed-skeleton>\n </article>\n <article class=\"flex min-w-0 flex-1 flex-col gap-base overflow-hidden rounded-lg border border-light bg-white p-base lg:flex-row lg:items-center\">\n <div class=\"flex w-full shrink-0 flex-col gap-sm lg:w-52\">\n <ed-skeleton class=\"h-3 w-36\"></ed-skeleton>\n <ed-skeleton class=\"h-7 w-20\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-40\"></ed-skeleton>\n </div>\n <div class=\"flex min-w-0 flex-1 flex-col gap-sm\">\n <div class=\"flex items-center justify-between gap-sm\">\n <ed-skeleton class=\"h-3 w-28\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-24\"></ed-skeleton>\n </div>\n <ed-skeleton class=\"h-8 w-full !rounded-md\"></ed-skeleton>\n <div class=\"flex flex-wrap gap-md\">\n @for (_ of legendSkeletonRows; track _) {\n <div class=\"inline-flex items-center gap-xs\">\n <ed-skeleton class=\"h-4 w-4 !rounded-sm\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-20\"></ed-skeleton>\n </div>\n }\n </div>\n </div>\n </article>\n </section>\n\n <section class=\"grid grid-cols-1 gap-base sm:grid-cols-2 xl:grid-cols-4\" aria-label=\"Activity KPIs\">\n @for (_ of kpiSkeletonRows; track _) {\n <article class=\"flex flex-col gap-2xs rounded-md border border-light bg-white p-base\">\n <ed-skeleton class=\"h-3 w-28\"></ed-skeleton>\n <ed-skeleton class=\"h-6 w-14\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-24\"></ed-skeleton>\n </article>\n }\n </section>\n\n <div class=\"grid grid-cols-1 gap-base md:grid-cols-2\">\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg border border-light bg-white p-base\">\n <div class=\"flex flex-wrap items-center justify-between gap-base\">\n <h3>Call Volume</h3>\n <div class=\"flex flex-wrap gap-md\">\n @for (_ of legendSkeletonRows; track _) {\n <ed-skeleton class=\"h-3 w-20\"></ed-skeleton>\n }\n </div>\n </div>\n <ed-skeleton class=\"h-60 w-full !rounded-md\"></ed-skeleton>\n </article>\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg border border-light bg-white p-base\">\n <h3>Top Call Reasons</h3>\n <div class=\"flex flex-col gap-md\">\n @for (_ of chartRowSkeletonRows; track _) {\n <div class=\"grid grid-cols-[minmax(110px,140px)_1fr_42px] items-center gap-sm\">\n <ed-skeleton class=\"h-3 w-20 justify-self-end\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-full !rounded-r-xl\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-8 justify-self-end\"></ed-skeleton>\n </div>\n }\n </div>\n </article>\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg border border-light bg-white p-base\">\n <h3>Reasons AI got stuck</h3>\n <div class=\"flex flex-col gap-md\">\n @for (_ of chartRowSkeletonRows; track _) {\n <div class=\"grid grid-cols-[minmax(110px,140px)_1fr_42px] items-center gap-sm\">\n <ed-skeleton class=\"h-3 w-20 justify-self-end\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-full !rounded-r-xl\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-8 justify-self-end\"></ed-skeleton>\n </div>\n }\n </div>\n </article>\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg border border-light bg-white p-base\">\n <h3>Peak Hours</h3>\n <div class=\"flex h-40 items-end gap-xs\">\n @for (heightClass of peakSkeletonRows; track $index) {\n <ed-skeleton [class]=\"'min-w-0 flex-1 !rounded-t-full ' + heightClass\"></ed-skeleton>\n }\n </div>\n </article>\n </div>\n </div>\n } @else {\n @if (data(); as d) {\n <div class=\"analytics-page__body flex flex-col gap-base\" [class.analytics-page__body--ready]=\"chartsReady()\">\n <section class=\"flex flex-col gap-base lg:flex-row\" aria-label=\"Key outcomes\">\n <article\n class=\"analytics-page__impact flex min-h-36 shrink-0 flex-col gap-sm rounded-md p-base text-white lg:w-72\"\n >\n <div class=\"flex items-center gap-2xs\">\n <ed-icon name=\"lightning\" [size]=\"12\" color=\"currentColor\" />\n <span class=\"fs-xs font-semibold uppercase tracking-wide opacity-90\">\n {{ impactKpi()?.label || 'Estimated Impact This Month' }}\n </span>\n </div>\n <strong class=\"fs-lg font-semibold leading-tight\">\n {{ impactKpi()?.value || '\u2014' }}\n </strong>\n <div class=\"flex flex-col gap-2xs opacity-90\">\n <p class=\"m-0 fs-sm\">{{ impactSubPrimary() }}</p>\n @if (impactSubSecondary(); as secondary) {\n <p class=\"m-0 fs-xs opacity-90\">{{ secondary }}</p>\n }\n </div>\n </article>\n\n <article\n class=\"analytics-page__card flex min-w-0 flex-1 flex-col gap-base overflow-hidden rounded-lg bg-white p-base lg:flex-row lg:items-center\"\n >\n <div class=\"flex w-full shrink-0 flex-col gap-sm lg:w-52\">\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-light\">\n {{ resolutionKpi()?.label || 'AI Autonomous Resolution' }}\n </span>\n <strong class=\"fs-lg font-semibold text-dark\">\n {{ resolutionKpi()?.value || '\u2014' }}\n </strong>\n <div class=\"flex flex-wrap items-center gap-xs\">\n <span class=\"fs-sm text-body\">\n {{ resolutionKpi()?.sub || 'without staff intervention' }}\n </span>\n @if (resolutionTrend(); as trend) {\n <span class=\"analytics-page__divider\" aria-hidden=\"true\"></span>\n <span\n class=\"analytics-page__trend inline-flex items-center gap-2xs\"\n [attr.data-tone]=\"trend.tone\"\n >\n <ed-icon [name]=\"trend.icon\" [size]=\"12\" color=\"currentColor\" />\n {{ trend.delta }}\n </span>\n }\n </div>\n </div>\n\n <div class=\"analytics-page__v-divider hidden lg:block\" aria-hidden=\"true\"></div>\n\n <div class=\"analytics-page__distribution flex min-w-0 flex-1 flex-col gap-sm\">\n <div class=\"flex flex-wrap items-center justify-between gap-sm\">\n <strong class=\"fs-sm font-semibold text-body\">{{ totalCallsLabel() }}</strong>\n @if (directionSub(); as dir) {\n <span class=\"fs-sm text-body\">{{ dir }}</span>\n }\n </div>\n <div\n class=\"analytics-page__stack flex h-8 gap-2xs overflow-hidden rounded-md\"\n role=\"img\"\n aria-label=\"Call distribution\"\n >\n @for (seg of distribution(); track seg.label) {\n <span\n class=\"analytics-page__stack-seg relative flex min-w-0 items-center justify-center\"\n [style.flex-grow]=\"seg.width\"\n [style.background]=\"seg.color\"\n [attr.aria-label]=\"seg.tooltip\"\n >\n @if (seg.width >= 8) {\n <span\n class=\"analytics-page__stack-pct fs-xs font-semibold uppercase tracking-wide\"\n [style.color]=\"seg.textColor\"\n >\n {{ seg.value }}%\n </span>\n }\n <span class=\"analytics-page__tooltip\">{{ seg.tooltip }}</span>\n </span>\n }\n </div>\n <ul class=\"m-0 flex list-none flex-wrap gap-x-md gap-y-xs p-0\">\n @for (seg of distribution(); track seg.label) {\n <li class=\"inline-flex items-center gap-xs\">\n <span\n class=\"h-4 w-4 shrink-0 rounded-sm\"\n [style.background]=\"seg.color\"\n ></span>\n <span class=\"inline-flex items-center gap-2xs\">\n <strong class=\"fs-sm font-semibold text-body\">{{ seg.label }}</strong>\n <span class=\"fs-xs text-light\">{{ seg.callsText }}</span>\n </span>\n </li>\n }\n </ul>\n </div>\n </article>\n </section>\n\n <section\n class=\"grid grid-cols-1 gap-base sm:grid-cols-2 xl:grid-cols-4\"\n aria-label=\"Activity KPIs\"\n >\n @for (kpi of secondaryKpis(); track kpi.label) {\n <article class=\"analytics-page__card flex flex-col gap-2xs rounded-md bg-white p-base\">\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-light\">{{ kpi.label }}</span>\n <strong class=\"font-semibold text-dark\">{{ kpi.value }}</strong>\n <div class=\"flex flex-wrap items-center gap-xs\">\n @if (kpi.sub) {\n <span class=\"fs-sm text-body\">{{ kpi.sub }}</span>\n }\n @if (kpi.delta) {\n <span class=\"analytics-page__divider\" aria-hidden=\"true\"></span>\n <span\n class=\"analytics-page__trend inline-flex items-center gap-2xs\"\n [attr.data-tone]=\"kpi.tone\"\n >\n <ed-icon [name]=\"kpi.trendIcon\" [size]=\"12\" color=\"currentColor\" />\n {{ kpi.delta }}\n </span>\n }\n </div>\n </article>\n }\n </section>\n\n <div class=\"grid grid-cols-1 gap-base md:grid-cols-2\">\n <article class=\"flex flex-col gap-md overflow-hidden rounded-lg bg-white p-base analytics-page__chart\">\n <div class=\"flex flex-wrap items-center justify-between gap-base\">\n <h3>Call Volume</h3>\n <ul class=\"m-0 flex list-none flex-wrap items-center justify-end gap-md p-0\">\n <li class=\"inline-flex items-center gap-2xs\">\n <span class=\"analytics-page__dot analytics-page__dot--total h-2 w-2 rounded-full\"></span>\n <span class=\"fs-sm font-semibold text-body\">Total Calls</span>\n </li>\n <li class=\"inline-flex items-center gap-2xs\">\n <span class=\"analytics-page__dot analytics-page__dot--in h-2 w-2 rounded-full\"></span>\n <span class=\"fs-sm font-semibold text-body\">Inbound Calls</span>\n </li>\n <li class=\"inline-flex items-center gap-2xs\">\n <span class=\"analytics-page__dot analytics-page__dot--out h-2 w-2 rounded-full\"></span>\n <span class=\"fs-sm font-semibold text-body\">Outbound Calls</span>\n </li>\n </ul>\n </div>\n <div class=\"analytics-page__line-chart relative h-60\">\n <svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\" aria-hidden=\"true\">\n <path class=\"analytics-page__area\" [attr.d]=\"volumeAreaPath()\" />\n <path\n class=\"analytics-page__line analytics-page__line--total\"\n [attr.d]=\"volumeTotalPath()\"\n fill=\"none\"\n />\n <path\n class=\"analytics-page__line analytics-page__line--in\"\n [attr.d]=\"volumeInboundPath()\"\n fill=\"none\"\n />\n <path\n class=\"analytics-page__line analytics-page__line--out\"\n [attr.d]=\"volumeOutboundPath()\"\n fill=\"none\"\n />\n </svg>\n <div\n class=\"analytics-page__volume-hit flex\"\n role=\"list\"\n aria-label=\"Call volume by period\"\n (mouseleave)=\"onVolumeLeave()\"\n >\n @for (point of volumePointVms(); track point.trackKey; let i = $index) {\n <button\n type=\"button\"\n class=\"analytics-page__volume-col relative min-w-0 flex-1 cursor-default border-none bg-transparent p-0\"\n role=\"listitem\"\n [class.analytics-page__volume-col--active]=\"hoveredVolume() === i\"\n [attr.aria-label]=\"point.tooltip\"\n (mouseenter)=\"onVolumeEnter(i)\"\n (focus)=\"onVolumeEnter(i)\"\n (blur)=\"onVolumeLeave()\"\n >\n <span class=\"analytics-page__tooltip analytics-page__tooltip--multiline\">\n {{ point.tooltip }}\n </span>\n </button>\n }\n </div>\n <div class=\"mt-xs flex justify-between gap-2xs\">\n @for (label of volumeAxisLabels(); track $index) {\n <span class=\"flex-1 text-center fs-xs font-semibold uppercase tracking-wide text-light\">\n {{ label }}\n </span>\n }\n </div>\n </div>\n </article>\n\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg bg-white p-base analytics-page__chart\">\n <h3>Top Call Reasons</h3>\n <div class=\"flex flex-col gap-md\">\n @for (item of reasonRows(); track item.label) {\n <div\n class=\"analytics-page__hbar-row grid items-center gap-sm\"\n [attr.aria-label]=\"item.tooltip\"\n >\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-light text-right\">\n {{ item.label }}\n </span>\n <div class=\"analytics-page__hbar-track relative h-3 overflow-hidden rounded-r-xl\">\n <span\n class=\"analytics-page__hbar-fill block h-full min-w-1 rounded-r-xl\"\n [style.width.%]=\"item.widthPct\"\n [style.background]=\"item.color\"\n [style.animation-delay.ms]=\"item.delayMs\"\n ></span>\n <span class=\"analytics-page__tooltip\">{{ item.tooltip }}</span>\n </div>\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-dark text-right\">\n {{ item.value }}%\n </span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg bg-white p-base analytics-page__chart\">\n <h3>Reasons AI got stuck</h3>\n <div class=\"flex flex-col gap-md\">\n @for (item of transferRows(); track item.label) {\n <div\n class=\"analytics-page__hbar-row grid items-center gap-sm\"\n [attr.aria-label]=\"item.tooltip\"\n >\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-light text-right\">\n {{ item.label }}\n </span>\n <div class=\"analytics-page__hbar-track relative h-3 overflow-hidden rounded-r-xl\">\n <span\n class=\"analytics-page__hbar-fill block h-full min-w-1 rounded-r-xl\"\n [style.width.%]=\"item.widthPct\"\n [style.background]=\"item.color\"\n [style.animation-delay.ms]=\"item.delayMs\"\n ></span>\n <span class=\"analytics-page__tooltip\">{{ item.tooltip }}</span>\n </div>\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-dark text-right\">\n {{ item.value }}%\n </span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"flex flex-col gap-md overflow-hidden rounded-lg bg-white p-base analytics-page__chart\">\n <h3>Peak Hours</h3>\n <div class=\"flex flex-col gap-xs\">\n <div class=\"flex h-40 items-end gap-xs\" role=\"list\" aria-label=\"Call volume by hour\">\n @for (point of peakRows(); track point.hour) {\n <div\n class=\"analytics-page__peak-col flex h-full min-w-0 flex-1 flex-col\"\n role=\"listitem\"\n [attr.aria-label]=\"point.tooltip\"\n >\n <div class=\"analytics-page__peak-wrap relative flex min-h-0 flex-1 items-end justify-center\">\n <div\n class=\"analytics-page__peak-bar w-3 min-h-0.5 rounded-t-full\"\n [style.height.%]=\"point.heightPct\"\n [style.animation-delay.ms]=\"point.delayMs\"\n ></div>\n <span class=\"analytics-page__tooltip\">{{ point.tooltip }}</span>\n </div>\n </div>\n }\n </div>\n <div class=\"flex gap-xs\">\n @for (point of peakRows(); track point.hour) {\n <span class=\"min-h-3.5 flex-1 text-center fs-xs font-semibold uppercase tracking-wide text-light\">\n @if (point.showLabel) {\n {{ point.label }}\n }\n </span>\n }\n </div>\n </div>\n </article>\n </div>\n </div>\n }\n }\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";.analytics-page__range-row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.analytics-page__range-btn{min-height:36px;padding:8px 10px;border:1px solid #d9d9de;border-radius:8px;background:#fff;color:#4d5573;font-size:13px;font-weight:500;line-height:1;cursor:pointer}.analytics-page__range-btn--active{border-color:#2162f0;background:#fff;color:#2162f0}.analytics-page__impact{background:linear-gradient(145deg,#2162f0,#214dd9)}.analytics-page__divider{display:inline-block;width:1px;height:1rem;background:var(--eds-border-default, #e5e5e5);flex-shrink:0}.analytics-page__v-divider{width:1px;align-self:stretch;min-height:5.5rem;background:var(--eds-border-default, #e5e5e5);flex-shrink:0}.analytics-page__stack{background:transparent}.analytics-page__stack-seg{flex-basis:0;min-width:.25rem}.analytics-page__stack-seg:hover .analytics-page__tooltip,.analytics-page__stack-seg:focus-within .analytics-page__tooltip{opacity:1;transform:translate(-50%)}.analytics-page__stack-pct{pointer-events:none;z-index:1}.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__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:analytics-peak-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__trend{font:400 11px/16px var(--eds-font-inter)}.analytics-page__trend[data-tone=pos]{color:var(--eds-green-600, #077c37)}.analytics-page__trend[data-tone=warn],.analytics-page__trend[data-tone=neg]{color:var(--eds-red-500, #db0c15)}.analytics-page__trend[data-tone=neutral]{color:var(--eds-text-secondary)}.analytics-page__dot--total{background:#749fff}.analytics-page__dot--in{background:#ca88f9}.analytics-page__dot--out{background:#4dce7d}.analytics-page__line-chart svg{width:100%;height:calc(100% - 28px);overflow:visible}.analytics-page__volume-hit{position:absolute;inset:0 0 28px;z-index:2}.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:#749fff33}.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:#749fff}.analytics-page__line--in{stroke:#ca88f9}.analytics-page__line--out{stroke:#4dce7d}.analytics-page__hbar-row{grid-template-columns:minmax(110px,140px) 1fr 42px}@media(max-width:768px){.analytics-page__hbar-row{grid-template-columns:100px 1fr 36px}}.analytics-page__hbar-track{background:var(--eds-surface-l1, #f7f9fa)}.analytics-page__hbar-track:hover .analytics-page__tooltip,.analytics-page__hbar-track:focus-within .analytics-page__tooltip{opacity:1;transform:translate(-50%)}.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{background:#a3b3ff}.analytics-page__peak-col:hover .analytics-page__peak-bar{background:#8494f0}@keyframes analytics-hbar-grow{0%{transform:scaleX(0)}to{transform:scaleX(1)}}@keyframes analytics-peak-grow{0%{transform:scaleY(0)}to{transform:scaleY(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}}\n"] }]
432
+ }] });
433
+
434
+ export { AnalyticsPageComponent };
435
+ //# sourceMappingURL=edvak99-voice-receptionist-widget-analytics-page.component-CmImX5FN.mjs.map