let globalBlur = false; let currentDateFilter = "all"; // 'all' | 'today' let currentStatusFilter = "all"; // 'all' | 'pending' | 'completed' | 'delayed' | 'pend_delay' let editingId = null; async function fetchTodos() { const res = await fetch("/api/todos"); return res.json(); } let searchTitle = ""; let searchDescription = ""; let searchBoth = ""; function autoResize(el) { el.style.height = "auto"; el.style.height = el.scrollHeight + "px"; } document.getElementById("todoText").addEventListener("input", (e) => { autoResize(e.target); }); document.getElementById("searchTitle").addEventListener("input", (e) => { searchTitle = e.target.value.toLowerCase(); renderTodos(); }); document.getElementById("searchDescription").addEventListener("input", (e) => { searchDescription = e.target.value.toLowerCase(); renderTodos(); }); document.getElementById("searchBoth").addEventListener("input", (e) => { searchBoth = e.target.value.toLowerCase(); renderTodos(); }); function toggleSidebar() { const sidebar = document.getElementById("sidebar"); sidebar.classList.toggle("collapsed"); const icon = document.getElementById("collapseIcon"); icon.textContent = sidebar.classList.contains("collapsed") ? "▶" : "◀"; } function toggleDarkMode() { document.body.classList.toggle("dark"); } // Sidebar "Today / All" buttons function filterTodos(type) { currentDateFilter = type; renderTodos(); } // Status dropdown function filterByStatus(status) { currentStatusFilter = status; renderTodos(); } async function toggleGlobalBlur() { globalBlur = !globalBlur; const todos = await fetchTodos(); // Wait for ALL updates to actually finish before re-rendering await Promise.all( todos.map((todo) => updateTodo(todo.id, { private: globalBlur }, { render: false }), ), ); renderTodos(); } async function deleteTodo(id) { await fetch(`/api/todos/${id}`, { method: "DELETE" }); renderTodos(); } async function updateTodo(id, data, { render = true } = {}) { await fetch(`/api/todos/${id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (render) await renderTodos(); } document.getElementById("todoForm").addEventListener("submit", async (e) => { e.preventDefault(); const today = new Date().toISOString().split("T")[0]; const newTodo = { title: document.getElementById("todoTitle").value || "", text: document.getElementById("todoText").value, date: today, status: "pending", private: globalBlur, }; await fetch("/api/todos", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(newTodo), }); e.target.reset(); document.getElementById("todoText").style.height = "auto"; // 👈 add this renderTodos(); document.getElementById("todoText").focus(); }); function startEdit(id) { editingId = id; renderTodos(); } function cancelEdit() { editingId = null; renderTodos(); } async function saveEdit(id) { const title = document.getElementById(`editTitle-${id}`).value; const text = document.getElementById(`editText-${id}`).value; editingId = null; await updateTodo(id, { title, text }); } // Prevent broken HTML if the todo text/title contains quotes, <, etc. function escapeHtml(str) { return str.replace(/&/g, "&").replace(//g, ">"); } function escapeAttr(str) { return escapeHtml(str).replace(/"/g, """); } function renderTodoCard(todo) { if (editingId === todo.id) { return `
`; } return `
${todo.title ? `
${todo.title}
` : ""}

${escapeHtml(todo.text || "")}

${todo.date} - ${todo.status}
`; } document.getElementById("todoText").addEventListener("keydown", (e) => { if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { e.preventDefault(); document.getElementById("todoForm").requestSubmit(); } }); async function renderTodos() { const todos = await fetchTodos(); const today = new Date().toISOString().split("T")[0]; let filtered = todos; if (currentDateFilter === "today") { filtered = filtered.filter((t) => t.date === today); } if (currentStatusFilter !== "all") { if (currentStatusFilter === "pend_delay") { filtered = filtered.filter((t) => t.status != "completed"); } else { filtered = filtered.filter((t) => t.status === currentStatusFilter); } } if (searchTitle) { filtered = filtered.filter((t) => (t.title || "").toLowerCase().includes(searchTitle), ); } if (searchDescription) { filtered = filtered.filter((t) => (t.text || "").toLowerCase().includes(searchDescription), ); } if (searchBoth) { filtered = filtered.filter( (t) => (t.title || "").toLowerCase().includes(searchBoth) || (t.text || "").toLowerCase().includes(searchBoth), ); } filtered = filtered.slice().reverse(); // newest first document.getElementById("todoList").innerHTML = filtered .map(renderTodoCard) .join(""); updateAnalytics(todos); } function updateAnalytics(todos) { const today = new Date().toISOString().split("T")[0]; const todayTodos = todos.filter((t) => t.date === today); const completedToday = todayTodos.filter( (t) => t.status === "completed", ).length; const pendingToday = todayTodos.filter((t) => t.status === "pending").length; const delayedToday = todayTodos.filter((t) => t.status === "delayed").length; const pend_delay = todayTodos.filter((t) => t.status != "completed").length; const allCompleted = todos.filter((t) => t.status === "completed").length; const allPending = todos.filter((t) => t.status === "pending").length; const allDelayed = todos.filter((t) => t.status === "delayed").length; const all_pend_delay = todos.filter((t) => t.status != "completed").length; document.getElementById("analytics").innerHTML = `

Today

Total Today: ${todayTodos.length}

Completed Today: ${completedToday}

pending+delayed Today: ${pend_delay}

Pending Today: ${pendingToday}

Delayed Today: ${delayedToday}


Overall

All Total: ${todos.length}

All Completed: ${allCompleted}

All pending+delayed: ${all_pend_delay}

All Pending: ${allPending}

All Delayed: ${allDelayed}

`; } renderTodos(); window.addEventListener("DOMContentLoaded", async () => { // ✅ add async document.getElementById("sidebar").classList.add("collapsed"); document.body.classList.add("dark"); document.getElementById("todoText").focus(); const todos = await fetchTodos(); globalBlur = todos.length > 0 && todos.every((t) => t.private); renderTodos(); // also make sure the UI actually reflects restored state });