diff --git a/dripzone-app/src/app/admin/encargos/page.tsx b/dripzone-app/src/app/admin/encargos/page.tsx index f8e6192..1131e6f 100644 --- a/dripzone-app/src/app/admin/encargos/page.tsx +++ b/dripzone-app/src/app/admin/encargos/page.tsx @@ -9,38 +9,72 @@ import { XCircle, MessageCircle, Phone, - Filter, DollarSign, - Tag, Search, - ExternalLink, - ChevronRight, - AlertCircle, Eye, + Edit, + Trash2, + Plus, + X, + Save, + Upload, + MapPin, + User, + Mail, + FileText, + AlertTriangle, + RotateCw, } from 'lucide-react'; import { CustomProductRequest } from '@/types'; import { formatCOP } from '@/lib/pricing-engine'; +import { OptimizedImage } from '@/components/ui/OptimizedImage'; export default function AdminCustomRequestsPage() { const [requests, setRequests] = useState([]); const [statusFilter, setStatusFilter] = useState('all'); const [search, setSearch] = useState(''); - const [selectedRequest, setSelectedRequest] = useState(null); - const [isQuoting, setIsQuoting] = useState(false); - const [quotedPriceInput, setQuotedPriceInput] = useState(''); - const [adminNotesInput, setAdminNotesInput] = useState(''); const [toastMessage, setToastMessage] = useState(null); const [isLoading, setIsLoading] = useState(true); + const [isSubmitting, setIsSubmitting] = useState(false); + + // Modales + const [selectedRequest, setSelectedRequest] = useState(null); + const [isViewDetailOpen, setIsViewDetailOpen] = useState(false); + const [isEditModalOpen, setIsEditModalOpen] = useState(false); + const [isNewModalOpen, setIsNewModalOpen] = useState(false); + const [deleteConfirmId, setDeleteConfirmId] = useState(null); + + // Formulario de Edición / Creación + const [formData, setFormData] = useState>({ + customerName: '', + customerPhone: '', + customerEmail: '', + customerCity: '', + brand: '', + modelName: '', + size: '40', + colorway: '', + targetBudget: undefined, + quotedPrice: undefined, + status: 'pending', + notes: '', + adminNotes: '', + imageUrl: '', + }); + + const [imagePreview, setImagePreview] = useState(''); + const [imageBase64, setImageBase64] = useState(''); const fetchRequests = async () => { try { + setIsLoading(true); const res = await fetch('/api/custom-requests'); const data = await res.json(); if (data.success && Array.isArray(data.requests)) { setRequests(data.requests); } } catch (e) { - console.error(e); + console.error('Error cargando encargos:', e); } finally { setIsLoading(false); } @@ -50,40 +84,198 @@ export default function AdminCustomRequestsPage() { fetchRequests(); }, []); - const handleUpdateStatus = async ( - id: string, - newStatus: CustomProductRequest['status'], - quotedPrice?: number, - notify: boolean = false - ) => { + const showToast = (msg: string) => { + setToastMessage(msg); + setTimeout(() => setToastMessage(null), 4000); + }; + + // Abrir Modal de Edición + const handleOpenEdit = (req: CustomProductRequest) => { + setSelectedRequest(req); + setFormData({ + customerName: req.customerName, + customerPhone: req.customerPhone, + customerEmail: req.customerEmail || '', + customerCity: req.customerCity || '', + brand: req.brand, + modelName: req.modelName, + size: req.size, + colorway: req.colorway || '', + targetBudget: req.targetBudget, + quotedPrice: req.quotedPrice, + status: req.status, + notes: req.notes || '', + adminNotes: req.adminNotes || '', + imageUrl: req.imageUrl || '', + }); + setImagePreview(req.imageUrl || ''); + setImageBase64(''); + setIsEditModalOpen(true); + }; + + // Abrir Modal de Creación + const handleOpenNew = () => { + setSelectedRequest(null); + setFormData({ + customerName: '', + customerPhone: '', + customerEmail: '', + customerCity: 'Cali', + brand: '', + modelName: '', + size: '40', + colorway: '', + targetBudget: undefined, + quotedPrice: undefined, + status: 'pending', + notes: '', + adminNotes: '', + imageUrl: '', + }); + setImagePreview(''); + setImageBase64(''); + setIsNewModalOpen(true); + }; + + // Procesar cambio de imagen (subida local) + const handleImageFileChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onloadend = () => { + const b64 = reader.result as string; + setImageBase64(b64); + setImagePreview(b64); + }; + reader.readAsDataURL(file); + }; + + // Guardar Cambios (Editar) + const handleSaveEdit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!selectedRequest) return; + + setIsSubmitting(true); + try { + const payload: any = { + id: selectedRequest.id, + updates: { + ...formData, + targetBudget: formData.targetBudget ? parseInt(String(formData.targetBudget), 10) : undefined, + quotedPrice: formData.quotedPrice ? parseInt(String(formData.quotedPrice), 10) : undefined, + }, + }; + + if (imageBase64) { + payload.updates.imageBase64 = imageBase64; + } + + const res = await fetch('/api/custom-requests', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + + const data = await res.json(); + if (data.success && data.request) { + setRequests((prev) => prev.map((r) => (r.id === selectedRequest.id ? data.request : r))); + setIsEditModalOpen(false); + showToast('✅ Encargo actualizado y sincronizado correctamente.'); + } else { + alert(data.error || 'No se pudo actualizar el encargo.'); + } + } catch (err: any) { + alert(`Error guardando: ${err.message}`); + } finally { + setIsSubmitting(false); + } + }; + + // Guardar Nuevo Encargo + const handleSaveNew = async (e: React.FormEvent) => { + e.preventDefault(); + if (!formData.customerName || !formData.customerPhone || !formData.brand || !formData.modelName || !formData.size) { + alert('Por favor diligencia los campos obligatorios (Nombre, Teléfono, Marca, Modelo y Talla).'); + return; + } + + setIsSubmitting(true); + try { + const payload: any = { + ...formData, + targetBudget: formData.targetBudget ? parseInt(String(formData.targetBudget), 10) : undefined, + quotedPrice: formData.quotedPrice ? parseInt(String(formData.quotedPrice), 10) : undefined, + }; + + if (imageBase64) { + payload.imageBase64 = imageBase64; + } + + const res = await fetch('/api/custom-requests', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + + const data = await res.json(); + if (data.success && data.request) { + setRequests((prev) => [data.request, ...prev]); + setIsNewModalOpen(false); + showToast('🎉 ¡Nuevo encargo especial registrado y notificado con éxito!'); + } else { + alert(data.error || 'No se pudo crear el encargo.'); + } + } catch (err: any) { + alert(`Error creando: ${err.message}`); + } finally { + setIsSubmitting(false); + } + }; + + // Eliminar Encargo + const handleDeleteRequest = async (id: string) => { + try { + const res = await fetch(`/api/custom-requests?id=${encodeURIComponent(id)}`, { + method: 'DELETE', + }); + const data = await res.json(); + if (data.success) { + setRequests((prev) => prev.filter((r) => r.id !== id)); + setDeleteConfirmId(null); + setIsEditModalOpen(false); + setIsViewDetailOpen(false); + showToast('🗑️ Encargo eliminado permanentemente.'); + } else { + alert(data.error || 'Error al eliminar'); + } + } catch (err: any) { + alert(`Error de red al eliminar: ${err.message}`); + } + }; + + // Actualización rápida de Estado + const handleQuickStatus = async (id: string, newStatus: CustomProductRequest['status']) => { try { const res = await fetch('/api/custom-requests', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, - updates: { - status: newStatus, - quotedPrice: quotedPrice !== undefined ? quotedPrice : undefined, - adminNotes: adminNotesInput || undefined, - }, - notifyCustomer: notify, + updates: { status: newStatus }, }), }); const data = await res.json(); if (data.success) { setRequests((prev) => prev.map((r) => (r.id === id ? data.request : r))); - setIsQuoting(false); - setToastMessage(`✅ Solicitud actualizada a "${newStatus.toUpperCase()}".`); - if (selectedRequest?.id === id) { - setSelectedRequest(data.request); - } + showToast(`Estado cambiado a: ${newStatus.toUpperCase()}`); } - } catch (e) { + } catch (e: any) { console.error(e); } }; + // Filtros const filtered = requests.filter((r) => { if (statusFilter !== 'all' && r.status !== statusFilter) return false; if (search) { @@ -92,7 +284,8 @@ export default function AdminCustomRequestsPage() { const matchModel = r.modelName?.toLowerCase().includes(q); const matchBrand = r.brand?.toLowerCase().includes(q); const matchPhone = r.customerPhone?.includes(q); - if (!matchName && !matchModel && !matchBrand && !matchPhone) return false; + const matchCity = r.customerCity?.toLowerCase().includes(q); + if (!matchName && !matchModel && !matchBrand && !matchPhone && !matchCity) return false; } return true; }); @@ -145,21 +338,39 @@ export default function AdminCustomRequestsPage() { Encargos y Pedidos Especiales ({requests.length})

- Gestión de solicitudes de zapatillas y artículos que los clientes piden por encargo. Cotiza con proveedores y responde directamente por WhatsApp. + Administración completa de solicitudes de calzado por encargo. Puedes editar, cotizar, actualizar estados, agregar encargos manuales y eliminar registros.

+ +
+ + + +
{toastMessage && ( -
- {toastMessage} +
+ {toastMessage}
)} - {/* Filters & Search Toolbar */} + {/* Toolbar: Filtros y Buscador */}
{[ @@ -168,11 +379,12 @@ export default function AdminCustomRequestsPage() { { id: 'searching', label: 'En Búsqueda' }, { id: 'quoted', label: 'Cotizados' }, { id: 'completed', label: 'Concretados' }, + { id: 'rejected', label: 'Descartados' }, ].map((tab) => (
-
+
+ setSearch(e.target.value)} - className="w-full bg-neutral-900 border border-neutral-800 rounded-xl px-3.5 py-2 text-xs text-white outline-hidden focus:border-purple-400" + className="w-full bg-neutral-900 border border-neutral-800 focus:border-purple-400 rounded-xl pl-9 pr-3.5 py-2 text-xs text-white placeholder-neutral-500 outline-none transition-all" />
- {/* Requests Table / Grid */} -
- {filtered.length === 0 ? ( -
- No hay solicitudes de encargo que coincidan con los filtros. -
- ) : ( - filtered.map((req) => { + {/* Grid de Encargos */} + {filtered.length === 0 ? ( +
+ +

No hay encargos con los filtros seleccionados.

+

Puedes crear uno manualmente con el botón "+ Nuevo Encargo".

+
+ ) : ( +
+ {filtered.map((req) => { const hasImage = Boolean(req.imageUrl); + const isDeleting = deleteConfirmId === req.id; + return (
+ {/* Top Bar: Estado + Fecha + Botón Eliminar Rápido */}
{getStatusBadge(req.status)} - - {new Date(req.createdAt).toLocaleDateString('es-CO', { - day: '2-digit', - month: 'short', - hour: '2-digit', - minute: '2-digit', - })} - +
+ + {new Date(req.createdAt).toLocaleDateString('es-CO', { + day: '2-digit', + month: 'short', + })} + + +
- {hasImage ? ( -
setSelectedRequest(req)} - className="relative aspect-video rounded-2xl overflow-hidden bg-black mb-3 border border-neutral-800 cursor-pointer group" - > - { + setSelectedRequest(req); + setIsViewDetailOpen(true); + }} + className="relative aspect-video rounded-2xl overflow-hidden bg-neutral-950 mb-3 border border-neutral-800 cursor-pointer group/img" + > + {hasImage ? ( + -
- - Ver Detalle + ) : ( +
+ + Sin foto adjunta
+ )} +
+ + Ver Detalles
- ) : ( -
- Sin foto adjunta -
- )} +
+ {/* Info Producto */}
-
+
{req.brand} • Talla {req.size}
-

{req.modelName}

- {req.colorway &&

Color: {req.colorway}

} +

+ {req.modelName} +

+ {req.colorway &&

Color: {req.colorway}

}
+ {/* Info Cliente & Presupuesto */}
- Cliente: - {req.customerName} + Cliente: + {req.customerName}
- Ciudad: + Teléfono: + {req.customerPhone} +
+
+ Ciudad: {req.customerCity || 'Colombia'}
{req.targetBudget && (
- Presupuesto: + Presupuesto cliente: {formatCOP(req.targetBudget)}
)} {req.quotedPrice && (
- Precio Cotizado: + Cotización: {formatCOP(req.quotedPrice)}
)}
- {/* Action Buttons */} -
- - - WhatsApp - + {/* Confirmación inline de eliminación */} + {isDeleting ? ( +
+

+ + ¿Eliminar este encargo permanentemente? +

+
+ + +
+
+ ) : ( + /* Action Buttons */ +
+ {/* Botón WhatsApp */} + + + WhatsApp + + {/* Botón Editar / Gestionar */} + +
+ )} +
+ ); + })} +
+ )} + + {/* ========================================================================= */} + {/* MODAL DE EDICIÓN COMPLETA */} + {/* ========================================================================= */} + {isEditModalOpen && selectedRequest && ( +
+
+
+
+

+ + Editar Encargo: {formData.brand} {formData.modelName} +

+ ID: {selectedRequest.id} +
+ +
+ +
+ {/* Sección 1: Datos del Calzado */} +
+ + 👟 Datos del Artículo Encargado + +
+
+ + setFormData({ ...formData, brand: e.target.value })} + placeholder="Ej. Nike, Adidas, Jordan" + className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-white focus:border-purple-400 outline-none" + /> +
+
+ + setFormData({ ...formData, modelName: e.target.value })} + placeholder="Ej. Air Jordan 4 Retro Black Cat" + className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-white focus:border-purple-400 outline-none" + /> +
+
+ +
+
+ + setFormData({ ...formData, size: e.target.value })} + placeholder="Ej. 41" + className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-white focus:border-purple-400 outline-none" + /> +
+
+ + setFormData({ ...formData, colorway: e.target.value })} + placeholder="Ej. Black / University Red" + className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-white focus:border-purple-400 outline-none" + /> +
+
+
+ + {/* Sección 2: Foto de Referencia */} +
+ + 🖼️ Fotografía de Referencia + +
+
+ {imagePreview ? ( + + ) : ( +
+ Sin foto +
+ )} +
+
+
+ + +
+
+ + { + setFormData({ ...formData, imageUrl: e.target.value }); + setImagePreview(e.target.value); + }} + placeholder="https://..." + className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-white focus:border-purple-400 outline-none font-mono text-[11px]" + /> +
+
+
+
+ + {/* Sección 3: Datos del Cliente */} +
+ + 👤 Información del Cliente + +
+
+ + setFormData({ ...formData, customerName: e.target.value })} + className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-white focus:border-purple-400 outline-none" + /> +
+
+ + setFormData({ ...formData, customerPhone: e.target.value })} + className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-white focus:border-purple-400 outline-none font-mono" + /> +
+
+ + setFormData({ ...formData, customerCity: e.target.value })} + className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-white focus:border-purple-400 outline-none" + /> +
+
+ + setFormData({ ...formData, customerEmail: e.target.value })} + className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-white focus:border-purple-400 outline-none" + /> +
+
+ +
+ +