"use client"; import { RiArrowGoBackLine, RiCheckLine, RiDeleteBinLine, RiFileList2Line, RiMoreLine, } from "@remixicon/react"; import { format, formatDistanceToNow } from "date-fns"; import { ptBR } from "date-fns/locale"; import Image from "next/image"; import { useMemo } from "react"; import MoneyValues from "@/components/money-values"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardAction, CardContent, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import type { InboxItem } from "./types"; interface InboxCardProps { item: InboxItem; readonly?: boolean; appLogoMap?: Record; onProcess?: (item: InboxItem) => void; onDiscard?: (item: InboxItem) => void; onViewDetails?: (item: InboxItem) => void; onDelete?: (item: InboxItem) => void; onRestoreToPending?: (item: InboxItem) => void | Promise; } function resolveLogoPath(logo: string): string { if ( logo.startsWith("http") || logo.startsWith("data:") || logo.startsWith("/") ) { return logo; } return `/logos/${logo}`; } function findMatchingLogo( sourceAppName: string | null, appLogoMap: Record, ): string | null { if (!sourceAppName) return null; const appName = sourceAppName.toLowerCase(); // Exact match first if (appLogoMap[appName]) return resolveLogoPath(appLogoMap[appName]); // Partial match: card/account name contains app name or vice versa for (const [name, logo] of Object.entries(appLogoMap)) { if (name.includes(appName) || appName.includes(name)) { return resolveLogoPath(logo); } } return null; } export function InboxCard({ item, readonly, appLogoMap, onProcess, onDiscard, onViewDetails, onDelete, onRestoreToPending, }: InboxCardProps) { const matchedLogo = useMemo( () => appLogoMap ? findMatchingLogo(item.sourceAppName, appLogoMap) : null, [item.sourceAppName, appLogoMap], ); const amount = item.parsedAmount ? parseFloat(item.parsedAmount) : null; // O timestamp vem do app Android em horário local mas salvo como UTC // Precisamos interpretar o valor UTC como se fosse horário de Brasília const rawDate = new Date(item.notificationTimestamp); // Ajusta adicionando o offset de Brasília (3 horas) para corrigir o cálculo do "há X tempo" const BRASILIA_OFFSET_MS = 3 * 60 * 60 * 1000; const notificationDate = new Date(rawDate.getTime() + BRASILIA_OFFSET_MS); const timeAgo = formatDistanceToNow(notificationDate, { addSuffix: true, locale: ptBR, }); // Para exibição, usa UTC pois o valor já representa horário de Brasília const _formattedTime = new Intl.DateTimeFormat("pt-BR", { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit", timeZone: "UTC", }).format(rawDate); const statusDate = item.status === "processed" ? item.processedAt : item.status === "discarded" ? item.discardedAt : null; const formattedStatusDate = statusDate ? format(new Date(statusDate), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }) : null; return ( {/* Header com app e valor */}
{matchedLogo && ( )} {item.sourceAppName || item.sourceApp} {" "} {timeAgo} {amount !== null && ( )}
{!readonly && ( onViewDetails?.(item)}> Ver detalhes onProcess?.(item)}> Processar onDiscard?.(item)} className="text-destructive" > Descartar )}
{/* Conteúdo da notificação */} {item.originalTitle && (

{item.originalTitle}

)}

{item.originalText}

{/* Botões de ação ou badge de status */} {readonly ? ( {item.status === "processed" ? "Processado" : "Descartado"} {formattedStatusDate && ( {formattedStatusDate} )}
{item.status === "discarded" && onRestoreToPending && ( )} {onDelete && ( )}
) : ( )}
); }