Modal view when you edit a todo
This commit is contained in:
parent
ba2861cc73
commit
23a38d42c2
54
app.js
54
app.js
@ -1,8 +1,11 @@
|
|||||||
// --- Basic Todo Model ---
|
// --- Basic Todo Model ---
|
||||||
const STORAGE_KEY = 'todos-v1';
|
const STORAGE_KEY = 'todos-v1';
|
||||||
const CATEGORY_KEY = 'todo-categories';
|
const CATEGORY_KEY = 'todo-categories';
|
||||||
let todos = [];
|
window.todos = window.todos || [];
|
||||||
let categories = [];
|
window.categories = window.categories || [];
|
||||||
|
// For backward compatibility, assign to local variables as references
|
||||||
|
let todos = window.todos;
|
||||||
|
let categories = window.categories;
|
||||||
|
|
||||||
function loadTodos() {
|
function loadTodos() {
|
||||||
todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
|
todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
|
||||||
@ -217,15 +220,53 @@ function renderTodos() {
|
|||||||
th.addEventListener('click', () => sortTodos(th.dataset.col));
|
th.addEventListener('click', () => sortTodos(th.dataset.col));
|
||||||
});
|
});
|
||||||
// Add modal editing listeners
|
// Add modal editing listeners
|
||||||
document.querySelectorAll('.todos-table td[data-editable="true"]').forEach(td => {
|
// Attach popup modal to ID cell only
|
||||||
|
document.querySelectorAll('.todo-id-cell').forEach(td => {
|
||||||
|
console.log('[TodoApp] Attaching click handler to ID cell:', td);
|
||||||
td.addEventListener('click', function(e) {
|
td.addEventListener('click', function(e) {
|
||||||
|
const todoId = +td.dataset.id;
|
||||||
const rowIdx = +td.dataset.row;
|
const rowIdx = +td.dataset.row;
|
||||||
const key = td.dataset.key;
|
console.log('[TodoApp] ID cell clicked. todoId:', todoId, 'rowIdx:', rowIdx);
|
||||||
if (window.showEditTodoModal) {
|
console.log('[TodoApp] window.showEditTodoModal available:', typeof window.showEditTodoModal);
|
||||||
window.showEditTodoModal(rowIdx, key);
|
console.log('[TodoApp] window.showEditTodoModal value:', window.showEditTodoModal);
|
||||||
|
|
||||||
|
// Find the todo by ID instead of row index
|
||||||
|
const todoIndex = todos.findIndex(t => t.id === todoId);
|
||||||
|
console.log('[TodoApp] Found todo at index:', todoIndex);
|
||||||
|
|
||||||
|
if (todoIndex === -1) {
|
||||||
|
console.error('[TodoApp] Todo not found with ID:', todoId);
|
||||||
|
alert('Error: Todo not found. Please refresh the page.');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Force set the function if needed
|
||||||
|
if (typeof showEditTodoModal === 'function' && typeof window.showEditTodoModal !== 'function') {
|
||||||
|
console.log('[TodoApp] Setting window.showEditTodoModal from local showEditTodoModal');
|
||||||
|
window.showEditTodoModal = showEditTodoModal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window.showEditTodoModal === 'function') {
|
||||||
|
try {
|
||||||
|
console.log('[TodoApp] Calling window.showEditTodoModal with todoIndex:', todoIndex);
|
||||||
|
window.showEditTodoModal(todoIndex, 'id');
|
||||||
|
console.log('[TodoApp] Editing via popup modal.');
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[TodoApp] Popup modal error:', err);
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('[TodoApp] showEditTodoModal is not a function:', window.showEditTodoModal);
|
||||||
|
alert('Error: Edit modal function not available. Please refresh the page.');
|
||||||
|
}
|
||||||
|
e.stopPropagation();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// Add inline editing to all other editable cells
|
||||||
|
document.querySelectorAll('.todos-table td[data-editable="true"]').forEach(td => {
|
||||||
|
if (td.classList.contains('todo-id-cell')) return; // skip ID, handled above
|
||||||
|
td.addEventListener('click', handleCellEdit);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderEditableCell(todo, key, rowIdx) {
|
function renderEditableCell(todo, key, rowIdx) {
|
||||||
@ -243,6 +284,7 @@ function renderEditableCell(todo, key, rowIdx) {
|
|||||||
if (key === 'coms' && value) value = `<a href="${value}" target="_blank">Link</a>`;
|
if (key === 'coms' && value) value = `<a href="${value}" target="_blank">Link</a>`;
|
||||||
if (key === 'link' && value) value = `<a href="${value}" target="_blank">Link</a>`;
|
if (key === 'link' && value) value = `<a href="${value}" target="_blank">Link</a>`;
|
||||||
if (key === 'status') return `<td class="status-${todo.status || 'blank'}" data-editable="true" data-row="${rowIdx}" data-key="${key}">${todo.status || ''}</td>`;
|
if (key === 'status') return `<td class="status-${todo.status || 'blank'}" data-editable="true" data-row="${rowIdx}" data-key="${key}">${todo.status || ''}</td>`;
|
||||||
|
if (key === 'id') return `<td class="todo-id-cell" data-row="${rowIdx}" data-id="${todo.id}" data-key="id" style="cursor:pointer;user-select:none;color:#2563eb;font-weight:bold;" title="Edit this todo">${todo[key]}</td>`;
|
||||||
if (nonEditable.includes(key)) return `<td>${todo[key]}</td>`;
|
if (nonEditable.includes(key)) return `<td>${todo[key]}</td>`;
|
||||||
return `<td data-editable="true" data-row="${rowIdx}" data-key="${key}">${value}</td>`;
|
return `<td data-editable="true" data-row="${rowIdx}" data-key="${key}">${value}</td>`;
|
||||||
}
|
}
|
||||||
|
|||||||
204
modal.js
204
modal.js
@ -2,18 +2,103 @@
|
|||||||
|
|
||||||
// Create and show a modal with the Add/Edit Todo form, focusing the relevant field
|
// Create and show a modal with the Add/Edit Todo form, focusing the relevant field
|
||||||
function showEditTodoModal(rowIdx, key) {
|
function showEditTodoModal(rowIdx, key) {
|
||||||
// Find the todo
|
console.log('[Modal] Opening modal for rowIdx:', rowIdx, 'key:', key);
|
||||||
const todo = window.todos[rowIdx];
|
console.log('[Modal] Initial window.todos:', window.todos ? window.todos.length : 'null/undefined');
|
||||||
if (!todo) return;
|
console.log('[Modal] Initial todos:', todos ? todos.length : 'null/undefined');
|
||||||
|
|
||||||
// Build modal overlay
|
// 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');
|
let modal = document.createElement('div');
|
||||||
modal.className = 'modal';
|
modal.className = 'modal';
|
||||||
modal.style.zIndex = 2000;
|
modal.style.zIndex = 2000;
|
||||||
modal.innerHTML = `
|
modal.innerHTML = `
|
||||||
<div class="modal-content modal-edit-todo">
|
<div class="modal-content">
|
||||||
<button class="modal-close-btn" aria-label="Close">×</button>
|
<h3 id="edit-todo-modal-title">Edit Todo</h3>
|
||||||
<h2>Edit Todo</h2>
|
|
||||||
<form id="modal-todo-form">
|
<form id="modal-todo-form">
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<label for="modal-task">Task Description</label>
|
<label for="modal-task">Task Description</label>
|
||||||
@ -86,37 +171,122 @@ function showEditTodoModal(rowIdx, key) {
|
|||||||
<label for="modal-comments">Comments</label>
|
<label for="modal-comments">Comments</label>
|
||||||
<textarea id="modal-comments" name="comments">${todo.comments || ''}</textarea>
|
<textarea id="modal-comments" name="comments">${todo.comments || ''}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="margin-top: 14px; display: flex; gap: 12px;">
|
||||||
<button type="submit" class="add-todo-btn">Save Changes</button>
|
<button type="submit" class="add-todo-btn">Save Changes</button>
|
||||||
|
<button type="button" id="modal-close-btn" class="secondary">Cancel</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div id="modal-error-msg" style="color:#d33; margin-top:8px; display:none;"></div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Close modal on overlay click or close button
|
// 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() {
|
function closeModal() {
|
||||||
modal.remove();
|
modal.remove();
|
||||||
document.body.style.overflow = '';
|
document.body.style.overflow = '';
|
||||||
}
|
}
|
||||||
modal.querySelector('.modal-close-btn').onclick = closeModal;
|
|
||||||
modal.onclick = (e) => { if (e.target === modal) closeModal(); };
|
modal.onclick = (e) => { if (e.target === modal) closeModal(); };
|
||||||
|
modal.querySelector('#modal-close-btn').onclick = closeModal;
|
||||||
|
|
||||||
// Save changes
|
// Save changes
|
||||||
modal.querySelector('#modal-todo-form').onsubmit = function(evt) {
|
const modalForm = modal.querySelector('#modal-todo-form');
|
||||||
|
modalForm.onsubmit = function(evt) {
|
||||||
|
console.log('[Modal] Save button clicked');
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
|
||||||
|
// Get form data and create an updated todo object
|
||||||
const formData = new FormData(this);
|
const formData = new FormData(this);
|
||||||
const updated = {};
|
const updated = {};
|
||||||
for (let [k, v] of formData.entries()) updated[k] = v;
|
for (let [k, v] of formData.entries()) {
|
||||||
|
if (k !== 'id') updated[k] = v; // Never overwrite the ID
|
||||||
|
}
|
||||||
|
|
||||||
// Type conversions
|
// Type conversions
|
||||||
updated.urgent = parseInt(updated.urgent) || 1;
|
updated.urgent = parseInt(updated.urgent) || 1;
|
||||||
updated.importance = parseInt(updated.importance) || 1;
|
updated.importance = parseInt(updated.importance) || 1;
|
||||||
updated.prio = updated.urgent * updated.importance;
|
updated.prio = updated.urgent * updated.importance;
|
||||||
updated.timeEstimation = parseInt(updated.timeEstimation) || 0;
|
updated.timeEstimation = parseInt(updated.timeEstimation) || 0;
|
||||||
updated.actualTime = parseInt(updated.actualTime) || 0;
|
updated.actualTime = parseInt(updated.actualTime) || 0;
|
||||||
window.todos[rowIdx] = { ...window.todos[rowIdx], ...updated };
|
|
||||||
window.localStorage.setItem('todos-v1', JSON.stringify(window.todos));
|
// 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();
|
closeModal();
|
||||||
window.renderTodos && window.renderTodos();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Debug: log if handler is attached
|
||||||
|
console.log('[Modal] onsubmit handler attached to modal-todo-form');
|
||||||
|
|
||||||
// Insert and focus correct field
|
// Insert and focus correct field
|
||||||
document.body.appendChild(modal);
|
document.body.appendChild(modal);
|
||||||
document.body.style.overflow = 'hidden';
|
document.body.style.overflow = 'hidden';
|
||||||
@ -127,7 +297,11 @@ function showEditTodoModal(rowIdx, key) {
|
|||||||
}, 80);
|
}, 80);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach to window for use in app.js
|
// Immediately attach to window for use in app.js
|
||||||
|
window.showEditTodoModal = showEditTodoModal;
|
||||||
|
|
||||||
|
// Also attach on DOMContentLoaded for safety
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
window.showEditTodoModal = showEditTodoModal;
|
window.showEditTodoModal = showEditTodoModal;
|
||||||
|
console.log('[Modal] showEditTodoModal attached to window object');
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user