203 lines
6.8 KiB
JavaScript
203 lines
6.8 KiB
JavaScript
const api = {
|
|
async get(path) {
|
|
const r = await fetch(path); return r.json();
|
|
},
|
|
async post(path, body) {
|
|
const r = await fetch(path, { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(body) });
|
|
return r.status === 204 ? null : r.json();
|
|
},
|
|
async put(path, body) {
|
|
const r = await fetch(path, { method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify(body) });
|
|
return r.json();
|
|
},
|
|
async del(path) {
|
|
await fetch(path, { method: 'DELETE' });
|
|
}
|
|
};
|
|
|
|
let memos = [];
|
|
let currentTab = 'pending';
|
|
|
|
function fmt(time) { return time ? time.slice(0,5) : ''; }
|
|
function fmtDate(dt) { return dt ? dt.slice(0,10) : ''; }
|
|
|
|
function renderMemo(m) {
|
|
const isDone = !!m.is_done;
|
|
const div = document.createElement('div');
|
|
div.className = 'memo-item' + (isDone ? ' done' : '');
|
|
div.dataset.id = m.id;
|
|
|
|
const check = document.createElement('button');
|
|
check.className = 'memo-check' + (isDone ? ' checked' : '');
|
|
check.title = isDone ? 'Rouvrir' : 'Marquer comme fait';
|
|
check.innerHTML = isDone ? '✓' : '';
|
|
check.onclick = () => toggleDone(m.id, isDone);
|
|
|
|
const body = document.createElement('div');
|
|
body.className = 'memo-body';
|
|
|
|
const title = document.createElement('div');
|
|
title.className = 'memo-title' + (isDone ? ' done-text' : '');
|
|
title.textContent = m.title;
|
|
|
|
body.appendChild(title);
|
|
|
|
if (m.description) {
|
|
const desc = document.createElement('div');
|
|
desc.className = 'memo-desc';
|
|
desc.textContent = m.description;
|
|
body.appendChild(desc);
|
|
}
|
|
|
|
const meta = document.createElement('div');
|
|
meta.className = 'memo-meta';
|
|
if (!isDone) {
|
|
meta.innerHTML = `<span>🔔 ${fmt(m.reminder_time)}</span><span>📅 ${fmtDate(m.created_at)}</span>`;
|
|
} else {
|
|
meta.innerHTML = `<span>✅ Fait le ${fmtDate(m.done_at)}</span>`;
|
|
}
|
|
body.appendChild(meta);
|
|
|
|
const actions = document.createElement('div');
|
|
actions.className = 'memo-actions';
|
|
const editBtn = document.createElement('button');
|
|
editBtn.className = 'btn-icon';
|
|
editBtn.title = 'Modifier';
|
|
editBtn.textContent = '✏️';
|
|
editBtn.onclick = () => openEdit(m);
|
|
actions.appendChild(editBtn);
|
|
|
|
div.appendChild(check);
|
|
div.appendChild(body);
|
|
div.appendChild(actions);
|
|
return div;
|
|
}
|
|
|
|
function render() {
|
|
const pending = memos.filter(m => !m.is_done);
|
|
const done = memos.filter(m => m.is_done);
|
|
|
|
const badge = document.getElementById('badge-pending');
|
|
badge.textContent = pending.length > 0 ? pending.length : '';
|
|
|
|
const listPending = document.getElementById('list-pending');
|
|
const listDone = document.getElementById('list-done');
|
|
listPending.innerHTML = '';
|
|
listDone.innerHTML = '';
|
|
|
|
if (pending.length === 0) {
|
|
listPending.innerHTML = '<div class="empty-state">Aucun mémo en cours 🎉</div>';
|
|
} else {
|
|
pending.forEach(m => listPending.appendChild(renderMemo(m)));
|
|
}
|
|
|
|
if (done.length === 0) {
|
|
listDone.innerHTML = '<div class="empty-state">Aucun mémo terminé</div>';
|
|
} else {
|
|
done.forEach(m => listDone.appendChild(renderMemo(m)));
|
|
}
|
|
}
|
|
|
|
async function load() {
|
|
memos = await api.get('/api/memos');
|
|
render();
|
|
}
|
|
|
|
async function toggleDone(id, isDone) {
|
|
const path = isDone ? `/api/memos/${id}/undone` : `/api/memos/${id}/done`;
|
|
const updated = await api.post(path, {});
|
|
const idx = memos.findIndex(m => m.id === id);
|
|
if (idx !== -1) memos[idx] = updated;
|
|
render();
|
|
}
|
|
|
|
// Add form
|
|
document.getElementById('btn-add').onclick = async () => {
|
|
const title = document.getElementById('input-title').value.trim();
|
|
if (!title) { document.getElementById('input-title').focus(); return; }
|
|
const desc = document.getElementById('input-desc').value.trim();
|
|
const time = document.getElementById('input-time').value || '09:00';
|
|
const memo = await api.post('/api/memos', { title, description: desc, reminder_time: time });
|
|
memos.unshift(memo);
|
|
document.getElementById('input-title').value = '';
|
|
document.getElementById('input-desc').value = '';
|
|
render();
|
|
// Switch to pending tab
|
|
setTab('pending');
|
|
};
|
|
|
|
document.getElementById('input-title').addEventListener('keydown', e => {
|
|
if (e.key === 'Enter') document.getElementById('btn-add').click();
|
|
});
|
|
|
|
// Tabs
|
|
function setTab(tab) {
|
|
currentTab = tab;
|
|
document.querySelectorAll('.tab').forEach(t => t.classList.toggle('active', t.dataset.tab === tab));
|
|
document.getElementById('list-pending').classList.toggle('hidden', tab !== 'pending');
|
|
document.getElementById('list-done').classList.toggle('hidden', tab !== 'done');
|
|
}
|
|
|
|
document.querySelectorAll('.tab').forEach(t => {
|
|
t.onclick = () => setTab(t.dataset.tab);
|
|
});
|
|
|
|
// Settings modal
|
|
document.getElementById('btn-settings').onclick = async () => {
|
|
const settings = await api.get('/api/settings');
|
|
document.getElementById('input-webhook').value = settings.discord_webhook || '';
|
|
document.getElementById('modal-settings').classList.remove('hidden');
|
|
};
|
|
document.getElementById('btn-settings-cancel').onclick = () => {
|
|
document.getElementById('modal-settings').classList.add('hidden');
|
|
};
|
|
document.getElementById('btn-settings-save').onclick = async () => {
|
|
const webhook = document.getElementById('input-webhook').value.trim();
|
|
await api.post('/api/settings', { discord_webhook: webhook });
|
|
document.getElementById('modal-settings').classList.add('hidden');
|
|
};
|
|
|
|
// Edit modal
|
|
function openEdit(m) {
|
|
document.getElementById('edit-id').value = m.id;
|
|
document.getElementById('edit-title').value = m.title;
|
|
document.getElementById('edit-desc').value = m.description || '';
|
|
document.getElementById('edit-time').value = fmt(m.reminder_time);
|
|
document.getElementById('modal-edit').classList.remove('hidden');
|
|
}
|
|
|
|
document.getElementById('btn-edit-cancel').onclick = () => {
|
|
document.getElementById('modal-edit').classList.add('hidden');
|
|
};
|
|
|
|
document.getElementById('btn-edit-save').onclick = async () => {
|
|
const id = parseInt(document.getElementById('edit-id').value);
|
|
const updated = await api.put(`/api/memos/${id}`, {
|
|
title: document.getElementById('edit-title').value.trim(),
|
|
description: document.getElementById('edit-desc').value.trim(),
|
|
reminder_time: document.getElementById('edit-time').value
|
|
});
|
|
const idx = memos.findIndex(m => m.id === id);
|
|
if (idx !== -1) memos[idx] = updated;
|
|
document.getElementById('modal-edit').classList.add('hidden');
|
|
render();
|
|
};
|
|
|
|
document.getElementById('btn-edit-delete').onclick = async () => {
|
|
const id = parseInt(document.getElementById('edit-id').value);
|
|
if (!confirm('Supprimer ce mémo ?')) return;
|
|
await api.del(`/api/memos/${id}`);
|
|
memos = memos.filter(m => m.id !== id);
|
|
document.getElementById('modal-edit').classList.add('hidden');
|
|
render();
|
|
};
|
|
|
|
// Close modals on backdrop click
|
|
document.querySelectorAll('.modal').forEach(modal => {
|
|
modal.addEventListener('click', e => {
|
|
if (e.target === modal) modal.classList.add('hidden');
|
|
});
|
|
});
|
|
|
|
load();
|