// DEPLOY_CONFIG: {"cron": [{"name": "end_of_semester_email_summary", "schedule": "0 9 * * 1-5", "action": "event", "config": {"event_type": "end_of_semester_email_summary"}}], "triggers": []} import { useMemo, useEffect, useRef, useState } from 'react'; import { useAuth, useCollection, renderChart } from '@deplixo/sdk'; import { CoursesTab } from './components/CoursesTab.jsx'; import { GradesTab } from './components/GradesTab.jsx'; import { DashboardTab } from './components/DashboardTab.jsx'; function App() { const { user, loading, login, logout } = useAuth(); const [activeTab, setActiveTab] = useState('dashboard'); const tabs = [ { id: 'dashboard', label: 'Dashboard', icon: '📊' }, { id: 'courses', label: 'Courses', icon: '📚' }, { id: 'grades', label: 'Grades', icon: '✏️' }, ]; if (loading) { return (

GradeTrack

Signing you in...

); } if (!user) { return (

GradeTrack

Your academic performance companion

🔒 Personal data access

Sign in with Google to continue

Use your Google account to securely access your courses, grades, and GPA dashboard.

); } return (

GradeTrack

Your academic performance companion

{user.avatar ? ( {user.name} ) : (
{(user.name || 'U').charAt(0)}
)}
{user.name} {user.email}
{activeTab === 'dashboard' && } {activeTab === 'courses' && } {activeTab === 'grades' && }
); } function getCourseDisplayName(course) { return course?.name || course?.title || 'Untitled Course'; } function getCategoryEntries(course) { if (!course) return []; if (Array.isArray(course.categories) && course.categories.length > 0) return course.categories; const fallback = [ { key: 'assignments', label: 'Assignments', weight: course.assignmentWeight ?? course.assignmentsWeight ?? 0 }, { key: 'midterm', label: 'Midterm', weight: course.midtermWeight ?? 0 }, { key: 'final', label: 'Final', weight: course.finalWeight ?? 0 }, ]; return fallback; } function normalizeScoreValue(value) { const num = Number(value); if (!Number.isFinite(num)) return null; return Math.max(0, Math.min(100, num)); } function calculateCourseGrade(course) { const categories = getCategoryEntries(course); const grades = Array.isArray(course?.grades) ? course.grades : Array.isArray(course?.items) ? course.items : []; if (!categories.length || !grades.length) return null; let totalWeight = 0; let weightedSum = 0; categories.forEach(cat => { const weight = Number(cat.weight ?? 0); if (weight <= 0) return; const catGrades = grades.filter(g => (g.category || g.type || g.bucket || '').toLowerCase() === String(cat.key || cat.label || '').toLowerCase()); if (!catGrades.length) return; const avg = catGrades .map(g => normalizeScoreValue(g.score ?? g.value ?? g.percentage)) .filter(v => v !== null) .reduce((a, b) => a + b, 0) / catGrades.length; if (Number.isFinite(avg)) { weightedSum += avg * weight; totalWeight += weight; } }); if (!totalWeight) return null; return weightedSum / totalWeight; } function calculateGpaFromPercentage(pct) { if (!Number.isFinite(pct)) return null; if (pct >= 93) return 4.0; if (pct >= 90) return 3.7; if (pct >= 87) return 3.3; if (pct >= 83) return 3.0; if (pct >= 80) return 2.7; if (pct >= 77) return 2.3; if (pct >= 73) return 2.0; if (pct >= 70) return 1.7; if (pct >= 67) return 1.3; if (pct >= 65) return 1.0; return 0; } function getSemesterLabel(course, index) { return ( course?.semester || course?.term || course?.period || course?.session || `Semester ${index + 1}` ); } function DashboardCharts() { const { items: courses = [] } = useCollection('courses', { personal: true }); const { items: grades = [] } = useCollection('grades', { personal: true }); const semesterChartRef = useRef(null); const distributionChartRef = useRef(null); const semesterData = useMemo(() => { const courseGradeEntries = courses .map(course => ({ course, percentage: calculateCourseGrade({ ...course, grades: grades.filter(g => String(g.courseId ?? g.course_id ?? g.course ?? '') === String(course.id ?? course._id)) }), })) .filter(entry => Number.isFinite(entry.percentage)); const semesterMap = new Map(); courseGradeEntries.forEach((entry, index) => { const label = getSemesterLabel(entry.course, index); const gpa = calculateGpaFromPercentage(entry.percentage); if (!semesterMap.has(label)) { semesterMap.set(label, { totalGpa: 0, count: 0 }); } const bucket = semesterMap.get(label); bucket.totalGpa += gpa ?? 0; bucket.count += 1; }); return Array.from(semesterMap.entries()).map(([label, bucket]) => ({ label, value: bucket.count ? Number((bucket.totalGpa / bucket.count).toFixed(2)) : 0, })); }, [courses, grades]); const distributionData = useMemo(() => { return courses.map((course, index) => { const courseGrades = grades.filter(g => String(g.courseId ?? g.course_id ?? g.course ?? '') === String(course.id ?? course._id)); const percentage = calculateCourseGrade({ ...course, grades: courseGrades }); return { label: getCourseDisplayName(course), value: Number.isFinite(percentage) ? Number(percentage.toFixed(1)) : 0, order: index, }; }); }, [courses, grades]); useEffect(() => { if (semesterChartRef.current && semesterData.length > 0) { renderChart(semesterChartRef.current, { type: 'line', data: { labels: semesterData.map(d => d.label), datasets: [{ label: 'GPA', data: semesterData.map(d => d.value) }], }, }); } }, [semesterData]); useEffect(() => { if (distributionChartRef.current && distributionData.length > 0) { renderChart(distributionChartRef.current, { type: 'bar', data: { labels: distributionData.map(d => d.label), datasets: [{ label: 'Course %', data: distributionData.map(d => d.value) }], }, }); } }, [distributionData]); return (

GPA Over Semesters

{semesterData.length > 0 ? (
) : (

Add courses and grades to see your GPA trend by semester.

)}

Grade Distribution by Course

{distributionData.length > 0 ? (
) : (

No course grades yet. Log grades to view per-course distribution.

)}
); } ReactDOM.createRoot(document.getElementById('root')).render();