refactor(ui): renomear "Pagador/Pagadores" para "Pessoa/Pessoas" na interface

Todas as strings visíveis ao usuário (labels, títulos, toasts, mensagens
de erro, cabeçalhos de tabela, exportações) foram atualizadas. Acordos
de gênero em português corrigidos. Código, rotas (/payers) e schema do
banco (pagadores) permanecem inalterados — divergência intencional
documentada em CLAUDE.md e CHANGELOG.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-04-20 18:29:55 +00:00
parent 2f68bcf039
commit 0bc3f06b77
42 changed files with 101 additions and 99 deletions

View File

@@ -9,7 +9,7 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
## [2.4.2] - 2026-04-20 ## [2.4.2] - 2026-04-20
Esta versão é quase toda sobre organização e polimento. O código interno do Dashboard foi reestruturado — módulos espalhados pela raiz da feature foram agrupados em subdiretórios coesos e a arquitetura de widgets foi renovada com um novo `widget-registry`. A sidebar lateral foi aposentada em favor de uma navegação concentrada na navbar. A interface passou por um refinamento visual amplo: cards redesenhados, dark mode mais consistente e efeitos decorativos removidos para uma composição mais limpa. As imagens de preview da landing page foram atualizadas. Por fim, a integração com Logo.dev ganhou uma arquitetura mais segura — o token agora é lido apenas no servidor e nunca chega ao cliente. Esta versão é quase toda sobre organização e polimento. O código interno do Dashboard foi reestruturado — módulos espalhados pela raiz da feature foram agrupados em subdiretórios coesos e a arquitetura de widgets foi renovada com um novo `widget-registry`. A sidebar lateral foi aposentada em favor de uma navegação concentrada na navbar. A interface passou por um refinamento visual amplo: cards redesenhados, dark mode mais consistente e efeitos decorativos removidos para uma composição mais limpa. As imagens de preview da landing page foram atualizadas. Por fim, a integração com Logo.dev ganhou uma arquitetura mais segura — o token agora é lido apenas no servidor e nunca chega ao cliente. O conceito de "Pagador" foi renomeado para "Pessoa" em toda a interface.
### Adicionado ### Adicionado
@@ -29,6 +29,7 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
- Landing: imagens de preview atualizadas; `mainFeatures` + `extraFeatures` unificados em grid único; dark mode nos botões de CTA - Landing: imagens de preview atualizadas; `mainFeatures` + `extraFeatures` unificados em grid único; dark mode nos botões de CTA
- Navbar: dark mode corrigido no navbar-shell (`dark:bg-card`, `dark:border-b-border/60`) - Navbar: dark mode corrigido no navbar-shell (`dark:bg-card`, `dark:border-b-border/60`)
- 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
- 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
### Removido ### Removido

View File

@@ -217,7 +217,9 @@ Layouts, `loading.tsx` e metadata continuam em `src/app/`.
| `contas` | `accounts` | | `contas` | `accounts` |
| `categorias` | `categories` | | `categorias` | `categories` |
| `orcamentos` | `budgets` | | `orcamentos` | `budgets` |
| `pagadores` | `payers` | | `pessoas` | `payers` |
> **Nota:** o conceito de "pagador" foi renomeado para **"pessoa"** na UI (labels, toasts, textos visíveis ao usuário). O código, rotas e schema continuam usando o termo original em inglês (`payer`, `payerId`, `adminPayerId`) e em português interno (`pagador` como variável). Não renomear esses identificadores — a divergência entre UI e código é intencional e documentada.
| `anotacoes` | `notes` | | `anotacoes` | `notes` |
| `calendario` | `calendar` | | `calendario` | `calendar` |
| `ajustes` | `settings` | | `ajustes` | `settings` |

View File

