Start integrating the onboarding flow with backend 6

This commit is contained in:
Urtzi Alfaro
2025-09-05 17:49:48 +02:00
parent 236c3a32ae
commit 069954981a
131 changed files with 5217 additions and 22838 deletions

View File

@@ -3,7 +3,7 @@ import { Store, MapPin, Clock, Phone, Mail, Globe, Save, X, Edit3, Zap, Plus, Se
import { Button, Card, Input, Select, Modal, Badge } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
import { useToast } from '../../../../hooks/ui/useToast';
import { posService, POSConfiguration } from '../../../../services/api/pos.service';
import { posService, POSConfiguration } from '../../../../api/services/pos.service';
interface BakeryConfig {
// General Info

View File

@@ -1,481 +0,0 @@
import React, { useState } from 'react';
import { Store, MapPin, Clock, Phone, Mail, Globe, Save, RotateCcw } from 'lucide-react';
import { Button, Card, Input } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const BakeryConfigPage: React.FC = () => {
const [config, setConfig] = useState({
general: {
name: 'Panadería Artesanal San Miguel',
description: 'Panadería tradicional con más de 30 años de experiencia',
logo: '',
website: 'https://panaderiasanmiguel.com',
email: 'info@panaderiasanmiguel.com',
phone: '+34 912 345 678'
},
location: {
address: 'Calle Mayor 123',
city: 'Madrid',
postalCode: '28001',
country: 'España',
coordinates: {
lat: 40.4168,
lng: -3.7038
}
},
schedule: {
monday: { open: '07:00', close: '20:00', closed: false },
tuesday: { open: '07:00', close: '20:00', closed: false },
wednesday: { open: '07:00', close: '20:00', closed: false },
thursday: { open: '07:00', close: '20:00', closed: false },
friday: { open: '07:00', close: '20:00', closed: false },
saturday: { open: '08:00', close: '14:00', closed: false },
sunday: { open: '09:00', close: '13:00', closed: false }
},
business: {
taxId: 'B12345678',
registrationNumber: 'REG-2024-001',
licenseNumber: 'LIC-FOOD-2024',
currency: 'EUR',
timezone: 'Europe/Madrid',
language: 'es'
},
preferences: {
enableOnlineOrders: true,
enableReservations: false,
enableDelivery: true,
deliveryRadius: 5,
minimumOrderAmount: 15.00,
enableLoyaltyProgram: true,
autoBackup: true,
emailNotifications: true,
smsNotifications: false
}
});
const [hasChanges, setHasChanges] = useState(false);
const [activeTab, setActiveTab] = useState('general');
const tabs = [
{ id: 'general', label: 'General', icon: Store },
{ id: 'location', label: 'Ubicación', icon: MapPin },
{ id: 'schedule', label: 'Horarios', icon: Clock },
{ id: 'business', label: 'Empresa', icon: Globe }
];
const daysOfWeek = [
{ key: 'monday', label: 'Lunes' },
{ key: 'tuesday', label: 'Martes' },
{ key: 'wednesday', label: 'Miércoles' },
{ key: 'thursday', label: 'Jueves' },
{ key: 'friday', label: 'Viernes' },
{ key: 'saturday', label: 'Sábado' },
{ key: 'sunday', label: 'Domingo' }
];
const handleInputChange = (section: string, field: string, value: any) => {
setConfig(prev => ({
...prev,
[section]: {
...prev[section as keyof typeof prev],
[field]: value
}
}));
setHasChanges(true);
};
const handleScheduleChange = (day: string, field: string, value: any) => {
setConfig(prev => ({
...prev,
schedule: {
...prev.schedule,
[day]: {
...prev.schedule[day as keyof typeof prev.schedule],
[field]: value
}
}
}));
setHasChanges(true);
};
const handleSave = () => {
// Handle save logic
console.log('Saving bakery config:', config);
setHasChanges(false);
};
const handleReset = () => {
// Reset to defaults
setHasChanges(false);
};
return (
<div className="p-6 space-y-6">
<PageHeader
title="Configuración de Panadería"
description="Configura los datos básicos y preferencias de tu panadería"
action={
<div className="flex space-x-2">
<Button variant="outline" onClick={handleReset}>
<RotateCcw className="w-4 h-4 mr-2" />
Restaurar
</Button>
<Button onClick={handleSave} disabled={!hasChanges}>
<Save className="w-4 h-4 mr-2" />
Guardar Cambios
</Button>
</div>
}
/>
<div className="flex flex-col lg:flex-row gap-6">
{/* Sidebar */}
<div className="w-full lg:w-64">
<Card className="p-4">
<nav className="space-y-2">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`w-full flex items-center space-x-3 px-3 py-2 text-left rounded-lg transition-colors ${
activeTab === tab.id
? 'bg-blue-100 text-blue-700'
: 'text-gray-600 hover:bg-gray-100'
}`}
>
<tab.icon className="w-4 h-4" />
<span className="text-sm font-medium">{tab.label}</span>
</button>
))}
</nav>
</Card>
</div>
{/* Content */}
<div className="flex-1">
{activeTab === 'general' && (
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-6">Información General</h3>
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Nombre de la Panadería
</label>
<Input
value={config.general.name}
onChange={(e) => handleInputChange('general', 'name', e.target.value)}
placeholder="Nombre de tu panadería"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Sitio Web
</label>
<Input
value={config.general.website}
onChange={(e) => handleInputChange('general', 'website', e.target.value)}
placeholder="https://tu-panaderia.com"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Descripción
</label>
<textarea
value={config.general.description}
onChange={(e) => handleInputChange('general', 'description', e.target.value)}
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
placeholder="Describe tu panadería..."
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Email de Contacto
</label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input
value={config.general.email}
onChange={(e) => handleInputChange('general', 'email', e.target.value)}
className="pl-10"
type="email"
placeholder="contacto@panaderia.com"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Teléfono
</label>
<div className="relative">
<Phone className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input
value={config.general.phone}
onChange={(e) => handleInputChange('general', 'phone', e.target.value)}
className="pl-10"
type="tel"
placeholder="+34 912 345 678"
/>
</div>
</div>
</div>
</div>
</Card>
)}
{activeTab === 'location' && (
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-6">Ubicación</h3>
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Dirección
</label>
<Input
value={config.location.address}
onChange={(e) => handleInputChange('location', 'address', e.target.value)}
placeholder="Calle, número, etc."
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Ciudad
</label>
<Input
value={config.location.city}
onChange={(e) => handleInputChange('location', 'city', e.target.value)}
placeholder="Ciudad"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Código Postal
</label>
<Input
value={config.location.postalCode}
onChange={(e) => handleInputChange('location', 'postalCode', e.target.value)}
placeholder="28001"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
País
</label>
<Input
value={config.location.country}
onChange={(e) => handleInputChange('location', 'country', e.target.value)}
placeholder="España"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Latitud
</label>
<Input
value={config.location.coordinates.lat}
onChange={(e) => handleInputChange('location', 'coordinates', {
...config.location.coordinates,
lat: parseFloat(e.target.value) || 0
})}
type="number"
step="0.000001"
placeholder="40.4168"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Longitud
</label>
<Input
value={config.location.coordinates.lng}
onChange={(e) => handleInputChange('location', 'coordinates', {
...config.location.coordinates,
lng: parseFloat(e.target.value) || 0
})}
type="number"
step="0.000001"
placeholder="-3.7038"
/>
</div>
</div>
</div>
</Card>
)}
{activeTab === 'schedule' && (
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-6">Horarios de Apertura</h3>
<div className="space-y-4">
{daysOfWeek.map((day) => {
const schedule = config.schedule[day.key as keyof typeof config.schedule];
return (
<div key={day.key} className="flex items-center space-x-4 p-4 border rounded-lg">
<div className="w-20">
<span className="text-sm font-medium text-gray-700">{day.label}</span>
</div>
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={schedule.closed}
onChange={(e) => handleScheduleChange(day.key, 'closed', e.target.checked)}
className="rounded border-gray-300"
/>
<span className="text-sm text-gray-600">Cerrado</span>
</label>
{!schedule.closed && (
<>
<div>
<label className="block text-xs text-gray-500 mb-1">Apertura</label>
<input
type="time"
value={schedule.open}
onChange={(e) => handleScheduleChange(day.key, 'open', e.target.value)}
className="px-3 py-1 border border-gray-300 rounded-md text-sm"
/>
</div>
<div>
<label className="block text-xs text-gray-500 mb-1">Cierre</label>
<input
type="time"
value={schedule.close}
onChange={(e) => handleScheduleChange(day.key, 'close', e.target.value)}
className="px-3 py-1 border border-gray-300 rounded-md text-sm"
/>
</div>
</>
)}
</div>
);
})}
</div>
</Card>
)}
{activeTab === 'business' && (
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-6">Datos de Empresa</h3>
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
NIF/CIF
</label>
<Input
value={config.business.taxId}
onChange={(e) => handleInputChange('business', 'taxId', e.target.value)}
placeholder="B12345678"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Número de Registro
</label>
<Input
value={config.business.registrationNumber}
onChange={(e) => handleInputChange('business', 'registrationNumber', e.target.value)}
placeholder="REG-2024-001"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Licencia Sanitaria
</label>
<Input
value={config.business.licenseNumber}
onChange={(e) => handleInputChange('business', 'licenseNumber', e.target.value)}
placeholder="LIC-FOOD-2024"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Moneda
</label>
<select
value={config.business.currency}
onChange={(e) => handleInputChange('business', 'currency', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
>
<option value="EUR">EUR (€)</option>
<option value="USD">USD ($)</option>
<option value="GBP">GBP (£)</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Zona Horaria
</label>
<select
value={config.business.timezone}
onChange={(e) => handleInputChange('business', 'timezone', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
>
<option value="Europe/Madrid">Madrid (GMT+1)</option>
<option value="Europe/London">Londres (GMT)</option>
<option value="America/New_York">Nueva York (GMT-5)</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Idioma
</label>
<select
value={config.business.language}
onChange={(e) => handleInputChange('business', 'language', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
>
<option value="es">Español</option>
<option value="en">English</option>
<option value="fr">Français</option>
</select>
</div>
</div>
</div>
</Card>
)}
</div>
</div>
{/* Save Changes Banner */}
{hasChanges && (
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 bg-blue-600 text-white px-6 py-3 rounded-lg shadow-lg flex items-center space-x-4">
<span className="text-sm">Tienes cambios sin guardar</span>
<div className="flex space-x-2">
<Button size="sm" variant="outline" className="text-blue-600 bg-white" onClick={handleReset}>
Descartar
</Button>
<Button size="sm" className="bg-blue-700 hover:bg-blue-800" onClick={handleSave}>
Guardar
</Button>
</div>
</div>
)}
</div>
);
};
export default BakeryConfigPage;

View File

@@ -3,4 +3,5 @@ export { default as ProfilePage } from './profile';
export { default as BakeryConfigPage } from './bakery-config';
export { default as TeamPage } from './team';
export { default as SubscriptionPage } from './subscription';
export { default as PreferencesPage } from './preferences';
export { default as PreferencesPage } from './preferences';
export { default as PreferencesPage } from './PreferencesPage';

View File

@@ -1,388 +0,0 @@
import React, { useState } from 'react';
import { Settings, Bell, Mail, MessageSquare, Smartphone, Save, RotateCcw } from 'lucide-react';
import { Button, Card } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const PreferencesPage: React.FC = () => {
const [preferences, setPreferences] = useState({
notifications: {
inventory: {
app: true,
email: false,
sms: true,
frequency: 'immediate'
},
sales: {
app: true,
email: true,
sms: false,
frequency: 'hourly'
},
production: {
app: true,
email: false,
sms: true,
frequency: 'immediate'
},
system: {
app: true,
email: true,
sms: false,
frequency: 'daily'
},
marketing: {
app: false,
email: true,
sms: false,
frequency: 'weekly'
}
},
global: {
doNotDisturb: false,
quietHours: {
enabled: false,
start: '22:00',
end: '07:00'
},
language: 'es',
timezone: 'Europe/Madrid',
soundEnabled: true,
vibrationEnabled: true
},
channels: {
email: 'panaderia@example.com',
phone: '+34 600 123 456',
slack: false,
webhook: ''
}
});
const [hasChanges, setHasChanges] = useState(false);
const categories = [
{
id: 'inventory',
name: 'Inventario',
description: 'Alertas de stock, reposiciones y vencimientos',
icon: '📦'
},
{
id: 'sales',
name: 'Ventas',
description: 'Pedidos, transacciones y reportes de ventas',
icon: '💰'
},
{
id: 'production',
name: 'Producción',
description: 'Hornadas, calidad y tiempos de producción',
icon: '🍞'
},
{
id: 'system',
name: 'Sistema',
description: 'Actualizaciones, mantenimiento y errores',
icon: '⚙️'
},
{
id: 'marketing',
name: 'Marketing',
description: 'Campañas, promociones y análisis',
icon: '📢'
}
];
const frequencies = [
{ value: 'immediate', label: 'Inmediato' },
{ value: 'hourly', label: 'Cada hora' },
{ value: 'daily', label: 'Diario' },
{ value: 'weekly', label: 'Semanal' }
];
const handleNotificationChange = (category: string, channel: string, value: boolean) => {
setPreferences(prev => ({
...prev,
notifications: {
...prev.notifications,
[category]: {
...prev.notifications[category as keyof typeof prev.notifications],
[channel]: value
}
}
}));
setHasChanges(true);
};
const handleFrequencyChange = (category: string, frequency: string) => {
setPreferences(prev => ({
...prev,
notifications: {
...prev.notifications,
[category]: {
...prev.notifications[category as keyof typeof prev.notifications],
frequency
}
}
}));
setHasChanges(true);
};
const handleGlobalChange = (setting: string, value: any) => {
setPreferences(prev => ({
...prev,
global: {
...prev.global,
[setting]: value
}
}));
setHasChanges(true);
};
const handleChannelChange = (channel: string, value: string | boolean) => {
setPreferences(prev => ({
...prev,
channels: {
...prev.channels,
[channel]: value
}
}));
setHasChanges(true);
};
const handleSave = () => {
// Handle save logic
console.log('Saving preferences:', preferences);
setHasChanges(false);
};
const handleReset = () => {
// Reset to defaults
setHasChanges(false);
};
const getChannelIcon = (channel: string) => {
switch (channel) {
case 'app':
return <Bell className="w-4 h-4" />;
case 'email':
return <Mail className="w-4 h-4" />;
case 'sms':
return <Smartphone className="w-4 h-4" />;
default:
return <MessageSquare className="w-4 h-4" />;
}
};
return (
<div className="p-6 space-y-6">
<PageHeader
title="Preferencias de Comunicación"
description="Configura cómo y cuándo recibir notificaciones"
action={
<div className="flex space-x-2">
<Button variant="outline" onClick={handleReset}>
<RotateCcw className="w-4 h-4 mr-2" />
Restaurar
</Button>
<Button onClick={handleSave} disabled={!hasChanges}>
<Save className="w-4 h-4 mr-2" />
Guardar Cambios
</Button>
</div>
}
/>
{/* Global Settings */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Configuración General</h3>
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={preferences.global.doNotDisturb}
onChange={(e) => handleGlobalChange('doNotDisturb', e.target.checked)}
className="rounded border-gray-300"
/>
<span className="text-sm font-medium text-gray-700">No molestar</span>
</label>
<p className="text-xs text-gray-500 mt-1">Silencia todas las notificaciones</p>
</div>
<div>
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={preferences.global.soundEnabled}
onChange={(e) => handleGlobalChange('soundEnabled', e.target.checked)}
className="rounded border-gray-300"
/>
<span className="text-sm font-medium text-gray-700">Sonidos</span>
</label>
<p className="text-xs text-gray-500 mt-1">Reproducir sonidos de notificación</p>
</div>
</div>
<div>
<label className="flex items-center space-x-2 mb-2">
<input
type="checkbox"
checked={preferences.global.quietHours.enabled}
onChange={(e) => handleGlobalChange('quietHours', {
...preferences.global.quietHours,
enabled: e.target.checked
})}
className="rounded border-gray-300"
/>
<span className="text-sm font-medium text-gray-700">Horas silenciosas</span>
</label>
{preferences.global.quietHours.enabled && (
<div className="flex space-x-4 ml-6">
<div>
<label className="block text-xs text-gray-500 mb-1">Desde</label>
<input
type="time"
value={preferences.global.quietHours.start}
onChange={(e) => handleGlobalChange('quietHours', {
...preferences.global.quietHours,
start: e.target.value
})}
className="px-3 py-1 border border-gray-300 rounded-md text-sm"
/>
</div>
<div>
<label className="block text-xs text-gray-500 mb-1">Hasta</label>
<input
type="time"
value={preferences.global.quietHours.end}
onChange={(e) => handleGlobalChange('quietHours', {
...preferences.global.quietHours,
end: e.target.value
})}
className="px-3 py-1 border border-gray-300 rounded-md text-sm"
/>
</div>
</div>
)}
</div>
</div>
</Card>
{/* Channel Settings */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Canales de Comunicación</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Email</label>
<input
type="email"
value={preferences.channels.email}
onChange={(e) => handleChannelChange('email', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
placeholder="tu-email@ejemplo.com"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Teléfono (SMS)</label>
<input
type="tel"
value={preferences.channels.phone}
onChange={(e) => handleChannelChange('phone', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
placeholder="+34 600 123 456"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Webhook URL</label>
<input
type="url"
value={preferences.channels.webhook}
onChange={(e) => handleChannelChange('webhook', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
placeholder="https://tu-webhook.com/notifications"
/>
<p className="text-xs text-gray-500 mt-1">URL para recibir notificaciones JSON</p>
</div>
</div>
</Card>
{/* Category Preferences */}
<div className="space-y-4">
{categories.map((category) => {
const categoryPrefs = preferences.notifications[category.id as keyof typeof preferences.notifications];
return (
<Card key={category.id} className="p-6">
<div className="flex items-start space-x-4">
<div className="text-2xl">{category.icon}</div>
<div className="flex-1">
<h3 className="text-lg font-semibold text-gray-900 mb-1">{category.name}</h3>
<p className="text-sm text-gray-600 mb-4">{category.description}</p>
<div className="space-y-4">
{/* Channel toggles */}
<div>
<h4 className="text-sm font-medium text-gray-700 mb-2">Canales</h4>
<div className="flex space-x-6">
{['app', 'email', 'sms'].map((channel) => (
<label key={channel} className="flex items-center space-x-2">
<input
type="checkbox"
checked={categoryPrefs[channel as keyof typeof categoryPrefs] as boolean}
onChange={(e) => handleNotificationChange(category.id, channel, e.target.checked)}
className="rounded border-gray-300"
/>
<div className="flex items-center space-x-1">
{getChannelIcon(channel)}
<span className="text-sm text-gray-700 capitalize">{channel}</span>
</div>
</label>
))}
</div>
</div>
{/* Frequency */}
<div>
<h4 className="text-sm font-medium text-gray-700 mb-2">Frecuencia</h4>
<select
value={categoryPrefs.frequency}
onChange={(e) => handleFrequencyChange(category.id, e.target.value)}
className="px-3 py-2 border border-gray-300 rounded-md text-sm"
>
{frequencies.map((freq) => (
<option key={freq.value} value={freq.value}>
{freq.label}
</option>
))}
</select>
</div>
</div>
</div>
</div>
</Card>
);
})}
</div>
{/* Save Changes Banner */}
{hasChanges && (
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 bg-blue-600 text-white px-6 py-3 rounded-lg shadow-lg flex items-center space-x-4">
<span className="text-sm">Tienes cambios sin guardar</span>
<div className="flex space-x-2">
<Button size="sm" variant="outline" className="text-blue-600 bg-white" onClick={handleReset}>
Descartar
</Button>
<Button size="sm" className="bg-blue-700 hover:bg-blue-800" onClick={handleSave}>
Guardar
</Button>
</div>
</div>
)}
</div>
);
};
export default PreferencesPage;

View File

@@ -37,7 +37,7 @@ import {
subscriptionService,
type UsageSummary,
type AvailablePlans
} from '../../../../services/api';
} from '../../../../api/services';
import { isMockMode, getMockSubscription } from '../../../../config/mock.config';
interface PlanComparisonProps {

View File

@@ -37,7 +37,7 @@ import {
subscriptionService,
type UsageSummary,
type AvailablePlans
} from '../../../../services/api';
} from '../../../../api/services';
import { isMockMode, getMockSubscription } from '../../../../config/mock.config';
interface PlanComparisonProps {

View File

@@ -1,406 +0,0 @@
import React, { useState } from 'react';
import { Users, Plus, Search, Mail, Phone, Shield, Edit, Trash2, UserCheck, UserX } from 'lucide-react';
import { Button, Card, Badge, Input } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const TeamPage: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('');
const [selectedRole, setSelectedRole] = useState('all');
const [showForm, setShowForm] = useState(false);
const teamMembers = [
{
id: '1',
name: 'María González',
email: 'maria.gonzalez@panaderia.com',
phone: '+34 600 123 456',
role: 'manager',
department: 'Administración',
status: 'active',
joinDate: '2022-03-15',
lastLogin: '2024-01-26 09:30:00',
permissions: ['inventory', 'sales', 'reports', 'team'],
avatar: '/avatars/maria.jpg',
schedule: {
monday: '07:00-15:00',
tuesday: '07:00-15:00',
wednesday: '07:00-15:00',
thursday: '07:00-15:00',
friday: '07:00-15:00',
saturday: 'Libre',
sunday: 'Libre'
}
},
{
id: '2',
name: 'Carlos Rodríguez',
email: 'carlos.rodriguez@panaderia.com',
phone: '+34 600 234 567',
role: 'baker',
department: 'Producción',
status: 'active',
joinDate: '2021-09-20',
lastLogin: '2024-01-26 08:45:00',
permissions: ['production', 'inventory'],
avatar: '/avatars/carlos.jpg',
schedule: {
monday: '05:00-13:00',
tuesday: '05:00-13:00',
wednesday: '05:00-13:00',
thursday: '05:00-13:00',
friday: '05:00-13:00',
saturday: '05:00-11:00',
sunday: 'Libre'
}
},
{
id: '3',
name: 'Ana Martínez',
email: 'ana.martinez@panaderia.com',
phone: '+34 600 345 678',
role: 'cashier',
department: 'Ventas',
status: 'active',
joinDate: '2023-01-10',
lastLogin: '2024-01-26 10:15:00',
permissions: ['sales', 'pos'],
avatar: '/avatars/ana.jpg',
schedule: {
monday: '08:00-16:00',
tuesday: '08:00-16:00',
wednesday: 'Libre',
thursday: '08:00-16:00',
friday: '08:00-16:00',
saturday: '09:00-14:00',
sunday: '09:00-14:00'
}
},
{
id: '4',
name: 'Luis Fernández',
email: 'luis.fernandez@panaderia.com',
phone: '+34 600 456 789',
role: 'baker',
department: 'Producción',
status: 'inactive',
joinDate: '2020-11-05',
lastLogin: '2024-01-20 16:30:00',
permissions: ['production'],
avatar: '/avatars/luis.jpg',
schedule: {
monday: '13:00-21:00',
tuesday: '13:00-21:00',
wednesday: '13:00-21:00',
thursday: 'Libre',
friday: '13:00-21:00',
saturday: 'Libre',
sunday: '13:00-21:00'
}
},
{
id: '5',
name: 'Isabel Torres',
email: 'isabel.torres@panaderia.com',
phone: '+34 600 567 890',
role: 'assistant',
department: 'Ventas',
status: 'active',
joinDate: '2023-06-01',
lastLogin: '2024-01-25 18:20:00',
permissions: ['sales'],
avatar: '/avatars/isabel.jpg',
schedule: {
monday: 'Libre',
tuesday: '16:00-20:00',
wednesday: '16:00-20:00',
thursday: '16:00-20:00',
friday: '16:00-20:00',
saturday: '14:00-20:00',
sunday: '14:00-20:00'
}
}
];
const roles = [
{ value: 'all', label: 'Todos los Roles', count: teamMembers.length },
{ value: 'manager', label: 'Gerente', count: teamMembers.filter(m => m.role === 'manager').length },
{ value: 'baker', label: 'Panadero', count: teamMembers.filter(m => m.role === 'baker').length },
{ value: 'cashier', label: 'Cajero', count: teamMembers.filter(m => m.role === 'cashier').length },
{ value: 'assistant', label: 'Asistente', count: teamMembers.filter(m => m.role === 'assistant').length }
];
const teamStats = {
total: teamMembers.length,
active: teamMembers.filter(m => m.status === 'active').length,
departments: {
production: teamMembers.filter(m => m.department === 'Producción').length,
sales: teamMembers.filter(m => m.department === 'Ventas').length,
admin: teamMembers.filter(m => m.department === 'Administración').length
}
};
const getRoleBadgeColor = (role: string) => {
switch (role) {
case 'manager': return 'purple';
case 'baker': return 'green';
case 'cashier': return 'blue';
case 'assistant': return 'yellow';
default: return 'gray';
}
};
const getStatusColor = (status: string) => {
return status === 'active' ? 'green' : 'red';
};
const getRoleLabel = (role: string) => {
switch (role) {
case 'manager': return 'Gerente';
case 'baker': return 'Panadero';
case 'cashier': return 'Cajero';
case 'assistant': return 'Asistente';
default: return role;
}
};
const filteredMembers = teamMembers.filter(member => {
const matchesRole = selectedRole === 'all' || member.role === selectedRole;
const matchesSearch = member.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
member.email.toLowerCase().includes(searchTerm.toLowerCase());
return matchesRole && matchesSearch;
});
const formatLastLogin = (timestamp: string) => {
const date = new Date(timestamp);
const now = new Date();
const diffInDays = Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));
if (diffInDays === 0) {
return 'Hoy ' + date.toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' });
} else if (diffInDays === 1) {
return 'Ayer';
} else {
return `hace ${diffInDays} días`;
}
};
return (
<div className="p-6 space-y-6">
<PageHeader
title="Gestión de Equipo"
description="Administra los miembros del equipo, roles y permisos"
action={
<Button onClick={() => setShowForm(true)}>
<Plus className="w-4 h-4 mr-2" />
Nuevo Miembro
</Button>
}
/>
{/* Team Stats */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Total Equipo</p>
<p className="text-3xl font-bold text-gray-900">{teamStats.total}</p>
</div>
<div className="h-12 w-12 bg-blue-100 rounded-full flex items-center justify-center">
<Users className="h-6 w-6 text-blue-600" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Activos</p>
<p className="text-3xl font-bold text-green-600">{teamStats.active}</p>
</div>
<div className="h-12 w-12 bg-green-100 rounded-full flex items-center justify-center">
<UserCheck className="h-6 w-6 text-green-600" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Producción</p>
<p className="text-3xl font-bold text-orange-600">{teamStats.departments.production}</p>
</div>
<div className="h-12 w-12 bg-orange-100 rounded-full flex items-center justify-center">
<Users className="h-6 w-6 text-orange-600" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Ventas</p>
<p className="text-3xl font-bold text-purple-600">{teamStats.departments.sales}</p>
</div>
<div className="h-12 w-12 bg-purple-100 rounded-full flex items-center justify-center">
<Users className="h-6 w-6 text-purple-600" />
</div>
</div>
</Card>
</div>
{/* Filters and Search */}
<Card className="p-6">
<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input
placeholder="Buscar miembros del equipo..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
</div>
<div className="flex gap-2 flex-wrap">
{roles.map((role) => (
<button
key={role.value}
onClick={() => setSelectedRole(role.value)}
className={`px-4 py-2 rounded-full text-sm font-medium transition-colors ${
selectedRole === role.value
? 'bg-blue-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
{role.label} ({role.count})
</button>
))}
</div>
</div>
</Card>
{/* Team Members List */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{filteredMembers.map((member) => (
<Card key={member.id} className="p-6">
<div className="flex items-start justify-between">
<div className="flex items-start space-x-4 flex-1">
<div className="w-12 h-12 bg-gray-200 rounded-full flex items-center justify-center">
<Users className="w-6 h-6 text-gray-500" />
</div>
<div className="flex-1">
<div className="flex items-center space-x-3 mb-2">
<h3 className="text-lg font-semibold text-gray-900">{member.name}</h3>
<Badge variant={getStatusColor(member.status)}>
{member.status === 'active' ? 'Activo' : 'Inactivo'}
</Badge>
</div>
<div className="space-y-1 mb-3">
<div className="flex items-center text-sm text-gray-600">
<Mail className="w-4 h-4 mr-2" />
{member.email}
</div>
<div className="flex items-center text-sm text-gray-600">
<Phone className="w-4 h-4 mr-2" />
{member.phone}
</div>
</div>
<div className="flex items-center space-x-2 mb-3">
<Badge variant={getRoleBadgeColor(member.role)}>
{getRoleLabel(member.role)}
</Badge>
<Badge variant="gray">
{member.department}
</Badge>
</div>
<div className="text-sm text-gray-500 mb-3">
<p>Se unió: {new Date(member.joinDate).toLocaleDateString('es-ES')}</p>
<p>Última conexión: {formatLastLogin(member.lastLogin)}</p>
</div>
{/* Permissions */}
<div className="mb-3">
<p className="text-xs font-medium text-gray-700 mb-2">Permisos:</p>
<div className="flex flex-wrap gap-1">
{member.permissions.map((permission, index) => (
<span
key={index}
className="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-full"
>
{permission}
</span>
))}
</div>
</div>
{/* Schedule Preview */}
<div className="text-xs text-gray-500">
<p className="font-medium mb-1">Horario esta semana:</p>
<div className="grid grid-cols-2 gap-1">
{Object.entries(member.schedule).slice(0, 4).map(([day, hours]) => (
<span key={day}>
{day.charAt(0).toUpperCase()}: {hours}
</span>
))}
</div>
</div>
</div>
</div>
<div className="flex space-x-2">
<Button size="sm" variant="outline">
<Edit className="w-4 h-4" />
</Button>
<Button
size="sm"
variant="outline"
className={member.status === 'active' ? 'text-red-600 hover:text-red-700' : 'text-green-600 hover:text-green-700'}
>
{member.status === 'active' ? <UserX className="w-4 h-4" /> : <UserCheck className="w-4 h-4" />}
</Button>
</div>
</div>
</Card>
))}
</div>
{filteredMembers.length === 0 && (
<Card className="p-12 text-center">
<Users className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-semibold text-gray-900 mb-2">No se encontraron miembros</h3>
<p className="text-gray-600">
No hay miembros del equipo que coincidan con los filtros seleccionados.
</p>
</Card>
)}
{/* Add Member Modal Placeholder */}
{showForm && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<Card className="p-6 max-w-md w-full mx-4">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Nuevo Miembro del Equipo</h3>
<p className="text-gray-600 mb-4">
Formulario para agregar un nuevo miembro del equipo.
</p>
<div className="flex space-x-2">
<Button size="sm" onClick={() => setShowForm(false)}>
Guardar
</Button>
<Button size="sm" variant="outline" onClick={() => setShowForm(false)}>
Cancelar
</Button>
</div>
</Card>
</div>
)}
</div>
);
};
export default TeamPage;