From de72b31473ff985660adf6c1765070c638b6574f Mon Sep 17 00:00:00 2001 From: Richard Frimpong Date: Sat, 21 Mar 2026 02:17:56 +0000 Subject: [PATCH 1/4] Add HTML structure for todo list app --- Sprint-3/todo-list/index.html | 97 ++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/Sprint-3/todo-list/index.html b/Sprint-3/todo-list/index.html index 4d12c4654..38f708600 100644 --- a/Sprint-3/todo-list/index.html +++ b/Sprint-3/todo-list/index.html @@ -1,40 +1,63 @@ - - - - ToDo List - - - - - - -
-

My ToDo List

- -
- - -
+ + + + Todo List + + + + +
+

My Todo List

+ +
+ + + + + +
+ +
+ +
-
    -
- - - - -
- - + + + + +
+ + \ No newline at end of file From 1d7b2c3d66d619cdb85a8b429fb36e36169323df Mon Sep 17 00:00:00 2001 From: Richard Frimpong Date: Sat, 21 Mar 2026 02:18:19 +0000 Subject: [PATCH 2/4] Style todo list layout and controls --- Sprint-3/todo-list/style.css | 159 ++++++++++++++++++++++------------- 1 file changed, 102 insertions(+), 57 deletions(-) diff --git a/Sprint-3/todo-list/style.css b/Sprint-3/todo-list/style.css index 535e91227..adbc54f6a 100644 --- a/Sprint-3/todo-list/style.css +++ b/Sprint-3/todo-list/style.css @@ -1,107 +1,152 @@ +/* Make box sizing more predictable */ * { box-sizing: border-box; - margin: 0; - padding: 0; - font-family: Arial, sans-serif; } +/* Base page styling */ body { + margin: 0; + font-family: Arial, Helvetica, sans-serif; background-color: #f4f4f4; - padding: 40px; + color: #222222; + padding: 40px 20px; } +/* Main app container */ .todo-container { - max-width: 500px; + max-width: 800px; margin: 0 auto; - background: white; - border-radius: 10px; - padding: 20px; - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); + background-color: #ffffff; + border-radius: 12px; + padding: 24px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); } +/* Heading */ h1 { text-align: center; - margin-bottom: 20px; + margin-bottom: 24px; } +/* Input area */ .todo-input { - display: flex; + display: grid; + grid-template-columns: 1fr 180px 120px; gap: 10px; + margin-bottom: 16px; +} + +/* Action area */ +.todo-actions { + display: flex; + justify-content: flex-end; margin-bottom: 20px; } -.todo-input input { - flex: 1; - padding: 10px; - font-size: 16px; - border-radius: 6px; - border: 1px solid #ccc; +/* Inputs and buttons */ +input, +button { + font: inherit; + padding: 12px; + border-radius: 8px; } -.todo-input button { - padding: 10px 20px; - font-size: 16px; - background-color: #4CAF50; - color: white; +input { + border: 1px solid #cccccc; +} + +button { border: none; - border-radius: 6px; + background-color: #0b5ed7; + color: #ffffff; cursor: pointer; } -.todo-input button:hover { - background-color: #45a049; +button:hover { + background-color: #094db1; +} + +button:focus, +input:focus { + outline: 3px solid #99c2ff; + outline-offset: 2px; } +/* Todo list */ .todo-list { - list-style-type: none; - padding-left: 0; + list-style: none; + padding: 0; + margin: 0; } +/* Todo item */ .todo-item { display: flex; - align-items: center; justify-content: space-between; - padding: 12px 10px; - margin-bottom: 10px; - border: 1px solid #ddd; - border-radius: 6px; - background-color: #fff; -} - -.description { - flex: 1; - margin-right: 10px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + align-items: center; + gap: 16px; + background-color: #ffffff; + border-radius: 12px; + padding: 14px 16px; + margin-bottom: 12px; + border: 1px solid #dddddd; } -.actions { +/* Left side of todo */ +.todo-content { display: flex; - gap: 10px; + align-items: center; + gap: 12px; + flex: 1; } -.actions button { - background: none; - border: none; - cursor: pointer; - font-size: 18px; +/* Text group */ +.todo-text-group { display: flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; + flex-direction: column; + gap: 4px; } -.complete-btn i { - color: green; +/* Description text */ +.description { + font-size: 1rem; + word-break: break-word; } -.delete-btn i { - color: red; +/* Deadline text */ +.deadline { + font-size: 0.9rem; + color: #666666; } +/* Completed task */ .todo-item.completed .description { text-decoration: line-through; - color: gray; + color: #777777; +} + +/* Icon-style buttons */ +.complete-btn, +.delete-btn { + background: transparent; + color: #222222; + padding: 6px 8px; + font-size: 1.2rem; } + +.complete-btn:hover, +.delete-btn:hover { + background-color: #eeeeee; + color: #222222; +} + +/* Mobile layout */ +@media (max-width: 700px) { + .todo-input { + grid-template-columns: 1fr; + } + + .todo-item { + align-items: flex-start; + } +} \ No newline at end of file From f70dc96ff114e2e557b456445483d8a7e07b6696 Mon Sep 17 00:00:00 2001 From: Richard Frimpong Date: Sat, 21 Mar 2026 02:18:32 +0000 Subject: [PATCH 3/4] Extend todo module with optional deadlines and delete completed support --- Sprint-3/todo-list/todos.mjs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/Sprint-3/todo-list/todos.mjs b/Sprint-3/todo-list/todos.mjs index f17ab6a25..9b2ba5702 100644 --- a/Sprint-3/todo-list/todos.mjs +++ b/Sprint-3/todo-list/todos.mjs @@ -1,17 +1,21 @@ /* - A ToDo List (todos) is expected to be represented as an array of objects in - the following manner: + A ToDo List (todos) is represented as an array of objects in this format: [ - { task: "Description of task 1", completed: false}, - { task: "Description of task 2", completed: true} + { task: "Description of task 1", completed: false }, + { task: "Description of task 2", completed: true, deadline: "2026-03-25" } ] - */ // Append a new task to todos[] -export function addTask(todos, task, completed = false) { - todos.push({ task, completed }); +export function addTask(todos, task, completed = false, deadline = "") { + const newTask = { task, completed }; + + if (deadline) { + newTask.deadline = deadline; + } + + todos.push(newTask); } // Delete todos[taskIndex] if it exists @@ -26,4 +30,13 @@ export function toggleCompletedOnTask(todos, taskIndex) { if (todos[taskIndex]) { todos[taskIndex].completed = !todos[taskIndex].completed; } -} \ No newline at end of file +} + +// Delete all completed tasks from the todos array +export function deleteCompletedTasks(todos) { + for (let index = todos.length - 1; index >= 0; index -= 1) { + if (todos[index].completed) { + todos.splice(index, 1); + } + } +} From 01dba5ff66b8ea8faf2bf189b7b47756940649c9 Mon Sep 17 00:00:00 2001 From: Richard Frimpong Date: Sat, 21 Mar 2026 02:18:47 +0000 Subject: [PATCH 4/4] Add todo list rendering, input handling and DOM interactions --- Sprint-3/todo-list/script.mjs | 116 ++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 40 deletions(-) diff --git a/Sprint-3/todo-list/script.mjs b/Sprint-3/todo-list/script.mjs index ba0b2ceae..fcb31b984 100644 --- a/Sprint-3/todo-list/script.mjs +++ b/Sprint-3/todo-list/script.mjs @@ -1,42 +1,65 @@ -// Store everything imported from './todos.mjs' module as properties of an object named Todos +// Import all exported functions from todos.mjs import * as Todos from "./todos.mjs"; -// To store the todo tasks +// Store todo tasks here const todos = []; -// Set up tasks to be performed once on page load +// Cache DOM elements so we do not query the DOM repeatedly +const todoListEl = document.getElementById("todo-list"); +const newTaskInputEl = document.getElementById("new-task-input"); +const deadlineInputEl = document.getElementById("deadline-input"); +const addTaskButtonEl = document.getElementById("add-task-btn"); +const deleteCompletedButtonEl = document.getElementById("delete-completed-btn"); + +// Template for each todo list item +const todoListItemTemplate = + document.getElementById("todo-item-template").content.firstElementChild; + +// Set up the app when the page loads window.addEventListener("load", () => { - document.getElementById("add-task-btn").addEventListener("click", addNewTodo); + addTaskButtonEl.addEventListener("click", addNewTodo); + deleteCompletedButtonEl.addEventListener("click", deleteCompletedTodos); - // Populate sample data - Todos.addTask(todos, "Wash the dishes", false); - Todos.addTask(todos, "Do the shopping", true); + // Hardcoded sample tasks shown on page load + Todos.addTask(todos, "Wash the dishes", false, ""); + Todos.addTask(todos, "Do the shopping", true, "2026-03-24"); + Todos.addTask(todos, "Prepare for Saturday class", false, "2026-03-26"); - render(); + populateTodoList(); }); - -// A callback that reads the task description from an input field and -// append a new task to the todo list. +/** + * Reads input values and adds a new todo to the list. + */ function addNewTodo() { - const taskInput = document.getElementById("new-task-input"); - const task = taskInput.value.trim(); - if (task) { - Todos.addTask(todos, task, false); - render(); + const task = newTaskInputEl.value.trim(); + const deadline = deadlineInputEl.value; + + // Do not add empty tasks + if (!task) { + return; } - taskInput.value = ""; + Todos.addTask(todos, task, false, deadline); + populateTodoList(); + + // Clear inputs after adding + newTaskInputEl.value = ""; + deadlineInputEl.value = ""; } -// Note: -// - Store the reference to the
    element with id "todo-list" here -// to avoid querying the DOM repeatedly inside render(). -// - This variable is declared here to be close to the only function that uses it. -const todoListEl = document.getElementById("todo-list"); +/** + * Deletes all completed todos. + */ +function deleteCompletedTodos() { + Todos.deleteCompletedTasks(todos); + populateTodoList(); +} -// Render the whole todo list -function render() { +/** + * Clears and rebuilds the todo list from the hardcoded/current todos array. + */ +function populateTodoList() { todoListEl.innerHTML = ""; todos.forEach((todo, index) => { @@ -45,32 +68,45 @@ function render() { }); } +/** + * Creates one
  • element for a given todo. + */ +function createListItem(todo, index) { + const li = todoListItemTemplate.cloneNode(true); -// Note: -// - First child of #todo-item-template is a
  • element. -// We will create each ToDo list item as a clone of this node. -// - This variable is declared here to be close to the only function that uses it. -const todoListItemTemplate = - document.getElementById("todo-item-template").content.firstElementChild; + const descriptionEl = li.querySelector(".description"); + const deadlineEl = li.querySelector(".deadline"); + const completeButtonEl = li.querySelector(".complete-btn"); + const deleteButtonEl = li.querySelector(".delete-btn"); -// Create a
  • element for the given todo task -function createListItem(todo, index) { - const li = todoListItemTemplate.cloneNode(true); // true => Do a deep copy of the node + descriptionEl.textContent = todo.task; - li.querySelector(".description").textContent = todo.task; + // Show deadline if one exists + if (todo.deadline) { + deadlineEl.textContent = `Deadline: ${todo.deadline}`; + } else { + deadlineEl.textContent = ""; + } + + // Show completed state if (todo.completed) { li.classList.add("completed"); + completeButtonEl.textContent = "✅"; + } else { + completeButtonEl.textContent = "☑"; } - li.querySelector('.complete-btn').addEventListener("click", () => { + // Toggle task completion + completeButtonEl.addEventListener("click", () => { Todos.toggleCompletedOnTask(todos, index); - render(); + populateTodoList(); }); - - li.querySelector('.delete-btn').addEventListener("click", () => { + + // Delete individual task + deleteButtonEl.addEventListener("click", () => { Todos.deleteTask(todos, index); - render(); + populateTodoList(); }); return li; -} \ No newline at end of file +}