feat(lancamentos): separar botões Nova Receita e Nova Despesa

- Substituir botão único "Novo lançamento" por dois botões separados
- Adicionar ícones coloridos (verde para Receita, vermelho para Despesa)
- Adicionar suporte a defaultTransactionType no dialog
- Atualizar título e descrição do dialog conforme tipo selecionado
- Ocultar campo de tipo de transação quando tipo é pré-definido

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-01-20 13:43:00 +00:00
parent 9b08a8e020
commit 478bd0c267
5 changed files with 126 additions and 65 deletions

View File

@@ -24,10 +24,13 @@ export function CategorySection({
categoriaOptions,
categoriaGroups,
isUpdateMode,
hideTransactionType = false,
}: CategorySectionProps) {
const showTransactionTypeField = !isUpdateMode && !hideTransactionType;
return (
<div className="flex w-full flex-col gap-2 md:flex-row">
{!isUpdateMode ? (
{showTransactionTypeField ? (
<div className="w-full space-y-1 md:w-1/2">
<Label htmlFor="transactionType">Tipo de transação</Label>
<Select
@@ -45,7 +48,7 @@ export function CategorySection({
</SelectTrigger>
<SelectContent>
{LANCAMENTO_TRANSACTION_TYPES.filter(
(type) => type !== "Transferência"
(type) => type !== "Transferência",
).map((type) => (
<SelectItem key={type} value={type}>
<TransactionTypeSelectContent label={type} />
@@ -59,7 +62,7 @@ export function CategorySection({
<div
className={cn(
"space-y-1 w-full",
!isUpdateMode ? "md:w-1/2" : "md:w-full"
showTransactionTypeField ? "md:w-1/2" : "md:w-full",
)}
>
<Label htmlFor="categoria">Categoria</Label>
@@ -72,7 +75,7 @@ export function CategorySection({
{formState.categoriaId &&
(() => {
const selectedOption = categoriaOptions.find(
(opt) => opt.value === formState.categoriaId
(opt) => opt.value === formState.categoriaId,
);
return selectedOption ? (
<CategoriaSelectContent

View File

@@ -23,6 +23,7 @@ export interface LancamentoDialogProps {
lockCartaoSelection?: boolean;
lockPaymentMethod?: boolean;
isImporting?: boolean;
defaultTransactionType?: "Despesa" | "Receita";
onBulkEditRequest?: (data: {
id: string;
name: string;
@@ -41,7 +42,7 @@ export interface BaseFieldSectionProps {
formState: FormState;
onFieldChange: <Key extends keyof FormState>(
key: Key,
value: FormState[Key]
value: FormState[Key],
) => void;
}
@@ -56,6 +57,7 @@ export interface CategorySectionProps extends BaseFieldSectionProps {
options: SelectOption[];
}>;
isUpdateMode: boolean;
hideTransactionType?: boolean;
}
export interface SplitAndSettlementSectionProps extends BaseFieldSectionProps {

View File

@@ -63,12 +63,13 @@ export function LancamentoDialog({
lockCartaoSelection,
lockPaymentMethod,
isImporting = false,
defaultTransactionType,
onBulkEditRequest,
}: LancamentoDialogProps) {
const [dialogOpen, setDialogOpen] = useControlledState(
open,
false,
onOpenChange
onOpenChange,
);
const [formState, setFormState] = useState<FormState>(() =>
@@ -76,8 +77,9 @@ export function LancamentoDialog({
defaultCartaoId,
defaultPaymentMethod,
defaultPurchaseDate,
defaultTransactionType,
isImporting,
})
}),
);
const [periodDirty, setPeriodDirty] = useState(false);
const [isPending, startTransition] = useTransition();
@@ -94,9 +96,10 @@ export function LancamentoDialog({
defaultCartaoId,
defaultPaymentMethod,
defaultPurchaseDate,
defaultTransactionType,
isImporting,
}
)
},
),
);
setErrorMessage(null);
setPeriodDirty(false);
@@ -109,6 +112,7 @@ export function LancamentoDialog({
defaultCartaoId,
defaultPaymentMethod,
defaultPurchaseDate,
defaultTransactionType,
isImporting,
]);
@@ -116,18 +120,17 @@ export function LancamentoDialog({
const secondaryPagadorOptions = useMemo(
() => filterSecondaryPagadorOptions(splitPagadorOptions, primaryPagador),
[splitPagadorOptions, primaryPagador]
[splitPagadorOptions, primaryPagador],
);
const categoriaGroups = useMemo(() => {
const filtered = categoriaOptions.filter(
(option) =>
option.group?.toLowerCase() === formState.transactionType.toLowerCase()
option.group?.toLowerCase() === formState.transactionType.toLowerCase(),
);
return groupAndSortCategorias(filtered);
}, [categoriaOptions, formState.transactionType]);
const handleFieldChange = useCallback(
<Key extends keyof FormState>(key: Key, value: FormState[Key]) => {
if (key === "period") {
@@ -139,7 +142,7 @@ export function LancamentoDialog({
key,
value,
prev,
periodDirty
periodDirty,
);
return {
@@ -149,7 +152,7 @@ export function LancamentoDialog({
};
});
},
[periodDirty]
[periodDirty],
);
const handleSubmit = useCallback(
@@ -302,25 +305,35 @@ export function LancamentoDialog({
lancamento?.seriesId,
setDialogOpen,
onBulkEditRequest,
]
],
);
const isCopyMode = mode === "create" && Boolean(lancamento) && !isImporting;
const isImportMode = mode === "create" && Boolean(lancamento) && isImporting;
const title = mode === "create"
? isImportMode
? "Importar para Minha Conta"
: isCopyMode
? "Copiar lançamento"
: "Novo lançamento"
: "Editar lançamento";
const isNewWithType =
mode === "create" && !lancamento && defaultTransactionType;
const title =
mode === "create"
? isImportMode
? "Importar para Minha Conta"
: isCopyMode
? "Copiar lançamento"
: isNewWithType
? defaultTransactionType === "Despesa"
? "Nova Despesa"
: "Nova Receita"
: "Novo lançamento"
: "Editar lançamento";
const description =
mode === "create"
? isImportMode
? "Importando lançamento de outro usuário. Ajuste a categoria, pagador e cartão/conta antes de salvar."
: isCopyMode
? "Os dados do lançamento foram copiados. Revise e ajuste conforme necessário antes de salvar."
: "Informe os dados abaixo para registrar um novo lançamento."
? "Os dados do lançamento foram copiados. Revise e ajuste conforme necessário antes de salvar."
: isNewWithType
? `Informe os dados abaixo para registrar ${defaultTransactionType === "Despesa" ? "uma nova despesa" : "uma nova receita"}.`
: "Informe os dados abaixo para registrar um novo lançamento."
: "Atualize as informações do lançamento selecionado.";
const submitLabel = mode === "create" ? "Salvar lançamento" : "Atualizar";
@@ -358,6 +371,7 @@ export function LancamentoDialog({
categoriaOptions={categoriaOptions}
categoriaGroups={categoriaGroups}
isUpdateMode={isUpdateMode}
hideTransactionType={Boolean(isNewWithType)}
/>
{!isUpdateMode ? (