Refactor PlotHistoricalManager: Remove TimeRangeSelector and manage time range directly in session creation; update PlotHistoricalSession for improved performance with simple plot mode toggle; enhance TimePointSelector with time picker styles for better UI consistency.

This commit is contained in:
Miguel 2025-08-16 21:12:19 +02:00
parent c3c55dd3dc
commit e46cc62a0d
4 changed files with 2043 additions and 1749 deletions

File diff suppressed because it is too large Load Diff

View File

@ -94,145 +94,6 @@ function ConfirmationDialog({ isOpen, onClose, onConfirm, title, message, itemNa
)
}
// Time Range Selector Component
function TimeRangeSelector({ startTime, endTime, onTimeRangeChange, isLoading }) {
const [localStartTime, setLocalStartTime] = useState('')
const [localEndTime, setLocalEndTime] = useState('')
const toast = useToast()
useEffect(() => {
// Initialize with current values with safe date handling
if (startTime) {
const startDate = new Date(startTime)
if (!isNaN(startDate.getTime())) {
setLocalStartTime(startDate.toISOString().slice(0, 16))
}
}
if (endTime) {
const endDate = new Date(endTime)
if (!isNaN(endDate.getTime())) {
setLocalEndTime(endDate.toISOString().slice(0, 16))
}
}
}, [startTime, endTime])
const handleApplyTimeRange = () => {
if (localStartTime && localEndTime) {
const start = new Date(localStartTime)
const end = new Date(localEndTime)
// Validate the time range
if (start >= end) {
toast({
title: "Invalid Time Range",
description: "Start time must be before end time",
status: "error",
duration: 3000,
isClosable: true
})
return
}
if (isNaN(start.getTime()) || isNaN(end.getTime())) {
toast({
title: "Invalid Date",
description: "Please enter valid dates",
status: "error",
duration: 3000,
isClosable: true
})
return
}
onTimeRangeChange(start, end)
}
}
const handleQuickRange = (hours) => {
const end = new Date()
const start = new Date(end.getTime() - hours * 60 * 60 * 1000)
// Update local state
setLocalStartTime(start.toISOString().slice(0, 16))
setLocalEndTime(end.toISOString().slice(0, 16))
// Immediately apply the time range
onTimeRangeChange(start, end)
toast({
title: "Time Range Updated",
description: `Set to last ${hours} hour${hours !== 1 ? 's' : ''}`,
status: "info",
duration: 2000,
isClosable: true
})
}
return (
<Card mb={4}>
<CardHeader>
<Heading size="sm">📅 Time Range Selection</Heading>
</CardHeader>
<CardBody>
<VStack spacing={4} align="stretch">
<HStack spacing={4}>
<FormControl>
<FormLabel fontSize="sm">Start Time</FormLabel>
<Input
type="datetime-local"
value={localStartTime}
onChange={(e) => setLocalStartTime(e.target.value)}
size="sm"
/>
</FormControl>
<FormControl>
<FormLabel fontSize="sm">End Time</FormLabel>
<Input
type="datetime-local"
value={localEndTime}
onChange={(e) => setLocalEndTime(e.target.value)}
size="sm"
/>
</FormControl>
<Button
colorScheme="blue"
onClick={handleApplyTimeRange}
isLoading={isLoading}
size="sm"
mt={6}
>
📊 Apply
</Button>
</HStack>
<Divider />
<Box>
<Text fontSize="sm" mb={2} fontWeight="medium">Quick Ranges:</Text>
<HStack spacing={2} flexWrap="wrap">
<Button size="xs" onClick={() => handleQuickRange(1)} isLoading={isLoading}>
Last Hour
</Button>
<Button size="xs" onClick={() => handleQuickRange(6)} isLoading={isLoading}>
Last 6 Hours
</Button>
<Button size="xs" onClick={() => handleQuickRange(24)} isLoading={isLoading}>
Last 24 Hours
</Button>
<Button size="xs" onClick={() => handleQuickRange(72)} isLoading={isLoading}>
Last 3 Days
</Button>
<Button size="xs" onClick={() => handleQuickRange(168)} isLoading={isLoading}>
Last Week
</Button>
</HStack>
</Box>
</VStack>
</CardBody>
</Card>
)
}
// Collapsible Plot Items Form - Each item in the array is individually collapsible
function CollapsiblePlotItemsForm({ data, schema, uiSchema, onSave, title, icon, getItemLabel, isExpanded, onToggleExpansion }) {
const [formData, setFormData] = useState(data)
@ -431,14 +292,6 @@ export default function PlotHistoricalManager() {
const [booleanVariables, setBooleanVariables] = useState([])
const [selectedPlotId, setSelectedPlotId] = useState('')
const [customTimeRange, setCustomTimeRange] = useState(() => {
const now = new Date()
const twentyFourHoursAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000)
return {
start: twentyFourHoursAgo,
end: now
}
})
const [isLoading, setIsLoading] = useState(false)
const [isLoadingHistoricalData, setIsLoadingHistoricalData] = useState(false)
@ -641,16 +494,22 @@ export default function PlotHistoricalManager() {
// Create historical session
const sessionId = `historical_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
// Default time range - individual charts will manage their own time ranges
const defaultTimeRange = {
start: new Date(Date.now() - 24 * 60 * 60 * 1000), // 24 hours ago
end: new Date()
}
const newSession = {
id: sessionId,
plotId: selectedPlotId,
name: plotDef.name,
variables: variableNames,
timeRange: { ...customTimeRange },
timeRange: defaultTimeRange,
config: {
...plotDef,
variables: variableNames,
time_window: Math.floor((customTimeRange.end - customTimeRange.start) / 1000),
time_window: 86400, // 24 hours in seconds
y_min: plotDef.y_min,
y_max: plotDef.y_max,
trigger_variable: plotDef.trigger_variable,
@ -696,11 +555,6 @@ export default function PlotHistoricalManager() {
})
}
const handleTimeRangeChange = (start, end) => {
console.log('📅 Time range changed:', { start, end })
setCustomTimeRange({ start, end })
}
const bgColor = useColorModeValue('gray.50', 'gray.900')
const cardBgColor = useColorModeValue('white', 'gray.800')
@ -758,14 +612,6 @@ export default function PlotHistoricalManager() {
</CardHeader>
</Card>
{/* Time Range Selector */}
<TimeRangeSelector
startTime={customTimeRange.start}
endTime={customTimeRange.end}
onTimeRangeChange={handleTimeRangeChange}
isLoading={isLoadingHistoricalData}
/>
{/* Plot Creation */}
<Card bg={cardBgColor}>
<CardHeader>
@ -852,36 +698,6 @@ export default function PlotHistoricalManager() {
</Collapse>
</Card>
{/* Plot Definitions Configuration */}
{plotDefinitionsSchema && (
<CollapsiblePlotItemsForm
data={plotDefinitions.plots || []}
schema={plotDefinitionsSchema}
uiSchema={plotDefinitionsUiSchema}
onSave={(newPlots) => savePlotDefinitions({ plots: newPlots })}
title="Plot Definitions"
icon="📊"
getItemLabel={(item) => item.name || 'Unnamed Plot'}
isExpanded={isDefinitionsExpanded}
onToggleExpansion={() => setIsDefinitionsExpanded(!isDefinitionsExpanded)}
/>
)}
{/* Plot Variables Configuration */}
{plotVariablesSchema && (
<CollapsiblePlotItemsForm
data={plotVariables.variables || []}
schema={plotVariablesSchema}
uiSchema={plotVariablesUiSchema}
onSave={(newVariables) => savePlotVariables({ variables: newVariables })}
title="Plot Variables"
icon="🔢"
getItemLabel={(item) => `${item.plot_id}: ${item.variable_name}` || 'Unnamed Variable'}
isExpanded={isVariablesExpanded}
onToggleExpansion={() => setIsVariablesExpanded(!isVariablesExpanded)}
/>
)}
{isLoading && (
<Flex justify="center" py={8}>
<Spinner size="lg" color="blue.500" />

View File

@ -46,7 +46,7 @@ import {
Progress,
Tooltip
} from '@chakra-ui/react'
import { SettingsIcon, RepeatIcon, ViewIcon, DeleteIcon, TimeIcon, CalendarIcon, ViewOffIcon } from '@chakra-ui/icons'
import { SettingsIcon, RepeatIcon, ViewIcon, DeleteIcon, TimeIcon, CalendarIcon, ViewOffIcon, StarIcon, ExternalLinkIcon, ChevronUpIcon, ChevronDownIcon } from '@chakra-ui/icons'
import ChartjsHistoricalPlot from './ChartjsHistoricalPlot.jsx'
import TimePointSelector from './TimePointSelector.jsx'
import DataAvailabilityBar from './DataAvailabilityBar.jsx'
@ -171,8 +171,8 @@ export default function PlotHistoricalSession({
const { isOpen: isConfigModalOpen, onOpen: onConfigModalOpen, onClose: onConfigModalClose } = useDisclosure()
const { isOpen: isFullscreen, onOpen: openFullscreen, onClose: closeFullscreen } = useDisclosure()
// NEW: Simple plot mode toggle
const [isSimplePlot, setIsSimplePlot] = useState(false)
// NEW: Simple plot mode toggle (default to true for better performance)
const [isSimplePlot, setIsSimplePlot] = useState(true)
// Keep track of the last loaded data range for optimization
const [loadedDataRange, setLoadedDataRange] = useState(null)
@ -438,6 +438,17 @@ export default function PlotHistoricalSession({
const infoBgColor = useColorModeValue('gray.50', 'gray.700')
const subtleTextColor = useColorModeValue('gray.500', 'gray.400')
const smallTextColor = useColorModeValue('gray.400', 'gray.500')
// Additional color mode values for conditional elements
const whiteAlphaBg = useColorModeValue('whiteAlpha.800', 'blackAlpha.800')
const inputBg = useColorModeValue('white', 'gray.700')
const inputHoverBg = useColorModeValue('gray.50', 'gray.600')
const editFieldColor = useColorModeValue('gray.900', 'gray.100')
const editFieldBg = useColorModeValue('gray.50', 'gray.600')
const calendarFilter = useColorModeValue('none', 'invert(1)')
const modalBg = useColorModeValue('white', 'gray.700')
const modalTextColor = useColorModeValue('gray.600', 'gray.300')
const highlightBg = useColorModeValue('blue.50', 'blue.900')
// Format time range for display
const formatTimeRange = (timeRange) => {
@ -552,20 +563,20 @@ export default function PlotHistoricalSession({
colorScheme={showDataPreview ? 'blue' : 'gray'}
/>
</Tooltip>
<Tooltip label="Simple Plot Mode">
<Tooltip label={isSimplePlot ? "Standard Plot Mode" : "Simple Plot Mode"}>
<IconButton
icon={<ViewOffIcon />}
icon={isSimplePlot ? <StarIcon /> : <ViewOffIcon />}
size="sm"
variant="ghost"
onClick={() => setIsSimplePlot(!isSimplePlot)}
colorScheme={isSimplePlot ? 'blue' : 'gray'}
colorScheme={isSimplePlot ? 'orange' : 'gray'}
/>
</Tooltip>
<Tooltip label="Fullscreen">
<Button
size="sm"
variant="ghost"
leftIcon={<ViewIcon />}
leftIcon={<ExternalLinkIcon />}
onClick={openFullscreen}
>
Fullscreen
@ -582,7 +593,7 @@ export default function PlotHistoricalSession({
</Tooltip>
<Tooltip label={isExpanded ? "Collapse" : "Expand"}>
<IconButton
icon={isExpanded ? <ViewIcon /> : <ViewIcon />}
icon={isExpanded ? <ChevronUpIcon /> : <ChevronDownIcon />}
size="sm"
variant="ghost"
onClick={() => setIsExpanded(!isExpanded)}
@ -687,7 +698,7 @@ export default function PlotHistoricalSession({
display="flex"
alignItems="center"
justifyContent="center"
bg={useColorModeValue('whiteAlpha.800', 'blackAlpha.800')}
bg={whiteAlphaBg}
backdropFilter="blur(2px)"
>
<VStack>
@ -728,6 +739,34 @@ export default function PlotHistoricalSession({
}
onChange={(e) => setCentralTime(new Date(e.target.value))}
size="sm"
bg={inputBg}
_hover={{ bg: inputHoverBg }}
sx={{
'&::-webkit-datetime-edit': {
color: editFieldColor
},
'&::-webkit-datetime-edit-fields-wrapper': {
background: 'transparent'
},
'&::-webkit-datetime-edit-hour-field': {
background: editFieldBg,
borderRadius: '2px',
padding: '1px 2px'
},
'&::-webkit-datetime-edit-minute-field': {
background: editFieldBg,
borderRadius: '2px',
padding: '1px 2px'
},
'&::-webkit-datetime-edit-second-field': {
background: editFieldBg,
borderRadius: '2px',
padding: '1px 2px'
},
'&::-webkit-calendar-picker-indicator': {
filter: calendarFilter
}
}}
/>
</FormControl>
<FormControl>
@ -830,12 +869,12 @@ export default function PlotHistoricalSession({
{/* Fullscreen Modal */}
<Modal isOpen={isFullscreen} onClose={closeFullscreen} size="full">
<ModalOverlay bg="blackAlpha.800" />
<ModalContent bg={useColorModeValue('white', 'gray.700')} m={0} borderRadius={0} h="100vh">
<ModalContent bg={modalBg} m={0} borderRadius={0} h="100vh">
<ModalHeader>
<HStack>
<Text>📈 {session.name} - Fullscreen Mode</Text>
<Spacer />
<Text fontSize="sm" color={useColorModeValue('gray.600', 'gray.300')}>
<Text fontSize="sm" color={modalTextColor}>
Zoom: Drag to select area | Pan: Shift + Drag | Double-click to reset
</Text>
</HStack>
@ -904,7 +943,7 @@ export default function PlotHistoricalSession({
</Button>
{/* Time range info */}
<Box px={3} py={1} bg={useColorModeValue('blue.50', 'blue.900')} borderRadius="md" fontSize="xs">
<Box px={3} py={1} bg={highlightBg} borderRadius="md" fontSize="xs">
<HStack spacing={4}>
<Text><strong>Range:</strong> {timeRangeSeconds}s</Text>
<Text><strong>From:</strong> {formatCentralTimeInfo().start}</Text>

View File

@ -203,6 +203,34 @@ export default function TimePointSelector({
'& .react-datepicker__day--selected': {
backgroundColor: 'blue.500',
color: 'white'
},
// Time picker specific styles
'& .react-datepicker__time-container': {
backgroundColor: useColorModeValue('white', 'gray.800'),
borderLeft: '1px solid',
borderColor: borderColor
},
'& .react-datepicker__time-box': {
backgroundColor: useColorModeValue('white', 'gray.800')
},
'& .react-datepicker__time-list': {
backgroundColor: useColorModeValue('white', 'gray.800'),
'& .react-datepicker__time-list-item': {
color: textColor,
backgroundColor: useColorModeValue('white', 'gray.800'),
'&:hover': {
backgroundColor: useColorModeValue('gray.100', 'gray.700')
},
'&--selected': {
backgroundColor: 'blue.500',
color: 'white',
fontWeight: 'bold'
}
}
},
'& .react-datepicker__time-name': {
color: textColor,
backgroundColor: useColorModeValue('gray.50', 'gray.700')
}
}}
>