diff --git a/src/App.tsx b/src/App.tsx index 82a19fa..e2ec29e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -34,6 +34,7 @@ import Invoices from "./pages/dashboard/Invoices"; import InvoiceDetail from "./pages/dashboard/InvoiceDetail"; import HotelManagement from "./pages/dashboard/HotelManagement"; import RestaurantPOS from "./pages/dashboard/RestaurantPOS"; +import Personalization from "./pages/dashboard/Personalization"; import NotFound from "./pages/NotFound"; const queryClient = new QueryClient(); @@ -255,6 +256,13 @@ const AppRouter = () => ( } /> + + + + + + } /> {/* Catch-all route */} } /> diff --git a/src/components/DashboardLayout.tsx b/src/components/DashboardLayout.tsx index 26eb0dd..d9d0086 100644 --- a/src/components/DashboardLayout.tsx +++ b/src/components/DashboardLayout.tsx @@ -38,7 +38,8 @@ import { ChevronDown, ChevronRight, Hotel, - UtensilsCrossed + UtensilsCrossed, + Brain } from 'lucide-react'; const DashboardLayout = ({ children }: { children: React.ReactNode }) => { @@ -63,6 +64,7 @@ const DashboardLayout = ({ children }: { children: React.ReactNode }) => { { icon: Plus, label: 'Channel Manager', path: '/dashboard/channel-manager' }, { icon: Hotel, label: 'Hotel Management', path: '/dashboard/hotel-management' }, { icon: UtensilsCrossed, label: 'Restaurant POS', path: '/dashboard/restaurant-pos' }, + { icon: Brain, label: 'Personalization', path: '/dashboard/personalization' }, { icon: Wallet, label: 'Wallet', path: '/dashboard/wallet' }, { icon: MessageSquare, label: 'Message', path: '/dashboard/messages', badge: '2' }, ]; diff --git a/src/components/personalization/AIRecommendations.tsx b/src/components/personalization/AIRecommendations.tsx new file mode 100644 index 0000000..6757fde --- /dev/null +++ b/src/components/personalization/AIRecommendations.tsx @@ -0,0 +1,181 @@ +import { useState } from 'react'; +import { Card, CardContent } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Brain, Star, MapPin, TrendingUp, ThumbsUp, ThumbsDown } from 'lucide-react'; +import { toast } from 'sonner'; + +interface Recommendation { + id: string; + userId: string; + userName: string; + type: 'destination' | 'activity' | 'restaurant' | 'hotel'; + item: string; + confidence: number; + reason: string; + matchScore: number; +} + +const AIRecommendations = () => { + const [recommendations] = useState([ + { + id: '1', + userId: 'user_123', + userName: 'María García', + type: 'destination', + item: 'Barcelona, España', + confidence: 95, + reason: 'Basado en historial de viajes a ciudades culturales', + matchScore: 95 + }, + { + id: '2', + userId: 'user_123', + userName: 'María García', + type: 'activity', + item: 'Tour Gastronómico Sagrada Familia', + confidence: 88, + reason: 'Usuario interesado en gastronomía y arquitectura', + matchScore: 88 + }, + { + id: '3', + userId: 'user_456', + userName: 'Carlos López', + type: 'hotel', + item: 'Hotel Boutique Centro Histórico', + confidence: 92, + reason: 'Prefiere alojamientos boutique en zonas céntricas', + matchScore: 92 + }, + { + id: '4', + userId: 'user_789', + userName: 'Ana Martínez', + type: 'restaurant', + item: 'Restaurante Vegetariano "Green Life"', + confidence: 90, + reason: 'Historial de preferencias vegetarianas y sostenibles', + matchScore: 90 + } + ]); + + const getTypeIcon = (type: Recommendation['type']) => { + switch (type) { + case 'destination': return MapPin; + case 'activity': return Star; + case 'restaurant': return Star; + case 'hotel': return MapPin; + default: return Star; + } + }; + + const getTypeLabel = (type: Recommendation['type']) => { + switch (type) { + case 'destination': return 'Destino'; + case 'activity': return 'Actividad'; + case 'restaurant': return 'Restaurante'; + case 'hotel': return 'Hotel'; + default: return type; + } + }; + + const getConfidenceColor = (confidence: number) => { + if (confidence >= 90) return 'bg-green-500'; + if (confidence >= 75) return 'bg-yellow-500'; + return 'bg-orange-500'; + }; + + const handleFeedback = (recId: string, positive: boolean) => { + toast.success(positive ? 'Feedback positivo registrado' : 'Feedback negativo registrado'); + }; + + return ( +
+
+ + Motor de IA analizando {recommendations.length} recomendaciones activas +
+ +
+ {recommendations.map((rec) => { + const Icon = getTypeIcon(rec.type); + + return ( + + +
+
+
+
+ + {getTypeLabel(rec.type)} +
+

{rec.item}

+

+ Para: {rec.userName} +

+
+
+
+
+ {rec.confidence}% +
+ confianza +
+
+ +
+
+ +
+
Razón de la recomendación:
+
{rec.reason}
+
+
+
+ +
+
+ + + Match Score: {rec.matchScore}% + +
+
+ + +
+
+
+
+
+ ); + })} +
+ + {recommendations.length === 0 && ( +
+ +

No hay recomendaciones activas

+
+ )} +
+ ); +}; + +export default AIRecommendations; diff --git a/src/components/personalization/BehaviorAnalytics.tsx b/src/components/personalization/BehaviorAnalytics.tsx new file mode 100644 index 0000000..ffefbc8 --- /dev/null +++ b/src/components/personalization/BehaviorAnalytics.tsx @@ -0,0 +1,180 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { TrendingUp, Eye, MousePointer, Clock, MapPin } from 'lucide-react'; + +const BehaviorAnalytics = () => { + const behaviorData = { + topDestinations: [ + { name: 'Barcelona', views: 1250, bookings: 85 }, + { name: 'Madrid', views: 1120, bookings: 72 }, + { name: 'Valencia', views: 890, bookings: 58 }, + { name: 'Sevilla', views: 780, bookings: 45 } + ], + userJourney: [ + { step: 'Landing Page', users: 1000, dropoff: 0 }, + { step: 'Búsqueda', users: 850, dropoff: 15 }, + { step: 'Detalle', users: 680, dropoff: 20 }, + { step: 'Reserva', users: 425, dropoff: 37.5 }, + { step: 'Pago', users: 340, dropoff: 20 } + ], + peakHours: [ + { hour: '09:00', activity: 45 }, + { hour: '12:00', activity: 78 }, + { hour: '15:00', activity: 65 }, + { hour: '18:00', activity: 92 }, + { hour: '21:00', activity: 58 } + ] + }; + + return ( +
+
+ {/* Top Destinations */} + + + + + Destinos Más Vistos + + + +
+ {behaviorData.topDestinations.map((dest, idx) => ( +
+
+ {dest.name} + {dest.views} vistas +
+
+
+
+
+ + {dest.bookings} reservas + +
+
+ ))} +
+
+
+ + {/* User Journey Funnel */} + + + + + Embudo de Conversión + + + +
+ {behaviorData.userJourney.map((step, idx) => ( +
+
+ {step.step} +
+ {step.users} usuarios + {step.dropoff > 0 && ( + + -{step.dropoff}% + + )} +
+
+
+
+
+
+ ))} +
+
+
+
+ + {/* Peak Activity Hours */} + + + + + Horas de Mayor Actividad + + + +
+ {behaviorData.peakHours.map((hour, idx) => ( +
+
+
+
+
{hour.hour}
+
+ ))} +
+
+
+ + {/* Quick Stats */} +
+ + +
+ +
+
4,890
+
Páginas vistas
+
+
+
+
+ + + +
+ +
+
68%
+
Click-through rate
+
+
+
+
+ + + +
+ +
+
4:32
+
Tiempo promedio
+
+
+
+
+ + + +
+ +
+
34%
+
Tasa conversión
+
+
+
+
+
+
+ ); +}; + +export default BehaviorAnalytics; diff --git a/src/components/personalization/SegmentManagement.tsx b/src/components/personalization/SegmentManagement.tsx new file mode 100644 index 0000000..67dda79 --- /dev/null +++ b/src/components/personalization/SegmentManagement.tsx @@ -0,0 +1,179 @@ +import { useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; +import { Users, Plus, Target, TrendingUp } from 'lucide-react'; +import { toast } from 'sonner'; + +interface Segment { + id: string; + name: string; + description: string; + userCount: number; + criteria: string[]; + conversionRate: number; + avgSpending: number; +} + +const SegmentManagement = () => { + const [segments] = useState([ + { + id: '1', + name: 'Viajeros Culturales', + description: 'Usuarios interesados en historia, arte y cultura', + userCount: 450, + criteria: ['Visita museos', 'Busca tours culturales', 'Presupuesto medio-alto'], + conversionRate: 42, + avgSpending: 850 + }, + { + id: '2', + name: 'Amantes de la Playa', + description: 'Prefieren destinos costeros y actividades acuáticas', + userCount: 380, + criteria: ['Busca playas', 'Reserva resorts', 'Temporada verano'], + conversionRate: 38, + avgSpending: 720 + }, + { + id: '3', + name: 'Aventureros', + description: 'Buscan experiencias de aventura y naturaleza', + userCount: 290, + criteria: ['Actividades outdoor', 'Deportes extremos', 'Ecoturismo'], + conversionRate: 45, + avgSpending: 950 + }, + { + id: '4', + name: 'Gastronómicos', + description: 'Viajeros enfocados en experiencias culinarias', + userCount: 320, + criteria: ['Tours gastronómicos', 'Restaurantes gourmet', 'Cocina local'], + conversionRate: 48, + avgSpending: 1100 + } + ]); + + const createSegment = () => { + toast.success('Segmento creado correctamente'); + }; + + return ( +
+
+
+

Segmentos Activos

+

+ {segments.length} segmentos de usuarios configurados +

+
+ + + + + + + Crear Nuevo Segmento + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+ +
+ {segments.map((segment) => ( + + +
+
+ + + {segment.name} + +

+ {segment.description} +

+
+
+
+ +
+
+ + Usuarios +
+ {segment.userCount} +
+ +
+
Criterios de segmentación:
+
+ {segment.criteria.map((criterion, idx) => ( + {criterion} + ))} +
+
+ +
+
+
+ + Conversión +
+
+ {segment.conversionRate}% +
+
+
+
+ Gasto Promedio +
+
+ €{segment.avgSpending} +
+
+
+ +
+ + +
+
+
+ ))} +
+
+ ); +}; + +export default SegmentManagement; diff --git a/src/components/personalization/UserPreferences.tsx b/src/components/personalization/UserPreferences.tsx new file mode 100644 index 0000000..b1110ce --- /dev/null +++ b/src/components/personalization/UserPreferences.tsx @@ -0,0 +1,154 @@ +import { useState } from 'react'; +import { Card, CardContent } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Switch } from '@/components/ui/switch'; +import { Label } from '@/components/ui/label'; +import { User, Heart, Tag, Bell } from 'lucide-react'; + +interface UserProfile { + id: string; + name: string; + email: string; + preferences: { + destinations: string[]; + activities: string[]; + budget: string; + travelStyle: string; + }; + notifications: { + email: boolean; + push: boolean; + sms: boolean; + }; + interests: string[]; +} + +const UserPreferences = () => { + const [users] = useState([ + { + id: '1', + name: 'María García', + email: 'maria@example.com', + preferences: { + destinations: ['Europa', 'Asia'], + activities: ['Cultura', 'Gastronomía', 'Historia'], + budget: 'Medio-Alto', + travelStyle: 'Cultural' + }, + notifications: { + email: true, + push: true, + sms: false + }, + interests: ['Arte', 'Museos', 'Cocina Local', 'Arquitectura'] + }, + { + id: '2', + name: 'Carlos López', + email: 'carlos@example.com', + preferences: { + destinations: ['Caribe', 'Mediterráneo'], + activities: ['Playa', 'Deportes', 'Relax'], + budget: 'Alto', + travelStyle: 'Lujo' + }, + notifications: { + email: true, + push: false, + sms: true + }, + interests: ['Golf', 'Spa', 'Gastronomía Gourmet', 'Vinos'] + } + ]); + + return ( +
+
+ {users.map((user) => ( + + +
+
+
+
+ +
+
+

{user.name}

+

{user.email}

+
+
+
+ +
+
+
+ + Destinos Favoritos +
+
+ {user.preferences.destinations.map((dest, idx) => ( + {dest} + ))} +
+
+ +
+
+ + Intereses +
+
+ {user.interests.map((interest, idx) => ( + {interest} + ))} +
+
+ +
+
+ Presupuesto: +
{user.preferences.budget}
+
+
+ Estilo: +
{user.preferences.travelStyle}
+
+
+ +
+
+ + Notificaciones +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+ ))} +
+
+ ); +}; + +export default UserPreferences; diff --git a/src/components/restaurant/TableConfiguration.tsx b/src/components/restaurant/TableConfiguration.tsx index 161cf66..d42a0c1 100644 --- a/src/components/restaurant/TableConfiguration.tsx +++ b/src/components/restaurant/TableConfiguration.tsx @@ -151,7 +151,22 @@ const TableConfiguration = () => { {tables.map((table) => (
{ + e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer.setData('tableId', table.id); + }} + onDragEnd={(e) => { + const rect = e.currentTarget.parentElement?.getBoundingClientRect(); + if (rect) { + const x = Math.max(0, Math.min(e.clientX - rect.left - 40, rect.width - 80)); + const y = Math.max(0, Math.min(e.clientY - rect.top - 40, rect.height - 80)); + setTables(tables.map(t => + t.id === table.id ? { ...t, position: { x, y } } : t + )); + } + }} style={{ left: `${table.position.x}px`, top: `${table.position.y}px` diff --git a/src/pages/dashboard/Personalization.tsx b/src/pages/dashboard/Personalization.tsx new file mode 100644 index 0000000..431af4e --- /dev/null +++ b/src/pages/dashboard/Personalization.tsx @@ -0,0 +1,146 @@ +import { useState } from 'react'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Target, Brain, TrendingUp, Users } from 'lucide-react'; +import UserPreferences from '@/components/personalization/UserPreferences'; +import AIRecommendations from '@/components/personalization/AIRecommendations'; +import BehaviorAnalytics from '@/components/personalization/BehaviorAnalytics'; +import SegmentManagement from '@/components/personalization/SegmentManagement'; + +const Personalization = () => { + const [totalUsers] = useState(1250); + const [activeSegments] = useState(8); + const [recommendationAccuracy] = useState(87); + const [engagement] = useState(72); + + return ( +
+
+
+

+ + Personalización con IA +

+

+ Recomendaciones inteligentes y segmentación de usuarios +

+
+
+ + {/* Stats Overview */} +
+ + + Usuarios Activos + + + +
{totalUsers.toLocaleString()}
+

perfiles analizados

+
+
+ + + + Segmentos + + + +
{activeSegments}
+

grupos activos

+
+
+ + + + Precisión IA + + + +
{recommendationAccuracy}%
+

accuracy de recomendaciones

+
+
+ + + + Engagement + + + +
{engagement}%
+

tasa de interacción

+
+
+
+ + {/* Main Content */} + + + Recomendaciones + Preferencias + Analytics + Segmentos + + + + + + Recomendaciones con IA + + Sistema de recomendaciones personalizadas basado en comportamiento + + + + + + + + + + + + Preferencias de Usuario + + Gestiona las preferencias y configuraciones de personalización + + + + + + + + + + + + Análisis de Comportamiento + + Insights sobre patrones de uso y preferencias de usuarios + + + + + + + + + + + + Gestión de Segmentos + + Crea y gestiona segmentos de usuarios para campañas dirigidas + + + + + + + + +
+ ); +}; + +export default Personalization;