Refactor PlotHistoricalSession and TimePointSelector components for improved UI and functionality

- Removed the range display box from PlotHistoricalSession for a cleaner layout.
- Updated TimePointSelector to support fullscreen mode with a more compact design.
- Integrated a Popover for date and time selection in fullscreen mode.
- Enhanced the DataAvailabilityBar and slider functionality for better user experience.
- Added responsive styling adjustments for both normal and fullscreen modes.
- Updated system state with the latest last_update timestamp.
This commit is contained in:
Miguel 2025-08-27 15:34:01 +02:00
parent 61d61d27d1
commit 81e5ddec57
4 changed files with 2806 additions and 204 deletions

File diff suppressed because it is too large Load Diff

View File

@ -633,13 +633,6 @@ export default function PlotHistoricalSession({
dataSegments={dataSegments}
onTimeChange={handleTimePointChange}
/>
<Box mt={2} p={2} bg={infoBgColor} borderRadius="md" border="1px solid" borderColor={borderColor}>
<HStack justify="space-between" fontSize="sm" color={textColor}>
<Text><strong>Range:</strong> {timeRangeSeconds}s</Text>
<Text><strong>{t('timeSelector.from')}:</strong> {formatCentralTimeInfo().start}</Text>
<Text><strong>{t('timeSelector.to')}:</strong> {formatCentralTimeInfo().end}</Text>
</HStack>
</Box>
</Box>
)}
@ -913,6 +906,7 @@ export default function PlotHistoricalSession({
stepMinutes={1}
dataSegments={dataSegments}
onTimeChange={handleTimePointChange}
isFullscreen={true}
/>
</Box>
)}

View File