@@ -2,7 +2,7 @@ import { RiGroupLine } from "@remixicon/react";
import PageDescription from "@/shared/components/page-description"; import PageDescription from "@/shared/components/page-description";
export const metadata = { export const metadata = {
title: "Pagadores", title: "Pessoas",
}; };
export default function RootLayout({ export default function RootLayout({
@@ -14,7 +14,7 @@ export default function RootLayout({
<section className="space-y-6"> <section className="space-y-6">
<PageDescription <PageDescription
icon={<RiGroupLine />} icon={<RiGroupLine />}
title="Pagadores" title="Pessoas"
subtitle="Gerencie as pessoas ou entidades responsáveis pelos pagamentos." subtitle="Gerencie as pessoas ou entidades responsáveis pelos pagamentos."
/> />
{children} {children}

View File

@@ -1,7 +1,7 @@
import { Skeleton } from "@/shared/components/ui/skeleton"; import { Skeleton } from "@/shared/components/ui/skeleton";
/** /**
* Loading state para a página de pagadores * Loading state para a página de pessoas
* Layout: Header + Input de compartilhamento + Grid de cards * Layout: Header + Input de compartilhamento + Grid de cards
*/ */
export default function PagadoresLoading() { export default function PagadoresLoading() {
@@ -17,7 +17,7 @@ export default function PagadoresLoading() {
</div> </div>
</div> </div>
{/* Grid de cards de pagadores */} {/* Grid de cards de pessoas */}
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"> <div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: 6 }).map((_, i) => ( {Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="rounded-md border p-6 space-y-4"> <div key={i} className="rounded-md border p-6 space-y-4">

View File

@@ -99,7 +99,7 @@ export async function createAccountAction(
if (hasInitialBalance && !adminPayerId) { if (hasInitialBalance && !adminPayerId) {
throw new Error( throw new Error(
"Pagador com papel administrador não encontrado. Crie um pagador admin antes de definir um saldo inicial.", "Pessoa com papel administrador não encontrado. Crie um pessoa admin antes de definir um saldo inicial.",
); );
} }
@@ -299,7 +299,7 @@ export async function transferBetweenAccountsAction(
if (!adminPayerId) { if (!adminPayerId) {
throw new Error( throw new Error(
"Pagador administrador não encontrado. Por favor, crie um pagador admin.", "Pessoa administrador não encontrado. Por favor, crie um pessoa admin.",
); );
} }

View File

@@ -123,7 +123,7 @@ export function AccountStatementCard({
<MetaItem <MetaItem
label="Saídas" label="Saídas"
tooltip="Total de despesas pagas neste mês (considerando divisão entre pagadores)." tooltip="Total de despesas pagas neste mês (considerando divisão entre pessoas)."
> >
<span className="text-sm font-medium text-destructive"> <span className="text-sm font-medium text-destructive">
{formatCurrency(totalExpenses)} {formatCurrency(totalExpenses)}

View File

@@ -38,7 +38,7 @@ const CARDS = [
helpTitle: "Como calculamos receitas", helpTitle: "Como calculamos receitas",
helpLines: [ helpLines: [
"Somamos os lançamentos do tipo Receita no período selecionado.", "Somamos os lançamentos do tipo Receita no período selecionado.",
"Consideramos lançamentos efetivados e não efetivados do pagador principal (admin).", "Consideramos lançamentos efetivados e não efetivados da pessoa principal (admin).",
"Movimentações de contas marcadas como não consideradas no saldo total ficam fora deste card.", "Movimentações de contas marcadas como não consideradas no saldo total ficam fora deste card.",
"Não entram transferências internas nem lançamentos automáticos de fatura.", "Não entram transferências internas nem lançamentos automáticos de fatura.",
"Saldo inicial também fica fora quando a conta está marcada para desconsiderá-lo das receitas.", "Saldo inicial também fica fora quando a conta está marcada para desconsiderá-lo das receitas.",
@@ -54,7 +54,7 @@ const CARDS = [
helpTitle: "Como calculamos despesas", helpTitle: "Como calculamos despesas",
helpLines: [ helpLines: [
"Somamos os lançamentos do tipo Despesa no período selecionado.", "Somamos os lançamentos do tipo Despesa no período selecionado.",
"Consideramos lançamentos efetivados e não efetivados do pagador principal (admin).", "Consideramos lançamentos efetivados e não efetivados da pessoa principal (admin).",
"Movimentações de contas marcadas como não consideradas no saldo total ficam fora deste card.", "Movimentações de contas marcadas como não consideradas no saldo total ficam fora deste card.",
"Não entram transferências internas nem lançamentos automáticos de fatura.", "Não entram transferências internas nem lançamentos automáticos de fatura.",
"O valor mostrado é a saída efetiva do período, sempre em número positivo no card.", "O valor mostrado é a saída efetiva do período, sempre em número positivo no card.",

View File

@@ -86,7 +86,7 @@ export function InvoiceListItem({ invoice, onPay }: InvoiceListItemProps) {
<HoverCardTrigger asChild>{linkNode}</HoverCardTrigger> <HoverCardTrigger asChild>{linkNode}</HoverCardTrigger>
<HoverCardContent align="start" className="w-80 space-y-3"> <HoverCardContent align="start" className="w-80 space-y-3">
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
Distribuição por pagador Distribuição por pessoa
</p> </p>
<ul className="space-y-2"> <ul className="space-y-2">
{breakdown.map((share, index) => ( {breakdown.map((share, index) => (

View File

@@ -28,8 +28,8 @@ export function PayersWidget({ payers }: PayersWidgetProps) {
{payers.length === 0 ? ( {payers.length === 0 ? (
<WidgetEmptyState <WidgetEmptyState
icon={<RiGroupLine className="size-6 text-muted-foreground" />} icon={<RiGroupLine className="size-6 text-muted-foreground" />}
title="Nenhum pagador para o período" title="Nenhuma pessoa para o período"
description="Quando houver despesas associadas a pagadores, eles aparecerão aqui." description="Quando houver despesas associadas a pessoas, elas aparecerão aqui."
/> />
) : ( ) : (
<div className="flex flex-col"> <div className="flex flex-col">

View File

@@ -71,7 +71,7 @@ export async function fetchInstallmentAnalysis(
return { installmentGroups: [], totalPendingInstallments: 0 }; return { installmentGroups: [], totalPendingInstallments: 0 };
} }
// 1. Buscar todos os lançamentos parcelados não antecipados do pagador admin // 1. Buscar todos os lançamentos parcelados não antecipados da pessoa admin
const installmentRows = await db const installmentRows = await db
.select({ .select({
id: transactions.id, id: transactions.id,

View File

@@ -244,7 +244,7 @@ export async function fetchDashboardInvoices(
} }
const payerId = row.payerId ?? null; const payerId = row.payerId ?? null;
const pagadorName = row.pagadorName?.trim() || "Sem pagador"; const pagadorName = row.pagadorName?.trim() || "Sem pessoa";
const pagadorAvatar = row.pagadorAvatar ?? null; const pagadorAvatar = row.pagadorAvatar ?? null;
const payerKey = payerId ?? "__without-payer__"; const payerKey = payerId ?? "__without-payer__";
const key = `${row.cardId}:${payerKey}`; const key = `${row.cardId}:${payerKey}`;

View File

@@ -251,8 +251,8 @@ export const widgetsConfig: WidgetConfig[] = [
}, },
{ {
id: "pagadores", id: "pagadores",
title: "Pagadores", title: "Pessoas",
subtitle: "Despesas por pagador no período", subtitle: "Despesas por pessoa no período",
icon: <RiGroupLine className="size-4" />, icon: <RiGroupLine className="size-4" />,
component: ({ data }) => ( component: ({ data }) => (
<PayersWidget payers={data.pagadoresSnapshot.payers} /> <PayersWidget payers={data.pagadoresSnapshot.payers} />

View File

@@ -34,9 +34,9 @@ const statusEnum = z
const baseSchema = z.object({ const baseSchema = z.object({
name: z name: z
.string({ message: "Informe o nome do pagador." }) .string({ message: "Informe o nome da pessoa." })
.trim() .trim()
.min(1, "Informe o nome do pagador."), .min(1, "Informe o nome da pessoa."),
email: z email: z
.string() .string()
.trim() .trim()
@@ -110,7 +110,7 @@ export async function createPayerAction(
revalidate(user.id); revalidate(user.id);
return { success: true, message: "Payer criado com sucesso." }; return { success: true, message: "Pessoa criada com sucesso." };
} catch (error) { } catch (error) {
return handleActionError(error); return handleActionError(error);
} }
@@ -130,7 +130,7 @@ export async function updatePayerAction(
if (!existing) { if (!existing) {
return { return {
success: false, success: false,
error: "Pagador não encontrado.", error: "Pessoa não encontrada.",
}; };
} }
@@ -160,7 +160,7 @@ export async function updatePayerAction(
revalidate(currentUser.id); revalidate(currentUser.id);
return { success: true, message: "Payer atualizado com sucesso." }; return { success: true, message: "Pessoa atualizada com sucesso." };
} catch (error) { } catch (error) {
return handleActionError(error); return handleActionError(error);
} }
@@ -180,14 +180,14 @@ export async function deletePayerAction(
if (!existing) { if (!existing) {
return { return {
success: false, success: false,
error: "Pagador não encontrado.", error: "Pessoa não encontrada.",
}; };
} }
if (existing.role === PAYER_ROLE_ADMIN) { if (existing.role === PAYER_ROLE_ADMIN) {
return { return {
success: false, success: false,
error: "Pagadores administradores não podem ser removidos.", error: "Pessoas administradoras não podem ser removidas.",
}; };
} }
@@ -197,7 +197,7 @@ export async function deletePayerAction(
revalidate(user.id); revalidate(user.id);
return { success: true, message: "Payer removido com sucesso." }; return { success: true, message: "Pessoa removida com sucesso." };
} catch (error) { } catch (error) {
return handleActionError(error); return handleActionError(error);
} }
@@ -221,7 +221,7 @@ export async function joinPayerByShareCodeAction(
if (pagadorRow.userId === user.id) { if (pagadorRow.userId === user.id) {
return { return {
success: false, success: false,
error: "Você já é o proprietário deste pagador.", error: "Você já é o proprietário desta entidade pagadora.",
}; };
} }
@@ -235,7 +235,7 @@ export async function joinPayerByShareCodeAction(
if (existingShare) { if (existingShare) {
return { return {
success: false, success: false,
error: "Você já possui acesso a este pagador.", error: "Você já possui acesso a esta pessoa.",
}; };
} }
@@ -248,7 +248,7 @@ export async function joinPayerByShareCodeAction(
revalidate(user.id); revalidate(user.id);
return { success: true, message: "Payer adicionado à sua lista." }; return { success: true, message: "Pessoa adicionada à sua lista." };
} catch (error) { } catch (error) {
return handleActionError(error); return handleActionError(error);
} }
@@ -313,7 +313,7 @@ export async function regeneratePayerShareCodeAction(
}); });
if (!existing) { if (!existing) {
return { success: false, error: "Payer não encontrado." }; return { success: false, error: "Pessoa não encontrada." };
} }
let attempts = 0; let attempts = 0;

View File

@@ -66,7 +66,7 @@ export function PayerHeaderCard({
const openConfirmDialog = () => { const openConfirmDialog = () => {
if (!payer.email) { if (!payer.email) {
toast.error("Cadastre um e-mail para este pagador antes de enviar."); toast.error("Cadastre um e-mail para esta pessoa antes de enviar.");
return; return;
} }
setConfirmOpen(true); setConfirmOpen(true);
@@ -74,7 +74,7 @@ export function PayerHeaderCard({
const handleSendSummary = () => { const handleSendSummary = () => {
if (!payer.email) { if (!payer.email) {
toast.error("Cadastre um e-mail para este pagador antes de enviar."); toast.error("Cadastre um e-mail para esta pessoa antes de enviar.");
return; return;
} }

View File

@@ -67,7 +67,7 @@ export function PayerHistoryCard({ data }: PagadorHistoryCardProps) {
Evolução (últimos 6 meses) Evolução (últimos 6 meses)
</CardTitle> </CardTitle>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
Despesas registradas para este pagador ao longo do tempo. Despesas registradas para esta pessoa ao longo do tempo.
</p> </p>
</CardHeader> </CardHeader>

View File

@@ -32,7 +32,7 @@ export function PagadorInfoCard({ payer }: PayerInfoCardProps) {
<Card className="border gap-4"> <Card className="border gap-4">
<CardHeader className="gap-1.5"> <CardHeader className="gap-1.5">
<CardTitle className="text-lg font-semibold"> <CardTitle className="text-lg font-semibold">
Detalhes do pagador Detalhes da pessoa
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
{showSensitiveDetails {showSensitiveDetails
@@ -106,7 +106,7 @@ export function PagadorInfoCard({ payer }: PayerInfoCardProps) {
const resolveRoleLabel = (role: string | null) => { const resolveRoleLabel = (role: string | null) => {
if (role === PAYER_ROLE_ADMIN) return "Administrador"; if (role === PAYER_ROLE_ADMIN) return "Administrador";
return "Pagador"; return "Pessoa";
}; };
type InfoItemProps = { type InfoItemProps = {

View File

@@ -89,7 +89,7 @@ export function PayerSharingCard({
</CardTitle> </CardTitle>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Compartilhe o código abaixo com outra pessoa. Ela poderá adicioná-lo Compartilhe o código abaixo com outra pessoa. Ela poderá adicioná-lo
na página de pagadores usando a opção Adicionar por código para ter na página de pessoas usando a opção Adicionar por código para ter
acesso somente leitura. acesso somente leitura.
</p> </p>
</CardHeader> </CardHeader>

View File

@@ -182,7 +182,7 @@ export function PayerDialog({
const payerId = payer?.id; const payerId = payer?.id;
if (mode === "update" && !payerId) { if (mode === "update" && !payerId) {
const message = "Pagador inválido."; const message = "Pessoa inválida.";
setErrorMessage(message); setErrorMessage(message);
toast.error(message); toast.error(message);
return; return;
@@ -216,13 +216,12 @@ export function PayerDialog({
}); });
}; };
const title = mode === "create" ? "Novo pagador" : "Editar pagador"; const title = mode === "create" ? "Nova pessoa" : "Editar pessoa";
const description = const description =
mode === "create" mode === "create"
? "Selecione um avatar e informe os detalhes para criar um novo pagador." ? "Selecione um avatar e informe os detalhes para criar uma nova pessoa."
: "Atualize os detalhes do pagador selecionado."; : "Atualize os detalhes da pessoa selecionada.";
const submitLabel = const submitLabel = mode === "create" ? "Salvar pessoa" : "Atualizar pessoa";
mode === "create" ? "Salvar pagador" : "Atualizar pagador";
const isUploadSelected = const isUploadSelected =
uploadedAvatar !== null && formState.avatarUrl === uploadedAvatar; uploadedAvatar !== null && formState.avatarUrl === uploadedAvatar;
@@ -388,7 +387,7 @@ export function PayerDialog({
id="payer-note" id="payer-note"
value={formState.note} value={formState.note}
onChange={(event) => updateField("note", event.target.value)} onChange={(event) => updateField("note", event.target.value)}
placeholder="Observações sobre este pagador" placeholder="Observações sobre esta pessoa"
/> />
</div> </div>
</div> </div>

View File

@@ -60,7 +60,7 @@ export function PayersPage({ payers, avatarOptions }: PayersPageProps) {
const handleRemoveRequest = (payer: Payer) => { const handleRemoveRequest = (payer: Payer) => {
if (payer.role === PAYER_ROLE_ADMIN) { if (payer.role === PAYER_ROLE_ADMIN) {
toast.error("Pagadores administradores não podem ser removidos."); toast.error("Pessoas administradoras não podem ser removidas.");
return; return;
} }
setPayerToRemove(payer); setPayerToRemove(payer);
@@ -91,8 +91,8 @@ export function PayersPage({ payers, avatarOptions }: PayersPageProps) {
}; };
const removeTitle = payerToRemove const removeTitle = payerToRemove
? `Remover pagador "${payerToRemove.name}"?` ? `Remover pessoa "${payerToRemove.name}"?`
: "Remover pagador?"; : "Remover pessoa?";
const handleJoinByCode = (event: React.FormEvent<HTMLFormElement>) => { const handleJoinByCode = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
@@ -127,7 +127,7 @@ export function PayersPage({ payers, avatarOptions }: PayersPageProps) {
trigger={ trigger={
<Button className="w-full sm:w-auto"> <Button className="w-full sm:w-auto">
<RiAddFill className="size-4" /> <RiAddFill className="size-4" />
Novo pagador Nova pessoa
</Button> </Button>
} }
/> />
@@ -151,7 +151,7 @@ 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 primeiro pagador para organizar cobranças e Cadastre seu primeira pessoa para organizar cobranças e
pagamentos recorrentes. pagamentos recorrentes.
</div> </div>
</div> </div>
@@ -185,8 +185,8 @@ export function PayersPage({ payers, avatarOptions }: PayersPageProps) {
open={removeOpen && !!payerToRemove} open={removeOpen && !!payerToRemove}
onOpenChange={handleRemoveOpenChange} onOpenChange={handleRemoveOpenChange}
title={removeTitle} title={removeTitle}
description="Ao remover este pagador, 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 pagador" confirmLabel="Remover pessoa"
pendingLabel="Removendo..." pendingLabel="Removendo..."
confirmVariant="destructive" confirmVariant="destructive"
onConfirm={handleRemoveConfirm} onConfirm={handleRemoveConfirm}

View File

@@ -19,7 +19,7 @@ import { formatDateTime } from "@/shared/utils/date";
import { displayPeriod } from "@/shared/utils/period"; import { displayPeriod } from "@/shared/utils/period";
const inputSchema = z.object({ const inputSchema = z.object({
payerId: z.string().uuid("Payer inválido."), payerId: z.string().uuid("Pessoa inválida."),
period: z period: z
.string() .string()
.regex(/^\d{4}-\d{2}$/, "Período inválido. Informe no formato AAAA-MM."), .regex(/^\d{4}-\d{2}$/, "Período inválido. Informe no formato AAAA-MM."),
@@ -404,7 +404,7 @@ export async function sendPayerSummaryAction(
}); });
if (!pagadorRow) { if (!pagadorRow) {
return { success: false, error: "Pagador não encontrado." }; return { success: false, error: "Pessoa não encontrada." };
} }
if (!pagadorRow.email) { if (!pagadorRow.email) {

View File

@@ -81,7 +81,7 @@ async function resetUserAppData(
const payerName = const payerName =
(user.name && user.name.trim().length > 0 (user.name && user.name.trim().length > 0
? user.name.trim() ? user.name.trim()
: normalizeNameFromEmail(user.email)) || "Payer principal"; : normalizeNameFromEmail(user.email)) || "Pessoa principal";
const avatarUrl = user.image ?? DEFAULT_PAYER_AVATAR; const avatarUrl = user.image ?? DEFAULT_PAYER_AVATAR;
const defaultPayerStatus = PAYER_STATUS_OPTIONS[0]; const defaultPayerStatus = PAYER_STATUS_OPTIONS[0];
@@ -176,7 +176,7 @@ export async function updateNameAction(
.set({ name: fullName }) .set({ name: fullName })
.where(eq(schema.user.id, session.user.id)); .where(eq(schema.user.id, session.user.id));
// Sincronizar nome com o pagador admin // Sincronizar nome com o pessoa admin
if (adminPayerId) { if (adminPayerId) {
await db await db
.update(payers) .update(payers)

View File

@@ -94,12 +94,12 @@ export function DeleteAccountForm() {
<ul className="list-disc list-inside text-sm text-muted-foreground space-y-1"> <ul className="list-disc list-inside text-sm text-muted-foreground space-y-1">
<li>Lançamentos, faturas, antecipações e pré-lançamentos</li> <li>Lançamentos, faturas, antecipações e pré-lançamentos</li>
<li>Contas, cartões, orçamentos e anotações</li> <li>Contas, cartões, orçamentos e anotações</li>
<li>Pagadores próprios e compartilhamentos recebidos</li> <li>Pessoas próprios e compartilhamentos recebidos</li>
<li> <li>
Preferências do app, insights salvos e tokens do Companion Preferências do app, insights salvos e tokens do Companion
</li> </li>
<li className="font-medium text-foreground"> <li className="font-medium text-foreground">
Categorias padrão e pagador admin serão recriados Categorias padrão e pessoa admin serão recriadas
automaticamente automaticamente
</li> </li>
</ul> </ul>
@@ -130,7 +130,7 @@ export function DeleteAccountForm() {
<ul className="list-disc list-inside text-sm text-destructive space-y-1"> <ul className="list-disc list-inside text-sm text-destructive space-y-1">
<li>Lançamentos, orçamentos e anotações</li> <li>Lançamentos, orçamentos e anotações</li>
<li>Contas, cartões e categorias</li> <li>Contas, cartões e categorias</li>
<li>Pagadores, credenciais e configurações</li> <li>Pessoas, credenciais e configurações</li>
<li className="font-medium"> <li className="font-medium">
Resumindo, sua conta irá de arrasta pra cima! Resumindo, sua conta irá de arrasta pra cima!
</li> </li>

View File

@@ -602,7 +602,7 @@ export async function createMassTransactionsAction(
if (transaction.payerId && invalidPayers.has(transaction.payerId)) { if (transaction.payerId && invalidPayers.has(transaction.payerId)) {
return { return {
success: false, success: false,
error: `Payer não encontrado na transação ${i + 1}.`, error: `Pessoa não encontrado na transação ${i + 1}.`,
}; };
} }
if ( if (
@@ -611,7 +611,7 @@ export async function createMassTransactionsAction(
) { ) {
return { return {
success: false, success: false,
error: `Category não encontrada na transação ${i + 1}.`, error: `Categoria não encontrada na transação ${i + 1}.`,
}; };
} }
} }

View File

@@ -189,8 +189,8 @@ export async function validateAllOwnership(
]; ];
const errors = [ const errors = [
"Pagador não encontrado ou sem permissão.", "Pessoa não encontrada ou sem permissão.",
"Pagador secundário não encontrado ou sem permissão.", "Pessoa secundário não encontrado ou sem permissão.",
"Categoria não encontrada.", "Categoria não encontrada.",
"Conta não encontrada.", "Conta não encontrada.",
"Cartão não encontrado.", "Cartão não encontrado.",
@@ -359,7 +359,7 @@ const refineLancamento = (
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
path: ["payerId"], path: ["payerId"],
message: "Selecione o pagador principal para dividir o lançamento.", message: "Selecione a pessoa principal para dividir o lançamento.",
}); });
} }
@@ -367,13 +367,13 @@ const refineLancamento = (
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
path: ["secondaryPayerId"], path: ["secondaryPayerId"],
message: "Selecione o pagador secundário para dividir o lançamento.", message: "Selecione a pessoa secundário para dividir o lançamento.",
}); });
} else if (data.payerId && data.secondaryPayerId === data.payerId) { } else if (data.payerId && data.secondaryPayerId === data.payerId) {
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
path: ["secondaryPayerId"], path: ["secondaryPayerId"],
message: "Escolha um pagador diferente para dividir o lançamento.", message: "Escolha uma pessoa diferente para dividir o lançamento.",
}); });
} }

View File

@@ -84,7 +84,7 @@ export async function importTransactionsAction(
validateCartaoOwnership(userId, cardId), validateCartaoOwnership(userId, cardId),
]); ]);
if (!payerOk) return { success: false, error: "Pagador não encontrado." }; if (!payerOk) return { success: false, error: "Pessoa não encontrada." };
if (!accountOk) return { success: false, error: "Conta não encontrada." }; if (!accountOk) return { success: false, error: "Conta não encontrada." };
if (!cardOk) return { success: false, error: "Cartão não encontrado." }; if (!cardOk) return { success: false, error: "Cartão não encontrado." };

View File

@@ -159,7 +159,7 @@ export async function createInstallmentAnticipationAction(
if (data.payerId && payer.length === 0) { if (data.payerId && payer.length === 0) {
return { return {
success: false, success: false,
error: "Pagador inválido para esta conta.", error: "Pessoa inválida para esta conta.",
}; };
} }

View File

@@ -21,7 +21,7 @@ export const LANCAMENTOS_COLUMN_LABELS: Record<string, string> = {
condition: "Condição", condition: "Condição",
paymentMethod: "Forma de Pagamento", paymentMethod: "Forma de Pagamento",
categoriaName: "Categoria", categoriaName: "Categoria",
pagadorName: "Pagador", pagadorName: "Pessoa",
note: "Anotação", note: "Anotação",
contaCartao: "Conta/Cartão", contaCartao: "Conta/Cartão",
}; };

View File

@@ -269,7 +269,7 @@ export function AnticipateInstallmentsDialog({
</Field> </Field>
<Field className="gap-1"> <Field className="gap-1">
<FieldLabel htmlFor="anticipation-pagador">Pagador</FieldLabel> <FieldLabel htmlFor="anticipation-pagador">Pessoa</FieldLabel>
<FieldContent> <FieldContent>
<Select <Select
value={formState.payerId} value={formState.payerId}

View File

@@ -116,7 +116,7 @@ export function BulkActionDialog({
htmlFor="period" htmlFor="period"
className="text-sm cursor-pointer font-medium" className="text-sm cursor-pointer font-medium"
> >
Todos os pagadores deste período Todas as pessoas deste período
</Label> </Label>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
Aplica a todos os lançamentos deste mesmo mês na série Aplica a todos os lançamentos deste mesmo mês na série
@@ -125,7 +125,7 @@ export function BulkActionDialog({
<div className="mt-1.5 flex items-start gap-1.5 rounded-md bg-amber-50 px-2 py-1.5 text-amber-800 dark:bg-amber-950/40 dark:text-amber-300"> <div className="mt-1.5 flex items-start gap-1.5 rounded-md bg-amber-50 px-2 py-1.5 text-amber-800 dark:bg-amber-950/40 dark:text-amber-300">
<RiErrorWarningLine className="mt-0.5 size-3.5 shrink-0" /> <RiErrorWarningLine className="mt-0.5 size-3.5 shrink-0" />
<p className="text-xs"> <p className="text-xs">
Atenção: os valores individuais de cada pagador serão Atenção: os valores individuais de cada pessoa serão
substituídos pelos valores deste lançamento. substituídos pelos valores deste lançamento.
</p> </p>
</div> </div>

View File

@@ -90,7 +90,7 @@ export function BulkImportDialog({
event.preventDefault(); event.preventDefault();
if (!payerId) { if (!payerId) {
toast.error("Selecione o pagador."); toast.error("Selecione a pessoa.");
return; return;
} }
@@ -197,16 +197,16 @@ export function BulkImportDialog({
<DialogDescription> <DialogDescription>
Importando {itemCount}{" "} Importando {itemCount}{" "}
{itemCount === 1 ? "lançamento" : "lançamentos"}. Selecione o {itemCount === 1 ? "lançamento" : "lançamentos"}. Selecione o
pagador, categoria e forma de pagamento para aplicar a todos. pessoa, categoria e forma de pagamento para aplicar a todos.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<form className="space-y-4" onSubmit={handleSubmit}> <form className="space-y-4" onSubmit={handleSubmit}>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="pagador">Pagador *</Label> <Label htmlFor="pagador">Pessoa *</Label>
<Select value={payerId} onValueChange={setPagadorId}> <Select value={payerId} onValueChange={setPagadorId}>
<SelectTrigger id="pagador" className="w-full"> <SelectTrigger id="pagador" className="w-full">
<SelectValue placeholder="Selecione o pagador"> <SelectValue placeholder="Selecione a pessoa">
{payerId && {payerId &&
(() => { (() => {
const selectedOption = payerOptions.find( const selectedOption = payerOptions.find(

View File

@@ -525,7 +525,7 @@ export function MassAddDialog({
htmlFor={`pagador-${transaction.id}`} htmlFor={`pagador-${transaction.id}`}
className="sr-only" className="sr-only"
> >
Pagador {index + 1} Pessoa {index + 1}
</Label> </Label>
<Select <Select
value={transaction.payerId} value={transaction.payerId}
@@ -537,7 +537,7 @@ export function MassAddDialog({
id={`pagador-${transaction.id}`} id={`pagador-${transaction.id}`}
className="w-32 truncate" className="w-32 truncate"
> >
<SelectValue placeholder="Pagador"> <SelectValue placeholder="Pessoa">
{transaction.payerId && {transaction.payerId &&
(() => { (() => {
const selectedOption = payerOptions.find( const selectedOption = payerOptions.find(

View File

@@ -50,7 +50,7 @@ export function PayerSection({
<div> <div>
<p className="text-sm text-foreground">Dividir lançamento</p> <p className="text-sm text-foreground">Dividir lançamento</p>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
Atribuir parte do valor a outro pagador. Atribuir parte do valor a outra pessoa.
</p> </p>
</div> </div>
</div> </div>
@@ -75,7 +75,7 @@ export function PayerSection({
<div className="flex w-full flex-col gap-2 md:flex-row"> <div className="flex w-full flex-col gap-2 md:flex-row">
<div className="w-full space-y-1"> <div className="w-full space-y-1">
<Label htmlFor="payer">Pagador</Label> <Label htmlFor="payer">Pessoa</Label>
<div className="flex gap-2"> <div className="flex gap-2">
<Select <Select
value={formState.payerId ?? ""} value={formState.payerId ?? ""}

View File

@@ -230,7 +230,7 @@ export function TransactionDialog({
if (formState.isSplit && !formState.payerId) { if (formState.isSplit && !formState.payerId) {
const message = const message =
"Selecione o pagador 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;
@@ -238,7 +238,7 @@ export function TransactionDialog({
if (formState.isSplit && !formState.secondaryPayerId) { if (formState.isSplit && !formState.secondaryPayerId) {
const message = const message =
"Selecione o pagador secundário para dividir o lançamento."; "Selecione a pessoa secundário para dividir o lançamento.";
setErrorMessage(message); setErrorMessage(message);
toast.error(message); toast.error(message);
return; return;
@@ -464,7 +464,7 @@ export function TransactionDialog({
const description = const description =
mode === "create" mode === "create"
? isImportMode ? isImportMode
? "Importando lançamento de outro usuário. Ajuste a categoria, pagador e cartão/conta antes de salvar." ? "Importando lançamento de outro usuário. Ajuste a categoria, pessoa e cartão/conta antes de salvar."
: isCopyMode : isCopyMode
? "Os dados do lançamento foram copiados. Revise e ajuste conforme necessário antes de salvar." ? "Os dados do lançamento foram copiados. Revise e ajuste conforme necessário antes de salvar."
: isNewWithType : isNewWithType
@@ -519,7 +519,7 @@ export function TransactionDialog({
<div className="border-t border-border/40 my-3" /> <div className="border-t border-border/40 my-3" />
{/* Pagador */} {/* Pessoa */}
<PayerSection <PayerSection
formState={formState} formState={formState}
onFieldChange={handleFieldChange} onFieldChange={handleFieldChange}

View File

@@ -123,13 +123,13 @@ export function GlobalFields({
</div> </div>
<div className="flex min-w-44 flex-col gap-1.5"> <div className="flex min-w-44 flex-col gap-1.5">
<Label>Pagador</Label> <Label>Pessoa</Label>
<Select <Select
value={payerId ?? ""} value={payerId ?? ""}
onValueChange={(v) => onPayerChange(v || null)} onValueChange={(v) => onPayerChange(v || null)}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Selecionar pagador…" /> <SelectValue placeholder="Selecionar pessoa…" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{payerOptions.map((opt) => ( {payerOptions.map((opt) => (

View File

@@ -134,7 +134,7 @@ export function AnticipationCard({
{anticipation.payer && ( {anticipation.payer && (
<div> <div>
<dt className="text-muted-foreground">Pagador</dt> <dt className="text-muted-foreground">Pessoa</dt>
<dd className="mt-1 font-medium">{anticipation.payer.name}</dd> <dd className="mt-1 font-medium">{anticipation.payer.name}</dd>
</div> </div>
)} )}

View File

@@ -229,12 +229,12 @@ function buildColumns({
aria-hidden aria-hidden
/> />
<span className="sr-only"> <span className="sr-only">
Dividido entre pagadores Dividido entre pessoas
</span> </span>
</span> </span>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="top"> <TooltipContent side="top">
Dividido entre pagadores Dividido entre pessoas
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
)} )}
@@ -408,10 +408,10 @@ function buildColumns({
}, },
{ {
accessorKey: "pagadorName", accessorKey: "pagadorName",
header: "Pagador", header: "Pessoa",
cell: ({ row }) => { cell: ({ row }) => {
const { payerId, pagadorName, pagadorAvatar } = row.original; const { payerId, pagadorName, pagadorAvatar } = row.original;
const label = pagadorName?.trim() || "Sem pagador"; const label = pagadorName?.trim() || "Sem pessoa";
const displayName = label.split(/\s+/)[0] ?? label; const displayName = label.split(/\s+/)[0] ?? label;
const avatarSrc = getAvatarSrc(pagadorAvatar); const avatarSrc = getAvatarSrc(pagadorAvatar);
const initial = displayName.charAt(0).toUpperCase() || "?"; const initial = displayName.charAt(0).toUpperCase() || "?";

View File

@@ -386,7 +386,7 @@ export function TransactionsFilters({
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">Pagador</label> <label className="text-sm font-medium">Pessoa</label>
<Select <Select
value={getParamValue("payer")} value={getParamValue("payer")}
onValueChange={(value) => onValueChange={(value) =>

View File

@@ -109,7 +109,7 @@ export function TransactionsExport({
"Valor", "Valor",
"Category", "Category",
"Conta/Cartão", "Conta/Cartão",
"Payer", "Pessoa",
]; ];
const rows: string[][] = []; const rows: string[][] = [];
@@ -169,7 +169,7 @@ export function TransactionsExport({
"Valor", "Valor",
"Category", "Category",
"Conta/Cartão", "Conta/Cartão",
"Payer", "Pessoa",
]; ];
const rows: (string | number)[][] = []; const rows: (string | number)[][] = [];
@@ -277,7 +277,7 @@ export function TransactionsExport({
"Valor", "Valor",
"Categoria", "Categoria",
"Conta/Cartão", "Conta/Cartão",
"Payer", "Pessoa",
], ],
]; ];
@@ -317,7 +317,7 @@ export function TransactionsExport({
5: { cellWidth: 24 }, // Valor 5: { cellWidth: 24 }, // Valor
6: { cellWidth: 30 }, // Categoria 6: { cellWidth: 30 }, // Categoria
7: { cellWidth: 30 }, // Conta/Cartão 7: { cellWidth: 30 }, // Conta/Cartão
8: { cellWidth: 31 }, // Payer 8: { cellWidth: 31 }, // Pessoa
}, },
didParseCell: (cellData) => { didParseCell: (cellData) => {
if (cellData.section === "body" && cellData.column.index === 5) { if (cellData.section === "body" && cellData.column.index === 5) {

View File

@@ -92,7 +92,7 @@ export const NAV_SECTIONS: NavSection[] = [
items: [ items: [
{ {
href: "/payers", href: "/payers",
label: "Pagadores", label: "Pessoas",
description: "Gerencie quem divide as despesas", description: "Gerencie quem divide as despesas",
icon: <RiGroupLine className="size-4" />, icon: <RiGroupLine className="size-4" />,
iconClass: "text-primary", iconClass: "text-primary",

View File

@@ -24,7 +24,7 @@ export function TransactionsTableSkeleton() {
<TableHead className="w-[120px]">Valor</TableHead> <TableHead className="w-[120px]">Valor</TableHead>
<TableHead className="w-[120px]">Condição</TableHead> <TableHead className="w-[120px]">Condição</TableHead>
<TableHead className="w-[120px]">Pagamento</TableHead> <TableHead className="w-[120px]">Pagamento</TableHead>
<TableHead className="w-[140px]">Pagador</TableHead> <TableHead className="w-[140px]">Pessoa</TableHead>
<TableHead className="w-[140px]">Categoria</TableHead> <TableHead className="w-[140px]">Categoria</TableHead>
<TableHead className="w-[140px]">Conta/Cartão</TableHead> <TableHead className="w-[140px]">Conta/Cartão</TableHead>
<TableHead className="w-[80px]">Ações</TableHead> <TableHead className="w-[80px]">Ações</TableHead>

View File

@@ -36,7 +36,7 @@ export async function ensureDefaultPagadorForUser(user: SeedUserLike) {
const name = const name =
(user.name && user.name.trim().length > 0 (user.name && user.name.trim().length > 0
? user.name.trim() ? user.name.trim()
: normalizeNameFromEmail(user.email)) || "Payer principal"; : normalizeNameFromEmail(user.email)) || "Pessoa principal";
// Usa a imagem do Google se disponível, senão usa o avatar padrão // Usa a imagem do Google se disponível, senão usa o avatar padrão
const avatarUrl = user.image ?? DEFAULT_PAYER_AVATAR; const avatarUrl = user.image ?? DEFAULT_PAYER_AVATAR;

View File

@@ -53,11 +53,11 @@ export const normalizeNameFromEmail = (
email: string | null | undefined, email: string | null | undefined,
): string => { ): string => {
if (!email) { if (!email) {
return "Novo pagador"; return "Nova pessoa";
} }
const [local] = email.split("@"); const [local] = email.split("@");
if (!local) { if (!local) {
return "Novo pagador"; return "Nova pessoa";
} }
return local return local
.split(".") .split(".")