From 3dd09d9fc74c3bb1086c4dfbf213f10009fc612c Mon Sep 17 00:00:00 2001 From: kiminchan Date: Wed, 10 Jun 2026 17:53:48 +0900 Subject: [PATCH] =?UTF-8?q?=ED=95=98=EC=88=98=EA=B4=80=EB=A1=9C=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EC=86=8D=EB=8F=84=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/popupComponent/MsSwagePpl.tsx | 22 ++++++++++-- .../popupComponent/featureClickEventPopup.tsx | 34 ++++++++++++------- .../001-fe-SFR-012-003-하수관로편집-통합구현기록.md | 12 +++++++ 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/MsSwagePpl.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/MsSwagePpl.tsx index 2fda0b285..6ddb8fd98 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/MsSwagePpl.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/MsSwagePpl.tsx @@ -46,6 +46,8 @@ const MsSwagePpl = ({ featureData, onSaveClick }: { featureData: any, onSaveClic }; const { BIZ_SE_CD, BIZ_TY_CD } = useStore(); + const requestMngInstCd = String(featureData?.mng_inst_cd ?? featureData?.mngInstCd ?? "").trim(); + const requestFcltMngNo = String(featureData?.fclt_mng_no ?? featureData?.fcltMngNo ?? "").trim(); // 마운트 및 업데이트 시 API 호출 @@ -53,9 +55,18 @@ const MsSwagePpl = ({ featureData, onSaveClick }: { featureData: any, onSaveClic if (featureData) { setFormData(featureData); } + }, [featureData]); + + useEffect(() => { + if (!requestMngInstCd || !requestFcltMngNo) { + setInspectionData([]); + setCleaningData([]); + setRepairData([]); + return; + } const fetchAll = async () => { - const operationData = await getData(`v1/bizInfo/bizByFclt?mngInstCd=${featureData?.mng_inst_cd}&fcltMngNo=${featureData?.fclt_mng_no}&page=0&size=1`); + const operationData = await getData(`v1/bizInfo/bizByFclt?mngInstCd=${requestMngInstCd}&fcltMngNo=${requestFcltMngNo}&page=0&size=1`); let inspectionArr: operationDataType[] = []; let cleaningArr: operationDataType[] = []; @@ -104,8 +115,13 @@ const MsSwagePpl = ({ featureData, onSaveClick }: { featureData: any, onSaveClic setCleaningData(cleaningArr); setRepairData(repairArr); }; - fetchAll(); - }, [featureData]); + fetchAll().catch((error) => { + console.error("운영 이력 조회 실패:", error); + setInspectionData([]); + setCleaningData([]); + setRepairData([]); + }); + }, [BIZ_SE_CD, BIZ_TY_CD, requestFcltMngNo, requestMngInstCd]); // 폼 데이터가 변경될 때 호출될 핸들러 const handleFormDataChange = (newFormData: any) => { diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx index cf56063d0..202f4e338 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx @@ -169,6 +169,15 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, return true; }; + const isValidDetailIdentifier = (value: unknown) => { + if (typeof value !== "string") return false; + const normalizedValue = value.trim(); + if (!normalizedValue) return false; + if (normalizedValue.toLowerCase() === "null") return false; + if (normalizedValue.toLowerCase() === "undefined") return false; + return true; + }; + const mergeDetailData = (baseData: any, detailData: any) => { if (!detailData || typeof detailData !== "object") return baseData; @@ -391,7 +400,6 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, try { const selectedLayerName = getSelectedLayers(); // 레이어 이름 가져오기 setLayerNm(selectedLayerName); // 상태 업데이트 - const isPipeDetailLayer = selectedLayerName === '하수관로'; let data; if (featureData['ol_uid']) data = featureData.values_; // Gis 레이어 클릭 이벤트일 때 @@ -430,21 +438,21 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, } } - if (!isPipeDetailLayer) { - setFetchData(formattedData); - setTableData(formattedData); - } + setFetchData(formattedData); + setTableData(formattedData); switch (selectedLayerName) { case '하수관로': setButtonChk(true) // ✅ codeMapper 사용하여 API 결과도 변환 - if (!isPipeDetailLayer) { - setTableData(withMngInstName(codeMapper({ + { + const initialPipeData = withMngInstName(codeMapper({ layerName: selectedLayerName, data: { ...formattedData } - }))); + })); + setFetchData(initialPipeData); + setTableData(initialPipeData); } break; case '하수맨홀': @@ -499,15 +507,16 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, // 자체입력정보 API 엔드포인트 선택 let apiEndpoint = ''; - const fcltMngNo = featureData?.values_?.fclt_mng_no || featureData?.fcltMngNo || null; - const mngInstCd = featureData?.values_?.mng_inst_cd || featureData?.mngInstCd || null; + const fcltMngNo = String(featureData?.values_?.fclt_mng_no || featureData?.fcltMngNo || "").trim(); + const mngInstCd = String(featureData?.values_?.mng_inst_cd || featureData?.mngInstCd || "").trim(); + const canRequestDetail = isValidDetailIdentifier(fcltMngNo) && isValidDetailIdentifier(mngInstCd); if (selectedLayerName === '하수관로') apiEndpoint = `v1/fcltppl/${fcltMngNo}/${mngInstCd}`; else if (selectedLayerName === '우수토실') apiEndpoint = `v1/fcltstofcbr/${fcltMngNo}/${mngInstCd}`; else if (selectedLayerName === '하수펌프장') apiEndpoint = `v1/fcltpump/${fcltMngNo}/${mngInstCd}`; else if (selectedLayerName === '하수맨홀') apiEndpoint = `v1/fcltmhl/${fcltMngNo}/${mngInstCd}`; // API에서 상세 데이터 가져오기 - if (apiEndpoint) { + if (apiEndpoint && canRequestDetail) { try { const result = await getData(apiEndpoint); @@ -522,7 +531,8 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, setTableData(processedApiData); } } catch (apiError) { - console.error('API 데이터 로딩 중 오류:', apiError); + void apiError; + console.warn("Pipe detail fallback kept initial feature data."); } } } catch (error) { diff --git a/apps/sn/docs/작업내용/SFR-012-003-하수관로편집/001-fe-SFR-012-003-하수관로편집-통합구현기록.md b/apps/sn/docs/작업내용/SFR-012-003-하수관로편집/001-fe-SFR-012-003-하수관로편집-통합구현기록.md index 30a40cca2..ed582e79e 100644 --- a/apps/sn/docs/작업내용/SFR-012-003-하수관로편집/001-fe-SFR-012-003-하수관로편집-통합구현기록.md +++ b/apps/sn/docs/작업내용/SFR-012-003-하수관로편집/001-fe-SFR-012-003-하수관로편집-통합구현기록.md @@ -313,3 +313,15 @@ - `004-fe-SFR-012-003-하수관로편집-실제지도조작확장.md` - `005-fe-SFR-012-003-하수관로편집-MOVE실드래그구현.md` - `006-fe-SFR-012-003-하수관로편집-VERTEX_EDIT실조작구현.md` + +## 2026-06-10 관로 상세 조회 성능 개선 + +- 작업 목적 + - 하수관로 클릭 후 상세 팝업이 API 응답 전까지 비어 보이던 체감을 줄이고, 잘못된 상세 호출을 막는다. +- 프론트 변경 + - `featureClickEventPopup.tsx`에서 지도 클릭 feature 값으로 하수관로 기본정보를 먼저 렌더링하고, 상세 API 응답이 오면 기존 병합 흐름으로 덮어쓴다. + - 상세 식별값이 비어 있거나 `null`/`undefined` 문자열인 경우 `fcltppl` 상세 호출을 건너뛴다. + - `MsSwagePpl.tsx`의 운영이력 조회는 snake/camel 식별값을 모두 허용하고, 식별값이 없는 경우 호출하지 않도록 가드했다. +- 기대 효과 + - 관로 클릭 직후 팝업 기본정보가 즉시 표시된다. + - `/api/v1/fcltppl/null/null` 같은 불필요 호출을 줄여 상세 체감 지연과 백엔드 부하를 함께 낮춘다.