"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 Image from "next/image"; import { PurchaseModal } from "./PurchaseModal"; import { BidModal } from "./BidModal"; import { SellerInfo } from "./SellerInfo"; import { ListingDetailsModal } from "./ListingDetailsModal"; // 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(); // Componente para exibir badges de preços const PriceBadges = ({ prices, compact = false }: { prices: PriceInfo[]; compact?: boolean }) => { if (prices.length === 0) { return
{listing.description}
)}{listing.description}
)}