diff --git a/src/features/notes/components/note-dialog.tsx b/src/features/notes/components/note-dialog.tsx index ba9133e..a6b0a7e 100644 --- a/src/features/notes/components/note-dialog.tsx +++ b/src/features/notes/components/note-dialog.tsx @@ -1,6 +1,10 @@ "use client"; -import { RiAddCircleFill, RiDeleteBinLine } from "@remixicon/react"; +import { + RiAddCircleFill, + RiCheckLine, + RiDeleteBinLine, +} from "@remixicon/react"; import { type ReactNode, useEffect, @@ -69,10 +73,13 @@ export function NoteDialog({ const [isPending, startTransition] = useTransition(); const [errorMessage, setErrorMessage] = useState(null); const [newTaskText, setNewTaskText] = useState(""); + const [editingTaskId, setEditingTaskId] = useState(null); + const [editingTaskText, setEditingTaskText] = useState(""); const titleRef = useRef(null); const descRef = useRef(null); const newTaskRef = useRef(null); + const editingTaskRef = useRef(null); const [dialogOpen, setDialogOpen] = useControlledState( open, @@ -90,6 +97,8 @@ export function NoteDialog({ resetForm(buildInitialValues(note)); setErrorMessage(null); setNewTaskText(""); + setEditingTaskId(null); + setEditingTaskText(""); requestAnimationFrame(() => titleRef.current?.focus()); } }, [dialogOpen, note, resetForm]); @@ -126,7 +135,12 @@ export function NoteDialog({ formState.description.trim() === (note?.description ?? "").trim() && JSON.stringify(formState.tasks) === JSON.stringify(note?.tasks); - const disableSubmit = isPending || onlySpaces || unchanged || invalidLen; + const disableSubmit = + isPending || + onlySpaces || + unchanged || + invalidLen || + Boolean(editingTaskId); const handleOpenChange = (v: boolean) => { setDialogOpen(v); @@ -159,6 +173,10 @@ export function NoteDialog({ "tasks", (formState.tasks || []).filter((t) => t.id !== taskId), ); + if (editingTaskId === taskId) { + setEditingTaskId(null); + setEditingTaskText(""); + } }; const handleToggleTask = (taskId: string) => { @@ -170,6 +188,40 @@ export function NoteDialog({ ); }; + const handleStartEditTask = (task: Task) => { + if (isPending) return; + + setEditingTaskId(task.id); + setEditingTaskText(task.text); + requestAnimationFrame(() => { + editingTaskRef.current?.focus(); + editingTaskRef.current?.select(); + }); + }; + + const handleSaveTask = (taskId: string) => { + const text = normalize(editingTaskText); + if (!text) { + toast.error("O texto da tarefa não pode estar vazio."); + editingTaskRef.current?.focus(); + return; + } + + updateField( + "tasks", + (formState.tasks || []).map((t) => + t.id === taskId ? { ...t, text } : t, + ), + ); + setEditingTaskId(null); + setEditingTaskText(""); + }; + + const handleCancelEditTask = () => { + setEditingTaskId(null); + setEditingTaskText(""); + }; + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); setErrorMessage(null); @@ -373,33 +425,78 @@ export function NoteDialog({ key={task.id} className="flex items-center gap-3 rounded-md px-3 py-1.5 hover:bg-muted/50" > - handleToggleTask(task.id)} - disabled={isPending} - aria-label={`Marcar "${task.text}" como ${ - task.completed ? "não concluída" : "concluída" - }`} - /> - - {task.text} - + {editingTaskId === task.id ? ( + setEditingTaskText(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + e.stopPropagation(); + handleSaveTask(task.id); + } + if (e.key === "Escape") { + e.preventDefault(); + e.stopPropagation(); + handleCancelEditTask(); + } + }} + disabled={isPending} + className="h-8 min-w-0 flex-1" + aria-label={`Editar "${task.text}"`} + /> + ) : ( + <> + handleToggleTask(task.id)} + disabled={isPending} + aria-label={`Marcar "${task.text}" como ${ + task.completed ? "não concluída" : "concluída" + }`} + /> + + + )} ))}