@ -1,7 +1,29 @@
import { useMemo, useState, useCallback, useRef, useEffect } from "react";
import { useTranslation } from 'react-i18next';
import { Box, Flex, Text, Slider, SliderTrack, SliderFilledTrack, SliderThumb, Button, IconButton, useColorModeValue, NumberInput, NumberInputField, NumberInputStepper, NumberIncrementStepper, NumberDecrementStepper } from "@chakra-ui/react";
import { CheckIcon } from "@chakra-ui/icons";
import {
Box,
Flex,
Text,
Slider,
SliderTrack,
SliderFilledTrack,
SliderThumb,
Button,
IconButton,
useColorModeValue,
NumberInput,
NumberInputField,
NumberInputStepper,
NumberIncrementStepper,
NumberDecrementStepper,
Popover,
PopoverTrigger,
PopoverContent,
PopoverBody,
PopoverArrow,
HStack
} from "@chakra-ui/react";
import { CheckIcon, CalendarIcon } from "@chakra-ui/icons";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import DataAvailabilityBar from "./DataAvailabilityBar.jsx";
@ -14,6 +36,7 @@ export default function TimePointSelector({
stepMinutes = 5,
dataSegments = [],
onTimeChange,
isFullscreen = false, // NEW: Enable ultra-compact mode for fullscreen
}) {
const { t } = useTranslation();
// Color mode values
@ -162,211 +185,417 @@ export default function TimePointSelector({
};
return (
<Box p={4} bg={bgColor} borderRadius="md" border="1px solid" borderColor={borderColor}>
<Flex gap={4} align="center" mb={3} wrap="wrap">
<Box>
<Text fontWeight="semibold" mb={1} color={textColor}>{t('timeSelector.selectDateTime')}</Text>
<Box p={isFullscreen ? 2 : 3} bg={bgColor} borderRadius="md" borderWidth="1px" borderColor={borderColor}>
{/* Compact header with date picker and controls */}
{!isFullscreen && (
<Flex gap={2} align="center" justify="space-between" mb={3}>
{/* Compact Date/Time Picker */}
<Popover placement="bottom-start">
<PopoverTrigger>
<Button
size="sm"
variant="outline"
leftIcon={<CalendarIcon />}
title="Select date and time"
>
📅 {value.toLocaleString('en-US', {
day: '2-digit',
month: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false
})}
</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverArrow />
<PopoverBody p={0}>
<Box
sx={{
'& .react-datepicker': {
fontFamily: 'inherit',
fontSize: '14px',
backgroundColor: useColorModeValue('white', 'gray.800'),
border: 'none',
borderRadius: '6px',
boxShadow: useColorModeValue('md', 'dark-lg'),
display: 'flex',
flexDirection: 'row' // Force horizontal layout
},
'& .react-datepicker__header': {
backgroundColor: useColorModeValue('gray.50', 'gray.700'),
borderBottom: '1px solid',
borderColor: borderColor,
height: '40px' // Fixed header height
},
'& .react-datepicker__current-month': {
color: textColor,
lineHeight: '40px' // Center text in header
},
'& .react-datepicker__day-names': {
height: '30px',
display: 'flex',
alignItems: 'center'
},
'& .react-datepicker__day-name': {
color: textColor,
height: '30px',
lineHeight: '30px'
},
'& .react-datepicker__week': {
height: '30px',
display: 'flex',
alignItems: 'center'
},
'& .react-datepicker__day': {
color: textColor,
height: '30px',
lineHeight: '30px',
'&:hover': {
backgroundColor: useColorModeValue('blue.50', 'blue.800')
}
},
'& .react-datepicker__day--selected': {
backgroundColor: 'blue.500',
color: 'white'
},
'& .react-datepicker__month': {
height: '210px' // 6 weeks × 30px + 30px for day names
},
// Time picker specific styles - match calendar height exactly
'& .react-datepicker__time-container': {
backgroundColor: useColorModeValue('white', 'gray.800'),
borderLeft: '1px solid',
borderColor: borderColor,
width: '100px',
flexShrink: 0,
height: '250px', // Match total calendar height (40px header + 210px month)
display: 'flex',
flexDirection: 'column'
},
'& .react-datepicker__time-box': {
backgroundColor: useColorModeValue('white', 'gray.800'),
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column'
},
'& .react-datepicker__time-name': {
color: textColor,
backgroundColor: useColorModeValue('gray.50', 'gray.700'),
textAlign: 'center',
padding: '8px',
height: '40px',
lineHeight: '24px',
borderBottom: '1px solid',
borderColor: borderColor,
flexShrink: 0
},
'& .react-datepicker__time-list': {
backgroundColor: useColorModeValue('white', 'gray.800'),
height: '210px', // Match month container height exactly
maxHeight: '210px',
overflowY: 'auto',
flex: 1,
'& .react-datepicker__time-list-item': {
color: textColor,
backgroundColor: useColorModeValue('white', 'gray.800'),
padding: '4px 8px',
fontSize: '13px',
lineHeight: '1.4',
height: '26px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
'&:hover': {
backgroundColor: useColorModeValue('gray.100', 'gray.700')
},
'&--selected': {
backgroundColor: 'blue.500',
color: 'white',
fontWeight: 'bold'
}
}
},
// Ensure calendar and time picker are side by side
'& .react-datepicker__month-container': {
float: 'none',
display: 'inline-block',
height: '250px' // Match time container height
}
}}
>
<DatePicker
selected={value}
onChange={onPick}
showTimeSelect
timeFormat="HH:mm"
timeIntervals={stepMinutes}
timeCaption="Time"
dateFormat="dd-MM-yyyy HH:mm"
minDate={minDate}
maxDate={maxDate}
minTime={new Date(new Date(value).setHours(0, 0, 0, 0))}
maxTime={new Date(new Date(value).setHours(23, 59, 59, 999))}
inline
/>
</Box>
</PopoverBody>
</PopoverContent>
</Popover>
{/* Apply button when there are pending changes */}
{hasPendingChanges && (
<Button
size="sm"
colorScheme="blue"
leftIcon={<CheckIcon />}
onClick={applyPendingChanges}
>
{t('timeSelector.applyChanges')}
</Button>
)}
</Flex>
)}
{/* Time navigation slider with data availability */}
<Box mb={isFullscreen ? 1 : 2}>
<Box position="relative">
{/* Data availability bar positioned above slider track */}
<Box
sx={{
'& .react-datepicker-wrapper': {
width: 'auto'
},
'& .react-datepicker__input-container input': {
padding: '8px 12px',
borderRadius: '6px',
border: '1px solid',
borderColor: borderColor,
backgroundColor: useColorModeValue('white', 'gray.800'),
color: textColor,
fontSize: '14px',
'&:focus': {
outline: 'none',
borderColor: 'blue.500',
boxShadow: '0 0 0 1px blue.500'
}
},
'& .react-datepicker': {
backgroundColor: useColorModeValue('white', 'gray.800'),
border: '1px solid',
borderColor: borderColor,
color: textColor
},
'& .react-datepicker__header': {
backgroundColor: useColorModeValue('gray.50', 'gray.700'),
borderBottom: '1px solid',
borderColor: borderColor
},
'& .react-datepicker__day': {
color: textColor,
'&:hover': {
backgroundColor: useColorModeValue('blue.50', 'blue.800')
}
},
'& .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')
}
}}
position="absolute"
top="-8px"
left="0"
right="0"
px="1"
zIndex={1}
>
<DatePicker
selected={value}
onChange={onPick}
showTimeSelect
timeFormat="HH:mm"
timeIntervals={stepMinutes}
timeCaption="Time"
dateFormat="dd-MM-yyyy HH:mm"
<DataAvailabilityBar
segments={dataSegments}
minDate={minDate}
maxDate={maxDate}
// Optional: force time range when navigating
minTime={new Date(new Date(value).setHours(0, 0, 0, 0))}
maxTime={new Date(new Date(value).setHours(23, 59, 59, 999))}
height="4px"
showTooltips={true}
/>
</Box>
<Slider
min={minMs}
max={maxMs}
step={stepMs}
value={sliderValue}
onChange={onSlide}
colorScheme="blue"
size={isFullscreen ? "sm" : "md"}
>
<SliderTrack bg={useColorModeValue('gray.200', 'gray.600')}>
<SliderFilledTrack bg="blue.500" />
</SliderTrack>
<SliderThumb bg="blue.500" />
</Slider>
</Box>
</Box>
<Box flex="1" minW="260px">
<Text color={textColor} mb={1}>{t('timeSelector.navigateSlider')}</Text>
{/* Slider with integrated data availability */}
<Box position="relative" mb={2}>
{/* Data availability bar positioned above slider track */}
<Box
position="absolute"
top="-8px"
left="0"
right="0"
px="1"
zIndex={1}
>
<DataAvailabilityBar
segments={dataSegments}
minDate={minDate}
maxDate={maxDate}
height="4px"
showTooltips={true}
/>
</Box>
<Slider
min={minMs}
max={maxMs}
step={stepMs}
value={sliderValue}
onChange={onSlide}
colorScheme="blue"
>
<SliderTrack bg={useColorModeValue('gray.200', 'gray.600')}>
<SliderFilledTrack bg="blue.500" />
</SliderTrack>
<SliderThumb bg="blue.500" />
</Slider>
</Box>
<Flex mt={2} justify="space-between" align="center">
<Text fontSize="sm" color={textColor}>
{new Date(sliderValue).toLocaleString('en-US', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false
})}
{/* Compact footer with editable range and status */}
<Flex justify="space-between" align="center" wrap="wrap" gap={2} fontSize={isFullscreen ? "xs" : "sm"}>
{/* Editable Range Display */}
<HStack spacing={1} align="center">
<Text color={useColorModeValue('gray.600', 'gray.400')}>
<strong>Range:</strong>
</Text>
<NumberInput
value={tempRangeSeconds}
onChange={(valueString, valueNumber) => onRangeChange(isNaN(valueNumber) ? 1000 : valueNumber)}
min={60}
max={86400}
size="xs"
width={isFullscreen ? "50px" : "60px"}
>
<NumberInputField
fontSize={isFullscreen ? "xs" : "sm"}
px={1}
py={isFullscreen ? "1px" : undefined}
bg={useColorModeValue('white', 'gray.700')}
borderColor={useColorModeValue('gray.300', 'gray.600')}
/>
</NumberInput>
<Text color={useColorModeValue('gray.600', 'gray.400')}>s</Text>
</HStack>
{/* Ultra-compact controls for fullscreen */}
{isFullscreen ? (
<HStack spacing={2} fontSize="xs">
{/* Minimal date picker button for fullscreen */}
<Popover placement="bottom">
<PopoverTrigger>
<IconButton
size="xs"
variant="outline"
icon={<CalendarIcon />}
title="Select date and time"
/>
</PopoverTrigger>
<PopoverContent>
<PopoverArrow />
<PopoverBody p={0}>
<Box
sx={{
'& .react-datepicker': {
fontFamily: 'inherit',
fontSize: '14px',
backgroundColor: useColorModeValue('white', 'gray.800'),
border: 'none',
borderRadius: '6px',
boxShadow: useColorModeValue('md', 'dark-lg'),
display: 'flex',
flexDirection: 'row' // Force horizontal layout
},
'& .react-datepicker__header': {
backgroundColor: useColorModeValue('gray.50', 'gray.700'),
borderBottom: '1px solid',
borderColor: borderColor,
height: '40px' // Fixed header height
},
'& .react-datepicker__current-month': {
color: textColor,
lineHeight: '40px' // Center text in header
},
'& .react-datepicker__day-names': {
height: '30px',
display: 'flex',
alignItems: 'center'
},
'& .react-datepicker__day-name': {
color: textColor,
height: '30px',
lineHeight: '30px'
},
'& .react-datepicker__week': {
height: '30px',
display: 'flex',
alignItems: 'center'
},
'& .react-datepicker__day': {
color: textColor,
height: '30px',
lineHeight: '30px',
'&:hover': {
backgroundColor: useColorModeValue('blue.50', 'blue.800')
}
},
'& .react-datepicker__day--selected': {
backgroundColor: 'blue.500',
color: 'white'
},
'& .react-datepicker__month': {
height: '210px' // 6 weeks × 30px + 30px for day names
},
// Time picker specific styles - match calendar height exactly
'& .react-datepicker__time-container': {
backgroundColor: useColorModeValue('white', 'gray.800'),
borderLeft: '1px solid',
borderColor: borderColor,
width: '100px',
flexShrink: 0,
height: '250px', // Match total calendar height (40px header + 210px month)
display: 'flex',
flexDirection: 'column'
},
'& .react-datepicker__time-box': {
backgroundColor: useColorModeValue('white', 'gray.800'),
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column'
},
'& .react-datepicker__time-name': {
color: textColor,
backgroundColor: useColorModeValue('gray.50', 'gray.700'),
textAlign: 'center',
padding: '8px',
height: '40px',
lineHeight: '24px',
borderBottom: '1px solid',
borderColor: borderColor,
flexShrink: 0
},
'& .react-datepicker__time-list': {
backgroundColor: useColorModeValue('white', 'gray.800'),
height: '210px', // Match month container height exactly
maxHeight: '210px',
overflowY: 'auto',
flex: 1,
'& .react-datepicker__time-list-item': {
color: textColor,
backgroundColor: useColorModeValue('white', 'gray.800'),
padding: '4px 8px',
fontSize: '13px',
lineHeight: '1.4',
height: '26px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
'&:hover': {
backgroundColor: useColorModeValue('gray.100', 'gray.700')
},
'&--selected': {
backgroundColor: 'blue.500',
color: 'white',
fontWeight: 'bold'
}
}
},
// Ensure calendar and time picker are side by side
'& .react-datepicker__month-container': {
float: 'none',
display: 'inline-block',
height: '250px' // Match time container height
}
}}
>
<DatePicker
selected={value}
onChange={onPick}
showTimeSelect
timeFormat="HH:mm"
timeIntervals={stepMinutes}
timeCaption="Time"
dateFormat="dd-MM-yyyy HH:mm"
minDate={minDate}
maxDate={maxDate}
minTime={new Date(new Date(value).setHours(0, 0, 0, 0))}
maxTime={new Date(new Date(value).setHours(23, 59, 59, 999))}
inline
/>
</Box>
</PopoverBody>
</PopoverContent>
</Popover>
{hasPendingChanges && (
<Button
size="xs"
colorScheme="blue"
leftIcon={<CheckIcon />}
onClick={applyPendingChanges}
>
Apply
</Button>
)}
</HStack>
) : (
/* Normal mode - Step info and pending changes status */
<HStack spacing={3} fontSize="xs">
<Text color={useColorModeValue('gray.500', 'gray.400')}>
Step: {stepMinutes} min
</Text>
{hasPendingChanges && (
<Text fontSize="xs" color="orange.500">
<Text color="orange.500" fontWeight="medium">
{t('timeSelector.pendingChanges')}
</Text>
)}
</Flex>
{/* Range and Apply Controls */}
<Flex mt={3} gap={3} align="center" wrap="wrap">
<Box>
<Text fontSize="sm" fontWeight="medium" color={textColor} mb={1}>
{t('timeSelector.timeRangeSeconds')}
</Text>
<NumberInput
value={tempRangeSeconds}
onChange={(valueString, valueNumber) => onRangeChange(isNaN(valueNumber) ? 1000 : valueNumber)}
min={60}
max={86400}
size="sm"
width="120px"
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</Box>
{hasPendingChanges && (
<Box>
<Text fontSize="sm" color="orange.500" mb={1}>
{t('timeSelector.pendingChanges')}
</Text>
<Button
size="sm"
colorScheme="blue"
leftIcon={<CheckIcon />}
onClick={applyPendingChanges}
>
{t('timeSelector.applyChanges')}
</Button>
</Box>
)}
</Flex>
</Box>
</HStack>
)}
</Flex>
<Text fontSize="sm" color={useColorModeValue('gray.500', 'gray.400')}>
Range: {minDate.toLocaleString('en-US', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false
})} {maxDate.toLocaleString('en-US', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false
})} | Step: {stepMinutes} min
</Text>
</Box>
);
}

View File

@ -7,6 +7,5 @@
]
},
"auto_recovery_enabled": true,
"last_update": "2025-08-27T12:48:41.182586",
"plotjuggler_path": "C:\\Program Files\\PlotJuggler\\plotjuggler.exe"
"last_update": "2025-08-27T15:19:56.923648"
}