Enhanced HistoryDisplay component with custom date range selection and improved loading states.

This commit is contained in:
Tom Butcher 2025-12-09 02:09:48 +00:00
parent 2d2aff125c
commit 3d17a08a71

View File

@ -1,6 +1,5 @@
import { useEffect, useState, useContext, useMemo } from 'react' import { useEffect, useState, useContext, useMemo } from 'react'
import { Card, Spin, Segmented, Flex } from 'antd' import { Card, Segmented, Flex, Popover, DatePicker, Button, Space } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import { Column } from '@ant-design/charts' import { Column } from '@ant-design/charts'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { getModelByName } from '../../../database/ObjectModels' import { getModelByName } from '../../../database/ObjectModels'
@ -8,14 +7,25 @@ import { ApiServerContext } from '../context/ApiServerContext'
import { AuthContext } from '../context/AuthContext' import { AuthContext } from '../context/AuthContext'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useThemeContext } from '../context/ThemeContext' import { useThemeContext } from '../context/ThemeContext'
import LoadingPlaceholder from './LoadingPlaceholder'
import MissingPlaceholder from './MissingPlaceholder'
import CheckIcon from '../../Icons/CheckIcon'
const HistoryDisplay = ({ objectType, startDate, endDate, styles }) => { const HistoryDisplay = ({
objectType,
startDate,
endDate,
styles,
height = 400
}) => {
const { getModelHistory, connected } = useContext(ApiServerContext) const { getModelHistory, connected } = useContext(ApiServerContext)
const { token } = useContext(AuthContext) const { token } = useContext(AuthContext)
const [historyData, setHistoryData] = useState([]) const [historyData, setHistoryData] = useState([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [error, setError] = useState(null) const [error, setError] = useState(null)
const [timeRange, setTimeRange] = useState('4hrs') const [timeRange, setTimeRange] = useState('4hrs')
const [startCustomDate, setStartCustomDate] = useState(null)
const [endCustomDate, setEndCustomDate] = useState(null)
const { isDarkMode, getColors } = useThemeContext() const { isDarkMode, getColors } = useThemeContext()
// Calculate dates based on selected time range or provided props // Calculate dates based on selected time range or provided props
const { defaultStartDate, defaultEndDate } = useMemo(() => { const { defaultStartDate, defaultEndDate } = useMemo(() => {
@ -30,6 +40,14 @@ const HistoryDisplay = ({ objectType, startDate, endDate, styles }) => {
} }
} }
// Handle custom date range
if (timeRange === 'custom' && startCustomDate && endCustomDate) {
return {
defaultStartDate: startCustomDate.toDate(),
defaultEndDate: endCustomDate.toDate()
}
}
// Otherwise, calculate based on selected time range // Otherwise, calculate based on selected time range
const hoursMap = { const hoursMap = {
'8hrs': 8, '8hrs': 8,
@ -43,7 +61,7 @@ const HistoryDisplay = ({ objectType, startDate, endDate, styles }) => {
defaultStartDate: new Date(now.getTime() - hours * 60 * 60 * 1000), defaultStartDate: new Date(now.getTime() - hours * 60 * 60 * 1000),
defaultEndDate: now defaultEndDate: now
} }
}, [startDate, endDate, timeRange]) }, [startDate, endDate, timeRange, startCustomDate, endCustomDate])
useEffect(() => { useEffect(() => {
if (!objectType || !getModelHistory || !token || !connected) { if (!objectType || !getModelHistory || !token || !connected) {
@ -251,6 +269,7 @@ const HistoryDisplay = ({ objectType, startDate, endDate, styles }) => {
const config = { const config = {
data: chartData, data: chartData,
height,
xField: 'dateFormatted', xField: 'dateFormatted',
yField: 'value', yField: 'value',
theme: { theme: {
@ -282,14 +301,58 @@ const HistoryDisplay = ({ objectType, startDate, endDate, styles }) => {
} }
} }
const customTimeRangeContent = (
<Space.Compact>
<DatePicker.RangePicker
onChange={(dates) => {
if (dates) {
setStartCustomDate(dates[0])
setEndCustomDate(dates[1])
} else {
setStartCustomDate(null)
setEndCustomDate(null)
}
}}
value={[startCustomDate, endCustomDate]}
/>
<Button
type='primary'
onClick={() => {
if (startCustomDate && endCustomDate) {
setTimeRange('custom')
}
}}
disabled={!startCustomDate || !endCustomDate}
icon={<CheckIcon />}
></Button>
</Space.Compact>
)
return ( return (
<Spin spinning={loading} indicator={<LoadingOutlined spin />}>
<Card <Card
style={{ width: '100%' }} style={{ width: '100%' }}
styles={{ body: { padding: '12px', ...styles } }} styles={{ body: { padding: '12px', ...styles } }}
> >
{!startDate && !endDate && ( {!startDate && !endDate && (
<Flex justify='flex-end'> <Flex justify='space-between'>
<Flex align='center' gap='5px'>
<Popover
content={customTimeRangeContent}
trigger='hover'
arrow={false}
placement='bottomLeft'
styles={{ body: { borderRadius: '22.5px' } }}
>
<Segmented
size='small'
options={[{ label: 'Custom', value: 'custom' }]}
value={timeRange}
onChange={setTimeRange}
disabled={loading}
/>
</Popover>
</Flex>
<Segmented <Segmented
size='small' size='small'
options={[ options={[
@ -301,12 +364,22 @@ const HistoryDisplay = ({ objectType, startDate, endDate, styles }) => {
]} ]}
value={timeRange} value={timeRange}
onChange={setTimeRange} onChange={setTimeRange}
disabled={loading}
/> />
</Flex> </Flex>
)} )}
<Column {...config} /> {loading == true && (
<Flex justify='center' align='center' style={{ height: `${height}px` }}>
<LoadingPlaceholder message='Loading history data...' />
</Flex>
)}
{chartData.length > 0 && <Column {...config} />}
{loading == false && chartData.length == 0 && (
<Flex justify='center' align='center' style={{ height: `${height}px` }}>
<MissingPlaceholder message='No data available.' />
</Flex>
)}
</Card> </Card>
</Spin>
) )
} }
@ -317,7 +390,8 @@ HistoryDisplay.propTypes = {
PropTypes.instanceOf(Date) PropTypes.instanceOf(Date)
]), ]),
styles: PropTypes.object, styles: PropTypes.object,
endDate: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]) endDate: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]),
height: PropTypes.string
} }
export default HistoryDisplay export default HistoryDisplay