"use client"; import { useQueryClient } from "@tanstack/react-query"; import { AlertCircle, CheckCircle2, Clock, Eye, Heart, MapPin, Pause, ShoppingBag, Star, Trash2, TrendingUp, X, } from "lucide-react"; import Image from "next/image"; import { useEffect, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { useSession } from "@/lib/hooks/use-session"; interface Listing { listing_id: number; seller_id: number; item_id: number; item_name: string; quantity: number; server_name: string; price_pix: number | string | null; price_tibia_coins: number | string | null; price_gold_coins: number | string | null; 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; 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; item_id_unified?: number | null; image_local_path?: string | null; image_url?: string | null; item_category?: string | null; seller_character_name?: string | null; highest_custom_offer_amount?: number | string | null; highest_custom_offer_currency?: string | null; } interface ListingDetailsModalProps { listing: Listing; open: boolean; onOpenChange: (open: boolean) => void; onPause?: () => void; onActivate?: () => void; onCancel?: () => void; } function formatTimeAgo(date: Date | string): string { const dateObj = typeof date === "string" ? new Date(date) : date; const seconds = Math.floor((Date.now() - dateObj.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"}`; } function getStatusBadge(status: string) { const statusConfig = { active: { label: "Ativo", variant: "default" as const, icon: CheckCircle2, className: "bg-green-500 hover:bg-green-600", }, sold: { label: "Vendido", variant: "secondary" as const, icon: CheckCircle2, className: "bg-blue-500 hover:bg-blue-600 text-white", }, cancelled: { label: "Cancelado", variant: "destructive" as const, icon: X, className: "bg-red-500 hover:bg-red-600 text-white", }, in_escrow: { label: "Em Escrow", variant: "default" as const, icon: AlertCircle, className: "bg-yellow-500 hover:bg-yellow-600 text-white", }, expired: { label: "Expirado", variant: "secondary" as const, icon: Clock, className: "bg-gray-500 hover:bg-gray-600 text-white", }, }; const config = statusConfig[status as keyof typeof statusConfig] || statusConfig.active; const Icon = config.icon; return ( {config.label} ); } export function ListingDetailsModal({ listing, open, onOpenChange, onPause, onActivate, onCancel, }: ListingDetailsModalProps) { const { user } = useSession(); const queryClient = useQueryClient(); const [itemImageUrl, setItemImageUrl] = useState(""); const [showMarkSoldDialog, setShowMarkSoldDialog] = useState(false); const [markingSold, setMarkingSold] = useState(false); // Verificar se o usuário é o dono do anúncio const isOwner = user && user.userId === listing.seller_id; useEffect(() => { // Construir URL da imagem if (listing.image_url) { setItemImageUrl(listing.image_url); } else if (listing.image_local_path) { const normalizedPath = listing.image_local_path.replace(/\\/g, "/"); if (normalizedPath.startsWith("data/")) { setItemImageUrl( `/api/images?path=${encodeURIComponent(normalizedPath)}`, ); } else { const category = listing.item_category || "other"; setItemImageUrl( `/api/images?path=${encodeURIComponent(`data/images/${category}/${normalizedPath}`)}`, ); } } else 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); } setItemImageUrl(`/api/images?${params.toString()}`); } else { const params = new URLSearchParams({ item: listing.item_id.toString() }); if (listing.item_name) { params.set("name", listing.item_name); } setItemImageUrl(`/api/images?${params.toString()}`); } }, [listing]); const getAvailablePrices = () => { const prices: Array<{ type: string; label: string; value: string }> = []; if (listing.price_pix) { const price = typeof listing.price_pix === "string" ? parseFloat(listing.price_pix) : listing.price_pix; if (!Number.isNaN(price)) { prices.push({ type: "pix", label: "PIX", value: `R$ ${price.toFixed(2)}`, }); } } if (listing.price_tibia_coins) { const price = typeof listing.price_tibia_coins === "string" ? parseInt(listing.price_tibia_coins, 10) : listing.price_tibia_coins; if (!Number.isNaN(price)) { prices.push({ type: "tibia_coins", label: "Tibia Coins", value: `${price.toLocaleString()} TC`, }); } } if (listing.price_gold_coins) { const price = typeof listing.price_gold_coins === "string" ? parseInt(listing.price_gold_coins, 10) : listing.price_gold_coins; if (!Number.isNaN(price)) { prices.push({ type: "gold_coins", label: "Gold Coins", value: `${price.toLocaleString()} GP`, }); } } if ( listing.trade_item_id && listing.trade_item_name && listing.trade_item_quantity ) { prices.push({ type: "trade", label: "Troca", value: `${listing.trade_item_quantity}x ${listing.trade_item_name}`, }); } return prices; }; const prices = getAvailablePrices(); const highestOfferLabel = (() => { if ( listing.highest_custom_offer_amount === null || listing.highest_custom_offer_amount === undefined || !listing.highest_custom_offer_currency ) { return null; } const amount = typeof listing.highest_custom_offer_amount === "string" ? Number(listing.highest_custom_offer_amount) : listing.highest_custom_offer_amount; if (Number.isNaN(amount)) { return null; } switch (listing.highest_custom_offer_currency) { case "pix": return `Maior oferta registrada: R$ ${amount.toFixed(2)}`; case "tibia_coins": return `Maior oferta registrada: ${Math.round(amount).toLocaleString()} TC`; case "gold_coins": return `Maior oferta registrada: ${Math.round(amount).toLocaleString()} GP`; default: return `Maior oferta registrada: ${amount}`; } })(); // Apenas o dono pode realizar ações const canPause = isOwner && listing.listing_status === "active"; const canActivate = isOwner && listing.listing_status === "cancelled"; const canCancel = isOwner && listing.listing_status === "active"; const canMarkAsSold = isOwner && (listing.listing_status === "active" || listing.listing_status === "in_escrow"); const handleMarkAsSold = async () => { setMarkingSold(true); try { const res = await fetch(`/api/listings/${listing.listing_id}/mark-sold`, { method: "POST", }); if (!res.ok) { const data = await res.json(); throw new Error(data.error || "Erro ao marcar como vendido"); } // Invalidar cache para atualizar a lista queryClient.invalidateQueries({ queryKey: ["listings"] }); queryClient.invalidateQueries({ queryKey: ["my-listings"] }); setShowMarkSoldDialog(false); onOpenChange(false); } catch (error) { console.error("Erro ao marcar como vendido:", error); alert( error instanceof Error ? error.message : "Erro ao marcar como vendido", ); } finally { setMarkingSold(false); } }; return ( Detalhes do Anúncio {getStatusBadge(listing.listing_status)} Informações completas do seu anúncio
{/* Imagem e informações principais */}
{/* Imagem do item */}
{listing.item_name} { (e.target as HTMLImageElement).src = "/placeholder-item.png"; }} />
{/* Informações do item */}

{listing.item_name}

Servidor: {listing.server_name}
Quantidade: {listing.quantity}
{/* Estatísticas */}
Visualizações
{listing.views_count}
Favoritos
{listing.favorites_count || 0}
Criado
{formatTimeAgo(listing.created_at)}
{/* Badges */}
{listing.is_featured && ( Destaque )} {listing.is_auction && ( Leilão )}
{/* Preços */}

Preços

{prices.map((price, index) => (
{price.label}
{price.value}
))}
{highestOfferLabel && (
{highestOfferLabel}
)}
{/* Informações de leilão */} {listing.is_auction && (

Informações do Leilão

{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.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: {new Date(listing.auction_ends_at).toLocaleString( "pt-BR", )}
)}
) : (
Sem lances ainda
)}
)} {/* Descrição */} {listing.description && (

Descrição

{listing.description}

)} {/* Informações adicionais */}
ID do Anúncio: {listing.listing_id}
{listing.item_id_unified && (
ID do Item (Unificado): {listing.item_id_unified}
)}
Criado em: {new Date(listing.created_at).toLocaleString("pt-BR")}
{/* Ações */} {(canPause || canActivate || canCancel || canMarkAsSold) && (
{canMarkAsSold && ( )} {canPause && ( )} {canActivate && onActivate && ( )} {canCancel && ( )}
)}
{/* Dialog de confirmação para marcar como vendido */} Marcar anúncio como vendido? Ao marcar este anúncio como vendido, as seguintes ações serão executadas:
  • O anúncio será marcado como "Vendido"
  • O anúncio continuará visível na lista por mais 1 hora
  • Após 1 hora, o anúncio sairá automaticamente da lista de anúncios ativos
  • Nenhuma interação (comprar, dar lance) será permitida

Tem certeza que deseja marcar este anúncio como vendido?

); }