32 lines
3.5 KiB
TypeScript
32 lines
3.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useMemo, useState } from "react";
|
|
import { ArrowLeft, CheckCircle2, Circle, ListChecks } from "lucide-react";
|
|
import { Progress } from "@/components/ui/progress";
|
|
import { TaskKey, plan } from "../plan";
|
|
|
|
type Rec = { doneTasks: string };
|
|
const groups: { key: TaskKey; title: string; hint: string; color: string }[] = [
|
|
{ key: "vocab", title: "词汇", hint: "积累与回炉高频词、搭配和错词", color: "blue" },
|
|
{ key: "listening", title: "听力", hint: "长对话、讲座、精听与模考", color: "violet" },
|
|
{ key: "reading", title: "阅读", hint: "仔细阅读、长篇匹配和错题复盘", color: "teal" },
|
|
{ key: "output", title: "输出", hint: "写作、翻译与可复用表达", color: "orange" },
|
|
{ key: "review", title: "复盘", hint: "记录错因、用时与下阶段重点", color: "rose" },
|
|
];
|
|
const parsed = (value?: string) => { try { return JSON.parse(value || "[]") as string[]; } catch { return []; } };
|
|
|
|
export default function TasksClient() {
|
|
const [records, setRecords] = useState<Record<string, Rec>>({});
|
|
const [active, setActive] = useState<TaskKey>("vocab");
|
|
const [status, setStatus] = useState("正在读取完成情况…");
|
|
useEffect(() => { fetch("/api/progress").then(async r => { const x = await r.json(); if (!r.ok) throw Error(x.error); const all: Record<string, Rec> = {}; x.progress.forEach((p: Rec & { planDate: string }) => all[p.planDate] = p); setRecords(all); setStatus("已同步云端进度"); }).catch(e => setStatus(e.message)); }, []);
|
|
const overview = useMemo(() => groups.map(group => { const tasks = plan.flatMap(day => day.tasks.filter(task => task.key === group.key).map(task => ({ ...task, date: day.date, week: day.week, phase: day.phase }))); const complete = tasks.filter(task => parsed(records[task.date]?.doneTasks).includes(group.key)).length; return { ...group, tasks, complete }; }), [records]);
|
|
const selected = overview.find(x => x.key === active)!;
|
|
return <main><div className="shell task-overview">
|
|
<header><div><p className="eyebrow">PLAN OVERVIEW</p><h1>任务总览</h1><p className="sub">按任务类型查看全部安排和完成情况。</p></div><a className="overview-link" href="/"><ArrowLeft size={17} />返回打卡</a></header>
|
|
<p className="overview-status">{status}</p>
|
|
<section className="type-cards">{overview.map(group => <button key={group.key} onClick={() => setActive(group.key)} className={"type-card " + group.color + (active === group.key ? " selected" : "")}><div><span>{group.title}</span><b>{group.complete} / {group.tasks.length}</b></div><Progress value={group.tasks.length ? group.complete / group.tasks.length * 100 : 0} /><small>{group.hint}</small></button>)}</section>
|
|
<section className="task-list-panel"><div className="task-list-head"><div><p className="eyebrow">{selected.title.toUpperCase()}</p><h2>{selected.title}任务</h2><p>{selected.hint}</p></div><div className="type-progress"><b>{selected.complete} / {selected.tasks.length}</b><span>已完成</span></div></div><div className="all-tasks">{selected.tasks.map((task, i) => { const complete = parsed(records[task.date]?.doneTasks).includes(active); return <a href="/" className={complete ? "task-row completed" : "task-row"} key={task.date + i}><span className="task-state">{complete ? <CheckCircle2 size={19} /> : <Circle size={19} />}</span><div><b>{task.date.slice(5).replace("-", "月")}日 · {task.week}</b><p>{task.text}</p></div><small>{task.phase}</small></a>; })}</div></section>
|
|
</div></main>;
|
|
}
|