import { useMemo, useState } from 'react' import { Card, Segmented, Flex, Popover, DatePicker, Button, Space } from 'antd' import PropTypes from 'prop-types' import dayjs from 'dayjs' import { useThemeContext } from '../context/ThemeContext' import MissingPlaceholder from './MissingPlaceholder' import HistoryEChart from './HistoryEChart' import HistoryChartLegend from './HistoryChartLegend' import { useHistoryLegendOverlay } from '../hooks/useHistoryLegendOverlay' import CheckIcon from '../../Icons/CheckIcon' import { LoadingOutlined } from '@ant-design/icons' const HISTORY_SERIES_CONFIG = { filamentStock: { series: [ { key: 'currentWeight.net', label: 'Net Weight', color: 'processing', suffix: 'g' }, { key: 'currentWeight.gross', label: 'Gross Weight', color: 'warning', suffix: 'g' } ], formatValue: (value, suffix = '') => `${Number(value ?? 0).toFixed(2)}${suffix}` }, partStock: { series: [ { key: 'currentQuantity', label: 'Current Quantity', color: 'processing' } ], formatValue: (value) => Number(value ?? 0).toFixed(2) }, productStock: { series: [ { key: 'currentQuantity', label: 'Current Quantity', color: 'processing' } ], formatValue: (value) => Number(value ?? 0).toFixed(2) }, stockEvent: { series: [ { key: 'value', label: 'Value', color: 'processing', variance: true } ], formatValue: (value, suffix = '') => { const numericValue = Number(value ?? 0) const isPositive = numericValue > 0 const isNegative = numericValue < 0 return `${isPositive ? '+' : isNegative ? '-' : ''}${Math.abs(numericValue).toFixed(2)}${suffix}` } } } const getNestedValue = (obj, path) => { if (!obj || !path) return undefined const keys = path.split('.') let value = obj for (const key of keys) { if (value === null || value === undefined) return undefined value = value[key] } return value } const legendMeasureStyle = { position: 'absolute', visibility: 'hidden', pointerEvents: 'none', whiteSpace: 'nowrap', width: 'max-content', height: 0, overflow: 'hidden' } const toCssHeight = (value) => typeof value === 'number' ? `${value}px` : value const HistoryDisplay = ({ loading = false, history = [], objectType, chartType = 'bar', unit, startDate, endDate, styles, height = 400 }) => { const [timeRange, setTimeRange] = useState('4hrs') const [startCustomDate, setStartCustomDate] = useState(null) const [endCustomDate, setEndCustomDate] = useState(null) const { isDarkMode, getColors } = useThemeContext() const config = HISTORY_SERIES_CONFIG[objectType] const { defaultStartDate, defaultEndDate } = useMemo(() => { const now = new Date() if (startDate || endDate) { return { defaultStartDate: startDate || new Date(now.getTime() - 24 * 60 * 60 * 1000), defaultEndDate: endDate || now } } if (timeRange === 'custom' && startCustomDate && endCustomDate) { return { defaultStartDate: startCustomDate.toDate(), defaultEndDate: endCustomDate.toDate() } } const timeRangeMap = { '24hrs': 24 * 60, '12hrs': 12 * 60, '8hrs': 8 * 60, '4hrs': 4 * 60, '1hrs': 60, '30mins': 30, '15mins': 15, '5mins': 5 } const minutes = timeRangeMap[timeRange] || 60 return { defaultStartDate: new Date(now.getTime() - minutes * 60 * 1000), defaultEndDate: now } }, [startDate, endDate, timeRange, startCustomDate, endCustomDate]) const chartRows = useMemo(() => { if (!config || !Array.isArray(history)) { return [] } const start = new Date(defaultStartDate).getTime() const end = new Date(defaultEndDate).getTime() return history .filter((point) => { const timestamp = new Date(point.timestamp).getTime() return timestamp >= start && timestamp <= end }) .map((point) => { const row = { timestamp: point.timestamp, dateFormatted: dayjs(point.timestamp).format('DD/MM HH:mm') } config.series.forEach((seriesDef) => { row[seriesDef.label] = getNestedValue(point, seriesDef.key) ?? 0 }) return row }) .sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp)) }, [history, config, defaultStartDate, defaultEndDate]) const themeColors = getColors() const seriesLabels = useMemo(() => { if (!config) { return [] } const colors = { success: themeColors.colorSuccess, processing: themeColors.colorInfo, error: themeColors.colorError, warning: themeColors.colorWarning, default: '#8c8c8c', cyan: themeColors.colorCyan, pink: themeColors.colorPink, purple: themeColors.colorPurple, magenta: themeColors.colorMagenta, volcano: themeColors.colorVolcano } return config.series.map((seriesDef) => { const color = seriesDef.color ? colors[seriesDef.color] || seriesDef.color : colors.default return { label: seriesDef.label, color } }) }, [config, themeColors]) const { slotRef, measureRef, overlay: legendOverflow } = useHistoryLegendOverlay(seriesLabels) if (!objectType || !config) { return null } const formatTooltipValue = (value, seriesLabel) => { const seriesDef = config.series.find((item) => item.label === seriesLabel) const suffix = seriesDef?.suffix || unit || '' return config.formatValue(value, suffix) } const customTimeRangeContent = ( { if (dates) { setStartCustomDate(dates[0]) setEndCustomDate(dates[1]) } else { setStartCustomDate(null) setEndCustomDate(null) } }} value={[startCustomDate, endCustomDate]} /> ) return ( {!startDate && !endDate && ( )}
{!legendOverflow && ( )}
{!startDate && !endDate && ( <> {loading == true && } )}
{legendOverflow && (
)}
{chartRows.length > 0 && ( )} {loading == false && chartRows.length == 0 && ( )}
) } HistoryDisplay.propTypes = { history: PropTypes.arrayOf(PropTypes.object), objectType: PropTypes.oneOf([ 'filamentStock', 'partStock', 'productStock', 'stockEvent' ]).isRequired, chartType: PropTypes.oneOf(['line', 'bar']), unit: PropTypes.string, startDate: PropTypes.oneOfType([ PropTypes.string, PropTypes.instanceOf(Date) ]), loading: PropTypes.bool, endDate: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]), styles: PropTypes.object, height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]) } export default HistoryDisplay