refactor(inbox): remove process dialog e integra fluxo ao lancamento-dialog

- Remove process-dialog.tsx (componente não mais utilizado)
- Simplifica inbox-page.tsx removendo estados e lógica do process dialog
- Atualiza inbox-details-dialog para usar lancamento-dialog diretamente
- Adiciona suporte a dados iniciais do inbox no lancamento-dialog
- Move campos de metadata da inbox para o form de lançamento
- Remove campo currency não utilizado do schema
- Atualiza actions e data com melhor tratamento de erros
This commit is contained in:
Felipe Coutinho
2026-01-26 13:31:37 +00:00
parent 18471f2225
commit c0fb11f89c
16 changed files with 203 additions and 421 deletions

View File

@@ -1,6 +1,6 @@
"use server";
import { inboxItems, lancamentos } from "@/db/schema";
import { inboxItems } from "@/db/schema";
import { handleActionError } from "@/lib/actions/helpers";
import type { ActionResult } from "@/lib/actions/types";
import { db } from "@/lib/db";
@@ -8,20 +8,9 @@ import { getUser } from "@/lib/auth/server";
import { and, eq, inArray } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { z } from "zod";
import { getCurrentPeriod } from "@/lib/utils/period";
const processInboxSchema = z.object({
const markProcessedSchema = z.object({
inboxItemId: z.string().uuid("ID do item inválido"),
name: z.string().min(1, "Nome é obrigatório"),
amount: z.coerce.number().positive("Valor deve ser positivo"),
purchaseDate: z.string().min(1, "Data é obrigatória"),
transactionType: z.enum(["Despesa", "Receita"]),
condition: z.string().min(1, "Condição é obrigatória"),
paymentMethod: z.string().min(1, "Forma de pagamento é obrigatória"),
categoriaId: z.string().uuid("Categoria inválida"),
contaId: z.string().uuid("Conta inválida").optional(),
cartaoId: z.string().uuid("Cartão inválido").optional(),
note: z.string().optional(),
});
const discardInboxSchema = z.object({
@@ -39,12 +28,15 @@ function revalidateInbox() {
revalidatePath("/dashboard");
}
export async function processInboxItemAction(
input: z.infer<typeof processInboxSchema>
/**
* Mark an inbox item as processed after a lancamento was created
*/
export async function markInboxAsProcessedAction(
input: z.infer<typeof markProcessedSchema>
): Promise<ActionResult> {
try {
const user = await getUser();
const data = processInboxSchema.parse(input);
const data = markProcessedSchema.parse(input);
// Verificar se item existe e pertence ao usuário
const [item] = await db
@@ -63,43 +55,19 @@ export async function processInboxItemAction(
return { success: false, error: "Item não encontrado ou já processado." };
}
// Determinar período baseado na data de compra
const purchaseDate = new Date(data.purchaseDate);
const period = getCurrentPeriod(purchaseDate);
// Criar lançamento
const [newLancamento] = await db
.insert(lancamentos)
.values({
userId: user.id,
name: data.name,
amount: data.amount.toString(),
purchaseDate: purchaseDate,
transactionType: data.transactionType,
condition: data.condition,
paymentMethod: data.paymentMethod,
categoriaId: data.categoriaId,
contaId: data.contaId,
cartaoId: data.cartaoId,
note: data.note,
period,
})
.returning({ id: lancamentos.id });
// Marcar item como processado
await db
.update(inboxItems)
.set({
status: "processed",
processedAt: new Date(),
lancamentoId: newLancamento.id,
updatedAt: new Date(),
})
.where(eq(inboxItems.id, data.inboxItemId));
revalidateInbox();
return { success: true, message: "Lançamento criado com sucesso!" };
return { success: true, message: "Item processado com sucesso!" };
} catch (error) {
return handleActionError(error);
}

View File

@@ -3,9 +3,14 @@
*/
import { db } from "@/lib/db";
import { inboxItems, categorias, contas, cartoes } from "@/db/schema";
import { eq, desc, and } from "drizzle-orm";
import { inboxItems, categorias, contas, cartoes, lancamentos } from "@/db/schema";
import { eq, desc, and, gte } from "drizzle-orm";
import type { InboxItem, SelectOption } from "@/components/caixa-de-entrada/types";
import {
fetchLancamentoFilterSources,
buildSluggedFilters,
buildOptionSets,
} from "@/lib/lancamentos/page-helpers";
export async function fetchInboxItems(
userId: string,
@@ -80,3 +85,70 @@ export async function fetchPendingInboxCount(userId: string): Promise<number> {
return items.length;
}
/**
* Fetch all data needed for the LancamentoDialog in inbox context
*/
export async function fetchInboxDialogData(userId: string): Promise<{
pagadorOptions: SelectOption[];
splitPagadorOptions: SelectOption[];
defaultPagadorId: string | null;
contaOptions: SelectOption[];
cartaoOptions: SelectOption[];
categoriaOptions: SelectOption[];
estabelecimentos: string[];
}> {
const filterSources = await fetchLancamentoFilterSources(userId);
const sluggedFilters = buildSluggedFilters(filterSources);
const {
pagadorOptions,
splitPagadorOptions,
defaultPagadorId,
contaOptions,
cartaoOptions,
categoriaOptions,
} = buildOptionSets({
...sluggedFilters,
pagadorRows: filterSources.pagadorRows,
});
// Fetch recent establishments (same approach as getRecentEstablishmentsAction)
const threeMonthsAgo = new Date();
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3);
const recentEstablishments = await db
.select({ name: lancamentos.name })
.from(lancamentos)
.where(
and(
eq(lancamentos.userId, userId),
gte(lancamentos.purchaseDate, threeMonthsAgo)
)
)
.orderBy(desc(lancamentos.purchaseDate));
// Remove duplicates and filter empty names
const filteredNames: string[] = recentEstablishments
.map((r: { name: string }) => r.name)
.filter(
(name: string | null): name is string =>
name != null &&
name.trim().length > 0 &&
!name.toLowerCase().startsWith("pagamento fatura")
);
const estabelecimentos = Array.from<string>(new Set(filteredNames)).slice(
0,
100
);
return {
pagadorOptions,
splitPagadorOptions,
defaultPagadorId,
contaOptions,
cartaoOptions,
categoriaOptions,
estabelecimentos,
};
}

View File

@@ -1,29 +1,26 @@
import { InboxPage } from "@/components/caixa-de-entrada/inbox-page";
import { getUserId } from "@/lib/auth/server";
import {
fetchInboxItems,
fetchCategoriasForSelect,
fetchContasForSelect,
fetchCartoesForSelect,
} from "./data";
import { fetchInboxItems, fetchInboxDialogData } from "./data";
export default async function Page() {
const userId = await getUserId();
const [items, categorias, contas, cartoes] = await Promise.all([
const [items, dialogData] = await Promise.all([
fetchInboxItems(userId, "pending"),
fetchCategoriasForSelect(userId),
fetchContasForSelect(userId),
fetchCartoesForSelect(userId),
fetchInboxDialogData(userId),
]);
return (
<main className="flex flex-col items-start gap-6">
<InboxPage
items={items}
categorias={categorias}
contas={contas}
cartoes={cartoes}
pagadorOptions={dialogData.pagadorOptions}
splitPagadorOptions={dialogData.splitPagadorOptions}
defaultPagadorId={dialogData.defaultPagadorId}
contaOptions={dialogData.contaOptions}
cartaoOptions={dialogData.cartaoOptions}
categoriaOptions={dialogData.categoriaOptions}
estabelecimentos={dialogData.estabelecimentos}
/>
</main>
);