@edvak99/voice-receptionist-widget 1.0.0 → 1.1.1

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 (123) hide show
  1. package/README.md +56 -43
  2. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-Cg15RZEY.mjs +113 -0
  3. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-Cg15RZEY.mjs.map +1 -0
  4. package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-DVQyKh_n.mjs +35 -0
  5. package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-DVQyKh_n.mjs.map +1 -0
  6. package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-CbMLabcp.mjs +316 -0
  7. package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-CbMLabcp.mjs.map +1 -0
  8. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DfX0pJIR.mjs +138 -0
  9. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DfX0pJIR.mjs.map +1 -0
  10. package/fesm2022/edvak99-voice-receptionist-widget-call-recording-player.component-BDJmnxUF.mjs +110 -0
  11. package/fesm2022/edvak99-voice-receptionist-widget-call-recording-player.component-BDJmnxUF.mjs.map +1 -0
  12. package/fesm2022/edvak99-voice-receptionist-widget-call-review.service-D6P-ojFa.mjs +29 -0
  13. package/fesm2022/edvak99-voice-receptionist-widget-call-review.service-D6P-ojFa.mjs.map +1 -0
  14. package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-DQLIw7xb.mjs +70 -0
  15. package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-DQLIw7xb.mjs.map +1 -0
  16. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-Yr8Fx4Tj.mjs +248 -0
  17. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-Yr8Fx4Tj.mjs.map +1 -0
  18. package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs +27 -0
  19. package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs.map +1 -0
  20. package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-KzX82V6L.mjs +136 -0
  21. package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-KzX82V6L.mjs.map +1 -0
  22. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DvJ2Ygc1.mjs +2653 -0
  23. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DvJ2Ygc1.mjs.map +1 -0
  24. package/fesm2022/edvak99-voice-receptionist-widget-knowledge-hub.page-DHFkyA6s.mjs +575 -0
  25. package/fesm2022/edvak99-voice-receptionist-widget-knowledge-hub.page-DHFkyA6s.mjs.map +1 -0
  26. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-list.service-D5jeLPo9.mjs +120 -0
  27. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-list.service-D5jeLPo9.mjs.map +1 -0
  28. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-Do_-sipd.mjs +115 -0
  29. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-Do_-sipd.mjs.map +1 -0
  30. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-pTwlznGI.mjs +574 -0
  31. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-pTwlznGI.mjs.map +1 -0
  32. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-BQECIuJu.mjs +54 -0
  33. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-BQECIuJu.mjs.map +1 -0
  34. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-CteOwr16.mjs +265 -0
  35. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-CteOwr16.mjs.map +1 -0
  36. package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-k-1Z4CXg.mjs +509 -0
  37. package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-k-1Z4CXg.mjs.map +1 -0
  38. package/fesm2022/edvak99-voice-receptionist-widget-task.util-DdtI0CR3.mjs +52 -0
  39. package/fesm2022/edvak99-voice-receptionist-widget-task.util-DdtI0CR3.mjs.map +1 -0
  40. package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-BgL50G-i.mjs +237 -0
  41. package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-BgL50G-i.mjs.map +1 -0
  42. package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-BOEnzklB.mjs +45 -0
  43. package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-BOEnzklB.mjs.map +1 -0
  44. package/fesm2022/edvak99-voice-receptionist-widget.mjs +1 -41
  45. package/fesm2022/edvak99-voice-receptionist-widget.mjs.map +1 -1
  46. package/lib/api-paths.d.ts +11 -0
  47. package/lib/components/call-conversation-panel/call-conversation-panel.component.d.ts +45 -0
  48. package/lib/components/call-recording-player/call-recording-player.component.d.ts +29 -0
  49. package/lib/components/call-tasks-panel/call-tasks-panel.component.d.ts +19 -0
  50. package/lib/components/live-transcript-panel/live-transcript-panel.component.d.ts +36 -0
  51. package/lib/components/outbound-dial-modal/outbound-dial-modal.component.d.ts +37 -0
  52. package/lib/components/outbound-purpose-form-modal/outbound-purpose-form-modal.component.d.ts +38 -0
  53. package/lib/components/task-detail-drawer/task-detail-drawer.component.d.ts +20 -0
  54. package/lib/config.d.ts +23 -0
  55. package/lib/extract-api-rows.d.ts +2 -0
  56. package/lib/knowledge/components/faq-empty-state/faq-empty-state.component.d.ts +22 -0
  57. package/lib/knowledge/components/faq-form/faq-form.component.d.ts +27 -0
  58. package/lib/knowledge/components/faq-search-tester/faq-search-tester.component.d.ts +17 -0
  59. package/lib/knowledge/mocks/practice-faq.seed.d.ts +2 -0
  60. package/lib/knowledge/models/practice-faq.model.d.ts +47 -0
  61. package/lib/knowledge/pages/knowledge-hub.page.d.ts +47 -0
  62. package/lib/knowledge/services/practice-faq-api.mock.d.ts +16 -0
  63. package/lib/knowledge/services/practice-faq-api.service.d.ts +22 -0
  64. package/lib/knowledge/utils/faq.util.d.ts +3 -0
  65. package/lib/layout/eds-app-shell.component.d.ts +5 -0
  66. package/lib/layout/eds-module-nav.component.d.ts +24 -0
  67. package/lib/layout/eds-topbar.component.d.ts +29 -0
  68. package/lib/mocks/api-log.seed.d.ts +5 -0
  69. package/lib/mocks/outbound-purpose.seed.d.ts +3 -0
  70. package/lib/mocks/seed-data.d.ts +38 -0
  71. package/lib/models/ai-receptionist.types.d.ts +257 -0
  72. package/lib/models/api-dto.types.d.ts +215 -0
  73. package/lib/models/api-log.types.d.ts +53 -0
  74. package/lib/models/live-transcript.types.d.ts +26 -0
  75. package/lib/models/outbound-purpose.types.d.ts +50 -0
  76. package/lib/pages/analytics-page/analytics-page.component.d.ts +44 -0
  77. package/lib/pages/audit-page/audit-page.component.d.ts +14 -0
  78. package/lib/pages/call-history-detail-page/call-history-detail-page.component.d.ts +25 -0
  79. package/lib/pages/call-history-page/call-history-page.component.d.ts +42 -0
  80. package/lib/pages/dashboard-page/dashboard-page.component.d.ts +60 -0
  81. package/lib/pages/live-calls-page/live-calls-page.component.d.ts +37 -0
  82. package/lib/pages/live-monitor-page/live-monitor-page.component.d.ts +43 -0
  83. package/lib/pages/outbound-purposes-page/outbound-purposes-page.component.d.ts +24 -0
  84. package/lib/pages/settings-page/settings-fields.config.d.ts +35 -0
  85. package/lib/pages/settings-page/settings-page.component.d.ts +50 -0
  86. package/lib/pages/settings-page/tts-voice-presets.d.ts +7 -0
  87. package/lib/pages/tasks-page/tasks-page.component.d.ts +35 -0
  88. package/lib/pages/wizard-page/wizard-page.component.d.ts +17 -0
  89. package/lib/pipes/eds-elapsed.pipe.d.ts +7 -0
  90. package/lib/provide-voice-receptionist.d.ts +6 -0
  91. package/lib/routes.d.ts +8 -0
  92. package/lib/services/agent-online.service.d.ts +29 -0
  93. package/lib/services/ai-receptionist-api.mappers.d.ts +45 -0
  94. package/lib/services/ai-receptionist-api.mock.d.ts +150 -0
  95. package/lib/services/ai-receptionist-api.service.d.ts +156 -0
  96. package/lib/services/call-review.service.d.ts +12 -0
  97. package/lib/services/dashboard-socket.service.d.ts +26 -0
  98. package/lib/services/live-calls-list.service.d.ts +29 -0
  99. package/lib/services/live-transcript.service.d.ts +39 -0
  100. package/lib/shared/copy-button.component.d.ts +14 -0
  101. package/lib/shared/modal.component.d.ts +20 -0
  102. package/lib/shared/toast-container.component.d.ts +7 -0
  103. package/lib/shared/toast.service.d.ts +16 -0
  104. package/lib/ui/eds-avatar.component.d.ts +8 -0
  105. package/lib/ui/eds-badge.component.d.ts +7 -0
  106. package/lib/ui/eds-button.component.d.ts +9 -0
  107. package/lib/ui/eds-icon.component.d.ts +10 -0
  108. package/lib/ui/eds-pulse-dot.component.d.ts +5 -0
  109. package/lib/ui/eds-waveform.component.d.ts +8 -0
  110. package/lib/utils/call-outcome.util.d.ts +8 -0
  111. package/lib/utils/call-review.util.d.ts +8 -0
  112. package/lib/utils/outbound-purpose.util.d.ts +14 -0
  113. package/lib/utils/task.util.d.ts +10 -0
  114. package/lib/utils/tool-labels.util.d.ts +14 -0
  115. package/lib/utils/transcript-timing.util.d.ts +17 -0
  116. package/lib/utils/welcome-message.util.d.ts +5 -0
  117. package/package.json +12 -5
  118. package/public-api.d.ts +6 -2
  119. package/styles/_index.scss +55 -0
  120. package/styles/chat-ui.scss +133 -0
  121. package/styles/eds.scss +328 -0
  122. package/lib/voice-receptionist-widget.component.d.ts +0 -5
  123. package/lib/voice-receptionist-widget.service.d.ts +0 -6
