style(notes): polimento visual nas tarefas e modal de detalhes

Ícone de tarefa concluída em card e detalhes simplificado para
RiCheckLine verde sem caixa. Checkbox no modal de edição usa bg/border
success com texto success-foreground (claro no light, escuro no dark).
Footer do modal de detalhes reordenado: Cancelar à esquerda, Alterar
primário à direita.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-04-20 19:23:36 +00:00
parent c41fafc319
commit cbc17c8513
22 changed files with 52 additions and 66 deletions

View File

@@ -58,9 +58,5 @@ OPENROUTER_API_KEY=
# === Logo.dev (Opcional) === # === Logo.dev (Opcional) ===
# Logos automáticos de estabelecimentos. Cadastre em https://www.logo.dev # Logos automáticos de estabelecimentos. Cadastre em https://www.logo.dev
# Ambas as variáveis são lidas em runtime pelo servidor — basta configurá-las
# no host (Coolify, Railway, Docker Compose etc.), sem mexer no CI.
# LOGO_DEV_TOKEN — token público usado pelo servidor para montar a URL da imagem
# LOGO_DEV_SECRET_KEY — chave secreta para a Brand Search API (picker de logo)
LOGO_DEV_TOKEN= LOGO_DEV_TOKEN=
LOGO_DEV_SECRET_KEY= LOGO_DEV_SECRET_KEY=

View File

@@ -31,6 +31,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
- Logo.dev: `NEXT_PUBLIC_LOGO_DEV_TOKEN` renomeado para `LOGO_DEV_TOKEN` (agora lido em runtime server-side apenas) - Logo.dev: `NEXT_PUBLIC_LOGO_DEV_TOKEN` renomeado para `LOGO_DEV_TOKEN` (agora lido em runtime server-side apenas)
- UI: conceito "Pagador/Pagadores" renomeado para **"Pessoa/Pessoas"** em toda a interface — labels, títulos, toasts, mensagens de erro, cabeçalhos de tabela e exportações. Código, rotas (`/payers`) e schema do banco (`pagadores`) permanecem inalterados; a divergência entre UI e código é intencional - UI: conceito "Pagador/Pagadores" renomeado para **"Pessoa/Pessoas"** em toda a interface — labels, títulos, toasts, mensagens de erro, cabeçalhos de tabela e exportações. Código, rotas (`/payers`) e schema do banco (`pagadores`) permanecem inalterados; a divergência entre UI e código é intencional
- Deps: next 16.2.3 → 16.2.4, better-auth 1.6.2 → 1.6.5, ai 6.0.159 → 6.0.168 e outros patches menores - Deps: next 16.2.3 → 16.2.4, better-auth 1.6.2 → 1.6.5, ai 6.0.159 → 6.0.168 e outros patches menores
- Notas/Tarefas: ícone de tarefa concluída em visualização (card e detalhes) simplificado para `RiCheckLine` verde sem caixa; checkbox no modal de edição usa fundo e borda `success` com ícone `success-foreground` (claro no light, escuro no dark)
- Notas/Detalhes: botões do footer reordenados ("Cancelar" à esquerda, "Alterar" primário à direita)
### Removido ### Removido

View File

