New number calculator functionality.
Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit
Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit
This commit is contained in:
parent
8150372fda
commit
e4255443a0
8
assets/icons/functionicon.svg
Normal file
8
assets/icons/functionicon.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(0.624686,0,0,0.624686,3,17.237995)">
|
||||
<path d="M45.17,47.212C46.58,47.212 47.578,46.42 47.578,45.039C47.578,44.432 47.455,44.033 47.022,43.038C43.627,36.904 41.807,30.56 41.807,23.653C41.807,16.937 43.541,10.39 47.022,4.224C47.455,3.24 47.578,2.84 47.578,2.233C47.578,0.895 46.58,0.05 45.17,0.05C43.622,0.05 42.412,0.807 41.094,2.698C36.763,8.399 34.637,15.598 34.637,23.632C34.637,31.696 36.75,38.726 41.094,44.574C42.402,46.475 43.622,47.212 45.17,47.212ZM53.441,39.915C54.789,39.915 55.627,39.472 56.736,37.85L63.381,28.189L63.526,28.189L70.367,38.036C71.337,39.448 72.177,39.915 73.434,39.915C75.47,39.915 76.876,38.574 76.876,36.677C76.876,35.855 76.63,35.133 76.075,34.383L68.175,23.647L76.006,13.227C76.629,12.367 76.927,11.646 76.927,10.722C76.927,8.851 75.474,7.596 73.587,7.596C72.104,7.596 71.3,8.297 70.379,9.72L64.033,19.158L63.877,19.158L57.501,9.689C56.548,8.256 55.66,7.596 54.075,7.596C52.07,7.596 50.568,9.08 50.568,10.866C50.568,11.932 50.899,12.683 51.435,13.403L59.08,23.63L51.105,34.459C50.429,35.36 50.226,36.071 50.226,36.964C50.226,38.669 51.625,39.915 53.441,39.915ZM82.304,47.212C83.862,47.212 85.062,46.465 86.38,44.574C90.754,38.736 92.847,31.696 92.847,23.632C92.847,15.598 90.682,8.419 86.38,2.698C85.072,0.797 83.862,0.05 82.304,0.05C80.894,0.05 79.896,0.895 79.896,2.233C79.896,2.84 80.029,3.24 80.452,4.224C83.933,10.39 85.667,16.937 85.667,23.653C85.667,30.56 83.848,36.904 80.452,43.038C80.029,44.033 79.896,44.432 79.896,45.039C79.896,46.377 80.894,47.212 82.304,47.212Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M5.252,47.17C12.053,47.17 15.203,44.455 16.783,37.027L20.127,21.046L25.664,21.046C27.685,21.046 29.099,19.888 29.099,17.909C29.099,16.108 27.887,15.025 26.141,15.025L21.395,15.025L22.161,11.302C22.935,7.691 24.066,6.295 27.165,6.295C27.697,6.295 28.187,6.263 28.56,6.222C30.37,5.986 31.317,5.049 31.317,3.437C31.317,1.14 29.52,0.071 25.884,0.071C19.19,0.071 15.859,3.04 14.354,10.214L13.352,15.025L9.606,15.025C7.544,15.025 6.149,16.193 6.149,18.163C6.149,19.974 7.342,21.046 9.139,21.046L12.084,21.046L8.945,35.949C8.159,39.645 7.007,40.946 3.961,40.946C3.534,40.946 3.096,40.987 2.775,41.019C0.93,41.266 0,42.325 0,43.856C0,46.1 1.765,47.17 5.252,47.17Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@ -417,3 +417,32 @@ body {
|
||||
.ant-badge .ant-badge-count-sm {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.input-number-cal {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.input-number-cal .ant-input-suffix {
|
||||
margin-right: 28px;
|
||||
}
|
||||
|
||||
.input-number-cal .ant-input-outlined {
|
||||
border-color: var(--color-purple);
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-purple) 31%, transparent);
|
||||
}
|
||||
|
||||
.input-number-cal .ant-input {
|
||||
font-family: 'DM Mono';
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.input-number-cal-icon {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0;
|
||||
bottom: 1px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
167
src/components/Dashboard/common/InputNumberCal.jsx
Normal file
167
src/components/Dashboard/common/InputNumberCal.jsx
Normal file
@ -0,0 +1,167 @@
|
||||
import { useState, useRef } from 'react'
|
||||
import { Input, InputNumber } from 'antd'
|
||||
import PropTypes from 'prop-types'
|
||||
import FunctionIcon from '../../Icons/FunctionIcon'
|
||||
|
||||
const OPERATOR_KEYS = ['+', '-', '*', '/']
|
||||
|
||||
/**
|
||||
* Safely evaluate a math expression. Only allows numbers and +, -, *, /
|
||||
*/
|
||||
function safeEval(expr) {
|
||||
const sanitized = String(expr)
|
||||
.replace(/\s/g, '')
|
||||
.replace(/[^0-9+\-*/().]/g, '')
|
||||
if (!sanitized) return null
|
||||
try {
|
||||
const fn = new Function(`return (${sanitized})`)
|
||||
const result = fn()
|
||||
return typeof result === 'number' && Number.isFinite(result) ? result : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const InputNumberCal = ({
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
min,
|
||||
max,
|
||||
prefix,
|
||||
suffix,
|
||||
placeholder,
|
||||
disabled,
|
||||
...rest
|
||||
}) => {
|
||||
const [isExprMode, setIsExprMode] = useState(false)
|
||||
const [exprValue, setExprValue] = useState('')
|
||||
const inputRef = useRef(null)
|
||||
|
||||
const switchToExprMode = (initialValue) => {
|
||||
setIsExprMode(true)
|
||||
setExprValue(initialValue)
|
||||
setTimeout(() => {
|
||||
//inputRef.current?.focus()
|
||||
const input = inputRef.current.getElementsByTagName('input')[0]
|
||||
if (input) {
|
||||
input.focus()
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
|
||||
const exitExprMode = (result) => {
|
||||
setIsExprMode(false)
|
||||
setExprValue('')
|
||||
if (result != null) {
|
||||
const clamped =
|
||||
min != null && result < min
|
||||
? min
|
||||
: max != null && result > max
|
||||
? max
|
||||
: result
|
||||
onChange?.(clamped)
|
||||
}
|
||||
}
|
||||
|
||||
const handleNumberKeyDown = (e) => {
|
||||
if (OPERATOR_KEYS.includes(e.key)) {
|
||||
e.preventDefault()
|
||||
const current = value ?? ''
|
||||
switchToExprMode(String(current) + e.key)
|
||||
}
|
||||
}
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
const next = e.target.value
|
||||
setExprValue(next)
|
||||
const num = parseFloat(next)
|
||||
if (!next) {
|
||||
onChange?.(null)
|
||||
} else if (!next.match(/[+\-*/=]/) && !Number.isNaN(num)) {
|
||||
onChange?.(num)
|
||||
}
|
||||
}
|
||||
|
||||
const handleInputKeyDown = (e) => {
|
||||
if (e.key === '=') {
|
||||
e.preventDefault()
|
||||
const result = safeEval(exprValue)
|
||||
if (result != null) {
|
||||
exitExprMode(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleInputBlur = (e) => {
|
||||
const expr = exprValue.trim()
|
||||
if (expr && expr.match(/[+\-*/]/)) {
|
||||
const result = safeEval(expr)
|
||||
if (result != null) {
|
||||
exitExprMode(result)
|
||||
} else {
|
||||
exitExprMode(null)
|
||||
}
|
||||
} else {
|
||||
const num = parseFloat(expr)
|
||||
if (!Number.isNaN(num)) {
|
||||
exitExprMode(num)
|
||||
} else {
|
||||
exitExprMode(null)
|
||||
}
|
||||
}
|
||||
onBlur?.(e)
|
||||
}
|
||||
|
||||
const commonProps = {
|
||||
prefix,
|
||||
suffix,
|
||||
placeholder,
|
||||
disabled,
|
||||
min,
|
||||
max,
|
||||
...rest
|
||||
}
|
||||
|
||||
if (isExprMode) {
|
||||
return (
|
||||
<div className='input-number-cal' ref={inputRef}>
|
||||
<Input
|
||||
value={exprValue}
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
onBlur={handleInputBlur}
|
||||
{...commonProps}
|
||||
/>
|
||||
<div className='input-number-cal-icon'>
|
||||
<FunctionIcon style={{ fontSize: 24 }} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<InputNumber
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
onKeyDown={handleNumberKeyDown}
|
||||
{...commonProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
InputNumberCal.propTypes = {
|
||||
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
onChange: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
min: PropTypes.number,
|
||||
max: PropTypes.number,
|
||||
step: PropTypes.number,
|
||||
prefix: PropTypes.node,
|
||||
suffix: PropTypes.node,
|
||||
placeholder: PropTypes.string,
|
||||
disabled: PropTypes.bool
|
||||
}
|
||||
|
||||
export default InputNumberCal
|
||||
@ -27,6 +27,7 @@ import { getPropertyValue } from '../../../database/ObjectModels'
|
||||
import PropertyChanges from './PropertyChanges'
|
||||
import NetGrossDisplay from './NetGrossDisplay'
|
||||
import NetGrossInput from './NetGrossInput'
|
||||
import InputNumberCal from './InputNumberCal'
|
||||
import ObjectList from './ObjectList'
|
||||
import VarianceDisplay from './VarianceDisplay'
|
||||
import OperationDisplay from './OperationDisplay'
|
||||
@ -713,7 +714,7 @@ const ObjectProperty = ({
|
||||
)
|
||||
case 'number':
|
||||
return (
|
||||
<InputNumber
|
||||
<InputNumberCal
|
||||
placeholder={label}
|
||||
prefix={prefix}
|
||||
suffix={suffix}
|
||||
|
||||
@ -13,7 +13,7 @@ const COLORS = {
|
||||
colorLink: '#5AC8F5',
|
||||
colorCyan: '#5AC8F5',
|
||||
colorPink: '#FF69B4',
|
||||
colorPurple: '#800080',
|
||||
colorPurple: '#6B5DFF',
|
||||
colorMagenta: '#FF00FF',
|
||||
colorVolcano: '#FF4500'
|
||||
}
|
||||
@ -114,7 +114,10 @@ export const ThemeProvider = ({ children }) => {
|
||||
root.style.setProperty('--color-purple', COLORS.colorPurple)
|
||||
root.style.setProperty('--color-magenta', COLORS.colorMagenta)
|
||||
root.style.setProperty('--color-volcano', COLORS.colorVolcano)
|
||||
root.style.setProperty('--layout-header-bg', isDarkMode ? '#141414' : '#ffffff')
|
||||
root.style.setProperty(
|
||||
'--layout-header-bg',
|
||||
isDarkMode ? '#141414' : '#ffffff'
|
||||
)
|
||||
}, [isDarkMode])
|
||||
|
||||
const themeConfig = {
|
||||
|
||||
6
src/components/Icons/FunctionIcon.jsx
Normal file
6
src/components/Icons/FunctionIcon.jsx
Normal file
@ -0,0 +1,6 @@
|
||||
import Icon from '@ant-design/icons'
|
||||
import CustomIconSvg from '../../../assets/icons/functionicon.svg?react'
|
||||
|
||||
const FunctionIcon = (props) => <Icon component={CustomIconSvg} {...props} />
|
||||
|
||||
export default FunctionIcon
|
||||
Loading…
x
Reference in New Issue
Block a user