package/README.md CHANGED
@@ -1,63 +1,76 @@
1
- # VoiceReceptionistWidget
1
+ # @edvak99/voice-receptionist-widget
2
2
 
3
- This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.2.0.
3
+ Embeddable **AI Receptionist** feature for Angular **19.2.x** hosts (EHR or standalone Voice Hub).
4
4
 
5
- ## Code scaffolding
6
-
7
- Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
5
+ ## Install
8
6
 
9
7
  ```bash
10
- ng generate component component-name
8
+ npm install @edvak99/voice-receptionist-widget socket.io-client
11
9
  ```
12
10
 
13
- For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
14
-
15
- ```bash
16
- ng generate --help
11
+ Peer deps: `@angular/core`, `common`, `forms`, `router` (^19.2), `rxjs`, `socket.io-client`.
12
+
13
+ ## Wire into EHR
14
+
15
+ ```ts
16
+ import {
17
+ provideVoiceReceptionist,
18
+ VOICE_RECEPTIONIST_ROUTES,
19
+ } from '@edvak99/voice-receptionist-widget';
20
+
21
+ export const appConfig = {
22
+ providers: [
23
+ provideHttpClient(withInterceptors([/* your auth interceptor */])),
24
+ provideVoiceReceptionist({
25
+ apiBaseUrl: 'https://your-brain-api.example.com',
26
+ // includePracticeNav: false // default — hide patients/providers in embed
27
+ }),
28
+ provideRouter([
29
+ {
30
+ path: 'ai-receptionist',
31
+ children: VOICE_RECEPTIONIST_ROUTES,
32
+ },
33
+ ]),
34
+ ],
35
+ };
17
36
  ```
18
37
 
19
- ## Building
20
-
21
- To build the library, run:
38
+ Or lazy:
22
39
 
23
- ```bash
24
- ng build voice-receptionist-widget
40
+ ```ts
41
+ {
42
+ path: 'ai-receptionist',
43
+ loadChildren: () =>
44
+ import('@edvak99/voice-receptionist-widget').then((m) => m.VOICE_RECEPTIONIST_ROUTES),
45
+ }
25
46
  ```
26
47
 
27
- This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
28
-
29
- ### Publishing the Library
30
-
31
- Once the project is built, you can publish your library by following these steps:
32
-
33
- 1. Navigate to the `dist` directory:
34
- ```bash
35
- cd dist/voice-receptionist-widget
36
- ```
37
-
38
- 2. Run the `npm publish` command to publish your library to the npm registry:
39
- ```bash
40
- npm publish
41
- ```
48
+ Host owns login and attaches `Authorization` via its HTTP interceptor. Optional session for topbar:
49
+
50
+ ```ts
51
+ provideVoiceReceptionist({
52
+ apiBaseUrl: '...',
53
+ session: {
54
+ email: computed(() => auth.session()?.email ?? null),
55
+ isAdmin: () => auth.isAdmin(),
56
+ logout: () => auth.logout(),
57
+ },
58
+ })
59
+ ```
42
60
 
43
- ## Running unit tests
61
+ ## Styles & fonts
44
62
 
45
- To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
63
+ In the host global stylesheet:
46
64
 
47
- ```bash
48
- ng test
65
+ ```scss
66
+ @use '@edvak99/voice-receptionist-widget/styles';
49
67
  ```
50
68
 
51
- ## Running end-to-end tests
69
+ Also load Material Symbols + fonts (Inter, DM Sans, Figtree, IBM Plex Mono) in `index.html`, same as the Voice Hub app.
52
70
 
53
- For end-to-end (e2e) testing, run:
71
+ ## Build this package
54
72
 
55
73
  ```bash
