fix(web): 對齊報表統計 API 路徑
This commit is contained in:
@@ -59,11 +59,16 @@ interface IncidentSummary {
|
||||
total?: number
|
||||
resolved?: number
|
||||
unresolved?: number
|
||||
total_incidents?: number
|
||||
resolved_rate?: number
|
||||
status_distribution?: Array<{ status: string; count: number }>
|
||||
}
|
||||
|
||||
interface ResolutionStats {
|
||||
resolutionRate?: number
|
||||
avgResolutionTime?: string | number
|
||||
avg_minutes?: number | null
|
||||
sample_size?: number
|
||||
}
|
||||
|
||||
interface ReportStatusCard {
|
||||
@@ -157,8 +162,8 @@ export default function ReportsPage({ params }: { params: { locale: string } })
|
||||
setLoading(true)
|
||||
setError(false)
|
||||
Promise.all([
|
||||
fetchJson<IncidentSummary>('/api/v1/stats/incident-summary'),
|
||||
fetchJson<ResolutionStats>('/api/v1/stats/resolution-stats'),
|
||||
fetchJson<IncidentSummary>('/api/v1/stats/incidents/summary?days=30'),
|
||||
fetchJson<ResolutionStats>('/api/v1/stats/incidents/resolution?days=30'),
|
||||
fetchJson<DispositionResponse>('/api/v1/stats/disposition'),
|
||||
fetchJson<ReportStatusBoardSnapshot>('/api/v1/agents/agent-report-status-board'),
|
||||
])
|
||||
@@ -182,11 +187,23 @@ export default function ReportsPage({ params }: { params: { locale: string } })
|
||||
useEffect(() => { fetchAll() }, [])
|
||||
|
||||
const ds = disposition?.summary
|
||||
const incidentTotal = summary?.total ?? summary?.total_incidents ?? 0
|
||||
const resolvedFromDistribution = summary?.status_distribution
|
||||
?.filter(item => item.status.toLowerCase().includes('resolved'))
|
||||
.reduce((sum, item) => sum + item.count, 0)
|
||||
const resolvedCount = summary?.resolved ?? resolvedFromDistribution
|
||||
const unresolvedCount = summary?.unresolved ?? (
|
||||
resolvedCount != null ? Math.max(incidentTotal - resolvedCount, 0) : undefined
|
||||
)
|
||||
const incidentResolvedRate = summary?.resolved_rate ?? resolution?.resolutionRate
|
||||
const avgResolutionTime = resolution?.avgResolutionTime ?? (
|
||||
resolution?.avg_minutes != null ? `${Math.round(resolution.avg_minutes)}m` : undefined
|
||||
)
|
||||
const sourceOkCount = Object.values(sourceState).filter(Boolean).length
|
||||
const sourceTotal = Object.keys(sourceState).length
|
||||
const allZeroSignal = Boolean(
|
||||
sourceOkCount > 0 &&
|
||||
(summary?.total ?? 0) === 0 &&
|
||||
incidentTotal === 0 &&
|
||||
(ds?.total ?? 0) === 0 &&
|
||||
(ds?.auto_repair ?? 0) === 0 &&
|
||||
(ds?.human_approved ?? 0) === 0
|
||||
@@ -275,7 +292,7 @@ export default function ReportsPage({ params }: { params: { locale: string } })
|
||||
<CommandMetric
|
||||
icon={<Activity className="h-4 w-4" />}
|
||||
label={t('command.metrics.alerts')}
|
||||
value={summary?.total ?? '--'}
|
||||
value={incidentTotal || '--'}
|
||||
detail={allZeroSignal ? t('command.details.noSignal') : t('command.details.alertSignal')}
|
||||
tone={allZeroSignal ? 'warn' : 'neutral'}
|
||||
/>
|
||||
@@ -356,8 +373,8 @@ export default function ReportsPage({ params }: { params: { locale: string } })
|
||||
<div className="rounded-[7px] border border-border bg-card p-4 shadow-sm">
|
||||
<SectionHeader icon={<Layers3 className="h-4 w-4" />} title={t('funnel.title')} subtitle={t('funnel.subtitle')} />
|
||||
<div className="mt-4 grid gap-3">
|
||||
<FunnelBar label={t('funnel.alerts')} value={summary?.total ?? 0} max={Math.max(summary?.total ?? 0, totalDispositions, 1)} tone="neutral" />
|
||||
<FunnelBar label={t('funnel.dispositions')} value={totalDispositions} max={Math.max(summary?.total ?? 0, totalDispositions, 1)} tone="neutral" />
|
||||
<FunnelBar label={t('funnel.alerts')} value={incidentTotal} max={Math.max(incidentTotal, totalDispositions, 1)} tone="neutral" />
|
||||
<FunnelBar label={t('funnel.dispositions')} value={totalDispositions} max={Math.max(incidentTotal, totalDispositions, 1)} tone="neutral" />
|
||||
<FunnelBar label={t('funnel.auto')} value={autoHandled} max={Math.max(totalDispositions, 1)} tone="ok" />
|
||||
<FunnelBar label={t('funnel.human')} value={(ds?.human_approved ?? 0) + (ds?.manual_resolved ?? 0)} max={Math.max(totalDispositions, 1)} tone="warn" />
|
||||
<FunnelBar label={t('funnel.waiting')} value={waitingApproval} max={Math.max(waitingApproval, reportBoard?.rollups?.workload_unit_total ?? 1)} tone={waitingApproval > 0 ? 'danger' : 'ok'} />
|
||||
@@ -464,16 +481,16 @@ export default function ReportsPage({ params }: { params: { locale: string } })
|
||||
<div className="rounded-[7px] border border-border bg-card p-4 shadow-sm">
|
||||
<p className="mb-3 text-xs font-bold uppercase tracking-wider text-muted-foreground">{t('incidentSummary')}</p>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<MiniStat label={t('total')} value={summary?.total ?? '--'} />
|
||||
<MiniStat label={t('resolved')} value={summary?.resolved ?? '--'} />
|
||||
<MiniStat label={t('unresolved')} value={summary?.unresolved ?? '--'} />
|
||||
<MiniStat label={t('total')} value={incidentTotal || '--'} />
|
||||
<MiniStat label={t('resolved')} value={resolvedCount ?? '--'} />
|
||||
<MiniStat label={t('unresolved')} value={unresolvedCount ?? '--'} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-[7px] border border-border bg-card p-4 shadow-sm">
|
||||
<p className="mb-3 text-xs font-bold uppercase tracking-wider text-muted-foreground">{t('resolutionStats')}</p>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<MiniStat label={t('resolutionRate')} value={resolution?.resolutionRate != null ? `${resolution.resolutionRate}%` : '--'} />
|
||||
<MiniStat label={t('avgResolutionTime')} value={resolution?.avgResolutionTime ?? '--'} />
|
||||
<MiniStat label={t('resolutionRate')} value={incidentResolvedRate != null ? `${incidentResolvedRate}%` : '--'} />
|
||||
<MiniStat label={t('avgResolutionTime')} value={avgResolutionTime ?? '--'} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user