diff --git a/README.md b/README.md
index 7103879..8703939 100644
--- a/README.md
+++ b/README.md
@@ -79,6 +79,11 @@ Planner + Calendar Update
- SQLite-based local database
- Structured task + subject mapping
+### 📥 Data Export
+- Export your study plan to CSV
+- Includes task details, subjects, and deadlines
+- Compatible with Excel, Google Sheets, and other tools
+
---
## 🧠System Architecture
diff --git a/backend/controllers/csvDownload.controller.js b/backend/controllers/csvDownload.controller.js
index 5ad0b63..54f30d9 100644
--- a/backend/controllers/csvDownload.controller.js
+++ b/backend/controllers/csvDownload.controller.js
@@ -34,8 +34,8 @@ async function downloadData(req, res) {
task.status,
task.priority,
task.confidence_score,
- `"${(task.notes || '').replace(/"/g, '""')}"`
- ])
+ task.notes || ''
+ ].map(val => `"${String(val).replace(/"/g, '""')}"`))
];
const csvString = rows.map(row => row.join(',')).join('\n');
diff --git a/css/index.css b/css/index.css
index 24a20f7..1721320 100644
--- a/css/index.css
+++ b/css/index.css
@@ -794,8 +794,6 @@ body {
#new-subject-modal .subject-color-swatch--selected {
box-shadow: 0 0 0 2px #0f172a, 0 0 0 4px #a5b4fc;
}
-/* css/index.css */
-*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--color-background-primary: #ffffff;
diff --git a/js/app.js b/js/app.js
index 9bfd993..8b83c36 100644
--- a/js/app.js
+++ b/js/app.js
@@ -14,8 +14,14 @@ function generateSummary(tasks, subjects) {
let weekCount = 0;
let subjectCount = {};
- tasks.forEach(t => {
- if (t.archived || t.status === 'Done' || !t.due_at) return;
+ // Progress calculation
+ const activeTasks = tasks.filter(t => !t.archived);
+ const totalActive = activeTasks.length;
+ const completedTasks = activeTasks.filter(t => t.status === 'Done').length;
+ const completionPercentage = totalActive > 0 ? Math.round((completedTasks / totalActive) * 100) : 0;
+
+ activeTasks.forEach(t => {
+ if (t.status === 'Done' || !t.due_at) return;
const d = new Date(t.due_at);
@@ -41,6 +47,14 @@ function generateSummary(tasks, subjects) {
: 'no specific subject';
return `
+ 📊 Overall Progress
+
+
+ ${completionPercentage}% completed (${completedTasks}/${totalActive} tasks)
+
+
📅 Daily
Today you have ${todayCount} task(s).
Focus on ${topSubject}.
@@ -123,7 +137,7 @@ function renderSidebarSubjects() {
countBySubject[s.id] = 0;
});
tasks.forEach(t => {
- if (t.archived || !t.subject_id || countBySubject[t.subject_id] === undefined) return;
+ if (t.archived || t.status === 'Done' || !t.subject_id || countBySubject[t.subject_id] === undefined) return;
countBySubject[t.subject_id]++;
});
@@ -397,7 +411,9 @@ function renderTasks() {
// Update badges
const allTasksBadge = document.querySelector('#all-tasks-btn .badge');
if (allTasksBadge) {
- allTasksBadge.textContent = activeTasks.length;
+ const pendingCount = activeTasks.filter(t => t.status !== 'Done').length;
+ allTasksBadge.textContent = pendingCount;
+ allTasksBadge.setAttribute('aria-label', `${pendingCount} pending tasks`);
}
const archivedBadge = document.querySelector('#archived-tasks-btn .badge');
if (archivedBadge) {
@@ -562,7 +578,7 @@ function renderTasks() {
: '';
tasksSection.innerHTML = actionBar +
- renderGroup(titlePrefix + 'âš Due soon', dueSoon, 'var(--color-text-danger)', true)
+ renderGroup(titlePrefix + 'âš Due soon', dueSoon, 'var(--color-text-danger)', true) +
renderGroup(titlePrefix + 'This week', thisWeek, 'var(--color-text-secondary)', true) +
renderGroup(titlePrefix + 'Completed', completed, 'var(--color-text-tertiary)') +
emptyState;
@@ -663,9 +679,11 @@ function renderTasks() {
}
-const summaryBox = document.getElementById('summary-box');
-if (summaryBox) {
- summaryBox.innerHTML = generateSummary(store.tasks, store.subjects);
+function renderSummary() {
+ const summaryBox = document.getElementById('summary-box');
+ if (summaryBox) {
+ summaryBox.innerHTML = generateSummary(store.tasks, store.subjects);
+ }
}
function renderCalendar() {
@@ -855,6 +873,7 @@ store.subscribe(renderExtraction);
store.subscribe(renderCalendar);
store.subscribe(renderFocusTasks);
store.subscribe(renderSidebarSubjects);
+store.subscribe(renderSummary);
document.addEventListener('DOMContentLoaded', () => {
if (newSubjectColorsEl) {
@@ -971,9 +990,8 @@ document.addEventListener('DOMContentLoaded', () => {
renderCalendar();
});
-
-//NEw Task addition event listeners
-newTaskBtn.addEventListener('click', () => {
+ // New Task addition event listeners
+ newTaskBtn.addEventListener('click', () => {
if (!store.subjects || store.subjects.length === 0) {
alert('Subjects are still loading. Please try again in a moment.');
@@ -1036,15 +1054,6 @@ newTaskSave.addEventListener('click', async () => {
newTaskModal.style.display = 'none';
});
-addItemsBtn.addEventListener('click', () => {
- if (store.currentPaste) {
- store.addTasks(store.currentPaste);
- store.clearExtracted();
- pasteInput.value = '';
- }
-});
-});
-
extractBtn.addEventListener('click', async () => {
const text = pasteInput.value;
if (!text.trim()) return;
@@ -1060,19 +1069,20 @@ extractBtn.addEventListener('click', async () => {
store.setExtracted(items);
});
-clearBtn.addEventListener('click', () => {
- pasteInput.value = '';
- store.clearExtracted();
-});
-
-addItemsBtn.addEventListener('click', () => {
- if (store.currentPaste) {
- store.addTasks(store.currentPaste);
- store.clearExtracted();
+ clearBtn.addEventListener('click', () => {
pasteInput.value = '';
- }
-});
+ store.clearExtracted();
+ });
+
+ addItemsBtn.addEventListener('click', () => {
+ if (store.currentPaste) {
+ store.addTasks(store.currentPaste);
+ store.clearExtracted();
+ pasteInput.value = '';
+ }
+ });
downloadBtn.addEventListener('click', () => {
downloadData();
});
+}); // Close DOMContentLoaded event listener
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index d4124c7..bdeb26a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1312,19 +1312,6 @@
"url": "https://opencollective.com/express"
}
},
- "node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
"node_modules/prebuild-install": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
diff --git a/server.js b/server.js
index 877f766..72a5cc5 100644
--- a/server.js
+++ b/server.js
@@ -425,7 +425,7 @@ Each object must have: title (string), subject_name (string), due_at (ISO 8601 d
Text: "${text}"
`;
const response = await ai.models.generateContent({
- model: 'gemini-2.5-flash',
+ model: 'gemini-1.5-flash',
contents: prompt
});
@@ -456,7 +456,7 @@ app.post('/api/auth/signup', (req, res) => {
return res.status(400).json({ error: 'User already exists' });
}
users[email] = { email, password };
- res.json({ success: true, message: 'Account created successfully' });
+ res.json({ success: true, email: email, message: 'Account created successfully' });
});
app.post('/api/auth/login', (req, res) => {