@edvak99/voice-receptionist-widget 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/README.md +56 -43
  2. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-Cg15RZEY.mjs +113 -0
  3. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-Cg15RZEY.mjs.map +1 -0
  4. package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-DVQyKh_n.mjs +35 -0
  5. package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-DVQyKh_n.mjs.map +1 -0
  6. package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-CbMLabcp.mjs +316 -0
  7. package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-CbMLabcp.mjs.map +1 -0
  8. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DfX0pJIR.mjs +138 -0
  9. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DfX0pJIR.mjs.map +1 -0
  10. package/fesm2022/edvak99-voice-receptionist-widget-call-recording-player.component-BDJmnxUF.mjs +110 -0
  11. package/fesm2022/edvak99-voice-receptionist-widget-call-recording-player.component-BDJmnxUF.mjs.map +1 -0
  12. package/fesm2022/edvak99-voice-receptionist-widget-call-review.service-D6P-ojFa.mjs +29 -0
  13. package/fesm2022/edvak99-voice-receptionist-widget-call-review.service-D6P-ojFa.mjs.map +1 -0
  14. package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-DQLIw7xb.mjs +70 -0
  15. package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-DQLIw7xb.mjs.map +1 -0
  16. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-Yr8Fx4Tj.mjs +248 -0
  17. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-Yr8Fx4Tj.mjs.map +1 -0
  18. package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs +27 -0
  19. package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs.map +1 -0
  20. package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-KzX82V6L.mjs +136 -0
  21. package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-KzX82V6L.mjs.map +1 -0
  22. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DvJ2Ygc1.mjs +2653 -0
  23. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DvJ2Ygc1.mjs.map +1 -0
  24. package/fesm2022/edvak99-voice-receptionist-widget-knowledge-hub.page-DHFkyA6s.mjs +575 -0
  25. package/fesm2022/edvak99-voice-receptionist-widget-knowledge-hub.page-DHFkyA6s.mjs.map +1 -0
  26. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-list.service-D5jeLPo9.mjs +120 -0
  27. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-list.service-D5jeLPo9.mjs.map +1 -0
  28. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-Do_-sipd.mjs +115 -0
  29. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-Do_-sipd.mjs.map +1 -0
  30. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-pTwlznGI.mjs +574 -0
  31. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-pTwlznGI.mjs.map +1 -0
  32. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-BQECIuJu.mjs +54 -0
  33. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-BQECIuJu.mjs.map +1 -0
  34. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-CteOwr16.mjs +265 -0
  35. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-CteOwr16.mjs.map +1 -0
  36. package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-k-1Z4CXg.mjs +509 -0
  37. package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-k-1Z4CXg.mjs.map +1 -0
  38. package/fesm2022/edvak99-voice-receptionist-widget-task.util-DdtI0CR3.mjs +52 -0
  39. package/fesm2022/edvak99-voice-receptionist-widget-task.util-DdtI0CR3.mjs.map +1 -0
  40. package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-BgL50G-i.mjs +237 -0
  41. package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-BgL50G-i.mjs.map +1 -0
  42. package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-BOEnzklB.mjs +45 -0
  43. package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-BOEnzklB.mjs.map +1 -0
  44. package/fesm2022/edvak99-voice-receptionist-widget.mjs +1 -41
  45. package/fesm2022/edvak99-voice-receptionist-widget.mjs.map +1 -1
  46. package/lib/api-paths.d.ts +11 -0
  47. package/lib/components/call-conversation-panel/call-conversation-panel.component.d.ts +45 -0
  48. package/lib/components/call-recording-player/call-recording-player.component.d.ts +29 -0
  49. package/lib/components/call-tasks-panel/call-tasks-panel.component.d.ts +19 -0
  50. package/lib/components/live-transcript-panel/live-transcript-panel.component.d.ts +36 -0
  51. package/lib/components/outbound-dial-modal/outbound-dial-modal.component.d.ts +37 -0
  52. package/lib/components/outbound-purpose-form-modal/outbound-purpose-form-modal.component.d.ts +38 -0
  53. package/lib/components/task-detail-drawer/task-detail-drawer.component.d.ts +20 -0
  54. package/lib/config.d.ts +23 -0
  55. package/lib/extract-api-rows.d.ts +2 -0
  56. package/lib/knowledge/components/faq-empty-state/faq-empty-state.component.d.ts +22 -0
  57. package/lib/knowledge/components/faq-form/faq-form.component.d.ts +27 -0
  58. package/lib/knowledge/components/faq-search-tester/faq-search-tester.component.d.ts +17 -0
  59. package/lib/knowledge/mocks/practice-faq.seed.d.ts +2 -0
  60. package/lib/knowledge/models/practice-faq.model.d.ts +47 -0
  61. package/lib/knowledge/pages/knowledge-hub.page.d.ts +47 -0
  62. package/lib/knowledge/services/practice-faq-api.mock.d.ts +16 -0
  63. package/lib/knowledge/services/practice-faq-api.service.d.ts +22 -0
  64. package/lib/knowledge/utils/faq.util.d.ts +3 -0
  65. package/lib/layout/eds-app-shell.component.d.ts +5 -0
  66. package/lib/layout/eds-module-nav.component.d.ts +24 -0
  67. package/lib/layout/eds-topbar.component.d.ts +29 -0
  68. package/lib/mocks/api-log.seed.d.ts +5 -0
  69. package/lib/mocks/outbound-purpose.seed.d.ts +3 -0
  70. package/lib/mocks/seed-data.d.ts +38 -0
  71. package/lib/models/ai-receptionist.types.d.ts +257 -0
  72. package/lib/models/api-dto.types.d.ts +215 -0
  73. package/lib/models/api-log.types.d.ts +53 -0
  74. package/lib/models/live-transcript.types.d.ts +26 -0
  75. package/lib/models/outbound-purpose.types.d.ts +50 -0
  76. package/lib/pages/analytics-page/analytics-page.component.d.ts +44 -0
  77. package/lib/pages/audit-page/audit-page.component.d.ts +14 -0
  78. package/lib/pages/call-history-detail-page/call-history-detail-page.component.d.ts +25 -0
  79. package/lib/pages/call-history-page/call-history-page.component.d.ts +42 -0
  80. package/lib/pages/dashboard-page/dashboard-page.component.d.ts +60 -0
  81. package/lib/pages/live-calls-page/live-calls-page.component.d.ts +37 -0
  82. package/lib/pages/live-monitor-page/live-monitor-page.component.d.ts +43 -0
  83. package/lib/pages/outbound-purposes-page/outbound-purposes-page.component.d.ts +24 -0
  84. package/lib/pages/settings-page/settings-fields.config.d.ts +35 -0
  85. package/lib/pages/settings-page/settings-page.component.d.ts +50 -0
  86. package/lib/pages/settings-page/tts-voice-presets.d.ts +7 -0
  87. package/lib/pages/tasks-page/tasks-page.component.d.ts +35 -0
  88. package/lib/pages/wizard-page/wizard-page.component.d.ts +17 -0
  89. package/lib/pipes/eds-elapsed.pipe.d.ts +7 -0
  90. package/lib/provide-voice-receptionist.d.ts +6 -0
  91. package/lib/routes.d.ts +8 -0
  92. package/lib/services/agent-online.service.d.ts +29 -0
  93. package/lib/services/ai-receptionist-api.mappers.d.ts +45 -0
  94. package/lib/services/ai-receptionist-api.mock.d.ts +150 -0
  95. package/lib/services/ai-receptionist-api.service.d.ts +156 -0
  96. package/lib/services/call-review.service.d.ts +12 -0
  97. package/lib/services/dashboard-socket.service.d.ts +26 -0
  98. package/lib/services/live-calls-list.service.d.ts +29 -0
  99. package/lib/services/live-transcript.service.d.ts +39 -0
  100. package/lib/shared/copy-button.component.d.ts +14 -0
  101. package/lib/shared/modal.component.d.ts +20 -0
  102. package/lib/shared/toast-container.component.d.ts +7 -0
  103. package/lib/shared/toast.service.d.ts +16 -0
  104. package/lib/ui/eds-avatar.component.d.ts +8 -0
  105. package/lib/ui/eds-badge.component.d.ts +7 -0
  106. package/lib/ui/eds-button.component.d.ts +9 -0
  107. package/lib/ui/eds-icon.component.d.ts +10 -0
  108. package/lib/ui/eds-pulse-dot.component.d.ts +5 -0
  109. package/lib/ui/eds-waveform.component.d.ts +8 -0
  110. package/lib/utils/call-outcome.util.d.ts +8 -0
  111. package/lib/utils/call-review.util.d.ts +8 -0
  112. package/lib/utils/outbound-purpose.util.d.ts +14 -0
  113. package/lib/utils/task.util.d.ts +10 -0
  114. package/lib/utils/tool-labels.util.d.ts +14 -0
  115. package/lib/utils/transcript-timing.util.d.ts +17 -0
  116. package/lib/utils/welcome-message.util.d.ts +5 -0
  117. package/package.json +12 -5
  118. package/public-api.d.ts +6 -2
  119. package/styles/_index.scss +55 -0
  120. package/styles/chat-ui.scss +133 -0
  121. package/styles/eds.scss +328 -0
  122. package/lib/voice-receptionist-widget.component.d.ts +0 -5
  123. package/lib/voice-receptionist-widget.service.d.ts +0 -6
