@edvak99/voice-receptionist-widget 1.0.0 → 1.1.0

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 (124) hide show
  1. package/README.md +56 -43
  2. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-CoVjr5Y3.mjs +113 -0
  3. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-CoVjr5Y3.mjs.map +1 -0
  4. package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-ou9oxnJp.mjs +35 -0
  5. package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-ou9oxnJp.mjs.map +1 -0
  6. package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-BV7KOhd9.mjs +316 -0
  7. package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-BV7KOhd9.mjs.map +1 -0
  8. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-CWWma-6r.mjs +138 -0
  9. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-CWWma-6r.mjs.map +1 -0
  10. package/fesm2022/edvak99-voice-receptionist-widget-call-recording-player.component-B2IuwsVH.mjs +110 -0
  11. package/fesm2022/edvak99-voice-receptionist-widget-call-recording-player.component-B2IuwsVH.mjs.map +1 -0
  12. package/fesm2022/edvak99-voice-receptionist-widget-call-review.service-D-GutOar.mjs +29 -0
  13. package/fesm2022/edvak99-voice-receptionist-widget-call-review.service-D-GutOar.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-B93giErR.mjs +248 -0
  17. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-B93giErR.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-BrMNsxB5.mjs +136 -0
  21. package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-BrMNsxB5.mjs.map +1 -0
  22. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DClMfxeY.mjs +2666 -0
  23. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DClMfxeY.mjs.map +1 -0
  24. package/fesm2022/edvak99-voice-receptionist-widget-knowledge-hub.page-BNsM-o3z.mjs +575 -0
  25. package/fesm2022/edvak99-voice-receptionist-widget-knowledge-hub.page-BNsM-o3z.mjs.map +1 -0
  26. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-list.service-C2UZJ5wF.mjs +120 -0
  27. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-list.service-C2UZJ5wF.mjs.map +1 -0
  28. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-DQTVJjsP.mjs +115 -0
  29. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-DQTVJjsP.mjs.map +1 -0
  30. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-C3iTtVdn.mjs +574 -0
  31. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-C3iTtVdn.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-C10N-x5g.mjs +265 -0
  35. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-C10N-x5g.mjs.map +1 -0
  36. package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-BBE_H2jb.mjs +509 -0
  37. package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-BBE_H2jb.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-COnYcaMT.mjs +237 -0
  41. package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-COnYcaMT.mjs.map +1 -0
  42. package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-Box7X-Cq.mjs +45 -0
  43. package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-Box7X-Cq.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-rail.component.d.ts +6 -0
  66. package/lib/layout/eds-app-shell.component.d.ts +5 -0
  67. package/lib/layout/eds-module-nav.component.d.ts +24 -0
  68. package/lib/layout/eds-topbar.component.d.ts +29 -0
  69. package/lib/mocks/api-log.seed.d.ts +5 -0
  70. package/lib/mocks/outbound-purpose.seed.d.ts +3 -0
  71. package/lib/mocks/seed-data.d.ts +38 -0
  72. package/lib/models/ai-receptionist.types.d.ts +257 -0
  73. package/lib/models/api-dto.types.d.ts +215 -0
  74. package/lib/models/api-log.types.d.ts +53 -0
  75. package/lib/models/live-transcript.types.d.ts +26 -0
  76. package/lib/models/outbound-purpose.types.d.ts +50 -0
  77. package/lib/pages/analytics-page/analytics-page.component.d.ts +44 -0
  78. package/lib/pages/audit-page/audit-page.component.d.ts +14 -0
  79. package/lib/pages/call-history-detail-page/call-history-detail-page.component.d.ts +25 -0
  80. package/lib/pages/call-history-page/call-history-page.component.d.ts +42 -0
  81. package/lib/pages/dashboard-page/dashboard-page.component.d.ts +60 -0
  82. package/lib/pages/live-calls-page/live-calls-page.component.d.ts +37 -0
  83. package/lib/pages/live-monitor-page/live-monitor-page.component.d.ts +43 -0
  84. package/lib/pages/outbound-purposes-page/outbound-purposes-page.component.d.ts +24 -0
  85. package/lib/pages/settings-page/settings-fields.config.d.ts +35 -0
  86. package/lib/pages/settings-page/settings-page.component.d.ts +50 -0
  87. package/lib/pages/settings-page/tts-voice-presets.d.ts +7 -0
  88. package/lib/pages/tasks-page/tasks-page.component.d.ts +35 -0
  89. package/lib/pages/wizard-page/wizard-page.component.d.ts +17 -0
  90. package/lib/pipes/eds-elapsed.pipe.d.ts +7 -0
  91. package/lib/provide-voice-receptionist.d.ts +6 -0
  92. package/lib/routes.d.ts +8 -0
  93. package/lib/services/agent-online.service.d.ts +29 -0
  94. package/lib/services/ai-receptionist-api.mappers.d.ts +45 -0
  95. package/lib/services/ai-receptionist-api.mock.d.ts +150 -0
  96. package/lib/services/ai-receptionist-api.service.d.ts +156 -0
  97. package/lib/services/call-review.service.d.ts +12 -0
  98. package/lib/services/dashboard-socket.service.d.ts +26 -0
  99. package/lib/services/live-calls-list.service.d.ts +29 -0
  100. package/lib/services/live-transcript.service.d.ts +39 -0
  101. package/lib/shared/copy-button.component.d.ts +14 -0
  102. package/lib/shared/modal.component.d.ts +20 -0
  103. package/lib/shared/toast-container.component.d.ts +7 -0
  104. package/lib/shared/toast.service.d.ts +16 -0
  105. package/lib/ui/eds-avatar.component.d.ts +8 -0
  106. package/lib/ui/eds-badge.component.d.ts +7 -0
  107. package/lib/ui/eds-button.component.d.ts +9 -0
  108. package/lib/ui/eds-icon.component.d.ts +10 -0
  109. package/lib/ui/eds-pulse-dot.component.d.ts +5 -0
  110. package/lib/ui/eds-waveform.component.d.ts +8 -0
  111. package/lib/utils/call-outcome.util.d.ts +8 -0
  112. package/lib/utils/call-review.util.d.ts +8 -0
  113. package/lib/utils/outbound-purpose.util.d.ts +14 -0
  114. package/lib/utils/task.util.d.ts +10 -0
  115. package/lib/utils/tool-labels.util.d.ts +14 -0
  116. package/lib/utils/transcript-timing.util.d.ts +17 -0
  117. package/lib/utils/welcome-message.util.d.ts +5 -0
  118. package/package.json +12 -5
  119. package/public-api.d.ts +6 -2
  120. package/styles/_index.scss +55 -0
  121. package/styles/chat-ui.scss +133 -0
  122. package/styles/eds.scss +328 -0
  123. package/lib/voice-receptionist-widget.component.d.ts +0 -5
  124. package/lib/voice-receptionist-widget.service.d.ts +0 -6
