From f1b1767eecc2d6c2a3a9413750b94c8d7446b301 Mon Sep 17 00:00:00 2001 From: Chinmayee Wuriti Date: Thu, 14 May 2026 18:43:34 +0530 Subject: [PATCH 01/15] ui: add month/week/day toggle buttons to topbar --- index.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 7f7e340..ba58480 100644 --- a/index.html +++ b/index.html @@ -94,8 +94,12 @@

StudyPlan

April 2026
- - + + +
+
From bb215156daf9f241241dd78f97dda14b5a944652 Mon Sep 17 00:00:00 2001 From: Chinmayee Wuriti Date: Thu, 14 May 2026 18:43:40 +0530 Subject: [PATCH 02/15] style: design view toggle button group --- css/index.css | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) diff --git a/css/index.css b/css/index.css index f5ee2db..d0ec279 100644 --- a/css/index.css +++ b/css/index.css @@ -269,6 +269,46 @@ body { border-color: var(--color-text-info); } +.task-count { + font-size: 10px; + color: var(--color-text-secondary); + text-align: center; + margin-top: 2px; + font-weight: 600; +} +.view-toggle-group { + display: flex; + gap: 4px; + background: var(--color-background-secondary); + padding: 4px; + border-radius: 12px; +} + +.view-btn { + padding: 6px 16px; + font-size: 13px; + font-weight: 500; + background: transparent; + border: none; + border-radius: 8px; + cursor: pointer; + color: var(--color-text-secondary); + transition: all 0.2s ease; +} + +.view-btn.active { + background: var(--color-background-primary); + color: var(--color-text-primary); + box-shadow: 0 1px 3px rgba(0,0,0,0.1); +} + +.view-btn:hover:not(.active) { + background: var(--color-background-hover); + color: var(--color-text-primary); +} + + + /* Right Panel */ .panel { border-left: 1px solid var(--color-border-tertiary); display: flex; flex-direction: column; background: var(--color-background-secondary); overflow: hidden; } .panel.panel-collapsed { overflow: visible; } @@ -1248,6 +1288,142 @@ body { font-weight: 600; } +.simple-day-view { + padding: 16px; +} + +.day-header-simple { + margin-bottom: 24px; + padding-bottom: 12px; + border-bottom: 2px solid var(--color-border-secondary); +} + +.day-header-simple h3 { + margin: 0 0 8px 0; + font-size: 18px; + font-weight: 600; + color: var(--color-text-primary); +} + +.task-stats { + font-size: 13px; + color: var(--color-text-tertiary); +} + +.tasks-list-simple { + display: flex; + flex-direction: column; + gap: 8px; +} + +.task-group-simple { + margin: 16px 0 8px 0; + font-size: 14px; + color: var(--color-text-secondary); +} + +.task-row { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 12px; + background: var(--color-background-primary); + border: 1px solid var(--color-border-secondary); + border-radius: 8px; + cursor: pointer; + transition: all 0.2s; +} + +.task-row:hover { + background: var(--color-background-hover); + transform: translateX(2px); +} + +.task-checkbox { + width: 18px; + height: 18px; + border: 2px solid var(--color-border-primary); + border-radius: 4px; + cursor: pointer; + flex-shrink: 0; +} + +.task-checkbox.checked { + background: var(--color-text-success); + border-color: var(--color-text-success); + position: relative; +} + +.task-checkbox.checked::after { + content: "✓"; + color: white; + font-size: 12px; + display: flex; + align-items: center; + justify-content: center; + line-height: 1; +} + +.task-details { + flex: 1; + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +.task-name { + font-size: 14px; + font-weight: 500; + color: var(--color-text-primary); +} + +.task-name.completed { + text-decoration: line-through; + color: var(--color-text-tertiary); +} + +.task-time { + font-size: 12px; + color: var(--color-text-info); + font-family: monospace; +} + +.task-subject-simple { + font-size: 10px; + padding: 2px 8px; + border-radius: 12px; + font-weight: 600; +} + +.completed-row { + opacity: 0.7; +} + +.empty-day-simple { + text-align: center; + padding: 48px 20px; + background: var(--color-background-secondary); + border-radius: 12px; +} + +.empty-emoji { + font-size: 48px; + display: block; + margin-bottom: 12px; +} + +.empty-day-simple p { + margin: 0 0 8px 0; + font-size: 16px; + font-weight: 500; + color: var(--color-text-primary); +} + +.empty-day-simple small { + font-size: 12px; + color: var(--color-text-tertiary); +} /* Footer */ .site-footer { width: 100%; From 63bc44c117f8b35e7d41b466894b9876de4b5bac Mon Sep 17 00:00:00 2001 From: Chinmayee Wuriti Date: Thu, 14 May 2026 18:43:46 +0530 Subject: [PATCH 03/15] feat: implement week calendar view --- js/app.js | 307 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 297 insertions(+), 10 deletions(-) diff --git a/js/app.js b/js/app.js index 335bafc..cf4c6a8 100644 --- a/js/app.js +++ b/js/app.js @@ -7,7 +7,7 @@ initGlobalErrorBoundary(); let currentMonthDate = new Date(); let selectedDate = null; let currentView = 'calendar'; // 'calendar', 'all-tasks', 'archived' - +let currentCalendarView = 'month'; const tasksSection = document.getElementById('tasks-section'); const focusSection = document.getElementById('focus-section'); const extractPreview = document.getElementById('extract-preview'); @@ -610,6 +610,137 @@ function renderTasks() { } } + +function renderDayView() { + const year = currentMonthDate.getFullYear(); + const month = currentMonthDate.getMonth(); + const day = currentMonthDate.getDate(); + + const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; + + // Update title + const topbarTitle = document.querySelector('.topbar-title'); + if(topbarTitle) { + topbarTitle.textContent = `${monthNames[month]} ${day}, ${year}`; + } + document.getElementById('cal-month-title').textContent = `${monthNames[month]} ${day}, ${year}`; + + // Get tasks for this day + const dayTasks = store.tasks.filter(t => { + if (t.archived) return false; + if (!t.due_at) return false; + const d = new Date(t.due_at); + return d.getDate() === day && d.getMonth() === month && d.getFullYear() === year; + }); + + // Filter tasks + const pending = dayTasks.filter(t => t.status !== 'Done'); + const completed = dayTasks.filter(t => t.status === 'Done'); + + // Sort pending by time + pending.sort((a, b) => new Date(a.due_at) - new Date(b.due_at)); + + let html = `
`; + + // Simple header + html += `
+

Tasks for Today

+
${pending.length} pending · ${completed.length} completed
+
`; + + if (pending.length === 0 && completed.length === 0) { + html += `
+
-
+
No tasks for today
+
Click "New task" to add one
+
`; + } else { + // Pending tasks + if (pending.length > 0) { + html += `
+
To do
+
`; + + pending.forEach(task => { + const sub = store.subjects.find(s => s.id === task.subject_id) || store.subjects[0]; + const timeStr = new Date(task.due_at).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); + + html += ` +
+
+
+
${escapeHtml(task.title)}
+
+ + ${sub?.short_code || 'Task'} +
+
+
+ `; + }); + + html += `
`; + } + + // Completed tasks + if (completed.length > 0) { + html += `
+
Done
+
`; + + completed.forEach(task => { + const sub = store.subjects.find(s => s.id === task.subject_id) || store.subjects[0]; + + html += ` +
+
+
+
${escapeHtml(task.title)}
+ ${sub?.short_code || 'Task'} +
+
+ `; + }); + + html += `
`; + } + } + + html += `
`; + + document.getElementById('cal-grid').innerHTML = html; +} + +// Add this helper function for toggling tasks from inline onclick +window.toggleTask = function(taskId) { + store.toggleTaskStatus(taskId); + setTimeout(() => { + if (currentCalendarView === 'day') renderDayView(); + else if (currentCalendarView === 'week') renderWeekCalendar(); + else if (currentCalendarView === 'month') renderCalendar(); + renderTasks(); + }, 50); +}; + + function renderCalendar() { const calTitle = document.getElementById('cal-month-title'); const calGrid = document.getElementById('cal-grid'); @@ -652,11 +783,11 @@ function renderCalendar() { let indicatorHtml = ''; 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 += `
`; + dayTasks.slice(0, 3).forEach(t => { + const sub = store.subjects.find(s => s.id === t.subject_id) || store.subjects[0]; + indicatorHtml += `
`; }); + indicatorHtml += `
${dayTasks.length}
`; // ADD THIS LINE indicatorHtml += `
`; } @@ -693,6 +824,93 @@ function renderCalendar() { }); } + +function renderWeekCalendar() { + const year = currentMonthDate.getFullYear(); + const month = currentMonthDate.getMonth(); + const today = currentMonthDate.getDate(); + + const currentDay = new Date(year, month, today); + const dayOfWeek = currentDay.getDay(); + const monday = new Date(currentDay); + monday.setDate(currentDay.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1)); + + const monthNames = ["January","February","March","April","May","June","July","August","September","October","November","December"]; + + // Update the title to show week range + const weekEnd = new Date(monday); + weekEnd.setDate(monday.getDate() + 6); + const topbarTitle = document.querySelector('.topbar-title'); + if(topbarTitle) { + topbarTitle.textContent = `${monthNames[monday.getMonth()]} ${monday.getDate()} - ${monthNames[weekEnd.getMonth()]} ${weekEnd.getDate()}, ${monday.getFullYear()}`; + } + + document.getElementById('cal-month-title').textContent = `${monthNames[month]} ${year}`; + + let html = `
Mon
+
Tue
+
Wed
+
Thu
+
Fri
+
Sat
+
Sun
`; + + for(let i = 0; i < 7; i++){ + const date = new Date(monday); + date.setDate(monday.getDate() + i); + const dayNum = date.getDate(); + const isToday = dayNum === new Date().getDate() && + date.getMonth() === new Date().getMonth() && + date.getFullYear() === new Date().getFullYear(); + + // Check if this day has tasks + const dayTasks = store.tasks.filter(t => { + if (t.archived) return false; + if (t.status === 'Done') return false; + if (!t.due_at) return false; + const d = new Date(t.due_at); + return d.getDate() === dayNum && d.getMonth() === date.getMonth() && d.getFullYear() === date.getFullYear(); + }); + + let indicatorHtml = ''; + 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 += `
`; + }); + indicatorHtml += `
`; + } + + html += `
+ ${dayNum} + ${indicatorHtml} +
`; + } + + document.getElementById('cal-grid').innerHTML = html; + + document.querySelectorAll('#cal-grid .interactive-day').forEach(el => { + el.addEventListener('click', (e) => { + const day = parseInt(el.dataset.day); + const month = parseInt(el.dataset.month); + const year = parseInt(el.dataset.year); + currentMonthDate = new Date(year, month, day); + currentCalendarView = 'day'; + + document.querySelectorAll('.view-btn').forEach(btn => { + btn.classList.remove('active'); + if(btn.dataset.view === 'day') btn.classList.add('active'); + }); + + renderDayView(); + renderTasks(); + }); +}); +} + + function renderExtraction() { const pasteItems = store.currentPaste; if (!pasteItems || pasteItems.length === 0) { @@ -797,6 +1015,11 @@ store.subscribe(renderExtraction); store.subscribe(renderCalendar); store.subscribe(renderFocusTasks); store.subscribe(renderSidebarSubjects); +store.subscribe(() => { + if (currentCalendarView === 'month') renderCalendar(); + else if (currentCalendarView === 'week') renderWeekCalendar(); + else if (currentCalendarView === 'day') renderDayView(); +}); document.addEventListener('DOMContentLoaded', () => { if (newSubjectColorsEl) { @@ -844,6 +1067,8 @@ document.addEventListener('DOMContentLoaded', () => { }); } + + if (newSubjectName) { newSubjectName.addEventListener('keydown', (e) => { if (e.key === 'Enter') { @@ -860,6 +1085,48 @@ document.addEventListener('DOMContentLoaded', () => { const archivedTasksBtn = document.getElementById('archived-tasks-btn'); const focusModeBtn = document.getElementById('focus-mode-btn'); + // View toggle buttons - replace the old week button code +const viewBtns = document.querySelectorAll('.view-btn'); +if(viewBtns.length > 0) { + viewBtns.forEach(btn => { + btn.addEventListener('click', () => { + currentCalendarView = btn.dataset.view; + + // Update active class + viewBtns.forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + + // Render the selected view + if(currentCalendarView === 'month') { + renderCalendar(); + } else if(currentCalendarView === 'week') { + renderWeekCalendar(); + } else if(currentCalendarView === 'day') { + renderDayView(); + } + renderTasks(); + }); + }); +} else { + + + const weekBtn = document.querySelector('.topbar .btn'); + if(weekBtn && weekBtn.textContent === 'Week') { + weekBtn.addEventListener('click', () => { + if(currentCalendarView === 'month') { + currentCalendarView = 'week'; + renderWeekCalendar(); + weekBtn.textContent = 'Month'; + } else { + currentCalendarView = 'month'; + renderCalendar(); + weekBtn.textContent = 'Week'; + } + renderTasks(); + }); + } +} + function updateSidebarActive(id) { document.querySelectorAll('.sidebar .nav-item').forEach(el => el.classList.remove('active')); document.getElementById(id).classList.add('active'); @@ -892,6 +1159,7 @@ document.addEventListener('DOMContentLoaded', () => { renderTasks(); }); + if(focusModeBtn) { focusModeBtn.addEventListener('click', () => { currentView = 'focus'; @@ -903,18 +1171,37 @@ document.addEventListener('DOMContentLoaded', () => { }); } - document.getElementById('cal-prev').addEventListener('click', () => { +document.getElementById('cal-prev').addEventListener('click', () => { + + if(currentCalendarView === 'month') { currentMonthDate.setMonth(currentMonthDate.getMonth() - 1); renderCalendar(); - }); + } else if(currentCalendarView === 'week') { + currentMonthDate.setDate(currentMonthDate.getDate() - 7); + renderWeekCalendar(); + } else if(currentCalendarView === 'day') { + currentMonthDate.setDate(currentMonthDate.getDate() - 1); + renderDayView(); + } + + renderTasks(); +}); - document.getElementById('cal-next').addEventListener('click', () => { +document.getElementById('cal-next').addEventListener('click', () => { + if(currentCalendarView === 'month') { currentMonthDate.setMonth(currentMonthDate.getMonth() + 1); renderCalendar(); - }); + } else if(currentCalendarView === 'week') { + currentMonthDate.setDate(currentMonthDate.getDate() + 7); + renderWeekCalendar(); + } else if(currentCalendarView === 'day') { + currentMonthDate.setDate(currentMonthDate.getDate() + 1); + renderDayView(); + } + renderTasks(); +}); -//NEw Task addition event listeners newTaskBtn.addEventListener('click', () => { if (!store.subjects || store.subjects.length === 0) { From 0cd36771571b08f800071093341da4e934ddfc19 Mon Sep 17 00:00:00 2001 From: Chinmayee Wuriti Date: Thu, 14 May 2026 18:48:27 +0530 Subject: [PATCH 04/15] feat: integrate navigation for month/week/day views --- js/app.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/js/app.js b/js/app.js index cf4c6a8..b10dc23 100644 --- a/js/app.js +++ b/js/app.js @@ -1172,7 +1172,6 @@ if(viewBtns.length > 0) { } document.getElementById('cal-prev').addEventListener('click', () => { - if(currentCalendarView === 'month') { currentMonthDate.setMonth(currentMonthDate.getMonth() - 1); renderCalendar(); @@ -1183,7 +1182,6 @@ document.getElementById('cal-prev').addEventListener('click', () => { currentMonthDate.setDate(currentMonthDate.getDate() - 1); renderDayView(); } - renderTasks(); }); @@ -1201,7 +1199,6 @@ document.getElementById('cal-next').addEventListener('click', () => { renderTasks(); }); - newTaskBtn.addEventListener('click', () => { if (!store.subjects || store.subjects.length === 0) { From a424df231285627c0b9a1c96ad453b680658fba6 Mon Sep 17 00:00:00 2001 From: Chinmayee Wuriti Date: Sat, 16 May 2026 09:03:10 +0530 Subject: [PATCH 05/15] Fixed dot alignment --- js/app.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/js/app.js b/js/app.js index b10dc23..5362df5 100644 --- a/js/app.js +++ b/js/app.js @@ -782,12 +782,14 @@ function renderCalendar() { let indicatorHtml = ''; if (dayTasks.length > 0) { - indicatorHtml = `
`; + indicatorHtml = `
`; dayTasks.slice(0, 3).forEach(t => { const sub = store.subjects.find(s => s.id === t.subject_id) || store.subjects[0]; - indicatorHtml += `
`; + indicatorHtml += `
`; }); - indicatorHtml += `
${dayTasks.length}
`; // ADD THIS LINE + if (dayTasks.length > 3) { + indicatorHtml += `+${dayTasks.length - 3}`; + } indicatorHtml += `
`; } From e7f1482ac70d4551766f9d484d475e5d92ef8e42 Mon Sep 17 00:00:00 2001 From: Chinmayee Wuriti Date: Sat, 16 May 2026 10:49:35 +0530 Subject: [PATCH 06/15] Improved Day View layout --- css/index.css | 108 +++++++++++++++++++++++++++++++++++++++ js/app.js | 137 ++++++++++++++++++++++---------------------------- 2 files changed, 167 insertions(+), 78 deletions(-) diff --git a/css/index.css b/css/index.css index 4930401..ef955e2 100644 --- a/css/index.css +++ b/css/index.css @@ -1303,6 +1303,114 @@ body { padding: 16px; } + +.cal-grid:has(.day-view-checklist) { + display: block; + width: 100%; +} + +.day-view-checklist { + padding: 24px; + width: 100%; + max-width: 100%; + margin: 0; +} + +.checklist-header { + font-size: 18px; + font-weight: 600; + padding-bottom: 16px; + margin-bottom: 20px; + border-bottom: 2px solid var(--color-border-secondary); +} + +.checklist-item { + display: flex; + align-items: center; + gap: 12px; + padding: 14px 0; + border-bottom: 1px solid var(--color-border-secondary); +} + +.checklist-box { + width: 20px; + height: 20px; + border: 2px solid var(--color-border-primary); + border-radius: 4px; + flex-shrink: 0; +} + +.checklist-box.checked { + background: #4caf50; + border-color: #4caf50; + position: relative; +} + +.checklist-box.checked::after { + content: ""; + position: absolute; + top: 2px; + left: 6px; + width: 5px; + height: 10px; + border: solid white; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} + +.checklist-text { + flex: 1; + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 12px; +} + +.checklist-title { + font-size: 15px; + font-weight: 500; + color: var(--color-text-primary); +} + +.checklist-title.done { + text-decoration: line-through; + color: var(--color-text-tertiary); +} + +.checklist-subject { + font-size: 12px; + padding: 2px 12px; + border-radius: 12px; + background: var(--color-background-secondary); + font-weight: 500; +} + +.checklist-empty { + text-align: center; + padding: 80px 20px; + color: var(--color-text-tertiary); + font-size: 14px; +} + +.checklist-add-btn { + width: 100%; + padding: 12px; + margin-top: 24px; + background: transparent; + border: 1px dashed var(--color-border-primary); + border-radius: 8px; + color: var(--color-text-secondary); + font-size: 14px; + cursor: pointer; +} + +.checklist-add-btn:hover { + background: var(--color-background-hover); + border-color: var(--color-text-info); + color: var(--color-text-info); +} + .day-header-simple { margin-bottom: 24px; padding-bottom: 12px; diff --git a/js/app.js b/js/app.js index ae13175..a73909e 100644 --- a/js/app.js +++ b/js/app.js @@ -664,14 +664,12 @@ function renderDayView() { const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; - // Update title const topbarTitle = document.querySelector('.topbar-title'); if(topbarTitle) { topbarTitle.textContent = `${monthNames[month]} ${day}, ${year}`; } document.getElementById('cal-month-title').textContent = `${monthNames[month]} ${day}, ${year}`; - // Get tasks for this day const dayTasks = store.tasks.filter(t => { if (t.archived) return false; if (!t.due_at) return false; @@ -679,100 +677,83 @@ function renderDayView() { return d.getDate() === day && d.getMonth() === month && d.getFullYear() === year; }); - // Filter tasks const pending = dayTasks.filter(t => t.status !== 'Done'); const completed = dayTasks.filter(t => t.status === 'Done'); - - // Sort pending by time pending.sort((a, b) => new Date(a.due_at) - new Date(b.due_at)); - let html = `
`; + let html = '
'; - // Simple header - html += `
-

Tasks for Today

-
${pending.length} pending · ${completed.length} completed
-
`; + html += '
'; + html += 'Tasks on ' + monthNames[month] + ' ' + day + ', ' + year; + html += '
'; if (pending.length === 0 && completed.length === 0) { - html += `
-
-
-
No tasks for today
-
Click "New task" to add one
-
`; + html += '
No tasks for this day
'; + html += ''; } else { - // Pending tasks if (pending.length > 0) { - html += `
-
To do
-
`; - - pending.forEach(task => { + for (let i = 0; i < pending.length; i++) { + const task = pending[i]; const sub = store.subjects.find(s => s.id === task.subject_id) || store.subjects[0]; - const timeStr = new Date(task.due_at).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); - - html += ` -
-
-
-
${escapeHtml(task.title)}
-
- ${timeStr} - ${sub?.short_code || 'Task'} -
-
-
- `; - }); - - html += `
`; + html += '
'; + html += '
'; + html += '
'; + html += '' + escapeHtml(task.title) + ''; + html += '' + (sub?.short_code || 'Task') + ''; + html += '
'; + html += '
'; + } } - // Completed tasks if (completed.length > 0) { - html += `
-
Done
-
`; - - completed.forEach(task => { + for (let i = 0; i < completed.length; i++) { + const task = completed[i]; const sub = store.subjects.find(s => s.id === task.subject_id) || store.subjects[0]; - - html += ` -
-
-
-
${escapeHtml(task.title)}
- ${sub?.short_code || 'Task'} -
-
- `; - }); - - html += `
`; + html += '
'; + html += '
'; + html += '
'; + html += '' + escapeHtml(task.title) + ''; + html += '' + (sub?.short_code || 'Task') + ''; + html += '
'; + html += '
'; + } } } - html += `
`; - + html += '
'; document.getElementById('cal-grid').innerHTML = html; + + const items = document.querySelectorAll('.checklist-item'); + for (let i = 0; i < items.length; i++) { + const item = items[i]; + item.addEventListener('click', function(e) { + if (e.target.classList.contains('checklist-box')) return; + const taskId = this.dataset.id; + if (taskId) { + store.toggleTaskStatus(taskId); + setTimeout(function() { renderDayView(); }, 50); + } + }); + + const box = item.querySelector('.checklist-box'); + if (box) { + box.addEventListener('click', function(e) { + e.stopPropagation(); + const taskId = item.dataset.id; + if (taskId) { + store.toggleTaskStatus(taskId); + setTimeout(function() { renderDayView(); }, 50); + } + }); + } + } + + const addBtn = document.getElementById('day-add-task-btn'); + if (addBtn) { + addBtn.addEventListener('click', function() { + document.getElementById('add-task-btn').click(); + }); + } } // Add this helper function for toggling tasks from inline onclick From ab9d9e17525ae902819d9081ee5d83c749cbb33f Mon Sep 17 00:00:00 2001 From: Chinmayee Wuriti Date: Sun, 17 May 2026 14:36:08 +0530 Subject: [PATCH 07/15] fix: prevent task indicators from overlapping date numbers --- css/index.css | 32 ++++++++++++++++++++++++++++++++ js/app.js | 48 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/css/index.css b/css/index.css index ef955e2..86ca7cc 100644 --- a/css/index.css +++ b/css/index.css @@ -1793,6 +1793,38 @@ body { .cal-legend span { display: flex; align-items: center; gap: 6px; } .legend-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; } +.cal-day { + position: relative; + min-height: 65px; + padding: 8px 4px 4px 4px; + display: flex; + flex-direction: column; + align-items: center; + font-size: 13px; +} + +.cal-day-indicators { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 2px; + margin-top: 4px; + max-width: 100%; +} + +.cal-day-indicator { + width: 5px; + height: 5px; + border-radius: 50%; + flex-shrink: 0; +} + +.task-count { + font-size: 8px; + color: var(--color-text-tertiary); + margin-left: 2px; +} + /* Tasks */ .tasks-section { flex: 1; overflow-y: auto; padding: 0 24px 24px; scroll-behavior: smooth; } .tasks-section::-webkit-scrollbar { width: 6px; } diff --git a/js/app.js b/js/app.js index a73909e..7f7ac25 100644 --- a/js/app.js +++ b/js/app.js @@ -813,14 +813,23 @@ function renderCalendar() { let indicatorHtml = ''; if (dayTasks.length > 0) { - indicatorHtml = `
`; - dayTasks.slice(0, 3).forEach(t => { - const sub = store.subjects.find(s => s.id === t.subject_id) || store.subjects[0]; - indicatorHtml += `
`; - }); - if (dayTasks.length > 3) { - indicatorHtml += `+${dayTasks.length - 3}`; + const maxDots = 3; + const hasMore = dayTasks.length > maxDots; + const dotsToShow = hasMore ? maxDots - 1 : dayTasks.length; + + indicatorHtml = `
`; + + for (let i = 0; i < dotsToShow; i++) { + const sub = store.subjects.find(s => s.id === dayTasks[i].subject_id) || store.subjects[0]; + indicatorHtml += `
`; } + + if (hasMore) { + indicatorHtml += `
+${dayTasks.length - (maxDots - 1)}
`; + } else if (dayTasks.length <= maxDots) { + indicatorHtml += `
${dayTasks.length}
`; + } + indicatorHtml += `
`; } @@ -907,19 +916,26 @@ function renderWeekCalendar() { let indicatorHtml = ''; if (dayTasks.length > 0) { + const maxDots = 3; + const hasMore = dayTasks.length > maxDots; + const dotsToShow = hasMore ? maxDots - 1 : dayTasks.length; + indicatorHtml = `
`; - dayTasks.forEach((t, idx) => { - if (idx > 2) return; - const sub = store.subjects.find(s => s.id === t.subject_id) || store.subjects[0]; + + for (let i = 0; i < dotsToShow; i++) { + const sub = store.subjects.find(s => s.id === dayTasks[i].subject_id) || store.subjects[0]; indicatorHtml += `
`; - }); + } + + if (hasMore) { + indicatorHtml += `
+${dayTasks.length - (maxDots - 1)}
`; + } else if (dayTasks.length <= maxDots) { + indicatorHtml += `
${dayTasks.length}
`; + } + indicatorHtml += `
`; } - - html += `
- ${dayNum} - ${indicatorHtml} -
`; + } document.getElementById('cal-grid').innerHTML = html; From 0f88ab96e22de862438e76435530979da736f712 Mon Sep 17 00:00:00 2001 From: Chinmayee Wuriti Date: Mon, 18 May 2026 14:26:39 +0530 Subject: [PATCH 08/15] Improved Week toggle and added arrow functionality for day view --- css/index.css | 28 ++++++++++++++++++++++++++++ js/app.js | 42 ++++++++++++++++++++++++++++-------------- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/css/index.css b/css/index.css index 86ca7cc..824d0a2 100644 --- a/css/index.css +++ b/css/index.css @@ -156,6 +156,34 @@ body { .cal-legend span { display: flex; align-items: center; gap: 6px; } .legend-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; } +.cal-week-view .cal-day { + min-height: 100px; + position: relative; + font-size: 18px; + font-weight: 700; + color: var(--color-text-primary); + padding-top: 12px; + display: flex; + flex-direction: column; + align-items: center; +} + +.cal-week-view .cal-day-indicators { + position: absolute; + bottom: 8px; + left: 0; + right: 0; + display: flex; + justify-content: center; + gap: 4px; +} + +.cal-week-view .cal-day-indicator { + width: 8px; + height: 8px; + border-radius: 50%; +} + /* Tasks */ .tasks-section { flex: 1; overflow-y: auto; padding: 0 24px 24px; scroll-behavior: smooth; } .tasks-section::-webkit-scrollbar { width: 6px; } diff --git a/js/app.js b/js/app.js index 7f7ac25..bdb32aa 100644 --- a/js/app.js +++ b/js/app.js @@ -658,9 +658,10 @@ function renderTasks() { function renderDayView() { - const year = currentMonthDate.getFullYear(); - const month = currentMonthDate.getMonth(); - const day = currentMonthDate.getDate(); + const targetDate = selectedDate || currentMonthDate; + const year = targetDate.getFullYear(); + const month = targetDate.getMonth(); + const day = targetDate.getDate(); const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; @@ -755,8 +756,6 @@ function renderDayView() { }); } } - -// Add this helper function for toggling tasks from inline onclick window.toggleTask = function(taskId) { store.toggleTaskStatus(taskId); setTimeout(() => { @@ -833,6 +832,7 @@ function renderCalendar() { indicatorHtml += `
`; } + document.getElementById('cal-grid').className = 'cal-grid cal-month-view'; const extraStyle = isSelected ? `border: 1.5px solid var(--color-text-primary);` : ''; html += `
@@ -915,15 +915,15 @@ function renderWeekCalendar() { }); let indicatorHtml = ''; - if (dayTasks.length > 0) { + if (dayTasks.length > 0) { const maxDots = 3; const hasMore = dayTasks.length > maxDots; const dotsToShow = hasMore ? maxDots - 1 : dayTasks.length; indicatorHtml = `
`; - for (let i = 0; i < dotsToShow; i++) { - const sub = store.subjects.find(s => s.id === dayTasks[i].subject_id) || store.subjects[0]; + for (let j = 0; j < dotsToShow; j++) { + const sub = store.subjects.find(s => s.id === dayTasks[j].subject_id) || store.subjects[0]; indicatorHtml += `
`; } @@ -935,6 +935,11 @@ function renderWeekCalendar() { indicatorHtml += `
`; } + + html += `
+ ${dayNum} + ${indicatorHtml} +
`; } @@ -945,7 +950,7 @@ function renderWeekCalendar() { const day = parseInt(el.dataset.day); const month = parseInt(el.dataset.month); const year = parseInt(el.dataset.year); - currentMonthDate = new Date(year, month, day); + selectedDate = new Date(year, month, day); currentCalendarView = 'day'; document.querySelectorAll('.view-btn').forEach(btn => { @@ -1134,23 +1139,25 @@ document.addEventListener('DOMContentLoaded', () => { const archivedTasksBtn = document.getElementById('archived-tasks-btn'); const focusModeBtn = document.getElementById('focus-mode-btn'); - // View toggle buttons - replace the old week button code const viewBtns = document.querySelectorAll('.view-btn'); if(viewBtns.length > 0) { viewBtns.forEach(btn => { btn.addEventListener('click', () => { currentCalendarView = btn.dataset.view; - // Update active class viewBtns.forEach(b => b.classList.remove('active')); btn.classList.add('active'); - // Render the selected view if(currentCalendarView === 'month') { + selectedDate = null; renderCalendar(); } else if(currentCalendarView === 'week') { + selectedDate = null; renderWeekCalendar(); } else if(currentCalendarView === 'day') { + if (!selectedDate) { + selectedDate = new Date(); + } renderDayView(); } renderTasks(); @@ -1228,7 +1235,10 @@ document.getElementById('cal-prev').addEventListener('click', () => { currentMonthDate.setDate(currentMonthDate.getDate() - 7); renderWeekCalendar(); } else if(currentCalendarView === 'day') { - currentMonthDate.setDate(currentMonthDate.getDate() - 1); + const currentDate = selectedDate || currentMonthDate; + const newDate = new Date(currentDate); + newDate.setDate(currentDate.getDate() - 1); + selectedDate = newDate; renderDayView(); } renderTasks(); @@ -1242,12 +1252,16 @@ document.getElementById('cal-next').addEventListener('click', () => { currentMonthDate.setDate(currentMonthDate.getDate() + 7); renderWeekCalendar(); } else if(currentCalendarView === 'day') { - currentMonthDate.setDate(currentMonthDate.getDate() + 1); + const currentDate = selectedDate || currentMonthDate; + const newDate = new Date(currentDate); + newDate.setDate(currentDate.getDate() + 1); + selectedDate = newDate; renderDayView(); } renderTasks(); }); + newTaskBtn.addEventListener('click', () => { if (!store.subjects || store.subjects.length === 0) { From b1d2a39ce9e6f2c13acc944a25aae98316c8e864 Mon Sep 17 00:00:00 2001 From: Chinmayee Wuriti Date: Mon, 18 May 2026 14:52:27 +0530 Subject: [PATCH 09/15] Added overdue tasks section --- css/index.css | 58 ++++++++++++++++++++++++++++++ js/app.js | 97 +++++++++++++++++++++++++++------------------------ 2 files changed, 110 insertions(+), 45 deletions(-) diff --git a/css/index.css b/css/index.css index 824d0a2..287fc21 100644 --- a/css/index.css +++ b/css/index.css @@ -183,6 +183,64 @@ body { height: 8px; border-radius: 50%; } +/* Make month view more compact */ +.cal-section { + padding: 12px 24px; + flex-shrink: 0; +} + +.cal-grid { + gap: 2px; +} + +.cal-day { + min-height: 50px; + padding: 4px 2px; + font-size: 12px; +} + +.cal-day-label { + font-size: 10px; + padding-bottom: 4px; +} + +.cal-day-indicators { + margin-top: 2px; + gap: 2px; +} + +.cal-day-indicator { + width: 4px; + height: 4px; +} + +.task-count { + font-size: 7px; +} + +.cal-header { + margin-bottom: 8px; +} + +.cal-legend { + margin-top: 8px; + font-size: 10px; +} + +/* Make month view even more compact */ +.cal-month-view .cal-day { + min-height: 55px; +} + +/* For week view keep taller cells */ +.cal-week-view .cal-day { + min-height: 80px; +} + +/* For day view - full height */ +.cal-grid:has(.day-view-checklist) { + display: block; +} /* Tasks */ .tasks-section { flex: 1; overflow-y: auto; padding: 0 24px 24px; scroll-behavior: smooth; } diff --git a/js/app.js b/js/app.js index bdb32aa..c6ead86 100644 --- a/js/app.js +++ b/js/app.js @@ -17,15 +17,13 @@ function generateSummary(tasks, subjects) { if (t.archived || t.status === 'Done' || !t.due_at) return; const d = new Date(t.due_at); - - // today - if (d.toDateString() === now.toDateString()) { - todayCount++; - } - - // this week - if (d >= now && d <= weekEnd) { - weekCount++; + const diffDays = (d - now) / (1000 * 60 * 60 * 24); + if (diffDays < 0) { + dueSoon.push(t); + } else if (diffDays <= 3) { + dueSoon.push(t); + } else { + thisWeek.push(t); } const sub = subjects.find(s => s.id === t.subject_id); @@ -407,36 +405,42 @@ function renderTasks() { const sorted = [...displayTasks].sort((a,b) => new Date(a.due_at) - new Date(b.due_at)); const now = new Date(); - - const dueSoon = []; - const thisWeek = []; - const completed = []; - const pending = []; - - if (currentView === 'calendar' && selectedDate) { - sorted.forEach(t => { - const d = new Date(t.due_at); - if (d.getDate() === selectedDate.getDate() && d.getMonth() === selectedDate.getMonth() && d.getFullYear() === selectedDate.getFullYear()) { - if (t.status === 'Done') completed.push(t); - else { - dueSoon.push(t); - pending.push(t); - } - } - }); - } else { - sorted.forEach(t => { - if (t.status === 'Done') { - completed.push(t); - return; + +const overdue = []; +const dueSoon = []; +const thisWeek = []; +const completed = []; +const pending = []; + +if (currentView === 'calendar' && selectedDate) { + sorted.forEach(t => { + const d = new Date(t.due_at); + if (d.getDate() === selectedDate.getDate() && d.getMonth() === selectedDate.getMonth() && d.getFullYear() === selectedDate.getFullYear()) { + if (t.status === 'Done') completed.push(t); + else { + dueSoon.push(t); + pending.push(t); } - pending.push(t); - const d = new Date(t.due_at); - const diffDays = (d - now) / (1000 * 60 * 60 * 24); - if (diffDays <= 3) dueSoon.push(t); - else thisWeek.push(t); - }); - } + } + }); +} else { + sorted.forEach(t => { + if (t.status === 'Done') { + completed.push(t); + return; + } + pending.push(t); + const d = new Date(t.due_at); + const diffDays = (d - now) / (1000 * 60 * 60 * 24); + if (diffDays < 0) { + overdue.push(t); + } else if (diffDays <= 3) { + dueSoon.push(t); + } else { + thisWeek.push(t); + } + }); +} const renderGroup = (title, items, titleColor, showConflict = false) => { if (items.length === 0) return ''; @@ -540,9 +544,11 @@ function renderTasks() { : ''; tasksSection.innerHTML = actionBar + - renderGroup(`Tasks for ${selStr}`, dueSoon, 'var(--color-text-primary)') + - renderGroup('Completed', completed, 'var(--color-text-tertiary)') + - emptyState; + renderGroup(titlePrefix + '⚠ Overdue', overdue, 'var(--color-text-danger)') + + renderGroup(titlePrefix + '⚠ Due soon', dueSoon, 'var(--color-text-warning)') + + renderGroup(titlePrefix + 'This week', thisWeek, 'var(--color-text-secondary)', true) + + renderGroup(titlePrefix + 'Completed', completed, 'var(--color-text-tertiary)') + + emptyState; } else { const actionBar = currentView === 'archived' ? '' : `
@@ -556,10 +562,11 @@ function renderTasks() { : ''; tasksSection.innerHTML = actionBar + - renderGroup(titlePrefix + '⚠ Due soon', dueSoon, 'var(--color-text-danger)') + - renderGroup(titlePrefix + 'This week', thisWeek, 'var(--color-text-secondary)', true) + - renderGroup(titlePrefix + 'Completed', completed, 'var(--color-text-tertiary)') + - emptyState; + renderGroup(titlePrefix + '⚠ Overdue', overdue, 'var(--color-text-danger)') + + renderGroup(titlePrefix + '⚠ Due soon', dueSoon, 'var(--color-text-warning)') + + renderGroup(titlePrefix + 'This week', thisWeek, 'var(--color-text-secondary)', true) + + renderGroup(titlePrefix + 'Completed', completed, 'var(--color-text-tertiary)') + + emptyState; } document.querySelectorAll('.task-item').forEach(el => { From 1b3f9a98d044abbdc8c336e7fcc5ea7b3c917558 Mon Sep 17 00:00:00 2001 From: Chinmayee Wuriti Date: Mon, 18 May 2026 15:05:41 +0530 Subject: [PATCH 10/15] fix: reposition collapse icon to avoid overlapping with nav arrows --- css/index.css | 58 +++++- index.html | 7 +- js/app.js | 493 ++++++++++++++++++++++++++------------------------ 3 files changed, 316 insertions(+), 242 deletions(-) diff --git a/css/index.css b/css/index.css index 287fc21..8c8f437 100644 --- a/css/index.css +++ b/css/index.css @@ -227,21 +227,73 @@ body { font-size: 10px; } -/* Make month view even more compact */ .cal-month-view .cal-day { min-height: 55px; } -/* For week view keep taller cells */ .cal-week-view .cal-day { min-height: 80px; } -/* For day view - full height */ .cal-grid:has(.day-view-checklist) { display: block; } +/* Collapsible calendar section */ +.cal-section { + transition: max-height 0.3s ease; + overflow: hidden; + position: relative; +} + +.cal-header { + display: flex; + align-items: center; + margin-bottom: 16px; + position: relative; +} + +.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; + margin-left: 4px; +} + +.collapse-icon { + cursor: pointer; + font-size: 14px; + color: var(--color-text-tertiary); + padding: 4px 8px; + margin-left: 12px; + border-radius: 4px; + transition: all 0.2s; +} + +.collapse-icon:hover { + background: var(--color-background-secondary); + color: var(--color-text-primary); +} + +.cal-section.collapsed { + max-height: 60px; +} + +.cal-section.collapsed .cal-grid, +.cal-section.collapsed .cal-legend { + display: none; +} + /* Tasks */ .tasks-section { flex: 1; overflow-y: auto; padding: 0 24px 24px; scroll-behavior: smooth; } .tasks-section::-webkit-scrollbar { width: 6px; } diff --git a/index.html b/index.html index b4007ca..1be2e59 100644 --- a/index.html +++ b/index.html @@ -106,10 +106,13 @@

StudyPlan

+
+
April 2026
+
Su
Mo
Tu
We
Th
Fr
Sa
@@ -205,8 +208,8 @@

StudyPlan

- - +
diff --git a/js/app.js b/js/app.js index c6ead86..52ed65b 100644 --- a/js/app.js +++ b/js/app.js @@ -17,13 +17,13 @@ function generateSummary(tasks, subjects) { if (t.archived || t.status === 'Done' || !t.due_at) return; const d = new Date(t.due_at); - const diffDays = (d - now) / (1000 * 60 * 60 * 24); - if (diffDays < 0) { - dueSoon.push(t); - } else if (diffDays <= 3) { - dueSoon.push(t); - } else { - thisWeek.push(t); + + if (d.toDateString() === now.toDateString()) { + todayCount++; + } + + if (d >= now && d <= weekEnd) { + weekCount++; } const sub = subjects.find(s => s.id === t.subject_id); @@ -38,16 +38,15 @@ function generateSummary(tasks, subjects) { : 'no specific subject'; return ` - 📅 Daily
+ Daily
Today you have ${todayCount} task(s).
Focus on ${topSubject}.

- 📊 Weekly
+ Weekly
This week you have ${weekCount} task(s).
Most work is in ${topSubject}. `; } - let currentMonthDate = new Date(); let selectedDate = null; let currentView = 'calendar'; // 'calendar', 'all-tasks', 'archived' @@ -406,261 +405,261 @@ function renderTasks() { const now = new Date(); -const overdue = []; -const dueSoon = []; -const thisWeek = []; -const completed = []; -const pending = []; + const overdue = []; + const dueSoon = []; + const thisWeek = []; + const completed = []; + const pending = []; -if (currentView === 'calendar' && selectedDate) { - sorted.forEach(t => { - const d = new Date(t.due_at); - if (d.getDate() === selectedDate.getDate() && d.getMonth() === selectedDate.getMonth() && d.getFullYear() === selectedDate.getFullYear()) { - if (t.status === 'Done') completed.push(t); - else { + if (currentView === 'calendar' && selectedDate) { + sorted.forEach(t => { + const d = new Date(t.due_at); + if (d.getDate() === selectedDate.getDate() && d.getMonth() === selectedDate.getMonth() && d.getFullYear() === selectedDate.getFullYear()) { + if (t.status === 'Done') completed.push(t); + else { + dueSoon.push(t); + pending.push(t); + } + } + }); + } else { + sorted.forEach(t => { + if (t.status === 'Done') { + completed.push(t); + return; + } + pending.push(t); + const d = new Date(t.due_at); + const diffDays = (d - now) / (1000 * 60 * 60 * 24); + if (diffDays < 0) { + overdue.push(t); + } else if (diffDays <= 3) { dueSoon.push(t); - pending.push(t); + } else { + thisWeek.push(t); } - } - }); -} else { - sorted.forEach(t => { - if (t.status === 'Done') { - completed.push(t); - return; - } - pending.push(t); - const d = new Date(t.due_at); - const diffDays = (d - now) / (1000 * 60 * 60 * 24); - if (diffDays < 0) { - overdue.push(t); - } else if (diffDays <= 3) { - dueSoon.push(t); - } else { - thisWeek.push(t); - } - }); -} - - const renderGroup = (title, items, titleColor, showConflict = false) => { - if (items.length === 0) return ''; - let html = `
-
- ${title} -
`; + }); + } - if (showConflict && items.length >= 3) { - html += `
- -
Multiple deadlines detected. Consider starting early to spread the load.
-
`; - } - - items.forEach(t => { - const sub = subjects.find(s => s.id === t.subject_id) || subjects[0]; - const isUrgent = t.priority === 'high' && title === '⚠ Due soon'; - const isDone = t.status === 'Done'; + const renderGroup = (title, items, titleColor, showConflict = false) => { + if (items.length === 0) return ''; + let html = `
+
+ ${title} +
`; - 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'; - else pillClass = 'pill-amber'; - - if (t._isEditing) { - let subjectOptions = subjects.map(s => - `` - ).join(''); + if (showConflict && items.length >= 3) { + html += `
+ +
Multiple deadlines detected. Consider starting early to spread the load.
+
`; + } - const localDate = t.due_at ? new Date(t.due_at).toISOString().substring(0, 16) : ''; - const isHighPriority = t.priority === 'high'; + items.forEach(t => { + const sub = subjects.find(s => s.id === t.subject_id) || subjects[0]; + const isUrgent = t.priority === 'high' && title === '⚠ Due soon'; + const isDone = t.status === 'Done'; - html += ` -
- - - - - - - - - - - - - - - -
- - -
-
- `; - } else { - const archiveBtn = !t.archived - ? ` - ` - : ` - - `; - - html += ` -
-
-
-
${t.title}
-
- ${isDone ? 'Done' : 'Due ' + formatDate(t.due_at)} - ${sub.short_code} + 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'; + else pillClass = 'pill-amber'; + + if (t._isEditing) { + let subjectOptions = subjects.map(s => + `` + ).join(''); + + const localDate = t.due_at ? new Date(t.due_at).toISOString().substring(0, 16) : ''; + const isHighPriority = t.priority === 'high'; + + html += ` +
+ + + + + + + + + + + + + + + +
+ +
-
- ${archiveBtn} + `; + } else { + const archiveBtn = !t.archived + ? ` + ` + : ` + + `; + + html += ` +
+
+
+
${t.title}
+
+ ${isDone ? 'Done' : 'Due ' + formatDate(t.due_at)} + ${sub.short_code} +
+
+
+ ${archiveBtn} +
-
- `; - } - }); - html += `
`; - return html; - }; - - if (currentView === 'calendar' && selectedDate) { - const selStr = selectedDate.toLocaleDateString('en-US', {month:'short', day:'numeric'}); - const actionBar = `
- - -
`; - - const emptyState = dueSoon.length === 0 && completed.length === 0 - ? `
No tasks for this day yet.
` - : ''; - - tasksSection.innerHTML = actionBar + - renderGroup(titlePrefix + '⚠ Overdue', overdue, 'var(--color-text-danger)') + - renderGroup(titlePrefix + '⚠ Due soon', dueSoon, 'var(--color-text-warning)') + - renderGroup(titlePrefix + 'This week', thisWeek, 'var(--color-text-secondary)', true) + - renderGroup(titlePrefix + 'Completed', completed, 'var(--color-text-tertiary)') + - emptyState; - } else { - const actionBar = currentView === 'archived' ? '' : `
- -
`; - - const titlePrefix = currentView === 'archived' ? 'Archived: ' : ''; - const emptyStateText = currentView === 'archived' ? 'No archived tasks.' : 'No tasks yet. Add tasks from Smart Paste to get started.'; - - const emptyState = dueSoon.length === 0 && thisWeek.length === 0 && completed.length === 0 - ? `
${emptyStateText}
` - : ''; - - tasksSection.innerHTML = actionBar + - renderGroup(titlePrefix + '⚠ Overdue', overdue, 'var(--color-text-danger)') + - renderGroup(titlePrefix + '⚠ Due soon', dueSoon, 'var(--color-text-warning)') + - renderGroup(titlePrefix + 'This week', thisWeek, 'var(--color-text-secondary)', true) + - renderGroup(titlePrefix + 'Completed', completed, 'var(--color-text-tertiary)') + - emptyState; - } - - 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); + `; + } + }); + html += `
`; + return html; + }; + + if (currentView === 'calendar' && selectedDate) { + const selStr = selectedDate.toLocaleDateString('en-US', {month:'short', day:'numeric'}); + const actionBar = `
+ + +
`; + + const emptyState = dueSoon.length === 0 && completed.length === 0 + ? `
No tasks for this day yet.
` + : ''; + + tasksSection.innerHTML = actionBar + + renderGroup('⚠ Overdue', overdue, 'var(--color-text-danger)') + + renderGroup('⚠ Due soon', dueSoon, 'var(--color-text-warning)') + + renderGroup('This week', thisWeek, 'var(--color-text-secondary)', true) + + renderGroup('Completed', completed, 'var(--color-text-tertiary)') + + emptyState; + } else { + const actionBar = currentView === 'archived' ? '' : `
+ +
`; + + const titlePrefix = currentView === 'archived' ? 'Archived: ' : ''; + const emptyStateText = currentView === 'archived' ? 'No archived tasks.' : 'No tasks yet. Add tasks from Smart Paste to get started.'; + + const emptyState = dueSoon.length === 0 && thisWeek.length === 0 && completed.length === 0 + ? `
${emptyStateText}
` + : ''; + + tasksSection.innerHTML = actionBar + + renderGroup(titlePrefix + '⚠ Overdue', overdue, 'var(--color-text-danger)') + + renderGroup(titlePrefix + '⚠ Due soon', dueSoon, 'var(--color-text-warning)') + + renderGroup(titlePrefix + 'This week', thisWeek, 'var(--color-text-secondary)', true) + + renderGroup(titlePrefix + 'Completed', completed, 'var(--color-text-tertiary)') + + emptyState; + } + + 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); + }); }); - }); - document.querySelectorAll('.edit-task-btn').forEach(el => { - el.addEventListener('click', (e) => { - e.stopPropagation(); - store.setTaskEditing(el.dataset.id, true); + document.querySelectorAll('.edit-task-btn').forEach(el => { + el.addEventListener('click', (e) => { + e.stopPropagation(); + store.setTaskEditing(el.dataset.id, true); + }); }); - }); - document.querySelectorAll('.cancel-board-edit-btn').forEach(el => { - el.addEventListener('click', (e) => { - e.stopPropagation(); - store.setTaskEditing(el.dataset.id, false); + document.querySelectorAll('.cancel-board-edit-btn').forEach(el => { + el.addEventListener('click', (e) => { + e.stopPropagation(); + store.setTaskEditing(el.dataset.id, false); + }); }); - }); - document.querySelectorAll('.save-board-edit-btn').forEach(el => { - el.addEventListener('click', (e) => { - e.stopPropagation(); - const taskId = el.dataset.id; - const itemEl = el.closest('.task-item'); - - const title = itemEl.querySelector('.board-edit-title').value; - const subject_id = itemEl.querySelector('.board-edit-subject').value; - let dateVal = itemEl.querySelector('.board-edit-date').value; - const notes = itemEl.querySelector('.board-edit-notes').value; - const priority = itemEl.querySelector('.board-edit-priority').value; - - store.updateTask(taskId, { - title, - subject_id, - due_at: dateVal ? new Date(dateVal).toISOString() : '', - notes, - priority + document.querySelectorAll('.save-board-edit-btn').forEach(el => { + el.addEventListener('click', (e) => { + e.stopPropagation(); + const taskId = el.dataset.id; + const itemEl = el.closest('.task-item'); + + const title = itemEl.querySelector('.board-edit-title').value; + const subject_id = itemEl.querySelector('.board-edit-subject').value; + let dateVal = itemEl.querySelector('.board-edit-date').value; + const notes = itemEl.querySelector('.board-edit-notes').value; + const priority = itemEl.querySelector('.board-edit-priority').value; + + store.updateTask(taskId, { + title, + subject_id, + due_at: dateVal ? new Date(dateVal).toISOString() : '', + notes, + priority + }); }); }); - }); - document.querySelectorAll('.task-check').forEach(el => { - el.addEventListener('click', (e) => { - e.stopPropagation(); - const taskId = el.closest('.task-item').dataset.id; - store.toggleTaskStatus(taskId); + document.querySelectorAll('.task-check').forEach(el => { + el.addEventListener('click', (e) => { + e.stopPropagation(); + const taskId = el.closest('.task-item').dataset.id; + store.toggleTaskStatus(taskId); + }); }); - }); - document.querySelectorAll('.archive-task-btn').forEach(el => { - el.addEventListener('click', (e) => { - e.stopPropagation(); - store.archiveTask(el.dataset.id); + document.querySelectorAll('.archive-task-btn').forEach(el => { + el.addEventListener('click', (e) => { + e.stopPropagation(); + store.archiveTask(el.dataset.id); + }); }); - }); - document.querySelectorAll('.restore-task-btn').forEach(el => { - el.addEventListener('click', (e) => { - e.stopPropagation(); - store.restoreTask(el.dataset.id); + document.querySelectorAll('.restore-task-btn').forEach(el => { + el.addEventListener('click', (e) => { + e.stopPropagation(); + store.restoreTask(el.dataset.id); + }); }); - }); - document.querySelectorAll('.delete-task-btn').forEach(el => { - el.addEventListener('click', (e) => { - e.stopPropagation(); - store.deleteTask(el.dataset.id); + document.querySelectorAll('.delete-task-btn').forEach(el => { + el.addEventListener('click', (e) => { + e.stopPropagation(); + store.deleteTask(el.dataset.id); + }); }); - }); - const markAllPendingBtn = document.getElementById('mark-all-pending-btn'); - if (markAllPendingBtn) { - markAllPendingBtn.addEventListener('click', (e) => { - e.stopPropagation(); - store.markAllPendingCompleted(); - }); - } + const markAllPendingBtn = document.getElementById('mark-all-pending-btn'); + if (markAllPendingBtn) { + markAllPendingBtn.addEventListener('click', (e) => { + e.stopPropagation(); + store.markAllPendingCompleted(); + }); + } - const markDayCompleteBtn = document.getElementById('mark-day-complete-btn'); - if (markDayCompleteBtn) { - markDayCompleteBtn.addEventListener('click', (e) => { - e.stopPropagation(); - store.markPendingTasksForDateCompleted(selectedDate); - }); - } + const markDayCompleteBtn = document.getElementById('mark-day-complete-btn'); + if (markDayCompleteBtn) { + markDayCompleteBtn.addEventListener('click', (e) => { + e.stopPropagation(); + store.markPendingTasksForDateCompleted(selectedDate); + }); + } } @@ -1141,6 +1140,26 @@ document.addEventListener('DOMContentLoaded', () => { store.fetchInitialData(); + + + const calSection = document.querySelector('.cal-section'); + const collapseIcon = document.querySelector('.collapse-icon'); + let calendarCollapsed = false; + + if (collapseIcon) { + collapseIcon.addEventListener('click', (e) => { + e.stopPropagation(); + calendarCollapsed = !calendarCollapsed; + if (calendarCollapsed) { + calSection.classList.add('collapsed'); + collapseIcon.textContent = '▶'; + } else { + calSection.classList.remove('collapsed'); + collapseIcon.textContent = '▼'; + } + }); + } + const calendarBtn = document.getElementById('calendar-btn'); const allTasksBtn = document.getElementById('all-tasks-btn'); const archivedTasksBtn = document.getElementById('archived-tasks-btn'); From 21df300fd9361c403ee40b39f3a815910d84df78 Mon Sep 17 00:00:00 2001 From: Chinmayee Wuriti Date: Mon, 18 May 2026 15:09:40 +0530 Subject: [PATCH 11/15] fix: correct generateSummary and add safety checks --- js/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/app.js b/js/app.js index 52ed65b..3c2bea3 100644 --- a/js/app.js +++ b/js/app.js @@ -541,7 +541,7 @@ function renderTasks() { const emptyState = dueSoon.length === 0 && completed.length === 0 ? `
No tasks for this day yet.
` : ''; - + tasksSection.innerHTML = actionBar + renderGroup('⚠ Overdue', overdue, 'var(--color-text-danger)') + renderGroup('⚠ Due soon', dueSoon, 'var(--color-text-warning)') + From f6f3a9cd99d98fedc3f2e9c909c52b05ffdf2a62 Mon Sep 17 00:00:00 2001 From: Chinmayee Date: Tue, 19 May 2026 08:54:49 +0530 Subject: [PATCH 12/15] Update index.html --- index.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/index.html b/index.html index c67d814..2fc37e0 100644 --- a/index.html +++ b/index.html @@ -112,14 +112,11 @@

StudyPlan

April 2026
-<<<<<<< week-view-feature -======= ->>>>>>> main
Su
Mo
Tu
We
Th
Fr
Sa
From bdae92a0ee0bf23203a93f77b739fcc523107f2f Mon Sep 17 00:00:00 2001 From: Chinmayee Date: Fri, 22 May 2026 07:30:30 +0530 Subject: [PATCH 13/15] Update app.js --- js/app.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/js/app.js b/js/app.js index 2bbf6f1..aa4c85c 100644 --- a/js/app.js +++ b/js/app.js @@ -442,14 +442,14 @@ function renderTasks() { }); } -<<<<<<< week-view-feature + const renderGroup = (title, items, titleColor, showConflict = false) => { if (items.length === 0) return ''; let html = `
${title}
`; -======= + if (showConflict) { const workloadSuggestions = analyzeWorkload(items); workloadSuggestions.forEach(workload => { @@ -472,7 +472,7 @@ function renderTasks() { else if(sub.short_code === 'Maths') pillClass = 'pill-green'; else if(sub.short_code === 'English') pillClass = 'pill-purple'; else pillClass = 'pill-amber'; ->>>>>>> main + if (showConflict && items.length >= 3) { html += `
@@ -550,7 +550,7 @@ function renderTasks() { ${archiveBtn}
-<<<<<<< week-view-feature + `; } }); @@ -568,7 +568,7 @@ function renderTasks() { const emptyState = dueSoon.length === 0 && completed.length === 0 ? `
No tasks for this day yet.
` : ''; -======= +
`; } @@ -618,7 +618,7 @@ function renderTasks() { const taskId = el.dataset.id; const task = store.tasks.find(t => String(t.id) === String(taskId)); if (task && task._isEditing) return; ->>>>>>> main + tasksSection.innerHTML = actionBar + renderGroup('⚠ Overdue', overdue, 'var(--color-text-danger)') + From 54777143b9ce82dc7d283bff85c362c559e474ec Mon Sep 17 00:00:00 2001 From: Chinmayee Wuriti Date: Fri, 22 May 2026 22:20:43 +0530 Subject: [PATCH 14/15] feat: add modern dashboard with custom cursor and 3-column layout --- css/dashboard.css | 350 ++++++++++++++++++++++++++++++++++++++++++++++ dashboard.html | 110 +++++++++++++++ js/dashboard.js | 194 +++++++++++++++++++++++++ server.js | 12 ++ 4 files changed, 666 insertions(+) create mode 100644 css/dashboard.css create mode 100644 dashboard.html create mode 100644 js/dashboard.js diff --git a/css/dashboard.css b/css/dashboard.css new file mode 100644 index 0000000..aca6f22 --- /dev/null +++ b/css/dashboard.css @@ -0,0 +1,350 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --color-bg: #efefec; + --color-card: #ffffff; + --color-text-primary: #1a1a18; + --color-text-secondary: #6b6b66; + --color-text-tertiary: #9c9a92; + --color-border: rgba(0,0,0,0.08); + --color-border-hover: rgba(0,0,0,0.12); + --shadow-sm: 0 2px 8px rgba(0,0,0,0.04); + --shadow-md: 0 4px 16px rgba(0,0,0,0.06); + --shadow-lg: 0 8px 32px rgba(0,0,0,0.08); + --border-radius: 16px; + --transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); +} + +@media (prefers-color-scheme: dark) { + :root { + --color-bg: #181816; + --color-card: #20201e; + --color-text-primary: #efede5; + --color-text-secondary: #a3a198; + --color-text-tertiary: #73726c; + --color-border: rgba(255,255,255,0.06); + --color-border-hover: rgba(255,255,255,0.12); + } +} + +body { + font-family: 'Inter', system-ui, -apple-system, sans-serif; + background: #efefec; + color: #1a1a18; + min-height: 100vh; + padding: 40px 24px; + cursor: none; +} + +@media (max-width: 768px) { + body { + cursor: auto; + padding: 24px 16px; + } + .custom-cursor { + display: none; + } +} + +.custom-cursor { + position: fixed; + width: 32px; + height: 32px; + border-radius: 50%; + background: rgba(0, 0, 0, 0.15); + backdrop-filter: blur(2px); + pointer-events: none; + z-index: 9999; + transform: translate(-50%, -50%); + transition: width 0.2s, height 0.2s, background 0.2s; +} + +.custom-cursor.active { + width: 48px; + height: 48px; + background: rgba(0, 0, 0, 0.08); +} + +.dashboard-container { + max-width: 1200px; + margin: 0 auto; +} + +.dashboard-header { + margin-bottom: 48px; +} + +.logo-section { + display: flex; + align-items: center; + gap: 12px; +} + +.dashboard-logo { + width: 40px; + height: 40px; + border-radius: 2px; +} + +.dashboard-title { + font-size: 20px; + font-weight: 700; + color: #1a1a18; +} + +.dashboard-content { + max-width: 900px; + margin: 0 auto; +} + +.greeting-section { + margin-bottom: 48px; + text-align: center; +} + +.greeting-text { + font-size: 13px; + font-weight: 600; + color: #6b6b66; + letter-spacing: 0.08em; + text-transform: uppercase; + margin-bottom: 12px; +} + +.user-name { + font-size: 36px; + font-weight: 600; + color: #1a1a18; + letter-spacing: -0.02em; + margin-bottom: 8px; +} + +.date-text { + font-size: 14px; + color: #6b6b66; +} + +.stats-row { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 12px; + margin-bottom: 40px; +} + +.stat-item { + background: #ffffff; + border: 1px solid rgba(0,0,0,0.08); + border-radius: var(--border-radius); + padding: 20px 12px; + text-align: center; + transition: var(--transition); +} + +.stat-item:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-md); + border-color: rgba(0,0,0,0.12); +} + +.stat-number { + font-size: 32px; + font-weight: 700; + color: #1a1a18; + margin-bottom: 6px; +} + +.stat-label { + font-size: 11px; + font-weight: 600; + color: #6b6b66; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.dashboard-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; + margin-bottom: 32px; +} + +.dashboard-card { + background: #ffffff; + border: 1px solid rgba(0,0,0,0.08); + border-radius: var(--border-radius); + padding: 20px; + transition: var(--transition); +} + +.dashboard-card:hover { + box-shadow: var(--shadow-md); + border-color: rgba(0,0,0,0.12); +} + +.card-header { + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid rgba(0,0,0,0.08); +} + +.card-title { + font-size: 12px; + font-weight: 600; + color: #6b6b66; + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.deadline-list, +.priority-list { + display: flex; + flex-direction: column; + gap: 12px; + min-height: 200px; +} + +.deadline-item, +.priority-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 0; + border-bottom: 1px solid rgba(0,0,0,0.08); + transition: var(--transition); +} + +.deadline-item:hover, +.priority-item:hover { + transform: translateX(4px); + border-bottom-color: rgba(0,0,0,0.12); +} + +.deadline-name, +.priority-name { + font-size: 14px; + font-weight: 500; + color: #1a1a18; +} + +.deadline-date { + font-size: 11px; + font-weight: 600; + color: #a32d2d; +} + +.priority-badge { + font-size: 10px; + font-weight: 600; + padding: 3px 10px; + border-radius: 20px; + background: #efefec; + color: #6b6b66; +} + +.empty-state { + text-align: center; + padding: 32px 16px; + color: #9c9a92; + font-size: 13px; +} + +.streak-content { + text-align: center; + padding: 8px 0; +} + +.streak-number { + font-size: 52px; + font-weight: 700; + color: #1a1a18; + letter-spacing: -0.02em; + margin-bottom: 8px; +} + +.streak-label { + font-size: 12px; + color: #6b6b66; + margin-bottom: 20px; +} + +.streak-progress-bar { + background: #efefec; + border-radius: 4px; + height: 4px; + margin-bottom: 12px; + overflow: hidden; +} + +.streak-progress-fill { + background: #1a1a18; + height: 100%; + border-radius: 4px; + transition: width 0.6s ease; +} + +.streak-best-text { + font-size: 11px; + color: #9c9a92; +} + +.start-planning-btn { + width: 100%; + padding: 14px 20px; + background: #1a1a18; + color: #ffffff; + border: none; + border-radius: var(--border-radius); + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} + +.start-planning-btn:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.start-planning-btn:active { + transform: translateY(0); +} + +.arrow { + transition: transform 0.2s ease; +} + +.start-planning-btn:hover .arrow { + transform: translateX(4px); +} + +@media (max-width: 768px) { + .stats-row { + grid-template-columns: repeat(2, 1fr); + gap: 10px; + } + + .dashboard-grid { + grid-template-columns: 1fr; + gap: 16px; + } + + .user-name { + font-size: 28px; + } + + .greeting-section { + margin-bottom: 32px; + } + + .stat-number { + font-size: 26px; + } +} \ No newline at end of file diff --git a/dashboard.html b/dashboard.html new file mode 100644 index 0000000..e751004 --- /dev/null +++ b/dashboard.html @@ -0,0 +1,110 @@ + + + + + + StudyPlan - Dashboard + + + + +
+
+
+ +

StudyPlan

+
+
+ +
+
+
Good evening
+
Chinmayee
+
+
+ +
+
+
0
+
total tasks
+
+
+
0
+
completed
+
+
+
0
+
pending
+
+
+
0
+
due soon
+
+
+ +
+
+
+ Upcoming Deadlines +
+
+
No upcoming deadlines
+
+
+ +
+
+ Today's Priority +
+
+
No tasks for today
+
+
+ +
+
+ Focus Streak +
+
+
0
+
current streak
+
+
+
+
Best: 0 days
+
+
+
+ + +
+
+ +
+ + + + + \ No newline at end of file diff --git a/js/dashboard.js b/js/dashboard.js new file mode 100644 index 0000000..6b6d793 --- /dev/null +++ b/js/dashboard.js @@ -0,0 +1,194 @@ +import { store } from './store.js'; +import { initGlobalErrorBoundary } from './utils/errorBoundary.js'; + +initGlobalErrorBoundary(); + +function formatDate(dateStr) { + if (!dateStr) return ''; + const d = new Date(dateStr); + return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); +} + +function calculateStreak(tasks) { + const completedTasks = tasks.filter(t => t.status === 'Done' && !t.archived); + const today = new Date(); + today.setHours(0, 0, 0, 0); + + let streak = 0; + let currentDate = new Date(today); + + for (let i = 0; i < 365; i++) { + const dateStr = currentDate.toDateString(); + const hasTaskOnDate = completedTasks.some(t => { + const taskDate = new Date(t.due_at); + taskDate.setHours(0, 0, 0, 0); + return taskDate.toDateString() === dateStr; + }); + + if (hasTaskOnDate) { + streak++; + currentDate.setDate(currentDate.getDate() - 1); + } else { + break; + } + } + + const allDates = completedTasks.map(t => { + const d = new Date(t.due_at); + d.setHours(0, 0, 0, 0); + return d.toDateString(); + }); + + const uniqueDates = [...new Set(allDates)]; + const bestStreak = Math.max(...uniqueDates.map((_, i, arr) => { + let streak = 1; + let current = new Date(arr[i]); + for (let j = i + 1; j < arr.length; j++) { + const next = new Date(arr[j]); + const diffDays = (next - current) / (1000 * 60 * 60 * 24); + if (diffDays === 1) { + streak++; + current = next; + } else { + break; + } + } + return streak; + }), 0); + + return { current: streak, best: bestStreak }; +} + +function updateDashboard() { + const tasks = store.tasks; + const subjects = store.subjects; + + const now = new Date(); + const weekFromNow = new Date(); + weekFromNow.setDate(now.getDate() + 7); + + const pendingTasks = tasks.filter(t => !t.archived && t.status !== 'Done'); + const completedThisWeek = tasks.filter(t => { + if (t.status !== 'Done') return false; + const d = new Date(t.due_at); + return d >= now && d <= weekFromNow; + }); + const dueSoonTasks = pendingTasks.filter(t => { + if (!t.due_at) return false; + const d = new Date(t.due_at); + const diffDays = (d - now) / (1000 * 60 * 60 * 24); + return diffDays <= 3; + }); + + const totalTasksElem = document.getElementById('total-tasks'); + const completedTasksElem = document.getElementById('completed-tasks'); + const dueTasksElem = document.getElementById('due-tasks'); + + if (totalTasksElem) totalTasksElem.textContent = pendingTasks.length; + if (completedTasksElem) completedTasksElem.textContent = completedThisWeek.length; + if (dueTasksElem) dueTasksElem.textContent = dueSoonTasks.length; + + const priorityTasks = [...pendingTasks] + .sort((a, b) => new Date(a.due_at) - new Date(b.due_at)) + .slice(0, 4); + + const priorityList = document.getElementById('priority-list'); + if (priorityList) { + if (priorityTasks.length === 0) { + priorityList.innerHTML = '
No pending tasks
'; + } else { + priorityList.innerHTML = priorityTasks.map(task => { + const sub = subjects.find(s => s.id === task.subject_id) || subjects[0]; + return ` +
+ ${escapeHtml(task.title)} + ${sub?.short_code || 'Task'} +
+ `; + }).join(''); + + document.querySelectorAll('.priority-item').forEach(el => { + el.addEventListener('click', () => { + window.location.href = '/index.html'; + }); + }); + } + } + + const upcomingTasks = [...pendingTasks] + .filter(t => t.due_at) + .sort((a, b) => new Date(a.due_at) - new Date(b.due_at)) + .slice(0, 5); + + const deadlineList = document.getElementById('deadline-list'); + if (deadlineList) { + if (upcomingTasks.length === 0) { + deadlineList.innerHTML = '
No upcoming deadlines
'; + } else { + deadlineList.innerHTML = upcomingTasks.map(task => { + const date = new Date(task.due_at); + const daysDiff = Math.ceil((date - now) / (1000 * 60 * 60 * 24)); + let dateClass = ''; + if (daysDiff <= 2) dateClass = 'deadline-date'; + return ` +
+ ${escapeHtml(task.title)} + ${formatDate(task.due_at)} +
+ `; + }).join(''); + + document.querySelectorAll('.deadline-item').forEach(el => { + el.addEventListener('click', () => { + window.location.href = '/index.html'; + }); + }); + } + } + + const streak = calculateStreak(tasks); + const streakElem = document.getElementById('streak-days'); + const streakBarElem = document.getElementById('streak-bar'); + const streakBestElem = document.querySelector('.streak-best'); + + if (streakElem) streakElem.textContent = streak.current; + if (streakBarElem) { + const percent = streak.best > 0 ? (streak.current / streak.best) * 100 : 0; + streakBarElem.style.width = percent + '%'; + } + if (streakBestElem) streakBestElem.textContent = `Best: ${streak.best} days`; +} + +function escapeHtml(str) { + if (!str) return ''; + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +const startBtn = document.getElementById('start-planning-btn'); +if (startBtn) { + startBtn.addEventListener('click', () => { + window.location.href = '/index.html'; + }); +} + +store.subscribe(() => { + updateDashboard(); +}); + +store.fetchInitialData().then(() => { + updateDashboard(); +}); + +const greetingElem = document.querySelector('.greeting-text'); +if (greetingElem) { + const hour = new Date().getHours(); + let greeting = 'Good evening'; + if (hour < 12) greeting = 'Good morning'; + else if (hour < 18) greeting = 'Good afternoon'; + greetingElem.textContent = greeting; +} \ No newline at end of file diff --git a/server.js b/server.js index 877f766..7dc58f9 100644 --- a/server.js +++ b/server.js @@ -504,6 +504,18 @@ app.use((err, req, res, next) => { return res.status(500).sendFile(page500Path); }); + +// Serve dashboard as default landing page +app.get('/', (req, res) => { + res.sendFile(path.join(__dirname, 'dashboard.html')); +}); + +// Keep existing index route +app.get('/index.html', (req, res) => { + res.sendFile(path.join(__dirname, 'index.html')); +}); + + // ================= SERVER ================= const PORT = process.env.PORT || 3000; app.listen(PORT, () => { From 85c286adfea8d8f4d321abaf6865428001123be7 Mon Sep 17 00:00:00 2001 From: Chinmayee Wuriti Date: Mon, 25 May 2026 09:02:08 +0530 Subject: [PATCH 15/15] feat: add dashboard with week stats, recent activity, dark mode support, and custom cursor --- css/dashboard.css | 410 +++++++++++++++++++++++++++++++++++++--------- css/index.css | 5 +- dashboard.html | 66 +++++--- index.html | 12 +- js/dashboard.js | 174 ++++++++++++++------ server.js | 26 +-- 6 files changed, 528 insertions(+), 165 deletions(-) diff --git a/css/dashboard.css b/css/dashboard.css index aca6f22..4e8caf1 100644 --- a/css/dashboard.css +++ b/css/dashboard.css @@ -1,45 +1,201 @@ -* { +*, *::before, *::after { + box-sizing: border-box; margin: 0; padding: 0; - box-sizing: border-box; } :root { - --color-bg: #efefec; - --color-card: #ffffff; + --color-background-primary: #ffffff; + --color-background-secondary: #f7f7f5; + --color-background-tertiary: #efefec; --color-text-primary: #1a1a18; --color-text-secondary: #6b6b66; --color-text-tertiary: #9c9a92; - --color-border: rgba(0,0,0,0.08); - --color-border-hover: rgba(0,0,0,0.12); + --color-border-tertiary: rgba(0,0,0,0.08); + --color-border-secondary: rgba(0,0,0,0.15); + --color-border-primary: rgba(0,0,0,0.30); + + --color-danger-rgb: 163, 45, 45; + --color-info-rgb: 24, 95, 165; + --color-success-rgb: 22, 101, 52; + + --color-text-danger: #a32d2d; + --color-background-danger: #fcebeb; + --color-border-danger: rgba(var(--color-danger-rgb), 0.35); + + --color-text-info: #161717; + --color-background-info: #e6f1fb; + --color-border-info: rgba(var(--color-info-rgb), 0.35); + + --color-text-success: #166534; + --color-background-success: #eaf3de; + --color-border-success: rgba(var(--color-success-rgb), 0.35); + + --color-text-warning: #854f0b; + --color-background-warning: #faeeda; + + --color-text-purple: #3c3489; + --color-background-purple: #eeedfe; + + --border-radius-sm: 6px; + --border-radius-md: 10px; + --border-radius-lg: 16px; + --font-sans: 'Inter', system-ui, -apple-system, sans-serif; + --shadow-sm: 0 2px 8px rgba(0,0,0,0.04); --shadow-md: 0 4px 16px rgba(0,0,0,0.06); --shadow-lg: 0 8px 32px rgba(0,0,0,0.08); - --border-radius: 16px; - --transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); -} - -@media (prefers-color-scheme: dark) { - :root { - --color-bg: #181816; - --color-card: #20201e; - --color-text-primary: #efede5; - --color-text-secondary: #a3a198; - --color-text-tertiary: #73726c; - --color-border: rgba(255,255,255,0.06); - --color-border-hover: rgba(255,255,255,0.12); - } } body { - font-family: 'Inter', system-ui, -apple-system, sans-serif; - background: #efefec; - color: #1a1a18; + font-family: var(--font-sans); + background: #ffffff; + color: var(--color-text-primary); min-height: 100vh; - padding: 40px 24px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 32px 16px; + overflow-x: hidden; + position: relative; cursor: none; } +.stars-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 0; +} + +.star { + position: absolute; + color: rgba(0, 0, 0, 1); + pointer-events: none; + /* animation: twinkle 3s ease-in-out infinite; */ +} + +/* @keyframes twinkle { + 0%, 100% { opacity: 0.3; transform: scale(0.8); } + 50% { opacity: 0.8; transform: scale(1.2); } +} */ + +@keyframes floatCrescent { + 0%, 100% { transform: translateY(0px) rotate(0deg); } + 50% { transform: translateY(-15px) rotate(-10deg); } +} + +.crescent { + position: fixed; + bottom: 5%; + right: 5%; + width: 80px; + height: 80px; + background: transparent; + border-radius: 50%; + box-shadow: -15px -15px 0 0 #1a1a18; + pointer-events: none; + z-index: 0; + animation: floatCrescent 8s ease-in-out infinite; +} + +.dashboard-card { + background: var(--color-background-secondary); + border: 1px solid var(--color-border-tertiary); + border-radius: var(--border-radius-md); + padding: 20px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.dashboard-card:hover { + background: #424242; + transform: translateY(-4px); + box-shadow: var(--shadow-lg); +} + +.dashboard-card:hover .card-title { + color: #ffffff; +} + +.dashboard-card:hover .deadline-name, +.dashboard-card:hover .priority-name, +.dashboard-card:hover .recent-text, +.dashboard-card:hover .streak-number, +.dashboard-card:hover .streak-label, +.dashboard-card:hover .streak-best-text { + color: #ffffff; +} + +.dashboard-card:hover .deadline-date { + color: #ff8888; +} + +.dashboard-card:hover .priority-badge { + background: rgba(255,255,255,0.2); + color: #ffffff; +} + +.dashboard-card:hover .recent-time { + color: #cccccc; +} + +.dashboard-card:hover .empty-state { + color: #aaaaaa; +} + +.dashboard-card:hover .deadline-item, +.dashboard-card:hover .priority-item, +.dashboard-card:hover .recent-item { + border-bottom-color: rgba(255,255,255,0.15); +} + +.stat-item:hover { + background: #343333; +} + +.stat-item:hover .stat-number, +.stat-item:hover .stat-label { + color: #ffffff; +} + +.dashboard-card:hover { + background: #3f3e3e; +} + +.dashboard-card:hover .card-title, +.dashboard-card:hover .deadline-name, +.dashboard-card:hover .priority-name, +.dashboard-card:hover .recent-text, +.dashboard-card:hover .recent-icon, +.dashboard-card:hover .recent-time, +.dashboard-card:hover .streak-number, +.dashboard-card:hover .streak-label, +.dashboard-card:hover .streak-best-text, +.dashboard-card:hover .empty-state, +.dashboard-card:hover .deadline-item { + color: #ffffff; +} + +.dashboard-card:hover .deadline-date { + color: #ffaaaa !important; +} + +.dashboard-card:hover .priority-badge { + background: rgba(255, 255, 255, 0.2) !important; + color: #ffffff !important; +} + +.dashboard-card:hover .recent-item { + border-bottom-color: rgba(255, 255, 255, 0.2); +} + +.deadline-item:hover{ + color: #ffffff; +} @media (max-width: 768px) { body { cursor: auto; @@ -48,6 +204,13 @@ body { .custom-cursor { display: none; } + .crescent { + width: 50px; + height: 50px; + box-shadow: -10px -10px 0 0 #1a1a18; + bottom: 3%; + right: 3%; + } } .custom-cursor { @@ -55,50 +218,127 @@ body { width: 32px; height: 32px; border-radius: 50%; - background: rgba(0, 0, 0, 0.15); - backdrop-filter: blur(2px); + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(4px); pointer-events: none; z-index: 9999; transform: translate(-50%, -50%); - transition: width 0.2s, height 0.2s, background 0.2s; + transition: width 0.2s ease, height 0.2s ease, background 0.2s ease; + border: 1px solid rgba(0,0,0,0.3); } .custom-cursor.active { width: 48px; height: 48px; - background: rgba(0, 0, 0, 0.08); + background: rgba(255, 255, 255, 0.9); + border-color: rgba(255, 255, 255, 0.5); } - -.dashboard-container { +.wrapper { + width: 100%; max-width: 1200px; - margin: 0 auto; + animation: fadeIn 0.6s cubic-bezier(0.16, 1, 0.3, 1); + position: relative; + z-index: 1; +} + +.recent-list { + display: flex; + flex-direction: column; + gap: 12px; + min-height: 200px; +} + +.recent-item { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 0; + border-bottom: 1px solid var(--color-border-tertiary); + transition: all 0.2s ease; +} + +.recent-item:hover { + transform: translateX(4px); + border-bottom-color: var(--color-border-secondary); +} + +.recent-icon { + font-size: 16px; + width: 24px; + text-align: center; +} + +.recent-text { + flex: 1; + font-size: 13px; + font-weight: 500; + color: var(--color-text-primary); +} + +.recent-time { + font-size: 10px; + color: var(--color-text-tertiary); +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(5px); } + to { opacity: 1; transform: translateY(0); } } .dashboard-header { - margin-bottom: 48px; + width: 100%; + margin: 0 auto 16px auto; + padding: 16px 24px; + background: var(--color-background-primary); + border: 1px solid var(--color-border-tertiary); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-md); + display: flex; + align-items: center; + justify-content: space-between; } -.logo-section { +.header-left { display: flex; align-items: center; gap: 12px; } -.dashboard-logo { +.logo { width: 40px; height: 40px; border-radius: 2px; } -.dashboard-title { +.site-title { font-size: 20px; font-weight: 700; - color: #1a1a18; + color: var(--color-text-primary); +} + +.header-nav { + display: flex; + gap: 24px; +} + +.header-nav a { + text-decoration: none; + color: var(--color-text-secondary); + font-weight: 500; + transition: color 0.2s; +} + +.header-nav a:hover { + color: var(--color-text-primary); } .dashboard-content { - max-width: 900px; - margin: 0 auto; + background: var(--color-background-primary); + border: 1px solid var(--color-border-tertiary); + border-radius: var(--border-radius-lg); + padding: 32px; + box-shadow: var(--shadow-lg); + margin-top: 16px; } .greeting-section { @@ -107,25 +347,25 @@ body { } .greeting-text { - font-size: 13px; + font-size: 12px; font-weight: 600; - color: #6b6b66; - letter-spacing: 0.08em; - text-transform: uppercase; + color: var(--color-text-tertiary); + letter-spacing: .08em; margin-bottom: 12px; + text-transform: uppercase; } .user-name { font-size: 36px; font-weight: 600; - color: #1a1a18; + color: var(--color-text-primary); letter-spacing: -0.02em; margin-bottom: 8px; } .date-text { font-size: 14px; - color: #6b6b66; + color: var(--color-text-secondary); } .stats-row { @@ -136,31 +376,31 @@ body { } .stat-item { - background: #ffffff; - border: 1px solid rgba(0,0,0,0.08); - border-radius: var(--border-radius); + background: var(--color-background-secondary); + border: 1px solid var(--color-border-tertiary); + border-radius: var(--border-radius-md); padding: 20px 12px; text-align: center; - transition: var(--transition); + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .stat-item:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); - border-color: rgba(0,0,0,0.12); + border-color: var(--color-border-secondary); } .stat-number { font-size: 32px; font-weight: 700; - color: #1a1a18; + color: var(--color-text-primary); margin-bottom: 6px; } .stat-label { font-size: 11px; font-weight: 600; - color: #6b6b66; + color: var(--color-text-tertiary); text-transform: uppercase; letter-spacing: 0.05em; } @@ -173,28 +413,28 @@ body { } .dashboard-card { - background: #ffffff; - border: 1px solid rgba(0,0,0,0.08); - border-radius: var(--border-radius); + background: var(--color-background-secondary); + border: 1px solid var(--color-border-tertiary); + border-radius: var(--border-radius-md); padding: 20px; - transition: var(--transition); + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .dashboard-card:hover { - box-shadow: var(--shadow-md); - border-color: rgba(0,0,0,0.12); + box-shadow: var(--shadow-sm); + border-color: var(--color-border-secondary); } .card-header { margin-bottom: 16px; padding-bottom: 12px; - border-bottom: 1px solid rgba(0,0,0,0.08); + border-bottom: 1px solid var(--color-border-tertiary); } .card-title { - font-size: 12px; - font-weight: 600; - color: #6b6b66; + font-size: 11px; + font-weight: 700; + color: var(--color-text-tertiary); text-transform: uppercase; letter-spacing: 0.08em; } @@ -213,27 +453,28 @@ body { align-items: center; justify-content: space-between; padding: 8px 0; - border-bottom: 1px solid rgba(0,0,0,0.08); - transition: var(--transition); + border-bottom: 1px solid var(--color-border-tertiary); + transition: all 0.2s ease; + cursor: pointer; } .deadline-item:hover, .priority-item:hover { transform: translateX(4px); - border-bottom-color: rgba(0,0,0,0.12); + border-bottom-color: var(--color-border-secondary); } .deadline-name, .priority-name { font-size: 14px; font-weight: 500; - color: #1a1a18; + color: var(--color-text-primary); } .deadline-date { font-size: 11px; font-weight: 600; - color: #a32d2d; + color: var(--color-text-danger); } .priority-badge { @@ -241,14 +482,14 @@ body { font-weight: 600; padding: 3px 10px; border-radius: 20px; - background: #efefec; - color: #6b6b66; + background: var(--color-background-tertiary); + color: var(--color-text-secondary); } .empty-state { text-align: center; padding: 32px 16px; - color: #9c9a92; + color: var(--color-text-tertiary); font-size: 13px; } @@ -260,19 +501,19 @@ body { .streak-number { font-size: 52px; font-weight: 700; - color: #1a1a18; + color: var(--color-text-primary); letter-spacing: -0.02em; margin-bottom: 8px; } .streak-label { font-size: 12px; - color: #6b6b66; + color: var(--color-text-secondary); margin-bottom: 20px; } .streak-progress-bar { - background: #efefec; + background: var(--color-background-tertiary); border-radius: 4px; height: 4px; margin-bottom: 12px; @@ -280,7 +521,7 @@ body { } .streak-progress-fill { - background: #1a1a18; + background: var(--color-text-primary); height: 100%; border-radius: 4px; transition: width 0.6s ease; @@ -288,20 +529,20 @@ body { .streak-best-text { font-size: 11px; - color: #9c9a92; + color: var(--color-text-tertiary); } .start-planning-btn { width: 100%; padding: 14px 20px; - background: #1a1a18; - color: #ffffff; + background: var(--color-text-primary); + color: var(--color-background-primary); border: none; - border-radius: var(--border-radius); + border-radius: var(--border-radius-md); font-size: 14px; font-weight: 600; cursor: pointer; - transition: var(--transition); + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); display: flex; align-items: center; justify-content: center; @@ -309,8 +550,9 @@ body { } .start-planning-btn:hover { + background: var(--color-text-secondary); transform: translateY(-2px); - box-shadow: var(--shadow-lg); + box-shadow: var(--shadow-md); } .start-planning-btn:active { @@ -347,4 +589,14 @@ body { .stat-number { font-size: 26px; } + + .dashboard-header { + flex-direction: column; + gap: 16px; + } + + .header-nav { + flex-wrap: wrap; + justify-content: center; + } } \ No newline at end of file diff --git a/css/index.css b/css/index.css index 8c8f437..7f0f0cb 100644 --- a/css/index.css +++ b/css/index.css @@ -1323,11 +1323,12 @@ body { .summary-box { margin-top: 16px; padding: 14px; - background: linear-gradient(135deg, #eef2ff, #f8fafc); + background: linear-gradient(135deg, var(--color-background-secondary), var(--color-background-tertiary)); border-radius: 12px; font-size: 14px; line-height: 1.6; - border: 1px solid #e5e7eb; + border: 1px solid var(--color-border-tertiary); + color: var(--color-text-primary); } .paste-input:focus, .paste-input:not(:placeholder-shown) { opacity: 1; z-index: 10; background: var(--color-background-primary); color: var(--color-text-primary); outline: none; } diff --git a/dashboard.html b/dashboard.html index e751004..88b0c44 100644 --- a/dashboard.html +++ b/dashboard.html @@ -8,17 +8,26 @@ -
-
-
- -

StudyPlan

+
+
+ +
+
+
+ +

StudyPlan

-
+ +
-
Good evening
+
Good morning
Chinmayee
@@ -62,18 +71,13 @@

StudyPlan

-
- Focus Streak -
-
-
0
-
current streak
-
-
+
+ Recent Activity +
+
+
No recent activity
+
-
Best: 0 days
-
-
- +
diff --git a/js/dashboard.js b/js/dashboard.js index 6b6d793..e89beb1 100644 --- a/js/dashboard.js +++ b/js/dashboard.js @@ -9,6 +9,19 @@ function formatDate(dateStr) { return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); } +function getTimeAgo(date) { + const now = new Date(); + const diffMs = now - date; + const diffMins = Math.floor(diffMs / 60000); + const diffHours = Math.floor(diffMs / 3600000); + const diffDays = Math.floor(diffMs / 86400000); + + if (diffMins < 1) return 'just now'; + if (diffMins < 60) return diffMins + 'm ago'; + if (diffHours < 24) return diffHours + 'h ago'; + return diffDays + 'd ago'; +} + function calculateStreak(tasks) { const completedTasks = tasks.filter(t => t.status === 'Done' && !t.archived); const today = new Date(); @@ -20,6 +33,7 @@ function calculateStreak(tasks) { for (let i = 0; i < 365; i++) { const dateStr = currentDate.toDateString(); const hasTaskOnDate = completedTasks.some(t => { + if (!t.due_at) return false; const taskDate = new Date(t.due_at); taskDate.setHours(0, 0, 0, 0); return taskDate.toDateString() === dateStr; @@ -34,27 +48,28 @@ function calculateStreak(tasks) { } const allDates = completedTasks.map(t => { + if (!t.due_at) return null; const d = new Date(t.due_at); d.setHours(0, 0, 0, 0); return d.toDateString(); - }); + }).filter(d => d); - const uniqueDates = [...new Set(allDates)]; - const bestStreak = Math.max(...uniqueDates.map((_, i, arr) => { - let streak = 1; - let current = new Date(arr[i]); - for (let j = i + 1; j < arr.length; j++) { - const next = new Date(arr[j]); - const diffDays = (next - current) / (1000 * 60 * 60 * 24); - if (diffDays === 1) { - streak++; - current = next; - } else { - break; - } + const uniqueDates = [...new Set(allDates)].sort(); + let bestStreak = 0; + let currentStreak = 1; + + for (let i = 1; i < uniqueDates.length; i++) { + const prev = new Date(uniqueDates[i-1]); + const curr = new Date(uniqueDates[i]); + const diffDays = (curr - prev) / (1000 * 60 * 60 * 24); + if (diffDays === 1) { + currentStreak++; + } else { + bestStreak = Math.max(bestStreak, currentStreak); + currentStreak = 1; } - return streak; - }), 0); + } + bestStreak = Math.max(bestStreak, currentStreak); return { current: streak, best: bestStreak }; } @@ -64,45 +79,61 @@ function updateDashboard() { const subjects = store.subjects; const now = new Date(); - const weekFromNow = new Date(); - weekFromNow.setDate(now.getDate() + 7); + const weekStart = new Date(now); + weekStart.setDate(now.getDate() - now.getDay()); + weekStart.setHours(0, 0, 0, 0); - const pendingTasks = tasks.filter(t => !t.archived && t.status !== 'Done'); - const completedThisWeek = tasks.filter(t => { - if (t.status !== 'Done') return false; + const weekEnd = new Date(weekStart); + weekEnd.setDate(weekStart.getDate() + 7); + + const thisWeekTasks = tasks.filter(t => { + if (t.archived) return false; + if (!t.due_at) return false; const d = new Date(t.due_at); - return d >= now && d <= weekFromNow; + return d >= weekStart && d < weekEnd; }); - const dueSoonTasks = pendingTasks.filter(t => { + + const totalThisWeek = thisWeekTasks.length; + const completedThisWeek = thisWeekTasks.filter(t => t.status === 'Done').length; + const pendingTasks = tasks.filter(t => !t.archived && t.status !== 'Done').length; + + const dueSoonTasks = tasks.filter(t => { + if (t.archived || t.status === 'Done') return false; if (!t.due_at) return false; const d = new Date(t.due_at); const diffDays = (d - now) / (1000 * 60 * 60 * 24); - return diffDays <= 3; - }); + return diffDays <= 3 && diffDays >= 0; + }).length; const totalTasksElem = document.getElementById('total-tasks'); const completedTasksElem = document.getElementById('completed-tasks'); + const pendingTasksElem = document.getElementById('pending-tasks'); const dueTasksElem = document.getElementById('due-tasks'); - if (totalTasksElem) totalTasksElem.textContent = pendingTasks.length; - if (completedTasksElem) completedTasksElem.textContent = completedThisWeek.length; - if (dueTasksElem) dueTasksElem.textContent = dueSoonTasks.length; + if (totalTasksElem) totalTasksElem.textContent = totalThisWeek; + if (completedTasksElem) completedTasksElem.textContent = completedThisWeek; + if (pendingTasksElem) pendingTasksElem.textContent = totalThisWeek - completedThisWeek; + if (dueTasksElem) dueTasksElem.textContent = dueSoonTasks; - const priorityTasks = [...pendingTasks] - .sort((a, b) => new Date(a.due_at) - new Date(b.due_at)) - .slice(0, 4); + const todayTasks = tasks.filter(t => { + if (t.archived || t.status === 'Done') return false; + if (!t.due_at) return false; + const d = new Date(t.due_at); + return d.toDateString() === now.toDateString(); + }).sort((a, b) => new Date(a.due_at) - new Date(b.due_at)); const priorityList = document.getElementById('priority-list'); if (priorityList) { - if (priorityTasks.length === 0) { - priorityList.innerHTML = '
No pending tasks
'; + if (todayTasks.length === 0) { + priorityList.innerHTML = '
No tasks for today
'; } else { - priorityList.innerHTML = priorityTasks.map(task => { + priorityList.innerHTML = todayTasks.slice(0, 5).map(task => { const sub = subjects.find(s => s.id === task.subject_id) || subjects[0]; + const timeStr = new Date(task.due_at).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); return `
${escapeHtml(task.title)} - ${sub?.short_code || 'Task'} + ${timeStr}
`; }).join(''); @@ -115,25 +146,29 @@ function updateDashboard() { } } - const upcomingTasks = [...pendingTasks] - .filter(t => t.due_at) - .sort((a, b) => new Date(a.due_at) - new Date(b.due_at)) - .slice(0, 5); + const upcomingTasks = tasks.filter(t => { + if (t.archived || t.status === 'Done') return false; + if (!t.due_at) return false; + const d = new Date(t.due_at); + return d > now; + }).sort((a, b) => new Date(a.due_at) - new Date(b.due_at)).slice(0, 5); const deadlineList = document.getElementById('deadline-list'); if (deadlineList) { if (upcomingTasks.length === 0) { - deadlineList.innerHTML = '
No upcoming deadlines
'; + deadlineList.innerHTML = '
No upcoming deadlines
'; } else { deadlineList.innerHTML = upcomingTasks.map(task => { - const date = new Date(task.due_at); - const daysDiff = Math.ceil((date - now) / (1000 * 60 * 60 * 24)); + const daysDiff = Math.ceil((new Date(task.due_at) - now) / (1000 * 60 * 60 * 24)); let dateClass = ''; - if (daysDiff <= 2) dateClass = 'deadline-date'; + let dateText = formatDate(task.due_at); + if (daysDiff === 0) dateText = 'Today'; + else if (daysDiff === 1) dateText = 'Tomorrow'; + else if (daysDiff <= 3) dateClass = 'deadline-date'; return `
${escapeHtml(task.title)} - ${formatDate(task.due_at)} + ${dateText}
`; }).join(''); @@ -146,17 +181,51 @@ function updateDashboard() { } } + const allTasks = [...tasks].filter(t => !t.archived).sort((a, b) => { + const dateA = new Date(a.due_at || a.created_at || 0); + const dateB = new Date(b.due_at || b.created_at || 0); + return dateB - dateA; + }).slice(0, 5); + + const recentList = document.getElementById('recent-list'); + if (recentList) { + if (allTasks.length === 0) { + recentList.innerHTML = '
No recent activity
'; + } else { + recentList.innerHTML = allTasks.map(task => { + const statusIcon = task.status === 'Done' ? '✓' : '○'; + const statusClass = task.status === 'Done' ? 'recent-text' : 'recent-text'; + const date = new Date(task.due_at || task.created_at || Date.now()); + const timeAgo = getTimeAgo(date); + const sub = subjects.find(s => s.id === task.subject_id) || subjects[0]; + return ` +
+
${statusIcon}
+
${escapeHtml(task.title)}
+
${timeAgo}
+
+ `; + }).join(''); + + document.querySelectorAll('.recent-item').forEach(el => { + el.addEventListener('click', () => { + window.location.href = '/index.html'; + }); + }); + } + } + const streak = calculateStreak(tasks); const streakElem = document.getElementById('streak-days'); const streakBarElem = document.getElementById('streak-bar'); - const streakBestElem = document.querySelector('.streak-best'); + const streakBestElem = document.getElementById('streak-best'); if (streakElem) streakElem.textContent = streak.current; if (streakBarElem) { const percent = streak.best > 0 ? (streak.current / streak.best) * 100 : 0; streakBarElem.style.width = percent + '%'; } - if (streakBestElem) streakBestElem.textContent = `Best: ${streak.best} days`; + if (streakBestElem) streakBestElem.textContent = 'Best: ' + streak.best + ' days'; } function escapeHtml(str) { @@ -191,4 +260,15 @@ if (greetingElem) { if (hour < 12) greeting = 'Good morning'; else if (hour < 18) greeting = 'Good afternoon'; greetingElem.textContent = greeting; -} \ No newline at end of file +} + +function setDate() { + const dateElem = document.getElementById('date-text'); + if (dateElem) { + const now = new Date(); + const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; + dateElem.textContent = now.toLocaleDateString('en-US', options); + } +} + +setDate(); \ No newline at end of file diff --git a/server.js b/server.js index 7dc58f9..220df3f 100644 --- a/server.js +++ b/server.js @@ -13,11 +13,23 @@ app.use(express.json()); const page404Path = path.join(__dirname, '404.html'); const page500Path = path.join(__dirname, 'error.html'); +app.get('/', (req, res) => { + res.sendFile(path.join(__dirname, 'dashboard.html')); +}); + +app.get('/index.html', (req, res) => { + res.sendFile(path.join(__dirname, 'index.html')); +}); + +app.get('/dashboard.html', (req, res) => { + res.sendFile(path.join(__dirname, 'dashboard.html')); +}); + + // Static app.use('/css', express.static(path.join(__dirname, 'css'))); app.use('/js', express.static(path.join(__dirname, 'js'))); app.use(express.static(__dirname)); - initDb(); const ai = process.env.GEMINI_API_KEY @@ -505,18 +517,8 @@ app.use((err, req, res, next) => { }); -// Serve dashboard as default landing page -app.get('/', (req, res) => { - res.sendFile(path.join(__dirname, 'dashboard.html')); -}); - -// Keep existing index route -app.get('/index.html', (req, res) => { - res.sendFile(path.join(__dirname, 'index.html')); -}); - -// ================= SERVER ================= +// // ================= SERVER ================= const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log('Server running on port ' + PORT);