@@ -227,12 +227,12 @@ export function AccountDialog({
}); });
}; };
const title = mode === "create" ? "Nova conta" : "Editar conta"; const title = mode === "create" ? "Nova conta" : "Atualizar conta";
const description = const description =
mode === "create" mode === "create"
? "Cadastre uma nova conta para organizar seus lançamentos." ? "Cadastre uma nova conta para organizar seus lançamentos."
: "Atualize as informações da conta selecionada."; : "Atualize as informações da conta selecionada.";
const submitLabel = mode === "create" ? "Salvar conta" : "Atualizar conta"; const submitLabel = mode === "create" ? "Salvar" : "Atualizar";
const handleMainDialogOpenChange = (open: boolean) => { const handleMainDialogOpenChange = (open: boolean) => {
if (!open && logoDialogOpen) { if (!open && logoDialogOpen) {

View File

@@ -212,7 +212,7 @@ export function AccountsPage({
onOpenChange={handleRemoveOpenChange} onOpenChange={handleRemoveOpenChange}
title={removeTitle} title={removeTitle}
description="Ao remover esta conta, todos os dados relacionados a ela serão perdidos." description="Ao remover esta conta, todos os dados relacionados a ela serão perdidos."
confirmLabel="Remover conta" confirmLabel="Remover"
pendingLabel="Removendo..." pendingLabel="Removendo..."
confirmVariant="destructive" confirmVariant="destructive"
onConfirm={handleRemoveConfirm} onConfirm={handleRemoveConfirm}

View File

@@ -161,13 +161,12 @@ export function BudgetDialog({
}); });
}; };
const title = mode === "create" ? "Novo orçamento" : "Editar orçamento"; const title = mode === "create" ? "Novo orçamento" : "Atualizar orçamento";
const description = const description =
mode === "create" mode === "create"
? "Defina um limite de gastos para acompanhar suas despesas." ? "Defina um limite de gastos para acompanhar suas despesas."
: "Atualize os detalhes do orçamento selecionado."; : "Atualize os detalhes do orçamento selecionado.";
const submitLabel = const submitLabel = mode === "create" ? "Salvar" : "Atualizar";
mode === "create" ? "Salvar orçamento" : "Atualizar orçamento";
const disabled = categories.length === 0; const disabled = categories.length === 0;
const parsedAmount = Number.parseFloat(formState.amount); const parsedAmount = Number.parseFloat(formState.amount);
const sliderValue = Number.isFinite(parsedAmount) const sliderValue = Number.isFinite(parsedAmount)

View File

@@ -165,7 +165,7 @@ export function BudgetsPage({
onOpenChange={handleRemoveOpenChange} onOpenChange={handleRemoveOpenChange}
title={removeTitle} title={removeTitle}
description="Esta ação remove o limite configurado para a categoria selecionada." description="Esta ação remove o limite configurado para a categoria selecionada."
confirmLabel="Remover orçamento" confirmLabel="Remover"
pendingLabel="Removendo..." pendingLabel="Removendo..."
confirmVariant="destructive" confirmVariant="destructive"
onConfirm={handleRemoveConfirm} onConfirm={handleRemoveConfirm}
@@ -176,7 +176,7 @@ export function BudgetsPage({
onOpenChange={setDuplicateOpen} onOpenChange={setDuplicateOpen}
title="Copiar orçamentos do último mês?" title="Copiar orçamentos do último mês?"
description="Isso copiará os limites definidos no mês anterior para as categorias que ainda não possuem orçamento neste mês." description="Isso copiará os limites definidos no mês anterior para as categorias que ainda não possuem orçamento neste mês."
confirmLabel="Copiar orçamentos" confirmLabel="Copiar"
pendingLabel="Copiando..." pendingLabel="Copiando..."
onConfirm={handleDuplicateConfirm} onConfirm={handleDuplicateConfirm}
/> />

View File

@@ -194,12 +194,12 @@ export function CardDialog({
}); });
}; };
const title = mode === "create" ? "Novo cartão" : "Editar cartão"; const title = mode === "create" ? "Novo cartão" : "Atualizar cartão";
const description = const description =
mode === "create" mode === "create"
? "Inclua um novo cartão de crédito para acompanhar seus gastos." ? "Inclua um novo cartão de crédito para acompanhar seus gastos."
: "Atualize as informações do cartão selecionado."; : "Atualize as informações do cartão selecionado.";
const submitLabel = mode === "create" ? "Salvar cartão" : "Atualizar cartão"; const submitLabel = mode === "create" ? "Salvar" : "Atualizar";
const handleMainDialogOpenChange = (open: boolean) => { const handleMainDialogOpenChange = (open: boolean) => {
if (!open && logoDialogOpen) { if (!open && logoDialogOpen) {

View File

@@ -201,7 +201,7 @@ export function CardsPage({
onOpenChange={handleRemoveOpenChange} onOpenChange={handleRemoveOpenChange}
title={removeTitle} title={removeTitle}
description="Ao remover este cartão, os registros relacionados a ele serão excluídos permanentemente." description="Ao remover este cartão, os registros relacionados a ele serão excluídos permanentemente."
confirmLabel="Remover cartão" confirmLabel="Remover"
pendingLabel="Removendo..." pendingLabel="Removendo..."
confirmVariant="destructive" confirmVariant="destructive"
onConfirm={handleRemoveConfirm} onConfirm={handleRemoveConfirm}

View File

@@ -11,6 +11,7 @@ import { useMemo, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { deleteCategoryAction } from "@/features/categories/actions"; import { deleteCategoryAction } from "@/features/categories/actions";
import { ConfirmActionDialog } from "@/shared/components/confirm-action-dialog"; import { ConfirmActionDialog } from "@/shared/components/confirm-action-dialog";
import { CategoryIconBadge } from "@/shared/components/entity-avatar";
import { Button } from "@/shared/components/ui/button"; import { Button } from "@/shared/components/ui/button";
import { Card, CardContent } from "@/shared/components/ui/card"; import { Card, CardContent } from "@/shared/components/ui/card";
import { import {
@@ -32,7 +33,6 @@ import {
CATEGORY_TYPES, CATEGORY_TYPES,
type CategoryType, type CategoryType,
} from "@/shared/lib/categories/constants"; } from "@/shared/lib/categories/constants";
import { CategoryIconBadge } from "@/shared/components/entity-avatar";
import { CategoryDialog } from "./category-dialog"; import { CategoryDialog } from "./category-dialog";
import type { Category } from "./types"; import type { Category } from "./types";
@@ -250,7 +250,7 @@ export function CategoriesPage({ categories }: CategoriesPageProps) {
onOpenChange={handleRemoveOpenChange} onOpenChange={handleRemoveOpenChange}
title={removeTitle} title={removeTitle}
description="Ao remover esta categoria, os lançamentos associados serão desrelacionados." description="Ao remover esta categoria, os lançamentos associados serão desrelacionados."
confirmLabel="Remover categoria" confirmLabel="Remover"
pendingLabel="Removendo..." pendingLabel="Removendo..."
confirmVariant="destructive" confirmVariant="destructive"
onConfirm={handleRemoveConfirm} onConfirm={handleRemoveConfirm}

View File

@@ -136,13 +136,12 @@ export function CategoryDialog({
}); });
}; };
const title = mode === "create" ? "Nova categoria" : "Editar categoria"; const title = mode === "create" ? "Nova categoria" : "Atualizar categoria";
const description = const description =
mode === "create" mode === "create"
? "Crie uma categoria para organizar seus lançamentos." ? "Crie uma categoria para organizar seus lançamentos."
: "Atualize os detalhes da categoria selecionada."; : "Atualize os detalhes da categoria selecionada.";
const submitLabel = const submitLabel = mode === "create" ? "Salvar" : "Atualizar";
mode === "create" ? "Salvar categoria" : "Atualizar categoria";
return ( return (
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}> <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>

View File

@@ -171,7 +171,7 @@ export function BillPaymentDialog({
Processando... Processando...
</> </>
) : ( ) : (
"Confirmar pagamento" "Confirmar"
)} )}
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@@ -59,7 +59,7 @@ export function GoalProgressItem({
size="icon-sm" size="icon-sm"
className="transition-opacity text-primary hover:opacity-80" className="transition-opacity text-primary hover:opacity-80"
onClick={() => onEdit(item)} onClick={() => onEdit(item)}
aria-label={`Editar orçamento de ${item.categoryName}`} aria-label={`Atualizar orçamento de ${item.categoryName}`}
> >
<RiPencilLine className="size-3.5" /> <RiPencilLine className="size-3.5" />
</Button> </Button>

View File

@@ -193,7 +193,7 @@ export function InvoicePaymentDialog({
Processando... Processando...
</> </>
) : ( ) : (
"Confirmar pagamento" "Confirmar"
)} )}
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@@ -38,12 +38,12 @@ export function EditPaymentDateDialog({
<DialogTrigger asChild>{trigger}</DialogTrigger> <DialogTrigger asChild>{trigger}</DialogTrigger>
<DialogContent className="sm:max-w-md"> <DialogContent className="sm:max-w-md">
<DialogHeader> <DialogHeader>
<DialogTitle>Editar data de pagamento</DialogTitle> <DialogTitle>Atualizar data de pagamento</DialogTitle>
<DialogDescription> <DialogDescription>
Selecione a data em que o pagamento foi realizado. Selecione a data em que o pagamento foi realizado.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="space-y-4 py-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="payment-date">Data de pagamento</Label> <Label htmlFor="payment-date">Data de pagamento</Label>
<DatePicker <DatePicker

View File

@@ -97,15 +97,11 @@ export function NoteCard({
<div className="min-h-0 flex-1 space-y-2 overflow-hidden"> <div className="min-h-0 flex-1 space-y-2 overflow-hidden">
{sortedTasks.slice(0, 5).map((task) => ( {sortedTasks.slice(0, 5).map((task) => (
<div key={task.id} className="flex items-start gap-2 text-sm"> <div key={task.id} className="flex items-start gap-2 text-sm">
<div <div className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center">
className={`mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border ${ {task.completed ? (
task.completed <RiCheckLine className="h-4 w-4 text-success" />
? "bg-success border-success" ) : (
: "border-input" <div className="h-4 w-4 rounded-sm border border-input" />
}`}
>
{task.completed && (
<RiCheckLine className="h-3 w-3 text-background" />
)} )}
</div> </div>
<span <span

View File

@@ -65,15 +65,11 @@ export function NoteDetailsDialog({
key={task.id} key={task.id}
className="flex items-center gap-3 rounded-md px-3 py-1.5" className="flex items-center gap-3 rounded-md px-3 py-1.5"
> >
<div <div className="flex h-4 w-4 shrink-0 items-center justify-center">
className={`flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border ${ {task.completed ? (
task.completed <RiCheckLine className="h-4 w-4 text-success" />
? "bg-success border-success" ) : (
: "border-input" <div className="h-4 w-4 rounded-sm border border-input" />
}`}
>
{task.completed && (
<RiCheckLine className="h-4 w-4 text-primary-foreground" />
)} )}
</div> </div>
<span <span
@@ -95,23 +91,22 @@ export function NoteDetailsDialog({
)} )}
<DialogFooter> <DialogFooter>
<DialogClose asChild>
<Button type="button" variant="outline">
Cancelar
</Button>
</DialogClose>
{onEdit && ( {onEdit && (
<Button <Button
type="button" type="button"
variant="outline"
onClick={() => { onClick={() => {
onOpenChange(false); onOpenChange(false);
onEdit(note); onEdit(note);
}} }}
> >
Editar Alterar
</Button> </Button>
)} )}
<DialogClose asChild>
<Button type="button" variant="outline">
Fechar
</Button>
</DialogClose>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@@ -94,13 +94,14 @@ export function NoteDialog({
} }
}, [dialogOpen, note, resetForm]); }, [dialogOpen, note, resetForm]);
const dialogTitle = mode === "create" ? "Nova anotação" : "Editar anotação"; const dialogTitle =
mode === "create" ? "Nova anotação" : "Atualizar anotação";
const description = const description =
mode === "create" mode === "create"
? "Crie uma nota simples ou uma lista de tarefas." ? "Crie uma nota simples ou uma lista de tarefas."
: note?.type === "tarefa" : note?.type === "tarefa"
? "Editando lista de tarefas." ? "Atualize sua lista de tarefas"
: "Editando nota."; : "Atualize sua nota";
const submitLabel = mode === "create" ? "Salvar" : "Atualizar"; const submitLabel = mode === "create" ? "Salvar" : "Atualizar";
const titleCount = formState.title.length; const titleCount = formState.title.length;
@@ -361,7 +362,6 @@ export function NoteDialog({
className="shrink-0 gap-1.5" className="shrink-0 gap-1.5"
> >
<RiAddCircleFill className="h-4 w-4" /> <RiAddCircleFill className="h-4 w-4" />
Adicionar
</Button> </Button>
</div> </div>
</div> </div>
@@ -374,7 +374,7 @@ export function NoteDialog({
className="flex items-center gap-3 rounded-md px-3 py-1.5 hover:bg-muted/50" className="flex items-center gap-3 rounded-md px-3 py-1.5 hover:bg-muted/50"
> >
<Checkbox <Checkbox
className="data-[state=checked]:bg-success data-[state=checked]:border-success" className="data-[state=checked]:bg-success! data-[state=checked]:border-success! data-[state=checked]:text-success-foreground!"
checked={task.completed} checked={task.completed}
onCheckedChange={() => handleToggleTask(task.id)} onCheckedChange={() => handleToggleTask(task.id)}
disabled={isPending} disabled={isPending}

View File

@@ -216,12 +216,12 @@ export function PayerDialog({
}); });
}; };
const title = mode === "create" ? "Nova pessoa" : "Editar pessoa"; const title = mode === "create" ? "Nova pessoa" : "Atualizar pessoa";
const description = const description =
mode === "create" mode === "create"
? "Selecione um avatar e informe os detalhes para criar uma nova pessoa." ? "Selecione um avatar e informe os detalhes para criar uma nova pessoa."
: "Atualize os detalhes da pessoa selecionada."; : "Atualize os detalhes da pessoa selecionada.";
const submitLabel = mode === "create" ? "Salvar pessoa" : "Atualizar pessoa"; const submitLabel = mode === "create" ? "Salvar" : "Atualizar";
const isUploadSelected = const isUploadSelected =
uploadedAvatar !== null && formState.avatarUrl === uploadedAvatar; uploadedAvatar !== null && formState.avatarUrl === uploadedAvatar;

View File

@@ -151,8 +151,8 @@ export function PayersPage({ payers, avatarOptions }: PayersPageProps) {
{orderedPayers.length === 0 ? ( {orderedPayers.length === 0 ? (
<div className="flex min-h-[320px] items-center justify-center rounded-lg border border-dashed bg-muted/30"> <div className="flex min-h-[320px] items-center justify-center rounded-lg border border-dashed bg-muted/30">
<div className="max-w-sm text-center text-sm text-muted-foreground"> <div className="max-w-sm text-center text-sm text-muted-foreground">
Cadastre seu primeira pessoa para organizar cobranças e Cadastre seu primeira pessoa para organizar cobranças e pagamentos
pagamentos recorrentes. recorrentes.
</div> </div>
</div> </div>
) : ( ) : (
@@ -186,7 +186,7 @@ export function PayersPage({ payers, avatarOptions }: PayersPageProps) {
onOpenChange={handleRemoveOpenChange} onOpenChange={handleRemoveOpenChange}
title={removeTitle} title={removeTitle}
description="Ao remover esta pessoa, os registros relacionados a ele deixarão de ser associados automaticamente." description="Ao remover esta pessoa, os registros relacionados a ele deixarão de ser associados automaticamente."
confirmLabel="Remover pessoa" confirmLabel="Remover"
pendingLabel="Removendo..." pendingLabel="Removendo..."
confirmVariant="destructive" confirmVariant="destructive"
onConfirm={handleRemoveConfirm} onConfirm={handleRemoveConfirm}

View File

@@ -642,7 +642,7 @@ export function MassAddDialog({
</Button> </Button>
<Button onClick={handleSubmit} disabled={loading}> <Button onClick={handleSubmit} disabled={loading}>
{loading && <Spinner className="size-4" />} {loading && <Spinner className="size-4" />}
Criar {transactions.length}{" "} Salvar {transactions.length}{" "}
{transactions.length === 1 ? "lançamento" : "lançamentos"} {transactions.length === 1 ? "lançamento" : "lançamentos"}
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@@ -242,7 +242,7 @@ export function TransactionDetailsDialog({
</Button> </Button>
</DialogClose> </DialogClose>
{onEdit && !transaction.readonly && ( {onEdit && !transaction.readonly && (
<Button onClick={handleEdit}>Editar</Button> <Button onClick={handleEdit}>Alterar</Button>
)} )}
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>

View File

@@ -229,8 +229,7 @@ export function TransactionDialog({
} }
if (formState.isSplit && !formState.payerId) { if (formState.isSplit && !formState.payerId) {
const message = const message = "Selecione a pessoa principal para dividir o lançamento.";
"Selecione a pessoa principal para dividir o lançamento.";
setErrorMessage(message); setErrorMessage(message);
toast.error(message); toast.error(message);
return; return;
@@ -460,7 +459,7 @@ export function TransactionDialog({
? "Nova Despesa" ? "Nova Despesa"
: "Nova Receita" : "Nova Receita"
: "Novo lançamento" : "Novo lançamento"
: "Editar lançamento"; : "Atualizar lançamento";
const description = const description =
mode === "create" mode === "create"
? isImportMode ? isImportMode
@@ -471,7 +470,7 @@ export function TransactionDialog({
? `Informe os dados abaixo para registrar ${defaultTransactionType === "Despesa" ? "uma nova despesa" : "uma nova receita"}.` ? `Informe os dados abaixo para registrar ${defaultTransactionType === "Despesa" ? "uma nova despesa" : "uma nova receita"}.`
: "Informe os dados abaixo para registrar um novo lançamento." : "Informe os dados abaixo para registrar um novo lançamento."
: "Atualize as informações do lançamento selecionado."; : "Atualize as informações do lançamento selecionado.";
const submitLabel = mode === "create" ? "Salvar lançamento" : "Atualizar"; const submitLabel = mode === "create" ? "Salvar" : "Atualizar";
const showInstallments = formState.condition === "Parcelado"; const showInstallments = formState.condition === "Parcelado";
const showRecurrence = formState.condition === "Recorrente"; const showRecurrence = formState.condition === "Recorrente";