From 54506d376db02647b1418da3667ed7c23563d478 Mon Sep 17 00:00:00 2001 From: pavel Date: Fri, 6 Feb 2026 01:38:30 +0100 Subject: [PATCH] updated design --- src/main/resources/static/app.js | 91 ++++- src/main/resources/static/index.html | 564 +++++++++++++++++++++++---- 2 files changed, 571 insertions(+), 84 deletions(-) diff --git a/src/main/resources/static/app.js b/src/main/resources/static/app.js index f416823..293f51a 100644 --- a/src/main/resources/static/app.js +++ b/src/main/resources/static/app.js @@ -3,11 +3,25 @@ const API_BASE = '/api/tasks'; const taskInput = document.getElementById('taskInput'); const addBtn = document.getElementById('addBtn'); const taskList = document.getElementById('taskList'); +const totalCount = document.getElementById('totalCount'); +const pendingCount = document.getElementById('pendingCount'); +const doneCount = document.getElementById('doneCount'); + +function updateStats(tasks) { + const total = tasks.length; + const done = tasks.filter(t => t.completed).length; + const pending = total - done; + + totalCount.textContent = total; + pendingCount.textContent = pending; + doneCount.textContent = done; +} async function fetchTasks() { try { const response = await fetch(API_BASE); const tasks = await response.json(); + updateStats(tasks); renderTasks(tasks); } catch (error) { console.error('Error fetching tasks:', error); @@ -18,29 +32,64 @@ function renderTasks(tasks) { taskList.innerHTML = ''; if (tasks.length === 0) { - taskList.innerHTML = '
No tasks yet. Add one above!
'; + taskList.innerHTML = ` +
+ / + no tasks yet +
type above to add your first task
+
+ `; return; } - tasks.forEach(task => { + // Show pending tasks first, then completed + const sorted = [...tasks].sort((a, b) => { + if (a.completed !== b.completed) return a.completed ? 1 : -1; + return 0; + }); + + sorted.forEach((task, index) => { const li = document.createElement('li'); li.className = `task-item ${task.completed ? 'completed' : ''}`; + li.style.animationDelay = `${index * 0.04}s`; + + const indexSpan = document.createElement('span'); + indexSpan.className = 'task-index'; + indexSpan.textContent = String(index + 1).padStart(2, '0'); + + const checkboxWrapper = document.createElement('label'); + checkboxWrapper.className = 'checkbox-wrapper'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.checked = task.completed; checkbox.addEventListener('change', () => toggleTask(task)); - const span = document.createElement('span'); - span.textContent = task.name; + const checkboxCustom = document.createElement('span'); + checkboxCustom.className = 'checkbox-custom'; + checkboxCustom.innerHTML = ``; + + checkboxWrapper.appendChild(checkbox); + checkboxWrapper.appendChild(checkboxCustom); + + const nameSpan = document.createElement('span'); + nameSpan.className = 'task-name'; + nameSpan.textContent = task.name; const deleteBtn = document.createElement('button'); deleteBtn.className = 'delete-btn'; - deleteBtn.textContent = 'Delete'; - deleteBtn.addEventListener('click', () => deleteTask(task.id)); + deleteBtn.textContent = 'del'; + deleteBtn.addEventListener('click', (e) => { + e.stopPropagation(); + li.style.transition = 'all 0.3s ease'; + li.style.opacity = '0'; + li.style.transform = 'translateX(20px)'; + setTimeout(() => deleteTask(task.id), 250); + }); - li.appendChild(checkbox); - li.appendChild(span); + li.appendChild(indexSpan); + li.appendChild(checkboxWrapper); + li.appendChild(nameSpan); li.appendChild(deleteBtn); taskList.appendChild(li); }); @@ -48,7 +97,13 @@ function renderTasks(tasks) { async function addTask() { const name = taskInput.value.trim(); - if (!name) return; + if (!name) { + // Subtle shake on empty submit + taskInput.parentElement.style.animation = 'none'; + taskInput.parentElement.offsetHeight; // reflow + taskInput.parentElement.style.animation = 'shake 0.4s ease'; + return; + } try { const response = await fetch(API_BASE, { @@ -103,6 +158,19 @@ async function deleteTask(id) { } } +// Add shake animation dynamically +const styleEl = document.createElement('style'); +styleEl.textContent = ` + @keyframes shake { + 0%, 100% { transform: translateX(0); } + 20% { transform: translateX(-4px); } + 40% { transform: translateX(4px); } + 60% { transform: translateX(-3px); } + 80% { transform: translateX(2px); } + } +`; +document.head.appendChild(styleEl); + addBtn.addEventListener('click', addTask); taskInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { @@ -110,4 +178,9 @@ taskInput.addEventListener('keypress', (e) => { } }); +// Focus input on page load +window.addEventListener('load', () => { + taskInput.focus(); +}); + fetchTasks(); diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 4bee4ff..b268439 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -3,8 +3,28 @@ - Todo App + todo. + + + -
-

Todo List

-
- - +
+
+ +
+
+
task manager
+

todo.

+
+
+ 0 total + 0 pending + 0 done +
+
+ +
+
+
+
+ + +
+ +
    + +
    + press Enter to add +
    +