diff --git a/Sprint-3/todo-list/index.html b/Sprint-3/todo-list/index.html
index 4d12c4654..fefd05855 100644
--- a/Sprint-3/todo-list/index.html
+++ b/Sprint-3/todo-list/index.html
@@ -15,12 +15,15 @@
My ToDo List
+
+
+
Task description
+
-
-
+
+
diff --git a/Sprint-3/todo-list/script.mjs b/Sprint-3/todo-list/script.mjs
index ba0b2ceae..e07dc6858 100644
--- a/Sprint-3/todo-list/script.mjs
+++ b/Sprint-3/todo-list/script.mjs
@@ -7,6 +7,7 @@ const todos = [];
// Set up tasks to be performed once on page load
window.addEventListener("load", () => {
document.getElementById("add-task-btn").addEventListener("click", addNewTodo);
+ document.getElementById("delete-completed-btn").addEventListener("click", deleteCompleted);
// Populate sample data
Todos.addTask(todos, "Wash the dishes", false);
@@ -16,17 +17,23 @@ window.addEventListener("load", () => {
});
+function deleteCompleted() {
+ Todos.deleteCompleted(todos);
+ render();
+}
+
// A callback that reads the task description from an input field and
// append a new task to the todo list.
function addNewTodo() {
const taskInput = document.getElementById("new-task-input");
+ const deadlineInput = document.getElementById("deadline-input");
const task = taskInput.value.trim();
if (task) {
- Todos.addTask(todos, task, false);
+ Todos.addTask(todos, task, false, deadlineInput.value || null);
render();
}
-
taskInput.value = "";
+ deadlineInput.value = "";
}
// Note:
@@ -58,6 +65,22 @@ function createListItem(todo, index) {
const li = todoListItemTemplate.cloneNode(true); // true => Do a deep copy of the node
li.querySelector(".description").textContent = todo.task;
+
+ const deadlineEl = li.querySelector(".deadline");
+ if (todo.deadline) {
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ const due = new Date(todo.deadline + "T00:00:00");
+ const daysLeft = Math.round((due - today) / (1000 * 60 * 60 * 24));
+ if (daysLeft < 0) {
+ deadlineEl.textContent = `Overdue by ${Math.abs(daysLeft)} day(s)`;
+ deadlineEl.classList.add("overdue");
+ } else if (daysLeft === 0) {
+ deadlineEl.textContent = "Due today";
+ } else {
+ deadlineEl.textContent = `${daysLeft} day(s) left`;
+ }
+ }
if (todo.completed) {
li.classList.add("completed");
}
diff --git a/Sprint-3/todo-list/style.css b/Sprint-3/todo-list/style.css
index 535e91227..7435dff94 100644
--- a/Sprint-3/todo-list/style.css
+++ b/Sprint-3/todo-list/style.css
@@ -93,11 +93,11 @@ h1 {
height: 32px;
}
-.complete-btn i {
+.complete-btn span {
color: green;
}
-.delete-btn i {
+.delete-btn span {
color: red;
}
@@ -105,3 +105,31 @@ h1 {
text-decoration: line-through;
color: gray;
}
+
+#delete-completed-btn {
+ display: block;
+ width: 100%;
+ margin-top: 16px;
+ padding: 10px;
+ font-size: 16px;
+ background-color: #e53935;
+ color: white;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+}
+
+#delete-completed-btn:hover {
+ background-color: #c62828;
+}
+
+.deadline {
+ font-size: 12px;
+ color: #888;
+ margin-right: 10px;
+ white-space: nowrap;
+}
+
+.deadline.overdue {
+ color: #e53935;
+}
diff --git a/Sprint-3/todo-list/todos.mjs b/Sprint-3/todo-list/todos.mjs
index f17ab6a25..343cfa913 100644
--- a/Sprint-3/todo-list/todos.mjs
+++ b/Sprint-3/todo-list/todos.mjs
@@ -10,8 +10,8 @@
*/
// 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 = null) {
+ todos.push({ task, completed, deadline });
}
// Delete todos[taskIndex] if it exists
@@ -26,4 +26,11 @@ export function toggleCompletedOnTask(todos, taskIndex) {
if (todos[taskIndex]) {
todos[taskIndex].completed = !todos[taskIndex].completed;
}
+}
+
+// Remove all completed tasks from todos[]
+export function deleteCompleted(todos) {
+ for (let i = todos.length - 1; i >= 0; i--) {
+ if (todos[i].completed) todos.splice(i, 1);
+ }
}
\ No newline at end of file
diff --git a/Sprint-3/todo-list/todos.test.mjs b/Sprint-3/todo-list/todos.test.mjs
index bae7ae491..393e7007e 100644
--- a/Sprint-3/todo-list/todos.test.mjs
+++ b/Sprint-3/todo-list/todos.test.mjs
@@ -10,15 +10,15 @@ import * as Todos from "./todos.mjs";
// Return a mock ToDo List data with exactly 4 elements.
function createMockTodos() {
return [
- { task: "Task 1 description", completed: true },
- { task: "Task 2 description", completed: false },
- { task: "Task 3 description", completed: true },
- { task: "Task 4 description", completed: false },
+ { task: "Task 1 description", completed: true, deadline: null },
+ { task: "Task 2 description", completed: false, deadline: null },
+ { task: "Task 3 description", completed: true, deadline: null },
+ { task: "Task 4 description", completed: false, deadline: null },
];
}
// A mock task to simulate user input
-const theTask = { task: "The Task", completed: false };
+const theTask = { task: "The Task", completed: false, deadline: null };
describe("addTask()", () => {
test("Add a task to an empty ToDo list", () => {
@@ -29,16 +29,24 @@ describe("addTask()", () => {
});
test("Should append a new task to the end of a ToDo list", () => {
-
const todos = createMockTodos();
const lengthBeforeAddition = todos.length;
Todos.addTask(todos, theTask.task, theTask.completed);
- // todos should now have one more task
expect(todos).toHaveLength(lengthBeforeAddition + 1);
-
- // New task should be appended to the todos
expect(todos[todos.length - 1]).toEqual(theTask);
});
+
+ test("Should store a deadline when provided", () => {
+ const todos = [];
+ Todos.addTask(todos, "Task with deadline", false, "2030-01-01");
+ expect(todos[0].deadline).toBe("2030-01-01");
+ });
+
+ test("Should default deadline to null when not provided", () => {
+ const todos = [];
+ Todos.addTask(todos, "Task without deadline", false);
+ expect(todos[0].deadline).toBeNull();
+ });
});
describe("deleteTask()", () => {
@@ -130,3 +138,24 @@ describe("toggleCompletedOnTask()", () => {
});
});
+describe("deleteCompleted()", () => {
+ test("Removes all completed tasks", () => {
+ const todos = createMockTodos(); // tasks 0 and 2 are completed
+ Todos.deleteCompleted(todos);
+ expect(todos).toHaveLength(2);
+ todos.forEach(t => expect(t.completed).toBe(false));
+ });
+
+ test("Does nothing when no tasks are completed", () => {
+ const todos = [{ task: "A", completed: false }, { task: "B", completed: false }];
+ Todos.deleteCompleted(todos);
+ expect(todos).toHaveLength(2);
+ });
+
+ test("Empties the list when all tasks are completed", () => {
+ const todos = [{ task: "A", completed: true }, { task: "B", completed: true }];
+ Todos.deleteCompleted(todos);
+ expect(todos).toHaveLength(0);
+ });
+});
+