learning 1. Juli 2024
Learning Progress Tracker
React Hook für Lernfortschritt-Tracking mit localStorage Persistenz und Statistiken.
reacthookstypescriptlocalStorage
useProgress Hook
Custom React Hook der den Lernfortschritt trackt, in localStorage persistiert, und Statistiken berechnet.
import { useState, useEffect, useCallback } from 'react';
interface ProgressEntry {
moduleId: string;
completedLessons: string[];
totalLessons: number;
lastAccessed: string;
timeSpentMinutes: number;
}
interface ProgressStats {
totalModules: number;
completedModules: number;
overallPercent: number;
totalTimeHours: number;
streak: number;
}
const STORAGE_KEY = 'learning_progress';
export function useProgress() {
const [progress, setProgress] = useState<Record<string, ProgressEntry>>({});
useEffect(() => {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
setProgress(JSON.parse(stored));
}
}, []);
const save = useCallback((data: Record<string, ProgressEntry>) => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
setProgress(data);
}, []);
const completeLesson = useCallback((moduleId: string, lessonId: string, totalLessons: number) => {
setProgress((prev) => {
const entry = prev[moduleId] || {
moduleId,
completedLessons: [],
totalLessons,
lastAccessed: new Date().toISOString(),
timeSpentMinutes: 0,
};
if (entry.completedLessons.includes(lessonId)) return prev;
const updated = {
...prev,
[moduleId]: {
...entry,
completedLessons: [...entry.completedLessons, lessonId],
lastAccessed: new Date().toISOString(),
},
};
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
return updated;
});
}, []);
const addTime = useCallback((moduleId: string, minutes: number) => {
setProgress((prev) => {
const entry = prev[moduleId];
if (!entry) return prev;
const updated = {
...prev,
[moduleId]: { ...entry, timeSpentMinutes: entry.timeSpentMinutes + minutes },
};
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
return updated;
});
}, []);
const getStats = useCallback((): ProgressStats => {
const entries = Object.values(progress);
const completedModules = entries.filter(
(e) => e.completedLessons.length >= e.totalLessons
).length;
const totalLessons = entries.reduce((sum, e) => sum + e.totalLessons, 0);
const completedLessons = entries.reduce((sum, e) => sum + e.completedLessons.length, 0);
return {
totalModules: entries.length,
completedModules,
overallPercent: totalLessons > 0 ? Math.round((completedLessons / totalLessons) * 100) : 0,
totalTimeHours: Math.round(entries.reduce((sum, e) => sum + e.timeSpentMinutes, 0) / 60 * 10) / 10,
streak: calculateStreak(entries),
};
}, [progress]);
return { progress, completeLesson, addTime, getStats, reset: () => save({}) };
}
function calculateStreak(entries: ProgressEntry[]): number {
const dates = entries
.map((e) => e.lastAccessed.split('T')[0])
.sort()
.reverse();
const unique = [...new Set(dates)];
let streak = 0;
const today = new Date();
for (let i = 0; i < unique.length; i++) {
const expected = new Date(today);
expected.setDate(expected.getDate() - i);
const expectedStr = expected.toISOString().split('T')[0];
if (unique[i] === expectedStr) {
streak++;
} else {
break;
}
}
return streak;
}