style: padronizar note-dialog com Labels e scroll apenas no conteúdo
Adiciona Labels (Título, Conteúdo, Tipo de anotação, Adicionar tarefa) seguindo o padrão dos demais dialogs do projeto (space-y-1 + Label). DialogDescription visível novamente com texto contextual. Scroll apenas no form (-mx-6 max-h-[80vh] overflow-y-auto px-6), header e footer fixos — mesmo padrão do lancamento-dialog. Footer movido para fora do form; submit via requestSubmit(). Corrige useMemo antes do early return no note-details-dialog. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -45,13 +45,14 @@ export function NoteDetailsDialog({
|
|||||||
};
|
};
|
||||||
}, [note]);
|
}, [note]);
|
||||||
|
|
||||||
|
const tasks = note?.tasks || [];
|
||||||
|
const sortedTasks = useMemo(() => sortTasksByStatus(tasks), [tasks]);
|
||||||
|
|
||||||
if (!note) {
|
if (!note) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTask = note.type === "tarefa";
|
const isTask = note.type === "tarefa";
|
||||||
const tasks = note.tasks || [];
|
|
||||||
const sortedTasks = useMemo(() => sortTasksByStatus(tasks), [tasks]);
|
|
||||||
const completedCount = tasks.filter((t) => t.completed).length;
|
const completedCount = tasks.filter((t) => t.completed).length;
|
||||||
const totalCount = tasks.length;
|
const totalCount = tasks.length;
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { useControlledState } from "@/hooks/use-controlled-state";
|
import { useControlledState } from "@/hooks/use-controlled-state";
|
||||||
@@ -76,7 +77,6 @@ export function NoteDialog({
|
|||||||
const descRef = useRef<HTMLTextAreaElement>(null);
|
const descRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const newTaskRef = useRef<HTMLInputElement>(null);
|
const newTaskRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
// Use controlled state hook for dialog open state
|
|
||||||
const [dialogOpen, setDialogOpen] = useControlledState(
|
const [dialogOpen, setDialogOpen] = useControlledState(
|
||||||
open,
|
open,
|
||||||
false,
|
false,
|
||||||
@@ -85,7 +85,6 @@ export function NoteDialog({
|
|||||||
|
|
||||||
const initialState = buildInitialValues(note);
|
const initialState = buildInitialValues(note);
|
||||||
|
|
||||||
// Use form state hook for form management
|
|
||||||
const { formState, updateField, setFormState } =
|
const { formState, updateField, setFormState } =
|
||||||
useFormState<NoteFormValues>(initialState);
|
useFormState<NoteFormValues>(initialState);
|
||||||
|
|
||||||
@@ -98,7 +97,11 @@ export function NoteDialog({
|
|||||||
}
|
}
|
||||||
}, [dialogOpen, note, setFormState]);
|
}, [dialogOpen, note, setFormState]);
|
||||||
|
|
||||||
const title = mode === "create" ? "Nova anotação" : "Editar anotação";
|
const dialogTitle = mode === "create" ? "Nova anotação" : "Editar anotação";
|
||||||
|
const description =
|
||||||
|
mode === "create"
|
||||||
|
? "Crie uma nota simples ou uma lista de tarefas."
|
||||||
|
: "Altere o conteúdo desta anotação.";
|
||||||
const submitLabel = mode === "create" ? "Salvar" : "Atualizar";
|
const submitLabel = mode === "create" ? "Salvar" : "Atualizar";
|
||||||
|
|
||||||
const titleCount = formState.title.length;
|
const titleCount = formState.title.length;
|
||||||
@@ -224,104 +227,114 @@ export function NoteDialog({
|
|||||||
return (
|
return (
|
||||||
<Dialog open={dialogOpen} onOpenChange={handleOpenChange}>
|
<Dialog open={dialogOpen} onOpenChange={handleOpenChange}>
|
||||||
{trigger ? <DialogTrigger asChild>{trigger}</DialogTrigger> : null}
|
{trigger ? <DialogTrigger asChild>{trigger}</DialogTrigger> : null}
|
||||||
<DialogContent className="max-h-[90vh] overflow-y-auto">
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{title}</DialogTitle>
|
<DialogTitle>{dialogTitle}</DialogTitle>
|
||||||
<DialogDescription className="sr-only">
|
<DialogDescription>{description}</DialogDescription>
|
||||||
{mode === "create"
|
|
||||||
? "Criar nova anotação"
|
|
||||||
: "Editar anotação existente"}
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
className="space-y-3"
|
className="space-y-3 -mx-6 max-h-[80vh] overflow-y-auto px-6 pb-1"
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
noValidate
|
noValidate
|
||||||
>
|
>
|
||||||
{mode === "create" && (
|
{mode === "create" && (
|
||||||
<RadioGroup
|
<div className="space-y-1">
|
||||||
value={formState.type}
|
<Label>Tipo de anotação</Label>
|
||||||
onValueChange={(value) =>
|
<RadioGroup
|
||||||
updateField("type", value as "nota" | "tarefa")
|
value={formState.type}
|
||||||
}
|
onValueChange={(value) =>
|
||||||
disabled={isPending}
|
updateField("type", value as "nota" | "tarefa")
|
||||||
className="flex gap-4"
|
}
|
||||||
>
|
disabled={isPending}
|
||||||
<div className="flex items-center gap-2">
|
className="flex gap-4"
|
||||||
<RadioGroupItem value="nota" id="tipo-nota" />
|
>
|
||||||
<label
|
<div className="flex items-center gap-2">
|
||||||
htmlFor="tipo-nota"
|
<RadioGroupItem value="nota" id="tipo-nota" />
|
||||||
className="text-sm cursor-pointer select-none"
|
<Label
|
||||||
>
|
htmlFor="tipo-nota"
|
||||||
Nota
|
className="font-normal cursor-pointer"
|
||||||
</label>
|
>
|
||||||
</div>
|
Nota
|
||||||
<div className="flex items-center gap-2">
|
</Label>
|
||||||
<RadioGroupItem value="tarefa" id="tipo-tarefa" />
|
</div>
|
||||||
<label
|
<div className="flex items-center gap-2">
|
||||||
htmlFor="tipo-tarefa"
|
<RadioGroupItem value="tarefa" id="tipo-tarefa" />
|
||||||
className="text-sm cursor-pointer select-none"
|
<Label
|
||||||
>
|
htmlFor="tipo-tarefa"
|
||||||
Tarefas
|
className="font-normal cursor-pointer"
|
||||||
</label>
|
>
|
||||||
</div>
|
Tarefas
|
||||||
</RadioGroup>
|
</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Input
|
<div className="space-y-1">
|
||||||
id="note-title"
|
<Label htmlFor="note-title">Título</Label>
|
||||||
ref={titleRef}
|
<Input
|
||||||
value={formState.title}
|
id="note-title"
|
||||||
onChange={(e) => updateField("title", e.target.value)}
|
ref={titleRef}
|
||||||
placeholder="Título"
|
value={formState.title}
|
||||||
maxLength={MAX_TITLE}
|
onChange={(e) => updateField("title", e.target.value)}
|
||||||
disabled={isPending}
|
placeholder={
|
||||||
required
|
isNote ? "Ex.: Revisar metas do mês" : "Ex.: Tarefas da semana"
|
||||||
/>
|
}
|
||||||
|
maxLength={MAX_TITLE}
|
||||||
{isNote && (
|
|
||||||
<Textarea
|
|
||||||
id="note-description"
|
|
||||||
className="field-sizing-fixed"
|
|
||||||
ref={descRef}
|
|
||||||
value={formState.description}
|
|
||||||
onChange={(e) => updateField("description", e.target.value)}
|
|
||||||
placeholder="Escreva sua anotação..."
|
|
||||||
rows={5}
|
|
||||||
maxLength={MAX_DESC}
|
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isNote && (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="note-description">Conteúdo</Label>
|
||||||
|
<Textarea
|
||||||
|
id="note-description"
|
||||||
|
className="field-sizing-fixed"
|
||||||
|
ref={descRef}
|
||||||
|
value={formState.description}
|
||||||
|
onChange={(e) => updateField("description", e.target.value)}
|
||||||
|
placeholder="Detalhe sua anotação..."
|
||||||
|
rows={5}
|
||||||
|
maxLength={MAX_DESC}
|
||||||
|
disabled={isPending}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isNote && (
|
{!isNote && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex gap-2">
|
<div className="space-y-1">
|
||||||
<Input
|
<Label htmlFor="new-task-input">Adicionar tarefa</Label>
|
||||||
id="new-task-input"
|
<div className="flex gap-2">
|
||||||
ref={newTaskRef}
|
<Input
|
||||||
value={newTaskText}
|
id="new-task-input"
|
||||||
onChange={(e) => setNewTaskText(e.target.value)}
|
ref={newTaskRef}
|
||||||
placeholder="Nova tarefa..."
|
value={newTaskText}
|
||||||
disabled={isPending}
|
onChange={(e) => setNewTaskText(e.target.value)}
|
||||||
onKeyDown={(e) => {
|
placeholder="Nova tarefa..."
|
||||||
if (e.key === "Enter") {
|
disabled={isPending}
|
||||||
e.preventDefault();
|
onKeyDown={(e) => {
|
||||||
handleAddTask();
|
if (e.key === "Enter") {
|
||||||
}
|
e.preventDefault();
|
||||||
}}
|
handleAddTask();
|
||||||
/>
|
}
|
||||||
<Button
|
}}
|
||||||
type="button"
|
/>
|
||||||
variant="outline"
|
<Button
|
||||||
onClick={handleAddTask}
|
type="button"
|
||||||
disabled={isPending || !normalize(newTaskText)}
|
variant="outline"
|
||||||
className="shrink-0"
|
onClick={handleAddTask}
|
||||||
>
|
disabled={isPending || !normalize(newTaskText)}
|
||||||
<RiAddLine className="h-4 w-4" />
|
className="shrink-0"
|
||||||
</Button>
|
>
|
||||||
|
<RiAddLine className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{sortedTasks.length > 0 && (
|
{sortedTasks.length > 0 && (
|
||||||
@@ -370,21 +383,33 @@ export function NoteDialog({
|
|||||||
{errorMessage}
|
{errorMessage}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => handleOpenChange(false)}
|
|
||||||
disabled={isPending}
|
|
||||||
>
|
|
||||||
Cancelar
|
|
||||||
</Button>
|
|
||||||
<Button type="submit" disabled={disableSubmit}>
|
|
||||||
{isPending ? "Salvando..." : submitLabel}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => handleOpenChange(false)}
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={disableSubmit}
|
||||||
|
onClick={(e) => {
|
||||||
|
const form = (
|
||||||
|
e.currentTarget.closest("[role=dialog]") as HTMLElement
|
||||||
|
)?.querySelector("form");
|
||||||
|
if (form) {
|
||||||
|
e.preventDefault();
|
||||||
|
form.requestSubmit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isPending ? "Salvando..." : submitLabel}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user