import { useState, useEffect, useRef } from "react"; import { Link } from "react-router-dom"; import { AdsV2Service, type AdListingDto } from "../services/ads.v2.service"; import { useAuth } from "../context/AuthContext"; import { ChatService, type ChatMessage } from "../services/chat.service"; import ChatModal from "../components/ChatModal"; import { formatCurrency, getImageUrl, parseUTCDate } from "../utils/app.utils"; import { AD_STATUSES, STATUS_CONFIG } from "../constants/adStatuses"; import ConfirmationModal from "../components/ConfirmationModal"; type TabType = "avisos" | "favoritos" | "mensajes"; export default function MisAvisosPage() { const [activeTab, setActiveTab] = useState("avisos"); const [avisos, setAvisos] = useState([]); const [favoritos, setFavoritos] = useState([]); const [mensajes, setMensajes] = useState([]); const [loading, setLoading] = useState(false); const { user, fetchUnreadCount } = useAuth(); const [selectedChat, setSelectedChat] = useState<{ adId: number; name: string; otherUserId: number; } | null>(null); const [modalConfig, setModalConfig] = useState<{ isOpen: boolean; title: string; message: string; adId: number | null; newStatus: number | null; isDanger: boolean; }>({ isOpen: false, title: "", message: "", adId: null, newStatus: null, isDanger: false, }); // Función para forzar chequeo manual desde Gestión const handleVerifyPayment = async (adId: number) => { try { const res = await AdsV2Service.checkPaymentStatus(adId); if (res.status === "approved") { alert("¡Pago confirmado! El aviso pasará a moderación."); cargarAvisos(user!.id); } else if (res.status === "rejected") { alert("El pago fue rechazado. Puedes intentar pagar nuevamente."); cargarAvisos(user!.id); // Debería volver a estado Draft/1 } else { alert("El pago sigue pendiente de aprobación por la tarjeta."); } } catch (e) { alert("Error verificando el pago."); } }; useEffect(() => { if (user) { cargarMensajes(user.id); if (activeTab === "avisos") cargarAvisos(user.id); else if (activeTab === "favoritos") cargarFavoritos(user.id); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [user?.id, activeTab]); const initiateStatusChange = (adId: number, newStatus: number) => { let title = "Cambiar Estado"; let message = "¿Estás seguro de realizar esta acción?"; let isDanger = false; // 1. ELIMINAR if (newStatus === AD_STATUSES.DELETED) { title = "¿Eliminar Aviso?"; message = "Esta acción eliminará el aviso permanentemente. No se puede deshacer.\n\n¿Estás seguro de continuar?"; isDanger = true; } // 2. PAUSAR else if (newStatus === AD_STATUSES.PAUSED) { title = "Pausar Publicación"; message = "Al pausar el aviso:\n\n• Dejará de ser visible en los listados.\n• Los usuarios NO podrán contactarte.\n\nPodrás reactivarlo cuando quieras, dentro de la vigencia de publicación."; } // 3. VENDIDO else if (newStatus === AD_STATUSES.SOLD) { title = "¡Felicitaciones!"; message = 'Al marcar como VENDIDO:\n\n• Se deshabilitarán nuevas consultas.\n• El aviso mostrará la etiqueta "Vendido" al público.\n\n¿Confirmas que ya vendiste el vehículo?'; } // 4. RESERVADO else if (newStatus === AD_STATUSES.RESERVED) { title = "Reservar Vehículo"; message = "Al reservar el aviso:\n\n• Se indicará a los interesados que el vehículo está reservado.\n• Se bloquearán nuevos contactos hasta que lo actives o vendas.\n\n¿Deseas continuar?"; } // 5. ACTIVAR (Desde Pausado/Reservado) else if (newStatus === AD_STATUSES.ACTIVE) { title = "Reactivar Aviso"; message = "El aviso volverá a estar visible para todos y recibirás consultas nuevamente."; } setModalConfig({ isOpen: true, title, message, adId, newStatus, isDanger, }); }; // Acción real al confirmar en el modal const confirmStatusChange = async () => { const { adId, newStatus } = modalConfig; if (!adId || !newStatus) return; try { setModalConfig({ ...modalConfig, isOpen: false }); // Cerrar modal primero await AdsV2Service.changeStatus(adId, newStatus); if (user) cargarAvisos(user.id); } catch (error) { alert("Error al actualizar estado"); } }; const cargarAvisos = async (userId: number) => { setLoading(true); try { const data = await AdsV2Service.getAll({ userId }); setAvisos(data); } catch (error) { console.error(error); } finally { setLoading(false); } }; const cargarFavoritos = async (userId: number) => { setLoading(true); try { const data = await AdsV2Service.getFavorites(userId); setFavoritos(data); } catch (error) { console.error(error); } finally { setLoading(false); } }; const cargarMensajes = async (userId: number) => { try { const data = await ChatService.getInbox(userId); setMensajes(data); } catch (error) { console.error(error); } }; // Marcar como leídos en DB const openChatForAd = async (adId: number, adTitle: string) => { if (!user) return; const relatedMsg = mensajes.find((m) => m.adID === adId); if (relatedMsg) { const otherId = relatedMsg.senderID === user.id ? relatedMsg.receiverID : relatedMsg.senderID; // Identificar mensajes no leídos para este chat const unreadMessages = mensajes.filter( (m) => m.adID === adId && !m.isRead && m.receiverID === user.id, ); if (unreadMessages.length > 0) { // Optimización visual: actualiza la UI localmente de inmediato setMensajes((prev) => prev.map((m) => unreadMessages.some((um) => um.messageID === m.messageID) ? { ...m, isRead: true } : m, ), ); try { // Crea un array de promesas para todas las llamadas a la API const markAsReadPromises = unreadMessages.map((m) => m.messageID ? ChatService.markAsRead(m.messageID) : Promise.resolve(), ); // Espera a que TODAS las llamadas al backend terminen await Promise.all(markAsReadPromises); // SOLO DESPUÉS de que el backend confirme, actualizamos el contador global await fetchUnreadCount(); } catch (error) { console.error("Error al marcar mensajes como leídos:", error); // Opcional: podrías revertir el estado local si la API falla } } // Abrir el modal de chat setSelectedChat({ adId, name: adTitle, otherUserId: otherId }); } else { alert("No tienes mensajes activos para este aviso."); } }; const handleRemoveFavorite = async (adId: number) => { if (!user) return; try { await AdsV2Service.removeFavorite(user.id, adId); cargarFavoritos(user.id); } catch (error) { console.error(error); } }; const handleCloseChat = () => { setSelectedChat(null); if (user) { cargarMensajes(user.id); // Recarga la lista de mensajes por si llegaron nuevos mientras estaba abierto } }; if (!user) { return (
🔒

Área Privada

Para gestionar tus publicaciones, primero debes iniciar sesión.

Identificarse
); } const totalVisitas = avisos.reduce( (acc, curr) => acc + (curr.viewsCounter || 0), 0, ); const avisosActivos = avisos.filter((a) => a.statusId === 4).length; return (

Mis Avisos

{user.username.charAt(0).toUpperCase()}
{user.firstName} {user.lastName} {user.email}
{(["avisos", "favoritos", "mensajes"] as TabType[]).map((tab) => ( ))}
{activeTab === "avisos" && (
)} {loading ? (
) : ( <> {activeTab === "avisos" && (
{avisos.filter((a) => a.statusId !== 9).length === 0 ? (
📂

No tienes avisos

Crear mi primer aviso
) : ( avisos .filter((a) => a.statusId !== 9) .map((av, index) => { const hasMessages = mensajes.some( (m) => m.adID === av.id, ); const hasUnread = mensajes.some( (m) => m.adID === av.id && !m.isRead && m.receiverID === user.id, ); return ( // 'relative z-index' dinámico // Esto permite que el dropdown se salga de la tarjeta sin cortarse. // Usamos un z-index decreciente para que los dropdowns de arriba tapen a las tarjetas de abajo.
{`${av.brandName}
#{av.id}

{av.brandName} {av.versionName}

{formatCurrency(av.price, av.currency)}
Año {av.year}
Visitas {av.viewsCounter || 0}
{av.isFeatured && (
⭐ Destacado
)}
{/* CASO 1: BORRADOR (1) -> Botón de Pagar */} {av.statusId === AD_STATUSES.DRAFT && ( Continuar Pago ➔ )} {/* CASO 2: PAGO PENDIENTE (2) -> Botón de Verificar */} {av.statusId === AD_STATUSES.PAYMENT_PENDING && (
⏳ Pago Pendiente
)} {/* CASO 3: EN REVISIÓN (3) -> Cartel informativo */} {av.statusId === AD_STATUSES.MODERATION_PENDING && (
⏳ En Revisión No editable
)} {/* CASO 4: VENCIDO (8) -> Botón de Republicar */} {av.statusId === AD_STATUSES.EXPIRED && (
⛔ Finalizado
🔄 Republicar
)} {/* CASO 5: ACTIVOS/PAUSADOS/OTROS (StatusDropdown) */} {av.statusId !== AD_STATUSES.DRAFT && av.statusId !== AD_STATUSES.PAYMENT_PENDING && av.statusId !== AD_STATUSES.MODERATION_PENDING && av.statusId !== AD_STATUSES.EXPIRED && av.statusId !== AD_STATUSES.REJECTED && ( initiateStatusChange(av.id, newStatus) } /> )} {/* --- NUEVO: CASO 6: RECHAZADO --- */} {av.statusId === AD_STATUSES.REJECTED && (
❌ Aviso Rechazado Revisa los motivos en tu email y edita para corregir
✏️ Corregir Aviso
)} {/* BOTONES COMUNES (Siempre visibles) */}
👁️ Ver Detalle {hasMessages && ( )}
); }) )}
)} {activeTab === "favoritos" && (
{favoritos.length === 0 ? (

No tienes favoritos

Explorar vehículos
) : ( favoritos.map((fav) => (
{`${fav.brandName}

{fav.brandName} {fav.versionName}

{fav.currency} {fav.price.toLocaleString()}

Ver Detalle
)) )}
)} {activeTab === "mensajes" && (
{mensajes.length === 0 ? (
💬

No tienes mensajes

Los moderadores te contactarán por aquí si es necesario.

) : ( Object.values( mensajes.reduce((acc: any, curr) => { const key = curr.adID; if (!acc[key]) acc[key] = { msg: curr, count: 0, unread: false }; acc[key].count++; if (!curr.isRead && curr.receiverID === user.id) acc[key].unread = true; if ( new Date(curr.sentAt!) > new Date(acc[key].msg.sentAt!) ) acc[key].msg = curr; return acc; }, {}), ).map((item: any) => { const aviso = avisos.find((a) => a.id === item.msg.adID); const tituloAviso = aviso ? `${aviso.brandName} ${aviso.versionName}` : `Aviso #${item.msg.adID}`; return (
openChatForAd(item.msg.adID, tituloAviso) } className="glass p-6 rounded-2xl flex items-center gap-6 border border-white/5 hover:border-blue-500/30 transition-all cursor-pointer group" >
🛡️

{tituloAviso}

{parseUTCDate( item.msg.sentAt!, ).toLocaleDateString("es-AR", { timeZone: "America/Argentina/Buenos_Aires", hour12: false, })}

{item.msg.senderID === user.id ? "Tú: " : ""} {item.msg.messageText}

{item.unread && (
)}
); }) )}
)} )}
{selectedChat && user && ( )} {/* MODAL DE CONFIRMACIÓN */} setModalConfig({ ...modalConfig, isOpen: false })} isDanger={modalConfig.isDanger} confirmText={ modalConfig.newStatus === AD_STATUSES.SOLD ? "¡Sí, vendido!" : "Confirmar" } />
); } // DROPDOWN DE ESTADO function StatusDropdown({ currentStatus, onChange, }: { currentStatus: number; onChange: (val: number) => void; }) { const [isOpen, setIsOpen] = useState(false); const wrapperRef = useRef(null); // Fallback seguro si currentStatus no tiene config const currentConfig = STATUS_CONFIG[currentStatus] || { label: "Desconocido", color: "text-gray-400", bg: "bg-gray-500/10", border: "border-gray-500/20", icon: "❓", }; const ALLOWED_STATUSES = [ AD_STATUSES.ACTIVE, AD_STATUSES.PAUSED, AD_STATUSES.RESERVED, AD_STATUSES.SOLD, AD_STATUSES.DELETED, ]; useEffect(() => { function handleClickOutside(event: MouseEvent) { if ( wrapperRef.current && !wrapperRef.current.contains(event.target as Node) ) { setIsOpen(false); } } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); return (
{isOpen && (
{ALLOWED_STATUSES.map((statusId) => { const config = STATUS_CONFIG[statusId]; // PROTECCIÓN CONTRA EL ERROR DE "UNDEFINED" if (!config) return null; return ( ); })}
)}
); } function MetricCard({ label, value, icon, }: { label: string; value: any; icon: string; }) { return (
{icon}
{value.toLocaleString()} {label}
); }