refactor: faxina arquitetural — código morto, identificadores em inglês e estrutura padronizada

Refatoração estrutural sem mudanças funcionais. Saldo líquido: −428 linhas.

Removido:
- 14 funções/constantes mortas verificadas via grep no repo todo: validateCategoriaOwnership,
  getInstallmentAnticipationsAction, getAnticipationDetailsAction, formatDecimalForDb,
  currencyFormatterNoCents, optionalDecimalSchema, formatMonthLabel,
  getGoalProgressStatusColorClass, MONTH_PERIOD_PARAM, calculateRemainingInstallments,
  e 5 funções fetch* não usadas em inbox/queries.ts.
- 1 tipo morto (ImportRow) + 2 órfãos consequentes (InstallmentAnticipationWithRelations,
  GoalProgressStatus convertido em interno).
- ~30 export keywords desnecessários (símbolos usados apenas no próprio arquivo).
- Re-exports mortos em barrels: EstablishmentLogoPicker, CategoryReportSkeleton,
  WidgetSkeleton, toNameKey.
- Arquivo features/reports/types.ts (barrel inteiro era órfão).

Padronizado (PT-BR→EN em identificadores expostos):
- 4 constantes globais (LANCAMENTOS_* → TRANSACTIONS_*).
- 12 tipos/interfaces (Lancamento*/Pagador*/Estabelecimento* → equivalentes EN).
- 13 funções/components exportados (fetchPagador*, EstabelecimentoInput, PagadorInfoCard, etc.).
- 5 props cross-file (preLancamentosCount → inboxPendingCount, pagadorAvatarUrl → payerAvatarUrl, etc.).
- Mantidas em PT-BR conforme exceção do CLAUDE.md: variáveis locais (pagador, categoria,
  lancamento), accessor key pagadorName (persistida em preferências), strings de UI.

Reorganizado:
- transactions/: 14 helpers soltos na raiz movidos para lib/; barrel actions.ts reduzido
  de 76 linhas de wrappers para 14 linhas de re-exports puros; anticipation-actions.ts
  movido para actions/anticipation.ts.
- dashboard/: 8 helpers soltos consolidados em dashboard/lib/.
- reports/: 5 query files na raiz consolidados em reports/lib/.
- payers/: detail-actions.ts (21KB) e detail-queries.ts movidos para payers/lib/.
- shared/components/: 9 dos 16 componentes soltos agrupados em brand/, widgets/, feedback/.
- shared/lib/fetch-json.ts movido para shared/utils/fetch-json.ts.

Validação: pnpm exec tsc --noEmit (0 erros), biome check (0 issues), knip (sem unused).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-05-06 18:42:54 +00:00
parent b9b843b9db
commit 7d0781b035
229 changed files with 415 additions and 872 deletions

View File

@@ -2,7 +2,7 @@ import {
PAYMENT_METHODS,
TRANSACTION_CONDITIONS,
TRANSACTION_TYPES,
} from "@/features/transactions/constants";
} from "@/features/transactions/lib/constants";
export const INITIAL_BALANCE_CATEGORY_NAME = "Saldo inicial";
export const INITIAL_BALANCE_NOTE = "saldo inicial";

View File

