Improve the dahboard 4

This commit is contained in:
Urtzi Alfaro
2025-08-18 20:50:41 +02:00
parent 523fc663e8
commit 18355cd8be
10 changed files with 1133 additions and 152 deletions

View File

@@ -16,6 +16,8 @@ import {
import { useExternal } from '../../api';
import { useTenantId } from '../../hooks/useTenantId';
import type { WeatherData, TrafficData, WeatherForecast } from '../../api/services/external.service';
import type { HourlyForecast } from '../../types/weather';
import { WeatherRecommendationService, WeatherRecommendation, WeatherAnalyzer } from '../../services/weatherRecommendationService';
import Card from '../ui/Card';
interface WeatherContextProps {
@@ -28,6 +30,7 @@ const WeatherContext: React.FC<WeatherContextProps> = ({ className = '' }) => {
getCurrentWeather,
getCurrentTraffic,
getWeatherForecast,
getHourlyWeatherForecast,
isLoading,
error
} = useExternal();
@@ -35,6 +38,8 @@ const WeatherContext: React.FC<WeatherContextProps> = ({ className = '' }) => {
const [weatherData, setWeatherData] = useState<WeatherData | null>(null);
const [trafficData, setTrafficData] = useState<TrafficData | null>(null);
const [weatherForecast, setWeatherForecast] = useState<WeatherForecast[]>([]);
const [hourlyForecast, setHourlyForecast] = useState<HourlyForecast[]>([]);
const [recommendations, setRecommendations] = useState<WeatherRecommendation[]>([]);
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
const [isRefreshing, setIsRefreshing] = useState(false);
@@ -49,10 +54,11 @@ const WeatherContext: React.FC<WeatherContextProps> = ({ className = '' }) => {
try {
// Load current weather data from AEMET
const [weather, traffic, forecast] = await Promise.allSettled([
const [weather, traffic, forecast, hourlyData] = await Promise.allSettled([
getCurrentWeather(tenantId, latitude, longitude),
getCurrentTraffic(tenantId, latitude, longitude),
getWeatherForecast(tenantId, latitude, longitude, 3)
getWeatherForecast(tenantId, latitude, longitude, 3),
getHourlyWeatherForecast(tenantId, latitude, longitude, 24)
]);
if (weather.status === 'fulfilled') {
@@ -76,6 +82,22 @@ const WeatherContext: React.FC<WeatherContextProps> = ({ className = '' }) => {
setWeatherForecast([]);
}
if (hourlyData.status === 'fulfilled') {
setHourlyForecast(hourlyData.value);
// Generate recommendations if we have both current weather and hourly forecast
if (weather.status === 'fulfilled' && hourlyData.value.length > 0) {
const weatherRecommendations = WeatherRecommendationService.generateRecommendations(
weather.value,
hourlyData.value
);
setRecommendations(weatherRecommendations);
}
} else {
console.warn('Hourly forecast unavailable:', hourlyData.reason);
setHourlyForecast([]);
}
setLastUpdated(new Date());
} catch (error) {
console.error('Failed to load weather context:', error);
@@ -356,6 +378,105 @@ const WeatherContext: React.FC<WeatherContextProps> = ({ className = '' }) => {
</div>
)}
{/* Business Recommendations - Most Important Section */}
{recommendations.length > 0 && (
<div className="mb-6 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-200 rounded-lg p-4">
<h4 className="font-bold text-blue-800 mb-4 flex items-center">
<TrendingUp className="h-5 w-5 mr-2" />
Recomendaciones Meteorológicas para el Negocio
</h4>
<div className="grid grid-cols-1 gap-3">
{recommendations.slice(0, 4).map((rec) => (
<div
key={rec.id}
className={`p-3 rounded-lg border-l-4 ${
rec.priority === 'urgent' ? 'border-red-500 bg-red-50' :
rec.priority === 'high' ? 'border-orange-500 bg-orange-50' :
rec.priority === 'medium' ? 'border-yellow-500 bg-yellow-50' :
'border-green-500 bg-green-50'
}`}
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center mb-1">
<span className={`text-xs px-2 py-1 rounded-full font-medium ${
rec.type === 'production' ? 'bg-blue-100 text-blue-800' :
rec.type === 'inventory' ? 'bg-green-100 text-green-800' :
rec.type === 'marketing' ? 'bg-purple-100 text-purple-800' :
rec.type === 'sales' ? 'bg-orange-100 text-orange-800' :
'bg-gray-100 text-gray-800'
}`}>
{rec.type.toUpperCase()}
</span>
<span className={`text-xs ml-2 px-1 py-0.5 rounded ${
rec.priority === 'urgent' ? 'bg-red-200 text-red-800' :
rec.priority === 'high' ? 'bg-orange-200 text-orange-800' :
rec.priority === 'medium' ? 'bg-yellow-200 text-yellow-800' :
'bg-green-200 text-green-800'
}`}>
{rec.priority.toUpperCase()}
</span>
</div>
<h5 className="font-semibold text-sm text-gray-900 mb-1">{rec.title}</h5>
<p className="text-xs text-gray-700 mb-1">{rec.description}</p>
<p className="text-xs font-medium text-gray-800 mb-1">
<strong>Acción:</strong> {rec.action}
</p>
<p className="text-xs text-gray-600">{rec.impact}</p>
{rec.businessMetrics?.expectedSalesChange && (
<div className="mt-2 text-xs">
<span className={`font-medium ${
rec.businessMetrics.expectedSalesChange > 0 ? 'text-green-700' : 'text-red-700'
}`}>
Ventas: {rec.businessMetrics.expectedSalesChange > 0 ? '+' : ''}{rec.businessMetrics.expectedSalesChange}%
</span>
</div>
)}
</div>
<div className="text-right ml-3">
<div className="text-xs text-gray-500">{rec.timeframe}</div>
<div className="text-xs text-gray-400 mt-1">
Confianza: {rec.confidence}%
</div>
</div>
</div>
</div>
))}
</div>
<div className="mt-3 text-xs text-blue-600 flex items-center">
<CheckCircle className="h-3 w-3 mr-1" />
Recomendaciones basadas en datos meteorológicos de AEMET en tiempo real
</div>
</div>
)}
{/* Hourly Forecast Preview */}
{hourlyForecast.length > 0 && (
<div className="mb-6">
<h4 className="font-medium text-gray-800 mb-3">Pronóstico por Horas (Próximas 8 horas)</h4>
<div className="grid grid-cols-4 gap-2">
{hourlyForecast.slice(0, 8).map((hourly, index) => (
<div key={index} className="bg-gray-50 rounded-lg p-2 text-center">
<div className="text-xs text-gray-600 mb-1">
{new Date(hourly.forecast_datetime).getHours().toString().padStart(2, '0')}:00
</div>
<div className="flex justify-center mb-1">
{getWeatherIcon(hourly.description, hourly.precipitation)}
</div>
<div className="text-sm font-semibold text-gray-900">
{hourly.temperature.toFixed(0)}°
</div>
{hourly.precipitation > 0 && (
<div className="text-xs text-blue-600">
{hourly.precipitation.toFixed(1)}mm
</div>
)}
</div>
))}
</div>
</div>
)}
{/* Weather Forecast */}
{weatherForecast.length > 0 && (
<div>