Fix: Improve table layout functionality
This commit is contained in:
181
src/components/personalization/AIRecommendations.tsx
Normal file
181
src/components/personalization/AIRecommendations.tsx
Normal file
@@ -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<Recommendation[]>([
|
||||
{
|
||||
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 (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Brain className="h-4 w-4" />
|
||||
<span>Motor de IA analizando {recommendations.length} recomendaciones activas</span>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{recommendations.map((rec) => {
|
||||
const Icon = getTypeIcon(rec.type);
|
||||
|
||||
return (
|
||||
<Card key={rec.id}>
|
||||
<CardContent className="p-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Icon className="h-4 w-4 text-primary" />
|
||||
<Badge variant="outline">{getTypeLabel(rec.type)}</Badge>
|
||||
</div>
|
||||
<h4 className="font-semibold">{rec.item}</h4>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Para: {rec.userName}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="flex items-center gap-1">
|
||||
<div className={`w-2 h-2 rounded-full ${getConfidenceColor(rec.confidence)}`}></div>
|
||||
<span className="text-sm font-medium">{rec.confidence}%</span>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">confianza</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-3 bg-muted rounded-lg">
|
||||
<div className="flex items-start gap-2">
|
||||
<Brain className="h-4 w-4 text-primary mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<div className="text-xs font-medium mb-1">Razón de la recomendación:</div>
|
||||
<div className="text-xs text-muted-foreground">{rec.reason}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<TrendingUp className="h-3 w-3 text-muted-foreground" />
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Match Score: {rec.matchScore}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-7 w-7 p-0"
|
||||
onClick={() => handleFeedback(rec.id, true)}
|
||||
>
|
||||
<ThumbsUp className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-7 w-7 p-0"
|
||||
onClick={() => handleFeedback(rec.id, false)}
|
||||
>
|
||||
<ThumbsDown className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{recommendations.length === 0 && (
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
<Brain className="h-12 w-12 mx-auto mb-4 opacity-50" />
|
||||
<p>No hay recomendaciones activas</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AIRecommendations;
|
||||
180
src/components/personalization/BehaviorAnalytics.tsx
Normal file
180
src/components/personalization/BehaviorAnalytics.tsx
Normal file
@@ -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 (
|
||||
<div className="space-y-6">
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{/* Top Destinations */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<MapPin className="h-5 w-5" />
|
||||
Destinos Más Vistos
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{behaviorData.topDestinations.map((dest, idx) => (
|
||||
<div key={idx} className="space-y-1">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="font-medium">{dest.name}</span>
|
||||
<Badge variant="secondary">{dest.views} vistas</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1 h-2 bg-muted rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-primary"
|
||||
style={{ width: `${(dest.bookings / dest.views) * 100}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{dest.bookings} reservas
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* User Journey Funnel */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<TrendingUp className="h-5 w-5" />
|
||||
Embudo de Conversión
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{behaviorData.userJourney.map((step, idx) => (
|
||||
<div key={idx} className="space-y-1">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-medium">{step.step}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm">{step.users} usuarios</span>
|
||||
{step.dropoff > 0 && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
-{step.dropoff}%
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 h-2 bg-muted rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-gradient-to-r from-green-500 to-blue-500"
|
||||
style={{ width: `${(step.users / 1000) * 100}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Peak Activity Hours */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Clock className="h-5 w-5" />
|
||||
Horas de Mayor Actividad
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex justify-between items-end h-40 gap-4">
|
||||
{behaviorData.peakHours.map((hour, idx) => (
|
||||
<div key={idx} className="flex-1 flex flex-col items-center gap-2">
|
||||
<div className="w-full flex items-end justify-center h-32">
|
||||
<div
|
||||
className="w-full bg-primary rounded-t-lg transition-all hover:bg-primary/80"
|
||||
style={{ height: `${hour.activity}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">{hour.hour}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Quick Stats */}
|
||||
<div className="grid gap-4 md:grid-cols-4">
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Eye className="h-8 w-8 text-blue-500" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold">4,890</div>
|
||||
<div className="text-xs text-muted-foreground">Páginas vistas</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<MousePointer className="h-8 w-8 text-green-500" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold">68%</div>
|
||||
<div className="text-xs text-muted-foreground">Click-through rate</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Clock className="h-8 w-8 text-purple-500" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold">4:32</div>
|
||||
<div className="text-xs text-muted-foreground">Tiempo promedio</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<TrendingUp className="h-8 w-8 text-orange-500" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold">34%</div>
|
||||
<div className="text-xs text-muted-foreground">Tasa conversión</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BehaviorAnalytics;
|
||||
179
src/components/personalization/SegmentManagement.tsx
Normal file
179
src/components/personalization/SegmentManagement.tsx
Normal file
@@ -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<Segment[]>([
|
||||
{
|
||||
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 (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">Segmentos Activos</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{segments.length} segmentos de usuarios configurados
|
||||
</p>
|
||||
</div>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="gap-2">
|
||||
<Plus className="h-4 w-4" />
|
||||
Nuevo Segmento
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Crear Nuevo Segmento</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="segment-name">Nombre del Segmento</Label>
|
||||
<Input id="segment-name" placeholder="Ej: Viajeros de Lujo" />
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="segment-desc">Descripción</Label>
|
||||
<Input id="segment-desc" placeholder="Descripción del segmento" />
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="segment-criteria">Criterios (separados por coma)</Label>
|
||||
<Input
|
||||
id="segment-criteria"
|
||||
placeholder="Alta capacidad de gasto, Hoteles 5 estrellas, ..."
|
||||
/>
|
||||
</div>
|
||||
<Button onClick={createSegment} className="w-full">
|
||||
Crear Segmento
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{segments.map((segment) => (
|
||||
<Card key={segment.id}>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="flex-1">
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Target className="h-5 w-5 text-primary" />
|
||||
{segment.name}
|
||||
</CardTitle>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{segment.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-between p-3 bg-muted rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm font-medium">Usuarios</span>
|
||||
</div>
|
||||
<span className="text-2xl font-bold">{segment.userCount}</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">Criterios de segmentación:</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{segment.criteria.map((criterion, idx) => (
|
||||
<Badge key={idx} variant="outline">{criterion}</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="p-3 bg-green-50 rounded-lg">
|
||||
<div className="flex items-center gap-1 mb-1">
|
||||
<TrendingUp className="h-3 w-3 text-green-600" />
|
||||
<span className="text-xs text-green-600">Conversión</span>
|
||||
</div>
|
||||
<div className="text-xl font-bold text-green-700">
|
||||
{segment.conversionRate}%
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-3 bg-blue-50 rounded-lg">
|
||||
<div className="flex items-center gap-1 mb-1">
|
||||
<span className="text-xs text-blue-600">Gasto Promedio</span>
|
||||
</div>
|
||||
<div className="text-xl font-bold text-blue-700">
|
||||
€{segment.avgSpending}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 pt-2">
|
||||
<Button variant="outline" size="sm" className="flex-1">
|
||||
Ver Usuarios
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" className="flex-1">
|
||||
Editar
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SegmentManagement;
|
||||
154
src/components/personalization/UserPreferences.tsx
Normal file
154
src/components/personalization/UserPreferences.tsx
Normal file
@@ -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<UserProfile[]>([
|
||||
{
|
||||
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 (
|
||||
<div className="space-y-6">
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{users.map((user) => (
|
||||
<Card key={user.id}>
|
||||
<CardContent className="p-4">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center">
|
||||
<User className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold">{user.name}</h4>
|
||||
<p className="text-xs text-muted-foreground">{user.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Heart className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm font-medium">Destinos Favoritos</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{user.preferences.destinations.map((dest, idx) => (
|
||||
<Badge key={idx} variant="secondary">{dest}</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Tag className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm font-medium">Intereses</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{user.interests.map((interest, idx) => (
|
||||
<Badge key={idx} variant="outline">{interest}</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div>
|
||||
<span className="text-muted-foreground">Presupuesto:</span>
|
||||
<div className="font-medium">{user.preferences.budget}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">Estilo:</span>
|
||||
<div className="font-medium">{user.preferences.travelStyle}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-3 border-t">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Bell className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm font-medium">Notificaciones</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor={`email-${user.id}`} className="text-xs">Email</Label>
|
||||
<Switch id={`email-${user.id}`} checked={user.notifications.email} />
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor={`push-${user.id}`} className="text-xs">Push</Label>
|
||||
<Switch id={`push-${user.id}`} checked={user.notifications.push} />
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor={`sms-${user.id}`} className="text-xs">SMS</Label>
|
||||
<Switch id={`sms-${user.id}`} checked={user.notifications.sms} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button variant="outline" className="w-full" size="sm">
|
||||
Editar Preferencias
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserPreferences;
|
||||
Reference in New Issue
Block a user