almeira-web/zapply-app/src/app/admin/usuarios/page.tsx

822 lines
37 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import React, { useState, useEffect } from 'react';
import { Users, UserPlus, Shield, ShieldCheck, UserCheck, MessageCircle, Mail, Phone, MapPin, Eye, EyeOff, Trash2, Edit, Check, X, Lock, Key, AlertTriangle, Sparkles } from 'lucide-react';
import { User } from '@/types';
export default function AdminUsersPage() {
const [users, setUsers] = useState<User[]>([]);
const [search, setSearch] = useState('');
const [roleFilter, setRoleFilter] = useState<'all' | 'admin' | 'operator' | 'customer'>('all');
const [statusFilter, setStatusFilter] = useState<'all' | 'active' | 'inactive'>('all');
const [isModalOpen, setIsModalOpen] = useState(false);
const [editingUser, setEditingUser] = useState<User | null>(null);
const [toastMessage, setToastMessage] = useState<string | null>(null);
// Modal para configurar Garantía de Envío / Anticipo
const [depositModalUser, setDepositModalUser] = useState<User | null>(null);
const [depositRequires, setDepositRequires] = useState<boolean>(false);
const [depositReasonInput, setDepositReasonInput] = useState<string>('');
// New user modal form
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [phone, setPhone] = useState('');
const [city, setCity] = useState('Cali');
const [address, setAddress] = useState('');
const [role, setRole] = useState<'admin' | 'operator' | 'customer'>('operator');
const [password, setPassword] = useState('');
// Edit user modal form
const [editName, setEditName] = useState('');
const [editEmail, setEditEmail] = useState('');
const [editPhone, setEditPhone] = useState('');
const [editCity, setEditCity] = useState('Cali');
const [editAddress, setEditAddress] = useState('');
const [editRole, setEditRole] = useState<'admin' | 'operator' | 'customer'>('operator');
const [editPassword, setEditPassword] = useState('');
useEffect(() => {
fetchUsers();
}, []);
const fetchUsers = async () => {
try {
const res = await fetch('/api/users');
const data = await res.json();
if (data.success) {
setUsers(data.users);
}
} catch (e) {
console.error(e);
}
};
const handleToggleStatus = async (user: User) => {
const newStatus = !user.isActive;
setUsers((prev) => prev.map((u) => (u.id === user.id ? { ...u, isActive: newStatus } : u)));
try {
await fetch('/api/users', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: user.id, updates: { isActive: newStatus } }),
});
setToastMessage(`Estado de ${user.name} actualizado a ${newStatus ? '🟢 ACTIVO' : '⚪ INACTIVO'}.`);
} catch (e) {
console.error(e);
}
};
const handleChangeRole = async (user: User, newRole: 'admin' | 'operator' | 'customer') => {
setUsers((prev) => prev.map((u) => (u.id === user.id ? { ...u, role: newRole } : u)));
try {
await fetch('/api/users', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: user.id, updates: { role: newRole } }),
});
setToastMessage(`Rol de ${user.name} actualizado a [${newRole.toUpperCase()}].`);
} catch (e) {
console.error(e);
}
};
const handleDeleteUser = async (id: string, name: string) => {
if (!confirm(`¿Estás seguro de eliminar permanentemente al usuario ${name}?`)) return;
setUsers((prev) => prev.filter((u) => u.id !== id));
try {
await fetch(`/api/users?id=${id}`, { method: 'DELETE' });
setToastMessage(`🗑️ Usuario ${name} eliminado del sistema.`);
} catch (e) {
console.error(e);
}
};
const handleOpenDepositModal = (user: User) => {
setDepositModalUser(user);
setDepositRequires(user.requiresShippingDeposit === true);
setDepositReasonInput(user.depositReason || '');
};
const handleSaveDepositSettings = async () => {
if (!depositModalUser) return;
const updates = {
requiresShippingDeposit: depositRequires,
depositReason: depositReasonInput.trim() || (depositRequires ? 'Garantía requerida por política administrativa' : 'Exonerado por administrador'),
};
setUsers((prev) =>
prev.map((u) => (u.id === depositModalUser.id ? { ...u, ...updates } : u))
);
try {
await fetch('/api/users', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: depositModalUser.id, updates }),
});
setToastMessage(
depositRequires
? `🔴 Garantía de flete ACTIVADA para "${depositModalUser.name}".`
: `🟢 Garantía de flete DESACTIVADA para "${depositModalUser.name}" (Pago 100% Contraentrega).`
);
setDepositModalUser(null);
} catch (e) {
console.error(e);
}
};
const handleCreateUser = async (e: React.FormEvent) => {
e.preventDefault();
try {
const res = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, email, phone, city, address, role, password }),
});
const data = await res.json();
if (data.success) {
setUsers((prev) => [data.user, ...prev]);
setIsModalOpen(false);
setName('');
setEmail('');
setPhone('');
setPassword('');
setToastMessage(`🎉 Usuario "${name}" registrado exitosamente.`);
} else {
alert(data.error || 'Error al crear usuario');
}
} catch (e) {
console.error(e);
}
};
const handleOpenEdit = (user: User) => {
setEditingUser(user);
setEditName(user.name);
setEditEmail(user.email);
setEditPhone(user.phone);
setEditCity(user.city || 'Cali');
setEditAddress(user.address || '');
setEditRole(user.role);
setEditPassword('');
};
const handleSaveEdit = async (e: React.FormEvent) => {
e.preventDefault();
if (!editingUser) return;
const updates: Partial<User> = {
name: editName,
email: editEmail,
phone: editPhone,
city: editCity,
address: editAddress,
role: editRole,
};
setUsers((prev) => prev.map((u) => (u.id === editingUser.id ? { ...u, ...updates } : u)));
try {
const res = await fetch('/api/users', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: editingUser.id, updates }),
});
const data = await res.json();
if (data.success) {
setEditingUser(null);
setToastMessage(`✅ Datos de "${editName}" actualizados correctamente.`);
}
} catch (e) {
console.error(e);
}
};
const handleWhatsApp = (user: User) => {
const cleanPhone = user.phone.startsWith('57') ? user.phone : `57${user.phone}`;
const text = encodeURIComponent(`Hola ${user.name}! Te escribimos del equipo de DripZone 🟣`);
window.open(`https://wa.me/${cleanPhone}?text=${text}`, '_blank');
};
const filtered = users.filter((u) => {
const matchSearch =
u.name.toLowerCase().includes(search.toLowerCase()) ||
u.email.toLowerCase().includes(search.toLowerCase()) ||
u.phone.includes(search) ||
(u.city && u.city.toLowerCase().includes(search.toLowerCase()));
if (!matchSearch) return false;
if (roleFilter !== 'all' && u.role !== roleFilter) return false;
if (statusFilter === 'active' && !u.isActive) return false;
if (statusFilter === 'inactive' && u.isActive) return false;
return true;
});
const adminsCount = users.filter((u) => u.role === 'admin').length;
const operatorsCount = users.filter((u) => u.role === 'operator').length;
const customersCount = users.filter((u) => u.role === 'customer').length;
const depositsRequiredCount = users.filter((u) => u.requiresShippingDeposit === true).length;
return (
<div className="space-y-6">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-black text-white flex items-center gap-2">
<Users className="w-6 h-6 text-purple-400" />
<span>Control de Usuarios, Roles & Garantías ({users.length})</span>
</h1>
<p className="text-xs text-neutral-400 mt-1">
Administra accesos, roles de equipo y control inteligente de <strong>Garantías de Envío Anticipado</strong> para clientes.
</p>
</div>
<button
onClick={() => setIsModalOpen(true)}
className="bg-gradient-to-r from-purple-600 to-fuchsia-500 hover:from-purple-500 hover:to-fuchsia-400 text-white font-bold text-xs px-4 py-2.5 rounded-xl flex items-center space-x-1.5 shadow-[0_0_15px_rgba(168,85,247,0.3)] cursor-pointer"
>
<UserPlus className="w-4 h-4" />
<span>+ Registrar Nuevo Usuario Admin</span>
</button>
</div>
{toastMessage && (
<div className="bg-purple-950/80 border border-purple-500 text-purple-300 px-4 py-3 rounded-2xl text-xs flex items-center justify-between shadow-md animate-in fade-in">
<span>{toastMessage}</span>
<button onClick={() => setToastMessage(null)} className="font-bold text-white cursor-pointer ml-3">
✕
</button>
</div>
)}
{/* KPI Stats Cards */}
<div className="grid grid-cols-1 sm:grid-cols-4 gap-4">
<div className="bg-neutral-900 border border-neutral-800 rounded-2xl p-4 flex items-center space-x-3 shadow-lg">
<div className="w-10 h-10 rounded-xl bg-purple-500/20 border border-purple-500/30 flex items-center justify-center text-purple-400">
<Shield className="w-5 h-5" />
</div>
<div>
<p className="text-[11px] text-neutral-400 font-semibold">Super Admins</p>
<p className="text-xl font-black text-white">{adminsCount}</p>
</div>
</div>
<div className="bg-neutral-900 border border-neutral-800 rounded-2xl p-4 flex items-center space-x-3 shadow-lg">
<div className="w-10 h-10 rounded-xl bg-fuchsia-500/20 border border-fuchsia-500/30 flex items-center justify-center text-fuchsia-400">
<ShieldCheck className="w-5 h-5" />
</div>
<div>
<p className="text-[11px] text-neutral-400 font-semibold">Operadores</p>
<p className="text-xl font-black text-white">{operatorsCount}</p>
</div>
</div>
<div className="bg-neutral-900 border border-neutral-800 rounded-2xl p-4 flex items-center space-x-3 shadow-lg">
<div className="w-10 h-10 rounded-xl bg-emerald-500/20 border border-emerald-500/30 flex items-center justify-center text-emerald-400">
<UserCheck className="w-5 h-5" />
</div>
<div>
<p className="text-[11px] text-neutral-400 font-semibold">Clientes Totales</p>
<p className="text-xl font-black text-white">{customersCount}</p>
</div>
</div>
<div className="bg-neutral-900 border border-neutral-800 rounded-2xl p-4 flex items-center space-x-3 shadow-lg">
<div className="w-10 h-10 rounded-xl bg-amber-500/20 border border-amber-500/30 flex items-center justify-center text-amber-400">
<AlertTriangle className="w-5 h-5" />
</div>
<div>
<p className="text-[11px] text-neutral-400 font-semibold">Garantía Exigida</p>
<p className="text-xl font-black text-amber-300">{depositsRequiredCount}</p>
</div>
</div>
</div>
{/* Filters Toolbar */}
<div className="flex flex-col md:flex-row items-stretch md:items-center justify-between gap-3">
<div className="flex items-center space-x-1.5 bg-neutral-900 border border-neutral-800 p-1 rounded-2xl overflow-x-auto">
<button
onClick={() => setRoleFilter('all')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl transition-all cursor-pointer ${
roleFilter === 'all' ? 'bg-white text-neutral-950 shadow' : 'text-neutral-400 hover:text-white'
}`}
>
Todos ({users.length})
</button>
<button
onClick={() => setRoleFilter('admin')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl transition-all cursor-pointer ${
roleFilter === 'admin' ? 'bg-purple-500/30 text-purple-300 border border-purple-500/50 shadow' : 'text-neutral-400 hover:text-purple-300'
}`}
>
Admins ({adminsCount})
</button>
<button
onClick={() => setRoleFilter('operator')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl transition-all cursor-pointer ${
roleFilter === 'operator' ? 'bg-fuchsia-500/30 text-fuchsia-300 border border-fuchsia-500/50 shadow' : 'text-neutral-400 hover:text-fuchsia-300'
}`}
>
Operadores ({operatorsCount})
</button>
<button
onClick={() => setRoleFilter('customer')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl transition-all cursor-pointer ${
roleFilter === 'customer' ? 'bg-emerald-500/30 text-emerald-300 border border-emerald-500/50 shadow' : 'text-neutral-400 hover:text-emerald-300'
}`}
>
Clientes ({customersCount})
</button>
</div>
<div className="max-w-xs w-full">
<input
type="text"
placeholder="Buscar por nombre, correo o celular..."
value={search}
onChange={(e) => 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"
/>
</div>
</div>
{/* Users Table */}
<div className="bg-neutral-900 border border-neutral-800 rounded-3xl overflow-hidden shadow-xl">
<div className="overflow-x-auto">
<table className="w-full text-left text-xs text-neutral-300">
<thead className="bg-neutral-950 text-neutral-400 font-bold uppercase text-[10px] border-b border-neutral-800">
<tr>
<th className="py-3.5 px-4">Estado</th>
<th className="py-3.5 px-4">Usuario</th>
<th className="py-3.5 px-4">Contacto Directo</th>
<th className="py-3.5 px-4">Garantía de Flete</th>
<th className="py-3.5 px-4">Ciudad / Dirección</th>
<th className="py-3.5 px-4">Rol & Permisos</th>
<th className="py-3.5 px-4 text-center">Acciones</th>
</tr>
</thead>
<tbody className="divide-y divide-neutral-800/60">
{filtered.length === 0 ? (
<tr>
<td colSpan={7} className="text-center py-8 text-neutral-500">
No se encontraron usuarios con el filtro seleccionado.
</td>
</tr>
) : (
filtered.map((user) => {
const isDepositRequired = user.requiresShippingDeposit === true;
return (
<tr
key={user.id}
className={`hover:bg-neutral-800/50 transition-colors ${
!user.isActive ? 'opacity-65 bg-neutral-950/40' : ''
}`}
>
{/* Active Status Toggle */}
<td className="py-3.5 px-4">
<button
onClick={() => handleToggleStatus(user)}
className={`text-[11px] font-bold px-2.5 py-1 rounded-xl border flex items-center gap-1.5 transition-all cursor-pointer ${
user.isActive
? 'bg-emerald-500/20 border-emerald-500/40 text-emerald-300 hover:bg-emerald-500/30'
: 'bg-neutral-950 border-neutral-700 text-neutral-400 hover:text-white'
}`}
title="Activar o suspender acceso"
>
{user.isActive ? (
<>
<Eye className="w-3.5 h-3.5 text-emerald-400" />
<span>Activo</span>
</>
) : (
<>
<EyeOff className="w-3.5 h-3.5 text-neutral-400" />
<span>Inactivo</span>
</>
)}
</button>
</td>
{/* User Avatar & Name */}
<td className="py-3.5 px-4 flex items-center space-x-3">
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-purple-600 to-fuchsia-500 flex items-center justify-center text-white font-bold text-xs shadow-sm shrink-0">
{user.name.charAt(0).toUpperCase()}
</div>
<div>
<p className="font-bold text-white text-xs">{user.name}</p>
<span className="text-[10px] text-neutral-400">{user.email}</span>
</div>
</td>
{/* Contact */}
<td className="py-3.5 px-4">
<div className="space-y-1">
<button
onClick={() => handleWhatsApp(user)}
className="text-[11px] text-emerald-400 hover:text-emerald-300 font-bold flex items-center gap-1 cursor-pointer"
>
<MessageCircle className="w-3.5 h-3.5" />
<span>{user.phone}</span>
</button>
</div>
</td>
{/* Shipping Guarantee Status (Dynamic Risk Policy) */}
<td className="py-3.5 px-4">
<button
onClick={() => handleOpenDepositModal(user)}
className={`text-[11px] font-bold px-2.5 py-1 rounded-xl border flex items-center gap-1.5 transition-all cursor-pointer ${
isDepositRequired
? 'bg-amber-500/20 border-amber-500/50 text-amber-300 hover:bg-amber-500/30'
: 'bg-emerald-500/10 border-emerald-500/30 text-emerald-400 hover:bg-emerald-500/20'
}`}
title={user.depositReason || (isDepositRequired ? 'Garantía exigida' : 'Contraentrega libre')}
>
<ShieldCheck className="w-3.5 h-3.5" />
<span>{isDepositRequired ? 'Exigir Anticipo' : 'Libre (Contraentrega)'}</span>
{user.rejectedOrdersCount && user.rejectedOrdersCount > 0 ? (
<span className="bg-red-500/40 text-red-200 text-[9px] px-1.5 py-0.5 rounded-full font-black">
{user.rejectedOrdersCount} rechazos
</span>
) : null}
</button>
{user.depositReason && (
<p className="text-[10px] text-neutral-400 truncate max-w-xs mt-0.5 italic">
{user.depositReason}
</p>
)}
</td>
{/* City */}
<td className="py-3.5 px-4">
<div className="text-[11px]">
<span className="text-white font-medium">{user.city || 'Cali'}</span>
{user.address && <p className="text-[10px] text-neutral-400 truncate max-w-xs">{user.address}</p>}
</div>
</td>
{/* Role Dropdown */}
<td className="py-3.5 px-4">
<select
value={user.role}
onChange={(e) => handleChangeRole(user, e.target.value as any)}
className={`text-[11px] font-bold px-2 py-1 rounded-lg border outline-hidden cursor-pointer ${
user.role === 'admin'
? 'bg-purple-500/20 border-purple-500/40 text-purple-300'
: user.role === 'operator'
? 'bg-fuchsia-500/20 border-fuchsia-500/40 text-fuchsia-300'
: 'bg-emerald-500/20 border-emerald-500/40 text-emerald-300'
}`}
>
<option value="admin" className="bg-neutral-900 text-white">👑 Admin Total</option>
<option value="operator" className="bg-neutral-900 text-white">📦 Operador Logística</option>
<option value="customer" className="bg-neutral-900 text-white">🛍️ Cliente Comprador</option>
</select>
</td>
{/* Actions (CRUD: Edit & Delete) */}
<td className="py-3.5 px-4 text-center">
<div className="flex items-center justify-center space-x-1.5">
<button
onClick={() => handleOpenEdit(user)}
className="text-neutral-400 hover:text-purple-300 p-1.5 rounded-lg hover:bg-purple-500/10 cursor-pointer transition-colors"
title="Editar datos del usuario"
>
<Edit className="w-4 h-4" />
</button>
<button
onClick={() => handleDeleteUser(user.id, user.name)}
className="text-neutral-400 hover:text-red-400 p-1.5 rounded-lg hover:bg-red-500/10 cursor-pointer transition-colors"
title="Eliminar usuario"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</td>
</tr>
);
})
)}
</tbody>
</table>
</div>
</div>
{/* Modal: Configuración de Garantía de Envío */}
{depositModalUser && (
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-xs flex items-center justify-center p-4 animate-in fade-in">
<div className="bg-neutral-900 border border-neutral-800 rounded-3xl max-w-md w-full p-6 space-y-4 shadow-2xl">
<div className="flex items-center justify-between border-b border-neutral-800 pb-3">
<h3 className="font-bold text-white text-base flex items-center gap-2">
<ShieldCheck className="w-5 h-5 text-amber-400" />
<span>Garantía de Envío / Anticipo de Flete</span>
</h3>
<button onClick={() => setDepositModalUser(null)} className="text-neutral-400 hover:text-white cursor-pointer">
<X className="w-5 h-5" />
</button>
</div>
<div className="bg-neutral-950 p-3.5 rounded-2xl border border-neutral-800 space-y-1 text-xs">
<p className="font-bold text-white">{depositModalUser.name}</p>
<p className="text-neutral-400">{depositModalUser.phone} • {depositModalUser.email}</p>
{depositModalUser.rejectedOrdersCount && depositModalUser.rejectedOrdersCount > 0 ? (
<p className="text-red-400 font-bold text-[11px] pt-1">
⚠️ Historial: {depositModalUser.rejectedOrdersCount} pedido(s) rechazado(s) en entrega.
</p>
) : null}
</div>
<div className="space-y-4 text-xs">
<div className="flex items-center justify-between bg-neutral-950 p-3.5 rounded-2xl border border-neutral-800">
<div>
<p className="font-bold text-white text-sm">Exigir Anticipo por Flete</p>
<p className="text-[11px] text-neutral-400">
Se le cobrará el flete de su ciudad como anticipo descontable al recibir.
</p>
</div>
<input
type="checkbox"
checked={depositRequires}
onChange={(e) => setDepositRequires(e.target.checked)}
className="w-5 h-5 accent-purple-600 rounded cursor-pointer"
/>
</div>
<div>
<label className="block text-neutral-300 font-bold mb-1">
Observación / Motivo Interno (Discreta)
</label>
<textarea
rows={3}
value={depositReasonInput}
onChange={(e) => setDepositReasonInput(e.target.value)}
placeholder="Ej. 1 pedido rechazado en portería / Exonerado por recomendación..."
className="w-full bg-neutral-950 border border-neutral-800 rounded-xl p-3 text-xs text-white outline-hidden focus:border-purple-400"
/>
<p className="text-[10px] text-neutral-500 mt-1">
* Esta observación es para control interno del equipo administrativo.
</p>
</div>
</div>
<div className="flex justify-end space-x-2 pt-3 border-t border-neutral-800">
<button
type="button"
onClick={() => setDepositModalUser(null)}
className="bg-neutral-800 hover:bg-neutral-700 text-neutral-300 font-bold py-2 px-4 rounded-xl cursor-pointer text-xs"
>
Cancelar
</button>
<button
type="button"
onClick={handleSaveDepositSettings}
className="bg-gradient-to-r from-amber-500 to-orange-500 hover:opacity-90 text-white font-bold py-2 px-4 rounded-xl shadow-md cursor-pointer text-xs"
>
Guardar Configuración
</button>
</div>
</div>
</div>
)}
{/* Modal Create User (POST) */}
{isModalOpen && (
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-xs flex items-center justify-center p-4 animate-in fade-in">
<div className="bg-neutral-900 border border-neutral-800 rounded-3xl max-w-md w-full p-6 space-y-4 shadow-2xl">
<div className="flex items-center justify-between border-b border-neutral-800 pb-3">
<h3 className="font-bold text-white text-base flex items-center gap-2">
<UserPlus className="w-5 h-5 text-purple-400" />
<span>Registrar Nuevo Usuario / Admin</span>
</h3>
<button onClick={() => setIsModalOpen(false)} className="text-neutral-400 hover:text-white cursor-pointer">
<X className="w-5 h-5" />
</button>
</div>
<form onSubmit={handleCreateUser} className="space-y-3 text-xs">
<div>
<label className="block text-neutral-400 font-bold mb-1">Nombre Completo *</label>
<input
type="text"
required
placeholder="Ej: Mateo Lasso"
value={name}
onChange={(e) => setName(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-400"
/>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-neutral-400 font-bold mb-1">Correo Electrónico *</label>
<input
type="email"
required
placeholder="correo@ejemplo.com"
value={email}
onChange={(e) => setEmail(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-400"
/>
</div>
<div>
<label className="block text-neutral-400 font-bold mb-1">Celular / WhatsApp *</label>
<input
type="tel"
required
placeholder="3001234567"
value={phone}
onChange={(e) => setPhone(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-400"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-neutral-400 font-bold mb-1">Ciudad Principal</label>
<input
type="text"
placeholder="Cali / Medellín / Bogotá"
value={city}
onChange={(e) => setCity(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-400"
/>
</div>
<div>
<label className="block text-neutral-400 font-bold mb-1">Contraseña Inicial</label>
<input
type="password"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(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-400"
/>
</div>
</div>
<div>
<label className="block text-neutral-400 font-bold mb-1">Dirección de Sede / Despachos</label>
<input
type="text"
placeholder="Calle 5 # 34-12"
value={address}
onChange={(e) => setAddress(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-400"
/>
</div>
<div>
<label className="block text-purple-400 font-bold mb-1">Rol Asignado *</label>
<select
value={role}
onChange={(e) => setRole(e.target.value as any)}
className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-white outline-hidden font-bold"
>
<option value="admin">👑 Super Administrador (Acceso Total)</option>
<option value="operator">📦 Operador de Despachos & Logística</option>
<option value="customer">🛍️ Cliente Registrado</option>
</select>
</div>
<div className="flex justify-end space-x-2 pt-3 border-t border-neutral-800">
<button
type="button"
onClick={() => setIsModalOpen(false)}
className="bg-neutral-800 hover:bg-neutral-700 text-neutral-300 font-bold py-2 px-4 rounded-xl cursor-pointer"
>
Cancelar
</button>
<button
type="submit"
className="bg-gradient-to-r from-purple-600 to-fuchsia-500 hover:from-purple-500 hover:to-fuchsia-400 text-white font-bold py-2 px-4 rounded-xl shadow-md cursor-pointer"
>
Crear Usuario
</button>
</div>
</form>
</div>
</div>
)}
{/* Modal Edit User (PUT) */}
{editingUser && (
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-xs flex items-center justify-center p-4 animate-in fade-in">
<div className="bg-neutral-900 border border-neutral-800 rounded-3xl max-w-md w-full p-6 space-y-4 shadow-2xl">
<div className="flex items-center justify-between border-b border-neutral-800 pb-3">
<h3 className="font-bold text-white text-base flex items-center gap-2">
<Edit className="w-5 h-5 text-purple-400" />
<span>Editar Usuario: {editingUser.name}</span>
</h3>
<button onClick={() => setEditingUser(null)} className="text-neutral-400 hover:text-white cursor-pointer">
<X className="w-5 h-5" />
</button>
</div>
<form onSubmit={handleSaveEdit} className="space-y-3 text-xs">
<div>
<label className="block text-neutral-400 font-bold mb-1">Nombre Completo *</label>
<input
type="text"
required
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 outline-hidden focus:border-purple-400"
/>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-neutral-400 font-bold mb-1">Correo Electrónico *</label>
<input
type="email"
required
value={editEmail}
onChange={(e) => setEditEmail(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-400"
/>
</div>
<div>
<label className="block text-neutral-400 font-bold mb-1">Celular / WhatsApp *</label>
<input
type="tel"
required
value={editPhone}
onChange={(e) => setEditPhone(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-400"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-neutral-400 font-bold mb-1">Ciudad Principal</label>
<input
type="text"
value={editCity}
onChange={(e) => setEditCity(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-400"
/>
</div>
<div>
<label className="block text-neutral-400 font-bold mb-1">Nueva Contraseña</label>
<input
type="password"
placeholder="•••••••• (dejar vacio si no cambia)"
value={editPassword}
onChange={(e) => setEditPassword(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-400"
/>
</div>
</div>
<div>
<label className="block text-neutral-400 font-bold mb-1">Dirección</label>
<input
type="text"
value={editAddress}
onChange={(e) => setEditAddress(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-400"
/>
</div>
<div>
<label className="block text-purple-400 font-bold mb-1">Rol y Privilegios *</label>
<select
value={editRole}
onChange={(e) => setEditRole(e.target.value as any)}
className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-white outline-hidden font-bold"
>
<option value="admin">👑 Super Administrador (Acceso Total)</option>
<option value="operator">📦 Operador de Despachos & Logística</option>
<option value="customer">🛍️ Cliente Registrado</option>
</select>
</div>
<div className="flex justify-end space-x-2 pt-3 border-t border-neutral-800">
<button
type="button"
onClick={() => setEditingUser(null)}
className="bg-neutral-800 hover:bg-neutral-700 text-neutral-300 font-bold py-2 px-4 rounded-xl cursor-pointer"
>
Cancelar
</button>
<button
type="submit"
className="bg-gradient-to-r from-purple-600 to-fuchsia-500 hover:from-purple-500 hover:to-fuchsia-400 text-white font-bold py-2 px-4 rounded-xl shadow-md cursor-pointer"
>
Guardar Cambios
</button>
</div>
</form>
</div>
</div>
)}
</div>
);
}