Compare commits
4 Commits
b230302745
...
e85eda5bb0
| Author | SHA1 | Date | |
|---|---|---|---|
| e85eda5bb0 | |||
| 8f25a8491d | |||
| 092b9ff4e4 | |||
| bdb2327aac |
@ -625,19 +625,19 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
|||||||
background-color: #141414;
|
background-color: #141414;
|
||||||
border-inline-end: 1px solid rgba(253, 253, 253, 0.12);
|
border-inline-end: 1px solid rgba(253, 253, 253, 0.12);
|
||||||
border-bottom: 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 {
|
.sidebar-scroll.simplebar-scrolling .simplebar-track.simplebar-vertical {
|
||||||
width: 10px !important;
|
width: 12px !important;
|
||||||
right: -11px;
|
right: -13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-scroll
|
.sidebar-scroll
|
||||||
.simplebar-track.simplebar-vertical
|
.simplebar-track.simplebar-vertical
|
||||||
.simplebar-scrollbar:before {
|
.simplebar-scrollbar:before {
|
||||||
top: 3px;
|
top: 4px;
|
||||||
bottom: 3px;
|
bottom: 4px;
|
||||||
left: 3px;
|
left: 4px;
|
||||||
right: 3px;
|
right: 4px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -186,6 +186,7 @@ const ObjectChildTable = ({
|
|||||||
{...property}
|
{...property}
|
||||||
longId={false}
|
longId={false}
|
||||||
objectData={record}
|
objectData={record}
|
||||||
|
parentData={objectData}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
useFormItem={false}
|
useFormItem={false}
|
||||||
name={undefined}
|
name={undefined}
|
||||||
|
|||||||
@ -6,11 +6,11 @@ import {
|
|||||||
InputNumber,
|
InputNumber,
|
||||||
Form,
|
Form,
|
||||||
Select,
|
Select,
|
||||||
DatePicker,
|
|
||||||
Switch
|
Switch
|
||||||
} from 'antd'
|
} from 'antd'
|
||||||
import IdDisplay from './IdDisplay'
|
import IdDisplay from './IdDisplay'
|
||||||
import TimeDisplay from './TimeDisplay'
|
import TimeDisplay from './TimeDisplay'
|
||||||
|
import TimeEdit from './TimeEdit'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import EmailDisplay from './EmailDisplay'
|
import EmailDisplay from './EmailDisplay'
|
||||||
import UrlDisplay from './UrlDisplay'
|
import UrlDisplay from './UrlDisplay'
|
||||||
@ -51,6 +51,10 @@ import { round } from '../utils/Utils'
|
|||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
|
const timeEditFormItemProps = {
|
||||||
|
getValueProps: (v) => ({ value: v ? dayjs(v) : null })
|
||||||
|
}
|
||||||
|
|
||||||
const ObjectProperty = ({
|
const ObjectProperty = ({
|
||||||
type = 'text',
|
type = 'text',
|
||||||
prefix,
|
prefix,
|
||||||
@ -70,6 +74,7 @@ const ObjectProperty = ({
|
|||||||
masterFilter = {},
|
masterFilter = {},
|
||||||
language = '',
|
language = '',
|
||||||
objectData = null,
|
objectData = null,
|
||||||
|
parentData = null,
|
||||||
objectType = 'unknown',
|
objectType = 'unknown',
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
@ -96,50 +101,51 @@ const ObjectProperty = ({
|
|||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
if (value && typeof value == 'function' && objectData) {
|
if (value && typeof value == 'function' && objectData) {
|
||||||
value = value(objectData)
|
value = value(objectData, parentData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (max && typeof max == 'function' && objectData) {
|
if (max && typeof max == 'function' && objectData) {
|
||||||
max = max(objectData)
|
max = max(objectData, parentData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (min && typeof min == 'function' && objectData) {
|
if (min && typeof min == 'function' && objectData) {
|
||||||
min = min(objectData)
|
min = min(objectData, parentData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (objectType && typeof objectType == 'function' && objectData) {
|
if (objectType && typeof objectType == 'function' && objectData) {
|
||||||
objectType = objectType(objectData)
|
objectType = objectType(objectData, parentData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (disabled && typeof disabled == 'function' && objectData) {
|
if (disabled && typeof disabled == 'function' && objectData) {
|
||||||
disabled = disabled(objectData)
|
disabled = disabled(objectData, parentData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty && typeof empty == 'function' && objectData) {
|
if (empty && typeof empty == 'function' && objectData) {
|
||||||
empty = empty(objectData)
|
empty = empty(objectData, parentData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (difference && typeof difference == 'function' && objectData) {
|
if (difference && typeof difference == 'function' && objectData) {
|
||||||
difference = difference(objectData)
|
difference = difference(objectData, parentData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefix && typeof prefix == 'function' && objectData) {
|
if (prefix && typeof prefix == 'function' && objectData) {
|
||||||
prefix = prefix(objectData)
|
prefix = prefix(objectData, parentData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (suffix && typeof suffix == 'function' && objectData) {
|
if (suffix && typeof suffix == 'function' && objectData) {
|
||||||
suffix = suffix(objectData)
|
suffix = suffix(objectData, parentData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (masterFilter && typeof masterFilter == 'function' && objectData) {
|
if (masterFilter && typeof masterFilter == 'function' && objectData) {
|
||||||
masterFilter = masterFilter(objectData)
|
masterFilter = masterFilter(objectData, parentData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options && typeof options == 'function' && objectData) {
|
if (options && typeof options == 'function' && objectData) {
|
||||||
options = options(objectData)
|
options = options(objectData, parentData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (readOnly && typeof readOnly == 'function' && objectData) {
|
if (readOnly && typeof readOnly == 'function' && objectData) {
|
||||||
readOnly = readOnly(objectData)
|
readOnly = readOnly(objectData, parentData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@ -708,14 +714,7 @@ const ObjectProperty = ({
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case 'dateTime':
|
case 'dateTime':
|
||||||
return (
|
return <TimeEdit {...inputProps} />
|
||||||
<DatePicker
|
|
||||||
showTime
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
{...inputProps}
|
|
||||||
{...(useFormItem ? {} : { value: value ? dayjs(value) : null })}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'country':
|
case 'country':
|
||||||
return <CountrySelect {...inputProps} />
|
return <CountrySelect {...inputProps} />
|
||||||
case 'color':
|
case 'color':
|
||||||
@ -854,9 +853,7 @@ const ObjectProperty = ({
|
|||||||
{...(type === 'color'
|
{...(type === 'color'
|
||||||
? { valuePropName: 'value', getValueFromEvent: (v) => v }
|
? { valuePropName: 'value', getValueFromEvent: (v) => v }
|
||||||
: {})}
|
: {})}
|
||||||
{...(type === 'dateTime'
|
{...(type === 'dateTime' ? timeEditFormItemProps : {})}
|
||||||
? { getValueProps: (v) => ({ value: v ? dayjs(v) : null }) }
|
|
||||||
: {})}
|
|
||||||
>
|
>
|
||||||
{renderInput()}
|
{renderInput()}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
180
src/components/Dashboard/common/TimeEdit.jsx
Normal file
180
src/components/Dashboard/common/TimeEdit.jsx
Normal 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
|
||||||
@ -402,7 +402,13 @@ export const Invoice = {
|
|||||||
objectType: 'orderItem',
|
objectType: 'orderItem',
|
||||||
required: true,
|
required: true,
|
||||||
columnWidth: 300,
|
columnWidth: 300,
|
||||||
showHyperlink: true
|
showHyperlink: true,
|
||||||
|
masterFilter: (objectData, parentData) => {
|
||||||
|
return {
|
||||||
|
orderType: parentData?.orderType,
|
||||||
|
order: parentData?.order?._id
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'invoiceQuantity',
|
name: 'invoiceQuantity',
|
||||||
@ -516,7 +522,13 @@ export const Invoice = {
|
|||||||
objectType: 'shipment',
|
objectType: 'shipment',
|
||||||
required: true,
|
required: true,
|
||||||
columnWidth: 300,
|
columnWidth: 300,
|
||||||
showHyperlink: true
|
showHyperlink: true,
|
||||||
|
masterFilter: (objectData, parentData) => {
|
||||||
|
return {
|
||||||
|
orderType: parentData?.orderType,
|
||||||
|
order: parentData?.order?._id
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'invoiceAmount',
|
name: 'invoiceAmount',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user