Imporve the predicciones page
This commit is contained in:
@@ -88,9 +88,15 @@ const DemandChart: React.FC<DemandChartProps> = ({
|
||||
|
||||
// Process forecast data for chart
|
||||
const chartData = useMemo(() => {
|
||||
console.log('🔍 Processing forecast data for chart:', data);
|
||||
|
||||
const processedData: ChartDataPoint[] = data.map(forecast => {
|
||||
// Convert forecast_date to a proper date format for the chart
|
||||
const forecastDate = new Date(forecast.forecast_date);
|
||||
const dateString = forecastDate.toISOString().split('T')[0];
|
||||
|
||||
return {
|
||||
date: forecast.forecast_date,
|
||||
date: dateString,
|
||||
actualDemand: undefined, // Not available in current forecast response
|
||||
predictedDemand: forecast.predicted_demand,
|
||||
confidenceLower: forecast.confidence_lower,
|
||||
@@ -99,23 +105,32 @@ const DemandChart: React.FC<DemandChartProps> = ({
|
||||
};
|
||||
});
|
||||
|
||||
console.log('📊 Processed chart data:', processedData);
|
||||
return processedData.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
|
||||
}, [data]);
|
||||
|
||||
// Filter data based on selected period
|
||||
const filteredData = useMemo(() => {
|
||||
console.log('🔍 Filtering data - selected period:', selectedPeriod);
|
||||
console.log('🔍 Chart data before filtering:', chartData);
|
||||
|
||||
if (!selectedPeriod.start || !selectedPeriod.end) {
|
||||
console.log('📊 No period filter, returning all chart data');
|
||||
return chartData;
|
||||
}
|
||||
|
||||
return chartData.filter(point => {
|
||||
|
||||
const filtered = chartData.filter(point => {
|
||||
const pointDate = new Date(point.date);
|
||||
return pointDate >= selectedPeriod.start! && pointDate <= selectedPeriod.end!;
|
||||
});
|
||||
|
||||
console.log('📊 Filtered data:', filtered);
|
||||
return filtered;
|
||||
}, [chartData, selectedPeriod]);
|
||||
|
||||
// Update zoomed data when filtered data changes
|
||||
useEffect(() => {
|
||||
console.log('🔍 Setting zoomed data from filtered data:', filteredData);
|
||||
setZoomedData(filteredData);
|
||||
}, [filteredData]);
|
||||
|
||||
@@ -221,8 +236,11 @@ const DemandChart: React.FC<DemandChartProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
// Empty state
|
||||
if (zoomedData.length === 0) {
|
||||
// Use filteredData if zoomedData is empty but we have data
|
||||
const displayData = zoomedData.length > 0 ? zoomedData : filteredData;
|
||||
|
||||
// Empty state - only show if we truly have no data
|
||||
if (displayData.length === 0 && chartData.length === 0) {
|
||||
return (
|
||||
<Card className={className}>
|
||||
<CardHeader>
|
||||
@@ -286,7 +304,7 @@ const DemandChart: React.FC<DemandChartProps> = ({
|
||||
)}
|
||||
|
||||
{/* Reset zoom */}
|
||||
{zoomedData.length !== filteredData.length && (
|
||||
{displayData.length !== filteredData.length && (
|
||||
<Button variant="ghost" size="sm" onClick={handleResetZoom}>
|
||||
Restablecer Zoom
|
||||
</Button>
|
||||
@@ -298,28 +316,57 @@ const DemandChart: React.FC<DemandChartProps> = ({
|
||||
<CardBody padding="lg">
|
||||
<div style={{ width: '100%', height }}>
|
||||
<ResponsiveContainer>
|
||||
<ComposedChart data={zoomedData} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
<ComposedChart data={displayData} margin={{ top: 20, right: 30, left: 20, bottom: 60 }}>
|
||||
<defs>
|
||||
<linearGradient id="demandGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#10b981" stopOpacity={0.3}/>
|
||||
<stop offset="95%" stopColor="#10b981" stopOpacity={0.05}/>
|
||||
</linearGradient>
|
||||
<linearGradient id="confidenceGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#10b981" stopOpacity={0.1}/>
|
||||
<stop offset="95%" stopColor="#10b981" stopOpacity={0.02}/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid
|
||||
strokeDasharray="2 2"
|
||||
stroke="#e5e7eb"
|
||||
strokeOpacity={0.5}
|
||||
horizontal={true}
|
||||
vertical={false}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
stroke="#6b7280"
|
||||
fontSize={12}
|
||||
fontSize={11}
|
||||
tickMargin={8}
|
||||
angle={-45}
|
||||
textAnchor="end"
|
||||
height={80}
|
||||
interval={0}
|
||||
tickFormatter={(value) => {
|
||||
const date = new Date(value);
|
||||
return timeframe === 'weekly'
|
||||
? date.toLocaleDateString('es-ES', { month: 'short', day: 'numeric' })
|
||||
: timeframe === 'monthly'
|
||||
? date.toLocaleDateString('es-ES', { month: 'short', year: '2-digit' })
|
||||
: date.getFullYear().toString();
|
||||
return date.toLocaleDateString('es-ES', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
weekday: displayData.length <= 7 ? 'short' : undefined
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<YAxis
|
||||
stroke="#6b7280"
|
||||
fontSize={12}
|
||||
tickFormatter={(value) => `${value}`}
|
||||
<YAxis
|
||||
stroke="#6b7280"
|
||||
fontSize={11}
|
||||
width={60}
|
||||
tickFormatter={(value) => value.toFixed(0)}
|
||||
domain={['dataMin - 5', 'dataMax + 5']}
|
||||
/>
|
||||
<Tooltip
|
||||
content={<CustomTooltip />}
|
||||
cursor={{ stroke: '#10b981', strokeWidth: 1, strokeOpacity: 0.5 }}
|
||||
/>
|
||||
<Legend
|
||||
wrapperStyle={{ paddingTop: '20px' }}
|
||||
iconType="line"
|
||||
/>
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
<Legend />
|
||||
|
||||
{/* Confidence interval area */}
|
||||
{showConfidenceInterval && (
|
||||
@@ -328,9 +375,9 @@ const DemandChart: React.FC<DemandChartProps> = ({
|
||||
dataKey="confidenceUpper"
|
||||
stackId={1}
|
||||
stroke="none"
|
||||
fill="#10b98120"
|
||||
fillOpacity={0.3}
|
||||
name="Intervalo de Confianza Superior"
|
||||
fill="url(#confidenceGradient)"
|
||||
fillOpacity={0.4}
|
||||
name="Límite Superior"
|
||||
/>
|
||||
)}
|
||||
{showConfidenceInterval && (
|
||||
@@ -340,20 +387,40 @@ const DemandChart: React.FC<DemandChartProps> = ({
|
||||
stackId={1}
|
||||
stroke="none"
|
||||
fill="#ffffff"
|
||||
name="Intervalo de Confianza Inferior"
|
||||
name="Límite Inferior"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Background area for main prediction */}
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="predictedDemand"
|
||||
stroke="none"
|
||||
fill="url(#demandGradient)"
|
||||
fillOpacity={0.2}
|
||||
name="Área de Demanda"
|
||||
/>
|
||||
|
||||
{/* Predicted demand line */}
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="predictedDemand"
|
||||
stroke="#10b981"
|
||||
strokeWidth={3}
|
||||
dot={true}
|
||||
dotSize={6}
|
||||
activeDot={{ r: 8, stroke: '#10b981', strokeWidth: 2 }}
|
||||
dot={{
|
||||
fill: '#10b981',
|
||||
strokeWidth: 2,
|
||||
stroke: '#ffffff',
|
||||
r: 4
|
||||
}}
|
||||
activeDot={{
|
||||
r: 6,
|
||||
stroke: '#10b981',
|
||||
strokeWidth: 3,
|
||||
fill: '#ffffff'
|
||||
}}
|
||||
name="Demanda Predicha"
|
||||
connectNulls={false}
|
||||
/>
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
@@ -386,18 +453,46 @@ const DemandChart: React.FC<DemandChartProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Chart legend */}
|
||||
<div className="flex items-center justify-center gap-6 mt-4 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-0.5 bg-green-500"></div>
|
||||
<span className="text-text-secondary">Demanda Predicha</span>
|
||||
</div>
|
||||
{showConfidenceInterval && (
|
||||
{/* Enhanced Chart legend and insights */}
|
||||
<div className="mt-6 space-y-4">
|
||||
<div className="flex items-center justify-center gap-6 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-2 bg-green-500 bg-opacity-20"></div>
|
||||
<span className="text-text-secondary">Intervalo de Confianza</span>
|
||||
<div className="w-4 h-0.5 bg-green-500 rounded"></div>
|
||||
<span className="text-text-secondary">Demanda Predicha</span>
|
||||
</div>
|
||||
)}
|
||||
{showConfidenceInterval && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-2 bg-green-500 bg-opacity-20 rounded"></div>
|
||||
<span className="text-text-secondary">Intervalo de Confianza</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
|
||||
<span className="text-text-secondary">Puntos de Datos</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick stats */}
|
||||
<div className="grid grid-cols-3 gap-4 p-4 bg-gray-50 rounded-lg">
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-bold text-green-600">
|
||||
{Math.min(...displayData.map(d => d.predictedDemand || 0)).toFixed(1)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">Mínimo</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-bold text-blue-600">
|
||||
{(displayData.reduce((sum, d) => sum + (d.predictedDemand || 0), 0) / displayData.length).toFixed(1)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">Promedio</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-bold text-orange-600">
|
||||
{Math.max(...displayData.map(d => d.predictedDemand || 0)).toFixed(1)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">Máximo</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user