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 }) => (
-
@@ -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 };
}