@@ -23,7 +23,7 @@ export function handleActionError(error: unknown): ActionResult {
/**
* Configuration for revalidation after mutations
*/
export const revalidateConfig = {
const revalidateConfig = {
cards: ["/cards", "/accounts", "/transactions"],
accounts: ["/accounts", "/transactions"],
categories: ["/categories"],

View File

@@ -4,7 +4,7 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle";
import type { GoogleProfile } from "better-auth/social-providers";
import { seedDefaultCategoriesForUser } from "@/shared/lib/categories/defaults";
import { db, schema } from "@/shared/lib/db";
import { ensureDefaultPagadorForUser } from "@/shared/lib/payers/defaults";
import { ensureDefaultPayerForUser } from "@/shared/lib/payers/defaults";
import { normalizeNameFromEmail } from "@/shared/lib/payers/utils";
// ============================================================================
@@ -131,7 +131,7 @@ export const auth = betterAuth({
// Se falhar aqui, o usuário já foi criado - considere usar queue para retry
try {
await seedDefaultCategoriesForUser(user.id);
await ensureDefaultPagadorForUser({
await ensureDefaultPayerForUser({
id: user.id,
name: user.name ?? undefined,
email: user.email ?? undefined,

View File

@@ -5,12 +5,12 @@
* - /lib/category-icons.ts
*/
export type CategoryIconOption = {
type CategoryIconOption = {
label: string;
value: string;
};
export const CATEGORY_ICON_OPTIONS: CategoryIconOption[] = [
const CATEGORY_ICON_OPTIONS: CategoryIconOption[] = [
// Finanças
{ label: "Dinheiro", value: "RiMoneyDollarCircleLine" },
{ label: "Carteira", value: "RiWallet3Line" },
@@ -156,7 +156,7 @@ export const CATEGORY_ICON_OPTIONS: CategoryIconOption[] = [
{ label: "Nuvem Upload", value: "RiCloudUploadLine" },
];
export type CategoryIconGroup = {
type CategoryIconGroup = {
label: string;
icons: CategoryIconOption[];
};

View File

@@ -1,26 +0,0 @@
export async function fetchJson<T>(
input: RequestInfo | URL,
init?: RequestInit,
): Promise<T> {
const response = await fetch(input, {
cache: "no-store",
...init,
});
if (!response.ok) {
let message = `Erro na requisição (${response.status})`;
try {
const payload = (await response.json()) as { error?: string };
if (payload.error) {
message = payload.error;
}
} catch {
// noop
}
throw new Error(message);
}
return response.json() as Promise<T>;
}

View File

@@ -4,7 +4,7 @@ import type { EligibleInstallment } from "./anticipation-types";
* Formata o resumo de parcelas antecipadas
* Exemplo: "Parcelas 1-3 de 12" ou "Parcela 5 de 12"
*/
export function formatAnticipatedInstallmentsRange(
function formatAnticipatedInstallmentsRange(
installments: EligibleInstallment[],
): string {
const numbers = installments
@@ -35,16 +35,6 @@ export function formatAnticipatedInstallmentsRange(
}
}
/**
* Calcula quantas parcelas restam após uma antecipação
*/
export function calculateRemainingInstallments(
totalInstallments: number,
anticipatedCount: number,
): number {
return Math.max(0, totalInstallments - anticipatedCount);
}
/**
* Gera descrição automática para o lançamento de antecipação
*/

View File

@@ -1,10 +1,3 @@
import type {
Category,
InstallmentAnticipation,
Payer,
Transaction,
} from "@/db/schema";
/**
* Parcela elegível para antecipação
*/
@@ -22,15 +15,6 @@ export type EligibleInstallment = {
payerId: string | null;
};
/**
* Antecipação com dados completos
*/
export type InstallmentAnticipationWithRelations = InstallmentAnticipation & {
transaction: Transaction | null;
payer: Payer | null;
category: Category | null;
};
/**
* Input para criar antecipação
*/

View File

@@ -3,8 +3,6 @@ import { establishmentLogos } from "@/db/schema";
import { db } from "@/shared/lib/db";
import { toNameKey } from "@/shared/lib/logo";
export { toNameKey };
/**
* Busca o domínio salvo para um único estabelecimento.
*/

View File

@@ -2,7 +2,7 @@ import { and, eq } from "drizzle-orm";
import { payerShares, payers, user as usersTable } from "@/db/schema";
import { db } from "@/shared/lib/db";
export type PayerWithAccess = Omit<typeof payers.$inferSelect, "shareCode"> & {
type PayerWithAccess = Omit<typeof payers.$inferSelect, "shareCode"> & {
shareCode: string | null;
canEdit: boolean;
sharedByName: string | null;

View File

@@ -18,7 +18,7 @@ interface SeedUserLike {
image?: string | null;
}
export async function ensureDefaultPagadorForUser(user: SeedUserLike) {
export async function ensureDefaultPayerForUser(user: SeedUserLike) {
const userId = user.id;
if (!userId) {

View File

@@ -47,7 +47,7 @@ export type PayerCardUsageItem = {
amount: number;
};
export type PayerBoletoStats = {
type PayerBoletoStats = {
totalAmount: number;
paidAmount: number;
pendingAmount: number;
@@ -196,7 +196,7 @@ export async function fetchPayerHistory({
}));
}
export async function fetchPagadorCardUsage({
export async function fetchPayerCardUsage({
userId,
payerId,
period,
@@ -239,7 +239,7 @@ export async function fetchPagadorCardUsage({
return items.sort((a, b) => b.amount - a.amount);
}
export async function fetchPagadorBoletoStats({
export async function fetchPayerBoletoStats({
userId,
payerId,
period,
@@ -288,7 +288,7 @@ export async function fetchPagadorBoletoStats({
};
}
export async function fetchPagadorBoletoItems({
export async function fetchPayerBoletoItems({
userId,
payerId,
period,
@@ -330,7 +330,7 @@ export async function fetchPagadorBoletoItems({
return items;
}
export async function fetchPagadorPaymentStatus({
export async function fetchPayerPaymentStatus({
userId,
payerId,
period,

View File

@@ -8,7 +8,7 @@ import { formatDateTime } from "@/shared/utils/date";
type ActionType = "created" | "deleted";
export type NotificationEntry = {
type NotificationEntry = {
payerId: string;
name: string | null;
amount: number;
@@ -20,10 +20,10 @@ export type NotificationEntry = {
note: string | null;
};
export type PayerNotificationRequest = {
type PayerNotificationRequest = {
userLabel: string;
action: ActionType;
entriesByPagador: Map<string, NotificationEntry[]>;
entriesByPayer: Map<string, NotificationEntry[]>;
};
type PayerNotificationRecipient = {
@@ -113,11 +113,11 @@ const buildHtmlBody = ({
export async function sendPayerAutoEmails({
userLabel,
action,
entriesByPagador,
entriesByPayer,
}: PayerNotificationRequest) {
"use server";
if (entriesByPagador.size === 0) {
if (entriesByPayer.size === 0) {
return;
}
@@ -131,7 +131,7 @@ export async function sendPayerAutoEmails({
return;
}
const pagadorIds = Array.from(entriesByPagador.keys());
const pagadorIds = Array.from(entriesByPayer.keys());
if (pagadorIds.length === 0) {
return;
}
@@ -154,7 +154,7 @@ export async function sendPayerAutoEmails({
return;
}
const entries = entriesByPagador.get(payer.id);
const entries = entriesByPayer.get(payer.id);
if (!entries || entries.length === 0) {
return;
}
@@ -186,7 +186,7 @@ export async function sendPayerAutoEmails({
});
}
export type RawNotificationRecord = {
type RawNotificationRecord = {
payerId: string | null;
name: string | null;
amount: string | number | null;

View File

@@ -12,25 +12,6 @@ export const uuidSchema = (entityName: string = "ID") =>
.string({ message: `${entityName} inválido.` })
.uuid(`${entityName} inválido.`);
/**
* Optional/nullable decimal string schema
*/
export const optionalDecimalSchema = z.union([
z.number().nullable(),
z
.string()
.trim()
.optional()
.transform((value) =>
value && value.length > 0 ? value.replace(",", ".") : null,
)
.refine(
(value) => value === null || !Number.isNaN(Number.parseFloat(value)),
"Informe um valor numérico válido.",
)
.transform((value) => (value === null ? null : Number.parseFloat(value))),
]);
/**
* Required positive decimal schema — accepts number or numeric string.
*/

View File

@@ -35,14 +35,14 @@ export type InsightCategoryId = keyof typeof INSIGHT_CATEGORIES;
/**
* Schema para item individual de insight
*/
export const InsightItemSchema = z.object({
const InsightItemSchema = z.object({
text: z.string().min(1),
});
/**
* Schema para categoria de insights
*/
export const InsightCategorySchema = z.object({
const InsightCategorySchema = z.object({
category: z.enum([
"behaviors",
"triggers",

View File

@@ -1,8 +1,8 @@
export type NotificationType = "overdue" | "due_soon";
type NotificationType = "overdue" | "due_soon";
export type BudgetStatus = "exceeded" | "critical";
type BudgetStatus = "exceeded" | "critical";
export type DashboardNotificationStateFields = {
type DashboardNotificationStateFields = {
notificationKey: string;
fingerprint: string;
href: string;