added demo stats page (needs refinement)

This commit is contained in:
George Powell
2026-02-04 23:35:23 -05:00
parent 4c82aa078b
commit 7d93ead70c
4 changed files with 434 additions and 0 deletions

View File

@@ -0,0 +1,149 @@
import { db } from '$lib/server/db';
import { dailyCompletions, type DailyCompletion } from '$lib/server/db/schema';
import { eq, desc } from 'drizzle-orm';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ url }) => {
const anonymousId = url.searchParams.get('anonymousId');
if (!anonymousId) {
return {
stats: null,
error: 'No anonymous ID provided'
};
}
try {
// Get all completions for this user
const completions = await db
.select()
.from(dailyCompletions)
.where(eq(dailyCompletions.anonymousId, anonymousId))
.orderBy(desc(dailyCompletions.date));
if (completions.length === 0) {
return {
stats: {
totalSolves: 0,
avgGuesses: 0,
gradeDistribution: {
'S++': 0,
'S+': 0,
'A+': 0,
'A': 0,
'B+': 0,
'B': 0,
'C+': 0,
'C': 0
},
currentStreak: 0,
bestStreak: 0,
recentCompletions: []
}
};
}
// Calculate basic stats
const totalSolves = completions.length;
const totalGuesses = completions.reduce((sum: number, c: DailyCompletion) => sum + c.guessCount, 0);
const avgGuesses = Math.round((totalGuesses / totalSolves) * 100) / 100;
// Calculate grade distribution
const gradeDistribution = {
'S++': 0, // This will be calculated differently since we don't store chapter correctness
'S+': completions.filter((c: DailyCompletion) => c.guessCount === 1).length,
'A+': completions.filter((c: DailyCompletion) => c.guessCount === 2).length,
'A': completions.filter((c: DailyCompletion) => c.guessCount === 3).length,
'B+': completions.filter((c: DailyCompletion) => c.guessCount >= 4 && c.guessCount <= 6).length,
'B': completions.filter((c: DailyCompletion) => c.guessCount >= 7 && c.guessCount <= 10).length,
'C+': completions.filter((c: DailyCompletion) => c.guessCount >= 11 && c.guessCount <= 15).length,
'C': completions.filter((c: DailyCompletion) => c.guessCount > 15).length
};
// Calculate streaks
const sortedDates = completions
.map((c: DailyCompletion) => c.date)
.sort();
let currentStreak = 0;
let bestStreak = 0;
let tempStreak = 1;
if (sortedDates.length > 0) {
// Check if current streak is active (includes today or yesterday)
const today = new Date().toISOString().split('T')[0];
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0];
const lastPlayedDate = sortedDates[sortedDates.length - 1];
if (lastPlayedDate === today || lastPlayedDate === yesterday) {
currentStreak = 1;
// Count backwards from the most recent date
for (let i = sortedDates.length - 2; i >= 0; i--) {
const currentDate = new Date(sortedDates[i + 1]);
const prevDate = new Date(sortedDates[i]);
const daysDiff = Math.floor((currentDate.getTime() - prevDate.getTime()) / (1000 * 60 * 60 * 24));
if (daysDiff === 1) {
currentStreak++;
} else {
break;
}
}
}
// Calculate best streak
bestStreak = 1;
for (let i = 1; i < sortedDates.length; i++) {
const currentDate = new Date(sortedDates[i]);
const prevDate = new Date(sortedDates[i - 1]);
const daysDiff = Math.floor((currentDate.getTime() - prevDate.getTime()) / (1000 * 60 * 60 * 24));
if (daysDiff === 1) {
tempStreak++;
} else {
bestStreak = Math.max(bestStreak, tempStreak);
tempStreak = 1;
}
}
bestStreak = Math.max(bestStreak, tempStreak);
}
// Get recent completions (last 7 days)
const recentCompletions = completions
.slice(0, 7)
.map((c: DailyCompletion) => ({
date: c.date,
guessCount: c.guessCount,
grade: getGradeFromGuesses(c.guessCount)
}));
return {
stats: {
totalSolves,
avgGuesses,
gradeDistribution,
currentStreak,
bestStreak,
recentCompletions
}
};
} catch (error) {
console.error('Error fetching user stats:', error);
return {
stats: null,
error: 'Failed to fetch stats'
};
}
};
function getGradeFromGuesses(guessCount: number): string {
if (guessCount === 1) return "S+";
if (guessCount === 2) return "A+";
if (guessCount === 3) return "A";
if (guessCount >= 4 && guessCount <= 6) return "B+";
if (guessCount >= 7 && guessCount <= 10) return "B";
if (guessCount >= 11 && guessCount <= 15) return "C+";
return "C";
}