import { useState, useRef } from 'react' import PropTypes from 'prop-types' import { DatePicker, Input } from 'antd' import dayjs from 'dayjs' import FunctionIcon from '../../Icons/FunctionIcon' const OPERATOR_KEYS = ['+', '-'] const TIME_UNITS = { s: 'second', m: 'minute', h: 'hour', d: 'day', w: 'week', M: 'month', y: 'year' } const TERM_REGEX = /([+-])(\d+(?:\.\d+)?)([smhdwMy])/g const RELATIVE_TERM_START = /[+-]\d+(?:\.\d+)?[smhdwMy]/ const DATE_EXPR_FORMAT = 'YYYY-MM-DD HH:mm:ss' function formatExprBase(date) { if (date == null || !dayjs(date).isValid()) return '' return dayjs(date).format(DATE_EXPR_FORMAT) } function applyTimeExpr(fallbackBase, expr) { const trimmed = String(expr).trim() if (!trimmed) return null const termStart = trimmed.search(RELATIVE_TERM_START) if (termStart === -1) return null let base let termsPart if (termStart === 0) { base = fallbackBase && dayjs(fallbackBase).isValid() ? dayjs(fallbackBase) : dayjs() termsPart = trimmed } else { const baseStr = trimmed.slice(0, termStart).trim() const parsed = dayjs(baseStr) base = parsed.isValid() ? parsed : fallbackBase && dayjs(fallbackBase).isValid() ? dayjs(fallbackBase) : dayjs() termsPart = trimmed.slice(termStart) } const termsStr = termsPart.replace(/\s/g, '') const terms = [...termsStr.matchAll(TERM_REGEX)] if (!terms.length) return null const consumed = terms.map((t) => t[0]).join('') if (consumed !== termsStr) return null let result = base for (const [, sign, amount, unit] of terms) { const unitKey = TIME_UNITS[unit] if (!unitKey) return null const delta = sign === '+' ? parseFloat(amount) : -parseFloat(amount) result = result.add(delta, unitKey) } return result } const TimeEdit = ({ value, onChange, onBlur, disabled, ...rest }) => { const [isExprMode, setIsExprMode] = useState(false) const [exprValue, setExprValue] = useState('') const inputRef = useRef(null) const dayjsValue = value == null ? null : dayjs.isDayjs(value) ? value : dayjs(value) const pickerValue = dayjsValue && dayjsValue.isValid() ? dayjsValue : null const switchToExprMode = (initialValue) => { setIsExprMode(true) setExprValue(initialValue) setTimeout(() => { const input = inputRef.current?.getElementsByTagName('input')[0] input?.focus() }, 0) } const exitExprMode = (result) => { setIsExprMode(false) setExprValue('') if (result != null && result.isValid()) { onChange?.(result) } } const commitExpr = () => { const expr = exprValue.trim() if (expr && expr.match(/[+-]/) && expr.match(/[smhdwMy]/)) { const result = applyTimeExpr(pickerValue, expr) if (result != null) { exitExprMode(result) return } } exitExprMode(null) } const handleDatePickerKeyDown = (e) => { if (OPERATOR_KEYS.includes(e.key)) { e.preventDefault() e.stopPropagation() switchToExprMode(formatExprBase(pickerValue) + e.key) } } const handleInputKeyDown = (e) => { if (e.key === 'Enter' || e.key === '=') { e.preventDefault() const result = applyTimeExpr(pickerValue, exprValue) if (result != null) { exitExprMode(result) } } } const handleInputBlur = (e) => { commitExpr() onBlur?.(e) } if (isExprMode) { return (