commited\

This commit is contained in:
2026-08-06 09:34:37 +05:30
commit 452bd8ad04
5 changed files with 1028 additions and 0 deletions
+250
View File
@@ -0,0 +1,250 @@
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function escapeAttr(str) {
return escapeHtml(str).replace(/"/g, "&quot;");
}
function renderTodoCard(todo) {
if (editingId === todo.id) {
return `
<div class="todo ${todo.status} editing">
<div class="todo-content">
<input type="text" id="editTitle-${todo.id}" value="${escapeAttr(todo.title || "")}" placeholder="Title">
<textarea id="editText-${todo.id}">${escapeHtml(todo.text || "")}</textarea>
</div>
<div class="todo-actions">
<button onclick="saveEdit(${todo.id})">Save</button>
<button onclick="cancelEdit()">Cancel</button>
</div>
</div>`;
}
return `
<div class="todo ${todo.status}">
<div class="todo-content ${todo.private ? "blur" : ""}">
${todo.title ? `<h5>${todo.title}</h5>` : ""}
<p class="todo-description">${escapeHtml(todo.text || "")}</p>
<small>${todo.date} - ${todo.status}</small> </div>
<div class="todo-actions">
<button onclick="updateTodo(${todo.id}, {status:'pending'})">Pending</button>
<button onclick="updateTodo(${todo.id}, {status:'completed'})">Done</button>
<button onclick="updateTodo(${todo.id}, {status:'delayed'})">Urgent</button>
<button onclick="startEdit(${todo.id})">Edit</button>
<button onclick="deleteTodo(${todo.id})">Delete</button>
</div>
</div>`;
}
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 = `
<p><strong>Today</strong></p>
<p>Total Today: ${todayTodos.length}</p>
<p>Completed Today: ${completedToday}</p>
<p>pending+delayed Today: ${pend_delay}</p>
<p>Pending Today: ${pendingToday}</p>
<p>Delayed Today: ${delayedToday}</p>
<hr>
<p><strong>Overall</strong></p>
<p>All Total: ${todos.length}</p>
<p>All Completed: ${allCompleted}</p>
<p>All pending+delayed: ${all_pend_delay}</p>
<p>All Pending: ${allPending}</p>
<p>All Delayed: ${allDelayed}</p>
`;
}
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
});