Compare commits

...

4 Commits

5 changed files with 223 additions and 33 deletions

View File

@ -625,19 +625,19 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
background-color: #141414;
border-inline-end: 1px solid rgba(253, 253, 253, 0.12);
border-bottom: 1px solid rgba(253, 253, 253, 0.12);
border-radius: 0 0 5px 0;
border-radius: 0 0 6.5px 0;
}
.sidebar-scroll.simplebar-scrolling .simplebar-track.simplebar-vertical {
width: 10px !important;
right: -11px;
width: 12px !important;
right: -13px;
}
.sidebar-scroll
.simplebar-track.simplebar-vertical
.simplebar-scrollbar:before {
top: 3px;
bottom: 3px;
left: 3px;
right: 3px;
top: 4px;
bottom: 4px;
left: 4px;
right: 4px;
}

View File

@ -186,6 +186,7 @@ const ObjectChildTable = ({
{...property}
longId={false}
objectData={record}
parentData={objectData}
isEditing={isEditing}
useFormItem={false}
name={undefined}

View File

@ -6,11 +6,11 @@ import {
InputNumber,
Form,
Select,
DatePicker,
Switch
} from 'antd'
import IdDisplay from './IdDisplay'
import TimeDisplay from './TimeDisplay'
import TimeEdit from './TimeEdit'
import dayjs from 'dayjs'
import EmailDisplay from './EmailDisplay'
import UrlDisplay from './UrlDisplay'
@ -51,6 +51,10 @@ import { round } from '../utils/Utils'
const { Text } = Typography
const timeEditFormItemProps = {
getValueProps: (v) => ({ value: v ? dayjs(v) : null })
}
const ObjectProperty = ({
type = 'text',
prefix,
@ -70,6 +74,7 @@ const ObjectProperty = ({
masterFilter = {},
language = '',
objectData = null,
parentData = null,
objectType = 'unknown',
readOnly = false,
disabled = false,
@ -96,50 +101,51 @@ const ObjectProperty = ({
...rest
}) => {
if (value && typeof value == 'function' && objectData) {
value = value(objectData)
value = value(objectData, parentData)
}
if (max && typeof max == 'function' && objectData) {
max = max(objectData)
max = max(objectData, parentData)
}
if (min && typeof min == 'function' && objectData) {
min = min(objectData)
min = min(objectData, parentData)
}
if (objectType && typeof objectType == 'function' && objectData) {
objectType = objectType(objectData)
objectType = objectType(objectData, parentData)
}
if (disabled && typeof disabled == 'function' && objectData) {
disabled = disabled(objectData)
disabled = disabled(objectData, parentData)
}
if (empty && typeof empty == 'function' && objectData) {
empty = empty(objectData)
empty = empty(objectData, parentData)
}
if (difference && typeof difference == 'function' && objectData) {
difference = difference(objectData)
difference = difference(objectData, parentData)
}
if (prefix && typeof prefix == 'function' && objectData) {
prefix = prefix(objectData)
prefix = prefix(objectData, parentData)
}
if (suffix && typeof suffix == 'function' && objectData) {
suffix = suffix(objectData)
suffix = suffix(objectData, parentData)
}
if (masterFilter && typeof masterFilter == 'function' && objectData) {
masterFilter = masterFilter(objectData)
masterFilter = masterFilter(objectData, parentData)
}
if (options && typeof options == 'function' && objectData) {
options = options(objectData)
options = options(objectData, parentData)
}
if (readOnly && typeof readOnly == 'function' && objectData) {
readOnly = readOnly(objectData)
readOnly = readOnly(objectData, parentData)
}
if (!value) {
@ -708,14 +714,7 @@ const ObjectProperty = ({
/>
)
case 'dateTime':
return (
<DatePicker
showTime
style={{ width: '100%' }}
{...inputProps}
{...(useFormItem ? {} : { value: value ? dayjs(value) : null })}
/>
)
return <TimeEdit {...inputProps} />
case 'country':
return <CountrySelect {...inputProps} />
case 'color':
@ -854,9 +853,7 @@ const ObjectProperty = ({
{...(type === 'color'
? { valuePropName: 'value', getValueFromEvent: (v) => v }
: {})}
{...(type === 'dateTime'
? { getValueProps: (v) => ({ value: v ? dayjs(v) : null }) }
: {})}
{...(type === 'dateTime' ? timeEditFormItemProps : {})}
>
{renderInput()}
</Form.Item>

View File

@ -0,0 +1,180 @@
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 (
<div className='input-number-cal' ref={inputRef}>
<Input
value={exprValue}
onChange={(e) => setExprValue(e.target.value)}
onKeyDown={handleInputKeyDown}
onBlur={handleInputBlur}
disabled={disabled}
placeholder='+1d, -2m, +1h'
style={{ width: '100%' }}
/>
<div className='input-number-cal-icon'>
<FunctionIcon style={{ fontSize: 24 }} />
</div>
</div>
)
}
return (
<DatePicker
showTime
style={{ width: '100%' }}
value={pickerValue}
onChange={onChange}
onKeyDown={handleDatePickerKeyDown}
disabled={disabled}
{...rest}
/>
)
}
TimeEdit.propTypes = {
value: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
onChange: PropTypes.func,
onBlur: PropTypes.func,
disabled: PropTypes.bool
}
export default TimeEdit

View File

@ -402,7 +402,13 @@ export const Invoice = {
objectType: 'orderItem',
required: true,
columnWidth: 300,
showHyperlink: true
showHyperlink: true,
masterFilter: (objectData, parentData) => {
return {
orderType: parentData?.orderType,
order: parentData?.order?._id
}
}
},
{
name: 'invoiceQuantity',
@ -516,7 +522,13 @@ export const Invoice = {
objectType: 'shipment',
required: true,
columnWidth: 300,
showHyperlink: true
showHyperlink: true,
masterFilter: (objectData, parentData) => {
return {
orderType: parentData?.orderType,
order: parentData?.order?._id
}
}
},
{
name: 'invoiceAmount',