Files
openmonetis/src/features/dashboard/inbox-snapshot-queries.ts
Felipe Coutinho 7a3bff52ac feat(dashboard): novos widgets de anexos, inbox e tendências de categoria
- Widget Anexos: resumo de arquivos do período (total, imagens, PDFs, recentes)
- Widget Inbox: snapshot de pré-lançamentos pendentes do Companion
- Widget Tendências de Categoria: redireciona para relatório de tendências
- fetch-dashboard-data: busca attachmentsSnapshot e inboxSnapshot em paralelo
- widgets-config: tipo DashboardWidgetQuickActionOptions centralizado; props
  adminPayerSlug e quickActionOptions adicionadas ao contrato do widget
- dashboard-grid-editable: usa o novo tipo unificado de quickActionOptions
- proxy.ts: frame-src adicionado à CSP para preview de PDFs via S3
- rota /attachments criada com layout próprio

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 17:51:09 +00:00

75 lines
2.0 KiB
TypeScript

import { and, count, desc, eq } from "drizzle-orm";
import { cacheLife, cacheTag } from "next/cache";
import { cards, financialAccounts, inboxItems } from "@/db/schema";
import { db } from "@/shared/lib/db";
export type DashboardInboxItem = {
id: string;
sourceAppName: string | null;
parsedName: string | null;
parsedAmount: string | null;
originalText: string;
notificationTimestamp: Date;
createdAt: Date;
};
export type DashboardInboxSnapshot = {
pendingCount: number;
recentItems: DashboardInboxItem[];
logoMap: Record<string, string>;
};
export async function fetchDashboardInboxSnapshot(
userId: string,
): Promise<DashboardInboxSnapshot> {
"use cache";
cacheTag(`dashboard-${userId}`);
cacheLife({ revalidate: 3 });
const [countRows, items, userCards, userAccounts] = await Promise.all([
db
.select({ total: count() })
.from(inboxItems)
.where(
and(eq(inboxItems.userId, userId), eq(inboxItems.status, "pending")),
),
db
.select({
id: inboxItems.id,
sourceAppName: inboxItems.sourceAppName,
parsedName: inboxItems.parsedName,
parsedAmount: inboxItems.parsedAmount,
originalText: inboxItems.originalText,
notificationTimestamp: inboxItems.notificationTimestamp,
createdAt: inboxItems.createdAt,
})
.from(inboxItems)
.where(
and(eq(inboxItems.userId, userId), eq(inboxItems.status, "pending")),
)
.orderBy(desc(inboxItems.notificationTimestamp))
.limit(10),
db
.select({ name: cards.name, logo: cards.logo })
.from(cards)
.where(eq(cards.userId, userId)),
db
.select({ name: financialAccounts.name, logo: financialAccounts.logo })
.from(financialAccounts)
.where(eq(financialAccounts.userId, userId)),
]);
const logoMap: Record<string, string> = {};
for (const item of [...userCards, ...userAccounts]) {
if (item.logo) {
logoMap[item.name.toLowerCase()] = item.logo;
}
}
return {
pendingCount: Number(countRows[0]?.total ?? 0),
recentItems: items,
logoMap,
};
}