From ac2ea63dbd706f4ee0fb68279257fbad8750ccfa Mon Sep 17 00:00:00 2001 From: Felipe Coutinho Date: Thu, 26 Feb 2026 17:23:23 +0000 Subject: [PATCH] refactor: simplificar lancamento-dialog e mass-add-dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Substitui FaturaWarningDialog por deriveCreditCardPeriod() que calcula o período da fatura automaticamente a partir da data de compra + dia de fechamento/vencimento do cartão. lancamento-dialog: remove periodDirty state, adiciona seção colapsável "Condições e anotações", propaga closingDay/dueDay via cardInfo. mass-add-dialog: unifica contaId/cartaoId em contaCartaoId com parsing por prefixo, period picker apenas para cartão de crédito. basic-fields-section: remove PeriodPicker (período agora auto-derivado), move Estabelecimento para topo. payment-method-section: adiciona InlinePeriodPicker como link "Fatura de [mês]" com popover MonthPicker. Co-Authored-By: Claude Opus 4.6 --- app/(dashboard)/lancamentos/actions.ts | 55 -- .../basic-fields-section.tsx | 50 +- .../boleto-fields-section.tsx | 2 +- .../lancamento-dialog/condition-section.tsx | 19 +- .../lancamento-dialog/lancamento-dialog.tsx | 289 +++---- .../payment-method-section.tsx | 68 +- .../split-settlement-section.tsx | 2 +- .../lancamentos/dialogs/mass-add-dialog.tsx | 766 ++++++++---------- components/lancamentos/types.ts | 2 + lib/lancamentos/form-helpers.ts | 117 ++- 10 files changed, 693 insertions(+), 677 deletions(-) diff --git a/app/(dashboard)/lancamentos/actions.ts b/app/(dashboard)/lancamentos/actions.ts index 4cec77f..b6364a8 100644 --- a/app/(dashboard)/lancamentos/actions.ts +++ b/app/(dashboard)/lancamentos/actions.ts @@ -7,7 +7,6 @@ import { cartoes, categorias, contas, - faturas, lancamentos, pagadores, } from "@/db/schema"; @@ -33,7 +32,6 @@ import { import { noteSchema, uuidSchema } from "@/lib/schemas/common"; import { formatDecimalForDbRequired } from "@/lib/utils/currency"; import { getTodayDate, parseLocalDateString } from "@/lib/utils/date"; -import { getNextPeriod } from "@/lib/utils/period"; // ============================================================================ // Authorization Validation Functions @@ -1641,59 +1639,6 @@ export async function deleteMultipleLancamentosAction( } } -// Check fatura payment status and card closing day for the given period -export async function checkFaturaStatusAction( - cartaoId: string, - period: string, -): Promise<{ - shouldSuggestNext: boolean; - isPaid: boolean; - isAfterClosing: boolean; - closingDay: string | null; - cardName: string; - nextPeriod: string; -} | null> { - try { - const user = await getUser(); - - const cartao = await db.query.cartoes.findFirst({ - where: and(eq(cartoes.id, cartaoId), eq(cartoes.userId, user.id)), - columns: { id: true, name: true, closingDay: true }, - }); - - if (!cartao) return null; - - const fatura = await db.query.faturas.findFirst({ - where: and( - eq(faturas.cartaoId, cartaoId), - eq(faturas.userId, user.id), - eq(faturas.period, period), - ), - columns: { paymentStatus: true }, - }); - - const isPaid = fatura?.paymentStatus === "pago"; - const today = new Date(); - const currentPeriod = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}`; - const closingDayNum = Number.parseInt(cartao.closingDay ?? "", 10); - const isAfterClosing = - period === currentPeriod && - !Number.isNaN(closingDayNum) && - today.getDate() > closingDayNum; - - return { - shouldSuggestNext: isPaid || isAfterClosing, - isPaid, - isAfterClosing, - closingDay: cartao.closingDay, - cardName: cartao.name, - nextPeriod: getNextPeriod(period), - }; - } catch { - return null; - } -} - // Get unique establishment names from the last 3 months export async function getRecentEstablishmentsAction(): Promise { try { diff --git a/components/lancamentos/dialogs/lancamento-dialog/basic-fields-section.tsx b/components/lancamentos/dialogs/lancamento-dialog/basic-fields-section.tsx index 07dda70..aae7127 100644 --- a/components/lancamentos/dialogs/lancamento-dialog/basic-fields-section.tsx +++ b/components/lancamentos/dialogs/lancamento-dialog/basic-fields-section.tsx @@ -2,7 +2,6 @@ import { RiCalculatorLine } from "@remixicon/react"; import { CalculatorDialogButton } from "@/components/calculadora/calculator-dialog"; -import { PeriodPicker } from "@/components/period-picker"; import { CurrencyInput } from "@/components/ui/currency-input"; import { DatePicker } from "@/components/ui/date-picker"; import { Label } from "@/components/ui/label"; @@ -15,39 +14,28 @@ export function BasicFieldsSection({ estabelecimentos, }: Omit) { return ( - <> -
-
- - onFieldChange("purchaseDate", value)} - placeholder="Data da transação" - required - /> -
- -
- - onFieldChange("period", value)} - className="w-full" - /> -
+
+
+ + onFieldChange("name", value)} + estabelecimentos={estabelecimentos} + placeholder="Ex.: Restaurante do Zé" + maxLength={20} + required + />
- - onFieldChange("name", value)} - estabelecimentos={estabelecimentos} - placeholder="Ex.: Padaria" - maxLength={20} + + onFieldChange("purchaseDate", value)} + placeholder="Data" required />
@@ -74,6 +62,6 @@ export function BasicFieldsSection({
- + ); } diff --git a/components/lancamentos/dialogs/lancamento-dialog/boleto-fields-section.tsx b/components/lancamentos/dialogs/lancamento-dialog/boleto-fields-section.tsx index 096048f..5a94ef3 100644 --- a/components/lancamentos/dialogs/lancamento-dialog/boleto-fields-section.tsx +++ b/components/lancamentos/dialogs/lancamento-dialog/boleto-fields-section.tsx @@ -14,7 +14,7 @@ export function BoletoFieldsSection({
diff --git a/components/lancamentos/dialogs/lancamento-dialog/condition-section.tsx b/components/lancamentos/dialogs/lancamento-dialog/condition-section.tsx index 28a1f64..e2a9022 100644 --- a/components/lancamentos/dialogs/lancamento-dialog/condition-section.tsx +++ b/components/lancamentos/dialogs/lancamento-dialog/condition-section.tsx @@ -1,6 +1,6 @@ "use client"; -import { useMemo } from "react"; +import { useCallback, useMemo } from "react"; import { Label } from "@/components/ui/label"; import { Select, @@ -32,13 +32,16 @@ export function ConditionSection({ return Number.isNaN(value) || value <= 0 ? null : value; }, [formState.amount]); - const getInstallmentLabel = (count: number) => { - if (amount) { - const installmentValue = amount / count; - return `${count}x de R$ ${formatCurrency(installmentValue)}`; - } - return `${count}x`; - }; + const getInstallmentLabel = useCallback( + (count: number) => { + if (amount) { + const installmentValue = amount / count; + return `${count}x de R$ ${formatCurrency(installmentValue)}`; + } + return `${count}x`; + }, + [amount], + ); const _getRecurrenceLabel = (count: number) => { return `${count} meses`; diff --git a/components/lancamentos/dialogs/lancamento-dialog/lancamento-dialog.tsx b/components/lancamentos/dialogs/lancamento-dialog/lancamento-dialog.tsx index 16861dc..0147620 100644 --- a/components/lancamentos/dialogs/lancamento-dialog/lancamento-dialog.tsx +++ b/components/lancamentos/dialogs/lancamento-dialog/lancamento-dialog.tsx @@ -1,19 +1,23 @@ "use client"; +import { RiAddLine } from "@remixicon/react"; import { useCallback, useEffect, useMemo, - useRef, useState, useTransition, } from "react"; import { toast } from "sonner"; import { - checkFaturaStatusAction, createLancamentoAction, updateLancamentoAction, } from "@/app/(dashboard)/lancamentos/actions"; import { Button } from "@/components/ui/button"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; import { Dialog, DialogContent, @@ -32,10 +36,6 @@ import { applyFieldDependencies, buildLancamentoInitialState, } from "@/lib/lancamentos/form-helpers"; -import { - type FaturaWarning, - FaturaWarningDialog, -} from "../fatura-warning-dialog"; import { BasicFieldsSection } from "./basic-fields-section"; import { BoletoFieldsSection } from "./boleto-fields-section"; import { CategorySection } from "./category-section"; @@ -93,13 +93,8 @@ export function LancamentoDialog({ isImporting, }), ); - const [periodDirty, setPeriodDirty] = useState(false); const [isPending, startTransition] = useTransition(); const [errorMessage, setErrorMessage] = useState(null); - const [faturaWarning, setFaturaWarning] = useState( - null, - ); - const lastCheckedRef = useRef(null); useEffect(() => { if (dialogOpen) { @@ -120,11 +115,6 @@ export function LancamentoDialog({ ), ); setErrorMessage(null); - setPeriodDirty(false); - setFaturaWarning(null); - lastCheckedRef.current = null; - } else { - setFaturaWarning(null); } }, [ dialogOpen, @@ -140,40 +130,6 @@ export function LancamentoDialog({ isImporting, ]); - useEffect(() => { - if (mode !== "create") return; - if (!dialogOpen) return; - if (formState.paymentMethod !== "Cartão de crédito") return; - if (!formState.cartaoId) return; - - const checkKey = `${formState.cartaoId}:${formState.period}`; - if (checkKey === lastCheckedRef.current) return; - lastCheckedRef.current = checkKey; - - checkFaturaStatusAction(formState.cartaoId, formState.period).then( - (result) => { - if (result?.shouldSuggestNext) { - setFaturaWarning({ - nextPeriod: result.nextPeriod, - cardName: result.cardName, - isPaid: result.isPaid, - isAfterClosing: result.isAfterClosing, - closingDay: result.closingDay, - currentPeriod: formState.period, - }); - } else { - setFaturaWarning(null); - } - }, - ); - }, [ - mode, - dialogOpen, - formState.paymentMethod, - formState.cartaoId, - formState.period, - ]); - const primaryPagador = formState.pagadorId; const secondaryPagadorOptions = useMemo( @@ -194,19 +150,27 @@ export function LancamentoDialog({ return Number.isNaN(parsed) ? 0 : Math.abs(parsed); }, [formState.amount]); + const getCardInfo = useCallback( + (cartaoId: string | undefined) => { + if (!cartaoId) return null; + const card = cartaoOptions.find((opt) => opt.value === cartaoId); + if (!card) return null; + return { + closingDay: card.closingDay ?? null, + dueDay: card.dueDay ?? null, + }; + }, + [cartaoOptions], + ); + const handleFieldChange = useCallback( (key: Key, value: FormState[Key]) => { - if (key === "period") { - setPeriodDirty(true); - } - setFormState((prev) => { - const dependencies = applyFieldDependencies( - key, - value, - prev, - periodDirty, - ); + const effectiveCartaoId = + key === "cartaoId" ? (value as string) : prev.cartaoId; + const cardInfo = getCardInfo(effectiveCartaoId); + + const dependencies = applyFieldDependencies(key, value, prev, cardInfo); return { ...prev, @@ -215,7 +179,7 @@ export function LancamentoDialog({ }; }); }, - [periodDirty], + [getCardInfo], ); const handleSubmit = useCallback( @@ -440,114 +404,115 @@ export function LancamentoDialog({ const disableCartaoSelect = Boolean(lockCartaoSelection && mode === "create"); return ( - <> - - {trigger ? {trigger} : null} - - - {title} - {description} - + + {trigger ? {trigger} : null} + + + {title} + {description} + -
+ + + + + {!isUpdateMode ? ( + + ) : null} + + + + + + {showDueDate ? ( + + ) : null} + + 0 + } > - + + + Condições e anotações + + + {!isUpdateMode ? ( + + ) : null} - - - {!isUpdateMode ? ( - - ) : null} + + - + {errorMessage ? ( +

{errorMessage}

+ ) : null} - - - {showDueDate ? ( - - ) : null} - - {!isUpdateMode ? ( - - ) : null} - - - - {errorMessage ? ( -

{errorMessage}

- ) : null} - - - - - - -
-
- - { - handleFieldChange("period", nextPeriod); - setFaturaWarning(null); - }} - onCancel={() => setFaturaWarning(null)} - /> - + + + + + +
+
); } diff --git a/components/lancamentos/dialogs/lancamento-dialog/payment-method-section.tsx b/components/lancamentos/dialogs/lancamento-dialog/payment-method-section.tsx index a7dee86..50ebe7c 100644 --- a/components/lancamentos/dialogs/lancamento-dialog/payment-method-section.tsx +++ b/components/lancamentos/dialogs/lancamento-dialog/payment-method-section.tsx @@ -1,6 +1,13 @@ "use client"; +import { useState } from "react"; import { Label } from "@/components/ui/label"; +import { MonthPicker } from "@/components/ui/monthpicker"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; import { Select, SelectContent, @@ -9,6 +16,7 @@ import { SelectValue, } from "@/components/ui/select"; import { LANCAMENTO_PAYMENT_METHODS } from "@/lib/lancamentos/constants"; +import { displayPeriod } from "@/lib/utils/period"; import { cn } from "@/lib/utils/ui"; import { ContaCartaoSelectContent, @@ -16,6 +24,52 @@ import { } from "../../select-items"; import type { PaymentMethodSectionProps } from "./lancamento-dialog-types"; +function periodToDate(period: string): Date { + const [year, month] = period.split("-").map(Number); + return new Date(year, month - 1, 1); +} + +function dateToPeriod(date: Date): string { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + return `${year}-${month}`; +} + +function InlinePeriodPicker({ + period, + onPeriodChange, +}: { + period: string; + onPeriodChange: (value: string) => void; +}) { + const [open, setOpen] = useState(false); + + return ( +
+ Fatura de + + + + + + { + onPeriodChange(dateToPeriod(date)); + setOpen(false); + }} + /> + + +
+ ); +} + export function PaymentMethodSection({ formState, onFieldChange, @@ -46,7 +100,7 @@ export function PaymentMethodSection({ return ( <> {!isUpdateMode ? ( -
+
+ {formState.cartaoId ? ( + onFieldChange("period", value)} + /> + ) : null}
) : null} @@ -239,6 +299,12 @@ export function PaymentMethodSection({ )} + {formState.cartaoId ? ( + onFieldChange("period", value)} + /> + ) : null}
) : null} diff --git a/components/lancamentos/dialogs/lancamento-dialog/split-settlement-section.tsx b/components/lancamentos/dialogs/lancamento-dialog/split-settlement-section.tsx index f2635da..818c130 100644 --- a/components/lancamentos/dialogs/lancamento-dialog/split-settlement-section.tsx +++ b/components/lancamentos/dialogs/lancamento-dialog/split-settlement-section.tsx @@ -10,7 +10,7 @@ export function SplitAndSettlementSection({ showSettledToggle, }: SplitAndSettlementSectionProps) { return ( -
+
( - null, - ); - const lastCheckedRef = useRef(null); - - useEffect(() => { - if (!open) { - setFaturaWarning(null); - lastCheckedRef.current = null; - return; - } - if (!isCartaoSelected || !cartaoId) return; - - const checkKey = `${cartaoId}:${period}`; - if (checkKey === lastCheckedRef.current) return; - lastCheckedRef.current = checkKey; - - checkFaturaStatusAction(cartaoId, period).then((result) => { - if (result?.shouldSuggestNext) { - setFaturaWarning({ - nextPeriod: result.nextPeriod, - cardName: result.cardName, - isPaid: result.isPaid, - isAfterClosing: result.isAfterClosing, - closingDay: result.closingDay, - currentPeriod: period, - }); - } else { - setFaturaWarning(null); - } - }); - }, [open, isCartaoSelected, cartaoId, period]); - // Transaction rows const [transactions, setTransactions] = useState([ { @@ -275,403 +237,387 @@ export function MassAddDialog({ } }; + // Show period picker only for credit card + const showPeriodPicker = isCartaoSelected; + return ( - <> - - - - Adicionar múltiplos lançamentos - - Configure os valores padrão e adicione várias transações de uma - vez. Todos os lançamentos adicionados aqui são{" "} - sempre à vista. - - + + + + Adicionar múltiplos lançamentos + + Configure os valores padrão e adicione várias transações de uma vez. + Todos os lançamentos adicionados aqui são{" "} + sempre à vista. + + -
- {/* Fixed Fields Section */} -
-

Valores Padrão

-
- {/* Transaction Type */} -
- - + + + {transactionType && ( + + )} + + + + + + + + + + + +
+ + {/* Payment Method */} +
+ + -
+ ))} + + +
- {/* Payment Method */} + {/* Period - only for credit card */} + {showPeriodPicker ? (
- - -
- - {/* Period */} -
- +
+ ) : null} - {/* Conta/Cartao */} -
- - + + + {contaCartaoId && + (() => { + if (isCartaoSelected) { + const selectedOption = cartaoOptions.find( + (opt) => opt.value === cartaoId, + ); + return selectedOption ? ( + + ) : null; + } else { + const selectedOption = contaOptions.find( + (opt) => opt.value === contaId, + ); + return selectedOption ? ( + + ) : null; + } + })()} + + + + {cartaoOptions.length > 0 && ( + + {!isLockedToCartao && ( + Cartões + )} + {cartaoOptions + .filter( + (option) => + !isLockedToCartao || + option.value === defaultCartaoId, + ) + .map((option) => ( ))} - - )} - - -
-
-
- - - - {/* Transactions Section */} -
-

Lançamentos

- -
- {transactions.map((transaction, index) => ( -
-
-
- - - updateTransaction( - transaction.id, - "purchaseDate", - value, - ) - } - placeholder="Data" - className="w-32 truncate" - required - /> -
-
- - - updateTransaction(transaction.id, "name", value) - } - estabelecimentos={estabelecimentos} - required - /> -
- -
- - - updateTransaction(transaction.id, "amount", value) - } - required - /> -
- -
- - -
- -
- - -
- - -
-
- ))} + + + ))} + + )} + +
- - - - - - + - { - setPeriod(nextPeriod); - setFaturaWarning(null); - }} - onCancel={() => setFaturaWarning(null)} - /> - + {/* Transactions Section */} +
+

Lançamentos

+ +
+ {transactions.map((transaction, index) => ( +
+
+
+ + + updateTransaction( + transaction.id, + "purchaseDate", + value, + ) + } + placeholder="Data" + className="w-32 truncate" + required + /> +
+
+ + + updateTransaction(transaction.id, "name", value) + } + estabelecimentos={estabelecimentos} + required + /> +
+ +
+ + + updateTransaction(transaction.id, "amount", value) + } + required + /> +
+ +
+ + +
+ +
+ + +
+ + +
+
+ ))} +
+
+
+ + + + + + + ); } diff --git a/components/lancamentos/types.ts b/components/lancamentos/types.ts index 626c940..084c488 100644 --- a/components/lancamentos/types.ts +++ b/components/lancamentos/types.ts @@ -46,6 +46,8 @@ export type SelectOption = { logo?: string | null; icon?: string | null; accountType?: string | null; + closingDay?: string | null; + dueDay?: string | null; }; export type LancamentoFilterOption = { diff --git a/lib/lancamentos/form-helpers.ts b/lib/lancamentos/form-helpers.ts index 7b25308..1a2e27b 100644 --- a/lib/lancamentos/form-helpers.ts +++ b/lib/lancamentos/form-helpers.ts @@ -1,12 +1,64 @@ import type { LancamentoItem } from "@/components/lancamentos/types"; import { getTodayDateString } from "@/lib/utils/date"; -import { derivePeriodFromDate } from "@/lib/utils/period"; +import { derivePeriodFromDate, getNextPeriod } from "@/lib/utils/period"; import { LANCAMENTO_CONDITIONS, LANCAMENTO_PAYMENT_METHODS, LANCAMENTO_TRANSACTION_TYPES, } from "./constants"; +/** + * Derives the fatura period for a credit card purchase based on closing day + * and due day. The period represents the month the fatura is due (vencimento). + * + * Steps: + * 1. If purchase day > closing day → the purchase missed this month's closing, + * so it enters the NEXT month's billing cycle (+1 month from purchase). + * 2. Then, if dueDay < closingDay, the due date falls in the month AFTER the + * closing month (e.g., closes 22nd, due 1st → closes Mar/22, due Apr/1), + * so we add another +1 month. + * + * @example + * // Card closes day 22, due day 1 (dueDay < closingDay → +1 extra) + * deriveCreditCardPeriod("2026-02-25", "22", "1") // "2026-04" (missed Feb closing → Mar cycle → due Apr) + * deriveCreditCardPeriod("2026-02-15", "22", "1") // "2026-03" (in Feb cycle → due Mar) + * + * // Card closes day 5, due day 15 (dueDay >= closingDay → no extra) + * deriveCreditCardPeriod("2026-02-10", "5", "15") // "2026-03" (missed Feb closing → Mar cycle → due Mar) + * deriveCreditCardPeriod("2026-02-03", "5", "15") // "2026-02" (in Feb cycle → due Feb) + */ +export function deriveCreditCardPeriod( + purchaseDate: string, + closingDay: string | null | undefined, + dueDay?: string | null | undefined, +): string { + const basePeriod = derivePeriodFromDate(purchaseDate); + if (!closingDay) return basePeriod; + + const closingDayNum = Number.parseInt(closingDay, 10); + if (Number.isNaN(closingDayNum)) return basePeriod; + + const dayPart = purchaseDate.split("-")[2]; + const purchaseDayNum = Number.parseInt(dayPart ?? "1", 10); + + // Start with the purchase month as the billing cycle + let period = basePeriod; + + // If purchase is after closing day, it enters the next billing cycle + if (purchaseDayNum > closingDayNum) { + period = getNextPeriod(period); + } + + // If due day < closing day, the due date falls in the month after closing + // (e.g., closes 22nd, due 1st → closing in March means due in April) + const dueDayNum = Number.parseInt(dueDay ?? "", 10); + if (!Number.isNaN(dueDayNum) && dueDayNum < closingDayNum) { + period = getNextPeriod(period); + } + + return period; +} + /** * Split type for dividing transactions between payers */ @@ -198,16 +250,44 @@ export function applyFieldDependencies( key: keyof LancamentoFormState, value: LancamentoFormState[keyof LancamentoFormState], currentState: LancamentoFormState, - _periodDirty: boolean, + cardInfo?: { closingDay: string | null; dueDay: string | null } | null, ): Partial { const updates: Partial = {}; - // Removed automatic period update when purchase date changes - // if (key === "purchaseDate" && typeof value === "string") { - // if (!periodDirty) { - // updates.period = derivePeriodFromDate(value); - // } - // } + // Auto-derive period from purchaseDate + if (key === "purchaseDate" && typeof value === "string" && value) { + const method = currentState.paymentMethod; + if (method === "Cartão de crédito") { + updates.period = deriveCreditCardPeriod( + value, + cardInfo?.closingDay, + cardInfo?.dueDay, + ); + } else if (method !== "Boleto") { + updates.period = derivePeriodFromDate(value); + } + } + + // Auto-derive period from dueDate when payment method is boleto + if (key === "dueDate" && typeof value === "string" && value) { + if (currentState.paymentMethod === "Boleto") { + updates.period = derivePeriodFromDate(value); + } + } + + // Auto-derive period when cartaoId changes (credit card selected) + if ( + key === "cartaoId" && + currentState.paymentMethod === "Cartão de crédito" + ) { + if (typeof value === "string" && value && currentState.purchaseDate) { + updates.period = deriveCreditCardPeriod( + currentState.purchaseDate, + cardInfo?.closingDay, + cardInfo?.dueDay, + ); + } + } // When condition changes, clear irrelevant fields if (key === "condition" && typeof value === "string") { @@ -229,6 +309,27 @@ export function applyFieldDependencies( updates.isSettled = currentState.isSettled ?? true; } + // Re-derive period based on new payment method + if (value === "Cartão de crédito") { + if ( + currentState.purchaseDate && + currentState.cartaoId && + cardInfo?.closingDay + ) { + updates.period = deriveCreditCardPeriod( + currentState.purchaseDate, + cardInfo.closingDay, + cardInfo.dueDay, + ); + } else if (currentState.purchaseDate) { + updates.period = derivePeriodFromDate(currentState.purchaseDate); + } + } else if (value === "Boleto" && currentState.dueDate) { + updates.period = derivePeriodFromDate(currentState.dueDate); + } else if (currentState.purchaseDate) { + updates.period = derivePeriodFromDate(currentState.purchaseDate); + } + // Clear boleto-specific fields if not boleto if (value !== "Boleto") { updates.dueDate = "";