336 lines
13 KiB
Plaintext
336 lines
13 KiB
Plaintext
import React, { useState } from 'react';
|
|
import { Users, Clock, TrendingUp, MapPin, Calendar, BarChart3, Download, Filter } from 'lucide-react';
|
|
import { Button, Card, Badge } from '../../../../components/ui';
|
|
import { PageHeader } from '../../../../components/layout';
|
|
|
|
const TrafficPage: React.FC = () => {
|
|
const [selectedPeriod, setSelectedPeriod] = useState('week');
|
|
const [selectedMetric, setSelectedMetric] = useState('visitors');
|
|
|
|
const trafficData = {
|
|
totalVisitors: 2847,
|
|
peakHour: '12:00',
|
|
averageVisitDuration: '23min',
|
|
busyDays: ['Viernes', 'Sábado'],
|
|
conversionRate: 68.4
|
|
};
|
|
|
|
const hourlyTraffic = [
|
|
{ hour: '07:00', visitors: 15, sales: 12, duration: '18min' },
|
|
{ hour: '08:00', visitors: 32, sales: 24, duration: '22min' },
|
|
{ hour: '09:00', visitors: 45, sales: 28, duration: '25min' },
|
|
{ hour: '10:00', visitors: 38, sales: 25, duration: '24min' },
|
|
{ hour: '11:00', visitors: 52, sales: 35, duration: '26min' },
|
|
{ hour: '12:00', visitors: 78, sales: 54, duration: '28min' },
|
|
{ hour: '13:00', visitors: 85, sales: 58, duration: '30min' },
|
|
{ hour: '14:00', visitors: 62, sales: 42, duration: '27min' },
|
|
{ hour: '15:00', visitors: 48, sales: 32, duration: '25min' },
|
|
{ hour: '16:00', visitors: 55, sales: 38, duration: '26min' },
|
|
{ hour: '17:00', visitors: 68, sales: 46, duration: '29min' },
|
|
{ hour: '18:00', visitors: 74, sales: 52, duration: '31min' },
|
|
{ hour: '19:00', visitors: 56, sales: 39, duration: '28min' },
|
|
{ hour: '20:00', visitors: 28, sales: 18, duration: '22min' }
|
|
];
|
|
|
|
const dailyTraffic = [
|
|
{ day: 'Lun', visitors: 245, sales: 168, conversion: 68.6, avgDuration: '22min' },
|
|
{ day: 'Mar', visitors: 289, sales: 195, conversion: 67.5, avgDuration: '24min' },
|
|
{ day: 'Mié', visitors: 321, sales: 218, conversion: 67.9, avgDuration: '25min' },
|
|
{ day: 'Jue', visitors: 356, sales: 242, conversion: 68.0, avgDuration: '26min' },
|
|
{ day: 'Vie', visitors: 445, sales: 312, conversion: 70.1, avgDuration: '28min' },
|
|
{ day: 'Sáb', visitors: 498, sales: 348, conversion: 69.9, avgDuration: '30min' },
|
|
{ day: 'Dom', visitors: 398, sales: 265, conversion: 66.6, avgDuration: '27min' }
|
|
];
|
|
|
|
const trafficSources = [
|
|
{ source: 'Pie', visitors: 1245, percentage: 43.7, trend: 5.2 },
|
|
{ source: 'Búsqueda Local', visitors: 687, percentage: 24.1, trend: 12.3 },
|
|
{ source: 'Recomendaciones', visitors: 423, percentage: 14.9, trend: -2.1 },
|
|
{ source: 'Redes Sociales', visitors: 298, percentage: 10.5, trend: 8.7 },
|
|
{ source: 'Publicidad', visitors: 194, percentage: 6.8, trend: 15.4 }
|
|
];
|
|
|
|
const customerSegments = [
|
|
{
|
|
segment: 'Regulares Matutinos',
|
|
count: 145,
|
|
percentage: 24.2,
|
|
peakHours: ['07:00-09:00'],
|
|
avgSpend: 12.50,
|
|
frequency: 'Diaria'
|
|
},
|
|
{
|
|
segment: 'Familia Fin de Semana',
|
|
count: 198,
|
|
percentage: 33.1,
|
|
peakHours: ['10:00-13:00'],
|
|
avgSpend: 28.90,
|
|
frequency: 'Semanal'
|
|
},
|
|
{
|
|
segment: 'Oficinistas Almuerzo',
|
|
count: 112,
|
|
percentage: 18.7,
|
|
peakHours: ['12:00-14:00'],
|
|
avgSpend: 8.75,
|
|
frequency: '2-3x semana'
|
|
},
|
|
{
|
|
segment: 'Clientes Ocasionales',
|
|
count: 143,
|
|
percentage: 23.9,
|
|
peakHours: ['16:00-19:00'],
|
|
avgSpend: 15.20,
|
|
frequency: 'Mensual'
|
|
}
|
|
];
|
|
|
|
const getTrendColor = (trend: number) => {
|
|
return trend >= 0 ? 'text-green-600' : 'text-red-600';
|
|
};
|
|
|
|
const getTrendIcon = (trend: number) => {
|
|
return trend >= 0 ? '↗' : '↘';
|
|
};
|
|
|
|
const maxVisitors = Math.max(...hourlyTraffic.map(h => h.visitors));
|
|
const maxDailyVisitors = Math.max(...dailyTraffic.map(d => d.visitors));
|
|
|
|
return (
|
|
<div className="p-6 space-y-6">
|
|
<PageHeader
|
|
title="Análisis de Tráfico"
|
|
description="Monitorea los patrones de visitas y flujo de clientes"
|
|
action={
|
|
<div className="flex space-x-2">
|
|
<Button variant="outline">
|
|
<Filter className="w-4 h-4 mr-2" />
|
|
Filtros
|
|
</Button>
|
|
<Button variant="outline">
|
|
<Download className="w-4 h-4 mr-2" />
|
|
Exportar
|
|
</Button>
|
|
</div>
|
|
}
|
|
/>
|
|
|
|
{/* Traffic Stats */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6">
|
|
<Card className="p-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm font-medium text-gray-600">Visitantes Totales</p>
|
|
<p className="text-3xl font-bold text-blue-600">{trafficData.totalVisitors.toLocaleString()}</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">Hora Pico</p>
|
|
<p className="text-3xl font-bold text-green-600">{trafficData.peakHour}</p>
|
|
</div>
|
|
<div className="h-12 w-12 bg-green-100 rounded-full flex items-center justify-center">
|
|
<Clock 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">Duración Promedio</p>
|
|
<p className="text-3xl font-bold text-purple-600">{trafficData.averageVisitDuration}</p>
|
|
</div>
|
|
<div className="h-12 w-12 bg-purple-100 rounded-full flex items-center justify-center">
|
|
<Clock className="h-6 w-6 text-purple-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">Conversión</p>
|
|
<p className="text-3xl font-bold text-orange-600">{trafficData.conversionRate}%</p>
|
|
</div>
|
|
<div className="h-12 w-12 bg-orange-100 rounded-full flex items-center justify-center">
|
|
<TrendingUp 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">Días Ocupados</p>
|
|
<p className="text-sm font-bold text-red-600">{trafficData.busyDays.join(', ')}</p>
|
|
</div>
|
|
<div className="h-12 w-12 bg-red-100 rounded-full flex items-center justify-center">
|
|
<Calendar className="h-6 w-6 text-red-600" />
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Controls */}
|
|
<Card className="p-6">
|
|
<div className="flex flex-col sm:flex-row gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">Período</label>
|
|
<select
|
|
value={selectedPeriod}
|
|
onChange={(e) => setSelectedPeriod(e.target.value)}
|
|
className="px-3 py-2 border border-gray-300 rounded-md"
|
|
>
|
|
<option value="day">Hoy</option>
|
|
<option value="week">Esta Semana</option>
|
|
<option value="month">Este Mes</option>
|
|
<option value="year">Este Año</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">Métrica</label>
|
|
<select
|
|
value={selectedMetric}
|
|
onChange={(e) => setSelectedMetric(e.target.value)}
|
|
className="px-3 py-2 border border-gray-300 rounded-md"
|
|
>
|
|
<option value="visitors">Visitantes</option>
|
|
<option value="sales">Ventas</option>
|
|
<option value="duration">Duración</option>
|
|
<option value="conversion">Conversión</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{/* Hourly Traffic */}
|
|
<Card className="p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">Tráfico por Hora</h3>
|
|
<div className="h-64 flex items-end space-x-1 justify-between">
|
|
{hourlyTraffic.map((data, index) => (
|
|
<div key={index} className="flex flex-col items-center flex-1">
|
|
<div className="text-xs text-gray-600 mb-1">{data.visitors}</div>
|
|
<div
|
|
className="w-full bg-blue-500 rounded-t"
|
|
style={{
|
|
height: `${(data.visitors / maxVisitors) * 200}px`,
|
|
minHeight: '4px'
|
|
}}
|
|
></div>
|
|
<span className="text-xs text-gray-500 mt-2 transform -rotate-45 origin-center">
|
|
{data.hour}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Daily Traffic */}
|
|
<Card className="p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">Tráfico Semanal</h3>
|
|
<div className="h-64 flex items-end space-x-2 justify-between">
|
|
{dailyTraffic.map((data, index) => (
|
|
<div key={index} className="flex flex-col items-center flex-1">
|
|
<div className="text-xs text-gray-600 mb-1">{data.visitors}</div>
|
|
<div
|
|
className="w-full bg-green-500 rounded-t"
|
|
style={{
|
|
height: `${(data.visitors / maxDailyVisitors) * 200}px`,
|
|
minHeight: '8px'
|
|
}}
|
|
></div>
|
|
<span className="text-sm text-gray-700 mt-2 font-medium">
|
|
{data.day}
|
|
</span>
|
|
<div className="text-xs text-gray-500 mt-1">
|
|
{data.conversion}%
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Traffic Sources */}
|
|
<Card className="p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">Fuentes de Tráfico</h3>
|
|
<div className="space-y-3">
|
|
{trafficSources.map((source, index) => (
|
|
<div key={index} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
|
<div className="flex items-center space-x-3">
|
|
<div className="w-3 h-3 bg-blue-500 rounded-full"></div>
|
|
<div>
|
|
<p className="text-sm font-medium text-gray-900">{source.source}</p>
|
|
<p className="text-xs text-gray-500">{source.visitors} visitantes</p>
|
|
</div>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="text-sm font-medium text-gray-900">{source.percentage}%</p>
|
|
<div className={`text-xs flex items-center ${getTrendColor(source.trend)}`}>
|
|
<span>{getTrendIcon(source.trend)} {Math.abs(source.trend).toFixed(1)}%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Customer Segments */}
|
|
<Card className="p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">Segmentos de Clientes</h3>
|
|
<div className="space-y-4">
|
|
{customerSegments.map((segment, index) => (
|
|
<div key={index} className="border rounded-lg p-4">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<h4 className="font-medium text-gray-900">{segment.segment}</h4>
|
|
<Badge variant="blue">{segment.percentage}%</Badge>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
|
<div>
|
|
<p className="text-gray-500">Clientes</p>
|
|
<p className="font-medium">{segment.count}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-gray-500">Gasto Promedio</p>
|
|
<p className="font-medium">€{segment.avgSpend}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-gray-500">Horario Pico</p>
|
|
<p className="font-medium">{segment.peakHours.join(', ')}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-gray-500">Frecuencia</p>
|
|
<p className="font-medium">{segment.frequency}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Traffic Heat Map placeholder */}
|
|
<Card className="p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">Mapa de Calor - Zonas de la Panadería</h3>
|
|
<div className="h-64 bg-gray-100 rounded-lg flex items-center justify-center">
|
|
<div className="text-center">
|
|
<MapPin className="h-12 w-12 text-gray-400 mx-auto mb-4" />
|
|
<p className="text-gray-600">Visualización de zonas de mayor tráfico</p>
|
|
<p className="text-sm text-gray-500 mt-1">Entrada: 45% • Mostrador: 32% • Zona sentada: 23%</p>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default TrafficPage; |