diff --git a/apps/sn/app/(map-layout)/map/components/common/table.ts b/apps/sn/app/(map-layout)/map/components/common/table.ts index 7b6c307cb..f18d402ff 100644 --- a/apps/sn/app/(map-layout)/map/components/common/table.ts +++ b/apps/sn/app/(map-layout)/map/components/common/table.ts @@ -30,75 +30,76 @@ export const MsSwagePplCol = [ // 하수처리시설 export const SwtrmtFcltCol = [ - { id: 'swtrmtFcltNm', title: '하수처리시설명' }, - { id: 'fcltCpct', title: '시설용량' }, - { id: 'rgnExpln', title: '지역설명' }, - { id: 'cmcnYmd', title: '준공일자' }, - { id: 'oprtngStrtYmd', title: '가동개시일자' }, - { id: 'clsgYmd', title: '폐쇄일자' }, - // { id: 'prcsMsrcCd', title: '처리공법코드' }, - // { id: 'laeCd', title: '지자체코드' }, - { id: 'laeNm', title: '지자체명' }, - { id: 'ctpvNm', title: '시도명' }, - { id: 'sggNm', title: '시군구명' }, - // { id: 'sareaCd', title: '권역코드' }, - { id: 'sareaNm', title: '권역명' }, - { id: 'lrgeSareaNm', title: '대권역명' }, - { id: 'rvrNm', title: '하천명' }, + { id: 'swtrmt_fclt_nm', title: '하수처리시설명' }, + { id: 'fclt_cpct', title: '시설용량' }, + { id: 'rgn_expln', title: '지역설명' }, + { id: 'cmcn_ymd', title: '준공일자' }, + { id: 'oprtng_strt_ymd', title: '가동개시일자' }, + { id: 'clsg_ymd', title: '폐쇄일자' }, + // { id: 'prcs_msrc_cd', title: '처리공법코드' }, + // { id: 'lae_cd', title: '지자체코드' }, + { id: 'lae_nm', title: '지자체명' }, + { id: 'ctpv_nm', title: '시도명' }, + { id: 'sgg_nm', title: '시군구명' }, + // { id: 'sarea_cd', title: '권역코드' }, + { id: 'sarea_nm', title: '권역명' }, + { id: 'lrge_sarea_nm', title: '대권역명' }, + { id: 'rvr_nm', title: '하천명' }, { id: 'addr', title: '주소' }, - { id: 'daddr', title: '상세주소' }, - { id: 'rdnmAddr', title: '도로명주소' }, - { id: 'dtlRdnmAddr', title: '상세도로명주소' }, - { id: 'dschgWtstNm', title: '방류수계명' }, - { id: 'pllnArea', title: '부지면적' }, - { id: 'mngRprtvBzentyNm', title: '관리대행업제명' }, - { id: 'swtrmtPpltnCnt', title: '하수처리인구수' }, - { id: 'prcsZoneAreaSum', title: '처리구역면적합계' }, + { id: 'dtl_addr', title: '상세주소' }, + { id: 'rdnm_addr', title: '도로명주소' }, + { id: 'dtl_rdnm_addr', title: '상세도로명주소' }, + { id: 'dschg_wtst_nm', title: '방류수계명' }, + { id: 'plln_area', title: '부지면적' }, + { id: 'mng_rprtv_bzenty_nm', title: '관리대행업제명' }, + { id: 'swtrmt_ppltn_cnt', title: '하수처리인구수' }, + { id: 'prcs_zone_area_sum', title: '처리구역면적합계' }, { id: 'xcrd', title: 'X좌표' }, { id: 'ycrd', title: 'Y좌표' }, // { id: 'oprtngSttsCd', title: '가동상태코드' }, - { id: 'rprsMsrcCn', title: '대표공법내용' }, - { id: 'sttyCrtrBod', title: '법정기준생물화학적산소요구량' }, - { id: 'sttyCrtrToc', title: '법정기준TOC' }, - { id: 'sttyCrtrSs', title: '법정기준SS' }, - { id: 'sttyCrtrTn', title: '법정기준총질소' }, - { id: 'sttyCrtrTph', title: '법정기준총인' }, - { id: 'sttyCrtrClbclCnt', title: '법정기준대장균수' }, - { id: 'bsnNm', title: '유역명' }, + { id: 'rprs_msrc_cn', title: '대표공법내용' }, + { id: 'stty_crtr_bod', title: '법정기준생물화학적산소요구량' }, + { id: 'stty_crtr_toc', title: '법정기준TOC' }, + { id: 'stty_crtr_ss', title: '법정기준SS' }, + { id: 'stty_crtr_tn', title: '법정기준총질소' }, + { id: 'stty_crtr_tph', title: '법정기준총인' }, + { id: 'stty_crtr_clbcl_cnt', title: '법정기준대장균수' }, + { id: 'bsn_nm', title: '유역명' }, // { id: 'obsrvnSptCd', title: '관측지점코드' }, ]; // 하수처리구역 export const SwtrmtPrcsZoneCol = [ - { id: 'swtrmtZoneSn', title: '하수처리구역 일련번호' }, - { id: 'swtrmtZoneNm', title: '하수처리구역명' }, - // { id: 'swtrmtFcltCd', title: '하수처리시설코드' }, - // { id: 'ctpvCd', title: '시도코드' }, - { id: 'ctpvNm', title: '시도명' }, - // { id: 'sggCd', title: '시군구코드' }, - { id: 'sggNm', title: '시군구명' }, - { id: 'swtrmtFcltNm', title: '하수처리시설명' }, + // { id: 'swtrmtZoneSn', title: '하수처리구역 일련번호' }, + { id: 'swtrmt_zone_nm', title: '하수처리구역명' }, + // { id: 'swtrmt_fclt_cd', title: '하수처리시설코드' }, + // { id: 'ctpv_cd', title: '시도코드' }, + { id: 'ctpv_nm', title: '시도명' }, + // { id: 'sgg_cd', title: '시군구코드' }, + { id: 'sgg_nm', title: '시군구명' }, + { id: 'swtrmt_fclt_nm', title: '하수처리시설명' }, ]; // 하수맨홀 export const MsSwageMhlCol = [ // { id: 'fid', title: 'FID' }, // { id: 'geom', title: 'geometry' }, - // { id: 'ftreSmbolCd', title: '지형지물부호코드' }, - { id: 'fcltMngNo', title: '시설관리번호' }, - // { id: 'pbadmsEmdCd', title: '행정읍면동코드' }, - { id: 'mapshtNo', title: '도엽번호' }, - // { id: 'mngInstCd', title: '관리기관코드' }, - { id: 'instlYmd', title: '설치일자' }, - { id: 'lastDrdgnYmd', title: '최종준설일자' }, - // { id: 'usgCd', title: '용도코드' }, - // { id: 'fcltyShpCd', title: '시설물형태코드' }, - // { id: 'mhlKndCd', title: '맨홀종류코드' }, - { id: 'swageMhDiam', title: '하수맨홀구경' }, - { id: 'wdthLen', title: '가로길이' }, - { id: 'vrtcLen', title: '세로길이' }, + // { id: 'ftre_smbol_cd', title: '지형지물부호코드' }, + { id: 'fclt_mng_no', title: '시설관리번호' }, + // { id: 'pbadms_emd_cd', title: '행정읍면동코드' }, + { id: 'mapsht_no', title: '도엽번호' }, + // { id: 'mng_inst_cd', title: '관리기관코드' }, + { id: 'instl_ymd', title: '설치일자' }, + { id: 'last_drdgn_ymd', title: '최종준설일자' }, + // { id: 'usg_cd', title: '용도코드' }, + // { id: 'fclty_shp_cd', title: '시설물형태코드' }, + // { id: 'mhl_knd_cd', title: '맨홀종류코드' }, + { id: 'swage_mh_diam', title: '하수맨홀구경' }, + { id: 'wdth_len', title: '가로길이' }, + { id: 'vrtc_len', title: '세로길이' }, // { id: 'mqltCd', title: '재질코드' }, - { id: 'altdVl', title: '표고' }, + // { id: 'ang_dir', title: '표고' }, + { id: 'altd_vl', title: '표고' }, { id: 'dpthlVl', title: '심도' }, // { id: 'instMngNo', title: '기관관리번호' } ]; 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 c6d10aa3f..27aa8e289 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/MsSwagePpl.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/MsSwagePpl.tsx @@ -3,9 +3,8 @@ import { VerticalTable } from '@/components/ui/verticalTable'; import React, { useState } from 'react' import InputSection from '../InputSection'; import { handleInputChange } from '../common'; -import { Button } from '@common/components/ui'; -import InfilChart from './chart/infilChart'; -import RdiiChart from './chart/rdiiChart'; + + // 하수관로 const inputColumn = [ { id: "title", title: "설치주제", type: "select", options: ["BTL", "재정사업"] }, @@ -16,80 +15,63 @@ const inputColumn = [ { id: "flow", title: "흐름방식", type: "select", options: ["자연유하", "압송"] }, { id: "place", title: "매설위치", type: "select", options: ["도로", "하상"] }, ]; -const MsSwagePpl = ({ featureData }: { featureData: any }) => { // SwtrmtFclt - console.log("featureData", featureData); - const [isModalInfil, setIsModalInfil] = React.useState(false); - const [isModalRdii, setIsModalRdii] = React.useState(false); - const [btnTxt, setBtnTxt] = useState(""); - const pplMngNo= featureData?.pplMngNo || ""; // 관로관리번호 - const openModalRdii = (e: React.MouseEvent) => { - e.currentTarget.textContent && setBtnTxt(e.currentTarget.textContent) - setIsModalRdii(true); - } - const openModalInfil = (e: React.MouseEvent) => { - e.currentTarget.textContent && setBtnTxt(e.currentTarget.textContent) - setIsModalInfil(true); - }; - const closeModalInfil = () => { - setIsModalInfil(false); - }; - const closeModalRdii = () => { - setIsModalRdii(false);}; + +const MsSwagePpl = ({ featureData }: { featureData: any }) => { const [repairData, setRepairData] = useState([ { year: "2025", type: "개대체" }, { year: "2025", type: "보수" }, ]); + const [cleaningData, setCleaningData] = useState([ { year: "2025", type: "청소" }, { year: "2025", type: "준설" }, ]); + const [inspectionData, setInspectionData] = useState([ { year: "2025", type: "관로기술진단" }, { year: "2025", type: "관로기술진단" }, ]); + const dataMap = { repair: { data: repairData, setData: setRepairData }, cleaning: { data: cleaningData, setData: setCleaningData }, inspection: { data: inspectionData, setData: setInspectionData }, }; - const keysToExtract = [ - "swtrmt_zone_cd", - "mltv_dtdv_nm", - "fid", - "lyrTypeNm", - "reg_dt", - "mdfcn_dt", - "rgtr", - "mdfr", - ]; - // const filteredData = Object.entries(featureData) - // .filter(([key]) => keysToExtract.includes(key)) // 필요한 키만 필터링 - // .map(([key, value]) => ({ key, value: value ?? "N/A" })); // 테이블 데이터 형식으로 변환 + return (
- - handleInputChange(section, index, field, value, dataMap)} /> - handleInputChange(section, index, field, value, dataMap)} /> - handleInputChange(section, index, field, value, dataMap)} /> -
- - -
- - + + handleInputChange(section, index, field, value, dataMap)} + /> + handleInputChange(section, index, field, value, dataMap)} + /> + handleInputChange(section, index, field, value, dataMap)} + />
) } diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx index 762929e69..7e22ea03b 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx @@ -1,36 +1,41 @@ // 우수토실 import { UiModal, UiTable } from '@common/business_components/ui'; import { Button, TableHead, TableRow } from '@common/components/ui'; -import React from 'react'; +import React, { useState } from 'react'; import { untrtSwgRainCol3, untrtSwgRainCol4, untrtSwgRainCol5 } from '../common/table'; +import LoadingOverlay from '@/components/ui/LoadingBar'; const Ssc = ({ featureData, chkMainSsc, additionalData }: { featureData: any, chkMainSsc?: boolean, additionalData?: any }) => { + const [isModalOpen, setIsModalOpen] = useState(false); + const [isDataLoaded, setIsDataLoaded] = useState(false); + console.log('featureData data received:', featureData); // 추가 데이터 확인용 console.log('Additional data received:', additionalData); // 추가 데이터 확인용 - - const [isModalOpen, setIsModalOpen] = React.useState(false); const openModal = () => { setIsModalOpen(true); }; const closeModal = () => { setIsModalOpen(false); + if (!isDataLoaded) { + setTimeout(() => setIsDataLoaded(true), 100); + } }; // 데이터 전처리: 합쳐진 필드 추가 - const preprocessedData = featureData ? [{ - ...featureData, - flwmtr_combined: `${featureData.flwmtr_mesure_tp_etc || '-'} / ${featureData.flwmtr_knd || '-'}` - }] : []; + // const preprocessedData = featureData ? [{ + // ...featureData, + // flwmtr_combined: `${featureData.flwmtr_mesure_tp_etc || '-'} / ${featureData.flwmtr_knd || '-'}` + // }] : []; const renderModalBody = () => (
{/* 강우정보 테이블 */}
- ); + ) return (
@@ -102,7 +107,7 @@ const Ssc = ({ featureData, chkMainSsc, additionalData }: { }} /> */} - {!chkMainSsc ? null : + {chkMainSsc && (

중점우수토실 운영정보

@@ -131,7 +136,7 @@ const Ssc = ({ featureData, chkMainSsc, additionalData }: { // footerContent={renderFooterContent()} />
- } + )}
) } diff --git a/apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx b/apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx index 99c16f063..2424f2f34 100644 --- a/apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx +++ b/apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx @@ -4,16 +4,15 @@ import Modal from "@/components/ui/modal"; import { VerticalTable } from "@/components/ui/verticalTable"; import { useGisContext } from "@common/business_components/ui/map/Gis"; import { Button } from "@common/components/ui"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState, useCallback, useMemo } from "react"; import { getLayerContentComponent } from "./components/common"; import { SwtrmtFcltCol, SwtrmtPrcsZoneCol, untrtSwgRainCol1, untrtSwgRainCol2, MsSwagePplCol, SewagePump, MsSwageMhlCol } from "./components/common/table"; +import InfilChart from './components/popupComponent/chart/infilChart'; +import RdiiChart from './components/popupComponent/chart/rdiiChart'; + // TODO: const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, featureData: any }) => { const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; }; - // const featureId = featureData.getId(); // 피처의 id 가져오기 - const featureId = typeof featureData.getId === 'function' - ? featureData.getId() - : featureData.fid; const [layerNm, setLayerNm] = useState(""); // 레이어 이름 상태 const [tableData, setTableData] = useState(null); // 피처 데이터 const [mntrngsData, setMntrngsData] = useState(false); // 수량/수질 측정 정보 @@ -24,6 +23,68 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, const contentRef = useRef(null); const [isModalOpen, setIsModalOpen] = useState(false); // 모달 열기 상태 const [chkMainSsc, setChkMainSsc] = useState(false); // 중점우수토실 여부 + const [isLoading, setIsLoading] = useState(false); + // 🎯 추가 데이터 로딩 상태 분리 + const [isAdditionalDataLoading, setIsAdditionalDataLoading] = useState(false); + const [additionalDataError, setAdditionalDataError] = useState(null); + // 🎯 InfilChart 및 RdiiChart 관련 상태 추가 + const [isModalInfil, setIsModalInfil] = useState(false); + const [isModalRdii, setIsModalRdii] = useState(false); + const [btnTxt, setBtnTxt] = useState(""); + + // 🎯 관로관리번호 추출 + const pplMngNo = featureData?.values_?.fclt_mng_no || ""; + // const featureId = featureData.getId(); // 피처의 id 가져오기 + + const featureId = typeof featureData.getId === 'function' + ? featureData.getId() + : featureData.fid; + console.log('featureId', featureId); + + // 🎯 추가 데이터 API 호출 함수 (독립적으로 실행) + const fetchAdditionalData = useCallback(async (adstDstrCd: string, manageNo: string) => { + if (!adstDstrCd || !manageNo) { + console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { adstDstrCd, manageNo }); + return []; + } + + setIsAdditionalDataLoading(true); + setAdditionalDataError(null); + + try { + console.log(`추가 데이터 API 호출 시작: v1/onsemiro/swgRain/${adstDstrCd}/${manageNo}/mntrngs`); + + // 🎯 타임아웃 설정 (10초) + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('API 타임아웃')), 10000) + ); + + const apiPromise = getData(`v1/onsemiro/swgRain/${adstDstrCd}/${manageNo}/mntrngs`); + + const response = await Promise.race([apiPromise, timeoutPromise]) as any; + + console.log('추가 데이터 API 호출 성공:', response); + return response?.content || []; + } catch (error) { + console.error('추가 데이터 로딩 실패:', error); + setAdditionalDataError(error instanceof Error ? error.message : '알 수 없는 오류'); + return []; + } finally { + setIsAdditionalDataLoading(false); + } + }, []); + + // 🎯 데이터 전처리 메모이제이션 + const processedData = useMemo(() => { + if (!featureData?.values_) return null; + + const data = featureData.values_; + return { + ...data, + discharge_pipe_spec: `직경: ${data.pipe_diameter || '-'} / 높이: ${data.pipe_hg || '-'} / 길이: ${data.pipe_lt || '-'}`, + flwmtr_combined: `${data.flwmtr_mesure_tp_etc || '-'} / ${data.flwmtr_knd || '-'}` + }; + }, [featureData?.values_]); // 피쳐데이터의 현재 레이어 이름 가져오는 함수 const getSelectedLayers = () => { @@ -37,7 +98,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, featureLayer = layer; // 피처가 속한 레이어 찾기 } }); - console.log("featureLayer.get('lyrTyNm');", featureLayer.get('lyrTyNm')); + // console.log("featureLayer.get('lyrTyNm');", featureLayer.get('lyrTyNm')); // SearchModal 클릭 이벤트와 Gis 클릭 이벤트 구분하기 if (featureLayer.get('lyrNm') === null) { if (featureData.lyrTypeNm === 'tb_il_swage_prcs_fclt') result = '하수처리시설'; @@ -64,15 +125,17 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, useEffect(() => { const fetchData = async () => { - if (featureId) { + if (!featureId) return; + + try { const selectedLayerName = getSelectedLayers(); // 레이어 이름 가져오기 setLayerNm(selectedLayerName); // 상태 업데이트 - const layerId = featureId.split('.')[0] + console.log('gis data', featureData.values_); - // let data = await getData(`v1/fclts/${featureId}?lyrTypeNm=${layerId}`); let data = featureData.values_; - console.log('api data', data) setfetchData(data) + setTableData(data); + switch (selectedLayerName) { case '하수처리시설': setHeadlist(SwtrmtFcltCol); @@ -88,16 +151,23 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, setButtonChk(false) setChkMainSsc(true) - // 데이터 전처리: 합쳐진 필드 추가 - data = data ? { - ...data, - discharge_pipe_spec: `직경: ${data.pipe_diameter || '-'} / 높이: ${data.pipe_hg || '-'} / 길이: ${data.pipe_lt || '-'}` - } : {}; + setfetchData([processedData]) + setTableData(processedData); + setIsLoading(false); // 기본 렌더링 완료 // 수량/수질측정 정보 데이터 호출 - const additionalResponse = await getData(`v1/onsemiro/swgRain/${data.adst_dstr_cd}/${data.manage_no}/mntrngs`); - setMntrngsData(additionalResponse.content); - break; + if (data?.adst_dstr_cd && data?.manage_no) { + fetchAdditionalData(data.adst_dstr_cd, data.manage_no) + .then(response => { + console.log('additionalResponse', response); + setMntrngsData(response || []); + }) + .catch(error => { + console.error('추가 데이터 로딩 실패:', error); + setMntrngsData(false); + }); + } + return; case '우수토실': setButtonChk(true) break; @@ -122,11 +192,33 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, default: break; } - setTableData(data); - }; - } + } catch (error) { + console.error('데이터 로딩 중 오류:', error); + } + }; + fetchData(); - }, [featureId]); + }, [featureId, processedData]); + + // 🎯 추가 데이터는 별도 useEffect로 비동기 로딩 + useEffect(() => { + if (layerNm === '중점우수토실' && featureData?.values_) { + const data = featureData.values_; + + if (data?.adst_dstr_cd && data?.manage_no) { + // 🎯 추가 데이터를 비동기로 로딩 (UI 블로킹 없음) + fetchAdditionalData(data.adst_dstr_cd, data.manage_no) + .then(response => { + console.log('추가 데이터 로딩 완료:', response); + setMntrngsData(response || []); + }) + .catch(error => { + console.error('추가 데이터 최종 로딩 실패:', error); + setMntrngsData(false); + }); + } + } + }, [layerNm, featureData?.values_, fetchAdditionalData]); // 저장 시 모달 띄우기 const handleSaveClick = () => { @@ -142,6 +234,25 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, setIsModalOpen(false); // 모달 닫기 }; + // 🎯 모달 열기/닫기 함수들 + const openModalInfil = () => { + setBtnTxt("Infil"); + setIsModalInfil(true); + }; + + const closeModalInfil = () => { + setIsModalInfil(false); + }; + + const openModalRdii = () => { + setBtnTxt("RDII"); + setIsModalRdii(true); + }; + + const closeModalRdii = () => { + setIsModalRdii(false); + }; + const btnClickEvent = async () => { const bodyData: Record = {} if (buttonChk && contentRef.current) { @@ -174,21 +285,45 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, } } } + + // 레이어명에 따라 상세페이지 렌더링 const ContentComponent = layerNm ? getLayerContentComponent(layerNm) : null; + return (