feat: implement 52-week annual curriculum system with admin management and automated topic progression
This commit is contained in:
@@ -6,6 +6,7 @@ import Button from '../components/ui/Button';
|
||||
import Tag from '../components/ui/Tag';
|
||||
import * as db from '../lib/db';
|
||||
import { getAssignedTopic } from '../lib/learningService';
|
||||
import { getYearProgress, getQuarterName, getQuarterForWeek, hasCurriculum as checkHasCurriculum } from '../lib/curriculumService';
|
||||
|
||||
const Dashboard = () => {
|
||||
const { state } = useApp();
|
||||
@@ -19,6 +20,8 @@ const Dashboard = () => {
|
||||
myRank: 0,
|
||||
myPoints: 0,
|
||||
activity: [],
|
||||
yearProgress: null,
|
||||
hasCurriculum: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -50,27 +53,103 @@ const Dashboard = () => {
|
||||
if (pastLearn) activity.push({ type: 'learn', week: w, topic: pastTopic?.label });
|
||||
}
|
||||
|
||||
setDashData({ topic, learnDone, testResult, top3, myRank, myPoints, activity });
|
||||
// Load curriculum progress
|
||||
let yearProgress = null;
|
||||
let curriculumExists = false;
|
||||
try {
|
||||
curriculumExists = await checkHasCurriculum();
|
||||
if (curriculumExists) {
|
||||
yearProgress = await getYearProgress(currentUser.id);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Dashboard] Could not load curriculum data:', e.message);
|
||||
}
|
||||
|
||||
setDashData({ topic, learnDone, testResult, top3, myRank, myPoints, activity, yearProgress, hasCurriculum: curriculumExists });
|
||||
};
|
||||
|
||||
load();
|
||||
}, [currentUser, weekNumber]);
|
||||
|
||||
const { topic, learnDone, testResult, top3, myRank, myPoints, activity } = dashData;
|
||||
const { topic, learnDone, testResult, top3, myRank, myPoints, activity, yearProgress, hasCurriculum: curriculumActive } = dashData;
|
||||
const currentQuarter = getQuarterForWeek(weekNumber);
|
||||
const quarterName = getQuarterName(weekNumber);
|
||||
|
||||
return (
|
||||
<div className="p-6 md:p-10 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||
<header>
|
||||
<h1 className="text-3xl md:text-5xl mb-2">Welcome, {currentUser?.name}</h1>
|
||||
<p className="text-fg-muted text-lg">Here is your overview for week {weekNumber}.</p>
|
||||
<p className="text-fg-muted text-lg">
|
||||
{curriculumActive
|
||||
? `Week ${weekNumber} · ${quarterName}`
|
||||
: `Here is your overview for week ${weekNumber}.`}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{/* Annual Progress Bar (only when curriculum exists) */}
|
||||
{curriculumActive && yearProgress && (
|
||||
<Card className="border border-bg-warm">
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="relative w-14 h-14 flex-shrink-0">
|
||||
<svg viewBox="0 0 36 36" className="w-full h-full -rotate-90">
|
||||
<circle cx="18" cy="18" r="15.5" fill="none" stroke="var(--color-bg-warm)" strokeWidth="3" />
|
||||
<circle
|
||||
cx="18" cy="18" r="15.5" fill="none"
|
||||
stroke="var(--color-teal)" strokeWidth="3"
|
||||
strokeDasharray={`${yearProgress.percentage} 100`}
|
||||
strokeLinecap="round"
|
||||
className="transition-all duration-700"
|
||||
/>
|
||||
</svg>
|
||||
<span className="absolute inset-0 flex items-center justify-center text-xs font-bold">
|
||||
{yearProgress.percentage}%
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-bold text-lg">Annual Progress</h3>
|
||||
<p className="text-sm text-fg-muted">{yearProgress.completed} of {yearProgress.total} weeks completed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Tag variant="dark" className="text-xs">Q{currentQuarter}</Tag>
|
||||
<span className="text-sm text-fg-muted">{52 - weekNumber} weeks remaining</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Visual week progress bar */}
|
||||
<div className="mt-4 flex gap-[2px] h-2 rounded-full overflow-hidden">
|
||||
{Array.from({ length: 52 }, (_, i) => {
|
||||
const w = i + 1;
|
||||
const isCurrent = w === weekNumber;
|
||||
const isPast = w < weekNumber;
|
||||
return (
|
||||
<div
|
||||
key={w}
|
||||
className="flex-1 rounded-sm transition-all"
|
||||
style={{
|
||||
backgroundColor: isCurrent
|
||||
? 'var(--color-teal)'
|
||||
: isPast
|
||||
? 'var(--color-teal)'
|
||||
: 'var(--color-bg-warm)',
|
||||
opacity: isPast ? 0.4 : 1,
|
||||
}}
|
||||
title={`Week ${w}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Card className="flex flex-col border border-bg-warm" hoverable>
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div>
|
||||
<h3 className="text-xl">Learning</h3>
|
||||
<p className="text-fg-muted text-sm mt-1">Your topic this week:</p>
|
||||
<p className="text-fg-muted text-sm mt-1">
|
||||
{curriculumActive ? `Week ${weekNumber} topic:` : 'Your topic this week:'}
|
||||
</p>
|
||||
</div>
|
||||
{learnDone ? <Tag variant="success">Completed</Tag> : <Tag variant="accent">To Do</Tag>}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user