@@ -0,0 +1,248 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, EventEmitter, signal, Output, Input, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { Router, ActivatedRoute, RouterLink } from '@angular/router';
4
+ import { DatePipe } from '@angular/common';
5
+ import { A as AiReceptionistApiService, T as ToastService, M as ModalComponent, E as EdsButtonComponent, a as EdsBadgeComponent, b as EdsIconComponent, c as EdsPulseDotComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DClMfxeY.mjs';
6
+ import { L as LiveCallsListService } from './edvak99-voice-receptionist-widget-live-calls-list.service-C2UZJ5wF.mjs';
7
+ import { o as outcomeBadgeTone, f as formatOutcomeLabel, r as reviewLabel, a as reviewBadgeTone, i as isNeedsReview } from './edvak99-voice-receptionist-widget-call-review.util-DQLIw7xb.mjs';
8
+ import * as i1 from '@angular/forms';
9
+ import { FormsModule } from '@angular/forms';
10
+ import { r as requiredFieldLabel, a as renderOpeningPreview, p as purposeShowsCustomScript, i as isCustomScriptRequired, b as isPurposeFormValid } from './edvak99-voice-receptionist-widget-outbound-purpose.util-BQECIuJu.mjs';
11
+ import { E as EdsAvatarComponent } from './edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs';
12
+ import { E as EdsElapsedPipe } from './edvak99-voice-receptionist-widget-eds-elapsed.pipe-BrMNsxB5.mjs';
13
+
14
+ const E164_PATTERN = /^\+[1-9]\d{6,14}$/;
15
+ class OutboundDialModalComponent {
16
+ constructor() {
17
+ this.api = inject(AiReceptionistApiService);
18
+ this.toast = inject(ToastService);
19
+ this.open = false;
20
+ this.closed = new EventEmitter();
21
+ this.dialed = new EventEmitter();
22
+ this.purposes = signal([]);
23
+ this.loading = signal(true);
24
+ this.submitting = signal(false);
25
+ this.serverError = signal(null);
26
+ this.phone = '';
27
+ this.name = '';
28
+ this.purposeId = '';
29
+ this.appointmentDate = '';
30
+ this.appointmentTime = '';
31
+ this.providerName = '';
32
+ this.customScript = '';
33
+ this.requiredFieldLabel = requiredFieldLabel;
34
+ }
35
+ ngOnChanges(changes) {
36
+ if (changes['open']?.currentValue) {
37
+ this.resetForm();
38
+ this.loadPurposes();
39
+ }
40
+ }
41
+ get selectedPurpose() {
42
+ return this.purposes().find((p) => p.purposeId === this.purposeId) ?? null;
43
+ }
44
+ get previewText() {
45
+ const p = this.selectedPurpose;
46
+ if (!p)
47
+ return '—';
48
+ return renderOpeningPreview(p.sampleOpening, p.openingTemplate, p.placeholders ?? []);
49
+ }
50
+ showsField(field) {
51
+ return this.selectedPurpose?.requiredFields.includes(field) ?? false;
52
+ }
53
+ showsCustomScript() {
54
+ const p = this.selectedPurpose;
55
+ return p ? purposeShowsCustomScript(p) : false;
56
+ }
57
+ customScriptRequired() {
58
+ const p = this.selectedPurpose;
59
+ return p ? isCustomScriptRequired(p) : false;
60
+ }
61
+ canPlaceCall() {
62
+ if (!E164_PATTERN.test(this.phone.trim()))
63
+ return false;
64
+ return isPurposeFormValid(this.selectedPurpose, {
65
+ phone: this.phone,
66
+ name: this.name,
67
+ appointmentDate: this.appointmentDate,
68
+ appointmentTime: this.appointmentTime,
69
+ providerName: this.providerName,
70
+ customScript: this.customScript,
71
+ });
72
+ }
73
+ onPurposeChange() {
74
+ this.appointmentDate = '';
75
+ this.appointmentTime = '';
76
+ this.providerName = '';
77
+ this.customScript = '';
78
+ this.serverError.set(null);
79
+ }
80
+ close() {
81
+ this.closed.emit();
82
+ }
83
+ submit() {
84
+ this.serverError.set(null);
85
+ if (!this.canPlaceCall() || !this.selectedPurpose)
86
+ return;
87
+ this.submitting.set(true);
88
+ const body = {
89
+ phone: this.phone.trim(),
90
+ name: this.name.trim(),
91
+ purpose: this.purposeId,
92
+ ...(this.appointmentDate ? { appointmentDate: this.appointmentDate } : {}),
93
+ ...(this.appointmentTime ? { appointmentTime: this.appointmentTime } : {}),
94
+ ...(this.providerName ? { providerName: this.providerName.trim() } : {}),
95
+ ...(this.customScript ? { customScript: this.customScript.trim() } : {}),
96
+ };
97
+ this.api.placeOutboundCall(body).subscribe({
98
+ next: (res) => {
99
+ this.submitting.set(false);
100
+ const msg = res.message ?? 'Outbound call started.';
101
+ this.toast.show(msg, 'success', 5000);
102
+ this.dialed.emit();
103
+ this.close();
104
+ },
105
+ error: (err) => {
106
+ this.submitting.set(false);
107
+ const msg = err?.message ?? 'Failed to place outbound call.';
108
+ this.serverError.set(msg);
109
+ this.toast.show(msg, 'error', 5000);
110
+ },
111
+ });
112
+ }
113
+ resetForm() {
114
+ this.phone = '';
115
+ this.name = '';
116
+ this.purposeId = '';
117
+ this.appointmentDate = '';
118
+ this.appointmentTime = '';
119
+ this.providerName = '';
120
+ this.customScript = '';
121
+ this.serverError.set(null);
122
+ this.submitting.set(false);
123
+ }
124
+ loadPurposes() {
125
+ this.loading.set(true);
126
+ this.api.getOutboundPurposes(false).subscribe({
127
+ next: ({ purposes }) => {
128
+ this.purposes.set(purposes);
129
+ if (purposes.length && !this.purposeId) {
130
+ this.purposeId = purposes[0].purposeId;
131
+ }
132
+ this.loading.set(false);
133
+ },
134
+ error: () => {
135
+ this.loading.set(false);
136
+ this.serverError.set('Failed to load outbound purposes.');
137
+ },
138
+ });
139
+ }
140
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: OutboundDialModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
141
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: OutboundDialModalComponent, isStandalone: true, selector: "eds-outbound-dial-modal", inputs: { open: "open" }, outputs: { closed: "closed", dialed: "dialed" }, usesOnChanges: true, ngImport: i0, template: "@if (open) {\n <app-modal\n title=\"Outbound call\"\n subtitle=\"Place a patient call using a purpose template\"\n maxWidth=\"560px\"\n (closed)=\"close()\"\n >\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading purposes\u2026</p>\n } @else {\n <form class=\"dial-form\" (ngSubmit)=\"submit()\" novalidate>\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">Phone <span class=\"dial-form__req\">*</span></span>\n <input\n type=\"tel\"\n class=\"dial-form__input\"\n [(ngModel)]=\"phone\"\n name=\"phone\"\n placeholder=\"+14155550100\"\n autocomplete=\"tel\"\n required\n />\n <span class=\"dial-form__hint\">E.164 format, e.g. +14155550100</span>\n </label>\n\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">Patient name <span class=\"dial-form__req\">*</span></span>\n <input\n type=\"text\"\n class=\"dial-form__input\"\n [(ngModel)]=\"name\"\n name=\"name\"\n placeholder=\"Maria Garcia\"\n required\n />\n </label>\n\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">Purpose <span class=\"dial-form__req\">*</span></span>\n <select\n class=\"dial-form__input\"\n [(ngModel)]=\"purposeId\"\n name=\"purposeId\"\n (ngModelChange)=\"onPurposeChange()\"\n required\n >\n @for (p of purposes(); track p.purposeId) {\n <option [value]=\"p.purposeId\">{{ p.label }}</option>\n }\n </select>\n </label>\n\n @if (showsField('appointmentDate')) {\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">{{ requiredFieldLabel('appointmentDate') }} <span class=\"dial-form__req\">*</span></span>\n <input type=\"date\" class=\"dial-form__input\" [(ngModel)]=\"appointmentDate\" name=\"appointmentDate\" required />\n </label>\n }\n\n @if (showsField('appointmentTime')) {\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">{{ requiredFieldLabel('appointmentTime') }} <span class=\"dial-form__req\">*</span></span>\n <input type=\"time\" class=\"dial-form__input\" [(ngModel)]=\"appointmentTime\" name=\"appointmentTime\" required />\n </label>\n }\n\n @if (showsField('providerName')) {\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">{{ requiredFieldLabel('providerName') }} <span class=\"dial-form__req\">*</span></span>\n <input\n type=\"text\"\n class=\"dial-form__input\"\n [(ngModel)]=\"providerName\"\n name=\"providerName\"\n placeholder=\"Dr. Patel\"\n required\n />\n </label>\n }\n\n @if (showsCustomScript()) {\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">\n Custom script\n @if (customScriptRequired()) {\n <span class=\"dial-form__req\">*</span>\n }\n </span>\n <textarea\n class=\"dial-form__textarea\"\n rows=\"4\"\n [(ngModel)]=\"customScript\"\n name=\"customScript\"\n [required]=\"customScriptRequired()\"\n placeholder=\"What should the agent say or do?\"\n ></textarea>\n </label>\n }\n\n <section class=\"dial-form__preview eds-card\">\n <h3 class=\"eds-label\">Opening preview</h3>\n <p class=\"dial-form__preview-text\">{{ previewText }}</p>\n </section>\n\n @if (serverError()) {\n <p class=\"dial-form__error\" role=\"alert\">{{ serverError() }}</p>\n }\n\n <footer class=\"dial-form__footer\">\n <eds-button variant=\"secondary\" type=\"button\" (click)=\"close()\" [disabled]=\"submitting()\">\n Cancel\n </eds-button>\n <eds-button variant=\"primary\" type=\"submit\" [disabled]=\"submitting() || !canPlaceCall()\">\n {{ submitting() ? 'Dialing\u2026' : 'Place call' }}\n </eds-button>\n </footer>\n </form>\n }\n </app-modal>\n}\n", styles: [".dial-form{display:flex;flex-direction:column;gap:12px}.dial-form__field{display:flex;flex-direction:column;gap:6px}.dial-form__input,.dial-form__textarea{padding:8px 10px;border:1px solid var(--border-subtle, #e4e4e7);border-radius:8px;font:inherit}.dial-form__textarea{resize:vertical}.dial-form__hint{font-size:12px;color:var(--text-secondary)}.dial-form__req{color:#dc2626}.dial-form__preview{padding:12px;background:var(--surface-muted, #f8fafc)}.dial-form__preview-text{margin:8px 0 0;font-size:14px;line-height:1.5;white-space:pre-wrap}.dial-form__error{color:#b91c1c;font-size:13px;margin:0}.dial-form__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:4px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { 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.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: ModalComponent, selector: "app-modal", inputs: ["title", "subtitle", "maxWidth"], outputs: ["closed"] }, { kind: "component", type: EdsButtonComponent, selector: "eds-button", inputs: ["variant", "type", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
142
+ }
143
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: OutboundDialModalComponent, decorators: [{
144
+ type: Component,
145
+ args: [{ selector: 'eds-outbound-dial-modal', imports: [FormsModule, ModalComponent, EdsButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (open) {\n <app-modal\n title=\"Outbound call\"\n subtitle=\"Place a patient call using a purpose template\"\n maxWidth=\"560px\"\n (closed)=\"close()\"\n >\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading purposes\u2026</p>\n } @else {\n <form class=\"dial-form\" (ngSubmit)=\"submit()\" novalidate>\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">Phone <span class=\"dial-form__req\">*</span></span>\n <input\n type=\"tel\"\n class=\"dial-form__input\"\n [(ngModel)]=\"phone\"\n name=\"phone\"\n placeholder=\"+14155550100\"\n autocomplete=\"tel\"\n required\n />\n <span class=\"dial-form__hint\">E.164 format, e.g. +14155550100</span>\n </label>\n\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">Patient name <span class=\"dial-form__req\">*</span></span>\n <input\n type=\"text\"\n class=\"dial-form__input\"\n [(ngModel)]=\"name\"\n name=\"name\"\n placeholder=\"Maria Garcia\"\n required\n />\n </label>\n\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">Purpose <span class=\"dial-form__req\">*</span></span>\n <select\n class=\"dial-form__input\"\n [(ngModel)]=\"purposeId\"\n name=\"purposeId\"\n (ngModelChange)=\"onPurposeChange()\"\n required\n >\n @for (p of purposes(); track p.purposeId) {\n <option [value]=\"p.purposeId\">{{ p.label }}</option>\n }\n </select>\n </label>\n\n @if (showsField('appointmentDate')) {\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">{{ requiredFieldLabel('appointmentDate') }} <span class=\"dial-form__req\">*</span></span>\n <input type=\"date\" class=\"dial-form__input\" [(ngModel)]=\"appointmentDate\" name=\"appointmentDate\" required />\n </label>\n }\n\n @if (showsField('appointmentTime')) {\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">{{ requiredFieldLabel('appointmentTime') }} <span class=\"dial-form__req\">*</span></span>\n <input type=\"time\" class=\"dial-form__input\" [(ngModel)]=\"appointmentTime\" name=\"appointmentTime\" required />\n </label>\n }\n\n @if (showsField('providerName')) {\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">{{ requiredFieldLabel('providerName') }} <span class=\"dial-form__req\">*</span></span>\n <input\n type=\"text\"\n class=\"dial-form__input\"\n [(ngModel)]=\"providerName\"\n name=\"providerName\"\n placeholder=\"Dr. Patel\"\n required\n />\n </label>\n }\n\n @if (showsCustomScript()) {\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">\n Custom script\n @if (customScriptRequired()) {\n <span class=\"dial-form__req\">*</span>\n }\n </span>\n <textarea\n class=\"dial-form__textarea\"\n rows=\"4\"\n [(ngModel)]=\"customScript\"\n name=\"customScript\"\n [required]=\"customScriptRequired()\"\n placeholder=\"What should the agent say or do?\"\n ></textarea>\n </label>\n }\n\n <section class=\"dial-form__preview eds-card\">\n <h3 class=\"eds-label\">Opening preview</h3>\n <p class=\"dial-form__preview-text\">{{ previewText }}</p>\n </section>\n\n @if (serverError()) {\n <p class=\"dial-form__error\" role=\"alert\">{{ serverError() }}</p>\n }\n\n <footer class=\"dial-form__footer\">\n <eds-button variant=\"secondary\" type=\"button\" (click)=\"close()\" [disabled]=\"submitting()\">\n Cancel\n </eds-button>\n <eds-button variant=\"primary\" type=\"submit\" [disabled]=\"submitting() || !canPlaceCall()\">\n {{ submitting() ? 'Dialing\u2026' : 'Place call' }}\n </eds-button>\n </footer>\n </form>\n }\n </app-modal>\n}\n", styles: [".dial-form{display:flex;flex-direction:column;gap:12px}.dial-form__field{display:flex;flex-direction:column;gap:6px}.dial-form__input,.dial-form__textarea{padding:8px 10px;border:1px solid var(--border-subtle, #e4e4e7);border-radius:8px;font:inherit}.dial-form__textarea{resize:vertical}.dial-form__hint{font-size:12px;color:var(--text-secondary)}.dial-form__req{color:#dc2626}.dial-form__preview{padding:12px;background:var(--surface-muted, #f8fafc)}.dial-form__preview-text{margin:8px 0 0;font-size:14px;line-height:1.5;white-space:pre-wrap}.dial-form__error{color:#b91c1c;font-size:13px;margin:0}.dial-form__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:4px}\n"] }]
146
+ }], propDecorators: { open: [{
147
+ type: Input
148
+ }], closed: [{
149
+ type: Output
150
+ }], dialed: [{
151
+ type: Output
152
+ }] } });
153
+
154
+ class DashboardPageComponent {
155
+ constructor() {
156
+ this.api = inject(AiReceptionistApiService);
157
+ this.liveCallsList = inject(LiveCallsListService);
158
+ this.router = inject(Router);
159
+ this.route = inject(ActivatedRoute);
160
+ this.data = signal(null);
161
+ this.loading = signal(true);
162
+ this.liveCalls = this.liveCallsList.calls;
163
+ this.liveCallsLoading = this.liveCallsList.loading;
164
+ this.liveCallsError = this.liveCallsList.error;
165
+ this.outboundOpen = signal(false);
166
+ this.statTiles = [
167
+ { key: 'activeCalls', label: 'Live calls', icon: 'graphic_eq' },
168
+ { key: 'answeredToday', label: 'Calls today', icon: 'call' },
169
+ { key: 'tasksCreated', label: 'Open tasks', icon: 'task_alt' },
170
+ {
171
+ key: 'appointmentsBooked',
172
+ label: 'Appointments booked',
173
+ icon: 'event_available',
174
+ },
175
+ { key: 'completionRate', label: 'AI handled', icon: 'trending_up', suffix: '%' },
176
+ ];
177
+ this.outcomeTone = outcomeBadgeTone;
178
+ this.outcomeLabel = formatOutcomeLabel;
179
+ this.reviewLabel = reviewLabel;
180
+ this.reviewTone = reviewBadgeTone;
181
+ this.isNeedsReview = isNeedsReview;
182
+ }
183
+ ngOnInit() {
184
+ this.liveCallsList.start();
185
+ this.api.getDashboard().subscribe(({ data }) => {
186
+ this.data.set(data);
187
+ this.loading.set(false);
188
+ });
189
+ }
190
+ ngOnDestroy() {
191
+ this.liveCallsList.stop();
192
+ }
193
+ statValue(key) {
194
+ if (key === 'activeCalls') {
195
+ return this.liveCallsList.calls().length;
196
+ }
197
+ return this.data()?.stats[key] ?? 0;
198
+ }
199
+ statusTone(status) {
200
+ const normalized = status || 'ai';
201
+ const map = {
202
+ ai: 'brand',
203
+ 'needs-help': 'notice',
204
+ 'human-requested': 'violet',
205
+ urgent: 'neg',
206
+ takeover: 'violet',
207
+ transferring: 'notice',
208
+ };
209
+ return map[normalized] ?? 'brand';
210
+ }
211
+ statusLabel(status) {
212
+ return status || 'ai';
213
+ }
214
+ exportCsv() {
215
+ // mock export
216
+ }
217
+ openOutboundDial() {
218
+ this.outboundOpen.set(true);
219
+ }
220
+ closeOutboundDial() {
221
+ this.outboundOpen.set(false);
222
+ }
223
+ openHistoryDetail(id) {
224
+ void this.router.navigate(['../history', id], { relativeTo: this.route });
225
+ }
226
+ callDetailLink(callId) {
227
+ return callId.startsWith('HC-') ? ['../history', callId] : ['../live', callId];
228
+ }
229
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DashboardPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
230
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: DashboardPageComponent, isStandalone: true, selector: "eds-dashboard-page", ngImport: i0, template: "<div class=\"eds-page dashboard-page\">\n <header class=\"dashboard-page__header\">\n <div>\n <h1 class=\"eds-h1\">Dashboard</h1>\n <p class=\"eds-body-sm\">AI receptionist overview and live activity</p>\n </div>\n <div class=\"dashboard-page__actions\">\n <eds-button variant=\"secondary\" (click)=\"exportCsv()\">\n <eds-icon name=\"download\" [size]=\"18\" /> Export\n </eds-button>\n <eds-button variant=\"primary\" (click)=\"openOutboundDial()\">\n <eds-icon name=\"call_made\" [size]=\"18\" /> New outbound call\n </eds-button>\n </div>\n </header>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading dashboard\u2026</p>\n } @else if (data()) {\n <section class=\"dashboard-page__stats\">\n @for (tile of statTiles; track tile.key) {\n @if (tile.key === 'appointmentsBooked') {\n <a routerLink=\"../appointments\" class=\"eds-card dashboard-page__stat dashboard-page__stat--link\">\n <eds-icon [name]=\"tile.icon\" [size]=\"22\" />\n <span class=\"eds-label\">{{ tile.label }}</span>\n <span class=\"dashboard-page__stat-value\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </span>\n </a>\n } @else if (tile.key === 'activeCalls') {\n <a routerLink=\"../live\" class=\"eds-card dashboard-page__stat dashboard-page__stat--link\">\n <eds-icon [name]=\"tile.icon\" [size]=\"22\" />\n <span class=\"eds-label\">{{ tile.label }}</span>\n <span class=\"dashboard-page__stat-value\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </span>\n </a>\n } @else {\n <article class=\"eds-card dashboard-page__stat\">\n <eds-icon [name]=\"tile.icon\" [size]=\"22\" />\n <span class=\"eds-label\">{{ tile.label }}</span>\n <span class=\"dashboard-page__stat-value\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </span>\n </article>\n }\n }\n </section>\n\n <section class=\"dashboard-page__grid\">\n <article class=\"eds-card dashboard-page__panel\">\n <header class=\"dashboard-page__panel-head\">\n <h2 class=\"eds-h2\">Live calls</h2>\n <eds-pulse-dot />\n </header>\n <ul class=\"dashboard-page__live-list\">\n @if (liveCallsError()) {\n <li class=\"dashboard-page__live-error eds-body-sm\" role=\"alert\">{{ liveCallsError() }}</li>\n }\n @if (liveCallsLoading() && liveCalls().length === 0) {\n <li class=\"eds-body-sm\">Loading live calls\u2026</li>\n }\n @for (call of liveCalls(); track call.id) {\n <li>\n <a [routerLink]=\"['../live', call.id]\" class=\"dashboard-page__live-row\">\n <eds-avatar [initials]=\"call.initials\" tone=\"brand\" />\n <div class=\"dashboard-page__live-meta\">\n <span class=\"eds-body-sm-bold\">{{ call.name }}</span>\n <span class=\"eds-body-sm\">{{ call.reason }}</span>\n </div>\n <span class=\"eds-mono-sm\">{{ call.started | edsElapsed }}</span>\n </a>\n </li>\n } @empty {\n @if (!liveCallsLoading()) {\n <li class=\"eds-body-sm\">No active calls right now.</li>\n }\n }\n </ul>\n </article>\n\n <article class=\"eds-card dashboard-page__panel\">\n <header class=\"dashboard-page__panel-head\">\n <h2 class=\"eds-h2\">Tasks created</h2>\n <a routerLink=\"../tasks\" class=\"eds-body-sm\">View all</a>\n </header>\n <ul class=\"dashboard-page__attention-list\">\n @for (task of data()!.latestTasks; track task.id) {\n <li>\n <a\n [routerLink]=\"task.callId ? callDetailLink(task.callId) : ['../tasks']\"\n class=\"dashboard-page__attention-row\"\n [attr.aria-label]=\"'View task: ' + task.title\"\n >\n \n <div>\n <span class=\"eds-body-sm-bold\">{{ task.title }}</span>\n <span class=\"eds-body-sm\">\n {{ task.patient }} \u00B7 {{ task.created | date: 'short' }}\n </span>\n </div>\n <eds-icon name=\"chevron_right\" [size]=\"20\" />\n </a>\n </li>\n } @empty {\n <li class=\"eds-body-sm\">No AI-created tasks yet.</li>\n }\n </ul>\n </article>\n\n <article class=\"eds-card dashboard-page__panel\">\n <header class=\"dashboard-page__panel-head\">\n <h2 class=\"eds-h2\">Appointments created</h2>\n <a routerLink=\"../appointments\" class=\"eds-body-sm\">View all</a>\n </header>\n <ul class=\"dashboard-page__attention-list\">\n @for (appointment of data()!.latestAppointments; track appointment.id) {\n <li>\n <a\n routerLink=\"../appointments\"\n class=\"dashboard-page__attention-row\"\n [attr.aria-label]=\"'View appointment for ' + appointment.patientName\"\n >\n \n <div>\n <span class=\"eds-body-sm-bold\">{{ appointment.patientName }}</span>\n <span class=\"eds-body-sm\">\n {{ appointment.providerName ?? 'Provider not assigned' }}\n \u00B7 {{ appointment.startTime ? (appointment.startTime | date: 'short') : 'Time pending' }}\n </span>\n </div>\n <eds-icon name=\"chevron_right\" [size]=\"20\" />\n </a>\n </li>\n } @empty {\n <li class=\"eds-body-sm\">No AI-booked appointments yet.</li>\n }\n </ul>\n </article>\n\n </section>\n\n <article class=\"eds-card dashboard-page__history\">\n <header class=\"dashboard-page__panel-head\">\n <h2 class=\"eds-h2\">Recent call history</h2>\n <a routerLink=\"../history\" class=\"eds-body-sm\">View all</a>\n </header>\n <table class=\"eds-table\">\n <thead>\n <tr>\n <th>Caller</th>\n <th>Reason</th>\n <th>Outcome</th>\n <th>Review</th>\n <th>Location</th>\n <th>When</th>\n </tr>\n </thead>\n <tbody>\n @for (row of data()!.recentHistory; track row.id) {\n <tr\n class=\"dashboard-page__history-row\"\n [class.dashboard-page__row--needs-review]=\"isNeedsReview(row.review)\"\n tabindex=\"0\"\n role=\"link\"\n [attr.aria-label]=\"'View call details for ' + row.name\"\n (click)=\"openHistoryDetail(row.id)\"\n (keyup.enter)=\"openHistoryDetail(row.id)\"\n >\n <td>\n <div class=\"dashboard-page__caller\">\n <eds-avatar [initials]=\"row.initials\" />\n <span>{{ row.name }}</span>\n </div>\n </td>\n <td>{{ row.reason }}</td>\n <td>\n <eds-badge [tone]=\"outcomeTone(row.outcome)\">\n {{ outcomeLabel(row.outcome) }}\n </eds-badge>\n </td>\n <td>\n <eds-badge [tone]=\"reviewTone(row.review)\">\n {{ reviewLabel(row.review) }}\n </eds-badge>\n </td>\n <td>{{ row.location }}</td>\n <td class=\"eds-mono-sm\">{{ row.whenLabel ?? (row.started ? (row.started | date: 'short') : '\u2014') }}</td>\n </tr>\n }\n </tbody>\n </table>\n </article>\n }\n</div>\n\n<eds-outbound-dial-modal\n [open]=\"outboundOpen()\"\n (closed)=\"closeOutboundDial()\"\n (dialed)=\"closeOutboundDial()\"\n/>\n", styles: [".dashboard-page__header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:24px}.dashboard-page__actions{display:flex;gap:8px;flex-shrink:0}.dashboard-page__stats{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:20px}.dashboard-page__stat{display:flex;flex-direction:column;gap:6px;padding:16px}.dashboard-page__stat--link{text-decoration:none;color:inherit;cursor:pointer;transition:border-color .15s ease}.dashboard-page__stat--link:hover{border-color:var(--text-brand)}.dashboard-page__review-queue{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px 20px;margin-bottom:20px;border-left:4px solid #f59e0b}.dashboard-page__review-link{white-space:nowrap;font-weight:600;text-decoration:none;color:var(--text-brand)}.dashboard-page__row--needs-review{border-left:3px solid #f59e0b}.dashboard-page__history-row{cursor:pointer}.dashboard-page__history-row:hover{background:var(--surface-l1)}.dashboard-page__history-row:focus-visible{outline:2px solid var(--brand-500, #0d9488);outline-offset:-2px}.dashboard-page__caller{display:flex;align-items:center;gap:8px;font-weight:500}.dashboard-page__attention-row{display:grid;grid-template-columns:auto 1fr auto;gap:10px;align-items:center;padding:8px;border-radius:var(--eds-radius-md);text-decoration:none;color:inherit;border-bottom:1px solid var(--border-default)}.dashboard-page__attention-row:hover{background:var(--surface-l1)}.dashboard-page__attention-row:last-child{border-bottom:none}.dashboard-page__stat-value{font-size:28px;font-weight:600;color:var(--text-primary);letter-spacing:-.02em}.dashboard-page__grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-bottom:20px}.dashboard-page__panel{padding:16px}.dashboard-page__panel-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px}.dashboard-page__live-list,.dashboard-page__attention-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.dashboard-page__live-row{display:grid;grid-template-columns:auto 1fr auto auto;align-items:center;gap:10px;padding:8px;border-radius:var(--eds-radius-md);text-decoration:none;color:inherit}.dashboard-page__live-row:hover{background:var(--surface-l1)}.dashboard-page__live-meta{display:flex;flex-direction:column;min-width:0}.dashboard-page__history{padding:16px;overflow-x:auto}@media(max-width:1200px){.dashboard-page__stats{grid-template-columns:repeat(3,1fr)}}@media(max-width:900px){.dashboard-page__stats{grid-template-columns:repeat(2,1fr)}.dashboard-page__grid{grid-template-columns:1fr}}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "component", type: EdsAvatarComponent, selector: "eds-avatar", inputs: ["initials", "tone", "gradient"] }, { kind: "component", type: EdsBadgeComponent, selector: "eds-badge", inputs: ["tone"] }, { kind: "component", type: EdsButtonComponent, selector: "eds-button", inputs: ["variant", "type", "disabled"] }, { kind: "component", type: EdsIconComponent, selector: "eds-icon", inputs: ["name", "size", "fill", "weight"] }, { kind: "component", type: EdsPulseDotComponent, selector: "eds-pulse-dot" }, { kind: "pipe", type: EdsElapsedPipe, name: "edsElapsed" }, { kind: "component", type: OutboundDialModalComponent, selector: "eds-outbound-dial-modal", inputs: ["open"], outputs: ["closed", "dialed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
231
+ }
232
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DashboardPageComponent, decorators: [{
233
+ type: Component,
234
+ args: [{ selector: 'eds-dashboard-page', imports: [
235
+ RouterLink,
236
+ DatePipe,
237
+ EdsAvatarComponent,
238
+ EdsBadgeComponent,
239
+ EdsButtonComponent,
240
+ EdsIconComponent,
241
+ EdsPulseDotComponent,
242
+ EdsElapsedPipe,
243
+ OutboundDialModalComponent,
244
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"eds-page dashboard-page\">\n <header class=\"dashboard-page__header\">\n <div>\n <h1 class=\"eds-h1\">Dashboard</h1>\n <p class=\"eds-body-sm\">AI receptionist overview and live activity</p>\n </div>\n <div class=\"dashboard-page__actions\">\n <eds-button variant=\"secondary\" (click)=\"exportCsv()\">\n <eds-icon name=\"download\" [size]=\"18\" /> Export\n </eds-button>\n <eds-button variant=\"primary\" (click)=\"openOutboundDial()\">\n <eds-icon name=\"call_made\" [size]=\"18\" /> New outbound call\n </eds-button>\n </div>\n </header>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading dashboard\u2026</p>\n } @else if (data()) {\n <section class=\"dashboard-page__stats\">\n @for (tile of statTiles; track tile.key) {\n @if (tile.key === 'appointmentsBooked') {\n <a routerLink=\"../appointments\" class=\"eds-card dashboard-page__stat dashboard-page__stat--link\">\n <eds-icon [name]=\"tile.icon\" [size]=\"22\" />\n <span class=\"eds-label\">{{ tile.label }}</span>\n <span class=\"dashboard-page__stat-value\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </span>\n </a>\n } @else if (tile.key === 'activeCalls') {\n <a routerLink=\"../live\" class=\"eds-card dashboard-page__stat dashboard-page__stat--link\">\n <eds-icon [name]=\"tile.icon\" [size]=\"22\" />\n <span class=\"eds-label\">{{ tile.label }}</span>\n <span class=\"dashboard-page__stat-value\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </span>\n </a>\n } @else {\n <article class=\"eds-card dashboard-page__stat\">\n <eds-icon [name]=\"tile.icon\" [size]=\"22\" />\n <span class=\"eds-label\">{{ tile.label }}</span>\n <span class=\"dashboard-page__stat-value\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </span>\n </article>\n }\n }\n </section>\n\n <section class=\"dashboard-page__grid\">\n <article class=\"eds-card dashboard-page__panel\">\n <header class=\"dashboard-page__panel-head\">\n <h2 class=\"eds-h2\">Live calls</h2>\n <eds-pulse-dot />\n </header>\n <ul class=\"dashboard-page__live-list\">\n @if (liveCallsError()) {\n <li class=\"dashboard-page__live-error eds-body-sm\" role=\"alert\">{{ liveCallsError() }}</li>\n }\n @if (liveCallsLoading() && liveCalls().length === 0) {\n <li class=\"eds-body-sm\">Loading live calls\u2026</li>\n }\n @for (call of liveCalls(); track call.id) {\n <li>\n <a [routerLink]=\"['../live', call.id]\" class=\"dashboard-page__live-row\">\n <eds-avatar [initials]=\"call.initials\" tone=\"brand\" />\n <div class=\"dashboard-page__live-meta\">\n <span class=\"eds-body-sm-bold\">{{ call.name }}</span>\n <span class=\"eds-body-sm\">{{ call.reason }}</span>\n </div>\n <span class=\"eds-mono-sm\">{{ call.started | edsElapsed }}</span>\n </a>\n </li>\n } @empty {\n @if (!liveCallsLoading()) {\n <li class=\"eds-body-sm\">No active calls right now.</li>\n }\n }\n </ul>\n </article>\n\n <article class=\"eds-card dashboard-page__panel\">\n <header class=\"dashboard-page__panel-head\">\n <h2 class=\"eds-h2\">Tasks created</h2>\n <a routerLink=\"../tasks\" class=\"eds-body-sm\">View all</a>\n </header>\n <ul class=\"dashboard-page__attention-list\">\n @for (task of data()!.latestTasks; track task.id) {\n <li>\n <a\n [routerLink]=\"task.callId ? callDetailLink(task.callId) : ['../tasks']\"\n class=\"dashboard-page__attention-row\"\n [attr.aria-label]=\"'View task: ' + task.title\"\n >\n \n <div>\n <span class=\"eds-body-sm-bold\">{{ task.title }}</span>\n <span class=\"eds-body-sm\">\n {{ task.patient }} \u00B7 {{ task.created | date: 'short' }}\n </span>\n </div>\n <eds-icon name=\"chevron_right\" [size]=\"20\" />\n </a>\n </li>\n } @empty {\n <li class=\"eds-body-sm\">No AI-created tasks yet.</li>\n }\n </ul>\n </article>\n\n <article class=\"eds-card dashboard-page__panel\">\n <header class=\"dashboard-page__panel-head\">\n <h2 class=\"eds-h2\">Appointments created</h2>\n <a routerLink=\"../appointments\" class=\"eds-body-sm\">View all</a>\n </header>\n <ul class=\"dashboard-page__attention-list\">\n @for (appointment of data()!.latestAppointments; track appointment.id) {\n <li>\n <a\n routerLink=\"../appointments\"\n class=\"dashboard-page__attention-row\"\n [attr.aria-label]=\"'View appointment for ' + appointment.patientName\"\n >\n \n <div>\n <span class=\"eds-body-sm-bold\">{{ appointment.patientName }}</span>\n <span class=\"eds-body-sm\">\n {{ appointment.providerName ?? 'Provider not assigned' }}\n \u00B7 {{ appointment.startTime ? (appointment.startTime | date: 'short') : 'Time pending' }}\n </span>\n </div>\n <eds-icon name=\"chevron_right\" [size]=\"20\" />\n </a>\n </li>\n } @empty {\n <li class=\"eds-body-sm\">No AI-booked appointments yet.</li>\n }\n </ul>\n </article>\n\n </section>\n\n <article class=\"eds-card dashboard-page__history\">\n <header class=\"dashboard-page__panel-head\">\n <h2 class=\"eds-h2\">Recent call history</h2>\n <a routerLink=\"../history\" class=\"eds-body-sm\">View all</a>\n </header>\n <table class=\"eds-table\">\n <thead>\n <tr>\n <th>Caller</th>\n <th>Reason</th>\n <th>Outcome</th>\n <th>Review</th>\n <th>Location</th>\n <th>When</th>\n </tr>\n </thead>\n <tbody>\n @for (row of data()!.recentHistory; track row.id) {\n <tr\n class=\"dashboard-page__history-row\"\n [class.dashboard-page__row--needs-review]=\"isNeedsReview(row.review)\"\n tabindex=\"0\"\n role=\"link\"\n [attr.aria-label]=\"'View call details for ' + row.name\"\n (click)=\"openHistoryDetail(row.id)\"\n (keyup.enter)=\"openHistoryDetail(row.id)\"\n >\n <td>\n <div class=\"dashboard-page__caller\">\n <eds-avatar [initials]=\"row.initials\" />\n <span>{{ row.name }}</span>\n </div>\n </td>\n <td>{{ row.reason }}</td>\n <td>\n <eds-badge [tone]=\"outcomeTone(row.outcome)\">\n {{ outcomeLabel(row.outcome) }}\n </eds-badge>\n </td>\n <td>\n <eds-badge [tone]=\"reviewTone(row.review)\">\n {{ reviewLabel(row.review) }}\n </eds-badge>\n </td>\n <td>{{ row.location }}</td>\n <td class=\"eds-mono-sm\">{{ row.whenLabel ?? (row.started ? (row.started | date: 'short') : '\u2014') }}</td>\n </tr>\n }\n </tbody>\n </table>\n </article>\n }\n</div>\n\n<eds-outbound-dial-modal\n [open]=\"outboundOpen()\"\n (closed)=\"closeOutboundDial()\"\n (dialed)=\"closeOutboundDial()\"\n/>\n", styles: [".dashboard-page__header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:24px}.dashboard-page__actions{display:flex;gap:8px;flex-shrink:0}.dashboard-page__stats{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:20px}.dashboard-page__stat{display:flex;flex-direction:column;gap:6px;padding:16px}.dashboard-page__stat--link{text-decoration:none;color:inherit;cursor:pointer;transition:border-color .15s ease}.dashboard-page__stat--link:hover{border-color:var(--text-brand)}.dashboard-page__review-queue{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px 20px;margin-bottom:20px;border-left:4px solid #f59e0b}.dashboard-page__review-link{white-space:nowrap;font-weight:600;text-decoration:none;color:var(--text-brand)}.dashboard-page__row--needs-review{border-left:3px solid #f59e0b}.dashboard-page__history-row{cursor:pointer}.dashboard-page__history-row:hover{background:var(--surface-l1)}.dashboard-page__history-row:focus-visible{outline:2px solid var(--brand-500, #0d9488);outline-offset:-2px}.dashboard-page__caller{display:flex;align-items:center;gap:8px;font-weight:500}.dashboard-page__attention-row{display:grid;grid-template-columns:auto 1fr auto;gap:10px;align-items:center;padding:8px;border-radius:var(--eds-radius-md);text-decoration:none;color:inherit;border-bottom:1px solid var(--border-default)}.dashboard-page__attention-row:hover{background:var(--surface-l1)}.dashboard-page__attention-row:last-child{border-bottom:none}.dashboard-page__stat-value{font-size:28px;font-weight:600;color:var(--text-primary);letter-spacing:-.02em}.dashboard-page__grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-bottom:20px}.dashboard-page__panel{padding:16px}.dashboard-page__panel-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px}.dashboard-page__live-list,.dashboard-page__attention-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.dashboard-page__live-row{display:grid;grid-template-columns:auto 1fr auto auto;align-items:center;gap:10px;padding:8px;border-radius:var(--eds-radius-md);text-decoration:none;color:inherit}.dashboard-page__live-row:hover{background:var(--surface-l1)}.dashboard-page__live-meta{display:flex;flex-direction:column;min-width:0}.dashboard-page__history{padding:16px;overflow-x:auto}@media(max-width:1200px){.dashboard-page__stats{grid-template-columns:repeat(3,1fr)}}@media(max-width:900px){.dashboard-page__stats{grid-template-columns:repeat(2,1fr)}.dashboard-page__grid{grid-template-columns:1fr}}\n"] }]
245
+ }] });
246
+
247
+ export { DashboardPageComponent };
248
+ //# sourceMappingURL=edvak99-voice-receptionist-widget-dashboard-page.component-B93giErR.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edvak99-voice-receptionist-widget-dashboard-page.component-B93giErR.mjs","sources":["../../../projects/voice-receptionist-widget/src/lib/components/outbound-dial-modal/outbound-dial-modal.component.ts","../../../projects/voice-receptionist-widget/src/lib/components/outbound-dial-modal/outbound-dial-modal.component.html","../../../projects/voice-receptionist-widget/src/lib/pages/dashboard-page/dashboard-page.component.ts","../../../projects/voice-receptionist-widget/src/lib/pages/dashboard-page/dashboard-page.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n EventEmitter,\n Input,\n OnChanges,\n Output,\n SimpleChanges,\n inject,\n signal,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\n\nimport { OutboundPurpose } from '../../models/outbound-purpose.types';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport {\n isCustomScriptRequired,\n isPurposeFormValid,\n purposeShowsCustomScript,\n renderOpeningPreview,\n requiredFieldLabel,\n} from '../../utils/outbound-purpose.util';\nimport { EdsButtonComponent } from '../../ui/eds-button.component';\nimport { ModalComponent } from '../../shared/modal.component';\nimport { ToastService } from '../../shared/toast.service';\n\nconst E164_PATTERN = /^\\+[1-9]\\d{6,14}$/;\n\n@Component({\n selector: 'eds-outbound-dial-modal',\n imports: [FormsModule, ModalComponent, EdsButtonComponent],\n templateUrl: './outbound-dial-modal.component.html',\n styleUrl: './outbound-dial-modal.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class OutboundDialModalComponent implements OnChanges {\n private readonly api = inject(AiReceptionistApiService);\n private readonly toast = inject(ToastService);\n\n @Input() open = false;\n @Output() readonly closed = new EventEmitter<void>();\n @Output() readonly dialed = new EventEmitter<void>();\n\n protected readonly purposes = signal<OutboundPurpose[]>([]);\n protected readonly loading = signal(true);\n protected readonly submitting = signal(false);\n protected readonly serverError = signal<string | null>(null);\n\n protected phone = '';\n protected name = '';\n protected purposeId = '';\n protected appointmentDate = '';\n protected appointmentTime = '';\n protected providerName = '';\n protected customScript = '';\n\n protected requiredFieldLabel = requiredFieldLabel;\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['open']?.currentValue) {\n this.resetForm();\n this.loadPurposes();\n }\n }\n\n protected get selectedPurpose(): OutboundPurpose | null {\n return this.purposes().find((p) => p.purposeId === this.purposeId) ?? null;\n }\n\n protected get previewText(): string {\n const p = this.selectedPurpose;\n if (!p) return '—';\n return renderOpeningPreview(p.sampleOpening, p.openingTemplate, p.placeholders ?? []);\n }\n\n protected showsField(field: 'appointmentDate' | 'appointmentTime' | 'providerName'): boolean {\n return this.selectedPurpose?.requiredFields.includes(field) ?? false;\n }\n\n protected showsCustomScript(): boolean {\n const p = this.selectedPurpose;\n return p ? purposeShowsCustomScript(p) : false;\n }\n\n protected customScriptRequired(): boolean {\n const p = this.selectedPurpose;\n return p ? isCustomScriptRequired(p) : false;\n }\n\n protected canPlaceCall(): boolean {\n if (!E164_PATTERN.test(this.phone.trim())) return false;\n return isPurposeFormValid(this.selectedPurpose, {\n phone: this.phone,\n name: this.name,\n appointmentDate: this.appointmentDate,\n appointmentTime: this.appointmentTime,\n providerName: this.providerName,\n customScript: this.customScript,\n });\n }\n\n protected onPurposeChange(): void {\n this.appointmentDate = '';\n this.appointmentTime = '';\n this.providerName = '';\n this.customScript = '';\n this.serverError.set(null);\n }\n\n protected close(): void {\n this.closed.emit();\n }\n\n protected submit(): void {\n this.serverError.set(null);\n if (!this.canPlaceCall() || !this.selectedPurpose) return;\n\n this.submitting.set(true);\n const body = {\n phone: this.phone.trim(),\n name: this.name.trim(),\n purpose: this.purposeId,\n ...(this.appointmentDate ? { appointmentDate: this.appointmentDate } : {}),\n ...(this.appointmentTime ? { appointmentTime: this.appointmentTime } : {}),\n ...(this.providerName ? { providerName: this.providerName.trim() } : {}),\n ...(this.customScript ? { customScript: this.customScript.trim() } : {}),\n };\n\n this.api.placeOutboundCall(body).subscribe({\n next: (res) => {\n this.submitting.set(false);\n const msg = res.message ?? 'Outbound call started.';\n this.toast.show(msg, 'success', 5000);\n this.dialed.emit();\n this.close();\n },\n error: (err: { message?: string }) => {\n this.submitting.set(false);\n const msg = err?.message ?? 'Failed to place outbound call.';\n this.serverError.set(msg);\n this.toast.show(msg, 'error', 5000);\n },\n });\n }\n\n private resetForm(): void {\n this.phone = '';\n this.name = '';\n this.purposeId = '';\n this.appointmentDate = '';\n this.appointmentTime = '';\n this.providerName = '';\n this.customScript = '';\n this.serverError.set(null);\n this.submitting.set(false);\n }\n\n private loadPurposes(): void {\n this.loading.set(true);\n this.api.getOutboundPurposes(false).subscribe({\n next: ({ purposes }) => {\n this.purposes.set(purposes);\n if (purposes.length && !this.purposeId) {\n this.purposeId = purposes[0].purposeId;\n }\n this.loading.set(false);\n },\n error: () => {\n this.loading.set(false);\n this.serverError.set('Failed to load outbound purposes.');\n },\n });\n }\n}\n","@if (open) {\n <app-modal\n title=\"Outbound call\"\n subtitle=\"Place a patient call using a purpose template\"\n maxWidth=\"560px\"\n (closed)=\"close()\"\n >\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading purposes…</p>\n } @else {\n <form class=\"dial-form\" (ngSubmit)=\"submit()\" novalidate>\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">Phone <span class=\"dial-form__req\">*</span></span>\n <input\n type=\"tel\"\n class=\"dial-form__input\"\n [(ngModel)]=\"phone\"\n name=\"phone\"\n placeholder=\"+14155550100\"\n autocomplete=\"tel\"\n required\n />\n <span class=\"dial-form__hint\">E.164 format, e.g. +14155550100</span>\n </label>\n\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">Patient name <span class=\"dial-form__req\">*</span></span>\n <input\n type=\"text\"\n class=\"dial-form__input\"\n [(ngModel)]=\"name\"\n name=\"name\"\n placeholder=\"Maria Garcia\"\n required\n />\n </label>\n\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">Purpose <span class=\"dial-form__req\">*</span></span>\n <select\n class=\"dial-form__input\"\n [(ngModel)]=\"purposeId\"\n name=\"purposeId\"\n (ngModelChange)=\"onPurposeChange()\"\n required\n >\n @for (p of purposes(); track p.purposeId) {\n <option [value]=\"p.purposeId\">{{ p.label }}</option>\n }\n </select>\n </label>\n\n @if (showsField('appointmentDate')) {\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">{{ requiredFieldLabel('appointmentDate') }} <span class=\"dial-form__req\">*</span></span>\n <input type=\"date\" class=\"dial-form__input\" [(ngModel)]=\"appointmentDate\" name=\"appointmentDate\" required />\n </label>\n }\n\n @if (showsField('appointmentTime')) {\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">{{ requiredFieldLabel('appointmentTime') }} <span class=\"dial-form__req\">*</span></span>\n <input type=\"time\" class=\"dial-form__input\" [(ngModel)]=\"appointmentTime\" name=\"appointmentTime\" required />\n </label>\n }\n\n @if (showsField('providerName')) {\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">{{ requiredFieldLabel('providerName') }} <span class=\"dial-form__req\">*</span></span>\n <input\n type=\"text\"\n class=\"dial-form__input\"\n [(ngModel)]=\"providerName\"\n name=\"providerName\"\n placeholder=\"Dr. Patel\"\n required\n />\n </label>\n }\n\n @if (showsCustomScript()) {\n <label class=\"dial-form__field\">\n <span class=\"eds-label\">\n Custom script\n @if (customScriptRequired()) {\n <span class=\"dial-form__req\">*</span>\n }\n </span>\n <textarea\n class=\"dial-form__textarea\"\n rows=\"4\"\n [(ngModel)]=\"customScript\"\n name=\"customScript\"\n [required]=\"customScriptRequired()\"\n placeholder=\"What should the agent say or do?\"\n ></textarea>\n </label>\n }\n\n <section class=\"dial-form__preview eds-card\">\n <h3 class=\"eds-label\">Opening preview</h3>\n <p class=\"dial-form__preview-text\">{{ previewText }}</p>\n </section>\n\n @if (serverError()) {\n <p class=\"dial-form__error\" role=\"alert\">{{ serverError() }}</p>\n }\n\n <footer class=\"dial-form__footer\">\n <eds-button variant=\"secondary\" type=\"button\" (click)=\"close()\" [disabled]=\"submitting()\">\n Cancel\n </eds-button>\n <eds-button variant=\"primary\" type=\"submit\" [disabled]=\"submitting() || !canPlaceCall()\">\n {{ submitting() ? 'Dialing…' : 'Place call' }}\n </eds-button>\n </footer>\n </form>\n }\n </app-modal>\n}\n","import { ChangeDetectionStrategy, Component, inject, OnDestroy, OnInit, signal } from '@angular/core';\nimport { ActivatedRoute, Router, RouterLink } from '@angular/router';\nimport { DatePipe } from '@angular/common';\n\nimport { DashboardData } from '../../models/ai-receptionist.types';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport { LiveCallsListService } from '../../services/live-calls-list.service';\nimport { formatOutcomeLabel, outcomeBadgeTone } from '../../utils/call-outcome.util';\nimport {\n isNeedsReview,\n reviewBadgeTone,\n reviewLabel,\n} from '../../utils/call-review.util';\nimport { OutboundDialModalComponent } from '../../components/outbound-dial-modal/outbound-dial-modal.component';\nimport { EdsAvatarComponent } from '../../ui/eds-avatar.component';\nimport { EdsBadgeComponent } from '../../ui/eds-badge.component';\nimport { EdsButtonComponent } from '../../ui/eds-button.component';\nimport { EdsIconComponent } from '../../ui/eds-icon.component';\nimport { EdsPulseDotComponent } from '../../ui/eds-pulse-dot.component';\nimport { EdsElapsedPipe } from '../../pipes/eds-elapsed.pipe';\n\n@Component({\n selector: 'eds-dashboard-page',\n imports: [\n RouterLink,\n DatePipe,\n EdsAvatarComponent,\n EdsBadgeComponent,\n EdsButtonComponent,\n EdsIconComponent,\n EdsPulseDotComponent,\n EdsElapsedPipe,\n OutboundDialModalComponent,\n ],\n templateUrl: './dashboard-page.component.html',\n styleUrl: './dashboard-page.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class DashboardPageComponent implements OnInit, OnDestroy {\n private readonly api = inject(AiReceptionistApiService);\n private readonly liveCallsList = inject(LiveCallsListService);\n private readonly router = inject(Router);\n private readonly route = inject(ActivatedRoute);\n\n protected readonly data = signal<DashboardData | null>(null);\n protected readonly loading = signal(true);\n\n protected readonly liveCalls = this.liveCallsList.calls;\n protected readonly liveCallsLoading = this.liveCallsList.loading;\n protected readonly liveCallsError = this.liveCallsList.error;\n protected readonly outboundOpen = signal(false);\n\n protected readonly statTiles = [\n { key: 'activeCalls' as const, label: 'Live calls', icon: 'graphic_eq' },\n { key: 'answeredToday' as const, label: 'Calls today', icon: 'call' },\n { key: 'tasksCreated' as const, label: 'Open tasks', icon: 'task_alt' },\n {\n key: 'appointmentsBooked' as const,\n label: 'Appointments booked',\n icon: 'event_available',\n },\n { key: 'completionRate' as const, label: 'AI handled', icon: 'trending_up', suffix: '%' },\n ];\n\n ngOnInit(): void {\n this.liveCallsList.start();\n this.api.getDashboard().subscribe(({ data }) => {\n this.data.set(data);\n this.loading.set(false);\n });\n }\n\n ngOnDestroy(): void {\n this.liveCallsList.stop();\n }\n\n protected statValue(key: keyof DashboardData['stats']): number {\n if (key === 'activeCalls') {\n return this.liveCallsList.calls().length;\n }\n return this.data()?.stats[key] ?? 0;\n }\n\n protected statusTone(status: string): 'brand' | 'pos' | 'notice' | 'neg' | 'violet' | 'gray' {\n const normalized = status || 'ai';\n const map: Record<string, 'brand' | 'pos' | 'notice' | 'neg' | 'violet'> = {\n ai: 'brand',\n 'needs-help': 'notice',\n 'human-requested': 'violet',\n urgent: 'neg',\n takeover: 'violet',\n transferring: 'notice',\n };\n return map[normalized] ?? 'brand';\n }\n\n protected statusLabel(status: string): string {\n return status || 'ai';\n }\n\n protected outcomeTone = outcomeBadgeTone;\n protected outcomeLabel = formatOutcomeLabel;\n protected reviewLabel = reviewLabel;\n protected reviewTone = reviewBadgeTone;\n protected isNeedsReview = isNeedsReview;\n\n protected exportCsv(): void {\n // mock export\n }\n\n protected openOutboundDial(): void {\n this.outboundOpen.set(true);\n }\n\n protected closeOutboundDial(): void {\n this.outboundOpen.set(false);\n }\n\n protected openHistoryDetail(id: string): void {\n void this.router.navigate(['../history', id], { relativeTo: this.route });\n }\n\n protected callDetailLink(callId: string): string[] {\n return callId.startsWith('HC-') ? ['../history', callId] : ['../live', callId];\n }\n}\n","<div class=\"eds-page dashboard-page\">\n <header class=\"dashboard-page__header\">\n <div>\n <h1 class=\"eds-h1\">Dashboard</h1>\n <p class=\"eds-body-sm\">AI receptionist overview and live activity</p>\n </div>\n <div class=\"dashboard-page__actions\">\n <eds-button variant=\"secondary\" (click)=\"exportCsv()\">\n <eds-icon name=\"download\" [size]=\"18\" /> Export\n </eds-button>\n <eds-button variant=\"primary\" (click)=\"openOutboundDial()\">\n <eds-icon name=\"call_made\" [size]=\"18\" /> New outbound call\n </eds-button>\n </div>\n </header>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading dashboard…</p>\n } @else if (data()) {\n <section class=\"dashboard-page__stats\">\n @for (tile of statTiles; track tile.key) {\n @if (tile.key === 'appointmentsBooked') {\n <a routerLink=\"../appointments\" class=\"eds-card dashboard-page__stat dashboard-page__stat--link\">\n <eds-icon [name]=\"tile.icon\" [size]=\"22\" />\n <span class=\"eds-label\">{{ tile.label }}</span>\n <span class=\"dashboard-page__stat-value\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </span>\n </a>\n } @else if (tile.key === 'activeCalls') {\n <a routerLink=\"../live\" class=\"eds-card dashboard-page__stat dashboard-page__stat--link\">\n <eds-icon [name]=\"tile.icon\" [size]=\"22\" />\n <span class=\"eds-label\">{{ tile.label }}</span>\n <span class=\"dashboard-page__stat-value\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </span>\n </a>\n } @else {\n <article class=\"eds-card dashboard-page__stat\">\n <eds-icon [name]=\"tile.icon\" [size]=\"22\" />\n <span class=\"eds-label\">{{ tile.label }}</span>\n <span class=\"dashboard-page__stat-value\">\n {{ statValue(tile.key) }}{{ tile.suffix ?? '' }}\n </span>\n </article>\n }\n }\n </section>\n\n <section class=\"dashboard-page__grid\">\n <article class=\"eds-card dashboard-page__panel\">\n <header class=\"dashboard-page__panel-head\">\n <h2 class=\"eds-h2\">Live calls</h2>\n <eds-pulse-dot />\n </header>\n <ul class=\"dashboard-page__live-list\">\n @if (liveCallsError()) {\n <li class=\"dashboard-page__live-error eds-body-sm\" role=\"alert\">{{ liveCallsError() }}</li>\n }\n @if (liveCallsLoading() && liveCalls().length === 0) {\n <li class=\"eds-body-sm\">Loading live calls…</li>\n }\n @for (call of liveCalls(); track call.id) {\n <li>\n <a [routerLink]=\"['../live', call.id]\" class=\"dashboard-page__live-row\">\n <eds-avatar [initials]=\"call.initials\" tone=\"brand\" />\n <div class=\"dashboard-page__live-meta\">\n <span class=\"eds-body-sm-bold\">{{ call.name }}</span>\n <span class=\"eds-body-sm\">{{ call.reason }}</span>\n </div>\n <span class=\"eds-mono-sm\">{{ call.started | edsElapsed }}</span>\n </a>\n </li>\n } @empty {\n @if (!liveCallsLoading()) {\n <li class=\"eds-body-sm\">No active calls right now.</li>\n }\n }\n </ul>\n </article>\n\n <article class=\"eds-card dashboard-page__panel\">\n <header class=\"dashboard-page__panel-head\">\n <h2 class=\"eds-h2\">Tasks created</h2>\n <a routerLink=\"../tasks\" class=\"eds-body-sm\">View all</a>\n </header>\n <ul class=\"dashboard-page__attention-list\">\n @for (task of data()!.latestTasks; track task.id) {\n <li>\n <a\n [routerLink]=\"task.callId ? callDetailLink(task.callId) : ['../tasks']\"\n class=\"dashboard-page__attention-row\"\n [attr.aria-label]=\"'View task: ' + task.title\"\n >\n \n <div>\n <span class=\"eds-body-sm-bold\">{{ task.title }}</span>\n <span class=\"eds-body-sm\">\n {{ task.patient }} · {{ task.created | date: 'short' }}\n </span>\n </div>\n <eds-icon name=\"chevron_right\" [size]=\"20\" />\n </a>\n </li>\n } @empty {\n <li class=\"eds-body-sm\">No AI-created tasks yet.</li>\n }\n </ul>\n </article>\n\n <article class=\"eds-card dashboard-page__panel\">\n <header class=\"dashboard-page__panel-head\">\n <h2 class=\"eds-h2\">Appointments created</h2>\n <a routerLink=\"../appointments\" class=\"eds-body-sm\">View all</a>\n </header>\n <ul class=\"dashboard-page__attention-list\">\n @for (appointment of data()!.latestAppointments; track appointment.id) {\n <li>\n <a\n routerLink=\"../appointments\"\n class=\"dashboard-page__attention-row\"\n [attr.aria-label]=\"'View appointment for ' + appointment.patientName\"\n >\n \n <div>\n <span class=\"eds-body-sm-bold\">{{ appointment.patientName }}</span>\n <span class=\"eds-body-sm\">\n {{ appointment.providerName ?? 'Provider not assigned' }}\n · {{ appointment.startTime ? (appointment.startTime | date: 'short') : 'Time pending' }}\n </span>\n </div>\n <eds-icon name=\"chevron_right\" [size]=\"20\" />\n </a>\n </li>\n } @empty {\n <li class=\"eds-body-sm\">No AI-booked appointments yet.</li>\n }\n </ul>\n </article>\n\n </section>\n\n <article class=\"eds-card dashboard-page__history\">\n <header class=\"dashboard-page__panel-head\">\n <h2 class=\"eds-h2\">Recent call history</h2>\n <a routerLink=\"../history\" class=\"eds-body-sm\">View all</a>\n </header>\n <table class=\"eds-table\">\n <thead>\n <tr>\n <th>Caller</th>\n <th>Reason</th>\n <th>Outcome</th>\n <th>Review</th>\n <th>Location</th>\n <th>When</th>\n </tr>\n </thead>\n <tbody>\n @for (row of data()!.recentHistory; track row.id) {\n <tr\n class=\"dashboard-page__history-row\"\n [class.dashboard-page__row--needs-review]=\"isNeedsReview(row.review)\"\n tabindex=\"0\"\n role=\"link\"\n [attr.aria-label]=\"'View call details for ' + row.name\"\n (click)=\"openHistoryDetail(row.id)\"\n (keyup.enter)=\"openHistoryDetail(row.id)\"\n >\n <td>\n <div class=\"dashboard-page__caller\">\n <eds-avatar [initials]=\"row.initials\" />\n <span>{{ row.name }}</span>\n </div>\n </td>\n <td>{{ row.reason }}</td>\n <td>\n <eds-badge [tone]=\"outcomeTone(row.outcome)\">\n {{ outcomeLabel(row.outcome) }}\n </eds-badge>\n </td>\n <td>\n <eds-badge [tone]=\"reviewTone(row.review)\">\n {{ reviewLabel(row.review) }}\n </eds-badge>\n </td>\n <td>{{ row.location }}</td>\n <td class=\"eds-mono-sm\">{{ row.whenLabel ?? (row.started ? (row.started | date: 'short') : '—') }}</td>\n </tr>\n }\n </tbody>\n </table>\n </article>\n }\n</div>\n\n<eds-outbound-dial-modal\n [open]=\"outboundOpen()\"\n (closed)=\"closeOutboundDial()\"\n (dialed)=\"closeOutboundDial()\"\n/>\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, ChangeDetectionStrategy, Component } from '@angular/core';
3
+
4
+ class EdsAvatarComponent {
5
+ constructor() {
6
+ this.initials = input('?');
7
+ this.tone = input('gray');
8
+ this.gradient = input(false);
9
+ }
10
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsAvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.17", type: EdsAvatarComponent, isStandalone: true, selector: "eds-avatar", inputs: { initials: { classPropertyName: "initials", publicName: "initials", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, gradient: { classPropertyName: "gradient", publicName: "gradient", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
12
+ <span class="eds-avatar" [attr.data-tone]="tone()" [class.eds-avatar--gradient]="gradient()">
13
+ {{ initials() }}
14
+ </span>
15
+ `, isInline: true, styles: [".eds-avatar{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:50%;font-family:var(--font-inter);font-size:12px;font-weight:600;flex-shrink:0}.eds-avatar[data-tone=gray]{background:var(--gray-200);color:var(--gray-600)}.eds-avatar[data-tone=brand]{background:#3681f426;color:var(--brand-600)}.eds-avatar--gradient{background:linear-gradient(135deg,var(--brand-500),var(--violet-500));color:#fff}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
16
+ }
17
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsAvatarComponent, decorators: [{
18
+ type: Component,
19
+ args: [{ selector: 'eds-avatar', standalone: true, template: `
20
+ <span class="eds-avatar" [attr.data-tone]="tone()" [class.eds-avatar--gradient]="gradient()">
21
+ {{ initials() }}
22
+ </span>
23
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".eds-avatar{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:50%;font-family:var(--font-inter);font-size:12px;font-weight:600;flex-shrink:0}.eds-avatar[data-tone=gray]{background:var(--gray-200);color:var(--gray-600)}.eds-avatar[data-tone=brand]{background:#3681f426;color:var(--brand-600)}.eds-avatar--gradient{background:linear-gradient(135deg,var(--brand-500),var(--violet-500));color:#fff}\n"] }]
24
+ }] });
25
+
26
+ export { EdsAvatarComponent as E };
27
+ //# sourceMappingURL=edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs","sources":["../../../projects/voice-receptionist-widget/src/lib/ui/eds-avatar.component.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, input } from '@angular/core';\n\n@Component({\n selector: 'eds-avatar',\n standalone: true,\n template: `\n <span class=\"eds-avatar\" [attr.data-tone]=\"tone()\" [class.eds-avatar--gradient]=\"gradient()\">\n {{ initials() }}\n </span>\n `,\n styleUrl: './eds-avatar.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class EdsAvatarComponent {\n readonly initials = input('?');\n readonly tone = input<'gray' | 'brand'>('gray');\n readonly gradient = input(false);\n}\n"],"names":[],"mappings":";;;MAaa,kBAAkB,CAAA;AAX/B,IAAA,WAAA,GAAA;AAYW,QAAA,IAAA,CAAA,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC;AACrB,QAAA,IAAA,CAAA,IAAI,GAAG,KAAK,CAAmB,MAAM,CAAC;AACtC,QAAA,IAAA,CAAA,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC;AACjC,IAAA;+GAJY,kBAAkB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;AAAlB,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,kBAAkB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,YAAA,EAAA,MAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EARnB;;;;AAIT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,+bAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA,CAAA;;4FAIU,kBAAkB,EAAA,UAAA,EAAA,CAAA;kBAX9B,SAAS;+BACE,YAAY,EAAA,UAAA,EACV,IAAI,EAAA,QAAA,EACN;;;;GAIT,EAAA,eAAA,EAEgB,uBAAuB,CAAC,MAAM,EAAA,MAAA,EAAA,CAAA,+bAAA,CAAA,EAAA;;;;;"}
@@ -0,0 +1,136 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, signal, Injectable, Pipe } from '@angular/core';
3
+ import { io } from 'socket.io-client';
4
+ import { V as VOICE_RECEPTIONIST_CONFIG, e as createVoiceReceptionistPaths } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DClMfxeY.mjs';
5
+
6
+ /**
7
+ * Single shared Socket.io connection to the `/dashboard` namespace.
8
+ * Used by live calls list + per-call transcript stream.
9
+ */
10
+ class DashboardSocketService {
11
+ constructor() {
12
+ this.config = inject(VOICE_RECEPTIONIST_CONFIG);
13
+ this.paths = createVoiceReceptionistPaths(this.config);
14
+ this.connected = signal(false);
15
+ this.socket = null;
16
+ this.refCount = 0;
17
+ this.handlers = new Map();
18
+ this.connectCallbacks = new Set();
19
+ }
20
+ acquire() {
21
+ this.refCount++;
22
+ if (this.refCount === 1) {
23
+ this.connect();
24
+ }
25
+ }
26
+ release() {
27
+ this.refCount = Math.max(0, this.refCount - 1);
28
+ if (this.refCount === 0) {
29
+ this.disconnect();
30
+ }
31
+ }
32
+ onConnect(callback) {
33
+ this.connectCallbacks.add(callback);
34
+ if (this.connected()) {
35
+ callback();
36
+ }
37
+ }
38
+ offConnect(callback) {
39
+ this.connectCallbacks.delete(callback);
40
+ }
41
+ on(event, handler) {
42
+ const wrapped = (payload) => handler(payload);
43
+ if (!this.handlers.has(event)) {
44
+ this.handlers.set(event, new Map());
45
+ }
46
+ this.handlers.get(event).set(handler, wrapped);
47
+ if (this.socket) {
48
+ this.socket.on(event, wrapped);
49
+ }
50
+ }
51
+ off(event, handler) {
52
+ const eventHandlers = this.handlers.get(event);
53
+ const wrapped = eventHandlers?.get(handler);
54
+ if (wrapped) {
55
+ this.socket?.off(event, wrapped);
56
+ eventHandlers.delete(handler);
57
+ }
58
+ }
59
+ emit(event, payload) {
60
+ this.socket?.emit(event, payload);
61
+ }
62
+ connect() {
63
+ if (this.config.useMockApi) {
64
+ this.connected.set(true);
65
+ this.fireConnectCallbacks();
66
+ return;
67
+ }
68
+ this.socket = io(`${this.paths.apiRoot()}/dashboard`, {
69
+ path: '/socket.io',
70
+ transports: ['websocket'],
71
+ autoConnect: false,
72
+ });
73
+ this.socket.on('connect', () => {
74
+ this.connected.set(true);
75
+ this.fireConnectCallbacks();
76
+ });
77
+ this.socket.on('disconnect', () => {
78
+ this.connected.set(false);
79
+ });
80
+ this.socket.io.on('reconnect', () => {
81
+ this.fireConnectCallbacks();
82
+ });
83
+ for (const [event, handlerMap] of this.handlers) {
84
+ for (const wrapped of handlerMap.values()) {
85
+ this.socket.on(event, wrapped);
86
+ }
87
+ }
88
+ this.socket.connect();
89
+ }
90
+ disconnect() {
91
+ if (!this.socket) {
92
+ this.connected.set(false);
93
+ return;
94
+ }
95
+ for (const [event, handlerMap] of this.handlers) {
96
+ for (const wrapped of handlerMap.values()) {
97
+ this.socket.off(event, wrapped);
98
+ }
99
+ }
100
+ this.socket.removeAllListeners();
101
+ this.socket.disconnect();
102
+ this.socket = null;
103
+ this.connected.set(false);
104
+ }
105
+ fireConnectCallbacks() {
106
+ for (const cb of this.connectCallbacks) {
107
+ cb();
108
+ }
109
+ }
110
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DashboardSocketService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
111
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DashboardSocketService, providedIn: 'root' }); }
112
+ }
113
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DashboardSocketService, decorators: [{
114
+ type: Injectable,
115
+ args: [{ providedIn: 'root' }]
116
+ }] });
117
+
118
+ class EdsElapsedPipe {
119
+ transform(startMs) {
120
+ if (!startMs)
121
+ return '0:00';
122
+ const sec = Math.max(0, Math.floor((Date.now() - startMs) / 1000));
123
+ const m = Math.floor(sec / 60);
124
+ const s = sec % 60;
125
+ return `${m}:${s.toString().padStart(2, '0')}`;
126
+ }
127
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsElapsedPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
128
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.17", ngImport: i0, type: EdsElapsedPipe, isStandalone: true, name: "edsElapsed", pure: false }); }
129
+ }
130
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsElapsedPipe, decorators: [{
131
+ type: Pipe,
132
+ args: [{ name: 'edsElapsed', standalone: true, pure: false }]
133
+ }] });
134
+
135
+ export { DashboardSocketService as D, EdsElapsedPipe as E };
136
+ //# sourceMappingURL=edvak99-voice-receptionist-widget-eds-elapsed.pipe-BrMNsxB5.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edvak99-voice-receptionist-widget-eds-elapsed.pipe-BrMNsxB5.mjs","sources":["../../../projects/voice-receptionist-widget/src/lib/services/dashboard-socket.service.ts","../../../projects/voice-receptionist-widget/src/lib/pipes/eds-elapsed.pipe.ts"],"sourcesContent":["import { Injectable, inject, signal } from '@angular/core';\nimport { io, Socket } from 'socket.io-client';\n\nimport { createVoiceReceptionistPaths } from '../api-paths';\nimport { VOICE_RECEPTIONIST_CONFIG } from '../config';\n\ntype SocketHandler = (...args: unknown[]) => void;\n\n/**\n * Single shared Socket.io connection to the `/dashboard` namespace.\n * Used by live calls list + per-call transcript stream.\n */\n@Injectable({ providedIn: 'root' })\nexport class DashboardSocketService {\n private readonly config = inject(VOICE_RECEPTIONIST_CONFIG);\n private readonly paths = createVoiceReceptionistPaths(this.config);\n\n readonly connected = signal(false);\n\n private socket: Socket | null = null;\n private refCount = 0;\n private readonly handlers = new Map<string, Map<SocketHandler, SocketHandler>>();\n private readonly connectCallbacks = new Set<() => void>();\n\n acquire(): void {\n this.refCount++;\n if (this.refCount === 1) {\n this.connect();\n }\n }\n\n release(): void {\n this.refCount = Math.max(0, this.refCount - 1);\n if (this.refCount === 0) {\n this.disconnect();\n }\n }\n\n onConnect(callback: () => void): void {\n this.connectCallbacks.add(callback);\n if (this.connected()) {\n callback();\n }\n }\n\n offConnect(callback: () => void): void {\n this.connectCallbacks.delete(callback);\n }\n\n on<T>(event: string, handler: (payload: T) => void): void {\n const wrapped: SocketHandler = (payload) => handler(payload as T);\n if (!this.handlers.has(event)) {\n this.handlers.set(event, new Map());\n }\n this.handlers.get(event)!.set(handler as SocketHandler, wrapped);\n\n if (this.socket) {\n this.socket.on(event, wrapped);\n }\n }\n\n off<T>(event: string, handler: (payload: T) => void): void {\n const eventHandlers = this.handlers.get(event);\n const wrapped = eventHandlers?.get(handler as SocketHandler);\n if (wrapped) {\n this.socket?.off(event, wrapped);\n eventHandlers!.delete(handler as SocketHandler);\n }\n }\n\n emit(event: string, payload?: unknown): void {\n this.socket?.emit(event, payload);\n }\n\n private connect(): void {\n if (this.config.useMockApi) {\n this.connected.set(true);\n this.fireConnectCallbacks();\n return;\n }\n\n this.socket = io(`${this.paths.apiRoot()}/dashboard`, {\n path: '/socket.io',\n transports: ['websocket'],\n autoConnect: false,\n });\n\n this.socket.on('connect', () => {\n this.connected.set(true);\n this.fireConnectCallbacks();\n });\n\n this.socket.on('disconnect', () => {\n this.connected.set(false);\n });\n\n this.socket.io.on('reconnect', () => {\n this.fireConnectCallbacks();\n });\n\n for (const [event, handlerMap] of this.handlers) {\n for (const wrapped of handlerMap.values()) {\n this.socket.on(event, wrapped);\n }\n }\n\n this.socket.connect();\n }\n\n private disconnect(): void {\n if (!this.socket) {\n this.connected.set(false);\n return;\n }\n\n for (const [event, handlerMap] of this.handlers) {\n for (const wrapped of handlerMap.values()) {\n this.socket.off(event, wrapped);\n }\n }\n\n this.socket.removeAllListeners();\n this.socket.disconnect();\n this.socket = null;\n this.connected.set(false);\n }\n\n private fireConnectCallbacks(): void {\n for (const cb of this.connectCallbacks) {\n cb();\n }\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({ name: 'edsElapsed', standalone: true, pure: false })\nexport class EdsElapsedPipe implements PipeTransform {\n transform(startMs: number | null | undefined): string {\n if (!startMs) return '0:00';\n const sec = Math.max(0, Math.floor((Date.now() - startMs) / 1000));\n const m = Math.floor(sec / 60);\n const s = sec % 60;\n return `${m}:${s.toString().padStart(2, '0')}`;\n }\n}\n"],"names":[],"mappings":"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"}