@edvak99/voice-receptionist-widget 1.1.19 → 1.1.21
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/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-CpXa1J9-.mjs +510 -0
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-CpXa1J9-.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-CmImX5FN.mjs +435 -0
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-CmImX5FN.mjs.map +1 -0
- package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-DTAB2WTX.mjs +78 -0
- package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-DTAB2WTX.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-call-history-detail-page.component-DShtvrbw.mjs → edvak99-voice-receptionist-widget-call-history-detail-page.component-BT4clUO-.mjs} +4 -4
- package/fesm2022/{edvak99-voice-receptionist-widget-call-history-detail-page.component-DShtvrbw.mjs.map → edvak99-voice-receptionist-widget-call-history-detail-page.component-BT4clUO-.mjs.map} +1 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-BACON4uq.mjs +247 -0
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-BACON4uq.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-CSKZTNFs.mjs → edvak99-voice-receptionist-widget-call-recording-player.component-4fL-zUWy.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-CSKZTNFs.mjs.map → edvak99-voice-receptionist-widget-call-recording-player.component-4fL-zUWy.mjs.map} +1 -1
- package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-C5BQdHNZ.mjs → edvak99-voice-receptionist-widget-call-review.service-BhbCmB1S.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-C5BQdHNZ.mjs.map → edvak99-voice-receptionist-widget-call-review.service-BhbCmB1S.mjs.map} +1 -1
- package/fesm2022/{edvak99-voice-receptionist-widget-dashboard-page.component-CE2Zh0eJ.mjs → edvak99-voice-receptionist-widget-dashboard-page.component-CxNYLBbg.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-dashboard-page.component-CE2Zh0eJ.mjs.map → edvak99-voice-receptionist-widget-dashboard-page.component-CxNYLBbg.mjs.map} +1 -1
- package/fesm2022/{edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-Cbn5DmQI.mjs → edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DDS7VdnD.mjs} +153 -28
- package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DDS7VdnD.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-live-calls-page.component-OYQuPU33.mjs → edvak99-voice-receptionist-widget-live-calls-page.component-4LtgT4lS.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-live-calls-page.component-OYQuPU33.mjs.map → edvak99-voice-receptionist-widget-live-calls-page.component-4LtgT4lS.mjs.map} +1 -1
- package/fesm2022/{edvak99-voice-receptionist-widget-live-monitor-page.component-jHHs_DbW.mjs → edvak99-voice-receptionist-widget-live-monitor-page.component-B1lKfw-j.mjs} +8 -8
- package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-B1lKfw-j.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-outbound-purposes-page.component-C8wLTIF0.mjs → edvak99-voice-receptionist-widget-outbound-purposes-page.component-BkIHblSu.mjs} +4 -4
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-BkIHblSu.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-task-detail-drawer.component-CDaYKu5_.mjs → edvak99-voice-receptionist-widget-task-detail-drawer.component-aDTKTV79.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-task-detail-drawer.component-CDaYKu5_.mjs.map → edvak99-voice-receptionist-widget-task-detail-drawer.component-aDTKTV79.mjs.map} +1 -1
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-Dj1nWx6V.mjs +150 -0
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-Dj1nWx6V.mjs.map +1 -0
- package/fesm2022/{edvak99-voice-receptionist-widget-wizard-page.component-BtfZJm3V.mjs → edvak99-voice-receptionist-widget-wizard-page.component-CQttbEzW.mjs} +2 -2
- package/fesm2022/{edvak99-voice-receptionist-widget-wizard-page.component-BtfZJm3V.mjs.map → edvak99-voice-receptionist-widget-wizard-page.component-CQttbEzW.mjs.map} +1 -1
- package/fesm2022/edvak99-voice-receptionist-widget.mjs +1 -1
- package/lib/models/ai-receptionist.types.d.ts +17 -0
- package/lib/pages/analytics-page/analytics-page.component.d.ts +25 -5
- package/lib/pages/call-history-page/call-history-page.component.d.ts +1 -0
- package/lib/services/ai-receptionist-api.mock.d.ts +5 -8
- package/lib/services/ai-receptionist-api.service.d.ts +10 -8
- package/lib/utils/csv-download.util.d.ts +2 -0
- package/package.json +1 -1
- package/styles/embed.scss +29 -1
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-D3FI89cY.mjs +0 -510
- package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-D3FI89cY.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-DjmJ2eUU.mjs +0 -363
- package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-DjmJ2eUU.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-DY_zyJli.mjs +0 -78
- package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-DY_zyJli.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-BftVST1n.mjs +0 -259
- package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-BftVST1n.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-Cbn5DmQI.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-jHHs_DbW.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-C8wLTIF0.mjs.map +0 -1
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-C0TaBp8P.mjs +0 -150
- package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-C0TaBp8P.mjs.map +0 -1
package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-DjmJ2eUU.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"edvak99-voice-receptionist-widget-analytics-page.component-DjmJ2eUU.mjs","sources":["../../../src/lib/pages/analytics-page/analytics-page.component.ts","../../../src/lib/pages/analytics-page/analytics-page.component.html"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, inject, OnInit, signal } from '@angular/core';\n\nimport {\n AnalyticsData,\n AnalyticsKpiCard,\n AnalyticsVolumePoint,\n} from '../../models/ai-receptionist.types';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport { EdButtonComponent } from '../../ui/ed-button.component';\nimport { EdSkeletonComponent } from '../../ui/ed-skeleton.component';\n\nimport { IconComponent } from '../../ui/ed-icon.component';\nimport { EdIconButtonComponent } from '../../ui/ed-icon-button.component';\n\n/** Figma pastel palette for horizontal reason bars */\nconst REASON_COLORS = ['#DAB2FF', '#A3B3FF', '#FFA1AD', '#74D4FF', 'rgba(255,32,86,0.2)', '#BBF451', '#FFD230'];\n/** Figma pastel palette for transfer / stuck bars */\nconst STUCK_COLORS = ['#DAB2FF', '#A3B3FF', '#74D4FF', '#FFD230'];\n\nconst OUTCOME_TEXT_COLORS = ['#8200DB', '#2162F0', '#DB0C15'];\nconst OUTCOME_FALLBACK_COLORS = ['#DAB2FF', '#A3B3FF', '#FFA1AD'];\n\ninterface DistributionSegVm {\n label: string;\n value: number;\n color: string;\n textColor: string;\n width: number;\n tooltip: string;\n callsText: string;\n}\n\ninterface SecondaryKpiVm {\n label: string;\n value: string;\n sub?: string;\n delta?: string;\n tone: string;\n trendIcon: string;\n}\n\ninterface HbarRowVm {\n label: string;\n value: number;\n widthPct: number;\n color: string;\n tooltip: string;\n delayMs: number;\n}\n\ninterface PeakRowVm {\n hour: number;\n label: string;\n heightPct: number;\n tooltip: string;\n showLabel: boolean;\n delayMs: number;\n}\n\ninterface VolumePointVm {\n trackKey: string;\n tooltip: string;\n}\n\n@Component({\n selector: 'eds-analytics-page',\n imports: [IconComponent, EdButtonComponent, EdIconButtonComponent, EdSkeletonComponent],\n templateUrl: './analytics-page.component.html',\n styleUrl: './analytics-page.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: { class: 'block h-full min-h-0' },\n})\nexport class AnalyticsPageComponent implements OnInit {\n private readonly api = inject(AiReceptionistApiService);\n\n protected readonly data = signal<AnalyticsData | null>(null);\n protected readonly range = signal('30d');\n protected readonly loading = signal(true);\n protected readonly kpiSkeletonRows = [0, 1, 2, 3] as const;\n protected readonly legendSkeletonRows = [0, 1, 2] as const;\n protected readonly chartRowSkeletonRows = [0, 1, 2, 3, 4] as const;\n protected readonly peakSkeletonRows = [\n 'h-16',\n 'h-24',\n 'h-20',\n 'h-32',\n 'h-28',\n 'h-36',\n 'h-24',\n 'h-40',\n 'h-28',\n 'h-20',\n 'h-16',\n 'h-12',\n ] as const;\n protected readonly chartsReady = signal(false);\n protected readonly hoveredVolume = signal<number | null>(null);\n protected readonly filtersOpen = signal(false);\n\n protected readonly ranges = [\n { id: '7d', label: '7 days' },\n { id: '30d', label: '30 days' },\n { id: '90d', label: '90 days' },\n ];\n\n protected readonly impactKpi = computed(() =>\n this.findKpi(/(impact|hours|front-desk|time saved)/i),\n );\n protected readonly resolutionKpi = computed(() =>\n this.findKpi(/(autonomous|resolution)/i),\n );\n\n protected readonly impactSubPrimary = computed(() => {\n const sub = this.impactKpi()?.sub || 'of front-desk time handled by the AI';\n const tilde = sub.indexOf('~');\n if (tilde === -1) return sub;\n return sub.slice(0, tilde).trim();\n });\n\n protected readonly impactSubSecondary = computed(() => {\n const sub = this.impactKpi()?.sub || '';\n const tilde = sub.indexOf('~');\n if (tilde === -1) return null;\n return sub.slice(tilde).trim();\n });\n\n protected readonly resolutionTrend = computed(() => {\n const kpi = this.resolutionKpi();\n if (!kpi?.delta) return null;\n const tone = this.deltaTone(kpi);\n return {\n delta: kpi.delta.replace(/^[+\\s]+/, ''),\n tone,\n icon: tone === 'pos' ? 'growth_chart_trend_up' : 'growth_chart_trend_down',\n };\n });\n\n protected readonly secondaryKpis = computed((): SecondaryKpiVm[] => {\n const cards = this.data()?.kpiCards ?? [];\n const preferred = [\n /appointment/i,\n /task/i,\n /patient|registr/i,\n /calls?\\s*handled|total\\s*calls|^calls$/i,\n ];\n const picked: AnalyticsKpiCard[] = [];\n for (const re of preferred) {\n const hit = cards.find((c) => re.test(c.label) && !picked.includes(c));\n if (hit) picked.push(hit);\n }\n const list =\n picked.length >= 4\n ? picked.slice(0, 4)\n : [\n ...picked,\n ...cards.filter(\n (c) =>\n !picked.includes(c) &&\n !/(impact|hours|front-desk|autonomous|resolution)/i.test(c.label),\n ),\n ].slice(0, 4);\n\n return list.map((kpi) => {\n const tone = this.deltaTone(kpi);\n return {\n label: kpi.label,\n value: kpi.value,\n sub: kpi.sub,\n delta: kpi.delta ? kpi.delta.replace(/^[+\\s]+/, '') : undefined,\n tone,\n trendIcon: tone === 'pos' ? 'growth_chart_trend_up' : 'growth_chart_trend_down',\n };\n });\n });\n\n protected readonly distribution = computed((): DistributionSegVm[] => {\n const outcomes = this.data()?.outcomes ?? [];\n const total = outcomes.reduce((s, o) => s + o.value, 0) || 1;\n return outcomes.map((o, i) => {\n const width = (o.value / total) * 100;\n const calls = this.estimatedCalls(o.value);\n return {\n ...o,\n color: o.color || OUTCOME_FALLBACK_COLORS[i % OUTCOME_FALLBACK_COLORS.length],\n textColor: OUTCOME_TEXT_COLORS[i % OUTCOME_TEXT_COLORS.length],\n pct: Math.round(width),\n width,\n tooltip: `${o.label}: ${o.value}% (${calls.toLocaleString()} calls)`,\n callsText: `${calls.toLocaleString()} calls`,\n };\n });\n });\n\n protected readonly totalCallsLabel = computed(() => {\n const handled = this.findKpi(/calls?\\s*handled|total\\s*calls|^calls$/i);\n if (handled?.value) return handled.value.includes('Call') ? handled.value : `${handled.value} Calls`;\n const n = this.data()?.kpis.totalCalls;\n return n ? `${n.toLocaleString()} Calls` : 'Calls';\n });\n\n protected readonly directionSub = computed(() => {\n const handled = this.findKpi(/calls?\\s*handled|total\\s*calls|^calls$/i);\n if (handled?.sub && /inbound|outbound/i.test(handled.sub)) return handled.sub;\n return null;\n });\n\n protected readonly volumePoints = computed(() => this.data()?.volume.points ?? []);\n\n protected readonly volumeTotalPath = computed(() => this.buildVolumePath('total'));\n protected readonly volumeInboundPath = computed(() => this.buildVolumePath('inbound'));\n protected readonly volumeOutboundPath = computed(() => this.buildVolumePath('outbound'));\n protected readonly volumeAreaPath = computed(() => {\n const line = this.volumeTotalPath();\n if (!line) return '';\n return `${line} L100,100 L0,100 Z`;\n });\n\n protected readonly volumePointVms = computed((): VolumePointVm[] =>\n this.volumePoints().map((point) => ({\n trackKey: point.date || point.label,\n tooltip: this.volumeTooltip(point),\n })),\n );\n\n protected readonly reasonRows = computed((): HbarRowVm[] => {\n const items = this.data()?.reasons ?? [];\n const max = Math.max(...items.map((i) => i.value), 1);\n return items.map((item, i) => ({\n label: item.label,\n value: item.value,\n widthPct: (item.value / max) * 100,\n color: REASON_COLORS[i % REASON_COLORS.length],\n tooltip: `${item.label}: ${item.value}%`,\n delayMs: i * 40,\n }));\n });\n\n protected readonly transferRows = computed((): HbarRowVm[] => {\n const items = this.data()?.transfers ?? [];\n const max = Math.max(...items.map((i) => i.value), 1);\n return items.map((item, i) => ({\n label: item.label,\n value: item.value,\n widthPct: (item.value / max) * 100,\n color: STUCK_COLORS[i % STUCK_COLORS.length],\n tooltip: `${item.label}: ${item.value}%`,\n delayMs: i * 40,\n }));\n });\n\n protected readonly peakRows = computed((): PeakRowVm[] => {\n const points = this.data()?.peakHours.points ?? [];\n const max = Math.max(...points.map((p) => p.count), 1);\n return points.map((point, i) => ({\n hour: point.hour,\n label: point.label,\n heightPct: point.count <= 0 ? 0 : (point.count / max) * 100,\n tooltip: `${point.label}: ${point.count} call${point.count === 1 ? '' : 's'}`,\n showLabel: this.showPeakAxisLabel(i, points.length),\n delayMs: i * 18,\n }));\n });\n\n /** Human date span for the selected analytics window (month + year included). */\n protected readonly periodLabel = computed(() => {\n const vol = this.data()?.volume;\n const start =\n this.parseIsoDate(vol?.startDate) ?? this.parseIsoDate(vol?.points?.[0]?.date) ?? null;\n const end =\n this.parseIsoDate(vol?.endDate) ??\n this.parseIsoDate(vol?.points?.[vol.points.length - 1]?.date) ??\n null;\n if (start && end) return this.formatPeriodRange(start, end);\n\n const days = this.range() === '7d' ? 7 : this.range() === '90d' ? 90 : 30;\n const endD = new Date();\n const startD = new Date();\n startD.setDate(endD.getDate() - (days - 1));\n return this.formatPeriodRange(startD, endD);\n });\n\n protected readonly volumeAxisLabels = computed(() => {\n const points = this.volumePoints();\n return points.map((p, i) => this.volumeAxisLabel(p, i, points.length));\n });\n\n ngOnInit(): void {\n this.load();\n }\n\n protected toggleFilters(): void {\n this.filtersOpen.update((open) => !open);\n }\n\n protected setRange(id: string): void {\n this.range.set(id);\n this.load();\n }\n\n protected onVolumeEnter(index: number): void {\n this.hoveredVolume.set(index);\n }\n\n protected onVolumeLeave(): void {\n this.hoveredVolume.set(null);\n }\n\n private buildVolumePath(series: 'total' | 'inbound' | 'outbound'): string {\n const points = this.volumePoints();\n if (!points.length) return '';\n const max = Math.max(\n ...points.map((p) => Math.max(p.count, p.inbound ?? 0, p.outbound ?? 0)),\n 1,\n );\n const w = 100;\n const h = 100;\n const step = points.length === 1 ? 0 : w / (points.length - 1);\n return points\n .map((p, i) => {\n const yVal =\n series === 'inbound'\n ? (p.inbound ?? Math.round(p.count * 0.8))\n : series === 'outbound'\n ? (p.outbound ?? Math.round(p.count * 0.2))\n : p.count;\n const x = i * step;\n const y = h - (yVal / max) * h;\n return `${i === 0 ? 'M' : 'L'}${x.toFixed(2)},${y.toFixed(2)}`;\n })\n .join(' ');\n }\n\n private volumeTooltip(point: AnalyticsVolumePoint): string {\n const inbound = point.inbound ?? Math.round(point.count * 0.8);\n const outbound = point.outbound ?? Math.max(0, point.count - inbound);\n const headline = this.volumePointHeadline(point);\n return `${headline}\\nTotal ${point.count} · In ${inbound} · Out ${outbound}`;\n }\n\n private showPeakAxisLabel(index: number, total: number): boolean {\n if (total <= 12) return true;\n const hour = this.data()?.peakHours.points[index]?.hour;\n if (hour != null && [9, 12, 15, 18, 21].includes(hour)) return true;\n const step = total <= 24 ? 3 : 4;\n return index % step === 0 || index === total - 1;\n }\n\n private volumeAxisLabel(point: AnalyticsVolumePoint, index: number, total: number): string {\n if (/week/i.test(point.day) || /week/i.test(point.label)) {\n const week = point.day || point.label;\n const month = point.date ? this.formatMonthYear(point.date) : '';\n return month ? `${week} · ${month}` : week;\n }\n if (point.date) {\n if (total <= 7) return this.formatChartDate(point.date);\n const step = total <= 14 ? 2 : total <= 31 ? 5 : 7;\n if (index % step !== 0 && index !== total - 1) return '';\n return this.formatChartDate(point.date);\n }\n return point.day || point.label;\n }\n\n private deltaTone(kpi: AnalyticsKpiCard | null | undefined): string {\n if (!kpi?.delta) return 'neutral';\n if (kpi.tone === 'pos' || kpi.tone === 'warn' || kpi.tone === 'neg') return kpi.tone;\n if (/^\\+/.test(kpi.delta.trim())) return 'pos';\n if (/^-|−/.test(kpi.delta.trim())) return 'warn';\n return 'neutral';\n }\n\n private formatChartDate(date: string): string {\n const iso = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(date.trim());\n if (iso) return `${iso[2]}/${iso[3]}`;\n return date;\n }\n\n private estimatedCalls(pct: number): number {\n const total = this.data()?.kpis.totalCalls ?? 0;\n if (!total) return Math.round(pct);\n return Math.round((total * pct) / 100);\n }\n\n private volumePointHeadline(point: AnalyticsVolumePoint): string {\n const raw = (point.label || point.day || '').trim();\n if (/week/i.test(raw) && point.date) {\n return `${raw} · ${this.formatMonthYear(point.date)}`;\n }\n if (point.date) return this.formatFullChartDate(point.date);\n return raw || 'Period';\n }\n\n private formatMonthYear(date: string): string {\n const d = this.parseIsoDate(date);\n if (!d) return '';\n return d.toLocaleDateString(undefined, { month: 'short', year: 'numeric' });\n }\n\n private formatFullChartDate(date: string): string {\n const d = this.parseIsoDate(date);\n if (!d) return this.formatChartDate(date);\n return d.toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n }\n\n private formatPeriodRange(start: Date, end: Date): string {\n const sameYear = start.getFullYear() === end.getFullYear();\n const sameMonth = sameYear && start.getMonth() === end.getMonth();\n if (sameMonth) {\n return `${start.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} – ${end.getDate()}, ${end.getFullYear()}`;\n }\n if (sameYear) {\n return `${start.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} – ${end.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}`;\n }\n return `${start.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })} – ${end.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}`;\n }\n\n private parseIsoDate(value: string | null | undefined): Date | null {\n if (!value?.trim()) return null;\n const iso = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(value.trim());\n if (iso) {\n const d = new Date(Number(iso[1]), Number(iso[2]) - 1, Number(iso[3]));\n return Number.isNaN(d.getTime()) ? null : d;\n }\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n\n private findKpi(re: RegExp): AnalyticsKpiCard | null {\n return this.data()?.kpiCards.find((c) => re.test(c.label)) ?? null;\n }\n\n private load(): void {\n this.loading.set(true);\n this.chartsReady.set(false);\n this.hoveredVolume.set(null);\n this.api.getAnalytics(this.range()).subscribe(({ data }) => {\n this.data.set(data);\n this.loading.set(false);\n // Next frame so CSS enter animations run after paint.\n requestAnimationFrame(() => this.chartsReady.set(true));\n });\n }\n}\n","<div class=\"flex h-full min-h-0 flex-col overflow-hidden bg-lighter p-base analytics-page\">\n <header class=\"mb-md flex shrink-0 items-start justify-between gap-base\">\n <div class=\"flex flex-col gap-2xs\">\n <h4>Analytics</h4>\n <p class=\"fs-sm text-light\">Call volume, outcomes, and agent performance</p>\n </div>\n <div class=\"flex shrink-0 items-center gap-base\">\n <ed-button variant=\"default\" outline size=\"sm\">\n <ed-icon slot=\"prefix\" name=\"download\" [size]=\"16\" />\n Export\n </ed-button>\n <ed-icon-button\n name=\"filter_alt\"\n label=\"Filters\" outline\n [pressed]=\"filtersOpen()\"\n toggle\n (click)=\"toggleFilters()\"\n />\n </div>\n </header>\n\n <div class=\"flex min-h-0 flex-1 flex-col gap-md overflow-y-auto\">\n @if (filtersOpen()) {\n <div\n class=\"flex flex-wrap items-center gap-sm rounded-lg border border-light bg-white p-base\"\n role=\"group\"\n aria-label=\"Date range\"\n >\n @for (r of ranges; track r.id) {\n <button\n type=\"button\"\n class=\"analytics-page__range-btn cursor-pointer rounded border-none bg-transparent px-3 py-2 text-light\"\n [class.analytics-page__range-btn--active]=\"range() === r.id\"\n (click)=\"setRange(r.id)\"\n >\n {{ r.label }}\n </button>\n }\n <span class=\"fs-sm text-light\">Showing data for {{ periodLabel() }}</span>\n </div>\n }\n\n @if (loading()) {\n <div class=\"flex flex-col gap-base\" aria-busy=\"true\" aria-live=\"polite\">\n <section class=\"flex flex-col gap-base lg:flex-row\" aria-label=\"Key outcomes\">\n <article class=\"flex min-h-36 shrink-0 flex-col gap-sm rounded-md border border-light bg-white p-base lg:w-72\">\n <ed-skeleton class=\"h-3 w-40\"></ed-skeleton>\n <ed-skeleton class=\"h-7 w-28\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-48\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-36\"></ed-skeleton>\n </article>\n <article class=\"flex min-w-0 flex-1 flex-col gap-base overflow-hidden rounded-lg border border-light bg-white p-base lg:flex-row lg:items-center\">\n <div class=\"flex w-full shrink-0 flex-col gap-sm lg:w-52\">\n <ed-skeleton class=\"h-3 w-36\"></ed-skeleton>\n <ed-skeleton class=\"h-7 w-20\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-40\"></ed-skeleton>\n </div>\n <div class=\"flex min-w-0 flex-1 flex-col gap-sm\">\n <div class=\"flex items-center justify-between gap-sm\">\n <ed-skeleton class=\"h-3 w-28\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-24\"></ed-skeleton>\n </div>\n <ed-skeleton class=\"h-8 w-full !rounded-md\"></ed-skeleton>\n <div class=\"flex flex-wrap gap-md\">\n @for (_ of legendSkeletonRows; track _) {\n <div class=\"inline-flex items-center gap-xs\">\n <ed-skeleton class=\"h-4 w-4 !rounded-sm\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-20\"></ed-skeleton>\n </div>\n }\n </div>\n </div>\n </article>\n </section>\n\n <section class=\"grid grid-cols-1 gap-base sm:grid-cols-2 xl:grid-cols-4\" aria-label=\"Activity KPIs\">\n @for (_ of kpiSkeletonRows; track _) {\n <article class=\"flex flex-col gap-2xs rounded-md border border-light bg-white p-base\">\n <ed-skeleton class=\"h-3 w-28\"></ed-skeleton>\n <ed-skeleton class=\"h-6 w-14\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-24\"></ed-skeleton>\n </article>\n }\n </section>\n\n <div class=\"grid grid-cols-1 gap-base md:grid-cols-2\">\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg border border-light bg-white p-base\">\n <div class=\"flex flex-wrap items-center justify-between gap-base\">\n <h3>Call Volume</h3>\n <div class=\"flex flex-wrap gap-md\">\n @for (_ of legendSkeletonRows; track _) {\n <ed-skeleton class=\"h-3 w-20\"></ed-skeleton>\n }\n </div>\n </div>\n <ed-skeleton class=\"h-60 w-full !rounded-md\"></ed-skeleton>\n </article>\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg border border-light bg-white p-base\">\n <h3>Top Call Reasons</h3>\n <div class=\"flex flex-col gap-md\">\n @for (_ of chartRowSkeletonRows; track _) {\n <div class=\"grid grid-cols-[minmax(110px,140px)_1fr_42px] items-center gap-sm\">\n <ed-skeleton class=\"h-3 w-20 justify-self-end\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-full !rounded-r-xl\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-8 justify-self-end\"></ed-skeleton>\n </div>\n }\n </div>\n </article>\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg border border-light bg-white p-base\">\n <h3>Reasons AI got stuck</h3>\n <div class=\"flex flex-col gap-md\">\n @for (_ of chartRowSkeletonRows; track _) {\n <div class=\"grid grid-cols-[minmax(110px,140px)_1fr_42px] items-center gap-sm\">\n <ed-skeleton class=\"h-3 w-20 justify-self-end\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-full !rounded-r-xl\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-8 justify-self-end\"></ed-skeleton>\n </div>\n }\n </div>\n </article>\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg border border-light bg-white p-base\">\n <h3>Peak Hours</h3>\n <div class=\"flex h-40 items-end gap-xs\">\n @for (heightClass of peakSkeletonRows; track $index) {\n <ed-skeleton [class]=\"'min-w-0 flex-1 !rounded-t-full ' + heightClass\"></ed-skeleton>\n }\n </div>\n </article>\n </div>\n </div>\n } @else {\n @if (data(); as d) {\n <div class=\"analytics-page__body flex flex-col gap-base\" [class.analytics-page__body--ready]=\"chartsReady()\">\n <section class=\"flex flex-col gap-base lg:flex-row\" aria-label=\"Key outcomes\">\n <article\n class=\"analytics-page__impact flex min-h-36 shrink-0 flex-col gap-sm rounded-md p-base text-white lg:w-72\"\n >\n <div class=\"flex items-center gap-2xs\">\n <ed-icon name=\"lightning\" [size]=\"12\" color=\"currentColor\" />\n <span class=\"fs-xs font-semibold uppercase tracking-wide opacity-90\">\n {{ impactKpi()?.label || 'Estimated Impact This Month' }}\n </span>\n </div>\n <strong class=\"fs-lg font-semibold leading-tight\">\n {{ impactKpi()?.value || '—' }}\n </strong>\n <div class=\"flex flex-col gap-2xs opacity-90\">\n <p class=\"m-0 fs-sm\">{{ impactSubPrimary() }}</p>\n @if (impactSubSecondary(); as secondary) {\n <p class=\"m-0 fs-xs opacity-90\">{{ secondary }}</p>\n }\n </div>\n </article>\n\n <article\n class=\"analytics-page__card flex min-w-0 flex-1 flex-col gap-base overflow-hidden rounded-lg bg-white p-base lg:flex-row lg:items-center\"\n >\n <div class=\"flex w-full shrink-0 flex-col gap-sm lg:w-52\">\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-light\">\n {{ resolutionKpi()?.label || 'AI Autonomous Resolution' }}\n </span>\n <strong class=\"fs-lg font-semibold text-dark\">\n {{ resolutionKpi()?.value || '—' }}\n </strong>\n <div class=\"flex flex-wrap items-center gap-xs\">\n <span class=\"fs-sm text-body\">\n {{ resolutionKpi()?.sub || 'without staff intervention' }}\n </span>\n @if (resolutionTrend(); as trend) {\n <span class=\"analytics-page__divider\" aria-hidden=\"true\"></span>\n <span\n class=\"analytics-page__trend inline-flex items-center gap-2xs\"\n [attr.data-tone]=\"trend.tone\"\n >\n <ed-icon [name]=\"trend.icon\" [size]=\"12\" color=\"currentColor\" />\n {{ trend.delta }}\n </span>\n }\n </div>\n </div>\n\n <div class=\"analytics-page__v-divider hidden lg:block\" aria-hidden=\"true\"></div>\n\n <div class=\"analytics-page__distribution flex min-w-0 flex-1 flex-col gap-sm\">\n <div class=\"flex flex-wrap items-center justify-between gap-sm\">\n <strong class=\"fs-sm font-semibold text-body\">{{ totalCallsLabel() }}</strong>\n @if (directionSub(); as dir) {\n <span class=\"fs-sm text-body\">{{ dir }}</span>\n }\n </div>\n <div\n class=\"analytics-page__stack flex h-8 gap-2xs overflow-hidden rounded-md\"\n role=\"img\"\n aria-label=\"Call distribution\"\n >\n @for (seg of distribution(); track seg.label) {\n <span\n class=\"analytics-page__stack-seg relative flex min-w-0 items-center justify-center\"\n [style.flex-grow]=\"seg.width\"\n [style.background]=\"seg.color\"\n [attr.aria-label]=\"seg.tooltip\"\n >\n @if (seg.width >= 8) {\n <span\n class=\"analytics-page__stack-pct fs-xs font-semibold uppercase tracking-wide\"\n [style.color]=\"seg.textColor\"\n >\n {{ seg.value }}%\n </span>\n }\n <span class=\"analytics-page__tooltip\">{{ seg.tooltip }}</span>\n </span>\n }\n </div>\n <ul class=\"m-0 flex list-none flex-wrap gap-x-md gap-y-xs p-0\">\n @for (seg of distribution(); track seg.label) {\n <li class=\"inline-flex items-center gap-xs\">\n <span\n class=\"h-4 w-4 shrink-0 rounded-sm\"\n [style.background]=\"seg.color\"\n ></span>\n <span class=\"inline-flex items-center gap-2xs\">\n <strong class=\"fs-sm font-semibold text-body\">{{ seg.label }}</strong>\n <span class=\"fs-xs text-light\">{{ seg.callsText }}</span>\n </span>\n </li>\n }\n </ul>\n </div>\n </article>\n </section>\n\n <section\n class=\"grid grid-cols-1 gap-base sm:grid-cols-2 xl:grid-cols-4\"\n aria-label=\"Activity KPIs\"\n >\n @for (kpi of secondaryKpis(); track kpi.label) {\n <article class=\"analytics-page__card flex flex-col gap-2xs rounded-md bg-white p-base\">\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-light\">{{ kpi.label }}</span>\n <strong class=\"font-semibold text-dark\">{{ kpi.value }}</strong>\n <div class=\"flex flex-wrap items-center gap-xs\">\n @if (kpi.sub) {\n <span class=\"fs-sm text-body\">{{ kpi.sub }}</span>\n }\n @if (kpi.delta) {\n <span class=\"analytics-page__divider\" aria-hidden=\"true\"></span>\n <span\n class=\"analytics-page__trend inline-flex items-center gap-2xs\"\n [attr.data-tone]=\"kpi.tone\"\n >\n <ed-icon [name]=\"kpi.trendIcon\" [size]=\"12\" color=\"currentColor\" />\n {{ kpi.delta }}\n </span>\n }\n </div>\n </article>\n }\n </section>\n\n <div class=\"grid grid-cols-1 gap-base md:grid-cols-2\">\n <article class=\"flex flex-col gap-md overflow-hidden rounded-lg bg-white p-base analytics-page__chart\">\n <div class=\"flex flex-wrap items-center justify-between gap-base\">\n <h3>Call Volume</h3>\n <ul class=\"m-0 flex list-none flex-wrap items-center justify-end gap-md p-0\">\n <li class=\"inline-flex items-center gap-2xs\">\n <span class=\"analytics-page__dot analytics-page__dot--total h-2 w-2 rounded-full\"></span>\n <span class=\"fs-sm font-semibold text-body\">Total Calls</span>\n </li>\n <li class=\"inline-flex items-center gap-2xs\">\n <span class=\"analytics-page__dot analytics-page__dot--in h-2 w-2 rounded-full\"></span>\n <span class=\"fs-sm font-semibold text-body\">Inbound Calls</span>\n </li>\n <li class=\"inline-flex items-center gap-2xs\">\n <span class=\"analytics-page__dot analytics-page__dot--out h-2 w-2 rounded-full\"></span>\n <span class=\"fs-sm font-semibold text-body\">Outbound Calls</span>\n </li>\n </ul>\n </div>\n <div class=\"analytics-page__line-chart relative h-60\">\n <svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\" aria-hidden=\"true\">\n <path class=\"analytics-page__area\" [attr.d]=\"volumeAreaPath()\" />\n <path\n class=\"analytics-page__line analytics-page__line--total\"\n [attr.d]=\"volumeTotalPath()\"\n fill=\"none\"\n />\n <path\n class=\"analytics-page__line analytics-page__line--in\"\n [attr.d]=\"volumeInboundPath()\"\n fill=\"none\"\n />\n <path\n class=\"analytics-page__line analytics-page__line--out\"\n [attr.d]=\"volumeOutboundPath()\"\n fill=\"none\"\n />\n </svg>\n <div\n class=\"analytics-page__volume-hit flex\"\n role=\"list\"\n aria-label=\"Call volume by period\"\n (mouseleave)=\"onVolumeLeave()\"\n >\n @for (point of volumePointVms(); track point.trackKey; let i = $index) {\n <button\n type=\"button\"\n class=\"analytics-page__volume-col relative min-w-0 flex-1 cursor-default border-none bg-transparent p-0\"\n role=\"listitem\"\n [class.analytics-page__volume-col--active]=\"hoveredVolume() === i\"\n [attr.aria-label]=\"point.tooltip\"\n (mouseenter)=\"onVolumeEnter(i)\"\n (focus)=\"onVolumeEnter(i)\"\n (blur)=\"onVolumeLeave()\"\n >\n <span class=\"analytics-page__tooltip analytics-page__tooltip--multiline\">\n {{ point.tooltip }}\n </span>\n </button>\n }\n </div>\n <div class=\"mt-xs flex justify-between gap-2xs\">\n @for (label of volumeAxisLabels(); track $index) {\n <span class=\"flex-1 text-center fs-xs font-semibold uppercase tracking-wide text-light\">\n {{ label }}\n </span>\n }\n </div>\n </div>\n </article>\n\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg bg-white p-base analytics-page__chart\">\n <h3>Top Call Reasons</h3>\n <div class=\"flex flex-col gap-md\">\n @for (item of reasonRows(); track item.label) {\n <div\n class=\"analytics-page__hbar-row grid items-center gap-sm\"\n [attr.aria-label]=\"item.tooltip\"\n >\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-light text-right\">\n {{ item.label }}\n </span>\n <div class=\"analytics-page__hbar-track relative h-3 overflow-hidden rounded-r-xl\">\n <span\n class=\"analytics-page__hbar-fill block h-full min-w-1 rounded-r-xl\"\n [style.width.%]=\"item.widthPct\"\n [style.background]=\"item.color\"\n [style.animation-delay.ms]=\"item.delayMs\"\n ></span>\n <span class=\"analytics-page__tooltip\">{{ item.tooltip }}</span>\n </div>\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-dark text-right\">\n {{ item.value }}%\n </span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"flex flex-col gap-base overflow-hidden rounded-lg bg-white p-base analytics-page__chart\">\n <h3>Reasons AI got stuck</h3>\n <div class=\"flex flex-col gap-md\">\n @for (item of transferRows(); track item.label) {\n <div\n class=\"analytics-page__hbar-row grid items-center gap-sm\"\n [attr.aria-label]=\"item.tooltip\"\n >\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-light text-right\">\n {{ item.label }}\n </span>\n <div class=\"analytics-page__hbar-track relative h-3 overflow-hidden rounded-r-xl\">\n <span\n class=\"analytics-page__hbar-fill block h-full min-w-1 rounded-r-xl\"\n [style.width.%]=\"item.widthPct\"\n [style.background]=\"item.color\"\n [style.animation-delay.ms]=\"item.delayMs\"\n ></span>\n <span class=\"analytics-page__tooltip\">{{ item.tooltip }}</span>\n </div>\n <span class=\"fs-xs font-semibold uppercase tracking-wide text-dark text-right\">\n {{ item.value }}%\n </span>\n </div>\n }\n </div>\n </article>\n\n <article class=\"flex flex-col gap-md overflow-hidden rounded-lg bg-white p-base analytics-page__chart\">\n <h3>Peak Hours</h3>\n <div class=\"flex flex-col gap-xs\">\n <div class=\"flex h-40 items-end gap-xs\" role=\"list\" aria-label=\"Call volume by hour\">\n @for (point of peakRows(); track point.hour) {\n <div\n class=\"analytics-page__peak-col flex h-full min-w-0 flex-1 flex-col\"\n role=\"listitem\"\n [attr.aria-label]=\"point.tooltip\"\n >\n <div class=\"analytics-page__peak-wrap relative flex min-h-0 flex-1 items-end justify-center\">\n <div\n class=\"analytics-page__peak-bar w-3 min-h-0.5 rounded-t-full\"\n [style.height.%]=\"point.heightPct\"\n [style.animation-delay.ms]=\"point.delayMs\"\n ></div>\n <span class=\"analytics-page__tooltip\">{{ point.tooltip }}</span>\n </div>\n </div>\n }\n </div>\n <div class=\"flex gap-xs\">\n @for (point of peakRows(); track point.hour) {\n <span class=\"min-h-3.5 flex-1 text-center fs-xs font-semibold uppercase tracking-wide text-light\">\n @if (point.showLabel) {\n {{ point.label }}\n }\n </span>\n }\n </div>\n </div>\n </article>\n </div>\n </div>\n }\n }\n </div>\n</div>\n"],"names":[],"mappings":"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"}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, signal, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
-
import { DatePipe } from '@angular/common';
|
|
4
|
-
import { FormsModule } from '@angular/forms';
|
|
5
|
-
import { A as AiReceptionistApiService, m as DEFAULT_PAGE_SIZE, n as totalPages, I as IconComponent, g as EdSkeletonComponent, f as EdButtonComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-Cbn5DmQI.mjs';
|
|
6
|
-
import { S as SmartTooltipDirective } from './edvak99-voice-receptionist-widget-smart-tooltip.directive-ChEdIem3.mjs';
|
|
7
|
-
import { T as TABLE_SKELETON_ROWS } from './edvak99-voice-receptionist-widget-table-skeleton.constants-C1Dg9Z_2.mjs';
|
|
8
|
-
import { E as EdsSearchFieldComponent } from './edvak99-voice-receptionist-widget-eds-search-field.component-BDGCmkoP.mjs';
|
|
9
|
-
|
|
10
|
-
class AuditPageComponent {
|
|
11
|
-
constructor() {
|
|
12
|
-
this.api = inject(AiReceptionistApiService);
|
|
13
|
-
this.pageSize = DEFAULT_PAGE_SIZE;
|
|
14
|
-
this.events = signal([]);
|
|
15
|
-
this.total = signal(0);
|
|
16
|
-
this.page = signal(1);
|
|
17
|
-
this.search = signal('');
|
|
18
|
-
this.actor = signal('');
|
|
19
|
-
this.loading = signal(true);
|
|
20
|
-
this.skeletonRows = TABLE_SKELETON_ROWS;
|
|
21
|
-
}
|
|
22
|
-
ngOnInit() {
|
|
23
|
-
this.load();
|
|
24
|
-
}
|
|
25
|
-
load() {
|
|
26
|
-
this.loading.set(true);
|
|
27
|
-
this.api
|
|
28
|
-
.getAudit(this.search() || undefined, {
|
|
29
|
-
page: this.page(),
|
|
30
|
-
limit: DEFAULT_PAGE_SIZE,
|
|
31
|
-
})
|
|
32
|
-
.subscribe({
|
|
33
|
-
next: ({ items, total, page }) => {
|
|
34
|
-
this.events.set(items);
|
|
35
|
-
this.total.set(total);
|
|
36
|
-
this.page.set(page);
|
|
37
|
-
this.loading.set(false);
|
|
38
|
-
},
|
|
39
|
-
error: () => this.loading.set(false),
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
searchSubmit() {
|
|
43
|
-
this.page.set(1);
|
|
44
|
-
this.load();
|
|
45
|
-
}
|
|
46
|
-
totalPages() {
|
|
47
|
-
return totalPages(this.total(), DEFAULT_PAGE_SIZE);
|
|
48
|
-
}
|
|
49
|
-
prevPage() {
|
|
50
|
-
if (this.page() <= 1)
|
|
51
|
-
return;
|
|
52
|
-
this.page.update((p) => p - 1);
|
|
53
|
-
this.load();
|
|
54
|
-
}
|
|
55
|
-
nextPage() {
|
|
56
|
-
if (this.page() >= this.totalPages())
|
|
57
|
-
return;
|
|
58
|
-
this.page.update((p) => p + 1);
|
|
59
|
-
this.load();
|
|
60
|
-
}
|
|
61
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AuditPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
62
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: AuditPageComponent, isStandalone: true, selector: "eds-audit-page", host: { classAttribute: "block h-full min-h-0" }, ngImport: i0, template: "<div class=\"flex h-full min-h-0 flex-col overflow-hidden bg-lighter p-base audit-page\">\n <header class=\"mb-base shrink-0\">\n <div>\n <h4>Audit log</h4>\n <p class=\"fs-sm text-light\">Track configuration and agent activity</p>\n </div>\n </header>\n\n <div class=\"mb-base flex shrink-0 flex-wrap items-center gap-sm rounded-md bg-white p-base\">\n <eds-search-field\n inputClass=\"form-input min-w-[180px]\"\n placeholder=\"Search action, target, detail\u2026\"\n [value]=\"search()\"\n (valueChange)=\"search.set($event)\"\n (enter)=\"searchSubmit()\"\n />\n <eds-search-field\n type=\"text\"\n inputClass=\"form-input min-w-[180px]\"\n placeholder=\"Filter by actor\"\n [value]=\"actor()\"\n (valueChange)=\"actor.set($event)\"\n (enter)=\"searchSubmit()\"\n />\n <ed-button variant=\"primary\" fill size=\"sm\" (click$)=\"searchSubmit()\">\n <ed-icon slot=\"prefix\" name=\"search\" [size]=\"16\" />\n Search\n </ed-button>\n </div>\n\n <div class=\"flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden rounded-md bg-white p-base\">\n <div class=\"min-h-0 min-w-0 flex-1 overflow-auto\">\n <table class=\"table-default relative w-full\" aria-label=\"Audit log\">\n <thead class=\"sticky top-0 z-[1] bg-light\">\n <tr>\n <th class=\"bg-light\">When</th>\n <th class=\"bg-light\">Actor</th>\n <th class=\"bg-light\">Action</th>\n <th class=\"bg-light\">Target</th>\n <th class=\"bg-light\">Detail</th>\n </tr>\n </thead>\n @if (loading()) {\n <tbody aria-busy=\"true\">\n @for (_ of skeletonRows; track _) {\n <tr>\n <td><ed-skeleton class=\"h-3 w-32\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-3 w-24\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-3 w-36\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-3 w-28\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-3 w-48\"></ed-skeleton></td>\n </tr>\n }\n </tbody>\n } @else {\n <tbody>\n @for (event of events(); track event.id) {\n <tr>\n <td class=\"font-['IBM_Plex_Mono'] fs-sm text-light\">{{ event.at | date: 'medium' }}</td>\n <td>\n <span\n class=\"inline-block min-w-0 max-w-48 truncate\"\n [smartTooltip]=\"event.actor\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ event.actor }}</span>\n </td>\n <td>\n <span\n class=\"inline-block min-w-0 max-w-56 truncate fs-sm font-semibold text-dark\"\n [smartTooltip]=\"event.action\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ event.action }}</span>\n </td>\n <td>\n <span\n class=\"inline-block min-w-0 max-w-48 truncate font-['IBM_Plex_Mono'] fs-sm text-light\"\n [smartTooltip]=\"event.target\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ event.target }}</span>\n </td>\n <td>\n <span\n class=\"inline-block min-w-0 max-w-72 truncate\"\n [smartTooltip]=\"event.detail\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ event.detail }}</span>\n </td>\n </tr>\n } @empty {\n <tr class=\"empty-row\">\n <td colspan=\"5\" class=\"py-lg text-center fs-sm text-light\">No events match your filters.</td>\n </tr>\n }\n </tbody>\n }\n </table>\n </div>\n </div>\n\n <footer class=\"mt-base flex shrink-0 items-center justify-between border-t border-light pt-sm\">\n <span class=\"fs-sm text-light\">\n Page {{ page() }} of {{ totalPages() }} \u00B7 {{ total() }} total\n </span>\n <div class=\"flex gap-2\">\n <ed-button variant=\"secondary\" outline size=\"sm\" [disabled]=\"page() <= 1\" (click$)=\"prevPage()\">Previous</ed-button>\n <ed-button variant=\"secondary\" outline size=\"sm\" [disabled]=\"page() >= totalPages()\" (click$)=\"nextPage()\">Next</ed-button>\n </div>\n </footer>\n</div>\n", dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: IconComponent, selector: "ed-icon", inputs: ["name", "size", "color", "cssClass", "decorative", "ariaHidden", "ariaLabel", "title", "desc", "focusable", "tabIndex"] }, { kind: "component", type: EdsSearchFieldComponent, selector: "eds-search-field", inputs: ["value", "inputId", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange", "enter"] }, { kind: "directive", type: SmartTooltipDirective, selector: "[smartTooltip]", inputs: ["smartTooltip", "tooltipAllowHtml", "tooltipTemplate", "tooltipTemplateContext", "tooltipPlacement", "tooltipTrigger", "tooltipDelayShow", "tooltipDelayHide", "tooltipOffset", "tooltipMaxWidth", "tooltipZIndex", "tooltipClass", "tooltipDisabled", "tooltipArrow", "tooltipHoverIntentMs", "tooltipHoverIntentRadius", "tooltipId", "tooltipRole", "tooltipAnimEnterMs", "tooltipAnimExitMs", "tooltipAnimEasing", "tooltipHoverable", "tooltipHandoffMs", "tooltipOpen", "tooltipOnlyWhenTruncated"] }, { kind: "component", type: EdSkeletonComponent, selector: "ed-skeleton", inputs: ["class"] }, { kind: "component", type: EdButtonComponent, selector: "ed-button", inputs: ["variant", "size", "disabled", "ghost", "outline", "subtle", "fill", "loading", "block", "elevated", "shape", "type", "href", "routerLink", "rel", "target", "download", "ariaLabel"], outputs: ["click$"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
63
|
-
}
|
|
64
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AuditPageComponent, decorators: [{
|
|
65
|
-
type: Component,
|
|
66
|
-
args: [{ selector: 'eds-audit-page', imports: [
|
|
67
|
-
DatePipe,
|
|
68
|
-
FormsModule,
|
|
69
|
-
IconComponent,
|
|
70
|
-
EdsSearchFieldComponent,
|
|
71
|
-
SmartTooltipDirective,
|
|
72
|
-
EdSkeletonComponent,
|
|
73
|
-
EdButtonComponent,
|
|
74
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'block h-full min-h-0' }, template: "<div class=\"flex h-full min-h-0 flex-col overflow-hidden bg-lighter p-base audit-page\">\n <header class=\"mb-base shrink-0\">\n <div>\n <h4>Audit log</h4>\n <p class=\"fs-sm text-light\">Track configuration and agent activity</p>\n </div>\n </header>\n\n <div class=\"mb-base flex shrink-0 flex-wrap items-center gap-sm rounded-md bg-white p-base\">\n <eds-search-field\n inputClass=\"form-input min-w-[180px]\"\n placeholder=\"Search action, target, detail\u2026\"\n [value]=\"search()\"\n (valueChange)=\"search.set($event)\"\n (enter)=\"searchSubmit()\"\n />\n <eds-search-field\n type=\"text\"\n inputClass=\"form-input min-w-[180px]\"\n placeholder=\"Filter by actor\"\n [value]=\"actor()\"\n (valueChange)=\"actor.set($event)\"\n (enter)=\"searchSubmit()\"\n />\n <ed-button variant=\"primary\" fill size=\"sm\" (click$)=\"searchSubmit()\">\n <ed-icon slot=\"prefix\" name=\"search\" [size]=\"16\" />\n Search\n </ed-button>\n </div>\n\n <div class=\"flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden rounded-md bg-white p-base\">\n <div class=\"min-h-0 min-w-0 flex-1 overflow-auto\">\n <table class=\"table-default relative w-full\" aria-label=\"Audit log\">\n <thead class=\"sticky top-0 z-[1] bg-light\">\n <tr>\n <th class=\"bg-light\">When</th>\n <th class=\"bg-light\">Actor</th>\n <th class=\"bg-light\">Action</th>\n <th class=\"bg-light\">Target</th>\n <th class=\"bg-light\">Detail</th>\n </tr>\n </thead>\n @if (loading()) {\n <tbody aria-busy=\"true\">\n @for (_ of skeletonRows; track _) {\n <tr>\n <td><ed-skeleton class=\"h-3 w-32\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-3 w-24\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-3 w-36\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-3 w-28\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-3 w-48\"></ed-skeleton></td>\n </tr>\n }\n </tbody>\n } @else {\n <tbody>\n @for (event of events(); track event.id) {\n <tr>\n <td class=\"font-['IBM_Plex_Mono'] fs-sm text-light\">{{ event.at | date: 'medium' }}</td>\n <td>\n <span\n class=\"inline-block min-w-0 max-w-48 truncate\"\n [smartTooltip]=\"event.actor\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ event.actor }}</span>\n </td>\n <td>\n <span\n class=\"inline-block min-w-0 max-w-56 truncate fs-sm font-semibold text-dark\"\n [smartTooltip]=\"event.action\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ event.action }}</span>\n </td>\n <td>\n <span\n class=\"inline-block min-w-0 max-w-48 truncate font-['IBM_Plex_Mono'] fs-sm text-light\"\n [smartTooltip]=\"event.target\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ event.target }}</span>\n </td>\n <td>\n <span\n class=\"inline-block min-w-0 max-w-72 truncate\"\n [smartTooltip]=\"event.detail\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ event.detail }}</span>\n </td>\n </tr>\n } @empty {\n <tr class=\"empty-row\">\n <td colspan=\"5\" class=\"py-lg text-center fs-sm text-light\">No events match your filters.</td>\n </tr>\n }\n </tbody>\n }\n </table>\n </div>\n </div>\n\n <footer class=\"mt-base flex shrink-0 items-center justify-between border-t border-light pt-sm\">\n <span class=\"fs-sm text-light\">\n Page {{ page() }} of {{ totalPages() }} \u00B7 {{ total() }} total\n </span>\n <div class=\"flex gap-2\">\n <ed-button variant=\"secondary\" outline size=\"sm\" [disabled]=\"page() <= 1\" (click$)=\"prevPage()\">Previous</ed-button>\n <ed-button variant=\"secondary\" outline size=\"sm\" [disabled]=\"page() >= totalPages()\" (click$)=\"nextPage()\">Next</ed-button>\n </div>\n </footer>\n</div>\n" }]
|
|
75
|
-
}] });
|
|
76
|
-
|
|
77
|
-
export { AuditPageComponent };
|
|
78
|
-
//# sourceMappingURL=edvak99-voice-receptionist-widget-audit-page.component-DY_zyJli.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"edvak99-voice-receptionist-widget-audit-page.component-DY_zyJli.mjs","sources":["../../../src/lib/pages/audit-page/audit-page.component.ts","../../../src/lib/pages/audit-page/audit-page.component.html"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\n\nimport { AuditEvent } from '../../models/ai-receptionist.types';\nimport { DEFAULT_PAGE_SIZE, totalPages as calcTotalPages } from '../../models/pagination';\nimport { AiReceptionistApiService } from '../../services/ai-receptionist-api.service';\nimport { EdButtonComponent } from '../../ui/ed-button.component';\nimport { EdSkeletonComponent } from '../../ui/ed-skeleton.component';\n\nimport { SmartTooltipDirective } from '../../shared/smart-tooltip.directive';\nimport { TABLE_SKELETON_ROWS } from '../../shared/table-skeleton.constants';\nimport { IconComponent } from '../../ui/ed-icon.component';\nimport { EdsSearchFieldComponent } from '../../ui/eds-search-field.component';\n\n@Component({\n selector: 'eds-audit-page',\n imports: [\n DatePipe,\n FormsModule,\n IconComponent,\n EdsSearchFieldComponent,\n SmartTooltipDirective,\n EdSkeletonComponent,\n EdButtonComponent,\n ],\n templateUrl: './audit-page.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: { class: 'block h-full min-h-0' },\n})\nexport class AuditPageComponent implements OnInit {\n private readonly api = inject(AiReceptionistApiService);\n\n protected readonly pageSize = DEFAULT_PAGE_SIZE;\n protected readonly events = signal<AuditEvent[]>([]);\n protected readonly total = signal(0);\n protected readonly page = signal(1);\n protected readonly search = signal('');\n protected readonly actor = signal('');\n protected readonly loading = signal(true);\n protected readonly skeletonRows = TABLE_SKELETON_ROWS;\n\n ngOnInit(): void {\n this.load();\n }\n\n protected load(): void {\n this.loading.set(true);\n this.api\n .getAudit(this.search() || undefined, {\n page: this.page(),\n limit: DEFAULT_PAGE_SIZE,\n })\n .subscribe({\n next: ({ items, total, page }) => {\n this.events.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 searchSubmit(): void {\n this.page.set(1);\n this.load();\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","<div class=\"flex h-full min-h-0 flex-col overflow-hidden bg-lighter p-base audit-page\">\n <header class=\"mb-base shrink-0\">\n <div>\n <h4>Audit log</h4>\n <p class=\"fs-sm text-light\">Track configuration and agent activity</p>\n </div>\n </header>\n\n <div class=\"mb-base flex shrink-0 flex-wrap items-center gap-sm rounded-md bg-white p-base\">\n <eds-search-field\n inputClass=\"form-input min-w-[180px]\"\n placeholder=\"Search action, target, detail…\"\n [value]=\"search()\"\n (valueChange)=\"search.set($event)\"\n (enter)=\"searchSubmit()\"\n />\n <eds-search-field\n type=\"text\"\n inputClass=\"form-input min-w-[180px]\"\n placeholder=\"Filter by actor\"\n [value]=\"actor()\"\n (valueChange)=\"actor.set($event)\"\n (enter)=\"searchSubmit()\"\n />\n <ed-button variant=\"primary\" fill size=\"sm\" (click$)=\"searchSubmit()\">\n <ed-icon slot=\"prefix\" name=\"search\" [size]=\"16\" />\n Search\n </ed-button>\n </div>\n\n <div class=\"flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden rounded-md bg-white p-base\">\n <div class=\"min-h-0 min-w-0 flex-1 overflow-auto\">\n <table class=\"table-default relative w-full\" aria-label=\"Audit log\">\n <thead class=\"sticky top-0 z-[1] bg-light\">\n <tr>\n <th class=\"bg-light\">When</th>\n <th class=\"bg-light\">Actor</th>\n <th class=\"bg-light\">Action</th>\n <th class=\"bg-light\">Target</th>\n <th class=\"bg-light\">Detail</th>\n </tr>\n </thead>\n @if (loading()) {\n <tbody aria-busy=\"true\">\n @for (_ of skeletonRows; track _) {\n <tr>\n <td><ed-skeleton class=\"h-3 w-32\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-3 w-24\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-3 w-36\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-3 w-28\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-3 w-48\"></ed-skeleton></td>\n </tr>\n }\n </tbody>\n } @else {\n <tbody>\n @for (event of events(); track event.id) {\n <tr>\n <td class=\"font-['IBM_Plex_Mono'] fs-sm text-light\">{{ event.at | date: 'medium' }}</td>\n <td>\n <span\n class=\"inline-block min-w-0 max-w-48 truncate\"\n [smartTooltip]=\"event.actor\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ event.actor }}</span>\n </td>\n <td>\n <span\n class=\"inline-block min-w-0 max-w-56 truncate fs-sm font-semibold text-dark\"\n [smartTooltip]=\"event.action\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ event.action }}</span>\n </td>\n <td>\n <span\n class=\"inline-block min-w-0 max-w-48 truncate font-['IBM_Plex_Mono'] fs-sm text-light\"\n [smartTooltip]=\"event.target\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ event.target }}</span>\n </td>\n <td>\n <span\n class=\"inline-block min-w-0 max-w-72 truncate\"\n [smartTooltip]=\"event.detail\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ event.detail }}</span>\n </td>\n </tr>\n } @empty {\n <tr class=\"empty-row\">\n <td colspan=\"5\" class=\"py-lg text-center fs-sm text-light\">No events match your filters.</td>\n </tr>\n }\n </tbody>\n }\n </table>\n </div>\n </div>\n\n <footer class=\"mt-base flex shrink-0 items-center justify-between border-t border-light pt-sm\">\n <span class=\"fs-sm text-light\">\n Page {{ page() }} of {{ totalPages() }} · {{ total() }} total\n </span>\n <div class=\"flex gap-2\">\n <ed-button variant=\"secondary\" outline size=\"sm\" [disabled]=\"page() <= 1\" (click$)=\"prevPage()\">Previous</ed-button>\n <ed-button variant=\"secondary\" outline size=\"sm\" [disabled]=\"page() >= totalPages()\" (click$)=\"nextPage()\">Next</ed-button>\n </div>\n </footer>\n</div>\n"],"names":["calcTotalPages"],"mappings":"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"}
|
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, signal, computed, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
-
import { ActivatedRoute, Router } from '@angular/router';
|
|
4
|
-
import * as i1 from '@angular/forms';
|
|
5
|
-
import { FormsModule } from '@angular/forms';
|
|
6
|
-
import { A as AiReceptionistApiService, T as ToastService, m as DEFAULT_PAGE_SIZE, n as totalPages, h as historyResolution, d as historyResultChips, r as resultChipIcon, e as historyResolutionLabel, c as callerMetaLine, E as EdAvatarComponent, I as IconComponent, o as EdIconButtonComponent, f as EdButtonComponent, g as EdSkeletonComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-Cbn5DmQI.mjs';
|
|
7
|
-
import { C as CallReviewService } from './edvak99-voice-receptionist-widget-call-review.service-C5BQdHNZ.mjs';
|
|
8
|
-
import { i as isNeedsReview } from './edvak99-voice-receptionist-widget-call-review.util-rWIPxDtQ.mjs';
|
|
9
|
-
import { S as SmartTooltipDirective } from './edvak99-voice-receptionist-widget-smart-tooltip.directive-ChEdIem3.mjs';
|
|
10
|
-
import { T as TABLE_SKELETON_ROWS } from './edvak99-voice-receptionist-widget-table-skeleton.constants-C1Dg9Z_2.mjs';
|
|
11
|
-
import { E as EdsSearchFieldComponent } from './edvak99-voice-receptionist-widget-eds-search-field.component-BDGCmkoP.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.skeletonRows = TABLE_SKELETON_ROWS;
|
|
26
|
-
this.reviewingId = signal(null);
|
|
27
|
-
this.search = signal('');
|
|
28
|
-
this.direction = signal('');
|
|
29
|
-
this.outcome = signal('');
|
|
30
|
-
this.location = signal('');
|
|
31
|
-
this.reviewTab = signal('all');
|
|
32
|
-
this.filtersOpen = signal(false);
|
|
33
|
-
this.reviewTabs = [
|
|
34
|
-
{ id: 'all', label: 'All' },
|
|
35
|
-
{ id: 'needs', label: 'Needs review' },
|
|
36
|
-
{ id: 'reviewed', label: 'Reviewed' },
|
|
37
|
-
];
|
|
38
|
-
this.totalPages = computed(() => totalPages(this.total(), DEFAULT_PAGE_SIZE));
|
|
39
|
-
this.tableRows = computed(() => this.rows().map((row) => this.toRow(row)));
|
|
40
|
-
}
|
|
41
|
-
toggleFilters(event) {
|
|
42
|
-
event?.stopPropagation();
|
|
43
|
-
this.filtersOpen.update((open) => !open);
|
|
44
|
-
}
|
|
45
|
-
closeFilters() {
|
|
46
|
-
this.filtersOpen.set(false);
|
|
47
|
-
}
|
|
48
|
-
onFiltersDocClick(event) {
|
|
49
|
-
const target = event.target;
|
|
50
|
-
if (!target?.closest('[data-history-filters]')) {
|
|
51
|
-
this.filtersOpen.set(false);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
ngOnInit() {
|
|
55
|
-
const reviewParam = this.route.snapshot.queryParamMap.get('review');
|
|
56
|
-
if (reviewParam === 'needs' || reviewParam === 'reviewed') {
|
|
57
|
-
this.reviewTab.set(reviewParam);
|
|
58
|
-
}
|
|
59
|
-
this.load();
|
|
60
|
-
}
|
|
61
|
-
load() {
|
|
62
|
-
this.loading.set(true);
|
|
63
|
-
const tab = this.reviewTab();
|
|
64
|
-
this.api
|
|
65
|
-
.getHistory({
|
|
66
|
-
q: this.search() || undefined,
|
|
67
|
-
dir: this.direction() || undefined,
|
|
68
|
-
outcome: this.outcome() || undefined,
|
|
69
|
-
location: this.location() || undefined,
|
|
70
|
-
review: tab === 'all' ? undefined : tab,
|
|
71
|
-
page: this.page(),
|
|
72
|
-
limit: DEFAULT_PAGE_SIZE,
|
|
73
|
-
})
|
|
74
|
-
.subscribe({
|
|
75
|
-
next: ({ items, total, page }) => {
|
|
76
|
-
this.rows.set(items);
|
|
77
|
-
this.total.set(total);
|
|
78
|
-
this.page.set(page);
|
|
79
|
-
this.loading.set(false);
|
|
80
|
-
},
|
|
81
|
-
error: () => this.loading.set(false),
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
applyFilters() {
|
|
85
|
-
this.page.set(1);
|
|
86
|
-
this.load();
|
|
87
|
-
}
|
|
88
|
-
setReviewTab(tab) {
|
|
89
|
-
this.reviewTab.set(tab);
|
|
90
|
-
this.page.set(1);
|
|
91
|
-
void this.router.navigate([], {
|
|
92
|
-
relativeTo: this.route,
|
|
93
|
-
queryParams: tab === 'all' ? { review: null } : { review: tab },
|
|
94
|
-
queryParamsHandling: 'merge',
|
|
95
|
-
replaceUrl: true,
|
|
96
|
-
});
|
|
97
|
-
this.load();
|
|
98
|
-
}
|
|
99
|
-
tabBadgeCount(tab) {
|
|
100
|
-
if (tab !== 'needs' || this.reviewTab() !== 'needs')
|
|
101
|
-
return null;
|
|
102
|
-
const count = this.total();
|
|
103
|
-
return count > 0 ? count : null;
|
|
104
|
-
}
|
|
105
|
-
prevPage() {
|
|
106
|
-
if (this.page() <= 1)
|
|
107
|
-
return;
|
|
108
|
-
this.page.update((p) => p - 1);
|
|
109
|
-
this.load();
|
|
110
|
-
}
|
|
111
|
-
nextPage() {
|
|
112
|
-
if (this.page() >= this.totalPages())
|
|
113
|
-
return;
|
|
114
|
-
this.page.update((p) => p + 1);
|
|
115
|
-
this.load();
|
|
116
|
-
}
|
|
117
|
-
markReviewed(row, event) {
|
|
118
|
-
event.preventDefault();
|
|
119
|
-
event.stopPropagation();
|
|
120
|
-
if (!isNeedsReview(row.review) || this.reviewingId())
|
|
121
|
-
return;
|
|
122
|
-
this.reviewingId.set(row.id);
|
|
123
|
-
this.rows.update((list) => list.map((r) => (r.id === row.id ? { ...r, review: 'reviewed' } : r)));
|
|
124
|
-
this.reviewService.markReviewed(row.id).subscribe({
|
|
125
|
-
next: () => {
|
|
126
|
-
this.reviewingId.set(null);
|
|
127
|
-
this.toast.show('Call marked as reviewed', 'success');
|
|
128
|
-
if (this.reviewTab() === 'needs')
|
|
129
|
-
this.load();
|
|
130
|
-
},
|
|
131
|
-
error: () => {
|
|
132
|
-
this.reviewingId.set(null);
|
|
133
|
-
this.rows.update((list) => list.map((r) => (r.id === row.id ? { ...r, review: 'needs' } : r)));
|
|
134
|
-
this.toast.show('Failed to mark call as reviewed', 'error');
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
openCallDetail(id) {
|
|
139
|
-
void this.router.navigate(['../history', id], { relativeTo: this.route });
|
|
140
|
-
}
|
|
141
|
-
exportCsv() {
|
|
142
|
-
const header = [
|
|
143
|
-
'id',
|
|
144
|
-
'name',
|
|
145
|
-
'phone',
|
|
146
|
-
'direction',
|
|
147
|
-
'goal',
|
|
148
|
-
'handledBy',
|
|
149
|
-
'outcome',
|
|
150
|
-
'results',
|
|
151
|
-
'duration',
|
|
152
|
-
'started',
|
|
153
|
-
'review',
|
|
154
|
-
];
|
|
155
|
-
const csvRows = this.rows().map((r) => [
|
|
156
|
-
r.id,
|
|
157
|
-
r.name,
|
|
158
|
-
r.phone,
|
|
159
|
-
r.direction,
|
|
160
|
-
r.reason,
|
|
161
|
-
r.handledBy ?? '',
|
|
162
|
-
r.outcome,
|
|
163
|
-
(r.results ?? []).join('; '),
|
|
164
|
-
String(r.duration),
|
|
165
|
-
String(r.started),
|
|
166
|
-
r.review,
|
|
167
|
-
]);
|
|
168
|
-
const csv = [header, ...csvRows]
|
|
169
|
-
.map((row) => row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(','))
|
|
170
|
-
.join('\n');
|
|
171
|
-
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
|
|
172
|
-
const url = URL.createObjectURL(blob);
|
|
173
|
-
const a = document.createElement('a');
|
|
174
|
-
a.href = url;
|
|
175
|
-
a.download = `call-history-page-${this.page()}.csv`;
|
|
176
|
-
a.click();
|
|
177
|
-
URL.revokeObjectURL(url);
|
|
178
|
-
}
|
|
179
|
-
toRow(row) {
|
|
180
|
-
const resolution = historyResolution({
|
|
181
|
-
outcome: row.outcome,
|
|
182
|
-
resolution: row.resolution,
|
|
183
|
-
risk: row.risk,
|
|
184
|
-
});
|
|
185
|
-
const handledByStaff = row.handledBy === 'staff';
|
|
186
|
-
return {
|
|
187
|
-
id: row.id,
|
|
188
|
-
name: row.name?.trim() || 'Unknown Caller',
|
|
189
|
-
initials: row.initials || '?',
|
|
190
|
-
avatarTone: this.avatarTone(row),
|
|
191
|
-
metaLine: callerMetaLine({
|
|
192
|
-
dob: row.dob,
|
|
193
|
-
age: row.age,
|
|
194
|
-
gender: row.gender,
|
|
195
|
-
phone: row.phone,
|
|
196
|
-
name: row.name,
|
|
197
|
-
}),
|
|
198
|
-
timeLabel: row.started ? this.formatTime(row.started) : row.whenLabel || '—',
|
|
199
|
-
dateLabel: row.started ? this.formatDate(row.started) : '',
|
|
200
|
-
direction: row.direction,
|
|
201
|
-
directionIcon: row.direction === 'inbound' ? 'call_received' : 'call_made',
|
|
202
|
-
directionTitle: row.direction === 'inbound' ? 'Incoming call' : 'Outgoing call',
|
|
203
|
-
goals: this.goalLines(row.reason),
|
|
204
|
-
handledLabel: handledByStaff ? 'STAFF' : 'AI',
|
|
205
|
-
handledVariant: handledByStaff ? 'primary' : 'opened',
|
|
206
|
-
resolutionLabel: historyResolutionLabel(resolution),
|
|
207
|
-
resolutionVariant: resolution === 'resolved' ? 'success' : 'warning',
|
|
208
|
-
resultChips: historyResultChips(row.results, row.outcome).map((label) => ({
|
|
209
|
-
label,
|
|
210
|
-
icon: resultChipIcon(label),
|
|
211
|
-
})),
|
|
212
|
-
durationLabel: row.durationLabel ?? this.formatDuration(row.duration),
|
|
213
|
-
needsReview: isNeedsReview(row.review),
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
goalLines(reason) {
|
|
217
|
-
const raw = String(reason ?? '').trim();
|
|
218
|
-
if (!raw)
|
|
219
|
-
return [];
|
|
220
|
-
return raw
|
|
221
|
-
.split(/[\n;|/]+/)
|
|
222
|
-
.map((part) => part.trim())
|
|
223
|
-
.filter(Boolean);
|
|
224
|
-
}
|
|
225
|
-
avatarTone(row) {
|
|
226
|
-
if (!row.name?.trim() || /^unknown/i.test(row.name) || row.initials === '?')
|
|
227
|
-
return 'gray';
|
|
228
|
-
return 'brand';
|
|
229
|
-
}
|
|
230
|
-
formatTime(ts) {
|
|
231
|
-
return new Date(ts).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
|
|
232
|
-
}
|
|
233
|
-
formatDate(ts) {
|
|
234
|
-
return new Date(ts).toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric' });
|
|
235
|
-
}
|
|
236
|
-
formatDuration(sec) {
|
|
237
|
-
const m = Math.floor(sec / 60);
|
|
238
|
-
const s = sec % 60;
|
|
239
|
-
return `${m}:${s.toString().padStart(2, '0')}`;
|
|
240
|
-
}
|
|
241
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CallHistoryPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
242
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: CallHistoryPageComponent, isStandalone: true, selector: "eds-call-history-page", host: { classAttribute: "block h-full min-h-0" }, ngImport: i0, template: "<div class=\"call-history-page flex min-h-0 flex-col overflow-hidden bg-lighter p-base\">\n <header class=\"mb-md flex shrink-0 items-start justify-between gap-base\">\n <div class=\"grid gap-2xs\">\n <h4>Call History</h4>\n <p class=\"fs-sm text-light\">\n Review past conversations and understand what happened during each call\n </p>\n </div>\n <div class=\"flex shrink-0 items-center gap-base\">\n <div class=\"relative w-60\">\n <eds-search-field\n inputClass=\"form-input w-full\"\n placeholder=\"Search here\"\n ariaLabel=\"Search call history\"\n [value]=\"search()\"\n (valueChange)=\"search.set($event)\"\n (enter)=\"applyFilters()\"\n />\n <span class=\"pointer-events-none absolute inset-y-0 right-3 inline-flex items-center text-lighter\">\n <ed-icon name=\"search\" [size]=\"20\" color=\"currentColor\" />\n </span>\n </div>\n <div\n class=\"relative\"\n data-history-filters\n (document:click)=\"onFiltersDocClick($event)\"\n >\n <ed-icon-button\n name=\"filter_alt\"\n label=\"Filters\"\n size=\"md\"\n outline\n allowBubble\n [pressed]=\"filtersOpen()\"\n toggle\n (click$)=\"toggleFilters($event)\"\n />\n @if (filtersOpen()) {\n <div\n class=\"absolute right-0 top-[calc(100%+4px)] z-20 w-80 rounded-md border border-light bg-white shadow-md\"\n role=\"dialog\"\n aria-label=\"Filter Options\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"flex items-center justify-between border-b border-light px-base py-sm\">\n <h5 class=\"m-0\">Filter Options</h5>\n <ed-icon-button\n name=\"close\"\n size=\"sm\"\n label=\"Close filters\"\n (click$)=\"closeFilters()\"\n />\n </header>\n <section class=\"grid gap-sm p-base\">\n <div class=\"grid gap-2xs\">\n <label class=\"fs-sm font-semibold text-light\" for=\"call-history-direction\">Direction</label>\n <select\n id=\"call-history-direction\"\n class=\"form-select is-sm\"\n [ngModel]=\"direction()\"\n (ngModelChange)=\"direction.set($event)\"\n autocomplete=\"off\"\n aria-label=\"Direction\"\n >\n <option value=\"\">All directions</option>\n <option value=\"inbound\">Inbound</option>\n <option value=\"outbound\">Outbound</option>\n </select>\n </div>\n <div class=\"grid gap-2xs\">\n <label class=\"fs-sm font-semibold text-light\">Outcome</label>\n <eds-search-field\n type=\"text\"\n inputClass=\"form-input is-sm\"\n placeholder=\"Outcome\"\n ariaLabel=\"Outcome\"\n [value]=\"outcome()\"\n (valueChange)=\"outcome.set($event)\"\n (enter)=\"applyFilters(); closeFilters()\"\n />\n </div>\n <div class=\"grid gap-2xs\">\n <label class=\"fs-sm font-semibold text-light\">Location</label>\n <eds-search-field\n type=\"text\"\n inputClass=\"form-input is-sm\"\n placeholder=\"Location\"\n ariaLabel=\"Location\"\n [value]=\"location()\"\n (valueChange)=\"location.set($event)\"\n (enter)=\"applyFilters(); closeFilters()\"\n />\n </div>\n </section>\n <footer class=\"flex flex-row-reverse flex-wrap items-center gap-sm border-t border-light px-base py-sm\">\n <ed-button\n variant=\"primary\"\n fill\n size=\"sm\"\n (click$)=\"applyFilters(); closeFilters()\"\n >\n Apply\n </ed-button>\n <ed-button\n variant=\"secondary\"\n outline\n size=\"sm\"\n (click$)=\"exportCsv()\"\n >\n <ed-icon slot=\"prefix\" name=\"download\" [size]=\"16\" />\n Export CSV\n </ed-button>\n </footer>\n </div>\n }\n </div>\n </div>\n </header>\n\n <div class=\"call-history-page__table min-h-0 min-w-0 flex-1 overflow-auto rounded-md bg-white p-base\">\n <table class=\"table-default relative min-w-5xl w-full\" aria-label=\"Call history\">\n <thead class=\"sticky top-0 z-[1] bg-light\">\n <tr>\n <th class=\"bg-light\">Time</th>\n <th class=\"bg-light\">Patient/Caller</th>\n <th class=\"bg-light text-center\">\n <span class=\"inline-flex items-center justify-center gap-2xs\">\n <ed-icon name=\"call_received\" [size]=\"12\" class=\"text-success\" color=\"currentColor\" />\n <span class=\"text-light\">/</span>\n <ed-icon name=\"call_made\" [size]=\"12\" class=\"text-primary\" color=\"currentColor\" />\n </span>\n </th>\n <th class=\"bg-light\">Goal</th>\n <th class=\"bg-light\">Handled By</th>\n <th class=\"bg-light\">Resolution</th>\n <th class=\"bg-light\">Result</th>\n <th class=\"bg-light\">Duration</th>\n </tr>\n </thead>\n @if (loading()) {\n <tbody aria-busy=\"true\">\n @for (_ of skeletonRows; track _) {\n <tr>\n <td>\n <div class=\"grid gap-2xs py-2xs\">\n <ed-skeleton class=\"h-3 w-16\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-20\"></ed-skeleton>\n </div>\n </td>\n <td>\n <div class=\"flex items-center gap-xs py-2xs\">\n <ed-skeleton class=\"h-8 w-8 !rounded-md\"></ed-skeleton>\n <div class=\"grid gap-2xs\">\n <ed-skeleton class=\"h-3 w-28\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-36\"></ed-skeleton>\n </div>\n </div>\n </td>\n <td class=\"text-center\">\n <div class=\"flex justify-center py-2xs\">\n <ed-skeleton class=\"h-5 w-5 !rounded\"></ed-skeleton>\n </div>\n </td>\n <td><ed-skeleton class=\"h-3 w-32\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-5 w-20 !rounded-full\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-5 w-24 !rounded-full\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-5 w-28 !rounded\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-3 w-12\"></ed-skeleton></td>\n </tr>\n }\n </tbody>\n } @else {\n <tbody>\n @for (row of tableRows(); track row.id; let i = $index) {\n <tr\n class=\"focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary\"\n role=\"button\"\n [attr.tabindex]=\"i === 0 ? 0 : -1\"\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=\"grid gap-2xs whitespace-nowrap\">\n <span class=\"fs-xs font-medium text-dark\">{{ row.timeLabel }}</span>\n <span class=\"fs-xs text-light\">{{ row.dateLabel }}</span>\n </div>\n </td>\n\n <td>\n <div class=\"flex min-w-52 items-center gap-xs\">\n <ed-avatar\n shape=\"box\"\n size=\"base\"\n [initials]=\"row.initials\"\n [tone]=\"row.avatarTone\"\n />\n <div class=\"grid min-w-0 gap-2xs\">\n <div class=\"flex min-w-0 items-center gap-2xs\">\n <span\n class=\"inline-block min-w-0 max-w-56 truncate fs-sm font-semibold text-dark\"\n [smartTooltip]=\"row.name\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ row.name }}</span>\n <!-- <span class=\"inline-flex shrink-0 text-light\">\n <ed-icon name=\"TagSimple\" [size]=\"14\" color=\"currentColor\" />\n </span> -->\n </div>\n <span\n class=\"inline-block min-w-0 max-w-64 truncate fs-xs text-light\"\n [smartTooltip]=\"row.metaLine\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ row.metaLine }}</span>\n </div>\n </div>\n </td>\n\n <td class=\"text-center\">\n <span\n class=\"inline-flex h-5 w-5 items-center justify-center rounded\"\n [class.bg-success-50]=\"row.direction === 'inbound'\"\n [class.text-success]=\"row.direction === 'inbound'\"\n [class.bg-primary-50]=\"row.direction === 'outbound'\"\n [class.text-primary]=\"row.direction === 'outbound'\"\n [smartTooltip]=\"row.directionTitle\"\n [attr.aria-label]=\"row.directionTitle\"\n >\n <ed-icon [name]=\"row.directionIcon\" [size]=\"12\" color=\"currentColor\" />\n </span>\n </td>\n\n <td>\n <div class=\"grid gap-2xs\">\n @for (goal of row.goals; track goal) {\n <span\n class=\"inline-block min-w-0 max-w-56 truncate fs-sm font-medium text-dark\"\n [smartTooltip]=\"goal\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ goal }}</span>\n } @empty {\n <span class=\"fs-sm text-light\">\u2014</span>\n }\n </div>\n </td>\n\n <td>\n <ed-badge-2 [attr.variant]=\"row.handledVariant\" pill uppercase>\n {{ row.handledLabel }}\n </ed-badge-2>\n </td>\n\n <td>\n <ed-badge-2 [attr.variant]=\"row.resolutionVariant\" pill uppercase>\n {{ row.resolutionLabel }}\n </ed-badge-2>\n </td>\n\n <td>\n <div class=\"flex min-w-0 max-w-80 flex-wrap gap-2xs\">\n @for (chip of row.resultChips; track chip.label) {\n <span\n class=\"inline-flex h-5 max-w-48 min-w-0 items-center gap-2xs rounded bg-light px-2xs fs-xs font-medium text-dark\"\n >\n <ed-icon [name]=\"chip.icon\" [size]=\"12\" color=\"currentColor\" />\n <span\n class=\"min-w-0 truncate\"\n [smartTooltip]=\"chip.label\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ chip.label }}</span>\n </span>\n } @empty {\n <span class=\"fs-sm text-light\">\u2014</span>\n }\n </div>\n </td>\n\n <td class=\"whitespace-nowrap tabular-nums fs-sm font-medium\">\n {{ row.durationLabel }}\n </td>\n </tr>\n } @empty {\n <tr class=\"empty-row\">\n <td colspan=\"8\" class=\"py-lg text-center fs-sm text-light\">\n No calls match this filter.\n </td>\n </tr>\n }\n </tbody>\n }\n </table>\n </div>\n\n <footer class=\"mt-base flex shrink-0 items-center justify-between border-t border-light pt-sm hidden\">\n <span class=\"fs-sm text-light\">\n Page {{ page() }} of {{ totalPages() }} \u00B7 {{ total() }} total\n </span>\n <div class=\"flex gap-xs\">\n <ed-button\n variant=\"secondary\"\n outline\n size=\"sm\"\n [disabled]=\"page() <= 1\"\n (click$)=\"prevPage()\"\n >\n Previous\n </ed-button>\n <ed-button\n variant=\"secondary\"\n outline\n size=\"sm\"\n [disabled]=\"page() >= totalPages()\"\n (click$)=\"nextPage()\"\n >\n Next\n </ed-button>\n </div>\n </footer>\n</div>\n", styles: [":host{display:block;height:100%;min-height:0}.call-history-page{height:100%;min-height:24rem}.call-history-page__table{min-height:16rem}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.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: EdAvatarComponent, selector: "ed-avatar", inputs: ["initials", "tone", "shape", "size", "gradient"] }, { kind: "component", type: IconComponent, selector: "ed-icon", inputs: ["name", "size", "color", "cssClass", "decorative", "ariaHidden", "ariaLabel", "title", "desc", "focusable", "tabIndex"] }, { kind: "component", type: EdIconButtonComponent, selector: "ed-icon-button", inputs: ["label", "name", "size", "variant", "allowBubble", "ghost", "outline", "solid", "subtle", "shape", "type", "disabled", "loading", "toggle", "pressed", "id", "title", "nameAttr", "value", "autofocus", "tabIndex"], outputs: ["click", "focus", "blur", "click$", "focus$", "blur$"] }, { kind: "component", type: EdButtonComponent, selector: "ed-button", inputs: ["variant", "size", "disabled", "ghost", "outline", "subtle", "fill", "loading", "block", "elevated", "shape", "type", "href", "routerLink", "rel", "target", "download", "ariaLabel"], outputs: ["click$"] }, { kind: "component", type: EdsSearchFieldComponent, selector: "eds-search-field", inputs: ["value", "inputId", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange", "enter"] }, { kind: "directive", type: SmartTooltipDirective, selector: "[smartTooltip]", inputs: ["smartTooltip", "tooltipAllowHtml", "tooltipTemplate", "tooltipTemplateContext", "tooltipPlacement", "tooltipTrigger", "tooltipDelayShow", "tooltipDelayHide", "tooltipOffset", "tooltipMaxWidth", "tooltipZIndex", "tooltipClass", "tooltipDisabled", "tooltipArrow", "tooltipHoverIntentMs", "tooltipHoverIntentRadius", "tooltipId", "tooltipRole", "tooltipAnimEnterMs", "tooltipAnimExitMs", "tooltipAnimEasing", "tooltipHoverable", "tooltipHandoffMs", "tooltipOpen", "tooltipOnlyWhenTruncated"] }, { kind: "component", type: EdSkeletonComponent, selector: "ed-skeleton", inputs: ["class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
243
|
-
}
|
|
244
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CallHistoryPageComponent, decorators: [{
|
|
245
|
-
type: Component,
|
|
246
|
-
args: [{ selector: 'eds-call-history-page', imports: [
|
|
247
|
-
FormsModule,
|
|
248
|
-
EdAvatarComponent,
|
|
249
|
-
IconComponent,
|
|
250
|
-
EdIconButtonComponent,
|
|
251
|
-
EdButtonComponent,
|
|
252
|
-
EdsSearchFieldComponent,
|
|
253
|
-
SmartTooltipDirective,
|
|
254
|
-
EdSkeletonComponent,
|
|
255
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, schemas: [CUSTOM_ELEMENTS_SCHEMA], host: { class: 'block h-full min-h-0' }, template: "<div class=\"call-history-page flex min-h-0 flex-col overflow-hidden bg-lighter p-base\">\n <header class=\"mb-md flex shrink-0 items-start justify-between gap-base\">\n <div class=\"grid gap-2xs\">\n <h4>Call History</h4>\n <p class=\"fs-sm text-light\">\n Review past conversations and understand what happened during each call\n </p>\n </div>\n <div class=\"flex shrink-0 items-center gap-base\">\n <div class=\"relative w-60\">\n <eds-search-field\n inputClass=\"form-input w-full\"\n placeholder=\"Search here\"\n ariaLabel=\"Search call history\"\n [value]=\"search()\"\n (valueChange)=\"search.set($event)\"\n (enter)=\"applyFilters()\"\n />\n <span class=\"pointer-events-none absolute inset-y-0 right-3 inline-flex items-center text-lighter\">\n <ed-icon name=\"search\" [size]=\"20\" color=\"currentColor\" />\n </span>\n </div>\n <div\n class=\"relative\"\n data-history-filters\n (document:click)=\"onFiltersDocClick($event)\"\n >\n <ed-icon-button\n name=\"filter_alt\"\n label=\"Filters\"\n size=\"md\"\n outline\n allowBubble\n [pressed]=\"filtersOpen()\"\n toggle\n (click$)=\"toggleFilters($event)\"\n />\n @if (filtersOpen()) {\n <div\n class=\"absolute right-0 top-[calc(100%+4px)] z-20 w-80 rounded-md border border-light bg-white shadow-md\"\n role=\"dialog\"\n aria-label=\"Filter Options\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"flex items-center justify-between border-b border-light px-base py-sm\">\n <h5 class=\"m-0\">Filter Options</h5>\n <ed-icon-button\n name=\"close\"\n size=\"sm\"\n label=\"Close filters\"\n (click$)=\"closeFilters()\"\n />\n </header>\n <section class=\"grid gap-sm p-base\">\n <div class=\"grid gap-2xs\">\n <label class=\"fs-sm font-semibold text-light\" for=\"call-history-direction\">Direction</label>\n <select\n id=\"call-history-direction\"\n class=\"form-select is-sm\"\n [ngModel]=\"direction()\"\n (ngModelChange)=\"direction.set($event)\"\n autocomplete=\"off\"\n aria-label=\"Direction\"\n >\n <option value=\"\">All directions</option>\n <option value=\"inbound\">Inbound</option>\n <option value=\"outbound\">Outbound</option>\n </select>\n </div>\n <div class=\"grid gap-2xs\">\n <label class=\"fs-sm font-semibold text-light\">Outcome</label>\n <eds-search-field\n type=\"text\"\n inputClass=\"form-input is-sm\"\n placeholder=\"Outcome\"\n ariaLabel=\"Outcome\"\n [value]=\"outcome()\"\n (valueChange)=\"outcome.set($event)\"\n (enter)=\"applyFilters(); closeFilters()\"\n />\n </div>\n <div class=\"grid gap-2xs\">\n <label class=\"fs-sm font-semibold text-light\">Location</label>\n <eds-search-field\n type=\"text\"\n inputClass=\"form-input is-sm\"\n placeholder=\"Location\"\n ariaLabel=\"Location\"\n [value]=\"location()\"\n (valueChange)=\"location.set($event)\"\n (enter)=\"applyFilters(); closeFilters()\"\n />\n </div>\n </section>\n <footer class=\"flex flex-row-reverse flex-wrap items-center gap-sm border-t border-light px-base py-sm\">\n <ed-button\n variant=\"primary\"\n fill\n size=\"sm\"\n (click$)=\"applyFilters(); closeFilters()\"\n >\n Apply\n </ed-button>\n <ed-button\n variant=\"secondary\"\n outline\n size=\"sm\"\n (click$)=\"exportCsv()\"\n >\n <ed-icon slot=\"prefix\" name=\"download\" [size]=\"16\" />\n Export CSV\n </ed-button>\n </footer>\n </div>\n }\n </div>\n </div>\n </header>\n\n <div class=\"call-history-page__table min-h-0 min-w-0 flex-1 overflow-auto rounded-md bg-white p-base\">\n <table class=\"table-default relative min-w-5xl w-full\" aria-label=\"Call history\">\n <thead class=\"sticky top-0 z-[1] bg-light\">\n <tr>\n <th class=\"bg-light\">Time</th>\n <th class=\"bg-light\">Patient/Caller</th>\n <th class=\"bg-light text-center\">\n <span class=\"inline-flex items-center justify-center gap-2xs\">\n <ed-icon name=\"call_received\" [size]=\"12\" class=\"text-success\" color=\"currentColor\" />\n <span class=\"text-light\">/</span>\n <ed-icon name=\"call_made\" [size]=\"12\" class=\"text-primary\" color=\"currentColor\" />\n </span>\n </th>\n <th class=\"bg-light\">Goal</th>\n <th class=\"bg-light\">Handled By</th>\n <th class=\"bg-light\">Resolution</th>\n <th class=\"bg-light\">Result</th>\n <th class=\"bg-light\">Duration</th>\n </tr>\n </thead>\n @if (loading()) {\n <tbody aria-busy=\"true\">\n @for (_ of skeletonRows; track _) {\n <tr>\n <td>\n <div class=\"grid gap-2xs py-2xs\">\n <ed-skeleton class=\"h-3 w-16\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-20\"></ed-skeleton>\n </div>\n </td>\n <td>\n <div class=\"flex items-center gap-xs py-2xs\">\n <ed-skeleton class=\"h-8 w-8 !rounded-md\"></ed-skeleton>\n <div class=\"grid gap-2xs\">\n <ed-skeleton class=\"h-3 w-28\"></ed-skeleton>\n <ed-skeleton class=\"h-3 w-36\"></ed-skeleton>\n </div>\n </div>\n </td>\n <td class=\"text-center\">\n <div class=\"flex justify-center py-2xs\">\n <ed-skeleton class=\"h-5 w-5 !rounded\"></ed-skeleton>\n </div>\n </td>\n <td><ed-skeleton class=\"h-3 w-32\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-5 w-20 !rounded-full\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-5 w-24 !rounded-full\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-5 w-28 !rounded\"></ed-skeleton></td>\n <td><ed-skeleton class=\"h-3 w-12\"></ed-skeleton></td>\n </tr>\n }\n </tbody>\n } @else {\n <tbody>\n @for (row of tableRows(); track row.id; let i = $index) {\n <tr\n class=\"focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary\"\n role=\"button\"\n [attr.tabindex]=\"i === 0 ? 0 : -1\"\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=\"grid gap-2xs whitespace-nowrap\">\n <span class=\"fs-xs font-medium text-dark\">{{ row.timeLabel }}</span>\n <span class=\"fs-xs text-light\">{{ row.dateLabel }}</span>\n </div>\n </td>\n\n <td>\n <div class=\"flex min-w-52 items-center gap-xs\">\n <ed-avatar\n shape=\"box\"\n size=\"base\"\n [initials]=\"row.initials\"\n [tone]=\"row.avatarTone\"\n />\n <div class=\"grid min-w-0 gap-2xs\">\n <div class=\"flex min-w-0 items-center gap-2xs\">\n <span\n class=\"inline-block min-w-0 max-w-56 truncate fs-sm font-semibold text-dark\"\n [smartTooltip]=\"row.name\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ row.name }}</span>\n <!-- <span class=\"inline-flex shrink-0 text-light\">\n <ed-icon name=\"TagSimple\" [size]=\"14\" color=\"currentColor\" />\n </span> -->\n </div>\n <span\n class=\"inline-block min-w-0 max-w-64 truncate fs-xs text-light\"\n [smartTooltip]=\"row.metaLine\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ row.metaLine }}</span>\n </div>\n </div>\n </td>\n\n <td class=\"text-center\">\n <span\n class=\"inline-flex h-5 w-5 items-center justify-center rounded\"\n [class.bg-success-50]=\"row.direction === 'inbound'\"\n [class.text-success]=\"row.direction === 'inbound'\"\n [class.bg-primary-50]=\"row.direction === 'outbound'\"\n [class.text-primary]=\"row.direction === 'outbound'\"\n [smartTooltip]=\"row.directionTitle\"\n [attr.aria-label]=\"row.directionTitle\"\n >\n <ed-icon [name]=\"row.directionIcon\" [size]=\"12\" color=\"currentColor\" />\n </span>\n </td>\n\n <td>\n <div class=\"grid gap-2xs\">\n @for (goal of row.goals; track goal) {\n <span\n class=\"inline-block min-w-0 max-w-56 truncate fs-sm font-medium text-dark\"\n [smartTooltip]=\"goal\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ goal }}</span>\n } @empty {\n <span class=\"fs-sm text-light\">\u2014</span>\n }\n </div>\n </td>\n\n <td>\n <ed-badge-2 [attr.variant]=\"row.handledVariant\" pill uppercase>\n {{ row.handledLabel }}\n </ed-badge-2>\n </td>\n\n <td>\n <ed-badge-2 [attr.variant]=\"row.resolutionVariant\" pill uppercase>\n {{ row.resolutionLabel }}\n </ed-badge-2>\n </td>\n\n <td>\n <div class=\"flex min-w-0 max-w-80 flex-wrap gap-2xs\">\n @for (chip of row.resultChips; track chip.label) {\n <span\n class=\"inline-flex h-5 max-w-48 min-w-0 items-center gap-2xs rounded bg-light px-2xs fs-xs font-medium text-dark\"\n >\n <ed-icon [name]=\"chip.icon\" [size]=\"12\" color=\"currentColor\" />\n <span\n class=\"min-w-0 truncate\"\n [smartTooltip]=\"chip.label\"\n [tooltipOnlyWhenTruncated]=\"true\"\n >{{ chip.label }}</span>\n </span>\n } @empty {\n <span class=\"fs-sm text-light\">\u2014</span>\n }\n </div>\n </td>\n\n <td class=\"whitespace-nowrap tabular-nums fs-sm font-medium\">\n {{ row.durationLabel }}\n </td>\n </tr>\n } @empty {\n <tr class=\"empty-row\">\n <td colspan=\"8\" class=\"py-lg text-center fs-sm text-light\">\n No calls match this filter.\n </td>\n </tr>\n }\n </tbody>\n }\n </table>\n </div>\n\n <footer class=\"mt-base flex shrink-0 items-center justify-between border-t border-light pt-sm hidden\">\n <span class=\"fs-sm text-light\">\n Page {{ page() }} of {{ totalPages() }} \u00B7 {{ total() }} total\n </span>\n <div class=\"flex gap-xs\">\n <ed-button\n variant=\"secondary\"\n outline\n size=\"sm\"\n [disabled]=\"page() <= 1\"\n (click$)=\"prevPage()\"\n >\n Previous\n </ed-button>\n <ed-button\n variant=\"secondary\"\n outline\n size=\"sm\"\n [disabled]=\"page() >= totalPages()\"\n (click$)=\"nextPage()\"\n >\n Next\n </ed-button>\n </div>\n </footer>\n</div>\n", styles: [":host{display:block;height:100%;min-height:0}.call-history-page{height:100%;min-height:24rem}.call-history-page__table{min-height:16rem}\n"] }]
|
|
256
|
-
}] });
|
|
257
|
-
|
|
258
|
-
export { CallHistoryPageComponent };
|
|
259
|
-
//# sourceMappingURL=edvak99-voice-receptionist-widget-call-history-page.component-BftVST1n.mjs.map
|