Todo/modal.js
2025-05-14 19:31:21 +02:00

308 lines
12 KiB
JavaScript

// Modal logic for editing a todo cell in a popup
// Create and show a modal with the Add/Edit Todo form, focusing the relevant field
function showEditTodoModal(rowIdx, key) {
console.log('[Modal] Opening modal for rowIdx:', rowIdx, 'key:', key);
console.log('[Modal] Initial window.todos:', window.todos ? window.todos.length : 'null/undefined');
console.log('[Modal] Initial todos:', todos ? todos.length : 'null/undefined');
// Ensure todos are loaded from localStorage if arrays are empty
if (!window.todos || window.todos.length === 0 || !todos || todos.length === 0) {
console.log('[Modal] Loading todos from localStorage at modal open');
const storedTodos = localStorage.getItem('todos-v1');
if (storedTodos) {
try {
window.todos = JSON.parse(storedTodos);
todos = window.todos; // Update local reference as well
console.log('[Modal] Successfully loaded todos from localStorage:', window.todos.length);
} catch (e) {
console.error('[Modal] Error parsing todos from localStorage:', e);
}
} else {
console.error('[Modal] No todos found in localStorage under key "todos-v1"');
}
}
// If called with an index, get the ID from that todo
let todoId = null;
if (typeof rowIdx === 'number' || !isNaN(Number(rowIdx))) {
// First look it up by index if number is within range
const numericIdx = Number(rowIdx);
if (window.todos && numericIdx >= 0 && numericIdx < window.todos.length) {
todoId = window.todos[numericIdx].id;
console.log('[Modal] Found todo ID by index in window.todos:', todoId);
}
// If index doesn't work, use the number directly as ID
else {
todoId = numericIdx;
console.log('[Modal] Using rowIdx directly as todoId:', todoId);
}
} else if (rowIdx && typeof rowIdx === 'object' && rowIdx.id) {
// Handle case where entire todo object is passed
todoId = rowIdx.id;
console.log('[Modal] Using object.id as todoId:', todoId);
} else if (rowIdx) {
// Any other non-null value, try to use directly
todoId = rowIdx;
console.log('[Modal] Using rowIdx as todoId:', todoId);
}
// Remove any existing modal
document.querySelectorAll('.modal').forEach(m => m.remove());
// Always find the todo by ID
let todo = null;
if (todoId !== null) {
console.log('[Modal] Looking for todo with ID:', todoId, 'Type:', typeof todoId);
// Debug all todos in the array to see what IDs we have
if (window.todos && window.todos.length > 0) {
window.todos.forEach((t, idx) => {
if (t) console.log(`[Modal] Todo ${idx}: ID=${t.id} (${typeof t.id})`);
});
}
// More flexible ID comparison logic
const findTodoById = (arr, id) => {
if (!arr || !Array.isArray(arr)) return null;
return arr.find(t => {
if (!t) return false;
const idStr = String(id).trim();
const tIdStr = String(t.id).trim();
const idNum = Number(id);
const tIdNum = Number(t.id);
return t.id === id || // Direct comparison
tIdStr === idStr || // String comparison
(!isNaN(idNum) && !isNaN(tIdNum) && idNum === tIdNum); // Number comparison
});
};
todo = findTodoById(window.todos, todoId);
if (todo) console.log('[Modal] Found todo by ID in window.todos:', todo);
if (!todo && todos !== window.todos) {
todo = findTodoById(todos, todoId);
if (todo) console.log('[Modal] Found todo by ID in local todos:', todo);
}
}
if (!todo && todoId !== null) {
console.error('[Modal] Todo not found for ID:', todoId);
alert('Error: Could not find the todo to edit. Please refresh the page and try again.');
return;
}
// Build modal overlay (same structure as password modal)
let modal = document.createElement('div');
modal.className = 'modal';
modal.style.zIndex = 2000;
modal.innerHTML = `
<div class="modal-content">
<h3 id="edit-todo-modal-title">Edit Todo</h3>
<form id="modal-todo-form">
<div class="form-section">
<label for="modal-task">Task Description</label>
<input type="text" id="modal-task" name="task" required value="${todo.task || ''}">
</div>
<div class="form-section grid-3">
<div>
<label for="modal-creation-date">Creation Date</label>
<input type="date" id="modal-creation-date" name="creationDate" value="${todo.creationDate || ''}">
</div>
<div>
<label for="modal-next-date">Next Planned Work Date</label>
<input type="date" id="modal-next-date" name="nextDate" value="${todo.nextDate || ''}">
</div>
<div>
<label for="modal-due-date">Due Date</label>
<input type="date" id="modal-due-date" name="dueDate" value="${todo.dueDate || ''}">
</div>
</div>
<div class="form-section grid-3">
<div>
<label for="modal-status">Status</label>
<select id="modal-status" name="status">
<option value="" ${!todo.status ? 'selected' : ''}>(Blank)</option>
<option value="Busy" ${todo.status==="Busy"?'selected':''}>Busy</option>
<option value="Done" ${todo.status==="Done"?'selected':''}>Done</option>
<option value="W4A" ${todo.status==="W4A"?'selected':''}>W4A</option>
</select>
</div>
<div>
<label for="modal-urgent">Urgency</label>
<input type="number" id="modal-urgent" name="urgent" min="1" max="7" value="${todo.urgent || ''}">
</div>
<div>
<label for="modal-importance">Importance (1-7)</label>
<input type="number" id="modal-importance" name="importance" min="1" max="7" value="${todo.importance || ''}">
</div>
</div>
<div class="form-section grid-2">
<div>
<label for="modal-time-estimation">Time Estimation (min)</label>
<input type="number" id="modal-time-estimation" name="timeEstimation" min="0" value="${todo.timeEstimation || ''}">
</div>
<div>
<label for="modal-actual-time">Actual Time Spent (min)</label>
<input type="number" id="modal-actual-time" name="actualTime" min="0" value="${todo.actualTime || ''}">
</div>
</div>
<div class="form-section grid-2">
<div>
<label for="modal-category">Category</label>
<input type="text" id="modal-category" name="category" value="${todo.category || ''}">
</div>
<div>
<label for="modal-linked-tasks">Link with other tasks (IDs, comma separated)</label>
<input type="text" id="modal-linked-tasks" name="linkedTasks" value="${todo.linkedTasks || ''}">
</div>
</div>
<div class="form-section grid-2">
<div>
<label for="modal-coms">Coms URL</label>
<input type="url" id="modal-coms" name="coms" value="${todo.coms || ''}">
</div>
<div>
<label for="modal-link">Link URL</label>
<input type="url" id="modal-link" name="link" value="${todo.link || ''}">
</div>
</div>
<div class="form-section">
<label for="modal-comments">Comments</label>
<textarea id="modal-comments" name="comments">${todo.comments || ''}</textarea>
</div>
<div style="margin-top: 14px; display: flex; gap: 12px;">
<button type="submit" class="add-todo-btn">Save Changes</button>
<button type="button" id="modal-close-btn" class="secondary">Cancel</button>
</div>
</form>
<div id="modal-error-msg" style="color:#d33; margin-top:8px; display:none;"></div>
</div>
`;
// Insert modal into the DOM before attaching event listeners
document.body.appendChild(modal);
document.body.style.overflow = 'hidden';
// Close modal on overlay click or close/cancel button
function closeModal() {
modal.remove();
document.body.style.overflow = '';
}
modal.onclick = (e) => { if (e.target === modal) closeModal(); };
modal.querySelector('#modal-close-btn').onclick = closeModal;
// Save changes
const modalForm = modal.querySelector('#modal-todo-form');
modalForm.onsubmit = function(evt) {
console.log('[Modal] Save button clicked');
evt.preventDefault();
// Get form data and create an updated todo object
const formData = new FormData(this);
const updated = {};
for (let [k, v] of formData.entries()) {
if (k !== 'id') updated[k] = v; // Never overwrite the ID
}
// Type conversions
updated.urgent = parseInt(updated.urgent) || 1;
updated.importance = parseInt(updated.importance) || 1;
updated.prio = updated.urgent * updated.importance;
updated.timeEstimation = parseInt(updated.timeEstimation) || 0;
updated.actualTime = parseInt(updated.actualTime) || 0;
// Get the original ID from the todo we found at modal open time
let todoId = todo.id;
console.log('[Modal] Updating todo with ID:', todoId);
// Create a new complete todo object with the updates
const updatedTodo = { ...todo, ...updated, id: todoId };
// DIRECT APPROACH: Simply replace the entire todos array
try {
// Get the todos directly from localStorage
let allTodos = [];
try {
const stored = localStorage.getItem('todos-v1');
if (stored) {
allTodos = JSON.parse(stored);
console.log('[Modal] Successfully loaded', allTodos.length, 'todos from localStorage');
}
} catch (err) {
console.error('[Modal] Error loading todos from localStorage:', err);
}
// If we couldn't load from localStorage, use window.todos
if (!allTodos || !allTodos.length) {
allTodos = Array.isArray(window.todos) ? [...window.todos] : [];
console.log('[Modal] Using window.todos instead, length:', allTodos.length);
}
// Find the todo by ID
let found = false;
for (let i = 0; i < allTodos.length; i++) {
if (allTodos[i] && String(allTodos[i].id) === String(todoId)) {
// Replace the todo with our updated version
allTodos[i] = updatedTodo;
found = true;
console.log('[Modal] Updated todo at index', i);
break;
}
}
if (!found) {
console.warn('[Modal] Todo with ID', todoId, 'not found in array, adding it');
allTodos.push(updatedTodo);
}
// Save the updated todos back to localStorage
localStorage.setItem('todos-v1', JSON.stringify(allTodos));
console.log('[Modal] Saved todos to localStorage');
// Update both global and local todos arrays
window.todos = allTodos;
todos = allTodos;
// Update the UI
if (typeof renderTodos === 'function') {
renderTodos();
console.log('[Modal] UI updated with new todos');
}
// Close the modal
closeModal();
return;
} catch (err) {
console.error('[Modal] Error saving todo:', err);
const errMsg = modal.querySelector('#modal-error-msg');
if (errMsg) {
errMsg.textContent = 'Error saving todo: ' + err.message;
errMsg.style.display = 'block';
}
}
closeModal();
};
// Debug: log if handler is attached
console.log('[Modal] onsubmit handler attached to modal-todo-form');
// Insert and focus correct field
document.body.appendChild(modal);
document.body.style.overflow = 'hidden';
setTimeout(() => {
let focusId = 'modal-' + key.replace(/([A-Z])/g, '-$1').toLowerCase();
let focusElem = modal.querySelector('#' + focusId);
if (focusElem) focusElem.focus();
}, 80);
}
// Immediately attach to window for use in app.js
window.showEditTodoModal = showEditTodoModal;
// Also attach on DOMContentLoaded for safety
document.addEventListener('DOMContentLoaded', () => {
window.showEditTodoModal = showEditTodoModal;
console.log('[Modal] showEditTodoModal attached to window object');
});