Add new Frontend

This commit is contained in:
Urtzi Alfaro
2025-08-03 19:23:20 +02:00
parent 03e9dc6469
commit 376ce3ee0d
45 changed files with 5352 additions and 9230 deletions

View File

@@ -0,0 +1,616 @@
import React, { useState } from 'react';
import {
User,
Bell,
Shield,
Globe,
Smartphone,
Mail,
LogOut,
Save,
ChevronRight,
MapPin,
Clock,
DollarSign
} from 'lucide-react';
import toast from 'react-hot-toast';
interface SettingsPageProps {
user: any;
onLogout: () => void;
}
interface UserSettings {
fullName: string;
email: string;
phone: string;
language: string;
timezone: string;
currency: string;
bakeryName: string;
bakeryAddress: string;
businessType: string;
}
interface NotificationSettings {
emailNotifications: boolean;
smsNotifications: boolean;
dailyReports: boolean;
weeklyReports: boolean;
forecastAlerts: boolean;
stockAlerts: boolean;
orderReminders: boolean;
}
const SettingsPage: React.FC<SettingsPageProps> = ({ user, onLogout }) => {
const [activeTab, setActiveTab] = useState('profile');
const [isLoading, setIsLoading] = useState(false);
const [userSettings, setUserSettings] = useState<UserSettings>({
fullName: user.fullName || '',
email: user.email || '',
phone: '',
language: 'es',
timezone: 'Europe/Madrid',
currency: 'EUR',
bakeryName: 'Mi Panadería',
bakeryAddress: '',
businessType: 'individual'
});
const [notificationSettings, setNotificationSettings] = useState<NotificationSettings>({
emailNotifications: true,
smsNotifications: false,
dailyReports: true,
weeklyReports: true,
forecastAlerts: true,
stockAlerts: true,
orderReminders: true
});
const tabs = [
{ id: 'profile', label: 'Perfil', icon: User },
{ id: 'notifications', label: 'Notificaciones', icon: Bell },
{ id: 'security', label: 'Seguridad', icon: Shield },
{ id: 'preferences', label: 'Preferencias', icon: Globe },
];
const handleSaveSettings = async () => {
setIsLoading(true);
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
toast.success('Configuración guardada exitosamente');
} catch (error) {
toast.error('Error al guardar la configuración');
} finally {
setIsLoading(false);
}
};
const handleLogout = () => {
if (window.confirm('¿Estás seguro de que quieres cerrar sesión?')) {
onLogout();
}
};
const renderProfileTab = () => (
<div className="space-y-6">
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4">Información Personal</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Nombre completo
</label>
<input
type="text"
value={userSettings.fullName}
onChange={(e) => setUserSettings(prev => ({ ...prev, fullName: e.target.value }))}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Correo electrónico
</label>
<input
type="email"
value={userSettings.email}
onChange={(e) => setUserSettings(prev => ({ ...prev, email: e.target.value }))}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Teléfono
</label>
<input
type="tel"
value={userSettings.phone}
onChange={(e) => setUserSettings(prev => ({ ...prev, phone: e.target.value }))}
placeholder="+34 600 000 000"
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
/>
</div>
</div>
</div>
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4">Información del Negocio</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Nombre de la panadería
</label>
<input
type="text"
value={userSettings.bakeryName}
onChange={(e) => setUserSettings(prev => ({ ...prev, bakeryName: e.target.value }))}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Dirección
</label>
<div className="relative">
<MapPin className="absolute left-3 top-3 h-5 w-5 text-gray-400" />
<input
type="text"
value={userSettings.bakeryAddress}
onChange={(e) => setUserSettings(prev => ({ ...prev, bakeryAddress: e.target.value }))}
placeholder="Calle Mayor, 123, Madrid"
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Tipo de negocio
</label>
<select
value={userSettings.businessType}
onChange={(e) => setUserSettings(prev => ({ ...prev, businessType: e.target.value }))}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
>
<option value="individual">Panadería Individual</option>
<option value="central_workshop">Obrador Central</option>
</select>
</div>
</div>
</div>
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4">Horarios de Operación</h3>
<div className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Hora de apertura
</label>
<input
type="time"
defaultValue="07:00"
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Hora de cierre
</label>
<input
type="time"
defaultValue="20:00"
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-3">
Días de operación
</label>
<div className="grid grid-cols-4 gap-2 sm:grid-cols-7">
{['L', 'M', 'X', 'J', 'V', 'S', 'D'].map((day, index) => (
<label key={day} className="flex items-center justify-center">
<input
type="checkbox"
defaultChecked={index < 6} // Monday to Saturday checked by default
className="sr-only peer"
/>
<div className="w-10 h-10 bg-gray-200 peer-checked:bg-primary-500 peer-checked:text-white rounded-lg flex items-center justify-center font-medium text-sm cursor-pointer transition-colors">
{day}
</div>
</label>
))}
</div>
</div>
</div>
</div>
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4">Integración con POS</h3>
<div className="space-y-4">
<div className="p-4 bg-gray-50 rounded-lg">
<div className="flex items-center justify-between mb-3">
<div>
<h4 className="font-medium text-gray-900">Sistema POS Conectado</h4>
<p className="text-sm text-gray-600">Sincroniza ventas automáticamente</p>
</div>
<span className="px-2 py-1 bg-red-100 text-red-800 rounded text-xs">
Desconectado
</span>
</div>
<button className="w-full px-4 py-2 bg-primary-500 text-white rounded-lg hover:bg-primary-600 transition-colors">
Conectar Sistema POS
</button>
</div>
</div>
</div>
</div>
);
const renderNotificationsTab = () => (
<div className="space-y-6">
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4">Canales de Notificación</h3>
<div className="space-y-4">
<div className="flex items-center justify-between p-4 bg-gray-50 rounded-lg">
<div className="flex items-center">
<Mail className="h-5 w-5 text-gray-600 mr-3" />
<div>
<div className="font-medium text-gray-900">Notificaciones por Email</div>
<div className="text-sm text-gray-500">Recibe alertas y reportes por correo</div>
</div>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={notificationSettings.emailNotifications}
onChange={(e) => setNotificationSettings(prev => ({
...prev,
emailNotifications: e.target.checked
}))}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary-600"></div>
</label>
</div>
<div className="flex items-center justify-between p-4 bg-gray-50 rounded-lg">
<div className="flex items-center">
<Smartphone className="h-5 w-5 text-gray-600 mr-3" />
<div>
<div className="font-medium text-gray-900">Notificaciones SMS</div>
<div className="text-sm text-gray-500">Alertas urgentes por mensaje de texto</div>
</div>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={notificationSettings.smsNotifications}
onChange={(e) => setNotificationSettings(prev => ({
...prev,
smsNotifications: e.target.checked
}))}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary-600"></div>
</label>
</div>
</div>
</div>
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4">Tipos de Notificación</h3>
<div className="space-y-3">
{[
{ key: 'dailyReports', label: 'Reportes Diarios', desc: 'Resumen diario de ventas y predicciones' },
{ key: 'weeklyReports', label: 'Reportes Semanales', desc: 'Análisis semanal de rendimiento' },
{ key: 'forecastAlerts', label: 'Alertas de Predicción', desc: 'Cambios significativos en demanda' },
{ key: 'stockAlerts', label: 'Alertas de Stock', desc: 'Inventario bajo o próximos vencimientos' },
{ key: 'orderReminders', label: 'Recordatorios de Pedidos', desc: 'Próximas entregas y fechas límite' }
].map((item) => (
<div key={item.key} className="flex items-center justify-between p-3 border border-gray-200 rounded-lg">
<div>
<div className="font-medium text-gray-900">{item.label}</div>
<div className="text-sm text-gray-500">{item.desc}</div>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={notificationSettings[item.key as keyof NotificationSettings] as boolean}
onChange={(e) => setNotificationSettings(prev => ({
...prev,
[item.key]: e.target.checked
}))}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary-600"></div>
</label>
</div>
))}
</div>
</div>
</div>
);
const renderSecurityTab = () => (
<div className="space-y-6">
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4">Cambiar Contraseña</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Contraseña actual
</label>
<input
type="password"
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
placeholder="••••••••"
/>
</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-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
placeholder="••••••••"
/>
</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-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
placeholder="••••••••"
/>
</div>
<button className="w-full sm:w-auto px-6 py-3 bg-primary-500 text-white rounded-xl hover:bg-primary-600 transition-colors">
Actualizar Contraseña
</button>
</div>
</div>
<div className="border-t pt-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Sesiones Activas</h3>
<div className="space-y-3">
<div className="flex items-center justify-between p-4 border border-gray-200 rounded-lg">
<div>
<div className="font-medium text-gray-900">Navegador actual</div>
<div className="text-sm text-gray-500">Chrome en Windows Madrid, España</div>
<div className="text-xs text-green-600 mt-1">Sesión actual</div>
</div>
</div>
<div className="flex items-center justify-between p-4 border border-gray-200 rounded-lg">
<div>
<div className="font-medium text-gray-900">Mobile App</div>
<div className="text-sm text-gray-500">iPhone Hace 2 días</div>
</div>
<button className="text-red-600 hover:text-red-700 text-sm">
Cerrar sesión
</button>
</div>
</div>
</div>
<div className="border-t pt-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4 text-red-600">Zona Peligrosa</h3>
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<h4 className="font-medium text-red-900 mb-2">Eliminar Cuenta</h4>
<p className="text-red-800 text-sm mb-4">
Esta acción eliminará permanentemente tu cuenta y todos los datos asociados.
No se puede deshacer.
</p>
<button className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors text-sm">
Eliminar Cuenta
</button>
</div>
</div>
</div>
);
const renderPreferencesTab = () => (
<div className="space-y-6">
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4">Configuración Regional</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
<Globe className="inline h-4 w-4 mr-1" />
Idioma
</label>
<select
value={userSettings.language}
onChange={(e) => setUserSettings(prev => ({ ...prev, language: e.target.value }))}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
>
<option value="es">Español</option>
<option value="en">English</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
<Clock className="inline h-4 w-4 mr-1" />
Zona horaria
</label>
<select
value={userSettings.timezone}
onChange={(e) => setUserSettings(prev => ({ ...prev, timezone: e.target.value }))}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
>
<option value="Europe/Madrid">Europa/Madrid (CET)</option>
<option value="Europe/London">Europa/Londres (GMT)</option>
<option value="America/New_York">América/Nueva York (EST)</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
<DollarSign className="inline h-4 w-4 mr-1" />
Moneda
</label>
<select
value={userSettings.currency}
onChange={(e) => setUserSettings(prev => ({ ...prev, currency: e.target.value }))}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
>
<option value="EUR">Euro ()</option>
<option value="USD">Dólar americano ($)</option>
<option value="GBP">Libra esterlina (£)</option>
</select>
</div>
</div>
</div>
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4">Exportar Datos</h3>
<div className="space-y-3">
<button className="w-full p-4 border border-gray-300 rounded-lg hover:border-primary-500 hover:bg-primary-50 transition-all text-left">
<div className="flex items-center justify-between">
<div>
<div className="font-medium text-gray-900">Exportar todas las predicciones</div>
<div className="text-sm text-gray-500">Descargar historial completo en CSV</div>
</div>
<ChevronRight className="h-5 w-5 text-gray-400" />
</div>
</button>
<button className="w-full p-4 border border-gray-300 rounded-lg hover:border-primary-500 hover:bg-primary-50 transition-all text-left">
<div className="flex items-center justify-between">
<div>
<div className="font-medium text-gray-900">Exportar datos de ventas</div>
<div className="text-sm text-gray-500">Historial de ventas y análisis</div>
</div>
<ChevronRight className="h-5 w-5 text-gray-400" />
</div>
</button>
<button className="w-full p-4 border border-gray-300 rounded-lg hover:border-primary-500 hover:bg-primary-50 transition-all text-left">
<div className="flex items-center justify-between">
<div>
<div className="font-medium text-gray-900">Exportar configuración</div>
<div className="text-sm text-gray-500">Respaldo de toda la configuración</div>
</div>
<ChevronRight className="h-5 w-5 text-gray-400" />
</div>
</button>
</div>
</div>
</div>
);
const renderTabContent = () => {
switch (activeTab) {
case 'profile':
return renderProfileTab();
case 'notifications':
return renderNotificationsTab();
case 'security':
return renderSecurityTab();
case 'preferences':
return renderPreferencesTab();
default:
return renderProfileTab();
}
};
return (
<div className="p-6 max-w-6xl mx-auto">
{/* Header */}
<div className="mb-8">
<h1 className="text-2xl font-bold text-gray-900 mb-2">Configuración</h1>
<p className="text-gray-600">
Administra tu cuenta y personaliza tu experiencia en PanIA
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
{/* Sidebar Navigation */}
<div className="lg:col-span-1">
<nav className="space-y-1">
{tabs.map((tab) => {
const Icon = tab.icon;
return (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`w-full flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all ${
activeTab === tab.id
? 'bg-primary-100 text-primary-700 shadow-soft'
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'
}`}
>
<Icon className="h-5 w-5 mr-3" />
{tab.label}
</button>
);
})}
{/* Logout Button */}
<button
onClick={handleLogout}
className="w-full flex items-center px-4 py-3 text-sm font-medium text-red-600 hover:text-red-700 hover:bg-red-50 rounded-lg transition-all mt-6"
>
<LogOut className="h-5 w-5 mr-3" />
Cerrar Sesión
</button>
</nav>
</div>
{/* Main Content */}
<div className="lg:col-span-3">
<div className="bg-white rounded-xl shadow-soft p-6">
{renderTabContent()}
{/* Save Button */}
{(activeTab === 'profile' || activeTab === 'notifications' || activeTab === 'preferences') && (
<div className="mt-8 pt-6 border-t border-gray-200">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between">
<p className="text-sm text-gray-600 mb-4 sm:mb-0">
Los cambios se guardarán automáticamente
</p>
<button
onClick={handleSaveSettings}
disabled={isLoading}
className="inline-flex items-center px-6 py-3 bg-primary-500 text-white rounded-xl hover:bg-primary-600 transition-all hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
Guardando...
</>
) : (
<>
<Save className="h-4 w-4 mr-2" />
Guardar Cambios
</>
)}
</button>
</div>
</div>
)}
</div>
</div>
</div>
</div>
);
};
export default SettingsPage;