Files
CA/frontend/src/pages/Insights.tsx

199 lines
6.3 KiB
TypeScript
Raw Normal View History

import { useEffect } from 'react';
import { useAnalysisStore } from '@/stores/analysisStore';
import { ErrorBanner } from '@/components/ErrorBanner';
import {
Lightbulb,
AlertTriangle,
CheckCircle,
Info,
XCircle,
TrendingUp,
TrendingDown,
Clock,
} from 'lucide-react';
const TYPE_CONFIG = {
warning: {
icon: AlertTriangle,
bg: 'bg-warning-light',
border: 'border-warning',
iconColor: 'text-warning',
badge: 'bg-warning text-white',
},
danger: {
icon: XCircle,
bg: 'bg-danger-light',
border: 'border-danger',
iconColor: 'text-danger',
badge: 'bg-danger text-white',
},
success: {
icon: CheckCircle,
bg: 'bg-success-light',
border: 'border-success',
iconColor: 'text-success',
badge: 'bg-success text-white',
},
info: {
icon: Info,
bg: 'bg-primary-muted',
border: 'border-primary',
iconColor: 'text-primary',
badge: 'bg-primary text-white',
},
};
export function Insights() {
const { insights, isLoading, error, clearError, fetchInsights } = useAnalysisStore();
useEffect(() => {
fetchInsights();
}, []);
const stats = insights
? [
{
label: '总洞察数',
value: insights.total_insights,
icon: Lightbulb,
color: 'text-primary',
bg: 'bg-primary-muted',
},
{
label: '预警',
value: insights.warning_count + ((insights as any).danger_count || 0),
icon: AlertTriangle,
color: 'text-warning',
bg: 'bg-warning-light',
},
{
label: '正常',
value: insights.success_count,
icon: CheckCircle,
color: 'text-success',
bg: 'bg-success-light',
},
{
label: '信息',
value: insights.info_count,
icon: Info,
color: 'text-primary',
bg: 'bg-primary-muted',
},
]
: [];
return (
<div>
{error && (
<ErrorBanner
error={error}
onRetry={() => { clearError(); fetchInsights(); }}
onDismiss={clearError}
/>
)}
<div className="mb-5">
<h1 className="font-display text-[18px] font-semibold mb-1 flex items-center gap-2">
<Lightbulb className="w-5 h-5 text-primary" />
</h1>
<p className="text-[12px] text-text-muted">
</p>
</div>
{isLoading && (
<div className="mb-4 text-center py-8 bg-bg-card rounded-lg border border-border">
<span className="text-text-secondary">...</span>
</div>
)}
{insights && (
<div className="grid grid-cols-4 gap-4 mb-4">
{stats.map((stat) => (
<div key={stat.label} className="card p-4">
<div className="flex items-center gap-2 mb-2">
<div className={`w-8 h-8 rounded-lg ${stat.bg} flex items-center justify-center`}>
<stat.icon className={`w-4 h-4 ${stat.color}`} />
</div>
<span className="text-[11px] font-medium text-text-muted uppercase tracking-wide">
{stat.label}
</span>
</div>
<div className="font-display text-[26px] font-bold text-text-primary">
{stat.value}
</div>
</div>
))}
</div>
)}
{insights && (
<div className="grid grid-cols-2 gap-4">
{insights.cards.map((card) => {
const config = TYPE_CONFIG[card.type];
const Icon = config.icon;
return (
<div
key={card.id}
className={`card p-4 border-l-4 ${config.border} hover:shadow-md transition-shadow`}
>
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-2">
<div className={`w-8 h-8 rounded-lg ${config.bg} flex items-center justify-center`}>
<Icon className={`w-4 h-4 ${config.iconColor}`} />
</div>
<div>
<h3 className="text-[14px] font-semibold text-text-primary">
{card.title}
</h3>
<span className="text-[11px] text-text-muted flex items-center gap-1">
<Clock className="w-3 h-3" />
{card.timestamp}
</span>
</div>
</div>
<span className={`text-[10px] font-semibold px-2 py-0.5 rounded ${config.badge}`}>
{card.type === 'warning' ? '预警' : card.type === 'danger' ? '紧急' : card.type === 'success' ? '正常' : '信息'}
</span>
</div>
<p className="text-[13px] text-text-secondary leading-relaxed mb-3">
{card.description}
</p>
{card.metric && card.metricValue && (
<div className="flex items-center gap-2 pt-3 border-t border-border">
<span className="text-[12px] text-text-muted">{card.metric}:</span>
<span className={`text-[14px] font-bold flex items-center gap-1 ${
card.type === 'warning' || card.type === 'danger'
? 'text-danger'
: card.type === 'success'
? 'text-success'
: 'text-primary'
}`}>
{card.metricValue.includes('+') ? (
<TrendingUp className="w-3.5 h-3.5" />
) : card.metricValue.includes('-') ? (
<TrendingDown className="w-3.5 h-3.5" />
) : null}
{card.metricValue}
</span>
</div>
)}
</div>
);
})}
</div>
)}
{!insights && !isLoading && (
<div className="card p-8 text-center">
<Lightbulb className="w-12 h-12 text-text-muted mx-auto mb-3" />
<p className="text-text-secondary"></p>
</div>
)}
</div>
);
}