Task Board
+Track tasks with filters and completion status.
+Total: 0 | Active: 0 | Completed: 0
+diff --git a/scripts/verify-task-board.sh b/scripts/verify-task-board.sh new file mode 100755 index 0000000..d13c908 --- /dev/null +++ b/scripts/verify-task-board.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +required_files=( + "task-board/index.html" + "task-board/styles.css" + "task-board/app.js" + "task-board/README.md" +) + +for file in "${required_files[@]}"; do + if [[ ! -f "$file" ]]; then + echo "FAIL: Missing required file: $file" + exit 1 + fi +done + +required_tokens=( + "addTask" + "toggleTaskComplete" + "deleteTask" + "setFilter" + "localStorage" +) + +for token in "${required_tokens[@]}"; do + if ! rg -q "$token" task-board/app.js; then + echo "FAIL: task-board/app.js missing token: $token" + exit 1 + fi +done + +echo "PASS: task-board verification succeeded" diff --git a/task-board/README.md b/task-board/README.md new file mode 100644 index 0000000..93b45e2 --- /dev/null +++ b/task-board/README.md @@ -0,0 +1,25 @@ +# Task Board + +A small vanilla JavaScript task board app. + +## Features + +- Add tasks with a single input and Add button +- Render tasks in a list +- Toggle task complete/incomplete +- Delete tasks +- Filter tasks by All, Active, and Completed +- Summary counts for total, active, and completed tasks +- Persist tasks in `localStorage` under the key `taskBoardItems` + +## Run locally + +1. Open `task-board/index.html` directly in a browser. +2. Or serve the directory with any static server, for example: + +```bash +cd task-board +python3 -m http.server 8080 +``` + +Then open `http://localhost:8080`. diff --git a/task-board/app.js b/task-board/app.js new file mode 100644 index 0000000..255f367 --- /dev/null +++ b/task-board/app.js @@ -0,0 +1,179 @@ +const STORAGE_KEY = "taskBoardItems"; + +const state = { + tasks: [], + filter: "all", +}; + +const elements = { + form: document.getElementById("task-form"), + input: document.getElementById("task-input"), + list: document.getElementById("task-list"), + summary: document.getElementById("summary"), + filters: document.getElementById("filters"), +}; + +function loadTasks() { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) { + return []; + } + + try { + const parsed = JSON.parse(raw); + return Array.isArray(parsed) ? parsed : []; + } catch { + return []; + } +} + +function saveTasks() { + localStorage.setItem(STORAGE_KEY, JSON.stringify(state.tasks)); +} + +function addTask(title) { + const cleanTitle = title.trim(); + if (!cleanTitle) { + return; + } + + state.tasks.push({ + id: crypto.randomUUID(), + title: cleanTitle, + completed: false, + }); + + saveTasks(); + render(); +} + +function toggleTaskComplete(taskId) { + const task = state.tasks.find((item) => item.id === taskId); + if (!task) { + return; + } + + task.completed = !task.completed; + saveTasks(); + render(); +} + +function deleteTask(taskId) { + state.tasks = state.tasks.filter((task) => task.id !== taskId); + saveTasks(); + render(); +} + +function setFilter(filterName) { + state.filter = filterName; + + Array.from(elements.filters.querySelectorAll("button")).forEach((button) => { + const isActive = button.dataset.filter === filterName; + button.classList.toggle("is-active", isActive); + button.setAttribute("aria-pressed", String(isActive)); + }); + + render(); +} + +function getFilteredTasks() { + if (state.filter === "active") { + return state.tasks.filter((task) => !task.completed); + } + + if (state.filter === "completed") { + return state.tasks.filter((task) => task.completed); + } + + return state.tasks; +} + +function renderSummary() { + const total = state.tasks.length; + const completed = state.tasks.filter((task) => task.completed).length; + const active = total - completed; + + elements.summary.textContent = `Total: ${total} | Active: ${active} | Completed: ${completed}`; +} + +function createTaskListItem(task) { + const item = document.createElement("li"); + item.className = "task-item"; + + const main = document.createElement("div"); + main.className = "task-main"; + + const checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.checked = task.completed; + checkbox.setAttribute("aria-label", `Toggle completion for ${task.title}`); + checkbox.addEventListener("change", () => toggleTaskComplete(task.id)); + + const title = document.createElement("span"); + title.className = "task-title"; + if (task.completed) { + title.classList.add("is-completed"); + } + title.textContent = task.title; + + const deleteButton = document.createElement("button"); + deleteButton.type = "button"; + deleteButton.className = "delete-btn"; + deleteButton.textContent = "Delete"; + deleteButton.addEventListener("click", () => deleteTask(task.id)); + + main.appendChild(checkbox); + main.appendChild(title); + item.appendChild(main); + item.appendChild(deleteButton); + + return item; +} + +function renderTaskList() { + const visibleTasks = getFilteredTasks(); + elements.list.innerHTML = ""; + + if (visibleTasks.length === 0) { + const empty = document.createElement("li"); + empty.className = "empty-state"; + empty.textContent = "No tasks to show for this filter."; + elements.list.appendChild(empty); + return; + } + + visibleTasks.forEach((task) => { + elements.list.appendChild(createTaskListItem(task)); + }); +} + +function render() { + renderTaskList(); + renderSummary(); +} + +function attachEventHandlers() { + elements.form.addEventListener("submit", (event) => { + event.preventDefault(); + addTask(elements.input.value); + elements.input.value = ""; + elements.input.focus(); + }); + + elements.filters.addEventListener("click", (event) => { + const button = event.target.closest("button[data-filter]"); + if (!button) { + return; + } + + setFilter(button.dataset.filter); + }); +} + +function initializeApp() { + state.tasks = loadTasks(); + attachEventHandlers(); + render(); +} + +initializeApp(); diff --git a/task-board/index.html b/task-board/index.html new file mode 100644 index 0000000..c477e8e --- /dev/null +++ b/task-board/index.html @@ -0,0 +1,50 @@ + + +
+ + +Track tasks with filters and completion status.
+Total: 0 | Active: 0 | Completed: 0
+