diff --git a/backend/controllers/tasksController.js b/backend/controllers/tasksController.js
index 745738d..2f1e314 100644
--- a/backend/controllers/tasksController.js
+++ b/backend/controllers/tasksController.js
@@ -3,6 +3,11 @@ const path = require('path');
const { v4: uuidv4 } = require('uuid');
const filePath = path.join(__dirname, '../data/tasks.json');
+const VALID_CATEGORIES = ['High', 'Medium', 'Low'];
+
+function normalizeCategory(category) {
+ return VALID_CATEGORIES.includes(category) ? category : 'Medium';
+}
function readTasks() {
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
@@ -18,8 +23,12 @@ exports.getAllTasks = (req, res) => {
exports.createTask = (req, res) => {
const tasks = readTasks();
- const { title, completed = false } = req.body;
- const newTask = { id: uuidv4(), title, completed };
+ const { title, completed = false, category = 'Medium' } = req.body;
+ if (typeof title !== 'string' || title.trim() === '') {
+ return res.status(400).json({ message: 'Title is required and cannot be empty' });
+ }
+ const normalizedCategory = normalizeCategory(category);
+ const newTask = { id: uuidv4(), title: title.trim(), completed, category: normalizedCategory };
tasks.push(newTask);
writeTasks(tasks);
res.status(201).json(newTask);
@@ -30,8 +39,16 @@ exports.updateTask = (req, res) => {
const task = tasks.find(t => t.id === req.params.id);
if (!task) return res.status(404).json({ message: 'Task not found' });
- task.title = req.body.title ?? task.title;
+ if (req.body.title !== undefined) {
+ if (typeof req.body.title !== 'string' || req.body.title.trim() === '') {
+ return res.status(400).json({ message: 'Title is required and cannot be empty' });
+ }
+ task.title = req.body.title.trim();
+ }
task.completed = req.body.completed ?? task.completed;
+ if (req.body.category !== undefined) {
+ task.category = normalizeCategory(req.body.category);
+ }
writeTasks(tasks);
res.json(task);
};
diff --git a/frontend/index.html b/frontend/index.html
index 915426f..4291634 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -14,6 +14,11 @@
Task List
diff --git a/frontend/script.js b/frontend/script.js
index a519ccf..e7cc9e2 100644
--- a/frontend/script.js
+++ b/frontend/script.js
@@ -1,7 +1,9 @@
const API_URL = 'http://localhost:3000/api/tasks';
+const DEFAULT_CATEGORY = 'Medium';
const taskList = document.getElementById('taskList');
const taskForm = document.getElementById('taskForm');
const taskInput = document.getElementById('taskInput');
+const taskCategory = document.getElementById('taskCategory');
const themeToggle = document.getElementById('themeToggle');
// Theme functionality
@@ -35,28 +37,54 @@ async function fetchTasks() {
}
function addTaskToDOM(task) {
+ const category = task.category || DEFAULT_CATEGORY;
const li = document.createElement('li');
- li.innerHTML = `
- ${task.title}
-
-
-
-
- `;
+ const taskContent = document.createElement('div');
+ taskContent.className = 'task-content';
+
+ const title = document.createElement('span');
+ title.className = `task-title ${task.completed ? 'completed' : ''}`;
+ title.textContent = task.title;
+
+ const badge = document.createElement('span');
+ badge.className = `category-badge category-${category.toLowerCase()}`;
+ badge.textContent = category;
+
+ const actions = document.createElement('div');
+
+ const completeButton = document.createElement('button');
+ completeButton.textContent = '✓';
+ completeButton.setAttribute('aria-label', task.completed ? 'Mark task as incomplete' : 'Mark task as complete');
+ completeButton.addEventListener('click', () => toggleComplete(task.id, !task.completed));
+
+ const deleteButton = document.createElement('button');
+ deleteButton.textContent = '✕';
+ deleteButton.setAttribute('aria-label', 'Delete task');
+ deleteButton.addEventListener('click', () => deleteTask(task.id));
+
+ taskContent.appendChild(title);
+ taskContent.appendChild(badge);
+ actions.appendChild(completeButton);
+ actions.appendChild(deleteButton);
+ li.appendChild(taskContent);
+ li.appendChild(actions);
taskList.appendChild(li);
}
taskForm.addEventListener('submit', async e => {
e.preventDefault();
- const title = taskInput.value;
+ const title = taskInput.value.trim();
+ if (!title) return;
+ const category = taskCategory.value || DEFAULT_CATEGORY;
const res = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ title })
+ body: JSON.stringify({ title, category })
});
const task = await res.json();
addTaskToDOM(task);
taskInput.value = '';
+ taskCategory.value = DEFAULT_CATEGORY;
});
async function toggleComplete(id, completed) {
diff --git a/frontend/style.css b/frontend/style.css
index d351594..65e6af1 100644
--- a/frontend/style.css
+++ b/frontend/style.css
@@ -88,7 +88,8 @@ form {
gap: 12px;
margin-bottom: 1.5em;
}
-#taskInput {
+#taskInput,
+#taskCategory {
flex: 1;
padding: 0.75em 1em;
border: none;
@@ -100,7 +101,8 @@ form {
outline: none;
transition: box-shadow 0.2s;
}
-#taskInput:focus {
+#taskInput:focus,
+#taskCategory:focus {
box-shadow: 0 2px 8px var(--input-focus-shadow);
}
button[type="submit"] {
@@ -138,12 +140,39 @@ li {
li:hover {
box-shadow: 0 4px 16px var(--task-hover-shadow);
}
-li span {
+.task-title {
font-size: 1.05em;
flex: 1;
color: var(--text-color);
transition: color 0.2s;
}
+.task-content {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ flex: 1;
+}
+.category-badge {
+ display: inline-block;
+ width: fit-content;
+ padding: 0.15em 0.6em;
+ border-radius: 999px;
+ font-size: 0.75em;
+ font-weight: 500;
+ line-height: 1.4;
+}
+.category-high {
+ background: #ffebee;
+ color: #8b0000;
+}
+.category-medium {
+ background: #fff8e1;
+ color: #8a3b00;
+}
+.category-low {
+ background: #e8f5e9;
+ color: #1b5e20;
+}
.completed {
text-decoration: line-through;
color: var(--completed-color);