feat: Enhance application event logging and improve historical plot components with new data handling and UI updates
This commit is contained in:
parent
d588574b4f
commit
fe1df15942
|
@ -8628,8 +8628,172 @@
|
|||
"activated_datasets": 2,
|
||||
"total_datasets": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-16T16:13:19.529732",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-16T16:13:19.594211",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-16T16:13:19.607075",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: Fast",
|
||||
"details": {
|
||||
"dataset_id": "Fast",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "fast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-16T16:13:19.619074",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 2 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 2,
|
||||
"total_datasets": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-16T16:14:53.763412",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-16T16:14:53.829817",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-16T16:14:53.839819",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: Fast",
|
||||
"details": {
|
||||
"dataset_id": "Fast",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "fast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-16T16:14:53.851597",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 2 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 2,
|
||||
"total_datasets": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-16T16:20:52.922366",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-16T16:20:52.988919",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-16T16:20:52.999920",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: Fast",
|
||||
"details": {
|
||||
"dataset_id": "Fast",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "fast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-16T16:20:53.010921",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 2 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 2,
|
||||
"total_datasets": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-16T16:41:27.411359",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-16T16:41:27.475416",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-16T16:41:27.485418",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: Fast",
|
||||
"details": {
|
||||
"dataset_id": "Fast",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "fast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-16T16:41:27.495418",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 2 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 2,
|
||||
"total_datasets": 3
|
||||
}
|
||||
}
|
||||
],
|
||||
"last_updated": "2025-08-16T16:01:24.342184",
|
||||
"total_entries": 724
|
||||
"last_updated": "2025-08-16T16:41:27.495418",
|
||||
"total_entries": 740
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useRef, useEffect, useState, useCallback } from 'react';
|
||||
import { Box, Text, Switch, FormLabel, HStack, useColorModeValue, Alert, AlertIcon } from '@chakra-ui/react';
|
||||
import { Box, Text, Switch, FormLabel, HStack, VStack, useColorModeValue, Alert, AlertIcon } from '@chakra-ui/react';
|
||||
|
||||
// Global dependencies check - run once
|
||||
let dependenciesChecked = false;
|
||||
|
@ -77,6 +77,15 @@ const ChartjsHistoricalPlot = ({
|
|||
|
||||
// Create/Update chart when data changes
|
||||
useEffect(() => {
|
||||
console.log('📊 ChartjsHistoricalPlot useEffect triggered:', {
|
||||
hasData: historicalData && historicalData.length > 0,
|
||||
dataLength: historicalData?.length || 0,
|
||||
chartExists: !!chartRef.current,
|
||||
sessionId: session?.id,
|
||||
configChanged: JSON.stringify(config),
|
||||
zoomEnabled: isZoomEnabled
|
||||
});
|
||||
|
||||
if (!historicalData || historicalData.length === 0) {
|
||||
setDataPointsCount(0);
|
||||
return;
|
||||
|
@ -94,6 +103,18 @@ const ChartjsHistoricalPlot = ({
|
|||
setCurrentTimeRange(timeRange);
|
||||
}, [timeRange]);
|
||||
|
||||
// Cleanup chart on unmount
|
||||
useEffect(() => {
|
||||
console.log('📊 ChartjsHistoricalPlot mounted');
|
||||
return () => {
|
||||
console.log('📊 ChartjsHistoricalPlot unmounting - destroying chart');
|
||||
if (chartRef.current) {
|
||||
chartRef.current.destroy();
|
||||
chartRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Clean up chart on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
@ -115,58 +136,93 @@ const ChartjsHistoricalPlot = ({
|
|||
}
|
||||
|
||||
try {
|
||||
// Destroy existing chart
|
||||
if (chartRef.current) {
|
||||
chartRef.current.destroy();
|
||||
chartRef.current = null;
|
||||
}
|
||||
|
||||
console.log(`📊 createOrUpdateChart called - Chart exists? ${!!chartRef.current}`);
|
||||
console.log(`📊 Processing ${historicalData.length} historical data points for variables:`, session?.variables);
|
||||
|
||||
// Process historical data into Chart.js format
|
||||
const processedData = processHistoricalData(historicalData, session?.variables || []);
|
||||
setDataPointsCount(historicalData.length);
|
||||
|
||||
if (processedData.datasets.length === 0) {
|
||||
console.log('📊 No datasets created from data');
|
||||
setError('No valid data to display');
|
||||
return;
|
||||
// Siempre crear el gráfico, incluso si no hay datasets visibles
|
||||
// Esto permite al usuario hacer pan/zoom para encontrar datos
|
||||
if (processedData.datasets.length === 0 && historicalData.length === 0) {
|
||||
console.log('📊 No data available - creating empty chart for navigation');
|
||||
// Crear datasets vacíos para todas las variables
|
||||
const emptyDatasets = (session?.variables || []).map((variable, index) => ({
|
||||
label: variable,
|
||||
data: [],
|
||||
borderColor: ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0'][index % 4],
|
||||
backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0'][index % 4] + '20',
|
||||
borderWidth: 2,
|
||||
fill: false
|
||||
}));
|
||||
processedData.datasets = emptyDatasets;
|
||||
} else if (processedData.datasets.length === 0 && historicalData.length > 0) {
|
||||
console.log('📊 Data available but not visible in current range - allowing navigation');
|
||||
// Los datos existen pero pueden estar fuera del rango visible
|
||||
// Crear datasets para permitir navegación
|
||||
const emptyDatasets = (session?.variables || []).map((variable, index) => ({
|
||||
label: variable,
|
||||
data: [],
|
||||
borderColor: ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0'][index % 4],
|
||||
backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0'][index % 4] + '20',
|
||||
borderWidth: 2,
|
||||
fill: false
|
||||
}));
|
||||
processedData.datasets = emptyDatasets;
|
||||
}
|
||||
|
||||
console.log(`📊 Created ${processedData.datasets.length} datasets:`, processedData.datasets.map(d => ({ label: d.label, points: d.data.length })));
|
||||
|
||||
// Log sample data for debugging
|
||||
processedData.datasets.forEach((dataset, index) => {
|
||||
if (dataset.data.length > 0) {
|
||||
console.log(`📊 Dataset ${index} (${dataset.label}):`, {
|
||||
totalPoints: dataset.data.length,
|
||||
firstPoint: dataset.data[0],
|
||||
lastPoint: dataset.data[dataset.data.length - 1],
|
||||
samplePoints: dataset.data.slice(0, 3)
|
||||
// Check if chart exists
|
||||
if (chartRef.current) {
|
||||
// UPDATE existing chart data (faster, preserves zoom/pan)
|
||||
console.log(`📊 Updating existing chart with ${processedData.datasets.length} datasets`);
|
||||
|
||||
chartRef.current.data.datasets = processedData.datasets;
|
||||
|
||||
// Update time range for axis if we have time range data
|
||||
const minTime = currentTimeRange?.start ? new Date(currentTimeRange.start) : null;
|
||||
const maxTime = currentTimeRange?.end ? new Date(currentTimeRange.end) : null;
|
||||
|
||||
if (minTime && maxTime && chartRef.current.options.scales?.x) {
|
||||
console.log('📊 Updating chart time range:', {
|
||||
minTime: minTime.toISOString(),
|
||||
maxTime: maxTime.toISOString()
|
||||
});
|
||||
chartRef.current.options.scales.x.min = minTime;
|
||||
chartRef.current.options.scales.x.max = maxTime;
|
||||
}
|
||||
});
|
||||
|
||||
// Update without animation for better performance
|
||||
chartRef.current.update('none');
|
||||
|
||||
console.log(`📊 Chart data updated (preserving zoom/pan state)`);
|
||||
} else {
|
||||
// CREATE new chart (first time only)
|
||||
console.log(`📊 Creating new chart with ${processedData.datasets.length} datasets`);
|
||||
|
||||
// Create chart configuration
|
||||
const chartConfig = createChartConfig(processedData, config, isZoomEnabled);
|
||||
|
||||
console.log('📊 Chart config created:', {
|
||||
datasetCount: chartConfig.data.datasets.length,
|
||||
hasData: chartConfig.data.datasets.some(d => d.data.length > 0),
|
||||
decimationEnabled: chartConfig.options.plugins.decimation?.enabled
|
||||
});
|
||||
// Create chart configuration
|
||||
const chartConfig = createChartConfig(processedData, config, isZoomEnabled);
|
||||
|
||||
console.log('📊 Chart config created:', {
|
||||
datasetCount: chartConfig.data.datasets.length,
|
||||
hasData: chartConfig.data.datasets.some(d => d.data.length > 0),
|
||||
decimationEnabled: chartConfig.options.plugins.decimation?.enabled
|
||||
});
|
||||
|
||||
// Create new chart
|
||||
const ctx = canvasRef.current.getContext('2d');
|
||||
chartRef.current = new window.Chart(ctx, chartConfig);
|
||||
// Create new chart
|
||||
const ctx = canvasRef.current.getContext('2d');
|
||||
chartRef.current = new window.Chart(ctx, chartConfig);
|
||||
console.log('📊 Chart reference created:', !!chartRef.current);
|
||||
|
||||
// Setup zoom/pan event listeners
|
||||
if (isZoomEnabled && chartRef.current) {
|
||||
setupZoomPanEvents(chartRef.current);
|
||||
// Setup zoom/pan event listeners
|
||||
if (isZoomEnabled && chartRef.current) {
|
||||
setupZoomPanEvents(chartRef.current);
|
||||
}
|
||||
|
||||
console.log(`📊 Historical chart created for session ${sessionDataRef.current.sessionId} with ${processedData.datasets.length} datasets`);
|
||||
}
|
||||
|
||||
setError(null);
|
||||
console.log(`📊 Historical chart created for session ${sessionDataRef.current.sessionId} with ${processedData.datasets.length} datasets`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error creating historical chart:', error);
|
||||
|
@ -408,7 +464,8 @@ const ChartjsHistoricalPlot = ({
|
|||
);
|
||||
}
|
||||
|
||||
if (!historicalData || historicalData.length === 0) {
|
||||
// Solo mostrar mensaje de "no data" si nunca se han cargado datos y el chart no existe
|
||||
if ((!historicalData || historicalData.length === 0) && !chartRef.current) {
|
||||
return (
|
||||
<Box
|
||||
height={height}
|
||||
|
@ -420,7 +477,12 @@ const ChartjsHistoricalPlot = ({
|
|||
borderColor={borderColor}
|
||||
borderRadius="md"
|
||||
>
|
||||
<Text color={textColor}>No historical data available</Text>
|
||||
<VStack>
|
||||
<Text color={textColor}>No historical data available</Text>
|
||||
<Text fontSize="sm" color={useColorModeValue('gray.500', 'gray.400')}>
|
||||
Use the time selector to navigate to data
|
||||
</Text>
|
||||
</VStack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -460,6 +522,11 @@ const ChartjsHistoricalPlot = ({
|
|||
(Min/Max decimation active)
|
||||
</Text>
|
||||
)}
|
||||
{dataPointsCount === 0 && chartRef.current && (
|
||||
<Text as="span" ml={2} fontSize="xs" color="orange.500">
|
||||
No data in current view - use pan/zoom to navigate
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
</HStack>
|
||||
|
||||
|
|
|
@ -140,7 +140,18 @@ export default function PlotHistoricalSession({
|
|||
const [showDataPreview, setShowDataPreview] = useState(false)
|
||||
|
||||
// Configuration state
|
||||
const [config, setConfig] = useState(session.config || {})
|
||||
// Config state (estabilizado para evitar re-renders)
|
||||
const [config, setConfig] = useState(() => session.config || {})
|
||||
|
||||
// Estabilizar config para evitar re-renders innecesarios
|
||||
const stableConfig = useMemo(() => config, [JSON.stringify(config)])
|
||||
|
||||
// Estabilizar historicalData para evitar re-renders innecesarios
|
||||
const stableHistoricalData = useMemo(() => historicalData, [
|
||||
historicalData?.length,
|
||||
historicalData?.[0]?.timestamp,
|
||||
historicalData?.[historicalData.length - 1]?.timestamp
|
||||
])
|
||||
|
||||
// Helper function to ensure valid Date objects
|
||||
const ensureValidDate = (dateValue, fallback) => {
|
||||
|
@ -166,7 +177,7 @@ export default function PlotHistoricalSession({
|
|||
|
||||
// Load historical data when derived time range changes
|
||||
useEffect(() => {
|
||||
if (dateRange) { // Only load after we have date range
|
||||
if (dateRange && !isLoading) { // Only load after we have date range and not already loading
|
||||
loadHistoricalData()
|
||||
}
|
||||
}, [session.id, derivedTimeRange, dateRange])
|
||||
|
@ -245,6 +256,12 @@ export default function PlotHistoricalSession({
|
|||
}
|
||||
|
||||
try {
|
||||
// Prevent concurrent loads
|
||||
if (isLoading) {
|
||||
console.log('📊 Skipping load - already loading')
|
||||
return
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
setLoadingProgress(0)
|
||||
|
@ -588,33 +605,45 @@ export default function PlotHistoricalSession({
|
|||
</Box>
|
||||
)}
|
||||
|
||||
{!isLoading && !error && (
|
||||
<Box height="400px">
|
||||
<ChartjsHistoricalPlot
|
||||
session={session}
|
||||
historicalData={historicalData}
|
||||
timeRange={derivedTimeRange}
|
||||
config={config}
|
||||
onZoomToTimeRange={handleZoomToTimeRange}
|
||||
onPanToTimeRange={handlePanToTimeRange}
|
||||
height="400px"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{isLoading && (
|
||||
<Box height="400px" display="flex" alignItems="center" justifyContent="center">
|
||||
<VStack>
|
||||
<Spinner size="lg" color="blue.500" />
|
||||
<Text fontSize="sm" color={subtleTextColor}>
|
||||
Loading historical data...
|
||||
</Text>
|
||||
<Text fontSize="xs" color={smallTextColor}>
|
||||
{loadingProgress}% complete
|
||||
</Text>
|
||||
</VStack>
|
||||
</Box>
|
||||
)}
|
||||
{/* Chart container - siempre montado para evitar remounting */}
|
||||
<Box height="400px" position="relative">
|
||||
<ChartjsHistoricalPlot
|
||||
key={session.id} // Estable key para evitar remounting
|
||||
session={session}
|
||||
historicalData={stableHistoricalData}
|
||||
timeRange={derivedTimeRange}
|
||||
config={stableConfig}
|
||||
onZoomToTimeRange={handleZoomToTimeRange}
|
||||
onPanToTimeRange={handlePanToTimeRange}
|
||||
height="400px"
|
||||
/>
|
||||
|
||||
{/* Loading overlay */}
|
||||
{isLoading && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
right="0"
|
||||
bottom="0"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
bg={useColorModeValue('whiteAlpha.800', 'blackAlpha.800')}
|
||||
backdropFilter="blur(2px)"
|
||||
>
|
||||
<VStack>
|
||||
<Spinner size="lg" color="blue.500" />
|
||||
<Text fontSize="sm" color={subtleTextColor}>
|
||||
Loading historical data...
|
||||
</Text>
|
||||
<Text fontSize="xs" color={smallTextColor}>
|
||||
{loadingProgress}% complete
|
||||
</Text>
|
||||
</VStack>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</CardBody>
|
||||
)}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useMemo, useState, useCallback, useRef, useEffect } from "react";
|
||||
import { Box, Flex, Text, Slider, SliderTrack, SliderFilledTrack, SliderThumb, useColorModeValue } from "@chakra-ui/react";
|
||||
import { Box, Flex, Text, Slider, SliderTrack, SliderFilledTrack, SliderThumb, Button, IconButton, useColorModeValue } from "@chakra-ui/react";
|
||||
import { CheckIcon } from "@chakra-ui/icons";
|
||||
import DatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
|
||||
|
@ -19,25 +20,42 @@ export default function TimePointSelector({
|
|||
const [minMs, maxMs] = useMemo(() => [minDate.getTime(), maxDate.getTime()], [minDate, maxDate]);
|
||||
const stepMs = useMemo(() => stepMinutes * 60 * 1000, [stepMinutes]);
|
||||
|
||||
// Estado único (Date)
|
||||
// Estado único (Date) - este es el valor "oficial"
|
||||
const [value, setValue] = useState(() => {
|
||||
// clamp al rango por si initial cae fuera
|
||||
const t = initial.getTime();
|
||||
return new Date(Math.min(Math.max(t, minMs), maxMs));
|
||||
});
|
||||
|
||||
// Sincronizar con prop initial cuando cambie externamente (pan/zoom)
|
||||
useEffect(() => {
|
||||
const t = initial.getTime();
|
||||
const clampedValue = new Date(Math.min(Math.max(t, minMs), maxMs));
|
||||
setValue(clampedValue);
|
||||
}, [initial, minMs, maxMs]);
|
||||
|
||||
const valueMs = value.getTime();
|
||||
// Estado temporal del slider (solo para UI, no dispara eventos)
|
||||
const [sliderValue, setSliderValue] = useState(() => value.getTime());
|
||||
|
||||
// Estado para detectar si hay cambios pendientes
|
||||
const [hasPendingChanges, setHasPendingChanges] = useState(false);
|
||||
|
||||
// Cooldown para evitar múltiples solicitudes
|
||||
const cooldownRef = useRef(null);
|
||||
const lastCallbackValueRef = useRef(null);
|
||||
const isExternalUpdateRef = useRef(false); // Flag para detectar actualizaciones externas
|
||||
|
||||
// Sincronizar con prop initial cuando cambie externamente (pan/zoom)
|
||||
useEffect(() => {
|
||||
const t = initial.getTime();
|
||||
const clampedValue = new Date(Math.min(Math.max(t, minMs), maxMs));
|
||||
|
||||
// Marcar que es una actualización externa
|
||||
isExternalUpdateRef.current = true;
|
||||
setValue(clampedValue);
|
||||
setSliderValue(clampedValue.getTime()); // Sincronizar slider también
|
||||
setHasPendingChanges(false); // Reset pending changes
|
||||
|
||||
// Reset flag después de un tick
|
||||
setTimeout(() => {
|
||||
isExternalUpdateRef.current = false;
|
||||
}, 0);
|
||||
}, [initial, minMs, maxMs]);
|
||||
|
||||
const valueMs = value.getTime();
|
||||
|
||||
// Redondea al paso del slider
|
||||
const snapToStep = useCallback((ms) => {
|
||||
|
@ -74,26 +92,42 @@ export default function TimePointSelector({
|
|||
};
|
||||
}, []);
|
||||
|
||||
// Cambio desde el DatePicker (sin cooldown, cambio directo)
|
||||
// Función para aplicar los cambios pendientes del slider
|
||||
const applySliderChanges = useCallback(() => {
|
||||
if (!hasPendingChanges) return;
|
||||
|
||||
const newValue = new Date(sliderValue);
|
||||
setValue(newValue);
|
||||
setHasPendingChanges(false);
|
||||
|
||||
if (onTimeChange) {
|
||||
console.log('📊 TimeSelector: Applying slider changes', newValue);
|
||||
onTimeChange(newValue);
|
||||
}
|
||||
}, [sliderValue, hasPendingChanges, onTimeChange]);
|
||||
|
||||
// Cambio desde el DatePicker (inmediato, actualiza ambos valores)
|
||||
const onPick = (d) => {
|
||||
if (!d) return;
|
||||
if (!d || isExternalUpdateRef.current) return;
|
||||
|
||||
const newValue = new Date(snapToStep(d.getTime()));
|
||||
setValue(newValue);
|
||||
setSliderValue(newValue.getTime());
|
||||
setHasPendingChanges(false);
|
||||
|
||||
// DatePicker no necesita cooldown, es cambio directo
|
||||
// DatePicker es cambio directo
|
||||
if (onTimeChange) {
|
||||
console.log('📊 TimeSelector: DatePicker change (immediate)', newValue);
|
||||
onTimeChange(newValue);
|
||||
}
|
||||
};
|
||||
|
||||
// Cambio desde el Slider (con cooldown)
|
||||
// Cambio desde el Slider (solo actualiza UI, no dispara eventos)
|
||||
const onSlide = (ms) => {
|
||||
const newValue = new Date(ms);
|
||||
setValue(newValue);
|
||||
if (isExternalUpdateRef.current) return;
|
||||
|
||||
// Usar cooldown para el slider
|
||||
debouncedOnTimeChange(newValue);
|
||||
setSliderValue(ms);
|
||||
setHasPendingChanges(Math.abs(ms - value.getTime()) > 1000); // Solo marcar si hay diferencia significativa
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -161,12 +195,25 @@ export default function TimePointSelector({
|
|||
</Box>
|
||||
|
||||
<Box flex="1" minW="260px">
|
||||
<Text mb={1} color={textColor}>Navegar con slider</Text>
|
||||
<Flex align="center" mb={1}>
|
||||
<Text color={textColor}>Navegar con slider</Text>
|
||||
{hasPendingChanges && (
|
||||
<Button
|
||||
size="xs"
|
||||
ml={2}
|
||||
colorScheme="blue"
|
||||
leftIcon={<CheckIcon />}
|
||||
onClick={applySliderChanges}
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
<Slider
|
||||
min={minMs}
|
||||
max={maxMs}
|
||||
step={stepMs}
|
||||
value={valueMs}
|
||||
value={sliderValue}
|
||||
onChange={onSlide}
|
||||
colorScheme="blue"
|
||||
>
|
||||
|
@ -175,16 +222,23 @@ export default function TimePointSelector({
|
|||
</SliderTrack>
|
||||
<SliderThumb bg="blue.500" />
|
||||
</Slider>
|
||||
<Text mt={2} fontSize="sm" color={textColor}>
|
||||
{value.toLocaleString('es-ES', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})}
|
||||
</Text>
|
||||
<Flex mt={2} justify="space-between" align="center">
|
||||
<Text fontSize="sm" color={textColor}>
|
||||
{new Date(sliderValue).toLocaleString('es-ES', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})}
|
||||
</Text>
|
||||
{hasPendingChanges && (
|
||||
<Text fontSize="xs" color="orange.500">
|
||||
Cambios pendientes
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
"should_connect": true,
|
||||
"should_stream": false,
|
||||
"active_datasets": [
|
||||
"DAR",
|
||||
"Test",
|
||||
"Fast",
|
||||
"DAR"
|
||||
"Fast"
|
||||
]
|
||||
},
|
||||
"auto_recovery_enabled": true,
|
||||
"last_update": "2025-08-16T16:01:29.169391",
|
||||
"last_update": "2025-08-16T16:41:43.854840",
|
||||
"plotjuggler_path": "C:\\Program Files\\PlotJuggler\\plotjuggler.exe"
|
||||
}
|
Loading…
Reference in New Issue