'use client' import React, { createContext, useContext, useState, useCallback, useEffect, useRef } from 'react' /** * Activity Tracking System * * Tracks user activities and calculates time savings compared to manual work. * Used for the "Zeitersparnis" dashboard tile and weekly reports. */ // ============================================================================= // TYPES // ============================================================================= export type ActivityType = | 'vocab_extraction' | 'klausur_correction' | 'worksheet_creation' | 'fairness_check' | 'gutachten_generation' export interface ActivityMetadata { vocabCount?: number studentCount?: number criteriaCount?: number elementCount?: number pageCount?: number description?: string } export interface ActivityLog { id: string type: ActivityType startedAt: string // ISO string for serialization completedAt: string actualSeconds: number estimatedManualSeconds: number savedSeconds: number metadata: ActivityMetadata } export interface ActivityStats { todaySavedSeconds: number weekSavedSeconds: number monthSavedSeconds: number totalSavedSeconds: number activityCount: number lastActivity: ActivityLog | null } interface ActiveActivity { type: ActivityType startedAt: Date metadata: ActivityMetadata } interface ActivityContextType { // Start tracking an activity startActivity: (type: ActivityType, metadata?: ActivityMetadata) => void // Complete the current activity completeActivity: (additionalMetadata?: ActivityMetadata) => ActivityLog | null // Cancel without saving cancelActivity: () => void // Check if currently tracking isTracking: boolean currentActivity: ActiveActivity | null // Get statistics stats: ActivityStats // Get recent activities recentActivities: ActivityLog[] // Refresh stats from storage refreshStats: () => void } // ============================================================================= // ESTIMATION FORMULAS // ============================================================================= /** * Calculate estimated manual time based on activity type and metadata */ function calculateManualEstimate(type: ActivityType, metadata: ActivityMetadata): number { switch (type) { case 'vocab_extraction': { // Typing: ~25 seconds per vocab (word + translation + example) // Layout: 15 minutes base const vocabCount = metadata.vocabCount || 20 return (vocabCount * 25) + (15 * 60) } case 'klausur_correction': { // Reading + grading + gutachten: ~45 minutes per student const studentCount = metadata.studentCount || 1 return studentCount * 45 * 60 } case 'worksheet_creation': { // Design + formatting: ~30 seconds per element + 20 min base const elementCount = metadata.elementCount || 10 return (elementCount * 30) + (20 * 60) } case 'fairness_check': { // Manual comparison: ~5 minutes per student const studentCount = metadata.studentCount || 10 return studentCount * 5 * 60 } case 'gutachten_generation': { // Writing gutachten manually: ~15 minutes per student const studentCount = metadata.studentCount || 1 return studentCount * 15 * 60 } default: return 10 * 60 // 10 minutes default } } // ============================================================================= // STORAGE // ============================================================================= const STORAGE_KEY = 'breakpilot_activity_logs' function loadActivities(): ActivityLog[] { if (typeof window === 'undefined') return [] try { const stored = localStorage.getItem(STORAGE_KEY) return stored ? JSON.parse(stored) : [] } catch { return [] } } function saveActivities(activities: ActivityLog[]): void { if (typeof window === 'undefined') return try { // Keep only last 1000 activities to prevent storage overflow const trimmed = activities.slice(-1000) localStorage.setItem(STORAGE_KEY, JSON.stringify(trimmed)) } catch (e) { console.error('Failed to save activities:', e) } } function addActivity(activity: ActivityLog): void { const activities = loadActivities() activities.push(activity) saveActivities(activities) } // ============================================================================= // STATISTICS CALCULATION // ============================================================================= function calculateStats(activities: ActivityLog[]): ActivityStats { const now = new Date() const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()) const weekStart = new Date(todayStart) weekStart.setDate(weekStart.getDate() - weekStart.getDay() + 1) // Monday const monthStart = new Date(now.getFullYear(), now.getMonth(), 1) let todaySavedSeconds = 0 let weekSavedSeconds = 0 let monthSavedSeconds = 0 let totalSavedSeconds = 0 for (const activity of activities) { const activityDate = new Date(activity.completedAt) totalSavedSeconds += activity.savedSeconds if (activityDate >= monthStart) { monthSavedSeconds += activity.savedSeconds } if (activityDate >= weekStart) { weekSavedSeconds += activity.savedSeconds } if (activityDate >= todayStart) { todaySavedSeconds += activity.savedSeconds } } const lastActivity = activities.length > 0 ? activities[activities.length - 1] : null return { todaySavedSeconds, weekSavedSeconds, monthSavedSeconds, totalSavedSeconds, activityCount: activities.length, lastActivity, } } // ============================================================================= // CONTEXT // ============================================================================= const ActivityContext = createContext(null) export function ActivityProvider({ children }: { children: React.ReactNode }) { const [currentActivity, setCurrentActivity] = useState(null) const [stats, setStats] = useState({ todaySavedSeconds: 0, weekSavedSeconds: 0, monthSavedSeconds: 0, totalSavedSeconds: 0, activityCount: 0, lastActivity: null, }) const [recentActivities, setRecentActivities] = useState([]) // Load initial stats useEffect(() => { refreshStats() }, []) const refreshStats = useCallback(() => { const activities = loadActivities() setStats(calculateStats(activities)) setRecentActivities(activities.slice(-10).reverse()) }, []) const startActivity = useCallback((type: ActivityType, metadata: ActivityMetadata = {}) => { setCurrentActivity({ type, startedAt: new Date(), metadata, }) }, []) const completeActivity = useCallback((additionalMetadata: ActivityMetadata = {}): ActivityLog | null => { if (!currentActivity) return null const completedAt = new Date() const actualSeconds = Math.round((completedAt.getTime() - currentActivity.startedAt.getTime()) / 1000) const mergedMetadata = { ...currentActivity.metadata, ...additionalMetadata } const estimatedManualSeconds = calculateManualEstimate(currentActivity.type, mergedMetadata) const savedSeconds = Math.max(0, estimatedManualSeconds - actualSeconds) const log: ActivityLog = { id: `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`, type: currentActivity.type, startedAt: currentActivity.startedAt.toISOString(), completedAt: completedAt.toISOString(), actualSeconds, estimatedManualSeconds, savedSeconds, metadata: mergedMetadata, } addActivity(log) setCurrentActivity(null) refreshStats() return log }, [currentActivity, refreshStats]) const cancelActivity = useCallback(() => { setCurrentActivity(null) }, []) return ( {children} ) } export function useActivity() { const context = useContext(ActivityContext) if (!context) { // Return a no-op version if not wrapped in provider return { startActivity: () => {}, completeActivity: () => null, cancelActivity: () => {}, isTracking: false, currentActivity: null, stats: { todaySavedSeconds: 0, weekSavedSeconds: 0, monthSavedSeconds: 0, totalSavedSeconds: 0, activityCount: 0, lastActivity: null, }, recentActivities: [], refreshStats: () => {}, } } return context } // ============================================================================= // UTILITY FUNCTIONS // ============================================================================= /** * Format seconds as human-readable duration */ export function formatDuration(seconds: number): string { if (seconds < 60) { return `${seconds}s` } const hours = Math.floor(seconds / 3600) const minutes = Math.floor((seconds % 3600) / 60) if (hours > 0) { return minutes > 0 ? `${hours}h ${minutes}min` : `${hours}h` } return `${minutes}min` } /** * Format seconds as compact duration (for tiles) */ export function formatDurationCompact(seconds: number): { value: string; unit: string } { if (seconds < 60) { return { value: String(seconds), unit: 's' } } const hours = Math.floor(seconds / 3600) const minutes = Math.floor((seconds % 3600) / 60) if (hours > 0) { const decimal = Math.round((minutes / 60) * 10) / 10 return { value: String(hours + (decimal > 0 ? decimal : 0)), unit: 'h' } } return { value: String(minutes), unit: 'min' } } /** * Get activity type display name */ export function getActivityTypeName(type: ActivityType): string { const names: Record = { vocab_extraction: 'Vokabelextraktion', klausur_correction: 'Klausurkorrektur', worksheet_creation: 'Arbeitsblatt erstellt', fairness_check: 'Fairness-Check', gutachten_generation: 'Gutachten generiert', } return names[type] || type }