Spaces:
Running
Running
<html lang="en" class="light"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>WJEC A-Level Revision Dashboard</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
.radar-chart-container { | |
position: relative; | |
height: 300px; | |
width: 100%; | |
} | |
.task-progress { | |
transition: width 0.5s ease-in-out; | |
} | |
.dark .dark\:bg-gray-800 { | |
background-color: #1f2937; | |
} | |
.dark .dark\:bg-gray-700 { | |
background-color: #374151; | |
} | |
.dark .dark\:text-white { | |
color: #f9fafb; | |
} | |
.dark .dark\:border-gray-600 { | |
border-color: #4b5563; | |
} | |
.profile-icon { | |
background: linear-gradient(135deg, #3b82f6, #8b5cf6); | |
} | |
.tab-active { | |
border-bottom: 3px solid #8b5cf6; | |
color: #8b5cf6; | |
font-weight: 600; | |
} | |
.dark .tab-active { | |
border-bottom: 3px solid #8b5cf6; | |
color: #8b5cf6; | |
} | |
.checkbox-container input:checked ~ .checkmark { | |
background-color: #8b5cf6; | |
border-color: #8b5cf6; | |
} | |
.checkbox-container .checkmark:after { | |
content: ""; | |
position: absolute; | |
display: none; | |
left: 6px; | |
top: 2px; | |
width: 5px; | |
height: 10px; | |
border: solid white; | |
border-width: 0 2px 2px 0; | |
transform: rotate(45deg); | |
} | |
.checkbox-container input:checked ~ .checkmark:after { | |
display: block; | |
} | |
.activity-icon { | |
width: 36px; | |
height: 36px; | |
border-radius: 50%; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
} | |
.exam-pill { | |
transition: all 0.2s ease; | |
} | |
.exam-pill:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
} | |
.radar-tooltip { | |
position: absolute; | |
background: white; | |
border: 1px solid #ddd; | |
border-radius: 8px; | |
padding: 10px; | |
box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
z-index: 100; | |
pointer-events: none; | |
display: none; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 text-gray-800 min-h-screen transition-colors duration-300"> | |
<div class="container mx-auto px-4 py-6 max-w-7xl"> | |
<!-- Header --> | |
<header class="flex justify-between items-center mb-8"> | |
<h1 class="text-3xl font-bold text-indigo-600">WJEC Revision Dashboard</h1> | |
<div class="flex items-center space-x-4"> | |
<div class="relative"> | |
<button id="theme-toggle" class="p-2 rounded-full bg-gray-200 dark:bg-gray-700"> | |
<i class="fas fa-moon dark:hidden"></i> | |
<i class="fas fa-sun hidden dark:block text-yellow-300"></i> | |
</button> | |
</div> | |
<div class="profile-icon w-10 h-10 rounded-full flex items-center justify-center text-white font-bold cursor-pointer hover:shadow-lg transition-shadow"> | |
M | |
</div> | |
</div> | |
</header> | |
<!-- Main Content --> | |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
<!-- Left Column --> | |
<div class="lg:col-span-2 space-y-6"> | |
<!-- Tabs Navigation --> | |
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-1"> | |
<div class="flex border-b border-gray-200 dark:border-gray-700"> | |
<button class="tab-button py-2 px-4 text-sm font-medium" data-tab="tasks">Tasks</button> | |
<button class="tab-button py-2 px-4 text-sm font-medium" data-tab="past-papers">Past Papers</button> | |
<button class="tab-button py-2 px-4 text-sm font-medium" data-tab="progress">Progress</button> | |
<button class="tab-button py-2 px-4 text-sm font-medium" data-tab="exams">Exams</button> | |
</div> | |
<!-- Tasks Tab --> | |
<div id="tasks-tab" class="tab-content p-4"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-semibold">My Revision Tasks</h2> | |
<button id="add-task-btn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg flex items-center"> | |
<i class="fas fa-plus mr-2"></i> Add Task | |
</button> | |
</div> | |
<!-- Task Filters --> | |
<div class="mb-4 flex flex-wrap gap-2"> | |
<button class="filter-btn px-3 py-1 rounded-full text-xs bg-indigo-100 text-indigo-800" data-filter="all">All</button> | |
<button class="filter-btn px-3 py-1 rounded-full text-xs bg-blue-100 text-blue-800" data-filter="Biology">Biology</button> | |
<button class="filter-btn px-3 py-1 rounded-full text-xs bg-green-100 text-green-800" data-filter="Chemistry">Chemistry</button> | |
<button class="filter-btn px-3 py-1 rounded-full text-xs bg-yellow-100 text-yellow-800" data-filter="Maths">Maths</button> | |
<button class="filter-btn px-3 py-1 rounded-full text-xs bg-purple-100 text-purple-800" data-filter="Flashcards">Flashcards</button> | |
<button class="filter-btn px-3 py-1 rounded-full text-xs bg-pink-100 text-pink-800" data-filter="Past Papers">Past Papers</button> | |
<button class="filter-btn px-3 py-1 rounded-full text-xs bg-gray-100 text-gray-800" data-filter="Revision">Revision</button> | |
</div> | |
<!-- Task List --> | |
<div id="task-list" class="space-y-3"> | |
<!-- Tasks will be dynamically inserted here --> | |
</div> | |
</div> | |
<!-- Past Papers Tab --> | |
<div id="past-papers-tab" class="tab-content hidden p-4"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-semibold">Past Paper Library (2017-2023)</h2> | |
<div class="relative"> | |
<input type="text" id="paper-search" placeholder="Search papers..." class="pl-8 pr-4 py-2 border rounded-lg w-64"> | |
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> | |
</div> | |
</div> | |
<!-- Paper Filters --> | |
<div class="mb-4 flex flex-wrap gap-2"> | |
<select id="paper-subject" class="px-3 py-1 rounded-lg border text-sm"> | |
<option value="all">All Subjects</option> | |
<option value="Biology">Biology</option> | |
<option value="Chemistry">Chemistry</option> | |
<option value="Maths">Maths</option> | |
</select> | |
<select id="paper-year" class="px-3 py-1 rounded-lg border text-sm"> | |
<option value="all">All Years</option> | |
<option value="2023">2023</option> | |
<option value="2022">2022</option> | |
<option value="2021">2021</option> | |
<option value="2020">2020</option> | |
<option value="2019">2019</option> | |
<option value="2018">2018</option> | |
<option value="2017">2017</option> | |
</select> | |
<select id="paper-status" class="px-3 py-1 rounded-lg border text-sm"> | |
<option value="all">All Statuses</option> | |
<option value="Not Started">Not Started</option> | |
<option value="In Progress">In Progress</option> | |
<option value="Completed">Completed</option> | |
</select> | |
</div> | |
<!-- Papers List --> | |
<div id="papers-list" class="space-y-3"> | |
<!-- Papers will be dynamically inserted here --> | |
</div> | |
</div> | |
<!-- Progress Tab --> | |
<div id="progress-tab" class="tab-content hidden p-4"> | |
<h2 class="text-xl font-semibold mb-4">Progress Overview</h2> | |
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6"> | |
<div class="bg-white dark:bg-gray-700 p-4 rounded-lg shadow"> | |
<h3 class="font-medium mb-2">Biology Progress</h3> | |
<div class="radar-chart-container"> | |
<canvas id="biology-radar"></canvas> | |
</div> | |
</div> | |
<div class="bg-white dark:bg-gray-700 p-4 rounded-lg shadow"> | |
<h3 class="font-medium mb-2">Chemistry Progress</h3> | |
<div class="radar-chart-container"> | |
<canvas id="chemistry-radar"></canvas> | |
</div> | |
</div> | |
<div class="bg-white dark:bg-gray-700 p-4 rounded-lg shadow"> | |
<h3 class="font-medium mb-2">Maths Progress</h3> | |
<div class="radar-chart-container"> | |
<canvas id="maths-radar"></canvas> | |
</div> | |
</div> | |
<div class="bg-white dark:bg-gray-700 p-4 rounded-lg shadow"> | |
<h3 class="font-medium mb-2">Overall Completion</h3> | |
<div class="h-64 flex items-center justify-center"> | |
<canvas id="overall-chart" class="max-h-full"></canvas> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Exams Tab --> | |
<div id="exams-tab" class="tab-content hidden p-4"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-semibold">Exam Countdown</h2> | |
<button id="add-exam-btn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg flex items-center"> | |
<i class="fas fa-plus mr-2"></i> Add Exam | |
</button> | |
</div> | |
<div id="exams-list" class="space-y-4"> | |
<!-- Exams will be dynamically inserted here --> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Right Column --> | |
<div class="space-y-6"> | |
<!-- Upcoming Deadlines --> | |
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4"> | |
<h2 class="text-xl font-semibold mb-4">Upcoming Deadlines</h2> | |
<div id="deadlines-list" class="space-y-3"> | |
<div class="p-3 bg-red-50 dark:bg-gray-700 rounded-lg border-l-4 border-red-500"> | |
<div class="flex justify-between items-start"> | |
<div> | |
<h3 class="font-medium">Biology Mock Exam</h3> | |
<p class="text-sm text-gray-600 dark:text-gray-300">Unit 3.1 Biological Molecules</p> | |
</div> | |
<span class="text-xs bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200 px-2 py-1 rounded">Tomorrow</span> | |
</div> | |
</div> | |
<div class="p-3 bg-yellow-50 dark:bg-gray-700 rounded-lg border-l-4 border-yellow-500"> | |
<div class="flex justify-between items-start"> | |
<div> | |
<h3 class="font-medium">Chemistry Practical</h3> | |
<p class="text-sm text-gray-600 dark:text-gray-300">Required Practical 4</p> | |
</div> | |
<span class="text-xs bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200 px-2 py-1 rounded">3 days</span> | |
</div> | |
</div> | |
<div class="p-3 bg-blue-50 dark:bg-gray-700 rounded-lg border-l-4 border-blue-500"> | |
<div class="flex justify-between items-start"> | |
<div> | |
<h3 class="font-medium">Maths Assignment</h3> | |
<p class="text-sm text-gray-600 dark:text-gray-300">Calculus Problems</p> | |
</div> | |
<span class="text-xs bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 px-2 py-1 rounded">1 week</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Quick Stats --> | |
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4"> | |
<h2 class="text-xl font-semibold mb-4">Quick Stats</h2> | |
<div class="grid grid-cols-2 gap-4"> | |
<div class="bg-indigo-50 dark:bg-gray-700 p-3 rounded-lg"> | |
<p class="text-sm text-indigo-600 dark:text-indigo-400">Tasks Completed</p> | |
<p class="text-2xl font-bold" id="completed-tasks">0</p> | |
</div> | |
<div class="bg-green-50 dark:bg-gray-700 p-3 rounded-lg"> | |
<p class="text-sm text-green-600 dark:text-green-400">Papers Attempted</p> | |
<p class="text-2xl font-bold" id="attempted-papers">0</p> | |
</div> | |
<div class="bg-purple-50 dark:bg-gray-700 p-3 rounded-lg"> | |
<p class="text-sm text-purple-600 dark:text-purple-400">Biology Progress</p> | |
<p class="text-2xl font-bold" id="biology-progress">0%</p> | |
</div> | |
<div class="bg-blue-50 dark:bg-gray-700 p-3 rounded-lg"> | |
<p class="text-sm text-blue-600 dark:text-blue-400">Days to Exams</p> | |
<p class="text-2xl font-bold" id="days-to-exams">42</p> | |
</div> | |
</div> | |
</div> | |
<!-- Recent Activity --> | |
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4"> | |
<h2 class="text-xl font-semibold mb-4">Recent Activity</h2> | |
<div class="space-y-3"> | |
<div class="flex items-start space-x-3"> | |
<div class="activity-icon bg-green-100 dark:bg-green-900"> | |
<i class="fas fa-check text-green-600 dark:text-green-400"></i> | |
</div> | |
<div> | |
<p class="text-sm">Completed <span class="font-medium">Chemistry Unit 2.3</span> flashcards</p> | |
<p class="text-xs text-gray-500 dark:text-gray-400">2 hours ago</p> | |
</div> | |
</div> | |
<div class="flex items-start space-x-3"> | |
<div class="activity-icon bg-blue-100 dark:bg-blue-900"> | |
<i class="fas fa-file-alt text-blue-600 dark:text-blue-400"></i> | |
</div> | |
<div> | |
<p class="text-sm">Started <span class="font-medium">Maths 2022 Paper 1</span></p> | |
<p class="text-xs text-gray-500 dark:text-gray-400">Yesterday</p> | |
</div> | |
</div> | |
<div class="flex items-start space-x-3"> | |
<div class="activity-icon bg-purple-100 dark:bg-purple-900"> | |
<i class="fas fa-tasks text-purple-600 dark:text-purple-400"></i> | |
</div> | |
<div> | |
<p class="text-sm">Added <span class="font-medium">Biology Unit 4.1</span> to tasks</p> | |
<p class="text-xs text-gray-500 dark:text-gray-400">2 days ago</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Add Task Modal --> | |
<div id="task-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md"> | |
<div class="p-4 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center"> | |
<h3 class="text-lg font-semibold">Add New Task</h3> | |
<button id="close-modal" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<div class="p-4"> | |
<form id="task-form"> | |
<div class="mb-4"> | |
<label for="task-title" class="block text-sm font-medium mb-1">Task Title</label> | |
<input type="text" id="task-title" class="w-full px-3 py-2 border rounded-lg dark:bg-gray-700 dark:border-gray-600" required> | |
</div> | |
<div class="mb-4"> | |
<label for="task-subject" class="block text-sm font-medium mb-1">Subject</label> | |
<select id="task-subject" class="w-full px-3 py-2 border rounded-lg dark:bg-gray-700 dark:border-gray-600"> | |
<option value="">Select Subject</option> | |
<option value="Biology">Biology</option> | |
<option value="Chemistry">Chemistry</option> | |
<option value="Maths">Maths</option> | |
</select> | |
</div> | |
<div class="mb-4"> | |
<label for="task-topic" class="block text-sm font-medium mb-1">Topic (WJEC Specification)</label> | |
<select id="task-topic" class="w-full px-3 py-2 border rounded-lg dark:bg-gray-700 dark:border-gray-600"> | |
<option value="">Select Topic</option> | |
<!-- Will be populated dynamically based on subject --> | |
</select> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-sm font-medium mb-1">Task Type</label> | |
<div class="flex flex-wrap gap-2"> | |
<label class="inline-flex items-center"> | |
<input type="checkbox" name="task-type" value="Flashcards" class="hidden"> | |
<span class="px-3 py-1 rounded-full text-xs bg-purple-100 text-purple-800 cursor-pointer">Flashcards</span> | |
</label> | |
<label class="inline-flex items-center"> | |
<input type="checkbox" name="task-type" value="Past Papers" class="hidden"> | |
<span class="px-3 py-1 rounded-full text-xs bg-pink-100 text-pink-800 cursor-pointer">Past Papers</span> | |
</label> | |
<label class="inline-flex items-center"> | |
<input type="checkbox" name="task-type" value="Revision" class="hidden"> | |
<span class="px-3 py-1 rounded-full text-xs bg-gray-100 text-gray-800 cursor-pointer">Revision</span> | |
</label> | |
</div> | |
</div> | |
<div class="mb-4"> | |
<label for="task-due" class="block text-sm font-medium mb-1">Due Date</label> | |
<input type="date" id="task-due" class="w-full px-3 py-2 border rounded-lg dark:bg-gray-700 dark:border-gray-600"> | |
</div> | |
<div class="mb-4"> | |
<label for="task-progress" class="block text-sm font-medium mb-1">Progress</label> | |
<input type="range" id="task-progress" min="0" max="100" value="0" class="w-full"> | |
<div class="flex justify-between text-xs text-gray-500"> | |
<span>0%</span> | |
<span>50%</span> | |
<span>100%</span> | |
</div> | |
</div> | |
<div class="flex justify-end space-x-2"> | |
<button type="button" id="cancel-task" class="px-4 py-2 border rounded-lg">Cancel</button> | |
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">Save Task</button> | |
</div> | |
</form> | |
</div> | |
</div> | |
</div> | |
<!-- Add Exam Modal --> | |
<div id="exam-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md"> | |
<div class="p-4 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center"> | |
<h3 class="text-lg font-semibold">Add New Exam</h3> | |
<button id="close-exam-modal" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<div class="p-4"> | |
<form id="exam-form"> | |
<div class="mb-4"> | |
<label for="exam-name" class="block text-sm font-medium mb-1">Exam Name</label> | |
<input type="text" id="exam-name" class="w-full px-3 py-2 border rounded-lg dark:bg-gray-700 dark:border-gray-600" required> | |
</div> | |
<div class="mb-4"> | |
<label for="exam-subject" class="block text-sm font-medium mb-1">Subject</label> | |
<select id="exam-subject" class="w-full px-3 py-2 border rounded-lg dark:bg-gray-700 dark:border-gray-600"> | |
<option value="">Select Subject</option> | |
<option value="Biology">Biology</option> | |
<option value="Chemistry">Chemistry</option> | |
<option value="Maths">Maths</option> | |
</select> | |
</div> | |
<div class="mb-4"> | |
<label for="exam-unit" class="block text-sm font-medium mb-1">Unit</label> | |
<select id="exam-unit" class="w-full px-3 py-2 border rounded-lg dark:bg-gray-700 dark:border-gray-600"> | |
<option value="">Select Unit</option> | |
<option value="Unit 1">Unit 1</option> | |
<option value="Unit 2">Unit 2</option> | |
<option value="Unit 3">Unit 3</option> | |
<option value="Unit 4">Unit 4</option> | |
</select> | |
</div> | |
<div class="mb-4"> | |
<label for="exam-date" class="block text-sm font-medium mb-1">Exam Date</label> | |
<input type="date" id="exam-date" class="w-full px-3 py-2 border rounded-lg dark:bg-gray-700 dark:border-gray-600" required> | |
</div> | |
<div class="mb-4"> | |
<label for="exam-time" class="block text-sm font-medium mb-1">Exam Time</label> | |
<input type="time" id="exam-time" class="w-full px-3 py-2 border rounded-lg dark:bg-gray-700 dark:border-gray-600"> | |
</div> | |
<div class="flex justify-end space-x-2"> | |
<button type="button" id="cancel-exam" class="px-4 py-2 border rounded-lg">Cancel</button> | |
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">Save Exam</button> | |
</div> | |
</form> | |
</div> | |
</div> | |
</div> | |
<!-- Radar Chart Tooltip --> | |
<div id="radar-tooltip" class="radar-tooltip"></div> | |
<script> | |
// Theme Toggle | |
const themeToggle = document.getElementById('theme-toggle'); | |
const html = document.documentElement; | |
themeToggle.addEventListener('click', () => { | |
html.classList.toggle('dark'); | |
localStorage.setItem('theme', html.classList.contains('dark') ? 'dark' : 'light'); | |
}); | |
// Check for saved theme preference | |
if (localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) { | |
html.classList.add('dark'); | |
} | |
// Tab Navigation | |
const tabButtons = document.querySelectorAll('.tab-button'); | |
const tabContents = document.querySelectorAll('.tab-content'); | |
tabButtons.forEach(button => { | |
button.addEventListener('click', () => { | |
const tabName = button.getAttribute('data-tab'); | |
// Update active tab button | |
tabButtons.forEach(btn => btn.classList.remove('tab-active')); | |
button.classList.add('tab-active'); | |
// Show corresponding tab content | |
tabContents.forEach(content => content.classList.add('hidden')); | |
document.getElementById(`${tabName}-tab`).classList.remove('hidden'); | |
// Update charts when progress tab is shown | |
if (tabName === 'progress') { | |
updateCharts(); | |
} | |
}); | |
}); | |
// Set first tab as active by default | |
if (tabButtons.length > 0) { | |
tabButtons[0].classList.add('tab-active'); | |
tabContents[0].classList.remove('hidden'); | |
} | |
// Task Management | |
let tasks = JSON.parse(localStorage.getItem('tasks')) || []; | |
let papers = JSON.parse(localStorage.getItem('papers')) || []; | |
let exams = JSON.parse(localStorage.getItem('exams')) || []; | |
// WJEC specification data with units | |
const wjecSpecs = { | |
Biology: { | |
"Unit 3": ["3.1", "3.2", "3.3", "3.4", "3.5", "3.6", "3.7", "3.8"], | |
"Unit 4": ["4.1", "4.2", "4.3", "4.4", "4.5", "4.6"] | |
}, | |
Chemistry: { | |
"Unit 1": ["1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7"], | |
"Unit 2": ["2.1", "2.2", "2.3", "2.4", "2.5", "2.6", "2.7", "2.8"], | |
"Unit 3": ["3.1", "3.2", "3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9"], | |
"Unit 4": ["4.1", "4.2", "4.3", "4.4", "4.5", "4.6", "4.7", "4.8"] | |
}, | |
Maths: { | |
"Unit 1": ["1.1 Proof", "1.2 Algebra and Functions"], | |
"Unit 2": ["2.1 Coordinate Geometry", "2.2 Sequences and Series"], | |
"Unit 3": ["3.1 Trigonometry", "3.2 Exponentials and Logarithms"], | |
"Unit 4": ["4.1 Differentiation", "4.2 Integration"] | |
} | |
}; | |
// Populate topic dropdown based on subject and unit | |
const subjectSelect = document.getElementById('task-subject'); | |
const topicSelect = document.getElementById('task-topic'); | |
subjectSelect.addEventListener('change', () => { | |
const subject = subjectSelect.value; | |
topicSelect.innerHTML = '<option value="">Select Topic</option>'; | |
if (subject && wjecSpecs[subject]) { | |
for (const unit in wjecSpecs[subject]) { | |
const optgroup = document.createElement('optgroup'); | |
optgroup.label = unit; | |
wjecSpecs[subject][unit].forEach(topic => { | |
const option = document.createElement('option'); | |
option.value = `${unit} ${topic}`; | |
option.textContent = `${unit} ${topic}`; | |
optgroup.appendChild(option); | |
}); | |
topicSelect.appendChild(optgroup); | |
} | |
} | |
}); | |
// Task Modal | |
const addTaskBtn = document.getElementById('add-task-btn'); | |
const taskModal = document.getElementById('task-modal'); | |
const closeModal = document.getElementById('close-modal'); | |
const cancelTask = document.getElementById('cancel-task'); | |
const taskForm = document.getElementById('task-form'); | |
addTaskBtn.addEventListener('click', () => { | |
taskModal.classList.remove('hidden'); | |
}); | |
closeModal.addEventListener('click', () => { | |
taskModal.classList.add('hidden'); | |
}); | |
cancelTask.addEventListener('click', () => { | |
taskModal.classList.add('hidden'); | |
}); | |
// Task Type Selection | |
const taskTypeLabels = document.querySelectorAll('label[class*="inline-flex"]'); | |
taskTypeLabels.forEach(label => { | |
label.addEventListener('click', () => { | |
const checkbox = label.querySelector('input[type="checkbox"]'); | |
checkbox.checked = !checkbox.checked; | |
if (checkbox.checked) { | |
label.querySelector('span').classList.add('bg-indigo-100', 'text-indigo-800'); | |
label.querySelector('span').classList.remove('bg-purple-100', 'bg-pink-100', 'bg-gray-100'); | |
} else { | |
const value = checkbox.value; | |
if (value === 'Flashcards') { | |
label.querySelector('span').classList.add('bg-purple-100', 'text-purple-800'); | |
} else if (value === 'Past Papers') { | |
label.querySelector('span').classList.add('bg-pink-100', 'text-pink-800'); | |
} else { | |
label.querySelector('span').classList.add('bg-gray-100', 'text-gray-800'); | |
} | |
label.querySelector('span').classList.remove('bg-indigo-100', 'text-indigo-800'); | |
} | |
}); | |
}); | |
// Save Task | |
taskForm.addEventListener('submit', (e) => { | |
e.preventDefault(); | |
const title = document.getElementById('task-title').value; | |
const subject = document.getElementById('task-subject').value; | |
const topic = document.getElementById('task-topic').value; | |
const dueDate = document.getElementById('task-due').value; | |
const progress = document.getElementById('task-progress').value; | |
// Get selected task types | |
const taskTypes = []; | |
document.querySelectorAll('input[name="task-type"]:checked').forEach(checkbox => { | |
taskTypes.push(checkbox.value); | |
}); | |
const newTask = { | |
id: Date.now(), | |
title, | |
subject, | |
topic, | |
types: taskTypes, | |
dueDate, | |
progress: parseInt(progress), | |
createdAt: new Date().toISOString() | |
}; | |
tasks.push(newTask); | |
saveTasks(); | |
renderTasks(); | |
updateCharts(); | |
updateStats(); | |
// Reset form and close modal | |
taskForm.reset(); | |
taskModal.classList.add('hidden'); | |
// Reset topic dropdown | |
topicSelect.innerHTML = '<option value="">Select Topic</option>'; | |
// Reset task type selections | |
document.querySelectorAll('input[name="task-type"]').forEach(checkbox => { | |
checkbox.checked = false; | |
}); | |
taskTypeLabels.forEach(label => { | |
const value = label.querySelector('input').value; | |
const span = label.querySelector('span'); | |
span.classList.remove('bg-indigo-100', 'text-indigo-800'); | |
if (value === 'Flashcards') { | |
span.classList.add('bg-purple-100', 'text-purple-800'); | |
} else if (value === 'Past Papers') { | |
span.classList.add('bg-pink-100', 'text-pink-800'); | |
} else { | |
span.classList.add('bg-gray-100', 'text-gray-800'); | |
} | |
}); | |
}); | |
// Filter Tasks | |
const filterButtons = document.querySelectorAll('.filter-btn'); | |
filterButtons.forEach(button => { | |
button.addEventListener('click', () => { | |
const filter = button.getAttribute('data-filter'); | |
renderTasks(filter); | |
}); | |
}); | |
// Render Tasks | |
function renderTasks(filter = 'all') { | |
const taskList = document.getElementById('task-list'); | |
taskList.innerHTML = ''; | |
let filteredTasks = tasks; | |
if (filter !== 'all') { | |
filteredTasks = tasks.filter(task => { | |
return task.subject === filter || | |
(task.types && task.types.includes(filter)); | |
}); | |
} | |
if (filteredTasks.length === 0) { | |
taskList.innerHTML = '<p class="text-center py-4 text-gray-500">No tasks found. Add a new task to get started!</p>'; | |
return; | |
} | |
filteredTasks.forEach(task => { | |
const taskElement = document.createElement('div'); | |
taskElement.className = 'bg-white dark:bg-gray-700 rounded-lg shadow p-4 border-l-4 border-indigo-500'; | |
taskElement.dataset.id = task.id; | |
// Determine badge color based on due date | |
let badgeColor = 'gray'; | |
let badgeText = ''; | |
if (task.dueDate) { | |
const today = new Date(); | |
const dueDate = new Date(task.dueDate); | |
const diffTime = dueDate - today; | |
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); | |
if (diffDays < 0) { | |
badgeColor = 'red'; | |
badgeText = 'Overdue'; | |
} else if (diffDays === 0) { | |
badgeColor = 'red'; | |
badgeText = 'Today'; | |
} else if (diffDays <= 3) { | |
badgeColor = 'orange'; | |
badgeText = 'Soon'; | |
} else if (diffDays <= 7) { | |
badgeColor = 'yellow'; | |
badgeText = 'Next week'; | |
} | |
} | |
// Create type badges | |
let typeBadges = ''; | |
if (task.types && task.types.length > 0) { | |
task.types.forEach(type => { | |
let bgClass = 'bg-gray-100'; | |
let textClass = 'text-gray-800'; | |
if (type === 'Flashcards') { | |
bgClass = 'bg-purple-100'; | |
textClass = 'text-purple-800'; | |
} else if (type === 'Past Papers') { | |
bgClass = 'bg-pink-100'; | |
textClass = 'text-pink-800'; | |
} else if (type === 'Revision') { | |
bgClass = 'bg-gray-100'; | |
textClass = 'text-gray-800'; | |
} | |
typeBadges += `<span class="text-xs ${bgClass} ${textClass} px-2 py-1 rounded mr-1">${type}</span>`; | |
}); | |
} | |
taskElement.innerHTML = ` | |
<div class="flex justify-between items-start mb-2"> | |
<div> | |
<h3 class="font-medium">${task.title}</h3> | |
${task.subject ? `<p class="text-sm text-gray-600 dark:text-gray-300">${task.subject} ${task.topic ? '· ' + task.topic : ''}</p>` : ''} | |
</div> | |
${badgeText ? `<span class="text-xs bg-${badgeColor}-100 dark:bg-${badgeColor}-900 text-${badgeColor}-800 dark:text-${badgeColor}-200 px-2 py-1 rounded">${badgeText}</span>` : ''} | |
</div> | |
${typeBadges ? `<div class="mb-2">${typeBadges}</div>` : ''} | |
<div class="flex items-center justify-between mt-3"> | |
<div class="w-full bg-gray-200 dark:bg-gray-600 rounded-full h-2.5 mr-2"> | |
<div class="task-progress bg-indigo-600 h-2.5 rounded-full" style="width: ${task.progress}%"></div> | |
</div> | |
<span class="text-sm font-medium">${task.progress}%</span> | |
</div> | |
<div class="flex justify-end mt-3 space-x-2"> | |
<button class="edit-task text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-300 text-sm"> | |
<i class="fas fa-edit mr-1"></i> Edit | |
</button> | |
<button class="delete-task text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 text-sm"> | |
<i class="fas fa-trash-alt mr-1"></i> Delete | |
</button> | |
</div> | |
`; | |
taskList.appendChild(taskElement); | |
}); | |
// Add event listeners to edit and delete buttons | |
document.querySelectorAll('.edit-task').forEach(button => { | |
button.addEventListener('click', (e) => { | |
const taskId = parseInt(e.target.closest('[data-id]').dataset.id); | |
editTask(taskId); | |
}); | |
}); | |
document.querySelectorAll('.delete-task').forEach(button => { | |
button.addEventListener('click', (e) => { | |
const taskId = parseInt(e.target.closest('[data-id]').dataset.id); | |
deleteTask(taskId); | |
}); | |
}); | |
} | |
// Edit Task | |
function editTask(taskId) { | |
const task = tasks.find(t => t.id === taskId); | |
if (!task) return; | |
// Fill the form with task data | |
document.getElementById('task-title').value = task.title; | |
document.getElementById('task-subject').value = task.subject || ''; | |
// Populate topics if subject is selected | |
if (task.subject) { | |
topicSelect.innerHTML = '<option value="">Select Topic</option>'; | |
for (const unit in wjecSpecs[task.subject]) { | |
const optgroup = document.createElement('optgroup'); | |
optgroup.label = unit; | |
wjecSpecs[task.subject][unit].forEach(topic => { | |
const option = document.createElement('option'); | |
option.value = `${unit} ${topic}`; | |
option.textContent = `${unit} ${topic}`; | |
optgroup.appendChild(option); | |
}); | |
topicSelect.appendChild(optgroup); | |
} | |
document.getElementById('task-topic').value = task.topic || ''; | |
} | |
// Set task types | |
document.querySelectorAll('input[name="task-type"]').forEach(checkbox => { | |
checkbox.checked = task.types && task.types.includes(checkbox.value); | |
// Update the visual state of the labels | |
const label = checkbox.closest('label'); | |
const span = label.querySelector('span'); | |
if (checkbox.checked) { | |
span.classList.add('bg-indigo-100', 'text-indigo-800'); | |
span.classList.remove('bg-purple-100', 'bg-pink-100', 'bg-gray-100'); | |
} else { | |
const value = checkbox.value; | |
if (value === 'Flashcards') { | |
span.classList.add('bg-purple-100', 'text-purple-800'); | |
} else if (value === 'Past Papers') { | |
span.classList.add('bg-pink-100', 'text-pink-800'); | |
} else { | |
span.classList.add('bg-gray-100', 'text-gray-800'); | |
} | |
span.classList.remove('bg-indigo-100', 'text-indigo-800'); | |
} | |
}); | |
document.getElementById('task-due').value = task.dueDate || ''; | |
document.getElementById('task-progress').value = task.progress; | |
// Show the modal | |
taskModal.classList.remove('hidden'); | |
// Change the form to update mode | |
taskForm.dataset.editing = taskId; | |
taskForm.querySelector('button[type="submit"]').textContent = 'Update Task'; | |
} | |
// Update Task | |
taskForm.addEventListener('submit', (e) => { | |
e.preventDefault(); | |
const taskId = taskForm.dataset.editing; | |
if (taskId) { | |
// Update existing task | |
const taskIndex = tasks.findIndex(t => t.id === parseInt(taskId)); | |
if (taskIndex === -1) return; | |
const title = document.getElementById('task-title').value; | |
const subject = document.getElementById('task-subject').value; | |
const topic = document.getElementById('task-topic').value; | |
const dueDate = document.getElementById('task-due').value; | |
const progress = document.getElementById('task-progress').value; | |
// Get selected task types | |
const taskTypes = []; | |
document.querySelectorAll('input[name="task-type"]:checked').forEach(checkbox => { | |
taskTypes.push(checkbox.value); | |
}); | |
tasks[taskIndex] = { | |
...tasks[taskIndex], | |
title, | |
subject, | |
topic, | |
types: taskTypes, | |
dueDate, | |
progress: parseInt(progress) | |
}; | |
saveTasks(); | |
renderTasks(); | |
updateCharts(); | |
updateStats(); | |
// Reset form and close modal | |
taskForm.reset(); | |
delete taskForm.dataset.editing; | |
taskForm.querySelector('button[type="submit"]').textContent = 'Save Task'; | |
taskModal.classList.add('hidden'); | |
} | |
}); | |
// Delete Task | |
function deleteTask(taskId) { | |
if (confirm('Are you sure you want to delete this task?')) { | |
tasks = tasks.filter(t => t.id !== taskId); | |
saveTasks(); | |
renderTasks(); | |
updateCharts(); | |
updateStats(); | |
} | |
} | |
// Save Tasks to Local Storage | |
function saveTasks() { | |
localStorage.setItem('tasks', JSON.stringify(tasks)); | |
} | |
// Initialize Past Papers | |
function initPapers() { | |
if (papers.length === 0) { | |
// Sample past papers data | |
const years = [2023, 2022, 2021, 2020, 2019, 2018, 2017]; | |
const subjects = ['Biology', 'Chemistry', 'Maths']; | |
const types = ['Paper 1', 'Paper 2', 'Paper 3']; | |
years.forEach(year => { | |
subjects.forEach(subject => { | |
types.forEach(type => { | |
papers.push({ | |
id: `${subject}-${year}-${type.replace(' ', '-')}`, | |
subject, | |
year, | |
type, | |
status: 'Not Started', | |
completed: false | |
}); | |
}); | |
}); | |
}); | |
localStorage.setItem('papers', JSON.stringify(papers)); | |
} | |
} | |
// Render Past Papers | |
function renderPapers() { | |
const papersList = document.getElementById('papers-list'); | |
papersList.innerHTML = ''; | |
const subjectFilter = document.getElementById('paper-subject').value; | |
const yearFilter = document.getElementById('paper-year').value; | |
const statusFilter = document.getElementById('paper-status').value; | |
const searchQuery = document.getElementById('paper-search').value.toLowerCase(); | |
let filteredPapers = papers; | |
if (subjectFilter !== 'all') { | |
filteredPapers = filteredPapers.filter(paper => paper.subject === subjectFilter); | |
} | |
if (yearFilter !== 'all') { | |
filteredPapers = filteredPapers.filter(paper => paper.year.toString() === yearFilter); | |
} | |
if (statusFilter !== 'all') { | |
filteredPapers = filteredPapers.filter(paper => paper.status === statusFilter); | |
} | |
if (searchQuery) { | |
filteredPapers = filteredPapers.filter(paper => | |
paper.subject.toLowerCase().includes(searchQuery) || | |
paper.type.toLowerCase().includes(searchQuery) || | |
paper.year.toString().includes(searchQuery) | |
); | |
} | |
if (filteredPapers.length === 0) { | |
papersList.innerHTML = '<p class="text-center py-4 text-gray-500">No papers found matching your criteria.</p>'; | |
return; | |
} | |
// Group papers by subject and year | |
const groupedPapers = {}; | |
filteredPapers.forEach(paper => { | |
const key = `${paper.subject}-${paper.year}`; | |
if (!groupedPapers[key]) { | |
groupedPapers[key] = []; | |
} | |
groupedPapers[key].push(paper); | |
}); | |
// Render grouped papers | |
for (const key in groupedPapers) { | |
const [subject, year] = key.split('-'); | |
const papersGroup = groupedPapers[key]; | |
const groupElement = document.createElement('div'); | |
groupElement.className = 'mb-6'; | |
groupElement.innerHTML = ` | |
<h3 class="font-medium text-lg mb-2">${subject} ${year}</h3> | |
<div class="bg-white dark:bg-gray-700 rounded-lg shadow divide-y divide-gray-200 dark:divide-gray-600"> | |
${papersGroup.map(paper => ` | |
<div class="p-3 flex items-center justify-between" data-id="${paper.id}"> | |
<div> | |
<h4 class="font-medium">${paper.type}</h4> | |
<div class="flex items-center mt-1"> | |
<span class="text-xs px-2 py-1 rounded mr-2 | |
${paper.status === 'Completed' ? 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200' : | |
paper.status === 'In Progress' ? 'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200' : | |
'bg-gray-100 dark:bg-gray-600 text-gray-800 dark:text-gray-200'}"> | |
${paper.status} | |
</span> | |
</div> | |
</div> | |
<div class="flex items-center"> | |
<a href="#" class="text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-300 mr-3"> | |
<i class="fas fa-download"></i> | |
</a> | |
<div class="checkbox-container relative inline-block w-5 h-5 mr-2"> | |
<input type="checkbox" class="absolute opacity-0 w-0 h-0" ${paper.status === 'Completed' ? 'checked' : ''}> | |
<span class="checkmark absolute top-0 left-0 h-5 w-5 border-2 border-gray-300 dark:border-gray-500 rounded"></span> | |
</div> | |
</div> | |
</div> | |
`).join('')} | |
</div> | |
`; | |
papersList.appendChild(groupElement); | |
} | |
// Add event listeners to checkboxes | |
document.querySelectorAll('.checkbox-container input').forEach(checkbox => { | |
checkbox.addEventListener('change', (e) => { | |
const paperId = e.target.closest('[data-id]').dataset.id; | |
const paperIndex = papers.findIndex(p => p.id === paperId); | |
if (paperIndex !== -1) { | |
papers[paperIndex].status = e.target.checked ? 'Completed' : 'Not Started'; | |
localStorage.setItem('papers', JSON.stringify(papers)); | |
updateStats(); | |
} | |
}); | |
}); | |
} | |
// Paper Filters | |
document.getElementById('paper-subject').addEventListener('change', renderPapers); | |
document.getElementById('paper-year').addEventListener('change', renderPapers); | |
document.getElementById('paper-status').addEventListener('change', renderPapers); | |
document.getElementById('paper-search').addEventListener('input', renderPapers); | |
// Exam Management | |
const addExamBtn = document.getElementById('add-exam-btn'); | |
const examModal = document.getElementById('exam-modal'); | |
const closeExamModal = document.getElementById('close-exam-modal'); | |
const cancelExam = document.getElementById('cancel-exam'); | |
const examForm = document.getElementById('exam-form'); | |
addExamBtn.addEventListener('click', () => { | |
examModal.classList.remove('hidden'); | |
}); | |
closeExamModal.addEventListener('click', () => { | |
examModal.classList.add('hidden'); | |
}); | |
cancelExam.addEventListener('click', () => { | |
examModal.classList.add('hidden'); | |
}); | |
// Save Exam | |
examForm.addEventListener('submit', (e) => { | |
e.preventDefault(); | |
const name = document.getElementById('exam-name').value; | |
const subject = document.getElementById('exam-subject').value; | |
const unit = document.getElementById('exam-unit').value; | |
const date = document.getElementById('exam-date').value; | |
const time = document.getElementById('exam-time').value; | |
const newExam = { | |
id: Date.now(), | |
name, | |
subject, | |
unit, | |
date, | |
time, | |
createdAt: new Date().toISOString() | |
}; | |
exams.push(newExam); | |
saveExams(); | |
renderExams(); | |
updateStats(); | |
// Reset form and close modal | |
examForm.reset(); | |
examModal.classList.add('hidden'); | |
}); | |
// Render Exams | |
function renderExams() { | |
const examsList = document.getElementById('exams-list'); | |
examsList.innerHTML = ''; | |
if (exams.length === 0) { | |
examsList.innerHTML = '<p class="text-center py-4 text-gray-500">No exams added yet. Add your first exam to see the countdown!</p>'; | |
return; | |
} | |
// Sort exams by date (soonest first) | |
const sortedExams = [...exams].sort((a, b) => { | |
return new Date(a.date) - new Date(b.date); | |
}); | |
sortedExams.forEach(exam => { | |
const examElement = document.createElement('div'); | |
examElement.className = 'bg-white dark:bg-gray-700 rounded-lg shadow p-4 exam-pill'; | |
examElement.dataset.id = exam.id; | |
const today = new Date(); | |
const examDate = new Date(exam.date); | |
const diffTime = examDate - today; | |
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); | |
let statusText = ''; | |
let statusClass = ''; | |
if (diffDays < 0) { | |
statusText = 'Past'; | |
statusClass = 'bg-gray-100 dark:bg-gray-600 text-gray-800 dark:text-gray-200'; | |
} else if (diffDays === 0) { | |
statusText = 'Today'; | |
statusClass = 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200'; | |
} else if (diffDays <= 7) { | |
statusText = `${diffDays} day${diffDays !== 1 ? 's' : ''}`; | |
statusClass = 'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200'; | |
} else { | |
statusText = `${diffDays} day${diffDays !== 1 ? 's' : ''}`; | |
statusClass = 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200'; | |
} | |
examElement.innerHTML = ` | |
<div class="flex justify-between items-start mb-2"> | |
<div> | |
<h3 class="font-medium">${exam.name}</h3> | |
<p class="text-sm text-gray-600 dark:text-gray-300">${exam.subject} ${exam.unit}</p> | |
</div> | |
<span class="text-xs ${statusClass} px-2 py-1 rounded">${statusText}</span> | |
</div> | |
<div class="flex justify-between items-center mt-3"> | |
<div> | |
<p class="text-sm text-gray-600 dark:text-gray-300"> | |
<i class="far fa-calendar-alt mr-1"></i> ${formatDate(exam.date)} | |
${exam.time ? `<span class="ml-2"><i class="far fa-clock mr-1"></i> ${exam.time}</span>` : ''} | |
</p> | |
</div> | |
<div class="flex space-x-2"> | |
<button class="delete-exam text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 text-sm"> | |
<i class="fas fa-trash-alt"></i> | |
</button> | |
</div> | |
</div> | |
`; | |
examsList.appendChild(examElement); | |
}); | |
// Add event listeners to delete buttons | |
document.querySelectorAll('.delete-exam').forEach(button => { | |
button.addEventListener('click', (e) => { | |
const examId = parseInt(e.target.closest('[data-id]').dataset.id); | |
deleteExam(examId); | |
}); | |
}); | |
} | |
// Delete Exam | |
function deleteExam(examId) { | |
if (confirm('Are you sure you want to delete this exam?')) { | |
exams = exams.filter(e => e.id !== examId); | |
saveExams(); | |
renderExams(); | |
updateStats(); | |
} | |
} | |
// Save Exams to Local Storage | |
function saveExams() { | |
localStorage.setItem('exams', JSON.stringify(exams)); | |
} | |
// Format date as DD/MM/YYYY | |
function formatDate(dateString) { | |
const date = new Date(dateString); | |
const day = date.getDate().toString().padStart(2, '0'); | |
const month = (date.getMonth() + 1).toString().padStart(2, '0'); | |
const year = date.getFullYear(); | |
return `${day}/${month}/${year}`; | |
} | |
// Charts | |
let biologyRadarChart, chemistryRadarChart, mathsRadarChart, overallChart; | |
const radarTooltip = document.getElementById('radar-tooltip'); | |
function initCharts() { | |
// Biology Radar Chart | |
const biologyCtx = document.getElementById('biology-radar').getContext('2d'); | |
biologyRadarChart = new Chart(biologyCtx, { | |
type: 'radar', | |
data: { | |
labels: [...wjecSpecs.Biology["Unit 3"].map(t => `Unit 3 ${t}`), ...wjecSpecs.Biology["Unit 4"].map(t => `Unit 4 ${t}`)], | |
datasets: [{ | |
label: 'Completion %', | |
data: Array([...wjecSpecs.Biology["Unit 3"], ...wjecSpecs.Biology["Unit 4"]].length).fill(0), | |
backgroundColor: 'rgba(79, 70, 229, 0.2)', | |
borderColor: 'rgba(79, 70, 229, 1)', | |
borderWidth: 2, | |
pointBackgroundColor: 'rgba(79, 70, 229, 1)', | |
pointRadius: 4 | |
}] | |
}, | |
options: { | |
scales: { | |
r: { | |
angleLines: { | |
display: true | |
}, | |
suggestedMin: 0, | |
suggestedMax: 100, | |
ticks: { | |
stepSize: 20 | |
} | |
} | |
}, | |
plugins: { | |
legend: { | |
display: false | |
} | |
}, | |
onHover: (event, chartElement) => { | |
if (chartElement.length > 0) { | |
const index = chartElement[0].index; | |
const label = biologyRadarChart.data.labels[index]; | |
const value = biologyRadarChart.data.datasets[0].data[index]; | |
// Find related tasks | |
const relatedTasks = tasks.filter(task => | |
task.subject === 'Biology' && task.topic === label | |
); | |
// Position tooltip near the point | |
const canvasPosition = event.nativeEvent; | |
radarTooltip.style.left = (canvasPosition.offsetX + 10) + 'px'; | |
radarTooltip.style.top = (canvasPosition.offsetY + 10) + 'px'; | |
// Set tooltip content | |
if (relatedTasks.length > 0) { | |
let tasksHtml = relatedTasks.map(task => | |
`<li class="text-xs">${task.title} (${task.progress}%)</li>` | |
).join(''); | |
radarTooltip.innerHTML = ` | |
<strong>${label}</strong> | |
<div>Progress: ${value}%</div> | |
<div class="mt-1">Related Tasks:</div> | |
<ul class="list-disc pl-4">${tasksHtml}</ul> | |
`; | |
} else { | |
radarTooltip.innerHTML = ` | |
<strong>${label}</strong> | |
<div>Progress: ${value}%</div> | |
<div class="mt-1">No tasks yet</div> | |
`; | |
} | |
radarTooltip.style.display = 'block'; | |
} else { | |
radarTooltip.style.display = 'none'; | |
} | |
} | |
} | |
}); | |
// Chemistry Radar Chart | |
const chemistryCtx = document.getElementById('chemistry-radar').getContext('2d'); | |
chemistryRadarChart = new Chart(chemistryCtx, { | |
type: 'radar', | |
data: { | |
labels: [ | |
...wjecSpecs.Chemistry["Unit 1"].map(t => `Unit 1 ${t}`), | |
...wjecSpecs.Chemistry["Unit 2"].map(t => `Unit 2 ${t}`), | |
...wjecSpecs.Chemistry["Unit 3"].map(t => `Unit 3 ${t}`), | |
...wjecSpecs.Chemistry["Unit 4"].map(t => `Unit 4 ${t}`) | |
], | |
datasets: [{ | |
label: 'Completion %', | |
data: Array([ | |
...wjecSpecs.Chemistry["Unit 1"], | |
...wjecSpecs.Chemistry["Unit 2"], | |
...wjecSpecs.Chemistry["Unit 3"], | |
...wjecSpecs.Chemistry["Unit 4"] | |
].length).fill(0), | |
backgroundColor: 'rgba(16, 185, 129, 0.2)', | |
borderColor: 'rgba(16, 185, 129, 1)', | |
borderWidth: 2, | |
pointBackgroundColor: 'rgba(16, 185, 129, 1)', | |
pointRadius: 4 | |
}] | |
}, | |
options: { | |
scales: { | |
r: { | |
angleLines: { | |
display: true | |
}, | |
suggestedMin: 0, | |
suggestedMax: 100, | |
ticks: { | |
stepSize: 20 | |
} | |
} | |
}, | |
plugins: { | |
legend: { | |
display: false | |
} | |
}, | |
onHover: (event, chartElement) => { | |
if (chartElement.length > 0) { | |
const index = chartElement[0].index; | |
const label = chemistryRadarChart.data.labels[index]; | |
const value = chemistryRadarChart.data.datasets[0].data[index]; | |
// Find related tasks | |
const relatedTasks = tasks.filter(task => | |
task.subject === 'Chemistry' && task.topic === label | |
); | |
// Position tooltip near the point | |
const canvasPosition = event.nativeEvent; | |
radarTooltip.style.left = (canvasPosition.offsetX + 10) + 'px'; | |
radarTooltip.style.top = (canvasPosition.offsetY + 10) + 'px'; | |
// Set tooltip content | |
if (relatedTasks.length > 0) { | |
let tasksHtml = relatedTasks.map(task => | |
`<li class="text-xs">${task.title} (${task.progress}%)</li>` | |
).join(''); | |
radarTooltip.innerHTML = ` | |
<strong>${label}</strong> | |
<div>Progress: ${value}%</div> | |
<div class="mt-1">Related Tasks:</div> | |
<ul class="list-disc pl-4">${tasksHtml}</ul> | |
`; | |
} else { | |
radarTooltip.innerHTML = ` | |
<strong>${label}</strong> | |
<div>Progress: ${value}%</div> | |
<div class="mt-1">No tasks yet</div> | |
`; | |
} | |
radarTooltip.style.display = 'block'; | |
} else { | |
radarTooltip.style.display = 'none'; | |
} | |
} | |
} | |
}); | |
// Maths Radar Chart | |
const mathsCtx = document.getElementById('maths-radar').getContext('2d'); | |
mathsRadarChart = new Chart(mathsCtx, { | |
type: 'radar', | |
data: { | |
labels: [ | |
...wjecSpecs.Maths["Unit 1"].map(t => `Unit 1 ${t}`), | |
...wjecSpecs.Maths["Unit 2"].map(t => `Unit 2 ${t}`), | |
...wjecSpecs.Maths["Unit 3"].map(t => `Unit 3 ${t}`), | |
...wjecSpecs.Maths["Unit 4"].map(t => `Unit 4 ${t}`) | |
], | |
datasets: [{ | |
label: 'Completion %', | |
data: Array([ | |
...wjecSpecs.Maths["Unit 1"], | |
...wjecSpecs.Maths["Unit 2"], | |
...wjecSpecs.Maths["Unit 3"], | |
...wjecSpecs.Maths["Unit 4"] | |
].length).fill(0), | |
backgroundColor: 'rgba(245, 158, 11, 0.2)', | |
borderColor: 'rgba(245, 158, 11, 1)', | |
borderWidth: 2, | |
pointBackgroundColor: 'rgba(245, 158, 11, 1)', | |
pointRadius: 4 | |
}] | |
}, | |
options: { | |
scales: { | |
r: { | |
angleLines: { | |
display: true | |
}, | |
suggestedMin: 0, | |
suggestedMax: 100, | |
ticks: { | |
stepSize: 20 | |
} | |
} | |
}, | |
plugins: { | |
legend: { | |
display: false | |
} | |
}, | |
onHover: (event, chartElement) => { | |
if (chartElement.length > 0) { | |
const index = chartElement[0].index; | |
const label = mathsRadarChart.data.labels[index]; | |
const value = mathsRadarChart.data.datasets[0].data[index]; | |
// Find related tasks | |
const relatedTasks = tasks.filter(task => | |
task.subject === 'Maths' && task.topic === label | |
); | |
// Position tooltip near the point | |
const canvasPosition = event.nativeEvent; | |
radarTooltip.style.left = (canvasPosition.offsetX + 10) + 'px'; | |
radarTooltip.style.top = (canvasPosition.offsetY + 10) + 'px'; | |
// Set tooltip content | |
if (relatedTasks.length > 0) { | |
let tasksHtml = relatedTasks.map(task => | |
`<li class="text-xs">${task.title} (${task.progress}%)</li>` | |
).join(''); | |
radarTooltip.innerHTML = ` | |
<strong>${label}</strong> | |
<div>Progress: ${value}%</div> | |
<div class="mt-1">Related Tasks:</div> | |
<ul class="list-disc pl-4">${tasksHtml}</ul> | |
`; | |
} else { | |
radarTooltip.innerHTML = ` | |
<strong>${label}</strong> | |
<div>Progress: ${value}%</div> | |
<div class="mt-1">No tasks yet</div> | |
`; | |
} | |
radarTooltip.style.display = 'block'; | |
} else { | |
radarTooltip.style.display = 'none'; | |
} | |
} | |
} | |
}); | |
// Overall Progress Chart | |
const overallCtx = document.getElementById('overall-chart').getContext('2d'); | |
overallChart = new Chart(overallCtx, { | |
type: 'doughnut', | |
data: { | |
labels: ['Biology', 'Chemistry', 'Maths'], | |
datasets: [{ | |
data: [0, 0, 0], | |
backgroundColor: [ | |
'rgba(79, 70, 229, 0.7)', | |
'rgba(16, 185, 129, 0.7)', | |
'rgba(245, 158, 11, 0.7)' | |
], | |
borderColor: [ | |
'rgba(79, 70, 229, 1)', | |
'rgba(16, 185, 129, 1)', | |
'rgba(245, 158, 11, 1)' | |
], | |
borderWidth: 1 | |
}] | |
}, | |
options: { | |
plugins: { | |
legend: { | |
position: 'right' | |
} | |
}, | |
cutout: '70%' | |
} | |
}); | |
} | |
// Update Charts | |
function updateCharts() { | |
// Calculate progress for each topic in Biology | |
const biologyProgress = [ | |
...wjecSpecs.Biology["Unit 3"].map(topic => { | |
const topicLabel = `Unit 3 ${topic}`; | |
const topicTasks = tasks.filter(task => | |
task.subject === 'Biology' && task.topic === topicLabel | |
); | |
if (topicTasks.length === 0) return 0; | |
const totalProgress = topicTasks.reduce((sum, task) => sum + task.progress, 0); | |
return Math.round(totalProgress / topicTasks.length); | |
}), | |
...wjecSpecs.Biology["Unit 4"].map(topic => { | |
const topicLabel = `Unit 4 ${topic}`; | |
const topicTasks = tasks.filter(task => | |
task.subject === 'Biology' && task.topic === topicLabel | |
); | |
if (topicTasks.length === 0) return 0; | |
const totalProgress = topicTasks.reduce((sum, task) => sum + task.progress, 0); | |
return Math.round(totalProgress / topicTasks.length); | |
}) | |
]; | |
biologyRadarChart.data.datasets[0].data = biologyProgress; | |
biologyRadarChart.update(); | |
// Calculate progress for each topic in Chemistry | |
const chemistryProgress = [ | |
...wjecSpecs.Chemistry["Unit 1"].map(topic => { | |
const topicLabel = `Unit 1 ${topic}`; | |
const topicTasks = tasks.filter(task => | |
task.subject === 'Chemistry' && task.topic === topicLabel | |
); | |
if (topicTasks.length === 0) return 0; | |
const totalProgress = topicTasks.reduce((sum, task) => sum + task.progress, 0); | |
return Math.round(totalProgress / topicTasks.length); | |
}), | |
...wjecSpecs.Chemistry["Unit 2"].map(topic => { | |
const topicLabel = `Unit 2 ${topic}`; | |
const topicTasks = tasks.filter(task => | |
task.subject === 'Chemistry' && task.topic === topicLabel | |
); | |
if (topicTasks.length === 0) return 0; | |
const totalProgress = topicTasks.reduce((sum, task) => sum + task.progress, 0); | |
return Math.round(totalProgress / topicTasks.length); | |
}), | |
...wjecSpecs.Chemistry["Unit 3"].map(topic => { | |
const topicLabel = `Unit 3 ${topic}`; | |
const topicTasks = tasks.filter(task => | |
task.subject === 'Chemistry' && task.topic === topicLabel | |
); | |
if (topicTasks.length === 0) return 0; | |
const totalProgress = topicTasks.reduce((sum, task) => sum + task.progress, 0); | |
return Math.round(totalProgress / topicTasks.length); | |
}), | |
...wjecSpecs.Chemistry["Unit 4"].map(topic => { | |
const topicLabel = `Unit 4 ${topic}`; | |
const topicTasks = tasks.filter(task => | |
task.subject === 'Chemistry' && task.topic === topicLabel | |
); | |
if (topicTasks.length === 0) return 0; | |
const totalProgress = topicTasks.reduce((sum, task) => sum + task.progress, 0); | |
return Math.round(totalProgress / topicTasks.length); | |
}) | |
]; | |
chemistryRadarChart.data.datasets[0].data = chemistryProgress; | |
chemistryRadarChart.update(); | |
// Calculate progress for each topic in Maths | |
const mathsProgress = [ | |
...wjecSpecs.Maths["Unit 1"].map(topic => { | |
const topicLabel = `Unit 1 ${topic}`; | |
const topicTasks = tasks.filter(task => | |
task.subject === 'Maths' && task.topic === topicLabel | |
); | |
if (topicTasks.length === 0) return 0; | |
const totalProgress = topicTasks.reduce((sum, task) => sum + task.progress, 0); | |
return Math.round(totalProgress / topicTasks.length); | |
}), | |
...wjecSpecs.Maths["Unit 2"].map(topic => { | |
const topicLabel = `Unit 2 ${topic}`; | |
const topicTasks = tasks.filter(task => | |
task.subject === 'Maths' && task.topic === topicLabel | |
); | |
if (topicTasks.length === 0) return 0; | |
const totalProgress = topicTasks.reduce((sum, task) => sum + task.progress, 0); | |
return Math.round(totalProgress / topicTasks.length); | |
}), | |
...wjecSpecs.Maths["Unit 3"].map(topic => { | |
const topicLabel = `Unit 3 ${topic}`; | |
const topicTasks = tasks.filter(task => | |
task.subject === 'Maths' && task.topic === topicLabel | |
); | |
if (topicTasks.length === 0) return 0; | |
const totalProgress = topicTasks.reduce((sum, task) => sum + task.progress, 0); | |
return Math.round(totalProgress / topicTasks.length); | |
}), | |
...wjecSpecs.Maths["Unit 4"].map(topic => { | |
const topicLabel = `Unit 4 ${topic}`; | |
const topicTasks = tasks.filter(task => | |
task.subject === 'Maths' && task.topic === topicLabel | |
); | |
if (topicTasks.length === 0) return 0; | |
const totalProgress = topicTasks.reduce((sum, task) => sum + task.progress, 0); | |
return Math.round(totalProgress / topicTasks.length); | |
}) | |
]; | |
mathsRadarChart.data.datasets[0].data = mathsProgress; | |
mathsRadarChart.update(); | |
// Calculate overall subject progress | |
const biologyTasks = tasks.filter(task => task.subject === 'Biology'); | |
const chemistryTasks = tasks.filter(task => task.subject === 'Chemistry'); | |
const mathsTasks = tasks.filter(task => task.subject === 'Maths'); | |
const biologyAvg | |
</html> |