56
- ng e2e
74
+ npx ng build voice-receptionist-widget
75
+ cd dist/voice-receptionist-widget && npm publish --access public
57
76
  ```
58
-
59
- Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
60
-
61
- ## Additional Resources
62
-
63
- For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
@@ -0,0 +1,113 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, signal, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { A as AiReceptionistApiService, b as EdsIconComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DvJ2Ygc1.mjs';
4
+
5
+ class AnalyticsPageComponent {
6
+ constructor() {
7
+ this.api = inject(AiReceptionistApiService);
8
+ this.data = signal(null);
9
+ this.range = signal('30d');
10
+ this.loading = signal(true);
11
+ this.ranges = [
12
+ { id: '7d', label: '7 days' },
13
+ { id: '30d', label: '30 days' },
14
+ { id: '90d', label: '90 days' },
15
+ ];
16
+ }
17
+ ngOnInit() {
18
+ this.load();
19
+ }
20
+ setRange(id) {
21
+ this.range.set(id);
22
+ this.load();
23
+ }
24
+ maxCount(items) {
25
+ return Math.max(...items.map((i) => i.count), 1);
26
+ }
27
+ barHeightPercent(point, points) {
28
+ if (point.count <= 0)
29
+ return 0;
30
+ return (point.count / this.maxCount(points)) * 100;
31
+ }
32
+ /** Hide inline counts when bars are too narrow to stay readable. */
33
+ showBarValue(point, points) {
34
+ return point.count > 0 && points.length <= 21;
35
+ }
36
+ maxValue(items) {
37
+ return Math.max(...items.map((i) => i.value), 1);
38
+ }
39
+ volumeTooltip(point) {
40
+ const datePart = point.date ? this.formatChartDate(point.date) : point.label;
41
+ return `${datePart}: ${point.count} call${point.count === 1 ? '' : 's'}`;
42
+ }
43
+ peakTooltip(point) {
44
+ return `${point.label}: ${point.count} call${point.count === 1 ? '' : 's'}`;
45
+ }
46
+ /** ISO or YYYY-MM-DD → MM/DD (zero-padded). */
47
+ formatChartDate(date) {
48
+ const iso = /^(\d{4})-(\d{2})-(\d{2})/.exec(date.trim());
49
+ if (iso) {
50
+ return `${iso[2]}/${iso[3]}`;
51
+ }
52
+ return date;
53
+ }
54
+ /** Compact x-axis for volume — full labels on 7d, sparse dates on longer ranges. */
55
+ volumeAxisLabel(point, index, total) {
56
+ if (point.date) {
57
+ if (total <= 7)
58
+ return this.formatChartDate(point.date);
59
+ const step = total <= 14 ? 2 : total <= 31 ? 5 : 7;
60
+ if (index % step !== 0 && index !== total - 1)
61
+ return '';
62
+ return this.formatChartDate(point.date);
63
+ }
64
+ if (total <= 7)
65
+ return point.day;
66
+ const step = total <= 14 ? 2 : total <= 31 ? 5 : 7;
67
+ if (index % step !== 0 && index !== total - 1)
68
+ return '';
69
+ return point.day;
70
+ }
71
+ showPeakAxisLabel(index, total) {
72
+ if (total <= 12)
73
+ return true;
74
+ const step = total <= 24 ? 3 : 4;
75
+ return index % step === 0 || index === total - 1;
76
+ }
77
+ volumeRangeLabel() {
78
+ const vol = this.data()?.volume;
79
+ if (!vol?.startDate || !vol?.endDate)
80
+ return 'Calls per day';
81
+ const start = this.formatChartDate(vol.startDate);
82
+ const end = this.formatChartDate(vol.endDate);
83
+ return `${start} – ${end}${vol.timezone ? ` (${vol.timezone})` : ''}`;
84
+ }
85
+ kpiIcon(label) {
86
+ const key = label.toLowerCase();
87
+ if (key.includes('call') || key.includes('handled'))
88
+ return 'call';
89
+ if (key.includes('time') || key.includes('duration'))
90
+ return 'schedule';
91
+ if (key.includes('resolution') || key.includes('book'))
92
+ return 'event_available';
93
+ if (key.includes('transfer'))
94
+ return 'phone_forwarded';
95
+ return 'monitoring';
96
+ }
97
+ load() {
98
+ this.loading.set(true);
99
+ this.api.getAnalytics(this.range()).subscribe(({ data }) => {
100
+ this.data.set(data);
101
+ this.loading.set(false);
102
+ });
103
+ }
104
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AnalyticsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
105
+ 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\">Call volume, outcomes, and performance KPIs</p>\n </div>\n <div class=\"analytics-page__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 </header>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading analytics\u2026</p>\n } @else if (data()) {\n <section class=\"analytics-page__kpis\">\n @for (kpi of data()!.kpiCards; track kpi.label) {\n <article class=\"eds-card analytics-page__kpi\">\n <eds-icon [name]=\"kpiIcon(kpi.label)\" [size]=\"22\" />\n <span class=\"eds-label\">{{ kpi.label }}</span>\n <span class=\"analytics-page__kpi-value\">{{ kpi.value }}</span>\n @if (kpi.delta) {\n <span class=\"analytics-page__kpi-delta\" [attr.data-tone]=\"kpi.tone\">{{ kpi.delta }}</span>\n }\n @if (kpi.sub) {\n <span class=\"analytics-page__kpi-sub\">{{ kpi.sub }}</span>\n }\n </article>\n }\n </section>\n\n <div class=\"analytics-page__charts\">\n <article class=\"eds-card analytics-page__chart\">\n <div class=\"analytics-page__chart-head\">\n <h2 class=\"eds-h2\">Call volume</h2>\n <span class=\"eds-label\">{{ volumeRangeLabel() }}</span>\n </div>\n <div class=\"analytics-page__volume-chart\">\n <div class=\"analytics-page__plot\">\n <div class=\"analytics-page__grid\" aria-hidden=\"true\"></div>\n <div class=\"analytics-page__bars\" role=\"list\" aria-label=\"Call volume by day\">\n @for (point of data()!.volume.points; track point.date || point.label; let i = $index) {\n <div\n class=\"analytics-page__bar-col\"\n role=\"listitem\"\n [attr.aria-label]=\"volumeTooltip(point)\"\n >\n <div class=\"analytics-page__bar-wrap\">\n @if (point.count > 0) {\n <div\n class=\"analytics-page__bar-stack\"\n [style.height.%]=\"barHeightPercent(point, data()!.volume.points)\"\n >\n @if (showBarValue(point, data()!.volume.points)) {\n <span class=\"analytics-page__bar-value eds-mono-sm\">{{ point.count }}</span>\n }\n <div class=\"analytics-page__bar\"></div>\n </div>\n }\n <span class=\"analytics-page__tooltip\">{{ volumeTooltip(point) }}</span>\n </div>\n <span class=\"eds-mono-sm analytics-page__bar-axis\">\n {{ volumeAxisLabel(point, i, data()!.volume.points.length) }}\n </span>\n </div>\n }\n </div>\n </div>\n </div>\n <!-- <p class=\"analytics-page__axis-note eds-label\">(MM/DD)/calls per day</p> -->\n </article>\n\n <article class=\"eds-card analytics-page__chart\">\n <h2 class=\"eds-h2\">Outcomes</h2>\n <div class=\"analytics-page__bars analytics-page__bars--horizontal\">\n @for (item of data()!.outcomes; track item.label) {\n <div class=\"analytics-page__hbar-row\">\n <span class=\"eds-body-sm\">{{ item.label }}</span>\n <div class=\"analytics-page__hbar-track\">\n <span\n class=\"analytics-page__hbar-fill\"\n [style.width.%]=\"(item.value / maxValue(data()!.outcomes)) * 100\"\n [style.background]=\"item.color\"\n ></span>\n </div>\n <span class=\"eds-mono-sm\">{{ item.value }}%</span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart\">\n <h2 class=\"eds-h2\">Top reasons</h2>\n <div class=\"analytics-page__bars analytics-page__bars--horizontal\">\n @for (item of data()!.reasons; track item.label) {\n <div class=\"analytics-page__hbar-row\">\n <span class=\"eds-body-sm\">{{ item.label }}</span>\n <div class=\"analytics-page__hbar-track\">\n <span\n class=\"analytics-page__hbar-fill\"\n [style.width.%]=\"(item.value / maxValue(data()!.reasons)) * 100\"\n ></span>\n </div>\n <span class=\"eds-mono-sm\">{{ item.value }}%</span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart\">\n <div class=\"analytics-page__chart-head\">\n <h2 class=\"eds-h2\">Peak hours</h2>\n <span class=\"eds-label\">\n @if (data()!.peakHours.timezone) {\n {{ data()!.peakHours.timezone }}\n } @else {\n Calls per hour\n }\n </span>\n </div>\n <div class=\"analytics-page__volume-chart analytics-page__volume-chart--peak\">\n <div class=\"analytics-page__plot\">\n <div class=\"analytics-page__grid\" aria-hidden=\"true\"></div>\n <div class=\"analytics-page__bars analytics-page__bars--peak\" role=\"list\" aria-label=\"Call volume by hour\">\n @for (point of data()!.peakHours.points; track point.hour) {\n <div\n class=\"analytics-page__bar-col analytics-page__bar-col--peak\"\n role=\"listitem\"\n [attr.aria-label]=\"peakTooltip(point)\"\n >\n <div class=\"analytics-page__bar-wrap\">\n @if (point.count > 0) {\n <div\n class=\"analytics-page__bar-stack\"\n [style.height.%]=\"barHeightPercent(point, data()!.peakHours.points)\"\n >\n <div class=\"analytics-page__bar\"></div>\n </div>\n }\n <span class=\"analytics-page__tooltip\">{{ peakTooltip(point) }}</span>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n <div class=\"analytics-page__peak-axis\">\n @for (point of data()!.peakHours.points; track point.hour; let i = $index) {\n <span class=\"analytics-page__peak-tick eds-mono-sm\">\n @if (showPeakAxisLabel(i, data()!.peakHours.points.length)) {\n {{ point.label }}\n }\n </span>\n }\n </div>\n </article>\n </div>\n }\n</div>\n", styles: [".analytics-page__header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:20px}.analytics-page__range{display:flex;gap:4px;background:var(--surface-l0);border:1px solid var(--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(--text-secondary)}.analytics-page__range-btn--active{background:var(--brand-500);color:#fff}.analytics-page__kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:20px}.analytics-page__kpi{display:flex;flex-direction:column;gap:6px;padding:16px}.analytics-page__kpi-value{font-size:24px;font-weight:600;color:var(--text-primary)}.analytics-page__kpi-delta{font-size:12px;font-weight:600}.analytics-page__kpi-delta[data-tone=pos]{color:var(--green-500, #16a34a)}.analytics-page__kpi-delta[data-tone=warn]{color:var(--amber-500, #d97706)}.analytics-page__kpi-delta[data-tone=neutral]{color:var(--text-secondary)}.analytics-page__kpi-sub{font-size:12px;color:var(--text-secondary)}.analytics-page__charts{display:grid;grid-template-columns:1fr 1fr;gap:16px}.analytics-page__chart{padding:16px}.analytics-page__chart .eds-h2{margin-bottom:0}.analytics-page__chart-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:16px;flex-wrap:wrap}.analytics-page__volume-chart--peak .analytics-page__grid{inset:0}.analytics-page__plot{position:relative;width:100%;min-width:0}.analytics-page__grid{position:absolute;inset:0 0 24px;pointer-events:none;background-image:linear-gradient(to top,var(--border-default) 1px,transparent 1px);background-size:100% 25%;opacity:.45}.analytics-page__bars{display:flex;align-items:flex-end;justify-content:space-between;gap:8px;height:160px;position:relative;z-index:1}.analytics-page__bars--horizontal{flex-direction:column;height:auto;align-items:stretch;gap:10px}.analytics-page__bars--peak{height:100px}.analytics-page__bars--peak .analytics-page__bar{max-width:100%}.analytics-page__bar-col{flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;height:100%;min-width:0;cursor:default}.analytics-page__bar-col:hover .analytics-page__tooltip,.analytics-page__bar-col:focus-within .analytics-page__tooltip{opacity:1}.analytics-page__bar-col--peak{min-width:0}.analytics-page__bar-value{flex-shrink:0;color:var(--text-primary);font-size:11px;font-weight:600;line-height:1}.analytics-page__bar-wrap{position:relative;width:100%;flex:1;display:flex;flex-direction:column;justify-content:flex-end;align-items:center;min-height:0}.analytics-page__bar-stack{width:100%;max-width:32px;display:flex;flex-direction:column;justify-content:flex-end;align-items:stretch;gap:4px;min-height:0}.analytics-page__bar-axis{flex-shrink:0;min-height:16px;color:var(--text-secondary);font-size:10px;text-align:center;line-height:1.2}.analytics-page__tooltip{position:absolute;bottom:calc(100% + 6px);left:50%;transform:translate(-50%);padding:4px 8px;background:var(--gray-900, #18181b);color:#fafafa;font-size:11px;font-weight:500;line-height:1.3;border-radius:4px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .15s ease;z-index:2;box-shadow:0 2px 8px #00000026}.analytics-page__tooltip:after{content:\"\";position:absolute;top:100%;left:50%;transform:translate(-50%);border:4px solid transparent;border-top-color:var(--gray-900, #18181b)}.analytics-page__peak-axis{display:flex;justify-content:space-between;gap:2px;margin-top:8px}.analytics-page__peak-tick{flex:1;text-align:center;min-height:16px;color:var(--text-secondary);font-size:10px}.analytics-page__bar{width:100%;flex:1 1 auto;min-height:3px;background:linear-gradient(180deg,var(--brand-500),var(--violet-500));border-radius:4px 4px 0 0;animation:eds-bar-grow .5s ease forwards;transform-origin:bottom}.analytics-page__hbar-row{display:grid;grid-template-columns:120px 1fr 40px;align-items:center;gap:10px}.analytics-page__hbar-track{height:8px;background:var(--surface-l2);border-radius:4px;overflow:hidden}.analytics-page__hbar-fill{display:block;height:100%;background:var(--brand-500);border-radius:4px;animation:eds-bar-grow .5s ease forwards;transform-origin:left}@media(max-width:1000px){.analytics-page__kpis{grid-template-columns:repeat(2,1fr)}.analytics-page__charts{grid-template-columns:1fr}}\n"], dependencies: [{ kind: "component", type: EdsIconComponent, selector: "eds-icon", inputs: ["name", "size", "fill", "weight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
106
+ }
107
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AnalyticsPageComponent, decorators: [{
108
+ type: Component,
109
+ args: [{ selector: 'eds-analytics-page', imports: [EdsIconComponent], 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\">Call volume, outcomes, and performance KPIs</p>\n </div>\n <div class=\"analytics-page__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 </header>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading analytics\u2026</p>\n } @else if (data()) {\n <section class=\"analytics-page__kpis\">\n @for (kpi of data()!.kpiCards; track kpi.label) {\n <article class=\"eds-card analytics-page__kpi\">\n <eds-icon [name]=\"kpiIcon(kpi.label)\" [size]=\"22\" />\n <span class=\"eds-label\">{{ kpi.label }}</span>\n <span class=\"analytics-page__kpi-value\">{{ kpi.value }}</span>\n @if (kpi.delta) {\n <span class=\"analytics-page__kpi-delta\" [attr.data-tone]=\"kpi.tone\">{{ kpi.delta }}</span>\n }\n @if (kpi.sub) {\n <span class=\"analytics-page__kpi-sub\">{{ kpi.sub }}</span>\n }\n </article>\n }\n </section>\n\n <div class=\"analytics-page__charts\">\n <article class=\"eds-card analytics-page__chart\">\n <div class=\"analytics-page__chart-head\">\n <h2 class=\"eds-h2\">Call volume</h2>\n <span class=\"eds-label\">{{ volumeRangeLabel() }}</span>\n </div>\n <div class=\"analytics-page__volume-chart\">\n <div class=\"analytics-page__plot\">\n <div class=\"analytics-page__grid\" aria-hidden=\"true\"></div>\n <div class=\"analytics-page__bars\" role=\"list\" aria-label=\"Call volume by day\">\n @for (point of data()!.volume.points; track point.date || point.label; let i = $index) {\n <div\n class=\"analytics-page__bar-col\"\n role=\"listitem\"\n [attr.aria-label]=\"volumeTooltip(point)\"\n >\n <div class=\"analytics-page__bar-wrap\">\n @if (point.count > 0) {\n <div\n class=\"analytics-page__bar-stack\"\n [style.height.%]=\"barHeightPercent(point, data()!.volume.points)\"\n >\n @if (showBarValue(point, data()!.volume.points)) {\n <span class=\"analytics-page__bar-value eds-mono-sm\">{{ point.count }}</span>\n }\n <div class=\"analytics-page__bar\"></div>\n </div>\n }\n <span class=\"analytics-page__tooltip\">{{ volumeTooltip(point) }}</span>\n </div>\n <span class=\"eds-mono-sm analytics-page__bar-axis\">\n {{ volumeAxisLabel(point, i, data()!.volume.points.length) }}\n </span>\n </div>\n }\n </div>\n </div>\n </div>\n <!-- <p class=\"analytics-page__axis-note eds-label\">(MM/DD)/calls per day</p> -->\n </article>\n\n <article class=\"eds-card analytics-page__chart\">\n <h2 class=\"eds-h2\">Outcomes</h2>\n <div class=\"analytics-page__bars analytics-page__bars--horizontal\">\n @for (item of data()!.outcomes; track item.label) {\n <div class=\"analytics-page__hbar-row\">\n <span class=\"eds-body-sm\">{{ item.label }}</span>\n <div class=\"analytics-page__hbar-track\">\n <span\n class=\"analytics-page__hbar-fill\"\n [style.width.%]=\"(item.value / maxValue(data()!.outcomes)) * 100\"\n [style.background]=\"item.color\"\n ></span>\n </div>\n <span class=\"eds-mono-sm\">{{ item.value }}%</span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart\">\n <h2 class=\"eds-h2\">Top reasons</h2>\n <div class=\"analytics-page__bars analytics-page__bars--horizontal\">\n @for (item of data()!.reasons; track item.label) {\n <div class=\"analytics-page__hbar-row\">\n <span class=\"eds-body-sm\">{{ item.label }}</span>\n <div class=\"analytics-page__hbar-track\">\n <span\n class=\"analytics-page__hbar-fill\"\n [style.width.%]=\"(item.value / maxValue(data()!.reasons)) * 100\"\n ></span>\n </div>\n <span class=\"eds-mono-sm\">{{ item.value }}%</span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart\">\n <div class=\"analytics-page__chart-head\">\n <h2 class=\"eds-h2\">Peak hours</h2>\n <span class=\"eds-label\">\n @if (data()!.peakHours.timezone) {\n {{ data()!.peakHours.timezone }}\n } @else {\n Calls per hour\n }\n </span>\n </div>\n <div class=\"analytics-page__volume-chart analytics-page__volume-chart--peak\">\n <div class=\"analytics-page__plot\">\n <div class=\"analytics-page__grid\" aria-hidden=\"true\"></div>\n <div class=\"analytics-page__bars analytics-page__bars--peak\" role=\"list\" aria-label=\"Call volume by hour\">\n @for (point of data()!.peakHours.points; track point.hour) {\n <div\n class=\"analytics-page__bar-col analytics-page__bar-col--peak\"\n role=\"listitem\"\n [attr.aria-label]=\"peakTooltip(point)\"\n >\n <div class=\"analytics-page__bar-wrap\">\n @if (point.count > 0) {\n <div\n class=\"analytics-page__bar-stack\"\n [style.height.%]=\"barHeightPercent(point, data()!.peakHours.points)\"\n >\n <div class=\"analytics-page__bar\"></div>\n </div>\n }\n <span class=\"analytics-page__tooltip\">{{ peakTooltip(point) }}</span>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n <div class=\"analytics-page__peak-axis\">\n @for (point of data()!.peakHours.points; track point.hour; let i = $index) {\n <span class=\"analytics-page__peak-tick eds-mono-sm\">\n @if (showPeakAxisLabel(i, data()!.peakHours.points.length)) {\n {{ point.label }}\n }\n </span>\n }\n </div>\n </article>\n </div>\n }\n</div>\n", styles: [".analytics-page__header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:20px}.analytics-page__range{display:flex;gap:4px;background:var(--surface-l0);border:1px solid var(--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(--text-secondary)}.analytics-page__range-btn--active{background:var(--brand-500);color:#fff}.analytics-page__kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:20px}.analytics-page__kpi{display:flex;flex-direction:column;gap:6px;padding:16px}.analytics-page__kpi-value{font-size:24px;font-weight:600;color:var(--text-primary)}.analytics-page__kpi-delta{font-size:12px;font-weight:600}.analytics-page__kpi-delta[data-tone=pos]{color:var(--green-500, #16a34a)}.analytics-page__kpi-delta[data-tone=warn]{color:var(--amber-500, #d97706)}.analytics-page__kpi-delta[data-tone=neutral]{color:var(--text-secondary)}.analytics-page__kpi-sub{font-size:12px;color:var(--text-secondary)}.analytics-page__charts{display:grid;grid-template-columns:1fr 1fr;gap:16px}.analytics-page__chart{padding:16px}.analytics-page__chart .eds-h2{margin-bottom:0}.analytics-page__chart-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:16px;flex-wrap:wrap}.analytics-page__volume-chart--peak .analytics-page__grid{inset:0}.analytics-page__plot{position:relative;width:100%;min-width:0}.analytics-page__grid{position:absolute;inset:0 0 24px;pointer-events:none;background-image:linear-gradient(to top,var(--border-default) 1px,transparent 1px);background-size:100% 25%;opacity:.45}.analytics-page__bars{display:flex;align-items:flex-end;justify-content:space-between;gap:8px;height:160px;position:relative;z-index:1}.analytics-page__bars--horizontal{flex-direction:column;height:auto;align-items:stretch;gap:10px}.analytics-page__bars--peak{height:100px}.analytics-page__bars--peak .analytics-page__bar{max-width:100%}.analytics-page__bar-col{flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;height:100%;min-width:0;cursor:default}.analytics-page__bar-col:hover .analytics-page__tooltip,.analytics-page__bar-col:focus-within .analytics-page__tooltip{opacity:1}.analytics-page__bar-col--peak{min-width:0}.analytics-page__bar-value{flex-shrink:0;color:var(--text-primary);font-size:11px;font-weight:600;line-height:1}.analytics-page__bar-wrap{position:relative;width:100%;flex:1;display:flex;flex-direction:column;justify-content:flex-end;align-items:center;min-height:0}.analytics-page__bar-stack{width:100%;max-width:32px;display:flex;flex-direction:column;justify-content:flex-end;align-items:stretch;gap:4px;min-height:0}.analytics-page__bar-axis{flex-shrink:0;min-height:16px;color:var(--text-secondary);font-size:10px;text-align:center;line-height:1.2}.analytics-page__tooltip{position:absolute;bottom:calc(100% + 6px);left:50%;transform:translate(-50%);padding:4px 8px;background:var(--gray-900, #18181b);color:#fafafa;font-size:11px;font-weight:500;line-height:1.3;border-radius:4px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .15s ease;z-index:2;box-shadow:0 2px 8px #00000026}.analytics-page__tooltip:after{content:\"\";position:absolute;top:100%;left:50%;transform:translate(-50%);border:4px solid transparent;border-top-color:var(--gray-900, #18181b)}.analytics-page__peak-axis{display:flex;justify-content:space-between;gap:2px;margin-top:8px}.analytics-page__peak-tick{flex:1;text-align:center;min-height:16px;color:var(--text-secondary);font-size:10px}.analytics-page__bar{width:100%;flex:1 1 auto;min-height:3px;background:linear-gradient(180deg,var(--brand-500),var(--violet-500));border-radius:4px 4px 0 0;animation:eds-bar-grow .5s ease forwards;transform-origin:bottom}.analytics-page__hbar-row{display:grid;grid-template-columns:120px 1fr 40px;align-items:center;gap:10px}.analytics-page__hbar-track{height:8px;background:var(--surface-l2);border-radius:4px;overflow:hidden}.analytics-page__hbar-fill{display:block;height:100%;background:var(--brand-500);border-radius:4px;animation:eds-bar-grow .5s ease forwards;transform-origin:left}@media(max-width:1000px){.analytics-page__kpis{grid-template-columns:repeat(2,1fr)}.analytics-page__charts{grid-template-columns:1fr}}\n"] }]
110
+ }] });
111
+
112
+ export { AnalyticsPageComponent };
113
+ //# sourceMappingURL=edvak99-voice-receptionist-widget-analytics-page.component-Cg15RZEY.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edvak99-voice-receptionist-widget-analytics-page.component-Cg15RZEY.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, inject, OnInit, signal } from '@angular/core';\n\nimport {\n AnalyticsData,\n AnalyticsPeakPoint,\n AnalyticsVolumePoint,\n} from '../../models/ai-receptionist.types';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport { EdsIconComponent } from '../../ui/eds-icon.component';\n\n@Component({\n selector: 'eds-analytics-page',\n imports: [EdsIconComponent],\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\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 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 /** Hide inline counts when bars are too narrow to stay readable. */\n protected showBarValue(point: { count: number }, points: { count: number }[]): boolean {\n return point.count > 0 && points.length <= 21;\n }\n\n protected maxValue(items: { value: number }[]): number {\n return Math.max(...items.map((i) => i.value), 1);\n }\n\n protected volumeTooltip(point: AnalyticsVolumePoint): string {\n const datePart = point.date ? this.formatChartDate(point.date) : point.label;\n return `${datePart}: ${point.count} call${point.count === 1 ? '' : 's'}`;\n }\n\n protected peakTooltip(point: AnalyticsPeakPoint): string {\n return `${point.label}: ${point.count} call${point.count === 1 ? '' : 's'}`;\n }\n\n /** ISO or YYYY-MM-DD → MM/DD (zero-padded). */\n protected formatChartDate(date: string): string {\n const iso = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(date.trim());\n if (iso) {\n return `${iso[2]}/${iso[3]}`;\n }\n return date;\n }\n\n /** Compact x-axis for volume — full labels on 7d, sparse dates on longer ranges. */\n protected volumeAxisLabel(point: AnalyticsVolumePoint, index: number, total: number): string {\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 if (total <= 7) return point.day;\n const step = total <= 14 ? 2 : total <= 31 ? 5 : 7;\n if (index % step !== 0 && index !== total - 1) return '';\n return point.day;\n }\n\n protected showPeakAxisLabel(index: number, total: number): boolean {\n if (total <= 12) return true;\n const step = total <= 24 ? 3 : 4;\n return index % step === 0 || index === total - 1;\n }\n\n protected volumeRangeLabel(): string {\n const vol = this.data()?.volume;\n if (!vol?.startDate || !vol?.endDate) return 'Calls per day';\n const start = this.formatChartDate(vol.startDate);\n const end = this.formatChartDate(vol.endDate);\n return `${start} – ${end}${vol.timezone ? ` (${vol.timezone})` : ''}`;\n }\n\n protected kpiIcon(label: string): string {\n const key = label.toLowerCase();\n if (key.includes('call') || key.includes('handled')) return 'call';\n if (key.includes('time') || key.includes('duration')) return 'schedule';\n if (key.includes('resolution') || key.includes('book')) return 'event_available';\n if (key.includes('transfer')) return 'phone_forwarded';\n return 'monitoring';\n }\n\n private load(): void {\n this.loading.set(true);\n this.api.getAnalytics(this.range()).subscribe(({ data }) => {\n this.data.set(data);\n this.loading.set(false);\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\">Call volume, outcomes, and performance KPIs</p>\n </div>\n <div class=\"analytics-page__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 </header>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading analytics…</p>\n } @else if (data()) {\n <section class=\"analytics-page__kpis\">\n @for (kpi of data()!.kpiCards; track kpi.label) {\n <article class=\"eds-card analytics-page__kpi\">\n <eds-icon [name]=\"kpiIcon(kpi.label)\" [size]=\"22\" />\n <span class=\"eds-label\">{{ kpi.label }}</span>\n <span class=\"analytics-page__kpi-value\">{{ kpi.value }}</span>\n @if (kpi.delta) {\n <span class=\"analytics-page__kpi-delta\" [attr.data-tone]=\"kpi.tone\">{{ kpi.delta }}</span>\n }\n @if (kpi.sub) {\n <span class=\"analytics-page__kpi-sub\">{{ kpi.sub }}</span>\n }\n </article>\n }\n </section>\n\n <div class=\"analytics-page__charts\">\n <article class=\"eds-card analytics-page__chart\">\n <div class=\"analytics-page__chart-head\">\n <h2 class=\"eds-h2\">Call volume</h2>\n <span class=\"eds-label\">{{ volumeRangeLabel() }}</span>\n </div>\n <div class=\"analytics-page__volume-chart\">\n <div class=\"analytics-page__plot\">\n <div class=\"analytics-page__grid\" aria-hidden=\"true\"></div>\n <div class=\"analytics-page__bars\" role=\"list\" aria-label=\"Call volume by day\">\n @for (point of data()!.volume.points; track point.date || point.label; let i = $index) {\n <div\n class=\"analytics-page__bar-col\"\n role=\"listitem\"\n [attr.aria-label]=\"volumeTooltip(point)\"\n >\n <div class=\"analytics-page__bar-wrap\">\n @if (point.count > 0) {\n <div\n class=\"analytics-page__bar-stack\"\n [style.height.%]=\"barHeightPercent(point, data()!.volume.points)\"\n >\n @if (showBarValue(point, data()!.volume.points)) {\n <span class=\"analytics-page__bar-value eds-mono-sm\">{{ point.count }}</span>\n }\n <div class=\"analytics-page__bar\"></div>\n </div>\n }\n <span class=\"analytics-page__tooltip\">{{ volumeTooltip(point) }}</span>\n </div>\n <span class=\"eds-mono-sm analytics-page__bar-axis\">\n {{ volumeAxisLabel(point, i, data()!.volume.points.length) }}\n </span>\n </div>\n }\n </div>\n </div>\n </div>\n <!-- <p class=\"analytics-page__axis-note eds-label\">(MM/DD)/calls per day</p> -->\n </article>\n\n <article class=\"eds-card analytics-page__chart\">\n <h2 class=\"eds-h2\">Outcomes</h2>\n <div class=\"analytics-page__bars analytics-page__bars--horizontal\">\n @for (item of data()!.outcomes; track item.label) {\n <div class=\"analytics-page__hbar-row\">\n <span class=\"eds-body-sm\">{{ item.label }}</span>\n <div class=\"analytics-page__hbar-track\">\n <span\n class=\"analytics-page__hbar-fill\"\n [style.width.%]=\"(item.value / maxValue(data()!.outcomes)) * 100\"\n [style.background]=\"item.color\"\n ></span>\n </div>\n <span class=\"eds-mono-sm\">{{ item.value }}%</span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart\">\n <h2 class=\"eds-h2\">Top reasons</h2>\n <div class=\"analytics-page__bars analytics-page__bars--horizontal\">\n @for (item of data()!.reasons; track item.label) {\n <div class=\"analytics-page__hbar-row\">\n <span class=\"eds-body-sm\">{{ item.label }}</span>\n <div class=\"analytics-page__hbar-track\">\n <span\n class=\"analytics-page__hbar-fill\"\n [style.width.%]=\"(item.value / maxValue(data()!.reasons)) * 100\"\n ></span>\n </div>\n <span class=\"eds-mono-sm\">{{ item.value }}%</span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart\">\n <div class=\"analytics-page__chart-head\">\n <h2 class=\"eds-h2\">Peak hours</h2>\n <span class=\"eds-label\">\n @if (data()!.peakHours.timezone) {\n {{ data()!.peakHours.timezone }}\n } @else {\n Calls per hour\n }\n </span>\n </div>\n <div class=\"analytics-page__volume-chart analytics-page__volume-chart--peak\">\n <div class=\"analytics-page__plot\">\n <div class=\"analytics-page__grid\" aria-hidden=\"true\"></div>\n <div class=\"analytics-page__bars analytics-page__bars--peak\" role=\"list\" aria-label=\"Call volume by hour\">\n @for (point of data()!.peakHours.points; track point.hour) {\n <div\n class=\"analytics-page__bar-col analytics-page__bar-col--peak\"\n role=\"listitem\"\n [attr.aria-label]=\"peakTooltip(point)\"\n >\n <div class=\"analytics-page__bar-wrap\">\n @if (point.count > 0) {\n <div\n class=\"analytics-page__bar-stack\"\n [style.height.%]=\"barHeightPercent(point, data()!.peakHours.points)\"\n >\n <div class=\"analytics-page__bar\"></div>\n </div>\n }\n <span class=\"analytics-page__tooltip\">{{ peakTooltip(point) }}</span>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n <div class=\"analytics-page__peak-axis\">\n @for (point of data()!.peakHours.points; track point.hour; let i = $index) {\n <span class=\"analytics-page__peak-tick eds-mono-sm\">\n @if (showPeakAxisLabel(i, data()!.peakHours.points.length)) {\n {{ point.label }}\n }\n </span>\n }\n </div>\n </article>\n </div>\n }\n</div>\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, signal, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { DatePipe } from '@angular/common';
4
+ import * as i1 from '@angular/forms';
5
+ import { FormsModule } from '@angular/forms';
6
+ import { A as AiReceptionistApiService, E as EdsButtonComponent, b as EdsIconComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DvJ2Ygc1.mjs';
7
+
8
+ class AuditPageComponent {
9
+ constructor() {
10
+ this.api = inject(AiReceptionistApiService);
11
+ this.events = signal([]);
12
+ this.search = signal('');
13
+ this.actor = signal('');
14
+ this.loading = signal(true);
15
+ }
16
+ ngOnInit() {
17
+ this.load();
18
+ }
19
+ load() {
20
+ this.loading.set(true);
21
+ this.api.getAudit(this.search() || undefined).subscribe(({ data }) => {
22
+ this.events.set(data);
23
+ this.loading.set(false);
24
+ });
25
+ }
26
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AuditPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
27
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: AuditPageComponent, isStandalone: true, selector: "eds-audit-page", ngImport: i0, template: "<div class=\"eds-page audit-page\">\n <header class=\"audit-page__header\">\n <div>\n <h1 class=\"eds-h1\">Audit log</h1>\n <p class=\"eds-body-sm\">Track configuration and agent activity</p>\n </div>\n </header>\n\n <div class=\"eds-card audit-page__filters\">\n <input\n type=\"search\"\n class=\"audit-page__input\"\n placeholder=\"Search action, target, detail\u2026\"\n [ngModel]=\"search()\"\n (ngModelChange)=\"search.set($event)\"\n (keyup.enter)=\"load()\"\n />\n <input\n type=\"text\"\n class=\"audit-page__input\"\n placeholder=\"Filter by actor\"\n [ngModel]=\"actor()\"\n (ngModelChange)=\"actor.set($event)\"\n (keyup.enter)=\"load()\"\n />\n <eds-button variant=\"primary\" (click)=\"load()\">\n <eds-icon name=\"search\" [size]=\"18\" /> Search\n </eds-button>\n </div>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading audit events\u2026</p>\n } @else {\n <div class=\"eds-card audit-page__table-wrap\">\n <table class=\"eds-table\">\n <thead>\n <tr>\n <th>When</th>\n <th>Actor</th>\n <th>Action</th>\n <th>Target</th>\n <th>Detail</th>\n </tr>\n </thead>\n <tbody>\n @for (event of events(); track event.id) {\n <tr>\n <td class=\"eds-mono-sm\">{{ event.at | date: 'medium' }}</td>\n <td>{{ event.actor }}</td>\n <td class=\"eds-body-sm-bold\">{{ event.action }}</td>\n <td class=\"eds-mono-sm\">{{ event.target }}</td>\n <td>{{ event.detail }}</td>\n </tr>\n } @empty {\n <tr>\n <td colspan=\"5\" class=\"audit-page__empty\">No events match your filters.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n</div>\n", styles: [".audit-page__header{margin-bottom:20px}.audit-page__filters{display:flex;flex-wrap:wrap;gap:10px;padding:14px;margin-bottom:16px;align-items:center}.audit-page__input{flex:1;min-width:180px;padding:8px 12px;border:1px solid var(--border-default);border-radius:var(--eds-radius-md);font-size:14px}.audit-page__table-wrap{overflow-x:auto;padding:0}.audit-page__empty{text-align:center;padding:32px!important;color:var(--text-secondary)}\n"], dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: EdsButtonComponent, selector: "eds-button", inputs: ["variant", "type", "disabled"] }, { kind: "component", type: EdsIconComponent, selector: "eds-icon", inputs: ["name", "size", "fill", "weight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
28
+ }
29
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AuditPageComponent, decorators: [{
30
+ type: Component,
31
+ args: [{ selector: 'eds-audit-page', imports: [DatePipe, FormsModule, EdsButtonComponent, EdsIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"eds-page audit-page\">\n <header class=\"audit-page__header\">\n <div>\n <h1 class=\"eds-h1\">Audit log</h1>\n <p class=\"eds-body-sm\">Track configuration and agent activity</p>\n </div>\n </header>\n\n <div class=\"eds-card audit-page__filters\">\n <input\n type=\"search\"\n class=\"audit-page__input\"\n placeholder=\"Search action, target, detail\u2026\"\n [ngModel]=\"search()\"\n (ngModelChange)=\"search.set($event)\"\n (keyup.enter)=\"load()\"\n />\n <input\n type=\"text\"\n class=\"audit-page__input\"\n placeholder=\"Filter by actor\"\n [ngModel]=\"actor()\"\n (ngModelChange)=\"actor.set($event)\"\n (keyup.enter)=\"load()\"\n />\n <eds-button variant=\"primary\" (click)=\"load()\">\n <eds-icon name=\"search\" [size]=\"18\" /> Search\n </eds-button>\n </div>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading audit events\u2026</p>\n } @else {\n <div class=\"eds-card audit-page__table-wrap\">\n <table class=\"eds-table\">\n <thead>\n <tr>\n <th>When</th>\n <th>Actor</th>\n <th>Action</th>\n <th>Target</th>\n <th>Detail</th>\n </tr>\n </thead>\n <tbody>\n @for (event of events(); track event.id) {\n <tr>\n <td class=\"eds-mono-sm\">{{ event.at | date: 'medium' }}</td>\n <td>{{ event.actor }}</td>\n <td class=\"eds-body-sm-bold\">{{ event.action }}</td>\n <td class=\"eds-mono-sm\">{{ event.target }}</td>\n <td>{{ event.detail }}</td>\n </tr>\n } @empty {\n <tr>\n <td colspan=\"5\" class=\"audit-page__empty\">No events match your filters.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n</div>\n", styles: [".audit-page__header{margin-bottom:20px}.audit-page__filters{display:flex;flex-wrap:wrap;gap:10px;padding:14px;margin-bottom:16px;align-items:center}.audit-page__input{flex:1;min-width:180px;padding:8px 12px;border:1px solid var(--border-default);border-radius:var(--eds-radius-md);font-size:14px}.audit-page__table-wrap{overflow-x:auto;padding:0}.audit-page__empty{text-align:center;padding:32px!important;color:var(--text-secondary)}\n"] }]
32
+ }] });
33
+
34
+ export { AuditPageComponent };
35
+ //# sourceMappingURL=edvak99-voice-receptionist-widget-audit-page.component-DVQyKh_n.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edvak99-voice-receptionist-widget-audit-page.component-DVQyKh_n.mjs","sources":["../../../projects/voice-receptionist-widget/src/lib/pages/audit-page/audit-page.component.ts","../../../projects/voice-receptionist-widget/src/lib/pages/audit-page/audit-page.component.html"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\n\nimport { AuditEvent } 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\n@Component({\n selector: 'eds-audit-page',\n imports: [DatePipe, FormsModule, EdsButtonComponent, EdsIconComponent],\n templateUrl: './audit-page.component.html',\n styleUrl: './audit-page.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class AuditPageComponent implements OnInit {\n private readonly api = inject(AiReceptionistApiService);\n\n protected readonly events = signal<AuditEvent[]>([]);\n protected readonly search = signal('');\n protected readonly actor = signal('');\n protected readonly loading = signal(true);\n\n ngOnInit(): void {\n this.load();\n }\n\n protected load(): void {\n this.loading.set(true);\n this.api.getAudit(this.search() || undefined).subscribe(({ data }) => {\n this.events.set(data);\n this.loading.set(false);\n });\n }\n}\n","<div class=\"eds-page audit-page\">\n <header class=\"audit-page__header\">\n <div>\n <h1 class=\"eds-h1\">Audit log</h1>\n <p class=\"eds-body-sm\">Track configuration and agent activity</p>\n </div>\n </header>\n\n <div class=\"eds-card audit-page__filters\">\n <input\n type=\"search\"\n class=\"audit-page__input\"\n placeholder=\"Search action, target, detail…\"\n [ngModel]=\"search()\"\n (ngModelChange)=\"search.set($event)\"\n (keyup.enter)=\"load()\"\n />\n <input\n type=\"text\"\n class=\"audit-page__input\"\n placeholder=\"Filter by actor\"\n [ngModel]=\"actor()\"\n (ngModelChange)=\"actor.set($event)\"\n (keyup.enter)=\"load()\"\n />\n <eds-button variant=\"primary\" (click)=\"load()\">\n <eds-icon name=\"search\" [size]=\"18\" /> Search\n </eds-button>\n </div>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading audit events…</p>\n } @else {\n <div class=\"eds-card audit-page__table-wrap\">\n <table class=\"eds-table\">\n <thead>\n <tr>\n <th>When</th>\n <th>Actor</th>\n <th>Action</th>\n <th>Target</th>\n <th>Detail</th>\n </tr>\n </thead>\n <tbody>\n @for (event of events(); track event.id) {\n <tr>\n <td class=\"eds-mono-sm\">{{ event.at | date: 'medium' }}</td>\n <td>{{ event.actor }}</td>\n <td class=\"eds-body-sm-bold\">{{ event.action }}</td>\n <td class=\"eds-mono-sm\">{{ event.target }}</td>\n <td>{{ event.detail }}</td>\n </tr>\n } @empty {\n <tr>\n <td colspan=\"5\" class=\"audit-page__empty\">No events match your filters.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n</div>\n"],"names":[],"mappings":";;;;;;;MAgBa,kBAAkB,CAAA;AAP/B,IAAA,WAAA,GAAA;AAQmB,QAAA,IAAA,CAAA,GAAG,GAAG,MAAM,CAAC,wBAAwB,CAAC;AAEpC,QAAA,IAAA,CAAA,MAAM,GAAG,MAAM,CAAe,EAAE,CAAC;AACjC,QAAA,IAAA,CAAA,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;AACnB,QAAA,IAAA,CAAA,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC;AAClB,QAAA,IAAA,CAAA,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC;AAa1C,IAAA;IAXC,QAAQ,GAAA;QACN,IAAI,CAAC,IAAI,EAAE;IACb;IAEU,IAAI,GAAA;AACZ,QAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,SAAS,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,KAAI;AACnE,YAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;AACrB,YAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;AACzB,QAAA,CAAC,CAAC;IACJ;+GAlBW,kBAAkB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;mGAAlB,kBAAkB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,gBAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EChB/B,i4DA+DA,EAAA,MAAA,EAAA,CAAA,sbAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EDpDc,QAAQ,4CAAE,WAAW,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,oBAAA,EAAA,QAAA,EAAA,8MAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,eAAA,EAAA,QAAA,EAAA,2CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,OAAA,EAAA,QAAA,EAAA,qDAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,UAAA,EAAA,SAAA,EAAA,gBAAA,CAAA,EAAA,OAAA,EAAA,CAAA,eAAA,CAAA,EAAA,QAAA,EAAA,CAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,kBAAkB,EAAA,QAAA,EAAA,YAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,MAAA,EAAA,UAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,gBAAgB,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,MAAA,EAAA,MAAA,EAAA,QAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA,CAAA;;4FAK5D,kBAAkB,EAAA,UAAA,EAAA,CAAA;kBAP9B,SAAS;AACI,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,gBAAgB,EAAA,OAAA,EACjB,CAAC,QAAQ,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,CAAC,EAAA,eAAA,EAGrD,uBAAuB,CAAC,MAAM,EAAA,QAAA,EAAA,i4DAAA,EAAA,MAAA,EAAA,CAAA,sbAAA,CAAA,EAAA;;;;;"}