432 lines
18 KiB
JavaScript
432 lines
18 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import { getCurrentUser } from '../api/users.ts';
|
|
|
|
const Settings = () => {
|
|
const [activeTab, setActiveTab] = useState('profile');
|
|
const [currentUser, setCurrentUser] = useState(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
// Cargar información del usuario al montar el componente
|
|
useEffect(() => {
|
|
const loadUserData = async () => {
|
|
try {
|
|
const token = localStorage.getItem('access');
|
|
if (token) {
|
|
const userData = await getCurrentUser(token);
|
|
setCurrentUser(userData);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error al cargar datos del usuario:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
loadUserData();
|
|
}, []);
|
|
|
|
// Solo mostrar tabs permitidas si es importador
|
|
const isImportador = typeof window !== 'undefined' && localStorage.getItem('user_is_importador') === 'true';
|
|
const tabs = [
|
|
{ id: 'profile', name: 'Perfil', icon: 'user' },
|
|
{ id: 'organization', name: 'Organización', icon: 'building' },
|
|
{ id: 'security', name: 'Seguridad', icon: 'shield' },
|
|
{ id: 'notifications', name: 'Notificaciones', icon: 'bell' }
|
|
].filter(tab =>
|
|
isImportador
|
|
? tab.id === 'profile' || tab.id === 'security' || tab.id === 'notifications'
|
|
: true
|
|
);
|
|
|
|
const getTabIcon = (iconType) => {
|
|
const icons = {
|
|
user: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
|
</svg>
|
|
),
|
|
building: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
|
</svg>
|
|
),
|
|
shield: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
|
</svg>
|
|
),
|
|
bell: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 17h5l-5-5v5zM4 4h5l-5 5v-5z" />
|
|
</svg>
|
|
)
|
|
};
|
|
return icons[iconType];
|
|
};
|
|
|
|
const renderProfileTab = () => (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h3 className="text-lg font-medium text-gray-900 mb-4">Información Personal</h3>
|
|
|
|
{loading ? (
|
|
<div className="animate-pulse space-y-4">
|
|
<div className="flex items-center space-x-4">
|
|
<div className="w-20 h-20 bg-gray-200 rounded-full"></div>
|
|
<div className="space-y-2">
|
|
<div className="h-4 bg-gray-200 rounded w-32"></div>
|
|
<div className="h-3 bg-gray-200 rounded w-24"></div>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
<div className="h-10 bg-gray-200 rounded"></div>
|
|
<div className="h-10 bg-gray-200 rounded"></div>
|
|
<div className="h-10 bg-gray-200 rounded"></div>
|
|
<div className="h-10 bg-gray-200 rounded"></div>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<>
|
|
{/* Avatar y información básica */}
|
|
<div className="flex items-center space-x-6 mb-6">
|
|
<div className="flex-shrink-0">
|
|
{currentUser?.profile_picture ? (
|
|
<img
|
|
className="w-20 h-20 rounded-full object-cover"
|
|
src={currentUser.profile_picture}
|
|
alt="Avatar del usuario"
|
|
/>
|
|
) : (
|
|
<div className="w-20 h-20 bg-gray-300 rounded-full flex items-center justify-center">
|
|
<svg className="w-10 h-10 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
|
</svg>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div>
|
|
<h4 className="text-lg font-semibold text-gray-900">
|
|
{currentUser ? `${currentUser.first_name} ${currentUser.last_name}` : 'Usuario'}
|
|
</h4>
|
|
<p className="text-sm text-gray-500">
|
|
{currentUser?.username || 'Sin username'}
|
|
</p>
|
|
<p className="text-sm text-gray-500">
|
|
ID: {currentUser?.id || 'Sin ID'}
|
|
</p>
|
|
<button className="mt-2 text-sm text-indigo-600 hover:text-indigo-500">
|
|
Cambiar foto
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Formulario de información */}
|
|
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Nombre
|
|
</label>
|
|
<input
|
|
type="text"
|
|
defaultValue={currentUser?.first_name || ''}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm transition-colors"
|
|
placeholder="Ingresa tu nombre"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Apellido
|
|
</label>
|
|
<input
|
|
type="text"
|
|
defaultValue={currentUser?.last_name || ''}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm transition-colors"
|
|
placeholder="Ingresa tu apellido"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Email
|
|
</label>
|
|
<input
|
|
type="email"
|
|
defaultValue={currentUser?.email || ''}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm transition-colors"
|
|
placeholder="correo@ejemplo.com"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Username
|
|
</label>
|
|
<input
|
|
type="text"
|
|
defaultValue={currentUser?.username || ''}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm transition-colors"
|
|
placeholder="nombre_usuario"
|
|
/>
|
|
</div>
|
|
|
|
{currentUser?.rfc && (
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
RFC
|
|
</label>
|
|
<input
|
|
type="text"
|
|
defaultValue={currentUser.rfc}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm transition-colors"
|
|
placeholder="XXXX000000XXX"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Organización ID
|
|
</label>
|
|
<input
|
|
type="text"
|
|
defaultValue={currentUser?.organizacion || ''}
|
|
disabled
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-gray-50 text-gray-500 cursor-not-allowed text-sm"
|
|
placeholder="ID de organización"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-end space-x-3 pt-6 border-t border-gray-200">
|
|
<button
|
|
type="button"
|
|
className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors"
|
|
>
|
|
Cancelar
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors"
|
|
>
|
|
Guardar cambios
|
|
</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
const renderOrganizationTab = () => (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h3 className="text-lg font-medium text-gray-900 mb-4">Configuración de Organización</h3>
|
|
<p className="text-sm text-gray-600 mb-6">
|
|
Gestiona la configuración relacionada con tu organización.
|
|
</p>
|
|
|
|
<div className="bg-yellow-50 border border-yellow-200 rounded-md p-4">
|
|
<div className="flex">
|
|
<div className="flex-shrink-0">
|
|
<svg className="h-5 w-5 text-yellow-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
|
</svg>
|
|
</div>
|
|
<div className="ml-3">
|
|
<h3 className="text-sm font-medium text-yellow-800">Próximamente</h3>
|
|
<div className="mt-2 text-sm text-yellow-700">
|
|
<p>Las configuraciones de organización estarán disponibles pronto.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
const renderSecurityTab = () => (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h3 className="text-lg font-medium text-gray-900 mb-4">Seguridad</h3>
|
|
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h4 className="text-sm font-medium text-gray-900 mb-2">Cambiar contraseña</h4>
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Contraseña actual
|
|
</label>
|
|
<input
|
|
type="password"
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm transition-colors"
|
|
placeholder="Ingresa tu contraseña actual"
|
|
/>
|
|
</div>
|
|
<div></div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Nueva contraseña
|
|
</label>
|
|
<input
|
|
type="password"
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm transition-colors"
|
|
placeholder="Ingresa una nueva contraseña"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Confirmar nueva contraseña
|
|
</label>
|
|
<input
|
|
type="password"
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm transition-colors"
|
|
placeholder="Confirma tu nueva contraseña"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="mt-6">
|
|
<button
|
|
type="button"
|
|
className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors"
|
|
>
|
|
Actualizar contraseña
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
const renderNotificationsTab = () => (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h3 className="text-lg font-medium text-gray-900 mb-4">Notificaciones</h3>
|
|
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-900">
|
|
Notificaciones por email
|
|
</label>
|
|
<p className="text-sm text-gray-500">
|
|
Recibir notificaciones importantes por correo electrónico
|
|
</p>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
className="bg-indigo-600 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
>
|
|
<span className="translate-x-5 pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"></span>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-900">
|
|
Notificaciones de documentos
|
|
</label>
|
|
<p className="text-sm text-gray-500">
|
|
Notificar cuando se suban o actualicen documentos
|
|
</p>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
className="bg-indigo-600 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
>
|
|
<span className="translate-x-5 pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"></span>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<label className="text-sm font-medium text-gray-900">
|
|
Notificaciones del sistema
|
|
</label>
|
|
<p className="text-sm text-gray-500">
|
|
Recibir actualizaciones del sistema y mantenimiento
|
|
</p>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
className="bg-gray-200 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
>
|
|
<span className="translate-x-0 pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
const renderTabContent = () => {
|
|
switch (activeTab) {
|
|
case 'profile':
|
|
return renderProfileTab();
|
|
case 'organization':
|
|
return renderOrganizationTab();
|
|
case 'security':
|
|
return renderSecurityTab();
|
|
case 'notifications':
|
|
return renderNotificationsTab();
|
|
default:
|
|
return renderProfileTab();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="max-w-6xl mx-auto p-6">
|
|
{/* Header */}
|
|
<div className="mb-8">
|
|
<h1 className="text-3xl font-bold text-gray-900">Configuración</h1>
|
|
<p className="mt-2 text-gray-600">
|
|
Gestiona tu perfil, configuración de cuenta y preferencias.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="lg:grid lg:grid-cols-12 lg:gap-x-5">
|
|
{/* Sidebar de navegación */}
|
|
<aside className="py-6 px-2 sm:px-6 lg:py-0 lg:px-0 lg:col-span-3">
|
|
<nav className="space-y-1">
|
|
{tabs.map((tab) => (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => setActiveTab(tab.id)}
|
|
className={`${
|
|
activeTab === tab.id
|
|
? 'bg-indigo-50 border-indigo-500 text-indigo-700'
|
|
: 'border-transparent text-gray-900 hover:bg-gray-50 hover:text-gray-900'
|
|
} group border-l-4 px-3 py-2 flex items-center text-sm font-medium w-full text-left`}
|
|
>
|
|
<span
|
|
className={`${
|
|
activeTab === tab.id
|
|
? 'text-indigo-500'
|
|
: 'text-gray-400 group-hover:text-gray-500'
|
|
} flex-shrink-0 -ml-1 mr-3 h-6 w-6`}
|
|
>
|
|
{getTabIcon(tab.icon)}
|
|
</span>
|
|
<span className="truncate">{tab.name}</span>
|
|
</button>
|
|
))}
|
|
</nav>
|
|
</aside>
|
|
|
|
{/* Contenido principal */}
|
|
<div className="space-y-6 sm:px-6 lg:px-0 lg:col-span-9">
|
|
<div className="bg-white shadow rounded-lg">
|
|
<div className="px-4 py-5 sm:p-6">
|
|
{renderTabContent()}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Settings;
|