feat(pagadores): adicionar widget no dashboard e atualizar avatares

- Novo widget de pagadores no dashboard com resumo de transações
- Substituir avatares SVG por PNG com melhor qualidade
- Melhorar seção de pagador no diálogo de lançamentos
- Adicionar ação para buscar pagadores por nome
- Atualizar componentes de seleção (cartões, categorias, contas)
- Melhorias no layout de ajustes e relatórios

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-02-04 01:44:50 +00:00
parent 76702d770f
commit a70a83dd9d
61 changed files with 509 additions and 148 deletions

View File

@@ -7,6 +7,11 @@ import {
LANCAMENTO_TRANSACTION_TYPES,
} from "./constants";
/**
* Split type for dividing transactions between payers
*/
export type SplitType = "equal" | "60-40" | "70-30" | "80-20" | "custom";
/**
* Form state type for lancamento dialog
*/
@@ -21,6 +26,9 @@ export type LancamentoFormState = {
pagadorId: string | undefined;
secondaryPagadorId: string | undefined;
isSplit: boolean;
splitType: SplitType;
primarySplitAmount: string;
secondarySplitAmount: string;
contaId: string | undefined;
cartaoId: string | undefined;
categoriaId: string | undefined;
@@ -115,6 +123,9 @@ export function buildLancamentoInitialState(
pagadorId: fallbackPagadorId ?? undefined,
secondaryPagadorId: undefined,
isSplit: false,
splitType: "equal",
primarySplitAmount: "",
secondarySplitAmount: "",
contaId:
paymentMethod === "Cartão de crédito"
? undefined
@@ -146,6 +157,39 @@ export function buildLancamentoInitialState(
};
}
/**
* Split presets with their percentages
*/
const SPLIT_PRESETS: Record<SplitType, { primary: number; secondary: number }> =
{
equal: { primary: 50, secondary: 50 },
"60-40": { primary: 60, secondary: 40 },
"70-30": { primary: 70, secondary: 30 },
"80-20": { primary: 80, secondary: 20 },
custom: { primary: 50, secondary: 50 },
};
/**
* Calculates split amounts based on total and split type
*/
export function calculateSplitAmounts(
totalAmount: number,
splitType: SplitType,
): { primary: string; secondary: string } {
if (totalAmount <= 0) {
return { primary: "", secondary: "" };
}
const preset = SPLIT_PRESETS[splitType];
const primaryAmount = (totalAmount * preset.primary) / 100;
const secondaryAmount = totalAmount - primaryAmount;
return {
primary: primaryAmount.toFixed(2),
secondary: secondaryAmount.toFixed(2),
};
}
/**
* Applies field dependencies when form state changes
* This function encapsulates the business logic for field interdependencies
@@ -202,9 +246,38 @@ export function applyFieldDependencies(
}
}
// When split is disabled, clear secondary pagador
// When split is disabled, clear secondary pagador and split fields
if (key === "isSplit" && value === false) {
updates.secondaryPagadorId = undefined;
updates.splitType = "equal";
updates.primarySplitAmount = "";
updates.secondarySplitAmount = "";
}
// When split is enabled and amount exists, calculate initial split amounts
if (key === "isSplit" && value === true) {
const totalAmount = Number.parseFloat(currentState.amount) || 0;
if (totalAmount > 0) {
const half = (totalAmount / 2).toFixed(2);
updates.primarySplitAmount = half;
updates.secondarySplitAmount = half;
}
}
// When amount changes and split is enabled, recalculate split amounts
if (key === "amount" && typeof value === "string" && currentState.isSplit) {
const totalAmount = Number.parseFloat(value) || 0;
if (totalAmount > 0) {
const splitAmounts = calculateSplitAmounts(
totalAmount,
currentState.splitType,
);
updates.primarySplitAmount = splitAmounts.primary;
updates.secondarySplitAmount = splitAmounts.secondary;
} else {
updates.primarySplitAmount = "";
updates.secondarySplitAmount = "";
}
}
// When primary pagador changes, clear secondary if it matches