Improve the dahboard 4
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user