"use client"; import { RiAlertFill, RiArchiveLine, RiArrowGoBackLine, RiAtLine, RiBankCardLine, RiBarChart2Line, RiCheckLine, RiErrorWarningLine, RiFileListLine, RiInboxUnarchiveLine, RiTimeLine, } from "@remixicon/react"; import Image from "next/image"; import StatusDot from "@/shared/components/status-dot"; import { buttonVariants } from "@/shared/components/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/shared/components/ui/tooltip"; import { resolveLogoSrc } from "@/shared/lib/logo"; import { formatCurrency } from "@/shared/utils/currency"; import { formatDateOnly } from "@/shared/utils/date"; import { formatPercentage } from "@/shared/utils/percentage"; import { cn } from "@/shared/utils/ui"; import type { ResolvedBudgetNotification, ResolvedDashboardNotification, StatefulNotification, } from "./types"; type NotificationBellContentProps = { displayedPreLancamentosCount: number; displayedBudgetNotifications: ResolvedBudgetNotification[]; invoiceNotifications: ResolvedDashboardNotification[]; boletoNotifications: ResolvedDashboardNotification[]; onInboxNavigate: () => void; onNotificationNavigate: (notification: StatefulNotification) => Promise; onToggleRead: (notification: StatefulNotification) => Promise; onToggleArchive: (notification: StatefulNotification) => Promise; }; // --------------------------------------------------------------------------- // Shared helpers // --------------------------------------------------------------------------- function formatDate(dateString: string): string { return ( formatDateOnly(dateString, { day: "2-digit", month: "short" }) ?? dateString ); } function getReadAction(notification: StatefulNotification) { return { label: notification.isRead ? "Marcar como não lida" : "Marcar como lida", icon: notification.isRead ? ( ) : ( ), }; } function getArchiveAction(notification: StatefulNotification) { return { label: notification.isArchived ? "Desarquivar notificação" : "Arquivar notificação", icon: notification.isArchived ? ( ) : ( ), }; } // --------------------------------------------------------------------------- // SectionLabel // --------------------------------------------------------------------------- function SectionLabel({ icon, title, }: { icon: React.ReactNode; title: string; }) { return (
{icon} {title}
); } // --------------------------------------------------------------------------- // Action button // --------------------------------------------------------------------------- function NotificationActionButton({ label, icon, onClick, disabled = false, }: { label: string; icon: React.ReactNode; onClick?: () => void | Promise; disabled?: boolean; }) { return ( {label} ); } // --------------------------------------------------------------------------- // NotificationItem // --------------------------------------------------------------------------- type NotificationItemProps = { icon: React.ReactNode; isOverdue: boolean; isRead?: boolean; isArchived?: boolean; isBusy?: boolean; showUnreadIndicator?: boolean; title: string; detail: string; onNavigate: () => void | Promise; onToggleRead?: () => void | Promise; onToggleArchive?: () => void | Promise; notification?: StatefulNotification; }; function NotificationItem({ icon, isOverdue, isRead = false, isArchived = false, isBusy = false, showUnreadIndicator = false, title, detail, onNavigate, onToggleRead, onToggleArchive, notification, }: NotificationItemProps) { const readAction = notification ? getReadAction(notification) : null; const archiveAction = notification ? getArchiveAction(notification) : null; return (
{(readAction || archiveAction) && (
{readAction && onToggleRead && ( )} {archiveAction && onToggleArchive && ( )}
)}
); } // --------------------------------------------------------------------------- // NotificationSection — generic wrapper to eliminate per-type repetition // --------------------------------------------------------------------------- type NotificationSectionProps< T extends StatefulNotification & { isBusy: boolean }, > = { icon: React.ReactNode; title: string; items: T[]; renderIcon: (item: T) => React.ReactNode; renderTitle: (item: T) => string; renderDetail: (item: T) => string; isOverdue: (item: T) => boolean; showUnreadIndicator?: boolean; onNavigate: (item: T) => void | Promise; onToggleRead: (item: T) => void | Promise; onToggleArchive: (item: T) => void | Promise; }; function NotificationSection< T extends StatefulNotification & { isBusy: boolean }, >({ icon, title, items, renderIcon, renderTitle, renderDetail, isOverdue, showUnreadIndicator = false, onNavigate, onToggleRead, onToggleArchive, }: NotificationSectionProps) { if (items.length === 0) return null; return (
{items.map((item) => ( onNavigate(item)} onToggleRead={() => onToggleRead(item)} onToggleArchive={() => onToggleArchive(item)} notification={item} /> ))}
); } // --------------------------------------------------------------------------- // Icon helpers (consistent between invoice / boleto) // --------------------------------------------------------------------------- function DueDateIcon({ isOverdue }: { isOverdue: boolean }) { return isOverdue ? ( ) : ( ); } function InvoiceIcon({ cardLogo, isOverdue, }: { cardLogo?: string | null; isOverdue: boolean; }) { const logo = resolveLogoSrc(cardLogo); if (logo) { return ( ); } return ; } function formatDueDateDetail( status: string, dueDate: string, amount: number, showAmount: boolean, ) { const verb = status === "overdue" ? "Venceu em" : "Vence em"; const amountStr = showAmount && amount > 0 ? ` — ${formatCurrency(amount)}` : ""; return `${verb} ${formatDate(dueDate)}${amountStr}`; } // --------------------------------------------------------------------------- // Main export // --------------------------------------------------------------------------- export function NotificationBellContent({ displayedPreLancamentosCount, displayedBudgetNotifications, invoiceNotifications, boletoNotifications, onInboxNavigate, onNotificationNavigate, onToggleRead, onToggleArchive, }: NotificationBellContentProps) { return (
{displayedPreLancamentosCount > 0 && (
} title="Pré-lançamentos" /> } isOverdue={false} title={ displayedPreLancamentosCount === 1 ? "1 pré-lançamento pendente" : `${displayedPreLancamentosCount} pré-lançamentos pendentes` } detail="Aguardando revisão" onNavigate={onInboxNavigate} />
)} } title="Orçamentos" items={displayedBudgetNotifications} isOverdue={(n) => n.status === "exceeded"} showUnreadIndicator renderIcon={(n) => n.status === "exceeded" ? ( ) : ( ) } renderTitle={(n) => n.categoryName} renderDetail={(n) => n.status === "exceeded" ? `Excedido — ${formatCurrency(n.spentAmount)} de ${formatCurrency(n.budgetAmount)} (${formatPercentage(n.usedPercentage, { maximumFractionDigits: 0, minimumFractionDigits: 0 })})` : `${formatPercentage(n.usedPercentage, { maximumFractionDigits: 0, minimumFractionDigits: 0 })} utilizado — ${formatCurrency(n.spentAmount)} de ${formatCurrency(n.budgetAmount)}` } onNavigate={(n) => onNotificationNavigate(n)} onToggleRead={(n) => onToggleRead(n)} onToggleArchive={(n) => onToggleArchive(n)} /> } title="Cartão de Crédito" items={invoiceNotifications} isOverdue={(n) => n.status === "overdue"} showUnreadIndicator renderIcon={(n) => ( )} renderTitle={(n) => n.name} renderDetail={(n) => formatDueDateDetail(n.status, n.dueDate, n.amount, n.showAmount) } onNavigate={(n) => onNotificationNavigate(n)} onToggleRead={(n) => onToggleRead(n)} onToggleArchive={(n) => onToggleArchive(n)} /> } title="Boletos" items={boletoNotifications} isOverdue={(n) => n.status === "overdue"} showUnreadIndicator renderIcon={(n) => } renderTitle={(n) => n.name} renderDetail={(n) => formatDueDateDetail(n.status, n.dueDate, n.amount, true) } onNavigate={(n) => onNotificationNavigate(n)} onToggleRead={(n) => onToggleRead(n)} onToggleArchive={(n) => onToggleArchive(n)} />
); }