"use client"; import { useState, useEffect } from "react"; import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Heart, Clock, Eye, Star, MapPin, DollarSign, Coins, RefreshCw, ShoppingCart, TrendingUp, User } from "lucide-react"; import { useAdTracking, useMarketTelemetry } from "@/lib/hooks/use-telemetry"; import { useSession } from "@/lib/hooks/use-session"; import { useQueryClient } from "@tanstack/react-query"; import { cn } from "@/lib/utils"; import Image from "next/image"; import { PurchaseModal } from "./PurchaseModal"; import { BidModal } from "./BidModal"; import { SellerInfo } from "./SellerInfo"; import { ListingDetailsModal } from "./ListingDetailsModal"; import { AuctionCountdown } from "./AuctionCountdown"; // Função auxiliar para formatar tempo relativo function formatTimeAgo(date: Date): string { const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000); if (seconds < 60) return "há alguns segundos"; const minutes = Math.floor(seconds / 60); if (minutes < 60) return `há ${minutes} ${minutes === 1 ? "minuto" : "minutos"}`; const hours = Math.floor(minutes / 60); if (hours < 24) return `há ${hours} ${hours === 1 ? "hora" : "horas"}`; const days = Math.floor(hours / 24); if (days < 30) return `há ${days} ${days === 1 ? "dia" : "dias"}`; const months = Math.floor(days / 30); if (months < 12) return `há ${months} ${months === 1 ? "mês" : "meses"}`; const years = Math.floor(months / 12); return `há ${years} ${years === 1 ? "ano" : "anos"}`; } interface Listing { listing_id: number; seller_id: number; item_id: number; // ID legado (SQLite) - mantido para compatibilidade item_name: string; quantity: number; server_name: string; price_pix: number | string | null; // PostgreSQL DECIMAL pode vir como string price_tibia_coins: number | string | null; // PostgreSQL INTEGER pode vir como string price_gold_coins: number | string | null; // PostgreSQL BIGINT pode vir como string is_auction: boolean; description: string | null; listing_status: string; created_at: string; views_count: number; favorites_count: number; is_featured?: boolean; trade_item_id?: number | null; trade_item_name?: string | null; trade_item_quantity?: number | null; // Campos de leilão current_bid_amount?: number | null; min_bid_increment?: number | null; initial_price_pix?: number | null; initial_price_tibia_coins?: number | null; initial_price_gold_coins?: number | null; auction_ends_at?: Date | null; // Campos do JOIN com items (unificado) item_id_unified?: number | null; // ID unificado da tabela items image_local_path?: string | null; image_url?: string | null; item_category?: string | null; // Campo do vendedor seller_character_name?: string | null; } interface PriceInfo { type: "pix" | "tc" | "gp" | "trade"; label: string; value: string; } interface AdCardProps { listing: Listing; viewMode?: "grid" | "list"; } export function AdCard({ listing, viewMode = "grid" }: AdCardProps) { const { user } = useSession(); const queryClient = useQueryClient(); const { trackAdClick, trackFavorite } = useMarketTelemetry(); const [isFavorited, setIsFavorited] = useState(false); const [favoritesCount, setFavoritesCount] = useState(listing.favorites_count || 0); const [showPurchaseModal, setShowPurchaseModal] = useState(false); const [showBidModal, setShowBidModal] = useState(false); const [showDetailsModal, setShowDetailsModal] = useState(false); const invalidateCache = () => { queryClient.invalidateQueries({ queryKey: ["listings"] }); queryClient.invalidateQueries({ queryKey: ["my-listings"] }); }; // Tracking automático de visualização useAdTracking(listing.listing_id); // Verificar se está favoritado useEffect(() => { if (user?.userId && listing.listing_id) { fetch(`/api/listings/${listing.listing_id}/favorite`) .then((res) => { if (res.ok) { return res.json(); } return null; }) .then((data) => { if (data?.success) { setIsFavorited(data.favorited); } }) .catch(() => { // Silenciar erro para não gerar mensagens no console }); } }, [user?.userId, listing.listing_id]); const handleFavorite = async () => { if (!user) return; const action = isFavorited ? "unfavorite" : "favorite"; trackFavorite(listing.listing_id, action); try { const res = await fetch(`/api/listings/${listing.listing_id}/favorite`, { method: "POST", }); if (res.ok) { const data = await res.json(); setIsFavorited(data.favorited); setFavoritesCount((prev) => (data.favorited ? prev + 1 : Math.max(0, prev - 1))); } } catch (error) { console.error("Erro ao favoritar:", error); } }; const handleClick = (e?: React.MouseEvent) => { if (e) { e.stopPropagation(); } trackAdClick(listing.listing_id); setShowDetailsModal(true); }; const handlePause = async () => { try { const res = await fetch(`/api/listings/${listing.listing_id}/pause`, { method: "POST", }); if (res.ok) { setShowDetailsModal(false); invalidateCache(); } } catch (error) { console.error("Erro ao pausar anúncio:", error); } }; const handleActivate = async () => { try { const res = await fetch(`/api/listings/${listing.listing_id}/activate`, { method: "POST", }); if (res.ok) { setShowDetailsModal(false); invalidateCache(); } else { const data = await res.json(); alert(data.error || "Erro ao reativar anúncio"); } } catch (error) { console.error("Erro ao reativar anúncio:", error); alert("Erro ao reativar anúncio"); } }; const handleCancel = async () => { if (!confirm("Tem certeza que deseja cancelar este anúncio?")) { return; } try { const res = await fetch(`/api/listings/${listing.listing_id}`, { method: "DELETE", }); if (res.ok) { setShowDetailsModal(false); invalidateCache(); } else { const data = await res.json(); alert(data.error || "Erro ao cancelar anúncio"); } } catch (error) { console.error("Erro ao cancelar anúncio:", error); alert("Erro ao cancelar anúncio"); } }; const getAvailablePrices = (): PriceInfo[] => { const prices: PriceInfo[] = []; // PIX if (listing.price_pix) { const price = typeof listing.price_pix === 'string' ? parseFloat(listing.price_pix) : listing.price_pix; if (!isNaN(price)) { prices.push({ type: "pix", label: "PIX", value: `R$ ${price.toFixed(2)}`, }); } } // Tibia Coins if (listing.price_tibia_coins) { const price = typeof listing.price_tibia_coins === 'string' ? parseInt(listing.price_tibia_coins, 10) : listing.price_tibia_coins; if (!isNaN(price)) { prices.push({ type: "tc", label: "TC", value: `${price.toLocaleString()} TC`, }); } } // Gold Coins if (listing.price_gold_coins) { const price = typeof listing.price_gold_coins === 'string' ? parseInt(listing.price_gold_coins, 10) : listing.price_gold_coins; if (!isNaN(price)) { prices.push({ type: "gp", label: "GP", value: `${price.toLocaleString()} GP`, }); } } // Item de Troca if (listing.trade_item_id && listing.trade_item_name && listing.trade_item_quantity) { const quantity = listing.trade_item_quantity; prices.push({ type: "trade", label: "Troca", value: `Troca por ${quantity}x ${listing.trade_item_name}`, }); } return prices; }; const timeAgo = formatTimeAgo(new Date(listing.created_at)); const availablePrices = getAvailablePrices(); const isSold = listing.listing_status === "sold"; // Função para obter URL da imagem do item const getItemImageUrl = (): string => { // Prioridade 1: image_url (URL externa) se disponível if (listing.image_url) { return listing.image_url; } // Prioridade 2: image_local_path se disponível if (listing.image_local_path) { const normalizedPath = listing.image_local_path.replace(/\\/g, "/"); if (normalizedPath.startsWith("data/")) { return `/api/images?path=${encodeURIComponent(normalizedPath)}`; } // Construir caminho se necessário const category = listing.item_category || "other"; return `/api/images?path=${encodeURIComponent(`data/images/${category}/${normalizedPath}`)}`; } // Prioridade 3: Usar item_id_unified se disponível (com nome para validação) if (listing.item_id_unified) { const params = new URLSearchParams({ item: listing.item_id_unified.toString() }); if (listing.item_name) { params.set("name", listing.item_name); } return `/api/images?${params.toString()}`; } // Fallback: Usar item_id legado (com nome para validação) const params = new URLSearchParams({ item: listing.item_id.toString() }); if (listing.item_name) { params.set("name", listing.item_name); } return `/api/images?${params.toString()}`; }; const itemImageUrl = getItemImageUrl(); // Função para obter URL da imagem do item de troca const getTradeItemImageUrl = (): string | null => { if (!listing.trade_item_id || !listing.trade_item_name) { return null; } // Usar trade_item_id para buscar imagem const params = new URLSearchParams({ item: listing.trade_item_id.toString() }); params.set("name", listing.trade_item_name); return `/api/images?${params.toString()}`; }; const tradeItemImageUrl = getTradeItemImageUrl(); // Componente para exibir badges de preços em quadrados const PriceBadges = ({ prices, compact = false, tradeItemImageUrl, listing }: { prices: PriceInfo[]; compact?: boolean; tradeItemImageUrl?: string | null; listing?: Listing }) => { if (prices.length === 0) { return
Preço não informado
; } const getBadgeClassName = (type: PriceInfo["type"]) => { switch (type) { case "pix": return "bg-green-100 hover:bg-green-200 dark:bg-green-900/30 dark:hover:bg-green-900/50 text-green-700 dark:text-green-300 border-green-300 dark:border-green-700"; case "tc": return "bg-blue-100 hover:bg-blue-200 dark:bg-blue-900/30 dark:hover:bg-blue-900/50 text-blue-700 dark:text-blue-300 border-blue-300 dark:border-blue-700"; case "gp": return "bg-amber-100 hover:bg-amber-200 dark:bg-amber-900/30 dark:hover:bg-amber-900/50 text-amber-700 dark:text-amber-300 border-amber-300 dark:border-amber-700"; case "trade": return "bg-purple-100 hover:bg-purple-200 dark:bg-purple-900/30 dark:hover:bg-purple-900/50 text-purple-700 dark:text-purple-300 border-purple-300 dark:border-purple-700"; default: return ""; } }; const getIcon = (type: PriceInfo["type"]) => { switch (type) { case "pix": return ; case "tc": case "gp": return ; case "trade": return ; default: return null; } }; // Calcular número de colunas (apenas preços) const totalCols = prices.length; // Ajustar tamanho mínimo para caber todos os itens const colClass = compact ? `flex-1 min-w-0` : `flex-1 min-w-0`; return (
{/* Quadrados de preços individuais */} {prices.map((price, index) => (
{price.type === "trade" ? ( <>
{getIcon(price.type)} Troca
{tradeItemImageUrl && listing && listing.trade_item_name && (
{listing.trade_item_name} { (e.target as HTMLImageElement).src = "/placeholder-item.png"; }} /> {listing.trade_item_quantity && (
{listing.trade_item_quantity}x
)}
)} ) : ( <>
{getIcon(price.type)} {price.label}
{price.value.split(' ')[0]} {price.value.includes(' ') && ( {price.value.split(' ').slice(1).join(' ')} )} )}
))}
); }; if (viewMode === "list") { return ( {isSold && (
)} {/* Título */}

Item a venda

{/* Thumbnails */}
{/* Imagem do item principal */}
{listing.item_name} { (e.target as HTMLImageElement).src = "/placeholder-item.png"; }} />
{/* Conteúdo */}

{listing.item_name}

{listing.seller_character_name && ( )}
{timeAgo}
{isSold && ( Vendido )} {listing.is_featured && ( Destaque )} {listing.is_auction && ( Leilão )}
{listing.description && (

{listing.description}

)}
{/* Preços - mostrar lance atual como principal em leilões */} {listing.is_auction && listing.current_bid_amount ? (
Lance Atual
R$ {typeof listing.current_bid_amount === 'number' ? listing.current_bid_amount.toFixed(2) : parseFloat(String(listing.current_bid_amount)).toFixed(2)}
{listing.initial_price_pix && (
Inicial: R$ {typeof listing.initial_price_pix === 'number' ? listing.initial_price_pix.toFixed(2) : parseFloat(String(listing.initial_price_pix)).toFixed(2)}
)}
) : (
)} {/* Informações de lance se for leilão */} {listing.is_auction && (
{listing.current_bid_amount ? ( <>
{listing.min_bid_increment && (
Incremento Mín R$ {typeof listing.min_bid_increment === 'number' ? listing.min_bid_increment.toFixed(2) : parseFloat(String(listing.min_bid_increment)).toFixed(2)}
)} {listing.auction_ends_at && (
Termina em
)}
Leilão Ativo
) : (
Sem lances ainda
{listing.initial_price_pix && (
Lance inicial: R$ {typeof listing.initial_price_pix === 'number' ? listing.initial_price_pix.toFixed(2) : parseFloat(String(listing.initial_price_pix)).toFixed(2)}
)}
{listing.auction_ends_at && (
)}
Leilão Ativo
)}
)}
{listing.views_count}
Qtd: {listing.quantity}
{/* Botões de ação - desabilitados se vendido */} {user && user.userId !== listing.seller_id && listing.listing_status === "active" && !isSold && (
{listing.is_auction ? ( ) : ( )}
)} {isSold && (
Este item já foi vendido
)}
{/* Modais */} { invalidateCache(); }} /> { invalidateCache(); }} /> ); } // View mode grid (padrão) return ( {isSold && (
)} {listing.is_auction && !isSold && (
)} {/* Título */}

