"use client"; import { RiAddLine, RiDeleteBinLine } from "@remixicon/react"; import { useMemo, useState } from "react"; import { toast } from "sonner"; import { groupAndSortCategories } from "@/features/transactions/category-helpers"; import { PAYMENT_METHODS, type TRANSACTION_TYPES, } from "@/features/transactions/constants"; import { Button } from "@/shared/components/ui/button"; import { CurrencyInput } from "@/shared/components/ui/currency-input"; import { DatePicker } from "@/shared/components/ui/date-picker"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/shared/components/ui/dialog"; import { Label } from "@/shared/components/ui/label"; import { MonthPicker } from "@/shared/components/ui/month-picker"; import { Popover, PopoverContent, PopoverTrigger, } from "@/shared/components/ui/popover"; import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from "@/shared/components/ui/select"; import { Separator } from "@/shared/components/ui/separator"; import { Spinner } from "@/shared/components/ui/spinner"; import { getTodayDateString } from "@/shared/utils/date"; import { createClientSafeId } from "@/shared/utils/id"; import { dateToPeriod, displayPeriod, periodToDate, } from "@/shared/utils/period"; import { AccountCardSelectContent, CategorySelectContent, PayerSelectContent, PaymentMethodSelectContent, TransactionTypeSelectContent, } from "../select-items"; import { EstabelecimentoInput } from "../shared/establishment-input"; import type { SelectOption } from "../types"; /** Payment methods sem Boleto para este modal */ const MASS_ADD_PAYMENT_METHODS = PAYMENT_METHODS.filter((m) => m !== "Boleto"); type MassAddTransactionType = (typeof TRANSACTION_TYPES)[number]; type MassAddPaymentMethod = (typeof PAYMENT_METHODS)[number]; function InlinePeriodPicker({ period, onPeriodChange, }: { period: string; onPeriodChange: (value: string) => void; }) { const [open, setOpen] = useState(false); return (
Fatura de { onPeriodChange(dateToPeriod(date)); setOpen(false); }} />
); } interface MassAddDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onSubmit: (data: MassAddFormData) => Promise; payerOptions: SelectOption[]; accountOptions: SelectOption[]; cardOptions: SelectOption[]; categoryOptions: SelectOption[]; estabelecimentos: string[]; selectedPeriod: string; defaultPayerId?: string | null; defaultCardId?: string | null; } export type MassAddFormData = Parameters< typeof import("@/features/transactions/actions").createMassTransactionsAction >[0]; interface TransactionRow { id: string; purchaseDate: string; name: string; amount: string; categoryId: string | undefined; payerId: string | undefined; } function createEmptyTransactionRow( defaultPayerId?: string | null, ): TransactionRow { return { id: createClientSafeId(), purchaseDate: getTodayDateString(), name: "", amount: "", categoryId: undefined, payerId: defaultPayerId ?? undefined, }; } export function MassAddDialog({ open, onOpenChange, onSubmit, payerOptions, accountOptions, cardOptions, categoryOptions, estabelecimentos, selectedPeriod, defaultPayerId, defaultCardId, }: MassAddDialogProps) { const [loading, setLoading] = useState(false); // Fixed fields state (sempre ativos, sem checkboxes) const [transactionType, setTransactionType] = useState("Despesa"); const [paymentMethod, setPaymentMethod] = useState( PAYMENT_METHODS[0], ); const [period, setPeriod] = useState(selectedPeriod); const [accountId, setContaId] = useState(undefined); const [cardId, setCartaoId] = useState( defaultCardId ?? undefined, ); // Quando defaultCardId está definido, exibe apenas o cartão específico const isLockedToCartao = !!defaultCardId; const isCartaoSelected = paymentMethod === "Cartão de crédito"; // Transaction rows const [transactions, setTransactions] = useState(() => [ createEmptyTransactionRow(defaultPayerId), ]); // Categorias agrupadas e filtradas por tipo de transação const groupedCategorias = useMemo(() => { const filtered = categoryOptions.filter( (option) => option.group?.toLowerCase() === transactionType.toLowerCase(), ); return groupAndSortCategories(filtered); }, [categoryOptions, transactionType]); const addTransaction = () => { setTransactions([ ...transactions, createEmptyTransactionRow(defaultPayerId), ]); }; const removeTransaction = (id: string) => { if (transactions.length === 1) { toast.error("É necessário ter pelo menos uma transação"); return; } setTransactions(transactions.filter((t) => t.id !== id)); }; const updateTransaction = ( id: string, field: keyof TransactionRow, value: string | undefined, ) => { setTransactions( transactions.map((t) => (t.id === id ? { ...t, [field]: value } : t)), ); }; const handleSubmit = async () => { // Validate conta/cartao selection if (isCartaoSelected && !cardId) { toast.error("Selecione um cartão para continuar"); return; } if (!isCartaoSelected && !accountId) { toast.error("Selecione uma conta para continuar"); return; } // Validate transactions const invalidTransactions = transactions.filter( (t) => !t.name.trim() || !t.amount.trim() || !t.purchaseDate, ); if (invalidTransactions.length > 0) { toast.error( "Preencha todos os campos obrigatórios das transações (data, estabelecimento e valor)", ); return; } // Build form data const formData: MassAddFormData = { fixedFields: { transactionType, paymentMethod, condition: "À vista", period, accountId, cardId, }, transactions: transactions.map((t) => ({ purchaseDate: t.purchaseDate, name: t.name.trim(), amount: Number(t.amount.trim()), categoryId: t.categoryId, payerId: t.payerId, })), }; setLoading(true); try { await onSubmit(formData); onOpenChange(false); // Reset form setTransactionType("Despesa"); setPaymentMethod(PAYMENT_METHODS[0]); setPeriod(selectedPeriod); setContaId(undefined); setCartaoId(defaultCardId ?? undefined); setTransactions([createEmptyTransactionRow(defaultPayerId)]); } catch (_error) { // Error is handled by the onSubmit function } finally { setLoading(false); } }; 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.
{/* Fixed Fields Section */}

Valores Padrão

{/* Transaction Type */}
{/* Payment Method */}
{/* Cartão (only for credit card) */} {isCartaoSelected ? (
{cardId ? ( ) : null}
) : null} {/* FinancialAccount (for non-credit-card methods) */} {!isCartaoSelected ? (
) : null}
{/* Transactions Section */}

Lançamentos

{transactions.map((transaction, index) => (
updateTransaction( transaction.id, "purchaseDate", value, ) } placeholder="Data" compact required />
updateTransaction(transaction.id, "name", value) } estabelecimentos={estabelecimentos} required />
updateTransaction(transaction.id, "amount", value) } required />
))}
); }