715 lines
30 KiB
TypeScript
715 lines
30 KiB
TypeScript
'use client';
|
||
|
||
import React, { useState, useEffect } from 'react';
|
||
import Link from 'next/link';
|
||
import {
|
||
Radio,
|
||
Sparkles,
|
||
RefreshCw,
|
||
Search,
|
||
CheckCircle2,
|
||
XCircle,
|
||
Plus,
|
||
Send,
|
||
Layers,
|
||
ShoppingBag,
|
||
ExternalLink,
|
||
MessageSquare,
|
||
ShieldCheck,
|
||
Zap,
|
||
Filter,
|
||
CheckSquare,
|
||
Square,
|
||
Edit3,
|
||
Trash2,
|
||
ArrowUpRight,
|
||
Eye,
|
||
} from 'lucide-react';
|
||
import { SupplierPost, Product, MonitoredWhatsAppGroup } from '@/types';
|
||
import { formatCOP } from '@/lib/pricing-engine';
|
||
import { BRANDS, BrandId } from '@/config/brands';
|
||
|
||
export default function SupplierFeedPage() {
|
||
const [posts, setPosts] = useState<SupplierPost[]>([]);
|
||
const [monitoredGroups, setMonitoredGroups] = useState<MonitoredWhatsAppGroup[]>([]);
|
||
const [selectedGroupFilter, setSelectedGroupFilter] = useState<string>('all');
|
||
const [tabStatus, setTabStatus] = useState<'pending' | 'published' | 'discarded'>('pending');
|
||
const [isLoading, setIsLoading] = useState(false);
|
||
const [isSyncingLive, setIsSyncingLive] = useState(false);
|
||
const [actionMessage, setActionMessage] = useState<string | null>(null);
|
||
|
||
// Multi-selection state
|
||
const [selectedPostIds, setSelectedPostIds] = useState<Set<string>>(new Set());
|
||
|
||
// Edit Modal State
|
||
const [editingPost, setEditingPost] = useState<SupplierPost | null>(null);
|
||
const [editName, setEditName] = useState('');
|
||
const [editBrand, setEditBrand] = useState('');
|
||
const [editPrice, setEditPrice] = useState(120000);
|
||
const [editSizes, setEditSizes] = useState('');
|
||
|
||
// Target brand selector for publishing
|
||
const [targetBrand, setTargetBrand] = useState<BrandId | 'all'>('almeira');
|
||
|
||
// Cargar grupos monitoreados y posts iniciales en vivo
|
||
const loadInitialData = async () => {
|
||
setIsLoading(true);
|
||
try {
|
||
// 1. Grupos
|
||
const grpRes = await fetch('/api/whatsapp-groups?fetchLive=true');
|
||
const grpData = await grpRes.json();
|
||
if (grpData.success && Array.isArray(grpData.groups)) {
|
||
setMonitoredGroups(grpData.groups);
|
||
}
|
||
|
||
// 2. Posts en vivo desde los grupos de WhatsApp
|
||
const feedRes = await fetch('/api/whatsapp-feed?fetchLive=true&limit=50');
|
||
const feedData = await feedRes.json();
|
||
if (feedData.success && Array.isArray(feedData.posts)) {
|
||
setPosts(feedData.posts);
|
||
}
|
||
} catch (err) {
|
||
console.error('Error cargando feed:', err);
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
loadInitialData();
|
||
}, []);
|
||
|
||
// Cambiar filtro de grupo y recargar mensajes
|
||
const handleGroupFilterChange = async (groupJid: string) => {
|
||
setSelectedGroupFilter(groupJid);
|
||
setIsSyncingLive(true);
|
||
setActionMessage(null);
|
||
try {
|
||
const url =
|
||
groupJid !== 'all'
|
||
? `/api/whatsapp-feed?fetchLive=true&groupJid=${encodeURIComponent(groupJid)}`
|
||
: '/api/whatsapp-feed?fetchLive=true&limit=50';
|
||
const res = await fetch(url);
|
||
const data = await res.json();
|
||
if (data.success && Array.isArray(data.posts)) {
|
||
setPosts(data.posts);
|
||
setActionMessage(
|
||
`✅ Se cargaron ${data.totalFound || data.posts.length} mensajes del grupo.`
|
||
);
|
||
}
|
||
} catch (err) {
|
||
console.error(err);
|
||
setActionMessage('❌ Error cargando mensajes del grupo.');
|
||
} finally {
|
||
setIsSyncingLive(false);
|
||
}
|
||
};
|
||
|
||
// Sincronizar mensajes en vivo desde Evolution API
|
||
const handleSyncLive = async () => {
|
||
setIsSyncingLive(true);
|
||
setActionMessage(null);
|
||
try {
|
||
const url =
|
||
selectedGroupFilter !== 'all'
|
||
? `/api/whatsapp-feed?fetchLive=true&groupJid=${encodeURIComponent(selectedGroupFilter)}`
|
||
: '/api/whatsapp-feed?fetchLive=true&limit=50';
|
||
|
||
const res = await fetch(url);
|
||
const data = await res.json();
|
||
if (data.success && Array.isArray(data.posts)) {
|
||
setPosts(data.posts);
|
||
setActionMessage(
|
||
`✅ ¡Sincronización completa! Se escanearon ${data.totalFound || data.posts.length} mensajes recientes de WhatsApp.`
|
||
);
|
||
} else {
|
||
setActionMessage('⚠️ No se encontraron mensajes nuevos en los grupos seleccionados.');
|
||
}
|
||
} catch (err: any) {
|
||
console.error(err);
|
||
setActionMessage('❌ Error conectando con Evolution API para traer mensajes.');
|
||
} finally {
|
||
setIsSyncingLive(false);
|
||
}
|
||
};
|
||
|
||
// Publicar un producto a la tienda
|
||
const handlePublishPost = async (
|
||
post: SupplierPost,
|
||
customData?: { name?: string; brand?: string; retailPrice?: number; sizes?: string[] }
|
||
) => {
|
||
try {
|
||
const res = await fetch('/api/whatsapp-feed', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
action: 'publish',
|
||
post,
|
||
customName: customData?.name,
|
||
customRetailPrice: customData?.retailPrice,
|
||
customSizes: customData?.sizes,
|
||
}),
|
||
});
|
||
const data = await res.json();
|
||
if (data.success) {
|
||
setPosts((prev) =>
|
||
prev.map((p) => (p.id === post.id ? { ...p, status: 'published' } : p))
|
||
);
|
||
setActionMessage(`🚀 ¡"${data.product.name}" ha sido publicado en la App Móvil y Web!`);
|
||
if (editingPost?.id === post.id) setEditingPost(null);
|
||
}
|
||
} catch (e) {
|
||
console.error(e);
|
||
setActionMessage('❌ Error al publicar producto.');
|
||
}
|
||
};
|
||
|
||
// Publicación masiva de los seleccionados
|
||
const handleBulkPublish = async () => {
|
||
const selectedPosts = posts.filter(
|
||
(p) => selectedPostIds.has(p.id) && p.status !== 'published'
|
||
);
|
||
if (selectedPosts.length === 0) return;
|
||
|
||
setIsLoading(true);
|
||
try {
|
||
const res = await fetch('/api/whatsapp-feed', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
action: 'bulk-publish',
|
||
posts: selectedPosts,
|
||
}),
|
||
});
|
||
const data = await res.json();
|
||
if (data.success) {
|
||
setPosts((prev) =>
|
||
prev.map((p) => (selectedPostIds.has(p.id) ? { ...p, status: 'published' } : p))
|
||
);
|
||
setSelectedPostIds(new Set());
|
||
setActionMessage(
|
||
`🎉 ¡Éxito! Se han publicado ${data.products?.length || selectedPosts.length} zapatillas en la App Móvil y Web.`
|
||
);
|
||
}
|
||
} catch (e) {
|
||
console.error(e);
|
||
setActionMessage('❌ Error en publicación masiva.');
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
};
|
||
|
||
// Descartar mensaje
|
||
const handleDiscardPost = async (post: SupplierPost) => {
|
||
try {
|
||
await fetch('/api/whatsapp-feed', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ action: 'discard', post }),
|
||
});
|
||
setPosts((prev) =>
|
||
prev.map((p) => (p.id === post.id ? { ...p, status: 'discarded' } : p))
|
||
);
|
||
setSelectedPostIds((prev) => {
|
||
const next = new Set(prev);
|
||
next.delete(post.id);
|
||
return next;
|
||
});
|
||
} catch (e) {
|
||
console.error(e);
|
||
}
|
||
};
|
||
|
||
// Manejo de Selección
|
||
const handleToggleSelect = (id: string) => {
|
||
setSelectedPostIds((prev) => {
|
||
const next = new Set(prev);
|
||
if (next.has(id)) next.delete(id);
|
||
else next.add(id);
|
||
return next;
|
||
});
|
||
};
|
||
|
||
// Filtrado de posts por estado y grupo
|
||
const filteredPosts = posts.filter((p) => {
|
||
// Estado
|
||
if (tabStatus === 'pending' && p.status === 'published') return false;
|
||
if (tabStatus === 'pending' && p.status === 'discarded') return false;
|
||
if (tabStatus === 'published' && p.status !== 'published') return false;
|
||
if (tabStatus === 'discarded' && p.status !== 'discarded') return false;
|
||
|
||
// Grupo
|
||
if (selectedGroupFilter !== 'all') {
|
||
return (p as any).channelJid === selectedGroupFilter || p.channelName === selectedGroupFilter;
|
||
}
|
||
return true;
|
||
});
|
||
|
||
const pendingCount = posts.filter((p) => p.status !== 'published' && p.status !== 'discarded').length;
|
||
const publishedCount = posts.filter((p) => p.status === 'published').length;
|
||
const discardedCount = posts.filter((p) => p.status === 'discarded').length;
|
||
|
||
const handleOpenEdit = (post: SupplierPost) => {
|
||
setEditingPost(post);
|
||
setEditName(post.extractedProduct?.name || 'Zapatilla Urbana');
|
||
setEditBrand(post.extractedProduct?.brand || 'Nike');
|
||
setEditPrice(
|
||
post.extractedProduct?.suggestedRetailPrice ||
|
||
(post.extractedProduct?.supplierPrice ? post.extractedProduct.supplierPrice + 35000 : 125000)
|
||
);
|
||
setEditSizes(post.extractedProduct?.sizes?.join(', ') || '38, 39, 40, 41, 42, 43');
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
{/* Header */}
|
||
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-4">
|
||
<div>
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-9 h-9 rounded-xl bg-purple-500/20 text-purple-400 flex items-center justify-center border border-purple-500/30 shadow-[0_0_15px_rgba(168,85,247,0.3)]">
|
||
<Radio className="w-5 h-5 animate-pulse" />
|
||
</div>
|
||
<div>
|
||
<h1 className="text-2xl font-black text-white">Feed de Mensajes de WhatsApp</h1>
|
||
<p className="text-xs text-neutral-400">
|
||
Visualiza los mensajes de tus grupos de proveedores y selecciona los calzados que deseas publicar en la app móvil.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Action Controls */}
|
||
<div className="flex items-center gap-2.5 flex-wrap">
|
||
<Link
|
||
href="/admin/grupos-whatsapp"
|
||
className="bg-neutral-900 hover:bg-neutral-800 border border-neutral-700 text-neutral-300 font-bold text-xs px-3.5 py-2.5 rounded-xl flex items-center gap-1.5 transition-all"
|
||
>
|
||
<MessageSquare className="w-4 h-4 text-purple-400" />
|
||
<span>Gestionar Grupos</span>
|
||
</Link>
|
||
|
||
<button
|
||
onClick={handleSyncLive}
|
||
disabled={isSyncingLive}
|
||
className="bg-gradient-to-r from-purple-600 to-fuchsia-600 hover:opacity-95 text-white font-black text-xs px-4 py-2.5 rounded-xl flex items-center gap-2 shadow-lg shadow-purple-600/30 cursor-pointer transition-all active:scale-95 disabled:opacity-50"
|
||
>
|
||
<RefreshCw className={`w-4 h-4 ${isSyncingLive ? 'animate-spin' : ''}`} />
|
||
<span>{isSyncingLive ? 'Consultando Grupos...' : 'Sincronizar WhatsApp en Vivo'}</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Toast feedback */}
|
||
{actionMessage && (
|
||
<div className="bg-purple-950/80 border border-purple-500/50 text-purple-200 px-4 py-3 rounded-2xl text-xs flex items-center justify-between animate-in fade-in shadow-xl">
|
||
<div className="flex items-center gap-2">
|
||
<Sparkles className="w-4 h-4 text-purple-400" />
|
||
<span>{actionMessage}</span>
|
||
</div>
|
||
<button
|
||
onClick={() => setActionMessage(null)}
|
||
className="text-white hover:text-purple-300 font-bold px-2 cursor-pointer"
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
)}
|
||
|
||
{/* Filter & Tabs Bar */}
|
||
<div className="bg-neutral-900/90 border border-neutral-800 rounded-3xl p-4 flex flex-col md:flex-row items-stretch md:items-center justify-between gap-4 backdrop-blur-xl">
|
||
{/* Status Tabs */}
|
||
<div className="flex items-center gap-1 bg-neutral-950 p-1 rounded-2xl border border-neutral-800 text-xs font-bold">
|
||
<button
|
||
onClick={() => setTabStatus('pending')}
|
||
className={`px-3.5 py-2 rounded-xl transition-all cursor-pointer flex items-center gap-1.5 ${
|
||
tabStatus === 'pending'
|
||
? 'bg-purple-600 text-white shadow'
|
||
: 'text-neutral-400 hover:text-white'
|
||
}`}
|
||
>
|
||
<span>📥 Pendientes</span>
|
||
<span className="bg-purple-950 text-purple-300 text-[10px] px-1.5 py-0.5 rounded-full font-mono">
|
||
{pendingCount}
|
||
</span>
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => setTabStatus('published')}
|
||
className={`px-3.5 py-2 rounded-xl transition-all cursor-pointer flex items-center gap-1.5 ${
|
||
tabStatus === 'published'
|
||
? 'bg-emerald-600 text-white shadow'
|
||
: 'text-neutral-400 hover:text-white'
|
||
}`}
|
||
>
|
||
<span>✅ Publicados en Tienda</span>
|
||
<span className="bg-emerald-950 text-emerald-300 text-[10px] px-1.5 py-0.5 rounded-full font-mono">
|
||
{publishedCount}
|
||
</span>
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => setTabStatus('discarded')}
|
||
className={`px-3.5 py-2 rounded-xl transition-all cursor-pointer flex items-center gap-1.5 ${
|
||
tabStatus === 'discarded'
|
||
? 'bg-neutral-800 text-white shadow'
|
||
: 'text-neutral-400 hover:text-white'
|
||
}`}
|
||
>
|
||
<span>🗑️ Descartados</span>
|
||
<span className="bg-neutral-900 text-neutral-400 text-[10px] px-1.5 py-0.5 rounded-full font-mono">
|
||
{discardedCount}
|
||
</span>
|
||
</button>
|
||
</div>
|
||
|
||
{/* Group Selector Filter */}
|
||
<div className="flex items-center gap-2">
|
||
<Filter className="w-4 h-4 text-purple-400 shrink-0" />
|
||
<select
|
||
value={selectedGroupFilter}
|
||
onChange={(e) => handleGroupFilterChange(e.target.value)}
|
||
className="bg-neutral-950 border border-neutral-800 text-white text-xs font-semibold rounded-xl px-3 py-2 outline-hidden cursor-pointer"
|
||
>
|
||
<option value="all">Todos los Grupos de WhatsApp</option>
|
||
{monitoredGroups.map((g) => (
|
||
<option key={g.id || g.jid} value={g.jid}>
|
||
{g.name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Multi-Selection Bulk Actions Bar */}
|
||
{selectedPostIds.size > 0 && tabStatus === 'pending' && (
|
||
<div className="bg-gradient-to-r from-purple-950/90 to-neutral-900 border border-purple-500/40 rounded-2xl p-3.5 flex items-center justify-between animate-in fade-in shadow-2xl">
|
||
<div className="flex items-center gap-2.5">
|
||
<span className="bg-purple-600 text-white text-xs font-black px-2.5 py-1 rounded-lg">
|
||
{selectedPostIds.size} seleccionados
|
||
</span>
|
||
<span className="text-xs text-neutral-300 font-semibold hidden sm:inline">
|
||
Listos para transferir al catálogo de la tienda
|
||
</span>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-2">
|
||
<button
|
||
onClick={() => setSelectedPostIds(new Set())}
|
||
className="text-neutral-400 hover:text-white text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer"
|
||
>
|
||
Desmarcar
|
||
</button>
|
||
|
||
<button
|
||
onClick={handleBulkPublish}
|
||
className="bg-gradient-to-r from-purple-600 to-fuchsia-500 hover:opacity-95 text-white text-xs font-black px-4 py-2 rounded-xl flex items-center gap-1.5 shadow-lg shadow-purple-600/30 cursor-pointer transition-all active:scale-95"
|
||
>
|
||
<Plus className="w-4 h-4" />
|
||
<span>Publicar Seleccionados ({selectedPostIds.size})</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Posts List / Grid */}
|
||
{filteredPosts.length === 0 ? (
|
||
<div className="bg-neutral-900/60 rounded-3xl p-12 text-center border border-neutral-800 space-y-4">
|
||
<div className="w-16 h-16 rounded-2xl bg-purple-500/10 border border-purple-500/20 text-purple-400 flex items-center justify-center mx-auto">
|
||
<MessageSquare className="w-8 h-8" />
|
||
</div>
|
||
<div className="space-y-1">
|
||
<h3 className="font-black text-white text-base">No hay mensajes en esta bandeja</h3>
|
||
<p className="text-xs text-neutral-400 max-w-md mx-auto">
|
||
Haz clic en <strong>"Sincronizar WhatsApp en Vivo"</strong> para extraer automáticamente las ofertas recientes de tus grupos seleccionados.
|
||
</p>
|
||
</div>
|
||
<button
|
||
onClick={handleSyncLive}
|
||
disabled={isSyncingLive}
|
||
className="bg-purple-600 hover:bg-purple-500 text-white font-bold text-xs px-5 py-2.5 rounded-xl inline-flex items-center gap-2 cursor-pointer shadow-md"
|
||
>
|
||
<RefreshCw className={`w-4 h-4 ${isSyncingLive ? 'animate-spin' : ''}`} />
|
||
<span>Escanear Grupos Ahora</span>
|
||
</button>
|
||
</div>
|
||
) : (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
{filteredPosts.map((post) => {
|
||
const extracted = post.extractedProduct;
|
||
const isSelected = selectedPostIds.has(post.id);
|
||
const isPublished = post.status === 'published';
|
||
const isDiscarded = post.status === 'discarded';
|
||
|
||
return (
|
||
<div
|
||
key={post.id}
|
||
className={`bg-neutral-900 border rounded-3xl p-5 space-y-4 transition-all ${
|
||
isSelected
|
||
? 'border-purple-500 shadow-[0_0_20px_rgba(168,85,247,0.2)]'
|
||
: isPublished
|
||
? 'border-emerald-500/30 bg-emerald-950/10'
|
||
: 'border-neutral-800 hover:border-neutral-700'
|
||
}`}
|
||
>
|
||
{/* Header info */}
|
||
<div className="flex items-center justify-between gap-2 border-b border-neutral-800 pb-3">
|
||
<div className="flex items-center gap-2">
|
||
{tabStatus === 'pending' && (
|
||
<button
|
||
onClick={() => handleToggleSelect(post.id)}
|
||
className="text-purple-400 hover:text-purple-300 cursor-pointer"
|
||
>
|
||
{isSelected ? (
|
||
<CheckSquare className="w-5 h-5 fill-purple-600 text-white" />
|
||
) : (
|
||
<Square className="w-5 h-5 text-neutral-600" />
|
||
)}
|
||
</button>
|
||
)}
|
||
|
||
<div>
|
||
<span className="bg-purple-950 text-purple-300 text-[10px] font-black px-2 py-0.5 rounded-full border border-purple-500/30 inline-block">
|
||
{post.channelName || 'Grupo WhatsApp'}
|
||
</span>
|
||
<span className="text-[10px] text-neutral-500 ml-2 font-mono">{post.receivedAt}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
{isPublished ? (
|
||
<span className="bg-emerald-500/20 text-emerald-300 border border-emerald-500/40 text-[10px] font-bold px-2.5 py-0.5 rounded-full flex items-center gap-1">
|
||
<CheckCircle2 className="w-3 h-3" /> Publicado en Tienda
|
||
</span>
|
||
) : isDiscarded ? (
|
||
<span className="bg-neutral-800 text-neutral-400 text-[10px] font-bold px-2.5 py-0.5 rounded-full">
|
||
Descartado
|
||
</span>
|
||
) : (
|
||
<span className="bg-amber-500/20 text-amber-300 border border-amber-500/40 text-[10px] font-bold px-2.5 py-0.5 rounded-full flex items-center gap-1">
|
||
<Sparkles className="w-3 h-3" /> Pendiente de Aprobación
|
||
</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Content Grid (Photo + AI extracted info) */}
|
||
<div className="grid grid-cols-1 sm:grid-cols-12 gap-4">
|
||
{/* Sneaker Image & Raw text */}
|
||
<div className="sm:col-span-5 space-y-2">
|
||
<div className="relative aspect-square w-full rounded-2xl overflow-hidden bg-neutral-950 border border-neutral-800 flex items-center justify-center group">
|
||
{post.rawImages && post.rawImages[0] ? (
|
||
<img
|
||
src={post.rawImages[0]}
|
||
alt={extracted?.name || 'Zapatilla'}
|
||
className="w-full h-full object-cover group-hover:scale-105 transition-all"
|
||
/>
|
||
) : (
|
||
<div className="text-neutral-600 text-xs">Sin foto</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Raw WhatsApp Message snippet */}
|
||
<div className="bg-neutral-950 rounded-xl p-2.5 border border-neutral-800/80">
|
||
<p className="text-[11px] text-neutral-400 font-mono whitespace-pre-line line-clamp-3">
|
||
{post.rawMessage}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* AI Extraction & Product Info */}
|
||
<div className="sm:col-span-7 space-y-3 flex flex-col justify-between">
|
||
<div className="space-y-2">
|
||
<div>
|
||
<span className="text-[10px] font-black uppercase text-purple-400 tracking-wider">
|
||
{extracted?.brand || 'Nike'} • {extracted?.category || 'Sneakers'}
|
||
</span>
|
||
<h3 className="text-base font-black text-white leading-tight">
|
||
{extracted?.name || 'Zapatilla Urbana'}
|
||
</h3>
|
||
</div>
|
||
|
||
{/* Pricing breakdown */}
|
||
<div className="bg-neutral-950 p-3 rounded-2xl border border-neutral-800 grid grid-cols-2 gap-2">
|
||
<div>
|
||
<span className="text-[10px] text-neutral-400 block font-bold">Costo Proveedor:</span>
|
||
<span className="text-xs font-bold text-neutral-300 font-mono">
|
||
{formatCOP(extracted?.supplierPrice || 90000)}
|
||
</span>
|
||
</div>
|
||
<div>
|
||
<span className="text-[10px] text-emerald-400 block font-bold">Venta Sugerida:</span>
|
||
<span className="text-sm font-black text-emerald-300 font-mono">
|
||
{formatCOP(extracted?.suggestedRetailPrice || 125000)}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Sizes badge pills */}
|
||
<div>
|
||
<span className="text-[10px] font-bold text-neutral-400 uppercase block mb-1">
|
||
Tallas Disponibles:
|
||
</span>
|
||
<div className="flex gap-1 flex-wrap">
|
||
{extracted?.sizes && extracted.sizes.length > 0 ? (
|
||
extracted.sizes.map((s) => (
|
||
<span
|
||
key={s}
|
||
className="bg-neutral-950 text-white font-bold text-[11px] px-2 py-0.5 rounded-lg border border-neutral-800"
|
||
>
|
||
{s}
|
||
</span>
|
||
))
|
||
) : (
|
||
<span className="text-xs text-neutral-500">37 a 43</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Card Actions */}
|
||
<div className="pt-2 border-t border-neutral-800 flex items-center gap-2">
|
||
{!isPublished ? (
|
||
<>
|
||
<button
|
||
onClick={() => handlePublishPost(post)}
|
||
className="flex-1 bg-gradient-to-r from-purple-600 to-fuchsia-600 hover:opacity-95 text-white font-black text-xs py-2.5 px-3 rounded-xl flex items-center justify-center gap-1.5 shadow-md shadow-purple-600/30 cursor-pointer transition-all active:scale-95"
|
||
>
|
||
<Plus className="w-3.5 h-3.5" />
|
||
<span>Publicar en la App</span>
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => handleOpenEdit(post)}
|
||
title="Editar antes de publicar"
|
||
className="bg-neutral-800 hover:bg-neutral-700 text-neutral-300 p-2.5 rounded-xl cursor-pointer transition-all"
|
||
>
|
||
<Edit3 className="w-4 h-4" />
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => handleDiscardPost(post)}
|
||
title="Descartar"
|
||
className="bg-neutral-800 hover:bg-red-950/40 text-neutral-400 hover:text-red-400 p-2.5 rounded-xl cursor-pointer transition-all"
|
||
>
|
||
<Trash2 className="w-4 h-4" />
|
||
</button>
|
||
</>
|
||
) : (
|
||
<div className="w-full flex items-center justify-between text-xs">
|
||
<span className="text-emerald-400 font-bold flex items-center gap-1">
|
||
<CheckCircle2 className="w-4 h-4" /> Disponible para clientes
|
||
</span>
|
||
<Link
|
||
href="/"
|
||
target="_blank"
|
||
className="text-purple-400 hover:underline font-bold flex items-center gap-1"
|
||
>
|
||
<span>Ver en Tienda</span>
|
||
<ArrowUpRight className="w-3 h-3" />
|
||
</Link>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
)}
|
||
|
||
{/* Edit Modal Before Publishing */}
|
||
{editingPost && (
|
||
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4">
|
||
<div className="bg-neutral-900 border border-purple-500/40 rounded-3xl max-w-lg w-full p-6 space-y-5 shadow-2xl animate-in zoom-in-95">
|
||
<div className="flex items-center justify-between border-b border-neutral-800 pb-3">
|
||
<h3 className="font-black text-white text-base flex items-center gap-2">
|
||
<Edit3 className="w-5 h-5 text-purple-400" />
|
||
<span>Personalizar antes de Publicar</span>
|
||
</h3>
|
||
<button
|
||
onClick={() => setEditingPost(null)}
|
||
className="text-neutral-400 hover:text-white font-bold"
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
|
||
<div className="space-y-3.5 text-xs">
|
||
<div>
|
||
<label className="font-bold text-neutral-300 block mb-1">Nombre Comercial del Modelo:</label>
|
||
<input
|
||
type="text"
|
||
value={editName}
|
||
onChange={(e) => setEditName(e.target.value)}
|
||
className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-white font-bold outline-hidden focus:border-purple-500"
|
||
/>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-3">
|
||
<div>
|
||
<label className="font-bold text-neutral-300 block mb-1">Marca:</label>
|
||
<input
|
||
type="text"
|
||
value={editBrand}
|
||
onChange={(e) => setEditBrand(e.target.value)}
|
||
className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-white outline-hidden focus:border-purple-500"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="font-bold text-emerald-400 block mb-1">Precio de Venta (COP):</label>
|
||
<input
|
||
type="number"
|
||
value={editPrice}
|
||
onChange={(e) => setEditPrice(Number(e.target.value))}
|
||
className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-emerald-300 font-black font-mono outline-hidden focus:border-emerald-500"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="font-bold text-neutral-300 block mb-1">Tallas (separadas por coma):</label>
|
||
<input
|
||
type="text"
|
||
value={editSizes}
|
||
onChange={(e) => setEditSizes(e.target.value)}
|
||
placeholder="38, 39, 40, 41, 42, 43"
|
||
className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-white outline-hidden focus:border-purple-500"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-2.5 pt-3 border-t border-neutral-800">
|
||
<button
|
||
onClick={() => setEditingPost(null)}
|
||
className="flex-1 bg-neutral-800 hover:bg-neutral-700 text-neutral-300 font-bold text-xs py-3 rounded-xl cursor-pointer"
|
||
>
|
||
Cancelar
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => {
|
||
const sizesArray = editSizes
|
||
.split(',')
|
||
.map((s) => s.trim())
|
||
.filter(Boolean);
|
||
handlePublishPost(editingPost, {
|
||
name: editName,
|
||
brand: editBrand,
|
||
retailPrice: editPrice,
|
||
sizes: sizesArray,
|
||
});
|
||
}}
|
||
className="flex-1 bg-gradient-to-r from-purple-600 to-fuchsia-600 hover:opacity-95 text-white font-black text-xs py-3 rounded-xl shadow-lg shadow-purple-600/30 cursor-pointer"
|
||
>
|
||
Guardar y Publicar en la App
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|