From f613e25407de9485a8728489476d0d625b1dc9de Mon Sep 17 00:00:00 2001 From: biss Date: Wed, 16 Sep 2026 21:27:29 +0800 Subject: [PATCH] Add exam countdown and task overview --- app/checkin-client.tsx | 57 +++++++++++++++++++++++++++++--------- app/globals.css | 5 ++++ app/tasks/page.tsx | 3 ++ app/tasks/tasks-client.tsx | 32 +++++++++++++++++++++ 4 files changed, 84 insertions(+), 13 deletions(-) create mode 100644 app/tasks/page.tsx create mode 100644 app/tasks/tasks-client.tsx diff --git a/app/checkin-client.tsx b/app/checkin-client.tsx index f091913..3bed7f4 100644 --- a/app/checkin-client.tsx +++ b/app/checkin-client.tsx @@ -1,14 +1,45 @@ "use client"; -import {useEffect,useMemo,useState} from "react"; -import {CalendarDays,CheckCircle2,Cloud,ChevronLeft,ChevronRight} 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")); -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 day=plan[selected],record=records[day.date]||{doneTasks:"[]",mood:"",note:""};const done=useMemo(()=>{try{return JSON.parse(record.doneTasks)as string[]}catch{return[]}},[record.doneTasks]);const total=plan.reduce((n,p)=>n+p.tasks.length,0),finished=Object.values(records).reduce((n,r)=>{try{return n+JSON.parse(r.doneTasks).length}catch{return n}},0);const complete=plan.filter(p=>{try{return JSON.parse(records[p.date]?.doneTasks||"[]").length===p.tasks.length}catch{return false}}).length;const blanks=Array(new Date(Date.UTC(2026,month,1)).getUTCDay()).fill(null),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")); -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:JSON.parse(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 日笔试"/>

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];let n=0;try{n=JSON.parse(records[date]?.doneTasks||"[]").length}catch{}const 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=>)}