@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-api-logs-table.component-CiNc65dO.mjs","sources":["../../../src/lib/components/api-log-detail-drawer/api-log-detail-drawer.component.ts","../../../src/lib/components/api-log-detail-drawer/api-log-detail-drawer.component.html","../../../src/lib/components/api-logs-table/api-logs-table.component.ts","../../../src/lib/components/api-logs-table/api-logs-table.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\nimport { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';\nimport { DatePipe } from '@angular/common';\nimport { RouterLink } from '@angular/router';\nimport { catchError, map, of, switchMap, tap } from 'rxjs';\n\nimport { ApiLogDetail, ApiLogListItem } from '../../models/api-log.types';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport {\n formatApiDuration,\n formatPreview,\n prettyJson,\n providerChipClass,\n providerLabel,\n statusBadgeTone,\n statusLabel,\n} from '../../utils/api-log.util';\nimport { EdsBadgeComponent } from '../../ui/eds-badge.component';\nimport { EdsButtonComponent } from '../../ui/eds-button.component';\nimport { EdsIconComponent } from '../../ui/eds-icon.component';\n\n@Component({\n selector: 'eds-api-log-detail-drawer',\n imports: [DatePipe, RouterLink, EdsBadgeComponent, EdsButtonComponent, EdsIconComponent],\n templateUrl: './api-log-detail-drawer.component.html',\n styleUrl: './api-log-detail-drawer.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class ApiLogDetailDrawerComponent {\n private readonly api = inject(AiReceptionistApiService);\n private readonly destroyRef = inject(DestroyRef);\n\n readonly open = input(false);\n readonly listItem = input<ApiLogListItem | null>(null);\n /** When true, show list-item previews only (skip GET /api-logs/:id). */\n readonly listOnly = input(false);\n readonly closed = output<void>();\n\n protected readonly detail = signal<ApiLogDetail | null>(null);\n protected readonly loading = signal(false);\n protected readonly error = signal<string | null>(null);\n\n protected providerLabel = providerLabel;\n protected providerChipClass = providerChipClass;\n protected formatDuration = formatApiDuration;\n protected statusTone = statusBadgeTone;\n protected statusLabel = statusLabel;\n protected prettyJson = prettyJson;\n protected formatPreview = formatPreview;\n\n /** Fetch key changes when drawer opens with a new log (non-listOnly). */\n private readonly fetchKey = computed(() => {\n if (!this.open()) return null;\n const item = this.listItem();\n if (!item || this.listOnly()) return null;\n return item.id;\n });\n\n constructor() {\n toObservable(this.fetchKey)\n .pipe(\n switchMap((logId) => {\n if (!logId) {\n this.detail.set(null);\n this.error.set(null);\n this.loading.set(false);\n return of(null);\n }\n this.loading.set(true);\n this.error.set(null);\n return this.api.getApiLogDetail(logId).pipe(\n map(({ data }) => data),\n tap((data) => {\n this.detail.set(data);\n this.loading.set(false);\n if (!data) this.error.set('Log entry not found.');\n }),\n catchError(() => {\n this.loading.set(false);\n this.error.set('Failed to load log detail.');\n return of(null);\n }),\n );\n }),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe();\n }\n\n protected close(): void {\n this.closed.emit();\n }\n\n protected onBackdropClick(event: MouseEvent): void {\n if (event.target === event.currentTarget) {\n this.close();\n }\n }\n}\n","@if (open()) {\n <div class=\"api-log-drawer__backdrop fixed inset-0 z-[1200] flex justify-end bg-black/45\" (click)=\"onBackdropClick($event)\" role=\"presentation\">\n <aside class=\"api-log-drawer flex h-full w-[min(520px,100vw)] flex-col border-l bg-[var(--eds-surface-card,#fff)] shadow-[-8px_0_32px_rgba(0,0,0,0.12)]\" role=\"dialog\" aria-modal=\"true\" aria-label=\"API log detail\">\n <header class=\"flex items-start justify-between gap-3 border-b px-5 pb-3 pt-5\">\n <div>\n @if (listItem(); as item) {\n <span class=\"api-log-chip\" [class]=\"providerChipClass(item.provider)\">\n {{ providerLabel(item.provider) }}\n </span>\n <h2 class=\"eds-h2 api-log-drawer__title my-2 mb-1\">{{ item.operation }}</h2>\n <p class=\"eds-body-sm api-log-drawer__meta m-0\">\n {{ item.method }} {{ item.urlPath }} · {{ item.startedAt | date: 'medium' }}\n </p>\n }\n </div>\n <button type=\"button\" class=\"api-log-drawer__close cursor-pointer border-none bg-transparent p-1\" aria-label=\"Close\" (click)=\"close()\">\n <eds-icon name=\"close\" [size]=\"20\" />\n </button>\n </header>\n\n @if (listItem(); as item) {\n @if (!listOnly() && loading()) {\n <p class=\"eds-body-sm flex-1 overflow-y-auto px-5 pb-6 pt-4\">Loading detail…</p>\n } @else if (!listOnly() && error()) {\n <p class=\"eds-body-sm px-5 py-4 text-[#b91c1c]\" role=\"alert\">{{ error() }}</p>\n } @else if (listOnly() || detail()) {\n <div class=\"flex-1 overflow-y-auto px-5 pb-6 pt-4\">\n <div class=\"mb-4 flex flex-wrap items-center gap-3\">\n <eds-badge [tone]=\"statusTone(listOnly() ? item.success : detail()!.success)\">\n {{ statusLabel(listOnly() ? item.success : detail()!.success, listOnly() ? item.statusCode : detail()!.statusCode) }}\n </eds-badge>\n <span class=\"eds-mono-sm\">{{ formatDuration(listOnly() ? item.durationMs : detail()!.durationMs) }}</span>\n @if (!listOnly() && detail()!.traceId) {\n <span class=\"eds-mono-sm\">trace: {{ detail()!.traceId }}</span>\n }\n @if (item.toolName) {\n <span class=\"eds-body-sm\">tool: {{ item.toolName }}</span>\n }\n </div>\n\n @if ((listOnly() ? item.errorMessage : detail()!.errorMessage)) {\n <section class=\"api-log-drawer__section api-log-drawer__section--error mb-4 rounded-lg p-3\">\n <h3 class=\"eds-label\">Error</h3>\n <p>{{ listOnly() ? item.errorMessage : detail()!.errorMessage }}</p>\n </section>\n }\n\n <section class=\"mb-4\">\n <h3 class=\"eds-label\">Request</h3>\n <pre class=\"api-log-drawer__json mt-2 overflow-x-auto whitespace-pre-wrap break-words rounded-lg p-3 font-[family-name:var(--eds-font-mono,ui-monospace,monospace)] text-xs leading-normal\">{{\n listOnly() ? formatPreview(item.requestPreview) : prettyJson(detail()!.requestBody)\n }}</pre>\n </section>\n\n <section class=\"mb-4\">\n <h3 class=\"eds-label\">Response</h3>\n <pre class=\"api-log-drawer__json mt-2 overflow-x-auto whitespace-pre-wrap break-words rounded-lg p-3 font-[family-name:var(--eds-font-mono,ui-monospace,monospace)] text-xs leading-normal\">{{\n listOnly() ? formatPreview(item.responsePreview) : prettyJson(detail()!.responseBody)\n }}</pre>\n </section>\n\n @if (!listOnly() && detail()!.metadata) {\n <section class=\"mb-4\">\n <h3 class=\"eds-label\">Metadata</h3>\n <pre class=\"api-log-drawer__json mt-2 overflow-x-auto whitespace-pre-wrap break-words rounded-lg p-3 font-[family-name:var(--eds-font-mono,ui-monospace,monospace)] text-xs leading-normal\">{{ prettyJson(detail()!.metadata) }}</pre>\n </section>\n }\n\n @if (!listOnly() && detail()!.callId) {\n <div class=\"mt-2\">\n <eds-button variant=\"secondary\" [routerLink]=\"['../history', detail()!.callId]\">\n View call\n </eds-button>\n </div>\n }\n </div>\n }\n }\n </aside>\n </div>\n}\n","import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { RouterLink } from '@angular/router';\n\nimport { ApiLogListItem } from '../../models/api-log.types';\nimport {\n formatApiDuration,\n providerChipClass,\n providerLabel,\n statusBadgeTone,\n statusLabel,\n truncatePreview,\n} from '../../utils/api-log.util';\nimport { EdsBadgeComponent } from '../../ui/eds-badge.component';\n\n@Component({\n selector: 'eds-api-logs-table',\n imports: [DatePipe, RouterLink, EdsBadgeComponent],\n templateUrl: './api-logs-table.component.html',\n styleUrl: './api-logs-table.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ApiLogsTableComponent {\n readonly items = input.required<ApiLogListItem[]>();\n readonly showCallId = input(false);\n readonly showPracticeId = input(false);\n readonly callLinkBase = input<string[]>(['../history']);\n readonly rowSelected = output<ApiLogListItem>();\n\n protected providerLabel = providerLabel;\n protected providerChipClass = providerChipClass;\n protected formatDuration = formatApiDuration;\n protected statusTone = statusBadgeTone;\n protected statusLabel = statusLabel;\n protected truncate = truncatePreview;\n\n protected onRowClick(item: ApiLogListItem): void {\n this.rowSelected.emit(item);\n }\n\n protected onCallClick(event: Event): void {\n event.stopPropagation();\n }\n\n protected callLink(callId: string): string[] {\n return [...this.callLinkBase(), callId];\n }\n}\n","<div class=\"api-logs-table__wrap overflow-x-auto\">\n <table class=\"eds-table api-logs-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th>Provider</th>\n <th>Operation</th>\n <th>Method</th>\n <th>Status</th>\n <th>Duration</th>\n <th>Success</th>\n <th>Tool</th>\n @if (showCallId()) {\n <th>Call</th>\n }\n @if (showPracticeId()) {\n <th>Practice</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of items(); track row.id) {\n <tr class=\"api-logs-table__row\" tabindex=\"0\" (click)=\"onRowClick(row)\" (keyup.enter)=\"onRowClick(row)\">\n <td class=\"eds-mono-sm whitespace-nowrap\">{{ row.startedAt | date: 'MMM d, HH:mm:ss' }}</td>\n <td>\n <span class=\"api-log-chip\" [class]=\"providerChipClass(row.provider)\">\n {{ providerLabel(row.provider) }}\n </span>\n </td>\n <td>\n <span class=\"api-logs-table__op block\">{{ row.operation }}</span>\n <span class=\"api-logs-table__preview eds-body-sm block mt-1\">{{ truncate(row.requestPreview) }}</span>\n </td>\n <td class=\"eds-mono-sm\">{{ row.method }}</td>\n <td>\n <eds-badge [tone]=\"statusTone(row.success)\">\n {{ statusLabel(row.success, row.statusCode) }}\n </eds-badge>\n </td>\n <td class=\"eds-mono-sm\">{{ formatDuration(row.durationMs) }}</td>\n <td class=\"eds-body-sm\">{{ row.success ? 'OK' : 'Fail' }}</td>\n <td class=\"eds-body-sm\">{{ row.toolName ?? '—' }}</td>\n @if (showCallId()) {\n <td class=\"eds-mono-sm\" (click)=\"onCallClick($event)\">\n @if (row.callId) {\n <a\n [routerLink]=\"callLink(row.callId)\"\n class=\"font-medium text-blue-600 no-underline hover:underline\"\n >{{ row.callId }}</a>\n } @else {\n —\n }\n </td>\n }\n @if (showPracticeId()) {\n <td class=\"eds-mono-sm\">{{ row.clientPracticeId || '—' }}</td>\n }\n </tr>\n }\n </tbody>\n </table>\n</div>\n"],"names":[],"mappings":"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"}
|
|
@@ -3,13 +3,11 @@ import { inject, DestroyRef, input, signal, computed, ChangeDetectionStrategy, C
|
|
|
3
3
|
import { RouterLink, ActivatedRoute } from '@angular/router';
|
|
4
4
|
import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
5
5
|
import { switchMap, EMPTY, interval, startWith, tap, catchError, finalize } from 'rxjs';
|
|
6
|
-
import { A as AiReceptionistApiService, t as toolEventTrackKey,
|
|
7
|
-
import {
|
|
8
|
-
import { A as ApiLogsTableComponent, a as ApiLogDetailDrawerComponent } from './edvak99-voice-receptionist-widget-api-logs-table.component-CEf4NPe5.mjs';
|
|
6
|
+
import { A as AiReceptionistApiService, D as DashboardSocketService, T as ToastService, t as toolEventTrackKey, l as EdsBadgeComponent, E as EdsButtonComponent, d as EdsIconComponent, m as activityTypeLabel, V as VOICE_RECEPTIONIST_CONFIG, i as isUnknownCaller } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-pS2jeB4x.mjs';
|
|
7
|
+
import { A as ApiLogsTableComponent, a as ApiLogDetailDrawerComponent } from './edvak99-voice-receptionist-widget-api-logs-table.component-CiNc65dO.mjs';
|
|
9
8
|
import { DatePipe } from '@angular/common';
|
|
10
|
-
import {
|
|
11
|
-
import { C as
|
|
12
|
-
import { C as CallReviewService } from './edvak99-voice-receptionist-widget-call-review.service-DQ4TT_Su.mjs';
|
|
9
|
+
import { C as CallRecordingPlayerComponent } from './edvak99-voice-receptionist-widget-call-recording-player.component-GVg1g-Vq.mjs';
|
|
10
|
+
import { C as CallReviewService } from './edvak99-voice-receptionist-widget-call-review.service-WG30w6Jl.mjs';
|
|
13
11
|
import { i as isNeedsReview } from './edvak99-voice-receptionist-widget-call-review.util-Deh3jPgo.mjs';
|
|
14
12
|
|
|
15
13
|
const POLL_MS = 5000;
|
|
@@ -512,4 +510,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
|
|
|
512
510
|
}] });
|
|
513
511
|
|
|
514
512
|
export { CallHistoryDetailPageComponent };
|
|
515
|
-
//# sourceMappingURL=edvak99-voice-receptionist-widget-call-history-detail-page.component-
|
|
513
|
+
//# sourceMappingURL=edvak99-voice-receptionist-widget-call-history-detail-page.component-ENq2c-sm.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-call-history-detail-page.component-ENq2c-sm.mjs","sources":["../../../src/lib/components/api-logs-call-panel/api-logs-call-panel.component.ts","../../../src/lib/components/api-logs-call-panel/api-logs-call-panel.component.html","../../../src/lib/utils/transcript-timing.util.ts","../../../src/lib/components/call-conversation-panel/call-conversation-panel.component.ts","../../../src/lib/components/call-conversation-panel/call-conversation-panel.component.html","../../../src/lib/components/call-tasks-panel/call-tasks-panel.component.ts","../../../src/lib/components/call-tasks-panel/call-tasks-panel.component.html","../../../src/lib/utils/call-outcome.util.ts","../../../src/lib/pages/call-history-detail-page/call-history-detail-page.component.ts","../../../src/lib/pages/call-history-detail-page/call-history-detail-page.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n inject,\n input,\n signal,\n} from '@angular/core';\nimport { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';\nimport {\n catchError,\n EMPTY,\n finalize,\n interval,\n startWith,\n switchMap,\n tap,\n} from 'rxjs';\n\nimport { ApiLogListItem } from '../../models/api-log.types';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport { DashboardSocketService } from '../../services/dashboard-socket.service';\nimport { ApiLogDetailDrawerComponent } from '../api-log-detail-drawer/api-log-detail-drawer.component';\nimport { ApiLogsTableComponent } from '../api-logs-table/api-logs-table.component';\n\nconst POLL_MS = 5000;\n\n@Component({\n selector: 'eds-api-logs-call-panel',\n imports: [ApiLogsTableComponent, ApiLogDetailDrawerComponent],\n templateUrl: './api-logs-call-panel.component.html',\n styleUrl: './api-logs-call-panel.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class ApiLogsCallPanelComponent {\n private readonly api = inject(AiReceptionistApiService);\n private readonly socket = inject(DashboardSocketService);\n private readonly destroyRef = inject(DestroyRef);\n\n readonly callId = input.required<string>();\n readonly live = input(false);\n readonly dark = input(false);\n\n protected readonly items = signal<ApiLogListItem[]>([]);\n protected readonly loading = signal(true);\n protected readonly error = signal<string | null>(null);\n protected readonly selected = signal<ApiLogListItem | null>(null);\n protected readonly drawerOpen = signal(false);\n\n private readonly panelState = computed(() => ({\n callId: this.callId(),\n live: this.live(),\n }));\n\n private readonly onApiLogUpdated = (payload: { callId?: string }) => {\n if (payload.callId === this.callId()) {\n this.refetchQuietly();\n }\n };\n\n constructor() {\n toObservable(this.panelState)\n .pipe(\n switchMap(({ callId, live }) => {\n if (!callId) {\n this.items.set([]);\n this.loading.set(false);\n this.error.set(null);\n return EMPTY;\n }\n\n if (live) {\n this.socket.acquire();\n this.socket.on('api_log_updated', this.onApiLogUpdated);\n }\n\n const logs$ = live\n ? interval(POLL_MS).pipe(\n startWith(0),\n switchMap(() => this.api.getCallApiLogs(callId)),\n )\n : this.api.getCallApiLogs(callId);\n\n return logs$.pipe(\n tap(({ items }) => {\n this.items.set(items);\n this.loading.set(false);\n this.error.set(null);\n }),\n catchError(() => {\n this.loading.set(false);\n this.error.set('Failed to load API logs.');\n return EMPTY;\n }),\n finalize(() => {\n if (live) {\n this.socket.off('api_log_updated', this.onApiLogUpdated);\n this.socket.release();\n }\n }),\n );\n }),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe();\n\n // Show loading when callId changes (before first emission).\n toObservable(this.callId)\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe((id) => {\n if (id) {\n this.loading.set(true);\n this.error.set(null);\n }\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 private refetchQuietly(): void {\n const id = this.callId();\n if (!id) return;\n this.api.getCallApiLogs(id).subscribe({\n next: ({ items }) => this.items.set(items),\n });\n }\n}\n","<div class=\"api-logs-call-panel min-h-[120px]\" [class.api-logs-call-panel--dark]=\"dark()\">\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading API logs…</p>\n } @else if (error()) {\n <p class=\"api-logs-call-panel__error eds-body-sm\" role=\"alert\">{{ error() }}</p>\n } @else if (items().length === 0) {\n <p class=\"eds-body-sm api-logs-call-panel__empty py-6 text-center\">No external API calls recorded for this call.</p>\n } @else {\n <eds-api-logs-table [items]=\"items()\" (rowSelected)=\"onRowSelected($event)\" />\n }\n\n <eds-api-log-detail-drawer\n [open]=\"drawerOpen()\"\n [listItem]=\"selected()\"\n (closed)=\"closeDrawer()\"\n />\n</div>\n","import { ToolEvent, TranscriptLine } from '../models/ai-receptionist.types';\n\nexport interface TimedTranscriptLine extends TranscriptLine {\n startSec: number;\n endSec: number;\n index: number;\n}\n\n/** Parse tool timestamp strings like \"1:02\" or \"0:42\" into seconds. */\nexport function parseClockTime(value: string): number | null {\n const trimmed = value?.trim();\n if (!trimmed) return null;\n const parts = trimmed.split(':').map((p) => Number(p));\n if (parts.some((n) => Number.isNaN(n))) return null;\n if (parts.length === 2) return parts[0] * 60 + parts[1];\n if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2];\n return null;\n}\n\nfunction resolveLineStart(line: TranscriptLine): number | null {\n if (line.at != null && line.at >= 0) return line.at;\n const raw = line as TranscriptLine & { offsetSeconds?: number; startTime?: number };\n if (raw.offsetSeconds != null && raw.offsetSeconds >= 0) return raw.offsetSeconds;\n if (raw.startTime != null && raw.startTime >= 0) return raw.startTime;\n return null;\n}\n\n/**\n * Assign start/end seconds to transcript lines for audio sync.\n * Uses explicit `at` when present; otherwise estimates from call duration.\n */\nexport function buildTimedTranscript(\n lines: TranscriptLine[],\n durationSeconds: number,\n): TimedTranscriptLine[] {\n if (!lines.length) return [];\n\n const total = Math.max(durationSeconds, 1);\n const explicit = lines.map((line) => resolveLineStart(line));\n const allExplicit = explicit.every((t) => t != null);\n\n const starts: number[] = new Array(lines.length).fill(0);\n\n if (allExplicit) {\n for (let i = 0; i < lines.length; i += 1) {\n starts[i] = explicit[i]!;\n }\n } else {\n const speakableCount = lines.filter((l) => l.who !== 'system').length;\n const step = total / Math.max(speakableCount, 1);\n let speakableIdx = 0;\n\n for (let i = 0; i < lines.length; i += 1) {\n if (explicit[i] != null) {\n starts[i] = explicit[i]!;\n continue;\n }\n const line = lines[i];\n if (line.who === 'system') {\n starts[i] = i > 0 ? starts[i - 1] : 0;\n continue;\n }\n starts[i] = speakableIdx * step;\n speakableIdx += 1;\n }\n }\n\n return lines.map((line, index) => {\n const startSec = starts[index];\n const nextStart = starts.slice(index + 1).find((s) => s > startSec);\n const endSec = nextStart ?? total;\n return { ...line, startSec, endSec: Math.max(endSec, startSec + 0.5), index };\n });\n}\n\nexport function activeTranscriptIndex(lines: TimedTranscriptLine[], currentSec: number): number {\n if (!lines.length || currentSec < 0) return -1;\n for (let i = lines.length - 1; i >= 0; i -= 1) {\n if (currentSec >= lines[i].startSec) return i;\n }\n return 0;\n}\n\nexport function formatPlaybackTime(seconds: number): string {\n const s = Math.max(0, Math.floor(seconds));\n const m = Math.floor(s / 60);\n const r = s % 60;\n return `${m}:${r.toString().padStart(2, '0')}`;\n}\n\nexport function toolStateClass(state: ToolEvent['state']): string {\n const map: Record<ToolEvent['state'], string> = {\n ok: 'call-conversation__tool--ok',\n warn: 'call-conversation__tool--warn',\n error: 'call-conversation__tool--error',\n active: 'call-conversation__tool--active',\n };\n return map[state] ?? '';\n}\n\nexport function transcriptRoleLabel(\n who: TranscriptLine['who'],\n callerName?: string | null,\n callerMatched?: boolean,\n): string {\n if (who === 'patient') {\n const name = String(callerName ?? '').trim();\n if (!name || callerMatched === false || /^unknown/i.test(name) || name === '?') {\n return 'Unknown caller';\n }\n return name;\n }\n const labels: Record<Exclude<TranscriptLine['who'], 'patient'>, string> = {\n ai: 'AI receptionist',\n staff: 'Staff',\n system: 'System',\n };\n return labels[who] ?? who;\n}\n","import { DatePipe } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n ElementRef,\n inject,\n input,\n output,\n signal,\n viewChild,\n} from '@angular/core';\n\nimport { ToolEvent, TranscriptLine, TranscriptWho } from '../../models/ai-receptionist.types';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport { ToastService } from '../../shared/toast.service';\nimport {\n activeTranscriptIndex,\n buildTimedTranscript,\n formatPlaybackTime,\n TimedTranscriptLine,\n transcriptRoleLabel,\n} from '../../utils/transcript-timing.util';\nimport { toolEventTrackKey } from '../../utils/tool-labels.util';\nimport { CallRecordingPlayerComponent } from '../call-recording-player/call-recording-player.component';\nimport { EdsBadgeComponent, type BadgeTone } from '../../ui/eds-badge.component';\nimport { EdsButtonComponent } from '../../ui/eds-button.component';\nimport { EdsIconComponent } from '../../ui/eds-icon.component';\n\n@Component({\n selector: 'eds-call-conversation-panel',\n imports: [\n DatePipe,\n CallRecordingPlayerComponent,\n EdsBadgeComponent,\n EdsButtonComponent,\n EdsIconComponent,\n ],\n templateUrl: './call-conversation-panel.component.html',\n styleUrl: './call-conversation-panel.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CallConversationPanelComponent {\n private readonly api = inject(AiReceptionistApiService);\n private readonly toast = inject(ToastService);\n\n readonly callId = input.required<string>();\n readonly summary = input.required<string>();\n readonly transcript = input.required<TranscriptLine[]>();\n readonly tools = input.required<ToolEvent[]>();\n readonly callDurationSec = input(0);\n readonly allowRegenerate = input(true);\n readonly callerName = input<string | null>(null);\n readonly callerMatched = input<boolean | null>(null);\n\n readonly summaryRegenerated = output<void>();\n\n private readonly player = viewChild(CallRecordingPlayerComponent);\n private readonly scrollHost = viewChild<ElementRef<HTMLElement>>('scrollHost');\n\n protected readonly currentTime = signal(0);\n protected readonly playbackDuration = signal(0);\n protected readonly summaryExpanded = signal(false);\n private readonly regeneratedSummary = signal<string | null>(null);\n protected readonly displaySummary = computed(\n () => this.regeneratedSummary() ?? this.summary(),\n );\n protected readonly regenerating = signal(false);\n protected readonly summaryMeta = signal<{\n model?: string;\n generatedAt?: string;\n turnCount?: number;\n } | null>(null);\n\n protected readonly timedLines = computed(() =>\n buildTimedTranscript(\n this.transcript(),\n this.playbackDuration() || this.callDurationSec() || 120,\n ),\n );\n\n protected readonly activeIndex = computed(() =>\n activeTranscriptIndex(this.timedLines(), this.currentTime()),\n );\n\n protected readonly hasExplicitTiming = computed(() =>\n this.transcript().some((line) => line.at != null),\n );\n\n protected readonly formatTime = formatPlaybackTime;\n protected readonly toolTrackKey = toolEventTrackKey;\n\n protected roleLabel(who: TranscriptLine['who']): string {\n return transcriptRoleLabel(who, this.callerName(), this.callerMatched() ?? undefined);\n }\n\n protected speakerIcon(who: TranscriptWho | string): string {\n switch (who) {\n case 'ai':\n return 'graphic_eq';\n case 'staff':\n return 'support_agent';\n case 'patient':\n return 'person';\n default:\n return 'info';\n }\n }\n\n protected toolTone(state: string): BadgeTone {\n switch (state) {\n case 'ok':\n return 'pos';\n case 'warn':\n return 'notice';\n case 'error':\n return 'neg';\n case 'active':\n return 'brand';\n default:\n return 'gray';\n }\n }\n\n constructor() {\n effect(\n () => {\n this.callId();\n this.regeneratedSummary.set(null);\n },\n { allowSignalWrites: true },\n );\n\n effect(() => {\n const idx = this.activeIndex();\n const host = this.scrollHost()?.nativeElement;\n if (idx < 0 || !host) return;\n const row = host.querySelector(`[data-line-index=\"${idx}\"]`);\n if (row instanceof HTMLElement) {\n row.scrollIntoView({ block: 'nearest', behavior: 'smooth' });\n }\n });\n }\n\n protected onPlaybackTime(sec: number): void {\n this.currentTime.set(sec);\n }\n\n protected onPlaybackDuration(sec: number): void {\n this.playbackDuration.set(sec);\n }\n\n protected seekLine(line: TimedTranscriptLine): void {\n if (line.who === 'system') return;\n this.player()?.seekTo(line.startSec);\n }\n\n protected toggleSummary(): void {\n this.summaryExpanded.update((v) => !v);\n }\n\n protected regenerateSummary(): void {\n if (this.regenerating()) return;\n\n this.regenerating.set(true);\n this.api.regenerateCallSummary(this.callId()).subscribe({\n next: ({ data }) => {\n this.regenerating.set(false);\n this.regeneratedSummary.set(data.summary);\n this.summaryExpanded.set(true);\n this.summaryMeta.set({\n model: data.summaryModel,\n generatedAt: data.summaryGeneratedAt,\n turnCount: data.transcriptTurnCount,\n });\n this.summaryRegenerated.emit();\n this.toast.show('Call summary regenerated', 'success');\n },\n error: (err: { message?: string }) => {\n this.regenerating.set(false);\n this.toast.show(err.message ?? 'Failed to regenerate summary', 'error');\n },\n });\n }\n}\n","<div class=\"flex flex-col gap-4\">\n <div class=\"grid grid-cols-1 items-stretch gap-4 lg:grid-cols-2\">\n <section class=\"eds-card p-4 shadow-sm\">\n <div class=\"flex items-center justify-between gap-3\">\n <button type=\"button\" class=\"flex min-w-0 flex-1 cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left\" (click)=\"toggleSummary()\">\n <eds-icon [name]=\"summaryExpanded() ? 'expand_less' : 'expand_more'\" [size]=\"20\" />\n <h2 class=\"eds-h2 m-0\">Call summary</h2>\n </button>\n @if (allowRegenerate()) {\n <eds-button\n variant=\"secondary\"\n type=\"button\"\n [disabled]=\"regenerating()\"\n (click)=\"regenerateSummary()\"\n >\n <eds-icon name=\"autorenew\" [size]=\"18\" />\n {{ regenerating() ? 'Regenerating…' : 'Regenerate' }}\n </eds-button>\n }\n </div>\n @if (summaryExpanded() || displaySummary().length < 180) {\n <p class=\"eds-body mt-3 leading-relaxed\">{{ displaySummary() }}</p>\n } @else {\n <p class=\"eds-body mt-3 line-clamp-2 leading-relaxed\">\n {{ displaySummary() }}\n </p>\n <button\n type=\"button\"\n class=\"cursor-pointer border-none bg-transparent px-0 pt-1 text-sm text-blue-600\"\n (click)=\"toggleSummary()\"\n >\n Read full summary\n </button>\n }\n @if (summaryMeta(); as meta) {\n <p class=\"eds-body-sm mt-3 flex flex-wrap gap-3 text-zinc-500\">\n @if (meta.model) {\n <span>Model: {{ meta.model }}</span>\n }\n @if (meta.generatedAt) {\n <span>Generated {{ meta.generatedAt | date: 'medium' }}</span>\n }\n @if (meta.turnCount != null) {\n <span>{{ meta.turnCount }} transcript turns</span>\n }\n </p>\n }\n </section>\n <ng-content select=\"[summary-side]\" />\n </div>\n\n <section class=\"eds-card overflow-hidden bg-white p-0 shadow-sm\">\n <eds-call-recording-player\n [callId]=\"callId()\"\n (currentTimeChange)=\"onPlaybackTime($event)\"\n (durationChange)=\"onPlaybackDuration($event)\"\n />\n </section>\n\n <div class=\"grid grid-cols-1 items-start gap-4 lg:grid-cols-[1fr_300px]\">\n <section class=\"eds-card p-4 shadow-sm\" aria-labelledby=\"call-transcript-title\">\n <header class=\"mb-3 flex items-center justify-between gap-3\">\n <h2 id=\"call-transcript-title\" class=\"eds-h2 m-0\">Transcript</h2>\n @if (playbackDuration() > 0) {\n <span class=\"eds-mono-sm text-zinc-500\">\n {{ formatTime(currentTime()) }} / {{ formatTime(playbackDuration()) }}\n </span>\n }\n </header>\n\n <div #scrollHost class=\"flex max-h-[62vh] flex-col gap-3 overflow-y-auto rounded-xl border border-zinc-200 bg-zinc-50 p-3\" role=\"log\">\n @for (line of timedLines(); track line.index) {\n @if (line.who === 'system') {\n <div\n [class]=\"\n 'inline-flex max-w-[90%] items-center gap-2 self-center rounded-full bg-zinc-100 px-3 py-2 text-xs leading-snug text-zinc-500' +\n (line.index === activeIndex() ? ' outline outline-2 outline-blue-200' : '')\n \"\n [attr.data-line-index]=\"line.index\"\n >\n <eds-icon name=\"info\" [size]=\"14\" />\n <span>{{ line.text }}</span>\n </div>\n } @else {\n <article\n [class]=\"\n 'flex max-w-[72%] cursor-pointer gap-3 border-none bg-transparent p-0 text-left max-lg:max-w-[92%] ' +\n (line.who === 'patient' ? 'mr-auto self-start' : 'ml-auto flex-row-reverse self-end')\n \"\n [attr.data-line-index]=\"line.index\"\n [attr.data-who]=\"line.who\"\n tabindex=\"0\"\n role=\"button\"\n [attr.aria-current]=\"line.index === activeIndex() ? 'true' : null\"\n (click)=\"seekLine(line)\"\n (keyup.enter)=\"seekLine(line)\"\n >\n <div\n [class]=\"\n 'flex h-8 w-8 shrink-0 items-center justify-center rounded-full ' +\n (line.who === 'ai'\n ? 'call-conversation__avatar--ai text-white'\n : line.who === 'staff'\n ? 'border border-violet-200 bg-violet-50 text-violet-700'\n : 'bg-zinc-200 text-zinc-600')\n \"\n >\n <eds-icon [name]=\"speakerIcon(line.who)\" [size]=\"16\" />\n </div>\n <div\n [class]=\"\n 'flex min-w-0 flex-col gap-1 ' +\n (line.who === 'patient' ? 'items-start' : 'items-end')\n \"\n >\n <span\n [class]=\"\n 'inline-flex items-center gap-2 text-xs font-medium leading-none ' +\n (line.who === 'ai'\n ? 'text-blue-600'\n : line.who === 'staff'\n ? 'text-violet-700'\n : 'text-zinc-500')\n \"\n >\n {{ roleLabel(line.who) }}\n <span class=\"font-mono text-xs font-normal leading-none text-zinc-400\">{{ formatTime(line.startSec) }}</span>\n </span>\n <div\n [class]=\"\n 'rounded-xl border px-4 py-2 ' +\n (line.who === 'patient'\n ? 'rounded-tl rounded-tr-xl rounded-br-xl rounded-bl-xl border-zinc-200 bg-white'\n : line.who === 'staff'\n ? 'rounded-tl-xl rounded-tr rounded-br-xl rounded-bl-xl border-violet-200 bg-violet-50'\n : 'rounded-tl-xl rounded-tr rounded-br-xl rounded-bl-xl border-blue-200 bg-blue-50') +\n (line.index === activeIndex() ? ' ring-2 ring-blue-200' : '')\n \"\n >\n <p\n [class]=\"\n 'm-0 text-sm leading-snug ' +\n (line.who === 'staff' ? 'text-violet-900' : 'text-zinc-900')\n \"\n >{{ line.text }}</p>\n </div>\n </div>\n </article>\n }\n } @empty {\n <p class=\"eds-body-sm py-3 text-zinc-500\">No transcript available for this call.</p>\n }\n </div>\n </section>\n\n <aside class=\"eds-card p-4 shadow-sm\" aria-labelledby=\"call-goal-timeline-title\">\n <header class=\"mb-3 flex items-center justify-between gap-3\">\n <h2 id=\"call-goal-timeline-title\" class=\"eds-h2 m-0\">Goal Timeline</h2>\n <span class=\"eds-body-sm\">{{ tools().length }}</span>\n </header>\n\n <ul class=\"flex max-h-[62vh] list-none flex-col gap-3 overflow-y-auto p-0\">\n @for (tool of tools(); track toolTrackKey(tool, $index)) {\n <li\n [class]=\"\n 'grid grid-cols-[auto_1fr_auto] items-start gap-3 rounded-md border-l-4 bg-zinc-50 p-3 ' +\n (tool.state === 'ok'\n ? 'border-l-green-500'\n : tool.state === 'warn'\n ? 'border-l-amber-500'\n : tool.state === 'error'\n ? 'border-l-red-500'\n : tool.state === 'active'\n ? 'border-l-blue-600'\n : 'border-l-zinc-400')\n \"\n >\n <span class=\"eds-mono-sm whitespace-nowrap text-zinc-500\">{{ tool.t }}</span>\n <div class=\"flex min-w-0 flex-col gap-1\">\n <span class=\"eds-body-sm-bold\">{{ tool.name }}</span>\n <span class=\"eds-body-sm\">{{ tool.result }}</span>\n </div>\n <eds-badge [tone]=\"toolTone(tool.state)\">{{ tool.state }}</eds-badge>\n </li>\n } @empty {\n <li class=\"eds-body-sm py-3 text-zinc-500\">No goal activity recorded.</li>\n }\n </ul>\n </aside>\n </div>\n</div>\n","import { ChangeDetectionStrategy, Component, computed, inject, input, OnInit, signal } from '@angular/core';\nimport { RouterLink } from '@angular/router';\n\nimport {\n AgentActivity,\n activityTypeLabel,\n} from '../../models/agent-activity.types';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport { EdsBadgeComponent } from '../../ui/eds-badge.component';\n\nconst PREVIEW_LIMIT = 4;\n\n@Component({\n selector: 'eds-call-tasks-panel',\n imports: [RouterLink, EdsBadgeComponent],\n templateUrl: './call-tasks-panel.component.html',\n styleUrl: './call-tasks-panel.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CallTasksPanelComponent implements OnInit {\n private readonly api = inject(AiReceptionistApiService);\n\n readonly callId = input.required<string>();\n readonly compact = input(false);\n\n protected readonly activities = signal<AgentActivity[]>([]);\n protected readonly total = signal(0);\n protected readonly loading = signal(true);\n protected readonly previewLimit = PREVIEW_LIMIT;\n protected readonly activityTypeLabel = activityTypeLabel;\n\n protected readonly previewActivities = computed(() =>\n this.activities().slice(0, PREVIEW_LIMIT),\n );\n\n ngOnInit(): void {\n this.api\n .getActivities({\n callId: this.callId(),\n page: 1,\n limit: 50,\n })\n .subscribe({\n next: ({ items, total }) => {\n this.activities.set(items);\n this.total.set(total);\n this.loading.set(false);\n },\n error: () => this.loading.set(false),\n });\n }\n\n protected activitySubline(item: AgentActivity): string {\n const bits = [\n item.patientName,\n item.visitType,\n item.providerName,\n ].filter(Boolean);\n return bits.join(' · ') || item.detail || '—';\n }\n\n protected activityTone(type: string): 'brand' | 'pos' | 'notice' | 'violet' | 'gray' {\n switch (type) {\n case 'appointment_booked':\n return 'brand';\n case 'patient_created':\n return 'pos';\n case 'task_created':\n return 'notice';\n default:\n return 'gray';\n }\n }\n}\n","<section class=\"call-tasks eds-card px-5 py-4 mt-5 shadow-sm\" [class.call-tasks--compact]=\"compact()\">\n <header class=\"mb-4 flex items-center justify-between gap-3\">\n <h2 class=\"eds-h2\">Activities from this call</h2>\n <a\n routerLink=\"../activities\"\n [queryParams]=\"{ callId: callId() }\"\n class=\"call-tasks__all-link eds-body-sm\"\n >\n View all Activities\n </a>\n </header>\n\n @if (loading()) {\n <p class=\"eds-body-sm call-tasks__empty\">Loading activities…</p>\n } @else if (activities().length === 0) {\n <p class=\"eds-body-sm call-tasks__empty\">No activities linked to this call.</p>\n } @else {\n <ul class=\"call-tasks__list overflow-hidden rounded-lg border border-[var(--eds-border-default,#e5e5e5)]\">\n @for (item of previewActivities(); track item.id) {\n <li class=\"call-tasks__item border-b border-[var(--eds-border-default,#e5e5e5)] last:border-b-0\">\n <div class=\"call-tasks__row grid w-full grid-cols-[minmax(0,1fr)_auto] items-center gap-3 border-none bg-transparent px-4 py-3 text-left font-[inherit]\">\n <div class=\"flex min-w-0 flex-col gap-1\">\n <span class=\"call-tasks__title min-w-0 overflow-hidden text-ellipsis whitespace-nowrap text-sm font-semibold\">{{ item.title || activityTypeLabel(item.type) }}</span>\n <span class=\"call-tasks__sub overflow-hidden text-ellipsis whitespace-nowrap text-xs\">{{ activitySubline(item) }}</span>\n </div>\n <eds-badge [tone]=\"activityTone(item.type)\">{{ activityTypeLabel(item.type) }}</eds-badge>\n </div>\n </li>\n }\n </ul>\n @if (total() > previewLimit) {\n <p class=\"call-tasks__more eds-body-sm mt-3\">\n +{{ total() - previewLimit }} more —\n <a routerLink=\"../activities\" [queryParams]=\"{ callId: callId() }\" class=\"call-tasks__all-link\">\n View all Activities\n </a>\n </p>\n }\n }\n</section>\n","import { BadgeTone } from '../ui/eds-badge.component';\n\n/** Known history outcomes from GET /calls/history (HistoryCallDto.outcome). */\nexport const HISTORY_OUTCOMES = [\n 'completed',\n 'failed',\n 'transferred',\n 'escalated',\n] as const;\n\nexport type HistoryOutcome = (typeof HISTORY_OUTCOMES)[number] | 'custom';\n\nexport function normalizeHistoryOutcome(outcome: string): HistoryOutcome {\n const key = outcome.trim().toLowerCase();\n if ((HISTORY_OUTCOMES as readonly string[]).includes(key)) {\n return key as HistoryOutcome;\n }\n return 'custom';\n}\n\n/** Badge tone for history table / detail — separate from lifecycle or live UI status. */\nexport function outcomeBadgeTone(outcome: string): BadgeTone {\n switch (normalizeHistoryOutcome(outcome)) {\n case 'completed':\n return 'pos';\n case 'failed':\n return 'neg';\n case 'transferred':\n return 'violet';\n case 'escalated':\n return 'neg';\n default: {\n const key = outcome.trim().toLowerCase();\n if (key.includes('transfer')) return 'violet';\n if (key.includes('fail') || key.includes('escalat') || key.includes('abandon')) return 'neg';\n if (key.includes('voicemail')) return 'gray';\n if (key.includes('complete') || key.includes('book') || key.includes('task')) return 'pos';\n return 'brand';\n }\n }\n}\n\nexport function formatOutcomeLabel(outcome: string): string {\n const trimmed = outcome.trim();\n if (!trimmed) return 'Unknown';\n const known = normalizeHistoryOutcome(trimmed);\n if (known === 'custom') return trimmed;\n return known.charAt(0).toUpperCase() + known.slice(1);\n}\n","import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';\nimport { ActivatedRoute, RouterLink } from '@angular/router';\n\nimport { ApiLogsCallPanelComponent } from '../../components/api-logs-call-panel/api-logs-call-panel.component';\nimport { CallConversationPanelComponent } from '../../components/call-conversation-panel/call-conversation-panel.component';\nimport { CallTasksPanelComponent } from '../../components/call-tasks-panel/call-tasks-panel.component';\nimport { VOICE_RECEPTIONIST_CONFIG } from '../../config';\nimport { HistoryDetail } from '../../models/ai-receptionist.types';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport { CallReviewService } from '../../services/call-review.service';\nimport { formatOutcomeLabel, outcomeBadgeTone } from '../../utils/call-outcome.util';\nimport {\n isNeedsReview,\n} from '../../utils/call-review.util';\nimport { isUnknownCaller } from '../../utils/call-history-display.util';\nimport { EdsBadgeComponent } from '../../ui/eds-badge.component';\nimport { EdsButtonComponent } from '../../ui/eds-button.component';\nimport { EdsIconComponent } from '../../ui/eds-icon.component';\nimport { ToastService } from '../../shared/toast.service';\n\n@Component({\n selector: 'eds-call-history-detail-page',\n imports: [\n RouterLink,\n EdsBadgeComponent,\n EdsButtonComponent,\n EdsIconComponent,\n CallConversationPanelComponent,\n CallTasksPanelComponent,\n ApiLogsCallPanelComponent,\n ],\n templateUrl: './call-history-detail-page.component.html',\n styleUrl: './call-history-detail-page.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class CallHistoryDetailPageComponent implements OnInit {\n private readonly api = inject(AiReceptionistApiService);\n private readonly reviewService = inject(CallReviewService);\n private readonly toast = inject(ToastService);\n private readonly route = inject(ActivatedRoute);\n private readonly config = inject(VOICE_RECEPTIONIST_CONFIG);\n\n /** Standalone dashboard only — hidden in EHR embed. */\n protected readonly showApiLogs = !!this.config.showModuleNav;\n\n /** Parent shell route — back targets sibling `history`, not `history/history`. */\n protected readonly historyListLink: string[] = ['history'];\n protected readonly historyListRelativeTo = this.route.parent;\n\n protected readonly detail = signal<HistoryDetail | null>(null);\n protected readonly loading = signal(true);\n protected readonly markingReview = signal(false);\n\n protected isNeedsReview = isNeedsReview;\n protected outcomeTone = outcomeBadgeTone;\n protected outcomeLabel = formatOutcomeLabel;\n\n protected isUnknownName(name: string | null | undefined): boolean {\n return isUnknownCaller(name);\n }\n\n ngOnInit(): void {\n const id = this.route.snapshot.paramMap.get('id') ?? '';\n this.api.getCallDetail(id).subscribe(({ data }) => {\n this.detail.set(data);\n this.loading.set(false);\n });\n }\n\n protected markReviewed(): void {\n const d = this.detail();\n if (!d || !isNeedsReview(d.review) || this.markingReview()) return;\n\n this.markingReview.set(true);\n const prev = d.review;\n this.detail.update((current) =>\n current ? { ...current, review: 'reviewed' } : current,\n );\n\n this.reviewService.markReviewed(d.id).subscribe({\n next: () => {\n this.markingReview.set(false);\n this.toast.show('Call marked as reviewed', 'success');\n },\n error: () => {\n this.markingReview.set(false);\n this.detail.update((current) =>\n current ? { ...current, review: prev } : current,\n );\n this.toast.show('Failed to mark call as reviewed', 'error');\n },\n });\n }\n\n protected refreshCallDetail(): void {\n const id = this.detail()?.id;\n if (!id) return;\n this.api.getCallDetail(id).subscribe(({ data }) => {\n if (data) this.detail.set(data);\n });\n }\n\n protected formatDuration(sec: number): string {\n const m = Math.floor(sec / 60);\n const s = sec % 60;\n return `${m}:${s.toString().padStart(2, '0')}`;\n }\n}\n","<div class=\"eds-page call-history-detail-page\">\n <a\n class=\"call-history-detail-page__back eds-body-sm mb-4 inline-flex items-center gap-1 no-underline\"\n [routerLink]=\"historyListLink\"\n [relativeTo]=\"historyListRelativeTo\"\n >\n <eds-icon name=\"arrow_back\" [size]=\"18\" /> Back to history\n </a>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading call detail…</p>\n } @else if (detail()) {\n <header class=\"mb-4 flex flex-wrap items-start justify-between gap-4\">\n <div>\n <h1 class=\"eds-h1\">{{ detail()!.name }}</h1>\n <p class=\"eds-body-sm\">{{ detail()!.phone }} · {{ detail()!.reason }}</p>\n </div>\n @if (isNeedsReview(detail()!.review)) {\n <eds-button\n variant=\"primary\"\n [disabled]=\"markingReview()\"\n (click)=\"markReviewed()\"\n >\n <eds-icon name=\"check_circle\" [size]=\"18\" />\n {{ markingReview() ? 'Saving…' : 'Mark as reviewed' }}\n </eds-button>\n }\n </header>\n\n <dl class=\"call-history-detail-page__meta mx-0 mt-0 mb-5 grid grid-cols-2 gap-4 sm:grid-cols-2\">\n <div><dt class=\"eds-label\">Outcome</dt><dd>\n <eds-badge [tone]=\"outcomeTone(detail()!.outcome)\">\n {{ outcomeLabel(detail()!.outcome) }}\n </eds-badge>\n </dd></div>\n <div><dt class=\"eds-label\">Duration</dt><dd>{{ detail()!.durationLabel ?? formatDuration(detail()!.duration) }}</dd></div>\n </dl>\n\n <eds-call-conversation-panel\n [callId]=\"detail()!.id\"\n [summary]=\"detail()!.summary\"\n [transcript]=\"detail()!.transcript\"\n [tools]=\"detail()!.tools\"\n [callDurationSec]=\"detail()!.duration\"\n [callerName]=\"detail()!.name\"\n [callerMatched]=\"!isUnknownName(detail()!.name)\"\n (summaryRegenerated)=\"refreshCallDetail()\"\n >\n <eds-call-tasks-panel\n summary-side\n [callId]=\"detail()!.id\"\n [compact]=\"true\"\n />\n </eds-call-conversation-panel>\n\n @if (showApiLogs) {\n <section class=\"mt-5\">\n <h2 class=\"eds-h2 mb-3\">API Logs</h2>\n <eds-api-logs-call-panel [callId]=\"detail()!.id\" />\n </section>\n }\n } @else {\n <p class=\"eds-body-sm\">Call not found.</p>\n }\n</div>\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, signal, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { ActivatedRoute, Router } from '@angular/router';
|
|
4
|
+
import { DatePipe } from '@angular/common';
|
|
5
|
+
import * as i1 from '@angular/forms';
|
|
6
|
+
import { FormsModule } from '@angular/forms';
|
|
7
|
+
import { A as AiReceptionistApiService, T as ToastService, j as DEFAULT_PAGE_SIZE, c as callerMetaLine, r as resultChipIcon, h as historyResultChips, b as historyResolution, a as historyResolutionLabel, k as totalPages, E as EdsButtonComponent, d as EdsIconComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-pS2jeB4x.mjs';
|
|
8
|
+
import { C as CallReviewService } from './edvak99-voice-receptionist-widget-call-review.service-WG30w6Jl.mjs';
|
|
9
|
+
import { i as isNeedsReview } from './edvak99-voice-receptionist-widget-call-review.util-Deh3jPgo.mjs';
|
|
10
|
+
import { E as EdsAvatarComponent } from './edvak99-voice-receptionist-widget-eds-avatar.component-BJs_TH8X.mjs';
|
|
11
|
+
import { E as EdsSearchFieldComponent } from './edvak99-voice-receptionist-widget-eds-search-field.component-OGRDvYBp.mjs';
|
|
12
|
+
|
|
13
|
+
class CallHistoryPageComponent {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.api = inject(AiReceptionistApiService);
|
|
16
|
+
this.reviewService = inject(CallReviewService);
|
|
17
|
+
this.toast = inject(ToastService);
|
|
18
|
+
this.route = inject(ActivatedRoute);
|
|
19
|
+
this.router = inject(Router);
|
|
20
|
+
this.pageSize = DEFAULT_PAGE_SIZE;
|
|
21
|
+
this.rows = signal([]);
|
|
22
|
+
this.total = signal(0);
|
|
23
|
+
this.page = signal(1);
|
|
24
|
+
this.loading = signal(true);
|
|
25
|
+
this.reviewingId = signal(null);
|
|
26
|
+
this.search = signal('');
|
|
27
|
+
this.direction = signal('');
|
|
28
|
+
this.outcome = signal('');
|
|
29
|
+
this.location = signal('');
|
|
30
|
+
this.reviewTab = signal('all');
|
|
31
|
+
this.reviewTabs = [
|
|
32
|
+
{ id: 'all', label: 'All' },
|
|
33
|
+
{ id: 'needs', label: 'Needs review' },
|
|
34
|
+
{ id: 'reviewed', label: 'Reviewed' },
|
|
35
|
+
];
|
|
36
|
+
this.isNeedsReview = isNeedsReview;
|
|
37
|
+
this.callerMetaLine = callerMetaLine;
|
|
38
|
+
this.resultChipIcon = resultChipIcon;
|
|
39
|
+
this.historyResultChips = historyResultChips;
|
|
40
|
+
this.historyResolution = historyResolution;
|
|
41
|
+
this.historyResolutionLabel = historyResolutionLabel;
|
|
42
|
+
}
|
|
43
|
+
ngOnInit() {
|
|
44
|
+
const reviewParam = this.route.snapshot.queryParamMap.get('review');
|
|
45
|
+
if (reviewParam === 'needs' || reviewParam === 'reviewed') {
|
|
46
|
+
this.reviewTab.set(reviewParam);
|
|
47
|
+
}
|
|
48
|
+
this.load();
|
|
49
|
+
}
|
|
50
|
+
load() {
|
|
51
|
+
this.loading.set(true);
|
|
52
|
+
const tab = this.reviewTab();
|
|
53
|
+
this.api
|
|
54
|
+
.getHistory({
|
|
55
|
+
q: this.search() || undefined,
|
|
56
|
+
dir: this.direction() || undefined,
|
|
57
|
+
outcome: this.outcome() || undefined,
|
|
58
|
+
location: this.location() || undefined,
|
|
59
|
+
review: tab === 'all' ? undefined : tab,
|
|
60
|
+
page: this.page(),
|
|
61
|
+
limit: DEFAULT_PAGE_SIZE,
|
|
62
|
+
})
|
|
63
|
+
.subscribe({
|
|
64
|
+
next: ({ items, total, page }) => {
|
|
65
|
+
this.rows.set(items);
|
|
66
|
+
this.total.set(total);
|
|
67
|
+
this.page.set(page);
|
|
68
|
+
this.loading.set(false);
|
|
69
|
+
},
|
|
70
|
+
error: () => this.loading.set(false),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
applyFilters() {
|
|
74
|
+
this.page.set(1);
|
|
75
|
+
this.load();
|
|
76
|
+
}
|
|
77
|
+
setReviewTab(tab) {
|
|
78
|
+
this.reviewTab.set(tab);
|
|
79
|
+
this.page.set(1);
|
|
80
|
+
void this.router.navigate([], {
|
|
81
|
+
relativeTo: this.route,
|
|
82
|
+
queryParams: tab === 'all' ? { review: null } : { review: tab },
|
|
83
|
+
queryParamsHandling: 'merge',
|
|
84
|
+
replaceUrl: true,
|
|
85
|
+
});
|
|
86
|
+
this.load();
|
|
87
|
+
}
|
|
88
|
+
tabBadgeCount(tab) {
|
|
89
|
+
if (tab !== 'needs' || this.reviewTab() !== 'needs')
|
|
90
|
+
return null;
|
|
91
|
+
const count = this.total();
|
|
92
|
+
return count > 0 ? count : null;
|
|
93
|
+
}
|
|
94
|
+
totalPages() {
|
|
95
|
+
return totalPages(this.total(), DEFAULT_PAGE_SIZE);
|
|
96
|
+
}
|
|
97
|
+
prevPage() {
|
|
98
|
+
if (this.page() <= 1)
|
|
99
|
+
return;
|
|
100
|
+
this.page.update((p) => p - 1);
|
|
101
|
+
this.load();
|
|
102
|
+
}
|
|
103
|
+
nextPage() {
|
|
104
|
+
if (this.page() >= this.totalPages())
|
|
105
|
+
return;
|
|
106
|
+
this.page.update((p) => p + 1);
|
|
107
|
+
this.load();
|
|
108
|
+
}
|
|
109
|
+
markReviewed(row, event) {
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
event.stopPropagation();
|
|
112
|
+
if (!isNeedsReview(row.review) || this.reviewingId())
|
|
113
|
+
return;
|
|
114
|
+
this.reviewingId.set(row.id);
|
|
115
|
+
this.rows.update((list) => list.map((r) => (r.id === row.id ? { ...r, review: 'reviewed' } : r)));
|
|
116
|
+
this.reviewService.markReviewed(row.id).subscribe({
|
|
117
|
+
next: () => {
|
|
118
|
+
this.reviewingId.set(null);
|
|
119
|
+
this.toast.show('Call marked as reviewed', 'success');
|
|
120
|
+
if (this.reviewTab() === 'needs')
|
|
121
|
+
this.load();
|
|
122
|
+
},
|
|
123
|
+
error: () => {
|
|
124
|
+
this.reviewingId.set(null);
|
|
125
|
+
this.rows.update((list) => list.map((r) => (r.id === row.id ? { ...r, review: 'needs' } : r)));
|
|
126
|
+
this.toast.show('Failed to mark call as reviewed', 'error');
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
formatDuration(sec) {
|
|
131
|
+
const m = Math.floor(sec / 60);
|
|
132
|
+
const s = sec % 60;
|
|
133
|
+
return `${m}:${s.toString().padStart(2, '0')}`;
|
|
134
|
+
}
|
|
135
|
+
openCallDetail(id) {
|
|
136
|
+
void this.router.navigate(['../history', id], { relativeTo: this.route });
|
|
137
|
+
}
|
|
138
|
+
avatarTone(row) {
|
|
139
|
+
if (!row.name?.trim() || /^unknown/i.test(row.name) || row.initials === '?')
|
|
140
|
+
return 'gray';
|
|
141
|
+
return 'brand';
|
|
142
|
+
}
|
|
143
|
+
resolutionOf(row) {
|
|
144
|
+
return historyResolution({ outcome: row.outcome, resolution: row.resolution, risk: row.risk });
|
|
145
|
+
}
|
|
146
|
+
exportCsv() {
|
|
147
|
+
const header = [
|
|
148
|
+
'id',
|
|
149
|
+
'name',
|
|
150
|
+
'phone',
|
|
151
|
+
'direction',
|
|
152
|
+
'goal',
|
|
153
|
+
'handledBy',
|
|
154
|
+
'outcome',
|
|
155
|
+
'results',
|
|
156
|
+
'duration',
|
|
157
|
+
'started',
|
|
158
|
+
'review',
|
|
159
|
+
];
|
|
160
|
+
const csvRows = this.rows().map((r) => [
|
|
161
|
+
r.id,
|
|
162
|
+
r.name,
|
|
163
|
+
r.phone,
|
|
164
|
+
r.direction,
|
|
165
|
+
r.reason,
|
|
166
|
+
r.handledBy ?? '',
|
|
167
|
+
r.outcome,
|
|
168
|
+
(r.results ?? []).join('; '),
|
|
169
|
+
String(r.duration),
|
|
170
|
+
String(r.started),
|
|
171
|
+
r.review,
|
|
172
|
+
]);
|
|
173
|
+
const csv = [header, ...csvRows]
|
|
174
|
+
.map((row) => row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(','))
|
|
175
|
+
.join('\n');
|
|
176
|
+
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
|
|
177
|
+
const url = URL.createObjectURL(blob);
|
|
178
|
+
const a = document.createElement('a');
|
|
179
|
+
a.href = url;
|
|
180
|
+
a.download = `call-history-page-${this.page()}.csv`;
|
|
181
|
+
a.click();
|
|
182
|
+
URL.revokeObjectURL(url);
|
|
183
|
+
}
|
|
184
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CallHistoryPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
185
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: CallHistoryPageComponent, isStandalone: true, selector: "eds-call-history-page", ngImport: i0, template: "<div class=\"eds-page call-history-page\">\n <header class=\"mb-5 flex items-start justify-between\">\n <div>\n <h1 class=\"eds-h1\">Call History</h1>\n <p class=\"eds-body-sm\">Search and review completed conversations</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 <div class=\"eds-card mb-4 flex flex-wrap items-center gap-3 p-4 shadow-sm\">\n <eds-search-field\n placeholder=\"Search name, phone, reason\u2026\"\n [value]=\"search()\"\n (valueChange)=\"search.set($event)\"\n (enter)=\"applyFilters()\"\n />\n <select\n class=\"rounded-md border border-zinc-200 bg-white px-3 py-2 text-sm\"\n [ngModel]=\"direction()\"\n (ngModelChange)=\"direction.set($event)\"\n autocomplete=\"off\"\n >\n <option value=\"\">All directions</option>\n <option value=\"inbound\">Inbound</option>\n <option value=\"outbound\">Outbound</option>\n </select>\n <eds-search-field\n type=\"text\"\n inputClass=\"rounded-md border border-zinc-200 bg-white px-3 py-2 text-sm\"\n placeholder=\"Outcome\"\n [value]=\"outcome()\"\n (valueChange)=\"outcome.set($event)\"\n (enter)=\"applyFilters()\"\n />\n <eds-search-field\n type=\"text\"\n inputClass=\"rounded-md border border-zinc-200 bg-white px-3 py-2 text-sm\"\n placeholder=\"Location\"\n [value]=\"location()\"\n (valueChange)=\"location.set($event)\"\n (enter)=\"applyFilters()\"\n />\n <eds-button variant=\"primary\" (click)=\"applyFilters()\">Apply</eds-button>\n </div>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading history\u2026</p>\n } @else {\n <div class=\"eds-card overflow-x-auto p-0 shadow-sm\">\n <table class=\"w-full min-w-5xl border-collapse\">\n <thead>\n <tr>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Time</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Patient/Caller</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\"></th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Goal Timeline</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Handled By</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Resolution</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Result</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Duration</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Reviewed</th>\n </tr>\n </thead>\n <tbody>\n @for (row of rows(); track row.id) {\n <tr\n class=\"call-history-page__row cursor-pointer select-none transition-colors hover:bg-zinc-50 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-blue-600\"\n [class.call-history-page__row--needs-review]=\"isNeedsReview(row.review)\"\n tabindex=\"0\"\n role=\"link\"\n [attr.aria-label]=\"'View call details for ' + row.name\"\n (click)=\"openCallDetail(row.id)\"\n (keyup.enter)=\"openCallDetail(row.id)\"\n >\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex flex-col gap-1 whitespace-nowrap\">\n @if (row.started) {\n <span class=\"text-sm font-semibold\">{{ row.started | date: 'shortTime' }}</span>\n <span class=\"text-xs text-zinc-500\">{{ row.started | date: 'MMM d, y' }}</span>\n } @else {\n <span class=\"text-sm font-semibold\">{{ row.whenLabel || '\u2014' }}</span>\n }\n </div>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex min-w-52 items-center gap-3\">\n <eds-avatar\n class=\"call-history-page__avatar\"\n [initials]=\"row.initials || '?'\"\n [tone]=\"avatarTone(row)\"\n />\n <div class=\"flex min-w-0 flex-col gap-1\">\n <span class=\"text-sm font-semibold text-zinc-900\">{{ row.name || 'Unknown Caller' }}</span>\n <span class=\"whitespace-nowrap text-xs text-zinc-500\">\n {{\n callerMetaLine({\n dob: row.dob,\n age: row.age,\n gender: row.gender,\n phone: row.phone,\n name: row.name,\n })\n }}\n </span>\n </div>\n </div>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <span\n class=\"inline-flex h-7 w-7 items-center justify-center rounded-lg\"\n [class.bg-blue-50]=\"row.direction === 'inbound'\"\n [class.text-blue-600]=\"row.direction === 'inbound'\"\n [class.bg-violet-50]=\"row.direction === 'outbound'\"\n [class.text-violet-600]=\"row.direction === 'outbound'\"\n [attr.title]=\"row.direction === 'inbound' ? 'Inbound' : 'Outbound'\"\n >\n <eds-icon\n [name]=\"row.direction === 'inbound' ? 'call_received' : 'call_made'\"\n [size]=\"16\"\n />\n </span>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <span class=\"text-sm text-zinc-700\">{{ row.reason || '\u2014' }}</span>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <span\n class=\"inline-flex items-center rounded-md px-2 py-1 text-xs font-medium\"\n [class.bg-violet-50]=\"(row.handledBy || 'ai') === 'ai'\"\n [class.text-violet-600]=\"(row.handledBy || 'ai') === 'ai'\"\n [class.bg-blue-50]=\"row.handledBy === 'staff'\"\n [class.text-blue-600]=\"row.handledBy === 'staff'\"\n >\n {{ row.handledBy === 'staff' ? 'STAFF' : 'AI' }}\n </span>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n @if (resolutionOf(row); as resolution) {\n <span\n class=\"inline-flex items-center whitespace-nowrap rounded-full px-3 py-1 text-xs font-medium\"\n [class.bg-emerald-50]=\"resolution === 'resolved'\"\n [class.text-emerald-700]=\"resolution === 'resolved'\"\n [class.bg-orange-50]=\"resolution === 'needs_attention'\"\n [class.text-orange-700]=\"resolution === 'needs_attention'\"\n >\n {{ historyResolutionLabel(resolution) }}\n </span>\n }\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex max-w-64 flex-wrap gap-2\">\n @for (chip of historyResultChips(row.results, row.outcome); track chip) {\n <span class=\"inline-flex items-center gap-1 whitespace-nowrap rounded-md bg-zinc-100 px-2 py-1 text-xs font-medium text-zinc-600\">\n <eds-icon [name]=\"resultChipIcon(chip)\" [size]=\"14\" />\n {{ chip }}\n </span>\n } @empty {\n <span class=\"text-sm text-zinc-500\">\u2014</span>\n }\n </div>\n </td>\n\n <td class=\"whitespace-nowrap border-b border-slate-100 px-4 py-4 align-middle text-sm tabular-nums text-zinc-700\">\n {{ row.durationLabel ?? formatDuration(row.duration) }}\n </td>\n\n <td class=\"w-14 border-b border-slate-100 px-4 py-4 text-center align-middle text-sm text-zinc-900\">\n @if (row.review === 'reviewed') {\n <span class=\"inline-flex text-green-600\" title=\"Reviewed\">\n <eds-icon name=\"check_circle\" [size]=\"20\" />\n </span>\n } @else {\n <button\n type=\"button\"\n class=\"cursor-pointer rounded-md border-none bg-transparent p-1 leading-none text-zinc-400 hover:bg-green-600/10 hover:text-green-600 disabled:cursor-wait disabled:opacity-50\"\n title=\"Mark as reviewed\"\n [disabled]=\"reviewingId() === row.id\"\n (click)=\"markReviewed(row, $event)\"\n >\n <eds-icon name=\"radio_button_unchecked\" [size]=\"20\" />\n </button>\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td colspan=\"9\" class=\"px-4 py-7 text-center text-sm text-zinc-500\">No calls match this filter.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n <footer class=\"mt-4 flex items-center justify-between border-t border-zinc-200 pt-3\">\n <span class=\"eds-body-sm\">\n Page {{ page() }} of {{ totalPages() }} \u00B7 {{ 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</div>\n", styles: [".call-history-page__row td{background:transparent;transition:background-color .12s ease;-webkit-user-select:none;user-select:none}.call-history-page__row:hover td{background:#fafafa}.call-history-page__row--needs-review td:first-child{box-shadow:inset 3px 0 #f59e0b}.call-history-page__avatar ::ng-deep .eds-avatar{width:36px;height:36px;border-radius:10px;font-size:12px}\n"], dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }, { 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.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: EdsAvatarComponent, selector: "eds-avatar", inputs: ["initials", "tone", "shape", "gradient"] }, { 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 }); }
|
|
186
|
+
}
|
|
187
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CallHistoryPageComponent, decorators: [{
|
|
188
|
+
type: Component,
|
|
189
|
+
args: [{ selector: 'eds-call-history-page', imports: [
|
|
190
|
+
DatePipe,
|
|
191
|
+
FormsModule,
|
|
192
|
+
EdsAvatarComponent,
|
|
193
|
+
EdsButtonComponent,
|
|
194
|
+
EdsIconComponent,
|
|
195
|
+
EdsSearchFieldComponent,
|
|
196
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"eds-page call-history-page\">\n <header class=\"mb-5 flex items-start justify-between\">\n <div>\n <h1 class=\"eds-h1\">Call History</h1>\n <p class=\"eds-body-sm\">Search and review completed conversations</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 <div class=\"eds-card mb-4 flex flex-wrap items-center gap-3 p-4 shadow-sm\">\n <eds-search-field\n placeholder=\"Search name, phone, reason\u2026\"\n [value]=\"search()\"\n (valueChange)=\"search.set($event)\"\n (enter)=\"applyFilters()\"\n />\n <select\n class=\"rounded-md border border-zinc-200 bg-white px-3 py-2 text-sm\"\n [ngModel]=\"direction()\"\n (ngModelChange)=\"direction.set($event)\"\n autocomplete=\"off\"\n >\n <option value=\"\">All directions</option>\n <option value=\"inbound\">Inbound</option>\n <option value=\"outbound\">Outbound</option>\n </select>\n <eds-search-field\n type=\"text\"\n inputClass=\"rounded-md border border-zinc-200 bg-white px-3 py-2 text-sm\"\n placeholder=\"Outcome\"\n [value]=\"outcome()\"\n (valueChange)=\"outcome.set($event)\"\n (enter)=\"applyFilters()\"\n />\n <eds-search-field\n type=\"text\"\n inputClass=\"rounded-md border border-zinc-200 bg-white px-3 py-2 text-sm\"\n placeholder=\"Location\"\n [value]=\"location()\"\n (valueChange)=\"location.set($event)\"\n (enter)=\"applyFilters()\"\n />\n <eds-button variant=\"primary\" (click)=\"applyFilters()\">Apply</eds-button>\n </div>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading history\u2026</p>\n } @else {\n <div class=\"eds-card overflow-x-auto p-0 shadow-sm\">\n <table class=\"w-full min-w-5xl border-collapse\">\n <thead>\n <tr>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Time</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Patient/Caller</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\"></th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Goal Timeline</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Handled By</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Resolution</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Result</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Duration</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Reviewed</th>\n </tr>\n </thead>\n <tbody>\n @for (row of rows(); track row.id) {\n <tr\n class=\"call-history-page__row cursor-pointer select-none transition-colors hover:bg-zinc-50 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-blue-600\"\n [class.call-history-page__row--needs-review]=\"isNeedsReview(row.review)\"\n tabindex=\"0\"\n role=\"link\"\n [attr.aria-label]=\"'View call details for ' + row.name\"\n (click)=\"openCallDetail(row.id)\"\n (keyup.enter)=\"openCallDetail(row.id)\"\n >\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex flex-col gap-1 whitespace-nowrap\">\n @if (row.started) {\n <span class=\"text-sm font-semibold\">{{ row.started | date: 'shortTime' }}</span>\n <span class=\"text-xs text-zinc-500\">{{ row.started | date: 'MMM d, y' }}</span>\n } @else {\n <span class=\"text-sm font-semibold\">{{ row.whenLabel || '\u2014' }}</span>\n }\n </div>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex min-w-52 items-center gap-3\">\n <eds-avatar\n class=\"call-history-page__avatar\"\n [initials]=\"row.initials || '?'\"\n [tone]=\"avatarTone(row)\"\n />\n <div class=\"flex min-w-0 flex-col gap-1\">\n <span class=\"text-sm font-semibold text-zinc-900\">{{ row.name || 'Unknown Caller' }}</span>\n <span class=\"whitespace-nowrap text-xs text-zinc-500\">\n {{\n callerMetaLine({\n dob: row.dob,\n age: row.age,\n gender: row.gender,\n phone: row.phone,\n name: row.name,\n })\n }}\n </span>\n </div>\n </div>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <span\n class=\"inline-flex h-7 w-7 items-center justify-center rounded-lg\"\n [class.bg-blue-50]=\"row.direction === 'inbound'\"\n [class.text-blue-600]=\"row.direction === 'inbound'\"\n [class.bg-violet-50]=\"row.direction === 'outbound'\"\n [class.text-violet-600]=\"row.direction === 'outbound'\"\n [attr.title]=\"row.direction === 'inbound' ? 'Inbound' : 'Outbound'\"\n >\n <eds-icon\n [name]=\"row.direction === 'inbound' ? 'call_received' : 'call_made'\"\n [size]=\"16\"\n />\n </span>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <span class=\"text-sm text-zinc-700\">{{ row.reason || '\u2014' }}</span>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <span\n class=\"inline-flex items-center rounded-md px-2 py-1 text-xs font-medium\"\n [class.bg-violet-50]=\"(row.handledBy || 'ai') === 'ai'\"\n [class.text-violet-600]=\"(row.handledBy || 'ai') === 'ai'\"\n [class.bg-blue-50]=\"row.handledBy === 'staff'\"\n [class.text-blue-600]=\"row.handledBy === 'staff'\"\n >\n {{ row.handledBy === 'staff' ? 'STAFF' : 'AI' }}\n </span>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n @if (resolutionOf(row); as resolution) {\n <span\n class=\"inline-flex items-center whitespace-nowrap rounded-full px-3 py-1 text-xs font-medium\"\n [class.bg-emerald-50]=\"resolution === 'resolved'\"\n [class.text-emerald-700]=\"resolution === 'resolved'\"\n [class.bg-orange-50]=\"resolution === 'needs_attention'\"\n [class.text-orange-700]=\"resolution === 'needs_attention'\"\n >\n {{ historyResolutionLabel(resolution) }}\n </span>\n }\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex max-w-64 flex-wrap gap-2\">\n @for (chip of historyResultChips(row.results, row.outcome); track chip) {\n <span class=\"inline-flex items-center gap-1 whitespace-nowrap rounded-md bg-zinc-100 px-2 py-1 text-xs font-medium text-zinc-600\">\n <eds-icon [name]=\"resultChipIcon(chip)\" [size]=\"14\" />\n {{ chip }}\n </span>\n } @empty {\n <span class=\"text-sm text-zinc-500\">\u2014</span>\n }\n </div>\n </td>\n\n <td class=\"whitespace-nowrap border-b border-slate-100 px-4 py-4 align-middle text-sm tabular-nums text-zinc-700\">\n {{ row.durationLabel ?? formatDuration(row.duration) }}\n </td>\n\n <td class=\"w-14 border-b border-slate-100 px-4 py-4 text-center align-middle text-sm text-zinc-900\">\n @if (row.review === 'reviewed') {\n <span class=\"inline-flex text-green-600\" title=\"Reviewed\">\n <eds-icon name=\"check_circle\" [size]=\"20\" />\n </span>\n } @else {\n <button\n type=\"button\"\n class=\"cursor-pointer rounded-md border-none bg-transparent p-1 leading-none text-zinc-400 hover:bg-green-600/10 hover:text-green-600 disabled:cursor-wait disabled:opacity-50\"\n title=\"Mark as reviewed\"\n [disabled]=\"reviewingId() === row.id\"\n (click)=\"markReviewed(row, $event)\"\n >\n <eds-icon name=\"radio_button_unchecked\" [size]=\"20\" />\n </button>\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td colspan=\"9\" class=\"px-4 py-7 text-center text-sm text-zinc-500\">No calls match this filter.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n <footer class=\"mt-4 flex items-center justify-between border-t border-zinc-200 pt-3\">\n <span class=\"eds-body-sm\">\n Page {{ page() }} of {{ totalPages() }} \u00B7 {{ 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</div>\n", styles: [".call-history-page__row td{background:transparent;transition:background-color .12s ease;-webkit-user-select:none;user-select:none}.call-history-page__row:hover td{background:#fafafa}.call-history-page__row--needs-review td:first-child{box-shadow:inset 3px 0 #f59e0b}.call-history-page__avatar ::ng-deep .eds-avatar{width:36px;height:36px;border-radius:10px;font-size:12px}\n"] }]
|
|
197
|
+
}] });
|
|
198
|
+
|
|
199
|
+
export { CallHistoryPageComponent };
|
|
200
|
+
//# sourceMappingURL=edvak99-voice-receptionist-widget-call-history-page.component-DoYJFpSa.mjs.map
|
package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DoYJFpSa.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-call-history-page.component-DoYJFpSa.mjs","sources":["../../../src/lib/pages/call-history-page/call-history-page.component.ts","../../../src/lib/pages/call-history-page/call-history-page.component.html"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { DatePipe } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\n\nimport { HistoryCall } from '../../models/ai-receptionist.types';\nimport { DEFAULT_PAGE_SIZE, totalPages as calcTotalPages } from '../../models/pagination';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport { CallReviewService } from '../../services/call-review.service';\nimport {\n callerMetaLine,\n historyResolution,\n historyResolutionLabel,\n historyResultChips,\n resultChipIcon,\n} from '../../utils/call-history-display.util';\nimport { isNeedsReview, ReviewFilterTab } from '../../utils/call-review.util';\nimport { EdsAvatarComponent } from '../../ui/eds-avatar.component';\nimport { EdsButtonComponent } from '../../ui/eds-button.component';\nimport { EdsIconComponent } from '../../ui/eds-icon.component';\nimport { EdsSearchFieldComponent } from '../../ui/eds-search-field.component';\nimport { ToastService } from '../../shared/toast.service';\n\n@Component({\n selector: 'eds-call-history-page',\n imports: [\n DatePipe,\n FormsModule,\n EdsAvatarComponent,\n EdsButtonComponent,\n EdsIconComponent,\n EdsSearchFieldComponent,\n ],\n templateUrl: './call-history-page.component.html',\n styleUrl: './call-history-page.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CallHistoryPageComponent implements OnInit {\n private readonly api = inject(AiReceptionistApiService);\n private readonly reviewService = inject(CallReviewService);\n private readonly toast = inject(ToastService);\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n\n protected readonly pageSize = DEFAULT_PAGE_SIZE;\n protected readonly rows = signal<HistoryCall[]>([]);\n protected readonly total = signal(0);\n protected readonly page = signal(1);\n protected readonly loading = signal(true);\n protected readonly reviewingId = signal<string | null>(null);\n protected readonly search = signal('');\n protected readonly direction = signal('');\n protected readonly outcome = signal('');\n protected readonly location = signal('');\n protected readonly reviewTab = signal<ReviewFilterTab>('all');\n\n protected readonly reviewTabs: { id: ReviewFilterTab; label: string }[] = [\n { id: 'all', label: 'All' },\n { id: 'needs', label: 'Needs review' },\n { id: 'reviewed', label: 'Reviewed' },\n ];\n\n protected isNeedsReview = isNeedsReview;\n protected callerMetaLine = callerMetaLine;\n protected resultChipIcon = resultChipIcon;\n protected historyResultChips = historyResultChips;\n protected historyResolution = historyResolution;\n protected historyResolutionLabel = historyResolutionLabel;\n\n ngOnInit(): void {\n const reviewParam = this.route.snapshot.queryParamMap.get('review');\n if (reviewParam === 'needs' || reviewParam === 'reviewed') {\n this.reviewTab.set(reviewParam);\n }\n this.load();\n }\n\n protected load(): void {\n this.loading.set(true);\n const tab = this.reviewTab();\n this.api\n .getHistory({\n q: this.search() || undefined,\n dir: this.direction() || undefined,\n outcome: this.outcome() || undefined,\n location: this.location() || undefined,\n review: tab === 'all' ? undefined : tab,\n page: this.page(),\n limit: DEFAULT_PAGE_SIZE,\n })\n .subscribe({\n next: ({ items, total, page }) => {\n this.rows.set(items);\n this.total.set(total);\n this.page.set(page);\n this.loading.set(false);\n },\n error: () => this.loading.set(false),\n });\n }\n\n protected applyFilters(): void {\n this.page.set(1);\n this.load();\n }\n\n protected setReviewTab(tab: ReviewFilterTab): void {\n this.reviewTab.set(tab);\n this.page.set(1);\n void this.router.navigate([], {\n relativeTo: this.route,\n queryParams: tab === 'all' ? { review: null } : { review: tab },\n queryParamsHandling: 'merge',\n replaceUrl: true,\n });\n this.load();\n }\n\n protected tabBadgeCount(tab: ReviewFilterTab): number | null {\n if (tab !== 'needs' || this.reviewTab() !== 'needs') return null;\n const count = this.total();\n return count > 0 ? count : null;\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 markReviewed(row: HistoryCall, event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n if (!isNeedsReview(row.review) || this.reviewingId()) return;\n\n this.reviewingId.set(row.id);\n this.rows.update((list) =>\n list.map((r) => (r.id === row.id ? { ...r, review: 'reviewed' as const } : r)),\n );\n\n this.reviewService.markReviewed(row.id).subscribe({\n next: () => {\n this.reviewingId.set(null);\n this.toast.show('Call marked as reviewed', 'success');\n if (this.reviewTab() === 'needs') this.load();\n },\n error: () => {\n this.reviewingId.set(null);\n this.rows.update((list) =>\n list.map((r) => (r.id === row.id ? { ...r, review: 'needs' as const } : r)),\n );\n this.toast.show('Failed to mark call as reviewed', 'error');\n },\n });\n }\n\n protected formatDuration(sec: number): string {\n const m = Math.floor(sec / 60);\n const s = sec % 60;\n return `${m}:${s.toString().padStart(2, '0')}`;\n }\n\n protected openCallDetail(id: string): void {\n void this.router.navigate(['../history', id], { relativeTo: this.route });\n }\n\n protected avatarTone(row: HistoryCall): 'gray' | 'brand' {\n if (!row.name?.trim() || /^unknown/i.test(row.name) || row.initials === '?') return 'gray';\n return 'brand';\n }\n\n protected resolutionOf(row: HistoryCall) {\n return historyResolution({ outcome: row.outcome, resolution: row.resolution, risk: row.risk });\n }\n\n protected exportCsv(): void {\n const header = [\n 'id',\n 'name',\n 'phone',\n 'direction',\n 'goal',\n 'handledBy',\n 'outcome',\n 'results',\n 'duration',\n 'started',\n 'review',\n ];\n const csvRows = this.rows().map((r) => [\n r.id,\n r.name,\n r.phone,\n r.direction,\n r.reason,\n r.handledBy ?? '',\n r.outcome,\n (r.results ?? []).join('; '),\n String(r.duration),\n String(r.started),\n r.review,\n ]);\n const csv = [header, ...csvRows]\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 = `call-history-page-${this.page()}.csv`;\n a.click();\n URL.revokeObjectURL(url);\n }\n}\n","<div class=\"eds-page call-history-page\">\n <header class=\"mb-5 flex items-start justify-between\">\n <div>\n <h1 class=\"eds-h1\">Call History</h1>\n <p class=\"eds-body-sm\">Search and review completed conversations</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 <div class=\"eds-card mb-4 flex flex-wrap items-center gap-3 p-4 shadow-sm\">\n <eds-search-field\n placeholder=\"Search name, phone, reason…\"\n [value]=\"search()\"\n (valueChange)=\"search.set($event)\"\n (enter)=\"applyFilters()\"\n />\n <select\n class=\"rounded-md border border-zinc-200 bg-white px-3 py-2 text-sm\"\n [ngModel]=\"direction()\"\n (ngModelChange)=\"direction.set($event)\"\n autocomplete=\"off\"\n >\n <option value=\"\">All directions</option>\n <option value=\"inbound\">Inbound</option>\n <option value=\"outbound\">Outbound</option>\n </select>\n <eds-search-field\n type=\"text\"\n inputClass=\"rounded-md border border-zinc-200 bg-white px-3 py-2 text-sm\"\n placeholder=\"Outcome\"\n [value]=\"outcome()\"\n (valueChange)=\"outcome.set($event)\"\n (enter)=\"applyFilters()\"\n />\n <eds-search-field\n type=\"text\"\n inputClass=\"rounded-md border border-zinc-200 bg-white px-3 py-2 text-sm\"\n placeholder=\"Location\"\n [value]=\"location()\"\n (valueChange)=\"location.set($event)\"\n (enter)=\"applyFilters()\"\n />\n <eds-button variant=\"primary\" (click)=\"applyFilters()\">Apply</eds-button>\n </div>\n\n @if (loading()) {\n <p class=\"eds-body-sm\">Loading history…</p>\n } @else {\n <div class=\"eds-card overflow-x-auto p-0 shadow-sm\">\n <table class=\"w-full min-w-5xl border-collapse\">\n <thead>\n <tr>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Time</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Patient/Caller</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\"></th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Goal Timeline</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Handled By</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Resolution</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Result</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Duration</th>\n <th class=\"whitespace-nowrap border-b border-zinc-200 bg-zinc-100 px-4 py-3 text-left text-sm font-semibold text-zinc-600\">Reviewed</th>\n </tr>\n </thead>\n <tbody>\n @for (row of rows(); track row.id) {\n <tr\n class=\"call-history-page__row cursor-pointer select-none transition-colors hover:bg-zinc-50 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-blue-600\"\n [class.call-history-page__row--needs-review]=\"isNeedsReview(row.review)\"\n tabindex=\"0\"\n role=\"link\"\n [attr.aria-label]=\"'View call details for ' + row.name\"\n (click)=\"openCallDetail(row.id)\"\n (keyup.enter)=\"openCallDetail(row.id)\"\n >\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex flex-col gap-1 whitespace-nowrap\">\n @if (row.started) {\n <span class=\"text-sm font-semibold\">{{ row.started | date: 'shortTime' }}</span>\n <span class=\"text-xs text-zinc-500\">{{ row.started | date: 'MMM d, y' }}</span>\n } @else {\n <span class=\"text-sm font-semibold\">{{ row.whenLabel || '—' }}</span>\n }\n </div>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex min-w-52 items-center gap-3\">\n <eds-avatar\n class=\"call-history-page__avatar\"\n [initials]=\"row.initials || '?'\"\n [tone]=\"avatarTone(row)\"\n />\n <div class=\"flex min-w-0 flex-col gap-1\">\n <span class=\"text-sm font-semibold text-zinc-900\">{{ row.name || 'Unknown Caller' }}</span>\n <span class=\"whitespace-nowrap text-xs text-zinc-500\">\n {{\n callerMetaLine({\n dob: row.dob,\n age: row.age,\n gender: row.gender,\n phone: row.phone,\n name: row.name,\n })\n }}\n </span>\n </div>\n </div>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <span\n class=\"inline-flex h-7 w-7 items-center justify-center rounded-lg\"\n [class.bg-blue-50]=\"row.direction === 'inbound'\"\n [class.text-blue-600]=\"row.direction === 'inbound'\"\n [class.bg-violet-50]=\"row.direction === 'outbound'\"\n [class.text-violet-600]=\"row.direction === 'outbound'\"\n [attr.title]=\"row.direction === 'inbound' ? 'Inbound' : 'Outbound'\"\n >\n <eds-icon\n [name]=\"row.direction === 'inbound' ? 'call_received' : 'call_made'\"\n [size]=\"16\"\n />\n </span>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <span class=\"text-sm text-zinc-700\">{{ row.reason || '—' }}</span>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <span\n class=\"inline-flex items-center rounded-md px-2 py-1 text-xs font-medium\"\n [class.bg-violet-50]=\"(row.handledBy || 'ai') === 'ai'\"\n [class.text-violet-600]=\"(row.handledBy || 'ai') === 'ai'\"\n [class.bg-blue-50]=\"row.handledBy === 'staff'\"\n [class.text-blue-600]=\"row.handledBy === 'staff'\"\n >\n {{ row.handledBy === 'staff' ? 'STAFF' : 'AI' }}\n </span>\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n @if (resolutionOf(row); as resolution) {\n <span\n class=\"inline-flex items-center whitespace-nowrap rounded-full px-3 py-1 text-xs font-medium\"\n [class.bg-emerald-50]=\"resolution === 'resolved'\"\n [class.text-emerald-700]=\"resolution === 'resolved'\"\n [class.bg-orange-50]=\"resolution === 'needs_attention'\"\n [class.text-orange-700]=\"resolution === 'needs_attention'\"\n >\n {{ historyResolutionLabel(resolution) }}\n </span>\n }\n </td>\n\n <td class=\"border-b border-slate-100 px-4 py-4 align-middle text-sm text-zinc-900\">\n <div class=\"flex max-w-64 flex-wrap gap-2\">\n @for (chip of historyResultChips(row.results, row.outcome); track chip) {\n <span class=\"inline-flex items-center gap-1 whitespace-nowrap rounded-md bg-zinc-100 px-2 py-1 text-xs font-medium text-zinc-600\">\n <eds-icon [name]=\"resultChipIcon(chip)\" [size]=\"14\" />\n {{ chip }}\n </span>\n } @empty {\n <span class=\"text-sm text-zinc-500\">—</span>\n }\n </div>\n </td>\n\n <td class=\"whitespace-nowrap border-b border-slate-100 px-4 py-4 align-middle text-sm tabular-nums text-zinc-700\">\n {{ row.durationLabel ?? formatDuration(row.duration) }}\n </td>\n\n <td class=\"w-14 border-b border-slate-100 px-4 py-4 text-center align-middle text-sm text-zinc-900\">\n @if (row.review === 'reviewed') {\n <span class=\"inline-flex text-green-600\" title=\"Reviewed\">\n <eds-icon name=\"check_circle\" [size]=\"20\" />\n </span>\n } @else {\n <button\n type=\"button\"\n class=\"cursor-pointer rounded-md border-none bg-transparent p-1 leading-none text-zinc-400 hover:bg-green-600/10 hover:text-green-600 disabled:cursor-wait disabled:opacity-50\"\n title=\"Mark as reviewed\"\n [disabled]=\"reviewingId() === row.id\"\n (click)=\"markReviewed(row, $event)\"\n >\n <eds-icon name=\"radio_button_unchecked\" [size]=\"20\" />\n </button>\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td colspan=\"9\" class=\"px-4 py-7 text-center text-sm text-zinc-500\">No calls match this filter.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n <footer class=\"mt-4 flex items-center justify-between border-t border-zinc-200 pt-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</div>\n"],"names":["calcTotalPages"],"mappings":"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"}
|
|
@@ -2,8 +2,7 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { input, output, viewChild, inject, DestroyRef, signal, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
3
|
import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
4
|
import { distinctUntilChanged, tap, switchMap, EMPTY, map, catchError } from 'rxjs';
|
|
5
|
-
import { A as AiReceptionistApiService } from './edvak99-voice-receptionist-widget-
|
|
6
|
-
import { EdsIconComponent } from './edvak99-voice-receptionist-widget.mjs';
|
|
5
|
+
import { A as AiReceptionistApiService, d as EdsIconComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-pS2jeB4x.mjs';
|
|
7
6
|
|
|
8
7
|
class CallRecordingPlayerComponent {
|
|
9
8
|
constructor() {
|
|
@@ -108,4 +107,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
|
|
|
108
107
|
}], ctorParameters: () => [] });
|
|
109
108
|
|
|
110
109
|
export { CallRecordingPlayerComponent as C };
|
|
111
|
-
//# sourceMappingURL=edvak99-voice-receptionist-widget-call-recording-player.component-
|
|
110
|
+
//# sourceMappingURL=edvak99-voice-receptionist-widget-call-recording-player.component-GVg1g-Vq.mjs.map
|
package/fesm2022/edvak99-voice-receptionist-widget-call-recording-player.component-GVg1g-Vq.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edvak99-voice-receptionist-widget-call-recording-player.component-GVg1g-Vq.mjs","sources":["../../../src/lib/components/call-recording-player/call-recording-player.component.ts","../../../src/lib/components/call-recording-player/call-recording-player.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n inject,\n input,\n output,\n signal,\n viewChild,\n} from '@angular/core';\nimport { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';\nimport { catchError, distinctUntilChanged, EMPTY, map, switchMap, tap } from 'rxjs';\n\nimport { CallRecordingStatus } from '../../models/ai-receptionist.types';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport { EdsIconComponent } from '../../ui/eds-icon.component';\n\ntype PlayerPhase = 'loading' | CallRecordingStatus | 'error';\n\n@Component({\n selector: 'eds-call-recording-player',\n imports: [EdsIconComponent],\n templateUrl: './call-recording-player.component.html',\n styleUrl: './call-recording-player.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class CallRecordingPlayerComponent {\n readonly callId = input.required<string>();\n readonly currentTimeChange = output<number>();\n readonly durationChange = output<number>();\n\n private readonly audioRef = viewChild<ElementRef<HTMLAudioElement>>('audioEl');\n\n private readonly api = inject(AiReceptionistApiService);\n private readonly destroyRef = inject(DestroyRef);\n\n protected readonly phase = signal<PlayerPhase>('loading');\n protected readonly blobUrl = signal<string | null>(null);\n protected readonly durationSeconds = signal<number | null>(null);\n protected readonly message = signal<string | null>(null);\n\n /** Tracked outside signals so cleanup never reads blobUrl (avoids effect/subscription loops). */\n private objectUrl: string | null = null;\n\n constructor() {\n toObservable(this.callId)\n .pipe(\n distinctUntilChanged(),\n tap(() => this.revokeObjectUrl()),\n switchMap((id) => {\n if (!id) {\n this.resetUi();\n return EMPTY;\n }\n this.phase.set('loading');\n this.message.set(null);\n return this.api.getCallRecording(id).pipe(\n switchMap((meta) => {\n if (meta.status !== 'ready' || !meta.url) {\n this.phase.set(meta.status);\n this.message.set(meta.message ?? null);\n this.durationSeconds.set(meta.durationSeconds ?? null);\n return EMPTY;\n }\n this.durationSeconds.set(meta.durationSeconds ?? null);\n return this.api.getCallRecordingBlob(meta.url).pipe(\n map((blob) => blob),\n catchError(() => {\n this.phase.set('error');\n this.message.set('Could not load recording audio.');\n return EMPTY;\n }),\n );\n }),\n catchError(() => {\n this.phase.set('unavailable');\n this.message.set('Could not load recording metadata.');\n return EMPTY;\n }),\n );\n }),\n takeUntilDestroyed(),\n )\n .subscribe((blob) => {\n this.revokeObjectUrl();\n this.objectUrl = URL.createObjectURL(blob);\n this.blobUrl.set(this.objectUrl);\n this.phase.set('ready');\n this.message.set(null);\n const dur = this.durationSeconds();\n if (dur != null && dur > 0) {\n this.durationChange.emit(dur);\n }\n });\n\n this.destroyRef.onDestroy(() => this.revokeObjectUrl());\n }\n\n protected formatDuration(seconds: number): string {\n const m = Math.floor(seconds / 60);\n const s = seconds % 60;\n return `${m}:${s.toString().padStart(2, '0')}`;\n }\n\n protected onAudioError(): void {\n this.phase.set('error');\n this.message.set('Playback error — try reloading the page.');\n }\n\n protected onTimeUpdate(event: Event): void {\n const el = event.target as HTMLAudioElement;\n this.currentTimeChange.emit(el.currentTime);\n }\n\n protected onLoadedMetadata(event: Event): void {\n const el = event.target as HTMLAudioElement;\n if (Number.isFinite(el.duration) && el.duration > 0) {\n this.durationChange.emit(el.duration);\n }\n }\n\n /** Seek playback — used by synced transcript lines. */\n seekTo(seconds: number): void {\n const el = this.audioRef()?.nativeElement;\n if (!el) return;\n el.currentTime = Math.max(0, seconds);\n void el.play().catch(() => undefined);\n this.currentTimeChange.emit(el.currentTime);\n }\n\n private revokeObjectUrl(): void {\n if (this.objectUrl) {\n URL.revokeObjectURL(this.objectUrl);\n this.objectUrl = null;\n }\n this.blobUrl.set(null);\n }\n\n private resetUi(): void {\n this.phase.set('loading');\n this.durationSeconds.set(null);\n this.message.set(null);\n }\n}\n","<section class=\"call-recording-player flex flex-col gap-3 rounded-[var(--eds-radius-md)] border p-4\" aria-labelledby=\"call-recording-title\">\n <header class=\"flex items-center gap-2\">\n <eds-icon name=\"graphic_eq\" [size]=\"20\" />\n <h3 id=\"call-recording-title\" class=\"eds-h3 m-0 flex-1\">Call recording</h3>\n @if (durationSeconds(); as dur) {\n <span class=\"eds-mono-sm call-recording-player__duration\">{{ formatDuration(dur) }}</span>\n }\n </header>\n\n @switch (phase()) {\n @case ('loading') {\n <p class=\"call-recording-player__status eds-body-sm m-0\">Loading recording…</p>\n }\n @case ('ready') {\n @if (blobUrl(); as src) {\n <audio\n #audioEl\n class=\"h-10 w-full\"\n controls\n [src]=\"src\"\n (error)=\"onAudioError()\"\n (timeupdate)=\"onTimeUpdate($event)\"\n (loadedmetadata)=\"onLoadedMetadata($event)\"\n >\n Your browser does not support audio playback.\n </audio>\n }\n }\n @case ('pending') {\n <p class=\"call-recording-player__status eds-body-sm m-0\">\n {{ message() ?? 'Recording is still processing…' }}\n </p>\n }\n @case ('unavailable') {\n <p class=\"call-recording-player__status eds-body-sm call-recording-player__status--muted m-0\">\n {{ message() ?? 'No recording available for this call.' }}\n </p>\n }\n @case ('failed') {\n <p class=\"call-recording-player__status eds-body-sm call-recording-player__status--error m-0\">\n {{ message() ?? 'Recording could not be retrieved.' }}\n </p>\n }\n @case ('error') {\n <p class=\"call-recording-player__status eds-body-sm call-recording-player__status--error m-0\">\n {{ message() ?? 'Playback error.' }}\n </p>\n }\n }\n</section>\n"],"names":[],"mappings":"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"}
|