@edvak99/voice-receptionist-widget 1.1.11 → 1.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -14
- package/fesm2022/{edvak99-voice-receptionist-widget-activities-page.component-CACfxM4x.mjs → edvak99-voice-receptionist-widget-activities-page.component-Br2Vi1Im.mjs} +3 -4
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-Br2Vi1Im.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-analytics-page.component-Dj9vw97B.mjs → edvak99-voice-receptionist-widget-analytics-page.component-CglgGV6H.mjs} +2 -3
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-CglgGV6H.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-api-log.util-C0TOKF1q.mjs.map +1 -1
- package/fesm2022/{edvak99-voice-receptionist-widget-api-logs-page.component-jKBA1N2A.mjs → edvak99-voice-receptionist-widget-api-logs-page.component-DPJiuNjk.mjs} +7 -6
- package/fesm2022/edvak99-voice-receptionist-widget-api-logs-page.component-DPJiuNjk.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-api-logs-table.component-CEf4NPe5.mjs → edvak99-voice-receptionist-widget-api-logs-table.component-CiNc65dO.mjs} +2 -3
- package/fesm2022/edvak99-voice-receptionist-widget-api-logs-table.component-CiNc65dO.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-call-history-detail-page.component-DEplWBbz.mjs → edvak99-voice-receptionist-widget-call-history-detail-page.component-ENq2c-sm.mjs} +5 -7
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-ENq2c-sm.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DoYJFpSa.mjs +200 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DoYJFpSa.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-j1mY5wU0.mjs → edvak99-voice-receptionist-widget-call-recording-player.component-GVg1g-Vq.mjs} +2 -3
- package/fesm2022/edvak99-voice-receptionist-widget-call-recording-player.component-GVg1g-Vq.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-DQ4TT_Su.mjs → edvak99-voice-receptionist-widget-call-review.service-WG30w6Jl.mjs} +2 -2
- package/fesm2022/edvak99-voice-receptionist-widget-call-review.service-WG30w6Jl.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-Deh3jPgo.mjs.map +1 -1
- package/fesm2022/{edvak99-voice-receptionist-widget-dashboard-page.component-DPxQ-wst.mjs → edvak99-voice-receptionist-widget-dashboard-page.component-B_EYljRs.mjs} +4 -5
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-B_EYljRs.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-BJs_TH8X.mjs.map +1 -1
- package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-BFCiJOT3.mjs.map +1 -1
- package/fesm2022/edvak99-voice-receptionist-widget-eds-pulse-dot.component-B8yxY-7p.mjs +14 -0
- package/fesm2022/edvak99-voice-receptionist-widget-eds-pulse-dot.component-B8yxY-7p.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-eds-search-field.component-OGRDvYBp.mjs +83 -0
- package/fesm2022/edvak99-voice-receptionist-widget-eds-search-field.component-OGRDvYBp.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-ai-receptionist-api.service-DcRiHn8q.mjs → edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-pS2jeB4x.mjs} +612 -8
- package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-pS2jeB4x.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-1woGuh0J.mjs +74 -0
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-1woGuh0J.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-live-monitor-page.component-BOQeTD3P.mjs → edvak99-voice-receptionist-widget-live-monitor-page.component-ihwm1LNn.mjs} +5 -7
- package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-ihwm1LNn.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-modal.component-uYApl1KF.mjs.map +1 -1
- package/fesm2022/{edvak99-voice-receptionist-widget-outbound-purposes-page.component-D407n7l0.mjs → edvak99-voice-receptionist-widget-outbound-purposes-page.component-B2koBFU3.mjs} +2 -3
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-B2koBFU3.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-settings-page.component-YAvQ_XV6.mjs → edvak99-voice-receptionist-widget-settings-page.component-C6F-Hgmk.mjs} +2 -3
- package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-C6F-Hgmk.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-task-detail-drawer.component-B3KmFJNH.mjs → edvak99-voice-receptionist-widget-task-detail-drawer.component-BojdiNNx.mjs} +2 -2
- package/fesm2022/edvak99-voice-receptionist-widget-task-detail-drawer.component-BojdiNNx.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-tasks-page.component-BIOGiTgM.mjs → edvak99-voice-receptionist-widget-tasks-page.component-Cf76XyCz.mjs} +3 -4
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-Cf76XyCz.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-wizard-page.component-DpF6y1go.mjs → edvak99-voice-receptionist-widget-wizard-page.component-BQTlz2F5.mjs} +2 -3
- package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-BQTlz2F5.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget.mjs +1 -262
- package/fesm2022/edvak99-voice-receptionist-widget.mjs.map +1 -1
- package/lib/layout/eds-app-shell.component.d.ts +5 -0
- package/lib/layout/eds-route-progress.component.d.ts +15 -0
- package/lib/models/ai-receptionist.types.d.ts +2 -0
- package/lib/models/api-dto.types.d.ts +2 -0
- package/lib/pages/live-calls-page/live-calls-page.component.d.ts +1 -4
- package/lib/pages/live-calls-page/live-calls-shell.component.d.ts +13 -0
- package/lib/services/live-calls-list.service.d.ts +2 -0
- package/lib/ui/eds-search-field.component.d.ts +22 -0
- package/lib/utils/call-history-display.util.d.ts +1 -0
- package/package.json +1 -1
- package/public-api.d.ts +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-CACfxM4x.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-ai-receptionist-api.service-DcRiHn8q.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-Dj9vw97B.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-api-logs-page.component-jKBA1N2A.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-api-logs-table.component-CEf4NPe5.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-DEplWBbz.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-CkjKCteR.mjs +0 -193
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-CkjKCteR.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-recording-player.component-j1mY5wU0.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-review.service-DQ4TT_Su.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-DPxQ-wst.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-socket.service-tTQIsZO6.mjs +0 -120
- package/fesm2022/edvak99-voice-receptionist-widget-dashboard-socket.service-tTQIsZO6.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-eds-pulse-dot.component-CUWZfYRR.mjs +0 -130
- package/fesm2022/edvak99-voice-receptionist-widget-eds-pulse-dot.component-CUWZfYRR.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-BG9cB3zW.mjs +0 -81
- package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-BG9cB3zW.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-BOQeTD3P.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-D407n7l0.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-YAvQ_XV6.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-task-detail-drawer.component-B3KmFJNH.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-BIOGiTgM.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-DpF6y1go.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ Also load Material Symbols + fonts (Inter, DM Sans, Figtree, IBM Plex Mono) in `
|
|
|
74
74
|
|
|
75
75
|
## Local path mapping (no npm publish)
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
For active widget development against an EHR or Voice Hub host, point the host `tsconfig.json` at this repo’s entry file:
|
|
78
78
|
|
|
79
79
|
```json
|
|
80
80
|
{
|
|
@@ -82,36 +82,45 @@ Point the EHR `tsconfig.json` at this repo’s public entry so UI changes hot-re
|
|
|
82
82
|
"baseUrl": "./",
|
|
83
83
|
"paths": {
|
|
84
84
|
"@edvak99/voice-receptionist-widget": [
|
|
85
|
-
"../
|
|
85
|
+
"../edvak_voice_receptionist_widget/src/public-api.ts"
|
|
86
86
|
]
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
Adjust `../` so it resolves
|
|
92
|
+
Adjust `../` so it resolves from the host app root.
|
|
93
93
|
|
|
94
|
-
**Styles (required):** TypeScript `paths` do not apply to Sass.
|
|
95
|
-
|
|
96
|
-
```scss
|
|
97
|
-
@use '../edvak_ai_receptionist_frontend/projects/voice-receptionist-widget/styles';
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
Or after `ng build voice-receptionist-widget`:
|
|
94
|
+
**Styles (required):** TypeScript `paths` do not apply to Sass. Use npm styles after publish:
|
|
101
95
|
|
|
102
96
|
```scss
|
|
103
97
|
@use '@edvak99/voice-receptionist-widget/styles';
|
|
104
98
|
```
|
|
105
99
|
|
|
106
|
-
|
|
100
|
+
For unpublished local builds, link the built package:
|
|
107
101
|
|
|
108
|
-
|
|
102
|
+
```json
|
|
103
|
+
"@edvak99/voice-receptionist-widget": "file:../edvak_voice_receptionist_widget/dist/voice-receptionist-widget"
|
|
104
|
+
```
|
|
109
105
|
|
|
110
|
-
|
|
106
|
+
**Voice Hub host** consumes this package from npm only — see `edvak_ai_receptionist_frontend/docs/REPO_SPLIT.md`.
|
|
111
107
|
|
|
112
108
|
## Build this package
|
|
113
109
|
|
|
114
110
|
```bash
|
|
115
|
-
|
|
111
|
+
npm install
|
|
112
|
+
npm run build
|
|
116
113
|
cd dist/voice-receptionist-widget && npm publish --access public
|
|
117
114
|
```
|
|
115
|
+
|
|
116
|
+
## CI publish (Bitbucket)
|
|
117
|
+
|
|
118
|
+
Pushes to `main`, `feature/**`, and `fix/**` build the library and publish `@edvak99/voice-receptionist-widget` to npm **if that version is not already published**. Bump `version` in `package.json` before a push that should go live.
|
|
119
|
+
|
|
120
|
+
Add the token on **this** repo (`ai_receptionist_widget`), not Voice Hub:
|
|
121
|
+
|
|
122
|
+
1. Open Repository settings → Pipelines → Repository variables
|
|
123
|
+
https://bitbucket.org/edvak-tech/ai_receptionist_widget/admin/addon/admin/pipelines/repository-variables
|
|
124
|
+
2. Enable Pipelines if prompted.
|
|
125
|
+
3. Add `NPM_TOKEN` (mark **Secured**). Use the same npm automation token that published from the old frontend repo. It must be allowed to publish `@edvak99`.
|
|
126
|
+
4. Do not put the token in git. The pipeline injects `${NPM_TOKEN}` at runtime.
|
|
@@ -3,9 +3,8 @@ import { input, output, ChangeDetectionStrategy, Component, inject, signal } fro
|
|
|
3
3
|
import { RouterLink, ActivatedRoute, Router } from '@angular/router';
|
|
4
4
|
import { DatePipe } from '@angular/common';
|
|
5
5
|
import { E as EdsAvatarComponent } from './edvak99-voice-receptionist-widget-eds-avatar.component-BJs_TH8X.mjs';
|
|
6
|
-
import { EdsIconComponent, EdsButtonComponent } from './edvak99-voice-receptionist-widget.mjs';
|
|
7
|
-
import { T as TaskDetailDrawerComponent } from './edvak99-voice-receptionist-widget-task-detail-drawer.component-
|
|
8
|
-
import { A as AiReceptionistApiService, D as DEFAULT_PAGE_SIZE, g as totalPages, d as ageFromDob, l as initialsFromName } from './edvak99-voice-receptionist-widget-ai-receptionist-api.service-DcRiHn8q.mjs';
|
|
6
|
+
import { d as EdsIconComponent, A as AiReceptionistApiService, j as DEFAULT_PAGE_SIZE, k as totalPages, e as ageFromDob, n as initialsFromName, E as EdsButtonComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-pS2jeB4x.mjs';
|
|
7
|
+
import { T as TaskDetailDrawerComponent } from './edvak99-voice-receptionist-widget-task-detail-drawer.component-BojdiNNx.mjs';
|
|
9
8
|
|
|
10
9
|
class PatientProfileDrawerComponent {
|
|
11
10
|
constructor() {
|
|
@@ -452,4 +451,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
|
|
|
452
451
|
}] });
|
|
453
452
|
|
|
454
453
|
export { ActivitiesPageComponent };
|
|
455
|
-
//# sourceMappingURL=edvak99-voice-receptionist-widget-activities-page.component-
|
|
454
|
+
//# sourceMappingURL=edvak99-voice-receptionist-widget-activities-page.component-Br2Vi1Im.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-activities-page.component-Br2Vi1Im.mjs","sources":["../../../src/lib/components/patient-profile-drawer/patient-profile-drawer.component.ts","../../../src/lib/components/patient-profile-drawer/patient-profile-drawer.component.html","../../../src/lib/pages/activities-page/activities-page.component.ts","../../../src/lib/pages/activities-page/activities-page.component.html"],"sourcesContent":["import { DatePipe } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n input,\n output,\n} from '@angular/core';\nimport { RouterLink } from '@angular/router';\n\nimport { AgentActivity } from '../../models/agent-activity.types';\nimport { EdsAvatarComponent } from '../../ui/eds-avatar.component';\nimport { EdsIconComponent } from '../../ui/eds-icon.component';\n\n@Component({\n selector: 'eds-patient-profile-drawer',\n imports: [DatePipe, RouterLink, EdsAvatarComponent, EdsIconComponent],\n templateUrl: './patient-profile-drawer.component.html',\n styleUrl: './patient-profile-drawer.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PatientProfileDrawerComponent {\n readonly patient = input.required<AgentActivity>();\n\n readonly closed = output<void>();\n\n protected close(): void {\n this.closed.emit();\n }\n\n protected onBackdropClick(event: MouseEvent): void {\n if (event.target === event.currentTarget) this.close();\n }\n\n protected display(value: string | null | undefined): string {\n const s = String(value ?? '').trim();\n return s || '—';\n }\n\n protected insuranceLabel(): string {\n const p = this.patient();\n if (p.hasInsurance === true) {\n return p.insuranceName ? `Insured — ${p.insuranceName}` : 'Insured';\n }\n if (p.hasInsurance === false) return 'No insurance on file';\n return p.insuranceName || 'Not verified';\n }\n\n protected insuranceTone(): 'covered' | 'none' | 'unknown' {\n const p = this.patient();\n if (p.hasInsurance === true) return 'covered';\n if (p.hasInsurance === false) return 'none';\n return 'unknown';\n }\n}\n","<div\n class=\"fixed inset-0 z-[200] flex justify-end bg-[rgba(15,23,42,0.4)]\"\n (click)=\"onBackdropClick($event)\"\n>\n <aside\n class=\"patient-profile-drawer flex h-full w-[min(520px,100%)] flex-col bg-[#f7f8fa] shadow-[-8px_0_32px_rgba(15,23,42,0.12)]\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"display(patient().patientName)\"\n >\n <header class=\"flex items-center justify-between gap-3 border-b border-zinc-200 bg-white px-4 py-3\">\n <h2 class=\"m-0 text-base font-semibold text-zinc-900\">Patient profile</h2>\n <button\n type=\"button\"\n class=\"inline-flex h-9 w-9 cursor-pointer items-center justify-center rounded-md border-none bg-transparent text-zinc-500 hover:bg-zinc-100 hover:text-zinc-800\"\n (click)=\"close()\"\n aria-label=\"Close\"\n >\n <eds-icon name=\"close\" [size]=\"22\" />\n </button>\n </header>\n\n <div class=\"flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto p-4\">\n <section class=\"rounded-xl border border-zinc-200 bg-white p-4\">\n <div class=\"flex items-start gap-3\">\n <eds-avatar\n shape=\"box\"\n [initials]=\"patient().patientInitials || '?'\"\n tone=\"brand\"\n />\n <div class=\"min-w-0 flex-1\">\n <h3 class=\"m-0 text-xl font-semibold leading-snug text-zinc-900\">\n {{ display(patient().patientName) }}\n </h3>\n @if (patient().patientDob || patient().patientAge != null || patient().patientGender) {\n <p class=\"mt-1 mb-0 text-sm text-zinc-500\">\n @if (patient().patientDob) {\n DOB: {{ patient().patientDob | date: 'MM/dd/yyyy' }}\n }\n @if (patient().patientAge != null) {\n · {{ patient().patientAge }}Y\n }\n @if (patient().patientGender) {\n · {{ patient().patientGender }}\n }\n </p>\n }\n </div>\n </div>\n </section>\n\n <section class=\"rounded-xl border border-zinc-200 bg-white px-4 py-3\">\n <div class=\"flex items-center justify-between gap-3\">\n <span class=\"text-sm font-medium text-zinc-800\">MRN</span>\n <span class=\"text-sm text-zinc-600\">{{ display(patient().patientMrn) }}</span>\n </div>\n </section>\n\n @if (patient().patientId) {\n <section class=\"rounded-xl border border-zinc-200 bg-white px-4 py-3\">\n <div class=\"flex items-center justify-between gap-3\">\n <span class=\"text-sm font-medium text-zinc-800\">Patient ID</span>\n <span class=\"font-mono text-sm text-zinc-600\">{{ patient().patientId }}</span>\n </div>\n </section>\n }\n\n <section class=\"rounded-xl border border-zinc-200 bg-white px-4 py-3\">\n <div class=\"flex items-center justify-between gap-3\">\n <span class=\"text-sm font-medium text-zinc-800\">Insurance</span>\n <span\n class=\"patient-profile-drawer__insurance inline-flex items-center rounded-full border px-2.5 py-1 text-xs font-medium\"\n [attr.data-state]=\"insuranceTone()\"\n >\n {{ insuranceLabel() }}\n </span>\n </div>\n </section>\n\n <section class=\"rounded-xl border border-zinc-200 bg-white px-4 py-3\">\n <div class=\"flex items-center justify-between gap-3\">\n <span class=\"text-sm font-medium text-zinc-800\">Registered</span>\n <span class=\"text-sm text-zinc-600\">\n @if (patient().occurredAt) {\n {{ patient().occurredAt | date: 'MMM d, y, h:mm a' }}\n } @else {\n —\n }\n </span>\n </div>\n </section>\n\n @if (patient().detail) {\n <section class=\"rounded-xl border border-zinc-200 bg-white p-4\">\n <h3 class=\"m-0 text-sm font-semibold text-zinc-800\">Details</h3>\n <p class=\"mt-2 mb-0 whitespace-pre-wrap text-sm leading-relaxed text-zinc-600\">\n {{ patient().detail }}\n </p>\n </section>\n }\n\n @if (patient().callId) {\n <section class=\"rounded-xl border border-zinc-200 bg-white px-4 py-3\">\n <div class=\"flex items-center justify-between gap-3\">\n <span class=\"text-sm font-medium text-zinc-800\">Call</span>\n <a\n [routerLink]=\"['../history', patient().callId]\"\n class=\"text-sm font-medium text-blue-600 no-underline hover:underline\"\n (click)=\"close()\"\n >\n {{ patient().callId }}\n </a>\n </div>\n </section>\n }\n </div>\n </aside>\n</div>\n","import { ChangeDetectionStrategy, Component, OnInit, inject, signal } from '@angular/core';\nimport { ActivatedRoute, Router } from '@angular/router';\n\nimport { PatientProfileDrawerComponent } from '../../components/patient-profile-drawer/patient-profile-drawer.component';\nimport { TaskDetailDrawerComponent } from '../../components/task-detail-drawer/task-detail-drawer.component';\nimport {\n ActivitiesTab,\n AgentActivity,\n initialsFromName,\n} from '../../models/agent-activity.types';\nimport { TaskItem, TaskStatus } from '../../models/ai-receptionist.types';\nimport { DEFAULT_PAGE_SIZE, totalPages as calcTotalPages } from '../../models/pagination';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport { ageFromDob } from '../../utils/call-history-display.util';\nimport { EdsAvatarComponent } from '../../ui/eds-avatar.component';\nimport { EdsButtonComponent } from '../../ui/eds-button.component';\nimport { EdsIconComponent } from '../../ui/eds-icon.component';\n\ntype TabId = ActivitiesTab;\n\nconst TABS: { id: TabId; label: string }[] = [\n { id: 'task_created', label: 'Tasks' },\n { id: 'appointment_booked', label: 'Appointments' },\n { id: 'patient_created', label: 'Registrations' },\n];\n\nconst AVATAR_TONES = ['green', 'amber', 'brand', 'violet', 'rose'] as const;\n\n@Component({\n selector: 'eds-activities-page',\n imports: [\n EdsAvatarComponent,\n EdsButtonComponent,\n EdsIconComponent,\n TaskDetailDrawerComponent,\n PatientProfileDrawerComponent,\n ],\n templateUrl: './activities-page.component.html',\n styleUrl: './activities-page.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ActivitiesPageComponent implements OnInit {\n private readonly api = inject(AiReceptionistApiService);\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n\n protected readonly tabs = TABS;\n protected readonly pageSize = DEFAULT_PAGE_SIZE;\n protected readonly items = signal<AgentActivity[]>([]);\n protected readonly total = signal(0);\n protected readonly page = signal(1);\n protected readonly activeTab = signal<TabId>('task_created');\n protected readonly callIdFilter = signal<string | null>(null);\n protected readonly loading = signal(true);\n protected readonly loadError = signal<string | null>(null);\n protected readonly selectedTask = signal<TaskItem | null>(null);\n protected readonly selectedPatient = signal<AgentActivity | null>(null);\n\n ngOnInit(): void {\n const callId = this.route.snapshot.queryParamMap.get('callId')?.trim() || null;\n if (callId) this.callIdFilter.set(callId);\n\n const tabParam = (this.route.snapshot.queryParamMap.get('tab') || '').trim().toLowerCase();\n const tabFromQuery = this.tabFromQuery(tabParam);\n if (tabFromQuery) {\n this.activeTab.set(tabFromQuery);\n } else {\n this.activeTab.set('task_created');\n void this.router.navigate([], {\n relativeTo: this.route,\n queryParams: { tab: 'tasks' },\n queryParamsHandling: 'merge',\n replaceUrl: true,\n });\n }\n\n this.load();\n }\n\n protected setTab(tab: TabId): void {\n if (this.activeTab() === tab) return;\n this.activeTab.set(tab);\n this.page.set(1);\n void this.router.navigate([], {\n relativeTo: this.route,\n queryParams: { tab: this.tabQueryValue(tab) },\n queryParamsHandling: 'merge',\n replaceUrl: true,\n });\n this.load();\n }\n\n private tabFromQuery(raw: string): TabId | null {\n switch (raw) {\n case 'tasks':\n case 'task':\n case 'task_created':\n return 'task_created';\n case 'appointments':\n case 'appointment':\n case 'appointment_booked':\n return 'appointment_booked';\n case 'registrations':\n case 'registration':\n case 'patient_created':\n return 'patient_created';\n default:\n return null;\n }\n }\n\n private tabQueryValue(tab: TabId): string {\n switch (tab) {\n case 'task_created':\n return 'tasks';\n case 'patient_created':\n return 'registrations';\n default:\n return 'appointments';\n }\n }\n\n protected clearCallFilter(): void {\n this.callIdFilter.set(null);\n this.page.set(1);\n this.load();\n }\n\n protected load(): void {\n this.loading.set(true);\n this.loadError.set(null);\n const tab = this.activeTab();\n this.api\n .getActivities({\n page: this.page(),\n limit: DEFAULT_PAGE_SIZE,\n types: tab,\n callId: this.callIdFilter() || undefined,\n })\n .subscribe({\n next: ({ items, total, page }) => {\n this.items.set(items);\n this.total.set(total);\n this.page.set(page);\n this.loading.set(false);\n },\n error: () => {\n this.loading.set(false);\n this.items.set([]);\n this.total.set(0);\n this.loadError.set(\n 'Activities feed is unavailable. Confirm GET /api/ai-receptionist/activities is deployed.',\n );\n },\n });\n }\n\n protected totalPages(): number {\n return calcTotalPages(this.total(), DEFAULT_PAGE_SIZE);\n }\n\n protected prevPage(): void {\n if (this.page() <= 1) return;\n this.page.update((p) => p - 1);\n this.load();\n }\n\n protected nextPage(): void {\n if (this.page() >= this.totalPages()) return;\n this.page.update((p) => p + 1);\n this.load();\n }\n\n protected historyLink(callId: string | null | undefined): string[] | null {\n return callId ? ['../history', callId] : null;\n }\n\n protected openCallHistory(callId: string | null | undefined, ev?: Event): void {\n if (!callId) return;\n if (ev && (ev.target as HTMLElement).closest('a,button')) return;\n void this.router.navigate(['../history', callId], { relativeTo: this.route });\n }\n\n protected openTask(item: AgentActivity): void {\n this.selectedPatient.set(null);\n this.selectedTask.set(this.activityToTask(item));\n }\n\n protected openPatient(item: AgentActivity): void {\n this.selectedTask.set(null);\n this.selectedPatient.set(item);\n }\n\n protected closeTaskDrawer(): void {\n this.selectedTask.set(null);\n }\n\n protected closePatientDrawer(): void {\n this.selectedPatient.set(null);\n }\n\n protected onTaskStatusChange(status: TaskStatus): void {\n const task = this.selectedTask();\n if (!task) return;\n this.api.patchTask(task.id, status).subscribe({\n next: ({ data }) => {\n if (data) {\n this.selectedTask.set(data);\n } else {\n this.selectedTask.set({ ...task, status });\n }\n },\n error: () => {\n // Optimistic local update if patch fails (activity-sourced ids may not exist).\n this.selectedTask.set({ ...task, status });\n },\n });\n }\n\n private activityToTask(item: AgentActivity): TaskItem {\n const priority =\n item.priority === 'urgent' || item.priority === 'high' ? 'urgent' : 'normal';\n const created = this.parseDate(item.occurredAt)?.getTime() ?? Date.now();\n return {\n id: item.taskId || item.id,\n created,\n title: this.taskTitle(item),\n type: 'task',\n description: item.detail || '',\n patient: item.patientName || 'Unknown caller',\n priority,\n status: 'open',\n source: 'voice',\n assignee: 'AI Receptionist',\n callId: item.callId || undefined,\n };\n }\n\n protected displayMrn(item: AgentActivity): string {\n return item.patientMrn?.trim() || '—';\n }\n\n protected providerSpecialtyLabel(item: AgentActivity): string | null {\n const s = item.providerSpecialty?.trim();\n if (!s) return null;\n // Hide stray numeric IDs that sometimes land in specialty.\n if (/^\\d+(\\.\\d+)?$/.test(s)) return null;\n return s;\n }\n\n /** Day-anchored stamp — a bare clock time gave no idea which day a row belonged to. */\n protected timestampLabel(item: AgentActivity): string {\n const start = this.parseDate(item.startAt || item.occurredAt);\n if (!start) return '—';\n const stamp = `${this.formatDay(start)}, ${this.formatClock(start)}`;\n const end = this.parseDate(item.endAt);\n if (end && end.getTime() > start.getTime()) {\n return `${stamp} – ${this.formatClock(end)}`;\n }\n return stamp;\n }\n\n protected durationLabel(item: AgentActivity): string | null {\n const mins = item.durationMinutes;\n if (mins == null || mins <= 0) {\n if (item.startAt && item.endAt) {\n const ms = Date.parse(item.endAt) - Date.parse(item.startAt);\n if (Number.isFinite(ms) && ms > 0) {\n return `${Math.round(ms / 60000)} MIN`;\n }\n }\n return null;\n }\n return `${mins} MIN`;\n }\n\n protected categoryLabel(item: AgentActivity): string {\n if (item.visitType) return item.visitType;\n switch (item.type) {\n case 'appointment_booked':\n return 'Appointment';\n case 'patient_created':\n return 'Registration';\n case 'task_created':\n return item.title && !/^task created$/i.test(item.title) ? item.title : 'Task';\n default:\n return item.title || 'Activity';\n }\n }\n\n protected patientMeta(item: AgentActivity): string | null {\n const parts: string[] = [];\n const dob = item.patientDob;\n const age = item.patientAge ?? (dob ? ageFromDob(dob) : null);\n if (dob && age != null) parts.push(`DOB: ${this.formatDob(dob)} · ${age}Y`);\n else if (dob) parts.push(`DOB: ${this.formatDob(dob)}`);\n else if (age != null) parts.push(`${age}Y`);\n if (item.patientGender) parts.push(item.patientGender);\n return parts.length ? parts.join(' | ') : null;\n }\n\n protected patientInitials(item: AgentActivity): string {\n return item.patientInitials || initialsFromName(item.patientName) || '?';\n }\n\n protected providerInitials(item: AgentActivity): string {\n return item.providerInitials || initialsFromName(item.providerName) || '?';\n }\n\n protected avatarTone(key: string | null | undefined, salt: number): string {\n const raw = `${key || 'x'}:${salt}`;\n let hash = 0;\n for (let i = 0; i < raw.length; i++) hash = (hash + raw.charCodeAt(i) * (i + 1)) % 997;\n return AVATAR_TONES[hash % AVATAR_TONES.length];\n }\n\n protected insuranceState(item: AgentActivity): 'covered' | 'none' | 'unknown' {\n if (item.hasInsurance === true) return 'covered';\n if (item.hasInsurance === false) return 'none';\n return 'unknown';\n }\n\n protected insuranceIcon(item: AgentActivity): string {\n switch (this.insuranceState(item)) {\n case 'covered':\n return 'verified_user';\n case 'none':\n return 'gpp_bad';\n default:\n return 'gpp_maybe';\n }\n }\n\n protected insuranceLabel(item: AgentActivity): string {\n switch (this.insuranceState(item)) {\n case 'covered':\n return 'Insured';\n case 'none':\n return 'No insurance';\n default:\n return 'Unverified';\n }\n }\n\n protected insuranceTitle(item: AgentActivity): string {\n switch (this.insuranceState(item)) {\n case 'covered':\n return item.insuranceName ? `Insured — ${item.insuranceName}` : 'Insured';\n case 'none':\n return 'No insurance on file';\n default:\n return 'Insurance not verified';\n }\n }\n\n protected isTaskRow(item: AgentActivity): boolean {\n return item.type === 'task_created';\n }\n\n protected taskTitle(item: AgentActivity): string {\n const title = (item.title || '').trim();\n if (title && !/^task created$/i.test(title)) return title;\n return item.visitType || 'Task';\n }\n\n protected priorityLabel(item: AgentActivity): string {\n switch (item.priority) {\n case 'urgent':\n return 'Urgent';\n case 'high':\n return 'High';\n case 'low':\n return 'Low';\n case 'medium':\n case 'normal':\n default:\n return 'Medium';\n }\n }\n\n protected priorityTone(item: AgentActivity): string {\n switch (item.priority) {\n case 'urgent':\n return 'urgent';\n case 'high':\n return 'high';\n case 'low':\n return 'low';\n default:\n return 'medium';\n }\n }\n\n protected isRegistrationTab(): boolean {\n return this.activeTab() === 'patient_created';\n }\n\n protected emptyLabel(): string {\n if (this.callIdFilter()) return 'No activities found for this call.';\n switch (this.activeTab()) {\n case 'task_created':\n return 'No tasks created by the AI yet.';\n case 'patient_created':\n return 'No patient registrations yet.';\n default:\n return 'No appointments booked by the AI yet.';\n }\n }\n\n private parseDate(iso: string | null | undefined): Date | null {\n if (!iso) return null;\n const d = new Date(iso);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n\n private formatDay(d: Date): string {\n const midnight = (x: Date) => new Date(x.getFullYear(), x.getMonth(), x.getDate()).getTime();\n const now = new Date();\n const days = Math.round((midnight(d) - midnight(now)) / 86_400_000);\n if (days === 0) return 'Today';\n if (days === 1) return 'Tomorrow';\n if (days === -1) return 'Yesterday';\n return d.toLocaleDateString(\n undefined,\n d.getFullYear() === now.getFullYear()\n ? { weekday: 'short', month: 'short', day: 'numeric' }\n : { month: 'short', day: 'numeric', year: 'numeric' },\n );\n }\n\n private formatClock(d: Date): string {\n return d.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });\n }\n\n private formatDob(iso: string): string {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(iso.trim());\n if (m) return `${m[2]}-${m[3]}-${m[1]}`;\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return iso;\n const mm = String(d.getMonth() + 1).padStart(2, '0');\n const dd = String(d.getDate()).padStart(2, '0');\n return `${mm}-${dd}-${d.getFullYear()}`;\n }\n}\n","<div class=\"eds-page activities-page\">\n <header class=\"mb-5\">\n <div>\n <h1 class=\"eds-h1\">Activities</h1>\n <p class=\"eds-body-sm\">Review all the activities done by the AI at one place</p>\n </div>\n </header>\n\n @if (callIdFilter(); as callId) {\n <div\n class=\"mb-4 flex flex-wrap items-center justify-between gap-3 rounded-lg bg-blue-50 px-4 py-3 text-sm font-normal leading-snug text-zinc-600\"\n role=\"status\"\n >\n <span>Showing activities for call <strong class=\"font-medium text-zinc-700\">{{ callId }}</strong></span>\n <button\n type=\"button\"\n class=\"cursor-pointer border-none bg-transparent px-0 py-1 text-sm font-medium text-blue-600 hover:underline\"\n (click)=\"clearCallFilter()\"\n >\n Clear filter\n </button>\n </div>\n }\n\n <nav class=\"mb-5 flex flex-wrap gap-2\" aria-label=\"Activity type\">\n @for (tab of tabs; track tab.id) {\n <button\n type=\"button\"\n [class]=\"\n activeTab() === tab.id\n ? 'cursor-pointer rounded-full border-none bg-blue-50 px-4 py-2 text-sm font-medium text-blue-600 transition-colors'\n : 'cursor-pointer rounded-full border-none bg-transparent px-4 py-2 text-sm font-medium text-zinc-500 transition-colors hover:bg-white hover:text-zinc-700'\n \"\n (click)=\"setTab(tab.id)\"\n >\n {{ tab.label }}\n </button>\n }\n </nav>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading activities…</p>\n } @else if (loadError()) {\n <p class=\"text-sm text-red-700\" role=\"alert\">{{ loadError() }}</p>\n } @else {\n @if (activeTab() === 'task_created') {\n <div\n class=\"activities-page__col-head activities-page__col-head--tasks grid gap-x-7 gap-y-4 pb-2 text-sm font-semibold leading-tight text-zinc-600 max-lg:hidden\"\n aria-hidden=\"true\"\n >\n <span>Priority</span>\n <span>Task</span>\n <span>Patient / Caller</span>\n </div>\n } @else if (activeTab() === 'appointment_booked') {\n <div\n class=\"activities-page__col-head activities-page__col-head--appointments grid gap-x-7 gap-y-4 pb-2 text-sm font-semibold leading-tight text-zinc-600 max-lg:hidden\"\n aria-hidden=\"true\"\n >\n <span>When</span>\n <span>Patient</span>\n <span>Provider</span>\n <span class=\"justify-self-end text-right\">Insurance</span>\n </div>\n } @else if (activeTab() === 'patient_created') {\n <div\n class=\"activities-page__col-head activities-page__col-head--registration grid gap-x-7 gap-y-4 pb-2 text-sm font-semibold leading-tight text-zinc-600 max-lg:hidden\"\n aria-hidden=\"true\"\n >\n <span>When</span>\n <span>Patient</span>\n <span>MRN</span>\n <span class=\"justify-self-end text-right\">Insurance</span>\n </div>\n }\n\n <ul class=\"m-0 flex list-none flex-col gap-3 p-0\">\n @for (item of items(); track item.id; let i = $index) {\n @if (isTaskRow(item)) {\n <li\n class=\"activities-page__card activities-page__card--task grid cursor-pointer items-center gap-x-7 gap-y-4 rounded-xl border border-zinc-200 bg-white px-4 py-4 max-lg:gap-x-4 max-lg:gap-y-4 hover:border-blue-200 hover:shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"'View task: ' + taskTitle(item)\"\n (click)=\"openTask(item)\"\n (keyup.enter)=\"openTask(item)\"\n >\n <div class=\"inline-flex items-center gap-2 text-sm font-medium leading-snug text-zinc-700\" [attr.data-tone]=\"priorityTone(item)\">\n <span class=\"activities-page__priority-mark h-2.5 w-2.5 shrink-0 rotate-45 rounded-sm\" aria-hidden=\"true\"></span>\n <span class=\"capitalize\">{{ priorityLabel(item) }}</span>\n </div>\n\n <div class=\"text-sm font-medium leading-snug text-zinc-700 max-lg:col-span-full\">{{ taskTitle(item) }}</div>\n\n <div class=\"flex min-w-0 items-center gap-3 max-lg:col-span-full\">\n <eds-avatar\n shape=\"box\"\n [initials]=\"patientInitials(item)\"\n [tone]=\"avatarTone(item.patientId || item.patientName || item.id, i)\"\n />\n <div class=\"flex min-w-0 flex-col gap-1\">\n <span class=\"truncate text-sm font-medium leading-snug text-zinc-700\">{{ item.patientName || 'Unknown caller' }}</span>\n @if (patientMeta(item); as meta) {\n <span class=\"truncate text-xs leading-snug text-zinc-500\">{{ meta }}</span>\n }\n </div>\n </div>\n </li>\n } @else if (isRegistrationTab()) {\n <li\n class=\"activities-page__card activities-page__card--registration grid cursor-pointer items-center gap-x-7 gap-y-4 rounded-xl border border-zinc-200 bg-white px-4 py-4 max-lg:gap-x-4 max-lg:gap-y-4 hover:border-blue-200 hover:shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"'View patient: ' + (item.patientName || 'Unknown patient')\"\n (click)=\"openPatient(item)\"\n (keyup.enter)=\"openPatient(item)\"\n >\n <div class=\"min-w-0\">\n <div class=\"mb-1 flex flex-wrap items-center gap-2\">\n <span class=\"whitespace-nowrap text-base font-medium leading-snug text-zinc-700 max-md:whitespace-normal\">{{ timestampLabel(item) }}</span>\n </div>\n <p class=\"m-0 text-sm leading-snug text-zinc-500\">{{ categoryLabel(item) }}</p>\n </div>\n\n <div class=\"flex min-w-0 items-center gap-3 max-lg:col-span-full\">\n <eds-avatar\n shape=\"box\"\n [initials]=\"patientInitials(item)\"\n [tone]=\"avatarTone(item.patientId || item.patientName || item.id, i)\"\n />\n <div class=\"flex min-w-0 flex-col gap-1\">\n <span class=\"truncate text-sm font-medium leading-snug text-zinc-700\">{{ item.patientName || 'Unknown patient' }}</span>\n @if (patientMeta(item); as meta) {\n <span class=\"truncate text-xs leading-snug text-zinc-500\">{{ meta }}</span>\n }\n </div>\n </div>\n\n <div class=\"text-sm font-normal leading-snug text-zinc-600\">{{ displayMrn(item) }}</div>\n\n <div class=\"flex items-center justify-end gap-2.5\">\n <span\n class=\"activities-page__insurance inline-flex h-8 w-8 items-center justify-center rounded-full border border-transparent max-md:px-1.5\"\n [attr.data-state]=\"insuranceState(item)\"\n [title]=\"insuranceTitle(item)\"\n [attr.aria-label]=\"insuranceLabel(item)\"\n >\n <eds-icon [name]=\"insuranceIcon(item)\" [size]=\"18\" />\n </span>\n </div>\n </li>\n } @else {\n <li\n [class]=\"\n 'activities-page__card grid items-center gap-x-7 gap-y-4 rounded-xl border border-zinc-200 bg-white px-4 py-4 max-lg:gap-x-4 max-lg:gap-y-4' +\n (item.callId\n ? ' cursor-pointer hover:border-blue-200 hover:shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600'\n : '')\n \"\n [attr.role]=\"item.callId ? 'link' : null\"\n [attr.tabindex]=\"item.callId ? 0 : null\"\n (click)=\"openCallHistory(item.callId, $event)\"\n (keyup.enter)=\"openCallHistory(item.callId)\"\n >\n <div class=\"min-w-0\">\n <div class=\"mb-1 flex flex-wrap items-center gap-2\">\n <span class=\"whitespace-nowrap text-base font-medium leading-snug text-zinc-700 max-md:whitespace-normal\">{{ timestampLabel(item) }}</span>\n </div>\n <p class=\"m-0 text-sm leading-snug text-zinc-500\">{{ categoryLabel(item) }}</p>\n </div>\n\n <div class=\"flex min-w-0 items-center gap-3 max-lg:col-span-full\">\n <eds-avatar\n shape=\"box\"\n [initials]=\"patientInitials(item)\"\n [tone]=\"avatarTone(item.patientId || item.patientName || item.id, i)\"\n />\n <div class=\"flex min-w-0 flex-col gap-1\">\n <span class=\"truncate text-sm font-medium leading-snug text-zinc-700\">{{ item.patientName || 'Unknown patient' }}</span>\n @if (patientMeta(item); as meta) {\n <span class=\"truncate text-xs leading-snug text-zinc-500\">{{ meta }}</span>\n }\n </div>\n </div>\n\n <div class=\"flex min-w-0 items-center gap-3 max-lg:col-span-full\">\n @if (item.providerName) {\n <eds-avatar\n shape=\"circle\"\n [initials]=\"providerInitials(item)\"\n [tone]=\"avatarTone(item.providerId || item.providerName, i + 3)\"\n />\n <div class=\"flex min-w-0 flex-col gap-1\">\n <span class=\"truncate text-sm font-medium leading-snug text-zinc-700\">{{ item.providerName }}</span>\n @if (providerSpecialtyLabel(item); as specialty) {\n <span class=\"truncate text-xs leading-snug text-zinc-500\">{{ specialty }}</span>\n }\n </div>\n } @else {\n <div class=\"flex min-h-9 items-center\">\n <span class=\"truncate text-xs leading-snug text-zinc-500\">No provider assigned</span>\n </div>\n }\n </div>\n\n <div class=\"flex items-center justify-end gap-3\">\n <span\n class=\"activities-page__insurance inline-flex h-8 w-8 items-center justify-center rounded-full border border-transparent max-md:px-2\"\n [attr.data-state]=\"insuranceState(item)\"\n [title]=\"insuranceTitle(item)\"\n [attr.aria-label]=\"insuranceLabel(item)\"\n >\n <eds-icon [name]=\"insuranceIcon(item)\" [size]=\"18\" />\n </span>\n </div>\n </li>\n }\n } @empty {\n <li class=\"rounded-xl border border-dashed border-zinc-200 bg-white px-4 py-10 text-center text-sm leading-snug text-zinc-500\">\n {{ emptyLabel() }}\n </li>\n }\n </ul>\n\n @if (total() > pageSize) {\n <footer class=\"mt-4 flex flex-wrap items-center justify-between gap-3\">\n <span class=\"eds-body-sm\">\n Page {{ page() }} of {{ totalPages() }} · {{ total() }} total\n </span>\n <div class=\"flex gap-2\">\n <eds-button variant=\"secondary\" [disabled]=\"page() <= 1\" (click)=\"prevPage()\">Previous</eds-button>\n <eds-button variant=\"secondary\" [disabled]=\"page() >= totalPages()\" (click)=\"nextPage()\">Next</eds-button>\n </div>\n </footer>\n }\n }\n</div>\n\n@if (selectedTask(); as task) {\n <eds-task-detail-drawer\n [task]=\"task\"\n (closed)=\"closeTaskDrawer()\"\n (statusChange)=\"onTaskStatusChange($event)\"\n />\n}\n\n@if (selectedPatient(); as patient) {\n <eds-patient-profile-drawer\n [patient]=\"patient\"\n (closed)=\"closePatientDrawer()\"\n />\n}\n"],"names":["calcTotalPages"],"mappings":"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"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, signal, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
-
import { A as AiReceptionistApiService } from './edvak99-voice-receptionist-widget-
|
|
4
|
-
import { EdsIconComponent, EdsButtonComponent } from './edvak99-voice-receptionist-widget.mjs';
|
|
3
|
+
import { A as AiReceptionistApiService, d as EdsIconComponent, E as EdsButtonComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-pS2jeB4x.mjs';
|
|
5
4
|
|
|
6
5
|
const REASON_COLORS = ['#8E51FF', '#3681F4', '#F472B6', '#7DD3FC', '#FDA4AF', '#34D399', '#FBBF24'];
|
|
7
6
|
const STUCK_COLORS = ['#8E51FF', '#3681F4', '#7DD3FC', '#FBBF24'];
|
|
@@ -274,4 +273,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
|
|
|
274
273
|
}] });
|
|
275
274
|
|
|
276
275
|
export { AnalyticsPageComponent };
|
|
277
|
-
//# sourceMappingURL=edvak99-voice-receptionist-widget-analytics-page.component-
|
|
276
|
+
//# sourceMappingURL=edvak99-voice-receptionist-widget-analytics-page.component-CglgGV6H.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-analytics-page.component-CglgGV6H.mjs","sources":["../../../src/lib/pages/analytics-page/analytics-page.component.ts","../../../src/lib/pages/analytics-page/analytics-page.component.html"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, inject, OnInit, signal } from '@angular/core';\n\nimport {\n AnalyticsData,\n AnalyticsKpiCard,\n AnalyticsPeakPoint,\n AnalyticsVolumePoint,\n} from '../../models/ai-receptionist.types';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport { EdsButtonComponent } from '../../ui/eds-button.component';\nimport { EdsIconComponent } from '../../ui/eds-icon.component';\n\nconst REASON_COLORS = ['#8E51FF', '#3681F4', '#F472B6', '#7DD3FC', '#FDA4AF', '#34D399', '#FBBF24'];\nconst STUCK_COLORS = ['#8E51FF', '#3681F4', '#7DD3FC', '#FBBF24'];\n\n@Component({\n selector: 'eds-analytics-page',\n imports: [EdsIconComponent, EdsButtonComponent],\n templateUrl: './analytics-page.component.html',\n styleUrl: './analytics-page.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AnalyticsPageComponent implements OnInit {\n private readonly api = inject(AiReceptionistApiService);\n\n protected readonly data = signal<AnalyticsData | null>(null);\n protected readonly range = signal('30d');\n protected readonly loading = signal(true);\n protected readonly chartsReady = signal(false);\n protected readonly hoveredVolume = signal<number | null>(null);\n\n protected readonly ranges = [\n { id: '7d', label: '7 days' },\n { id: '30d', label: '30 days' },\n { id: '90d', label: '90 days' },\n ];\n\n protected readonly impactKpi = computed(() =>\n this.findKpi(/(impact|hours|front-desk|time saved)/i),\n );\n protected readonly resolutionKpi = computed(() =>\n this.findKpi(/(autonomous|resolution)/i),\n );\n protected readonly secondaryKpis = computed(() => {\n const cards = this.data()?.kpiCards ?? [];\n const preferred = [\n /appointment/i,\n /task/i,\n /patient|registr/i,\n /calls?\\s*handled|total\\s*calls|^calls$/i,\n ];\n const picked: AnalyticsKpiCard[] = [];\n for (const re of preferred) {\n const hit = cards.find((c) => re.test(c.label) && !picked.includes(c));\n if (hit) picked.push(hit);\n }\n if (picked.length >= 4) return picked.slice(0, 4);\n const rest = cards.filter(\n (c) =>\n !picked.includes(c) &&\n !/(impact|hours|front-desk|autonomous|resolution)/i.test(c.label),\n );\n return [...picked, ...rest].slice(0, 4);\n });\n\n protected readonly distribution = computed(() => {\n const outcomes = this.data()?.outcomes ?? [];\n const total = outcomes.reduce((s, o) => s + o.value, 0) || 1;\n return outcomes.map((o) => ({\n ...o,\n pct: Math.round((o.value / total) * 100),\n width: (o.value / total) * 100,\n }));\n });\n\n protected readonly totalCallsLabel = computed(() => {\n const handled = this.findKpi(/calls?\\s*handled|total\\s*calls|^calls$/i);\n if (handled?.value) return handled.value.includes('Call') ? handled.value : `${handled.value} Calls`;\n const n = this.data()?.kpis.totalCalls;\n return n ? `${n.toLocaleString()} Calls` : 'Calls';\n });\n\n protected readonly directionSub = computed(() => {\n const handled = this.findKpi(/calls?\\s*handled|total\\s*calls|^calls$/i);\n if (handled?.sub && /inbound|outbound/i.test(handled.sub)) return handled.sub;\n return null;\n });\n\n protected readonly volumePoints = computed(() => this.data()?.volume.points ?? []);\n\n /** Human date span for the selected analytics window (month + year included). */\n protected readonly periodLabel = computed(() => {\n const vol = this.data()?.volume;\n const start =\n this.parseIsoDate(vol?.startDate) ?? this.parseIsoDate(vol?.points?.[0]?.date) ?? null;\n const end =\n this.parseIsoDate(vol?.endDate) ??\n this.parseIsoDate(vol?.points?.[vol.points.length - 1]?.date) ??\n null;\n if (start && end) return this.formatPeriodRange(start, end);\n\n const days = this.range() === '7d' ? 7 : this.range() === '90d' ? 90 : 30;\n const endD = new Date();\n const startD = new Date();\n startD.setDate(endD.getDate() - (days - 1));\n return this.formatPeriodRange(startD, endD);\n });\n\n ngOnInit(): void {\n this.load();\n }\n\n protected setRange(id: string): void {\n this.range.set(id);\n this.load();\n }\n\n protected maxCount(items: { count: number }[]): number {\n return Math.max(...items.map((i) => i.count), 1);\n }\n\n protected barHeightPercent(point: { count: number }, points: { count: number }[]): number {\n if (point.count <= 0) return 0;\n return (point.count / this.maxCount(points)) * 100;\n }\n\n protected maxValue(items: { value: number }[]): number {\n return Math.max(...items.map((i) => i.value), 1);\n }\n\n protected reasonColor(index: number): string {\n return REASON_COLORS[index % REASON_COLORS.length];\n }\n\n protected stuckColor(index: number): string {\n return STUCK_COLORS[index % STUCK_COLORS.length];\n }\n\n protected peakTooltip(point: AnalyticsPeakPoint): string {\n return `${point.label}: ${point.count} call${point.count === 1 ? '' : 's'}`;\n }\n\n protected volumeTooltip(point: AnalyticsVolumePoint): string {\n const inbound = point.inbound ?? Math.round(point.count * 0.8);\n const outbound = point.outbound ?? Math.max(0, point.count - inbound);\n const headline = this.volumePointHeadline(point);\n return `${headline}\\nTotal ${point.count} · In ${inbound} · Out ${outbound}`;\n }\n\n protected hbarTooltip(label: string, value: number): string {\n return `${label}: ${value}%`;\n }\n\n protected distributionTooltip(label: string, value: number): string {\n const calls = this.estimatedCalls(value);\n return `${label}: ${value}% (${calls.toLocaleString()} calls)`;\n }\n\n protected showPeakAxisLabel(index: number, total: number): boolean {\n if (total <= 12) return true;\n const hour = this.data()?.peakHours.points[index]?.hour;\n if (hour != null && [9, 12, 15, 18, 21].includes(hour)) return true;\n const step = total <= 24 ? 3 : 4;\n return index % step === 0 || index === total - 1;\n }\n\n protected volumePath(series: 'total' | 'inbound' | 'outbound'): string {\n const points = this.volumePoints();\n if (!points.length) return '';\n const max = Math.max(\n ...points.map((p) => Math.max(p.count, p.inbound ?? 0, p.outbound ?? 0)),\n 1,\n );\n const w = 100;\n const h = 100;\n const step = points.length === 1 ? 0 : w / (points.length - 1);\n return points\n .map((p, i) => {\n const yVal =\n series === 'inbound'\n ? (p.inbound ?? Math.round(p.count * 0.8))\n : series === 'outbound'\n ? (p.outbound ?? Math.round(p.count * 0.2))\n : p.count;\n const x = i * step;\n const y = h - (yVal / max) * h;\n return `${i === 0 ? 'M' : 'L'}${x.toFixed(2)},${y.toFixed(2)}`;\n })\n .join(' ');\n }\n\n protected volumeAreaPath(): string {\n const line = this.volumePath('total');\n if (!line) return '';\n return `${line} L100,100 L0,100 Z`;\n }\n\n protected volumeAxisLabels(): string[] {\n const points = this.volumePoints();\n return points.map((p, i) => this.volumeAxisLabel(p, i, points.length));\n }\n\n protected volumeAxisLabel(point: AnalyticsVolumePoint, index: number, total: number): string {\n if (/week/i.test(point.day) || /week/i.test(point.label)) {\n const week = point.day || point.label;\n const month = point.date ? this.formatMonthYear(point.date) : '';\n return month ? `${week} · ${month}` : week;\n }\n if (point.date) {\n if (total <= 7) return this.formatChartDate(point.date);\n const step = total <= 14 ? 2 : total <= 31 ? 5 : 7;\n if (index % step !== 0 && index !== total - 1) return '';\n return this.formatChartDate(point.date);\n }\n return point.day || point.label;\n }\n\n protected deltaTone(kpi: AnalyticsKpiCard | null | undefined): string {\n if (!kpi?.delta) return 'neutral';\n if (kpi.tone === 'pos' || kpi.tone === 'warn' || kpi.tone === 'neg') return kpi.tone;\n if (/^\\+/.test(kpi.delta.trim())) return 'pos';\n if (/^-|−/.test(kpi.delta.trim())) return 'warn';\n return 'neutral';\n }\n\n protected formatChartDate(date: string): string {\n const iso = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(date.trim());\n if (iso) return `${iso[2]}/${iso[3]}`;\n return date;\n }\n\n protected estimatedCalls(pct: number): number {\n const total = this.data()?.kpis.totalCalls ?? 0;\n if (!total) return Math.round(pct);\n return Math.round((total * pct) / 100);\n }\n\n protected onVolumeEnter(index: number): void {\n this.hoveredVolume.set(index);\n }\n\n protected onVolumeLeave(): void {\n this.hoveredVolume.set(null);\n }\n\n private volumePointHeadline(point: AnalyticsVolumePoint): string {\n const raw = (point.label || point.day || '').trim();\n if (/week/i.test(raw) && point.date) {\n return `${raw} · ${this.formatMonthYear(point.date)}`;\n }\n if (point.date) return this.formatFullChartDate(point.date);\n return raw || 'Period';\n }\n\n private formatMonthYear(date: string): string {\n const d = this.parseIsoDate(date);\n if (!d) return '';\n return d.toLocaleDateString(undefined, { month: 'short', year: 'numeric' });\n }\n\n private formatFullChartDate(date: string): string {\n const d = this.parseIsoDate(date);\n if (!d) return this.formatChartDate(date);\n return d.toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n }\n\n private formatPeriodRange(start: Date, end: Date): string {\n const sameYear = start.getFullYear() === end.getFullYear();\n const sameMonth = sameYear && start.getMonth() === end.getMonth();\n if (sameMonth) {\n return `${start.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} – ${end.getDate()}, ${end.getFullYear()}`;\n }\n if (sameYear) {\n return `${start.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} – ${end.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}`;\n }\n return `${start.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })} – ${end.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}`;\n }\n\n private parseIsoDate(value: string | null | undefined): Date | null {\n if (!value?.trim()) return null;\n const iso = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(value.trim());\n if (iso) {\n const d = new Date(Number(iso[1]), Number(iso[2]) - 1, Number(iso[3]));\n return Number.isNaN(d.getTime()) ? null : d;\n }\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n\n private findKpi(re: RegExp): AnalyticsKpiCard | null {\n return this.data()?.kpiCards.find((c) => re.test(c.label)) ?? null;\n }\n\n private load(): void {\n this.loading.set(true);\n this.chartsReady.set(false);\n this.hoveredVolume.set(null);\n this.api.getAnalytics(this.range()).subscribe(({ data }) => {\n this.data.set(data);\n this.loading.set(false);\n // Next frame so CSS enter animations run after paint.\n requestAnimationFrame(() => this.chartsReady.set(true));\n });\n }\n}\n","<div class=\"eds-page analytics-page\">\n <header class=\"mb-5 flex flex-wrap items-start justify-between gap-4\">\n <div>\n <h1 class=\"eds-h1\">Analytics</h1>\n <p class=\"eds-body-sm\">How the AI is doing over time, and what’s worth changing.</p>\n <p class=\"mt-1.5 text-sm font-medium text-zinc-500\">\n Showing data for\n <span class=\"text-zinc-700\">{{ periodLabel() }}</span>\n </p>\n </div>\n <div class=\"flex flex-wrap items-center gap-2\">\n <div class=\"analytics-page__range flex gap-1 rounded-md border p-1\" role=\"group\" aria-label=\"Date range\">\n @for (r of ranges; track r.id) {\n <button\n type=\"button\"\n class=\"analytics-page__range-btn cursor-pointer rounded border-none bg-transparent px-3 py-2 text-sm text-zinc-500\"\n [class.analytics-page__range-btn--active]=\"range() === r.id\"\n (click)=\"setRange(r.id)\"\n >\n {{ r.label }}\n </button>\n }\n </div>\n <eds-button variant=\"secondary\" type=\"button\">\n <eds-icon name=\"download\" [size]=\"18\" />\n Export\n </eds-button>\n </div>\n </header>\n\n @if (loading()) {\n <div class=\"flex flex-col gap-4\" aria-busy=\"true\" aria-live=\"polite\">\n <div class=\"analytics-page__skeleton analytics-page__skeleton--hero h-36 rounded-2xl border\"></div>\n <div class=\"flex gap-3\">\n <div class=\"analytics-page__skeleton analytics-page__skeleton--stat h-24 flex-1 rounded-2xl border\"></div>\n <div class=\"analytics-page__skeleton analytics-page__skeleton--stat h-24 flex-1 rounded-2xl border\"></div>\n <div class=\"analytics-page__skeleton analytics-page__skeleton--stat h-24 flex-1 rounded-2xl border\"></div>\n <div class=\"analytics-page__skeleton analytics-page__skeleton--stat h-24 flex-1 rounded-2xl border\"></div>\n </div>\n <div class=\"flex flex-wrap gap-3\">\n <div class=\"analytics-page__skeleton analytics-page__skeleton--chart h-72 flex-1 rounded-2xl border\"></div>\n <div class=\"analytics-page__skeleton analytics-page__skeleton--chart h-72 flex-1 rounded-2xl border\"></div>\n </div>\n <p class=\"eds-body-sm mt-1\">Loading analytics…</p>\n </div>\n } @else {\n @if (data(); as d) {\n <div class=\"analytics-page__body\" [class.analytics-page__body--ready]=\"chartsReady()\">\n <section\n class=\"analytics-page__hero eds-animate-fade-up mb-3 grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3\"\n aria-label=\"Key outcomes\"\n >\n <article class=\"analytics-page__impact flex min-h-36 flex-col gap-2 rounded-2xl p-5 text-white\">\n <span class=\"text-sm font-medium leading-snug opacity-90\">\n {{ impactKpi()?.label || 'Estimated Impact This Month' }}\n </span>\n <strong class=\"text-4xl font-bold leading-tight tracking-tight\">\n {{ impactKpi()?.value || '—' }}\n </strong>\n <p class=\"m-0 max-w-sm text-sm leading-normal opacity-90\">\n {{ impactKpi()?.sub || 'of front-desk time handled by the AI' }}\n </p>\n </article>\n\n <article class=\"analytics-page__card flex flex-col justify-center gap-2 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">{{ resolutionKpi()?.label || 'AI Autonomous Resolution' }}</span>\n <div class=\"flex items-baseline gap-3\">\n <strong class=\"text-4xl font-bold leading-none text-zinc-900\">\n {{ resolutionKpi()?.value || '—' }}\n </strong>\n @if (resolutionKpi()?.delta) {\n <span\n class=\"analytics-page__trend inline-flex items-center gap-1 text-xs font-semibold leading-none\"\n [attr.data-tone]=\"deltaTone(resolutionKpi())\"\n >\n <eds-icon\n [name]=\"deltaTone(resolutionKpi()) === 'pos' ? 'trending_up' : 'trending_down'\"\n [size]=\"16\"\n />\n {{ resolutionKpi()!.delta }}\n </span>\n }\n </div>\n <p class=\"m-0 text-xs leading-normal text-zinc-500\">\n {{ resolutionKpi()?.sub || 'without staff intervention' }}\n </p>\n </article>\n\n <article\n class=\"analytics-page__distribution analytics-page__card flex flex-col gap-3 rounded-2xl border bg-white px-4 py-4 md:col-span-2 lg:col-span-1\"\n >\n <div class=\"flex flex-wrap items-baseline justify-between gap-3\">\n <strong class=\"text-xl font-bold leading-snug text-zinc-900\">{{ totalCallsLabel() }}</strong>\n @if (directionSub(); as dir) {\n <span class=\"text-xs leading-normal text-zinc-500\">{{ dir }}</span>\n }\n </div>\n <div class=\"analytics-page__stack flex h-3.5 rounded-full\" role=\"img\" aria-label=\"Call distribution\">\n @for (seg of distribution(); track seg.label) {\n <span\n class=\"analytics-page__stack-seg relative block h-full min-w-0.5 cursor-default\"\n [style.width.%]=\"seg.width\"\n [style.background]=\"seg.color\"\n [attr.aria-label]=\"distributionTooltip(seg.label, seg.value)\"\n >\n <span class=\"analytics-page__tooltip\">{{ distributionTooltip(seg.label, seg.value) }}</span>\n </span>\n }\n </div>\n <ul class=\"m-0 flex list-none flex-wrap gap-x-4 gap-y-2 p-0 text-xs text-zinc-500\">\n @for (seg of distribution(); track seg.label) {\n <li class=\"inline-flex items-center gap-2\">\n <span class=\"h-2 w-2 shrink-0 rounded-full\" [style.background]=\"seg.color\"></span>\n <span>\n {{ seg.label }}\n <strong class=\"font-semibold text-zinc-900\">{{ estimatedCalls(seg.value).toLocaleString() }}</strong>\n calls\n </span>\n <em class=\"not-italic font-semibold\">{{ seg.value }}%</em>\n </li>\n }\n </ul>\n </article>\n </section>\n\n <section\n class=\"eds-animate-fade-up mb-4 grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-4\"\n aria-label=\"Activity KPIs\"\n >\n @for (kpi of secondaryKpis(); track kpi.label) {\n <article class=\"analytics-page__card flex flex-col gap-2 rounded-2xl border bg-white px-4 py-4\">\n <div class=\"flex items-center justify-between gap-2\">\n <span class=\"eds-label\">{{ kpi.label }}</span>\n @if (kpi.delta) {\n <span\n class=\"analytics-page__trend inline-flex items-center gap-1 text-xs font-semibold leading-none\"\n [attr.data-tone]=\"deltaTone(kpi)\"\n >\n <eds-icon\n [name]=\"deltaTone(kpi) === 'pos' ? 'trending_up' : 'trending_down'\"\n [size]=\"14\"\n />\n {{ kpi.delta }}\n </span>\n }\n </div>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">{{ kpi.value }}</strong>\n @if (kpi.sub) {\n <span class=\"text-xs leading-normal text-zinc-500\">{{ kpi.sub }}</span>\n }\n </article>\n }\n </section>\n\n <div class=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <article class=\"eds-card analytics-page__chart eds-animate-fade-up px-4 py-4 shadow-sm\">\n <div class=\"analytics-page__chart-head mb-3 flex flex-wrap items-baseline justify-between gap-3\">\n <div class=\"flex min-w-0 flex-wrap items-baseline gap-3\">\n <h2 class=\"eds-h2\">Call Volume</h2>\n <span\n class=\"analytics-page__chart-period inline-flex items-center rounded-full border px-3 py-1 text-xs font-medium text-zinc-500\"\n >\n {{ periodLabel() }}\n </span>\n </div>\n <ul class=\"m-0 flex list-none flex-wrap gap-x-4 gap-y-3 p-0 text-xs font-medium text-zinc-500\">\n <li>\n <span class=\"analytics-page__dot analytics-page__dot--total mr-2 inline-block h-2 w-2 rounded-full\"></span>\n Total Calls\n </li>\n <li>\n <span class=\"analytics-page__dot analytics-page__dot--in mr-2 inline-block h-2 w-2 rounded-full\"></span>\n Inbound Calls\n </li>\n <li>\n <span class=\"analytics-page__dot analytics-page__dot--out mr-2 inline-block h-2 w-2 rounded-full\"></span>\n Outbound Calls\n </li>\n </ul>\n </div>\n <div class=\"analytics-page__line-chart relative h-56\">\n <svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\" aria-hidden=\"true\">\n <path class=\"analytics-page__area\" [attr.d]=\"volumeAreaPath()\" />\n <path class=\"analytics-page__line analytics-page__line--total\" [attr.d]=\"volumePath('total')\" fill=\"none\" />\n <path class=\"analytics-page__line analytics-page__line--in\" [attr.d]=\"volumePath('inbound')\" fill=\"none\" />\n <path class=\"analytics-page__line analytics-page__line--out\" [attr.d]=\"volumePath('outbound')\" fill=\"none\" />\n </svg>\n <div\n class=\"analytics-page__volume-hit flex\"\n role=\"list\"\n aria-label=\"Call volume by period\"\n (mouseleave)=\"onVolumeLeave()\"\n >\n @for (point of volumePoints(); track point.date || point.label; let i = $index) {\n <button\n type=\"button\"\n class=\"analytics-page__volume-col relative min-w-0 flex-1 cursor-default border-none bg-transparent p-0\"\n role=\"listitem\"\n [class.analytics-page__volume-col--active]=\"hoveredVolume() === i\"\n [attr.aria-label]=\"volumeTooltip(point)\"\n (mouseenter)=\"onVolumeEnter(i)\"\n (focus)=\"onVolumeEnter(i)\"\n (blur)=\"onVolumeLeave()\"\n >\n <span class=\"analytics-page__tooltip analytics-page__tooltip--multiline\">\n {{ volumeTooltip(point) }}\n </span>\n </button>\n }\n </div>\n <div class=\"mt-2 flex justify-between gap-1\">\n @for (label of volumeAxisLabels(); track $index) {\n <span class=\"flex-1 text-center font-mono text-xs font-medium uppercase text-zinc-500\">\n {{ label }}\n </span>\n }\n </div>\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart eds-animate-fade-up px-4 py-4 shadow-sm\">\n <h2 class=\"eds-h2 mb-4\">Top Call Reasons</h2>\n <div class=\"flex flex-col gap-3\">\n @for (item of d.reasons; track item.label; let i = $index) {\n <div\n class=\"analytics-page__hbar-row grid items-center gap-3\"\n [attr.aria-label]=\"hbarTooltip(item.label, item.value)\"\n >\n <span class=\"text-sm font-medium text-zinc-900\">{{ item.label }}</span>\n <div class=\"analytics-page__hbar-track relative h-2.5 rounded-full\">\n <span\n class=\"analytics-page__hbar-fill block h-full min-w-1 rounded-full\"\n [style.width.%]=\"(item.value / maxValue(d.reasons)) * 100\"\n [style.background]=\"reasonColor(i)\"\n [style.animation-delay.ms]=\"i * 40\"\n ></span>\n <span class=\"analytics-page__tooltip\">{{ hbarTooltip(item.label, item.value) }}</span>\n </div>\n <span class=\"text-right font-mono text-xs font-semibold text-zinc-500\">{{ item.value }}%</span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart eds-animate-fade-up px-4 py-4 shadow-sm\">\n <h2 class=\"eds-h2 mb-4\">Reasons AI got stuck</h2>\n <div class=\"flex flex-col gap-3\">\n @for (item of d.transfers; track item.label; let i = $index) {\n <div\n class=\"analytics-page__hbar-row grid items-center gap-3\"\n [attr.aria-label]=\"hbarTooltip(item.label, item.value)\"\n >\n <span class=\"text-sm font-medium text-zinc-900\">{{ item.label }}</span>\n <div class=\"analytics-page__hbar-track relative h-2.5 rounded-full\">\n <span\n class=\"analytics-page__hbar-fill block h-full min-w-1 rounded-full\"\n [style.width.%]=\"(item.value / maxValue(d.transfers)) * 100\"\n [style.background]=\"stuckColor(i)\"\n [style.animation-delay.ms]=\"i * 40\"\n ></span>\n <span class=\"analytics-page__tooltip\">{{ hbarTooltip(item.label, item.value) }}</span>\n </div>\n <span class=\"text-right font-mono text-xs font-semibold text-zinc-500\">{{ item.value }}%</span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"eds-card analytics-page__chart eds-animate-fade-up px-4 py-4 shadow-sm\">\n <div class=\"analytics-page__chart-head mb-3 flex flex-wrap items-baseline justify-between gap-3\">\n <h2 class=\"eds-h2\">Peak Hours</h2>\n <span class=\"eds-label\">\n @if (d.peakHours.timezone) {\n {{ d.peakHours.timezone }}\n } @else {\n Calls per hour\n }\n </span>\n </div>\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex h-40 items-end gap-1\" role=\"list\" aria-label=\"Call volume by hour\">\n @for (point of d.peakHours.points; track point.hour; let i = $index) {\n <div\n class=\"analytics-page__peak-col flex h-full min-w-0 flex-1 flex-col\"\n role=\"listitem\"\n [attr.aria-label]=\"peakTooltip(point)\"\n >\n <div class=\"analytics-page__peak-wrap relative flex min-h-0 flex-1 items-end\">\n <div\n class=\"analytics-page__peak-bar w-full min-h-0.5 rounded-t bg-blue-300 transition-colors duration-150\"\n [style.height.%]=\"barHeightPercent(point, d.peakHours.points)\"\n [style.animation-delay.ms]=\"i * 18\"\n ></div>\n <span class=\"analytics-page__tooltip\">{{ peakTooltip(point) }}</span>\n </div>\n </div>\n }\n </div>\n <div class=\"flex gap-1\">\n @for (point of d.peakHours.points; track point.hour; let i = $index) {\n <span class=\"min-h-3.5 flex-1 text-center font-mono text-xs font-medium text-zinc-500\">\n @if (showPeakAxisLabel(i, d.peakHours.points.length)) {\n {{ point.label }}\n }\n </span>\n }\n </div>\n </div>\n </article>\n </div>\n </div>\n }\n }\n</div>\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edvak99-voice-receptionist-widget-api-log.util-C0TOKF1q.mjs","sources":["../../../
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-api-log.util-C0TOKF1q.mjs","sources":["../../../src/lib/utils/api-log.util.ts"],"sourcesContent":["import { BadgeTone } from '../ui/eds-badge.component';\nimport { ApiProvider } from '../models/api-log.types';\n\nexport function formatApiDuration(ms: number): string {\n if (ms >= 1000) {\n return `${(ms / 1000).toFixed(1)}s`;\n }\n return `${Math.round(ms)}ms`;\n}\n\nexport function providerLabel(provider: ApiProvider): string {\n const labels: Record<ApiProvider, string> = {\n edvak: 'Edvak',\n vonage: 'Vonage',\n openai: 'OpenAI',\n livekit: 'LiveKit',\n };\n return labels[provider] ?? provider;\n}\n\nexport function providerChipClass(provider: ApiProvider): string {\n return `api-log-chip--${provider}`;\n}\n\nexport function statusBadgeTone(success: boolean): BadgeTone {\n return success ? 'pos' : 'neg';\n}\n\nexport function statusLabel(success: boolean, statusCode: number | null): string {\n if (statusCode != null) return String(statusCode);\n return success ? 'OK' : 'TIMEOUT';\n}\n\nexport function truncatePreview(text: string, max = 80): string {\n const t = text?.trim() ?? '';\n if (t.length <= max) return t;\n return `${t.slice(0, max)}…`;\n}\n\nexport function prettyJson(value: unknown): string {\n if (value == null) return '—';\n try {\n return JSON.stringify(value, null, 2);\n } catch {\n return String(value);\n }\n}\n\n/** Pretty-print a redacted preview string when it looks like JSON. */\nexport function formatPreview(text: string | null | undefined): string {\n const t = text?.trim() ?? '';\n if (!t) return '—';\n try {\n return JSON.stringify(JSON.parse(t), null, 2);\n } catch {\n return t;\n }\n}\n"],"names":[],"mappings":"AAGM,SAAU,iBAAiB,CAAC,EAAU,EAAA;AAC1C,IAAA,IAAI,EAAE,IAAI,IAAI,EAAE;AACd,QAAA,OAAO,CAAA,EAAG,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA,CAAA,CAAG;IACrC;IACA,OAAO,CAAA,EAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI;AAC9B;AAEM,SAAU,aAAa,CAAC,QAAqB,EAAA;AACjD,IAAA,MAAM,MAAM,GAAgC;AAC1C,QAAA,KAAK,EAAE,OAAO;AACd,QAAA,MAAM,EAAE,QAAQ;AAChB,QAAA,MAAM,EAAE,QAAQ;AAChB,QAAA,OAAO,EAAE,SAAS;KACnB;AACD,IAAA,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,QAAQ;AACrC;AAEM,SAAU,iBAAiB,CAAC,QAAqB,EAAA;IACrD,OAAO,CAAA,cAAA,EAAiB,QAAQ,CAAA,CAAE;AACpC;AAEM,SAAU,eAAe,CAAC,OAAgB,EAAA;IAC9C,OAAO,OAAO,GAAG,KAAK,GAAG,KAAK;AAChC;AAEM,SAAU,WAAW,CAAC,OAAgB,EAAE,UAAyB,EAAA;IACrE,IAAI,UAAU,IAAI,IAAI;AAAE,QAAA,OAAO,MAAM,CAAC,UAAU,CAAC;IACjD,OAAO,OAAO,GAAG,IAAI,GAAG,SAAS;AACnC;SAEgB,eAAe,CAAC,IAAY,EAAE,GAAG,GAAG,EAAE,EAAA;IACpD,MAAM,CAAC,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE;AAC5B,IAAA,IAAI,CAAC,CAAC,MAAM,IAAI,GAAG;AAAE,QAAA,OAAO,CAAC;IAC7B,OAAO,CAAA,EAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA,CAAA,CAAG;AAC9B;AAEM,SAAU,UAAU,CAAC,KAAc,EAAA;IACvC,IAAI,KAAK,IAAI,IAAI;AAAE,QAAA,OAAO,GAAG;AAC7B,IAAA,IAAI;QACF,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC;AAAE,IAAA,MAAM;AACN,QAAA,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB;AACF;AAEA;AACM,SAAU,aAAa,CAAC,IAA+B,EAAA;IAC3D,MAAM,CAAC,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE;AAC5B,IAAA,IAAI,CAAC,CAAC;AAAE,QAAA,OAAO,GAAG;AAClB,IAAA,IAAI;AACF,QAAA,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C;AAAE,IAAA,MAAM;AACN,QAAA,OAAO,CAAC;IACV;AACF;;;;"}
|
|
@@ -3,10 +3,10 @@ import { inject, signal, computed, ChangeDetectionStrategy, Component } from '@a
|
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { FormsModule } from '@angular/forms';
|
|
5
5
|
import { ActivatedRoute } from '@angular/router';
|
|
6
|
-
import { A as ApiLogsTableComponent, a as ApiLogDetailDrawerComponent } from './edvak99-voice-receptionist-widget-api-logs-table.component-
|
|
7
|
-
import { VOICE_RECEPTIONIST_SESSION, EdsButtonComponent, EdsIconComponent } from './edvak99-voice-receptionist-widget.mjs';
|
|
8
|
-
import { A as AiReceptionistApiService } from './edvak99-voice-receptionist-widget-ai-receptionist-api.service-DcRiHn8q.mjs';
|
|
6
|
+
import { A as ApiLogsTableComponent, a as ApiLogDetailDrawerComponent } from './edvak99-voice-receptionist-widget-api-logs-table.component-CiNc65dO.mjs';
|
|
7
|
+
import { A as AiReceptionistApiService, o as VOICE_RECEPTIONIST_SESSION, E as EdsButtonComponent, d as EdsIconComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-pS2jeB4x.mjs';
|
|
9
8
|
import { p as providerLabel, a as providerChipClass, f as formatApiDuration } from './edvak99-voice-receptionist-widget-api-log.util-C0TOKF1q.mjs';
|
|
9
|
+
import { E as EdsSearchFieldComponent } from './edvak99-voice-receptionist-widget-eds-search-field.component-OGRDvYBp.mjs';
|
|
10
10
|
|
|
11
11
|
/** Roles that may pass `practiceId` / view all practices. */
|
|
12
12
|
function isSuperAdminRole(role) {
|
|
@@ -208,7 +208,7 @@ class ApiLogsPageComponent {
|
|
|
208
208
|
});
|
|
209
209
|
}
|
|
210
210
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ApiLogsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
211
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: ApiLogsPageComponent, isStandalone: true, selector: "eds-api-logs-page", ngImport: i0, template: "<div class=\"eds-page api-logs-page\">\n <header class=\"api-logs-page__header mb-5 flex items-start justify-between gap-4\">\n <div>\n <h1 class=\"eds-h1\">API Logs</h1>\n <p class=\"eds-body-sm\">Edvak EHR, Vonage, OpenAI, and LiveKit request history</p>\n </div>\n <eds-button variant=\"secondary\" (click)=\"exportCsv()\">\n <eds-icon name=\"download\" [size]=\"18\" /> Export CSV\n </eds-button>\n </header>\n\n <section class=\"mb-4 grid grid-cols-2 gap-3 md:grid-cols-4\" aria-label=\"API health last 24 hours\">\n @if (stats(); as s) {\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">Total (24h)</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">{{ s.last24h.total }}</strong>\n </article>\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">Failed (24h)</span>\n <strong class=\"text-3xl font-bold leading-tight text-red-600\">{{ s.last24h.failed }}</strong>\n </article>\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">Avg latency</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">{{ formatDuration(s.last24h.avgDurationMs) }}</strong>\n </article>\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">By provider</span>\n <ul class=\"m-0 mt-1 flex list-none flex-wrap gap-1.5 p-0\">\n @for (row of s.byProvider; track row.provider) {\n <li class=\"inline-flex items-center gap-1 text-xs text-zinc-600\">\n <span class=\"api-log-chip\" [class]=\"providerChipClass(row.provider)\">{{ providerLabel(row.provider) }}</span>\n {{ row.total }}\n </li>\n } @empty {\n <li class=\"text-sm text-zinc-500\">\u2014</li>\n }\n </ul>\n </article>\n } @else if (statsError()) {\n <p class=\"col-span-full eds-body-sm text-zinc-500\">{{ statsError() }}</p>\n } @else {\n <p class=\"col-span-full eds-body-sm\">Loading stats\u2026</p>\n }\n </section>\n\n <section class=\"eds-card api-logs-page__filters mb-4 p-4 shadow-sm\">\n <div class=\"api-logs-page__filter-row mb-3\">\n <span class=\"eds-label\">Provider</span>\n <div class=\"api-logs-page__chips mt-2 flex flex-wrap gap-2\">\n @for (p of providers; track p) {\n <button\n type=\"button\"\n class=\"api-log-chip api-logs-page__chip\"\n [class]=\"'api-log-chip--' + p\"\n [class.api-logs-page__chip--on]=\"isProviderSelected(p)\"\n (click)=\"toggleProvider(p)\"\n >\n {{ p }}\n </button>\n }\n </div>\n </div>\n\n <div class=\"api-logs-page__filter-grid mb-3 grid grid-cols-2 gap-3 md:grid-cols-3\">\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">Result</span>\n <select [ngModel]=\"successFilter()\" (ngModelChange)=\"successFilter.set($event)\" autocomplete=\"off\">\n <option value=\"all\">All</option>\n <option value=\"ok\">Success</option>\n <option value=\"fail\">Failed</option>\n </select>\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">Status code</span>\n <input type=\"number\" placeholder=\"e.g. 500\" [ngModel]=\"statusCode()\" (ngModelChange)=\"statusCode.set($event)\" autocomplete=\"off\" />\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">Call ID</span>\n <input type=\"text\" placeholder=\"LC-4821\" [ngModel]=\"callIdFilter()\" (ngModelChange)=\"callIdFilter.set($event)\" autocomplete=\"off\" />\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">Search</span>\n <input type=\"search\" placeholder=\"operation, path, error\u2026\" [ngModel]=\"search()\" (ngModelChange)=\"search.set($event)\" autocomplete=\"off\" />\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">From</span>\n <input type=\"datetime-local\" [ngModel]=\"fromDate()\" (ngModelChange)=\"fromDate.set($event)\" autocomplete=\"off\" />\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">To</span>\n <input type=\"datetime-local\" [ngModel]=\"toDate()\" (ngModelChange)=\"toDate.set($event)\" autocomplete=\"off\" />\n </label>\n\n @if (isSuperAdmin()) {\n <label class=\"api-logs-page__field flex flex-col gap-1 md:col-span-2\">\n <span class=\"eds-label\">Practice ID (super-admin)</span>\n <input\n type=\"text\"\n placeholder=\"Leave empty for all practices\"\n [ngModel]=\"practiceIdFilter()\"\n (ngModelChange)=\"practiceIdFilter.set($event)\"\n autocomplete=\"off\"\n />\n </label>\n }\n </div>\n\n <div class=\"api-logs-page__filter-actions flex gap-2\">\n <eds-button variant=\"primary\" (click)=\"applyFilters()\">Apply filters</eds-button>\n <eds-button variant=\"secondary\" (click)=\"clearFilters()\">Clear</eds-button>\n </div>\n </section>\n\n <article class=\"eds-card api-logs-page__table-card p-4 shadow-sm\">\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading\u2026</p>\n } @else if (error()) {\n <p class=\"api-logs-page__error eds-body-sm text-red-700\" role=\"alert\">{{ error() }}</p>\n } @else if (items().length === 0) {\n <p class=\"eds-body-sm\">No API logs match your filters.</p>\n } @else {\n <eds-api-logs-table\n [items]=\"items()\"\n [showCallId]=\"true\"\n [showPracticeId]=\"showPracticeColumn()\"\n (rowSelected)=\"onRowSelected($event)\"\n />\n }\n\n <footer class=\"api-logs-page__pager mt-4 flex items-center justify-between pt-3\">\n <span class=\"eds-body-sm\">\n Page {{ page() }} of {{ totalPages() }} \u00B7 {{ total() }} total\n </span>\n <div class=\"api-logs-page__pager-actions flex gap-2\">\n <eds-button variant=\"secondary\" [disabled]=\"page() <= 1\" (click)=\"prevPage()\">Previous</eds-button>\n <eds-button variant=\"secondary\" [disabled]=\"page() >= totalPages()\" (click)=\"nextPage()\">Next</eds-button>\n </div>\n </footer>\n </article>\n\n <eds-api-log-detail-drawer\n [open]=\"drawerOpen()\"\n [listItem]=\"selected()\"\n (closed)=\"closeDrawer()\"\n />\n</div>\n", styles: [".api-log-chip{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600;letter-spacing:.02em;text-transform:uppercase}.api-log-chip--edvak{background:#3b82f626;color:#3b82f6}.api-log-chip--vonage{background:#8b5cf626;color:#8b5cf6}.api-log-chip--openai{background:#22c55e26;color:#22c55e}.api-log-chip--livekit{background:#f9731626;color:#f97316}.api-logs-page__chip{border:1px solid transparent;cursor:pointer;opacity:.55}.api-logs-page__chip--on{opacity:1;border-color:currentColor}.api-logs-page__field input,.api-logs-page__field select{padding:8px 10px;border-radius:8px;border:1px solid var(--eds-border-subtle, #e4e4e7);font:inherit}.api-logs-page__pager{border-top:1px solid var(--eds-border-subtle, #e4e4e7)}@media(max-width:900px){.api-logs-page__filter-grid{grid-template-columns:1fr}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ApiLogsTableComponent, selector: "eds-api-logs-table", inputs: ["items", "showCallId", "showPracticeId", "callLinkBase"], outputs: ["rowSelected"] }, { kind: "component", type: ApiLogDetailDrawerComponent, selector: "eds-api-log-detail-drawer", inputs: ["open", "listItem", "listOnly"], outputs: ["closed"] }, { 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 }); }
|
|
211
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: ApiLogsPageComponent, isStandalone: true, selector: "eds-api-logs-page", ngImport: i0, template: "<div class=\"eds-page api-logs-page\">\n <header class=\"api-logs-page__header mb-5 flex items-start justify-between gap-4\">\n <div>\n <h1 class=\"eds-h1\">API Logs</h1>\n <p class=\"eds-body-sm\">Edvak EHR, Vonage, OpenAI, and LiveKit request history</p>\n </div>\n <eds-button variant=\"secondary\" (click)=\"exportCsv()\">\n <eds-icon name=\"download\" [size]=\"18\" /> Export CSV\n </eds-button>\n </header>\n\n <section class=\"mb-4 grid grid-cols-2 gap-3 md:grid-cols-4\" aria-label=\"API health last 24 hours\">\n @if (stats(); as s) {\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">Total (24h)</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">{{ s.last24h.total }}</strong>\n </article>\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">Failed (24h)</span>\n <strong class=\"text-3xl font-bold leading-tight text-red-600\">{{ s.last24h.failed }}</strong>\n </article>\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">Avg latency</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">{{ formatDuration(s.last24h.avgDurationMs) }}</strong>\n </article>\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">By provider</span>\n <ul class=\"m-0 mt-1 flex list-none flex-wrap gap-1.5 p-0\">\n @for (row of s.byProvider; track row.provider) {\n <li class=\"inline-flex items-center gap-1 text-xs text-zinc-600\">\n <span class=\"api-log-chip\" [class]=\"providerChipClass(row.provider)\">{{ providerLabel(row.provider) }}</span>\n {{ row.total }}\n </li>\n } @empty {\n <li class=\"text-sm text-zinc-500\">\u2014</li>\n }\n </ul>\n </article>\n } @else if (statsError()) {\n <p class=\"col-span-full eds-body-sm text-zinc-500\">{{ statsError() }}</p>\n } @else {\n <p class=\"col-span-full eds-body-sm\">Loading stats\u2026</p>\n }\n </section>\n\n <section class=\"eds-card api-logs-page__filters mb-4 p-4 shadow-sm\">\n <div class=\"api-logs-page__filter-row mb-3\">\n <span class=\"eds-label\">Provider</span>\n <div class=\"api-logs-page__chips mt-2 flex flex-wrap gap-2\">\n @for (p of providers; track p) {\n <button\n type=\"button\"\n class=\"api-log-chip api-logs-page__chip\"\n [class]=\"'api-log-chip--' + p\"\n [class.api-logs-page__chip--on]=\"isProviderSelected(p)\"\n (click)=\"toggleProvider(p)\"\n >\n {{ p }}\n </button>\n }\n </div>\n </div>\n\n <div class=\"api-logs-page__filter-grid mb-3 grid grid-cols-2 gap-3 md:grid-cols-3\">\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">Result</span>\n <select [ngModel]=\"successFilter()\" (ngModelChange)=\"successFilter.set($event)\" autocomplete=\"off\">\n <option value=\"all\">All</option>\n <option value=\"ok\">Success</option>\n <option value=\"fail\">Failed</option>\n </select>\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">Status code</span>\n <input type=\"number\" placeholder=\"e.g. 500\" [ngModel]=\"statusCode()\" (ngModelChange)=\"statusCode.set($event)\" autocomplete=\"off\" />\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">Call ID</span>\n <eds-search-field\n type=\"text\"\n inputClass=\"rounded-md border border-zinc-200 px-3 py-2 text-sm\"\n placeholder=\"LC-4821\"\n [value]=\"callIdFilter()\"\n (valueChange)=\"callIdFilter.set($event)\"\n (enter)=\"applyFilters()\"\n />\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">Search</span>\n <eds-search-field\n inputClass=\"rounded-md border border-zinc-200 px-3 py-2 text-sm\"\n placeholder=\"operation, path, error\u2026\"\n [value]=\"search()\"\n (valueChange)=\"search.set($event)\"\n (enter)=\"applyFilters()\"\n />\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">From</span>\n <input type=\"datetime-local\" [ngModel]=\"fromDate()\" (ngModelChange)=\"fromDate.set($event)\" autocomplete=\"off\" />\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">To</span>\n <input type=\"datetime-local\" [ngModel]=\"toDate()\" (ngModelChange)=\"toDate.set($event)\" autocomplete=\"off\" />\n </label>\n\n @if (isSuperAdmin()) {\n <label class=\"api-logs-page__field flex flex-col gap-1 md:col-span-2\">\n <span class=\"eds-label\">Practice ID (super-admin)</span>\n <eds-search-field\n type=\"text\"\n inputClass=\"rounded-md border border-zinc-200 px-3 py-2 text-sm\"\n placeholder=\"Leave empty for all practices\"\n [value]=\"practiceIdFilter()\"\n (valueChange)=\"practiceIdFilter.set($event)\"\n (enter)=\"applyFilters()\"\n />\n </label>\n }\n </div>\n\n <div class=\"api-logs-page__filter-actions flex gap-2\">\n <eds-button variant=\"primary\" (click)=\"applyFilters()\">Apply filters</eds-button>\n <eds-button variant=\"secondary\" (click)=\"clearFilters()\">Clear</eds-button>\n </div>\n </section>\n\n <article class=\"eds-card api-logs-page__table-card p-4 shadow-sm\">\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading\u2026</p>\n } @else if (error()) {\n <p class=\"api-logs-page__error eds-body-sm text-red-700\" role=\"alert\">{{ error() }}</p>\n } @else if (items().length === 0) {\n <p class=\"eds-body-sm\">No API logs match your filters.</p>\n } @else {\n <eds-api-logs-table\n [items]=\"items()\"\n [showCallId]=\"true\"\n [showPracticeId]=\"showPracticeColumn()\"\n (rowSelected)=\"onRowSelected($event)\"\n />\n }\n\n <footer class=\"api-logs-page__pager mt-4 flex items-center justify-between pt-3\">\n <span class=\"eds-body-sm\">\n Page {{ page() }} of {{ totalPages() }} \u00B7 {{ total() }} total\n </span>\n <div class=\"api-logs-page__pager-actions flex gap-2\">\n <eds-button variant=\"secondary\" [disabled]=\"page() <= 1\" (click)=\"prevPage()\">Previous</eds-button>\n <eds-button variant=\"secondary\" [disabled]=\"page() >= totalPages()\" (click)=\"nextPage()\">Next</eds-button>\n </div>\n </footer>\n </article>\n\n <eds-api-log-detail-drawer\n [open]=\"drawerOpen()\"\n [listItem]=\"selected()\"\n (closed)=\"closeDrawer()\"\n />\n</div>\n", styles: [".api-log-chip{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600;letter-spacing:.02em;text-transform:uppercase}.api-log-chip--edvak{background:#3b82f626;color:#3b82f6}.api-log-chip--vonage{background:#8b5cf626;color:#8b5cf6}.api-log-chip--openai{background:#22c55e26;color:#22c55e}.api-log-chip--livekit{background:#f9731626;color:#f97316}.api-logs-page__chip{border:1px solid transparent;cursor:pointer;opacity:.55}.api-logs-page__chip--on{opacity:1;border-color:currentColor}.api-logs-page__field input,.api-logs-page__field select{padding:8px 10px;border-radius:8px;border:1px solid var(--eds-border-subtle, #e4e4e7);font:inherit}.api-logs-page__pager{border-top:1px solid var(--eds-border-subtle, #e4e4e7)}@media(max-width:900px){.api-logs-page__filter-grid{grid-template-columns:1fr}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ApiLogsTableComponent, selector: "eds-api-logs-table", inputs: ["items", "showCallId", "showPracticeId", "callLinkBase"], outputs: ["rowSelected"] }, { kind: "component", type: ApiLogDetailDrawerComponent, selector: "eds-api-log-detail-drawer", inputs: ["open", "listItem", "listOnly"], outputs: ["closed"] }, { 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: EdsSearchFieldComponent, selector: "eds-search-field", inputs: ["value", "inputId", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange", "enter"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
212
212
|
}
|
|
213
213
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ApiLogsPageComponent, decorators: [{
|
|
214
214
|
type: Component,
|
|
@@ -218,8 +218,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
|
|
|
218
218
|
ApiLogDetailDrawerComponent,
|
|
219
219
|
EdsButtonComponent,
|
|
220
220
|
EdsIconComponent,
|
|
221
|
-
|
|
221
|
+
EdsSearchFieldComponent,
|
|
222
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"eds-page api-logs-page\">\n <header class=\"api-logs-page__header mb-5 flex items-start justify-between gap-4\">\n <div>\n <h1 class=\"eds-h1\">API Logs</h1>\n <p class=\"eds-body-sm\">Edvak EHR, Vonage, OpenAI, and LiveKit request history</p>\n </div>\n <eds-button variant=\"secondary\" (click)=\"exportCsv()\">\n <eds-icon name=\"download\" [size]=\"18\" /> Export CSV\n </eds-button>\n </header>\n\n <section class=\"mb-4 grid grid-cols-2 gap-3 md:grid-cols-4\" aria-label=\"API health last 24 hours\">\n @if (stats(); as s) {\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">Total (24h)</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">{{ s.last24h.total }}</strong>\n </article>\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">Failed (24h)</span>\n <strong class=\"text-3xl font-bold leading-tight text-red-600\">{{ s.last24h.failed }}</strong>\n </article>\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">Avg latency</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">{{ formatDuration(s.last24h.avgDurationMs) }}</strong>\n </article>\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">By provider</span>\n <ul class=\"m-0 mt-1 flex list-none flex-wrap gap-1.5 p-0\">\n @for (row of s.byProvider; track row.provider) {\n <li class=\"inline-flex items-center gap-1 text-xs text-zinc-600\">\n <span class=\"api-log-chip\" [class]=\"providerChipClass(row.provider)\">{{ providerLabel(row.provider) }}</span>\n {{ row.total }}\n </li>\n } @empty {\n <li class=\"text-sm text-zinc-500\">\u2014</li>\n }\n </ul>\n </article>\n } @else if (statsError()) {\n <p class=\"col-span-full eds-body-sm text-zinc-500\">{{ statsError() }}</p>\n } @else {\n <p class=\"col-span-full eds-body-sm\">Loading stats\u2026</p>\n }\n </section>\n\n <section class=\"eds-card api-logs-page__filters mb-4 p-4 shadow-sm\">\n <div class=\"api-logs-page__filter-row mb-3\">\n <span class=\"eds-label\">Provider</span>\n <div class=\"api-logs-page__chips mt-2 flex flex-wrap gap-2\">\n @for (p of providers; track p) {\n <button\n type=\"button\"\n class=\"api-log-chip api-logs-page__chip\"\n [class]=\"'api-log-chip--' + p\"\n [class.api-logs-page__chip--on]=\"isProviderSelected(p)\"\n (click)=\"toggleProvider(p)\"\n >\n {{ p }}\n </button>\n }\n </div>\n </div>\n\n <div class=\"api-logs-page__filter-grid mb-3 grid grid-cols-2 gap-3 md:grid-cols-3\">\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">Result</span>\n <select [ngModel]=\"successFilter()\" (ngModelChange)=\"successFilter.set($event)\" autocomplete=\"off\">\n <option value=\"all\">All</option>\n <option value=\"ok\">Success</option>\n <option value=\"fail\">Failed</option>\n </select>\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">Status code</span>\n <input type=\"number\" placeholder=\"e.g. 500\" [ngModel]=\"statusCode()\" (ngModelChange)=\"statusCode.set($event)\" autocomplete=\"off\" />\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">Call ID</span>\n <eds-search-field\n type=\"text\"\n inputClass=\"rounded-md border border-zinc-200 px-3 py-2 text-sm\"\n placeholder=\"LC-4821\"\n [value]=\"callIdFilter()\"\n (valueChange)=\"callIdFilter.set($event)\"\n (enter)=\"applyFilters()\"\n />\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">Search</span>\n <eds-search-field\n inputClass=\"rounded-md border border-zinc-200 px-3 py-2 text-sm\"\n placeholder=\"operation, path, error\u2026\"\n [value]=\"search()\"\n (valueChange)=\"search.set($event)\"\n (enter)=\"applyFilters()\"\n />\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">From</span>\n <input type=\"datetime-local\" [ngModel]=\"fromDate()\" (ngModelChange)=\"fromDate.set($event)\" autocomplete=\"off\" />\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">To</span>\n <input type=\"datetime-local\" [ngModel]=\"toDate()\" (ngModelChange)=\"toDate.set($event)\" autocomplete=\"off\" />\n </label>\n\n @if (isSuperAdmin()) {\n <label class=\"api-logs-page__field flex flex-col gap-1 md:col-span-2\">\n <span class=\"eds-label\">Practice ID (super-admin)</span>\n <eds-search-field\n type=\"text\"\n inputClass=\"rounded-md border border-zinc-200 px-3 py-2 text-sm\"\n placeholder=\"Leave empty for all practices\"\n [value]=\"practiceIdFilter()\"\n (valueChange)=\"practiceIdFilter.set($event)\"\n (enter)=\"applyFilters()\"\n />\n </label>\n }\n </div>\n\n <div class=\"api-logs-page__filter-actions flex gap-2\">\n <eds-button variant=\"primary\" (click)=\"applyFilters()\">Apply filters</eds-button>\n <eds-button variant=\"secondary\" (click)=\"clearFilters()\">Clear</eds-button>\n </div>\n </section>\n\n <article class=\"eds-card api-logs-page__table-card p-4 shadow-sm\">\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading\u2026</p>\n } @else if (error()) {\n <p class=\"api-logs-page__error eds-body-sm text-red-700\" role=\"alert\">{{ error() }}</p>\n } @else if (items().length === 0) {\n <p class=\"eds-body-sm\">No API logs match your filters.</p>\n } @else {\n <eds-api-logs-table\n [items]=\"items()\"\n [showCallId]=\"true\"\n [showPracticeId]=\"showPracticeColumn()\"\n (rowSelected)=\"onRowSelected($event)\"\n />\n }\n\n <footer class=\"api-logs-page__pager mt-4 flex items-center justify-between pt-3\">\n <span class=\"eds-body-sm\">\n Page {{ page() }} of {{ totalPages() }} \u00B7 {{ total() }} total\n </span>\n <div class=\"api-logs-page__pager-actions flex gap-2\">\n <eds-button variant=\"secondary\" [disabled]=\"page() <= 1\" (click)=\"prevPage()\">Previous</eds-button>\n <eds-button variant=\"secondary\" [disabled]=\"page() >= totalPages()\" (click)=\"nextPage()\">Next</eds-button>\n </div>\n </footer>\n </article>\n\n <eds-api-log-detail-drawer\n [open]=\"drawerOpen()\"\n [listItem]=\"selected()\"\n (closed)=\"closeDrawer()\"\n />\n</div>\n", styles: [".api-log-chip{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600;letter-spacing:.02em;text-transform:uppercase}.api-log-chip--edvak{background:#3b82f626;color:#3b82f6}.api-log-chip--vonage{background:#8b5cf626;color:#8b5cf6}.api-log-chip--openai{background:#22c55e26;color:#22c55e}.api-log-chip--livekit{background:#f9731626;color:#f97316}.api-logs-page__chip{border:1px solid transparent;cursor:pointer;opacity:.55}.api-logs-page__chip--on{opacity:1;border-color:currentColor}.api-logs-page__field input,.api-logs-page__field select{padding:8px 10px;border-radius:8px;border:1px solid var(--eds-border-subtle, #e4e4e7);font:inherit}.api-logs-page__pager{border-top:1px solid var(--eds-border-subtle, #e4e4e7)}@media(max-width:900px){.api-logs-page__filter-grid{grid-template-columns:1fr}}\n"] }]
|
|
222
223
|
}] });
|
|
223
224
|
|
|
224
225
|
export { ApiLogsPageComponent };
|
|
225
|
-
//# sourceMappingURL=edvak99-voice-receptionist-widget-api-logs-page.component-
|
|
226
|
+
//# sourceMappingURL=edvak99-voice-receptionist-widget-api-logs-page.component-DPJiuNjk.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-api-logs-page.component-DPJiuNjk.mjs","sources":["../../../src/lib/models/api-log.types.ts","../../../src/lib/pages/api-logs-page/api-logs-page.component.ts","../../../src/lib/pages/api-logs-page/api-logs-page.component.html"],"sourcesContent":["export type ApiProvider = 'edvak' | 'vonage' | 'openai' | 'livekit';\n\nexport interface ApiLogListItem {\n id: string;\n callId: string | null;\n /** Present when viewing cross-practice (super-admin). */\n clientPracticeId?: string | null;\n provider: ApiProvider;\n operation: string;\n method: string;\n urlPath: string;\n statusCode: number | null;\n success: boolean;\n durationMs: number;\n toolName: string | null;\n errorMessage: string | null;\n startedAt: string;\n requestPreview: string;\n responsePreview: string;\n}\n\nexport interface ApiLogDetail extends ApiLogListItem {\n traceId: string | null;\n requestBody: Record<string, unknown> | null;\n responseBody: Record<string, unknown> | null;\n metadata: Record<string, unknown> | null;\n completedAt: string | null;\n}\n\nexport interface ApiLogStats {\n last24h: { total: number; failed: number; avgDurationMs: number };\n byProvider: { provider: ApiProvider; total: number; failed: number }[];\n recentFailures: ApiLogListItem[];\n}\n\nexport interface ApiLogListResponse {\n items: ApiLogListItem[];\n total: number;\n page?: number;\n limit?: number;\n /** Filter applied; null = all practices (super-admin). */\n practiceId?: string | null;\n}\n\nexport interface ApiLogFilters {\n provider?: string;\n success?: boolean;\n statusCode?: number;\n operation?: string;\n callId?: string;\n from?: string;\n to?: string;\n search?: string;\n page?: number;\n limit?: number;\n /** Super-admin only. Omit for all practices. */\n practiceId?: string;\n}\n\n/** Roles that may pass `practiceId` / view all practices. */\nexport function isSuperAdminRole(role: string | null | undefined): boolean {\n const key = String(role ?? '')\n .trim()\n .toLowerCase()\n .replace(/[\\s-]+/g, '_');\n return key === 'super_admin' || key === 'superadmin' || key === 'platform_admin';\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n inject,\n OnInit,\n signal,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { ActivatedRoute } from '@angular/router';\n\nimport { ApiLogDetailDrawerComponent } from '../../components/api-log-detail-drawer/api-log-detail-drawer.component';\nimport { ApiLogsTableComponent } from '../../components/api-logs-table/api-logs-table.component';\nimport { VOICE_RECEPTIONIST_SESSION } from '../../config';\nimport {\n ApiLogListItem,\n ApiLogStats,\n ApiProvider,\n isSuperAdminRole,\n} from '../../models/api-log.types';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport { formatApiDuration, providerChipClass, providerLabel } from '../../utils/api-log.util';\nimport { EdsButtonComponent } from '../../ui/eds-button.component';\nimport { EdsIconComponent } from '../../ui/eds-icon.component';\nimport { EdsSearchFieldComponent } from '../../ui/eds-search-field.component';\n\nconst PROVIDERS: ApiProvider[] = ['edvak', 'vonage', 'openai', 'livekit'];\nconst PAGE_SIZE = 50;\n\n@Component({\n selector: 'eds-api-logs-page',\n imports: [\n FormsModule,\n ApiLogsTableComponent,\n ApiLogDetailDrawerComponent,\n EdsButtonComponent,\n EdsIconComponent,\n EdsSearchFieldComponent,\n ],\n templateUrl: './api-logs-page.component.html',\n styleUrl: './api-logs-page.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ApiLogsPageComponent implements OnInit {\n private readonly api = inject(AiReceptionistApiService);\n private readonly route = inject(ActivatedRoute);\n private readonly session = inject(VOICE_RECEPTIONIST_SESSION);\n\n protected readonly providers = PROVIDERS;\n protected readonly pageSize = PAGE_SIZE;\n protected readonly providerLabel = providerLabel;\n protected readonly providerChipClass = providerChipClass;\n protected readonly formatDuration = formatApiDuration;\n\n protected readonly items = signal<ApiLogListItem[]>([]);\n protected readonly total = signal(0);\n protected readonly page = signal(1);\n protected readonly loading = signal(true);\n protected readonly error = signal<string | null>(null);\n protected readonly stats = signal<ApiLogStats | null>(null);\n protected readonly statsError = signal<string | null>(null);\n\n protected readonly selectedProviders = signal<Set<ApiProvider>>(new Set());\n protected readonly successFilter = signal<'all' | 'ok' | 'fail'>('all');\n protected readonly statusCode = signal('');\n protected readonly search = signal('');\n protected readonly callIdFilter = signal('');\n protected readonly fromDate = signal('');\n protected readonly toDate = signal('');\n /** Empty string = all practices (super-admin). */\n protected readonly practiceIdFilter = signal('');\n\n protected readonly selected = signal<ApiLogListItem | null>(null);\n protected readonly drawerOpen = signal(false);\n\n protected readonly isSuperAdmin = computed(() => {\n if (this.session?.isSuperAdmin?.()) return true;\n const role = this.session?.role?.() ?? null;\n return isSuperAdminRole(role);\n });\n\n protected readonly showPracticeColumn = computed(\n () => this.isSuperAdmin() && !this.practiceIdFilter().trim(),\n );\n\n ngOnInit(): void {\n const callId = this.route.snapshot.queryParamMap.get('callId');\n if (callId) this.callIdFilter.set(callId);\n const practiceId = this.route.snapshot.queryParamMap.get('practiceId');\n if (practiceId && this.isSuperAdmin()) this.practiceIdFilter.set(practiceId);\n this.loadStats();\n this.load();\n }\n\n protected toggleProvider(p: ApiProvider): void {\n this.selectedProviders.update((set) => {\n const next = new Set(set);\n if (next.has(p)) next.delete(p);\n else next.add(p);\n return next;\n });\n }\n\n protected isProviderSelected(p: ApiProvider): boolean {\n return this.selectedProviders().has(p);\n }\n\n protected applyFilters(): void {\n this.page.set(1);\n this.loadStats();\n this.load();\n }\n\n protected clearFilters(): void {\n this.selectedProviders.set(new Set());\n this.successFilter.set('all');\n this.statusCode.set('');\n this.search.set('');\n this.callIdFilter.set('');\n this.fromDate.set('');\n this.toDate.set('');\n this.practiceIdFilter.set('');\n this.page.set(1);\n this.loadStats();\n this.load();\n }\n\n protected prevPage(): void {\n if (this.page() > 1) {\n this.page.update((p) => p - 1);\n this.load();\n }\n }\n\n protected nextPage(): void {\n if (this.page() < this.totalPages()) {\n this.page.update((p) => p + 1);\n this.load();\n }\n }\n\n protected onRowSelected(item: ApiLogListItem): void {\n this.selected.set(item);\n this.drawerOpen.set(true);\n }\n\n protected closeDrawer(): void {\n this.drawerOpen.set(false);\n this.selected.set(null);\n }\n\n protected exportCsv(): void {\n const header = [\n 'time',\n 'provider',\n 'operation',\n 'method',\n 'status',\n 'duration',\n 'success',\n 'callId',\n 'practiceId',\n 'error',\n ];\n const rows = this.items().map((r) => [\n r.startedAt,\n r.provider,\n r.operation,\n r.method,\n r.statusCode != null ? String(r.statusCode) : r.success ? 'OK' : 'TIMEOUT',\n String(r.durationMs),\n r.success ? 'true' : 'false',\n r.callId ?? '',\n r.clientPracticeId ?? '',\n r.errorMessage ?? '',\n ]);\n const csv = [header, ...rows]\n .map((row) => row.map((cell) => `\"${String(cell).replace(/\"/g, '\"\"')}\"`).join(','))\n .join('\\n');\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `api-logs-page-${this.page()}.csv`;\n a.click();\n URL.revokeObjectURL(url);\n }\n\n protected totalPages(): number {\n return Math.max(1, Math.ceil(this.total() / PAGE_SIZE));\n }\n\n private practiceIdParam(): string | undefined {\n if (!this.isSuperAdmin()) return undefined;\n const id = this.practiceIdFilter().trim();\n return id || undefined;\n }\n\n private loadStats(): void {\n this.statsError.set(null);\n this.api.getApiLogStats(this.practiceIdParam()).subscribe({\n next: ({ data }) => this.stats.set(data),\n error: () => {\n this.stats.set(null);\n this.statsError.set('Stats unavailable');\n },\n });\n }\n\n private load(): void {\n this.loading.set(true);\n this.error.set(null);\n const providers = [...this.selectedProviders()];\n this.api\n .getApiLogs({\n provider: providers.length ? providers.join(',') : undefined,\n success:\n this.successFilter() === 'all'\n ? undefined\n : this.successFilter() === 'ok',\n statusCode: this.statusCode() ? Number(this.statusCode()) : undefined,\n callId: this.callIdFilter() || undefined,\n from: this.fromDate() ? new Date(this.fromDate()).toISOString() : undefined,\n to: this.toDate() ? new Date(this.toDate()).toISOString() : undefined,\n search: this.search() || undefined,\n page: this.page(),\n limit: PAGE_SIZE,\n practiceId: this.practiceIdParam(),\n })\n .subscribe({\n next: ({ items, total }) => {\n this.items.set(items);\n this.total.set(total);\n this.loading.set(false);\n },\n error: (err: { status?: number; message?: string }) => {\n this.loading.set(false);\n if (err?.status === 403) {\n this.error.set('You do not have access to that practice’s API logs.');\n } else {\n this.error.set(err?.message ?? 'Failed to load API logs.');\n }\n },\n });\n }\n}\n","<div class=\"eds-page api-logs-page\">\n <header class=\"api-logs-page__header mb-5 flex items-start justify-between gap-4\">\n <div>\n <h1 class=\"eds-h1\">API Logs</h1>\n <p class=\"eds-body-sm\">Edvak EHR, Vonage, OpenAI, and LiveKit request history</p>\n </div>\n <eds-button variant=\"secondary\" (click)=\"exportCsv()\">\n <eds-icon name=\"download\" [size]=\"18\" /> Export CSV\n </eds-button>\n </header>\n\n <section class=\"mb-4 grid grid-cols-2 gap-3 md:grid-cols-4\" aria-label=\"API health last 24 hours\">\n @if (stats(); as s) {\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">Total (24h)</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">{{ s.last24h.total }}</strong>\n </article>\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">Failed (24h)</span>\n <strong class=\"text-3xl font-bold leading-tight text-red-600\">{{ s.last24h.failed }}</strong>\n </article>\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">Avg latency</span>\n <strong class=\"text-3xl font-bold leading-tight text-zinc-900\">{{ formatDuration(s.last24h.avgDurationMs) }}</strong>\n </article>\n <article class=\"flex flex-col gap-1.5 rounded-2xl border bg-white px-4 py-4\">\n <span class=\"eds-label\">By provider</span>\n <ul class=\"m-0 mt-1 flex list-none flex-wrap gap-1.5 p-0\">\n @for (row of s.byProvider; track row.provider) {\n <li class=\"inline-flex items-center gap-1 text-xs text-zinc-600\">\n <span class=\"api-log-chip\" [class]=\"providerChipClass(row.provider)\">{{ providerLabel(row.provider) }}</span>\n {{ row.total }}\n </li>\n } @empty {\n <li class=\"text-sm text-zinc-500\">—</li>\n }\n </ul>\n </article>\n } @else if (statsError()) {\n <p class=\"col-span-full eds-body-sm text-zinc-500\">{{ statsError() }}</p>\n } @else {\n <p class=\"col-span-full eds-body-sm\">Loading stats…</p>\n }\n </section>\n\n <section class=\"eds-card api-logs-page__filters mb-4 p-4 shadow-sm\">\n <div class=\"api-logs-page__filter-row mb-3\">\n <span class=\"eds-label\">Provider</span>\n <div class=\"api-logs-page__chips mt-2 flex flex-wrap gap-2\">\n @for (p of providers; track p) {\n <button\n type=\"button\"\n class=\"api-log-chip api-logs-page__chip\"\n [class]=\"'api-log-chip--' + p\"\n [class.api-logs-page__chip--on]=\"isProviderSelected(p)\"\n (click)=\"toggleProvider(p)\"\n >\n {{ p }}\n </button>\n }\n </div>\n </div>\n\n <div class=\"api-logs-page__filter-grid mb-3 grid grid-cols-2 gap-3 md:grid-cols-3\">\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">Result</span>\n <select [ngModel]=\"successFilter()\" (ngModelChange)=\"successFilter.set($event)\" autocomplete=\"off\">\n <option value=\"all\">All</option>\n <option value=\"ok\">Success</option>\n <option value=\"fail\">Failed</option>\n </select>\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">Status code</span>\n <input type=\"number\" placeholder=\"e.g. 500\" [ngModel]=\"statusCode()\" (ngModelChange)=\"statusCode.set($event)\" autocomplete=\"off\" />\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">Call ID</span>\n <eds-search-field\n type=\"text\"\n inputClass=\"rounded-md border border-zinc-200 px-3 py-2 text-sm\"\n placeholder=\"LC-4821\"\n [value]=\"callIdFilter()\"\n (valueChange)=\"callIdFilter.set($event)\"\n (enter)=\"applyFilters()\"\n />\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">Search</span>\n <eds-search-field\n inputClass=\"rounded-md border border-zinc-200 px-3 py-2 text-sm\"\n placeholder=\"operation, path, error…\"\n [value]=\"search()\"\n (valueChange)=\"search.set($event)\"\n (enter)=\"applyFilters()\"\n />\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">From</span>\n <input type=\"datetime-local\" [ngModel]=\"fromDate()\" (ngModelChange)=\"fromDate.set($event)\" autocomplete=\"off\" />\n </label>\n\n <label class=\"api-logs-page__field flex flex-col gap-1\">\n <span class=\"eds-label\">To</span>\n <input type=\"datetime-local\" [ngModel]=\"toDate()\" (ngModelChange)=\"toDate.set($event)\" autocomplete=\"off\" />\n </label>\n\n @if (isSuperAdmin()) {\n <label class=\"api-logs-page__field flex flex-col gap-1 md:col-span-2\">\n <span class=\"eds-label\">Practice ID (super-admin)</span>\n <eds-search-field\n type=\"text\"\n inputClass=\"rounded-md border border-zinc-200 px-3 py-2 text-sm\"\n placeholder=\"Leave empty for all practices\"\n [value]=\"practiceIdFilter()\"\n (valueChange)=\"practiceIdFilter.set($event)\"\n (enter)=\"applyFilters()\"\n />\n </label>\n }\n </div>\n\n <div class=\"api-logs-page__filter-actions flex gap-2\">\n <eds-button variant=\"primary\" (click)=\"applyFilters()\">Apply filters</eds-button>\n <eds-button variant=\"secondary\" (click)=\"clearFilters()\">Clear</eds-button>\n </div>\n </section>\n\n <article class=\"eds-card api-logs-page__table-card p-4 shadow-sm\">\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading…</p>\n } @else if (error()) {\n <p class=\"api-logs-page__error eds-body-sm text-red-700\" role=\"alert\">{{ error() }}</p>\n } @else if (items().length === 0) {\n <p class=\"eds-body-sm\">No API logs match your filters.</p>\n } @else {\n <eds-api-logs-table\n [items]=\"items()\"\n [showCallId]=\"true\"\n [showPracticeId]=\"showPracticeColumn()\"\n (rowSelected)=\"onRowSelected($event)\"\n />\n }\n\n <footer class=\"api-logs-page__pager mt-4 flex items-center justify-between pt-3\">\n <span class=\"eds-body-sm\">\n Page {{ page() }} of {{ totalPages() }} · {{ total() }} total\n </span>\n <div class=\"api-logs-page__pager-actions flex gap-2\">\n <eds-button variant=\"secondary\" [disabled]=\"page() <= 1\" (click)=\"prevPage()\">Previous</eds-button>\n <eds-button variant=\"secondary\" [disabled]=\"page() >= totalPages()\" (click)=\"nextPage()\">Next</eds-button>\n </div>\n </footer>\n </article>\n\n <eds-api-log-detail-drawer\n [open]=\"drawerOpen()\"\n [listItem]=\"selected()\"\n (closed)=\"closeDrawer()\"\n />\n</div>\n"],"names":[],"mappings":"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"}
|
|
@@ -4,9 +4,8 @@ import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
|
4
4
|
import { DatePipe } from '@angular/common';
|
|
5
5
|
import { RouterLink } from '@angular/router';
|
|
6
6
|
import { switchMap, of, map, tap, catchError } from 'rxjs';
|
|
7
|
-
import { A as AiReceptionistApiService } from './edvak99-voice-receptionist-widget-
|
|
7
|
+
import { A as AiReceptionistApiService, l as EdsBadgeComponent, E as EdsButtonComponent, d as EdsIconComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-pS2jeB4x.mjs';
|
|
8
8
|
import { p as providerLabel, a as providerChipClass, f as formatApiDuration, s as statusBadgeTone, b as statusLabel, c as prettyJson, d as formatPreview, t as truncatePreview } from './edvak99-voice-receptionist-widget-api-log.util-C0TOKF1q.mjs';
|
|
9
|
-
import { EdsBadgeComponent, EdsButtonComponent, EdsIconComponent } from './edvak99-voice-receptionist-widget.mjs';
|
|
10
9
|
|
|
11
10
|
class ApiLogDetailDrawerComponent {
|
|
12
11
|
constructor() {
|
|
@@ -107,4 +106,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
|
|
|
107
106
|
}] });
|
|
108
107
|
|
|
109
108
|
export { ApiLogsTableComponent as A, ApiLogDetailDrawerComponent as a };
|
|
110
|
-
//# sourceMappingURL=edvak99-voice-receptionist-widget-api-logs-table.component-
|
|
109
|
+
//# sourceMappingURL=edvak99-voice-receptionist-widget-api-logs-table.component-CiNc65dO.mjs.map
|