"use client"; import { useEffect, useMemo, useState } from "react"; import { ArrowLeft, Download, Save, Timer, Trophy } from "lucide-react"; import { plan, TaskKey } from "../plan"; type Progress = { planDate: string; doneTasks: string }; type Study = { planDate: string; durations: string; weeklyReview: string; nextFocus: string }; type Mock = { examDate: string; listening: string; reading: string; writing: string; translation: string; note: string }; const keys: { key: TaskKey; label: string }[] = [{ key: "vocab", label: "词汇" }, { key: "listening", label: "听力" }, { key: "reading", label: "阅读" }, { key: "output", label: "输出" }, { key: "review", label: "复盘" }]; const day = () => new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Shanghai" }).format(new Date()); const parse = (v?: string) => { try { return JSON.parse(v || "{}") as Record; } catch { return {}; } }; const checked = (v?: string) => { try { return JSON.parse(v || "[]") as string[]; } catch { return []; } }; const initialMock = { listening: "", reading: "", writing: "", translation: "", note: "" }; export default function ReviewClient() { const [progress, setProgress] = useState>({}), [studies, setStudies] = useState>({}), [mocks, setMocks] = useState([]), [date, setDate] = useState(day()), [durations, setDurations] = useState>({}), [review, setReview] = useState(""), [focus, setFocus] = useState(""), [mock, setMock] = useState(initialMock), [status, setStatus] = useState("正在读取学习中心…"); useEffect(() => { Promise.all([fetch("/api/progress").then(r => r.json()), fetch("/api/study").then(r => r.json())]).then(([p, s]) => { if (p.error || s.error) throw Error(p.error || s.error); setProgress(Object.fromEntries(p.progress.map((x: Progress) => [x.planDate, x]))); const all = Object.fromEntries(s.studyDays.map((x: Study) => [x.planDate, x])); setStudies(all); setMocks(s.mockExams); const current = all[date]; if (current) { setDurations(parse(current.durations)); setReview(current.weeklyReview); setFocus(current.nextFocus); } setStatus("云端数据已同步"); }).catch(e => setStatus(e.message)); }, []); const insight = useMemo(() => { const past = plan.filter(x => x.date < day()), future = plan.filter(x => x.date >= day()); const missing = past.reduce((n, x) => n + x.tasks.filter(t => !checked(progress[x.date]?.doneTasks).includes(t.key)).length, 0); const extra = future.length ? Math.ceil(missing / future.length) : missing; const week = plan.find(x => x.date === date)?.week || "本周"; return { missing, extra, week }; }, [progress, date]); const total = ([mock.listening, mock.reading, mock.writing, mock.translation].reduce((n, x) => n + (Number(x) || 0), 0)); const saveDay = async () => { setStatus("正在保存…"); const r = await fetch("/api/study", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ type: "day", date, durations, weeklyReview: review, nextFocus: focus }) }); const x = await r.json(); if (!r.ok) return setStatus(x.error); setStudies(v => ({ ...v, [date]: x.studyDay })); setStatus("学习记录已保存"); }; const saveMock = async () => { setStatus("正在保存模考…"); const r = await fetch("/api/study", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ type: "mock", date, ...mock }) }); const x = await r.json(); if (!r.ok) return setStatus(x.error); setMocks(v => [...v.filter(m => m.examDate !== date), x.mockExam].sort((a, b) => a.examDate.localeCompare(b.examDate))); setStatus("模考记录已保存"); }; const exportData = () => { const rows = ["日期,词汇分钟,听力分钟,阅读分钟,输出分钟,复盘分钟,周复盘,下周重点", ...Object.values(studies).sort((a,b)=>a.planDate.localeCompare(b.planDate)).map(x => { const d = parse(x.durations); return [x.planDate, ...keys.map(k => d[k.key] || 0), x.weeklyReview, x.nextFocus].map(v => `"${String(v).replaceAll('"','""')}"`).join(","); }), "", "模考日期,听力,阅读,写作,翻译,总分,备注", ...mocks.map(x => [x.examDate,x.listening,x.reading,x.writing,x.translation,[x.listening,x.reading,x.writing,x.translation].reduce((n,v)=>n+(Number(v)||0),0),x.note].map(v => `"${String(v).replaceAll('"','""')}"`).join(","))]; const url = URL.createObjectURL(new Blob([rows.join("\n")], { type: "text/csv;charset=utf-8" })); const a = document.createElement("a"); a.href = url; a.download = "cet6-study-record.csv"; a.click(); URL.revokeObjectURL(url); }; return

STUDY HUB

复盘与冲刺

记录投入、复盘阶段成果,再把计划推进到下一天。

{status}

PLAN PULSE

计划调整建议

{insight.missing} 项

历史日期仍有未完成任务。若要在原计划结束前补齐,建议未来每天额外完成 {insight.extra} 项;优先补“复盘”和当天同类型任务。

这是顺延建议,不会自动改动原计划。

DAILY TIME

今日用时

{keys.map(x => )}

WEEKLY REVIEW · {insight.week}

本周复盘