Tom Butcher d02895b78d
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
Add Timeline View Support to ObjectTable and Enhance CSS Styles
- Introduced a new timeline view in the ObjectTable component, allowing users to visualize data over time.
- Updated view mode utilities to include timeline-specific settings for start and end dates, and color properties.
- Enhanced ObjectTableViewButton to manage timeline settings and integrate timeline icon.
- Added CSS styles for timeline components to improve layout and visual feedback.
- Refactored StateTag component to utilize a utility function for state representation, improving maintainability.
2026-09-03 22:25:46 +01:00

96 lines
2.8 KiB
JavaScript

import dayjs from 'dayjs'
import { getPropertyValue } from '../../../database/ObjectModels'
const toTimestamp = (value) => {
if (value == null || value === '') return null
const date = dayjs(value)
return date.isValid() ? date.valueOf() : null
}
export const getTimelineDates = (values = []) => {
const timestamps = []
for (const value of values) {
const timestamp = toTimestamp(value)
if (timestamp != null) timestamps.push(timestamp)
}
return timestamps
}
export const getTimelineRangeFromValues = (startValues = [], endValues = []) => {
const timestamps = [
...getTimelineDates(startValues),
...getTimelineDates(endValues)
]
if (timestamps.length === 0) return null
const minimum = Math.min(...timestamps)
const maximum = Math.max(...timestamps)
const start = dayjs(minimum).startOf('day').subtract(1, 'day')
const end = dayjs(maximum).startOf('day').add(2, 'day')
return {
start: start.valueOf(),
end: end.valueOf()
}
}
export const getTimelineRange = (records, startDate, endDate) => {
const startValues = []
const endValues = []
records.forEach((record) => {
if (record?.isSkeleton) return
if (startDate) startValues.push(getPropertyValue(record, startDate))
if (endDate) endValues.push(getPropertyValue(record, endDate))
})
return getTimelineRangeFromValues(startValues, endValues)
}
export const getTimelineTicks = (rangeStart, rangeEnd) => {
if (rangeStart == null || rangeEnd == null) return []
const start = dayjs(rangeStart).startOf('day')
const end = dayjs(rangeEnd).startOf('day')
const dayCount = Math.max(1, end.diff(start, 'day'))
const lastDay = end.subtract(1, 'day')
const includeYear = start.year() !== lastDay.year() || start.year() !== end.year()
const format = includeYear ? 'MMM D YYYY' : 'MMM D'
return Array.from({ length: dayCount + 1 }, (_, index) => {
const value = start.add(index, 'day')
return {
value: value.valueOf(),
label: value.format(format)
}
})
}
export const getTimelineItemPosition = (
startValue,
endValue,
rangeStart,
rangeEnd
) => {
if (startValue == null || startValue === '') return null
const start = dayjs(startValue)
const rangeStartMs = dayjs(rangeStart).valueOf()
const rangeEndMs = dayjs(rangeEnd).valueOf()
const duration = rangeEndMs - rangeStartMs
if (!start.isValid() || duration <= 0) return null
const startMs = Math.min(rangeEndMs, Math.max(rangeStartMs, start.valueOf()))
const end = endValue == null || endValue === '' ? null : dayjs(endValue)
const hasValidEnd = end?.isValid() && end.valueOf() > startMs
const endMs = hasValidEnd
? Math.min(rangeEndMs, Math.max(startMs, end.valueOf()))
: startMs
return {
left: ((startMs - rangeStartMs) / duration) * 100,
width: ((endMs - startMs) / duration) * 100,
isPoint: !hasValidEnd
}
}