diff --git a/css/index.css b/css/index.css
index 28fbfd31..1db3b97a 100644
--- a/css/index.css
+++ b/css/index.css
@@ -150,9 +150,61 @@ body {
/* Sidebar */
.sidebar {
- background: var(--color-background-secondary);
- border-right: 1px solid var(--color-border-tertiary);
- padding: 20px 16px;
+ background: var(--color-background-secondary); border-right: 1px solid var(--color-border-tertiary);
+ padding: 20px 16px; display: flex; flex-direction: column; gap: 4px;
+}
+.sidebar-header { font-size: 11px; font-weight: 700; color: var(--color-text-tertiary); padding: 8px 12px; margin-bottom: 2px; letter-spacing: .06em; text-transform: uppercase; }
+.nav-item { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-radius: var(--border-radius-md); cursor: pointer; font-size: 14px; font-weight: 500; color: var(--color-text-secondary); transition: all 0.2s ease; position: relative; }
+.nav-item:hover { background: var(--color-background-primary); color: var(--color-text-primary); transform: translateX(2px); }
+.nav-item.active { background: var(--color-background-primary); color: var(--color-text-primary); box-shadow: var(--shadow-sm); }
+.nav-item.active::before { content: ''; position: absolute; left: 0; top: 15%; height: 70%; width: 3px; background: var(--color-text-primary); border-radius: 4px; }
+.nav-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.1); }
+.badge { margin-left: auto; font-size: 12px; font-weight: 600; background: var(--color-background-tertiary); color: var(--color-text-secondary); padding: 2px 8px; border-radius: 12px; transition: all 0.2s; }
+.nav-item:hover .badge { background: var(--color-background-primary); border: 1px solid var(--color-border-tertiary); }
+.sidebar-divider { height: 1px; background: var(--color-border-tertiary); margin: 12px 0; }
+.add-subject { font-size: 13px; font-weight: 500; color: var(--color-text-tertiary); padding: 8px 12px; cursor: pointer; transition: color 0.2s; display: flex; align-items: center; gap: 8px; }
+.add-subject:hover { color: var(--color-text-primary); }
+
+/* Main */
+.main { display: flex; flex-direction: column; overflow: hidden; position: relative; }
+.topbar { display: flex; align-items: center; gap: 12px; padding: 16px 24px; border-bottom: 1px solid var(--color-border-tertiary); flex-shrink: 0; background: rgba(var(--color-background-primary), 0.8); backdrop-filter: blur(10px); z-index: 10; }
+.topbar-title { font-size: 18px; font-weight: 600; color: var(--color-text-primary); flex: 1; }
+.btn { font-family: inherit; font-size: 13px; font-weight: 600; padding: 8px 16px; border: 1px solid var(--color-border-secondary); border-radius: var(--border-radius-sm); background: var(--color-background-primary); color: var(--color-text-primary); cursor: pointer; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: var(--shadow-sm); display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
+.btn:hover { background: var(--color-background-secondary); transform: translateY(-1px); box-shadow: var(--shadow-md); }
+.btn:active { transform: translateY(0); box-shadow: 0 0 0; }
+.btn-primary { background: var(--color-text-primary); color: var(--color-background-primary); border-color: transparent; }
+.btn-primary:hover { background: var(--color-text-secondary); }
+
+/* Calendar */
+.cal-section { padding: 16px 24px; border-bottom: 1px solid var(--color-border-tertiary); flex-shrink: 0; }
+.cal-header { display: flex; align-items: center; margin-bottom: 16px; justify-content:space-between; }
+.cal-title { font-size: 15px; font-weight: 600; flex: 1; color: var(--color-text-primary); }
+.cal-nav { font-size: 18px; color: var(--color-text-secondary); cursor: pointer; padding: 4px 8px; border-radius: 4px; transition: background 0.2s; }
+.cal-nav:hover { background: var(--color-background-secondary); color: var(--color-text-primary); }
+.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; text-align: center; }
+.cal-day-label { font-size: 11px; color: var(--color-text-tertiary); padding-bottom: 8px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; }
+.cal-day { font-size: 13px; font-weight: 500; height: 32px; display: flex; align-items: center; justify-content: center; border-radius: var(--border-radius-md); cursor: pointer; color: var(--color-text-secondary); position: relative; transition: all 0.2s; }
+.cal-day:hover { background: var(--color-background-secondary); color: var(--color-text-primary); transform: scale(1.1); z-index: 1; }
+.cal-day.today { background: var(--color-text-primary); color: var(--color-background-primary); box-shadow: var(--shadow-sm); }
+.cal-day-indicators { position: absolute; bottom: 3px; left: 0; right: 0; display: flex; justify-content: center; gap: 3px; }
+.cal-day-indicator { width: 5px; height: 5px; border-radius: 50%; }
+.cal-day.muted { color: var(--color-border-secondary); }
+.cal-legend { display: flex; gap: 16px; margin-top: 16px; font-size: 11px; font-weight: 500; color: var(--color-text-tertiary); justify-content: flex-end; }
+.cal-legend span { display: flex; align-items: center; gap: 6px; }
+.legend-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
+
+/*Streak*/
+.streak-display { display: flex; align-items: center; gap: 6px; padding: 8px 10px; border-radius: 12px; cursor: pointer; transition: all 0.2s ease; }
+.streak-display:hover { background: rgba(0, 0, 0, 0.04); }
+.streak-icon { width: 22px; height: 22px; object-fit: contain; }
+#streak-count { font-size: 20px; font-weight: 700; color: var(--color-text-primary); }
+
+/* Tasks */
+.tasks-section { flex: 1; overflow-y: auto; padding: 0 24px 24px; scroll-behavior: smooth; }
+.tasks-section::-webkit-scrollbar { width: 6px; }
+.tasks-section::-webkit-scrollbar-thumb { background: var(--color-border-secondary); border-radius: 10px; }
+.tasks-actions-bar {
+ margin-top: 18px;
display: flex;
flex-direction: column;
gap: 4px;
@@ -2932,6 +2984,112 @@ body {
align-items: center;
gap: 12px;
}
+/* Header right & Badges */
+
+.header-right {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.streak-badges {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ margin-right: 6px;
+
+ transform: translateY(1px);
+}
+
+.badge-wrapper {
+ width: 45px;
+ height: 45px;
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ border-radius: 999px;
+
+ background: rgba(0, 0, 0, 0.025);
+
+ transition: all 0.2s ease;
+}
+
+.badge-wrapper:hover {
+ background: rgba(0, 0, 0, 0.05);
+}
+
+.streak-badge {
+ width: 30px;
+ height: 30px;
+
+ object-fit: contain;
+
+ transition: all 0.2s ease;
+}
+
+.streak-badge.locked {
+ filter: grayscale(1);
+ opacity: 0.32;
+}
+
+.streak-badge.unlocked {
+ opacity: 0.92;
+}
+
+.badge-wrapper:hover .streak-badge {
+ transform: scale(1.08);
+}
+
+.streak-display {
+ position: relative;
+}
+
+.streak-tooltip {
+ position: absolute;
+
+ top: 36px;
+ left: 50%;
+
+ transform: translateX(-50%) translateY(4px);
+
+ min-width: 220px;
+
+ padding: 10px 12px;
+
+ border-radius: 12px;
+
+ background: #111;
+ color: white;
+
+ font-size: 12px;
+ font-weight: 500;
+ line-height: 1.5;
+
+ text-align: center;
+
+ opacity: 0;
+ visibility: hidden;
+
+ transition: all 0.2s ease;
+
+ pointer-events: none;
+
+ z-index: 100;
+}
+
+.streak-display:hover .streak-tooltip {
+ opacity: 1;
+ visibility: visible;
+
+ transform: translateX(-50%) translateY(0);
+}
+
+.hidden {
+ display: none;
+}
.logo {
width: 40px;
@@ -2947,7 +3105,7 @@ body {
.header-nav {
display: flex;
- gap: 24px;
+ gap: 20px;
}
.header-nav a {
@@ -2962,6 +3120,7 @@ body {
}
.profile-btn {
+ height:40px;
padding: 8px 16px;
border: none;
border-radius: var(--border-radius-md);
@@ -3934,6 +4093,7 @@ body {
min-width: 48px;
}
.summary-box {
+ position:relative;
margin-top: 16px;
padding: 14px;
background: var(--color-background-secondary);
@@ -4161,6 +4321,19 @@ body {
.tasks-section { flex: 1; overflow-y: auto; padding: 0 24px 24px; scroll-behavior: smooth; }
.tasks-section::-webkit-scrollbar { width: 6px; }
.tasks-section::-webkit-scrollbar-thumb { background: var(--color-border-secondary); border-radius: 10px; }
+.profile-section { flex: 1; overflow-y: auto; padding: 24px; display: flex; flex-direction: column; gap: 24px; }
+.profile-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 20px; }
+.profile-page-title { font-size: 24px; font-weight: 700; color: var(--color-text-primary); }
+.profile-page-subtitle { margin: 8px 0 0; color: var(--color-text-secondary); max-width: 640px; line-height: 1.6; }
+.profile-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }
+.profile-card { background: var(--color-background-primary); border: 1px solid var(--color-border-tertiary); border-radius: var(--border-radius-md); padding: 24px; box-shadow: var(--shadow-sm); }
+.profile-card h2 { margin: 0 0 16px; font-size: 16px; font-weight: 700; color: var(--color-text-primary); }
+.profile-field { display: flex; justify-content: space-between; gap: 16px; margin-bottom: 14px; font-size: 14px; color: var(--color-text-secondary); }
+.profile-field-label { color: var(--color-text-tertiary); font-weight: 600; }
+.profile-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
+.profile-stat-value { display: block; font-size: 22px; font-weight: 700; color: var(--color-text-primary); margin-bottom: 4px; }
+.profile-summary-card p { margin: 0; color: var(--color-text-secondary); line-height: 1.8; }
+@media (max-width: 900px) { .profile-grid { grid-template-columns: 1fr; } }
.tasks-actions-bar {
margin-top: 18px;
display: flex;
@@ -4700,6 +4873,7 @@ body {
font-weight: 600;
}
+
/* Footer */
.site-footer {
width: 100%;
diff --git a/database.js b/database.js
index 33a1b17d..d2a34f89 100644
--- a/database.js
+++ b/database.js
@@ -5,6 +5,7 @@ const db = new sqlite3.Database(path.join(__dirname, 'studyplan.db'));
function initDb() {
db.serialize(() => {
+
// Subjects Table
db.run(`CREATE TABLE IF NOT EXISTS subjects (
id TEXT PRIMARY KEY,
@@ -14,9 +15,18 @@ function initDb() {
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`);
- // Tasks Table
+ // ================= USERS TABLE =================
+ db.run(`CREATE TABLE IF NOT EXISTS users (
+ id TEXT PRIMARY KEY,
+ email TEXT UNIQUE NOT NULL,
+ password TEXT NOT NULL,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ )`);
+
+ // ================= TASKS TABLE =================
db.run(`CREATE TABLE IF NOT EXISTS tasks (
id TEXT PRIMARY KEY,
+ user_email TEXT,
subject_id TEXT,
title TEXT NOT NULL,
description TEXT,
@@ -33,6 +43,21 @@ function initDb() {
FOREIGN KEY (subject_id) REFERENCES subjects(id)
)`);
+ // Add labels column to existing tasks table if it doesn't exist
+ db.all("PRAGMA table_info(tasks)", (err, rows) => {
+ if (err) return;
+
+ const hasLabels = rows.some(r => r.name === 'labels');
+ if (!hasLabels) {
+ db.run("ALTER TABLE tasks ADD COLUMN labels TEXT DEFAULT '[]'");
+ }
+
+ // ================= ADD USER EMAIL COLUMN =================
+ const hasUserEmail = rows.some(r => r.name === 'user_email');
+ if (!hasUserEmail) {
+ db.run("ALTER TABLE tasks ADD COLUMN user_email TEXT");
+ }
+ });
db.all("PRAGMA table_info(tasks)", (err, rows) => {
if (err) return;
@@ -59,15 +84,21 @@ db.all("PRAGMA table_info(tasks)", (err, rows) => {
db.get('SELECT COUNT(*) as count FROM subjects', (err, row) => {
if (row && row.count === 0) {
console.log("Seeding subjects...");
- const stmt = db.prepare("INSERT INTO subjects (id, name, short_code, color) VALUES (?, ?, ?, ?)");
+
+ const stmt = db.prepare(
+ "INSERT INTO subjects (id, name, short_code, color) VALUES (?, ?, ?, ?)"
+ );
+
stmt.run('sub_1', 'Computer Science', 'CS', 'var(--color-text-info)');
stmt.run('sub_2', 'Mathematics', 'Maths', 'var(--color-text-success)');
stmt.run('sub_3', 'English Lit', 'English', 'var(--color-text-purple)');
stmt.run('sub_4', 'Physics', 'Physics', 'var(--color-text-warning)');
+
stmt.finalize();
}
});
+
});
}
-module.exports = { db, initDb };
+module.exports = { db, initDb };
\ No newline at end of file
diff --git a/index.html b/index.html
index 543be764..9c950bd6 100644
--- a/index.html
+++ b/index.html
@@ -55,14 +55,54 @@
StudyPlan
+ Dashboard
+ Tasks
+ Calendar
+ Settings
+
@@ -115,6 +155,21 @@ StudyPlan
April 2026
+
+
+
+
0
+
+
+ Complete tasks to build streak & earn cool badges
+
+
+
All Labels
@@ -207,6 +262,61 @@
StudyPlan
+
+
+
+
+
+
+ Account details
+
+ Username
+ StudyPlan User
+
+
+ Email
+ user@studyplan.app
+
+
+ Member since
+ June 2026
+
+
+
+
+ Study statistics
+
+
+ 0
+ Completed
+
+
+ 0
+ Pending
+
+
+ 0
+ Archived
+
+
+ 0
+ Subjects
+
+
+
+
+
+
+ Account overview
+ Your profile information and study statistics will update automatically as you use StudyPlan.
+
+
+
diff --git a/js/app.js b/js/app.js
index 2bc3bceb..f462681b 100644
--- a/js/app.js
+++ b/js/app.js
@@ -59,8 +59,8 @@ function generateSummary(tasks, subjects) {
const topSubject = Object.keys(subjectCount).length
? Object.keys(subjectCount).reduce((a, b) =>
- subjectCount[a] > subjectCount[b] ? a : b
- )
+ subjectCount[a] > subjectCount[b] ? a : b
+ )
: 'no specific subject';
return `
@@ -99,6 +99,9 @@ const downloadBtn = document.getElementById('download-btn');
const calendarDownloadBtn = document.getElementById('calendar-download-btn');
const newTaskBtn = document.getElementById('add-task-btn');
const labelFilterSelect = document.getElementById('label-filter');
+const profileSection = document.getElementById('profile-section');
+const profileBtn = document.getElementById('profile-btn');
+const topbar = document.querySelector('.topbar');
if (labelFilterSelect) {
labelFilterSelect.addEventListener('change', (e) => {
@@ -484,39 +487,39 @@ if (panelToggleBtn) {
});
}
-if(timerStartBtn) timerStartBtn.addEventListener('click', startTimer);
-if(timerPauseBtn) timerPauseBtn.addEventListener('click', pauseTimer);
-if(timerResetBtn) timerResetBtn.addEventListener('click', resetTimer);
+if (timerStartBtn) timerStartBtn.addEventListener('click', startTimer);
+if (timerPauseBtn) timerPauseBtn.addEventListener('click', pauseTimer);
+if (timerResetBtn) timerResetBtn.addEventListener('click', resetTimer);
function renderFocusTasks() {
- if(!focusTaskList || !activeFocusTask) return;
+ if (!focusTaskList || !activeFocusTask) return;
const tasks = store.tasks;
const subjects = store.subjects;
-
+
const activeTasks = tasks.filter(t => !t.archived && t.status !== 'Done');
const now = new Date();
-
+
const dueSoon = [];
activeTasks.forEach(t => {
- if(!t.due_at) return;
+ if (!t.due_at) return;
const d = new Date(t.due_at);
const diffDays = (d - now) / (1000 * 60 * 60 * 24);
if (diffDays <= 3) dueSoon.push(t);
});
-
- dueSoon.sort((a,b) => new Date(a.due_at) - new Date(b.due_at));
-
+
+ dueSoon.sort((a, b) => new Date(a.due_at) - new Date(b.due_at));
+
if (dueSoon.length === 0) {
focusTaskList.innerHTML = 'No tasks due soon to focus on.
';
} else {
focusTaskList.innerHTML = dueSoon.map(t => {
const sub = subjects.find(s => s.id === t.subject_id) || subjects[0] || { short_code: 'Gen' };
let pillClass = '';
- if(sub.short_code === 'CS') pillClass = 'pill-blue';
- else if(sub.short_code === 'Maths') pillClass = 'pill-green';
- else if(sub.short_code === 'English') pillClass = 'pill-purple';
+ if (sub.short_code === 'CS') pillClass = 'pill-blue';
+ else if (sub.short_code === 'Maths') pillClass = 'pill-green';
+ else if (sub.short_code === 'English') pillClass = 'pill-purple';
else pillClass = 'pill-amber';
-
+
return `
${t.title}
@@ -526,7 +529,7 @@ function renderFocusTasks() {
`;
}).join('');
-
+
document.querySelectorAll('.focus-task-item').forEach(el => {
el.addEventListener('click', () => {
activeFocusTaskId = el.dataset.id;
@@ -534,7 +537,7 @@ function renderFocusTasks() {
});
});
}
-
+
if (activeFocusTaskId) {
const activeT = store.tasks.find(t => t.id === activeFocusTaskId);
if (activeT) {
@@ -552,7 +555,7 @@ function renderFocusTasks() {
`;
-
+
const completeBtn = activeFocusTask.querySelector('.complete-focus-task-btn');
if (completeBtn) {
completeBtn.addEventListener('click', () => {
@@ -561,7 +564,7 @@ function renderFocusTasks() {
renderFocusTasks();
});
}
-
+
const clearBtn = activeFocusTask.querySelector('.clear-focus-task-btn');
if (clearBtn) {
clearBtn.addEventListener('click', () => {
@@ -578,34 +581,117 @@ function renderFocusTasks() {
}
}
+function renderProfileSection() {
+ if (!profileSection) return;
+
+ const tasks = store.tasks || [];
+ const subjects = store.subjects || [];
+ const completedCount = tasks.filter(t => t.status === 'Done').length;
+ const pendingCount = tasks.filter(t => t.status !== 'Done' && !t.archived).length;
+ const archivedCount = tasks.filter(t => t.archived).length;
+ const subjectsCount = subjects.length;
+ const username = localStorage.getItem('studyplan_username') || 'StudyPlan User';
+ const email = localStorage.getItem('studyplan_email') || 'user@studyplan.app';
+ const joinedDate = localStorage.getItem('studyplan_joined') || 'June 2026';
+
+ profileSection.innerHTML = `
+
+
+
+
+ Account details
+
+ Username
+ ${escapeHtml(username)}
+
+
+ Email
+ ${escapeHtml(email)}
+
+
+ Member since
+ ${escapeHtml(joinedDate)}
+
+
+
+
+ Study statistics
+
+
+ ${completedCount}
+ Completed
+
+
+ ${pendingCount}
+ Pending
+
+
+ ${archivedCount}
+ Archived
+
+
+ ${subjectsCount}
+ Subjects
+
+
+
+
+
+
+ Account overview
+ Your profile information and study statistics will update automatically as you use StudyPlan.
+
+ `;
+}
+
+function showProfileSection() {
+ currentView = 'profile';
+ document.querySelector('.cal-section')?.classList.add('hidden');
+ document.getElementById('tasks-section')?.classList.add('hidden');
+ document.getElementById('focus-section')?.classList.add('hidden');
+ profileSection?.classList.remove('hidden');
+ topbar?.classList.add('hidden');
+ renderProfileSection();
+}
+
+function hideProfileSection() {
+ profileSection?.classList.add('hidden');
+ topbar?.classList.remove('hidden');
+}
+
function formatDate(dateStr) {
if (!dateStr) return 'No Date';
const d = new Date(dateStr);
- return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute:'2-digit' });
+ return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
}
async function downloadData() {
- try {
- const response = await fetch('/api/download');
-
- if (!response.ok) {
- throw new Error('Failed to download data');
- }
-
- const blob = await response.blob();
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = 'study_data.csv';
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- setTimeout(() => URL.revokeObjectURL(url), 100);
-
- } catch (error) {
+ try {
+ const response = await fetch('/api/download');
+
+ if (!response.ok) {
+ throw new Error('Failed to download data');
+ }
+
+ const blob = await response.blob();
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'study_data.csv';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ setTimeout(() => URL.revokeObjectURL(url), 100);
+
+ } catch (error) {
console.error(error);
Toast.show('Failed to download data', 'error');
- }
+ }
}
async function downloadCalendar() {
@@ -635,13 +721,13 @@ async function downloadCalendar() {
function renderTasks() {
const tasks = store.tasks;
const subjects = store.subjects;
-
+
if (subjects.length === 0) return; // Wait for subjects to load
-
+
// Filter based on archived status
const activeTasks = tasks.filter(t => !t.archived);
const archivedTasks = tasks.filter(t => t.archived);
-
+
// Update badges
const allTasksBadge = document.querySelector('#all-tasks-btn .badge');
if (allTasksBadge) {
@@ -651,12 +737,14 @@ function renderTasks() {
if (archivedBadge) {
archivedBadge.textContent = archivedTasks.length;
}
+
+
const displayTasksRaw = currentView === 'archived' ? archivedTasks : activeTasks;
const displayTasks = activeLabelFilter
? displayTasksRaw.filter(t => t.labels && t.labels.includes(activeLabelFilter))
: displayTasksRaw;
-
+
// Extract unique labels to populate the filter dropdown
if (labelFilterSelect) {
const uniqueLabels = new Set();
@@ -683,7 +771,7 @@ function renderTasks() {
const thisWeek = [];
const completed = [];
const pending = [];
-
+
if (currentView === 'calendar' && selectedDate) {
sorted.forEach(t => {
const d = new Date(t.due_at);
@@ -708,14 +796,14 @@ function renderTasks() {
else thisWeek.push(t);
});
}
-
+
const renderGroup = (title, items, titleColor, showConflict = false) => {
if (items.length === 0) return '';
let html = `
`;
-
+
if (showConflict) {
const workloadSuggestions = analyzeWorkload(items);
workloadSuggestions.forEach(workload => {
@@ -726,26 +814,27 @@ function renderTasks() {
`;
});
}
-
-
+
+
items.forEach(t => {
const sub = subjects.find(s => s.id === t.subject_id) || subjects[0];
const isDone = t.status === 'Done';
+
const isHighPriority = t.priority === 'high';
const isOverdue = !isDone && t.due_at && new Date(t.due_at) < now;
const isUrgent = isHighPriority && title === '⚠ Due soon';
let pillClass = '';
- if(sub.short_code === 'CS') pillClass = 'pill-blue';
- else if(sub.short_code === 'Maths') pillClass = 'pill-green';
- else if(sub.short_code === 'English') pillClass = 'pill-purple';
+ if (sub.short_code === 'CS') pillClass = 'pill-blue';
+ else if (sub.short_code === 'Maths') pillClass = 'pill-green';
+ else if (sub.short_code === 'English') pillClass = 'pill-purple';
else pillClass = 'pill-amber';
-
+
if (t._isEditing) {
- let subjectOptions = subjects.map(s =>
+ let subjectOptions = subjects.map(s =>
`${s.name} `
).join('');
-
+
const localDate = t.due_at ? new Date(t.due_at).toISOString().substring(0, 16) : '';
const isHighPriority = t.priority === 'high';
const editDurationUnit = t.is_estimated_duration_min === 0 ? 'hours' : 'minutes';
@@ -826,9 +915,9 @@ function renderTasks() {
html += ``;
return html;
};
-
+
if (currentView === 'calendar' && selectedDate) {
- const selStr = selectedDate.toLocaleDateString('en-US', {month:'short', day:'numeric'});
+ const selStr = selectedDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
const actionBar = `
Mark all pending completed (${pending.length})
Mark selected day completed
@@ -862,9 +951,9 @@ function renderTasks() {
}
tasksSection.innerHTML = actionBar +
- renderGroup(`Tasks for ${selStr}`, dueSoon, 'var(--color-text-primary)') +
- renderGroup('Completed', completed, 'var(--color-text-tertiary)') +
- emptyState;
+ renderGroup(`Tasks for ${selStr}`, dueSoon, 'var(--color-text-primary)') +
+ renderGroup('Completed', completed, 'var(--color-text-tertiary)') +
+ emptyState;
} else {
console.log('[Daily Study Time] Hiding banner - currentView:', currentView, 'selectedDate:', selectedDate);
const studyTimeEl = document.getElementById('daily-study-time');
@@ -914,11 +1003,11 @@ function renderTasks() {
document.querySelectorAll('.task-item').forEach(el => {
el.addEventListener('click', (e) => {
if (e.target.closest('.task-actions') || e.target.closest('.task-check')) return;
-
+
const taskId = el.dataset.id;
const task = store.tasks.find(t => String(t.id) === String(taskId));
if (task && task._isEditing) return;
-
+
store.toggleTaskStatus(taskId);
});
});
@@ -954,6 +1043,7 @@ function renderTasks() {
e.stopPropagation();
const taskId = el.dataset.id;
const itemEl = el.closest('.task-item');
+
const rawTitle = itemEl.querySelector('.board-edit-title').value;
const subject_id = itemEl.querySelector('.board-edit-subject').value;
@@ -1037,32 +1127,32 @@ function renderCalendar() {
const calTitle = document.getElementById('cal-month-title');
const calGrid = document.getElementById('cal-grid');
if (!calGrid) return;
-
+
const year = currentMonthDate.getFullYear();
const month = currentMonthDate.getMonth();
-
+
const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
calTitle.textContent = `${monthNames[month]} ${year}`;
-
+
const topbarTitle = document.querySelector('.topbar-title');
- if(topbarTitle) topbarTitle.textContent = `${monthNames[month]} ${year}`;
+ if (topbarTitle) topbarTitle.textContent = `${monthNames[month]} ${year}`;
const firstDay = new Date(year, month, 1).getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
const prevMonthDays = new Date(year, month, 0).getDate();
-
+
const today = new Date();
-
+
let html = `
Su
Mo
Tu
We
Th
Fr
Sa
`;
-
+
for (let i = 0; i < firstDay; i++) {
html += `
${prevMonthDays - firstDay + i + 1}
`;
}
-
+
for (let i = 1; i <= daysInMonth; i++) {
const isToday = i === today.getDate() && month === today.getMonth() && year === today.getFullYear();
const isSelected = selectedDate && i === selectedDate.getDate() && month === selectedDate.getMonth() && year === selectedDate.getFullYear();
-
+
// Find tasks for this day
const dayTasks = store.tasks.filter(t => {
if (t.archived) return false;
@@ -1076,9 +1166,9 @@ function renderCalendar() {
if (dayTasks.length > 0) {
indicatorHtml = `
`;
dayTasks.forEach((t, idx) => {
- if (idx > 2) return;
- const sub = store.subjects.find(s => s.id === t.subject_id) || store.subjects[0];
- indicatorHtml += `
`;
+ if (idx > 2) return;
+ const sub = store.subjects.find(s => s.id === t.subject_id) || store.subjects[0];
+ indicatorHtml += `
`;
});
indicatorHtml += `
`;
}
@@ -1090,13 +1180,13 @@ function renderCalendar() {
${indicatorHtml}
`;
}
-
+
const totalCells = firstDay + daysInMonth;
const nextDays = (7 - (totalCells % 7)) % 7;
for (let i = 1; i <= nextDays; i++) {
html += `${i}
`;
}
-
+
calGrid.innerHTML = html;
// Bind day clicks
@@ -1104,7 +1194,7 @@ function renderCalendar() {
el.addEventListener('click', (e) => {
const d = parseInt(e.currentTarget.getAttribute('data-day'));
const clickedDate = new Date(year, month, d);
-
+
if (selectedDate && clickedDate.getTime() === selectedDate.getTime()) {
selectedDate = null;
} else {
@@ -1124,24 +1214,24 @@ function renderExtraction() {
addItemsBtn.textContent = 'Add items to planner';
return;
}
-
+
addItemsBtn.disabled = false;
addItemsBtn.textContent = `Add ${pasteItems.length} items to planner`;
-
+
let html = ``;
pasteItems.forEach((item, index) => {
// try to match subject name
const sub = store.subjects.find(s => s.name.toLowerCase().includes((item.subject_name || '').toLowerCase())) || store.subjects[3];
// Attach subject id to item so Add will work
item.subject_id = sub.id;
-
+
if (item._isEditing) {
- let subjectOptions = store.subjects.map(s =>
+ let subjectOptions = store.subjects.map(s =>
`${s.name} `
).join('');
-
+
const localDate = item.due_at ? new Date(item.due_at).toISOString().substring(0, 16) : '';
-
+
html += `