diff --git a/src/app/(dashboard)/attachments/page.tsx b/src/app/(dashboard)/attachments/page.tsx index 47f9006..cc39731 100644 --- a/src/app/(dashboard)/attachments/page.tsx +++ b/src/app/(dashboard)/attachments/page.tsx @@ -1,6 +1,6 @@ import { connection } from "next/server"; import { AttachmentsPage } from "@/features/attachments/components/attachments-page"; -import { fetchAttachmentsForPeriod } from "@/features/attachments/queries"; +import { fetchAttachmentsPageData } from "@/features/attachments/queries"; import { getUserId } from "@/shared/lib/auth/server"; import { parsePeriodParam } from "@/shared/utils/period"; @@ -26,11 +26,14 @@ export default async function Page({ searchParams }: PageProps) { const periodoParam = getSingleParam(resolvedSearchParams, "periodo"); const { period } = parsePeriodParam(periodoParam); - const attachments = await fetchAttachmentsForPeriod(userId, period); + const data = await fetchAttachmentsPageData(userId, period); return (
- +
); } diff --git a/src/features/attachments/components/attachment-grid-item.tsx b/src/features/attachments/components/attachment-grid-item.tsx index 265f88d..27719bc 100644 --- a/src/features/attachments/components/attachment-grid-item.tsx +++ b/src/features/attachments/components/attachment-grid-item.tsx @@ -162,9 +162,15 @@ export function AttachmentGridItem({ {/* Data */} - - {formatDate(attachment.purchaseDate)} - +
+ + {formatDate(attachment.purchaseDate)} + + · + + {attachment.payerName} + +
{/* Transação e Valor */}
diff --git a/src/features/attachments/components/attachments-page.tsx b/src/features/attachments/components/attachments-page.tsx index 379a4d6..d224af8 100644 --- a/src/features/attachments/components/attachments-page.tsx +++ b/src/features/attachments/components/attachments-page.tsx @@ -4,6 +4,8 @@ import { RiAttachmentLine, RiFilePdf2Line, RiImageLine, + RiUserLine, + RiVerifiedBadgeFill, } from "@remixicon/react"; import { useRouter } from "next/navigation"; import type React from "react"; @@ -17,9 +19,17 @@ import type { TransactionDialogOptions } from "@/features/transactions/actions/f import { fetchTransactionDialogOptionsAction } from "@/features/transactions/actions/fetch-dialog-options"; import { TransactionDetailsDialog } from "@/features/transactions/components/dialogs/transaction-details-dialog"; import { TransactionDialog } from "@/features/transactions/components/dialogs/transaction-dialog/transaction-dialog"; +import { PayerSelectContent } from "@/features/transactions/components/select-items"; import type { TransactionItem } from "@/features/transactions/components/types"; import { EmptyState } from "@/shared/components/feedback/empty-state"; import { Card, CardContent } from "@/shared/components/ui/card"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/shared/components/ui/select"; import { cn } from "@/shared/utils/ui"; type FilterType = "all" | "images" | "pdfs"; @@ -73,11 +83,18 @@ const FILTERS: { interface AttachmentsPageProps { attachments: AttachmentForPeriod[]; + adminPayerId: string; } -export function AttachmentsPage({ attachments }: AttachmentsPageProps) { +const ALL_PAYERS = "all"; + +export function AttachmentsPage({ + attachments, + adminPayerId, +}: AttachmentsPageProps) { const router = useRouter(); const [filter, setFilter] = useState("all"); + const [payerFilter, setPayerFilter] = useState(adminPayerId); const [selectedIndex, setSelectedIndex] = useState(-1); const [transactionDetails, setTransactionDetails] = useState(null); @@ -93,21 +110,44 @@ export function AttachmentsPage({ attachments }: AttachmentsPageProps) { const [dialogOptions, setDialogOptions] = useState(null); - const filteredAttachments = attachments.filter((a) => { + const payerOptions = Array.from( + new Map( + attachments.map((attachment) => [ + attachment.payerId, + { + value: attachment.payerId, + label: attachment.payerName, + avatarUrl: attachment.payerAvatarUrl, + }, + ]), + ).values(), + ).sort((a, b) => + a.label.localeCompare(b.label, "pt-BR", { sensitivity: "base" }), + ); + + const payerAttachments = attachments.filter( + (attachment) => + payerFilter === ALL_PAYERS || attachment.payerId === payerFilter, + ); + const selectedPayer = payerOptions.find( + (option) => option.value === payerFilter, + ); + + const filteredAttachments = payerAttachments.filter((a) => { if (filter === "images") return a.mimeType.startsWith("image/"); if (filter === "pdfs") return a.mimeType === "application/pdf"; return true; }); - const imageCount = attachments.filter((a) => + const imageCount = payerAttachments.filter((a) => a.mimeType.startsWith("image/"), ).length; - const pdfCount = attachments.filter( + const pdfCount = payerAttachments.filter( (a) => a.mimeType === "application/pdf", ).length; const counts: Record = { - all: attachments.length, + all: payerAttachments.length, images: imageCount, pdfs: pdfCount, }; @@ -161,36 +201,98 @@ export function AttachmentsPage({ attachments }: AttachmentsPageProps) { {filter !== "all" && ` · ${FILTERS.find((f) => f.value === filter)?.label.toLowerCase()}`}

-
- {FILTERS.map(({ value, label, icon }) => ( - - ))} + + {icon} + + {label}{" "} + + ({counts[value]}) + + + ))} +
@@ -199,7 +301,11 @@ export function AttachmentsPage({ attachments }: AttachmentsPageProps) { } title="Nenhum anexo encontrado" - description="Não há anexos do tipo selecionado neste mês." + description={ + payerAttachments.length === 0 + ? "Não há anexos desta pessoa neste mês." + : "Não há anexos do tipo selecionado neste mês." + } /> ) : ( diff --git a/src/features/attachments/queries.ts b/src/features/attachments/queries.ts index 8ecc736..3f27981 100644 --- a/src/features/attachments/queries.ts +++ b/src/features/attachments/queries.ts @@ -3,6 +3,7 @@ import { cacheLife, cacheTag } from "next/cache"; import { attachments, categories, + payers, transactionAttachments, transactions, } from "@/db/schema"; @@ -21,11 +22,20 @@ export type AttachmentForPeriod = { purchaseDate: Date; categoryName: string | null; categoryIcon: string | null; + payerId: string; + payerName: string; + payerAvatarUrl: string | null; +}; + +export type AttachmentsPageData = { + attachments: AttachmentForPeriod[]; + adminPayerId: string; }; export async function fetchAttachmentsForPeriod( userId: string, period: string, + payerScope?: string | "all", ): Promise { "use cache"; cacheTag(`dashboard-${userId}`); @@ -33,8 +43,9 @@ export async function fetchAttachmentsForPeriod( const adminPayerId = await getAdminPayerId(userId); if (!adminPayerId) return []; + const payerId = payerScope ?? adminPayerId; - return db + const rows = await db .select({ attachmentId: attachments.id, fileName: attachments.fileName, @@ -47,6 +58,9 @@ export async function fetchAttachmentsForPeriod( purchaseDate: transactions.purchaseDate, categoryName: categories.name, categoryIcon: categories.icon, + payerId: payers.id, + payerName: payers.name, + payerAvatarUrl: payers.avatarUrl, }) .from(transactionAttachments) .innerJoin( @@ -61,10 +75,32 @@ export async function fetchAttachmentsForPeriod( and( eq(transactionAttachments.transactionId, transactions.id), eq(transactions.userId, userId), - eq(transactions.payerId, adminPayerId), eq(transactions.period, period), + payerId === "all" ? undefined : eq(transactions.payerId, payerId), + ), + ) + .innerJoin( + payers, + and(eq(transactions.payerId, payers.id), eq(payers.userId, userId)), + ) + .leftJoin( + categories, + and( + eq(transactions.categoryId, categories.id), + eq(categories.userId, userId), ), ) - .leftJoin(categories, eq(transactions.categoryId, categories.id)) .orderBy(desc(transactions.purchaseDate), desc(attachments.id)); + + return rows; +} + +export async function fetchAttachmentsPageData( + userId: string, + period: string, +): Promise { + const adminPayerId = await getAdminPayerId(userId); + if (!adminPayerId) return null; + const rows = await fetchAttachmentsForPeriod(userId, period, "all"); + return { attachments: rows, adminPayerId }; }