130 lines
5.7 KiB
TypeScript
130 lines
5.7 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import api from '../../services/api';
|
|
import { Check, X, Printer, Globe, MessageSquare } from 'lucide-react';
|
|
|
|
interface Listing {
|
|
id: number;
|
|
title: string;
|
|
description: string;
|
|
price: number;
|
|
currency: string;
|
|
createdAt: string;
|
|
status: string;
|
|
printText: string;
|
|
isBold: boolean;
|
|
isFrame: boolean;
|
|
printDaysCount: number;
|
|
}
|
|
|
|
export default function ModerationPage() {
|
|
const [listings, setListings] = useState<Listing[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => { loadPending(); }, []);
|
|
|
|
const loadPending = async () => {
|
|
setLoading(true);
|
|
try {
|
|
// Este endpoint ya lo tenemos en el ListingsController que hicimos al inicio
|
|
const res = await api.get('/listings/pending');
|
|
setListings(res.data);
|
|
} catch (error) { console.error(error); }
|
|
finally { setLoading(false); }
|
|
};
|
|
|
|
const handleAction = async (id: number, action: 'Published' | 'Rejected') => {
|
|
try {
|
|
await api.put(`/listings/${id}/status`, JSON.stringify(action), {
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
setListings(listings.filter(l => l.id !== id));
|
|
} catch (e) { alert("Error al procesar el aviso"); }
|
|
};
|
|
|
|
if (loading) return <div className="p-10 text-center text-gray-500">Cargando avisos para revisar...</div>;
|
|
|
|
return (
|
|
<div className="max-w-6xl mx-auto">
|
|
<div className="mb-6">
|
|
<h2 className="text-2xl font-bold text-gray-800">Panel de Moderación</h2>
|
|
<p className="text-gray-500">Revisión de avisos entrantes para Web y Diario Papel</p>
|
|
</div>
|
|
|
|
{listings.length === 0 ? (
|
|
<div className="bg-white p-12 rounded-xl border-2 border-dashed text-center">
|
|
<Check className="mx-auto text-green-500 mb-4" size={48} />
|
|
<h3 className="text-lg font-bold text-gray-700">¡Bandeja vacía!</h3>
|
|
<p className="text-gray-500">No hay avisos pendientes de moderación en este momento.</p>
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 gap-6">
|
|
{listings.map(listing => (
|
|
<div key={listing.id} className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden flex flex-col md:flex-row">
|
|
|
|
{/* Info del Aviso */}
|
|
<div className="flex-1 p-6">
|
|
<div className="flex justify-between items-start mb-4">
|
|
<div>
|
|
<span className="text-xs font-bold text-blue-600 bg-blue-50 px-2 py-1 rounded uppercase tracking-wider">
|
|
ID: #{listing.id}
|
|
</span>
|
|
<h3 className="text-xl font-bold text-gray-900 mt-1">{listing.title}</h3>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="text-lg font-black text-gray-900">{listing.currency} ${listing.price.toLocaleString()}</p>
|
|
<p className="text-xs text-gray-400">{new Date(listing.createdAt).toLocaleString()}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
{/* Versión Web */}
|
|
<div className="bg-gray-50 p-4 rounded-lg border border-gray-100">
|
|
<div className="flex items-center gap-2 mb-2 text-gray-700 font-bold text-sm">
|
|
<Globe size={16} /> VERSIÓN WEB / MOBILE
|
|
</div>
|
|
<p className="text-sm text-gray-600 italic line-clamp-3">"{listing.description}"</p>
|
|
</div>
|
|
|
|
{/* Versión Impresa - CRÍTICO */}
|
|
<div className={`p-4 rounded-lg border-2 ${listing.isFrame ? 'border-black' : 'border-gray-200'} bg-white`}>
|
|
<div className="flex items-center justify-between mb-2">
|
|
<div className="flex items-center gap-2 text-gray-700 font-bold text-sm">
|
|
<Printer size={16} /> VERSIÓN IMPRESA
|
|
</div>
|
|
<span className="text-[10px] bg-gray-100 px-2 py-0.5 rounded font-bold">
|
|
{listing.printDaysCount} DÍAS
|
|
</span>
|
|
</div>
|
|
<div className={`text-sm p-2 bg-gray-50 rounded border border-dashed border-gray-300 ${listing.isBold ? 'font-bold' : ''}`}>
|
|
{listing.printText || "Sin texto de impresión definido"}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Acciones Laterales */}
|
|
<div className="bg-gray-50 border-t md:border-t-0 md:border-l border-gray-200 p-6 flex flex-row md:flex-col gap-3 justify-center min-w-[200px]">
|
|
<button
|
|
onClick={() => handleAction(listing.id, 'Published')}
|
|
className="flex-1 bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-4 rounded-lg flex items-center justify-center gap-2 transition shadow-sm"
|
|
>
|
|
<Check size={20} /> APROBAR
|
|
</button>
|
|
<button
|
|
onClick={() => handleAction(listing.id, 'Rejected')}
|
|
className="flex-1 bg-white hover:bg-red-50 text-red-600 border border-red-200 font-bold py-3 px-4 rounded-lg flex items-center justify-center gap-2 transition"
|
|
>
|
|
<X size={20} /> RECHAZAR
|
|
</button>
|
|
<button className="p-3 text-gray-400 hover:text-gray-600 flex items-center justify-center gap-2 text-xs font-medium">
|
|
<MessageSquare size={16} /> Enviar Nota
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
} |