| @@ -30,6 +30,7 @@ import DialogContentText from "@mui/material/DialogContentText"; | |||||
| import DialogActions from "@mui/material/DialogActions"; | import DialogActions from "@mui/material/DialogActions"; | ||||
| import TextField from "@mui/material/TextField"; | import TextField from "@mui/material/TextField"; | ||||
| import Autocomplete from "@mui/material/Autocomplete"; | import Autocomplete from "@mui/material/Autocomplete"; | ||||
| import InputAdornment from "@mui/material/InputAdornment"; | |||||
| type Props = { | type Props = { | ||||
| equipments: EquipmentResult[]; | equipments: EquipmentResult[]; | ||||
| @@ -71,6 +72,8 @@ const EquipmentSearch: React.FC<Props> = ({ equipments, tabIndex = 0 }) => { | |||||
| const [selectedDescription, setSelectedDescription] = useState<string>(""); | const [selectedDescription, setSelectedDescription] = useState<string>(""); | ||||
| const [selectedName, setSelectedName] = useState<string>(""); | const [selectedName, setSelectedName] = useState<string>(""); | ||||
| const [selectedEquipmentCode, setSelectedEquipmentCode] = useState<string>(""); | const [selectedEquipmentCode, setSelectedEquipmentCode] = useState<string>(""); | ||||
| const [equipmentCodePrefix, setEquipmentCodePrefix] = useState<string>(""); | |||||
| const [equipmentCodeNumber, setEquipmentCodeNumber] = useState<string>(""); | |||||
| const [isExistingCombination, setIsExistingCombination] = useState(false); | const [isExistingCombination, setIsExistingCombination] = useState(false); | ||||
| const [loadingEquipments, setLoadingEquipments] = useState(false); | const [loadingEquipments, setLoadingEquipments] = useState(false); | ||||
| const [saving, setSaving] = useState(false); | const [saving, setSaving] = useState(false); | ||||
| @@ -237,6 +240,8 @@ const EquipmentSearch: React.FC<Props> = ({ equipments, tabIndex = 0 }) => { | |||||
| setSelectedDescription(""); | setSelectedDescription(""); | ||||
| setSelectedName(""); | setSelectedName(""); | ||||
| setSelectedEquipmentCode(""); | setSelectedEquipmentCode(""); | ||||
| setEquipmentCodePrefix(""); | |||||
| setEquipmentCodeNumber(""); | |||||
| setIsExistingCombination(false); | setIsExistingCombination(false); | ||||
| }, []); | }, []); | ||||
| @@ -315,12 +320,76 @@ const EquipmentSearch: React.FC<Props> = ({ equipments, tabIndex = 0 }) => { | |||||
| } else { | } else { | ||||
| setIsExistingCombination(false); | setIsExistingCombination(false); | ||||
| setSelectedEquipmentCode(""); | setSelectedEquipmentCode(""); | ||||
| setEquipmentCodePrefix(""); | |||||
| setEquipmentCodeNumber(""); | |||||
| } | } | ||||
| }; | }; | ||||
| checkAndGenerateEquipmentCode(); | checkAndGenerateEquipmentCode(); | ||||
| }, [selectedDescription, selectedName, equipmentList]); | }, [selectedDescription, selectedName, equipmentList]); | ||||
| useEffect(() => { | |||||
| const generateNumberForPrefix = async () => { | |||||
| if (isExistingCombination || !equipmentCodePrefix) { | |||||
| return; | |||||
| } | |||||
| if (equipmentCodePrefix.length !== 3 || !/^[A-Z]{3}$/.test(equipmentCodePrefix)) { | |||||
| setEquipmentCodeNumber(""); | |||||
| setSelectedEquipmentCode(equipmentCodePrefix); | |||||
| return; | |||||
| } | |||||
| try { | |||||
| const response = await axiosInstance.get<{ | |||||
| records: EquipmentResult[]; | |||||
| total: number; | |||||
| }>(`${NEXT_PUBLIC_API_URL}/EquipmentDetail/getRecordByPage`, { | |||||
| params: { | |||||
| pageNum: 1, | |||||
| pageSize: 1000, | |||||
| }, | |||||
| }); | |||||
| let maxNumber = 0; | |||||
| let maxPaddingLength = 2; | |||||
| if (response.data.records && response.data.records.length > 0) { | |||||
| const matchingCodes = response.data.records | |||||
| .map((detail) => { | |||||
| if (!detail.equipmentCode) return null; | |||||
| const match = detail.equipmentCode.match(new RegExp(`^${equipmentCodePrefix}(\\d+)$`)); | |||||
| if (match) { | |||||
| const numberStr = match[1]; | |||||
| return { | |||||
| number: parseInt(numberStr, 10), | |||||
| paddingLength: numberStr.length | |||||
| }; | |||||
| } | |||||
| return null; | |||||
| }) | |||||
| .filter((item): item is { number: number; paddingLength: number } => item !== null); | |||||
| if (matchingCodes.length > 0) { | |||||
| maxNumber = Math.max(...matchingCodes.map(c => c.number)); | |||||
| maxPaddingLength = Math.max(...matchingCodes.map(c => c.paddingLength)); | |||||
| } | |||||
| } | |||||
| const nextNumber = maxNumber + 1; | |||||
| const numberStr = String(nextNumber).padStart(maxPaddingLength, '0'); | |||||
| setEquipmentCodeNumber(numberStr); | |||||
| setSelectedEquipmentCode(`${equipmentCodePrefix}${numberStr}`); | |||||
| } catch (error) { | |||||
| console.error("Error generating equipment code number:", error); | |||||
| setEquipmentCodeNumber(""); | |||||
| setSelectedEquipmentCode(equipmentCodePrefix); | |||||
| } | |||||
| }; | |||||
| generateNumberForPrefix(); | |||||
| }, [equipmentCodePrefix, isExistingCombination]); | |||||
| const handleToggleExpand = useCallback( | const handleToggleExpand = useCallback( | ||||
| (id: string | number, code: string) => { | (id: string | number, code: string) => { | ||||
| setExpandedRows(prev => { | setExpandedRows(prev => { | ||||
| @@ -546,9 +615,15 @@ const EquipmentSearch: React.FC<Props> = ({ equipments, tabIndex = 0 }) => { | |||||
| return; | return; | ||||
| } | } | ||||
| if (!isExistingCombination && !selectedEquipmentCode) { | |||||
| alert("請輸入設備編號"); | |||||
| return; | |||||
| if (!isExistingCombination) { | |||||
| if (equipmentCodePrefix.length !== 3 || !/^[A-Z]{3}$/.test(equipmentCodePrefix)) { | |||||
| alert("請輸入3個大寫英文字母作為設備編號前綴"); | |||||
| return; | |||||
| } | |||||
| if (!equipmentCodeNumber) { | |||||
| alert("設備編號生成中,請稍候"); | |||||
| return; | |||||
| } | |||||
| } | } | ||||
| setSaving(true); | setSaving(true); | ||||
| @@ -623,13 +698,17 @@ const EquipmentSearch: React.FC<Props> = ({ equipments, tabIndex = 0 }) => { | |||||
| newEquipmentCode = `LSS${String(1).padStart(2, '0')}`; | newEquipmentCode = `LSS${String(1).padStart(2, '0')}`; | ||||
| } | } | ||||
| } else { | } else { | ||||
| newEquipmentCode = selectedEquipmentCode; | |||||
| if (isExistingCombination) { | |||||
| newEquipmentCode = selectedEquipmentCode; | |||||
| } else { | |||||
| newEquipmentCode = `${equipmentCodePrefix}${equipmentCodeNumber}`; | |||||
| } | |||||
| } | } | ||||
| } else { | } else { | ||||
| if (isExistingCombination) { | if (isExistingCombination) { | ||||
| newEquipmentCode = `LSS${String(1).padStart(2, '0')}`; | newEquipmentCode = `LSS${String(1).padStart(2, '0')}`; | ||||
| } else { | } else { | ||||
| newEquipmentCode = selectedEquipmentCode; | |||||
| newEquipmentCode = `${equipmentCodePrefix}${equipmentCodeNumber}`; | |||||
| } | } | ||||
| } | } | ||||
| @@ -666,7 +745,7 @@ const EquipmentSearch: React.FC<Props> = ({ equipments, tabIndex = 0 }) => { | |||||
| } finally { | } finally { | ||||
| setSaving(false); | setSaving(false); | ||||
| } | } | ||||
| }, [selectedName, selectedDescription, selectedEquipmentCode, isExistingCombination, equipmentList, refetchData, filterObj, handleAddDialogClose, tabIndex, equipmentDetailsMap, fetchEquipmentDetailsByEquipmentId]); | |||||
| }, [selectedName, selectedDescription, selectedEquipmentCode, equipmentCodePrefix, equipmentCodeNumber, isExistingCombination, equipmentList, refetchData, filterObj, handleAddDialogClose, tabIndex, equipmentDetailsMap, fetchEquipmentDetailsByEquipmentId]); | |||||
| const renderExpandedRow = useCallback((item: EquipmentResult): React.ReactNode => { | const renderExpandedRow = useCallback((item: EquipmentResult): React.ReactNode => { | ||||
| if (tabIndex !== 0) { | if (tabIndex !== 0) { | ||||
| @@ -899,20 +978,45 @@ const EquipmentSearch: React.FC<Props> = ({ equipments, tabIndex = 0 }) => { | |||||
| /> | /> | ||||
| )} | )} | ||||
| /> | /> | ||||
| <TextField | |||||
| fullWidth | |||||
| label="設備編號" | |||||
| value={selectedEquipmentCode} | |||||
| onChange={(e) => { | |||||
| if (!isExistingCombination) { | |||||
| setSelectedEquipmentCode(e.target.value); | |||||
| } | |||||
| }} | |||||
| disabled={isExistingCombination || loadingEquipments || saving} | |||||
| placeholder={isExistingCombination ? "自動生成" : "輸入設備編號"} | |||||
| sx={{ mt: 2 }} | |||||
| required={!isExistingCombination} | |||||
| /> | |||||
| <Box sx={{ mt: 2 }}> | |||||
| <TextField | |||||
| fullWidth | |||||
| label="設備編號" | |||||
| value={isExistingCombination ? selectedEquipmentCode : equipmentCodePrefix} | |||||
| onChange={(e) => { | |||||
| if (!isExistingCombination) { | |||||
| const input = e.target.value.toUpperCase().replace(/[^A-Z]/g, '').slice(0, 3); | |||||
| setEquipmentCodePrefix(input); | |||||
| } | |||||
| }} | |||||
| disabled={isExistingCombination || loadingEquipments || saving} | |||||
| placeholder={isExistingCombination ? "自動生成" : "輸入3個大寫英文字母"} | |||||
| required={!isExistingCombination} | |||||
| InputProps={{ | |||||
| endAdornment: !isExistingCombination && equipmentCodeNumber ? ( | |||||
| <InputAdornment position="end"> | |||||
| <Typography | |||||
| sx={{ | |||||
| color: 'text.secondary', | |||||
| fontSize: '1rem', | |||||
| fontWeight: 500, | |||||
| minWidth: '30px', | |||||
| textAlign: 'right', | |||||
| }} | |||||
| > | |||||
| {equipmentCodeNumber} | |||||
| </Typography> | |||||
| </InputAdornment> | |||||
| ) : null, | |||||
| }} | |||||
| helperText={!isExistingCombination && equipmentCodePrefix.length > 0 && equipmentCodePrefix.length !== 3 | |||||
| ? "必須輸入3個大寫英文字母" | |||||
| : !isExistingCombination && equipmentCodePrefix.length === 3 && !/^[A-Z]{3}$/.test(equipmentCodePrefix) | |||||
| ? "必須是大寫英文字母" | |||||
| : ""} | |||||
| error={!isExistingCombination && equipmentCodePrefix.length > 0 && (equipmentCodePrefix.length !== 3 || !/^[A-Z]{3}$/.test(equipmentCodePrefix))} | |||||
| /> | |||||
| </Box> | |||||
| </Box> | </Box> | ||||
| </DialogContent> | </DialogContent> | ||||
| <DialogActions> | <DialogActions> | ||||