Add study activity tracking and refresh class header UI
All checks were successful
Automated Container Build / build-and-push (push) Successful in 54s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 54s
This commit is contained in:
parent
95af276d2e
commit
dad62c9914
21 changed files with 1598 additions and 44 deletions
104
src/services/activityService.ts
Normal file
104
src/services/activityService.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import { prisma } from "@/lib/db";
|
||||
import type { StudyActivityType } from "@/lib/validation/activitySchemas";
|
||||
|
||||
const ARIZONA_OFFSET_MS = 7 * 60 * 60 * 1000;
|
||||
const WEEKS_TO_DISPLAY = 53;
|
||||
const DAYS_TO_DISPLAY = WEEKS_TO_DISPLAY * 7;
|
||||
const STREAK_THRESHOLD = 20;
|
||||
|
||||
export interface DailyActivity {
|
||||
date: string;
|
||||
flashcards: number;
|
||||
questions: number;
|
||||
total: number;
|
||||
level: 0 | 1 | 2 | 3 | 4;
|
||||
}
|
||||
|
||||
export interface ActivitySummary {
|
||||
days: DailyActivity[];
|
||||
today: string;
|
||||
activeDays: number;
|
||||
flashcards: number;
|
||||
questions: number;
|
||||
currentStreak: number;
|
||||
}
|
||||
|
||||
function toArizonaDateKey(date: Date): string {
|
||||
return new Date(date.getTime() - ARIZONA_OFFSET_MS).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function dateKeyToUtcDate(dateKey: string): Date {
|
||||
return new Date(`${dateKey}T00:00:00.000Z`);
|
||||
}
|
||||
|
||||
function addDays(dateKey: string, days: number): string {
|
||||
const date = dateKeyToUtcDate(dateKey);
|
||||
date.setUTCDate(date.getUTCDate() + days);
|
||||
return date.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function getLevel(total: number): 0 | 1 | 2 | 3 | 4 {
|
||||
if (total === 0) return 0;
|
||||
if (total < 20) return 1;
|
||||
if (total < 50) return 2;
|
||||
if (total < 100) return 3;
|
||||
return 4;
|
||||
}
|
||||
|
||||
function emptyDay(date: string): DailyActivity {
|
||||
return { date, flashcards: 0, questions: 0, total: 0, level: 0 };
|
||||
}
|
||||
|
||||
export async function recordActivity(type: StudyActivityType) {
|
||||
return prisma.studyActivity.create({ data: { type } });
|
||||
}
|
||||
|
||||
export async function getActivitySummary(now = new Date()): Promise<ActivitySummary> {
|
||||
const today = toArizonaDateKey(now);
|
||||
const todayDate = dateKeyToUtcDate(today);
|
||||
const dayOfWeek = todayDate.getUTCDay();
|
||||
const startDate = addDays(today, -(dayOfWeek + (WEEKS_TO_DISPLAY - 1) * 7));
|
||||
const endDate = addDays(startDate, DAYS_TO_DISPLAY - 1);
|
||||
|
||||
const activities = await prisma.studyActivity.findMany({
|
||||
select: { type: true, occurredAt: true },
|
||||
orderBy: { occurredAt: "asc" },
|
||||
});
|
||||
const totals = new Map<string, DailyActivity>();
|
||||
|
||||
for (const activity of activities) {
|
||||
const date = toArizonaDateKey(activity.occurredAt);
|
||||
const day = totals.get(date) ?? emptyDay(date);
|
||||
if (activity.type === "FLASHCARD") day.flashcards += 1;
|
||||
if (activity.type === "QUIZ_QUESTION") day.questions += 1;
|
||||
day.total += 1;
|
||||
day.level = getLevel(day.total);
|
||||
totals.set(date, day);
|
||||
}
|
||||
|
||||
const days = Array.from({ length: DAYS_TO_DISPLAY }, (_, index) => {
|
||||
const date = addDays(startDate, index);
|
||||
return totals.get(date) ?? emptyDay(date);
|
||||
});
|
||||
const visibleDays = days.filter((day) => day.date <= today && day.date <= endDate);
|
||||
const flashcards = visibleDays.reduce((sum, day) => sum + day.flashcards, 0);
|
||||
const questions = visibleDays.reduce((sum, day) => sum + day.questions, 0);
|
||||
|
||||
let streakDate = (totals.get(today)?.total ?? 0) >= STREAK_THRESHOLD
|
||||
? today
|
||||
: addDays(today, -1);
|
||||
let currentStreak = 0;
|
||||
while ((totals.get(streakDate)?.total ?? 0) >= STREAK_THRESHOLD) {
|
||||
currentStreak += 1;
|
||||
streakDate = addDays(streakDate, -1);
|
||||
}
|
||||
|
||||
return {
|
||||
days,
|
||||
today,
|
||||
activeDays: visibleDays.filter((day) => day.total > 0).length,
|
||||
flashcards,
|
||||
questions,
|
||||
currentStreak,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue