Agregar funcionalidad de edición de título de tareas - #12
Agregar funcionalidad de edición de título de tareas#12saulCleverit with Claude wants to merge 1 commit into
Conversation
Agent-Logs-Url: https://github.com/CleveritDemo/copilot-agentic-workshop/sessions/85a6299d-6ca4-4e0d-a790-21012b03a46d Co-authored-by: saulCleverit <121240069+saulCleverit@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Este PR agrega la edición inline del título de las tareas en el frontend, incorporando UI/UX (botón de editar, modo edición, estilos) y validación básica para evitar títulos vacíos.
Changes:
- Se añadió un botón de edición por tarea y un modo de edición inline (input + acciones guardar/cancelar).
- Se implementó la actualización del título vía
PUT /api/tasks/:iddesde el frontend, con validación de “no vacío”. - Se agregaron estilos CSS para el input de edición y botones de guardar/cancelar.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| frontend/script.js | Agrega UI de edición inline y lógica para guardar/cancelar cambios del título usando PUT. |
| frontend/style.css | Incorpora estilos para input de edición y botones de guardar/cancelar. |
| function addTaskToDOM(task) { | ||
| const li = document.createElement('li'); | ||
| li.innerHTML = ` | ||
| <span class="${task.completed ? 'completed' : ''}">${task.title}</span> | ||
| <span class="${task.completed ? 'completed' : ''}" data-task-id="${task.id}">${task.title}</span> | ||
| <div> | ||
| <button onclick="editTask('${task.id}')" title="Edit task">✎</button> | ||
| <button onclick="toggleComplete('${task.id}', ${!task.completed})">✓</button> | ||
| <button onclick="deleteTask('${task.id}')">✕</button> | ||
| </div> |
There was a problem hiding this comment.
addTaskToDOM builds the task row via innerHTML and interpolates task.title directly. Since titles come from user input and are served back by the API, this allows HTML/script injection (XSS). Prefer constructing DOM nodes and setting the title via textContent (or escaping) instead of injecting untrusted content into innerHTML.
| await fetch(`${API_URL}/${id}`, { | ||
| method: 'PUT', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ title: newTitle }) | ||
| }); |
There was a problem hiding this comment.
saveEdit does not check the HTTP response status. fetch only rejects on network errors, so non-2xx responses will still call fetchTasks() and make failures hard to notice. Capture the response, check res.ok (and optionally parse/display server error details) before refreshing the list.
| await fetch(`${API_URL}/${id}`, { | |
| method: 'PUT', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ title: newTitle }) | |
| }); | |
| const res = await fetch(`${API_URL}/${id}`, { | |
| method: 'PUT', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ title: newTitle }) | |
| }); | |
| if (!res.ok) { | |
| let errorMessage = 'Error al actualizar la tarea'; | |
| try { | |
| const data = await res.json(); | |
| if (data && (data.message || data.error)) { | |
| errorMessage = data.message || data.error; | |
| } | |
| } catch (parseError) { | |
| // Ignore JSON parse errors and use the default message | |
| } | |
| alert(errorMessage); | |
| return; | |
| } |
| input.addEventListener('keypress', (e) => { | ||
| if (e.key === 'Enter') { |
There was a problem hiding this comment.
The Enter key handler uses the keypress event, which is deprecated/inconsistent across browsers for non-printable keys. Use a keydown handler for Enter (and consider calling e.preventDefault() if needed) to make keyboard saving reliable.
| input.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') { | |
| input.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter') { | |
| e.preventDefault(); |
| } | ||
|
|
||
| function cancelEdit(id, originalTitle, wasCompleted) { | ||
| fetchTasks(); |
There was a problem hiding this comment.
cancelEdit accepts originalTitle and wasCompleted but does not use them, and always re-fetches the full task list. Either remove the unused parameters or restore the original DOM state locally to avoid an unnecessary network request on cancel.
| fetchTasks(); | |
| // Try to restore the original DOM state locally to avoid a full refetch | |
| const editedElement = document.querySelector(`[data-task-id="${id}"]`); | |
| // If we cannot find the element for some reason, fall back to refetching | |
| if (!editedElement) { | |
| fetchTasks(); | |
| return; | |
| } | |
| const li = editedElement.closest('li'); | |
| if (!li) { | |
| fetchTasks(); | |
| return; | |
| } | |
| // Restore the original markup, mirroring addTaskToDOM | |
| li.innerHTML = ` | |
| <span class="${wasCompleted ? 'completed' : ''}" data-task-id="${id}">${originalTitle}</span> | |
| <div> | |
| <button onclick="editTask('${id}')" title="Edit task">✎</button> | |
| <button onclick="toggleComplete('${id}', ${!wasCompleted})">✓</button> | |
| <button onclick="deleteTask('${id}')">✕</button> | |
| </div> | |
| `; |
| <button onclick="editTask('${task.id}')" title="Edit task">✎</button> | ||
| <button onclick="toggleComplete('${task.id}', ${!task.completed})">✓</button> | ||
| <button onclick="deleteTask('${task.id}')">✕</button> |
There was a problem hiding this comment.
The new edit button is icon-only (✎) and relies on title. For accessibility/screen readers, add an explicit accessible name (e.g., aria-label="Edit task") and consider doing the same for the other icon-only action buttons for consistency.
| <button onclick="editTask('${task.id}')" title="Edit task">✎</button> | |
| <button onclick="toggleComplete('${task.id}', ${!task.completed})">✓</button> | |
| <button onclick="deleteTask('${task.id}')">✕</button> | |
| <button onclick="editTask('${task.id}')" title="Edit task" aria-label="Edit task">✎</button> | |
| <button onclick="toggleComplete('${task.id}', ${!task.completed})" aria-label="${task.completed ? 'Mark task as incomplete' : 'Mark task as complete'}">✓</button> | |
| <button onclick="deleteTask('${task.id}')" aria-label="Delete task">✕</button> |
| .save-btn { | ||
| background: #4caf50 !important; | ||
| color: white !important; | ||
| border-radius: 4px !important; | ||
| width: auto !important; | ||
| padding: 0 0.75em !important; | ||
| font-weight: 500; | ||
| } | ||
| .save-btn:hover { | ||
| background: #45a049 !important; | ||
| } | ||
| .cancel-btn { | ||
| background: #f44336 !important; | ||
| color: white !important; | ||
| border-radius: 4px !important; | ||
| width: auto !important; | ||
| padding: 0 0.75em !important; | ||
| font-weight: 500; | ||
| } | ||
| .cancel-btn:hover { | ||
| background: #da190b !important; |
There was a problem hiding this comment.
The save/cancel button styles rely heavily on !important to override the base li button rules. This makes future styling harder and can cause specificity battles. Prefer increasing selector specificity (e.g., li button.save-btn, li button.cancel-btn) or scoping styles so !important isn't needed.
| .save-btn { | |
| background: #4caf50 !important; | |
| color: white !important; | |
| border-radius: 4px !important; | |
| width: auto !important; | |
| padding: 0 0.75em !important; | |
| font-weight: 500; | |
| } | |
| .save-btn:hover { | |
| background: #45a049 !important; | |
| } | |
| .cancel-btn { | |
| background: #f44336 !important; | |
| color: white !important; | |
| border-radius: 4px !important; | |
| width: auto !important; | |
| padding: 0 0.75em !important; | |
| font-weight: 500; | |
| } | |
| .cancel-btn:hover { | |
| background: #da190b !important; | |
| li button.save-btn { | |
| background: #4caf50; | |
| color: white; | |
| border-radius: 4px; | |
| width: auto; | |
| padding: 0 0.75em; | |
| font-weight: 500; | |
| } | |
| li button.save-btn:hover { | |
| background: #45a049; | |
| } | |
| li button.cancel-btn { | |
| background: #f44336; | |
| color: white; | |
| border-radius: 4px; | |
| width: auto; | |
| padding: 0 0.75em; | |
| font-weight: 500; | |
| } | |
| li button.cancel-btn:hover { | |
| background: #da190b; |
Uh oh!
There was an error while loading. Please reload this page.