feat(reports): melhora notas, calendario e analises

This commit is contained in:
Felipe Coutinho
2026-03-09 17:14:04 +00:00
parent ada1377640
commit 6205dee42a
35 changed files with 429 additions and 590 deletions

View File

@@ -8,15 +8,11 @@ import {
RiInboxUnarchiveLine,
RiPencilLine,
} from "@remixicon/react";
import { useMemo } from "react";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { buildNoteDisplayTitle } from "@/lib/notes/formatters";
import { type Note, sortTasksByStatus } from "./types";
const DATE_FORMATTER = new Intl.DateTimeFormat("pt-BR", {
dateStyle: "medium",
});
interface NoteCardProps {
note: Note;
onEdit?: (note: Note) => void;
@@ -34,20 +30,10 @@ export function NoteCard({
onArquivar,
isArquivadas = false,
}: NoteCardProps) {
const { displayTitle } = useMemo(() => {
const resolvedTitle = note.title.trim().length
? note.title
: "Anotação sem título";
return {
displayTitle: resolvedTitle,
formattedDate: DATE_FORMATTER.format(new Date(note.createdAt)),
};
}, [note.createdAt, note.title]);
const displayTitle = buildNoteDisplayTitle(note.title);
const isTask = note.type === "tarefa";
const tasks = note.tasks || [];
const sortedTasks = useMemo(() => sortTasksByStatus(tasks), [tasks]);
const sortedTasks = sortTasksByStatus(tasks);
const completedCount = tasks.filter((t) => t.completed).length;
const totalCount = tasks.length;

View File

@@ -1,7 +1,6 @@
"use client";
import { RiCheckLine } from "@remixicon/react";
import { useMemo } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
@@ -13,14 +12,13 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
buildNoteDisplayTitle,
formatNoteCreatedAtLong,
} from "@/lib/notes/formatters";
import { Card } from "../ui/card";
import { type Note, sortTasksByStatus } from "./types";
const DATE_FORMATTER = new Intl.DateTimeFormat("pt-BR", {
dateStyle: "long",
timeStyle: "short",
});
interface NoteDetailsDialogProps {
note: Note | null;
open: boolean;
@@ -32,26 +30,14 @@ export function NoteDetailsDialog({
open,
onOpenChange,
}: NoteDetailsDialogProps) {
const { formattedDate, displayTitle } = useMemo(() => {
if (!note) {
return { formattedDate: "", displayTitle: "" };
}
const title = note.title.trim().length ? note.title : "Anotação sem título";
return {
formattedDate: DATE_FORMATTER.format(new Date(note.createdAt)),
displayTitle: title,
};
}, [note]);
const tasks = note?.tasks || [];
const sortedTasks = useMemo(() => sortTasksByStatus(tasks), [tasks]);
if (!note) {
return null;
}
const formattedDate = formatNoteCreatedAtLong(note.createdAt) ?? "";
const displayTitle = buildNoteDisplayTitle(note.title);
const tasks = note.tasks || [];
const sortedTasks = sortTasksByStatus(tasks);
const isTask = note.type === "tarefa";
const completedCount = tasks.filter((t) => t.completed).length;
const totalCount = tasks.length;

View File

@@ -1,13 +1,13 @@
"use client";
import { RiAddCircleLine, RiTodoLine } from "@remixicon/react";
import { useCallback, useMemo, useState } from "react";
import { useMemo, useState } from "react";
import { toast } from "sonner";
import {
arquivarAnotacaoAction,
deleteNoteAction,
} from "@/app/(dashboard)/anotacoes/actions";
import { ConfirmActionDialog } from "@/components/confirm-action-dialog";
import { ConfirmActionDialog } from "@/components/shared/confirm-action-dialog";
import { EmptyState } from "@/components/shared/empty-state";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -34,76 +34,78 @@ export function NotesPage({ notes, archivedNotes }: NotesPageProps) {
const [arquivarOpen, setArquivarOpen] = useState(false);
const [noteToArquivar, setNoteToArquivar] = useState<Note | null>(null);
const sortNotes = useCallback(
(list: Note[]) =>
[...list].sort(
const sortedNotes = useMemo(
() =>
[...notes].sort(
(a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
),
[],
[notes],
);
const sortedNotes = useMemo(() => sortNotes(notes), [notes, sortNotes]);
const sortedArchivedNotes = useMemo(
() => sortNotes(archivedNotes),
[archivedNotes, sortNotes],
() =>
[...archivedNotes].sort(
(a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
),
[archivedNotes],
);
const isArquivadas = activeTab === "arquivadas";
const handleCreateOpenChange = useCallback((open: boolean) => {
const handleCreateOpenChange = (open: boolean) => {
setCreateOpen(open);
}, []);
};
const handleEditOpenChange = useCallback((open: boolean) => {
const handleEditOpenChange = (open: boolean) => {
setEditOpen(open);
if (!open) {
setNoteToEdit(null);
}
}, []);
};
const handleDetailsOpenChange = useCallback((open: boolean) => {
const handleDetailsOpenChange = (open: boolean) => {
setDetailsOpen(open);
if (!open) {
setNoteDetails(null);
}
}, []);
};
const handleRemoveOpenChange = useCallback((open: boolean) => {
const handleRemoveOpenChange = (open: boolean) => {
setRemoveOpen(open);
if (!open) {
setNoteToRemove(null);
}
}, []);
};
const handleArquivarOpenChange = useCallback((open: boolean) => {
const handleArquivarOpenChange = (open: boolean) => {
setArquivarOpen(open);
if (!open) {
setNoteToArquivar(null);
}
}, []);
};
const handleEditRequest = useCallback((note: Note) => {
const handleEditRequest = (note: Note) => {
setNoteToEdit(note);
setEditOpen(true);
}, []);
};
const handleDetailsRequest = useCallback((note: Note) => {
const handleDetailsRequest = (note: Note) => {
setNoteDetails(note);
setDetailsOpen(true);
}, []);
};
const handleRemoveRequest = useCallback((note: Note) => {
const handleRemoveRequest = (note: Note) => {
setNoteToRemove(note);
setRemoveOpen(true);
}, []);
};
const handleArquivarRequest = useCallback((note: Note) => {
const handleArquivarRequest = (note: Note) => {
setNoteToArquivar(note);
setArquivarOpen(true);
}, []);
};
const handleArquivarConfirm = useCallback(async () => {
const handleArquivarConfirm = async () => {
if (!noteToArquivar) {
return;
}
@@ -120,9 +122,9 @@ export function NotesPage({ notes, archivedNotes }: NotesPageProps) {
toast.error(result.error);
throw new Error(result.error);
}, [noteToArquivar, isArquivadas]);
};
const handleRemoveConfirm = useCallback(async () => {
const handleRemoveConfirm = async () => {
if (!noteToRemove) {
return;
}
@@ -136,7 +138,7 @@ export function NotesPage({ notes, archivedNotes }: NotesPageProps) {
toast.error(result.error);
throw new Error(result.error);
}, [noteToRemove]);
};
const removeTitle = noteToRemove
? noteToRemove.title.trim().length