Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions backend/controllers/tasksController.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand All @@ -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);
Expand All @@ -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);
};
Expand Down
5 changes: 5 additions & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ <h1>Task List</h1>
</div>
<form id="taskForm">
<input type="text" id="taskInput" placeholder="New task" required />
<select id="taskCategory" aria-label="Task category">
<option value="High">High</option>
<option value="Medium" selected>Medium</option>
<option value="Low">Low</option>
</select>
<button type="submit" title="Click to add a new task to the list">Add Task</button>
</form>
<ul id="taskList"></ul>
Expand Down
46 changes: 37 additions & 9 deletions frontend/script.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -35,28 +37,54 @@ async function fetchTasks() {
}

function addTaskToDOM(task) {
const category = task.category || DEFAULT_CATEGORY;
const li = document.createElement('li');
li.innerHTML = `
<span class="${task.completed ? 'completed' : ''}">${task.title}</span>
<div>
<button onclick="toggleComplete('${task.id}', ${!task.completed})">✓</button>
<button onclick="deleteTask('${task.id}')">✕</button>
</div>
`;
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) {
Expand Down
35 changes: 32 additions & 3 deletions frontend/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ form {
gap: 12px;
margin-bottom: 1.5em;
}
#taskInput {
#taskInput,
#taskCategory {
flex: 1;
padding: 0.75em 1em;
border: none;
Expand All @@ -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"] {
Expand Down Expand Up @@ -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);
Expand Down