From d3fc81db73cb97e1fbfb8cf75aab375919b857a1 Mon Sep 17 00:00:00 2001 From: Felipe Coutinho Date: Sat, 21 Mar 2026 19:31:38 +0000 Subject: [PATCH] fix(inbox): melhorar filtros e identidade visual --- public/avatars/default_icon.png | Bin 3388 -> 5537 bytes src/app/(dashboard)/inbox/page.tsx | 28 +- src/features/inbox/components/inbox-card.tsx | 56 ++- .../inbox/components/inbox-details-dialog.tsx | 19 +- src/features/inbox/components/inbox-page.tsx | 422 ++++++++++++++---- src/features/inbox/page-helpers.ts | 4 + src/features/inbox/queries.ts | 28 +- 7 files changed, 425 insertions(+), 132 deletions(-) diff --git a/public/avatars/default_icon.png b/public/avatars/default_icon.png index 87cb80e6249de853c5597cb9d45abd86490cf3dc..45ae426fb325ca992055580fcede394e7b85edf5 100644 GIT binary patch literal 5537 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 Bf2{xj literal 3388 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 [] as string[]), + activeStatus === "pending" + ? fetchInboxDialogData(userId) + : Promise.resolve(EMPTY_DIALOG_DATA), + fetchAppLogoMap(userId), + ]); + + const normalizedSourceApps = Array.isArray(sourceApps) ? sourceApps : []; return (
- {matchedLogo && ( - onSelectToggle(item.id)} + aria-label="Selecionar item" + className="shrink-0" /> )} + {item.sourceAppName || item.sourceApp} - - {timeAgo} - + + + + {timeAgo} + + + {fullDate} + {amount !== null && ( @@ -174,13 +196,6 @@ export function InboxCard({ )} - {onSelectToggle && ( - onSelectToggle(item.id)} - aria-label="Selecionar item" - /> - )}
) : ( @@ -213,13 +228,6 @@ export function InboxCard({ > - {onSelectToggle && ( - onSelectToggle(item.id)} - aria-label="Selecionar item" - /> - )} )} diff --git a/src/features/inbox/components/inbox-details-dialog.tsx b/src/features/inbox/components/inbox-details-dialog.tsx index c712118..b7ae91a 100644 --- a/src/features/inbox/components/inbox-details-dialog.tsx +++ b/src/features/inbox/components/inbox-details-dialog.tsx @@ -52,7 +52,14 @@ export function InboxDetailsDialog({
App - {item.sourceAppName || item.sourceApp} +
+ {item.sourceAppName || item.sourceApp} + {item.sourceAppName && ( + + {item.sourceApp} + + )} +
@@ -109,6 +116,11 @@ export function InboxDetailsDialog({ + + + {isPending && onProcess && ( - diff --git a/src/features/inbox/components/inbox-page.tsx b/src/features/inbox/components/inbox-page.tsx index d2c3339..dd02bdb 100644 --- a/src/features/inbox/components/inbox-page.tsx +++ b/src/features/inbox/components/inbox-page.tsx @@ -6,8 +6,12 @@ import { RiArrowRightDoubleLine, RiArrowRightSLine, RiAtLine, + RiCalendarEventLine, RiDeleteBinLine, } from "@remixicon/react"; +import { format } from "date-fns"; +import { ptBR } from "date-fns/locale"; +import Image from "next/image"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { useEffect, useMemo, useState, useTransition } from "react"; import { toast } from "sonner"; @@ -42,6 +46,7 @@ import { TabsList, TabsTrigger, } from "@/shared/components/ui/tabs"; +import { resolveLogoSrc } from "@/shared/lib/logo"; import { InboxCard } from "./inbox-card"; import { InboxDetailsDialog } from "./inbox-details-dialog"; import type { @@ -52,8 +57,71 @@ import type { SelectOption, } from "./types"; +const BRASILIA_OFFSET_MS = 3 * 60 * 60 * 1000; +const DEFAULT_INBOX_APP_LOGO = "/avatars/default_icon.png"; + +function getDateKey(date: Date): string { + const adjusted = new Date(date.getTime() + BRASILIA_OFFSET_MS); + return adjusted.toISOString().slice(0, 10); +} + +function getGroupLabel(dateKey: string): string { + const now = new Date(); + const todayKey = getDateKey(now); + const yesterdayKey = getDateKey( + new Date(now.getTime() - 24 * 60 * 60 * 1000), + ); + if (dateKey === todayKey) return "Hoje"; + if (dateKey === yesterdayKey) return "Ontem"; + const [year, month, day] = dateKey.split("-").map(Number); + return format(new Date(year, month - 1, day), "d 'de' MMMM", { + locale: ptBR, + }); +} + +function groupItemsByDay( + items: InboxItem[], +): { label: string; items: InboxItem[] }[] { + const groups = new Map(); + for (const item of items) { + const key = getDateKey(new Date(item.notificationTimestamp)); + const group = groups.get(key); + if (group) { + group.push(item); + } else { + groups.set(key, [item]); + } + } + const sortedKeys = [...groups.keys()].sort((a, b) => b.localeCompare(a)); + return sortedKeys.map((key) => ({ + label: getGroupLabel(key), + items: groups.get(key) ?? [], + })); +} + +function findMatchingLogo( + sourceAppName: string | null, + appLogoMap: Record, +): string | null { + if (!sourceAppName) return null; + + const appName = sourceAppName.toLowerCase(); + + if (appLogoMap[appName]) return resolveLogoSrc(appLogoMap[appName]); + + for (const [name, logo] of Object.entries(appLogoMap)) { + if (name.includes(appName) || appName.includes(name)) { + return resolveLogoSrc(logo); + } + } + + return null; +} + interface InboxPageProps { activeStatus: InboxStatus; + activeApp: string | null; + sourceApps: string[]; items: InboxItem[]; counts: InboxStatusCounts; pagination: InboxPaginationState; @@ -69,6 +137,8 @@ interface InboxPageProps { export function InboxPage({ activeStatus, + activeApp, + sourceApps = [], items, counts, pagination, @@ -111,6 +181,38 @@ export function InboxPage({ const [selectionBulkStatus, setSelectionBulkStatus] = useState("pending"); + const normalizedSourceApps = useMemo(() => { + if (!Array.isArray(sourceApps)) { + return []; + } + + const uniqueApps = new Set(); + for (const app of sourceApps) { + if (typeof app !== "string") { + continue; + } + + const trimmedApp = app.trim(); + if (!trimmedApp) { + continue; + } + + uniqueApps.add(trimmedApp); + } + + return [...uniqueApps].sort((left, right) => + left.localeCompare(right, "pt-BR"), + ); + }, [sourceApps]); + + const appFilterOptions = + activeApp && !normalizedSourceApps.includes(activeApp) + ? [activeApp, ...normalizedSourceApps] + : normalizedSourceApps; + + const getAppLogo = (appName: string | null) => + findMatchingLogo(appName, appLogoMap) ?? DEFAULT_INBOX_APP_LOGO; + const handleProcessOpenChange = (open: boolean) => { setProcessOpen(open); if (!open) { @@ -239,7 +341,6 @@ export function InboxPage({ setSelectedIds([]); return; } - setSelectedIds(items.map((item) => item.id)); }; @@ -276,8 +377,42 @@ export function InboxPage({ }); }; + const handleAppChange = (nextApp: string) => { + const nextParams = new URLSearchParams(searchParams.toString()); + if (nextApp === "all") { + nextParams.delete("app"); + } else { + nextParams.set("app", nextApp); + } + nextParams.delete("page"); + startTransition(() => { + const target = nextParams.toString() + ? `${pathname}?${nextParams.toString()}` + : pathname; + router.replace(target, { scroll: false }); + }); + }; + const handleTabChange = (nextStatus: string) => { - updateUrl(nextStatus as InboxStatus, 1, pagination.pageSize); + const nextParams = new URLSearchParams(searchParams.toString()); + nextParams.delete("app"); + if (nextStatus === "pending") { + nextParams.delete("status"); + } else { + nextParams.set("status", nextStatus); + } + nextParams.delete("page"); + if (pagination.pageSize === INBOX_DEFAULT_PAGE_SIZE) { + nextParams.delete("pageSize"); + } else { + nextParams.set("pageSize", pagination.pageSize.toString()); + } + startTransition(() => { + const target = nextParams.toString() + ? `${pathname}?${nextParams.toString()}` + : pathname; + router.replace(target, { scroll: false }); + }); }; const handleSelectionBulkRequest = (status: InboxStatus) => { @@ -401,32 +536,105 @@ export function InboxPage({ ); - const renderGrid = (list: InboxItem[], readonly?: boolean) => - list.length === 0 ? ( - renderEmptyState( + const renderGroupedGrid = (list: InboxItem[], readonly?: boolean) => { + if (list.length === 0) { + if (activeApp) { + return renderEmptyState("Nenhuma notificação deste app"); + } + return renderEmptyState( readonly ? "Nenhuma notificação nesta aba" : "Nenhum pré-lançamento pendente", - ) - ) : ( -
- {list.map((item) => ( - + ); + } + + const groups = groupItemsByDay(list); + + return ( +
+ {groups.map((group) => ( +
+
+ +

{group.label}

+
+
+ {group.items.map((item) => ( + + ))} +
+
))}
); + }; + + const renderAppFilter = () => { + if (appFilterOptions.length === 0) { + return null; + } + + return ( + + ); + }; return ( <> @@ -463,80 +671,110 @@ export function InboxPage({ - {activeStatus === "pending" && items.length > 0 && ( -
- - {selectedIds.length > 0 && ( - - )} -
- )} - {activeStatus === "pending" ? renderGrid(items, false) : null} + {activeStatus === "pending" && + (appFilterOptions.length > 0 || items.length > 0) && ( +
+ {renderAppFilter()} + {items.length > 0 ? ( +
+ + {selectedIds.length > 0 && ( + + )} +
+ ) : null} +
+ )} + {activeStatus === "pending" ? renderGroupedGrid(items, false) : null}
- {activeStatus === "processed" && items.length > 0 && ( -
- - {selectedIds.length > 0 && ( - - )} - -
- )} - {activeStatus === "processed" ? renderGrid(items, true) : null} + {activeStatus === "processed" && + (appFilterOptions.length > 0 || items.length > 0) && ( +
+ {renderAppFilter()} + {items.length > 0 ? ( +
+ + {selectedIds.length > 0 && ( + + )} + +
+ ) : null} +
+ )} + {activeStatus === "processed" ? renderGroupedGrid(items, true) : null}
- {activeStatus === "discarded" && items.length > 0 && ( -
- - {selectedIds.length > 0 && ( - - )} - -
- )} - {activeStatus === "discarded" ? renderGrid(items, true) : null} + {activeStatus === "discarded" && + (appFilterOptions.length > 0 || items.length > 0) && ( +
+ {renderAppFilter()} + {items.length > 0 ? ( +
+ + {selectedIds.length > 0 && ( + + )} + +
+ ) : null} +
+ )} + {activeStatus === "discarded" ? renderGroupedGrid(items, true) : null}
diff --git a/src/features/inbox/page-helpers.ts b/src/features/inbox/page-helpers.ts index 2f23f81..592a14e 100644 --- a/src/features/inbox/page-helpers.ts +++ b/src/features/inbox/page-helpers.ts @@ -31,6 +31,10 @@ export const resolveInboxStatus = ( : "pending"; }; +export const resolveInboxApp = ( + params: ResolvedInboxSearchParams, +): string | null => getSingleParam(params, "app"); + export const resolveInboxPagination = ( params: ResolvedInboxSearchParams, ): Pick => { diff --git a/src/features/inbox/queries.ts b/src/features/inbox/queries.ts index 4cbc3dc..a4fb5cb 100644 --- a/src/features/inbox/queries.ts +++ b/src/features/inbox/queries.ts @@ -39,18 +39,26 @@ export async function fetchInboxItemsPage( { page, pageSize, + sourceApp, }: { page: number; pageSize: number; + sourceApp?: string | null; }, ): Promise<{ items: InboxItem[]; pagination: InboxPaginationState; }> { + const where = and( + eq(inboxItems.userId, userId), + eq(inboxItems.status, status), + sourceApp ? eq(inboxItems.sourceAppName, sourceApp) : undefined, + ); + const [countRow] = await db .select({ total: count() }) .from(inboxItems) - .where(and(eq(inboxItems.userId, userId), eq(inboxItems.status, status))); + .where(where); const totalItems = Number(countRow?.total ?? 0); const totalPages = Math.max(Math.ceil(totalItems / pageSize), 1); @@ -60,7 +68,7 @@ export async function fetchInboxItemsPage( const items = await db .select() .from(inboxItems) - .where(and(eq(inboxItems.userId, userId), eq(inboxItems.status, status))) + .where(where) .orderBy(desc(inboxItems.notificationTimestamp), desc(inboxItems.createdAt)) .limit(pageSize) .offset(offset); @@ -76,6 +84,22 @@ export async function fetchInboxItemsPage( }; } +export async function fetchInboxSourceApps( + userId: string, + status: InboxStatus, +): Promise { + const rows = await db + .select({ name: inboxItems.sourceAppName }) + .from(inboxItems) + .where(and(eq(inboxItems.userId, userId), eq(inboxItems.status, status))); + + const seen = new Set(); + for (const row of rows) { + if (row.name) seen.add(row.name); + } + return [...seen].sort(); +} + export async function fetchInboxStatusCounts( userId: string, ): Promise {