From 92cdbd8a7b11f68579d0525096e9871e99ca8c56 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 11 Aug 2025 13:03:43 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=EC=98=A4=EC=88=98=EB=B0=9C=EC=83=9D?= =?UTF-8?q?=EB=9F=89=20=EC=A1=B0=EC=B9=98=20=EC=A7=84=ED=96=89=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(with-layout)/sewageOccur/modal/page.tsx | 39 +++++++++++++++++++ apps/fo/app/store/sewageOccur/index.tsx | 25 +++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/apps/fo/app/pages/(with-layout)/sewageOccur/modal/page.tsx b/apps/fo/app/pages/(with-layout)/sewageOccur/modal/page.tsx index 06ca11cc1..c9c883041 100644 --- a/apps/fo/app/pages/(with-layout)/sewageOccur/modal/page.tsx +++ b/apps/fo/app/pages/(with-layout)/sewageOccur/modal/page.tsx @@ -5,6 +5,7 @@ import { DateMonthPicker, UiTable } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; import { Table, TableCell, TableHead, TableRow } from "@common/components/ui"; import { useEffect, useState } from "react"; +import { start } from "repl"; export default function SewageOccurModal(props: { fcltyName: string; facilityCd : string; searchYear : string}) { @@ -13,10 +14,15 @@ export default function SewageOccurModal(props: { fcltyName: string; facilityCd // searchYear 유효성 검사 및 기본값 설정 const validSearchYear = props.searchYear && /^\d{4}$/.test(props.searchYear) ? props.searchYear : new Date().getFullYear().toString(); + const [startYearMonth, setStartYearMonth] = useState(validSearchYear+'01'); const [endYearMonth, setEndYearMonth] = useState(validSearchYear+'12'); const [detailList, setDetailList] = useState([]); + // 가능한 시작년월 설정 + const [enableStartMonth, setEnableStartMonth] = useState(['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월']); + const [enableEndMonth, setEnableEndMonth] = useState(['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월']); + const { upHeadListSewageOccurDetail, gridListSewageOccurDetail @@ -33,6 +39,37 @@ export default function SewageOccurModal(props: { fcltyName: string; facilityCd useEffect(() => { searchSewageOccurDetailList(props.facilityCd, validSearchYear, startYearMonth, endYearMonth); + }, [startYearMonth, endYearMonth, searchSewageOccurDetailList, props.facilityCd, validSearchYear]); + + useEffect(() => { + const standdardMonthList = ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'] + + // 시작년월이 바뀌면 선택가능한 년월 데이터가 변경돼야 한다. + if(startYearMonth){ + const startMm = startYearMonth.substring(4, 6); + + standdardMonthList.forEach((month) => { + setEnableStartMonth((prev) => { + if (parseInt(month.replaceAll('월', '')) < parseInt(startMm)) { + return prev.filter((m) => m !== month); + } + return prev; + }); + }); + } + + if(endYearMonth){ + const endMm = endYearMonth.substring(4, 6); + standdardMonthList.forEach((month) => { + setEnableEndMonth((prev) => { + if (parseInt(month.replaceAll('월', '')) > parseInt(endMm)) { + return prev.filter((m) => m !== month); + } + return prev; + }); + }); + } + }, [startYearMonth, endYearMonth]); return ( @@ -63,6 +100,7 @@ export default function SewageOccurModal(props: { fcltyName: string; facilityCd selectClass="ml-5 w-[100px]" startYear={1900} yearReadOnly={true} + enableMonth={enableStartMonth} />~ diff --git a/apps/fo/app/store/sewageOccur/index.tsx b/apps/fo/app/store/sewageOccur/index.tsx index 5feb91f94..f97520d17 100644 --- a/apps/fo/app/store/sewageOccur/index.tsx +++ b/apps/fo/app/store/sewageOccur/index.tsx @@ -222,6 +222,7 @@ export const sewageOccurReducer:(set:any)=>SewageOccurActions=(set: any) => { }, })); const res = await fetchSewageOccurList(pageNo, pageSize, searchYear, part, nameCd, fcltCpct, operationValue, fcltyNm); + const excelDownList: ExcelType[] = []; const data = res.data; // Promise.all을 사용하여 모든 비동기 작업이 완료될 때까지 기다림 const calData = await Promise.all(data.map(async (item: SewageOccurProps) => { @@ -301,6 +302,28 @@ export const sewageOccurReducer:(set:any)=>SewageOccurActions=(set: any) => { item.estimationSewage = parseFloat((item.waterUsage * (item.swageChangePercent/100)).toFixed(2)); return item; })); + + if(pageSize>5000){ + calData.forEach((item: SewageOccurProps) => { + excelDownList.push({ + 시설코드: item.bscFctCd, + 권역: item.rvbsn, + 단위유역: item.lgbsn, + 중권역: item.mdmbsn, + 환경청: item.rgnNm, + 시설명: item.bscFctNm, + 시도: item.sido, + 시군구: item.sigungo, + 주소: item.daddr, + 시설용량: item.fctCpc, + 물사용량: item.waterUsage, + 오수전환율: item.swageChangePercent, + 오수발생량추정: item.estimationSewage, + 연간하수유입량 : item.sewageInputYear, + 청천시하수유입량 : item.sewageInputChungchun, + }); + }); + } // 조회 데이터 설정 set((state: any) => ({ ...state, @@ -311,7 +334,7 @@ export const sewageOccurReducer:(set:any)=>SewageOccurActions=(set: any) => { isLoading : false, }:{ ...state.SewageOccur, - excelList: [], + excelList: excelDownList, isLoading : false, }, })) From 83880cea6d25ea6356eddf8ca925d94e6886058d Mon Sep 17 00:00:00 2001 From: jsyang Date: Mon, 11 Aug 2025 14:04:31 +0900 Subject: [PATCH 2/3] =?UTF-8?q?-=20=EC=98=A4=EC=88=98=EB=B0=9C=EC=83=9D?= =?UTF-8?q?=EB=9F=89=20=EC=B6=94=EC=A0=95=20=EA=B8=B0=EA=B0=84=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EB=AC=B8=EC=A0=9C=ED=95=B4=EA=B2=B0=20-=20geoserve?= =?UTF-8?q?r=20=EC=A3=BC=EC=86=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + apps/fo/.env.development | 3 +- apps/fo/.env.local | 3 +- apps/fo/.env.production | 3 +- .../(with-layout)/sewageOccur/modal/page.tsx | 29 ++++++------------- 5 files changed, 16 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 6b685ea40..945daeeba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # turbo .turbo/ +.vscode/**/* # dependencies node_modules/ diff --git a/apps/fo/.env.development b/apps/fo/.env.development index d362ecf94..de81dde66 100644 --- a/apps/fo/.env.development +++ b/apps/fo/.env.development @@ -5,7 +5,8 @@ NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1 #NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1 NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 -NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver +#NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver +NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver NEXT_PUBLIC_CLAIM_NAME = Authorization NEXT_PUBLIC_AUTH_USER_ID = token-id diff --git a/apps/fo/.env.local b/apps/fo/.env.local index 4313985b8..2e937af0c 100644 --- a/apps/fo/.env.local +++ b/apps/fo/.env.local @@ -5,7 +5,8 @@ NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1 #NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1 NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 -NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver +#NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver +NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver NEXT_PUBLIC_CLAIM_NAME = Authorization NEXT_PUBLIC_AUTH_USER_ID = token-id diff --git a/apps/fo/.env.production b/apps/fo/.env.production index aa2cb2c90..797328765 100644 --- a/apps/fo/.env.production +++ b/apps/fo/.env.production @@ -18,7 +18,8 @@ NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1 #NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1 NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 -NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver +#NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver +NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver NEXT_PUBLIC_CLAIM_NAME = Authorization NEXT_PUBLIC_AUTH_USER_ID = token-id diff --git a/apps/fo/app/pages/(with-layout)/sewageOccur/modal/page.tsx b/apps/fo/app/pages/(with-layout)/sewageOccur/modal/page.tsx index c9c883041..4081b4547 100644 --- a/apps/fo/app/pages/(with-layout)/sewageOccur/modal/page.tsx +++ b/apps/fo/app/pages/(with-layout)/sewageOccur/modal/page.tsx @@ -5,8 +5,6 @@ import { DateMonthPicker, UiTable } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; import { Table, TableCell, TableHead, TableRow } from "@common/components/ui"; import { useEffect, useState } from "react"; -import { start } from "repl"; - export default function SewageOccurModal(props: { fcltyName: string; facilityCd : string; searchYear : string}) { // 그리드 관련 @@ -19,8 +17,8 @@ export default function SewageOccurModal(props: { fcltyName: string; facilityCd const [startYearMonth, setStartYearMonth] = useState(validSearchYear+'01'); const [endYearMonth, setEndYearMonth] = useState(validSearchYear+'12'); const [detailList, setDetailList] = useState([]); + // 가능한 시작년월 설정 - const [enableStartMonth, setEnableStartMonth] = useState(['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월']); const [enableEndMonth, setEnableEndMonth] = useState(['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월']); const { @@ -43,13 +41,17 @@ export default function SewageOccurModal(props: { fcltyName: string; facilityCd useEffect(() => { const standdardMonthList = ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'] - + setEnableEndMonth(standdardMonthList); // 시작년월이 바뀌면 선택가능한 년월 데이터가 변경돼야 한다. if(startYearMonth){ const startMm = startYearMonth.substring(4, 6); + if(endYearMonth < startYearMonth) { + setEndYearMonth(startYearMonth); + } + standdardMonthList.forEach((month) => { - setEnableStartMonth((prev) => { + setEnableEndMonth((prev) => { if (parseInt(month.replaceAll('월', '')) < parseInt(startMm)) { return prev.filter((m) => m !== month); } @@ -57,20 +59,7 @@ export default function SewageOccurModal(props: { fcltyName: string; facilityCd }); }); } - - if(endYearMonth){ - const endMm = endYearMonth.substring(4, 6); - standdardMonthList.forEach((month) => { - setEnableEndMonth((prev) => { - if (parseInt(month.replaceAll('월', '')) > parseInt(endMm)) { - return prev.filter((m) => m !== month); - } - return prev; - }); - }); - } - - }, [startYearMonth, endYearMonth]); + }, [endYearMonth, startYearMonth]); return ( <> @@ -100,7 +89,7 @@ export default function SewageOccurModal(props: { fcltyName: string; facilityCd selectClass="ml-5 w-[100px]" startYear={1900} yearReadOnly={true} - enableMonth={enableStartMonth} + enableMonth={['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월']} />~ Date: Mon, 11 Aug 2025 14:09:26 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/fo/app/pages/(map-layout)/map/page.tsx | 18 +++- .../src/ui/map/searchBar/searchBar.tsx | 85 ++++++++++++------- 2 files changed, 71 insertions(+), 32 deletions(-) diff --git a/apps/fo/app/pages/(map-layout)/map/page.tsx b/apps/fo/app/pages/(map-layout)/map/page.tsx index ad3f70295..984a8afa7 100644 --- a/apps/fo/app/pages/(map-layout)/map/page.tsx +++ b/apps/fo/app/pages/(map-layout)/map/page.tsx @@ -3,7 +3,7 @@ import Gis from "@common/business_components/ui/map/Gis"; import GisApp from "@common/business_components/ui/map/lib/gis.core"; import { useEffect, useRef, useState } from "react"; -import { ProcSearchBar } from "@common/business_components/ui/map/searchBar"; +import { SearchBar } from "@common/business_components/ui/map/searchBar"; // OpenLayers overlay/extent 유틸 import Overlay from "ol/Overlay"; @@ -278,7 +278,19 @@ const Map = () => { setIsSearchModalOpen(false); }; - // (선택) 다른 위치 클릭 시 오버레이 닫고 싶다면 Gis 쪽에서 지도 클릭 이벤트 받아 여기서 제거하는 훅을 연결하면 됨. + const onInit = () => { + + const map = gisMaps.current; + if (!map) { + console.warn("지도가 초기화되지 않았습니다."); + return; + } + + if (overlayRef.current) { + map.removeOverlay?.(overlayRef.current); + overlayRef.current = null; + } + } return (
@@ -299,7 +311,7 @@ const Map = () => { onCheckedLayersChange={undefined} imgVisble={false} searchBar={ - + } waterDepth={undefined} /> diff --git a/packages/common/business_components/src/ui/map/searchBar/searchBar.tsx b/packages/common/business_components/src/ui/map/searchBar/searchBar.tsx index 3f5fc1820..623758ec3 100644 --- a/packages/common/business_components/src/ui/map/searchBar/searchBar.tsx +++ b/packages/common/business_components/src/ui/map/searchBar/searchBar.tsx @@ -26,12 +26,14 @@ interface SearchBarProps { gisMaps: React.RefObject; facilityTypes: { text: string; val: string }[]; onFeatureFocus: any; + onInit?: any; } export const SearchBar = ({ gisMaps, facilityTypes, onFeatureFocus, + onInit = () => { }, }: SearchBarProps) => { const [sidoValue, setSidoValue] = useState("0"); const [sigunValue, setSigunValue] = useState("0"); @@ -134,10 +136,8 @@ export const SearchBar = ({ text: f.properties.swtrmt_fclt_nm ?? "(이름없음)", val: f.id, })); - } else if ( - type === "tb_wr_bdst_ruse_fclt" // 재이용시설 - ) { - //재이용에서 참조하는 하수처리시설 목록 + } else if (type === "tb_wr_bdst_ruse_fclt") { + // 재이용시설 const procRes = await fetch("/reuse-service/reuseFc/pubFc"); const procJson = await procRes.json(); const procData = procJson.data; @@ -147,8 +147,8 @@ export const SearchBar = ({ return code; }); - //처리시설명으로 처리시설코드 조회 - let procUrl2 = "/operation-service/prcsFclt/ruseInfo/1/1000/A/{sggCd}/0/1/SW/{fcltyNm}"; + let procUrl2 = + "/operation-service/prcsFclt/ruseInfo/1/1000/A/{sggCd}/0/1/SW/{fcltyNm}"; procUrl2 = procUrl2 .replace("{sggCd}", sigunValue) .replace("{fcltyNm}", encodeURIComponent(fcltyNm)); @@ -161,9 +161,10 @@ export const SearchBar = ({ body: JSON.stringify(procResult), }); const procJson2 = await procRes2.json(); - const procResult2 = (procJson2.data || []).map((item: any) => item.bscFctCd); + const procResult2 = (procJson2.data || []).map( + (item: any) => item.bscFctCd + ); - //최종 처리시설코드로 재이용시설조회 const res = await fetch(url, { method: "POST", headers: { @@ -177,20 +178,16 @@ export const SearchBar = ({ text: item.ruseUtztnFcltNm ?? "(이름없음)", val: item.pbnstRuseFcltId, })); - - } else if ( - type === "tb_wr_ruse_fclt_dmd_co" //수요처 - ) { + } else if (type === "tb_wr_ruse_fclt_dmd_co") { + // 수요처 const res = await fetch(url); const json = await res.json(); list = (json.data || []).map((item: any) => ({ text: item.dmdCoNm ?? "(이름없음)", val: item.dmdInfoId, })); - } else if ( - type === "tb_wr_tmpr_dmd_co" //잠재수요처 - ) { - // GET 방식 + } else if (type === "tb_wr_tmpr_dmd_co") { + // 잠재수요처 (GET) const res = await fetch(url); const json = await res.json(); list = (json.data || []).map((item: any) => ({ @@ -198,7 +195,7 @@ export const SearchBar = ({ val: item.fctryMngSn, })); } else { - // GET 방식 + // 기타 (GET) const res = await fetch(url); const json = await res.json(); list = (json.data || []).map((item: any) => ({ @@ -222,14 +219,12 @@ export const SearchBar = ({ setSearchList(newList); }; - //해당 시설물로 이동 + //해당 시설물로 이동 const handleFeatureFocus = (data: any) => { - let featureId = data.fid; const layerNm = data.layerNm; if (!isNaN(Number(featureId))) { - // 숫자면 문자열로 변환 featureId = String(featureId); } @@ -246,12 +241,9 @@ export const SearchBar = ({ console.warn("지도가 초기화되지 않았습니다."); return; } - // 항상 같은 select 객체 사용 const select = GisApp.Module.select as any; if (!select) return; - // 1. 기존 선택 초기화 - //select.resetSelectedFeature(); - // 2. data가 OpenLayers Feature 인스턴스인지 확인 + let foundFeature: any = null; map @@ -264,7 +256,7 @@ export const SearchBar = ({ if (feature) foundFeature = feature; } }); - // 3. 피처 선택 + if (foundFeature) { const geometry = foundFeature.getGeometry(); if (geometry) { @@ -277,13 +269,35 @@ export const SearchBar = ({ nearest: true, }); } else { + alert("위치정보를 찾을 수 없습니다."); console.warn("geometry를 찾을 수 없습니다:", data); } } else { console.warn("Feature를 찾을 수 없습니다:", data); } - onFeatureFocus(foundFeature); + if (onFeatureFocus) { + onFeatureFocus(foundFeature); + } + }; + + // 초기화(검색 조건 리셋) + const handleInit = () => { + // 시설 구분 기본값: 단일 항목이면 그 값, 아니면 "0" + const defaultFacilityType = + facilityTypes?.length === 1 && facilityTypes[0] + ? facilityTypes[0].val + : "0"; + + // 시도/시군구/시설구분/시설명/검색목록 초기화 + setSidoValue("0"); + initializeSigunData("0"); // 시군구 목록도 "시군구/0"로 리셋되고, sigunValue도 "0"으로 맞춤 + setFacilityType(defaultFacilityType); + setFcltyNm(""); + setSearchList([]); + + // 필요 시 외부 초기화 콜백 실행 + onInit?.(); }; return ( @@ -313,7 +327,9 @@ export const SearchBar = ({ selectClass="rounded-lg bg-white" label="" selectArray={sidoData} - onSelectValue={(val: any) => onSelectValue(val.toString(), "ctpvCd")} + onSelectValue={(val: any) => + onSelectValue(val.toString(), "ctpvCd") + } value={sidoValue} /> onSelectValue(val.toString(), "sggCd")} + onSelectValue={(val: any) => + onSelectValue(val.toString(), "sggCd") + } value={sigunValue} /> { - // 선택된 항목이 있을 경우 onFeatureSelect 실행 const type = facilityType; handleFeatureFocus({ layerNm: type, @@ -352,6 +369,16 @@ export const SearchBar = ({ > 조회 + {onInit && ( + + )}