| @@ -30,6 +30,7 @@ import DialogContentText from "@mui/material/DialogContentText"; | |||
| import DialogActions from "@mui/material/DialogActions"; | |||
| import TextField from "@mui/material/TextField"; | |||
| import Autocomplete from "@mui/material/Autocomplete"; | |||
| import InputAdornment from "@mui/material/InputAdornment"; | |||
| type Props = { | |||
| equipments: EquipmentResult[]; | |||
| @@ -71,6 +72,8 @@ const EquipmentSearch: React.FC<Props> = ({ equipments, tabIndex = 0 }) => { | |||
| const [selectedDescription, setSelectedDescription] = useState<string>(""); | |||
| const [selectedName, setSelectedName] = useState<string>(""); | |||
| const [selectedEquipmentCode, setSelectedEquipmentCode] = useState<string>(""); | |||
| const [equipmentCodePrefix, setEquipmentCodePrefix] = useState<string>(""); | |||
| const [equipmentCodeNumber, setEquipmentCodeNumber] = useState<string>(""); | |||
| const [isExistingCombination, setIsExistingCombination] = useState(false); | |||
| const [loadingEquipments, setLoadingEquipments] = useState(false); | |||
| const [saving, setSaving] = useState(false); | |||
| @@ -237,6 +240,8 @@ const EquipmentSearch: React.FC<Props> = ({ equipments, tabIndex = 0 }) => { | |||
| setSelectedDescription(""); | |||
| setSelectedName(""); | |||
| setSelectedEquipmentCode(""); | |||
| setEquipmentCodePrefix(""); | |||
| setEquipmentCodeNumber(""); | |||
| setIsExistingCombination(false); | |||
| }, []); | |||
| @@ -315,12 +320,76 @@ const EquipmentSearch: React.FC<Props> = ({ equipments, tabIndex = 0 }) => { | |||
| } else { | |||
| setIsExistingCombination(false); | |||
| setSelectedEquipmentCode(""); | |||
| setEquipmentCodePrefix(""); | |||
| setEquipmentCodeNumber(""); | |||
| } | |||
| }; | |||
| checkAndGenerateEquipmentCode(); | |||
| }, [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( | |||
| (id: string | number, code: string) => { | |||
| setExpandedRows(prev => { | |||
| @@ -546,9 +615,15 @@ const EquipmentSearch: React.FC<Props> = ({ equipments, tabIndex = 0 }) => { | |||
| 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); | |||
| @@ -623,13 +698,17 @@ const EquipmentSearch: React.FC<Props> = ({ equipments, tabIndex = 0 }) => { | |||
| newEquipmentCode = `LSS${String(1).padStart(2, '0')}`; | |||
| } | |||
| } else { | |||
| newEquipmentCode = selectedEquipmentCode; | |||
| if (isExistingCombination) { | |||
| newEquipmentCode = selectedEquipmentCode; | |||
| } else { | |||
| newEquipmentCode = `${equipmentCodePrefix}${equipmentCodeNumber}`; | |||
| } | |||
| } | |||
| } else { | |||
| if (isExistingCombination) { | |||
| newEquipmentCode = `LSS${String(1).padStart(2, '0')}`; | |||
| } else { | |||
| newEquipmentCode = selectedEquipmentCode; | |||
| newEquipmentCode = `${equipmentCodePrefix}${equipmentCodeNumber}`; | |||
| } | |||
| } | |||
| @@ -666,7 +745,7 @@ const EquipmentSearch: React.FC<Props> = ({ equipments, tabIndex = 0 }) => { | |||
| } finally { | |||
| 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 => { | |||
| 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> | |||
| </DialogContent> | |||
| <DialogActions> | |||