feat: implement 52-week annual curriculum system with admin management and automated topic progression

This commit is contained in:
RaymondVerhoef
2026-05-18 19:49:05 +02:00
parent 228d0d7a54
commit 08f5b1fe18
10 changed files with 945 additions and 16 deletions

View File

@@ -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>