216 lines
5.1 KiB
HTML
216 lines
5.1 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Todo List</title>
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
background: #f0f2f5;
|
|
color: #1a1a2e;
|
|
display: flex;
|
|
justify-content: center;
|
|
padding: 40px 16px;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.app {
|
|
width: 100%;
|
|
max-width: 520px;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
margin-bottom: 24px;
|
|
text-align: center;
|
|
}
|
|
|
|
.input-row {
|
|
display: flex;
|
|
gap: 8px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.input-row input {
|
|
flex: 1;
|
|
padding: 12px 16px;
|
|
font-size: 1rem;
|
|
border: 2px solid #d1d5db;
|
|
border-radius: 8px;
|
|
outline: none;
|
|
transition: border-color 0.2s;
|
|
}
|
|
|
|
.input-row input:focus {
|
|
border-color: #6366f1;
|
|
}
|
|
|
|
.input-row button {
|
|
padding: 12px 20px;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
background: #6366f1;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.input-row button:hover { background: #4f46e5; }
|
|
|
|
.todo-list {
|
|
list-style: none;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.todo-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
background: white;
|
|
padding: 14px 16px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
|
|
transition: opacity 0.2s;
|
|
}
|
|
|
|
.todo-item.done .todo-text {
|
|
text-decoration: line-through;
|
|
color: #9ca3af;
|
|
}
|
|
|
|
.todo-item input[type="checkbox"] {
|
|
width: 20px;
|
|
height: 20px;
|
|
accent-color: #6366f1;
|
|
cursor: pointer;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.todo-text {
|
|
flex: 1;
|
|
font-size: 1rem;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.todo-date {
|
|
font-size: 0.75rem;
|
|
color: #9ca3af;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.todo-item .delete-btn {
|
|
background: none;
|
|
border: none;
|
|
color: #ef4444;
|
|
font-size: 1.2rem;
|
|
cursor: pointer;
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
transition: background 0.2s;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.todo-item .delete-btn:hover {
|
|
background: #fef2f2;
|
|
}
|
|
|
|
.empty {
|
|
text-align: center;
|
|
color: #9ca3af;
|
|
padding: 40px 0;
|
|
font-size: 1rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="app">
|
|
<h1>✅ Todo List</h1>
|
|
<form class="input-row" id="addForm">
|
|
<input type="text" id="todoInput" placeholder="What needs to be done?" autocomplete="off" required>
|
|
<button type="submit">Add</button>
|
|
</form>
|
|
<ul class="todo-list" id="todoList"></ul>
|
|
<p class="empty" id="emptyMsg" style="display:none">No todos yet — add one above!</p>
|
|
</div>
|
|
|
|
<script>
|
|
const API = '/api/todos';
|
|
const todoList = document.getElementById('todoList');
|
|
const addForm = document.getElementById('addForm');
|
|
const todoInput = document.getElementById('todoInput');
|
|
const emptyMsg = document.getElementById('emptyMsg');
|
|
|
|
async function loadTodos() {
|
|
const res = await fetch(API);
|
|
const todos = await res.json();
|
|
todoList.innerHTML = '';
|
|
if (todos.length === 0) {
|
|
emptyMsg.style.display = 'block';
|
|
} else {
|
|
emptyMsg.style.display = 'none';
|
|
todos.forEach(t => {
|
|
const li = document.createElement('li');
|
|
li.className = 'todo-item' + (t.done ? ' done' : '');
|
|
li.dataset.id = t.id;
|
|
|
|
const date = new Date(t.created_at).toLocaleDateString(undefined, {
|
|
month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'
|
|
});
|
|
|
|
li.innerHTML = `
|
|
<input type="checkbox" ${t.done ? 'checked' : ''} />
|
|
<span class="todo-text"></span>
|
|
<span class="todo-date">${date}</span>
|
|
<button class="delete-btn" title="Delete">✕</button>
|
|
`;
|
|
li.querySelector('.todo-text').textContent = t.text;
|
|
|
|
li.querySelector('input[type="checkbox"]').addEventListener('change', () => toggleTodo(t.id));
|
|
li.querySelector('.delete-btn').addEventListener('click', () => deleteTodo(t.id));
|
|
|
|
todoList.appendChild(li);
|
|
});
|
|
}
|
|
}
|
|
|
|
async function addTodo(text) {
|
|
await fetch(API, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ text }),
|
|
});
|
|
loadTodos();
|
|
}
|
|
|
|
async function toggleTodo(id) {
|
|
await fetch(`${API}/${id}/toggle`, { method: 'PUT' });
|
|
loadTodos();
|
|
}
|
|
|
|
async function deleteTodo(id) {
|
|
await fetch(`${API}/${id}`, { method: 'DELETE' });
|
|
loadTodos();
|
|
}
|
|
|
|
addForm.addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
const text = todoInput.value.trim();
|
|
if (text) {
|
|
addTodo(text);
|
|
todoInput.value = '';
|
|
todoInput.focus();
|
|
}
|
|
});
|
|
|
|
loadTodos();
|
|
</script>
|
|
</body>
|
|
</html>
|