init
This commit is contained in:
150
web/app.js
Normal file
150
web/app.js
Normal file
@@ -0,0 +1,150 @@
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user