"use client"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import SearchBox, { Criterion } from "../SearchBox"; import { EquipmentResult } from "@/app/api/settings/equipment"; import { useTranslation } from "react-i18next"; import EquipmentSearchResults, { Column } from "./EquipmentSearchResults"; import { EditNote } from "@mui/icons-material"; import { useRouter } from "next/navigation"; import { BASE_API_URL, NEXT_PUBLIC_API_URL } from "@/config/api"; import axiosInstance from "@/app/(main)/axios/axiosInstance"; import { arrayToDateTimeString } from "@/app/utils/formatUtil"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import IconButton from "@mui/material/IconButton"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; import CircularProgress from "@mui/material/CircularProgress"; import TableRow from "@mui/material/TableRow"; import TableCell from "@mui/material/TableCell"; import Collapse from "@mui/material/Collapse"; import Grid from "@mui/material/Grid"; import DeleteIcon from "@mui/icons-material/Delete"; import AddIcon from "@mui/icons-material/Add"; import Button from "@mui/material/Button"; import Dialog from "@mui/material/Dialog"; import DialogTitle from "@mui/material/DialogTitle"; import DialogContent from "@mui/material/DialogContent"; 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[]; tabIndex?: number; }; type SearchQuery = Partial>; type SearchParamNames = keyof SearchQuery; const EquipmentSearch: React.FC = ({ equipments, tabIndex = 0 }) => { const [filteredEquipments, setFilteredEquipments] = useState([]); const { t } = useTranslation("common"); const router = useRouter(); const [filterObjByTab, setFilterObjByTab] = useState>({ 0: {}, 1: {}, }); const [pagingControllerByTab, setPagingControllerByTab] = useState>({ 0: { pageNum: 1, pageSize: 10 }, 1: { pageNum: 1, pageSize: 10 }, }); const [totalCount, setTotalCount] = useState(0); const [isLoading, setIsLoading] = useState(true); const [isReady, setIsReady] = useState(false); const filterObj = filterObjByTab[tabIndex] || {}; const pagingController = pagingControllerByTab[tabIndex] || { pageNum: 1, pageSize: 10 }; const [expandedRows, setExpandedRows] = useState>(new Set()); const [equipmentDetailsMap, setEquipmentDetailsMap] = useState>(new Map()); const [loadingDetailsMap, setLoadingDetailsMap] = useState>(new Map()); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [itemToDelete, setItemToDelete] = useState<{ id: string | number; equipmentId: string | number } | null>(null); const [deleting, setDeleting] = useState(false); const [addDialogOpen, setAddDialogOpen] = useState(false); const [equipmentList, setEquipmentList] = useState([]); const [selectedDescription, setSelectedDescription] = useState(""); const [selectedName, setSelectedName] = useState(""); const [selectedEquipmentCode, setSelectedEquipmentCode] = useState(""); const [equipmentCodePrefix, setEquipmentCodePrefix] = useState(""); const [equipmentCodeNumber, setEquipmentCodeNumber] = useState(""); const [isExistingCombination, setIsExistingCombination] = useState(false); const [loadingEquipments, setLoadingEquipments] = useState(false); const [saving, setSaving] = useState(false); useEffect(() => { const checkReady = () => { try { const token = localStorage.getItem("accessToken"); const hasAuthHeader = axiosInstance.defaults.headers?.common?.Authorization || axiosInstance.defaults.headers?.Authorization; if (token && hasAuthHeader) { setIsReady(true); } else if (token) { setTimeout(checkReady, 50); } else { setTimeout(checkReady, 100); } } catch (e) { console.warn("localStorage unavailable", e); } }; const timer = setTimeout(checkReady, 100); return () => clearTimeout(timer); }, []); const displayDateTime = useCallback((dateValue: string | Date | number[] | null | undefined): string => { if (!dateValue) return "-"; if (Array.isArray(dateValue)) { return arrayToDateTimeString(dateValue); } if (typeof dateValue === "string") { return dateValue; } return String(dateValue); }, []); const searchCriteria: Criterion[] = useMemo(() => { if (tabIndex === 1) { return [ { label: "設備名稱/設備編號", paramName: "equipmentCode", type: "text" }, { label: t("Repair and Maintenance Status"), paramName: "repairAndMaintenanceStatus", type: "select", options: ["正常使用中", "正在維護中"] }, ]; } return [ { label: "設備編號", paramName: "code", type: "text" }, ]; }, [t, tabIndex]); const onMaintenanceEditClick = useCallback( (equipment: EquipmentResult) => { router.push(`/settings/equipment/MaintenanceEdit?id=${equipment.id}`); }, [router], ); const onDeleteClick = useCallback( (equipment: EquipmentResult) => {}, [router], ); const fetchEquipmentDetailsByEquipmentId = useCallback(async (equipmentId: string | number) => { setLoadingDetailsMap(prev => new Map(prev).set(equipmentId, true)); try { const response = await axiosInstance.get<{ records: EquipmentResult[]; total: number; }>(`${NEXT_PUBLIC_API_URL}/EquipmentDetail/byEquipmentId/${equipmentId}`); if (response.status === 200) { setEquipmentDetailsMap(prev => new Map(prev).set(equipmentId, response.data.records || [])); } } catch (error) { console.error("Error fetching equipment details:", error); setEquipmentDetailsMap(prev => new Map(prev).set(equipmentId, [])); } finally { setLoadingDetailsMap(prev => new Map(prev).set(equipmentId, false)); } }, []); const handleDeleteClick = useCallback((detailId: string | number, equipmentId: string | number) => { setItemToDelete({ id: detailId, equipmentId }); setDeleteDialogOpen(true); }, []); const handleDeleteConfirm = useCallback(async () => { if (!itemToDelete) return; setDeleting(true); try { const response = await axiosInstance.delete( `${NEXT_PUBLIC_API_URL}/EquipmentDetail/delete/${itemToDelete.id}` ); if (response.status === 200 || response.status === 204) { setEquipmentDetailsMap(prev => { const newMap = new Map(prev); const currentDetails = newMap.get(itemToDelete.equipmentId) || []; const updatedDetails = currentDetails.filter(detail => detail.id !== itemToDelete.id); newMap.set(itemToDelete.equipmentId, updatedDetails); return newMap; }); } } catch (error) { console.error("Error deleting equipment detail:", error); alert("刪除失敗,請稍後再試"); } finally { setDeleting(false); setDeleteDialogOpen(false); setItemToDelete(null); } }, [itemToDelete]); const handleDeleteCancel = useCallback(() => { setDeleteDialogOpen(false); setItemToDelete(null); }, []); const fetchEquipmentList = useCallback(async () => { setLoadingEquipments(true); try { const response = await axiosInstance.get<{ records: EquipmentResult[]; total: number; }>(`${NEXT_PUBLIC_API_URL}/Equipment/getRecordByPage`, { params: { pageNum: 1, pageSize: 1000, }, }); if (response.status === 200) { setEquipmentList(response.data.records || []); } } catch (error) { console.error("Error fetching equipment list:", error); setEquipmentList([]); } finally { setLoadingEquipments(false); } }, []); const handleAddClick = useCallback(() => { setAddDialogOpen(true); fetchEquipmentList(); }, [fetchEquipmentList]); const handleAddDialogClose = useCallback(() => { setAddDialogOpen(false); setSelectedDescription(""); setSelectedName(""); setSelectedEquipmentCode(""); setEquipmentCodePrefix(""); setEquipmentCodeNumber(""); setIsExistingCombination(false); }, []); const availableDescriptions = useMemo(() => { const descriptions = equipmentList .map((eq) => eq.description) .filter((desc): desc is string => Boolean(desc)); return Array.from(new Set(descriptions)); }, [equipmentList]); const availableNames = useMemo(() => { const names = equipmentList .map((eq) => eq.name) .filter((name): name is string => Boolean(name)); return Array.from(new Set(names)); }, [equipmentList]); useEffect(() => { const checkAndGenerateEquipmentCode = async () => { if (!selectedDescription || !selectedName) { setIsExistingCombination(false); setSelectedEquipmentCode(""); return; } const equipmentCode = `${selectedDescription}-${selectedName}`; const existingEquipment = equipmentList.find((eq) => eq.code === equipmentCode); if (existingEquipment) { setIsExistingCombination(true); try { const existingDetailsResponse = await axiosInstance.get<{ records: EquipmentResult[]; total: number; }>(`${NEXT_PUBLIC_API_URL}/EquipmentDetail/byDescriptionIncludingDeleted/${encodeURIComponent(equipmentCode)}`); let newEquipmentCode = ""; if (existingDetailsResponse.data.records && existingDetailsResponse.data.records.length > 0) { const equipmentCodePatterns = existingDetailsResponse.data.records .map((detail) => { if (!detail.equipmentCode) return null; const match = detail.equipmentCode.match(/^([A-Za-z]+)(\d+)$/); if (match) { const originalNumber = match[2]; return { prefix: match[1], number: parseInt(match[2], 10), paddingLength: originalNumber.length }; } return null; }) .filter((pattern): pattern is { prefix: string; number: number; paddingLength: number } => pattern !== null); if (equipmentCodePatterns.length > 0) { const prefix = equipmentCodePatterns[0].prefix; const maxEquipmentCodeNumber = Math.max(...equipmentCodePatterns.map(p => p.number)); const maxPaddingLength = Math.max(...equipmentCodePatterns.map(p => p.paddingLength)); const nextNumber = maxEquipmentCodeNumber + 1; newEquipmentCode = `${prefix}${String(nextNumber).padStart(maxPaddingLength, '0')}`; } else { newEquipmentCode = `LSS${String(1).padStart(2, '0')}`; } } else { newEquipmentCode = `LSS${String(1).padStart(2, '0')}`; } setSelectedEquipmentCode(newEquipmentCode); } catch (error) { console.error("Error checking existing equipment details:", error); setIsExistingCombination(false); setSelectedEquipmentCode(""); } } 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 => { const newSet = new Set(prev); if (newSet.has(id)) { newSet.delete(id); } else { newSet.add(id); if (!equipmentDetailsMap.has(id)) { fetchEquipmentDetailsByEquipmentId(id); } } return newSet; }); }, [equipmentDetailsMap, fetchEquipmentDetailsByEquipmentId] ); const generalDataColumns = useMemo[]>( () => [ { name: "code", label: "設備編號", renderCell: (item) => ( { e.stopPropagation(); handleToggleExpand(item.id, item.code); }} sx={{ padding: 0.5 }} > {expandedRows.has(item.id) ? ( ) : ( )} {item.code} ), }, ], [t, handleToggleExpand, expandedRows], ); const repairMaintenanceColumns = useMemo[]>( () => [ { name: "id", label: "編輯", onClick: onMaintenanceEditClick, buttonIcon: , align: "left", headerAlign: "left", sx: { width: "60px", minWidth: "60px" }, }, { name: "code", label: "設備名稱", align: "left", headerAlign: "left", sx: { width: "200px", minWidth: "200px" }, }, { name: "equipmentCode", label: "設備編號", align: "left", headerAlign: "left", sx: { width: "150px", minWidth: "150px" }, renderCell: (item) => { return item.equipmentCode || "-"; }, }, { name: "repairAndMaintenanceStatus", label: t("Repair and Maintenance Status"), align: "left", headerAlign: "left", sx: { width: "150px", minWidth: "150px" }, renderCell: (item) => { const status = item.repairAndMaintenanceStatus; if (status === 1 || status === true) { return ( 正在維護中 ); } else if (status === 0 || status === false) { return ( 正常使用中 ); } return "-"; }, }, { name: "latestRepairAndMaintenanceDate", label: t("Latest Repair and Maintenance Date"), align: "left", headerAlign: "left", sx: { width: "200px", minWidth: "200px" }, renderCell: (item) => displayDateTime(item.latestRepairAndMaintenanceDate), }, { name: "lastRepairAndMaintenanceDate", label: t("Last Repair and Maintenance Date"), align: "left", headerAlign: "left", sx: { width: "200px", minWidth: "200px" }, renderCell: (item) => displayDateTime(item.lastRepairAndMaintenanceDate), }, { name: "repairAndMaintenanceRemarks", label: t("Repair and Maintenance Remarks"), align: "left", headerAlign: "left", sx: { width: "200px", minWidth: "200px" }, }, ], [onMaintenanceEditClick, t, displayDateTime], ); const columns = useMemo(() => { return tabIndex === 1 ? repairMaintenanceColumns : generalDataColumns; }, [tabIndex, repairMaintenanceColumns, generalDataColumns]); interface ApiResponse { records: T[]; total: number; } const refetchData = useCallback( async (filterObj: SearchQuery) => { const token = localStorage.getItem("accessToken"); const hasAuthHeader = axiosInstance.defaults.headers?.common?.Authorization || axiosInstance.defaults.headers?.Authorization; if (!token || !hasAuthHeader) { console.warn("Token or auth header not ready, skipping API call"); setIsLoading(false); return; } setIsLoading(true); const transformedFilter: any = { ...filterObj }; if (tabIndex === 1 && transformedFilter.equipmentCode) { transformedFilter.code = transformedFilter.equipmentCode; } if (transformedFilter.repairAndMaintenanceStatus) { if (transformedFilter.repairAndMaintenanceStatus === "正常使用中") { transformedFilter.repairAndMaintenanceStatus = false; } else if (transformedFilter.repairAndMaintenanceStatus === "正在維護中") { transformedFilter.repairAndMaintenanceStatus = true; } else if (transformedFilter.repairAndMaintenanceStatus === "All") { delete transformedFilter.repairAndMaintenanceStatus; } } const params = { pageNum: pagingController.pageNum, pageSize: pagingController.pageSize, ...transformedFilter, }; try { const endpoint = tabIndex === 1 ? `${NEXT_PUBLIC_API_URL}/EquipmentDetail/getRecordByPage` : `${NEXT_PUBLIC_API_URL}/Equipment/getRecordByPage`; const response = await axiosInstance.get>( endpoint, { params }, ); console.log("API Response:", response); console.log("Records:", response.data.records); console.log("Total:", response.data.total); if (response.status == 200) { setFilteredEquipments(response.data.records || []); setTotalCount(response.data.total || 0); } else { throw "400"; } } catch (error) { console.error("Error fetching equipment types:", error); setFilteredEquipments([]); setTotalCount(0); } finally { setIsLoading(false); } }, [pagingController.pageNum, pagingController.pageSize, tabIndex], ); useEffect(() => { if (isReady) { refetchData(filterObj); } }, [filterObj, pagingController.pageNum, pagingController.pageSize, tabIndex, isReady, refetchData]); const onReset = useCallback(() => { setFilterObjByTab(prev => ({ ...prev, [tabIndex]: {}, })); setPagingControllerByTab(prev => ({ ...prev, [tabIndex]: { pageNum: 1, pageSize: prev[tabIndex]?.pageSize || 10, }, })); }, [tabIndex]); const handleSaveEquipmentDetail = useCallback(async () => { if (!selectedName || !selectedDescription) { return; } if (!isExistingCombination) { if (equipmentCodePrefix.length !== 3 || !/^[A-Z]{3}$/.test(equipmentCodePrefix)) { alert("請輸入3個大寫英文字母作為設備編號前綴"); return; } if (!equipmentCodeNumber) { alert("設備編號生成中,請稍候"); return; } } setSaving(true); try { const equipmentCode = `${selectedDescription}-${selectedName}`; let equipment = equipmentList.find((eq) => eq.code === equipmentCode); let equipmentId: string | number; if (!equipment) { const equipmentResponse = await axiosInstance.post( `${NEXT_PUBLIC_API_URL}/Equipment/save`, { code: equipmentCode, name: selectedName, description: selectedDescription, id: null, } ); equipment = equipmentResponse.data; equipmentId = equipment.id; } else { equipmentId = equipment.id; } const existingDetailsResponse = await axiosInstance.get<{ records: EquipmentResult[]; total: number; }>(`${NEXT_PUBLIC_API_URL}/EquipmentDetail/byDescriptionIncludingDeleted/${encodeURIComponent(equipmentCode)}`); let newName = "1號"; let newEquipmentCode = ""; if (existingDetailsResponse.data.records && existingDetailsResponse.data.records.length > 0) { const numbers = existingDetailsResponse.data.records .map((detail) => { const match = detail.name?.match(/(\d+)號/); return match ? parseInt(match[1], 10) : 0; }) .filter((num) => num > 0); if (numbers.length > 0) { const maxNumber = Math.max(...numbers); newName = `${maxNumber + 1}號`; } if (isExistingCombination) { const equipmentCodePatterns = existingDetailsResponse.data.records .map((detail) => { if (!detail.equipmentCode) return null; const match = detail.equipmentCode.match(/^([A-Za-z]+)(\d+)$/); if (match) { const originalNumber = match[2]; return { prefix: match[1], number: parseInt(match[2], 10), paddingLength: originalNumber.length }; } return null; }) .filter((pattern): pattern is { prefix: string; number: number; paddingLength: number } => pattern !== null); if (equipmentCodePatterns.length > 0) { const prefix = equipmentCodePatterns[0].prefix; const maxEquipmentCodeNumber = Math.max(...equipmentCodePatterns.map(p => p.number)); const maxPaddingLength = Math.max(...equipmentCodePatterns.map(p => p.paddingLength)); const nextNumber = maxEquipmentCodeNumber + 1; newEquipmentCode = `${prefix}${String(nextNumber).padStart(maxPaddingLength, '0')}`; } else { newEquipmentCode = `LSS${String(1).padStart(2, '0')}`; } } else { if (isExistingCombination) { newEquipmentCode = selectedEquipmentCode; } else { newEquipmentCode = `${equipmentCodePrefix}${equipmentCodeNumber}`; } } } else { if (isExistingCombination) { newEquipmentCode = `LSS${String(1).padStart(2, '0')}`; } else { newEquipmentCode = `${equipmentCodePrefix}${equipmentCodeNumber}`; } } const detailCode = `${equipmentCode}-${newName}`; await axiosInstance.post( `${NEXT_PUBLIC_API_URL}/EquipmentDetail/save`, { code: detailCode, name: newName, description: equipmentCode, equipmentCode: newEquipmentCode, id: null, equipmentTypeId: equipmentId, repairAndMaintenanceStatus: false, } ); handleAddDialogClose(); if (tabIndex === 0) { await refetchData(filterObj); if (equipmentDetailsMap.has(equipmentId)) { await fetchEquipmentDetailsByEquipmentId(equipmentId); } } alert("新增成功"); } catch (error: any) { console.error("Error saving equipment detail:", error); const errorMessage = error.response?.data?.message || error.message || "保存失敗,請稍後再試"; alert(errorMessage); } finally { setSaving(false); } }, [selectedName, selectedDescription, selectedEquipmentCode, equipmentCodePrefix, equipmentCodeNumber, isExistingCombination, equipmentList, refetchData, filterObj, handleAddDialogClose, tabIndex, equipmentDetailsMap, fetchEquipmentDetailsByEquipmentId]); const renderExpandedRow = useCallback((item: EquipmentResult): React.ReactNode => { if (tabIndex !== 0) { return null; } const details = equipmentDetailsMap.get(item.id) || []; const isLoading = loadingDetailsMap.get(item.id) || false; return ( {isLoading ? ( 載入中... ) : details.length === 0 ? ( 無相關設備詳細資料 ) : ( 設備詳細資料 (設備編號: {item.code}) {details.map((detail) => ( 編號: {detail.code || "-"} handleDeleteClick(detail.id, item.id)} sx={{ ml: 1 }} > {detail.name && ( 名稱: {detail.name} )} {detail.description && ( 描述: {detail.description} )} {detail.equipmentCode && ( 設備編號: {detail.equipmentCode} )} ))} )} ); }, [columns.length, equipmentDetailsMap, loadingDetailsMap, expandedRows, tabIndex, handleDeleteClick]); return ( <> { setFilterObjByTab(prev => { const newState = { ...prev }; newState[tabIndex] = query as unknown as SearchQuery; return newState; }); }} onReset={onReset} /> {tabIndex === 0 && ( 設備編號 )} items={filteredEquipments} columns={columns} setPagingController={(newController) => { setPagingControllerByTab(prev => { const newState = { ...prev }; newState[tabIndex] = typeof newController === 'function' ? newController(prev[tabIndex] || { pageNum: 1, pageSize: 10 }) : newController; return newState; }); }} pagingController={pagingController} totalCount={totalCount} isAutoPaging={false} renderExpandedRow={renderExpandedRow} hideHeader={tabIndex === 0} /> {/* Delete Confirmation Dialog */} {deleteDialogOpen && ( 確認刪除 您確定要刪除此設備詳細資料嗎?此操作無法復原。 )} {/* Add Equipment Detail Dialog */} 新增設備詳細資料 { setSelectedDescription(newValue || ''); }} onInputChange={(event, newInputValue) => { setSelectedDescription(newInputValue); }} loading={loadingEquipments} disabled={loadingEquipments || saving} renderInput={(params) => ( )} sx={{ mb: 2 }} /> { setSelectedName(newValue || ''); }} onInputChange={(event, newInputValue) => { setSelectedName(newInputValue); }} loading={loadingEquipments} disabled={loadingEquipments || saving} componentsProps={{ popper: { placement: 'bottom-start', modifiers: [ { name: 'flip', enabled: false, }, { name: 'preventOverflow', enabled: true, }, ], }, }} renderInput={(params) => ( )} /> { 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 ? ( {equipmentCodeNumber} ) : 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))} /> ); }; export default EquipmentSearch;