Files
todo-vibe-coding/web/app.js
2026-01-22 22:42:10 +08:00

151 lines
4.1 KiB
JavaScript

const API_BASE = 'http://localhost:8080/api/v1';
let token = '';
const sessionState = document.getElementById('sessionState');
const loginBtn = document.getElementById('loginBtn');
const createForm = document.getElementById('createForm');
const taskList = document.getElementById('taskList');
const refreshBtn = document.getElementById('refreshBtn');
const clearBtn = document.getElementById('clearBtn');
const template = document.getElementById('taskTemplate');
function setSessionState(text) {
sessionState.textContent = text;
}
function getHeaders() {
return {
'Content-Type': 'application/json',
Authorization: token ? `Bearer ${token}` : '',
};
}
async function login() {
const response = await fetch(`${API_BASE}/auth/login`, {
method: 'POST',
});
if (!response.ok) {
setSessionState('Login failed');
return;
}
const data = await response.json();
token = data.token || 'demo-token';
setSessionState('Connected');
await loadTasks();
}
function buildMeta(task) {
const parts = [];
if (task.status) parts.push(task.status.toUpperCase());
if (task.due_at) parts.push(`Due: ${task.due_at}`);
if (task.priority) parts.push(`P${task.priority}`);
if (task.tags && task.tags.length > 0) parts.push(task.tags.join(', '));
return parts.join(' • ');
}
function renderTasks(tasks) {
taskList.innerHTML = '';
if (!tasks || tasks.length === 0) {
taskList.innerHTML = '<div class="empty">No tasks yet.</div>';
return;
}
tasks.forEach((task) => {
const node = template.content.cloneNode(true);
node.querySelector('h3').textContent = task.title || 'Untitled task';
node.querySelector('.meta').textContent = buildMeta(task);
node.querySelector('.desc').textContent = task.description || '';
node.querySelector('.badge').textContent = task.status || 'todo';
node.querySelector('.toggle').addEventListener('click', () => toggleStatus(task));
node.querySelector('.delete').addEventListener('click', () => deleteTask(task));
taskList.appendChild(node);
});
}
async function loadTasks() {
const response = await fetch(`${API_BASE}/tasks`, {
headers: getHeaders(),
});
if (!response.ok) {
setSessionState('Auth required');
renderTasks([]);
return;
}
const data = await response.json();
renderTasks(data);
}
async function createTask(event) {
event.preventDefault();
const form = new FormData(createForm);
const payload = {
title: form.get('title'),
description: form.get('description'),
due_at: toISO(form.get('due_at')),
priority: Number(form.get('priority') || 0),
tags: String(form.get('tags') || '')
.split(',')
.map((tag) => tag.trim())
.filter(Boolean),
};
const response = await fetch(`${API_BASE}/tasks`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify(payload),
});
if (!response.ok) {
setSessionState('Failed to create task');
return;
}
createForm.reset();
await loadTasks();
}
async function toggleStatus(task) {
const nextStatus = task.status === 'done' ? 'todo' : 'done';
await fetch(`${API_BASE}/tasks/${task.id}`, {
method: 'PUT',
headers: getHeaders(),
body: JSON.stringify({ status: nextStatus }),
});
await loadTasks();
}
async function deleteTask(task) {
await fetch(`${API_BASE}/tasks/${task.id}`, {
method: 'DELETE',
headers: getHeaders(),
});
await loadTasks();
}
async function clearCompleted() {
const response = await fetch(`${API_BASE}/api/tasks`, {
headers: getHeaders(),
});
if (!response.ok) {
return;
}
const tasks = await response.json();
const completed = tasks.filter((task) => task.status === 'done');
for (const task of completed) {
await deleteTask(task);
}
}
loginBtn.addEventListener('click', login);
createForm.addEventListener('submit', createTask);
refreshBtn.addEventListener('click', loadTasks);
clearBtn.addEventListener('click', clearCompleted);
setSessionState('Not connected');
function toISO(value) {
if (!value) return '';
const date = new Date(value);
if (Number.isNaN(date.getTime())) return '';
return date.toISOString();
}