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;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -186,6 +186,7 @@ const ObjectChildTable = ({
|
||||
{...property}
|
||||
longId={false}
|
||||
objectData={record}
|
||||
parentData={objectData}
|
||||
isEditing={isEditing}
|
||||
useFormItem={false}
|
||||
name={undefined}
|
||||
|
||||
@ -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>
|
||||
|
||||
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',
|
||||
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',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user