Item a venda

{/* Container de imagens */}
{/* Imagem do item principal */}
{listing.item_name} { (e.target as HTMLImageElement).src = "/placeholder-item.png"; }} />
{/* Badges de status */} {isSold && (
Vendido
)} {listing.is_featured && (
Destaque
)} {listing.is_auction && !isSold && (
Leilão
)}

{listing.item_name}

{listing.seller_character_name && ( )}
{listing.description && (

{listing.description}

)}
{/* Preços - mostrar lance atual como principal em leilões */} {listing.is_auction && listing.current_bid_amount ? (
Lance Atual
R$ {typeof listing.current_bid_amount === 'number' ? listing.current_bid_amount.toFixed(2) : parseFloat(String(listing.current_bid_amount)).toFixed(2)}
{listing.initial_price_pix && (
Preço inicial: R$ {typeof listing.initial_price_pix === 'number' ? listing.initial_price_pix.toFixed(2) : parseFloat(String(listing.initial_price_pix)).toFixed(2)}
)}
) : (
)} {/* Informações de lance se for leilão */} {listing.is_auction && (
{listing.current_bid_amount ? ( <>
{listing.min_bid_increment && (
Incremento Mínimo R$ {typeof listing.min_bid_increment === 'number' ? listing.min_bid_increment.toFixed(2) : parseFloat(String(listing.min_bid_increment)).toFixed(2)}
)} {listing.auction_ends_at && (
Termina em
)}
Leilão Ativo
) : (
Sem lances ainda
{listing.initial_price_pix && (
Lance inicial: R$ {typeof listing.initial_price_pix === 'number' ? listing.initial_price_pix.toFixed(2) : parseFloat(String(listing.initial_price_pix)).toFixed(2)}
)}
{listing.auction_ends_at && (
)}
Leilão Ativo
)}
)}
{timeAgo}
{listing.views_count}
{/* Botão de compra/lance - apenas se não for o vendedor, estiver ativo e não vendido */} {user && user.userId !== listing.seller_id && listing.listing_status === "active" && !isSold && ( <> {listing.is_auction ? ( ) : ( )} )} {isSold && (
Este item já foi vendido
)} {/* Se não for o vendedor, mostrar botão de detalhes também */} {(!user || user.userId === listing.seller_id) && ( )}
{/* Modais */} { // Recarregar página ou atualizar lista window.location.reload(); }} /> { // Recarregar página ou atualizar lista window.location.reload(); }} /> ); }