'use client'; import { useEffect, useState, useMemo } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { ProgressRing } from '@/components/progress-ring'; import { calculateProgress, isKRActive } from '@/lib/kr-utils'; import { REASON_CODES } from '@/lib/constants'; import type { Project, KR, User, Checkin } from '@/types/database'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, LineChart, Line, Legend, } from 'recharts'; import { BarChart3, PieChart as PieChartIcon, TrendingUp, Users, Target } from 'lucide-react'; import { fetchApi } from '@/lib/api-client'; interface KRWithRelations extends KR { owner: User | null; project: { id: string; name: string } | null; } interface CheckinWithRelations extends Checkin { user: User | null; kr: { id: string; title: string } | null; } const COLORS = ['#10b981', '#f59e0b', '#ef4444', '#6366f1', '#8b5cf6', '#ec4899']; export default function AnalysisPage() { const [projects, setProjects] = useState([]); const [krs, setKRs] = useState([]); const [checkins, setCheckins] = useState([]); const [users, setUsers] = useState([]); const [selectedProjectId, setSelectedProjectId] = useState('all'); const [isLoading, setIsLoading] = useState(true); useEffect(() => { async function fetchData() { try { const [projectsRes, krsRes, checkinsRes, usersRes] = await Promise.all([ fetchApi('/api/projects'), fetchApi('/api/krs'), fetchApi('/api/checkins?limit=100'), fetchApi('/api/users'), ]); if (projectsRes.ok) setProjects(await projectsRes.json()); if (krsRes.ok) setKRs(await krsRes.json()); if (checkinsRes.ok) setCheckins(await checkinsRes.json()); if (usersRes.ok) setUsers(await usersRes.json()); } catch (error) { console.error('Failed to fetch data:', error); } finally { setIsLoading(false); } } fetchData(); }, []); const filteredKRs = useMemo(() => { if (selectedProjectId === 'all') return krs; return krs.filter(kr => kr.project_id === selectedProjectId); }, [krs, selectedProjectId]); const activeKRs = filteredKRs.filter(isKRActive); const statusDistribution = useMemo(() => { const green = activeKRs.filter(kr => kr.status === 'green').length; const yellow = activeKRs.filter(kr => kr.status === 'yellow').length; const red = activeKRs.filter(kr => kr.status === 'red').length; return [ { name: '正常', value: green, color: '#10b981' }, { name: '警告', value: yellow, color: '#f59e0b' }, { name: '风险', value: red, color: '#ef4444' }, ].filter(item => item.value > 0); }, [activeKRs]); const reasonDistribution = useMemo(() => { const reasons: Record = {}; activeKRs .filter(kr => kr.reason_code) .forEach(kr => { const code = kr.reason_code!; reasons[code] = (reasons[code] || 0) + 1; }); return Object.entries(reasons).map(([code, count]) => ({ name: REASON_CODES[code as keyof typeof REASON_CODES], value: count, })); }, [activeKRs]); const userPerformance = useMemo(() => { const userMap: Record = {}; users.forEach(user => { userMap[user.id] = { name: user.name, total: 0, completed: 0 }; }); activeKRs.forEach(kr => { if (kr.owner_user_id && userMap[kr.owner_user_id]) { userMap[kr.owner_user_id].total++; if (calculateProgress(kr) >= 1) { userMap[kr.owner_user_id].completed++; } } }); return Object.values(userMap) .filter(u => u.total > 0) .map(u => ({ name: u.name, total: u.total, completed: u.completed, rate: Math.round((u.completed / u.total) * 100), })) .sort((a, b) => b.rate - a.rate); }, [activeKRs, users]); const projectHealth = useMemo(() => { return projects.map(project => { const projectKRs = krs.filter(kr => kr.project_id === project.id && isKRActive(kr)); const completed = projectKRs.filter(kr => calculateProgress(kr) >= 1).length; const total = projectKRs.length; return { name: project.name.length > 10 ? project.name.substring(0, 10) + '...' : project.name, fullName: project.name, total, completed, rate: total > 0 ? Math.round((completed / total) * 100) : 0, status: project.status, }; }); }, [projects, krs]); const checkinTrend = useMemo(() => { const last7Days: Record = {}; for (let i = 6; i >= 0; i--) { const date = new Date(); date.setDate(date.getDate() - i); const dateStr = date.toISOString().split('T')[0]; last7Days[dateStr] = 0; } checkins.forEach(checkin => { const dateStr = checkin.date; if (last7Days[dateStr] !== undefined) { last7Days[dateStr]++; } }); return Object.entries(last7Days).map(([date, count]) => ({ date: new Date(date).toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' }), count, })); }, [checkins]); if (isLoading) { return (
{[1, 2, 3, 4].map(i => (
))}
); } return (

数据分析

KR 执行情况分析与洞察

{activeKRs.length}

进行中 KR

{activeKRs.filter(kr => calculateProgress(kr) >= 1).length}

已达标

{userPerformance.length}

参与人数

{activeKRs.length > 0 ? Math.round( (activeKRs.filter(kr => calculateProgress(kr) >= 1).length / activeKRs.length) * 100 ) : 0}%

整体完成率

KR 状态分布 {statusDistribution.length > 0 ? ( `${name}: ${value}`} > {statusDistribution.map((entry, index) => ( ))} ) : (

暂无数据

)}
问题原因分布 {reasonDistribution.length > 0 ? ( ) : (

暂无问题记录

)}
项目完成率 {projectHealth.length > 0 ? ( { if (active && payload && payload.length) { const data = payload[0].payload; return (

{data.fullName}

完成: {data.completed}/{data.total} ({data.rate}%)

); } return null; }} />
) : (

暂无数据

)}
打卡趋势 (近7天)
成员完成率排行 {userPerformance.length > 0 ? (
{userPerformance.map((user, index) => (
{index + 1}
{user.name} {user.completed}/{user.total} ({user.rate}%)
))}
) : (

暂无数据

)}
); }