@@ -0,0 +1,138 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, signal, computed, 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, a as EdsBadgeComponent, E as EdsButtonComponent, b as EdsIconComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DvJ2Ygc1.mjs';
8
+ import { C as CallReviewService } from './edvak99-voice-receptionist-widget-call-review.service-D6P-ojFa.mjs';
9
+ import { m as matchesReviewFilter, i as isNeedsReview, r as reviewLabel, a as reviewBadgeTone, o as outcomeBadgeTone, f as formatOutcomeLabel } from './edvak99-voice-receptionist-widget-call-review.util-DQLIw7xb.mjs';
10
+ import { E as EdsAvatarComponent } from './edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs';
11
+
12
+ class CallHistoryPageComponent {
13
+ constructor() {
14
+ this.api = inject(AiReceptionistApiService);
15
+ this.reviewService = inject(CallReviewService);
16
+ this.toast = inject(ToastService);
17
+ this.route = inject(ActivatedRoute);
18
+ this.router = inject(Router);
19
+ this.allRows = signal([]);
20
+ this.loading = signal(true);
21
+ this.reviewingId = signal(null);
22
+ this.search = signal('');
23
+ this.direction = signal('');
24
+ this.outcome = signal('');
25
+ this.location = signal('');
26
+ this.reviewTab = signal('all');
27
+ this.reviewTabs = [
28
+ { id: 'all', label: 'All' },
29
+ { id: 'needs', label: 'Needs review' },
30
+ { id: 'reviewed', label: 'Reviewed' },
31
+ ];
32
+ this.rows = computed(() => {
33
+ const tab = this.reviewTab();
34
+ return this.allRows().filter((row) => matchesReviewFilter(row.review, tab));
35
+ });
36
+ this.needsReviewCount = computed(() => this.allRows().filter((r) => isNeedsReview(r.review)).length);
37
+ this.reviewLabel = reviewLabel;
38
+ this.reviewTone = reviewBadgeTone;
39
+ this.isNeedsReview = isNeedsReview;
40
+ this.outcomeTone = outcomeBadgeTone;
41
+ this.outcomeLabel = formatOutcomeLabel;
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
+ this.api
53
+ .getHistory({
54
+ q: this.search() || undefined,
55
+ dir: this.direction() || undefined,
56
+ outcome: this.outcome() || undefined,
57
+ location: this.location() || undefined,
58
+ })
59
+ .subscribe(({ data }) => {
60
+ this.allRows.set(data);
61
+ this.loading.set(false);
62
+ });
63
+ }
64
+ applyFilters() {
65
+ this.load();
66
+ }
67
+ setReviewTab(tab) {
68
+ this.reviewTab.set(tab);
69
+ void this.router.navigate([], {
70
+ relativeTo: this.route,
71
+ queryParams: tab === 'all' ? { review: null } : { review: tab },
72
+ queryParamsHandling: 'merge',
73
+ replaceUrl: true,
74
+ });
75
+ }
76
+ tabBadgeCount(tab) {
77
+ if (tab !== 'needs')
78
+ return null;
79
+ const count = this.needsReviewCount();
80
+ return count > 0 ? count : null;
81
+ }
82
+ markReviewed(row, event) {
83
+ event.preventDefault();
84
+ event.stopPropagation();
85
+ if (!isNeedsReview(row.review) || this.reviewingId())
86
+ return;
87
+ this.reviewingId.set(row.id);
88
+ this.allRows.update((list) => list.map((r) => (r.id === row.id ? { ...r, review: 'reviewed' } : r)));
89
+ this.reviewService.markReviewed(row.id).subscribe({
90
+ next: () => {
91
+ this.reviewingId.set(null);
92
+ this.toast.show('Call marked as reviewed', 'success');
93
+ },
94
+ error: () => {
95
+ this.reviewingId.set(null);
96
+ this.allRows.update((list) => list.map((r) => (r.id === row.id ? { ...r, review: 'needs' } : r)));
97
+ this.toast.show('Failed to mark call as reviewed', 'error');
98
+ },
99
+ });
100
+ }
101
+ formatDuration(sec) {
102
+ const m = Math.floor(sec / 60);
103
+ const s = sec % 60;
104
+ return `${m}:${s.toString().padStart(2, '0')}`;
105
+ }
106
+ openCallDetail(id) {
107
+ void this.router.navigate(['../history', id], { relativeTo: this.route });
108
+ }
109
+ exportCsv() {
110
+ const header = 'ID,Name,Phone,Direction,Outcome,Reason,Duration,Location,Review\n';
111
+ const body = this.rows()
112
+ .map((r) => `${r.id},${r.name},${r.phone},${r.direction},${r.outcome},${r.reason},${r.duration},${r.location},${reviewLabel(r.review)}`)
113
+ .join('\n');
114
+ const blob = new Blob([header + body], { type: 'text/csv' });
115
+ const url = URL.createObjectURL(blob);
116
+ const a = document.createElement('a');
117
+ a.href = url;
118
+ a.download = 'call-history.csv';
119
+ a.click();
120
+ URL.revokeObjectURL(url);
121
+ }
122
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CallHistoryPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
123
+ 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\n <header class=\"call-history-page__header\">\n\n <div>\n\n <h1 class=\"eds-h1\">Call history</h1>\n\n <p class=\"eds-body-sm\">Search and review completed conversations</p>\n\n </div>\n\n <eds-button variant=\"secondary\" (click)=\"exportCsv()\">\n\n <eds-icon name=\"download\" [size]=\"18\" /> Export CSV\n\n </eds-button>\n\n </header>\n\n\n\n <nav class=\"call-history-page__review-tabs\" aria-label=\"Review status\">\n\n @for (t of reviewTabs; track t.id) {\n\n <button\n\n type=\"button\"\n\n class=\"call-history-page__review-tab\"\n\n [class.call-history-page__review-tab--active]=\"reviewTab() === t.id\"\n\n (click)=\"setReviewTab(t.id)\"\n\n >\n\n {{ t.label }}\n\n @if (tabBadgeCount(t.id); as count) {\n\n <span class=\"call-history-page__tab-badge\">{{ count }}</span>\n\n }\n\n </button>\n\n }\n\n </nav>\n\n\n\n <div class=\"eds-card call-history-page__filters\">\n\n <input\n\n type=\"search\"\n\n class=\"call-history-page__search\"\n\n placeholder=\"Search name, phone, reason\u2026\"\n\n [ngModel]=\"search()\"\n\n (ngModelChange)=\"search.set($event)\"\n\n (keyup.enter)=\"applyFilters()\"\n\n />\n\n <select class=\"call-history-page__select\" [ngModel]=\"direction()\" (ngModelChange)=\"direction.set($event)\">\n\n <option value=\"\">All directions</option>\n\n <option value=\"inbound\">Inbound</option>\n\n <option value=\"outbound\">Outbound</option>\n\n </select>\n\n <input\n\n type=\"text\"\n\n class=\"call-history-page__select\"\n\n placeholder=\"Outcome\"\n\n [ngModel]=\"outcome()\"\n\n (ngModelChange)=\"outcome.set($event)\"\n\n />\n\n <input\n\n type=\"text\"\n\n class=\"call-history-page__select\"\n\n placeholder=\"Location\"\n\n [ngModel]=\"location()\"\n\n (ngModelChange)=\"location.set($event)\"\n\n />\n\n <eds-button variant=\"primary\" (click)=\"applyFilters()\">Apply</eds-button>\n\n </div>\n\n\n\n @if (loading()) {\n\n <p class=\"eds-body-sm\">Loading history\u2026</p>\n\n } @else {\n\n <div class=\"eds-card call-history-page__table-wrap\">\n\n <table class=\"eds-table\">\n\n <thead>\n\n <tr>\n\n <th>Caller</th>\n\n <th>Direction</th>\n\n <th>Reason</th>\n\n <th>Status</th>\n\n <th>Duration</th>\n\n <th>Date &amp; time</th>\n\n <th>Review</th>\n\n <th></th>\n\n </tr>\n\n </thead>\n\n <tbody>\n\n @for (row of rows(); track row.id) {\n\n <tr\n class=\"call-history-page__row\"\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>\n <div class=\"call-history-page__caller\">\n <eds-avatar [initials]=\"row.initials\" />\n <span>{{ row.name }}</span>\n </div>\n </td>\n\n <td>{{ row.direction }}</td>\n\n <td>{{ row.reason }}</td>\n\n <td>\n\n <eds-badge [tone]=\"outcomeTone(row.outcome)\">\n\n {{ outcomeLabel(row.outcome) }}\n\n </eds-badge>\n\n </td>\n\n <td class=\"eds-mono-sm\">{{ row.durationLabel ?? formatDuration(row.duration) }}</td>\n\n <td class=\"eds-mono-sm\">\n {{ row.started ? (row.started | date: 'medium') : (row.whenLabel ?? '\u2014') }}\n </td>\n\n <td>\n\n <eds-badge [tone]=\"reviewTone(row.review)\">\n\n {{ reviewLabel(row.review) }}\n\n </eds-badge>\n\n </td>\n\n <td class=\"call-history-page__actions\">\n\n @if (isNeedsReview(row.review)) {\n\n <button\n\n type=\"button\"\n\n class=\"call-history-page__mark-btn\"\n\n title=\"Mark as reviewed\"\n\n [disabled]=\"reviewingId() === row.id\"\n\n (click)=\"markReviewed(row, $event)\"\n\n >\n\n <eds-icon name=\"check_circle\" [size]=\"18\" />\n\n </button>\n\n }\n\n </td>\n\n </tr>\n\n } @empty {\n\n <tr>\n\n <td colspan=\"8\" class=\"eds-body-sm\">No calls match this filter.</td>\n\n </tr>\n\n }\n\n </tbody>\n\n </table>\n\n </div>\n\n }\n\n</div>\n\n\n", styles: [".call-history-page__header{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:20px}.call-history-page__review-tabs{display:flex;gap:4px;margin-bottom:12px;padding:4px;width:fit-content;background:var(--surface-l1);border-radius:var(--eds-radius-md)}.call-history-page__review-tab{border:none;background:transparent;padding:8px 14px;border-radius:var(--eds-radius-sm);font-size:13px;font-weight:500;cursor:pointer;color:var(--text-secondary);display:inline-flex;align-items:center;gap:6px}.call-history-page__review-tab--active{background:var(--surface-l0);color:var(--text-primary);box-shadow:0 1px 2px #0000000f}.call-history-page__tab-badge{min-width:18px;padding:0 6px;border-radius:999px;background:#f59e0b;color:#fff;font-size:11px;font-weight:700;line-height:18px;text-align:center}.call-history-page__row{cursor:pointer}.call-history-page__row:hover{background:var(--surface-l1)}.call-history-page__row:focus-visible{outline:2px solid var(--brand-500, #0d9488);outline-offset:-2px}.call-history-page__row--needs-review{border-left:3px solid #f59e0b}.call-history-page__actions{width:40px;text-align:center}.call-history-page__mark-btn{border:none;background:transparent;color:var(--green-600, #16a34a);cursor:pointer;padding:4px;border-radius:6px}.call-history-page__mark-btn:hover:not(:disabled){background:#16a34a1a}.call-history-page__mark-btn:disabled{opacity:.5;cursor:wait}.call-history-page__filters{display:flex;flex-wrap:wrap;gap:10px;padding:14px;margin-bottom:16px;align-items:center}.call-history-page__search{flex:1;min-width:200px;padding:8px 12px;border:1px solid var(--border-default);border-radius:var(--eds-radius-md);font-size:14px}.call-history-page__select{padding:8px 12px;border:1px solid var(--border-default);border-radius:var(--eds-radius-md);font-size:13px;background:var(--surface-l0)}.call-history-page__table-wrap{overflow-x:auto;padding:0}.call-history-page__caller{display:flex;align-items:center;gap:8px;font-weight:500}\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.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.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", "gradient"] }, { kind: "component", type: EdsBadgeComponent, selector: "eds-badge", inputs: ["tone"] }, { kind: "component", type: EdsButtonComponent, selector: "eds-button", inputs: ["variant", "type", "disabled"] }, { kind: "component", type: EdsIconComponent, selector: "eds-icon", inputs: ["name", "size", "fill", "weight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
124
+ }
125
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CallHistoryPageComponent, decorators: [{
126
+ type: Component,
127
+ args: [{ selector: 'eds-call-history-page', imports: [
128
+ DatePipe,
129
+ FormsModule,
130
+ EdsAvatarComponent,
131
+ EdsBadgeComponent,
132
+ EdsButtonComponent,
133
+ EdsIconComponent,
134
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"eds-page call-history-page\">\n\n <header class=\"call-history-page__header\">\n\n <div>\n\n <h1 class=\"eds-h1\">Call history</h1>\n\n <p class=\"eds-body-sm\">Search and review completed conversations</p>\n\n </div>\n\n <eds-button variant=\"secondary\" (click)=\"exportCsv()\">\n\n <eds-icon name=\"download\" [size]=\"18\" /> Export CSV\n\n </eds-button>\n\n </header>\n\n\n\n <nav class=\"call-history-page__review-tabs\" aria-label=\"Review status\">\n\n @for (t of reviewTabs; track t.id) {\n\n <button\n\n type=\"button\"\n\n class=\"call-history-page__review-tab\"\n\n [class.call-history-page__review-tab--active]=\"reviewTab() === t.id\"\n\n (click)=\"setReviewTab(t.id)\"\n\n >\n\n {{ t.label }}\n\n @if (tabBadgeCount(t.id); as count) {\n\n <span class=\"call-history-page__tab-badge\">{{ count }}</span>\n\n }\n\n </button>\n\n }\n\n </nav>\n\n\n\n <div class=\"eds-card call-history-page__filters\">\n\n <input\n\n type=\"search\"\n\n class=\"call-history-page__search\"\n\n placeholder=\"Search name, phone, reason\u2026\"\n\n [ngModel]=\"search()\"\n\n (ngModelChange)=\"search.set($event)\"\n\n (keyup.enter)=\"applyFilters()\"\n\n />\n\n <select class=\"call-history-page__select\" [ngModel]=\"direction()\" (ngModelChange)=\"direction.set($event)\">\n\n <option value=\"\">All directions</option>\n\n <option value=\"inbound\">Inbound</option>\n\n <option value=\"outbound\">Outbound</option>\n\n </select>\n\n <input\n\n type=\"text\"\n\n class=\"call-history-page__select\"\n\n placeholder=\"Outcome\"\n\n [ngModel]=\"outcome()\"\n\n (ngModelChange)=\"outcome.set($event)\"\n\n />\n\n <input\n\n type=\"text\"\n\n class=\"call-history-page__select\"\n\n placeholder=\"Location\"\n\n [ngModel]=\"location()\"\n\n (ngModelChange)=\"location.set($event)\"\n\n />\n\n <eds-button variant=\"primary\" (click)=\"applyFilters()\">Apply</eds-button>\n\n </div>\n\n\n\n @if (loading()) {\n\n <p class=\"eds-body-sm\">Loading history\u2026</p>\n\n } @else {\n\n <div class=\"eds-card call-history-page__table-wrap\">\n\n <table class=\"eds-table\">\n\n <thead>\n\n <tr>\n\n <th>Caller</th>\n\n <th>Direction</th>\n\n <th>Reason</th>\n\n <th>Status</th>\n\n <th>Duration</th>\n\n <th>Date &amp; time</th>\n\n <th>Review</th>\n\n <th></th>\n\n </tr>\n\n </thead>\n\n <tbody>\n\n @for (row of rows(); track row.id) {\n\n <tr\n class=\"call-history-page__row\"\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>\n <div class=\"call-history-page__caller\">\n <eds-avatar [initials]=\"row.initials\" />\n <span>{{ row.name }}</span>\n </div>\n </td>\n\n <td>{{ row.direction }}</td>\n\n <td>{{ row.reason }}</td>\n\n <td>\n\n <eds-badge [tone]=\"outcomeTone(row.outcome)\">\n\n {{ outcomeLabel(row.outcome) }}\n\n </eds-badge>\n\n </td>\n\n <td class=\"eds-mono-sm\">{{ row.durationLabel ?? formatDuration(row.duration) }}</td>\n\n <td class=\"eds-mono-sm\">\n {{ row.started ? (row.started | date: 'medium') : (row.whenLabel ?? '\u2014') }}\n </td>\n\n <td>\n\n <eds-badge [tone]=\"reviewTone(row.review)\">\n\n {{ reviewLabel(row.review) }}\n\n </eds-badge>\n\n </td>\n\n <td class=\"call-history-page__actions\">\n\n @if (isNeedsReview(row.review)) {\n\n <button\n\n type=\"button\"\n\n class=\"call-history-page__mark-btn\"\n\n title=\"Mark as reviewed\"\n\n [disabled]=\"reviewingId() === row.id\"\n\n (click)=\"markReviewed(row, $event)\"\n\n >\n\n <eds-icon name=\"check_circle\" [size]=\"18\" />\n\n </button>\n\n }\n\n </td>\n\n </tr>\n\n } @empty {\n\n <tr>\n\n <td colspan=\"8\" class=\"eds-body-sm\">No calls match this filter.</td>\n\n </tr>\n\n }\n\n </tbody>\n\n </table>\n\n </div>\n\n }\n\n</div>\n\n\n", styles: [".call-history-page__header{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:20px}.call-history-page__review-tabs{display:flex;gap:4px;margin-bottom:12px;padding:4px;width:fit-content;background:var(--surface-l1);border-radius:var(--eds-radius-md)}.call-history-page__review-tab{border:none;background:transparent;padding:8px 14px;border-radius:var(--eds-radius-sm);font-size:13px;font-weight:500;cursor:pointer;color:var(--text-secondary);display:inline-flex;align-items:center;gap:6px}.call-history-page__review-tab--active{background:var(--surface-l0);color:var(--text-primary);box-shadow:0 1px 2px #0000000f}.call-history-page__tab-badge{min-width:18px;padding:0 6px;border-radius:999px;background:#f59e0b;color:#fff;font-size:11px;font-weight:700;line-height:18px;text-align:center}.call-history-page__row{cursor:pointer}.call-history-page__row:hover{background:var(--surface-l1)}.call-history-page__row:focus-visible{outline:2px solid var(--brand-500, #0d9488);outline-offset:-2px}.call-history-page__row--needs-review{border-left:3px solid #f59e0b}.call-history-page__actions{width:40px;text-align:center}.call-history-page__mark-btn{border:none;background:transparent;color:var(--green-600, #16a34a);cursor:pointer;padding:4px;border-radius:6px}.call-history-page__mark-btn:hover:not(:disabled){background:#16a34a1a}.call-history-page__mark-btn:disabled{opacity:.5;cursor:wait}.call-history-page__filters{display:flex;flex-wrap:wrap;gap:10px;padding:14px;margin-bottom:16px;align-items:center}.call-history-page__search{flex:1;min-width:200px;padding:8px 12px;border:1px solid var(--border-default);border-radius:var(--eds-radius-md);font-size:14px}.call-history-page__select{padding:8px 12px;border:1px solid var(--border-default);border-radius:var(--eds-radius-md);font-size:13px;background:var(--surface-l0)}.call-history-page__table-wrap{overflow-x:auto;padding:0}.call-history-page__caller{display:flex;align-items:center;gap:8px;font-weight:500}\n"] }]
135
+ }] });
136
+
137
+ export { CallHistoryPageComponent };
138
+ //# sourceMappingURL=edvak99-voice-receptionist-widget-call-history-page.component-DfX0pJIR.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edvak99-voice-receptionist-widget-call-history-page.component-DfX0pJIR.mjs","sources":["../../../projects/voice-receptionist-widget/src/lib/pages/call-history-page/call-history-page.component.ts","../../../projects/voice-receptionist-widget/src/lib/pages/call-history-page/call-history-page.component.html"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, inject, OnInit, signal } from '@angular/core';\n\nimport { ActivatedRoute, Router } from '@angular/router';\n\nimport { DatePipe } from '@angular/common';\n\nimport { FormsModule } from '@angular/forms';\n\n\n\nimport { HistoryCall } from '../../models/ai-receptionist.types';\n\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\n\nimport { CallReviewService } from '../../services/call-review.service';\n\nimport { formatOutcomeLabel, outcomeBadgeTone } from '../../utils/call-outcome.util';\n\nimport {\n\n isNeedsReview,\n\n matchesReviewFilter,\n\n reviewBadgeTone,\n\n reviewLabel,\n\n ReviewFilterTab,\n\n} from '../../utils/call-review.util';\n\nimport { EdsAvatarComponent } from '../../ui/eds-avatar.component';\n\nimport { EdsBadgeComponent } from '../../ui/eds-badge.component';\n\nimport { EdsButtonComponent } from '../../ui/eds-button.component';\n\nimport { EdsIconComponent } from '../../ui/eds-icon.component';\n\nimport { ToastService } from '../../shared/toast.service';\n\n\n\n@Component({\n selector: 'eds-call-history-page',\n imports: [\n DatePipe,\n FormsModule,\n EdsAvatarComponent,\n EdsBadgeComponent,\n EdsButtonComponent,\n EdsIconComponent,\n ],\n templateUrl: './call-history-page.component.html',\n styleUrl: './call-history-page.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush\n})\n\nexport class CallHistoryPageComponent implements OnInit {\n\n private readonly api = inject(AiReceptionistApiService);\n\n private readonly reviewService = inject(CallReviewService);\n\n private readonly toast = inject(ToastService);\n\n private readonly route = inject(ActivatedRoute);\n\n private readonly router = inject(Router);\n\n\n\n protected readonly allRows = signal<HistoryCall[]>([]);\n\n protected readonly loading = signal(true);\n\n protected readonly reviewingId = signal<string | null>(null);\n\n protected readonly search = signal('');\n\n protected readonly direction = signal('');\n\n protected readonly outcome = signal('');\n\n protected readonly location = signal('');\n\n protected readonly reviewTab = signal<ReviewFilterTab>('all');\n\n\n\n protected readonly reviewTabs: { id: ReviewFilterTab; label: string }[] = [\n\n { id: 'all', label: 'All' },\n\n { id: 'needs', label: 'Needs review' },\n\n { id: 'reviewed', label: 'Reviewed' },\n\n ];\n\n\n\n protected readonly rows = computed(() => {\n\n const tab = this.reviewTab();\n\n return this.allRows().filter((row) => matchesReviewFilter(row.review, tab));\n\n });\n\n\n\n protected readonly needsReviewCount = computed(\n\n () => this.allRows().filter((r) => isNeedsReview(r.review)).length,\n\n );\n\n\n\n protected reviewLabel = reviewLabel;\n\n protected reviewTone = reviewBadgeTone;\n\n protected isNeedsReview = isNeedsReview;\n\n protected outcomeTone = outcomeBadgeTone;\n\n protected outcomeLabel = formatOutcomeLabel;\n\n\n\n ngOnInit(): void {\n\n const reviewParam = this.route.snapshot.queryParamMap.get('review');\n\n if (reviewParam === 'needs' || reviewParam === 'reviewed') {\n\n this.reviewTab.set(reviewParam);\n\n }\n\n this.load();\n\n }\n\n\n\n protected load(): void {\n\n this.loading.set(true);\n\n this.api\n\n .getHistory({\n\n q: this.search() || undefined,\n\n dir: this.direction() || undefined,\n\n outcome: this.outcome() || undefined,\n\n location: this.location() || undefined,\n\n })\n\n .subscribe(({ data }) => {\n\n this.allRows.set(data);\n\n this.loading.set(false);\n\n });\n\n }\n\n\n\n protected applyFilters(): void {\n\n this.load();\n\n }\n\n\n\n protected setReviewTab(tab: ReviewFilterTab): void {\n\n this.reviewTab.set(tab);\n\n void this.router.navigate([], {\n\n relativeTo: this.route,\n\n queryParams: tab === 'all' ? { review: null } : { review: tab },\n\n queryParamsHandling: 'merge',\n\n replaceUrl: true,\n\n });\n\n }\n\n\n\n protected tabBadgeCount(tab: ReviewFilterTab): number | null {\n\n if (tab !== 'needs') return null;\n\n const count = this.needsReviewCount();\n\n return count > 0 ? count : null;\n\n }\n\n\n\n protected markReviewed(row: HistoryCall, event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n\n if (!isNeedsReview(row.review) || this.reviewingId()) return;\n\n\n\n this.reviewingId.set(row.id);\n\n this.allRows.update((list) =>\n\n list.map((r) => (r.id === row.id ? { ...r, review: 'reviewed' as const } : r)),\n\n );\n\n\n\n this.reviewService.markReviewed(row.id).subscribe({\n\n next: () => {\n\n this.reviewingId.set(null);\n\n this.toast.show('Call marked as reviewed', 'success');\n\n },\n\n error: () => {\n\n this.reviewingId.set(null);\n\n this.allRows.update((list) =>\n\n list.map((r) => (r.id === row.id ? { ...r, review: 'needs' as const } : r)),\n\n );\n\n this.toast.show('Failed to mark call as reviewed', 'error');\n\n },\n\n });\n\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 exportCsv(): void {\n\n const header = 'ID,Name,Phone,Direction,Outcome,Reason,Duration,Location,Review\\n';\n\n const body = this.rows()\n\n .map(\n\n (r) =>\n\n `${r.id},${r.name},${r.phone},${r.direction},${r.outcome},${r.reason},${r.duration},${r.location},${reviewLabel(r.review)}`,\n\n )\n\n .join('\\n');\n\n const blob = new Blob([header + body], { type: 'text/csv' });\n\n const url = URL.createObjectURL(blob);\n\n const a = document.createElement('a');\n\n a.href = url;\n\n a.download = 'call-history.csv';\n\n a.click();\n\n URL.revokeObjectURL(url);\n\n }\n\n}\n\n\n","<div class=\"eds-page call-history-page\">\n\n <header class=\"call-history-page__header\">\n\n <div>\n\n <h1 class=\"eds-h1\">Call history</h1>\n\n <p class=\"eds-body-sm\">Search and review completed conversations</p>\n\n </div>\n\n <eds-button variant=\"secondary\" (click)=\"exportCsv()\">\n\n <eds-icon name=\"download\" [size]=\"18\" /> Export CSV\n\n </eds-button>\n\n </header>\n\n\n\n <nav class=\"call-history-page__review-tabs\" aria-label=\"Review status\">\n\n @for (t of reviewTabs; track t.id) {\n\n <button\n\n type=\"button\"\n\n class=\"call-history-page__review-tab\"\n\n [class.call-history-page__review-tab--active]=\"reviewTab() === t.id\"\n\n (click)=\"setReviewTab(t.id)\"\n\n >\n\n {{ t.label }}\n\n @if (tabBadgeCount(t.id); as count) {\n\n <span class=\"call-history-page__tab-badge\">{{ count }}</span>\n\n }\n\n </button>\n\n }\n\n </nav>\n\n\n\n <div class=\"eds-card call-history-page__filters\">\n\n <input\n\n type=\"search\"\n\n class=\"call-history-page__search\"\n\n placeholder=\"Search name, phone, reason…\"\n\n [ngModel]=\"search()\"\n\n (ngModelChange)=\"search.set($event)\"\n\n (keyup.enter)=\"applyFilters()\"\n\n />\n\n <select class=\"call-history-page__select\" [ngModel]=\"direction()\" (ngModelChange)=\"direction.set($event)\">\n\n <option value=\"\">All directions</option>\n\n <option value=\"inbound\">Inbound</option>\n\n <option value=\"outbound\">Outbound</option>\n\n </select>\n\n <input\n\n type=\"text\"\n\n class=\"call-history-page__select\"\n\n placeholder=\"Outcome\"\n\n [ngModel]=\"outcome()\"\n\n (ngModelChange)=\"outcome.set($event)\"\n\n />\n\n <input\n\n type=\"text\"\n\n class=\"call-history-page__select\"\n\n placeholder=\"Location\"\n\n [ngModel]=\"location()\"\n\n (ngModelChange)=\"location.set($event)\"\n\n />\n\n <eds-button variant=\"primary\" (click)=\"applyFilters()\">Apply</eds-button>\n\n </div>\n\n\n\n @if (loading()) {\n\n <p class=\"eds-body-sm\">Loading history…</p>\n\n } @else {\n\n <div class=\"eds-card call-history-page__table-wrap\">\n\n <table class=\"eds-table\">\n\n <thead>\n\n <tr>\n\n <th>Caller</th>\n\n <th>Direction</th>\n\n <th>Reason</th>\n\n <th>Status</th>\n\n <th>Duration</th>\n\n <th>Date &amp; time</th>\n\n <th>Review</th>\n\n <th></th>\n\n </tr>\n\n </thead>\n\n <tbody>\n\n @for (row of rows(); track row.id) {\n\n <tr\n class=\"call-history-page__row\"\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>\n <div class=\"call-history-page__caller\">\n <eds-avatar [initials]=\"row.initials\" />\n <span>{{ row.name }}</span>\n </div>\n </td>\n\n <td>{{ row.direction }}</td>\n\n <td>{{ row.reason }}</td>\n\n <td>\n\n <eds-badge [tone]=\"outcomeTone(row.outcome)\">\n\n {{ outcomeLabel(row.outcome) }}\n\n </eds-badge>\n\n </td>\n\n <td class=\"eds-mono-sm\">{{ row.durationLabel ?? formatDuration(row.duration) }}</td>\n\n <td class=\"eds-mono-sm\">\n {{ row.started ? (row.started | date: 'medium') : (row.whenLabel ?? '—') }}\n </td>\n\n <td>\n\n <eds-badge [tone]=\"reviewTone(row.review)\">\n\n {{ reviewLabel(row.review) }}\n\n </eds-badge>\n\n </td>\n\n <td class=\"call-history-page__actions\">\n\n @if (isNeedsReview(row.review)) {\n\n <button\n\n type=\"button\"\n\n class=\"call-history-page__mark-btn\"\n\n title=\"Mark as reviewed\"\n\n [disabled]=\"reviewingId() === row.id\"\n\n (click)=\"markReviewed(row, $event)\"\n\n >\n\n <eds-icon name=\"check_circle\" [size]=\"18\" />\n\n </button>\n\n }\n\n </td>\n\n </tr>\n\n } @empty {\n\n <tr>\n\n <td colspan=\"8\" class=\"eds-body-sm\">No calls match this filter.</td>\n\n </tr>\n\n }\n\n </tbody>\n\n </table>\n\n </div>\n\n }\n\n</div>\n\n\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, output, viewChild, inject, DestroyRef, signal, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import { distinctUntilChanged, tap, switchMap, EMPTY, map, catchError } from 'rxjs';
5
+ import { A as AiReceptionistApiService, b as EdsIconComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DvJ2Ygc1.mjs';
6
+
7
+ class CallRecordingPlayerComponent {
8
+ constructor() {
9
+ this.callId = input.required();
10
+ this.currentTimeChange = output();
11
+ this.durationChange = output();
12
+ this.audioRef = viewChild('audioEl');
13
+ this.api = inject(AiReceptionistApiService);
14
+ this.destroyRef = inject(DestroyRef);
15
+ this.phase = signal('loading');
16
+ this.blobUrl = signal(null);
17
+ this.durationSeconds = signal(null);
18
+ this.message = signal(null);
19
+ /** Tracked outside signals so cleanup never reads blobUrl (avoids effect/subscription loops). */
20
+ this.objectUrl = null;
21
+ toObservable(this.callId)
22
+ .pipe(distinctUntilChanged(), tap(() => this.revokeObjectUrl()), switchMap((id) => {
23
+ if (!id) {
24
+ this.resetUi();
25
+ return EMPTY;
26
+ }
27
+ this.phase.set('loading');
28
+ this.message.set(null);
29
+ return this.api.getCallRecording(id).pipe(switchMap((meta) => {
30
+ if (meta.status !== 'ready' || !meta.url) {
31
+ this.phase.set(meta.status);
32
+ this.message.set(meta.message ?? null);
33
+ this.durationSeconds.set(meta.durationSeconds ?? null);
34
+ return EMPTY;
35
+ }
36
+ this.durationSeconds.set(meta.durationSeconds ?? null);
37
+ return this.api.getCallRecordingBlob(meta.url).pipe(map((blob) => blob), catchError(() => {
38
+ this.phase.set('error');
39
+ this.message.set('Could not load recording audio.');
40
+ return EMPTY;
41
+ }));
42
+ }), catchError(() => {
43
+ this.phase.set('unavailable');
44
+ this.message.set('Could not load recording metadata.');
45
+ return EMPTY;
46
+ }));
47
+ }), takeUntilDestroyed())
48
+ .subscribe((blob) => {
49
+ this.revokeObjectUrl();
50
+ this.objectUrl = URL.createObjectURL(blob);
51
+ this.blobUrl.set(this.objectUrl);
52
+ this.phase.set('ready');
53
+ this.message.set(null);
54
+ const dur = this.durationSeconds();
55
+ if (dur != null && dur > 0) {
56
+ this.durationChange.emit(dur);
57
+ }
58
+ });
59
+ this.destroyRef.onDestroy(() => this.revokeObjectUrl());
60
+ }
61
+ formatDuration(seconds) {
62
+ const m = Math.floor(seconds / 60);
63
+ const s = seconds % 60;
64
+ return `${m}:${s.toString().padStart(2, '0')}`;
65
+ }
66
+ onAudioError() {
67
+ this.phase.set('error');
68
+ this.message.set('Playback error — try reloading the page.');
69
+ }
70
+ onTimeUpdate(event) {
71
+ const el = event.target;
72
+ this.currentTimeChange.emit(el.currentTime);
73
+ }
74
+ onLoadedMetadata(event) {
75
+ const el = event.target;
76
+ if (Number.isFinite(el.duration) && el.duration > 0) {
77
+ this.durationChange.emit(el.duration);
78
+ }
79
+ }
80
+ /** Seek playback — used by synced transcript lines. */
81
+ seekTo(seconds) {
82
+ const el = this.audioRef()?.nativeElement;
83
+ if (!el)
84
+ return;
85
+ el.currentTime = Math.max(0, seconds);
86
+ void el.play().catch(() => undefined);
87
+ this.currentTimeChange.emit(el.currentTime);
88
+ }
89
+ revokeObjectUrl() {
90
+ if (this.objectUrl) {
91
+ URL.revokeObjectURL(this.objectUrl);
92
+ this.objectUrl = null;
93
+ }
94
+ this.blobUrl.set(null);
95
+ }
96
+ resetUi() {
97
+ this.phase.set('loading');
98
+ this.durationSeconds.set(null);
99
+ this.message.set(null);
100
+ }
101
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CallRecordingPlayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
102
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: CallRecordingPlayerComponent, isStandalone: true, selector: "eds-call-recording-player", inputs: { callId: { classPropertyName: "callId", publicName: "callId", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { currentTimeChange: "currentTimeChange", durationChange: "durationChange" }, viewQueries: [{ propertyName: "audioRef", first: true, predicate: ["audioEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<section class=\"call-recording-player\" aria-labelledby=\"call-recording-title\">\n <header class=\"call-recording-player__header\">\n <eds-icon name=\"graphic_eq\" [size]=\"20\" />\n <h3 id=\"call-recording-title\" class=\"eds-h3\">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\">Loading recording\u2026</p>\n }\n @case ('ready') {\n @if (blobUrl(); as src) {\n <audio\n #audioEl\n class=\"call-recording-player__audio\"\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\">\n {{ message() ?? 'Recording is still processing\u2026' }}\n </p>\n }\n @case ('unavailable') {\n <p class=\"call-recording-player__status eds-body-sm call-recording-player__status--muted\">\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\">\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\">\n {{ message() ?? 'Playback error.' }}\n </p>\n }\n }\n</section>\n", styles: [".call-recording-player{display:flex;flex-direction:column;gap:12px;padding:16px;border-radius:var(--eds-radius-md);background:var(--surface-l1);border:1px solid var(--border-default)}.call-recording-player__header{display:flex;align-items:center;gap:8px}.call-recording-player__header .eds-h3{margin:0;flex:1}.call-recording-player__duration{color:var(--text-secondary)}.call-recording-player__status{margin:0;color:var(--text-secondary)}.call-recording-player__status--muted{color:var(--text-secondary)}.call-recording-player__status--error{color:var(--red-500, #dc2626)}.call-recording-player__audio{width:100%;height:40px}\n"], dependencies: [{ kind: "component", type: EdsIconComponent, selector: "eds-icon", inputs: ["name", "size", "fill", "weight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
103
+ }
104
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CallRecordingPlayerComponent, decorators: [{
105
+ type: Component,
106
+ args: [{ selector: 'eds-call-recording-player', imports: [EdsIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"call-recording-player\" aria-labelledby=\"call-recording-title\">\n <header class=\"call-recording-player__header\">\n <eds-icon name=\"graphic_eq\" [size]=\"20\" />\n <h3 id=\"call-recording-title\" class=\"eds-h3\">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\">Loading recording\u2026</p>\n }\n @case ('ready') {\n @if (blobUrl(); as src) {\n <audio\n #audioEl\n class=\"call-recording-player__audio\"\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\">\n {{ message() ?? 'Recording is still processing\u2026' }}\n </p>\n }\n @case ('unavailable') {\n <p class=\"call-recording-player__status eds-body-sm call-recording-player__status--muted\">\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\">\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\">\n {{ message() ?? 'Playback error.' }}\n </p>\n }\n }\n</section>\n", styles: [".call-recording-player{display:flex;flex-direction:column;gap:12px;padding:16px;border-radius:var(--eds-radius-md);background:var(--surface-l1);border:1px solid var(--border-default)}.call-recording-player__header{display:flex;align-items:center;gap:8px}.call-recording-player__header .eds-h3{margin:0;flex:1}.call-recording-player__duration{color:var(--text-secondary)}.call-recording-player__status{margin:0;color:var(--text-secondary)}.call-recording-player__status--muted{color:var(--text-secondary)}.call-recording-player__status--error{color:var(--red-500, #dc2626)}.call-recording-player__audio{width:100%;height:40px}\n"] }]
107
+ }], ctorParameters: () => [] });
108
+
109
+ export { CallRecordingPlayerComponent as C };
110
+ //# sourceMappingURL=edvak99-voice-receptionist-widget-call-recording-player.component-BDJmnxUF.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edvak99-voice-receptionist-widget-call-recording-player.component-BDJmnxUF.mjs","sources":["../../../projects/voice-receptionist-widget/src/lib/components/call-recording-player/call-recording-player.component.ts","../../../projects/voice-receptionist-widget/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\" aria-labelledby=\"call-recording-title\">\n <header class=\"call-recording-player__header\">\n <eds-icon name=\"graphic_eq\" [size]=\"20\" />\n <h3 id=\"call-recording-title\" class=\"eds-h3\">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\">Loading recording…</p>\n }\n @case ('ready') {\n @if (blobUrl(); as src) {\n <audio\n #audioEl\n class=\"call-recording-player__audio\"\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\">\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\">\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\">\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\">\n {{ message() ?? 'Playback error.' }}\n </p>\n }\n }\n</section>\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, signal, Injectable } from '@angular/core';
3
+ import { tap, map } from 'rxjs';
4
+ import { A as AiReceptionistApiService } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DvJ2Ygc1.mjs';
5
+
6
+ class CallReviewService {
7
+ constructor() {
8
+ this.api = inject(AiReceptionistApiService);
9
+ /** Cached from dashboard summary; decremented optimistically on review. */
10
+ this.needsReviewCount = signal(null);
11
+ }
12
+ setNeedsReviewCount(count) {
13
+ this.needsReviewCount.set(count);
14
+ }
15
+ markReviewed(callId) {
16
+ return this.api.postReview(callId).pipe(tap(() => {
17
+ this.needsReviewCount.update((c) => (c != null && c > 0 ? c - 1 : c));
18
+ }), map(() => 'reviewed'));
19
+ }
20
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CallReviewService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
21
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CallReviewService, providedIn: 'root' }); }
22
+ }
23
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CallReviewService, decorators: [{
24
+ type: Injectable,
25
+ args: [{ providedIn: 'root' }]
26
+ }] });
27
+
28
+ export { CallReviewService as C };
29
+ //# sourceMappingURL=edvak99-voice-receptionist-widget-call-review.service-D6P-ojFa.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edvak99-voice-receptionist-widget-call-review.service-D6P-ojFa.mjs","sources":["../../../projects/voice-receptionist-widget/src/lib/services/call-review.service.ts"],"sourcesContent":["import { Injectable, inject, signal } from '@angular/core';\nimport { Observable, map, tap } from 'rxjs';\n\nimport { CallReviewStatus } from '../utils/call-review.util';\nimport { AiReceptionistApiService } from './ai-receptionist-api.service';\n\n@Injectable({ providedIn: 'root' })\nexport class CallReviewService {\n private readonly api = inject(AiReceptionistApiService);\n\n /** Cached from dashboard summary; decremented optimistically on review. */\n readonly needsReviewCount = signal<number | null>(null);\n\n setNeedsReviewCount(count: number): void {\n this.needsReviewCount.set(count);\n }\n\n markReviewed(callId: string): Observable<CallReviewStatus> {\n return this.api.postReview(callId).pipe(\n tap(() => {\n this.needsReviewCount.update((c) => (c != null && c > 0 ? c - 1 : c));\n }),\n map(() => 'reviewed'),\n );\n }\n}\n"],"names":[],"mappings":";;;;;MAOa,iBAAiB,CAAA;AAD9B,IAAA,WAAA,GAAA;AAEmB,QAAA,IAAA,CAAA,GAAG,GAAG,MAAM,CAAC,wBAAwB,CAAC;;AAG9C,QAAA,IAAA,CAAA,gBAAgB,GAAG,MAAM,CAAgB,IAAI,CAAC;AAcxD,IAAA;AAZC,IAAA,mBAAmB,CAAC,KAAa,EAAA;AAC/B,QAAA,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC;IAClC;AAEA,IAAA,YAAY,CAAC,MAAc,EAAA;AACzB,QAAA,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CACrC,GAAG,CAAC,MAAK;AACP,YAAA,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QACvE,CAAC,CAAC,EACF,GAAG,CAAC,MAAM,UAAU,CAAC,CACtB;IACH;+GAjBW,iBAAiB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA,CAAA;AAAjB,IAAA,SAAA,IAAA,CAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,iBAAiB,cADJ,MAAM,EAAA,CAAA,CAAA;;4FACnB,iBAAiB,EAAA,UAAA,EAAA,CAAA;kBAD7B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;;;;"}
@@ -0,0 +1,70 @@
1
+ /** Known history outcomes from GET /calls/history (HistoryCallDto.outcome). */
2
+ const HISTORY_OUTCOMES = [
3
+ 'completed',
4
+ 'failed',
5
+ 'transferred',
6
+ 'escalated',
7
+ ];
8
+ function normalizeHistoryOutcome(outcome) {
9
+ const key = outcome.trim().toLowerCase();
10
+ if (HISTORY_OUTCOMES.includes(key)) {
11
+ return key;
12
+ }
13
+ return 'custom';
14
+ }
15
+ /** Badge tone for history table / detail — separate from lifecycle or live UI status. */
16
+ function outcomeBadgeTone(outcome) {
17
+ switch (normalizeHistoryOutcome(outcome)) {
18
+ case 'completed':
19
+ return 'pos';
20
+ case 'failed':
21
+ return 'neg';
22
+ case 'transferred':
23
+ return 'violet';
24
+ case 'escalated':
25
+ return 'neg';
26
+ default: {
27
+ const key = outcome.trim().toLowerCase();
28
+ if (key.includes('transfer'))
29
+ return 'violet';
30
+ if (key.includes('fail') || key.includes('escalat') || key.includes('abandon'))
31
+ return 'neg';
32
+ if (key.includes('voicemail'))
33
+ return 'gray';
34
+ if (key.includes('complete') || key.includes('book') || key.includes('task'))
35
+ return 'pos';
36
+ return 'brand';
37
+ }
38
+ }
39
+ }
40
+ function formatOutcomeLabel(outcome) {
41
+ const trimmed = outcome.trim();
42
+ if (!trimmed)
43
+ return 'Unknown';
44
+ const known = normalizeHistoryOutcome(trimmed);
45
+ if (known === 'custom')
46
+ return trimmed;
47
+ return known.charAt(0).toUpperCase() + known.slice(1);
48
+ }
49
+
50
+ function reviewLabel(status) {
51
+ return status === 'reviewed' ? 'Reviewed' : 'Pending review';
52
+ }
53
+ function reviewBadgeTone(status) {
54
+ return status === 'reviewed' ? 'pos' : 'notice';
55
+ }
56
+ function isNeedsReview(status) {
57
+ return status === 'needs';
58
+ }
59
+ /** Normalize legacy or API values to CallReviewStatus. */
60
+ function normalizeReviewStatus(value) {
61
+ return value === 'reviewed' ? 'reviewed' : 'needs';
62
+ }
63
+ function matchesReviewFilter(status, tab) {
64
+ if (tab === 'all')
65
+ return true;
66
+ return status === tab;
67
+ }
68
+
69
+ export { reviewBadgeTone as a, formatOutcomeLabel as f, isNeedsReview as i, matchesReviewFilter as m, outcomeBadgeTone as o, reviewLabel as r };
70
+ //# sourceMappingURL=edvak99-voice-receptionist-widget-call-review.util-DQLIw7xb.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edvak99-voice-receptionist-widget-call-review.util-DQLIw7xb.mjs","sources":["../../../projects/voice-receptionist-widget/src/lib/utils/call-outcome.util.ts","../../../projects/voice-receptionist-widget/src/lib/utils/call-review.util.ts"],"sourcesContent":["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","export type CallReviewStatus = 'needs' | 'reviewed';\n\nexport function reviewLabel(status: CallReviewStatus): string {\n return status === 'reviewed' ? 'Reviewed' : 'Pending review';\n}\n\nexport function reviewBadgeTone(status: CallReviewStatus): 'pos' | 'notice' {\n return status === 'reviewed' ? 'pos' : 'notice';\n}\n\nexport function isNeedsReview(status: CallReviewStatus): boolean {\n return status === 'needs';\n}\n\n/** Normalize legacy or API values to CallReviewStatus. */\nexport function normalizeReviewStatus(value: string | undefined | null): CallReviewStatus {\n return value === 'reviewed' ? 'reviewed' : 'needs';\n}\n\nexport type ReviewFilterTab = 'all' | 'needs' | 'reviewed';\n\nexport function matchesReviewFilter(\n status: CallReviewStatus,\n tab: ReviewFilterTab,\n): boolean {\n if (tab === 'all') return true;\n return status === tab;\n}\n"],"names":[],"mappings":"AAEA;AACO,MAAM,gBAAgB,GAAG;IAC9B,WAAW;IACX,QAAQ;IACR,aAAa;IACb,WAAW;CACH;AAIJ,SAAU,uBAAuB,CAAC,OAAe,EAAA;IACrD,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;AACxC,IAAA,IAAK,gBAAsC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AACzD,QAAA,OAAO,GAAqB;IAC9B;AACA,IAAA,OAAO,QAAQ;AACjB;AAEA;AACM,SAAU,gBAAgB,CAAC,OAAe,EAAA;AAC9C,IAAA,QAAQ,uBAAuB,CAAC,OAAO,CAAC;AACtC,QAAA,KAAK,WAAW;AACd,YAAA,OAAO,KAAK;AACd,QAAA,KAAK,QAAQ;AACX,YAAA,OAAO,KAAK;AACd,QAAA,KAAK,aAAa;AAChB,YAAA,OAAO,QAAQ;AACjB,QAAA,KAAK,WAAW;AACd,YAAA,OAAO,KAAK;QACd,SAAS;YACP,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;AACxC,YAAA,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC;AAAE,gBAAA,OAAO,QAAQ;AAC7C,YAAA,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC;AAAE,gBAAA,OAAO,KAAK;AAC5F,YAAA,IAAI,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC;AAAE,gBAAA,OAAO,MAAM;AAC5C,YAAA,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;AAAE,gBAAA,OAAO,KAAK;AAC1F,YAAA,OAAO,OAAO;QAChB;;AAEJ;AAEM,SAAU,kBAAkB,CAAC,OAAe,EAAA;AAChD,IAAA,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE;AAC9B,IAAA,IAAI,CAAC,OAAO;AAAE,QAAA,OAAO,SAAS;AAC9B,IAAA,MAAM,KAAK,GAAG,uBAAuB,CAAC,OAAO,CAAC;IAC9C,IAAI,KAAK,KAAK,QAAQ;AAAE,QAAA,OAAO,OAAO;AACtC,IAAA,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AACvD;;AC9CM,SAAU,WAAW,CAAC,MAAwB,EAAA;IAClD,OAAO,MAAM,KAAK,UAAU,GAAG,UAAU,GAAG,gBAAgB;AAC9D;AAEM,SAAU,eAAe,CAAC,MAAwB,EAAA;IACtD,OAAO,MAAM,KAAK,UAAU,GAAG,KAAK,GAAG,QAAQ;AACjD;AAEM,SAAU,aAAa,CAAC,MAAwB,EAAA;IACpD,OAAO,MAAM,KAAK,OAAO;AAC3B;AAEA;AACM,SAAU,qBAAqB,CAAC,KAAgC,EAAA;IACpE,OAAO,KAAK,KAAK,UAAU,GAAG,UAAU,GAAG,OAAO;AACpD;AAIM,SAAU,mBAAmB,CACjC,MAAwB,EACxB,GAAoB,EAAA;IAEpB,IAAI,GAAG,KAAK,KAAK;AAAE,QAAA,OAAO,IAAI;IAC9B,OAAO,MAAM,KAAK,GAAG;AACvB;;;;"}