import { useAuth } from "@/_core/hooks/useAuth"; import { trpc } from "@/lib/trpc"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; import { Button } from "@/components/ui/button"; import { Activity, Calendar, CheckCircle2, ChevronDown, ChevronUp, Clock, TrendingUp, Target, Sparkles } from "lucide-react"; import { formatDateTimeShanghai, formatMonthDayShanghai } from "@/lib/time"; import { useState } from "react"; import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, LineChart, Line, Legend } from "recharts"; import { useLocation } from "wouter"; const ACTION_LABEL_MAP: Record = { forehand: "正手挥拍", backhand: "反手挥拍", serve: "发球", volley: "截击", overhead: "高压", slice: "切削", lob: "挑高球", unknown: "未知动作", }; function getRecordMetadata(record: any) { if (!record?.metadata || typeof record.metadata !== "object") { return null; } return record.metadata as Record; } function getActionLabel(actionType: string) { return ACTION_LABEL_MAP[actionType] || actionType; } export default function Progress() { const { user } = useAuth(); const { data: records, isLoading } = trpc.record.list.useQuery({ limit: 100 }); const { data: analyses } = trpc.analysis.list.useQuery(); const { data: stats } = trpc.profile.stats.useQuery(); const [, setLocation] = useLocation(); const [expandedRecordId, setExpandedRecordId] = useState(null); if (isLoading) { return (
{[1, 2, 3].map(i => )}
); } // Aggregate data by date for charts const dateMap = new Map(); (records || []).forEach((r: any) => { const date = formatMonthDayShanghai(r.trainingDate || r.createdAt); const existing = dateMap.get(date) || { date, sessions: 0, minutes: 0, avgScore: 0, scores: [] }; existing.sessions++; existing.minutes += r.durationMinutes || 0; if (r.poseScore) existing.scores.push(r.poseScore); dateMap.set(date, existing); }); const chartData = Array.from(dateMap.values()).map(d => ({ ...d, avgScore: d.scores.length > 0 ? Math.round(d.scores.reduce((a, b) => a + b, 0) / d.scores.length) : 0, })); // Analysis score trend const scoreTrend = (analyses || []).map((a: any) => ({ date: formatMonthDayShanghai(a.createdAt), overall: Math.round(a.overallScore || 0), consistency: Math.round(a.strokeConsistency || 0), footwork: Math.round(a.footworkScore || 0), fluidity: Math.round(a.fluidityScore || 0), })); const completedRecords = (records || []).filter((r: any) => r.completed === 1); const totalMinutes = (records || []).reduce((sum: number, r: any) => sum + (r.durationMinutes || 0), 0); return (

训练进度

追踪您的训练历史和能力提升趋势

{/* Summary stats */}
总训练次数

{stats?.totalSessions || 0}

总训练时长

{totalMinutes}分钟

已完成

{completedRecords.length}

视频分析

{analyses?.length || 0}

实时分析

{stats?.recentLiveSessions?.length || 0}

{/* Training frequency chart */} 训练频率 {chartData.length > 0 ? ( ) : (

开始训练后将显示频率统计

)}
{/* Score improvement trend */} 能力提升趋势 {scoreTrend.length > 0 ? ( ) : (

完成视频分析后将显示能力趋势

)}
{/* Recent records */} 最近训练记录 {(records?.length || 0) > 0 ? (
{(records || []).slice(0, 20).map((record: any) => { const metadata = getRecordMetadata(record); const actionSummary = metadata?.actionSummary && typeof metadata.actionSummary === "object" ? Object.entries(metadata.actionSummary as Record).filter(([, count]) => Number(count) > 0) : []; const topActions = actionSummary .sort((left, right) => Number(right[1]) - Number(left[1])) .slice(0, 3); return (
{record.completed ? : }

{record.exerciseName}

{formatDateTimeShanghai(record.trainingDate || record.createdAt, { second: "2-digit" })} {record.durationMinutes ? ` · ${record.durationMinutes}分钟` : ""} {record.sourceType ? ` · ${record.sourceType}` : ""}

{record.actionCount ? ( 动作数 {record.actionCount} ) : null} {metadata?.dominantAction ? ( 主动作 {getActionLabel(String(metadata.dominantAction))} ) : null} {topActions.map(([actionType, count]) => ( {getActionLabel(actionType)} {count} 次 ))}
{record.poseScore && ( {Math.round(record.poseScore)}分 )} {record.completed ? ( 已完成 ) : ( 进行中 )}
{expandedRecordId === record.id ? (
记录时间
{formatDateTimeShanghai(record.trainingDate || record.createdAt, { second: "2-digit" })}
动作数据
动作数 {record.actionCount || 0}
{metadata ? (
{metadata.dominantAction ? (
主动作
{getActionLabel(String(metadata.dominantAction))}
) : null} {metadata.actionSummary && Object.keys(metadata.actionSummary).length > 0 ? (
动作明细
{Object.entries(metadata.actionSummary as Record) .filter(([, count]) => Number(count) > 0) .map(([actionType, count]) => ( {getActionLabel(actionType)} {count} 次 ))}
) : null} {metadata.validityStatus ? (
录制有效性
{String(metadata.validityStatus)}
{metadata.invalidReason ? (
{String(metadata.invalidReason)}
) : null}
) : null} {record.notes ? (
备注
{record.notes}
) : null}
) : null}
) : null}
); })}
) : (

还没有训练记录

)}
); }