malikm's picture
Add 3 files
61fdb09 verified
<!DOCTYPE html>
<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>