forked from git.gladyson/openmonetis
refactor: migrate from ESLint to Biome and extract SQL queries to data.ts
- Replace ESLint with Biome for linting and formatting - Configure Biome with tabs, double quotes, and organized imports - Move all SQL/Drizzle queries from page.tsx files to data.ts files - Create new data.ts files for: ajustes, dashboard, relatorios/categorias - Update existing data.ts files: extrato, fatura (add lancamentos queries) - Remove all drizzle-orm imports from page.tsx files - Update README.md with new tooling info Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,336 +1,344 @@
|
||||
"use server";
|
||||
|
||||
import {
|
||||
categorias,
|
||||
installmentAnticipations,
|
||||
lancamentos,
|
||||
pagadores,
|
||||
type InstallmentAnticipation,
|
||||
type Lancamento,
|
||||
} from "@/db/schema";
|
||||
import { handleActionError } from "@/lib/actions/helpers";
|
||||
import type { ActionResult } from "@/lib/actions/types";
|
||||
import { db } from "@/lib/db";
|
||||
import { getUser } from "@/lib/auth/server";
|
||||
import {
|
||||
generateAnticipationDescription,
|
||||
generateAnticipationNote,
|
||||
} from "@/lib/installments/anticipation-helpers";
|
||||
import type {
|
||||
CancelAnticipationInput,
|
||||
CreateAnticipationInput,
|
||||
EligibleInstallment,
|
||||
InstallmentAnticipationWithRelations,
|
||||
} from "@/lib/installments/anticipation-types";
|
||||
import { uuidSchema } from "@/lib/schemas/common";
|
||||
import { formatDecimalForDbRequired } from "@/lib/utils/currency";
|
||||
import { and, asc, desc, eq, inArray, isNull, or } from "drizzle-orm";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
categorias,
|
||||
installmentAnticipations,
|
||||
lancamentos,
|
||||
pagadores,
|
||||
} from "@/db/schema";
|
||||
import { handleActionError } from "@/lib/actions/helpers";
|
||||
import type { ActionResult } from "@/lib/actions/types";
|
||||
import { getUser } from "@/lib/auth/server";
|
||||
import { db } from "@/lib/db";
|
||||
import {
|
||||
generateAnticipationDescription,
|
||||
generateAnticipationNote,
|
||||
} from "@/lib/installments/anticipation-helpers";
|
||||
import type {
|
||||
CancelAnticipationInput,
|
||||
CreateAnticipationInput,
|
||||
EligibleInstallment,
|
||||
InstallmentAnticipationWithRelations,
|
||||
} from "@/lib/installments/anticipation-types";
|
||||
import { uuidSchema } from "@/lib/schemas/common";
|
||||
import { formatDecimalForDbRequired } from "@/lib/utils/currency";
|
||||
|
||||
/**
|
||||
* Schema de validação para criar antecipação
|
||||
*/
|
||||
const createAnticipationSchema = z.object({
|
||||
seriesId: uuidSchema("Série"),
|
||||
installmentIds: z
|
||||
.array(uuidSchema("Parcela"))
|
||||
.min(1, "Selecione pelo menos uma parcela para antecipar."),
|
||||
anticipationPeriod: z
|
||||
.string()
|
||||
.trim()
|
||||
.regex(/^(\d{4})-(\d{2})$/, {
|
||||
message: "Selecione um período válido.",
|
||||
}),
|
||||
discount: z.coerce
|
||||
.number()
|
||||
.min(0, "Informe um desconto maior ou igual a zero.")
|
||||
.optional()
|
||||
.default(0),
|
||||
pagadorId: uuidSchema("Pagador").optional(),
|
||||
categoriaId: uuidSchema("Categoria").optional(),
|
||||
note: z.string().trim().optional(),
|
||||
seriesId: uuidSchema("Série"),
|
||||
installmentIds: z
|
||||
.array(uuidSchema("Parcela"))
|
||||
.min(1, "Selecione pelo menos uma parcela para antecipar."),
|
||||
anticipationPeriod: z
|
||||
.string()
|
||||
.trim()
|
||||
.regex(/^(\d{4})-(\d{2})$/, {
|
||||
message: "Selecione um período válido.",
|
||||
}),
|
||||
discount: z.coerce
|
||||
.number()
|
||||
.min(0, "Informe um desconto maior ou igual a zero.")
|
||||
.optional()
|
||||
.default(0),
|
||||
pagadorId: uuidSchema("Pagador").optional(),
|
||||
categoriaId: uuidSchema("Categoria").optional(),
|
||||
note: z.string().trim().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Schema de validação para cancelar antecipação
|
||||
*/
|
||||
const cancelAnticipationSchema = z.object({
|
||||
anticipationId: uuidSchema("Antecipação"),
|
||||
anticipationId: uuidSchema("Antecipação"),
|
||||
});
|
||||
|
||||
/**
|
||||
* Busca parcelas elegíveis para antecipação de uma série
|
||||
*/
|
||||
export async function getEligibleInstallmentsAction(
|
||||
seriesId: string
|
||||
seriesId: string,
|
||||
): Promise<ActionResult<EligibleInstallment[]>> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
try {
|
||||
const user = await getUser();
|
||||
|
||||
// Validar seriesId
|
||||
const validatedSeriesId = uuidSchema("Série").parse(seriesId);
|
||||
// Validar seriesId
|
||||
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({
|
||||
where: and(
|
||||
eq(lancamentos.seriesId, validatedSeriesId),
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.condition, "Parcelado"),
|
||||
// Apenas parcelas não pagas e não antecipadas
|
||||
or(eq(lancamentos.isSettled, false), isNull(lancamentos.isSettled)),
|
||||
eq(lancamentos.isAnticipated, false)
|
||||
),
|
||||
orderBy: [asc(lancamentos.currentInstallment)],
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
amount: true,
|
||||
period: true,
|
||||
purchaseDate: true,
|
||||
dueDate: true,
|
||||
currentInstallment: true,
|
||||
installmentCount: true,
|
||||
paymentMethod: true,
|
||||
categoriaId: true,
|
||||
pagadorId: true,
|
||||
},
|
||||
});
|
||||
// Buscar todas as parcelas da série que estão elegíveis
|
||||
const rows = await db.query.lancamentos.findMany({
|
||||
where: and(
|
||||
eq(lancamentos.seriesId, validatedSeriesId),
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.condition, "Parcelado"),
|
||||
// Apenas parcelas não pagas e não antecipadas
|
||||
or(eq(lancamentos.isSettled, false), isNull(lancamentos.isSettled)),
|
||||
eq(lancamentos.isAnticipated, false),
|
||||
),
|
||||
orderBy: [asc(lancamentos.currentInstallment)],
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
amount: true,
|
||||
period: true,
|
||||
purchaseDate: true,
|
||||
dueDate: true,
|
||||
currentInstallment: true,
|
||||
installmentCount: true,
|
||||
paymentMethod: true,
|
||||
categoriaId: true,
|
||||
pagadorId: true,
|
||||
},
|
||||
});
|
||||
|
||||
const eligibleInstallments: EligibleInstallment[] = rows.map((row) => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
amount: row.amount,
|
||||
period: row.period,
|
||||
purchaseDate: row.purchaseDate,
|
||||
dueDate: row.dueDate,
|
||||
currentInstallment: row.currentInstallment,
|
||||
installmentCount: row.installmentCount,
|
||||
paymentMethod: row.paymentMethod,
|
||||
categoriaId: row.categoriaId,
|
||||
pagadorId: row.pagadorId,
|
||||
}));
|
||||
const eligibleInstallments: EligibleInstallment[] = rows.map((row) => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
amount: row.amount,
|
||||
period: row.period,
|
||||
purchaseDate: row.purchaseDate,
|
||||
dueDate: row.dueDate,
|
||||
currentInstallment: row.currentInstallment,
|
||||
installmentCount: row.installmentCount,
|
||||
paymentMethod: row.paymentMethod,
|
||||
categoriaId: row.categoriaId,
|
||||
pagadorId: row.pagadorId,
|
||||
}));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: eligibleInstallments,
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: eligibleInstallments,
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cria uma antecipação de parcelas
|
||||
*/
|
||||
export async function createInstallmentAnticipationAction(
|
||||
input: CreateAnticipationInput
|
||||
input: CreateAnticipationInput,
|
||||
): Promise<ActionResult> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = createAnticipationSchema.parse(input);
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = createAnticipationSchema.parse(input);
|
||||
|
||||
// 1. Validar parcelas selecionadas
|
||||
const installments = await db.query.lancamentos.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)
|
||||
),
|
||||
});
|
||||
// 1. Validar parcelas selecionadas
|
||||
const installments = await db.query.lancamentos.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),
|
||||
),
|
||||
});
|
||||
|
||||
if (installments.length !== data.installmentIds.length) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Algumas parcelas não estão elegíveis para antecipação.",
|
||||
};
|
||||
}
|
||||
if (installments.length !== data.installmentIds.length) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Algumas parcelas não estão elegíveis para antecipação.",
|
||||
};
|
||||
}
|
||||
|
||||
if (installments.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Nenhuma parcela selecionada para antecipação.",
|
||||
};
|
||||
}
|
||||
if (installments.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Nenhuma parcela selecionada para antecipação.",
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Calcular valor total
|
||||
const totalAmountCents = installments.reduce(
|
||||
(sum, inst) => sum + Number(inst.amount) * 100,
|
||||
0
|
||||
);
|
||||
const totalAmount = totalAmountCents / 100;
|
||||
const totalAmountAbs = Math.abs(totalAmount);
|
||||
// 2. Calcular valor total
|
||||
const totalAmountCents = installments.reduce(
|
||||
(sum, inst) => sum + Number(inst.amount) * 100,
|
||||
0,
|
||||
);
|
||||
const totalAmount = totalAmountCents / 100;
|
||||
const totalAmountAbs = Math.abs(totalAmount);
|
||||
|
||||
// 2.1. Aplicar desconto
|
||||
const discount = data.discount || 0;
|
||||
// 2.1. Aplicar desconto
|
||||
const discount = data.discount || 0;
|
||||
|
||||
// 2.2. Validar que o desconto não é maior que o valor absoluto total
|
||||
if (discount > totalAmountAbs) {
|
||||
return {
|
||||
success: false,
|
||||
error: "O desconto não pode ser maior que o valor total das parcelas.",
|
||||
};
|
||||
}
|
||||
// 2.2. Validar que o desconto não é maior que o valor absoluto total
|
||||
if (discount > totalAmountAbs) {
|
||||
return {
|
||||
success: false,
|
||||
error: "O desconto não pode ser maior que o valor total das parcelas.",
|
||||
};
|
||||
}
|
||||
|
||||
// 2.3. Calcular valor final (se negativo, soma o desconto para reduzir a despesa)
|
||||
const finalAmount = totalAmount < 0
|
||||
? totalAmount + discount // Despesa: -1000 + 20 = -980
|
||||
: totalAmount - discount; // Receita: 1000 - 20 = 980
|
||||
// 2.3. Calcular valor final (se negativo, soma o desconto para reduzir a despesa)
|
||||
const finalAmount =
|
||||
totalAmount < 0
|
||||
? totalAmount + discount // Despesa: -1000 + 20 = -980
|
||||
: totalAmount - discount; // Receita: 1000 - 20 = 980
|
||||
|
||||
// 3. Pegar dados da primeira parcela para referência
|
||||
const firstInstallment = installments[0]!;
|
||||
// 3. Pegar dados da primeira parcela para referência
|
||||
const firstInstallment = installments[0]!;
|
||||
|
||||
// 4. Criar lançamento e antecipação em transação
|
||||
await db.transaction(async (tx) => {
|
||||
// 4.1. Criar o lançamento de antecipação (com desconto aplicado)
|
||||
const [newLancamento] = await tx
|
||||
.insert(lancamentos)
|
||||
.values({
|
||||
name: generateAnticipationDescription(
|
||||
firstInstallment.name,
|
||||
installments.length
|
||||
),
|
||||
condition: "À vista",
|
||||
transactionType: firstInstallment.transactionType,
|
||||
paymentMethod: firstInstallment.paymentMethod,
|
||||
amount: formatDecimalForDbRequired(finalAmount),
|
||||
purchaseDate: new Date(),
|
||||
period: data.anticipationPeriod,
|
||||
dueDate: null,
|
||||
isSettled: false,
|
||||
pagadorId: data.pagadorId ?? firstInstallment.pagadorId,
|
||||
categoriaId: data.categoriaId ?? firstInstallment.categoriaId,
|
||||
cartaoId: firstInstallment.cartaoId,
|
||||
contaId: firstInstallment.contaId,
|
||||
note:
|
||||
data.note ||
|
||||
generateAnticipationNote(
|
||||
installments.map((inst) => ({
|
||||
id: inst.id,
|
||||
name: inst.name,
|
||||
amount: inst.amount,
|
||||
period: inst.period,
|
||||
purchaseDate: inst.purchaseDate,
|
||||
dueDate: inst.dueDate,
|
||||
currentInstallment: inst.currentInstallment,
|
||||
installmentCount: inst.installmentCount,
|
||||
paymentMethod: inst.paymentMethod,
|
||||
categoriaId: inst.categoriaId,
|
||||
pagadorId: inst.pagadorId,
|
||||
}))
|
||||
),
|
||||
userId: user.id,
|
||||
installmentCount: null,
|
||||
currentInstallment: null,
|
||||
recurrenceCount: null,
|
||||
isAnticipated: false,
|
||||
isDivided: false,
|
||||
seriesId: null,
|
||||
transferId: null,
|
||||
anticipationId: null,
|
||||
boletoPaymentDate: null,
|
||||
})
|
||||
.returning();
|
||||
// 4. Criar lançamento e antecipação em transação
|
||||
await db.transaction(async (tx) => {
|
||||
// 4.1. Criar o lançamento de antecipação (com desconto aplicado)
|
||||
const [newLancamento] = await tx
|
||||
.insert(lancamentos)
|
||||
.values({
|
||||
name: generateAnticipationDescription(
|
||||
firstInstallment.name,
|
||||
installments.length,
|
||||
),
|
||||
condition: "À vista",
|
||||
transactionType: firstInstallment.transactionType,
|
||||
paymentMethod: firstInstallment.paymentMethod,
|
||||
amount: formatDecimalForDbRequired(finalAmount),
|
||||
purchaseDate: new Date(),
|
||||
period: data.anticipationPeriod,
|
||||
dueDate: null,
|
||||
isSettled: false,
|
||||
pagadorId: data.pagadorId ?? firstInstallment.pagadorId,
|
||||
categoriaId: data.categoriaId ?? firstInstallment.categoriaId,
|
||||
cartaoId: firstInstallment.cartaoId,
|
||||
contaId: firstInstallment.contaId,
|
||||
note:
|
||||
data.note ||
|
||||
generateAnticipationNote(
|
||||
installments.map((inst) => ({
|
||||
id: inst.id,
|
||||
name: inst.name,
|
||||
amount: inst.amount,
|
||||
period: inst.period,
|
||||
purchaseDate: inst.purchaseDate,
|
||||
dueDate: inst.dueDate,
|
||||
currentInstallment: inst.currentInstallment,
|
||||
installmentCount: inst.installmentCount,
|
||||
paymentMethod: inst.paymentMethod,
|
||||
categoriaId: inst.categoriaId,
|
||||
pagadorId: inst.pagadorId,
|
||||
})),
|
||||
),
|
||||
userId: user.id,
|
||||
installmentCount: null,
|
||||
currentInstallment: null,
|
||||
recurrenceCount: null,
|
||||
isAnticipated: false,
|
||||
isDivided: false,
|
||||
seriesId: null,
|
||||
transferId: null,
|
||||
anticipationId: null,
|
||||
boletoPaymentDate: null,
|
||||
})
|
||||
.returning();
|
||||
|
||||
// 4.2. Criar registro de antecipação
|
||||
const [anticipation] = await tx
|
||||
.insert(installmentAnticipations)
|
||||
.values({
|
||||
seriesId: data.seriesId,
|
||||
anticipationPeriod: data.anticipationPeriod,
|
||||
anticipationDate: new Date(),
|
||||
anticipatedInstallmentIds: data.installmentIds,
|
||||
totalAmount: formatDecimalForDbRequired(totalAmount),
|
||||
installmentCount: installments.length,
|
||||
discount: formatDecimalForDbRequired(discount),
|
||||
lancamentoId: newLancamento.id,
|
||||
pagadorId: data.pagadorId ?? firstInstallment.pagadorId,
|
||||
categoriaId: data.categoriaId ?? firstInstallment.categoriaId,
|
||||
note: data.note || null,
|
||||
userId: user.id,
|
||||
})
|
||||
.returning();
|
||||
// 4.2. Criar registro de antecipação
|
||||
const [anticipation] = await tx
|
||||
.insert(installmentAnticipations)
|
||||
.values({
|
||||
seriesId: data.seriesId,
|
||||
anticipationPeriod: data.anticipationPeriod,
|
||||
anticipationDate: new Date(),
|
||||
anticipatedInstallmentIds: data.installmentIds,
|
||||
totalAmount: formatDecimalForDbRequired(totalAmount),
|
||||
installmentCount: installments.length,
|
||||
discount: formatDecimalForDbRequired(discount),
|
||||
lancamentoId: newLancamento.id,
|
||||
pagadorId: data.pagadorId ?? firstInstallment.pagadorId,
|
||||
categoriaId: data.categoriaId ?? firstInstallment.categoriaId,
|
||||
note: data.note || null,
|
||||
userId: user.id,
|
||||
})
|
||||
.returning();
|
||||
|
||||
// 4.3. Marcar parcelas como antecipadas e zerar seus valores
|
||||
await tx
|
||||
.update(lancamentos)
|
||||
.set({
|
||||
isAnticipated: true,
|
||||
anticipationId: anticipation.id,
|
||||
amount: "0", // Zera o valor para não contar em dobro
|
||||
})
|
||||
.where(inArray(lancamentos.id, data.installmentIds));
|
||||
});
|
||||
// 4.3. Marcar parcelas como antecipadas e zerar seus valores
|
||||
await tx
|
||||
.update(lancamentos)
|
||||
.set({
|
||||
isAnticipated: true,
|
||||
anticipationId: anticipation.id,
|
||||
amount: "0", // Zera o valor para não contar em dobro
|
||||
})
|
||||
.where(inArray(lancamentos.id, data.installmentIds));
|
||||
});
|
||||
|
||||
revalidatePath("/lancamentos");
|
||||
revalidatePath("/dashboard");
|
||||
revalidatePath("/lancamentos");
|
||||
revalidatePath("/dashboard");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `${installments.length} ${
|
||||
installments.length === 1 ? "parcela antecipada" : "parcelas antecipadas"
|
||||
} com sucesso!`,
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
message: `${installments.length} ${
|
||||
installments.length === 1
|
||||
? "parcela antecipada"
|
||||
: "parcelas antecipadas"
|
||||
} com sucesso!`,
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca histórico de antecipações de uma série
|
||||
*/
|
||||
export async function getInstallmentAnticipationsAction(
|
||||
seriesId: string
|
||||
seriesId: string,
|
||||
): Promise<ActionResult<InstallmentAnticipationWithRelations[]>> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
try {
|
||||
const user = await getUser();
|
||||
|
||||
// Validar seriesId
|
||||
const validatedSeriesId = uuidSchema("Série").parse(seriesId);
|
||||
// Validar seriesId
|
||||
const validatedSeriesId = uuidSchema("Série").parse(seriesId);
|
||||
|
||||
// Usar query builder ao invés de db.query para evitar problemas de tipagem
|
||||
const anticipations = await db
|
||||
.select({
|
||||
id: installmentAnticipations.id,
|
||||
seriesId: installmentAnticipations.seriesId,
|
||||
anticipationPeriod: installmentAnticipations.anticipationPeriod,
|
||||
anticipationDate: installmentAnticipations.anticipationDate,
|
||||
anticipatedInstallmentIds: installmentAnticipations.anticipatedInstallmentIds,
|
||||
totalAmount: installmentAnticipations.totalAmount,
|
||||
installmentCount: installmentAnticipations.installmentCount,
|
||||
discount: installmentAnticipations.discount,
|
||||
lancamentoId: installmentAnticipations.lancamentoId,
|
||||
pagadorId: installmentAnticipations.pagadorId,
|
||||
categoriaId: installmentAnticipations.categoriaId,
|
||||
note: installmentAnticipations.note,
|
||||
userId: installmentAnticipations.userId,
|
||||
createdAt: installmentAnticipations.createdAt,
|
||||
// Joins
|
||||
lancamento: lancamentos,
|
||||
pagador: pagadores,
|
||||
categoria: categorias,
|
||||
})
|
||||
.from(installmentAnticipations)
|
||||
.leftJoin(lancamentos, eq(installmentAnticipations.lancamentoId, lancamentos.id))
|
||||
.leftJoin(pagadores, eq(installmentAnticipations.pagadorId, pagadores.id))
|
||||
.leftJoin(categorias, eq(installmentAnticipations.categoriaId, categorias.id))
|
||||
.where(
|
||||
and(
|
||||
eq(installmentAnticipations.seriesId, validatedSeriesId),
|
||||
eq(installmentAnticipations.userId, user.id)
|
||||
)
|
||||
)
|
||||
.orderBy(desc(installmentAnticipations.createdAt));
|
||||
// Usar query builder ao invés de db.query para evitar problemas de tipagem
|
||||
const anticipations = await db
|
||||
.select({
|
||||
id: installmentAnticipations.id,
|
||||
seriesId: installmentAnticipations.seriesId,
|
||||
anticipationPeriod: installmentAnticipations.anticipationPeriod,
|
||||
anticipationDate: installmentAnticipations.anticipationDate,
|
||||
anticipatedInstallmentIds:
|
||||
installmentAnticipations.anticipatedInstallmentIds,
|
||||
totalAmount: installmentAnticipations.totalAmount,
|
||||
installmentCount: installmentAnticipations.installmentCount,
|
||||
discount: installmentAnticipations.discount,
|
||||
lancamentoId: installmentAnticipations.lancamentoId,
|
||||
pagadorId: installmentAnticipations.pagadorId,
|
||||
categoriaId: installmentAnticipations.categoriaId,
|
||||
note: installmentAnticipations.note,
|
||||
userId: installmentAnticipations.userId,
|
||||
createdAt: installmentAnticipations.createdAt,
|
||||
// Joins
|
||||
lancamento: lancamentos,
|
||||
pagador: pagadores,
|
||||
categoria: categorias,
|
||||
})
|
||||
.from(installmentAnticipations)
|
||||
.leftJoin(
|
||||
lancamentos,
|
||||
eq(installmentAnticipations.lancamentoId, lancamentos.id),
|
||||
)
|
||||
.leftJoin(pagadores, eq(installmentAnticipations.pagadorId, pagadores.id))
|
||||
.leftJoin(
|
||||
categorias,
|
||||
eq(installmentAnticipations.categoriaId, categorias.id),
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
eq(installmentAnticipations.seriesId, validatedSeriesId),
|
||||
eq(installmentAnticipations.userId, user.id),
|
||||
),
|
||||
)
|
||||
.orderBy(desc(installmentAnticipations.createdAt));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: anticipations,
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: anticipations,
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,134 +346,138 @@ export async function getInstallmentAnticipationsAction(
|
||||
* Remove o lançamento de antecipação e restaura as parcelas originais
|
||||
*/
|
||||
export async function cancelInstallmentAnticipationAction(
|
||||
input: CancelAnticipationInput
|
||||
input: CancelAnticipationInput,
|
||||
): Promise<ActionResult> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = cancelAnticipationSchema.parse(input);
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = cancelAnticipationSchema.parse(input);
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
// 1. Buscar antecipação usando query builder
|
||||
const anticipationRows = await tx
|
||||
.select({
|
||||
id: installmentAnticipations.id,
|
||||
seriesId: installmentAnticipations.seriesId,
|
||||
anticipationPeriod: installmentAnticipations.anticipationPeriod,
|
||||
anticipationDate: installmentAnticipations.anticipationDate,
|
||||
anticipatedInstallmentIds: installmentAnticipations.anticipatedInstallmentIds,
|
||||
totalAmount: installmentAnticipations.totalAmount,
|
||||
installmentCount: installmentAnticipations.installmentCount,
|
||||
discount: installmentAnticipations.discount,
|
||||
lancamentoId: installmentAnticipations.lancamentoId,
|
||||
pagadorId: installmentAnticipations.pagadorId,
|
||||
categoriaId: installmentAnticipations.categoriaId,
|
||||
note: installmentAnticipations.note,
|
||||
userId: installmentAnticipations.userId,
|
||||
createdAt: installmentAnticipations.createdAt,
|
||||
lancamento: lancamentos,
|
||||
})
|
||||
.from(installmentAnticipations)
|
||||
.leftJoin(lancamentos, eq(installmentAnticipations.lancamentoId, lancamentos.id))
|
||||
.where(
|
||||
and(
|
||||
eq(installmentAnticipations.id, data.anticipationId),
|
||||
eq(installmentAnticipations.userId, user.id)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
await db.transaction(async (tx) => {
|
||||
// 1. Buscar antecipação usando query builder
|
||||
const anticipationRows = await tx
|
||||
.select({
|
||||
id: installmentAnticipations.id,
|
||||
seriesId: installmentAnticipations.seriesId,
|
||||
anticipationPeriod: installmentAnticipations.anticipationPeriod,
|
||||
anticipationDate: installmentAnticipations.anticipationDate,
|
||||
anticipatedInstallmentIds:
|
||||
installmentAnticipations.anticipatedInstallmentIds,
|
||||
totalAmount: installmentAnticipations.totalAmount,
|
||||
installmentCount: installmentAnticipations.installmentCount,
|
||||
discount: installmentAnticipations.discount,
|
||||
lancamentoId: installmentAnticipations.lancamentoId,
|
||||
pagadorId: installmentAnticipations.pagadorId,
|
||||
categoriaId: installmentAnticipations.categoriaId,
|
||||
note: installmentAnticipations.note,
|
||||
userId: installmentAnticipations.userId,
|
||||
createdAt: installmentAnticipations.createdAt,
|
||||
lancamento: lancamentos,
|
||||
})
|
||||
.from(installmentAnticipations)
|
||||
.leftJoin(
|
||||
lancamentos,
|
||||
eq(installmentAnticipations.lancamentoId, lancamentos.id),
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
eq(installmentAnticipations.id, data.anticipationId),
|
||||
eq(installmentAnticipations.userId, user.id),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
const anticipation = anticipationRows[0];
|
||||
const anticipation = anticipationRows[0];
|
||||
|
||||
if (!anticipation) {
|
||||
throw new Error("Antecipação não encontrada.");
|
||||
}
|
||||
if (!anticipation) {
|
||||
throw new Error("Antecipação não encontrada.");
|
||||
}
|
||||
|
||||
// 2. Verificar se o lançamento já foi pago
|
||||
if (anticipation.lancamento?.isSettled === true) {
|
||||
throw new Error(
|
||||
"Não é possível cancelar uma antecipação já paga. Remova o pagamento primeiro."
|
||||
);
|
||||
}
|
||||
// 2. Verificar se o lançamento já foi pago
|
||||
if (anticipation.lancamento?.isSettled === true) {
|
||||
throw new Error(
|
||||
"Não é possível cancelar uma antecipação já paga. Remova o pagamento primeiro.",
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Calcular valor original por parcela (totalAmount sem desconto / quantidade)
|
||||
const originalTotalAmount = Number(anticipation.totalAmount);
|
||||
const originalValuePerInstallment =
|
||||
originalTotalAmount / anticipation.installmentCount;
|
||||
// 3. Calcular valor original por parcela (totalAmount sem desconto / quantidade)
|
||||
const originalTotalAmount = Number(anticipation.totalAmount);
|
||||
const originalValuePerInstallment =
|
||||
originalTotalAmount / anticipation.installmentCount;
|
||||
|
||||
// 4. Remover flag de antecipação e restaurar valores das parcelas
|
||||
await tx
|
||||
.update(lancamentos)
|
||||
.set({
|
||||
isAnticipated: false,
|
||||
anticipationId: null,
|
||||
amount: formatDecimalForDbRequired(originalValuePerInstallment),
|
||||
})
|
||||
.where(
|
||||
inArray(
|
||||
lancamentos.id,
|
||||
anticipation.anticipatedInstallmentIds as string[]
|
||||
)
|
||||
);
|
||||
// 4. Remover flag de antecipação e restaurar valores das parcelas
|
||||
await tx
|
||||
.update(lancamentos)
|
||||
.set({
|
||||
isAnticipated: false,
|
||||
anticipationId: null,
|
||||
amount: formatDecimalForDbRequired(originalValuePerInstallment),
|
||||
})
|
||||
.where(
|
||||
inArray(
|
||||
lancamentos.id,
|
||||
anticipation.anticipatedInstallmentIds as string[],
|
||||
),
|
||||
);
|
||||
|
||||
// 5. Deletar lançamento de antecipação
|
||||
await tx
|
||||
.delete(lancamentos)
|
||||
.where(eq(lancamentos.id, anticipation.lancamentoId));
|
||||
// 5. Deletar lançamento de antecipação
|
||||
await tx
|
||||
.delete(lancamentos)
|
||||
.where(eq(lancamentos.id, anticipation.lancamentoId));
|
||||
|
||||
// 6. Deletar registro de antecipação
|
||||
await tx
|
||||
.delete(installmentAnticipations)
|
||||
.where(eq(installmentAnticipations.id, data.anticipationId));
|
||||
});
|
||||
// 6. Deletar registro de antecipação
|
||||
await tx
|
||||
.delete(installmentAnticipations)
|
||||
.where(eq(installmentAnticipations.id, data.anticipationId));
|
||||
});
|
||||
|
||||
revalidatePath("/lancamentos");
|
||||
revalidatePath("/dashboard");
|
||||
revalidatePath("/lancamentos");
|
||||
revalidatePath("/dashboard");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Antecipação cancelada com sucesso!",
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
message: "Antecipação cancelada com sucesso!",
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca detalhes de uma antecipação específica
|
||||
*/
|
||||
export async function getAnticipationDetailsAction(
|
||||
anticipationId: string
|
||||
anticipationId: string,
|
||||
): Promise<ActionResult<InstallmentAnticipationWithRelations>> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
try {
|
||||
const user = await getUser();
|
||||
|
||||
// Validar anticipationId
|
||||
const validatedId = uuidSchema("Antecipação").parse(anticipationId);
|
||||
// Validar anticipationId
|
||||
const validatedId = uuidSchema("Antecipação").parse(anticipationId);
|
||||
|
||||
const anticipation = await db.query.installmentAnticipations.findFirst({
|
||||
where: and(
|
||||
eq(installmentAnticipations.id, validatedId),
|
||||
eq(installmentAnticipations.userId, user.id)
|
||||
),
|
||||
with: {
|
||||
lancamento: true,
|
||||
pagador: true,
|
||||
categoria: true,
|
||||
},
|
||||
});
|
||||
const anticipation = await db.query.installmentAnticipations.findFirst({
|
||||
where: and(
|
||||
eq(installmentAnticipations.id, validatedId),
|
||||
eq(installmentAnticipations.userId, user.id),
|
||||
),
|
||||
with: {
|
||||
lancamento: true,
|
||||
pagador: true,
|
||||
categoria: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!anticipation) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Antecipação não encontrada.",
|
||||
};
|
||||
}
|
||||
if (!anticipation) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Antecipação não encontrada.",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: anticipation,
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: anticipation,
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,47 @@
|
||||
import { lancamentos, contas, pagadores, cartoes, categorias } from "@/db/schema";
|
||||
import { and, desc, eq, isNull, ne, or, type SQL } from "drizzle-orm";
|
||||
import {
|
||||
cartoes,
|
||||
categorias,
|
||||
contas,
|
||||
lancamentos,
|
||||
pagadores,
|
||||
} from "@/db/schema";
|
||||
import { INITIAL_BALANCE_NOTE } from "@/lib/accounts/constants";
|
||||
import { db } from "@/lib/db";
|
||||
import { and, desc, eq, isNull, ne, or, type SQL } from "drizzle-orm";
|
||||
|
||||
export async function fetchLancamentos(filters: SQL[]) {
|
||||
const lancamentoRows = await db
|
||||
.select({
|
||||
lancamento: lancamentos,
|
||||
pagador: pagadores,
|
||||
conta: contas,
|
||||
cartao: cartoes,
|
||||
categoria: categorias,
|
||||
})
|
||||
.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))
|
||||
.where(
|
||||
and(
|
||||
...filters,
|
||||
// Excluir saldos iniciais de contas que têm excludeInitialBalanceFromIncome = true
|
||||
or(
|
||||
ne(lancamentos.note, INITIAL_BALANCE_NOTE),
|
||||
isNull(contas.excludeInitialBalanceFromIncome),
|
||||
eq(contas.excludeInitialBalanceFromIncome, false)
|
||||
)
|
||||
)
|
||||
)
|
||||
.orderBy(desc(lancamentos.purchaseDate), desc(lancamentos.createdAt));
|
||||
const lancamentoRows = await db
|
||||
.select({
|
||||
lancamento: lancamentos,
|
||||
pagador: pagadores,
|
||||
conta: contas,
|
||||
cartao: cartoes,
|
||||
categoria: categorias,
|
||||
})
|
||||
.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))
|
||||
.where(
|
||||
and(
|
||||
...filters,
|
||||
// Excluir saldos iniciais de contas que têm excludeInitialBalanceFromIncome = true
|
||||
or(
|
||||
ne(lancamentos.note, INITIAL_BALANCE_NOTE),
|
||||
isNull(contas.excludeInitialBalanceFromIncome),
|
||||
eq(contas.excludeInitialBalanceFromIncome, false),
|
||||
),
|
||||
),
|
||||
)
|
||||
.orderBy(desc(lancamentos.purchaseDate), desc(lancamentos.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,
|
||||
}));
|
||||
// Transformar resultado para o formato esperado
|
||||
return lancamentoRows.map((row) => ({
|
||||
...row.lancamento,
|
||||
pagador: row.pagador,
|
||||
conta: row.conta,
|
||||
cartao: row.cartao,
|
||||
categoria: row.categoria,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import PageDescription from "@/components/page-description";
|
||||
import { RiArrowLeftRightLine } from "@remixicon/react";
|
||||
import PageDescription from "@/components/page-description";
|
||||
|
||||
export const metadata = {
|
||||
title: "Lançamentos | Opensheets",
|
||||
title: "Lançamentos | Opensheets",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section className="space-y-6 px-6">
|
||||
<PageDescription
|
||||
icon={<RiArrowLeftRightLine />}
|
||||
title="Lançamentos"
|
||||
subtitle="Acompanhe todos os lançamentos financeiros do mês selecionado incluindo
|
||||
return (
|
||||
<section className="space-y-6 px-6">
|
||||
<PageDescription
|
||||
icon={<RiArrowLeftRightLine />}
|
||||
title="Lançamentos"
|
||||
subtitle="Acompanhe todos os lançamentos financeiros do mês selecionado incluindo
|
||||
receitas, despesas e transações previstas. Use o seletor abaixo para
|
||||
navegar pelos meses e visualizar as movimentações correspondentes."
|
||||
/>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
/>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
FilterSkeleton,
|
||||
TransactionsTableSkeleton,
|
||||
FilterSkeleton,
|
||||
TransactionsTableSkeleton,
|
||||
} from "@/components/skeletons";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
@@ -9,24 +9,24 @@ import { Skeleton } from "@/components/ui/skeleton";
|
||||
* Mantém o mesmo layout da página final
|
||||
*/
|
||||
export default function LancamentosLoading() {
|
||||
return (
|
||||
<main className="flex flex-col gap-6">
|
||||
{/* Month Picker placeholder */}
|
||||
<div className="h-[60px] animate-pulse rounded-2xl bg-foreground/10" />
|
||||
return (
|
||||
<main className="flex flex-col gap-6">
|
||||
{/* Month Picker placeholder */}
|
||||
<div className="h-[60px] animate-pulse rounded-2xl bg-foreground/10" />
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Header com título e botão */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Skeleton className="h-8 w-48 rounded-2xl bg-foreground/10" />
|
||||
<Skeleton className="h-10 w-40 rounded-2xl bg-foreground/10" />
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
{/* Header com título e botão */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Skeleton className="h-8 w-48 rounded-2xl bg-foreground/10" />
|
||||
<Skeleton className="h-10 w-40 rounded-2xl bg-foreground/10" />
|
||||
</div>
|
||||
|
||||
{/* Filtros */}
|
||||
<FilterSkeleton />
|
||||
{/* Filtros */}
|
||||
<FilterSkeleton />
|
||||
|
||||
{/* Tabela */}
|
||||
<TransactionsTableSkeleton />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
{/* Tabela */}
|
||||
<TransactionsTableSkeleton />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,86 +1,86 @@
|
||||
import MonthNavigation from "@/components/month-picker/month-navigation";
|
||||
import { LancamentosPage } from "@/components/lancamentos/page/lancamentos-page";
|
||||
import MonthNavigation from "@/components/month-picker/month-navigation";
|
||||
import { getUserId } from "@/lib/auth/server";
|
||||
import {
|
||||
buildLancamentoWhere,
|
||||
buildOptionSets,
|
||||
buildSluggedFilters,
|
||||
buildSlugMaps,
|
||||
extractLancamentoSearchFilters,
|
||||
fetchLancamentoFilterSources,
|
||||
getSingleParam,
|
||||
mapLancamentosData,
|
||||
type ResolvedSearchParams,
|
||||
buildLancamentoWhere,
|
||||
buildOptionSets,
|
||||
buildSluggedFilters,
|
||||
buildSlugMaps,
|
||||
extractLancamentoSearchFilters,
|
||||
fetchLancamentoFilterSources,
|
||||
getSingleParam,
|
||||
mapLancamentosData,
|
||||
type ResolvedSearchParams,
|
||||
} from "@/lib/lancamentos/page-helpers";
|
||||
import { parsePeriodParam } from "@/lib/utils/period";
|
||||
import { fetchLancamentos } from "./data";
|
||||
import { getRecentEstablishmentsAction } from "./actions";
|
||||
import { fetchLancamentos } from "./data";
|
||||
|
||||
type PageSearchParams = Promise<ResolvedSearchParams>;
|
||||
|
||||
type PageProps = {
|
||||
searchParams?: PageSearchParams;
|
||||
searchParams?: PageSearchParams;
|
||||
};
|
||||
|
||||
export default async function Page({ searchParams }: PageProps) {
|
||||
const userId = await getUserId();
|
||||
const resolvedSearchParams = searchParams ? await searchParams : undefined;
|
||||
const userId = await getUserId();
|
||||
const resolvedSearchParams = searchParams ? await searchParams : undefined;
|
||||
|
||||
const periodoParamRaw = getSingleParam(resolvedSearchParams, "periodo");
|
||||
const { period: selectedPeriod } = parsePeriodParam(periodoParamRaw);
|
||||
const periodoParamRaw = getSingleParam(resolvedSearchParams, "periodo");
|
||||
const { period: selectedPeriod } = parsePeriodParam(periodoParamRaw);
|
||||
|
||||
const searchFilters = extractLancamentoSearchFilters(resolvedSearchParams);
|
||||
const searchFilters = extractLancamentoSearchFilters(resolvedSearchParams);
|
||||
|
||||
const filterSources = await fetchLancamentoFilterSources(userId);
|
||||
const filterSources = await fetchLancamentoFilterSources(userId);
|
||||
|
||||
const sluggedFilters = buildSluggedFilters(filterSources);
|
||||
const slugMaps = buildSlugMaps(sluggedFilters);
|
||||
const sluggedFilters = buildSluggedFilters(filterSources);
|
||||
const slugMaps = buildSlugMaps(sluggedFilters);
|
||||
|
||||
const filters = buildLancamentoWhere({
|
||||
userId,
|
||||
period: selectedPeriod,
|
||||
filters: searchFilters,
|
||||
slugMaps,
|
||||
});
|
||||
const filters = buildLancamentoWhere({
|
||||
userId,
|
||||
period: selectedPeriod,
|
||||
filters: searchFilters,
|
||||
slugMaps,
|
||||
});
|
||||
|
||||
const lancamentoRows = await fetchLancamentos(filters);
|
||||
const lancamentosData = mapLancamentosData(lancamentoRows);
|
||||
const lancamentoRows = await fetchLancamentos(filters);
|
||||
const lancamentosData = mapLancamentosData(lancamentoRows);
|
||||
|
||||
const {
|
||||
pagadorOptions,
|
||||
splitPagadorOptions,
|
||||
defaultPagadorId,
|
||||
contaOptions,
|
||||
cartaoOptions,
|
||||
categoriaOptions,
|
||||
pagadorFilterOptions,
|
||||
categoriaFilterOptions,
|
||||
contaCartaoFilterOptions,
|
||||
} = buildOptionSets({
|
||||
...sluggedFilters,
|
||||
pagadorRows: filterSources.pagadorRows,
|
||||
});
|
||||
const {
|
||||
pagadorOptions,
|
||||
splitPagadorOptions,
|
||||
defaultPagadorId,
|
||||
contaOptions,
|
||||
cartaoOptions,
|
||||
categoriaOptions,
|
||||
pagadorFilterOptions,
|
||||
categoriaFilterOptions,
|
||||
contaCartaoFilterOptions,
|
||||
} = buildOptionSets({
|
||||
...sluggedFilters,
|
||||
pagadorRows: filterSources.pagadorRows,
|
||||
});
|
||||
|
||||
const estabelecimentos = await getRecentEstablishmentsAction();
|
||||
const estabelecimentos = await getRecentEstablishmentsAction();
|
||||
|
||||
return (
|
||||
<main className="flex flex-col gap-6">
|
||||
<MonthNavigation />
|
||||
<LancamentosPage
|
||||
currentUserId={userId}
|
||||
lancamentos={lancamentosData}
|
||||
pagadorOptions={pagadorOptions}
|
||||
splitPagadorOptions={splitPagadorOptions}
|
||||
defaultPagadorId={defaultPagadorId}
|
||||
contaOptions={contaOptions}
|
||||
cartaoOptions={cartaoOptions}
|
||||
categoriaOptions={categoriaOptions}
|
||||
pagadorFilterOptions={pagadorFilterOptions}
|
||||
categoriaFilterOptions={categoriaFilterOptions}
|
||||
contaCartaoFilterOptions={contaCartaoFilterOptions}
|
||||
selectedPeriod={selectedPeriod}
|
||||
estabelecimentos={estabelecimentos}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
return (
|
||||
<main className="flex flex-col gap-6">
|
||||
<MonthNavigation />
|
||||
<LancamentosPage
|
||||
currentUserId={userId}
|
||||
lancamentos={lancamentosData}
|
||||
pagadorOptions={pagadorOptions}
|
||||
splitPagadorOptions={splitPagadorOptions}
|
||||
defaultPagadorId={defaultPagadorId}
|
||||
contaOptions={contaOptions}
|
||||
cartaoOptions={cartaoOptions}
|
||||
categoriaOptions={categoriaOptions}
|
||||
pagadorFilterOptions={pagadorFilterOptions}
|
||||
categoriaFilterOptions={categoriaFilterOptions}
|
||||
contaCartaoFilterOptions={contaCartaoFilterOptions}
|
||||
selectedPeriod={selectedPeriod}
|
||||
estabelecimentos={estabelecimentos}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user