"use client"; import { useEffect, useMemo, useState } from "react"; import { CalendarDays, CheckCircle2, ChevronLeft, ChevronRight, Cloud, LayoutList, Timer } from "lucide-react"; import { Checkbox } from "@/components/ui/checkbox"; import { Progress } from "@/components/ui/progress"; import { daysBetween, plan } from "./plan"; type Rec = { doneTasks: string; mood: string; note: string }; const fmt = (s: string) => new Intl.DateTimeFormat("zh-CN", { month: "long", day: "numeric", weekday: "short", timeZone: "UTC" }).format(new Date(s + "T00:00:00Z")); const parseTasks = (value?: string) => { try { return JSON.parse(value || "[]") as string[]; } catch { return []; } }; export default function CheckinClient() { const [selected, setSelected] = useState(Math.max(0, Math.min(89, daysBetween("2026-09-13")))); const [month, setMonth] = useState(8); const [records, setRecords] = useState>({}); const [status, setStatus] = useState("正在读取云端进度…"); const [saving, setSaving] = useState(false); const [now, setNow] = useState(Date.now()); const day = plan[selected], record = records[day.date] || { doneTasks: "[]", mood: "", note: "" }; const done = useMemo(() => parseTasks(record.doneTasks), [record.doneTasks]); const total = plan.reduce((n, p) => n + p.tasks.length, 0); const finished = Object.values(records).reduce((n, r) => n + parseTasks(r.doneTasks).length, 0); const complete = plan.filter(p => parseTasks(records[p.date]?.doneTasks).length === p.tasks.length).length; const blanks = Array(new Date(Date.UTC(2026, month, 1)).getUTCDay()).fill(null); const length = new Date(Date.UTC(2026, month + 1, 0)).getUTCDate(); const dates = Array.from({ length }, (_, i) => "2026-" + String(month + 1).padStart(2, "0") + "-" + String(i + 1).padStart(2, "0")); const seconds = Math.max(0, Math.floor((new Date("2026-12-12T09:00:00+08:00").getTime() - now) / 1000)); const remaining = { days: Math.floor(seconds / 86400), hours: Math.floor(seconds % 86400 / 3600), minutes: Math.floor(seconds % 3600 / 60) }; useEffect(() => { const id = window.setInterval(() => setNow(Date.now()), 30000); return () => window.clearInterval(id); }, []); useEffect(() => { fetch("/api/progress").then(async r => { const x = await r.json(); if (!r.ok) throw Error(x.error); const all: Record = {}; x.progress.forEach((p: Rec & { planDate: string }) => all[p.planDate] = p); setRecords(all); setStatus("云端同步已开启"); }).catch(e => setStatus(e.message)); }, []); async function save(next: Rec) { setRecords(v => ({ ...v, [day.date]: next })); setSaving(true); try { const r = await fetch("/api/progress", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ date: day.date, doneTasks: parseTasks(next.doneTasks), mood: next.mood, note: next.note }) }), x = await r.json(); if (!r.ok) throw Error(x.error); setRecords(v => ({ ...v, [day.date]: x.progress })); setStatus("已保存到云端"); } catch (e) { setStatus(e instanceof Error ? e.message : "保存失败"); } finally { setSaving(false); } } const toggle = (key: string, on: boolean) => void save({ ...record, doneTasks: JSON.stringify(on ? [...done, key] : done.filter(x => x !== key)) }); return

CET-6 · 2026

大学英语六级 · 90天打卡

从今天的一件事开始,完成比完美重要。

任务总览

{saving ? "正在云端保存…" : status}

} value={Math.round(finished / total * 100) + "%"} label="总体完成度" detail={finished + " / " + total + " 项任务"} />} value={complete + " 天"} label="完整完成" detail="目标共 90 天" />} value={day.phase} label="当前阶段" detail="12 月 12 日笔试" />

距离六级笔试

{remaining.days}天 {remaining.hours}时 {remaining.minutes}分2026 年 12 月 12 日 09:00

CHECK-IN CALENDAR

打卡日历

{[8, 9, 10, 11].map(m => )}
{["日", "一", "二", "三", "四", "五", "六"].map(x => {x})}
{blanks.map((_, i) => )}{dates.map(date => { const idx = plan.findIndex(p => p.date === date), p = plan[idx], n = parseTasks(records[date]?.doneTasks).length, full = !!p && n === p.tasks.length, partial = !!p && n > 0 && !full; return ; })}

全部完成 进行中 点击日期即可查看并打卡

{day.phase} · {day.week}

{fmt(day.date)}

已完成 {done.length} / {day.tasks.length} 项
{day.tasks.map(t => )}

今日状态

{["专注", "平稳", "有点累", "需要调整"].map(m => )}