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 { 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}

{av.currency} {av.price.toLocaleString()}
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 && ( initiateStatusChange(av.id, newStatus)} /> )} {/* 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}
); }