| | |
| | | import React, { useState, useRef, useEffect, useMemo } from "react"; |
| | | import React, { useEffect, useMemo } from "react"; |
| | | import { |
| | | Edit, |
| | | SimpleForm, |
| | |
| | | DeleteButton, |
| | | } from 'react-admin'; |
| | | import { useWatch, useFormContext } from "react-hook-form"; |
| | | import { Stack, Grid, Box, Typography } from '@mui/material'; |
| | | import { Stack, Grid, Box, Typography, ToggleButton, ToggleButtonGroup } from '@mui/material'; |
| | | import { alpha } from '@mui/material/styles'; |
| | | import * as Common from '@/utils/common'; |
| | | import { EDIT_MODE } from '@/config/setting'; |
| | | import EditBaseAside from "../components/EditBaseAside"; |
| | |
| | | </Toolbar> |
| | | ) |
| | | } |
| | | |
| | | const DIR_RULE_ANGLES = [0, 90, 180, 270]; |
| | | |
| | | const normalizeDirRuleValue = (value) => { |
| | | let parsed = []; |
| | | |
| | | if (Array.isArray(value)) { |
| | | parsed = value; |
| | | } else if (typeof value === 'string' && value.trim()) { |
| | | try { |
| | | parsed = JSON.parse(value); |
| | | } catch (error) { |
| | | parsed = []; |
| | | } |
| | | } else if (value && typeof value === 'object') { |
| | | parsed = [value]; |
| | | } |
| | | |
| | | const angleMap = new Map(); |
| | | |
| | | parsed.forEach(item => { |
| | | const angle = typeof item?.angle === 'number' ? item.angle : Number(item?.angle); |
| | | if (!Number.isFinite(angle)) { |
| | | return; |
| | | } |
| | | const enabled = !( |
| | | item?.enabled === false || |
| | | item?.enabled === 'false' || |
| | | item?.enabled === 0 || |
| | | item?.enabled === '0' |
| | | ); |
| | | angleMap.set(angle, { angle, enabled }); |
| | | }); |
| | | |
| | | let disabledCaptured = false; |
| | | |
| | | return DIR_RULE_ANGLES.map(angle => { |
| | | const matched = angleMap.get(angle); |
| | | if (matched && matched.enabled === false) { |
| | | if (disabledCaptured) { |
| | | return { angle, enabled: true }; |
| | | } |
| | | disabledCaptured = true; |
| | | return { angle, enabled: false }; |
| | | } |
| | | |
| | | return { angle, enabled: true }; |
| | | }); |
| | | }; |
| | | |
| | | const DirectionRuleInput = () => { |
| | | const translate = useTranslate(); |
| | | const { register, setValue } = useFormContext(); |
| | | const dirRuleValue = useWatch({ name: 'dirRule' }); |
| | | |
| | | useEffect(() => { |
| | | register('dirRule'); |
| | | }, [register]); |
| | | |
| | | const rules = useMemo(() => normalizeDirRuleValue(dirRuleValue), [dirRuleValue]); |
| | | const normalizedRulesString = useMemo(() => JSON.stringify(rules), [rules]); |
| | | |
| | | useEffect(() => { |
| | | if (dirRuleValue !== normalizedRulesString) { |
| | | setValue('dirRule', normalizedRulesString, { shouldDirty: false, shouldTouch: false }); |
| | | } |
| | | }, [dirRuleValue, normalizedRulesString, setValue]); |
| | | |
| | | const disabledAngle = useMemo(() => { |
| | | const disabled = rules.find(rule => !rule.enabled); |
| | | return typeof disabled?.angle === 'number' ? disabled.angle : null; |
| | | }, [rules]); |
| | | |
| | | const handleToggle = (_, newDisabledAngle) => { |
| | | if (newDisabledAngle === null) { |
| | | const resetRules = rules.map(rule => ({ ...rule, enabled: true })); |
| | | setValue('dirRule', JSON.stringify(resetRules), { shouldDirty: true, shouldTouch: true }); |
| | | return; |
| | | } |
| | | |
| | | const parsedAngle = typeof newDisabledAngle === 'number' ? newDisabledAngle : Number(newDisabledAngle); |
| | | if (Number.isNaN(parsedAngle)) { |
| | | return; |
| | | } |
| | | |
| | | const nextRules = rules.map(rule => |
| | | rule.angle === parsedAngle ? { ...rule, enabled: false } : { ...rule, enabled: true } |
| | | ); |
| | | |
| | | setValue('dirRule', JSON.stringify(nextRules), { shouldDirty: true, shouldTouch: true }); |
| | | }; |
| | | |
| | | return ( |
| | | <Box> |
| | | <Typography variant="subtitle2" sx={{ fontWeight: 500, mb: 1 }}> |
| | | {translate('table.field.code.dirRule')} |
| | | </Typography> |
| | | <Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 1 }}> |
| | | {translate('page.code.dirRule.helper')} |
| | | </Typography> |
| | | <ToggleButtonGroup |
| | | fullWidth |
| | | exclusive |
| | | value={disabledAngle} |
| | | onChange={handleToggle} |
| | | color="primary" |
| | | > |
| | | {rules.map(rule => { |
| | | const isDisabled = !rule.enabled; |
| | | return ( |
| | | <ToggleButton |
| | | key={rule.angle} |
| | | value={rule.angle} |
| | | sx={{ |
| | | textTransform: 'none', |
| | | flex: 1, |
| | | flexDirection: 'column', |
| | | gap: 0.5, |
| | | py: 1.5, |
| | | '&.Mui-selected': { |
| | | color: (theme) => theme.palette.error.main, |
| | | borderColor: (theme) => theme.palette.error.main, |
| | | bgcolor: (theme) => alpha(theme.palette.error.main, 0.08), |
| | | '&:hover': { |
| | | bgcolor: (theme) => alpha(theme.palette.error.main, 0.16), |
| | | }, |
| | | }, |
| | | '& .dirRuleStatus': { |
| | | color: isDisabled ? 'error.main' : 'text.secondary', |
| | | }, |
| | | '& .dirRuleAngle': { |
| | | fontWeight: 600, |
| | | }, |
| | | '&.Mui-selected .dirRuleStatus': { |
| | | color: (theme) => theme.palette.error.main, |
| | | }, |
| | | }} |
| | | > |
| | | <Typography className="dirRuleAngle" variant="body2"> |
| | | {rule.angle}° |
| | | </Typography> |
| | | <Typography |
| | | variant="caption" |
| | | className="dirRuleStatus" |
| | | sx={{ fontWeight: 600, letterSpacing: 0.2, textTransform: 'uppercase' }} |
| | | > |
| | | {translate(isDisabled ? 'page.code.dirRule.status.disabled' : 'page.code.dirRule.status.enabled')} |
| | | </Typography> |
| | | </ToggleButton> |
| | | ); |
| | | })} |
| | | </ToggleButtonGroup> |
| | | </Box> |
| | | ); |
| | | }; |
| | | |
| | | const CodeEdit = () => { |
| | | const translate = useTranslate(); |
| | |
| | | /> |
| | | </Stack> */} |
| | | |
| | | <Box mt={2}> |
| | | <DirectionRuleInput /> |
| | | </Box> |
| | | |
| | | </Grid> |
| | | <Grid item xs={12} md={4}> |
| | | <Typography variant="h6" gutterBottom> |