mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 11:01:45 +00:00
refactor: atualiza transacoes dashboard e relatorios
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,10 @@
|
||||
import { and, asc, desc, eq, inArray, isNull, or } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
antecipacoesParcelas,
|
||||
categorias,
|
||||
lancamentos,
|
||||
pagadores,
|
||||
categories,
|
||||
installmentAnticipations,
|
||||
payers,
|
||||
transactions,
|
||||
} from "@/db/schema";
|
||||
import {
|
||||
handleActionError,
|
||||
@@ -47,8 +47,8 @@ const createAnticipationSchema = z.object({
|
||||
.min(0, "Informe um desconto maior ou igual a zero.")
|
||||
.optional()
|
||||
.default(0),
|
||||
pagadorId: uuidSchema("Pagador").optional(),
|
||||
categoriaId: uuidSchema("Categoria").optional(),
|
||||
payerId: uuidSchema("Payer").optional(),
|
||||
categoryId: uuidSchema("Category").optional(),
|
||||
note: z.string().trim().optional(),
|
||||
});
|
||||
|
||||
@@ -72,16 +72,16 @@ export async function getEligibleInstallmentsAction(
|
||||
const validatedSeriesId = uuidSchema("Série").parse(seriesId);
|
||||
|
||||
// Buscar todas as parcelas da série que estão elegíveis
|
||||
const rows = await db.query.lancamentos.findMany({
|
||||
const rows = await db.query.transactions.findMany({
|
||||
where: and(
|
||||
eq(lancamentos.seriesId, validatedSeriesId),
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.condition, "Parcelado"),
|
||||
eq(transactions.seriesId, validatedSeriesId),
|
||||
eq(transactions.userId, user.id),
|
||||
eq(transactions.condition, "Parcelado"),
|
||||
// Apenas parcelas não pagas e não antecipadas
|
||||
or(eq(lancamentos.isSettled, false), isNull(lancamentos.isSettled)),
|
||||
eq(lancamentos.isAnticipated, false),
|
||||
or(eq(transactions.isSettled, false), isNull(transactions.isSettled)),
|
||||
eq(transactions.isAnticipated, false),
|
||||
),
|
||||
orderBy: [asc(lancamentos.currentInstallment)],
|
||||
orderBy: [asc(transactions.currentInstallment)],
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
@@ -92,8 +92,8 @@ export async function getEligibleInstallmentsAction(
|
||||
currentInstallment: true,
|
||||
installmentCount: true,
|
||||
paymentMethod: true,
|
||||
categoriaId: true,
|
||||
pagadorId: true,
|
||||
categoryId: true,
|
||||
payerId: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -107,8 +107,8 @@ export async function getEligibleInstallmentsAction(
|
||||
currentInstallment: row.currentInstallment,
|
||||
installmentCount: row.installmentCount,
|
||||
paymentMethod: row.paymentMethod,
|
||||
categoriaId: row.categoriaId,
|
||||
pagadorId: row.pagadorId,
|
||||
categoryId: row.categoryId,
|
||||
payerId: row.payerId,
|
||||
}));
|
||||
|
||||
return {
|
||||
@@ -132,13 +132,13 @@ export async function createInstallmentAnticipationAction(
|
||||
const data = createAnticipationSchema.parse(input);
|
||||
|
||||
// 1. Validar parcelas selecionadas
|
||||
const installments = await db.query.lancamentos.findMany({
|
||||
const installments = await db.query.transactions.findMany({
|
||||
where: and(
|
||||
inArray(lancamentos.id, data.installmentIds),
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.seriesId, data.seriesId),
|
||||
or(eq(lancamentos.isSettled, false), isNull(lancamentos.isSettled)),
|
||||
eq(lancamentos.isAnticipated, false),
|
||||
inArray(transactions.id, data.installmentIds),
|
||||
eq(transactions.userId, user.id),
|
||||
eq(transactions.seriesId, data.seriesId),
|
||||
or(eq(transactions.isSettled, false), isNull(transactions.isSettled)),
|
||||
eq(transactions.isAnticipated, false),
|
||||
),
|
||||
});
|
||||
|
||||
@@ -187,8 +187,8 @@ export async function createInstallmentAnticipationAction(
|
||||
// 4. Criar lançamento e antecipação em transação
|
||||
await db.transaction(async (tx: typeof db) => {
|
||||
// 4.1. Criar o lançamento de antecipação (com desconto aplicado)
|
||||
const [newLancamento] = await tx
|
||||
.insert(lancamentos)
|
||||
const [newLancamento] = (await tx
|
||||
.insert(transactions)
|
||||
.values({
|
||||
name: generateAnticipationDescription(
|
||||
firstInstallment.name,
|
||||
@@ -202,10 +202,10 @@ export async function createInstallmentAnticipationAction(
|
||||
period: data.anticipationPeriod,
|
||||
dueDate: null,
|
||||
isSettled: false,
|
||||
pagadorId: data.pagadorId ?? firstInstallment.pagadorId,
|
||||
categoriaId: data.categoriaId ?? firstInstallment.categoriaId,
|
||||
cartaoId: firstInstallment.cartaoId,
|
||||
contaId: firstInstallment.contaId,
|
||||
payerId: data.payerId ?? firstInstallment.payerId,
|
||||
categoryId: data.categoryId ?? firstInstallment.categoryId,
|
||||
cardId: firstInstallment.cardId,
|
||||
accountId: firstInstallment.accountId,
|
||||
note:
|
||||
data.note ||
|
||||
generateAnticipationNote(
|
||||
@@ -219,8 +219,8 @@ export async function createInstallmentAnticipationAction(
|
||||
currentInstallment: inst.currentInstallment,
|
||||
installmentCount: inst.installmentCount,
|
||||
paymentMethod: inst.paymentMethod,
|
||||
categoriaId: inst.categoriaId,
|
||||
pagadorId: inst.pagadorId,
|
||||
categoryId: inst.categoryId,
|
||||
payerId: inst.payerId,
|
||||
})),
|
||||
),
|
||||
userId: user.id,
|
||||
@@ -234,11 +234,11 @@ export async function createInstallmentAnticipationAction(
|
||||
anticipationId: null,
|
||||
boletoPaymentDate: null,
|
||||
})
|
||||
.returning();
|
||||
.returning()) as Array<typeof transactions.$inferSelect>;
|
||||
|
||||
// 4.2. Criar registro de antecipação
|
||||
const [anticipation] = await tx
|
||||
.insert(antecipacoesParcelas)
|
||||
const [anticipation] = (await tx
|
||||
.insert(installmentAnticipations)
|
||||
.values({
|
||||
seriesId: data.seriesId,
|
||||
anticipationPeriod: data.anticipationPeriod,
|
||||
@@ -247,26 +247,26 @@ export async function createInstallmentAnticipationAction(
|
||||
totalAmount: formatDecimalForDbRequired(totalAmount),
|
||||
installmentCount: installments.length,
|
||||
discount: formatDecimalForDbRequired(discount),
|
||||
lancamentoId: newLancamento.id,
|
||||
pagadorId: data.pagadorId ?? firstInstallment.pagadorId,
|
||||
categoriaId: data.categoriaId ?? firstInstallment.categoriaId,
|
||||
transactionId: newLancamento.id,
|
||||
payerId: data.payerId ?? firstInstallment.payerId,
|
||||
categoryId: data.categoryId ?? firstInstallment.categoryId,
|
||||
note: data.note || null,
|
||||
userId: user.id,
|
||||
})
|
||||
.returning();
|
||||
.returning()) as Array<typeof installmentAnticipations.$inferSelect>;
|
||||
|
||||
// 4.3. Marcar parcelas como antecipadas e zerar seus valores
|
||||
await tx
|
||||
.update(lancamentos)
|
||||
.update(transactions)
|
||||
.set({
|
||||
isAnticipated: true,
|
||||
anticipationId: anticipation.id,
|
||||
amount: "0", // Zera o valor para não contar em dobro
|
||||
})
|
||||
.where(inArray(lancamentos.id, data.installmentIds));
|
||||
.where(inArray(transactions.id, data.installmentIds));
|
||||
});
|
||||
|
||||
revalidateForEntity("lancamentos");
|
||||
revalidateForEntity("transactions");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -296,40 +296,43 @@ export async function getInstallmentAnticipationsAction(
|
||||
// Usar query builder ao invés de db.query para evitar problemas de tipagem
|
||||
const anticipations = await db
|
||||
.select({
|
||||
id: antecipacoesParcelas.id,
|
||||
seriesId: antecipacoesParcelas.seriesId,
|
||||
anticipationPeriod: antecipacoesParcelas.anticipationPeriod,
|
||||
anticipationDate: antecipacoesParcelas.anticipationDate,
|
||||
id: installmentAnticipations.id,
|
||||
seriesId: installmentAnticipations.seriesId,
|
||||
anticipationPeriod: installmentAnticipations.anticipationPeriod,
|
||||
anticipationDate: installmentAnticipations.anticipationDate,
|
||||
anticipatedInstallmentIds:
|
||||
antecipacoesParcelas.anticipatedInstallmentIds,
|
||||
totalAmount: antecipacoesParcelas.totalAmount,
|
||||
installmentCount: antecipacoesParcelas.installmentCount,
|
||||
discount: antecipacoesParcelas.discount,
|
||||
lancamentoId: antecipacoesParcelas.lancamentoId,
|
||||
pagadorId: antecipacoesParcelas.pagadorId,
|
||||
categoriaId: antecipacoesParcelas.categoriaId,
|
||||
note: antecipacoesParcelas.note,
|
||||
userId: antecipacoesParcelas.userId,
|
||||
createdAt: antecipacoesParcelas.createdAt,
|
||||
installmentAnticipations.anticipatedInstallmentIds,
|
||||
totalAmount: installmentAnticipations.totalAmount,
|
||||
installmentCount: installmentAnticipations.installmentCount,
|
||||
discount: installmentAnticipations.discount,
|
||||
transactionId: installmentAnticipations.transactionId,
|
||||
payerId: installmentAnticipations.payerId,
|
||||
categoryId: installmentAnticipations.categoryId,
|
||||
note: installmentAnticipations.note,
|
||||
userId: installmentAnticipations.userId,
|
||||
createdAt: installmentAnticipations.createdAt,
|
||||
// Joins
|
||||
lancamento: lancamentos,
|
||||
pagador: pagadores,
|
||||
categoria: categorias,
|
||||
transaction: transactions,
|
||||
payer: payers,
|
||||
category: categories,
|
||||
})
|
||||
.from(antecipacoesParcelas)
|
||||
.from(installmentAnticipations)
|
||||
.leftJoin(
|
||||
lancamentos,
|
||||
eq(antecipacoesParcelas.lancamentoId, lancamentos.id),
|
||||
transactions,
|
||||
eq(installmentAnticipations.transactionId, transactions.id),
|
||||
)
|
||||
.leftJoin(payers, eq(installmentAnticipations.payerId, payers.id))
|
||||
.leftJoin(
|
||||
categories,
|
||||
eq(installmentAnticipations.categoryId, categories.id),
|
||||
)
|
||||
.leftJoin(pagadores, eq(antecipacoesParcelas.pagadorId, pagadores.id))
|
||||
.leftJoin(categorias, eq(antecipacoesParcelas.categoriaId, categorias.id))
|
||||
.where(
|
||||
and(
|
||||
eq(antecipacoesParcelas.seriesId, validatedSeriesId),
|
||||
eq(antecipacoesParcelas.userId, user.id),
|
||||
eq(installmentAnticipations.seriesId, validatedSeriesId),
|
||||
eq(installmentAnticipations.userId, user.id),
|
||||
),
|
||||
)
|
||||
.orderBy(desc(antecipacoesParcelas.createdAt));
|
||||
.orderBy(desc(installmentAnticipations.createdAt));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -358,32 +361,32 @@ export async function cancelInstallmentAnticipationAction(
|
||||
// 1. Buscar antecipação usando query builder
|
||||
const anticipationRows = await tx
|
||||
.select({
|
||||
id: antecipacoesParcelas.id,
|
||||
seriesId: antecipacoesParcelas.seriesId,
|
||||
anticipationPeriod: antecipacoesParcelas.anticipationPeriod,
|
||||
anticipationDate: antecipacoesParcelas.anticipationDate,
|
||||
id: installmentAnticipations.id,
|
||||
seriesId: installmentAnticipations.seriesId,
|
||||
anticipationPeriod: installmentAnticipations.anticipationPeriod,
|
||||
anticipationDate: installmentAnticipations.anticipationDate,
|
||||
anticipatedInstallmentIds:
|
||||
antecipacoesParcelas.anticipatedInstallmentIds,
|
||||
totalAmount: antecipacoesParcelas.totalAmount,
|
||||
installmentCount: antecipacoesParcelas.installmentCount,
|
||||
discount: antecipacoesParcelas.discount,
|
||||
lancamentoId: antecipacoesParcelas.lancamentoId,
|
||||
pagadorId: antecipacoesParcelas.pagadorId,
|
||||
categoriaId: antecipacoesParcelas.categoriaId,
|
||||
note: antecipacoesParcelas.note,
|
||||
userId: antecipacoesParcelas.userId,
|
||||
createdAt: antecipacoesParcelas.createdAt,
|
||||
lancamento: lancamentos,
|
||||
installmentAnticipations.anticipatedInstallmentIds,
|
||||
totalAmount: installmentAnticipations.totalAmount,
|
||||
installmentCount: installmentAnticipations.installmentCount,
|
||||
discount: installmentAnticipations.discount,
|
||||
transactionId: installmentAnticipations.transactionId,
|
||||
payerId: installmentAnticipations.payerId,
|
||||
categoryId: installmentAnticipations.categoryId,
|
||||
note: installmentAnticipations.note,
|
||||
userId: installmentAnticipations.userId,
|
||||
createdAt: installmentAnticipations.createdAt,
|
||||
transaction: transactions,
|
||||
})
|
||||
.from(antecipacoesParcelas)
|
||||
.from(installmentAnticipations)
|
||||
.leftJoin(
|
||||
lancamentos,
|
||||
eq(antecipacoesParcelas.lancamentoId, lancamentos.id),
|
||||
transactions,
|
||||
eq(installmentAnticipations.transactionId, transactions.id),
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
eq(antecipacoesParcelas.id, data.anticipationId),
|
||||
eq(antecipacoesParcelas.userId, user.id),
|
||||
eq(installmentAnticipations.id, data.anticipationId),
|
||||
eq(installmentAnticipations.userId, user.id),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
@@ -395,7 +398,7 @@ export async function cancelInstallmentAnticipationAction(
|
||||
}
|
||||
|
||||
// 2. Verificar se o lançamento já foi pago
|
||||
if (anticipation.lancamento?.isSettled === true) {
|
||||
if (anticipation.transaction?.isSettled === true) {
|
||||
throw new Error(
|
||||
"Não é possível cancelar uma antecipação já paga. Remova o pagamento primeiro.",
|
||||
);
|
||||
@@ -408,7 +411,7 @@ export async function cancelInstallmentAnticipationAction(
|
||||
|
||||
// 4. Remover flag de antecipação e restaurar valores das parcelas
|
||||
await tx
|
||||
.update(lancamentos)
|
||||
.update(transactions)
|
||||
.set({
|
||||
isAnticipated: false,
|
||||
anticipationId: null,
|
||||
@@ -416,23 +419,23 @@ export async function cancelInstallmentAnticipationAction(
|
||||
})
|
||||
.where(
|
||||
inArray(
|
||||
lancamentos.id,
|
||||
transactions.id,
|
||||
anticipation.anticipatedInstallmentIds as string[],
|
||||
),
|
||||
);
|
||||
|
||||
// 5. Deletar lançamento de antecipação
|
||||
await tx
|
||||
.delete(lancamentos)
|
||||
.where(eq(lancamentos.id, anticipation.lancamentoId));
|
||||
.delete(transactions)
|
||||
.where(eq(transactions.id, anticipation.transactionId));
|
||||
|
||||
// 6. Deletar registro de antecipação
|
||||
await tx
|
||||
.delete(antecipacoesParcelas)
|
||||
.where(eq(antecipacoesParcelas.id, data.anticipationId));
|
||||
.delete(installmentAnticipations)
|
||||
.where(eq(installmentAnticipations.id, data.anticipationId));
|
||||
});
|
||||
|
||||
revalidateForEntity("lancamentos");
|
||||
revalidateForEntity("transactions");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -455,15 +458,15 @@ export async function getAnticipationDetailsAction(
|
||||
// Validar anticipationId
|
||||
const validatedId = uuidSchema("Antecipação").parse(anticipationId);
|
||||
|
||||
const anticipation = await db.query.antecipacoesParcelas.findFirst({
|
||||
const anticipation = await db.query.installmentAnticipations.findFirst({
|
||||
where: and(
|
||||
eq(antecipacoesParcelas.id, validatedId),
|
||||
eq(antecipacoesParcelas.userId, user.id),
|
||||
eq(installmentAnticipations.id, validatedId),
|
||||
eq(installmentAnticipations.userId, user.id),
|
||||
),
|
||||
with: {
|
||||
lancamento: true,
|
||||
pagador: true,
|
||||
categoria: true,
|
||||
transaction: true,
|
||||
payer: true,
|
||||
category: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import type { SelectOption } from "@/features/transactions/components/types";
|
||||
import { capitalize } from "@/shared/utils/string";
|
||||
|
||||
/**
|
||||
* Group label for categorias
|
||||
* Group label for category options
|
||||
*/
|
||||
type CategoriaGroup = {
|
||||
type CategoryGroup = {
|
||||
label: string;
|
||||
options: SelectOption[];
|
||||
};
|
||||
@@ -24,15 +24,15 @@ function normalizeCategoryGroupLabel(value: string): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups and sorts categoria options by their group property
|
||||
* @param categoriaOptions - Array of categoria select options
|
||||
* @returns Array of grouped and sorted categoria options
|
||||
* Groups and sorts category options by their group property
|
||||
* @param categoryOptions - Array of category select options
|
||||
* @returns Array of grouped and sorted category options
|
||||
*/
|
||||
export function groupAndSortCategorias(
|
||||
categoriaOptions: SelectOption[],
|
||||
): CategoriaGroup[] {
|
||||
// Group categorias by their group property
|
||||
const groups = categoriaOptions.reduce<Record<string, SelectOption[]>>(
|
||||
export function groupAndSortCategories(
|
||||
categoryOptions: SelectOption[],
|
||||
): CategoryGroup[] {
|
||||
// Group category options by their group property
|
||||
const groups = categoryOptions.reduce<Record<string, SelectOption[]>>(
|
||||
(acc, option) => {
|
||||
const key = option.group ?? "Outros";
|
||||
if (!acc[key]) {
|
||||
@@ -63,11 +63,11 @@ export function groupAndSortCategorias(
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters secondary pagador options to exclude the primary pagador
|
||||
* Filters secondary payer options to exclude the primary payer
|
||||
*/
|
||||
export function filterSecondaryPagadorOptions(
|
||||
export function filterSecondaryPayerOptions(
|
||||
allOptions: SelectOption[],
|
||||
primaryPagadorId?: string,
|
||||
primaryPayerId?: string,
|
||||
): SelectOption[] {
|
||||
return allOptions.filter((option) => option.value !== primaryPagadorId);
|
||||
return allOptions.filter((option) => option.value !== primaryPayerId);
|
||||
}
|
||||
@@ -20,8 +20,8 @@ export const LANCAMENTOS_COLUMN_LABELS: Record<string, string> = {
|
||||
amount: "Valor",
|
||||
condition: "Condição",
|
||||
paymentMethod: "Forma de Pagamento",
|
||||
categoriaName: "Categoria",
|
||||
pagadorName: "Pagador",
|
||||
categoriaName: "Category",
|
||||
pagadorName: "Payer",
|
||||
note: "Anotação",
|
||||
contaCartao: "Conta/Cartão",
|
||||
};
|
||||
|
||||
@@ -55,8 +55,8 @@ interface AnticipateInstallmentsDialogProps {
|
||||
type AnticipationFormValues = {
|
||||
anticipationPeriod: string;
|
||||
discount: string;
|
||||
pagadorId: string;
|
||||
categoriaId: string;
|
||||
payerId: string;
|
||||
categoryId: string;
|
||||
note: string;
|
||||
};
|
||||
|
||||
@@ -90,8 +90,8 @@ export function AnticipateInstallmentsDialog({
|
||||
useFormState<AnticipationFormValues>({
|
||||
anticipationPeriod: defaultPeriod,
|
||||
discount: "0",
|
||||
pagadorId: "",
|
||||
categoriaId: "",
|
||||
payerId: "",
|
||||
categoryId: "",
|
||||
note: "",
|
||||
});
|
||||
|
||||
@@ -119,8 +119,8 @@ export function AnticipateInstallmentsDialog({
|
||||
replaceForm({
|
||||
anticipationPeriod: defaultPeriod,
|
||||
discount: "0",
|
||||
pagadorId: first.pagadorId ?? "",
|
||||
categoriaId: first.categoriaId ?? "",
|
||||
payerId: first.payerId ?? "",
|
||||
categoryId: first.categoryId ?? "",
|
||||
note: "",
|
||||
});
|
||||
}
|
||||
@@ -182,8 +182,8 @@ export function AnticipateInstallmentsDialog({
|
||||
installmentIds: selectedIds,
|
||||
anticipationPeriod: formState.anticipationPeriod,
|
||||
discount: Number(formState.discount) || 0,
|
||||
pagadorId: formState.pagadorId || undefined,
|
||||
categoriaId: formState.categoriaId || undefined,
|
||||
payerId: formState.payerId || undefined,
|
||||
categoryId: formState.categoryId || undefined,
|
||||
note: formState.note || undefined,
|
||||
});
|
||||
|
||||
@@ -269,11 +269,11 @@ export function AnticipateInstallmentsDialog({
|
||||
</Field>
|
||||
|
||||
<Field className="gap-1">
|
||||
<FieldLabel htmlFor="anticipation-pagador">Pagador</FieldLabel>
|
||||
<FieldLabel htmlFor="anticipation-pagador">Payer</FieldLabel>
|
||||
<FieldContent>
|
||||
<Select
|
||||
value={formState.pagadorId}
|
||||
onValueChange={(value) => updateField("pagadorId", value)}
|
||||
value={formState.payerId}
|
||||
onValueChange={(value) => updateField("payerId", value)}
|
||||
disabled={isPending}
|
||||
>
|
||||
<SelectTrigger id="anticipation-pagador" className="w-full">
|
||||
@@ -292,12 +292,12 @@ export function AnticipateInstallmentsDialog({
|
||||
|
||||
<Field className="gap-1">
|
||||
<FieldLabel htmlFor="anticipation-categoria">
|
||||
Categoria
|
||||
Category
|
||||
</FieldLabel>
|
||||
<FieldContent>
|
||||
<Select
|
||||
value={formState.categoriaId}
|
||||
onValueChange={(value) => updateField("categoriaId", value)}
|
||||
value={formState.categoryId}
|
||||
onValueChange={(value) => updateField("categoryId", value)}
|
||||
disabled={isPending}
|
||||
>
|
||||
<SelectTrigger
|
||||
|
||||
@@ -29,7 +29,7 @@ interface AnticipationHistoryDialogProps {
|
||||
lancamentoName: string;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
onViewLancamento?: (lancamentoId: string) => void;
|
||||
onViewLancamento?: (transactionId: string) => void;
|
||||
}
|
||||
|
||||
export function AnticipationHistoryDialog({
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import { useMemo, useState, useTransition } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { createLancamentoAction } from "@/features/transactions/actions";
|
||||
import { groupAndSortCategorias } from "@/features/transactions/categoria-helpers";
|
||||
import { createTransactionAction } from "@/features/transactions/actions";
|
||||
import { groupAndSortCategories } from "@/features/transactions/category-helpers";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -24,46 +24,46 @@ import {
|
||||
SelectValue,
|
||||
} from "@/shared/components/ui/select";
|
||||
import {
|
||||
CategoriaSelectContent,
|
||||
ContaCartaoSelectContent,
|
||||
PagadorSelectContent,
|
||||
CategorySelectContent,
|
||||
AccountCardSelectContent,
|
||||
PayerSelectContent,
|
||||
} from "../select-items";
|
||||
import type { LancamentoItem, SelectOption } from "../types";
|
||||
import type { SelectOption, TransactionItem } from "../types";
|
||||
|
||||
interface BulkImportDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
items: LancamentoItem[];
|
||||
pagadorOptions: SelectOption[];
|
||||
contaOptions: SelectOption[];
|
||||
cartaoOptions: SelectOption[];
|
||||
categoriaOptions: SelectOption[];
|
||||
defaultPagadorId?: string | null;
|
||||
items: TransactionItem[];
|
||||
payerOptions: SelectOption[];
|
||||
accountOptions: SelectOption[];
|
||||
cardOptions: SelectOption[];
|
||||
categoryOptions: SelectOption[];
|
||||
defaultPayerId?: string | null;
|
||||
}
|
||||
|
||||
export function BulkImportDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
items,
|
||||
pagadorOptions,
|
||||
contaOptions,
|
||||
cartaoOptions,
|
||||
categoriaOptions,
|
||||
defaultPagadorId,
|
||||
payerOptions,
|
||||
accountOptions,
|
||||
cardOptions,
|
||||
categoryOptions,
|
||||
defaultPayerId,
|
||||
}: BulkImportDialogProps) {
|
||||
const [pagadorId, setPagadorId] = useState<string | undefined>(
|
||||
defaultPagadorId ?? undefined,
|
||||
const [payerId, setPagadorId] = useState<string | undefined>(
|
||||
defaultPayerId ?? undefined,
|
||||
);
|
||||
const [categoriaId, setCategoriaId] = useState<string | undefined>(undefined);
|
||||
const [contaId, setContaId] = useState<string | undefined>(undefined);
|
||||
const [cartaoId, setCartaoId] = useState<string | undefined>(undefined);
|
||||
const [categoryId, setCategoriaId] = useState<string | undefined>(undefined);
|
||||
const [accountId, setContaId] = useState<string | undefined>(undefined);
|
||||
const [cardId, setCartaoId] = useState<string | undefined>(undefined);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
type CreateLancamentoInput = Parameters<typeof createLancamentoAction>[0];
|
||||
type CreateTransactionInput = Parameters<typeof createTransactionAction>[0];
|
||||
|
||||
// Reset form when dialog opens/closes
|
||||
const handleOpenChange = (newOpen: boolean) => {
|
||||
if (!newOpen) {
|
||||
setPagadorId(defaultPagadorId ?? undefined);
|
||||
setPagadorId(defaultPayerId ?? undefined);
|
||||
setCategoriaId(undefined);
|
||||
setContaId(undefined);
|
||||
setCartaoId(undefined);
|
||||
@@ -71,30 +71,30 @@ export function BulkImportDialog({
|
||||
onOpenChange(newOpen);
|
||||
};
|
||||
|
||||
const categoriaGroups = useMemo(() => {
|
||||
const categoryGroups = useMemo(() => {
|
||||
// Get unique transaction types from items
|
||||
const transactionTypes = new Set(items.map((item) => item.transactionType));
|
||||
|
||||
// Filter categories based on transaction types
|
||||
const filtered = categoriaOptions.filter((option) => {
|
||||
const filtered = categoryOptions.filter((option) => {
|
||||
if (!option.group) return false;
|
||||
return Array.from(transactionTypes).some(
|
||||
(type) => option.group?.toLowerCase() === type.toLowerCase(),
|
||||
);
|
||||
});
|
||||
|
||||
return groupAndSortCategorias(filtered);
|
||||
}, [categoriaOptions, items]);
|
||||
return groupAndSortCategories(filtered);
|
||||
}, [categoryOptions, items]);
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (!pagadorId) {
|
||||
if (!payerId) {
|
||||
toast.error("Selecione o pagador.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!categoriaId) {
|
||||
if (!categoryId) {
|
||||
toast.error("Selecione a categoria.");
|
||||
return;
|
||||
}
|
||||
@@ -110,32 +110,32 @@ export function BulkImportDialog({
|
||||
const isCredit = item.paymentMethod === "Cartão de crédito";
|
||||
|
||||
// Validate payment method fields
|
||||
if (isCredit && !cartaoId) {
|
||||
if (isCredit && !cardId) {
|
||||
toast.error("Selecione um cartão de crédito.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isCredit && !contaId) {
|
||||
if (!isCredit && !accountId) {
|
||||
toast.error("Selecione uma conta.");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload: CreateLancamentoInput = {
|
||||
const payload: CreateTransactionInput = {
|
||||
purchaseDate: item.purchaseDate,
|
||||
period: item.period,
|
||||
name: item.name,
|
||||
transactionType:
|
||||
item.transactionType as CreateLancamentoInput["transactionType"],
|
||||
item.transactionType as CreateTransactionInput["transactionType"],
|
||||
amount: sanitizedAmount,
|
||||
condition: item.condition as CreateLancamentoInput["condition"],
|
||||
condition: item.condition as CreateTransactionInput["condition"],
|
||||
paymentMethod:
|
||||
item.paymentMethod as CreateLancamentoInput["paymentMethod"],
|
||||
pagadorId: pagadorId ?? null,
|
||||
secondaryPagadorId: undefined,
|
||||
item.paymentMethod as CreateTransactionInput["paymentMethod"],
|
||||
payerId: payerId ?? null,
|
||||
secondaryPayerId: undefined,
|
||||
isSplit: false,
|
||||
contaId: isCredit ? null : (contaId ?? null),
|
||||
cartaoId: isCredit ? (cartaoId ?? null) : null,
|
||||
categoriaId: categoriaId ?? null,
|
||||
accountId: isCredit ? null : (accountId ?? null),
|
||||
cardId: isCredit ? (cardId ?? null) : null,
|
||||
categoryId: categoryId ?? null,
|
||||
note: item.note ?? null,
|
||||
isSettled: isCredit ? null : Boolean(item.isSettled),
|
||||
installmentCount:
|
||||
@@ -152,7 +152,7 @@ export function BulkImportDialog({
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const result = await createLancamentoAction(payload);
|
||||
const result = await createTransactionAction(payload);
|
||||
|
||||
if (result.success) {
|
||||
successCount++;
|
||||
@@ -203,17 +203,17 @@ export function BulkImportDialog({
|
||||
|
||||
<form className="space-y-4" onSubmit={handleSubmit}>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="pagador">Pagador *</Label>
|
||||
<Select value={pagadorId} onValueChange={setPagadorId}>
|
||||
<Label htmlFor="pagador">Payer *</Label>
|
||||
<Select value={payerId} onValueChange={setPagadorId}>
|
||||
<SelectTrigger id="pagador" className="w-full">
|
||||
<SelectValue placeholder="Selecione o pagador">
|
||||
{pagadorId &&
|
||||
{payerId &&
|
||||
(() => {
|
||||
const selectedOption = pagadorOptions.find(
|
||||
(opt) => opt.value === pagadorId,
|
||||
const selectedOption = payerOptions.find(
|
||||
(opt) => opt.value === payerId,
|
||||
);
|
||||
return selectedOption ? (
|
||||
<PagadorSelectContent
|
||||
<PayerSelectContent
|
||||
label={selectedOption.label}
|
||||
avatarUrl={selectedOption.avatarUrl}
|
||||
/>
|
||||
@@ -222,9 +222,9 @@ export function BulkImportDialog({
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{pagadorOptions.map((option) => (
|
||||
{payerOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<PagadorSelectContent
|
||||
<PayerSelectContent
|
||||
label={option.label}
|
||||
avatarUrl={option.avatarUrl}
|
||||
/>
|
||||
@@ -235,17 +235,17 @@ export function BulkImportDialog({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="categoria">Categoria *</Label>
|
||||
<Select value={categoriaId} onValueChange={setCategoriaId}>
|
||||
<Label htmlFor="categoria">Category *</Label>
|
||||
<Select value={categoryId} onValueChange={setCategoriaId}>
|
||||
<SelectTrigger id="categoria" className="w-full">
|
||||
<SelectValue placeholder="Selecione a categoria">
|
||||
{categoriaId &&
|
||||
{categoryId &&
|
||||
(() => {
|
||||
const selectedOption = categoriaOptions.find(
|
||||
(opt) => opt.value === categoriaId,
|
||||
const selectedOption = categoryOptions.find(
|
||||
(opt) => opt.value === categoryId,
|
||||
);
|
||||
return selectedOption ? (
|
||||
<CategoriaSelectContent
|
||||
<CategorySelectContent
|
||||
label={selectedOption.label}
|
||||
icon={selectedOption.icon}
|
||||
/>
|
||||
@@ -254,12 +254,12 @@ export function BulkImportDialog({
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categoriaGroups.map((group) => (
|
||||
{categoryGroups.map((group) => (
|
||||
<SelectGroup key={group.label}>
|
||||
<SelectLabel>{group.label}</SelectLabel>
|
||||
{group.options.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<CategoriaSelectContent
|
||||
<CategorySelectContent
|
||||
label={option.label}
|
||||
icon={option.icon}
|
||||
/>
|
||||
@@ -274,18 +274,18 @@ export function BulkImportDialog({
|
||||
{hasNonCredit && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="conta">
|
||||
Conta {hasCredit ? "(para não cartão)" : "*"}
|
||||
FinancialAccount {hasCredit ? "(para não cartão)" : "*"}
|
||||
</Label>
|
||||
<Select value={contaId} onValueChange={setContaId}>
|
||||
<Select value={accountId} onValueChange={setContaId}>
|
||||
<SelectTrigger id="conta" className="w-full">
|
||||
<SelectValue placeholder="Selecione a conta">
|
||||
{contaId &&
|
||||
{accountId &&
|
||||
(() => {
|
||||
const selectedOption = contaOptions.find(
|
||||
(opt) => opt.value === contaId,
|
||||
const selectedOption = accountOptions.find(
|
||||
(opt) => opt.value === accountId,
|
||||
);
|
||||
return selectedOption ? (
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={selectedOption.label}
|
||||
logo={selectedOption.logo}
|
||||
isCartao={false}
|
||||
@@ -295,9 +295,9 @@ export function BulkImportDialog({
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{contaOptions.map((option) => (
|
||||
{accountOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={option.label}
|
||||
logo={option.logo}
|
||||
isCartao={false}
|
||||
@@ -314,16 +314,16 @@ export function BulkImportDialog({
|
||||
<Label htmlFor="cartao">
|
||||
Cartão {hasNonCredit ? "(para cartão de crédito)" : "*"}
|
||||
</Label>
|
||||
<Select value={cartaoId} onValueChange={setCartaoId}>
|
||||
<Select value={cardId} onValueChange={setCartaoId}>
|
||||
<SelectTrigger id="cartao" className="w-full">
|
||||
<SelectValue placeholder="Selecione o cartão">
|
||||
{cartaoId &&
|
||||
{cardId &&
|
||||
(() => {
|
||||
const selectedOption = cartaoOptions.find(
|
||||
(opt) => opt.value === cartaoId,
|
||||
const selectedOption = cardOptions.find(
|
||||
(opt) => opt.value === cardId,
|
||||
);
|
||||
return selectedOption ? (
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={selectedOption.label}
|
||||
logo={selectedOption.logo}
|
||||
isCartao={true}
|
||||
@@ -333,9 +333,9 @@ export function BulkImportDialog({
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{cartaoOptions.map((option) => (
|
||||
{cardOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={option.label}
|
||||
logo={option.logo}
|
||||
isCartao={true}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
import { RiAddLine, RiDeleteBinLine } from "@remixicon/react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { groupAndSortCategorias } from "@/features/transactions/categoria-helpers";
|
||||
import { groupAndSortCategories } from "@/features/transactions/category-helpers";
|
||||
import {
|
||||
LANCAMENTO_PAYMENT_METHODS,
|
||||
type LANCAMENTO_TRANSACTION_TYPES,
|
||||
PAYMENT_METHODS,
|
||||
type TRANSACTION_TYPES,
|
||||
} from "@/features/transactions/constants";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { CurrencyInput } from "@/shared/components/ui/currency-input";
|
||||
@@ -44,9 +44,9 @@ import {
|
||||
periodToDate,
|
||||
} from "@/shared/utils/period";
|
||||
import {
|
||||
CategoriaSelectContent,
|
||||
ContaCartaoSelectContent,
|
||||
PagadorSelectContent,
|
||||
CategorySelectContent,
|
||||
AccountCardSelectContent,
|
||||
PayerSelectContent,
|
||||
PaymentMethodSelectContent,
|
||||
TransactionTypeSelectContent,
|
||||
} from "../select-items";
|
||||
@@ -54,11 +54,9 @@ import { EstabelecimentoInput } from "../shared/establishment-input";
|
||||
import type { SelectOption } from "../types";
|
||||
|
||||
/** Payment methods sem Boleto para este modal */
|
||||
const MASS_ADD_PAYMENT_METHODS = LANCAMENTO_PAYMENT_METHODS.filter(
|
||||
(m) => m !== "Boleto",
|
||||
);
|
||||
type MassAddTransactionType = (typeof LANCAMENTO_TRANSACTION_TYPES)[number];
|
||||
type MassAddPaymentMethod = (typeof LANCAMENTO_PAYMENT_METHODS)[number];
|
||||
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,
|
||||
@@ -71,7 +69,7 @@ function InlinePeriodPicker({
|
||||
|
||||
return (
|
||||
<div className="-mt-1">
|
||||
<span className="text-xs text-muted-foreground">Fatura de </span>
|
||||
<span className="text-xs text-muted-foreground">Invoice de </span>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
@@ -99,18 +97,18 @@ interface MassAddDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSubmit: (data: MassAddFormData) => Promise<void>;
|
||||
pagadorOptions: SelectOption[];
|
||||
contaOptions: SelectOption[];
|
||||
cartaoOptions: SelectOption[];
|
||||
categoriaOptions: SelectOption[];
|
||||
payerOptions: SelectOption[];
|
||||
accountOptions: SelectOption[];
|
||||
cardOptions: SelectOption[];
|
||||
categoryOptions: SelectOption[];
|
||||
estabelecimentos: string[];
|
||||
selectedPeriod: string;
|
||||
defaultPagadorId?: string | null;
|
||||
defaultCartaoId?: string | null;
|
||||
defaultPayerId?: string | null;
|
||||
defaultCardId?: string | null;
|
||||
}
|
||||
|
||||
export type MassAddFormData = Parameters<
|
||||
typeof import("@/features/transactions/actions").createMassLancamentosAction
|
||||
typeof import("@/features/transactions/actions").createMassTransactionsAction
|
||||
>[0];
|
||||
|
||||
interface TransactionRow {
|
||||
@@ -118,22 +116,22 @@ interface TransactionRow {
|
||||
purchaseDate: string;
|
||||
name: string;
|
||||
amount: string;
|
||||
categoriaId: string | undefined;
|
||||
pagadorId: string | undefined;
|
||||
categoryId: string | undefined;
|
||||
payerId: string | undefined;
|
||||
}
|
||||
|
||||
export function MassAddDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
onSubmit,
|
||||
pagadorOptions,
|
||||
contaOptions,
|
||||
cartaoOptions,
|
||||
categoriaOptions,
|
||||
payerOptions,
|
||||
accountOptions,
|
||||
cardOptions,
|
||||
categoryOptions,
|
||||
estabelecimentos,
|
||||
selectedPeriod,
|
||||
defaultPagadorId,
|
||||
defaultCartaoId,
|
||||
defaultPayerId,
|
||||
defaultCardId,
|
||||
}: MassAddDialogProps) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
@@ -141,16 +139,16 @@ export function MassAddDialog({
|
||||
const [transactionType, setTransactionType] =
|
||||
useState<MassAddTransactionType>("Despesa");
|
||||
const [paymentMethod, setPaymentMethod] = useState<MassAddPaymentMethod>(
|
||||
LANCAMENTO_PAYMENT_METHODS[0],
|
||||
PAYMENT_METHODS[0],
|
||||
);
|
||||
const [period, setPeriod] = useState<string>(selectedPeriod);
|
||||
const [contaId, setContaId] = useState<string | undefined>(undefined);
|
||||
const [cartaoId, setCartaoId] = useState<string | undefined>(
|
||||
defaultCartaoId ?? undefined,
|
||||
const [accountId, setContaId] = useState<string | undefined>(undefined);
|
||||
const [cardId, setCartaoId] = useState<string | undefined>(
|
||||
defaultCardId ?? undefined,
|
||||
);
|
||||
|
||||
// Quando defaultCartaoId está definido, exibe apenas o cartão específico
|
||||
const isLockedToCartao = !!defaultCartaoId;
|
||||
// Quando defaultCardId está definido, exibe apenas o cartão específico
|
||||
const isLockedToCartao = !!defaultCardId;
|
||||
|
||||
const isCartaoSelected = paymentMethod === "Cartão de crédito";
|
||||
|
||||
@@ -161,18 +159,18 @@ export function MassAddDialog({
|
||||
purchaseDate: getTodayDateString(),
|
||||
name: "",
|
||||
amount: "",
|
||||
categoriaId: undefined,
|
||||
pagadorId: defaultPagadorId ?? undefined,
|
||||
categoryId: undefined,
|
||||
payerId: defaultPayerId ?? undefined,
|
||||
},
|
||||
]);
|
||||
|
||||
// Categorias agrupadas e filtradas por tipo de transação
|
||||
const groupedCategorias = useMemo(() => {
|
||||
const filtered = categoriaOptions.filter(
|
||||
const filtered = categoryOptions.filter(
|
||||
(option) => option.group?.toLowerCase() === transactionType.toLowerCase(),
|
||||
);
|
||||
return groupAndSortCategorias(filtered);
|
||||
}, [categoriaOptions, transactionType]);
|
||||
return groupAndSortCategories(filtered);
|
||||
}, [categoryOptions, transactionType]);
|
||||
|
||||
const addTransaction = () => {
|
||||
setTransactions([
|
||||
@@ -182,8 +180,8 @@ export function MassAddDialog({
|
||||
purchaseDate: getTodayDateString(),
|
||||
name: "",
|
||||
amount: "",
|
||||
categoriaId: undefined,
|
||||
pagadorId: defaultPagadorId ?? undefined,
|
||||
categoryId: undefined,
|
||||
payerId: defaultPayerId ?? undefined,
|
||||
},
|
||||
]);
|
||||
};
|
||||
@@ -208,11 +206,11 @@ export function MassAddDialog({
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// Validate conta/cartao selection
|
||||
if (isCartaoSelected && !cartaoId) {
|
||||
if (isCartaoSelected && !cardId) {
|
||||
toast.error("Selecione um cartão para continuar");
|
||||
return;
|
||||
}
|
||||
if (!isCartaoSelected && !contaId) {
|
||||
if (!isCartaoSelected && !accountId) {
|
||||
toast.error("Selecione uma conta para continuar");
|
||||
return;
|
||||
}
|
||||
@@ -236,15 +234,15 @@ export function MassAddDialog({
|
||||
paymentMethod,
|
||||
condition: "À vista",
|
||||
period,
|
||||
contaId,
|
||||
cartaoId,
|
||||
accountId,
|
||||
cardId,
|
||||
},
|
||||
transactions: transactions.map((t) => ({
|
||||
purchaseDate: t.purchaseDate,
|
||||
name: t.name.trim(),
|
||||
amount: Number(t.amount.trim()),
|
||||
categoriaId: t.categoriaId,
|
||||
pagadorId: t.pagadorId,
|
||||
categoryId: t.categoryId,
|
||||
payerId: t.payerId,
|
||||
})),
|
||||
};
|
||||
|
||||
@@ -254,18 +252,18 @@ export function MassAddDialog({
|
||||
onOpenChange(false);
|
||||
// Reset form
|
||||
setTransactionType("Despesa");
|
||||
setPaymentMethod(LANCAMENTO_PAYMENT_METHODS[0]);
|
||||
setPaymentMethod(PAYMENT_METHODS[0]);
|
||||
setPeriod(selectedPeriod);
|
||||
setContaId(undefined);
|
||||
setCartaoId(defaultCartaoId ?? undefined);
|
||||
setCartaoId(defaultCardId ?? undefined);
|
||||
setTransactions([
|
||||
{
|
||||
id: crypto.randomUUID(),
|
||||
purchaseDate: getTodayDateString(),
|
||||
name: "",
|
||||
amount: "",
|
||||
categoriaId: undefined,
|
||||
pagadorId: defaultPagadorId ?? undefined,
|
||||
categoryId: undefined,
|
||||
payerId: defaultPayerId ?? undefined,
|
||||
},
|
||||
]);
|
||||
} catch (_error) {
|
||||
@@ -356,19 +354,19 @@ export function MassAddDialog({
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cartao">Cartão</Label>
|
||||
<Select
|
||||
value={cartaoId}
|
||||
value={cardId}
|
||||
onValueChange={setCartaoId}
|
||||
disabled={isLockedToCartao}
|
||||
>
|
||||
<SelectTrigger id="cartao" className="w-full">
|
||||
<SelectValue placeholder="Selecione">
|
||||
{cartaoId &&
|
||||
{cardId &&
|
||||
(() => {
|
||||
const selectedOption = cartaoOptions.find(
|
||||
(opt) => opt.value === cartaoId,
|
||||
const selectedOption = cardOptions.find(
|
||||
(opt) => opt.value === cardId,
|
||||
);
|
||||
return selectedOption ? (
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={selectedOption.label}
|
||||
logo={selectedOption.logo}
|
||||
isCartao={true}
|
||||
@@ -378,22 +376,22 @@ export function MassAddDialog({
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{cartaoOptions.length === 0 ? (
|
||||
{cardOptions.length === 0 ? (
|
||||
<div className="px-2 py-6 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Nenhum cartão cadastrado
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
cartaoOptions
|
||||
cardOptions
|
||||
.filter(
|
||||
(option) =>
|
||||
!isLockedToCartao ||
|
||||
option.value === defaultCartaoId,
|
||||
option.value === defaultCardId,
|
||||
)
|
||||
.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={option.label}
|
||||
logo={option.logo}
|
||||
isCartao={true}
|
||||
@@ -403,7 +401,7 @@ export function MassAddDialog({
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{cartaoId ? (
|
||||
{cardId ? (
|
||||
<InlinePeriodPicker
|
||||
period={period}
|
||||
onPeriodChange={setPeriod}
|
||||
@@ -412,20 +410,20 @@ export function MassAddDialog({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* Conta (for non-credit-card methods) */}
|
||||
{/* FinancialAccount (for non-credit-card methods) */}
|
||||
{!isCartaoSelected ? (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="conta">Conta</Label>
|
||||
<Select value={contaId} onValueChange={setContaId}>
|
||||
<Label htmlFor="conta">FinancialAccount</Label>
|
||||
<Select value={accountId} onValueChange={setContaId}>
|
||||
<SelectTrigger id="conta" className="w-full">
|
||||
<SelectValue placeholder="Selecione">
|
||||
{contaId &&
|
||||
{accountId &&
|
||||
(() => {
|
||||
const selectedOption = contaOptions.find(
|
||||
(opt) => opt.value === contaId,
|
||||
const selectedOption = accountOptions.find(
|
||||
(opt) => opt.value === accountId,
|
||||
);
|
||||
return selectedOption ? (
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={selectedOption.label}
|
||||
logo={selectedOption.logo}
|
||||
isCartao={false}
|
||||
@@ -435,16 +433,16 @@ export function MassAddDialog({
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{contaOptions.length === 0 ? (
|
||||
{accountOptions.length === 0 ? (
|
||||
<div className="px-2 py-6 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Nenhuma conta cadastrada
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
contaOptions.map((option) => (
|
||||
accountOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={option.label}
|
||||
logo={option.logo}
|
||||
isCartao={false}
|
||||
@@ -536,26 +534,26 @@ export function MassAddDialog({
|
||||
htmlFor={`pagador-${transaction.id}`}
|
||||
className="sr-only"
|
||||
>
|
||||
Pagador {index + 1}
|
||||
Payer {index + 1}
|
||||
</Label>
|
||||
<Select
|
||||
value={transaction.pagadorId}
|
||||
value={transaction.payerId}
|
||||
onValueChange={(value) =>
|
||||
updateTransaction(transaction.id, "pagadorId", value)
|
||||
updateTransaction(transaction.id, "payerId", value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger
|
||||
id={`pagador-${transaction.id}`}
|
||||
className="w-32 truncate"
|
||||
>
|
||||
<SelectValue placeholder="Pagador">
|
||||
{transaction.pagadorId &&
|
||||
<SelectValue placeholder="Payer">
|
||||
{transaction.payerId &&
|
||||
(() => {
|
||||
const selectedOption = pagadorOptions.find(
|
||||
(opt) => opt.value === transaction.pagadorId,
|
||||
const selectedOption = payerOptions.find(
|
||||
(opt) => opt.value === transaction.payerId,
|
||||
);
|
||||
return selectedOption ? (
|
||||
<PagadorSelectContent
|
||||
<PayerSelectContent
|
||||
label={selectedOption.label}
|
||||
avatarUrl={selectedOption.avatarUrl}
|
||||
/>
|
||||
@@ -564,9 +562,9 @@ export function MassAddDialog({
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{pagadorOptions.map((option) => (
|
||||
{payerOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<PagadorSelectContent
|
||||
<PayerSelectContent
|
||||
label={option.label}
|
||||
avatarUrl={option.avatarUrl}
|
||||
/>
|
||||
@@ -581,23 +579,19 @@ export function MassAddDialog({
|
||||
htmlFor={`categoria-${transaction.id}`}
|
||||
className="sr-only"
|
||||
>
|
||||
Categoria {index + 1}
|
||||
Category {index + 1}
|
||||
</Label>
|
||||
<Select
|
||||
value={transaction.categoriaId}
|
||||
value={transaction.categoryId}
|
||||
onValueChange={(value) =>
|
||||
updateTransaction(
|
||||
transaction.id,
|
||||
"categoriaId",
|
||||
value,
|
||||
)
|
||||
updateTransaction(transaction.id, "categoryId", value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger
|
||||
id={`categoria-${transaction.id}`}
|
||||
className="w-32 truncate"
|
||||
>
|
||||
<SelectValue placeholder="Categoria" />
|
||||
<SelectValue placeholder="Category" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{groupedCategorias.map((group) => (
|
||||
@@ -608,7 +602,7 @@ export function MassAddDialog({
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
>
|
||||
<CategoriaSelectContent
|
||||
<CategorySelectContent
|
||||
label={option.label}
|
||||
icon={option.icon}
|
||||
/>
|
||||
|
||||
@@ -25,29 +25,29 @@ import { Separator } from "@/shared/components/ui/separator";
|
||||
import { parseLocalDateString } from "@/shared/utils/date";
|
||||
import { getPaymentMethodIcon } from "@/shared/utils/icons";
|
||||
import { InstallmentTimeline } from "../shared/installment-timeline";
|
||||
import type { LancamentoItem } from "../types";
|
||||
import type { TransactionItem } from "../types";
|
||||
|
||||
interface LancamentoDetailsDialogProps {
|
||||
interface TransactionDetailsDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
lancamento: LancamentoItem | null;
|
||||
transaction: TransactionItem | null;
|
||||
}
|
||||
|
||||
export function LancamentoDetailsDialog({
|
||||
export function TransactionDetailsDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
lancamento,
|
||||
}: LancamentoDetailsDialogProps) {
|
||||
if (!lancamento) return null;
|
||||
transaction,
|
||||
}: TransactionDetailsDialogProps) {
|
||||
if (!transaction) return null;
|
||||
|
||||
const isInstallment =
|
||||
lancamento.condition?.toLowerCase() === "parcelado" &&
|
||||
lancamento.currentInstallment &&
|
||||
lancamento.installmentCount;
|
||||
transaction.condition?.toLowerCase() === "parcelado" &&
|
||||
transaction.currentInstallment &&
|
||||
transaction.installmentCount;
|
||||
|
||||
const valorParcela = Math.abs(lancamento.amount);
|
||||
const totalParcelas = lancamento.installmentCount ?? 1;
|
||||
const parcelaAtual = lancamento.currentInstallment ?? 1;
|
||||
const valorParcela = Math.abs(transaction.amount);
|
||||
const totalParcelas = transaction.installmentCount ?? 1;
|
||||
const parcelaAtual = transaction.currentInstallment ?? 1;
|
||||
const valorTotal = isInstallment
|
||||
? valorParcela * totalParcelas
|
||||
: valorParcela;
|
||||
@@ -62,10 +62,10 @@ export function LancamentoDetailsDialog({
|
||||
<CardHeader className="flex flex-row items-start border-b sm:border-b-0">
|
||||
<div>
|
||||
<DialogTitle className="group flex items-center gap-2 text-lg">
|
||||
#{lancamento.id}
|
||||
#{transaction.id}
|
||||
</DialogTitle>
|
||||
<CardDescription>
|
||||
{formatDate(lancamento.purchaseDate)}
|
||||
{formatDate(transaction.purchaseDate)}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
@@ -73,11 +73,11 @@ export function LancamentoDetailsDialog({
|
||||
<CardContent className="text-sm">
|
||||
<div className="grid gap-3">
|
||||
<ul className="grid gap-3">
|
||||
<DetailRow label="Descrição" value={lancamento.name} />
|
||||
<DetailRow label="Descrição" value={transaction.name} />
|
||||
|
||||
<DetailRow
|
||||
label="Período"
|
||||
value={formatPeriod(lancamento.period)}
|
||||
value={formatPeriod(transaction.period)}
|
||||
/>
|
||||
|
||||
<li className="flex items-center justify-between">
|
||||
@@ -85,21 +85,21 @@ export function LancamentoDetailsDialog({
|
||||
Forma de Pagamento
|
||||
</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
{getPaymentMethodIcon(lancamento.paymentMethod)}
|
||||
{getPaymentMethodIcon(transaction.paymentMethod)}
|
||||
<span className="capitalize">
|
||||
{lancamento.paymentMethod}
|
||||
{transaction.paymentMethod}
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<DetailRow
|
||||
label={lancamento.cartaoName ? "Cartão" : "Conta"}
|
||||
value={lancamento.cartaoName ?? lancamento.contaName ?? "—"}
|
||||
label={transaction.cartaoName ? "Cartão" : "FinancialAccount"}
|
||||
value={transaction.cartaoName ?? transaction.contaName ?? "—"}
|
||||
/>
|
||||
|
||||
<DetailRow
|
||||
label="Categoria"
|
||||
value={lancamento.categoriaName ?? "—"}
|
||||
label="Category"
|
||||
value={transaction.categoriaName ?? "—"}
|
||||
/>
|
||||
|
||||
<li className="flex items-center justify-between">
|
||||
@@ -109,37 +109,37 @@ export function LancamentoDetailsDialog({
|
||||
<span className="capitalize">
|
||||
<Badge
|
||||
variant={getTransactionBadgeVariant(
|
||||
lancamento.categoriaName === "Saldo inicial"
|
||||
transaction.categoriaName === "Saldo inicial"
|
||||
? "Saldo inicial"
|
||||
: lancamento.transactionType,
|
||||
: transaction.transactionType,
|
||||
)}
|
||||
>
|
||||
{lancamento.categoriaName === "Saldo inicial"
|
||||
{transaction.categoriaName === "Saldo inicial"
|
||||
? "Saldo Inicial"
|
||||
: lancamento.transactionType}
|
||||
: transaction.transactionType}
|
||||
</Badge>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<DetailRow
|
||||
label="Condição"
|
||||
value={formatCondition(lancamento.condition)}
|
||||
value={formatCondition(transaction.condition)}
|
||||
/>
|
||||
|
||||
<li className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Responsável</span>
|
||||
<span className="flex items-center gap-2 capitalize">
|
||||
<span>{lancamento.pagadorName}</span>
|
||||
<span>{transaction.pagadorName}</span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<DetailRow
|
||||
label="Status"
|
||||
value={lancamento.isSettled ? "Pago" : "Pendente"}
|
||||
value={transaction.isSettled ? "Pago" : "Pendente"}
|
||||
/>
|
||||
|
||||
{lancamento.note && (
|
||||
<DetailRow label="Notas" value={lancamento.note} />
|
||||
{transaction.note && (
|
||||
<DetailRow label="Notas" value={transaction.note} />
|
||||
)}
|
||||
</ul>
|
||||
|
||||
@@ -148,11 +148,11 @@ export function LancamentoDetailsDialog({
|
||||
<li className="mt-4">
|
||||
<InstallmentTimeline
|
||||
purchaseDate={parseLocalDateString(
|
||||
lancamento.purchaseDate,
|
||||
transaction.purchaseDate,
|
||||
)}
|
||||
currentInstallment={parcelaAtual}
|
||||
totalInstallments={totalParcelas}
|
||||
period={lancamento.period}
|
||||
period={transaction.period}
|
||||
/>
|
||||
</li>
|
||||
)}
|
||||
@@ -169,10 +169,10 @@ export function LancamentoDetailsDialog({
|
||||
/>
|
||||
)}
|
||||
|
||||
{lancamento.recurrenceCount && (
|
||||
{transaction.recurrenceCount && (
|
||||
<DetailRow
|
||||
label="Quantidade de Recorrências"
|
||||
value={`${lancamento.recurrenceCount} meses`}
|
||||
value={`${transaction.recurrenceCount} meses`}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { LANCAMENTO_TRANSACTION_TYPES } from "@/features/transactions/constants";
|
||||
import { TRANSACTION_TYPES } from "@/features/transactions/constants";
|
||||
import { Label } from "@/shared/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
} from "@/shared/components/ui/select";
|
||||
import { cn } from "@/shared/utils/ui";
|
||||
import {
|
||||
CategoriaSelectContent,
|
||||
CategorySelectContent,
|
||||
TransactionTypeSelectContent,
|
||||
} from "../../select-items";
|
||||
import type { CategorySectionProps } from "./transaction-dialog-types";
|
||||
@@ -21,8 +21,8 @@ import type { CategorySectionProps } from "./transaction-dialog-types";
|
||||
export function CategorySection({
|
||||
formState,
|
||||
onFieldChange,
|
||||
categoriaOptions,
|
||||
categoriaGroups,
|
||||
categoryOptions,
|
||||
categoryGroups,
|
||||
isUpdateMode,
|
||||
hideTransactionType = false,
|
||||
}: CategorySectionProps) {
|
||||
@@ -47,13 +47,13 @@ export function CategorySection({
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{LANCAMENTO_TRANSACTION_TYPES.filter(
|
||||
(type) => type !== "Transferência",
|
||||
).map((type) => (
|
||||
<SelectItem key={type} value={type}>
|
||||
<TransactionTypeSelectContent label={type} />
|
||||
</SelectItem>
|
||||
))}
|
||||
{TRANSACTION_TYPES.filter((type) => type !== "Transferência").map(
|
||||
(type) => (
|
||||
<SelectItem key={type} value={type}>
|
||||
<TransactionTypeSelectContent label={type} />
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -65,20 +65,20 @@ export function CategorySection({
|
||||
showTransactionTypeField ? "md:w-1/2" : "md:w-full",
|
||||
)}
|
||||
>
|
||||
<Label htmlFor="categoria">Categoria</Label>
|
||||
<Label htmlFor="categoria">Category</Label>
|
||||
<Select
|
||||
value={formState.categoriaId}
|
||||
onValueChange={(value) => onFieldChange("categoriaId", value)}
|
||||
value={formState.categoryId}
|
||||
onValueChange={(value) => onFieldChange("categoryId", value)}
|
||||
>
|
||||
<SelectTrigger id="categoria" className="w-full">
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.categoriaId &&
|
||||
{formState.categoryId &&
|
||||
(() => {
|
||||
const selectedOption = categoriaOptions.find(
|
||||
(opt) => opt.value === formState.categoriaId,
|
||||
const selectedOption = categoryOptions.find(
|
||||
(opt) => opt.value === formState.categoryId,
|
||||
);
|
||||
return selectedOption ? (
|
||||
<CategoriaSelectContent
|
||||
<CategorySelectContent
|
||||
label={selectedOption.label}
|
||||
icon={selectedOption.icon}
|
||||
/>
|
||||
@@ -87,12 +87,12 @@ export function CategorySection({
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categoriaGroups.map((group) => (
|
||||
{categoryGroups.map((group) => (
|
||||
<SelectGroup key={group.label}>
|
||||
<SelectLabel>{group.label}</SelectLabel>
|
||||
{group.options.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<CategoriaSelectContent
|
||||
<CategorySelectContent
|
||||
label={option.label}
|
||||
icon={option.icon}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { LANCAMENTO_CONDITIONS } from "@/features/transactions/constants";
|
||||
import { TRANSACTION_CONDITIONS } from "@/features/transactions/constants";
|
||||
import { Label } from "@/shared/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
@@ -64,7 +64,7 @@ export function ConditionSection({
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{LANCAMENTO_CONDITIONS.map((condition) => (
|
||||
{TRANSACTION_CONDITIONS.map((condition) => (
|
||||
<SelectItem key={condition} value={condition}>
|
||||
<ConditionSelectContent label={condition} />
|
||||
</SelectItem>
|
||||
|
||||
@@ -9,16 +9,16 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/shared/components/ui/select";
|
||||
import { PagadorSelectContent } from "../../select-items";
|
||||
import type { PagadorSectionProps } from "./transaction-dialog-types";
|
||||
import { PayerSelectContent } from "../../select-items";
|
||||
import type { PayerSectionProps } from "./transaction-dialog-types";
|
||||
|
||||
export function PagadorSection({
|
||||
export function PayerSection({
|
||||
formState,
|
||||
onFieldChange,
|
||||
pagadorOptions,
|
||||
secondaryPagadorOptions,
|
||||
payerOptions,
|
||||
secondaryPayerOptions,
|
||||
totalAmount,
|
||||
}: PagadorSectionProps) {
|
||||
}: PayerSectionProps) {
|
||||
const handlePrimaryAmountChange = (value: string) => {
|
||||
onFieldChange("primarySplitAmount", value);
|
||||
const numericValue = Number.parseFloat(value) || 0;
|
||||
@@ -36,24 +36,24 @@ export function PagadorSection({
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-2 md:flex-row">
|
||||
<div className="w-full space-y-1">
|
||||
<Label htmlFor="pagador">Pagador</Label>
|
||||
<Label htmlFor="payer">Payer</Label>
|
||||
<div className="flex gap-2">
|
||||
<Select
|
||||
value={formState.pagadorId}
|
||||
onValueChange={(value) => onFieldChange("pagadorId", value)}
|
||||
value={formState.payerId}
|
||||
onValueChange={(value) => onFieldChange("payerId", value)}
|
||||
>
|
||||
<SelectTrigger
|
||||
id="pagador"
|
||||
id="payer"
|
||||
className={formState.isSplit ? "w-[55%]" : "w-full"}
|
||||
>
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.pagadorId &&
|
||||
{formState.payerId &&
|
||||
(() => {
|
||||
const selectedOption = pagadorOptions.find(
|
||||
(opt) => opt.value === formState.pagadorId,
|
||||
const selectedOption = payerOptions.find(
|
||||
(opt) => opt.value === formState.payerId,
|
||||
);
|
||||
return selectedOption ? (
|
||||
<PagadorSelectContent
|
||||
<PayerSelectContent
|
||||
label={selectedOption.label}
|
||||
avatarUrl={selectedOption.avatarUrl}
|
||||
/>
|
||||
@@ -62,9 +62,9 @@ export function PagadorSection({
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{pagadorOptions.map((option) => (
|
||||
{payerOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<PagadorSelectContent
|
||||
<PayerSelectContent
|
||||
label={option.label}
|
||||
avatarUrl={option.avatarUrl}
|
||||
/>
|
||||
@@ -85,27 +85,27 @@ export function PagadorSection({
|
||||
|
||||
{formState.isSplit ? (
|
||||
<div className="w-full space-y-1 mb-1">
|
||||
<Label htmlFor="secondaryPagador">Dividir com</Label>
|
||||
<Label htmlFor="secondaryPayer">Dividir com</Label>
|
||||
<div className="flex gap-2">
|
||||
<Select
|
||||
value={formState.secondaryPagadorId}
|
||||
value={formState.secondaryPayerId}
|
||||
onValueChange={(value) =>
|
||||
onFieldChange("secondaryPagadorId", value)
|
||||
onFieldChange("secondaryPayerId", value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger
|
||||
id="secondaryPagador"
|
||||
disabled={secondaryPagadorOptions.length === 0}
|
||||
id="secondaryPayer"
|
||||
disabled={secondaryPayerOptions.length === 0}
|
||||
className="w-[55%]"
|
||||
>
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.secondaryPagadorId &&
|
||||
{formState.secondaryPayerId &&
|
||||
(() => {
|
||||
const selectedOption = secondaryPagadorOptions.find(
|
||||
(opt) => opt.value === formState.secondaryPagadorId,
|
||||
const selectedOption = secondaryPayerOptions.find(
|
||||
(opt) => opt.value === formState.secondaryPayerId,
|
||||
);
|
||||
return selectedOption ? (
|
||||
<PagadorSelectContent
|
||||
<PayerSelectContent
|
||||
label={selectedOption.label}
|
||||
avatarUrl={selectedOption.avatarUrl}
|
||||
/>
|
||||
@@ -114,9 +114,9 @@ export function PagadorSection({
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{secondaryPagadorOptions.map((option) => (
|
||||
{secondaryPayerOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<PagadorSelectContent
|
||||
<PayerSelectContent
|
||||
label={option.label}
|
||||
avatarUrl={option.avatarUrl}
|
||||
/>
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { LANCAMENTO_PAYMENT_METHODS } from "@/features/transactions/constants";
|
||||
import { PAYMENT_METHODS } from "@/features/transactions/constants";
|
||||
import { Label } from "@/shared/components/ui/label";
|
||||
import { MonthPicker } from "@/shared/components/ui/month-picker";
|
||||
import {
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
} from "@/shared/utils/period";
|
||||
import { cn } from "@/shared/utils/ui";
|
||||
import {
|
||||
ContaCartaoSelectContent,
|
||||
AccountCardSelectContent,
|
||||
PaymentMethodSelectContent,
|
||||
} from "../../select-items";
|
||||
import type { PaymentMethodSectionProps } from "./transaction-dialog-types";
|
||||
@@ -39,7 +39,7 @@ function InlinePeriodPicker({
|
||||
|
||||
return (
|
||||
<div className="ml-1">
|
||||
<span className="text-xs text-muted-foreground">Fatura de </span>
|
||||
<span className="text-xs text-muted-foreground">Invoice de </span>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
@@ -66,11 +66,11 @@ function InlinePeriodPicker({
|
||||
export function PaymentMethodSection({
|
||||
formState,
|
||||
onFieldChange,
|
||||
contaOptions,
|
||||
cartaoOptions,
|
||||
accountOptions,
|
||||
cardOptions,
|
||||
isUpdateMode,
|
||||
disablePaymentMethod,
|
||||
disableCartaoSelect,
|
||||
disableCardSelect,
|
||||
}: PaymentMethodSectionProps) {
|
||||
const isCartaoSelected = formState.paymentMethod === "Cartão de crédito";
|
||||
const showContaSelect = [
|
||||
@@ -85,10 +85,10 @@ export function PaymentMethodSection({
|
||||
// Filtrar contas apenas do tipo "Pré-Pago | VR/VA" quando forma de pagamento for "Pré-Pago | VR/VA"
|
||||
const filteredContaOptions =
|
||||
formState.paymentMethod === "Pré-Pago | VR/VA"
|
||||
? contaOptions.filter(
|
||||
? accountOptions.filter(
|
||||
(option) => option.accountType === "Pré-Pago | VR/VA",
|
||||
)
|
||||
: contaOptions;
|
||||
: accountOptions;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -120,7 +120,7 @@ export function PaymentMethodSection({
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{LANCAMENTO_PAYMENT_METHODS.map((method) => (
|
||||
{PAYMENT_METHODS.map((method) => (
|
||||
<SelectItem key={method} value={method}>
|
||||
<PaymentMethodSelectContent label={method} />
|
||||
</SelectItem>
|
||||
@@ -133,23 +133,23 @@ export function PaymentMethodSection({
|
||||
<div className="space-y-1 w-full md:w-1/2">
|
||||
<Label htmlFor="cartao">Cartão</Label>
|
||||
<Select
|
||||
value={formState.cartaoId}
|
||||
onValueChange={(value) => onFieldChange("cartaoId", value)}
|
||||
disabled={disableCartaoSelect}
|
||||
value={formState.cardId}
|
||||
onValueChange={(value) => onFieldChange("cardId", value)}
|
||||
disabled={disableCardSelect}
|
||||
>
|
||||
<SelectTrigger
|
||||
id="cartao"
|
||||
className="w-full"
|
||||
disabled={disableCartaoSelect}
|
||||
disabled={disableCardSelect}
|
||||
>
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.cartaoId &&
|
||||
{formState.cardId &&
|
||||
(() => {
|
||||
const selectedOption = cartaoOptions.find(
|
||||
(opt) => opt.value === formState.cartaoId,
|
||||
const selectedOption = cardOptions.find(
|
||||
(opt) => opt.value === formState.cardId,
|
||||
);
|
||||
return selectedOption ? (
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={selectedOption.label}
|
||||
logo={selectedOption.logo}
|
||||
isCartao={true}
|
||||
@@ -159,16 +159,16 @@ export function PaymentMethodSection({
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{cartaoOptions.length === 0 ? (
|
||||
{cardOptions.length === 0 ? (
|
||||
<div className="px-2 py-6 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Nenhum cartão cadastrado
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
cartaoOptions.map((option) => (
|
||||
cardOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={option.label}
|
||||
logo={option.logo}
|
||||
isCartao={true}
|
||||
@@ -178,7 +178,7 @@ export function PaymentMethodSection({
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{formState.cartaoId ? (
|
||||
{formState.cardId ? (
|
||||
<InlinePeriodPicker
|
||||
period={formState.period}
|
||||
onPeriodChange={(value) => onFieldChange("period", value)}
|
||||
@@ -194,20 +194,20 @@ export function PaymentMethodSection({
|
||||
!isUpdateMode ? "md:w-1/2" : "md:w-full",
|
||||
)}
|
||||
>
|
||||
<Label htmlFor="conta">Conta</Label>
|
||||
<Label htmlFor="conta">FinancialAccount</Label>
|
||||
<Select
|
||||
value={formState.contaId}
|
||||
onValueChange={(value) => onFieldChange("contaId", value)}
|
||||
value={formState.accountId}
|
||||
onValueChange={(value) => onFieldChange("accountId", value)}
|
||||
>
|
||||
<SelectTrigger id="conta" className="w-full">
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.contaId &&
|
||||
{formState.accountId &&
|
||||
(() => {
|
||||
const selectedOption = filteredContaOptions.find(
|
||||
(opt) => opt.value === formState.contaId,
|
||||
(opt) => opt.value === formState.accountId,
|
||||
);
|
||||
return selectedOption ? (
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={selectedOption.label}
|
||||
logo={selectedOption.logo}
|
||||
isCartao={false}
|
||||
@@ -226,7 +226,7 @@ export function PaymentMethodSection({
|
||||
) : (
|
||||
filteredContaOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={option.label}
|
||||
logo={option.logo}
|
||||
isCartao={false}
|
||||
@@ -252,18 +252,18 @@ export function PaymentMethodSection({
|
||||
>
|
||||
<Label htmlFor="cartaoUpdate">Cartão</Label>
|
||||
<Select
|
||||
value={formState.cartaoId}
|
||||
onValueChange={(value) => onFieldChange("cartaoId", value)}
|
||||
value={formState.cardId}
|
||||
onValueChange={(value) => onFieldChange("cardId", value)}
|
||||
>
|
||||
<SelectTrigger id="cartaoUpdate" className="w-full">
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.cartaoId &&
|
||||
{formState.cardId &&
|
||||
(() => {
|
||||
const selectedOption = cartaoOptions.find(
|
||||
(opt) => opt.value === formState.cartaoId,
|
||||
const selectedOption = cardOptions.find(
|
||||
(opt) => opt.value === formState.cardId,
|
||||
);
|
||||
return selectedOption ? (
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={selectedOption.label}
|
||||
logo={selectedOption.logo}
|
||||
isCartao={true}
|
||||
@@ -273,16 +273,16 @@ export function PaymentMethodSection({
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{cartaoOptions.length === 0 ? (
|
||||
{cardOptions.length === 0 ? (
|
||||
<div className="px-2 py-6 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Nenhum cartão cadastrado
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
cartaoOptions.map((option) => (
|
||||
cardOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={option.label}
|
||||
logo={option.logo}
|
||||
isCartao={true}
|
||||
@@ -292,7 +292,7 @@ export function PaymentMethodSection({
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{formState.cartaoId ? (
|
||||
{formState.cardId ? (
|
||||
<InlinePeriodPicker
|
||||
period={formState.period}
|
||||
onPeriodChange={(value) => onFieldChange("period", value)}
|
||||
@@ -308,20 +308,20 @@ export function PaymentMethodSection({
|
||||
!isUpdateMode ? "md:w-1/2" : "md:w-full",
|
||||
)}
|
||||
>
|
||||
<Label htmlFor="contaUpdate">Conta</Label>
|
||||
<Label htmlFor="contaUpdate">FinancialAccount</Label>
|
||||
<Select
|
||||
value={formState.contaId}
|
||||
onValueChange={(value) => onFieldChange("contaId", value)}
|
||||
value={formState.accountId}
|
||||
onValueChange={(value) => onFieldChange("accountId", value)}
|
||||
>
|
||||
<SelectTrigger id="contaUpdate" className="w-full">
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.contaId &&
|
||||
{formState.accountId &&
|
||||
(() => {
|
||||
const selectedOption = filteredContaOptions.find(
|
||||
(opt) => opt.value === formState.contaId,
|
||||
(opt) => opt.value === formState.accountId,
|
||||
);
|
||||
return selectedOption ? (
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={selectedOption.label}
|
||||
logo={selectedOption.logo}
|
||||
isCartao={false}
|
||||
@@ -340,7 +340,7 @@ export function PaymentMethodSection({
|
||||
) : (
|
||||
filteredContaOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={option.label}
|
||||
logo={option.logo}
|
||||
isCartao={false}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import type { LancamentoFormState } from "@/features/transactions/form-helpers";
|
||||
import type { LancamentoItem, SelectOption } from "../../types";
|
||||
import type { TransactionFormState } from "@/features/transactions/form-helpers";
|
||||
import type { SelectOption, TransactionItem } from "../../types";
|
||||
|
||||
export type FormState = LancamentoFormState;
|
||||
export type FormState = TransactionFormState;
|
||||
|
||||
export interface LancamentoDialogProps {
|
||||
export interface TransactionDialogProps {
|
||||
mode: "create" | "update";
|
||||
trigger?: React.ReactNode;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
pagadorOptions: SelectOption[];
|
||||
splitPagadorOptions: SelectOption[];
|
||||
defaultPagadorId?: string | null;
|
||||
contaOptions: SelectOption[];
|
||||
cartaoOptions: SelectOption[];
|
||||
categoriaOptions: SelectOption[];
|
||||
payerOptions: SelectOption[];
|
||||
splitPayerOptions: SelectOption[];
|
||||
defaultPayerId?: string | null;
|
||||
accountOptions: SelectOption[];
|
||||
cardOptions: SelectOption[];
|
||||
categoryOptions: SelectOption[];
|
||||
estabelecimentos: string[];
|
||||
lancamento?: LancamentoItem;
|
||||
transaction?: TransactionItem;
|
||||
defaultPeriod?: string;
|
||||
defaultCartaoId?: string | null;
|
||||
defaultCardId?: string | null;
|
||||
defaultPaymentMethod?: string | null;
|
||||
defaultPurchaseDate?: string | null;
|
||||
defaultName?: string | null;
|
||||
defaultAmount?: string | null;
|
||||
lockCartaoSelection?: boolean;
|
||||
lockCardSelection?: boolean;
|
||||
lockPaymentMethod?: boolean;
|
||||
isImporting?: boolean;
|
||||
defaultTransactionType?: "Despesa" | "Receita";
|
||||
@@ -33,11 +33,11 @@ export interface LancamentoDialogProps {
|
||||
onBulkEditRequest?: (data: {
|
||||
id: string;
|
||||
name: string;
|
||||
categoriaId: string | undefined;
|
||||
categoryId: string | undefined;
|
||||
note: string;
|
||||
pagadorId: string | undefined;
|
||||
contaId: string | undefined;
|
||||
cartaoId: string | undefined;
|
||||
payerId: string | undefined;
|
||||
accountId: string | undefined;
|
||||
cardId: string | undefined;
|
||||
amount: number;
|
||||
dueDate: string | null;
|
||||
boletoPaymentDate: string | null;
|
||||
@@ -57,8 +57,8 @@ export interface BasicFieldsSectionProps extends BaseFieldSectionProps {
|
||||
}
|
||||
|
||||
export interface CategorySectionProps extends BaseFieldSectionProps {
|
||||
categoriaOptions: SelectOption[];
|
||||
categoriaGroups: Array<{
|
||||
categoryOptions: SelectOption[];
|
||||
categoryGroups: Array<{
|
||||
label: string;
|
||||
options: SelectOption[];
|
||||
}>;
|
||||
@@ -70,18 +70,18 @@ export interface SplitAndSettlementSectionProps extends BaseFieldSectionProps {
|
||||
showSettledToggle: boolean;
|
||||
}
|
||||
|
||||
export interface PagadorSectionProps extends BaseFieldSectionProps {
|
||||
pagadorOptions: SelectOption[];
|
||||
secondaryPagadorOptions: SelectOption[];
|
||||
export interface PayerSectionProps extends BaseFieldSectionProps {
|
||||
payerOptions: SelectOption[];
|
||||
secondaryPayerOptions: SelectOption[];
|
||||
totalAmount: number;
|
||||
}
|
||||
|
||||
export interface PaymentMethodSectionProps extends BaseFieldSectionProps {
|
||||
contaOptions: SelectOption[];
|
||||
cartaoOptions: SelectOption[];
|
||||
accountOptions: SelectOption[];
|
||||
cardOptions: SelectOption[];
|
||||
isUpdateMode: boolean;
|
||||
disablePaymentMethod: boolean;
|
||||
disableCartaoSelect: boolean;
|
||||
disableCardSelect: boolean;
|
||||
}
|
||||
|
||||
export interface BoletoFieldsSectionProps extends BaseFieldSectionProps {
|
||||
|
||||
@@ -3,16 +3,16 @@ import { RiAddLine } from "@remixicon/react";
|
||||
import { useEffect, useMemo, useState, useTransition } from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
createLancamentoAction,
|
||||
updateLancamentoAction,
|
||||
createTransactionAction,
|
||||
updateTransactionAction,
|
||||
} from "@/features/transactions/actions";
|
||||
import {
|
||||
filterSecondaryPagadorOptions,
|
||||
groupAndSortCategorias,
|
||||
} from "@/features/transactions/categoria-helpers";
|
||||
filterSecondaryPayerOptions,
|
||||
groupAndSortCategories,
|
||||
} from "@/features/transactions/category-helpers";
|
||||
import {
|
||||
applyFieldDependencies,
|
||||
buildLancamentoInitialState,
|
||||
buildTransactionInitialState,
|
||||
deriveCreditCardPeriod,
|
||||
} from "@/features/transactions/form-helpers";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
@@ -36,41 +36,41 @@ import { BoletoFieldsSection } from "./boleto-fields-section";
|
||||
import { CategorySection } from "./category-section";
|
||||
import { ConditionSection } from "./condition-section";
|
||||
import { NoteSection } from "./note-section";
|
||||
import { PagadorSection } from "./pagador-section";
|
||||
import { PayerSection } from "./payer-section";
|
||||
import { PaymentMethodSection } from "./payment-method-section";
|
||||
import { SplitAndSettlementSection } from "./split-settlement-section";
|
||||
import type {
|
||||
FormState,
|
||||
LancamentoDialogProps,
|
||||
TransactionDialogProps,
|
||||
} from "./transaction-dialog-types";
|
||||
|
||||
export function LancamentoDialog({
|
||||
export function TransactionDialog({
|
||||
mode,
|
||||
trigger,
|
||||
open,
|
||||
onOpenChange,
|
||||
pagadorOptions,
|
||||
splitPagadorOptions,
|
||||
defaultPagadorId,
|
||||
contaOptions,
|
||||
cartaoOptions,
|
||||
categoriaOptions,
|
||||
payerOptions,
|
||||
splitPayerOptions,
|
||||
defaultPayerId,
|
||||
accountOptions,
|
||||
cardOptions,
|
||||
categoryOptions,
|
||||
estabelecimentos,
|
||||
lancamento,
|
||||
transaction,
|
||||
defaultPeriod,
|
||||
defaultCartaoId,
|
||||
defaultCardId,
|
||||
defaultPaymentMethod,
|
||||
defaultPurchaseDate,
|
||||
defaultName,
|
||||
defaultAmount,
|
||||
lockCartaoSelection,
|
||||
lockCardSelection,
|
||||
lockPaymentMethod,
|
||||
isImporting = false,
|
||||
defaultTransactionType,
|
||||
forceShowTransactionType = false,
|
||||
onSuccess,
|
||||
onBulkEditRequest,
|
||||
}: LancamentoDialogProps) {
|
||||
}: TransactionDialogProps) {
|
||||
const [dialogOpen, setDialogOpen] = useControlledState(
|
||||
open,
|
||||
false,
|
||||
@@ -78,8 +78,8 @@ export function LancamentoDialog({
|
||||
);
|
||||
|
||||
const [formState, setFormState] = useState<FormState>(() =>
|
||||
buildLancamentoInitialState(lancamento, defaultPagadorId, defaultPeriod, {
|
||||
defaultCartaoId,
|
||||
buildTransactionInitialState(transaction, defaultPayerId, defaultPeriod, {
|
||||
defaultCardId,
|
||||
defaultPaymentMethod,
|
||||
defaultPurchaseDate,
|
||||
defaultName,
|
||||
@@ -93,12 +93,12 @@ export function LancamentoDialog({
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogOpen) {
|
||||
const initial = buildLancamentoInitialState(
|
||||
lancamento,
|
||||
defaultPagadorId,
|
||||
const initial = buildTransactionInitialState(
|
||||
transaction,
|
||||
defaultPayerId,
|
||||
defaultPeriod,
|
||||
{
|
||||
defaultCartaoId,
|
||||
defaultCardId,
|
||||
defaultPaymentMethod,
|
||||
defaultPurchaseDate,
|
||||
defaultName,
|
||||
@@ -108,15 +108,13 @@ export function LancamentoDialog({
|
||||
},
|
||||
);
|
||||
|
||||
// Derive credit card period on open when cartaoId is pre-filled
|
||||
// Derive credit card period on open when cardId is pre-filled
|
||||
if (
|
||||
initial.paymentMethod === "Cartão de crédito" &&
|
||||
initial.cartaoId &&
|
||||
initial.cardId &&
|
||||
initial.purchaseDate
|
||||
) {
|
||||
const card = cartaoOptions.find(
|
||||
(opt) => opt.value === initial.cartaoId,
|
||||
);
|
||||
const card = cardOptions.find((opt) => opt.value === initial.cardId);
|
||||
if (card?.closingDay) {
|
||||
initial.period = deriveCreditCardPeriod(
|
||||
initial.purchaseDate,
|
||||
@@ -131,45 +129,45 @@ export function LancamentoDialog({
|
||||
}
|
||||
}, [
|
||||
dialogOpen,
|
||||
lancamento,
|
||||
defaultPagadorId,
|
||||
transaction,
|
||||
defaultPayerId,
|
||||
defaultPeriod,
|
||||
defaultCartaoId,
|
||||
defaultCardId,
|
||||
defaultPaymentMethod,
|
||||
defaultPurchaseDate,
|
||||
defaultName,
|
||||
defaultAmount,
|
||||
defaultTransactionType,
|
||||
isImporting,
|
||||
cartaoOptions,
|
||||
cardOptions,
|
||||
]);
|
||||
|
||||
const primaryPagador = formState.pagadorId;
|
||||
const primaryPayerId = formState.payerId;
|
||||
|
||||
const secondaryPagadorOptions = useMemo(
|
||||
() => filterSecondaryPagadorOptions(splitPagadorOptions, primaryPagador),
|
||||
[splitPagadorOptions, primaryPagador],
|
||||
const secondaryPayerOptions = useMemo(
|
||||
() => filterSecondaryPayerOptions(splitPayerOptions, primaryPayerId),
|
||||
[splitPayerOptions, primaryPayerId],
|
||||
);
|
||||
|
||||
const categoriaGroups = useMemo(() => {
|
||||
const filtered = categoriaOptions.filter(
|
||||
const categoryGroups = useMemo(() => {
|
||||
const filtered = categoryOptions.filter(
|
||||
(option) =>
|
||||
option.group?.toLowerCase() === formState.transactionType.toLowerCase(),
|
||||
);
|
||||
return groupAndSortCategorias(filtered);
|
||||
}, [categoriaOptions, formState.transactionType]);
|
||||
return groupAndSortCategories(filtered);
|
||||
}, [categoryOptions, formState.transactionType]);
|
||||
|
||||
type CreateLancamentoInput = Parameters<typeof createLancamentoAction>[0];
|
||||
type UpdateLancamentoInput = Parameters<typeof updateLancamentoAction>[0];
|
||||
type CreateTransactionInput = Parameters<typeof createTransactionAction>[0];
|
||||
type UpdateTransactionInput = Parameters<typeof updateTransactionAction>[0];
|
||||
|
||||
const totalAmount = useMemo(() => {
|
||||
const parsed = Number.parseFloat(formState.amount);
|
||||
return Number.isNaN(parsed) ? 0 : Math.abs(parsed);
|
||||
}, [formState.amount]);
|
||||
|
||||
function getCardInfo(cartaoId: string | undefined) {
|
||||
if (!cartaoId) return null;
|
||||
const card = cartaoOptions.find((opt) => opt.value === cartaoId);
|
||||
function getCardInfo(cardId: string | undefined) {
|
||||
if (!cardId) return null;
|
||||
const card = cardOptions.find((opt) => opt.value === cardId);
|
||||
if (!card) return null;
|
||||
return {
|
||||
closingDay: card.closingDay ?? null,
|
||||
@@ -182,9 +180,9 @@ export function LancamentoDialog({
|
||||
value: FormState[Key],
|
||||
) {
|
||||
setFormState((prev) => {
|
||||
const effectiveCartaoId =
|
||||
key === "cartaoId" ? (value as string) : prev.cartaoId;
|
||||
const cardInfo = getCardInfo(effectiveCartaoId);
|
||||
const effectiveCardId =
|
||||
key === "cardId" ? (value as string) : prev.cardId;
|
||||
const cardInfo = getCardInfo(effectiveCardId);
|
||||
|
||||
const dependencies = applyFieldDependencies(key, value, prev, cardInfo);
|
||||
|
||||
@@ -214,7 +212,7 @@ export function LancamentoDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
if (formState.isSplit && !formState.pagadorId) {
|
||||
if (formState.isSplit && !formState.payerId) {
|
||||
const message =
|
||||
"Selecione o pagador principal para dividir o lançamento.";
|
||||
setErrorMessage(message);
|
||||
@@ -222,7 +220,7 @@ export function LancamentoDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
if (formState.isSplit && !formState.secondaryPagadorId) {
|
||||
if (formState.isSplit && !formState.secondaryPayerId) {
|
||||
const message =
|
||||
"Selecione o pagador secundário para dividir o lançamento.";
|
||||
setErrorMessage(message);
|
||||
@@ -240,7 +238,7 @@ export function LancamentoDialog({
|
||||
|
||||
const sanitizedAmount = Math.abs(amountValue);
|
||||
|
||||
if (!formState.categoriaId) {
|
||||
if (!formState.categoryId) {
|
||||
const message = "Selecione uma categoria.";
|
||||
setErrorMessage(message);
|
||||
toast.error(message);
|
||||
@@ -248,32 +246,32 @@ export function LancamentoDialog({
|
||||
}
|
||||
|
||||
if (formState.paymentMethod === "Cartão de crédito") {
|
||||
if (!formState.cartaoId) {
|
||||
if (!formState.cardId) {
|
||||
const message = "Selecione o cartão.";
|
||||
setErrorMessage(message);
|
||||
toast.error(message);
|
||||
return;
|
||||
}
|
||||
} else if (!formState.contaId) {
|
||||
} else if (!formState.accountId) {
|
||||
const message = "Selecione a conta.";
|
||||
setErrorMessage(message);
|
||||
toast.error(message);
|
||||
return;
|
||||
}
|
||||
|
||||
const payload: CreateLancamentoInput = {
|
||||
const payload: CreateTransactionInput = {
|
||||
purchaseDate: formState.purchaseDate,
|
||||
period: formState.period,
|
||||
name: formState.name.trim(),
|
||||
transactionType:
|
||||
formState.transactionType as CreateLancamentoInput["transactionType"],
|
||||
formState.transactionType as CreateTransactionInput["transactionType"],
|
||||
amount: sanitizedAmount,
|
||||
condition: formState.condition as CreateLancamentoInput["condition"],
|
||||
condition: formState.condition as CreateTransactionInput["condition"],
|
||||
paymentMethod:
|
||||
formState.paymentMethod as CreateLancamentoInput["paymentMethod"],
|
||||
pagadorId: formState.pagadorId ?? null,
|
||||
secondaryPagadorId: formState.isSplit
|
||||
? formState.secondaryPagadorId
|
||||
formState.paymentMethod as CreateTransactionInput["paymentMethod"],
|
||||
payerId: formState.payerId ?? null,
|
||||
secondaryPayerId: formState.isSplit
|
||||
? formState.secondaryPayerId
|
||||
: undefined,
|
||||
isSplit: formState.isSplit,
|
||||
primarySplitAmount: formState.isSplit
|
||||
@@ -282,9 +280,9 @@ export function LancamentoDialog({
|
||||
secondarySplitAmount: formState.isSplit
|
||||
? Number.parseFloat(formState.secondarySplitAmount) || undefined
|
||||
: undefined,
|
||||
contaId: formState.contaId ?? null,
|
||||
cartaoId: formState.cartaoId ?? null,
|
||||
categoriaId: formState.categoriaId ?? null,
|
||||
accountId: formState.accountId ?? null,
|
||||
cardId: formState.cardId ?? null,
|
||||
categoryId: formState.categoryId ?? null,
|
||||
note: formState.note.trim() || null,
|
||||
isSettled:
|
||||
formState.paymentMethod === "Cartão de crédito"
|
||||
@@ -309,7 +307,7 @@ export function LancamentoDialog({
|
||||
|
||||
startTransition(async () => {
|
||||
if (mode === "create") {
|
||||
const result = await createLancamentoAction(payload);
|
||||
const result = await createTransactionAction(payload);
|
||||
|
||||
if (result.success) {
|
||||
toast.success(result.message);
|
||||
@@ -324,18 +322,18 @@ export function LancamentoDialog({
|
||||
}
|
||||
|
||||
// Update mode
|
||||
const hasSeriesId = Boolean(lancamento?.seriesId);
|
||||
const hasSeriesId = Boolean(transaction?.seriesId);
|
||||
|
||||
if (hasSeriesId && onBulkEditRequest) {
|
||||
// Para lançamentos em série, abre o diálogo de bulk action
|
||||
onBulkEditRequest({
|
||||
id: lancamento?.id ?? "",
|
||||
id: transaction?.id ?? "",
|
||||
name: formState.name.trim(),
|
||||
categoriaId: formState.categoriaId,
|
||||
categoryId: formState.categoryId,
|
||||
note: formState.note.trim() || "",
|
||||
pagadorId: formState.pagadorId,
|
||||
contaId: formState.contaId,
|
||||
cartaoId: formState.cartaoId,
|
||||
payerId: formState.payerId,
|
||||
accountId: formState.accountId,
|
||||
cardId: formState.cardId,
|
||||
amount: sanitizedAmount,
|
||||
dueDate:
|
||||
formState.paymentMethod === "Boleto"
|
||||
@@ -350,12 +348,12 @@ export function LancamentoDialog({
|
||||
}
|
||||
|
||||
// Atualização normal para lançamentos únicos ou todos os campos
|
||||
const updatePayload: UpdateLancamentoInput = {
|
||||
id: lancamento?.id ?? "",
|
||||
const updatePayload: UpdateTransactionInput = {
|
||||
id: transaction?.id ?? "",
|
||||
...payload,
|
||||
};
|
||||
|
||||
const result = await updateLancamentoAction(updatePayload);
|
||||
const result = await updateTransactionAction(updatePayload);
|
||||
|
||||
if (result.success) {
|
||||
toast.success(result.message);
|
||||
@@ -369,15 +367,15 @@ export function LancamentoDialog({
|
||||
});
|
||||
};
|
||||
|
||||
const isCopyMode = mode === "create" && Boolean(lancamento) && !isImporting;
|
||||
const isImportMode = mode === "create" && Boolean(lancamento) && isImporting;
|
||||
const isCopyMode = mode === "create" && Boolean(transaction) && !isImporting;
|
||||
const isImportMode = mode === "create" && Boolean(transaction) && isImporting;
|
||||
const isNewWithType =
|
||||
mode === "create" && !lancamento && defaultTransactionType;
|
||||
mode === "create" && !transaction && defaultTransactionType;
|
||||
|
||||
const title =
|
||||
mode === "create"
|
||||
? isImportMode
|
||||
? "Importar para Minha Conta"
|
||||
? "Importar para Minha FinancialAccount"
|
||||
: isCopyMode
|
||||
? "Copiar lançamento"
|
||||
: isNewWithType
|
||||
@@ -405,7 +403,7 @@ export function LancamentoDialog({
|
||||
const showSettledToggle = formState.paymentMethod !== "Cartão de crédito";
|
||||
const isUpdateMode = mode === "update";
|
||||
const disablePaymentMethod = Boolean(lockPaymentMethod && mode === "create");
|
||||
const disableCartaoSelect = Boolean(lockCartaoSelection && mode === "create");
|
||||
const disableCardSelect = Boolean(lockCardSelection && mode === "create");
|
||||
|
||||
return (
|
||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
@@ -430,8 +428,8 @@ export function LancamentoDialog({
|
||||
<CategorySection
|
||||
formState={formState}
|
||||
onFieldChange={handleFieldChange}
|
||||
categoriaOptions={categoriaOptions}
|
||||
categoriaGroups={categoriaGroups}
|
||||
categoryOptions={categoryOptions}
|
||||
categoryGroups={categoryGroups}
|
||||
isUpdateMode={isUpdateMode}
|
||||
hideTransactionType={
|
||||
Boolean(isNewWithType) && !forceShowTransactionType
|
||||
@@ -446,22 +444,22 @@ export function LancamentoDialog({
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<PagadorSection
|
||||
<PayerSection
|
||||
formState={formState}
|
||||
onFieldChange={handleFieldChange}
|
||||
pagadorOptions={pagadorOptions}
|
||||
secondaryPagadorOptions={secondaryPagadorOptions}
|
||||
payerOptions={payerOptions}
|
||||
secondaryPayerOptions={secondaryPayerOptions}
|
||||
totalAmount={totalAmount}
|
||||
/>
|
||||
|
||||
<PaymentMethodSection
|
||||
formState={formState}
|
||||
onFieldChange={handleFieldChange}
|
||||
contaOptions={contaOptions}
|
||||
cartaoOptions={cartaoOptions}
|
||||
accountOptions={accountOptions}
|
||||
cardOptions={cardOptions}
|
||||
isUpdateMode={isUpdateMode}
|
||||
disablePaymentMethod={disablePaymentMethod}
|
||||
disableCartaoSelect={disableCartaoSelect}
|
||||
disableCardSelect={disableCardSelect}
|
||||
/>
|
||||
|
||||
{showDueDate ? (
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
createMassLancamentosAction,
|
||||
deleteLancamentoAction,
|
||||
deleteLancamentoBulkAction,
|
||||
deleteMultipleLancamentosAction,
|
||||
toggleLancamentoSettlementAction,
|
||||
updateLancamentoBulkAction,
|
||||
createMassTransactionsAction,
|
||||
deleteMultipleTransactionsAction,
|
||||
deleteTransactionAction,
|
||||
deleteTransactionBulkAction,
|
||||
toggleTransactionSettlementAction,
|
||||
updateTransactionBulkAction,
|
||||
} from "@/features/transactions/actions";
|
||||
import { ConfirmActionDialog } from "@/shared/components/confirm-action-dialog";
|
||||
|
||||
@@ -23,88 +23,88 @@ import {
|
||||
MassAddDialog,
|
||||
type MassAddFormData,
|
||||
} from "../dialogs/mass-add-dialog";
|
||||
import { LancamentoDetailsDialog } from "../dialogs/transaction-details-dialog";
|
||||
import { LancamentoDialog } from "../dialogs/transaction-dialog/transaction-dialog";
|
||||
import { LancamentosTable } from "../table/transactions-table";
|
||||
import { TransactionDetailsDialog } from "../dialogs/transaction-details-dialog";
|
||||
import { TransactionDialog } from "../dialogs/transaction-dialog/transaction-dialog";
|
||||
import { TransactionsTable } from "../table/transactions-table";
|
||||
import type {
|
||||
ContaCartaoFilterOption,
|
||||
LancamentoFilterOption,
|
||||
LancamentoItem,
|
||||
AccountCardFilterOption,
|
||||
SelectOption,
|
||||
TransactionFilterOption,
|
||||
TransactionItem,
|
||||
} from "../types";
|
||||
|
||||
interface LancamentosPageProps {
|
||||
interface TransactionsPageProps {
|
||||
currentUserId: string;
|
||||
lancamentos: LancamentoItem[];
|
||||
pagadorOptions: SelectOption[];
|
||||
splitPagadorOptions: SelectOption[];
|
||||
defaultPagadorId: string | null;
|
||||
contaOptions: SelectOption[];
|
||||
cartaoOptions: SelectOption[];
|
||||
categoriaOptions: SelectOption[];
|
||||
pagadorFilterOptions: LancamentoFilterOption[];
|
||||
categoriaFilterOptions: LancamentoFilterOption[];
|
||||
contaCartaoFilterOptions: ContaCartaoFilterOption[];
|
||||
transactions: TransactionItem[];
|
||||
payerOptions: SelectOption[];
|
||||
splitPayerOptions: SelectOption[];
|
||||
defaultPayerId: string | null;
|
||||
accountOptions: SelectOption[];
|
||||
cardOptions: SelectOption[];
|
||||
categoryOptions: SelectOption[];
|
||||
payerFilterOptions: TransactionFilterOption[];
|
||||
categoryFilterOptions: TransactionFilterOption[];
|
||||
accountCardFilterOptions: AccountCardFilterOption[];
|
||||
selectedPeriod: string;
|
||||
estabelecimentos: string[];
|
||||
allowCreate?: boolean;
|
||||
noteAsColumn?: boolean;
|
||||
columnOrder?: string[] | null;
|
||||
defaultCartaoId?: string | null;
|
||||
defaultCardId?: string | null;
|
||||
defaultPaymentMethod?: string | null;
|
||||
lockCartaoSelection?: boolean;
|
||||
lockCardSelection?: boolean;
|
||||
lockPaymentMethod?: boolean;
|
||||
// Opções específicas para o dialog de importação (quando visualizando dados de outro usuário)
|
||||
importPagadorOptions?: SelectOption[];
|
||||
importSplitPagadorOptions?: SelectOption[];
|
||||
importDefaultPagadorId?: string | null;
|
||||
importContaOptions?: SelectOption[];
|
||||
importCartaoOptions?: SelectOption[];
|
||||
importCategoriaOptions?: SelectOption[];
|
||||
importPayerOptions?: SelectOption[];
|
||||
importSplitPayerOptions?: SelectOption[];
|
||||
importDefaultPayerId?: string | null;
|
||||
importAccountOptions?: SelectOption[];
|
||||
importCardOptions?: SelectOption[];
|
||||
importCategoryOptions?: SelectOption[];
|
||||
}
|
||||
|
||||
export function LancamentosPage({
|
||||
export function TransactionsPage({
|
||||
currentUserId,
|
||||
lancamentos,
|
||||
pagadorOptions,
|
||||
splitPagadorOptions,
|
||||
defaultPagadorId,
|
||||
contaOptions,
|
||||
cartaoOptions,
|
||||
categoriaOptions,
|
||||
pagadorFilterOptions,
|
||||
categoriaFilterOptions,
|
||||
contaCartaoFilterOptions,
|
||||
transactions: transactionList,
|
||||
payerOptions,
|
||||
splitPayerOptions,
|
||||
defaultPayerId,
|
||||
accountOptions,
|
||||
cardOptions,
|
||||
categoryOptions,
|
||||
payerFilterOptions,
|
||||
categoryFilterOptions,
|
||||
accountCardFilterOptions,
|
||||
selectedPeriod,
|
||||
estabelecimentos,
|
||||
allowCreate = true,
|
||||
noteAsColumn = false,
|
||||
columnOrder = null,
|
||||
defaultCartaoId,
|
||||
defaultCardId,
|
||||
defaultPaymentMethod,
|
||||
lockCartaoSelection,
|
||||
lockCardSelection,
|
||||
lockPaymentMethod,
|
||||
importPagadorOptions,
|
||||
importSplitPagadorOptions,
|
||||
importDefaultPagadorId,
|
||||
importContaOptions,
|
||||
importCartaoOptions,
|
||||
importCategoriaOptions,
|
||||
}: LancamentosPageProps) {
|
||||
const [selectedLancamento, setSelectedLancamento] =
|
||||
useState<LancamentoItem | null>(null);
|
||||
importPayerOptions,
|
||||
importSplitPayerOptions,
|
||||
importDefaultPayerId,
|
||||
importAccountOptions,
|
||||
importCardOptions,
|
||||
importCategoryOptions,
|
||||
}: TransactionsPageProps) {
|
||||
const [selectedTransaction, setSelectedTransaction] =
|
||||
useState<TransactionItem | null>(null);
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [copyOpen, setCopyOpen] = useState(false);
|
||||
const [lancamentoToCopy, setLancamentoToCopy] =
|
||||
useState<LancamentoItem | null>(null);
|
||||
const [transactionToCopy, setTransactionToCopy] =
|
||||
useState<TransactionItem | null>(null);
|
||||
const [importOpen, setImportOpen] = useState(false);
|
||||
const [lancamentoToImport, setLancamentoToImport] =
|
||||
useState<LancamentoItem | null>(null);
|
||||
const [transactionToImport, setTransactionToImport] =
|
||||
useState<TransactionItem | null>(null);
|
||||
const [massAddOpen, setMassAddOpen] = useState(false);
|
||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||
const [lancamentoToDelete, setLancamentoToDelete] =
|
||||
useState<LancamentoItem | null>(null);
|
||||
const [transactionToDelete, setTransactionToDelete] =
|
||||
useState<TransactionItem | null>(null);
|
||||
const [detailsOpen, setDetailsOpen] = useState(false);
|
||||
const [settlementLoadingId, setSettlementLoadingId] = useState<string | null>(
|
||||
null,
|
||||
@@ -114,32 +114,32 @@ export function LancamentosPage({
|
||||
const [pendingEditData, setPendingEditData] = useState<{
|
||||
id: string;
|
||||
name: string;
|
||||
categoriaId: string | undefined;
|
||||
categoryId: string | undefined;
|
||||
note: string;
|
||||
pagadorId: string | undefined;
|
||||
contaId: string | undefined;
|
||||
cartaoId: string | undefined;
|
||||
payerId: string | undefined;
|
||||
accountId: string | undefined;
|
||||
cardId: string | undefined;
|
||||
amount: number;
|
||||
dueDate: string | null;
|
||||
boletoPaymentDate: string | null;
|
||||
lancamento: LancamentoItem;
|
||||
transaction: TransactionItem;
|
||||
} | null>(null);
|
||||
const [pendingDeleteData, setPendingDeleteData] =
|
||||
useState<LancamentoItem | null>(null);
|
||||
useState<TransactionItem | null>(null);
|
||||
const [multipleBulkDeleteOpen, setMultipleBulkDeleteOpen] = useState(false);
|
||||
const [pendingMultipleDeleteData, setPendingMultipleDeleteData] = useState<
|
||||
LancamentoItem[]
|
||||
TransactionItem[]
|
||||
>([]);
|
||||
const [anticipateOpen, setAnticipateOpen] = useState(false);
|
||||
const [anticipationHistoryOpen, setAnticipationHistoryOpen] = useState(false);
|
||||
const [selectedForAnticipation, setSelectedForAnticipation] =
|
||||
useState<LancamentoItem | null>(null);
|
||||
useState<TransactionItem | null>(null);
|
||||
const [bulkImportOpen, setBulkImportOpen] = useState(false);
|
||||
const [lancamentosToImport, setLancamentosToImport] = useState<
|
||||
LancamentoItem[]
|
||||
const [transactionsToImport, setTransactionsToImport] = useState<
|
||||
TransactionItem[]
|
||||
>([]);
|
||||
|
||||
const handleToggleSettlement = async (item: LancamentoItem) => {
|
||||
const handleToggleSettlement = async (item: TransactionItem) => {
|
||||
if (item.paymentMethod === "Cartão de crédito") {
|
||||
toast.info(
|
||||
"Pagamentos com cartão são conciliados automaticamente. Ajuste pelo cartão.",
|
||||
@@ -163,7 +163,7 @@ export function LancamentosPage({
|
||||
|
||||
try {
|
||||
setSettlementLoadingId(item.id);
|
||||
const result = await toggleLancamentoSettlementAction({
|
||||
const result = await toggleTransactionSettlementAction({
|
||||
id: item.id,
|
||||
value: nextValue,
|
||||
});
|
||||
@@ -185,12 +185,12 @@ export function LancamentosPage({
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!lancamentoToDelete) {
|
||||
if (!transactionToDelete) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await deleteLancamentoAction({
|
||||
id: lancamentoToDelete.id,
|
||||
const result = await deleteTransactionAction({
|
||||
id: transactionToDelete.id,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
@@ -207,7 +207,7 @@ export function LancamentosPage({
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await deleteLancamentoBulkAction({
|
||||
const result = await deleteTransactionBulkAction({
|
||||
id: pendingDeleteData.id,
|
||||
scope,
|
||||
});
|
||||
@@ -225,22 +225,22 @@ export function LancamentosPage({
|
||||
const handleBulkEditRequest = (data: {
|
||||
id: string;
|
||||
name: string;
|
||||
categoriaId: string | undefined;
|
||||
categoryId: string | undefined;
|
||||
note: string;
|
||||
pagadorId: string | undefined;
|
||||
contaId: string | undefined;
|
||||
cartaoId: string | undefined;
|
||||
payerId: string | undefined;
|
||||
accountId: string | undefined;
|
||||
cardId: string | undefined;
|
||||
amount: number;
|
||||
dueDate: string | null;
|
||||
boletoPaymentDate: string | null;
|
||||
}) => {
|
||||
if (!selectedLancamento) {
|
||||
if (!selectedTransaction) {
|
||||
return;
|
||||
}
|
||||
|
||||
setPendingEditData({
|
||||
...data,
|
||||
lancamento: selectedLancamento,
|
||||
transaction: selectedTransaction,
|
||||
});
|
||||
setEditOpen(false);
|
||||
setBulkEditOpen(true);
|
||||
@@ -251,15 +251,15 @@ export function LancamentosPage({
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await updateLancamentoBulkAction({
|
||||
const result = await updateTransactionBulkAction({
|
||||
id: pendingEditData.id,
|
||||
scope,
|
||||
name: pendingEditData.name,
|
||||
categoriaId: pendingEditData.categoriaId,
|
||||
categoryId: pendingEditData.categoryId,
|
||||
note: pendingEditData.note,
|
||||
pagadorId: pendingEditData.pagadorId,
|
||||
contaId: pendingEditData.contaId,
|
||||
cartaoId: pendingEditData.cartaoId,
|
||||
payerId: pendingEditData.payerId,
|
||||
accountId: pendingEditData.accountId,
|
||||
cardId: pendingEditData.cardId,
|
||||
amount: pendingEditData.amount,
|
||||
dueDate: pendingEditData.dueDate,
|
||||
boletoPaymentDate: pendingEditData.boletoPaymentDate,
|
||||
@@ -276,7 +276,7 @@ export function LancamentosPage({
|
||||
};
|
||||
|
||||
const handleMassAddSubmit = async (data: MassAddFormData) => {
|
||||
const result = await createMassLancamentosAction(data);
|
||||
const result = await createMassTransactionsAction(data);
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error);
|
||||
@@ -286,7 +286,7 @@ export function LancamentosPage({
|
||||
toast.success(result.message);
|
||||
};
|
||||
|
||||
const handleMultipleBulkDelete = (items: LancamentoItem[]) => {
|
||||
const handleMultipleBulkDelete = (items: TransactionItem[]) => {
|
||||
// Se todos os selecionados são da mesma série (parcelado/recorrente), abrir dialog de escopo
|
||||
const withSeries = items.filter((i) => i.seriesId);
|
||||
const sameSeries =
|
||||
@@ -308,7 +308,7 @@ export function LancamentosPage({
|
||||
}
|
||||
|
||||
const ids = pendingMultipleDeleteData.map((item) => item.id);
|
||||
const result = await deleteMultipleLancamentosAction({ ids });
|
||||
const result = await deleteMultipleTransactionsAction({ ids });
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error);
|
||||
@@ -333,61 +333,61 @@ export function LancamentosPage({
|
||||
setMassAddOpen(true);
|
||||
};
|
||||
|
||||
const handleEdit = (item: LancamentoItem) => {
|
||||
setSelectedLancamento(item);
|
||||
const handleEdit = (item: TransactionItem) => {
|
||||
setSelectedTransaction(item);
|
||||
setEditOpen(true);
|
||||
};
|
||||
|
||||
const handleCopy = (item: LancamentoItem) => {
|
||||
setLancamentoToCopy(item);
|
||||
const handleCopy = (item: TransactionItem) => {
|
||||
setTransactionToCopy(item);
|
||||
setCopyOpen(true);
|
||||
};
|
||||
|
||||
const handleImport = (item: LancamentoItem) => {
|
||||
setLancamentoToImport(item);
|
||||
const handleImport = (item: TransactionItem) => {
|
||||
setTransactionToImport(item);
|
||||
setImportOpen(true);
|
||||
};
|
||||
|
||||
const handleBulkImport = (items: LancamentoItem[]) => {
|
||||
setLancamentosToImport(items);
|
||||
const handleBulkImport = (items: TransactionItem[]) => {
|
||||
setTransactionsToImport(items);
|
||||
setBulkImportOpen(true);
|
||||
};
|
||||
|
||||
const handleConfirmDelete = (item: LancamentoItem) => {
|
||||
const handleConfirmDelete = (item: TransactionItem) => {
|
||||
if (item.seriesId) {
|
||||
setPendingDeleteData(item);
|
||||
setBulkDeleteOpen(true);
|
||||
} else {
|
||||
setLancamentoToDelete(item);
|
||||
setTransactionToDelete(item);
|
||||
setDeleteOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleViewDetails = (item: LancamentoItem) => {
|
||||
setSelectedLancamento(item);
|
||||
const handleViewDetails = (item: TransactionItem) => {
|
||||
setSelectedTransaction(item);
|
||||
setDetailsOpen(true);
|
||||
};
|
||||
|
||||
const handleAnticipate = (item: LancamentoItem) => {
|
||||
const handleAnticipate = (item: TransactionItem) => {
|
||||
setSelectedForAnticipation(item);
|
||||
setAnticipateOpen(true);
|
||||
};
|
||||
|
||||
const handleViewAnticipationHistory = (item: LancamentoItem) => {
|
||||
const handleViewAnticipationHistory = (item: TransactionItem) => {
|
||||
setSelectedForAnticipation(item);
|
||||
setAnticipationHistoryOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<LancamentosTable
|
||||
data={lancamentos}
|
||||
<TransactionsTable
|
||||
data={transactionList}
|
||||
currentUserId={currentUserId}
|
||||
noteAsColumn={noteAsColumn}
|
||||
columnOrder={columnOrder}
|
||||
pagadorFilterOptions={pagadorFilterOptions}
|
||||
categoriaFilterOptions={categoriaFilterOptions}
|
||||
contaCartaoFilterOptions={contaCartaoFilterOptions}
|
||||
payerFilterOptions={payerFilterOptions}
|
||||
categoryFilterOptions={categoryFilterOptions}
|
||||
accountCardFilterOptions={accountCardFilterOptions}
|
||||
selectedPeriod={selectedPeriod}
|
||||
onCreate={allowCreate ? handleCreate : undefined}
|
||||
onMassAdd={allowCreate ? handleMassAdd : undefined}
|
||||
@@ -405,111 +405,111 @@ export function LancamentosPage({
|
||||
/>
|
||||
|
||||
{allowCreate ? (
|
||||
<LancamentoDialog
|
||||
<TransactionDialog
|
||||
mode="create"
|
||||
open={createOpen}
|
||||
onOpenChange={setCreateOpen}
|
||||
pagadorOptions={pagadorOptions}
|
||||
splitPagadorOptions={splitPagadorOptions}
|
||||
defaultPagadorId={defaultPagadorId}
|
||||
contaOptions={contaOptions}
|
||||
cartaoOptions={cartaoOptions}
|
||||
categoriaOptions={categoriaOptions}
|
||||
payerOptions={payerOptions}
|
||||
splitPayerOptions={splitPayerOptions}
|
||||
defaultPayerId={defaultPayerId}
|
||||
accountOptions={accountOptions}
|
||||
cardOptions={cardOptions}
|
||||
categoryOptions={categoryOptions}
|
||||
estabelecimentos={estabelecimentos}
|
||||
defaultPeriod={selectedPeriod}
|
||||
defaultCartaoId={defaultCartaoId}
|
||||
defaultCardId={defaultCardId}
|
||||
defaultPaymentMethod={defaultPaymentMethod}
|
||||
lockCartaoSelection={lockCartaoSelection}
|
||||
lockCardSelection={lockCardSelection}
|
||||
lockPaymentMethod={lockPaymentMethod}
|
||||
defaultTransactionType={transactionTypeForCreate ?? undefined}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<LancamentoDialog
|
||||
<TransactionDialog
|
||||
mode="create"
|
||||
open={copyOpen && !!lancamentoToCopy}
|
||||
open={copyOpen && !!transactionToCopy}
|
||||
onOpenChange={(open) => {
|
||||
setCopyOpen(open);
|
||||
if (!open) {
|
||||
setLancamentoToCopy(null);
|
||||
setTransactionToCopy(null);
|
||||
}
|
||||
}}
|
||||
pagadorOptions={pagadorOptions}
|
||||
splitPagadorOptions={splitPagadorOptions}
|
||||
defaultPagadorId={defaultPagadorId}
|
||||
contaOptions={contaOptions}
|
||||
cartaoOptions={cartaoOptions}
|
||||
categoriaOptions={categoriaOptions}
|
||||
payerOptions={payerOptions}
|
||||
splitPayerOptions={splitPayerOptions}
|
||||
defaultPayerId={defaultPayerId}
|
||||
accountOptions={accountOptions}
|
||||
cardOptions={cardOptions}
|
||||
categoryOptions={categoryOptions}
|
||||
estabelecimentos={estabelecimentos}
|
||||
lancamento={lancamentoToCopy ?? undefined}
|
||||
transaction={transactionToCopy ?? undefined}
|
||||
defaultPeriod={selectedPeriod}
|
||||
/>
|
||||
|
||||
<LancamentoDialog
|
||||
<TransactionDialog
|
||||
mode="create"
|
||||
open={importOpen && !!lancamentoToImport}
|
||||
open={importOpen && !!transactionToImport}
|
||||
onOpenChange={(open) => {
|
||||
setImportOpen(open);
|
||||
if (!open) {
|
||||
setLancamentoToImport(null);
|
||||
setTransactionToImport(null);
|
||||
}
|
||||
}}
|
||||
pagadorOptions={importPagadorOptions ?? pagadorOptions}
|
||||
splitPagadorOptions={importSplitPagadorOptions ?? splitPagadorOptions}
|
||||
defaultPagadorId={importDefaultPagadorId ?? defaultPagadorId}
|
||||
contaOptions={importContaOptions ?? contaOptions}
|
||||
cartaoOptions={importCartaoOptions ?? cartaoOptions}
|
||||
categoriaOptions={importCategoriaOptions ?? categoriaOptions}
|
||||
payerOptions={importPayerOptions ?? payerOptions}
|
||||
splitPayerOptions={importSplitPayerOptions ?? splitPayerOptions}
|
||||
defaultPayerId={importDefaultPayerId ?? defaultPayerId}
|
||||
accountOptions={importAccountOptions ?? accountOptions}
|
||||
cardOptions={importCardOptions ?? cardOptions}
|
||||
categoryOptions={importCategoryOptions ?? categoryOptions}
|
||||
estabelecimentos={estabelecimentos}
|
||||
lancamento={lancamentoToImport ?? undefined}
|
||||
transaction={transactionToImport ?? undefined}
|
||||
defaultPeriod={selectedPeriod}
|
||||
isImporting={true}
|
||||
/>
|
||||
|
||||
<BulkImportDialog
|
||||
open={bulkImportOpen && lancamentosToImport.length > 0}
|
||||
open={bulkImportOpen && transactionsToImport.length > 0}
|
||||
onOpenChange={setBulkImportOpen}
|
||||
items={lancamentosToImport}
|
||||
pagadorOptions={importPagadorOptions ?? pagadorOptions}
|
||||
contaOptions={importContaOptions ?? contaOptions}
|
||||
cartaoOptions={importCartaoOptions ?? cartaoOptions}
|
||||
categoriaOptions={importCategoriaOptions ?? categoriaOptions}
|
||||
defaultPagadorId={importDefaultPagadorId ?? defaultPagadorId}
|
||||
items={transactionsToImport}
|
||||
payerOptions={importPayerOptions ?? payerOptions}
|
||||
accountOptions={importAccountOptions ?? accountOptions}
|
||||
cardOptions={importCardOptions ?? cardOptions}
|
||||
categoryOptions={importCategoryOptions ?? categoryOptions}
|
||||
defaultPayerId={importDefaultPayerId ?? defaultPayerId}
|
||||
/>
|
||||
|
||||
<LancamentoDialog
|
||||
<TransactionDialog
|
||||
mode="update"
|
||||
open={editOpen && !!selectedLancamento}
|
||||
open={editOpen && !!selectedTransaction}
|
||||
onOpenChange={setEditOpen}
|
||||
pagadorOptions={pagadorOptions}
|
||||
splitPagadorOptions={splitPagadorOptions}
|
||||
defaultPagadorId={defaultPagadorId}
|
||||
contaOptions={contaOptions}
|
||||
cartaoOptions={cartaoOptions}
|
||||
categoriaOptions={categoriaOptions}
|
||||
payerOptions={payerOptions}
|
||||
splitPayerOptions={splitPayerOptions}
|
||||
defaultPayerId={defaultPayerId}
|
||||
accountOptions={accountOptions}
|
||||
cardOptions={cardOptions}
|
||||
categoryOptions={categoryOptions}
|
||||
estabelecimentos={estabelecimentos}
|
||||
lancamento={selectedLancamento ?? undefined}
|
||||
transaction={selectedTransaction ?? undefined}
|
||||
defaultPeriod={selectedPeriod}
|
||||
onBulkEditRequest={handleBulkEditRequest}
|
||||
/>
|
||||
|
||||
<LancamentoDetailsDialog
|
||||
open={detailsOpen && !!selectedLancamento}
|
||||
<TransactionDetailsDialog
|
||||
open={detailsOpen && !!selectedTransaction}
|
||||
onOpenChange={(open) => {
|
||||
setDetailsOpen(open);
|
||||
if (!open) {
|
||||
setSelectedLancamento(null);
|
||||
setSelectedTransaction(null);
|
||||
}
|
||||
}}
|
||||
lancamento={detailsOpen ? selectedLancamento : null}
|
||||
transaction={detailsOpen ? selectedTransaction : null}
|
||||
/>
|
||||
|
||||
<ConfirmActionDialog
|
||||
open={deleteOpen && !!lancamentoToDelete}
|
||||
open={deleteOpen && !!transactionToDelete}
|
||||
onOpenChange={setDeleteOpen}
|
||||
title={
|
||||
lancamentoToDelete
|
||||
? `Remover lançamento "${lancamentoToDelete.name}"?`
|
||||
transactionToDelete
|
||||
? `Remover lançamento "${transactionToDelete.name}"?`
|
||||
: "Remover lançamento?"
|
||||
}
|
||||
description="Essa ação é irreversível e removerá o lançamento de forma permanente."
|
||||
@@ -517,7 +517,7 @@ export function LancamentosPage({
|
||||
pendingLabel="Removendo..."
|
||||
confirmVariant="destructive"
|
||||
onConfirm={handleDelete}
|
||||
disabled={!lancamentoToDelete}
|
||||
disabled={!transactionToDelete}
|
||||
/>
|
||||
|
||||
<BulkActionDialog
|
||||
@@ -543,16 +543,16 @@ export function LancamentosPage({
|
||||
onOpenChange={setBulkEditOpen}
|
||||
actionType="edit"
|
||||
seriesType={
|
||||
pendingEditData?.lancamento.condition === "Parcelado"
|
||||
pendingEditData?.transaction.condition === "Parcelado"
|
||||
? "installment"
|
||||
: "recurring"
|
||||
}
|
||||
currentNumber={
|
||||
pendingEditData?.lancamento.currentInstallment ?? undefined
|
||||
pendingEditData?.transaction.currentInstallment ?? undefined
|
||||
}
|
||||
totalCount={
|
||||
pendingEditData?.lancamento.installmentCount ??
|
||||
pendingEditData?.lancamento.recurrenceCount ??
|
||||
pendingEditData?.transaction.installmentCount ??
|
||||
pendingEditData?.transaction.recurrenceCount ??
|
||||
undefined
|
||||
}
|
||||
onConfirm={handleBulkEdit}
|
||||
@@ -563,14 +563,14 @@ export function LancamentosPage({
|
||||
open={massAddOpen}
|
||||
onOpenChange={setMassAddOpen}
|
||||
onSubmit={handleMassAddSubmit}
|
||||
pagadorOptions={pagadorOptions}
|
||||
contaOptions={contaOptions}
|
||||
cartaoOptions={cartaoOptions}
|
||||
categoriaOptions={categoriaOptions}
|
||||
payerOptions={payerOptions}
|
||||
accountOptions={accountOptions}
|
||||
cardOptions={cardOptions}
|
||||
categoryOptions={categoryOptions}
|
||||
estabelecimentos={estabelecimentos}
|
||||
selectedPeriod={selectedPeriod}
|
||||
defaultPagadorId={defaultPagadorId}
|
||||
defaultCartaoId={defaultCartaoId}
|
||||
defaultPayerId={defaultPayerId}
|
||||
defaultCardId={defaultCardId}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -595,12 +595,12 @@ export function LancamentosPage({
|
||||
onOpenChange={setAnticipateOpen}
|
||||
seriesId={selectedForAnticipation.seriesId as string}
|
||||
lancamentoName={selectedForAnticipation.name}
|
||||
categorias={categoriaOptions.map((c) => ({
|
||||
categorias={categoryOptions.map((c) => ({
|
||||
id: c.value,
|
||||
name: c.label,
|
||||
icon: c.icon ?? null,
|
||||
}))}
|
||||
pagadores={pagadorOptions.map((p) => ({
|
||||
pagadores={payerOptions.map((p) => ({
|
||||
id: p.value,
|
||||
name: p.label,
|
||||
}))}
|
||||
@@ -614,10 +614,12 @@ export function LancamentosPage({
|
||||
onOpenChange={setAnticipationHistoryOpen}
|
||||
seriesId={selectedForAnticipation.seriesId as string}
|
||||
lancamentoName={selectedForAnticipation.name}
|
||||
onViewLancamento={(lancamentoId) => {
|
||||
const lancamento = lancamentos.find((l) => l.id === lancamentoId);
|
||||
if (lancamento) {
|
||||
setSelectedLancamento(lancamento);
|
||||
onViewLancamento={(transactionId) => {
|
||||
const transaction = transactionList.find(
|
||||
(l) => l.id === transactionId,
|
||||
);
|
||||
if (transaction) {
|
||||
setSelectedTransaction(transaction);
|
||||
setDetailsOpen(true);
|
||||
setAnticipationHistoryOpen(false);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ type SelectItemContentProps = {
|
||||
icon?: string | null;
|
||||
};
|
||||
|
||||
export function PagadorSelectContent({
|
||||
export function PayerSelectContent({
|
||||
label,
|
||||
avatarUrl,
|
||||
}: SelectItemContentProps) {
|
||||
@@ -40,10 +40,7 @@ export function PagadorSelectContent({
|
||||
);
|
||||
}
|
||||
|
||||
export function CategoriaSelectContent({
|
||||
label,
|
||||
icon,
|
||||
}: SelectItemContentProps) {
|
||||
export function CategorySelectContent({ label, icon }: SelectItemContentProps) {
|
||||
return (
|
||||
<span className="flex items-center gap-2">
|
||||
<CategoryIcon name={icon} className="size-4" />
|
||||
@@ -89,7 +86,7 @@ export function ConditionSelectContent({ label }: { label: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function ContaCartaoSelectContent({
|
||||
export function AccountCardSelectContent({
|
||||
label,
|
||||
logo,
|
||||
isCartao,
|
||||
|
||||
@@ -23,7 +23,7 @@ import { displayPeriod } from "@/shared/utils/period";
|
||||
|
||||
interface AnticipationCardProps {
|
||||
anticipation: InstallmentAnticipationWithRelations;
|
||||
onViewLancamento?: (lancamentoId: string) => void;
|
||||
onViewLancamento?: (transactionId: string) => void;
|
||||
onCanceled?: () => void;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export function AnticipationCard({
|
||||
}: AnticipationCardProps) {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const isSettled = anticipation.lancamento.isSettled === true;
|
||||
const isSettled = anticipation.transaction.isSettled === true;
|
||||
const canCancel = !isSettled;
|
||||
|
||||
const formatDate = (date: Date) => {
|
||||
@@ -57,7 +57,7 @@ export function AnticipationCard({
|
||||
};
|
||||
|
||||
const handleViewLancamento = () => {
|
||||
onViewLancamento?.(anticipation.lancamentoId);
|
||||
onViewLancamento?.(anticipation.transactionId);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -132,19 +132,17 @@ export function AnticipationCard({
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
{anticipation.pagador && (
|
||||
{anticipation.payer && (
|
||||
<div>
|
||||
<dt className="text-muted-foreground">Pagador</dt>
|
||||
<dd className="mt-1 font-medium">{anticipation.pagador.name}</dd>
|
||||
<dt className="text-muted-foreground">Payer</dt>
|
||||
<dd className="mt-1 font-medium">{anticipation.payer.name}</dd>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{anticipation.categoria && (
|
||||
{anticipation.category && (
|
||||
<div>
|
||||
<dt className="text-muted-foreground">Categoria</dt>
|
||||
<dd className="mt-1 font-medium">
|
||||
{anticipation.categoria.name}
|
||||
</dd>
|
||||
<dt className="text-muted-foreground">Category</dt>
|
||||
<dd className="mt-1 font-medium">{anticipation.category.name}</dd>
|
||||
</div>
|
||||
)}
|
||||
</dl>
|
||||
|
||||
@@ -14,9 +14,9 @@ import {
|
||||
useTransition,
|
||||
} from "react";
|
||||
import {
|
||||
LANCAMENTO_CONDITIONS,
|
||||
LANCAMENTO_PAYMENT_METHODS,
|
||||
LANCAMENTO_TRANSACTION_TYPES,
|
||||
PAYMENT_METHODS,
|
||||
TRANSACTION_CONDITIONS,
|
||||
TRANSACTION_TYPES,
|
||||
} from "@/features/transactions/constants";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import {
|
||||
@@ -52,14 +52,17 @@ import {
|
||||
} from "@/shared/components/ui/select";
|
||||
import { cn } from "@/shared/utils/ui";
|
||||
import {
|
||||
CategoriaSelectContent,
|
||||
AccountCardSelectContent,
|
||||
CategorySelectContent,
|
||||
ConditionSelectContent,
|
||||
ContaCartaoSelectContent,
|
||||
PagadorSelectContent,
|
||||
PayerSelectContent,
|
||||
PaymentMethodSelectContent,
|
||||
TransactionTypeSelectContent,
|
||||
} from "../select-items";
|
||||
import type { ContaCartaoFilterOption, LancamentoFilterOption } from "../types";
|
||||
import type {
|
||||
AccountCardFilterOption,
|
||||
TransactionFilterOption,
|
||||
} from "../types";
|
||||
|
||||
const FILTER_EMPTY_VALUE = "__all";
|
||||
|
||||
@@ -124,23 +127,23 @@ function FilterSelect({
|
||||
);
|
||||
}
|
||||
|
||||
interface LancamentosFiltersProps {
|
||||
pagadorOptions: LancamentoFilterOption[];
|
||||
categoriaOptions: LancamentoFilterOption[];
|
||||
contaCartaoOptions: ContaCartaoFilterOption[];
|
||||
interface TransactionsFiltersProps {
|
||||
payerOptions: TransactionFilterOption[];
|
||||
categoryOptions: TransactionFilterOption[];
|
||||
accountCardOptions: AccountCardFilterOption[];
|
||||
className?: string;
|
||||
exportButton?: ReactNode;
|
||||
hideAdvancedFilters?: boolean;
|
||||
}
|
||||
|
||||
export function LancamentosFilters({
|
||||
pagadorOptions,
|
||||
categoriaOptions,
|
||||
contaCartaoOptions,
|
||||
export function TransactionsFilters({
|
||||
payerOptions,
|
||||
categoryOptions,
|
||||
accountCardOptions,
|
||||
className,
|
||||
exportButton,
|
||||
hideAdvancedFilters = false,
|
||||
}: LancamentosFiltersProps) {
|
||||
}: TransactionsFiltersProps) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
@@ -195,7 +198,7 @@ export function LancamentosFilters({
|
||||
nextParams.set("periodo", periodValue);
|
||||
}
|
||||
setSearchValue("");
|
||||
setCategoriaOpen(false);
|
||||
setCategoryOpen(false);
|
||||
startTransition(() => {
|
||||
const target = nextParams.toString()
|
||||
? `${pathname}?${nextParams.toString()}`
|
||||
@@ -204,13 +207,13 @@ export function LancamentosFilters({
|
||||
});
|
||||
};
|
||||
|
||||
const pagadorSelectOptions = pagadorOptions.map((option) => ({
|
||||
const payerSelectOptions = payerOptions.map((option) => ({
|
||||
value: option.slug,
|
||||
label: option.label,
|
||||
avatarUrl: option.avatarUrl,
|
||||
}));
|
||||
|
||||
const contaOptions = contaCartaoOptions
|
||||
const accountOptions = accountCardOptions
|
||||
.filter((option) => option.kind === "conta")
|
||||
.map((option) => ({
|
||||
value: option.slug,
|
||||
@@ -218,7 +221,7 @@ export function LancamentosFilters({
|
||||
logo: option.logo,
|
||||
}));
|
||||
|
||||
const cartaoOptions = contaCartaoOptions
|
||||
const cardOptions = accountCardOptions
|
||||
.filter((option) => option.kind === "cartao")
|
||||
.map((option) => ({
|
||||
value: option.slug,
|
||||
@@ -226,34 +229,34 @@ export function LancamentosFilters({
|
||||
logo: option.logo,
|
||||
}));
|
||||
|
||||
const categoriaValue = getParamValue("categoria");
|
||||
const selectedCategoria =
|
||||
categoriaValue !== FILTER_EMPTY_VALUE
|
||||
? categoriaOptions.find((option) => option.slug === categoriaValue)
|
||||
const categoryValue = getParamValue("category");
|
||||
const selectedCategory =
|
||||
categoryValue !== FILTER_EMPTY_VALUE
|
||||
? categoryOptions.find((option) => option.slug === categoryValue)
|
||||
: null;
|
||||
|
||||
const pagadorValue = getParamValue("pagador");
|
||||
const selectedPagador =
|
||||
pagadorValue !== FILTER_EMPTY_VALUE
|
||||
? pagadorOptions.find((option) => option.slug === pagadorValue)
|
||||
const payerValue = getParamValue("payer");
|
||||
const selectedPayer =
|
||||
payerValue !== FILTER_EMPTY_VALUE
|
||||
? payerOptions.find((option) => option.slug === payerValue)
|
||||
: null;
|
||||
|
||||
const contaCartaoValue = getParamValue("contaCartao");
|
||||
const selectedContaCartao =
|
||||
contaCartaoValue !== FILTER_EMPTY_VALUE
|
||||
? contaCartaoOptions.find((option) => option.slug === contaCartaoValue)
|
||||
const accountCardValue = getParamValue("accountCard");
|
||||
const selectedAccountCard =
|
||||
accountCardValue !== FILTER_EMPTY_VALUE
|
||||
? accountCardOptions.find((option) => option.slug === accountCardValue)
|
||||
: null;
|
||||
|
||||
const [categoriaOpen, setCategoriaOpen] = useState(false);
|
||||
const [categoryOpen, setCategoryOpen] = useState(false);
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
|
||||
const hasActiveFilters =
|
||||
searchParams.get("transacao") ||
|
||||
searchParams.get("condicao") ||
|
||||
searchParams.get("pagamento") ||
|
||||
searchParams.get("pagador") ||
|
||||
searchParams.get("categoria") ||
|
||||
searchParams.get("contaCartao");
|
||||
searchParams.get("type") ||
|
||||
searchParams.get("condition") ||
|
||||
searchParams.get("payment") ||
|
||||
searchParams.get("payer") ||
|
||||
searchParams.get("category") ||
|
||||
searchParams.get("accountCard");
|
||||
|
||||
const handleResetFilters = () => {
|
||||
handleReset();
|
||||
@@ -315,9 +318,9 @@ export function LancamentosFilters({
|
||||
Tipo de Lançamento
|
||||
</label>
|
||||
<FilterSelect
|
||||
param="transacao"
|
||||
param="type"
|
||||
placeholder="Todos"
|
||||
options={buildStaticOptions(LANCAMENTO_TRANSACTION_TYPES)}
|
||||
options={buildStaticOptions(TRANSACTION_TYPES)}
|
||||
widthClass="w-full border-dashed"
|
||||
disabled={isPending}
|
||||
getParamValue={getParamValue}
|
||||
@@ -333,9 +336,9 @@ export function LancamentosFilters({
|
||||
Condição de Lançamento
|
||||
</label>
|
||||
<FilterSelect
|
||||
param="condicao"
|
||||
param="condition"
|
||||
placeholder="Todas"
|
||||
options={buildStaticOptions(LANCAMENTO_CONDITIONS)}
|
||||
options={buildStaticOptions(TRANSACTION_CONDITIONS)}
|
||||
widthClass="w-full border-dashed"
|
||||
disabled={isPending}
|
||||
getParamValue={getParamValue}
|
||||
@@ -351,9 +354,9 @@ export function LancamentosFilters({
|
||||
Forma de Pagamento
|
||||
</label>
|
||||
<FilterSelect
|
||||
param="pagamento"
|
||||
param="payment"
|
||||
placeholder="Todos"
|
||||
options={buildStaticOptions(LANCAMENTO_PAYMENT_METHODS)}
|
||||
options={buildStaticOptions(PAYMENT_METHODS)}
|
||||
widthClass="w-full border-dashed"
|
||||
disabled={isPending}
|
||||
getParamValue={getParamValue}
|
||||
@@ -365,12 +368,12 @@ export function LancamentosFilters({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Pagador</label>
|
||||
<label className="text-sm font-medium">Payer</label>
|
||||
<Select
|
||||
value={getParamValue("pagador")}
|
||||
value={getParamValue("payer")}
|
||||
onValueChange={(value) =>
|
||||
handleFilterChange(
|
||||
"pagador",
|
||||
"payer",
|
||||
value === FILTER_EMPTY_VALUE ? null : value,
|
||||
)
|
||||
}
|
||||
@@ -381,10 +384,10 @@ export function LancamentosFilters({
|
||||
disabled={isPending}
|
||||
>
|
||||
<span className="truncate">
|
||||
{selectedPagador ? (
|
||||
<PagadorSelectContent
|
||||
label={selectedPagador.label}
|
||||
avatarUrl={selectedPagador.avatarUrl}
|
||||
{selectedPayer ? (
|
||||
<PayerSelectContent
|
||||
label={selectedPayer.label}
|
||||
avatarUrl={selectedPayer.avatarUrl}
|
||||
/>
|
||||
) : (
|
||||
"Todos"
|
||||
@@ -393,9 +396,9 @@ export function LancamentosFilters({
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={FILTER_EMPTY_VALUE}>Todos</SelectItem>
|
||||
{pagadorSelectOptions.map((option) => (
|
||||
{payerSelectOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<PagadorSelectContent
|
||||
<PayerSelectContent
|
||||
label={option.label}
|
||||
avatarUrl={option.avatarUrl}
|
||||
/>
|
||||
@@ -406,25 +409,25 @@ export function LancamentosFilters({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Categoria</label>
|
||||
<label className="text-sm font-medium">Category</label>
|
||||
<Popover
|
||||
open={categoriaOpen}
|
||||
onOpenChange={setCategoriaOpen}
|
||||
open={categoryOpen}
|
||||
onOpenChange={setCategoryOpen}
|
||||
modal
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={categoriaOpen}
|
||||
aria-expanded={categoryOpen}
|
||||
className="w-full justify-between text-sm border-dashed"
|
||||
disabled={isPending}
|
||||
>
|
||||
<span className="truncate flex items-center gap-2">
|
||||
{selectedCategoria ? (
|
||||
<CategoriaSelectContent
|
||||
label={selectedCategoria.label}
|
||||
icon={selectedCategoria.icon}
|
||||
{selectedCategory ? (
|
||||
<CategorySelectContent
|
||||
label={selectedCategory.label}
|
||||
icon={selectedCategory.icon}
|
||||
/>
|
||||
) : (
|
||||
"Todas"
|
||||
@@ -442,29 +445,29 @@ export function LancamentosFilters({
|
||||
<CommandItem
|
||||
value={FILTER_EMPTY_VALUE}
|
||||
onSelect={() => {
|
||||
handleFilterChange("categoria", null);
|
||||
setCategoriaOpen(false);
|
||||
handleFilterChange("category", null);
|
||||
setCategoryOpen(false);
|
||||
}}
|
||||
>
|
||||
Todas
|
||||
{categoriaValue === FILTER_EMPTY_VALUE ? (
|
||||
{categoryValue === FILTER_EMPTY_VALUE ? (
|
||||
<RiCheckLine className="ml-auto size-4" />
|
||||
) : null}
|
||||
</CommandItem>
|
||||
{categoriaOptions.map((option) => (
|
||||
{categoryOptions.map((option) => (
|
||||
<CommandItem
|
||||
key={option.slug}
|
||||
value={option.slug}
|
||||
onSelect={() => {
|
||||
handleFilterChange("categoria", option.slug);
|
||||
setCategoriaOpen(false);
|
||||
handleFilterChange("category", option.slug);
|
||||
setCategoryOpen(false);
|
||||
}}
|
||||
>
|
||||
<CategoriaSelectContent
|
||||
<CategorySelectContent
|
||||
label={option.label}
|
||||
icon={option.icon}
|
||||
/>
|
||||
{categoriaValue === option.slug ? (
|
||||
{categoryValue === option.slug ? (
|
||||
<RiCheckLine className="ml-auto size-4" />
|
||||
) : null}
|
||||
</CommandItem>
|
||||
@@ -479,10 +482,10 @@ export function LancamentosFilters({
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Conta/Cartão</label>
|
||||
<Select
|
||||
value={getParamValue("contaCartao")}
|
||||
value={getParamValue("accountCard")}
|
||||
onValueChange={(value) =>
|
||||
handleFilterChange(
|
||||
"contaCartao",
|
||||
"accountCard",
|
||||
value === FILTER_EMPTY_VALUE ? null : value,
|
||||
)
|
||||
}
|
||||
@@ -493,11 +496,11 @@ export function LancamentosFilters({
|
||||
disabled={isPending}
|
||||
>
|
||||
<span className="truncate">
|
||||
{selectedContaCartao ? (
|
||||
<ContaCartaoSelectContent
|
||||
label={selectedContaCartao.label}
|
||||
logo={selectedContaCartao.logo}
|
||||
isCartao={selectedContaCartao.kind === "cartao"}
|
||||
{selectedAccountCard ? (
|
||||
<AccountCardSelectContent
|
||||
label={selectedAccountCard.label}
|
||||
logo={selectedAccountCard.logo}
|
||||
isCartao={selectedAccountCard.kind === "cartao"}
|
||||
/>
|
||||
) : (
|
||||
"Todos"
|
||||
@@ -506,12 +509,12 @@ export function LancamentosFilters({
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={FILTER_EMPTY_VALUE}>Todos</SelectItem>
|
||||
{contaOptions.length > 0 ? (
|
||||
{accountOptions.length > 0 ? (
|
||||
<SelectGroup>
|
||||
<SelectLabel>Contas</SelectLabel>
|
||||
{contaOptions.map((option) => (
|
||||
{accountOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={option.label}
|
||||
logo={option.logo}
|
||||
isCartao={false}
|
||||
@@ -520,12 +523,12 @@ export function LancamentosFilters({
|
||||
))}
|
||||
</SelectGroup>
|
||||
) : null}
|
||||
{cartaoOptions.length > 0 ? (
|
||||
{cardOptions.length > 0 ? (
|
||||
<SelectGroup>
|
||||
<SelectLabel>Cartões</SelectLabel>
|
||||
{cartaoOptions.map((option) => (
|
||||
{cardOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={option.label}
|
||||
logo={option.logo}
|
||||
isCartao={true}
|
||||
|
||||
@@ -79,25 +79,25 @@ import { formatDate } from "@/shared/utils/date";
|
||||
import { getConditionIcon, getPaymentMethodIcon } from "@/shared/utils/icons";
|
||||
import { cn } from "@/shared/utils/ui";
|
||||
import { EstabelecimentoLogo } from "../shared/establishment-logo";
|
||||
import { LancamentosExport } from "../transactions-export";
|
||||
import { TransactionsExport } from "../transactions-export";
|
||||
import type {
|
||||
ContaCartaoFilterOption,
|
||||
LancamentoFilterOption,
|
||||
LancamentoItem,
|
||||
AccountCardFilterOption,
|
||||
TransactionFilterOption,
|
||||
TransactionItem,
|
||||
} from "../types";
|
||||
import { LancamentosFilters } from "./transactions-filters";
|
||||
import { TransactionsFilters } from "./transactions-filters";
|
||||
|
||||
type BuildColumnsArgs = {
|
||||
currentUserId: string;
|
||||
noteAsColumn: boolean;
|
||||
onEdit?: (item: LancamentoItem) => void;
|
||||
onCopy?: (item: LancamentoItem) => void;
|
||||
onImport?: (item: LancamentoItem) => void;
|
||||
onConfirmDelete?: (item: LancamentoItem) => void;
|
||||
onViewDetails?: (item: LancamentoItem) => void;
|
||||
onToggleSettlement?: (item: LancamentoItem) => void;
|
||||
onAnticipate?: (item: LancamentoItem) => void;
|
||||
onViewAnticipationHistory?: (item: LancamentoItem) => void;
|
||||
onEdit?: (item: TransactionItem) => void;
|
||||
onCopy?: (item: TransactionItem) => void;
|
||||
onImport?: (item: TransactionItem) => void;
|
||||
onConfirmDelete?: (item: TransactionItem) => void;
|
||||
onViewDetails?: (item: TransactionItem) => void;
|
||||
onToggleSettlement?: (item: TransactionItem) => void;
|
||||
onAnticipate?: (item: TransactionItem) => void;
|
||||
onViewAnticipationHistory?: (item: TransactionItem) => void;
|
||||
isSettlementLoading: (id: string) => boolean;
|
||||
showActions: boolean;
|
||||
};
|
||||
@@ -115,7 +115,7 @@ const buildColumns = ({
|
||||
onViewAnticipationHistory,
|
||||
isSettlementLoading,
|
||||
showActions,
|
||||
}: BuildColumnsArgs): ColumnDef<LancamentoItem>[] => {
|
||||
}: BuildColumnsArgs): ColumnDef<TransactionItem>[] => {
|
||||
const noop = () => undefined;
|
||||
const handleEdit = onEdit ?? noop;
|
||||
const handleCopy = onCopy ?? noop;
|
||||
@@ -126,7 +126,7 @@ const buildColumns = ({
|
||||
const handleAnticipate = onAnticipate ?? noop;
|
||||
const handleViewAnticipationHistory = onViewAnticipationHistory ?? noop;
|
||||
|
||||
const columns: ColumnDef<LancamentoItem>[] = [
|
||||
const columns: ColumnDef<TransactionItem>[] = [
|
||||
{
|
||||
id: "select",
|
||||
header: ({ table }) => (
|
||||
@@ -380,7 +380,7 @@ const buildColumns = ({
|
||||
accessorKey: "pagadorName",
|
||||
header: "Pagador",
|
||||
cell: ({ row }) => {
|
||||
const { pagadorId, pagadorName, pagadorAvatar } = row.original;
|
||||
const { payerId, pagadorName, pagadorAvatar } = row.original;
|
||||
|
||||
const label = pagadorName?.trim() || "Sem pagador";
|
||||
const displayName = label.split(/\s+/)[0] ?? label;
|
||||
@@ -398,7 +398,7 @@ const buildColumns = ({
|
||||
</>
|
||||
);
|
||||
|
||||
if (!pagadorId) {
|
||||
if (!payerId) {
|
||||
return (
|
||||
<span className="inline-flex items-center gap-2">{content}</span>
|
||||
);
|
||||
@@ -406,7 +406,7 @@ const buildColumns = ({
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`/payers/${pagadorId}`}
|
||||
href={`/payers/${payerId}`}
|
||||
className="inline-flex items-center gap-2 hover:underline"
|
||||
title={label}
|
||||
>
|
||||
@@ -424,17 +424,17 @@ const buildColumns = ({
|
||||
contaName,
|
||||
cartaoLogo,
|
||||
contaLogo,
|
||||
cartaoId,
|
||||
contaId,
|
||||
cardId,
|
||||
accountId,
|
||||
userId,
|
||||
} = row.original;
|
||||
const isCartao = Boolean(cartaoName);
|
||||
const label = cartaoName ?? contaName;
|
||||
const logoSrc = resolveLogoSrc(cartaoLogo ?? contaLogo);
|
||||
const href = cartaoId
|
||||
? `/cards/${cartaoId}/invoice`
|
||||
: contaId
|
||||
? `/accounts/${contaId}/statement`
|
||||
const href = cardId
|
||||
? `/cards/${cardId}/invoice`
|
||||
: accountId
|
||||
? `/accounts/${accountId}/statement`
|
||||
: null;
|
||||
const isOwnData = userId === currentUserId;
|
||||
|
||||
@@ -458,7 +458,7 @@ const buildColumns = ({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>{content}</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
{isCartao ? "Cartão" : "Conta"}: {label}
|
||||
{isCartao ? "Cartão" : "FinancialAccount"}: {label}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
@@ -484,7 +484,7 @@ const buildColumns = ({
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
{isCartao ? "Cartão" : "Conta"}: {label}
|
||||
{isCartao ? "Cartão" : "FinancialAccount"}: {label}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
@@ -493,8 +493,8 @@ const buildColumns = ({
|
||||
];
|
||||
|
||||
if (noteAsColumn) {
|
||||
const contaCartaoIndex = columns.findIndex((c) => c.id === "contaCartao");
|
||||
const noteColumn: ColumnDef<LancamentoItem> = {
|
||||
const accountCardIndex = columns.findIndex((c) => c.id === "contaCartao");
|
||||
const noteColumn: ColumnDef<TransactionItem> = {
|
||||
accessorKey: "note",
|
||||
header: "Anotação",
|
||||
cell: ({ row }) => {
|
||||
@@ -511,7 +511,7 @@ const buildColumns = ({
|
||||
);
|
||||
},
|
||||
};
|
||||
columns.splice(contaCartaoIndex, 0, noteColumn);
|
||||
columns.splice(accountCardIndex, 0, noteColumn);
|
||||
}
|
||||
|
||||
if (showActions) {
|
||||
@@ -607,7 +607,7 @@ const buildColumns = ({
|
||||
row.original.userId !== currentUserId && (
|
||||
<DropdownMenuItem onSelect={() => handleImport(row.original)}>
|
||||
<RiFileCopyLine className="size-4" />
|
||||
Importar para Minha Conta
|
||||
Importar para Minha FinancialAccount
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{row.original.userId === currentUserId && (
|
||||
@@ -669,7 +669,7 @@ const buildColumns = ({
|
||||
const FIXED_START_IDS = ["select", "purchaseDate"];
|
||||
const FIXED_END_IDS = ["actions"];
|
||||
|
||||
function getColumnId(col: ColumnDef<LancamentoItem>): string {
|
||||
function getColumnId(col: ColumnDef<TransactionItem>): string {
|
||||
const c = col as { id?: string; accessorKey?: string };
|
||||
return c.id ?? c.accessorKey ?? "";
|
||||
}
|
||||
@@ -686,15 +686,15 @@ function reorderColumnsByPreference<T>(
|
||||
const fixedEnd: ColumnDef<T>[] = [];
|
||||
|
||||
for (const col of columns) {
|
||||
const id = getColumnId(col as ColumnDef<LancamentoItem>);
|
||||
const id = getColumnId(col as ColumnDef<TransactionItem>);
|
||||
if (FIXED_START_IDS.includes(id)) fixedStart.push(col);
|
||||
else if (FIXED_END_IDS.includes(id)) fixedEnd.push(col);
|
||||
else reorderable.push(col);
|
||||
}
|
||||
|
||||
const sorted = [...reorderable].sort((a, b) => {
|
||||
const idA = getColumnId(a as ColumnDef<LancamentoItem>);
|
||||
const idB = getColumnId(b as ColumnDef<LancamentoItem>);
|
||||
const idA = getColumnId(a as ColumnDef<TransactionItem>);
|
||||
const idB = getColumnId(b as ColumnDef<TransactionItem>);
|
||||
const indexA = order.indexOf(idA);
|
||||
const indexB = order.indexOf(idB);
|
||||
if (indexA === -1 && indexB === -1) return 0;
|
||||
@@ -707,39 +707,39 @@ function reorderColumnsByPreference<T>(
|
||||
}
|
||||
|
||||
type LancamentosTableProps = {
|
||||
data: LancamentoItem[];
|
||||
data: TransactionItem[];
|
||||
currentUserId: string;
|
||||
noteAsColumn?: boolean;
|
||||
columnOrder?: string[] | null;
|
||||
pagadorFilterOptions?: LancamentoFilterOption[];
|
||||
categoriaFilterOptions?: LancamentoFilterOption[];
|
||||
contaCartaoFilterOptions?: ContaCartaoFilterOption[];
|
||||
payerFilterOptions?: TransactionFilterOption[];
|
||||
categoryFilterOptions?: TransactionFilterOption[];
|
||||
accountCardFilterOptions?: AccountCardFilterOption[];
|
||||
selectedPeriod?: string;
|
||||
onCreate?: (type: "Despesa" | "Receita") => void;
|
||||
onMassAdd?: () => void;
|
||||
onEdit?: (item: LancamentoItem) => void;
|
||||
onCopy?: (item: LancamentoItem) => void;
|
||||
onImport?: (item: LancamentoItem) => void;
|
||||
onConfirmDelete?: (item: LancamentoItem) => void;
|
||||
onBulkDelete?: (items: LancamentoItem[]) => void;
|
||||
onBulkImport?: (items: LancamentoItem[]) => void;
|
||||
onViewDetails?: (item: LancamentoItem) => void;
|
||||
onToggleSettlement?: (item: LancamentoItem) => void;
|
||||
onAnticipate?: (item: LancamentoItem) => void;
|
||||
onViewAnticipationHistory?: (item: LancamentoItem) => void;
|
||||
onEdit?: (item: TransactionItem) => void;
|
||||
onCopy?: (item: TransactionItem) => void;
|
||||
onImport?: (item: TransactionItem) => void;
|
||||
onConfirmDelete?: (item: TransactionItem) => void;
|
||||
onBulkDelete?: (items: TransactionItem[]) => void;
|
||||
onBulkImport?: (items: TransactionItem[]) => void;
|
||||
onViewDetails?: (item: TransactionItem) => void;
|
||||
onToggleSettlement?: (item: TransactionItem) => void;
|
||||
onAnticipate?: (item: TransactionItem) => void;
|
||||
onViewAnticipationHistory?: (item: TransactionItem) => void;
|
||||
isSettlementLoading?: (id: string) => boolean;
|
||||
showActions?: boolean;
|
||||
showFilters?: boolean;
|
||||
};
|
||||
|
||||
export function LancamentosTable({
|
||||
export function TransactionsTable({
|
||||
data,
|
||||
currentUserId,
|
||||
noteAsColumn = false,
|
||||
columnOrder: columnOrderPreference = null,
|
||||
pagadorFilterOptions = [],
|
||||
categoriaFilterOptions = [],
|
||||
contaCartaoFilterOptions = [],
|
||||
payerFilterOptions = [],
|
||||
categoryFilterOptions = [],
|
||||
accountCardFilterOptions = [],
|
||||
selectedPeriod,
|
||||
onCreate,
|
||||
onMassAdd,
|
||||
@@ -904,15 +904,15 @@ export function LancamentosTable({
|
||||
)}
|
||||
|
||||
{showFilters ? (
|
||||
<LancamentosFilters
|
||||
pagadorOptions={pagadorFilterOptions}
|
||||
categoriaOptions={categoriaFilterOptions}
|
||||
contaCartaoOptions={contaCartaoFilterOptions}
|
||||
<TransactionsFilters
|
||||
payerOptions={payerFilterOptions}
|
||||
categoryOptions={categoryFilterOptions}
|
||||
accountCardOptions={accountCardFilterOptions}
|
||||
className="w-full lg:flex-1 lg:justify-end"
|
||||
hideAdvancedFilters={hasOtherUserData}
|
||||
exportButton={
|
||||
selectedPeriod ? (
|
||||
<LancamentosExport
|
||||
<TransactionsExport
|
||||
lancamentos={data}
|
||||
period={selectedPeriod}
|
||||
/>
|
||||
|
||||
@@ -25,14 +25,14 @@ import {
|
||||
loadExportLogoDataUrl,
|
||||
} from "@/shared/utils/export-branding";
|
||||
import { displayPeriod } from "@/shared/utils/period";
|
||||
import type { LancamentoItem } from "./types";
|
||||
import type { TransactionItem } from "./types";
|
||||
|
||||
interface LancamentosExportProps {
|
||||
lancamentos: LancamentoItem[];
|
||||
lancamentos: TransactionItem[];
|
||||
period: string;
|
||||
}
|
||||
|
||||
export function LancamentosExport({
|
||||
export function TransactionsExport({
|
||||
lancamentos,
|
||||
period,
|
||||
}: LancamentosExportProps) {
|
||||
@@ -52,21 +52,21 @@ export function LancamentosExport({
|
||||
);
|
||||
};
|
||||
|
||||
const getContaCartaoName = (lancamento: LancamentoItem) => {
|
||||
if (lancamento.contaName) return lancamento.contaName;
|
||||
if (lancamento.cartaoName) return lancamento.cartaoName;
|
||||
const getContaCartaoName = (transaction: TransactionItem) => {
|
||||
if (transaction.contaName) return transaction.contaName;
|
||||
if (transaction.cartaoName) return transaction.cartaoName;
|
||||
return "-";
|
||||
};
|
||||
|
||||
const getNameWithInstallment = (lancamento: LancamentoItem) => {
|
||||
const getNameWithInstallment = (transaction: TransactionItem) => {
|
||||
const isInstallment =
|
||||
lancamento.condition.trim().toLowerCase() === "parcelado";
|
||||
transaction.condition.trim().toLowerCase() === "parcelado";
|
||||
|
||||
if (!isInstallment || !lancamento.installmentCount) {
|
||||
return lancamento.name;
|
||||
if (!isInstallment || !transaction.installmentCount) {
|
||||
return transaction.name;
|
||||
}
|
||||
|
||||
return `${lancamento.name} (${lancamento.currentInstallment ?? 1}/${lancamento.installmentCount})`;
|
||||
return `${transaction.name} (${transaction.currentInstallment ?? 1}/${transaction.installmentCount})`;
|
||||
};
|
||||
|
||||
const exportToCSV = () => {
|
||||
@@ -80,9 +80,9 @@ export function LancamentosExport({
|
||||
"Condição",
|
||||
"Pagamento",
|
||||
"Valor",
|
||||
"Categoria",
|
||||
"Category",
|
||||
"Conta/Cartão",
|
||||
"Pagador",
|
||||
"Payer",
|
||||
];
|
||||
const rows: string[][] = [];
|
||||
|
||||
@@ -138,9 +138,9 @@ export function LancamentosExport({
|
||||
"Condição",
|
||||
"Pagamento",
|
||||
"Valor",
|
||||
"Categoria",
|
||||
"Category",
|
||||
"Conta/Cartão",
|
||||
"Pagador",
|
||||
"Payer",
|
||||
];
|
||||
const rows: (string | number)[][] = [];
|
||||
|
||||
@@ -168,9 +168,9 @@ export function LancamentosExport({
|
||||
{ wch: 15 }, // Condição
|
||||
{ wch: 20 }, // Pagamento
|
||||
{ wch: 15 }, // Valor
|
||||
{ wch: 20 }, // Categoria
|
||||
{ wch: 20 }, // Category
|
||||
{ wch: 20 }, // Conta/Cartão
|
||||
{ wch: 20 }, // Pagador
|
||||
{ wch: 20 }, // Payer
|
||||
];
|
||||
|
||||
const wb = XLSX.utils.book_new();
|
||||
@@ -241,7 +241,7 @@ export function LancamentosExport({
|
||||
"Valor",
|
||||
"Categoria",
|
||||
"Conta/Cartão",
|
||||
"Pagador",
|
||||
"Payer",
|
||||
],
|
||||
];
|
||||
|
||||
@@ -281,7 +281,7 @@ export function LancamentosExport({
|
||||
5: { cellWidth: 24 }, // Valor
|
||||
6: { cellWidth: 30 }, // Categoria
|
||||
7: { cellWidth: 30 }, // Conta/Cartão
|
||||
8: { cellWidth: 31 }, // Pagador
|
||||
8: { cellWidth: 31 }, // Payer
|
||||
},
|
||||
didParseCell: (cellData) => {
|
||||
if (cellData.section === "body" && cellData.column.index === 5) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type LancamentoItem = {
|
||||
export type TransactionItem = {
|
||||
id: string;
|
||||
userId: string;
|
||||
name: string;
|
||||
@@ -8,17 +8,17 @@ export type LancamentoItem = {
|
||||
amount: number;
|
||||
condition: string;
|
||||
paymentMethod: string;
|
||||
pagadorId: string | null;
|
||||
payerId: string | null;
|
||||
pagadorName: string | null;
|
||||
pagadorAvatar: string | null;
|
||||
pagadorRole: string | null;
|
||||
contaId: string | null;
|
||||
accountId: string | null;
|
||||
contaName: string | null;
|
||||
contaLogo: string | null;
|
||||
cartaoId: string | null;
|
||||
cardId: string | null;
|
||||
cartaoName: string | null;
|
||||
cartaoLogo: string | null;
|
||||
categoriaId: string | null;
|
||||
categoryId: string | null;
|
||||
categoriaName: string | null;
|
||||
categoriaType: string | null;
|
||||
categoriaIcon: string | null;
|
||||
@@ -50,14 +50,14 @@ export type SelectOption = {
|
||||
dueDay?: string | null;
|
||||
};
|
||||
|
||||
export type LancamentoFilterOption = {
|
||||
export type TransactionFilterOption = {
|
||||
slug: string;
|
||||
label: string;
|
||||
icon?: string | null;
|
||||
avatarUrl?: string | null;
|
||||
};
|
||||
|
||||
export type ContaCartaoFilterOption = LancamentoFilterOption & {
|
||||
export type AccountCardFilterOption = TransactionFilterOption & {
|
||||
kind: "conta" | "cartao";
|
||||
logo?: string | null;
|
||||
};
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
export const LANCAMENTO_TRANSACTION_TYPES = [
|
||||
export const TRANSACTION_TYPES = [
|
||||
"Despesa",
|
||||
"Receita",
|
||||
"Transferência",
|
||||
] as const;
|
||||
|
||||
export const LANCAMENTO_CONDITIONS = [
|
||||
export const TRANSACTION_CONDITIONS = [
|
||||
"À vista",
|
||||
"Parcelado",
|
||||
"Recorrente",
|
||||
] as const;
|
||||
|
||||
export const LANCAMENTO_PAYMENT_METHODS = [
|
||||
export const PAYMENT_METHODS = [
|
||||
"Cartão de crédito",
|
||||
"Cartão de débito",
|
||||
"Pix",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { LancamentoItem } from "@/features/transactions/components/types";
|
||||
import type { TransactionItem } from "@/features/transactions/components/types";
|
||||
import { getTodayDateString } from "@/shared/utils/date";
|
||||
import { derivePeriodFromDate, getNextPeriod } from "@/shared/utils/period";
|
||||
import {
|
||||
LANCAMENTO_CONDITIONS,
|
||||
LANCAMENTO_PAYMENT_METHODS,
|
||||
LANCAMENTO_TRANSACTION_TYPES,
|
||||
PAYMENT_METHODS,
|
||||
TRANSACTION_CONDITIONS,
|
||||
TRANSACTION_TYPES,
|
||||
} from "./constants";
|
||||
|
||||
/**
|
||||
@@ -68,7 +68,7 @@ export type SplitType = "equal" | "60-40" | "70-30" | "80-20" | "custom";
|
||||
/**
|
||||
* Form state type for lancamento dialog
|
||||
*/
|
||||
export type LancamentoFormState = {
|
||||
export type TransactionFormState = {
|
||||
purchaseDate: string;
|
||||
period: string;
|
||||
name: string;
|
||||
@@ -76,15 +76,15 @@ export type LancamentoFormState = {
|
||||
amount: string;
|
||||
condition: string;
|
||||
paymentMethod: string;
|
||||
pagadorId: string | undefined;
|
||||
secondaryPagadorId: string | undefined;
|
||||
payerId: string | undefined;
|
||||
secondaryPayerId: string | undefined;
|
||||
isSplit: boolean;
|
||||
splitType: SplitType;
|
||||
primarySplitAmount: string;
|
||||
secondarySplitAmount: string;
|
||||
contaId: string | undefined;
|
||||
cartaoId: string | undefined;
|
||||
categoriaId: string | undefined;
|
||||
accountId: string | undefined;
|
||||
cardId: string | undefined;
|
||||
categoryId: string | undefined;
|
||||
installmentCount: string;
|
||||
recurrenceCount: string;
|
||||
dueDate: string;
|
||||
@@ -97,7 +97,7 @@ export type LancamentoFormState = {
|
||||
* Initial state overrides for lancamento form
|
||||
*/
|
||||
export type LancamentoFormOverrides = {
|
||||
defaultCartaoId?: string | null;
|
||||
defaultCardId?: string | null;
|
||||
defaultPaymentMethod?: string | null;
|
||||
defaultPurchaseDate?: string | null;
|
||||
defaultName?: string | null;
|
||||
@@ -109,20 +109,20 @@ export type LancamentoFormOverrides = {
|
||||
/**
|
||||
* Builds initial form state from lancamento data and defaults
|
||||
*/
|
||||
export function buildLancamentoInitialState(
|
||||
lancamento?: LancamentoItem,
|
||||
defaultPagadorId?: string | null,
|
||||
export function buildTransactionInitialState(
|
||||
transaction?: TransactionItem,
|
||||
defaultPayerId?: string | null,
|
||||
preferredPeriod?: string,
|
||||
overrides?: LancamentoFormOverrides,
|
||||
): LancamentoFormState {
|
||||
const purchaseDate = lancamento?.purchaseDate
|
||||
? lancamento.purchaseDate.slice(0, 10)
|
||||
): TransactionFormState {
|
||||
const purchaseDate = transaction?.purchaseDate
|
||||
? transaction.purchaseDate.slice(0, 10)
|
||||
: (overrides?.defaultPurchaseDate ?? "");
|
||||
|
||||
const paymentMethod =
|
||||
lancamento?.paymentMethod ??
|
||||
transaction?.paymentMethod ??
|
||||
overrides?.defaultPaymentMethod ??
|
||||
LANCAMENTO_PAYMENT_METHODS[0];
|
||||
PAYMENT_METHODS[0];
|
||||
|
||||
const derivedPeriod = derivePeriodFromDate(purchaseDate);
|
||||
const fallbackPeriod =
|
||||
@@ -132,28 +132,28 @@ export function buildLancamentoInitialState(
|
||||
|
||||
// Quando importando, usar valores padrão do usuário logado ao invés dos valores do lançamento original
|
||||
const isImporting = overrides?.isImporting ?? false;
|
||||
const fallbackPagadorId = isImporting
|
||||
? (defaultPagadorId ?? null)
|
||||
: (lancamento?.pagadorId ?? defaultPagadorId ?? null);
|
||||
const fallbackPayerId = isImporting
|
||||
? (defaultPayerId ?? null)
|
||||
: (transaction?.payerId ?? defaultPayerId ?? null);
|
||||
|
||||
const boletoPaymentDate =
|
||||
lancamento?.boletoPaymentDate ??
|
||||
(paymentMethod === "Boleto" && (lancamento?.isSettled ?? false)
|
||||
transaction?.boletoPaymentDate ??
|
||||
(paymentMethod === "Boleto" && (transaction?.isSettled ?? false)
|
||||
? getTodayDateString()
|
||||
: "");
|
||||
|
||||
// Calcular o valor correto para importação de parcelados
|
||||
let amountValue = overrides?.defaultAmount ?? "";
|
||||
if (!amountValue && typeof lancamento?.amount === "number") {
|
||||
let baseAmount = Math.abs(lancamento.amount);
|
||||
if (!amountValue && typeof transaction?.amount === "number") {
|
||||
let baseAmount = Math.abs(transaction.amount);
|
||||
|
||||
// Se está importando e é parcelado, usar o valor total (parcela * quantidade)
|
||||
if (
|
||||
isImporting &&
|
||||
lancamento.condition === "Parcelado" &&
|
||||
lancamento.installmentCount
|
||||
transaction.condition === "Parcelado" &&
|
||||
transaction.installmentCount
|
||||
) {
|
||||
baseAmount = baseAmount * lancamento.installmentCount;
|
||||
baseAmount = baseAmount * transaction.installmentCount;
|
||||
}
|
||||
|
||||
amountValue = (Math.round(baseAmount * 100) / 100).toFixed(2);
|
||||
@@ -162,51 +162,51 @@ export function buildLancamentoInitialState(
|
||||
return {
|
||||
purchaseDate,
|
||||
period:
|
||||
lancamento?.period && /^\d{4}-\d{2}$/.test(lancamento.period)
|
||||
? lancamento.period
|
||||
transaction?.period && /^\d{4}-\d{2}$/.test(transaction.period)
|
||||
? transaction.period
|
||||
: fallbackPeriod,
|
||||
name: lancamento?.name ?? overrides?.defaultName ?? "",
|
||||
name: transaction?.name ?? overrides?.defaultName ?? "",
|
||||
transactionType:
|
||||
lancamento?.transactionType ??
|
||||
transaction?.transactionType ??
|
||||
overrides?.defaultTransactionType ??
|
||||
LANCAMENTO_TRANSACTION_TYPES[0],
|
||||
TRANSACTION_TYPES[0],
|
||||
amount: amountValue,
|
||||
condition: lancamento?.condition ?? LANCAMENTO_CONDITIONS[0],
|
||||
condition: transaction?.condition ?? TRANSACTION_CONDITIONS[0],
|
||||
paymentMethod,
|
||||
pagadorId: fallbackPagadorId ?? undefined,
|
||||
secondaryPagadorId: undefined,
|
||||
payerId: fallbackPayerId ?? undefined,
|
||||
secondaryPayerId: undefined,
|
||||
isSplit: false,
|
||||
splitType: "equal",
|
||||
primarySplitAmount: "",
|
||||
secondarySplitAmount: "",
|
||||
contaId:
|
||||
accountId:
|
||||
paymentMethod === "Cartão de crédito"
|
||||
? undefined
|
||||
: isImporting
|
||||
? undefined
|
||||
: (lancamento?.contaId ?? undefined),
|
||||
cartaoId:
|
||||
: (transaction?.accountId ?? undefined),
|
||||
cardId:
|
||||
paymentMethod === "Cartão de crédito"
|
||||
? isImporting
|
||||
? (overrides?.defaultCartaoId ?? undefined)
|
||||
: (lancamento?.cartaoId ?? overrides?.defaultCartaoId ?? undefined)
|
||||
? (overrides?.defaultCardId ?? undefined)
|
||||
: (transaction?.cardId ?? overrides?.defaultCardId ?? undefined)
|
||||
: undefined,
|
||||
categoriaId: isImporting
|
||||
categoryId: isImporting
|
||||
? undefined
|
||||
: (lancamento?.categoriaId ?? undefined),
|
||||
installmentCount: lancamento?.installmentCount
|
||||
? String(lancamento.installmentCount)
|
||||
: (transaction?.categoryId ?? undefined),
|
||||
installmentCount: transaction?.installmentCount
|
||||
? String(transaction.installmentCount)
|
||||
: "",
|
||||
recurrenceCount: lancamento?.recurrenceCount
|
||||
? String(lancamento.recurrenceCount)
|
||||
recurrenceCount: transaction?.recurrenceCount
|
||||
? String(transaction.recurrenceCount)
|
||||
: "",
|
||||
dueDate: lancamento?.dueDate ?? "",
|
||||
dueDate: transaction?.dueDate ?? "",
|
||||
boletoPaymentDate,
|
||||
note: lancamento?.note ?? "",
|
||||
note: transaction?.note ?? "",
|
||||
isSettled:
|
||||
paymentMethod === "Cartão de crédito"
|
||||
? null
|
||||
: (lancamento?.isSettled ?? true),
|
||||
: (transaction?.isSettled ?? true),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -248,12 +248,12 @@ export function calculateSplitAmounts(
|
||||
* This function encapsulates the business logic for field interdependencies
|
||||
*/
|
||||
export function applyFieldDependencies(
|
||||
key: keyof LancamentoFormState,
|
||||
value: LancamentoFormState[keyof LancamentoFormState],
|
||||
currentState: LancamentoFormState,
|
||||
key: keyof TransactionFormState,
|
||||
value: TransactionFormState[keyof TransactionFormState],
|
||||
currentState: TransactionFormState,
|
||||
cardInfo?: { closingDay: string | null; dueDay: string | null } | null,
|
||||
): Partial<LancamentoFormState> {
|
||||
const updates: Partial<LancamentoFormState> = {};
|
||||
): Partial<TransactionFormState> {
|
||||
const updates: Partial<TransactionFormState> = {};
|
||||
|
||||
// Auto-derive period from purchaseDate
|
||||
if (key === "purchaseDate" && typeof value === "string" && value) {
|
||||
@@ -276,11 +276,8 @@ export function applyFieldDependencies(
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-derive period when cartaoId changes (credit card selected)
|
||||
if (
|
||||
key === "cartaoId" &&
|
||||
currentState.paymentMethod === "Cartão de crédito"
|
||||
) {
|
||||
// Auto-derive period when cardId changes (credit card selected)
|
||||
if (key === "cardId" && currentState.paymentMethod === "Cartão de crédito") {
|
||||
if (typeof value === "string" && value && currentState.purchaseDate) {
|
||||
updates.period = deriveCreditCardPeriod(
|
||||
currentState.purchaseDate,
|
||||
@@ -303,10 +300,10 @@ export function applyFieldDependencies(
|
||||
// When payment method changes, adjust related fields
|
||||
if (key === "paymentMethod" && typeof value === "string") {
|
||||
if (value === "Cartão de crédito") {
|
||||
updates.contaId = undefined;
|
||||
updates.accountId = undefined;
|
||||
updates.isSettled = null;
|
||||
} else {
|
||||
updates.cartaoId = undefined;
|
||||
updates.cardId = undefined;
|
||||
updates.isSettled = currentState.isSettled ?? true;
|
||||
}
|
||||
|
||||
@@ -314,7 +311,7 @@ export function applyFieldDependencies(
|
||||
if (value === "Cartão de crédito") {
|
||||
if (
|
||||
currentState.purchaseDate &&
|
||||
currentState.cartaoId &&
|
||||
currentState.cardId &&
|
||||
cardInfo?.closingDay
|
||||
) {
|
||||
updates.period = deriveCreditCardPeriod(
|
||||
@@ -350,7 +347,7 @@ export function applyFieldDependencies(
|
||||
|
||||
// When split is disabled, clear secondary pagador and split fields
|
||||
if (key === "isSplit" && value === false) {
|
||||
updates.secondaryPagadorId = undefined;
|
||||
updates.secondaryPayerId = undefined;
|
||||
updates.splitType = "equal";
|
||||
updates.primarySplitAmount = "";
|
||||
updates.secondarySplitAmount = "";
|
||||
@@ -383,10 +380,10 @@ export function applyFieldDependencies(
|
||||
}
|
||||
|
||||
// When primary pagador changes, clear secondary if it matches
|
||||
if (key === "pagadorId" && typeof value === "string") {
|
||||
const secondaryValue = currentState.secondaryPagadorId;
|
||||
if (key === "payerId" && typeof value === "string") {
|
||||
const secondaryValue = currentState.secondaryPayerId;
|
||||
if (secondaryValue && secondaryValue === value) {
|
||||
updates.secondaryPagadorId = undefined;
|
||||
updates.secondaryPayerId = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import type { SQL } from "drizzle-orm";
|
||||
import { and, eq, ilike, isNotNull, or } from "drizzle-orm";
|
||||
import {
|
||||
cartoes,
|
||||
type categorias,
|
||||
contas,
|
||||
lancamentos,
|
||||
type pagadores,
|
||||
cards,
|
||||
type categories,
|
||||
financialAccounts,
|
||||
type payers,
|
||||
transactions,
|
||||
} from "@/db/schema";
|
||||
import type { SelectOption } from "@/features/transactions/components/types";
|
||||
import {
|
||||
LANCAMENTO_CONDITIONS,
|
||||
LANCAMENTO_PAYMENT_METHODS,
|
||||
LANCAMENTO_TRANSACTION_TYPES,
|
||||
PAYMENT_METHODS,
|
||||
TRANSACTION_CONDITIONS,
|
||||
TRANSACTION_TYPES,
|
||||
} from "@/features/transactions/constants";
|
||||
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants";
|
||||
import {
|
||||
PAGADOR_ROLE_ADMIN,
|
||||
PAGADOR_ROLE_TERCEIRO,
|
||||
PAYER_ROLE_ADMIN,
|
||||
PAYER_ROLE_THIRD_PARTY,
|
||||
} from "@/shared/lib/payers/constants";
|
||||
import { toDateOnlyString } from "@/shared/utils/date";
|
||||
|
||||
type PagadorRow = typeof pagadores.$inferSelect;
|
||||
type ContaRow = typeof contas.$inferSelect;
|
||||
type CartaoRow = typeof cartoes.$inferSelect;
|
||||
type CategoriaRow = typeof categorias.$inferSelect;
|
||||
type PayerRow = typeof payers.$inferSelect;
|
||||
type AccountRow = typeof financialAccounts.$inferSelect;
|
||||
type CardRow = typeof cards.$inferSelect;
|
||||
type CategoryRow = typeof categories.$inferSelect;
|
||||
|
||||
export type ResolvedSearchParams =
|
||||
| Record<string, string | string[] | undefined>
|
||||
| undefined;
|
||||
|
||||
export type LancamentoSearchFilters = {
|
||||
export type TransactionSearchFilters = {
|
||||
transactionFilter: string | null;
|
||||
conditionFilter: string | null;
|
||||
paymentFilter: string | null;
|
||||
pagadorFilter: string | null;
|
||||
categoriaFilter: string | null;
|
||||
contaCartaoFilter: string | null;
|
||||
payerFilter: string | null;
|
||||
categoryFilter: string | null;
|
||||
accountCardFilter: string | null;
|
||||
searchFilter: string | null;
|
||||
};
|
||||
|
||||
@@ -45,23 +45,23 @@ type BaseSluggedOption = {
|
||||
slug: string;
|
||||
};
|
||||
|
||||
type PagadorSluggedOption = BaseSluggedOption & {
|
||||
type PayerSluggedOption = BaseSluggedOption & {
|
||||
role: string | null;
|
||||
avatarUrl: string | null;
|
||||
};
|
||||
|
||||
type CategoriaSluggedOption = BaseSluggedOption & {
|
||||
type CategorySluggedOption = BaseSluggedOption & {
|
||||
type: string | null;
|
||||
icon: string | null;
|
||||
};
|
||||
|
||||
type ContaSluggedOption = BaseSluggedOption & {
|
||||
type AccountSluggedOption = BaseSluggedOption & {
|
||||
kind: "conta";
|
||||
logo: string | null;
|
||||
accountType: string | null;
|
||||
};
|
||||
|
||||
type CartaoSluggedOption = BaseSluggedOption & {
|
||||
type CardSluggedOption = BaseSluggedOption & {
|
||||
kind: "cartao";
|
||||
logo: string | null;
|
||||
closingDay: string | null;
|
||||
@@ -69,17 +69,17 @@ type CartaoSluggedOption = BaseSluggedOption & {
|
||||
};
|
||||
|
||||
export type SluggedFilters = {
|
||||
pagadorFiltersRaw: PagadorSluggedOption[];
|
||||
categoriaFiltersRaw: CategoriaSluggedOption[];
|
||||
contaFiltersRaw: ContaSluggedOption[];
|
||||
cartaoFiltersRaw: CartaoSluggedOption[];
|
||||
payerFiltersRaw: PayerSluggedOption[];
|
||||
categoryFiltersRaw: CategorySluggedOption[];
|
||||
accountFiltersRaw: AccountSluggedOption[];
|
||||
cardFiltersRaw: CardSluggedOption[];
|
||||
};
|
||||
|
||||
export type SlugMaps = {
|
||||
pagador: Map<string, string>;
|
||||
categoria: Map<string, string>;
|
||||
conta: Map<string, string>;
|
||||
cartao: Map<string, string>;
|
||||
payer: Map<string, string>;
|
||||
category: Map<string, string>;
|
||||
financialAccount: Map<string, string>;
|
||||
card: Map<string, string>;
|
||||
};
|
||||
|
||||
export type FilterOption = {
|
||||
@@ -87,20 +87,20 @@ export type FilterOption = {
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type ContaCartaoFilterOption = FilterOption & {
|
||||
export type AccountCardFilterOption = FilterOption & {
|
||||
kind: "conta" | "cartao";
|
||||
};
|
||||
|
||||
export type LancamentoOptionSets = {
|
||||
pagadorOptions: SelectOption[];
|
||||
splitPagadorOptions: SelectOption[];
|
||||
defaultPagadorId: string | null;
|
||||
contaOptions: SelectOption[];
|
||||
cartaoOptions: SelectOption[];
|
||||
categoriaOptions: SelectOption[];
|
||||
pagadorFilterOptions: FilterOption[];
|
||||
categoriaFilterOptions: FilterOption[];
|
||||
contaCartaoFilterOptions: ContaCartaoFilterOption[];
|
||||
export type TransactionOptionSets = {
|
||||
payerOptions: SelectOption[];
|
||||
splitPayerOptions: SelectOption[];
|
||||
defaultPayerId: string | null;
|
||||
accountOptions: SelectOption[];
|
||||
cardOptions: SelectOption[];
|
||||
categoryOptions: SelectOption[];
|
||||
payerFilterOptions: FilterOption[];
|
||||
categoryFilterOptions: FilterOption[];
|
||||
accountCardFilterOptions: AccountCardFilterOption[];
|
||||
};
|
||||
|
||||
export const getSingleParam = (
|
||||
@@ -114,15 +114,15 @@ export const getSingleParam = (
|
||||
return Array.isArray(value) ? (value[0] ?? null) : value;
|
||||
};
|
||||
|
||||
export const extractLancamentoSearchFilters = (
|
||||
export const extractTransactionSearchFilters = (
|
||||
params: ResolvedSearchParams,
|
||||
): LancamentoSearchFilters => ({
|
||||
transactionFilter: getSingleParam(params, "transacao"),
|
||||
conditionFilter: getSingleParam(params, "condicao"),
|
||||
paymentFilter: getSingleParam(params, "pagamento"),
|
||||
pagadorFilter: getSingleParam(params, "pagador"),
|
||||
categoriaFilter: getSingleParam(params, "categoria"),
|
||||
contaCartaoFilter: getSingleParam(params, "contaCartao"),
|
||||
): TransactionSearchFilters => ({
|
||||
transactionFilter: getSingleParam(params, "type"),
|
||||
conditionFilter: getSingleParam(params, "condition"),
|
||||
paymentFilter: getSingleParam(params, "payment"),
|
||||
payerFilter: getSingleParam(params, "payer"),
|
||||
categoryFilter: getSingleParam(params, "category"),
|
||||
accountCardFilter: getSingleParam(params, "accountCard"),
|
||||
searchFilter: getSingleParam(params, "q"),
|
||||
});
|
||||
|
||||
@@ -179,177 +179,178 @@ export const toOption = (
|
||||
});
|
||||
|
||||
export const buildSluggedFilters = ({
|
||||
pagadorRows,
|
||||
categoriaRows,
|
||||
contaRows,
|
||||
cartaoRows,
|
||||
payerRows,
|
||||
categoryRows,
|
||||
accountRows,
|
||||
cardRows,
|
||||
}: {
|
||||
pagadorRows: PagadorRow[];
|
||||
categoriaRows: CategoriaRow[];
|
||||
contaRows: ContaRow[];
|
||||
cartaoRows: CartaoRow[];
|
||||
payerRows: PayerRow[];
|
||||
categoryRows: CategoryRow[];
|
||||
accountRows: AccountRow[];
|
||||
cardRows: CardRow[];
|
||||
}): SluggedFilters => {
|
||||
const pagadorSlugger = createSlugGenerator();
|
||||
const categoriaSlugger = createSlugGenerator();
|
||||
const contaCartaoSlugger = createSlugGenerator();
|
||||
const payerSlugger = createSlugGenerator();
|
||||
const categorySlugger = createSlugGenerator();
|
||||
const accountCardSlugger = createSlugGenerator();
|
||||
|
||||
const pagadorFiltersRaw = pagadorRows.map((pagador) => {
|
||||
const label = normalizeLabel(pagador.name);
|
||||
const payerFiltersRaw = payerRows.map((payer) => {
|
||||
const label = normalizeLabel(payer.name);
|
||||
return {
|
||||
id: pagador.id,
|
||||
id: payer.id,
|
||||
label,
|
||||
slug: pagadorSlugger(label),
|
||||
role: pagador.role ?? null,
|
||||
avatarUrl: pagador.avatarUrl ?? null,
|
||||
slug: payerSlugger(label),
|
||||
role: payer.role ?? null,
|
||||
avatarUrl: payer.avatarUrl ?? null,
|
||||
};
|
||||
});
|
||||
|
||||
const categoriaFiltersRaw = categoriaRows.map((categoria) => {
|
||||
const label = normalizeLabel(categoria.name);
|
||||
const categoryFiltersRaw = categoryRows.map((category) => {
|
||||
const label = normalizeLabel(category.name);
|
||||
return {
|
||||
id: categoria.id,
|
||||
id: category.id,
|
||||
label,
|
||||
slug: categoriaSlugger(label),
|
||||
type: categoria.type ?? null,
|
||||
icon: categoria.icon ?? null,
|
||||
slug: categorySlugger(label),
|
||||
type: category.type ?? null,
|
||||
icon: category.icon ?? null,
|
||||
};
|
||||
});
|
||||
|
||||
const contaFiltersRaw = contaRows.map((conta) => {
|
||||
const label = normalizeLabel(conta.name);
|
||||
const accountFiltersRaw = accountRows.map((account) => {
|
||||
const label = normalizeLabel(account.name);
|
||||
return {
|
||||
id: conta.id,
|
||||
id: account.id,
|
||||
label,
|
||||
slug: contaCartaoSlugger(label),
|
||||
slug: accountCardSlugger(label),
|
||||
kind: "conta" as const,
|
||||
logo: conta.logo ?? null,
|
||||
accountType: conta.accountType ?? null,
|
||||
logo: account.logo ?? null,
|
||||
accountType: account.accountType ?? null,
|
||||
};
|
||||
});
|
||||
|
||||
const cartaoFiltersRaw = cartaoRows.map((cartao) => {
|
||||
const label = normalizeLabel(cartao.name);
|
||||
const cardFiltersRaw = cardRows.map((card) => {
|
||||
const label = normalizeLabel(card.name);
|
||||
return {
|
||||
id: cartao.id,
|
||||
id: card.id,
|
||||
label,
|
||||
slug: contaCartaoSlugger(label),
|
||||
slug: accountCardSlugger(label),
|
||||
kind: "cartao" as const,
|
||||
logo: cartao.logo ?? null,
|
||||
closingDay: cartao.closingDay ?? null,
|
||||
dueDay: cartao.dueDay ?? null,
|
||||
logo: card.logo ?? null,
|
||||
closingDay: card.closingDay ?? null,
|
||||
dueDay: card.dueDay ?? null,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
pagadorFiltersRaw,
|
||||
categoriaFiltersRaw,
|
||||
contaFiltersRaw,
|
||||
cartaoFiltersRaw,
|
||||
payerFiltersRaw,
|
||||
categoryFiltersRaw,
|
||||
accountFiltersRaw,
|
||||
cardFiltersRaw,
|
||||
};
|
||||
};
|
||||
|
||||
export const buildSlugMaps = ({
|
||||
pagadorFiltersRaw,
|
||||
categoriaFiltersRaw,
|
||||
contaFiltersRaw,
|
||||
cartaoFiltersRaw,
|
||||
payerFiltersRaw,
|
||||
categoryFiltersRaw,
|
||||
accountFiltersRaw,
|
||||
cardFiltersRaw,
|
||||
}: SluggedFilters): SlugMaps => ({
|
||||
pagador: new Map(pagadorFiltersRaw.map(({ slug, id }) => [slug, id])),
|
||||
categoria: new Map(categoriaFiltersRaw.map(({ slug, id }) => [slug, id])),
|
||||
conta: new Map(contaFiltersRaw.map(({ slug, id }) => [slug, id])),
|
||||
cartao: new Map(cartaoFiltersRaw.map(({ slug, id }) => [slug, id])),
|
||||
payer: new Map(payerFiltersRaw.map(({ slug, id }) => [slug, id])),
|
||||
category: new Map(categoryFiltersRaw.map(({ slug, id }) => [slug, id])),
|
||||
financialAccount: new Map(
|
||||
accountFiltersRaw.map(({ slug, id }) => [slug, id]),
|
||||
),
|
||||
card: new Map(cardFiltersRaw.map(({ slug, id }) => [slug, id])),
|
||||
});
|
||||
|
||||
const isValidTransaction = (
|
||||
value: string | null,
|
||||
): value is (typeof LANCAMENTO_TRANSACTION_TYPES)[number] =>
|
||||
!!value &&
|
||||
(LANCAMENTO_TRANSACTION_TYPES as readonly string[]).includes(value ?? "");
|
||||
): value is (typeof TRANSACTION_TYPES)[number] =>
|
||||
!!value && (TRANSACTION_TYPES as readonly string[]).includes(value ?? "");
|
||||
|
||||
const isValidCondition = (
|
||||
value: string | null,
|
||||
): value is (typeof LANCAMENTO_CONDITIONS)[number] =>
|
||||
!!value && (LANCAMENTO_CONDITIONS as readonly string[]).includes(value ?? "");
|
||||
): value is (typeof TRANSACTION_CONDITIONS)[number] =>
|
||||
!!value &&
|
||||
(TRANSACTION_CONDITIONS as readonly string[]).includes(value ?? "");
|
||||
|
||||
const isValidPaymentMethod = (
|
||||
value: string | null,
|
||||
): value is (typeof LANCAMENTO_PAYMENT_METHODS)[number] =>
|
||||
!!value &&
|
||||
(LANCAMENTO_PAYMENT_METHODS as readonly string[]).includes(value ?? "");
|
||||
): value is (typeof PAYMENT_METHODS)[number] =>
|
||||
!!value && (PAYMENT_METHODS as readonly string[]).includes(value ?? "");
|
||||
|
||||
const buildSearchPattern = (value: string | null) =>
|
||||
value ? `%${value.trim().replace(/\s+/g, "%")}%` : null;
|
||||
|
||||
export const buildLancamentoWhere = ({
|
||||
export const buildTransactionWhere = ({
|
||||
userId,
|
||||
period,
|
||||
filters,
|
||||
slugMaps,
|
||||
cardId,
|
||||
accountId,
|
||||
pagadorId,
|
||||
payerId,
|
||||
}: {
|
||||
userId: string;
|
||||
period: string;
|
||||
filters: LancamentoSearchFilters;
|
||||
filters: TransactionSearchFilters;
|
||||
slugMaps: SlugMaps;
|
||||
cardId?: string;
|
||||
accountId?: string;
|
||||
pagadorId?: string;
|
||||
payerId?: string;
|
||||
}): SQL[] => {
|
||||
const where: SQL[] = [
|
||||
eq(lancamentos.userId, userId),
|
||||
eq(lancamentos.period, period),
|
||||
eq(transactions.userId, userId),
|
||||
eq(transactions.period, period),
|
||||
];
|
||||
|
||||
if (pagadorId) {
|
||||
where.push(eq(lancamentos.pagadorId, pagadorId));
|
||||
if (payerId) {
|
||||
where.push(eq(transactions.payerId, payerId));
|
||||
}
|
||||
|
||||
if (cardId) {
|
||||
where.push(eq(lancamentos.cartaoId, cardId));
|
||||
where.push(eq(transactions.cardId, cardId));
|
||||
}
|
||||
|
||||
if (accountId) {
|
||||
where.push(eq(lancamentos.contaId, accountId));
|
||||
where.push(eq(transactions.accountId, accountId));
|
||||
}
|
||||
|
||||
if (isValidTransaction(filters.transactionFilter)) {
|
||||
where.push(eq(lancamentos.transactionType, filters.transactionFilter));
|
||||
where.push(eq(transactions.transactionType, filters.transactionFilter));
|
||||
}
|
||||
|
||||
if (isValidCondition(filters.conditionFilter)) {
|
||||
where.push(eq(lancamentos.condition, filters.conditionFilter));
|
||||
where.push(eq(transactions.condition, filters.conditionFilter));
|
||||
}
|
||||
|
||||
if (isValidPaymentMethod(filters.paymentFilter)) {
|
||||
where.push(eq(lancamentos.paymentMethod, filters.paymentFilter));
|
||||
where.push(eq(transactions.paymentMethod, filters.paymentFilter));
|
||||
}
|
||||
|
||||
if (!pagadorId && filters.pagadorFilter) {
|
||||
const id = slugMaps.pagador.get(filters.pagadorFilter);
|
||||
if (!payerId && filters.payerFilter) {
|
||||
const id = slugMaps.payer.get(filters.payerFilter);
|
||||
if (id) {
|
||||
where.push(eq(lancamentos.pagadorId, id));
|
||||
where.push(eq(transactions.payerId, id));
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.categoriaFilter) {
|
||||
const id = slugMaps.categoria.get(filters.categoriaFilter);
|
||||
if (filters.categoryFilter) {
|
||||
const id = slugMaps.category.get(filters.categoryFilter);
|
||||
if (id) {
|
||||
where.push(eq(lancamentos.categoriaId, id));
|
||||
where.push(eq(transactions.categoryId, id));
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.contaCartaoFilter) {
|
||||
const contaId = slugMaps.conta.get(filters.contaCartaoFilter);
|
||||
const relatedCartaoId = contaId
|
||||
if (filters.accountCardFilter) {
|
||||
const accountId = slugMaps.financialAccount.get(filters.accountCardFilter);
|
||||
const relatedCardId = accountId
|
||||
? null
|
||||
: slugMaps.cartao.get(filters.contaCartaoFilter);
|
||||
if (contaId) {
|
||||
where.push(eq(lancamentos.contaId, contaId));
|
||||
: slugMaps.card.get(filters.accountCardFilter);
|
||||
if (accountId) {
|
||||
where.push(eq(transactions.accountId, accountId));
|
||||
}
|
||||
if (!contaId && relatedCartaoId) {
|
||||
where.push(eq(lancamentos.cartaoId, relatedCartaoId));
|
||||
if (!accountId && relatedCardId) {
|
||||
where.push(eq(transactions.cardId, relatedCardId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,12 +358,15 @@ export const buildLancamentoWhere = ({
|
||||
if (searchPattern) {
|
||||
where.push(
|
||||
or(
|
||||
ilike(lancamentos.name, searchPattern),
|
||||
ilike(lancamentos.note, searchPattern),
|
||||
ilike(lancamentos.paymentMethod, searchPattern),
|
||||
ilike(lancamentos.condition, searchPattern),
|
||||
and(isNotNull(contas.name), ilike(contas.name, searchPattern)),
|
||||
and(isNotNull(cartoes.name), ilike(cartoes.name, searchPattern)),
|
||||
ilike(transactions.name, searchPattern),
|
||||
ilike(transactions.note, searchPattern),
|
||||
ilike(transactions.paymentMethod, searchPattern),
|
||||
ilike(transactions.condition, searchPattern),
|
||||
and(
|
||||
isNotNull(financialAccounts.name),
|
||||
ilike(financialAccounts.name, searchPattern),
|
||||
),
|
||||
and(isNotNull(cards.name), ilike(cards.name, searchPattern)),
|
||||
) as SQL,
|
||||
);
|
||||
}
|
||||
@@ -370,38 +374,38 @@ export const buildLancamentoWhere = ({
|
||||
return where;
|
||||
};
|
||||
|
||||
type LancamentoRowWithRelations = typeof lancamentos.$inferSelect & {
|
||||
pagador?: PagadorRow | null;
|
||||
conta?: ContaRow | null;
|
||||
cartao?: CartaoRow | null;
|
||||
categoria?: CategoriaRow | null;
|
||||
type TransactionRowWithRelations = Partial<typeof transactions.$inferSelect> & {
|
||||
payer?: PayerRow | null;
|
||||
financialAccount?: AccountRow | null;
|
||||
card?: CardRow | null;
|
||||
category?: CategoryRow | null;
|
||||
};
|
||||
|
||||
export const mapLancamentosData = (rows: LancamentoRowWithRelations[]) =>
|
||||
export const mapTransactionsData = (rows: TransactionRowWithRelations[]) =>
|
||||
rows.map((item) => ({
|
||||
id: item.id,
|
||||
userId: item.userId,
|
||||
name: item.name,
|
||||
id: item.id ?? "",
|
||||
userId: item.userId ?? "",
|
||||
name: item.name ?? "",
|
||||
purchaseDate: toDateOnlyString(item.purchaseDate) ?? "",
|
||||
period: item.period ?? "",
|
||||
transactionType: item.transactionType,
|
||||
transactionType: item.transactionType ?? "",
|
||||
amount: Number(item.amount ?? 0),
|
||||
condition: item.condition,
|
||||
paymentMethod: item.paymentMethod,
|
||||
pagadorId: item.pagadorId ?? null,
|
||||
pagadorName: item.pagador?.name ?? null,
|
||||
pagadorAvatar: item.pagador?.avatarUrl ?? null,
|
||||
pagadorRole: item.pagador?.role ?? null,
|
||||
contaId: item.contaId ?? null,
|
||||
contaName: item.conta?.name ?? null,
|
||||
contaLogo: item.conta?.logo ?? null,
|
||||
cartaoId: item.cartaoId ?? null,
|
||||
cartaoName: item.cartao?.name ?? null,
|
||||
cartaoLogo: item.cartao?.logo ?? null,
|
||||
categoriaId: item.categoriaId ?? null,
|
||||
categoriaName: item.categoria?.name ?? null,
|
||||
categoriaType: item.categoria?.type ?? null,
|
||||
categoriaIcon: item.categoria?.icon ?? null,
|
||||
condition: item.condition ?? "",
|
||||
paymentMethod: item.paymentMethod ?? "",
|
||||
payerId: item.payerId ?? null,
|
||||
pagadorName: item.payer?.name ?? null,
|
||||
pagadorAvatar: item.payer?.avatarUrl ?? null,
|
||||
pagadorRole: item.payer?.role ?? null,
|
||||
accountId: item.accountId ?? null,
|
||||
contaName: item.financialAccount?.name ?? null,
|
||||
contaLogo: item.financialAccount?.logo ?? null,
|
||||
cardId: item.cardId ?? null,
|
||||
cartaoName: item.card?.name ?? null,
|
||||
cartaoLogo: item.card?.logo ?? null,
|
||||
categoryId: item.categoryId ?? null,
|
||||
categoriaName: item.category?.name ?? null,
|
||||
categoriaType: item.category?.type ?? null,
|
||||
categoriaIcon: item.category?.icon ?? null,
|
||||
installmentCount: item.installmentCount ?? null,
|
||||
recurrenceCount: item.recurrenceCount ?? null,
|
||||
currentInstallment: item.currentInstallment ?? null,
|
||||
@@ -417,8 +421,8 @@ export const mapLancamentosData = (rows: LancamentoRowWithRelations[]) =>
|
||||
seriesId: item.seriesId ?? null,
|
||||
readonly:
|
||||
Boolean(item.note?.startsWith(ACCOUNT_AUTO_INVOICE_NOTE_PREFIX)) ||
|
||||
item.categoria?.name === "Saldo inicial" ||
|
||||
item.categoria?.name === "Pagamentos",
|
||||
item.category?.name === "Saldo inicial" ||
|
||||
item.category?.name === "Pagamentos",
|
||||
}));
|
||||
|
||||
const sortByLabel = <T extends { label: string }>(items: T[]) =>
|
||||
@@ -427,45 +431,44 @@ const sortByLabel = <T extends { label: string }>(items: T[]) =>
|
||||
);
|
||||
|
||||
export const buildOptionSets = ({
|
||||
pagadorFiltersRaw,
|
||||
categoriaFiltersRaw,
|
||||
contaFiltersRaw,
|
||||
cartaoFiltersRaw,
|
||||
pagadorRows,
|
||||
payerFiltersRaw,
|
||||
categoryFiltersRaw,
|
||||
accountFiltersRaw,
|
||||
cardFiltersRaw,
|
||||
payerRows,
|
||||
limitCartaoId,
|
||||
limitContaId,
|
||||
}: SluggedFilters & {
|
||||
pagadorRows: PagadorRow[];
|
||||
payerRows: PayerRow[];
|
||||
limitCartaoId?: string;
|
||||
limitContaId?: string;
|
||||
}): LancamentoOptionSets => {
|
||||
const pagadorOptions = sortByLabel(
|
||||
pagadorFiltersRaw.map(({ id, label, role, slug, avatarUrl }) =>
|
||||
}): TransactionOptionSets => {
|
||||
const payerOptions = sortByLabel(
|
||||
payerFiltersRaw.map(({ id, label, role, slug, avatarUrl }) =>
|
||||
toOption(id, label, role, undefined, slug, avatarUrl),
|
||||
),
|
||||
);
|
||||
|
||||
const pagadorFilterOptions = sortByLabel(
|
||||
pagadorFiltersRaw.map(({ slug, label, avatarUrl }) => ({
|
||||
const payerFilterOptions = sortByLabel(
|
||||
payerFiltersRaw.map(({ slug, label, avatarUrl }) => ({
|
||||
slug,
|
||||
label,
|
||||
avatarUrl,
|
||||
})),
|
||||
);
|
||||
|
||||
const defaultPagadorId =
|
||||
pagadorRows.find((pagador) => pagador.role === PAGADOR_ROLE_ADMIN)?.id ??
|
||||
null;
|
||||
const defaultPayerId =
|
||||
payerRows.find((payer) => payer.role === PAYER_ROLE_ADMIN)?.id ?? null;
|
||||
|
||||
const splitPagadorOptions = pagadorOptions.filter(
|
||||
(option) => option.role === PAGADOR_ROLE_TERCEIRO,
|
||||
const splitPayerOptions = payerOptions.filter(
|
||||
(option) => option.role === PAYER_ROLE_THIRD_PARTY,
|
||||
);
|
||||
|
||||
const contaOptionsSource = limitContaId
|
||||
? contaFiltersRaw.filter((conta) => conta.id === limitContaId)
|
||||
: contaFiltersRaw;
|
||||
? accountFiltersRaw.filter((conta) => conta.id === limitContaId)
|
||||
: accountFiltersRaw;
|
||||
|
||||
const contaOptions = sortByLabel(
|
||||
const accountOptions = sortByLabel(
|
||||
contaOptionsSource.map(({ id, label, slug, logo, accountType }) =>
|
||||
toOption(
|
||||
id,
|
||||
@@ -482,10 +485,10 @@ export const buildOptionSets = ({
|
||||
);
|
||||
|
||||
const cartaoOptionsSource = limitCartaoId
|
||||
? cartaoFiltersRaw.filter((cartao) => cartao.id === limitCartaoId)
|
||||
: cartaoFiltersRaw;
|
||||
? cardFiltersRaw.filter((cartao) => cartao.id === limitCartaoId)
|
||||
: cardFiltersRaw;
|
||||
|
||||
const cartaoOptions = sortByLabel(
|
||||
const cardOptions = sortByLabel(
|
||||
cartaoOptionsSource.map(({ id, label, slug, logo, closingDay, dueDay }) =>
|
||||
toOption(
|
||||
id,
|
||||
@@ -503,18 +506,18 @@ export const buildOptionSets = ({
|
||||
),
|
||||
);
|
||||
|
||||
const categoriaOptions = sortByLabel(
|
||||
categoriaFiltersRaw.map(({ id, label, type, slug, icon }) =>
|
||||
const categoryOptions = sortByLabel(
|
||||
categoryFiltersRaw.map(({ id, label, type, slug, icon }) =>
|
||||
toOption(id, label, undefined, type, slug, undefined, undefined, icon),
|
||||
),
|
||||
);
|
||||
|
||||
const categoriaFilterOptions = sortByLabel(
|
||||
categoriaFiltersRaw.map(({ slug, label, icon }) => ({ slug, label, icon })),
|
||||
const categoryFilterOptions = sortByLabel(
|
||||
categoryFiltersRaw.map(({ slug, label, icon }) => ({ slug, label, icon })),
|
||||
);
|
||||
|
||||
const contaCartaoFilterOptions = sortByLabel(
|
||||
[...contaFiltersRaw, ...cartaoFiltersRaw]
|
||||
const accountCardFilterOptions = sortByLabel(
|
||||
[...accountFiltersRaw, ...cardFiltersRaw]
|
||||
.filter(
|
||||
(option) =>
|
||||
(limitCartaoId && option.kind === "cartao"
|
||||
@@ -528,14 +531,14 @@ export const buildOptionSets = ({
|
||||
);
|
||||
|
||||
return {
|
||||
pagadorOptions,
|
||||
splitPagadorOptions,
|
||||
defaultPagadorId,
|
||||
contaOptions,
|
||||
cartaoOptions,
|
||||
categoriaOptions,
|
||||
pagadorFilterOptions,
|
||||
categoriaFilterOptions,
|
||||
contaCartaoFilterOptions,
|
||||
payerOptions,
|
||||
splitPayerOptions,
|
||||
defaultPayerId,
|
||||
accountOptions,
|
||||
cardOptions,
|
||||
categoryOptions,
|
||||
payerFilterOptions,
|
||||
categoryFilterOptions,
|
||||
accountCardFilterOptions,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,69 +1,73 @@
|
||||
import { and, desc, eq, gte, isNull, ne, or, type SQL } from "drizzle-orm";
|
||||
import {
|
||||
cartoes,
|
||||
categorias,
|
||||
contas,
|
||||
lancamentos,
|
||||
pagadores,
|
||||
cards,
|
||||
categories,
|
||||
financialAccounts,
|
||||
payers,
|
||||
transactions,
|
||||
} from "@/db/schema";
|
||||
import { INITIAL_BALANCE_NOTE } from "@/shared/lib/accounts/constants";
|
||||
import { db } from "@/shared/lib/db";
|
||||
|
||||
export async function fetchLancamentoFilterSources(userId: string) {
|
||||
const [pagadorRows, contaRows, cartaoRows, categoriaRows] = await Promise.all(
|
||||
[
|
||||
db.query.pagadores.findMany({
|
||||
where: eq(pagadores.userId, userId),
|
||||
}),
|
||||
db.query.contas.findMany({
|
||||
where: and(eq(contas.userId, userId), eq(contas.status, "Ativa")),
|
||||
}),
|
||||
db.query.cartoes.findMany({
|
||||
where: and(eq(cartoes.userId, userId), eq(cartoes.status, "Ativo")),
|
||||
}),
|
||||
db.query.categorias.findMany({
|
||||
where: eq(categorias.userId, userId),
|
||||
}),
|
||||
],
|
||||
);
|
||||
export async function fetchTransactionFilterSources(userId: string) {
|
||||
const [payerRows, accountRows, cardRows, categoryRows] = await Promise.all([
|
||||
db.query.payers.findMany({
|
||||
where: eq(payers.userId, userId),
|
||||
}),
|
||||
db.query.financialAccounts.findMany({
|
||||
where: and(
|
||||
eq(financialAccounts.userId, userId),
|
||||
eq(financialAccounts.status, "Ativa"),
|
||||
),
|
||||
}),
|
||||
db.query.cards.findMany({
|
||||
where: and(eq(cards.userId, userId), eq(cards.status, "Ativo")),
|
||||
}),
|
||||
db.query.categories.findMany({
|
||||
where: eq(categories.userId, userId),
|
||||
}),
|
||||
]);
|
||||
|
||||
return { pagadorRows, contaRows, cartaoRows, categoriaRows };
|
||||
return { payerRows, accountRows, cardRows, categoryRows };
|
||||
}
|
||||
|
||||
export async function fetchLancamentos(filters: SQL[]) {
|
||||
const lancamentoRows = await db
|
||||
export async function fetchTransactions(filters: SQL[]) {
|
||||
const transactionRows = await db
|
||||
.select({
|
||||
lancamento: lancamentos,
|
||||
pagador: pagadores,
|
||||
conta: contas,
|
||||
cartao: cartoes,
|
||||
categoria: categorias,
|
||||
transaction: transactions,
|
||||
payer: payers,
|
||||
financialAccount: financialAccounts,
|
||||
card: cards,
|
||||
category: categories,
|
||||
})
|
||||
.from(lancamentos)
|
||||
.leftJoin(pagadores, eq(lancamentos.pagadorId, pagadores.id))
|
||||
.leftJoin(contas, eq(lancamentos.contaId, contas.id))
|
||||
.leftJoin(cartoes, eq(lancamentos.cartaoId, cartoes.id))
|
||||
.leftJoin(categorias, eq(lancamentos.categoriaId, categorias.id))
|
||||
.from(transactions)
|
||||
.leftJoin(payers, eq(transactions.payerId, payers.id))
|
||||
.leftJoin(
|
||||
financialAccounts,
|
||||
eq(transactions.accountId, financialAccounts.id),
|
||||
)
|
||||
.leftJoin(cards, eq(transactions.cardId, cards.id))
|
||||
.leftJoin(categories, eq(transactions.categoryId, categories.id))
|
||||
.where(
|
||||
and(
|
||||
...filters,
|
||||
// Excluir saldos iniciais de contas que têm excludeInitialBalanceFromIncome = true
|
||||
// Excluir saldos iniciais de financialAccounts que têm excludeInitialBalanceFromIncome = true
|
||||
or(
|
||||
ne(lancamentos.note, INITIAL_BALANCE_NOTE),
|
||||
isNull(contas.excludeInitialBalanceFromIncome),
|
||||
eq(contas.excludeInitialBalanceFromIncome, false),
|
||||
ne(transactions.note, INITIAL_BALANCE_NOTE),
|
||||
isNull(financialAccounts.excludeInitialBalanceFromIncome),
|
||||
eq(financialAccounts.excludeInitialBalanceFromIncome, false),
|
||||
),
|
||||
),
|
||||
)
|
||||
.orderBy(desc(lancamentos.purchaseDate), desc(lancamentos.createdAt));
|
||||
.orderBy(desc(transactions.purchaseDate), desc(transactions.createdAt));
|
||||
|
||||
// Transformar resultado para o formato esperado
|
||||
return lancamentoRows.map((row) => ({
|
||||
...row.lancamento,
|
||||
pagador: row.pagador,
|
||||
conta: row.conta,
|
||||
cartao: row.cartao,
|
||||
categoria: row.categoria,
|
||||
return transactionRows.map((row) => ({
|
||||
...row.transaction,
|
||||
payer: row.payer,
|
||||
financialAccount: row.financialAccount,
|
||||
card: row.card,
|
||||
category: row.category,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -74,15 +78,15 @@ export async function fetchRecentEstablishments(
|
||||
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3);
|
||||
|
||||
const results = await db
|
||||
.select({ name: lancamentos.name })
|
||||
.from(lancamentos)
|
||||
.select({ name: transactions.name })
|
||||
.from(transactions)
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, userId),
|
||||
gte(lancamentos.purchaseDate, threeMonthsAgo),
|
||||
eq(transactions.userId, userId),
|
||||
gte(transactions.purchaseDate, threeMonthsAgo),
|
||||
),
|
||||
)
|
||||
.orderBy(desc(lancamentos.purchaseDate));
|
||||
.orderBy(desc(transactions.purchaseDate));
|
||||
|
||||
const uniqueNames = Array.from(
|
||||
new Set<string>(
|
||||
|
||||
Reference in New Issue
Block a user