feat: boton editar (lapiz) en cada tarea de una actividad

El backend ya soportaba renombrar una tarea (PUT /api/tasks/:id), solo
faltaba el boton en la UI. Edicion en linea: input + guardar (Enter o
icono) / cancelar (Escape o icono).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-09-14 09:29:49 -05:00
parent 9dad88e7e4
commit a9904074e5
5 changed files with 51 additions and 8 deletions
@@ -3,7 +3,7 @@ import { Pencil, Save, X, Plus, Trash2, Link2, Repeat } from 'lucide-react';
import { useUi } from '../../context/ui-context.jsx';
import { useAsync, useAction } from '../../hooks/useAsync.js';
import { updateActivity, addDependency, removeDependency } from '../../services/activities.js';
import { listTasks, createTask, setTaskDone, deleteTask } from '../../services/tasks.js';
import { listTasks, createTask, updateTask, setTaskDone, deleteTask } from '../../services/tasks.js';
import { promoteTaskToRecurring } from '../../services/recurringTasks.js';
import { listCatalog } from '../../services/catalogs.js';
import { listUsers } from '../../services/users.js';
@@ -39,6 +39,8 @@ export default function ActivityDetailPanel({ activity, projectId, siblingActivi
const [editing, setEditing] = useState(false);
const [form, setForm] = useState(() => toForm(activity));
const [newTask, setNewTask] = useState('');
const [editingTaskId, setEditingTaskId] = useState(null);
const [editingTaskName, setEditingTaskName] = useState('');
const [addingDep, setAddingDep] = useState('');
const [promoting, setPromoting] = useState(null);
const [promoteForm, setPromoteForm] = useState(null);
@@ -80,6 +82,26 @@ export default function ActivityDetailPanel({ activity, projectId, siblingActivi
await runAction(async () => { await deleteTask(task.id); await reloadTasks(); onChanged(); });
}
function startEditTask(task) {
setEditingTaskId(task.id);
setEditingTaskName(task.name);
}
function cancelEditTask() {
setEditingTaskId(null);
setEditingTaskName('');
}
async function saveEditTask() {
if (!editingTaskName.trim()) return;
await runAction(async () => {
await updateTask(editingTaskId, { name: editingTaskName.trim() });
cancelEditTask();
await reloadTasks();
onChanged();
});
}
function startPromote(task) {
setPromoting(task);
setPromoteForm({
@@ -208,10 +230,25 @@ export default function ActivityDetailPanel({ activity, projectId, siblingActivi
<div>
{(tasks || []).map((task) => (
<div key={task.id} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 0' }}>
<input type="checkbox" checked={!!task.is_done} onChange={() => toggleTask(task)} />
<span style={{ flex: 1, fontSize: '.8rem', textDecoration: task.is_done ? 'line-through' : 'none', opacity: task.is_done ? .6 : 1 }}>{task.name}</span>
<button className="icon-btn" title={t('recurring_task_promote')} onClick={() => startPromote(task)}><Repeat size={15} /></button>
<button className="icon-btn" onClick={() => removeTaskItem(task)}><Trash2 size={15} /></button>
{editingTaskId === task.id ? (
<>
<input
className="input" style={{ flex: 1 }} value={editingTaskName} autoFocus
onChange={(e) => setEditingTaskName(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter') saveEditTask(); if (e.key === 'Escape') cancelEditTask(); }}
/>
<button className="icon-btn" title={t('common_save')} onClick={saveEditTask}><Save size={15} /></button>
<button className="icon-btn" title={t('common_cancel')} onClick={cancelEditTask}><X size={15} /></button>
</>
) : (
<>
<input type="checkbox" checked={!!task.is_done} onChange={() => toggleTask(task)} />
<span style={{ flex: 1, fontSize: '.8rem', textDecoration: task.is_done ? 'line-through' : 'none', opacity: task.is_done ? .6 : 1 }}>{task.name}</span>
<button className="icon-btn" title={t('common_edit')} onClick={() => startEditTask(task)}><Pencil size={15} /></button>
<button className="icon-btn" title={t('recurring_task_promote')} onClick={() => startPromote(task)}><Repeat size={15} /></button>
<button className="icon-btn" onClick={() => removeTaskItem(task)}><Trash2 size={15} /></button>
</>
)}
</div>
))}
{(!tasks || tasks.length === 0) && <div className="text-muted" style={{ fontSize: '.78rem' }}>{t('common_empty')}</div>}