From 97727908fa3ff130b8bf4f920f90566c29fcb9f1 Mon Sep 17 00:00:00 2001 From: leedj312 Date: Mon, 28 Jul 2025 10:32:58 +0900 Subject: [PATCH 01/71] =?UTF-8?q?=EC=88=98=EC=9A=94=EC=B2=98=20nullish=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wr/app/store/matDmd/coMatRvw/type.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wr/app/store/matDmd/coMatRvw/type.tsx b/apps/wr/app/store/matDmd/coMatRvw/type.tsx index 34990c6b3..58d539538 100644 --- a/apps/wr/app/store/matDmd/coMatRvw/type.tsx +++ b/apps/wr/app/store/matDmd/coMatRvw/type.tsx @@ -98,7 +98,7 @@ export const DmdCoReqSchema = z.object({ z.object({ ruseUtztnFcltNm: z.string(), ruseUtztnFcltCd: z.string(), - }) + }).nullish() ).nullish(), totalConstCost: z.number().nullish(), }); From 662a461cfaad71ed04f01bad498712cb61589d93 Mon Sep 17 00:00:00 2001 From: leedj312 Date: Mon, 28 Jul 2025 18:26:43 +0900 Subject: [PATCH 02/71] =?UTF-8?q?=ED=95=98=EC=88=98=EC=9E=AC=EC=9D=B4?= =?UTF-8?q?=EC=9A=A9=20=ED=98=84=ED=99=A9=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EC=85=98=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruseFcltMng/fcltSttn/pbnstSttn/page.tsx | 26 ++++---- .../ruseFcltMng/fcltSttn/pbnstSttn/index.tsx | 65 ++++++++++++------- 2 files changed, 56 insertions(+), 35 deletions(-) diff --git a/apps/wr/app/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx b/apps/wr/app/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx index eb7a398b1..5208edc02 100644 --- a/apps/wr/app/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx @@ -33,6 +33,9 @@ export default function PbnstSttn() { } = useWrStore((state) => state); const { pbnstSttnRes, fclts, pbnstSttnResCount } = pbnstSttn; + + const [isReset, setIsReset] = useState(false); + //const [tableIndex, setTableIndex] = useState(1) const ref = useRef({} as ReturnType); const [searchYear, setSearchYear] = useState( String(new Date().getFullYear()) @@ -84,7 +87,7 @@ export default function PbnstSttn() { if (!pbnstSttn.isInit) { getFlcts(); } - }, [pbnstSttn.isInit, pbnSttnInit, ref.current]); + }, [pbnstSttn.isInit]); useEffect(() => { if (pbnstSttnRes && pbnstSttnRes.length > 0) { setBdstTable(pbnstSttnRes); @@ -94,7 +97,8 @@ export default function PbnstSttn() { }, [pbnstSttnRes]); useEffect(() => { - pbnSttnInit("1", "15", ref.current, searchYear, fclts); + + pbnSttnInit("1", "15", ref.current, searchYear, fclts); }, [fclts]); useEffect(() => { @@ -157,16 +161,11 @@ export default function PbnstSttn() { /> } - queryClick={function (returnType: ReturnType): void { - pbnstSttnSearch( - false, - "1", - "15", - returnType, - pbnRuseNm, - searchYear, - fclts - ); + queryClick={ ()=> { + // pbnSearch(returnType); + + //setTableIndex(0) + setIsReset(true); }} excelClick={excelClick} initClick={function (): void { @@ -200,7 +199,10 @@ export default function PbnstSttn() { searchYear, fclts ); + setIsReset(false); }} + + resetPageNo={isReset} > diff --git a/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx index 542622d34..7f905dd49 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx @@ -95,43 +95,59 @@ const hasuListFetch = async ( year: string, flcts: string[] ) => { - let url = `/prcsFclt/ruseInfo/`; + let url = ``; + let filteredFclt = flcts; + const filterData = reurnValue.sigunValue !== '0' ? reurnValue.sigunValue : reurnValue.sidoValue + + if(filterData !== '0') + filteredFclt= flcts.filter((fclt: string) => { + return filterData.length==2 ?fclt.slice(0, 2) === filterData:fclt.slice(0, 5) === filterData + }) if (reurnValue.partValue === "A") { - url += `${page}/${total}/${reurnValue.partValue}/${ + url += `${reurnValue.partValue}/${ // reurnValue.sidoValue ? reurnValue.sidoValue : reurnValue.sigunValue reurnValue.sigunValue !== '0' ? reurnValue.sigunValue : reurnValue.sidoValue }/0/1/SW` + + if(filterData !== '0') + filteredFclt= flcts.filter((fclt: string) => { + return filterData.length==2 ?fclt.slice(0, 2) === filterData:fclt.slice(0, 5) === filterData + }) } else if (reurnValue.partValue === "R") { - url += `${page}/${total}/${reurnValue.partValue}/${reurnValue.regionValue + url += `${reurnValue.partValue}/${reurnValue.regionValue ? reurnValue.regionValue : reurnValue.unitWaterShedValue ? reurnValue.unitWaterShedValue : reurnValue.middleRegionValue }/0/1/SW` } else if (reurnValue.partValue === 'G') { - - - url += `${page}/${total}/${reurnValue.envPartValue}/${reurnValue.sigunValue !== '0' ? reurnValue.sigunValue : reurnValue.sidoValue}/0/1/SW` - + url += `${reurnValue.envPartValue}/${reurnValue.sigunValue !== '0' ? reurnValue.sigunValue : reurnValue.sidoValue}/0/1/SW` + if(filterData !== '0') + filteredFclt= flcts.filter((fclt: string) => { + return filterData.length==2 ?fclt.slice(0, 2) === filterData:fclt.slice(0, 5) === filterData + }) + } else { - url += `1/2/A/0/0/1/SW` + url += `A/0/0/1/SW` } if (searchNm && searchNm?.length > 0) { url += "/" + searchNm } - const pbnList = await postFectch(url, flcts, fetchType.COMMON); + const pbnList = await postFectch( `/prcsFclt/ruseInfo/1/${filteredFclt.length}/`+url, filteredFclt, fetchType.COMMON); + + + const checkRuseFcltList = searchNm && searchNm?.length > 0?pbnList.data.map((item)=> item.bscFctCd ):filteredFclt const getRuseList = await postFectch( - `/reuseFc/pubDetailList/${year}`, - flcts, + `/reuseFc/pubDetailList/${year}/${filterData}/${page}/${total}`, + checkRuseFcltList, fetchType.WR ); const checkWrRegListRes = getRuseList.data; - const data: WaterReuseFacility[] = checkWrRegListRes.map( (item: FcPrfType) => { const findData = pbnList.data.find( @@ -140,7 +156,6 @@ const hasuListFetch = async ( ); if (findData) { - const usage = item.prfList.reduce( (acc, cur) => { acc.inUsage += @@ -197,7 +212,11 @@ const hasuListFetch = async ( } ).filter(Boolean); - return data; + + + + + return {data:data, count:getRuseList.count}; }; export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( @@ -237,8 +256,7 @@ export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( fclts: string[] ) => { try { - - + const pbnstSttnRes = await hasuListFetch( page, total, @@ -250,7 +268,7 @@ export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( // if (Number(total) > 9000) { if (isExcel) { - const pbnstExcel = excelPbnstSttnParsing(pbnstSttnRes); + const pbnstExcel = excelPbnstSttnParsing(pbnstSttnRes.data); set((state: pbnstSttnState) => { return { ...state, @@ -267,8 +285,8 @@ export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( ...state, pbnstSttn: { ...state.pbnstSttn, - pbnstSttnRes: pbnstSttnRes, - pbnstSttnResCount: pbnstSttnRes.length + pbnstSttnRes: pbnstSttnRes.data, + pbnstSttnResCount: pbnstSttnRes.count // isInit: false, }, }; @@ -295,13 +313,14 @@ export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( fclts ); + set((state: pbnstSttnState) => { return { ...state, pbnstSttn: { ...state.pbnstSttn, - pbnstSttnRes: pbnstSttnRes, - pbnstSttnResCount: pbnstSttnRes.length, + pbnstSttnRes: pbnstSttnRes.data, + pbnstSttnResCount: pbnstSttnRes.count, isInit: false, pbnstDetail: {} as customPbnst }, @@ -365,8 +384,8 @@ export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( ...state, pbnstSttn: { ...state.pbnstSttn, - pbnstSearchData: pbnstSttnRes, - pbnstSttnResCount: pbnstSttnRes.length, + pbnstSearchData: pbnstSttnRes.data, + pbnstSttnResCount: pbnstSttnRes.count, }, }; }); From 64698fb97e39f5b35b0fd05012f94833dead1522 Mon Sep 17 00:00:00 2001 From: jyha00 Date: Tue, 29 Jul 2025 10:33:34 +0900 Subject: [PATCH 03/71] =?UTF-8?q?=EC=9E=AC=EC=9D=B4=EC=9A=A9=EC=8B=A4?= =?UTF-8?q?=EC=A0=81=20=ED=98=84=ED=99=A9=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EC=85=98=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../prfmncSttn/pbnstPrfmncSttn/index.tsx | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/apps/wr/app/store/prfmncSttn/pbnstPrfmncSttn/index.tsx b/apps/wr/app/store/prfmncSttn/pbnstPrfmncSttn/index.tsx index 13093f98f..1a833313d 100644 --- a/apps/wr/app/store/prfmncSttn/pbnstPrfmncSttn/index.tsx +++ b/apps/wr/app/store/prfmncSttn/pbnstPrfmncSttn/index.tsx @@ -50,42 +50,56 @@ const hasuListFetch = async ( page: number, returnType: ReturnType, searchNm: string | undefined, - year: string, + fromDate: string, + toDate: string, flcts: string[] ) => { - let url = `/prcsFclt/ruseInfo/`; + let url = ''; + let filteredFclt = flcts; + const filterData = returnType.sigunValue !== '0' ? returnType.sigunValue : returnType.sidoValue + if (filterData !== '0') + filteredFclt = flcts.filter((fclt: string) => { + return filterData.length == 2 ? fclt.slice(0, 2) === filterData : fclt.slice(0, 5) === filterData + }) if (returnType.partValue === "A") { - url += `${page}/5/${returnType.partValue}/${ + url += `${returnType.partValue}/${ // returnType.sidoValue ? returnType.sidoValue : returnType.sigunValue returnType.sigunValue !== '0' ? returnType.sigunValue : returnType.sidoValue }/0/1/SW` + + if (filterData !== '0') + filteredFclt = flcts.filter((fclt: string) => { + return filterData.length == 2 ? fclt.slice(0, 2) === filterData : fclt.slice(0, 5) === filterData + }) } else if (returnType.partValue === "R") { - url += `${page}/5/${returnType.partValue}/${returnType.regionValue + url += `${returnType.partValue}/${returnType.regionValue ? returnType.regionValue : returnType.unitWaterShedValue ? returnType.unitWaterShedValue : returnType.middleRegionValue }/0/1/SW` } else if (returnType.partValue === 'G') { - url += `${page}/5/${returnType.envPartValue}/${returnType.sigunValue !== '0' ? returnType.sigunValue : returnType.sidoValue}/0/1/SW` - + url += `${returnType.envPartValue}/${returnType.sigunValue !== '0' ? returnType.sigunValue : returnType.sidoValue}/0/1/SW` + if (filterData !== '0') + filteredFclt = flcts.filter((fclt: string) => { + return filterData.length == 2 ? fclt.slice(0, 2) === filterData : fclt.slice(0, 5) === filterData + }) } else { - url += `1/2/A/0/0/1/SW` + url += `A/0/0/1/SW` } - if (searchNm && searchNm?.length > 0) { url += "/" + searchNm } - - const pbnList = await postFectch(url, flcts, fetchType.COMMON); + const pbnList = await postFectch(`/prcsFclt/ruseInfo/1/${filteredFclt.length}/` + url, filteredFclt, fetchType.COMMON); + const checkRuseFcltList = searchNm && searchNm?.length > 0 ? pbnList.data.map((item) => item.bscFctCd) : filteredFclt const getRuseList = await postFectch( - `/reuseFc/pubDetailList/${year}`, - flcts, + `/reuseFc/pubDetailPrfList/${fromDate}/${toDate}/${filterData}/${page}/5`, + checkRuseFcltList, fetchType.WR ); - let count = 0; const checkWrRegListRes = getRuseList.data; + const data: WaterReuseFacility[] = checkWrRegListRes.map( (item: FcPrfType) => { if (!item.prfList || item.prfList.length === 0) { @@ -96,7 +110,6 @@ const hasuListFetch = async ( fclt.bscFctCd === item.pbnstRuseFcltId.substring(0, 11) ); if (findData) { - count++; return { sido: findData?.sido || '-', gugun: findData?.gugun || '-', @@ -104,12 +117,12 @@ const hasuListFetch = async ( ruseUtztnFcltNm: item?.ruseUtztnFcltNm || '-', pbnstRuseFcltId: item?.pbnstRuseFcltId || '-', bscFctCd: findData?.bscFctCd || '-', - count + prfList: item.prfList, } } } ).filter(Boolean); - return data; + return {data: data, count: getRuseList.count}; } export const pbnstPrfmncSttnActionsExport: (set: any) => pbnstPrfmncSttnActions = ( @@ -175,29 +188,17 @@ export const pbnstPrfmncSttnActionsExport: (set: any) => pbnstPrfmncSttnActions page, returnType, fcltNm, + fromDate, toDate, fclts); - // const bscFctCdList = prfmncRes.map((item: any) => { - // return item.bscFctCd; - // }) - const bscFctCdList = prfmncRes - .map((item: any) => item.bscFctCd) - .filter((value, index, self) => self.indexOf(value) === index); - // const bscFctCdList = [...new Set(prfmncRes.map((item: any) => item.bscFctCd))]; - - const res = await postFectch( - `/reuseFc/pubDetailList/${fromDate}/${toDate}`, - bscFctCdList, - `wr`, - ) let inUsageSum = 0; let outUsageSum = 0; let year = ""; const yearsData = [] as any - res.data.forEach((item: any) => { - if (item.prfList.length === 0) { + prfmncRes.data.forEach((item: any) => { + if (!item.prfList || item.prfList.length === 0) { const year: string = ""; // 연도 정보가 없으면 빈 문자열, 필요시 item에서 추출. const yearVal: { [key: string]: any } = { year: year, pbnstRuseFcltId: item.pbnstRuseFcltId ?? "", ruseUtztnFcltNm: item.ruseUtztnFcltNm ?? "" }; yearVal[`${year}inUsage`] = 0; @@ -205,7 +206,7 @@ export const pbnstPrfmncSttnActionsExport: (set: any) => pbnstPrfmncSttnActions yearVal[`${year}total`] = 0; yearsData.push(yearVal); } - item.prfList.forEach((acc) => { + item.prfList?.forEach((acc: any) => { year = acc.ym?.substring(0, 4) ?? ""; inUsageSum = (acc.wshwtr ?? 0) + //조경용수 @@ -273,7 +274,7 @@ export const pbnstPrfmncSttnActionsExport: (set: any) => pbnstPrfmncSttnActions // prfmncRes 정보 merge const resData = Object.values(mergedMap).map((item: any) => { - const match = prfmncRes.find((res) => res.pbnstRuseFcltId === item.pbnstRuseFcltId); + const match = prfmncRes.data.find((res) => res.pbnstRuseFcltId === item.pbnstRuseFcltId); return match ? { ...item, ...match } : item; }); @@ -282,10 +283,10 @@ export const pbnstPrfmncSttnActionsExport: (set: any) => pbnstPrfmncSttnActions pbnstPrfmncSttn: { ...state.pbnstPrfmncSttn, // prfmncRes: prfmncRes, - prfmncResCount: fclts.length - 5, + prfmncResCount: prfmncRes.count, isInit: false, prfmncRes: resData, - rawData: res.data + rawData: prfmncRes.data }, })); } catch (errorHasu) { From c30de7f40d6b810cc19d5cd23a581d012116899f Mon Sep 17 00:00:00 2001 From: jyha00 Date: Tue, 29 Jul 2025 11:10:22 +0900 Subject: [PATCH 04/71] =?UTF-8?q?=EC=9E=AC=EC=9D=B4=EC=9A=A9=EC=8B=A4?= =?UTF-8?q?=EC=A0=81=20=ED=98=84=ED=99=A9=20=EC=97=91=EC=85=80=20=EB=8B=A4?= =?UTF-8?q?=EC=9A=B4=EB=A1=9C=EB=93=9C=20=EC=88=98=EC=A0=95,=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=EB=B2=84=ED=8A=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx b/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx index 8035d2875..ac77639a4 100644 --- a/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx @@ -150,9 +150,9 @@ export default function PbnstPrfmncSttn() { } const tableData = [...inUsageData, ...outUsageData, usageTotalObj]; setPrfData(tableData); - setExcelData(excelPrfmncSttnParsing(prfData ?? [])) - // }); + setExcelData(excelPrfmncSttnParsing(tableData ?? [])) } else { + setExcelData(excelPrfmncSttnParsing([])) setPrfData([]); } } @@ -605,7 +605,11 @@ export default function PbnstPrfmncSttn() { isResetPage(true) }} excelClick={excelClick} - initClick={function (): void { }} + initClick={function (): void { + setPbnRuseNm(""); + setFromDate(String(new Date().getFullYear())); + setToDate(String(new Date().getFullYear())) + }} btnClass={btnClass} ref={ref} isAdmin={false} /> From 913256cac4546dcf2d896e39c4574ba8698ea1f3 Mon Sep 17 00:00:00 2001 From: yschoi Date: Tue, 29 Jul 2025 15:16:45 +0900 Subject: [PATCH 05/71] =?UTF-8?q?=EC=9B=90=EB=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dm/src/entities/auth/api/login-service.ts | 1 - .../fo/src/entities/auth/api/login-service.ts | 2 - apps/wr/app/map/mainMap/page.tsx | 17 +--- apps/wr/app/map/newWindowPopup.tsx | 21 ++-- apps/wr/app/store/matDmd/coSttn/index.tsx | 2 - .../wr/src/entities/auth/api/login-service.ts | 1 - .../business_components/src/ui/map/Gis.js | 95 +------------------ .../business_components/src/ui/map/GisTree.js | 2 +- .../src/ui/map/lib/gis.layer.js | 2 - 9 files changed, 19 insertions(+), 124 deletions(-) diff --git a/apps/dm/src/entities/auth/api/login-service.ts b/apps/dm/src/entities/auth/api/login-service.ts index 614aadbf9..43ea1c33d 100644 --- a/apps/dm/src/entities/auth/api/login-service.ts +++ b/apps/dm/src/entities/auth/api/login-service.ts @@ -16,7 +16,6 @@ export class LoginApiService implements LoginService { public async login(payload: ILogin): Promise { try { - console.log(' ENDPOINT.USER_SERVICE.LOGIN ', ENDPOINT.USER_SERVICE.LOGIN) const response = await fetch( PROXY_HOST+ ENDPOINT.USER_SERVICE.LOGIN, { method: "POST", body: JSON.stringify({ diff --git a/apps/fo/src/entities/auth/api/login-service.ts b/apps/fo/src/entities/auth/api/login-service.ts index fafb467e7..3f2d958c6 100644 --- a/apps/fo/src/entities/auth/api/login-service.ts +++ b/apps/fo/src/entities/auth/api/login-service.ts @@ -17,8 +17,6 @@ export class LoginApiService implements LoginService { public async login(payload: ILogin): Promise { try { - console.log(' ENDPOINT.USER_SERVICE.LOGIN ', ENDPOINT.USER_SERVICE.LOGIN) - console.log(payload.userId+' '+payload.password) const response = await fetch( PROXY_HOST+ ENDPOINT.USER_SERVICE.LOGIN, { method: "POST", body: JSON.stringify({ diff --git a/apps/wr/app/map/mainMap/page.tsx b/apps/wr/app/map/mainMap/page.tsx index 1e9afd74b..cc71e69aa 100644 --- a/apps/wr/app/map/mainMap/page.tsx +++ b/apps/wr/app/map/mainMap/page.tsx @@ -61,13 +61,16 @@ const Map = () => { // 2. data가 OpenLayers Feature 인스턴스인지 확인 let foundFeature: any = null; + console.log(data) + //console.log(data.getId()) let featureId = data.fid; const layerName = data.layerNm; - if (layerName === "tb_wr_bdst_ruse_fclt") { + + if (layerName === "재이용시설") { // 재이용시설 featureId = `${layerName}.${data.searchPk}`; } else if ( //빗물이용시설, 중수도시설 - layerName === "tb_wr_rnwtr_utztn_fclt" || + layerName === "빗물이용시설" || layerName == "tb_wr_rswtr_fclt" ) { featureId = `${layerName}.${data.bdstInfoSn}`; @@ -136,16 +139,6 @@ const Map = () => { gisMaps={gisMaps} isMiniMap={false} searchView={undefined} - // searchView={(props: any) => ( - // - // ) - // } detailView={(props: any) => ( = ({ const [mngList, setMngList] = useState(coSttnRes || []); ///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 /////////////////////////////////////////////// const getSelectedLayers = () => { + console.log(1) let result = null; const layers = gisMap ? gisMap.getLayers().getArray().filter((l: any) => l.getVisible()) : []; // gisMap이 null인지 확인 후 레이어 가져오기 let featureLayer: { get: (key: string) => any } = { get: () => null }; @@ -127,6 +128,7 @@ const NewWindowPopup: React.FC = ({ } }); result = featureLayer.get('lyrNm'); + console.log(result) return result; }; @@ -156,15 +158,15 @@ const NewWindowPopup: React.FC = ({ } }; const detainSttn = async (featureCd: string, layerNm: string) => { - if (layerNm == 'tb_wr_rnwtr_utztn_fclt') { //빗물이용시설 + if (layerNm == '빗물이용시설') { //빗물이용시설 const res = await getFetch(fetchType.WR, `/reuseFc/rainyInfoList/1/20/0/${featureCd}`) setRainList(res) } - else if (layerNm == 'tb_wr_rswtr_fclt') { //중수도 시설 + else if (layerNm == '중수도시설') { //중수도 시설 const res = await getFetch(fetchType.WR, `/reuseFc/waterreuseFc/1/20/0/${featureCd}`) setRswtrList(res) } - else if(layerNm =='tb_wr_ruse_fclt_dmd_co'){ //수요처 + else if(layerNm =='수요처'){ //수요처 const res = await getFetch(fetchType.WR, `/reuseFc/request/1/20/0/${featureCd}`) setMngList(res) } @@ -192,6 +194,7 @@ const NewWindowPopup: React.FC = ({ },[mngList]) const layerId = getSelectedLayers(); + console.log(layerId) if (featureData && typeof featureData.getProperties === "function") { const selectedVal = featureData.getProperties(); let featureCd = layerId == '하수재이용시설' ? selectedVal?.swtrmt_fclt_cd : layerId == '빗물이용시설' || layerId == '중수도시설' || layerId == '온배수시설' || layerId == '수요처' ? selectedVal?.bdst_nm : layerId == '하수처리시설' ? selectedVal?.swtrmt_fclt_nm : null; @@ -505,17 +508,17 @@ const NewWindowPopup: React.FC = ({ layerId = seletectedLayerNm || ''; } //하수처리시설 - const isPrcsFclt = layerId == 'tb_il_swage_prcs_fclt' + const isPrcsFclt = layerId == '하수처리시설' // 재이용시설 - const isPbnst = layerId == 'tb_wr_bdst_ruse_fclt'; + const isPbnst = layerId == '재이용시설'; // 빗물이용시설 - const isRain = layerId == 'tb_wr_rnwtr_utztn_fclt'; + const isRain = layerId == '빗물이용시설'; // 중수도시설 - const isRswt = layerId == 'tb_wr_rswtr_fclt'; + const isRswt = layerId == '중수도시설'; // 온배수시설 - const isHtdfl = layerId == 'tb_wr_htdfl_fclt'; + const isHtdfl = layerId == '온배수시설'; // 수요처관리 - const isReuseMng = layerId == 'tb_wr_ruse_fclt_dmd_co'; + const isReuseMng = layerId == '수요처관리'; if (isPrcsFclt) { if (viewMode == 'list') { return ( diff --git a/apps/wr/app/store/matDmd/coSttn/index.tsx b/apps/wr/app/store/matDmd/coSttn/index.tsx index cc1db4566..1e20e8a76 100644 --- a/apps/wr/app/store/matDmd/coSttn/index.tsx +++ b/apps/wr/app/store/matDmd/coSttn/index.tsx @@ -52,8 +52,6 @@ export const coSttnActionsExport: (set: any) => coSttnActions = ( `/reuseFc/request/${pageNo}/${pageCnt}/${con}` ); - console.log('coSttnRes ', coSttnRes) - set((state: coSttnState) => { return { ...state, diff --git a/apps/wr/src/entities/auth/api/login-service.ts b/apps/wr/src/entities/auth/api/login-service.ts index 1808f7de8..77d114004 100644 --- a/apps/wr/src/entities/auth/api/login-service.ts +++ b/apps/wr/src/entities/auth/api/login-service.ts @@ -17,7 +17,6 @@ export class LoginApiService implements LoginService { public async login(payload: ILogin): Promise { try { - console.log(' ENDPOINT.USER_SERVICE.LOGIN ', ENDPOINT.USER_SERVICE.LOGIN) const response = await fetch( PROXY_HOST+ ENDPOINT.USER_SERVICE.LOGIN, { method: "POST", body: JSON.stringify({ diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index 6898e37ee..117601cc3 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -128,101 +128,7 @@ function Gis({ // Feature Click Event const handleFeatureSelect = useCallback((data) => { console.log(data); - // gisMap = mapInstanceRef.current; - // gisMap = mapInstanceRef.current; - - // if (!gisMap) { - // console.warn('지도가 초기화되지 않았습니다.'); - // return; - // } - // if (!gisMap) { - // console.warn('지도가 초기화되지 않았습니다.'); - // return; - // } - - // // 항상 같은 select 객체 사용 - // const select = GisApp.Module.select; - // if (!select) return; - // // 항상 같은 select 객체 사용 - // const select = GisApp.Module.select; - // if (!select) return; - - // // 1. 기존 선택 초기화 - // select.resetSelectedFeature(); - - // // 2. data가 OpenLayers Feature 인스턴스인지 확인 - // let foundFeature = null; - // if (data && typeof data.getGeometry === 'function' && typeof data.setStyle === 'function') { - // // 이미 Feature 인스턴스라면 그대로 사용 - // foundFeature = data; - // } else { - // // 아니면 featureId로 맵에서 Feature 인스턴스를 찾아서 사용 - // let featureId = data.fid; - // const layerName = layerNmRef.current - // if (layerName === '하수재이용시설') { - // featureId = data.searchPk; - // } else if (layerName === '빗물이용시설' || layerName == '중수도시설') { - // featureId = data.bdstInfoSn; - // } else if (layerName === '하수처리시설') { - // featureId = data.bscFctCd; - // } else if (layerName === '온배수시설') { - // featureId = data.bdstRuseFcltSn; - // } else if(layerName == '수요처'){ - // featureId = data.dmdInfoId.replaceAll('-','') - // } - // else { - // console.warn('해당 레이어에 대한 featureId 매핑 없음', layerNm); - // featureId = data.fid || data.id || data.ftrIdn; - // } - // // gisMap.getLayers().getArray().forEach(layer => { - // // const source = layer.getSource?.(); - // // if (source?.getFeatureById) { - // // const feature = source.getFeatureById(String(featureId).trim()); - // // if (feature) foundFeature = feature; - // // } - // // }); - // // const [layerType, id] = String(featureId).split('.'); - // gisMap.getLayers().getArray().forEach(layer => { - // const source = layer.getSource?.(); - // if (source?.getFeatures) { - // const allFeatures = source.getFeatures(); - // for (const f of allFeatures) { - // const id = String(f.getId()); - // // console.log('id', id, featureId) - // if (id && id.split('.')[1] === String(featureId)) { - // foundFeature = f; - // break; - // } - // } - // } - // }); - // } - - // // 3. 피처 선택 - // if (foundFeature) { - // let geometry = foundFeature.getGeometry(); - // if (geometry) { - // let extent = geometry.getExtent(); - - // gisMap.getView().fit(extent, { - // duration: 300, - // padding: [50, 50, 50, 50], - // maxZoom: 16, - // constrainResolution: true, - // nearest: true, - // }); - // } - // select.selectFeature(foundFeature, true); - // } else { - // console.warn('OpenLayers Feature를 찾을 수 없습니다:', data); - // } - - // setFeatureData(data); - // setIsOpen(true); - // setIsSearchModalOpen(false); - onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출 - console.log(data) }, [mapInstanceRef]); // 지도 생성 및 초기화 @@ -467,6 +373,7 @@ function Gis({ setVisible={setTabVisible} onCheckedChange={handleCheckedLayersChange} onLayerIconClick={(layerNm) => { + console.log(layerNm) layerNmRef.current = layerNm setLayerNm(layerNm); setIsOpen(true); const dummyFeature = { getProperties: () => ({}), // 최소 구조 diff --git a/packages/common/business_components/src/ui/map/GisTree.js b/packages/common/business_components/src/ui/map/GisTree.js index ccaa3c186..335b1b119 100644 --- a/packages/common/business_components/src/ui/map/GisTree.js +++ b/packages/common/business_components/src/ui/map/GisTree.js @@ -189,7 +189,7 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes }; const handleLayerImgClick = (nodeNm, nodeId, layerNm) => { if(!checked.includes(nodeId)) alert('시설을 선택해주세요') - else onLayerIconClick(layerNm) + else onLayerIconClick(nodeNm) } if (!treeData || treeData.length === 0) return null; diff --git a/packages/common/business_components/src/ui/map/lib/gis.layer.js b/packages/common/business_components/src/ui/map/lib/gis.layer.js index fee3f247b..d8474ce0a 100644 --- a/packages/common/business_components/src/ui/map/lib/gis.layer.js +++ b/packages/common/business_components/src/ui/map/lib/gis.layer.js @@ -130,8 +130,6 @@ GisOlLayer.prototype = { strategy: all, }); return source; - - return source; }, /** * [사용]WFS 레이어 생성 From aaed8fe971d0ccf31cb0b07f96bb51af4c125a1f Mon Sep 17 00:00:00 2001 From: leedj312 Date: Tue, 29 Jul 2025 15:51:18 +0900 Subject: [PATCH 06/71] =?UTF-8?q?=EC=9E=AC=EC=9D=B4=EC=9A=A9=20=EC=8B=9C?= =?UTF-8?q?=EC=84=A4=20=EC=8B=A4=EC=A0=81=20=EB=93=B1=EB=A1=9D=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ruseFcltMng/fcltReg/pbnstReg/page.tsx | 10 +- .../ruseFcltMng/fcltSttn/pbnstSttn/page.tsx | 39 ++-- .../prfmncReg/pbnstPrfmncReg/page.tsx | 62 +++--- .../store/prfmncReg/pbnstPrfmncReg/index.tsx | 189 +++++++++++++----- .../fcltReg/pbnstReg/buseReg/bugRegForm.tsx | 10 - 5 files changed, 186 insertions(+), 124 deletions(-) diff --git a/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx b/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx index 934946b74..d08d6f06e 100644 --- a/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx @@ -71,7 +71,7 @@ export default function PublicReuseFaciltyInsert() { // defaultValues: { items: pbnstRegArrayZod.parse([]) }, mode: "onChange", }); - const { isDirty } = form.formState; + // const { isDirty } = form.formState; const { fields: items, @@ -90,10 +90,10 @@ export default function PublicReuseFaciltyInsert() { }, [pbnst.isInit, pubHasuInit]); const onSubmit = async (valuse: { items: PbnstRegArrrayType }) => { - if (!isDirty) { - toast.error("변경사항이 없습니다."); // 변경사항이 없으면 메시지 표시 - return; - } + // if (!isDirty) { + // toast.error("변경사항이 없습니다."); // 변경사항이 없으면 메시지 표시 + // return; + // } const data = writeRgtrAndMdfr(valuse.items); let bizs = [] as fcBizType[]; data.forEach((fclt) => { diff --git a/apps/wr/app/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx b/apps/wr/app/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx index 5208edc02..8aea16c00 100644 --- a/apps/wr/app/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useWrStore } from "@/app/store"; import { btnClass } from "@/app/store/common/code"; +import { prfRegPageSize } from "@/app/store/prfmncReg/pbnstPrfmncReg"; import type { customPbnst } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; import type { WaterReuseFacility } from "@/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type"; import { excelDownload, yearOptions } from "@/components/lib/func/commom"; @@ -35,7 +36,6 @@ export default function PbnstSttn() { const { pbnstSttnRes, fclts, pbnstSttnResCount } = pbnstSttn; const [isReset, setIsReset] = useState(false); - //const [tableIndex, setTableIndex] = useState(1) const ref = useRef({} as ReturnType); const [searchYear, setSearchYear] = useState( String(new Date().getFullYear()) @@ -70,6 +70,24 @@ export default function PbnstSttn() { }, [getPbnStnDetail, pbnstSttnRes] ); + useEffect(() => { + if (!pbnstSttn.isInit) { + getFlcts(); + } + }, [pbnstSttn.isInit]); + useEffect(() => { + + pbnSttnInit("1", "15", ref.current, searchYear, fclts); + }, [fclts]); + + useEffect(() => { + if (pbnstSttnRes && pbnstSttnRes.length > 0) { + setBdstTable(pbnstSttnRes); + } else { + setBdstTable([]); + } + }, [pbnstSttnRes]); + useEffect(() => { if (pbnstSttn.excelList.length >= 0) { @@ -82,24 +100,7 @@ export default function PbnstSttn() { ); } }, [pbnstSttn.excelList]); - useEffect(() => { - if (!pbnstSttn.isInit) { - getFlcts(); - } - }, [pbnstSttn.isInit]); - useEffect(() => { - if (pbnstSttnRes && pbnstSttnRes.length > 0) { - setBdstTable(pbnstSttnRes); - } else { - setBdstTable([]); - } - }, [pbnstSttnRes]); - - useEffect(() => { - - pbnSttnInit("1", "15", ref.current, searchYear, fclts); - }, [fclts]); useEffect(() => { if (pbnstSttn.pbnstDetail.pbnstRuseFcltId) @@ -193,7 +194,7 @@ export default function PbnstSttn() { pbnstSttnSearch( false, String(page), - String(15), + String(prfRegPageSize), ref.current, pbnRuseNm, searchYear, diff --git a/apps/wr/app/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx b/apps/wr/app/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx index fbab00284..4fd866d75 100644 --- a/apps/wr/app/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx @@ -38,11 +38,12 @@ import { useTranslation } from "react-i18next"; import { headMakeprfmnc } from "../chkgdnReg/TableHead"; export default function PrfmncPrfmncReg() { - const { auth, prfmnc, prfmncHasuInit, prfmnRegData, prfmncHasuNmSearch } = useWrStore( + const { auth, prfmnc, prfmncHasuInit, prfmnRegData, prfmncHasuNmSearch,getPrfFmcRes } = useWrStore( (state) => state ); - const { pbnstPrfmncHeadList, monthHeadList, prfmncRes, prfmncResCount } = prfmnc; + const { prfPbnFclts, pbnstPrfmncHeadList, monthHeadList, prfmncRes, prfmncResCount } = prfmnc; const { regAuthrtYn, mdfcnAuthrtYn } = auth; + const [isReset, setIsReset] = useState(false); const [bdstTable, setbdstTable] = useState(); const [searchTxt, setSearchTxt] = useState(); const [isExcelOpen, setIsExcelOPen] = useState<{ @@ -51,7 +52,6 @@ export default function PrfmncPrfmncReg() { }>({ isModal: false, data: {} as FcPrfType }); const [inputData, setInputData] = useState(); const [pbnstRuseFcltId, setpbnstRuseFcltId] = useState(); - const [pageNation, setPageNation] = useState(1); const [cellNum, setCellNum] = useState(1); const [searchYear, setSearchYear] = useState( String(new Date().getFullYear()) @@ -65,20 +65,6 @@ export default function PrfmncPrfmncReg() { "A1:O1" ); }, [prfmnc.excelList]); - /* - const onSave = useCallback( - async() => { - if (pbnstRuseFcltId) { - await prfmnRegData(pageNation,'41', - excelToPbnPrfData( - inputData as unknown as JSON[], - searchYear, - pbnstRuseFcltId - ) as unknown as PrfType - ); - // cellClick(cellNum) - } - }, [inputData, searchYear, pbnstRuseFcltId, pageNation,cellNum]);*/ const { t } = useTranslation('base'); const onSave = useCallback(() => { @@ -86,19 +72,20 @@ export default function PrfmncPrfmncReg() { if (!pbnstRuseFcltId) return; prfmnRegData( - pageNation, + 1, '41', excelToPbnPrfData( inputData as unknown as JSON[], searchYear, pbnstRuseFcltId ) as unknown as PrfType - , searchYear + , searchYear, + prfPbnFclts ); }; confirmToast(t('isUpdateMsg'), handleConfirm); - }, [inputData, searchYear, pbnstRuseFcltId, pageNation, cellNum]); + }, [inputData, searchYear, pbnstRuseFcltId, cellNum,prfPbnFclts]); const cellClick = useCallback( (index: number | undefined) => { @@ -194,10 +181,15 @@ export default function PrfmncPrfmncReg() { useEffect(() => { if (!prfmnc.isInit) { - const year = new Date().getFullYear() - prfmncHasuInit(1, '41', String(year)); + getPrfFmcRes() } - }, [prfmnc.isInit, prfmncHasuInit]); + }, [prfmnc.isInit, getPrfFmcRes]); + + + useEffect(() =>{ + const year = new Date().getFullYear() + prfmncHasuInit(1, '41',String(year) ,prfPbnFclts); + },[prfPbnFclts]) useEffect(() => { @@ -254,14 +246,8 @@ export default function PrfmncPrfmncReg() {
@@ -314,10 +300,15 @@ export default function PrfmncPrfmncReg() { setCellNum(index ?? 0); }} onPageChange={ - (page) => { - prfmncHasuInit(page, '41', searchYear); - setPageNation(page) + (page:number) => { + if (searchTxt && searchTxt?.length > 0) { + prfmncHasuNmSearch(page, '41', searchYear, searchTxt,prfPbnFclts) + } else { + prfmncHasuInit(page, '41', searchYear,prfPbnFclts) + } + setIsReset(false); }} + resetPageNo={isReset} /> @@ -350,9 +341,10 @@ export default function PrfmncPrfmncReg() { setIsExcelOPen({ ...isExcelOpen, isModal: open }) }} setModalExcelData={async (excelData) => { - await prfmnRegData(pageNation, '41', + await prfmnRegData(1, '41', excelData, searchYear + ,prfPbnFclts ); // 데이터 저장 후 UI 업데이트는 useEffect가 감지하여 처리하므로 여기서는 호출하지 않습니다. } diff --git a/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx b/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx index fbdd4fba6..6c2564655 100644 --- a/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx +++ b/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx @@ -7,16 +7,17 @@ import toast from "react-hot-toast"; const pbnstPrfmncHeadList = pbnstPrfmncHeadListExport; const monthHeadList = monthHeadListExport; - +export const prfRegPageSize = 10; export type pbnstPrfmncState = { prfmnc: { isInit: boolean; pbnstPrfmncHeadList: TableUpperProps[]; monthHeadList: TableUpperProps[]; prfmncRes: FcPrfType[]; - prfmncResCount:number; + prfmncResCount: number; excelList: number[]; - pageKey:number + pageKey: number; + prfPbnFclts: string[]; }; }; @@ -26,68 +27,126 @@ export const pbnstPrfmncStateExport: pbnstPrfmncState = { pbnstPrfmncHeadList: pbnstPrfmncHeadList, monthHeadList: monthHeadList, prfmncRes: [], - prfmncResCount:1, + prfPbnFclts: [], + prfmncResCount: 1, excelList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - pageKey:1 + pageKey: 1, }, }; export type pbnstPrfmncType = pbnstPrfmncState & pbnstPrfmncActions; type pbnstPrfmncActions = { - prfmncHasuInit: (page: number, sggOrCity: string ,year:string) => void; - prfmncHasuNmSearch: (page: number, sggOrCity: string , txt:string ,year:string) => void; - prfmnRegData: (page:number,sggOrCity:string, data: PrfType,year:string) => void; + prfmncHasuInit: ( + page: number, + sggOrCity: string, + year: string, + prfPbnFclts: string[] + ) => void; + prfmncHasuNmSearch: ( + page: number, + sggOrCity: string, + year: string, + searchTxt: string, + prfPbnFclts: string[] + ) => void; + prfmnRegData: ( + page: number, + sggOrCity: string, + data: PrfType, + year: string, + prfPbnFclts: string[] + ) => void; + getPrfFmcRes: () => void; }; -const checkWrRegList = async (year:string) => { - const url =`/reuseFc/pubDetailList` - if(year&& year.length>0){ - // url+=`/${year}` +const hasuListFetch = async ( + page: number, + sggOrCity: string, + year: string, + prfPbnFclts: string[], + searchTxt?: string +) => { + let filteredFclt = prfPbnFclts; + if (sggOrCity !== "0") { + filteredFclt = prfPbnFclts?.filter((fclt: string) => { + return sggOrCity.length == 2 + ? fclt.slice(0, 2) === sggOrCity + : fclt.slice(0, 5) === sggOrCity; + }); } - const wrRegList = await getFetch("wr", url); - return wrRegList; -}; -const hasuListFetch = async (page: number, sggOrCity: string , year:string,searchNm?:string ) => { - const checkWrRegListRes = await checkWrRegList(year); - const rainRes = await postFectch( - `/prcsFclt/ruseInfo/${page ? page : 1}/10/A/${sggOrCity ? sggOrCity : "11"}/0/1/SW`, - checkWrRegListRes?.fcltCds, - fetchType.COMMON + + let url = `/prcsFclt/ruseInfo/1/${filteredFclt.length + 1}/A/${sggOrCity}/0/1/SW`; + if (searchTxt && searchTxt?.length > 0) { + url += `/${searchTxt}`; + } + const rainRes = await postFectch(url, filteredFclt, fetchType.COMMON); + + const checkRuseFcltList = + searchTxt && searchTxt?.length > 0 + ? rainRes.data.map((item) => item.bscFctCd) + : filteredFclt; + + const pubRuseList = await postFectch( + `/reuseFc/pubDetailList/${year}/${sggOrCity}/${page}/${prfRegPageSize}`, + checkRuseFcltList, + fetchType.WR ); - let findData = rainRes.data.map((item: pubHasuType) => { - const data = checkWrRegListRes?.fcPrfList.find( - (fclt: FcPrfType) => - fclt.pbnstRuseFcltId.substring(0, 11) === item?.bscFctCd - ); - if(data !==undefined){ - return( { + const rusePrfList = pubRuseList.data + .map((item: FcPrfType) => { + const findData = rainRes.data?.find( + (fclt: pubHasuType) => + item.pbnstRuseFcltId.substring(0, 11) === fclt?.bscFctCd + ); + if (findData !== undefined) { + return { + sido: findData.sido, + gugun: findData.gugun, + bscFctNm: findData.bscFctNm, + bscFctCd: findData.bscFctCd, + ruseUtztnFcltNm: item.ruseUtztnFcltNm, + pbnstRuseFcltId: item.pbnstRuseFcltId, + prfList: item.prfList, + }; + } + }) + .filter(Boolean); // undefined 값을 제거합니다. - sido: item.sido, - gugun: item.gugun, - bscFctNm: item.bscFctNm, - bscFctCd: item.bscFctCd, - ruseUtztnFcltNm: data.ruseUtztnFcltNm, - pbnstRuseFcltId: data.pbnstRuseFcltId, - prfList: data.prfList, - }); - } - }).filter(Boolean); // undefined 값을 제거합니다. - - if(searchNm){ - - findData= findData.filter((ruse:any) => ruse.ruseUtztnFcltNm.includes(searchNm)) - } - - return {data:findData, count:findData.length}; + return { data: rusePrfList, count: pubRuseList.count }; }; export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = ( set: any ) => { return { - prfmncHasuInit: async (page: number, sggOrCity: string,year:string) => { + getPrfFmcRes: async () => { try { - const prfmncRes = await hasuListFetch(page, sggOrCity, year); + const pbnPrfRes = await getFetch(fetchType.WR, "/reuseFc/pubFc"); + console.log("pbnPrfRes ", pbnPrfRes); + set((state: pbnstPrfmncState) => ({ + ...state, + prfmnc: { + ...state.prfmnc, + prfPbnFclts: pbnPrfRes, + }, + })); + } catch (error) { + console.log(error); + } + }, + prfmncHasuInit: async ( + page: number, + sggOrCity: string, + year: string, + prfPbnFclts: string[] + ) => { + try { + console.log("prfPbnFclts ", prfPbnFclts); + const prfmncRes = await hasuListFetch( + page, + sggOrCity, + year, + prfPbnFclts + ); return set((state: pbnstPrfmncState) => ({ ...state, prfmnc: { @@ -101,9 +160,21 @@ export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = ( console.log(errorHasu); } }, - prfmncHasuNmSearch: async (page: number, sggOrCity: string,fcltNm:string ,year:string)=> { + prfmncHasuNmSearch: async ( + page: number, + sggOrCity: string, + year: string, + searchTxt: string, + prfPbnFclts: string[] + ) => { try { - const prfmncRes = await hasuListFetch(page, sggOrCity,fcltNm ,year); + const prfmncRes = await hasuListFetch( + page, + sggOrCity, + year, + prfPbnFclts, + searchTxt + ); return set((state: pbnstPrfmncState) => ({ ...state, prfmnc: { @@ -117,7 +188,13 @@ export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = ( console.log(errorHasu); } }, - prfmnRegData: async (page:number,sggOrCity:string,data: PrfType,year:string) => { + prfmnRegData: async ( + page: number, + sggOrCity: string, + data: PrfType, + year: string, + prfPbnFclts: string[] + ) => { try { const ressponse = await postFectch( "/reuseFc/pubFcPrf", @@ -125,11 +202,14 @@ export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = ( fetchType.WR ); - if (ressponse.status === 200) { toast.success("하수재이용 시설 실적 등록이 완료되었습니다."); - const prfmncRes = await hasuListFetch(page, sggOrCity,year) ; - + const prfmncRes = await hasuListFetch( + page, + sggOrCity, + year, + prfPbnFclts + ); return set((state: pbnstPrfmncState) => ({ ...state, @@ -138,10 +218,9 @@ export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = ( prfmncRes: prfmncRes.data, prfmncResCount: prfmncRes.count, isInit: false, - pageKey: state.prfmnc.pageKey + 1 + pageKey: state.prfmnc.pageKey + 1, }, })); - } } catch (e) { console.log(e); diff --git a/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx b/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx index 2589422c8..0fb24366a 100644 --- a/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx @@ -488,16 +488,6 @@ export function PbnstBuseForm({ />
- {/* */}
Date: Tue, 29 Jul 2025 20:11:51 +0900 Subject: [PATCH 07/71] =?UTF-8?q?=EB=B9=97=EB=AC=BC=20=EC=97=91=EC=85=80?= =?UTF-8?q?=20=EB=93=B1=EB=A1=9D=20=ED=95=AD=EB=AA=A9=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=EC=A0=90=EA=B2=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../prfmncReg/chkgdnReg/rainChkgdn/excel.tsx | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx index 6d6ef18a6..a484f87c2 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx @@ -6,6 +6,7 @@ import { isInvalidNumber, parseRawValue, } from "@/components/lib/func/excelFunc"; +import toast from "react-hot-toast"; export const useTypes = [ "조경용수", "청소화장실용수", @@ -16,6 +17,18 @@ export const useTypes = [ "기타", ]; + +export const chkMngType =[ + '위탁', + '자체' +] +export const chkSpwtPositnType = [ + '유량계', + '수위계', + '펌프적산전력계', + '기타' +] + export type Buildingtype = { bdstInfoSn: number; ctpvCd: 1; @@ -143,43 +156,56 @@ export const excelToRainChkData = (excelData: any, year: string) => { }) .filter((el: any) => el !== undefined); +console.log("rainChkgdData", rainChkgdData); if (rainChkgdData.length > 0) { rainChkgdData.forEach((rain: any) => { const defaultMessage = `[시설명: ${rain.bdstNm}, 점검일: ${rain.chckYmd}, 이용용도: ${rain.guideDetails[0]?.utztnUsgCd}] 항목에서 `; if (rain.chckYmd && isInvalidDate(rain.chckYmd)) { issues.push( - defaultMessage + `점검일: 날짜 형식 아님 (${rain.chckYmd})` + defaultMessage + `점검일: 날짜 형식 아님 입력값 : (${rain.chckYmd})` ); } if (rain.anlOperCst.length > 2 && isInvalidNumber(rain.anlOperCst)) { issues.push( defaultMessage + - ` 연간운영비(원/년): 숫자 형식 아님 (${rain.anlOperCst})` + ` 연간운영비(원/년): 숫자 형식 아님 /입력값:${rain.anlOperCst}` ); } + if (rain.oilqMeasMthd.length > 1 && !chkMngType.includes(rain.oilqMeasMthd)) { + issues.push( + defaultMessage + `유량측정방법: 위탁 혹은 자체 만 허용) /입력값:${rain.oilqMeasMthd}` + ); + } + if (rain.spwtPositn.length > 1 && !chkSpwtPositnType.includes(rain.spwtPositn)) { + issues.push( + defaultMessage + `측정위치: 유량계, 수위계, 펌프적산전력계, 기타 중 하나만 허용) /입력값:${rain.spwtPositn}` + ); + } + + rain.rainyDetails?.forEach((key) => { if (key.ph?.length > 1 && isInvalidNumber(key.ph)) { - issues.push(defaultMessage + `PH: 숫자 형식 아님 (${key.ph})`); + issues.push(defaultMessage + `PH: 숫자 형식 아님 /입력값:${key.ph}`); } if (key.trbt?.length > 1 && isInvalidNumber(key.trbt)) { - issues.push(defaultMessage + `탁도: 숫자 형식 아님 (${key.trbt})`); + issues.push(defaultMessage + `탁도: 숫자 형식 아님 /입력값:${key.trbt}`); } if (key.grssClfg?.length > 1 && isInvalidNumber(key.grssClfg)) { issues.push( - defaultMessage + `총대장균군수: 숫자 형식 아님 (${key.grssClfg})` + defaultMessage + `총대장균군수: 숫자 형식 아님 /입력값:${key.grssClfg}` ); } if (key.levyAmt?.length > 1 && isInvalidNumber(key.levyAmt)) { issues.push( defaultMessage + - `과태료부과금액(천원): 숫자 형식 아님 (${key.levyAmt})` + `과태료부과금액(천원): 숫자 형식 아님 /입력값:${key.levyAmt}` ); } if (key.vltnDt?.length > 2 && isInvalidDate(key.vltnDt)) { issues.push( defaultMessage + - `과태료 부과일(위반일): 날짜 형식 아님 (${key.vltnDt})` + `과태료 부과일(위반일): 날짜 형식 아님 /입력값:${key.vltnDt}` ); } }); @@ -188,17 +214,19 @@ export const excelToRainChkData = (excelData: any, year: string) => { if (isInvalidNumber(guide.splyUswtrUtztnQnt)) { issues.push( defaultMessage + - `대체_${mm}: 숫자 형식 아님 (${guide.splyUswtrUtztnQnt})` + `대체_${mm}: 숫자 형식 아님 /입력값:${guide.splyUswtrUtztnQnt}` ); } if (isInvalidNumber(guide.ruseFcltUtztnQnt)) { issues.push( defaultMessage + - `빗물_${mm}: 숫자 형식 아님 (${guide.ruseFcltUtztnQnt})` + `빗물_${mm}: 숫자 형식 아님 /입력값:${guide.ruseFcltUtztnQnt}` ); } }); }); + }else{ + toast.error("데이터가 없습니다. 점검 데이터를 확인해주세요."); } if (issues.length > 0) { From e5f2cb2182f407a78d25edc05f81e5cc6f49deaf Mon Sep 17 00:00:00 2001 From: jyha00 Date: Tue, 29 Jul 2025 20:40:05 +0900 Subject: [PATCH 08/71] =?UTF-8?q?=EB=AC=BC=EC=9E=AC=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?=EC=82=AC=EB=A1=80=EA=B4=80=EB=A6=AC=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx | 238 ++++++++++-------- .../app/store/matDmd/ruseCaseMng/headList.tsx | 2 +- .../wr/app/store/matDmd/ruseCaseMng/index.tsx | 154 +++++++----- .../ruseCaseMng/ruseCaseMngExcelModal.tsx | 38 ++- apps/wr/public/sample_ruseCaseMng.xlsx | Bin 20324 -> 20272 bytes 5 files changed, 245 insertions(+), 187 deletions(-) diff --git a/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx b/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx index e45a0a0f2..813b4e23f 100644 --- a/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx +++ b/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx @@ -17,35 +17,45 @@ import { UiModal, UiTable, type SelectDataType, + type SheetParameter, } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; -import { Button } from "@common/components/ui"; +import { Button, Label } from "@common/components/ui"; import { useEffect, useState } from "react"; -import { Toaster } from "react-hot-toast"; +import toast, { Toaster } from "react-hot-toast"; +import { useTranslation } from "react-i18next"; export default function RuseCaseMng() { - const { code, ruseCaseMng, selectRuseCaseMng } = useWrStore((state) => state); - const { ruseCaseMngList, status, count } = ruseCaseMng; + const { code, ruseCaseMng, selectRuseCaseMng, ruseCaseMngInit } = useWrStore((state) => state); + const { ruseCaseMngList, status, count, excelList } = ruseCaseMng; const [selectSgg, setSelectSgg] = useState([]); const [objSearch, setObjSearch] = useState({ sgg: "", city: "" }); - + const [sidoCd, setSidoCd] = useState(""); const [sigunguCd, setSigunguCd] = useState(""); - const [selectUtz, setSelectUtz] = useState(["조경용수"]); + // const [selectUtz, setSelectUtz] = useState(["조경용수"]); + const [selectUtz, setSelectUtz] = useState(""); const [name, setName] = useState(""); + const [resetPage, isResetPage] = useState(false); + const { t } = useTranslation('base'); // 물재이용사례 조회 - const searchRuseCaseMng = async (pageNo: number, pageSize: number) => { + const searchRuseCaseMng = async (isExcel: boolean, pageNo: number, pageSize: number) => { await selectRuseCaseMng( + isExcel, pageNo, pageSize, - sidoCd ?? "", - sigunguCd ?? "", - selectUtz[0] ?? "", + sidoCd ?? "", + sigunguCd ?? "", + selectUtz ?? "", name ?? "" ); } + useEffect(() => { + ruseCaseMngInit(); + }, []) + // 시도 선택 const getSelCity = (val: string | number) => { const cityValue = String(val); @@ -69,91 +79,97 @@ export default function RuseCaseMng() { isModal: boolean; }>({ isModal: false }); - // 다운로드 - const download = () => { - searchRuseCaseMng(1, 10000);; - + + useEffect(() => { + + if (excelList !== undefined && excelList.length === 0) { + toast.error(t('excelDownErrMsg')); + return; + } fetch(`/sample_ruseCaseMng.xlsx`, {}).then(async (response) => { - if (!response.ok) { - throw Error(response.statusText); - } - // 받은 파일 바인너리 처리 - const blobRet = await response.arrayBuffer(); - - const excelData1 = ruseCaseMngList.map((item) => { - return [ - item.ruseBizNm, - item.bizBgngDate, - item.bizEndDate, - item.splyFcltKnd, - item.splyFcltNm, - item.splyFcltCpct, - item.dmdFcltNm, - item.dmdFcltAddr, - item.ruseFcltCpct, - item.ruseFcltPplLen, - item.ruseFcltUsg, - item.ruseFcltSplyCpct, - item.ruseFcltPrcsMsrc, - item.grnteWqUsg, - item.grnteWqGrssClfg, - item.grnteWqCombResdch, - item.grnteWqTrbt, - item.grnteWqBod, - item.grnteWqSmll, - item.grnteWqCrmty, - item.grnteWqTn, - item.grnteWqTp, - item.grnteWqPh, - item.grnteWqClIonQnt, - item.grnteWqCndctStat, - item.grnteWqRmrk, - item.reqBizGrssCst, - item.reqBizReuseFcltCst, - item.reqBizPplCst, - item.reqBizArchCst, - item.reqBizMchnCst, - item.reqBizMtrngCst, - item.reqBizGisCst, - item.reqBizCctvCst, - item.reqBizCmcnCst, - item.reqBizWstCst, - item.reqBizDesignCst, - item.reqBizSpvsCst, - item.reqBizSbsdCst, - item.reqBizEtc, - item.prcmPlanGrssCst, - item.prcmPlanNlep, - item.prcmPlanLcep, - item.prcmPlanBrdnCst, - item.prcmPlanEcqrRt, - item.clclnGrssCst, - item.clclnNlep, - item.clclnLcep, - item.clclnBrdnCst, - item.clclnEcqrRt, - ]; - }); - - const sheetParameter = [ - { - data: excelData1, - headerNum: 2, - headerRange: 'A1:AX1', - sheetName: '물재이용사례', - startCol: 'A' + if (!response.ok) { + throw Error(response.statusText); + } + // 받은 파일 바인너리 처리 + const blobRet = await response.arrayBuffer(); + if(excelList){ + const excelData1 = excelList.map((item) => { + return [ + item.ruseBizNm, + item.bizBgngDate, + item.bizEndDate, + item.splyFcltKnd, + item.splyFcltNm, + item.splyFcltCpct, + item.dmdFcltNm, + item.dmdFcltAddr, + item.ruseFcltCpct, + item.ruseFcltPplLen, + item.ruseFcltUsg, + item.ruseFcltSplyCpct, + item.ruseFcltPrcsMsrc, + item.grnteWqUsg, + item.grnteWqGrssClfg, + item.grnteWqCombResdch, + item.grnteWqTrbt, + item.grnteWqBod, + item.grnteWqSmll, + item.grnteWqCrmty, + item.grnteWqTn, + item.grnteWqTp, + item.grnteWqPh, + item.grnteWqClIonQnt, + item.grnteWqCndctStat, + item.grnteWqRmrk, + item.reqBizGrssCst, + item.reqBizReuseFcltCst, + item.reqBizPplCst, + item.reqBizArchCst, + item.reqBizMchnCst, + item.reqBizMtrngCst, + item.reqBizGisCst, + item.reqBizCctvCst, + item.reqBizCmcnCst, + item.reqBizWstCst, + item.reqBizDesignCst, + item.reqBizSpvsCst, + item.reqBizSbsdCst, + item.reqBizEtc, + item.prcmPlanGrssCst, + item.prcmPlanNlep, + item.prcmPlanLcep, + item.prcmPlanBrdnCst, + item.prcmPlanEcqrRt, + item.clclnGrssCst, + item.clclnNlep, + item.clclnLcep, + item.clclnBrdnCst, + item.clclnEcqrRt, + ]; + }); + + const sheetParameter: SheetParameter[] = [ + { + data: excelData1, + headerNum: 2, + headerRange: 'A1:AX1', + sheetName: '물재이용사례', + startCol: 'A' + } + ]; + // 버퍼에 있는 엑셀데이터 전달 후 json으로 변환 + await filePickedBlobArray(blobRet, '물재이용사례현황', sheetParameter); } - ]; - // 버퍼에 있는 엑셀데이터 전달 후 json으로 변환 - await filePickedBlobArray(blobRet, '물재이용사례현황', sheetParameter); }); - } + + + }, [excelList]) // 양식받기 const sampleDownLoad = () => { excelDownload( [[]], - "물재이용사례 관리", + "물재이용사례 관리 등록양식", "sample_ruseCaseMng.xlsx", "A1:AX1", 2 @@ -161,7 +177,7 @@ export default function RuseCaseMng() { } useEffect(() => { - if(status === 200){ + if (status === 200) { setIsExcelOpen({ isModal: false }); } }, [status]); @@ -174,7 +190,7 @@ export default function RuseCaseMng() { <SearchDiv> <SearchFormLeft> - <div className="col-span-8 flex flex-row"> + <div className="col-span-8 flex flex-row items-center"> <SelectBox className="col-span-2 w-1/3 h-full text-2xl" label="시도" @@ -183,24 +199,28 @@ export default function RuseCaseMng() { onSelectValue={getSelCity} /> <SelectBox - className="col-span-2 w-1/3 h-full text-2xl" + className="col-span-2 w-1/3 h-full text-2xl ml-4" label="시군구" selectClass={""} selectArray={selectSgg || []} onSelectValue={getSgg} /> <SelectBox - className="col-span-2 w-1/3 h-full text-2xl" + className="col-span-2 w-1/3 h-full text-2xl ml-4" label="용도" selectClass={""} selectArray={ruseUtzType} onSelectValue={(val) => { - if (val) setSelectUtz([String(val)]); + if (val) setSelectUtz(String(val)); }} + value={selectUtz || ruseUtzType[0]?.val} /> + <Label className="w-28 text-xl ml-4 mr-6"> + {"수요처명"} + </Label> <SearchInput - className={"text-2xl"} - label="수요처명" + className={"col-span-2 w-1/3 text-2xl"} + label="" textValue={name} setText={(e) => { setName(e); @@ -211,13 +231,29 @@ export default function RuseCaseMng() { <SearchFormRight className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2"> <div className="space-y"> <div className="flex flex-row space-x-2"> - <Button size={"sm"} onClick={() => { searchRuseCaseMng(1, 20) }}> + <Button size={"sm"} onClick={() => { + searchRuseCaseMng(false, 1, 10); + isResetPage(true); + }}> 조회 </Button> <Button size={"sm"} onClick={() => { setIsExcelOpen({ isModal: true }); }}> 엑셀등록 </Button> - <Button size={"sm"} onClick={() => { download() }}> + <Button size={"sm"} onClick={() => { + selectRuseCaseMng( + true, + 1, + count, + sidoCd ?? "", + sigunguCd ?? "", + selectUtz ?? "", + name ?? "" + ); + // download() + + + }} className={"green"}> 다운로드 </Button> <Button size={"sm"} onClick={() => { sampleDownLoad() }}> @@ -239,10 +275,12 @@ export default function RuseCaseMng() { headlist={ruseCaseMng.ruseHeadList} pageSize={20} total={count} - cellClick={() => {}} + cellClick={undefined} onPageChange={(pageNo: number) => { - searchRuseCaseMng(pageNo, 10); + searchRuseCaseMng(false, pageNo, 10); + isResetPage(false) }} + resetPageNo={resetPage} /> </div> </ListDiv> @@ -254,9 +292,9 @@ export default function RuseCaseMng() { <UiModal open={isExcelOpen.isModal} onOpenChange={() => { - setIsExcelOpen({ isModal: !isExcelOpen.isModal }) + setIsExcelOpen({ isModal: !isExcelOpen.isModal }) }} - bodyClass={"w-[40vw] max-w-none"} + bodyClass={"w-[35vw] max-w-none"} body={<RuseCaseMngExcel />} /> </> diff --git a/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx b/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx index 8c3a232e7..bc71fbf81 100644 --- a/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx +++ b/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx @@ -1,7 +1,6 @@ import type { SelectDataType } from "@common/business_components/ui"; import type { TableUpperProps } from "../../common/type/header"; -//★☆★☆ 여기서부터 수정하기 ★☆★☆ export const ruseCaseHeadListExport: TableUpperProps[] = [ { id: "ruseBizNm", @@ -358,6 +357,7 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [ ]; export const ruseUtzType: SelectDataType[] = [ + { text: "전체", val: "0" }, { text: "조경용수", val: "조경용수" }, { text: "청소화장실용수", val: "청소화장실용수" }, { text: "세척살수용수", val: "세척살수용수" }, diff --git a/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx b/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx index 5ae2e67b4..160e1f7f3 100644 --- a/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx +++ b/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx @@ -4,90 +4,93 @@ import type { TableUpperProps } from "../../common/type/header"; import { ruseCaseHeadListExport } from "./headList"; export type ruseCaseState = { ruseCaseMng: { - ruseHeadList:TableUpperProps[]; - ruseCaseMngList:ruseCaseMngExcelProps[]; + ruseHeadList: TableUpperProps[]; + ruseCaseMngList: ruseCaseMngExcelProps[]; status: number; count: number; + excelList?: ruseCaseMngExcelProps[]; }; }; export interface ruseCaseMngExcelProps { - ruseBizNm: string, - bizBgngDate: string, - bizEndDate: string, - splyFcltKnd: string, - splyFcltNm: string, - splyFcltCpct: number | 0 |'', - dmdFcltNm: string, - dmdFcltAddr: string, - ruseFcltCpct: number | 0 |'', - ruseFcltPplLen: string, - ruseFcltUsg: string, - ruseFcltSplyCpct: number | 0 |'', - ruseFcltPrcsMsrc: string, - grnteWqUsg: string, - grnteWqGrssClfg: number | 0 |'', - grnteWqCombResdch: number | 0 |'', - grnteWqTrbt: string, - grnteWqBod: string, - grnteWqSmll: string, - grnteWqCrmty: string, - grnteWqTn: string, - grnteWqTp: string, - grnteWqPh: string, - grnteWqClIonQnt: string, - grnteWqCndctStat: string, - grnteWqRmrk: string, - reqBizReuseFcltCst: number | 0 |'', - reqBizPplCst: number | 0 |'', - reqBizArchCst: number | 0 |'', - reqBizMchnCst: number | 0 |'', - reqBizMtrngCst: number | 0 |'', - reqBizGisCst: number | 0 |'', - reqBizCctvCst: number | 0 |'', - reqBizCmcnCst: number | 0 |'', - reqBizWstCst: number | 0 |'', - reqBizDesignCst: number | 0 |'', - reqBizSpvsCst: number | 0 |'', - reqBizSbsdCst: number | 0 |'', - reqBizEtc: string, - reqBizGrssCst: number | 0 |'', - prcmPlanNlep: number | 0 |'', - prcmPlanLcep: number | 0 |'', - prcmPlanBrdnCst: number | 0 |'', - prcmPlanEcqrRt: number | 0 |'', - prcmPlanGrssCst: number | 0 |'', - clclnNlep: number | 0 |'', - clclnLcep: number | 0 |'', - clclnBrdnCst: number | 0 |'', - clclnEcqrRt: number | 0 |'', - clclnGrssCst: number | 0 |'', + ruseBizNm: string, + bizBgngDate: string, + bizEndDate: string, + splyFcltKnd: string, + splyFcltNm: string, + splyFcltCpct: number | 0 | '', + dmdFcltNm: string, + dmdFcltAddr: string, + ruseFcltCpct: number | 0 | '', + ruseFcltPplLen: string, + ruseFcltUsg: string, + ruseFcltSplyCpct: number | 0 | '', + ruseFcltPrcsMsrc: string, + grnteWqUsg: string, + grnteWqGrssClfg: number | 0 | '', + grnteWqCombResdch: number | 0 | '', + grnteWqTrbt: string, + grnteWqBod: string, + grnteWqSmll: string, + grnteWqCrmty: string, + grnteWqTn: string, + grnteWqTp: string, + grnteWqPh: string, + grnteWqClIonQnt: string, + grnteWqCndctStat: string, + grnteWqRmrk: string, + reqBizReuseFcltCst: number | 0 | '', + reqBizPplCst: number | 0 | '', + reqBizArchCst: number | 0 | '', + reqBizMchnCst: number | 0 | '', + reqBizMtrngCst: number | 0 | '', + reqBizGisCst: number | 0 | '', + reqBizCctvCst: number | 0 | '', + reqBizCmcnCst: number | 0 | '', + reqBizWstCst: number | 0 | '', + reqBizDesignCst: number | 0 | '', + reqBizSpvsCst: number | 0 | '', + reqBizSbsdCst: number | 0 | '', + reqBizEtc: string, + reqBizGrssCst: number | 0 | '', + prcmPlanNlep: number | 0 | '', + prcmPlanLcep: number | 0 | '', + prcmPlanBrdnCst: number | 0 | '', + prcmPlanEcqrRt: number | 0 | '', + prcmPlanGrssCst: number | 0 | '', + clclnNlep: number | 0 | '', + clclnLcep: number | 0 | '', + clclnBrdnCst: number | 0 | '', + clclnEcqrRt: number | 0 | '', + clclnGrssCst: number | 0 | '', } type ruseCaseActions = { - selectRuseCaseMng: (pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd : string, name: string) => Promise<void>; + ruseCaseMngInit: () => void; + selectRuseCaseMng: (isExcel: boolean, pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd: string, name: string) => Promise<void>; insertRuseCaseMng: (data: any) => Promise<void>; }; -export const fetchSearchRuseCaseMng = async (pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd : string, name: string) => { - const tailUrl = '/'+pageNo+'/'+pageSize+ ((sigunguCd !== '전체' && sigunguCd !=='') ? `/${sigunguCd}` : (sidoCd != '전체' && sidoCd != '') ? `/${sidoCd}` : `/0`) + (usageCd? `/${usageCd}` : '/0') + (name? `/${name}` : ''); - const resp = await getFetchWithCount(fetchType.WR,`/reuseFc/pubFcRuseCase${tailUrl}`); - return {data:resp.data, count:resp.count}; +export const fetchSearchRuseCaseMng = async (pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd: string, name: string) => { + const tailUrl = '/' + pageNo + '/' + pageSize + ((sigunguCd !== '전체' && sigunguCd !== '') ? `/${sigunguCd}` : (sidoCd != '전체' && sidoCd != '') ? `/${sidoCd}` : `/0`) + (usageCd ? `/${usageCd}` : '/0') + (name ? `/${name}` : ''); + const resp = await getFetchWithCount(fetchType.WR, `/reuseFc/pubFcRuseCase${tailUrl}`); + return { data: resp.data, count: resp.count }; }; export const fetchInsertRuseCaseMng = async (data: any) => { const response = await postFectch('/reuseFc/pubFcRuseCase', data, fetchType.WR); - return {data:response.data, status:response.status}; + return { data: response.data, status: response.status }; }; export type ruseCaseType = ruseCaseState & ruseCaseActions; export const ruseCaseStateExport: ruseCaseState = { ruseCaseMng: { - ruseHeadList:ruseCaseHeadListExport, - ruseCaseMngList:[], + ruseHeadList: ruseCaseHeadListExport, + ruseCaseMngList: [], status: 0, count: 0, + excelList: undefined, }, }; @@ -95,8 +98,8 @@ export const ruseCaseActionsExport: (set: any) => ruseCaseActions = ( set: any ) => { return { - selectRuseCaseMng: async (pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd : string, name: string) => { - const res = await fetchSearchRuseCaseMng(pageNo, pageSize, sidoCd, sigunguCd, usageCd, name); + ruseCaseMngInit: async () => { + const res = await getFetchWithCount(fetchType.WR, `/reuseFc/pubFcRuseCase/1/10/0/0`); set((state: ruseCaseState) => ({ ruseCaseMng: { ...state.ruseCaseMng, @@ -105,12 +108,35 @@ export const ruseCaseActionsExport: (set: any) => ruseCaseActions = ( }, })); }, + selectRuseCaseMng: async (isExcel: boolean, pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd: string, name: string) => { + const res = await fetchSearchRuseCaseMng(pageNo, pageSize, sidoCd, sigunguCd, usageCd, name); + if (isExcel) { + set((state: ruseCaseState) => { + return { + ...state, + ruseCaseMng: { + ...state.ruseCaseMng, + count: res.count, + excelList: res.data + }, + }; + }); + } else { + set((state: ruseCaseState) => ({ + ruseCaseMng: { + ...state.ruseCaseMng, + ruseCaseMngList: res.data, + count: res.count, + }, + })); + } + }, insertRuseCaseMng: async (data: any) => { const response = await fetchInsertRuseCaseMng(data); - if(response.status === 200){ + if (response.status === 200) { toast.success("물재이용사례 등록이 완료되었습니다. "); - }else{ + } else { toast.error("물재이용사례 등록에 실패하였습니다. "); } set((state: ruseCaseState) => ({ diff --git a/apps/wr/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal.tsx b/apps/wr/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal.tsx index 57424996e..a809c88f6 100644 --- a/apps/wr/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal.tsx +++ b/apps/wr/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal.tsx @@ -1,14 +1,14 @@ +import { useWrStore } from "@/app/store"; import { coRegExcel } from "@/app/store/matDmd/coSttn/headList"; -import type { ruseCaseMngExcelProps } from "@/app/store/matDmd/ruseCaseMng/index"; import { reUseCaseNames } from "@/app/store/matDmd/ruseCaseMng/excel"; -import { filePicked, FileUpload, FormUi, Title } from "@common/business_components/ui"; -import { Form, FormField } from "@common/business_components/ui/form"; -import { Button } from "@common/components/ui"; +import type { ruseCaseMngExcelProps } from "@/app/store/matDmd/ruseCaseMng/index"; +import { filePicked, Title } from "@common/business_components/ui"; +import { Form } from "@common/business_components/ui/form"; +import { Button, Input } from "@common/components/ui"; import { zodResolver } from "@hookform/resolvers/zod"; import { useState } from "react"; import { useForm } from "react-hook-form"; import type { z } from "zod"; -import { useWrStore } from "@/app/store"; export default function RuseCaseMngExcel() { const { insertRuseCaseMng } = useWrStore((state) => state); @@ -79,6 +79,7 @@ export default function RuseCaseMngExcel() { }; Object.keys(reUseCaseNames).forEach((key: string) => { + console.log("reUseCaseNames", reUseCaseNames) if (item && reUseCaseNames[key] !== undefined) { const mappedKey = reUseCaseNames[key]; if (mappedKey && object.hasOwnProperty(mappedKey)) { @@ -146,23 +147,16 @@ export default function RuseCaseMngExcel() { </div> <div> - <FormField - control={form.control} - name="file" - render={() => ( - <FormUi label=""> - <FileUpload - files={file ? [file] : []} - setFiles={(files: File[] | null) => { - console.log("files", files) - if (files && files?.length === 1) setFiles(files[0]); - }} - fileTypes="" - maxFiles={1} - ></FileUpload> - </FormUi> - )} - /> + <div className="space-y-4 p-4"> + <Input + type="file" + onChange={(e) => { + const selectedFile = e.target.files?.[0]; + if (selectedFile) setFiles(selectedFile); + }} + accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel" + /> + </div> </div> </form> </Form> diff --git a/apps/wr/public/sample_ruseCaseMng.xlsx b/apps/wr/public/sample_ruseCaseMng.xlsx index 96027d557fe2cfc60d3de10c313c49d78f984591..3676b4f71f972b6d877a9f03946a2c5af8cd5b0e 100644 GIT binary patch delta 9160 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 delta 9127 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 From 2f553a0dffd15dfe38e30586b96f4e724cd6655f Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Wed, 30 Jul 2025 09:44:09 +0900 Subject: [PATCH 09/71] =?UTF-8?q?fix:=20searchModal.tsx=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=ED=97=A4=EB=8D=94=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=A4=84=20=EB=B0=94=EA=BF=88=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../map/components/common/table.ts | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) 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 84de2b4ab..0cfc261a2 100644 --- a/apps/sn/app/(map-layout)/map/components/common/table.ts +++ b/apps/sn/app/(map-layout)/map/components/common/table.ts @@ -28,20 +28,20 @@ export const MsSwagePplCol = [ // { id: 'lyrTypeNm', title: '레이어타입명' }, ] export const MsSwagePplCol2 = [ - { id: 'rowNumber', title: '일련번호' }, - { id: 'fcltMngNo', title: '시설 관리번호' }, - { id: 'instMngNo', title: '기관 관리번호' }, + { id: 'rowNumber', title: '일련\n번호' }, + { id: 'fcltMngNo', title: '시설\n관리번호' }, + { id: 'instMngNo', title: '기관\n관리번호' }, { id: 'instlYmd', title: '설치일자' }, // { id: 'mapshtNo', title: '도엽번호' }, - { id: 'pdmt', title: '관경(mm)' }, - { id: 'prlg', title: '연장(m)' }, - { id: 'avgGrdn', title: '평균구배(m/m)' }, - { id: 'tmptDpth', title: '시점깊이(m)' }, - { id: 'tmptMhlMngNo', title: '시점맨홀 관리번호' }, - { id: 'scendDpth', title: '종점깊이(m)' }, - { id: 'scendMhlMngNo', title: '종점맨홀 관리번호' }, - { id: 'vrtcLen', title: '세로길이(m)' }, - { id: 'wdthLen', title: '가로길이(m)' }, + { id: 'pdmt', title: '관경\n(mm)' }, + { id: 'prlg', title: '연장\n(m)' }, + { id: 'avgGrdn', title: '평균구배\n(m/m)' }, + { id: 'tmptDpth', title: '시점깊이\n(m)' }, + { id: 'tmptMhlMngNo', title: '시점맨홀\n관리번호' }, + { id: 'scendDpth', title: '종점깊이\n(m)' }, + { id: 'scendMhlMngNo', title: '종점맨홀\n관리번호' }, + { id: 'vrtcLen', title: '세로길이\n(m)' }, + { id: 'wdthLen', title: '가로길이\n(m)' }, // { id: 'fcltyShpCd', title: '시설물형태코드' }, // { id: 'fid', title: 'FID' }, // { id: 'ftreSmbolCd', title: '지형지물부호코드' }, @@ -297,10 +297,10 @@ export const SwtrmtFcltCol = [ // { id: 'obsrvnSptCd', title: '관측지점코드' }, ]; export const SwtrmtFcltCol2 = [ - { id: 'rowNumber', title: '일련번호' }, - { id: 'swtrmtFcltNm', title: '하수처리시설명' }, - { id: 'fcltCpct', title: '시설용량(㎥/d)' }, - { id: 'rgnExpln', title: '지역설명' }, + { id: 'rowNumber', title: '일련\n번호' }, + { id: 'swtrmtFcltNm', title: '하수\n처리\n시설명' }, + { id: 'fcltCpct', title: '시설\n용량\n(㎥/d)' }, + { id: 'rgnExpln', title: '지역\n설명' }, // { id: 'cmcnYmd', title: '준공일자' }, { id: 'oprtngStrtYmd', title: '가동개시일자' }, // { id: 'clsgYmd', title: '폐쇄일자' }, @@ -320,12 +320,12 @@ export const SwtrmtFcltCol2 = [ { id: 'dschgWtstNm', title: '방류수계명' }, // { id: 'pllnArea', title: '부지면적(m²)' }, // { id: 'mngRprtvBzentyNm', title: '관리대행업체명' }, - { id: 'swtrmtPpltnCnt', title: '하수처리인구수(인)' }, + { id: 'swtrmtPpltnCnt', title: '하수\n처리\n인구수\n(인)' }, // { id: 'prcsZoneAreaSum', title: '처리구역면적합계(m²)' }, // { id: 'xcrd', title: 'X좌표' }, // { id: 'ycrd', title: 'Y좌표' }, // { id: 'oprtngSttsCd', title: '가동상태코드' }, - { id: 'rprsMsrcCn', title: '대표공법내용' }, + { id: 'rprsMsrcCn', title: '대표\n공법\n내용' }, // { id: 'sttyCrtrBod', title: '법정기준생물화학적산소요구량(mg/L)' }, // { id: 'sttyCrtrToc', title: '법정기준TOC(mg/L)' }, // { id: 'sttyCrtrSs', title: '법정기준SS(mg/L)' }, From fcfaa23dc398f13614057c587c978da93a457f4e Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Wed, 30 Jul 2025 15:35:45 +0900 Subject: [PATCH 10/71] =?UTF-8?q?fix:=20=EA=B2=80=EC=83=89=20=EC=B0=BD=20?= =?UTF-8?q?=EC=9C=84=20=EC=95=84=EB=9E=98=20=EA=B0=84=EA=B2=A9=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx | 2 +- apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx | 2 +- apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx index 310528aed..b91271251 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx @@ -78,7 +78,7 @@ const BasicTab: React.FC<BasicTabProps> = ({ setVar2, }) => { return ( - <div className="relative border rounded-lg p-6 w-full h-[10vh]"> + <div className="relative border rounded-lg p-5 w-full h-[10vh]"> {/* 구분 */} <div className="mb-3 flex items-center justify-between gap-4"> <div className="flex items-center"> diff --git a/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx index 5f24c65fa..9a46e2bf8 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx @@ -111,7 +111,7 @@ const OperationTab: React.FC<OperationTabProps> = ({ setYearFilter, }) => { return ( - <div className="relative flex flex-col border rounded-lg p-6 w-full h-[10vh]"> + <div className="relative flex flex-col border rounded-lg p-5 w-full h-[10vh]"> <div className="flex items-center mb-3"> <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">업무구분</Label> <SelectBox diff --git a/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx index 53dd6d0e0..5ca3f7059 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx @@ -165,7 +165,7 @@ const ReportTab: React.FC<BasicTabProps> = ({ open }) => { return ( <> - <div className="relative flex flex-col border rounded-lg p-6 w-full h-[10vh]"> + <div className="relative flex flex-col border rounded-lg p-5 w-full h-[10vh]"> <div className="flex items-center mb-3"> <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시도</Label> <SelectBox className="w-60 mr-8" From 18b8a6edabc424b3c3747d816a2b3dcd5d9de8a4 Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Wed, 30 Jul 2025 15:42:47 +0900 Subject: [PATCH 11/71] =?UTF-8?q?fix:=20searchModal.tsx=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=ED=97=A4=EB=8D=94=20=EC=A4=84=20=EB=B0=94?= =?UTF-8?q?=EA=BF=88=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/(map-layout)/map/components/common/table.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 0cfc261a2..51ef93ddf 100644 --- a/apps/sn/app/(map-layout)/map/components/common/table.ts +++ b/apps/sn/app/(map-layout)/map/components/common/table.ts @@ -298,9 +298,9 @@ export const SwtrmtFcltCol = [ ]; export const SwtrmtFcltCol2 = [ { id: 'rowNumber', title: '일련\n번호' }, - { id: 'swtrmtFcltNm', title: '하수\n처리\n시설명' }, + { id: 'swtrmtFcltNm', title: '하수처리\n시설명' }, { id: 'fcltCpct', title: '시설\n용량\n(㎥/d)' }, - { id: 'rgnExpln', title: '지역\n설명' }, + { id: 'rgnExpln', title: '지역설명' }, // { id: 'cmcnYmd', title: '준공일자' }, { id: 'oprtngStrtYmd', title: '가동개시일자' }, // { id: 'clsgYmd', title: '폐쇄일자' }, @@ -311,7 +311,7 @@ export const SwtrmtFcltCol2 = [ // { id: 'sggNm', title: '시군구명' }, // { id: 'sareaCd', title: '권역코드' }, { id: 'sareaNm', title: '권역명' }, - { id: 'lrgeSareaNm', title: '대권역명' }, + // { id: 'lrgeSareaNm', title: '대권역명' }, { id: 'rvrNm', title: '하천명' }, // { id: 'addr', title: '주소' }, // { id: 'dtlAddr', title: '상세주소' }, @@ -332,7 +332,7 @@ export const SwtrmtFcltCol2 = [ // { id: 'sttyCrtrTn', title: '법정기준총질소(mg/L)' }, // { id: 'sttyCrtrTph', title: '법정기준총인(mg/L)' }, // { id: 'sttyCrtrClbclCnt', title: '법정기준대장균수(군수/100mL)' }, - { id: 'bsnNm', title: '유역명' }, + // { id: 'bsnNm', title: '유역명' }, // { id: 'obsrvnSptCd', title: '관측지점코드' }, ]; @@ -353,9 +353,9 @@ export const SwtrmtPrcsZoneCol2 = [ { id: 'swtrmtZoneNm', title: '하수처리구역명' }, // { id: 'swtrmt_fclt_cd', title: '하수처리시설코드' }, // { id: 'ctpv_cd', title: '시도코드' }, - { id: 'ctpvNm', title: '시도명' }, + { id: 'ctpvNm', title: '시도' }, // { id: 'sgg_cd', title: '시군구코드' }, - { id: 'sggNm', title: '시군구명' }, + { id: 'sggNm', title: '시군구' }, { id: 'swtrmtFcltNm', title: '하수처리시설명' }, ]; From df18a795280467afdfe9c8eb6c6e08b950e47141 Mon Sep 17 00:00:00 2001 From: Your Name <mind-003@mind-one.co.kr> Date: Wed, 30 Jul 2025 16:00:22 +0900 Subject: [PATCH 12/71] =?UTF-8?q?=EB=AC=BC=EC=82=AC=EC=9A=A9=EB=9F=89=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EB=B0=8F=20null=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/fo/app/pages/dashboard/facilityInfo/page.tsx | 7 +++---- apps/fo/app/store/dashboard/index.tsx | 6 +++--- apps/fo/app/store/underWaterInput/index.tsx | 3 --- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/apps/fo/app/pages/dashboard/facilityInfo/page.tsx b/apps/fo/app/pages/dashboard/facilityInfo/page.tsx index fa6905fa2..9c16161f5 100644 --- a/apps/fo/app/pages/dashboard/facilityInfo/page.tsx +++ b/apps/fo/app/pages/dashboard/facilityInfo/page.tsx @@ -169,10 +169,9 @@ export default function ProcessFacilitySearch({searchYear = '', fcltyList = []}: } else if (index === 8) { el['item'] = (Number(searchYear)-0).toString(); el['provisionalApproval'] = waterUsage1; - } else if(index === 4){ - el['provisionalApproval'] = Math.round((Number(waterUsage1)+Number(waterUsage2)+Number(waterUsage3))/3 * 100)/100; } }); + console.log('planFcltyList :', planFcltyList); } }, [waterUsage1, waterUsage2, waterUsage3, searchYear, planFcltyList]); // 연도가 바뀌면 hasSearched를 false로 리셋 @@ -226,8 +225,8 @@ export default function ProcessFacilitySearch({searchYear = '', fcltyList = []}: :(index < 5 || index > 5)&& (index!= 4) && (head.id ==='provisionalApproval'||head.id ==='now'||head.id ==='stepOne'||head.id ==='stepTwo'||head.id ==='stepThree'||head.id ==='stepFour') ?<TableCell className="text-right" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{el[head.id]}</TableCell> :(head.id === "provisionalApproval" && index === 4) - ?<TableCell colSpan={6} className="text-right border border-[var(--border)] text-[rgb(100,119,132)]" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{el[head.id]}</TableCell> - :((head.id ==='now'||head.id ==='stepOne'||head.id ==='stepTwo'||head.id ==='stepThree'||head.id ==='stepFour') && index === 4) + ?<TableCell colSpan={6} className="text-right border border-[var(--border)] text-[rgb(100,119,132)]" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{Math.round((Number(waterUsage1)+Number(waterUsage2)+Number(waterUsage3))/3 * 100)/100}</TableCell> + :((head.id ==='now'||head.id ==='stepOne'||head.id ==='stepTwo'||head.id ==='stepThree'||head.id ==='stepFour') ) ?null :(head.id === 'item' && index != 5)? <TableCell className="text-center text-[rgb(100,119,132)]" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{el[head.id]}</TableCell> diff --git a/apps/fo/app/store/dashboard/index.tsx b/apps/fo/app/store/dashboard/index.tsx index a415d89b3..b23006c10 100644 --- a/apps/fo/app/store/dashboard/index.tsx +++ b/apps/fo/app/store/dashboard/index.tsx @@ -1232,9 +1232,9 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) } if(operationalStatusList[0] && operationalStatusList[7]){ - operationalStatusList[7].groundWaterAvg = operationalStatusList[0].groundWaterAvg === null ? '' : data.yearData.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterAvg) / Number(data.yearData.AVG_INFL_AMNT) * 100) * 10) / 10+'%'; - operationalStatusList[7].groundWaterSummer = operationalStatusList[0].groundWaterSummer === null ? '' : data.summer.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterSummer) / Number(data.summer.AVG_INFL_AMNT) * 100) * 10) / 10+'%'; - operationalStatusList[7].groundWaterWinter = operationalStatusList[0].groundWaterWinter === null ? '' : data.winter.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterWinter) / Number(data.winter.AVG_INFL_AMNT) * 100) * 10) / 10+'%'; + operationalStatusList[7].groundWaterAvg = operationalStatusList[0].groundWaterAvg === null ? '' : data.yearData && data.yearData.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterAvg) / (data.yearData && data.yearData.AVG_INFL_AMNT ? Number(data.yearData.AVG_INFL_AMNT) : 0) * 100) * 10) / 10+'%'; + operationalStatusList[7].groundWaterSummer = operationalStatusList[0].groundWaterSummer === null ? '' : data.summer && data.summer.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterSummer) / (data.summer && data.summer.AVG_INFL_AMNT ? Number(data.summer.AVG_INFL_AMNT) : 0) * 100) * 10) / 10+'%'; + operationalStatusList[7].groundWaterWinter = operationalStatusList[0].groundWaterWinter === null ? '' : data.winter && data.winter.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterWinter) / (data.winter && data.winter.AVG_INFL_AMNT ? Number(data.winter.AVG_INFL_AMNT) : 0) * 100) * 10) / 10+'%'; operationalStatusList[7].rdi = operationalStatusList[0].rdi === null ? '' : data.summerBod && data.summerBod.INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].rdi) / (data.summerBod&&data.summerBod.INFL_AMNT&&data.summerBod.INFL_AMNT!='null'?Number(data.summerBod.INFL_AMNT):0) * 100) * 10) / 10+'%'; /* operationalStatusList[7].groundWaterAvg = operationalStatusList[0].groundWaterAvg === null ? '' : Number(Number(operationalStatusList[0].groundWaterAvg) / Number(data.yearData.AVG_INFL_AMNT)) * 100; operationalStatusList[7].groundWaterSummer = operationalStatusList[0].groundWaterSummer === null ? '' : Number(Number(operationalStatusList[0].groundWaterSummer) / Number(data.summer.AVG_INFL_AMNT)) * 100; diff --git a/apps/fo/app/store/underWaterInput/index.tsx b/apps/fo/app/store/underWaterInput/index.tsx index 70964d496..589c35962 100644 --- a/apps/fo/app/store/underWaterInput/index.tsx +++ b/apps/fo/app/store/underWaterInput/index.tsx @@ -446,7 +446,6 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy let maxWaterUsage = 0; let minWaterUsage = 9999999999; let avgWaterUsage = 0; - console.log('monthData : ', monthData); monthData.forEach((item, index) => { item.usege = item.sum/item.cnt tableData.push({ @@ -465,8 +464,6 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy }); avgWaterUsage = tot/cnt - console.log('avgWaterUsage : ', avgWaterUsage); - console.log('tableData : ', tableData); const newData= [ { msrDt: '평균', From c89c588487632650abb4e7be3c53a08fab4e12ab Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Wed, 30 Jul 2025 16:25:09 +0900 Subject: [PATCH 13/71] =?UTF-8?q?fix:=20=EC=8B=9C=EB=8F=84,=20=EC=8B=9C?= =?UTF-8?q?=EA=B5=B0=EA=B5=AC=20=ED=95=98=EB=93=9C=EC=BD=94=EB=94=A9=20->?= =?UTF-8?q?=20=EC=95=8C=EC=97=90=EC=8A=A4=EC=9B=90=20API=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(=EC=9D=B4=ED=9B=84=20Store=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=ED=95=84=EC=9A=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ui/SidoSigunguSelectBox.tsx | 81 +++++++++++++++---- 1 file changed, 65 insertions(+), 16 deletions(-) diff --git a/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx b/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx index 00f8f1dbb..e32875d42 100644 --- a/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx +++ b/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx @@ -1,6 +1,7 @@ -import React, { useMemo } from "react"; +import React, { useState, useMemo, useEffect } from "react"; import { SelectBox } from '@common/business_components/ui' import { Label } from '@common/components'; +import { getGeoData } from '@/components/lib/api'; interface Option { id: number; @@ -15,19 +16,19 @@ interface SidoSigunguSelectBoxProps { className?: string; } -const sidoList: { id: number, name: string }[] = [ - { id: 0, name: '전체' }, - { id: 26, name: '부산광역시' }, - { id: 28, name: '인천광역시' }, - { id: 30, name: '대전광역시' }, - { id: 41, name: '경기도' }, - { id: 42, name: '강원도' }, - { id: 44, name: '충청남도' }, - { id: 45, name: '전라북도' }, - { id: 46, name: '전라남도' }, - { id: 47, name: '경상북도' }, - { id: 48, name: '경상남도' }, -]; +// const sidoList: { id: number, name: string }[] = [ +// { id: 0, name: '전체' }, +// { id: 26, name: '부산광역시' }, +// { id: 28, name: '인천광역시' }, +// { id: 30, name: '대전광역시' }, +// { id: 41, name: '경기도' }, +// { id: 42, name: '강원도' }, +// { id: 44, name: '충청남도' }, +// { id: 45, name: '전라북도' }, +// { id: 46, name: '전라남도' }, +// { id: 47, name: '경상북도' }, +// { id: 48, name: '경상남도' }, +// ]; const sigunguMap: Record<number, { id: number; name: string }[]> = { 26: [ { id: 0, name: '전체' }, @@ -134,9 +135,47 @@ export function SidoSigunguSelectBox({ onSigunguChange, className = "", }: SidoSigunguSelectBoxProps) { + + const [ctpvList, setCtpvList] = useState<Option[]>([]); + const [sggList, setSggList] = useState<Option[]>([]); + // 선택된 시도에 따라 시군구 리스트를 가져옴 + const fetchCtpvData = useMemo(() => ( + async () => { + const ctpvData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_ctpv&outputFormat=application%2Fjson'); + const ctpvList: Option[] = ctpvData.features + .map((feature: any) => ({ + id: feature.properties.mctpv_cd.trim(), + name: feature.properties.mctpv_korn_nm + })) + .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); // 가나다순 정렬 + setCtpvList(ctpvList); + } + ), []); + + // 선택된 시도에 따라 시군구 리스트를 가져옴 + const fetchSggData = useMemo(() => ( + async () => { + const sggData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_sgg&outputFormat=application%2Fjson'); + const sggList: Option[] = sggData.features + .map((feature: any) => ({ id: feature.properties.sgg_cd, name: feature.properties.sgg_korn_nm })) + .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); // 가나다순 정렬 + setSggList(sggList); + } + ), []); + + useEffect(() => { + (async () => { + await fetchCtpvData(); + await fetchSggData(); + onSidoChange(0); + onSigunguChange(0); + })(); + }, []); + const sigunguList = useMemo(() => sigunguMap[sido] || [{ id: 0, name: '전체' }], [sido]); + return ( <div className={`flex items-center justify-center ${className} mr-8`}> <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시도</Label> @@ -144,7 +183,11 @@ export function SidoSigunguSelectBox({ className="w-60 mr-8" selectClass="rounded-lg bg-white" label="" - selectArray={sidoList.map(opt => ({ val: opt.id, text: opt.name }))} + selectArray={ + [{ val: 0, text: '전체' }, + ...ctpvList.map(opt => ({ val: opt.id, text: opt.name })) + ] + } value={sido} onSelectValue={val => onSidoChange(Number(val))} selectContentClass="z-[101]" @@ -154,7 +197,13 @@ export function SidoSigunguSelectBox({ className="w-48" selectClass="w-48 rounded-lg bg-white truncate" label="" - selectArray={sigunguList.map(opt => ({ val: opt.id, text: opt.name }))} + selectArray={ + [{ val: 0, text: '전체' }, + ...sggList + .filter(opt => String(opt.id).slice(0, 2) === String(sido)) + .map(opt => ({ val: opt.id, text: opt.name })) + ] + } value={sigungu} onSelectValue={val => onSigunguChange(Number(val))} selectContentClass="z-[101]" From a05b0e1cfebfedd21475105bc6ff028b36692d09 Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Wed, 30 Jul 2025 16:33:31 +0900 Subject: [PATCH 14/71] =?UTF-8?q?feat,=20remove:=20-=20GeoServer=20API=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=20=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?-=20=ED=95=98=EB=93=9C=EC=BD=94=EB=94=A9=20=EC=8B=9C=EB=8F=84,?= =?UTF-8?q?=20=EC=8B=9C=EA=B5=B0=EA=B5=AC=20=ED=85=8C=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/components/lib/api/index.ts | 16 ++- .../components/ui/SidoSigunguSelectBox.tsx | 112 ------------------ 2 files changed, 14 insertions(+), 114 deletions(-) diff --git a/apps/sn/app/components/lib/api/index.ts b/apps/sn/app/components/lib/api/index.ts index ab8936329..6574567ad 100644 --- a/apps/sn/app/components/lib/api/index.ts +++ b/apps/sn/app/components/lib/api/index.ts @@ -85,7 +85,7 @@ export const deleteData = async (url: string, data?: object): Promise<any> => { return res; }; -// RSONE DC API 호출 +// DC API 호출 export const getDcData = async (url: string, data?: object): Promise<any> => { const res = await fetch('/dc-service' + url, { method: "GET", @@ -97,7 +97,7 @@ export const getDcData = async (url: string, data?: object): Promise<any> => { throw new Error('Failed to fetch data'); }; -// RSONE DC API 호출 +// CMS API 호출 export const getCmsData = async (url: string, data?: object): Promise<any> => { console.log(url); const res = await fetch('/cms-service' + url, { @@ -110,6 +110,18 @@ export const getCmsData = async (url: string, data?: object): Promise<any> => { throw new Error('Failed to fetch data'); }; +// GeoServer API 호출 +export const getGeoData = async (url: string, data?: object): Promise<any> => { + const res = await fetch('/geoserver' + url, { + method: "GET", + }); + if (res.ok) { + const data = await res.json(); + return data; + } + throw new Error('Failed to fetch data'); +}; + export function getCookie(name: string) { const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); if (match && match[2]) { diff --git a/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx b/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx index e32875d42..d27da3791 100644 --- a/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx +++ b/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx @@ -16,118 +16,6 @@ interface SidoSigunguSelectBoxProps { className?: string; } -// const sidoList: { id: number, name: string }[] = [ -// { id: 0, name: '전체' }, -// { id: 26, name: '부산광역시' }, -// { id: 28, name: '인천광역시' }, -// { id: 30, name: '대전광역시' }, -// { id: 41, name: '경기도' }, -// { id: 42, name: '강원도' }, -// { id: 44, name: '충청남도' }, -// { id: 45, name: '전라북도' }, -// { id: 46, name: '전라남도' }, -// { id: 47, name: '경상북도' }, -// { id: 48, name: '경상남도' }, -// ]; -const sigunguMap: Record<number, { id: number; name: string }[]> = { - 26: [ - { id: 0, name: '전체' }, - // ...부산산광역시의 시군구 추가 - ], - 28: [ - { id: 0, name: '전체' }, - // ...인천광역시의 시군구 추가 - ], - 30: [ - { id: 0, name: '전체' }, - // ...대전광역시의 시군구 추가 - ], - 41: [ - { id: 0, name: '전체' }, - { id: 41820, name: '가평군' }, - { id: 41550, name: '안성시' }, - { id: 41830, name: '양평군' }, - { id: 41670, name: '여주시' }, - { id: 41500, name: '이천시' }, - { id: 41430, name: '의왕시' }, - { id: 41150, name: '의정부시' }, - { id: 41480, name: '파주시' }, - { id: 41650, name: '포천시' }, - // ...경기도의 시군구 추가 - ], - 42: [ - { id: 0, name: '전체' }, - { id: 42170, name: '동해시' }, - { id: 42110, name: '춘천시' }, - { id: 42720, name: '홍천군' }, - // ...강원도의 시군구 추가 - ], - 44: [ - { id: 0, name: '전체' }, - { id: 44770, name: '서천군' }, - // ...충청남도의 시군구 추가 - ], - 45: [ - { id: 0, name: '전체' }, - { id: 45130, name: '군산시' }, - { id: 45190, name: '남원시' }, - { id: 45730, name: '무주군' }, - { id: 45800, name: '부안군' }, - { id: 45770, name: '순창군' }, - { id: 45140, name: '익산시' }, - { id: 45750, name: '임실군' }, - { id: 45710, name: '완주군' }, - { id: 45110, name: '전주시' }, - // ...전라북도의 시군구 추가 - ], - 46: [ - { id: 0, name: '전체' }, - { id: 46810, name: "강진군" }, - { id: 46770, name: "고흥군" }, - { id: 46720, name: "곡성군" }, - { id: 46730, name: "구례군" }, - { id: 46230, name: "광양시" }, - { id: 46170, name: "나주시" }, - { id: 46110, name: "목포시" }, - { id: 46840, name: "무안군" }, - { id: 46150, name: "순천시" }, - { id: 46910, name: "신안군" }, - { id: 46130, name: "여수시" }, - { id: 46890, name: "완도군" }, - // ...전라남도의 시군구 추가 - ], - 47: [ - { id: 0, name: '전체' }, - { id: 47130, name: "경주시" }, - { id: 47830, name: "고령군" }, - { id: 47190, name: "구미시" }, - { id: 47720, name: "군위군" }, - { id: 47150, name: "김천시" }, - { id: 47280, name: "문경시" }, - { id: 47920, name: "봉화군" }, - { id: 47250, name: "상주시" }, - { id: 47770, name: "영덕군" }, - { id: 47760, name: "영양군" }, - { id: 47210, name: "영주시" }, - { id: 47900, name: "예천군" }, - { id: 47930, name: "울진군" }, - { id: 47730, name: "의성군" }, - { id: 47750, name: "청송군" }, - { id: 47850, name: "칠곡군" }, - { id: 47110, name: "포항시" }, - // ...경상북도의 시군구 추가 - ], - 48: [ - { id: 0, name: '전체' }, - { id: 48310, name: "거제시" }, - { id: 48250, name: "김해시" }, - { id: 48270, name: "밀양시" }, - { id: 48220, name: "통영시" }, - // ...경상남도의 시군구 추가 - ], - // ...나머지 시도별 시군구 추가 -}; - export function SidoSigunguSelectBox({ sido, sigungu, From 5cc86d81f1aa3675725a8a83885b3cc65912f7f3 Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Wed, 30 Jul 2025 16:35:11 +0900 Subject: [PATCH 15/71] =?UTF-8?q?refactor:=20featureClickEventPopup.tsx=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(popupComponent=20?= =?UTF-8?q?=EB=82=B4=EB=B6=80=EB=A1=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../featureClickEventPopup.tsx | 104 +++--------------- 1 file changed, 14 insertions(+), 90 deletions(-) rename apps/sn/app/(map-layout)/map/{ => components/popupComponent}/featureClickEventPopup.tsx (84%) diff --git a/apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx similarity index 84% rename from apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx rename to apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx index e9a4f9e9a..679122d6b 100644 --- a/apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx @@ -5,13 +5,13 @@ 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, useCallback, useMemo } from "react"; -import { getLayerContentComponent } from "./components/common"; -import { SwtrmtFcltCol, SwtrmtFcltCol2, SwtrmtPrcsZoneCol, SwtrmtPrcsZoneCol2, untrtSwgRainCol1, untrtSwgRainCol2, MsSwagePplCol, MsSwagePplCol2, ExclnSewagePumpCol, ExclnSewagePumpCol2, DrtwSewagePumpCol, DrtwSewagePumpCol2, MsSwageMhlCol, MsSwageMhlCol2, stofcbrCol, stofcbrCol2, MS_SWAGE_MHL_CODES, DRTW_SEWAGE_PUMP_USE_CODES } from "./components/common/table"; -import InfilChart from './components/popupComponent/chart/infilChart'; -import RdiiChart from './components/popupComponent/chart/rdiiChart'; -import { useInfilChartModalStore, useRdiiChartModalStore } from "./components/popupComponent/chartModalStore"; +import { getLayerContentComponent } from "../common"; +import { SwtrmtFcltCol, SwtrmtFcltCol2, SwtrmtPrcsZoneCol, SwtrmtPrcsZoneCol2, untrtSwgRainCol1, untrtSwgRainCol2, MsSwagePplCol, MsSwagePplCol2, ExclnSewagePumpCol, ExclnSewagePumpCol2, DrtwSewagePumpCol, DrtwSewagePumpCol2, MsSwageMhlCol, MsSwageMhlCol2, stofcbrCol, stofcbrCol2, MS_SWAGE_MHL_CODES, DRTW_SEWAGE_PUMP_USE_CODES } from "../common/table"; +import InfilChart from './chart/infilChart'; +import RdiiChart from './chart/rdiiChart'; +import { useInfilChartModalStore, useRdiiChartModalStore } from "./chartModalStore"; import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; -import { codeMapper } from "./components/common/codeMapper"; +import { codeMapper } from "../common/codeMapper"; import { searchUpperPpls } from '@common/business_components/ui/map/Gis'; import LoadingOverlay from '@/components/ui/LoadingBar'; @@ -33,9 +33,10 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, // 🎯 InfilChart 및 RdiiChart 관련 상태 추가 const { setIsOpen: setIsModalInfil } = useInfilChartModalStore(); const { setIsOpen: setIsModalRdii } = useRdiiChartModalStore(); - const [curPage, setCurPage] = useState(1); // 현재 페이지 상태 const [isClicked, setIsClicked] = useState(true); // 클릭 여부 상태 const [isLoading, setIsLoading] = useState(false); // 로딩 상태 + const [chmbrXcnts, setChmbrXcnts] = useState(); // 로딩 상태 + const [chmbrYdnts, setChmbrYdnts] = useState(); // 로딩 상태 // 🎯 관로관리번호 추출 const pplMngNo = featureData?.values_?.fclt_mng_no || ""; @@ -263,18 +264,9 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, data: { ...processedFormattedData } })); - // 강우/측정 정보 데이터 호출 - if (tableData?.chmbr_xcnts && tableData?.chmbr_ydnts) { - fetchRainMeasureData(tableData.chmbr_xcnts, tableData.chmbr_ydnts) - .then(response => { - console.log('additionalResponse', response); - setRainMeasureData(response || []); - }) - .catch(error => { - console.error('추가 데이터 로딩 실패:', error); - setRainMeasureData([]); - }); - } + setChmbrXcnts(data.chmbr_xcnts); + setChmbrYdnts(data.chmbr_ydnts); + break; default: break; @@ -317,76 +309,6 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, fetchData(); }, [featureId, processedData]); - // 🎯 추가 데이터 API 호출 함수 - const fetchRainMeasureData = useCallback(async (chmbrXcnts: string, chmbrYdnts: string) => { - if (!chmbrXcnts || !chmbrYdnts) { - console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { chmbrXcnts, chmbrYdnts }); - return []; - } - - try { - const queryParams = new URLSearchParams({ - chmbrXcnts: chmbrXcnts, - chmbrYdnts: chmbrYdnts, - pageable: `{}`, - }); - - console.log(`추가 데이터 API 호출 시작: v1/onsemiro/getSwgRainsByGeomety?${queryParams.toString()}`); - - const result = await getData(`v1/onsemiro/getSwgRainsByGeomety?${queryParams.toString()}`); - - console.log('추가 데이터 API 호출 성공:', result); - - // 🎯 result가 페이지네이션 객체인 경우 content 배열 추출 - let dataArray: any[] = []; - if (result && typeof result === 'object' && 'content' in result && Array.isArray(result.content)) { - dataArray = result.content; - } else if (Array.isArray(result)) { - dataArray = result; - } else { - console.warn('예상치 못한 API 응답 형식:', result); - return []; - } - - // 🎯 각 아이템을 전처리하여 반환 - const processedDataArray = dataArray.map((data: any) => { - // 🎯 formatDisplayData로 기본 포맷팅 적용 - const formattedItem = formatDisplayData(data); - - // 🎯 추가 데이터 조합 - return { - ...formattedItem, - flwmtrCombined: `${data.flwmtrMesureTpEtc || '-'} / ${data.flwmtrKnd || '-'}`, - }; - }); - - return processedDataArray; - } catch (error) { - console.error('추가 데이터 로딩 실패:', error); - return []; - } - }, []); - - // 🎯 추가 데이터는 별도 useEffect로 비동기 로딩 - useEffect(() => { - if (layerNm === '중점우수토실' && featureData?.values_) { - const data = featureData.values_; - - if (data?.chmbr_xcnts && data?.chmbr_ydnts) { - // 🎯 추가 데이터를 비동기로 로딩 (UI 블로킹 없음) - fetchRainMeasureData(data.chmbr_xcnts, data.chmbr_ydnts) - .then(response => { - console.log('추가 데이터 로딩 완료:', response); - setRainMeasureData(response || []); - }) - .catch(error => { - console.error('추가 데이터 최종 로딩 실패:', error); - setRainMeasureData([]); - }); - } - } - }, [layerNm, featureData?.values_, fetchRainMeasureData]); - // 저장 시 모달 띄우기 const handleSaveClick = () => { setIsModalOpen(true); // 모달 열기 @@ -555,8 +477,10 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, featureData={fetchData} isMainSsc={isMainSsc} isDrtwSewagePump={isDrtwSewagePump} - rainMeasureData={rainMeasureData} + // rainMeasureData={rainMeasureData} onSaveClick={handleSaveClick} + chmbrXcnts={chmbrXcnts} + chmbrYdnts={chmbrYdnts} /> } </div> From 340814439a123249f8355dce47219341a59ede98 Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Wed, 30 Jul 2025 16:44:11 +0900 Subject: [PATCH 16/71] =?UTF-8?q?fix,=20remove:=20-=20featureClickEventPop?= =?UTF-8?q?up.tsx=20=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95=20-=20?= =?UTF-8?q?=EC=9D=B4=EC=A0=84=20=EC=8B=9C=EB=8F=84,=20=EC=8B=9C=EA=B5=B0?= =?UTF-8?q?=EA=B5=AC=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/(map-layout)/map/page.tsx | 2 +- apps/sn/app/components/ui/SidoSigunguSelectBox.tsx | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index 2dbbcdd1a..b2ed8ecd2 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -5,7 +5,7 @@ import { getData } from "../../components/lib/api"; import { useCallback, useEffect, useRef, useState } from "react"; import GisApp from "@common/business_components/ui/map/lib/gis.core"; import SearchModal from "./searchModal/searchModal"; -import FeatureClickEventPopup from "./featureClickEventPopup"; +import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup"; // 타입 정의 추가 export type LyrStngProps = { diff --git a/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx b/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx index d27da3791..86e144073 100644 --- a/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx +++ b/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx @@ -61,8 +61,6 @@ export function SidoSigunguSelectBox({ })(); }, []); - const sigunguList = useMemo(() => sigunguMap[sido] || [{ id: 0, name: '전체' }], [sido]); - return ( <div className={`flex items-center justify-center ${className} mr-8`}> From bbfd7fd201af0e564b566f01390152a27e69c9a0 Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Wed, 30 Jul 2025 17:00:15 +0900 Subject: [PATCH 17/71] =?UTF-8?q?fix:=20=EC=97=B0=EC=9B=94=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=20=EC=88=98=EC=A0=95=20(=EB=85=84=EC=9B=94=EC=9D=BC?= =?UTF-8?q?=20->=20-=EB=A1=9C=20=EA=B5=AC=EB=B6=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/components/lib/utils/date.ts | 8 +++++--- apps/sn/app/components/lib/utils/formatDisplayValue.tsx | 9 +++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/sn/app/components/lib/utils/date.ts b/apps/sn/app/components/lib/utils/date.ts index a401ad738..30bea45a0 100644 --- a/apps/sn/app/components/lib/utils/date.ts +++ b/apps/sn/app/components/lib/utils/date.ts @@ -103,8 +103,10 @@ export const formatDateString = (dateStr: string): string => { } const year = cleanStr.substring(0, 4); - const month = parseInt(cleanStr.substring(4, 6)); // parseInt로 앞의 0 제거 - const day = parseInt(cleanStr.substring(6, 8)); // parseInt로 앞의 0 제거 + const month = cleanStr.substring(4, 6); // parseInt로 앞의 0 제거 + // const month = parseInt(cleanStr.substring(4, 6)); // parseInt로 앞의 0 제거 + // const day = parseInt(cleanStr.substring(6, 8)); // parseInt로 앞의 0 제거 + const day = cleanStr.substring(6, 8); // parseInt로 앞의 0 제거 - return `${year}년 ${month}월 ${day}일`; + return `${year}-${month}-${day}`; }; \ No newline at end of file diff --git a/apps/sn/app/components/lib/utils/formatDisplayValue.tsx b/apps/sn/app/components/lib/utils/formatDisplayValue.tsx index a5bd847cf..289b05b97 100644 --- a/apps/sn/app/components/lib/utils/formatDisplayValue.tsx +++ b/apps/sn/app/components/lib/utils/formatDisplayValue.tsx @@ -21,7 +21,7 @@ export const formatDisplayData = (data: { [key: string]: any }): { [key: string] if (/^\d{6}$/.test(dateStr)) { const year = dateStr.substring(0, 4); const month = parseInt(dateStr.substring(4, 6)); - formattedData[fieldId] = `${year}년 ${month}월`; + formattedData[fieldId] = `${year}-${month}`; } else { formattedData[fieldId] = String(value); } @@ -45,9 +45,10 @@ export const formatDisplayData = (data: { [key: string]: any }): { [key: string] // 🎯 undefined 체크 후 parseInt 적용 if (year && month && day && hour && minute) { - const formattedMonth = parseInt(month); - const formattedDay = parseInt(day); - formattedData[fieldId] = `${year}년 ${formattedMonth}월 ${formattedDay}일 ${hour}시 ${minute}분 ${second}초`; + // const formattedMonth = parseInt(month); + // const formattedDay = parseInt(day); + // formattedData[fieldId] = `${year}-${formattedMonth}-${formattedDay} ${hour}:${minute}:${second}`; + formattedData[fieldId] = `${year}-${month}-${day} ${hour}:${minute}:${second}`; } else { formattedData[fieldId] = String(value); } From 6c6b5e662cb8c8dd867a8827d554d003137ee4a3 Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Wed, 30 Jul 2025 17:47:16 +0900 Subject: [PATCH 18/71] =?UTF-8?q?feat:=20=ED=95=98=EC=88=98=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EC=8B=9C=EC=84=A4=EC=97=90=20=EC=8B=9C=EB=8F=84,=20?= =?UTF-8?q?=EC=8B=9C=EA=B5=B0=EA=B5=AC=20=EA=B2=80=EC=83=89=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(map-layout)/map/searchModal/basicTab.tsx | 10 +++++ .../map/searchModal/searchModal.tsx | 26 ++++++++----- .../map/searchModal/swagePrcsFclt.tsx | 37 ++++++++++++++++--- .../components/ui/SidoSigunguSelectBox.tsx | 22 +++++++++-- 4 files changed, 76 insertions(+), 19 deletions(-) diff --git a/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx index b91271251..57afc22c7 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx @@ -39,7 +39,9 @@ interface BasicTabProps { // setTableCount?: (count: number) => void; setBasicType: (type: string) => void; setCity: (city: number) => void; + setCtpvName: (name: string) => void; setSgg: (sgg: number) => void; + setSggName: (name: string) => void; setYearFilter: (filter: { start: string; end: string }) => void; setStart1: (value: string) => void; setStart2: (value: string) => void; @@ -68,7 +70,9 @@ const BasicTab: React.FC<BasicTabProps> = ({ // setTableCount, setBasicType, setCity, + setCtpvName, setSgg, + setSggName, setYearFilter, setStart1, setStart2, @@ -124,12 +128,18 @@ const BasicTab: React.FC<BasicTabProps> = ({ start2={start2} end1={end1} end2={end2} + city={city} + sgg={sgg} yearFilter={yearFilter} setVar1={setVar1} setStart1={setStart1} setStart2={setStart2} setEnd1={setEnd1} setEnd2={setEnd2} + setCity={setCity} + setCtpvName={setCtpvName} + setSgg={setSgg} + setSggName={setSggName} setYearFilter={setYearFilter} handleSearch={handleSearch} /> diff --git a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx index afc61252e..b453ba20f 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx @@ -63,8 +63,10 @@ const SearchModal: React.FC<SearchModalProps> = ({ const [isResetSelection, setIsResetSelection] = useState<boolean>(false); const [isResetDetailSelection, setIsResetDetailSelection] = useState<boolean>(false); const [detailBizId, setDetailBizId] = useState(0); - const [city, setCity] = useState(0); - const [sgg, setSgg] = useState(0); + const [city, setCity] = useState<number>(0); + const [ctpvName, setCtpvName] = useState<string>(''); + const [sgg, setSgg] = useState<number>(0); + const [sggName, setSggName] = useState<string>(''); const [ctpvCd, setCtpvCd] = useState('0'); // 하수정비기본계획 report const [sigungu, setSigungu] = useState('0'); // 하수정비기본계획 report const [gubun, setGubun] = useState(""); // 하수정비기본계획 report @@ -341,13 +343,17 @@ const SearchModal: React.FC<SearchModalProps> = ({ cqlFilters.push(`fclt_cpct BETWEEN ${start1} AND ${end1}`); else if (start1) cqlFilters.push(`fclt_cpct >= ${start1}`); else if (end1) cqlFilters.push(`fclt_cpct <= ${end1}`); - // 처리구역 면적 - if (start2 && end2) - cqlFilters.push(`prcsZoneAreaSum BETWEEN ${start2} AND ${end2}`); - else if (start2) cqlFilters.push(`prcsZoneAreaSum >= ${start2}`); - else if (end2) cqlFilters.push(`prcsZoneAreaSum <= ${end2}`); - // 하수처리시설명 - if (var1) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${var1}%'`); + + // 시도 + if (ctpvName) cqlFilters.push(`ctpv_nm ILIKE '%${ctpvName}%'`); + if (sggName) cqlFilters.push(`sgg_nm ILIKE '%${sggName}%'`); + // // 처리구역 면적 + // if (start2 && end2) + // cqlFilters.push(`prcsZoneAreaSum BETWEEN ${start2} AND ${end2}`); + // else if (start2) cqlFilters.push(`prcsZoneAreaSum >= ${start2}`); + // else if (end2) cqlFilters.push(`prcsZoneAreaSum <= ${end2}`); + // // 하수처리시설명 + // if (var1) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${var1}%'`); return cqlFilters; }; // 하수처리구역용 CQL 필터 생성 @@ -575,7 +581,9 @@ const SearchModal: React.FC<SearchModalProps> = ({ handleSearch={handleSearch} setBasicType={setBasicType} setCity={setCity} + setCtpvName={setCtpvName} setSgg={setSgg} + setSggName={setSggName} setYearFilter={setYearFilter} setStart1={setStart1} setStart2={setStart2} diff --git a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx index 6f76554d9..85b32c9ef 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx @@ -1,8 +1,10 @@ // 하수처리시설 +import React from 'react'; import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox'; import { Button, Input, Label } from '@common/components'; -import React from 'react'; +import { SidoSigunguSelectBox } from '@/components/ui/SidoSigunguSelectBox'; + interface SwagePrcsFcltProps { var1: string; @@ -10,12 +12,18 @@ interface SwagePrcsFcltProps { start2: string; end1: string; end2: string; + city: number; + sgg: number; yearFilter: { start: string; end: string }; setVar1: (value: string) => void; setStart1: (value: string) => void; setStart2: (value: string) => void; setEnd1: (value: string) => void; setEnd2: (value: string) => void; + setCity: (value: number) => void; + setCtpvName: (name: string) => void; + setSgg: (value: number) => void; + setSggName: (name: string) => void; setYearFilter: (filter: { start: string; end: string }) => void; handleSearch: () => void; } @@ -26,20 +34,37 @@ const SwagePrcsFclt: React.FC<SwagePrcsFcltProps> = ({ start2, end1, end2, + city, + sgg, yearFilter, setVar1, setStart1, setStart2, setEnd1, setEnd2, + setCity, + setCtpvName, + setSgg, + setSggName, setYearFilter, handleSearch, }) => { return ( <div className="mb-3"> - {/* 가동개시일자 */} <div className="mb-2 flex items-center"> + <SidoSigunguSelectBox + sido={city} + sigungu={sgg} + onSidoChange={(sidoId) => { + setCity(sidoId); + setSgg(0); + }} + onSigunguChange={setSgg} + setCtpvName={setCtpvName} + setSggName={setSggName} + /> + {/* 가동개시일자 */} <Label className="mr-2 w-auto text-xl text-gray-600">가동개시일자</Label> <YearRangeSelectBox start={yearFilter.start} @@ -90,7 +115,7 @@ const SwagePrcsFclt: React.FC<SwagePrcsFcltProps> = ({ }} /> {/* 처리구역 면적 */} - <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">처리구역 면적(m²)</Label> + {/* <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">처리구역 면적(m²)</Label> <Input className="w-[60px] !text-xl placeholder:text-center" placeholder="입력" @@ -123,9 +148,9 @@ const SwagePrcsFclt: React.FC<SwagePrcsFcltProps> = ({ handleSearch(); } }} - /> + /> */} {/* 하수처리시설명 */} - <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">하수처리시설명</Label> + {/* <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">하수처리시설명</Label> <Input className="mr-8 w-[100px] !text-xl placeholder:text-center" placeholder="입력" @@ -139,7 +164,7 @@ const SwagePrcsFclt: React.FC<SwagePrcsFcltProps> = ({ handleSearch(); } }} - /> + /> */} {/* 조회 버튼 */} <div className="flex-1 flex justify-end"> <Button className="text-base py-2" onClick={handleSearch}>조회</Button> diff --git a/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx b/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx index 86e144073..96f7841b4 100644 --- a/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx +++ b/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx @@ -12,7 +12,9 @@ interface SidoSigunguSelectBoxProps { sido: number; sigungu: number; onSidoChange: (id: number) => void; + setCtpvName?: (name: string) => void; onSigunguChange: (id: number) => void; + setSggName?: (name: string) => void; className?: string; } @@ -20,7 +22,9 @@ export function SidoSigunguSelectBox({ sido, sigungu, onSidoChange, + setCtpvName, onSigunguChange, + setSggName, className = "", }: SidoSigunguSelectBoxProps) { @@ -46,8 +50,8 @@ export function SidoSigunguSelectBox({ async () => { const sggData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_sgg&outputFormat=application%2Fjson'); const sggList: Option[] = sggData.features - .map((feature: any) => ({ id: feature.properties.sgg_cd, name: feature.properties.sgg_korn_nm })) - .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); // 가나다순 정렬 + .map((feature: any) => ({ id: feature.properties.sgg_cd, name: feature.properties.sgg_korn_nm })) + .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); // 가나다순 정렬 setSggList(sggList); } ), []); @@ -58,6 +62,10 @@ export function SidoSigunguSelectBox({ await fetchSggData(); onSidoChange(0); onSigunguChange(0); + if (setCtpvName && setSggName) { + setCtpvName(""); + setSggName(""); + } })(); }, []); @@ -75,7 +83,10 @@ export function SidoSigunguSelectBox({ ] } value={sido} - onSelectValue={val => onSidoChange(Number(val))} + onSelectValue={val => { + onSidoChange(Number(val)); + if (setCtpvName) setCtpvName(ctpvList.find(opt => Number(opt.id) === Number(val))?.name || ""); + }} selectContentClass="z-[101]" /> <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시군구</Label> @@ -91,7 +102,10 @@ export function SidoSigunguSelectBox({ ] } value={sigungu} - onSelectValue={val => onSigunguChange(Number(val))} + onSelectValue={val => { + onSigunguChange(Number(val)) + if (setSggName) setSggName(sggList.find(opt => Number(opt.id) === Number(val))?.name || ""); + }} selectContentClass="z-[101]" /> </div> From 886561535e4feb5a442fd74ec0f934793823106b Mon Sep 17 00:00:00 2001 From: leedj312 <dlehd467@gmail.com> Date: Wed, 30 Jul 2025 17:57:03 +0900 Subject: [PATCH 19/71] =?UTF-8?q?=EB=8C=80=EB=AF=BC=20=EC=88=98=EC=9A=94?= =?UTF-8?q?=EC=8B=A0=EC=B2=AD=20=EC=A0=95=EB=B3=B4=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/sdmMng/sdmApplyList/page.tsx | 26 +- .../pages/sdmMng/sdmInfoInsert/page.tsx | 282 +++--- .../pages/sdmMng/sdmInsert/page.tsx | 11 +- apps/dm/app/store/sdmMng/index.tsx | 174 +++- .../ui/common/searhDemandInput/component.tsx | 2 +- apps/dm/components/ui/common/utils/utils.ts | 18 + .../sdmInfoInsertForm/AccessibleFormField.tsx | 14 +- .../ui/sdmInfoInsertForm/component.tsx | 863 ++++++++++++------ .../components/ui/sdmLoginForm/component.tsx | 2 +- 9 files changed, 948 insertions(+), 444 deletions(-) create mode 100644 apps/dm/components/ui/common/utils/utils.ts diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx index 212eca6e7..fb81197d6 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx @@ -7,7 +7,7 @@ import ListDiv from '@common/business_components/ui/list_div'; import { useEffect, useState } from 'react'; import { Toaster } from 'react-hot-toast'; import SdmLogin from '../sdmLogin/page'; -import type { DemandCompType } from '@/app/store/sdmMng'; +import type { DemandCompBizType } from '@/app/store/sdmMng'; import TransValueFormat from '@/app/common/transValueFormat'; import { useRouter } from 'next/navigation'; @@ -31,8 +31,8 @@ export default function SdmApplyList({}: SdmApplyProps) { const {selectSidoData, selectSigunData} = Common; const [isLogin, setIsLogin] = useState(false); const [isLoginStorageInfo, setIsLoginStorageInfo] = useState(false); - const [sdmApplyDetail, setSdmApplyDetail] = useState<DemandCompType>({} as DemandCompType); - const [transformedList, setTransformedList] = useState<DemandCompType[]>([]); + const [sdmApplyDetail, setSdmApplyDetail] = useState<DemandCompBizType>({} as DemandCompBizType); + const [transformedList, setTransformedList] = useState<DemandCompBizType[]>([]); useEffect(() => { console.log('sessionStorage : ',sessionStorage.getItem('user')); @@ -60,7 +60,7 @@ export default function SdmApplyList({}: SdmApplyProps) { // 데이터 변환을 위한 별도의 useEffect useEffect(() => { if (sdmInfoStatusList.length > 0) { - const newTransformedList = sdmInfoStatusList.map((item: DemandCompType) => ({ + const newTransformedList = sdmInfoStatusList.map((item: DemandCompBizType) => ({ ...item, ctpvCd: selectSidoData.find(sido => sido.val === item.ctpvCd?.trim())?.text || '', sggCd: selectSigunData.find(sigun => sigun.val === item.sggCd?.trim())?.text || '' @@ -121,33 +121,33 @@ export default function SdmApplyList({}: SdmApplyProps) { </TableRow> <TableRow> <TableCell colSpan={1} className={TABLE_STYLES.header}>총대장균군수(개/100ml)</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.grssClfg} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.grssClfg ?? ''} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>결합잔류염소(mg/L)​</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.combResdch} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.combResdch ?? ''} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>탁도(NTU)</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.trbt} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.trbt ?? ''} format="number"/>}</TableCell> </TableRow> <TableRow> <TableCell colSpan={1} className={TABLE_STYLES.header}>생물학적 산소요구량(BOD)(mg/L)</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.blgcOxygDmndQnt} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.blgcOxygDmndQnt ?? ''} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>냄새</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.smll} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>색도(도)</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.crmty} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.crmty ?? ''} format="number"/>}</TableCell> </TableRow> <TableRow> <TableCell colSpan={1} className={TABLE_STYLES.header}>총질소(T-N)(mg/L)</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tn} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tn ?? ''} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>총인(T-P)(mg/L)​</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tph} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tph ?? ''} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>수소이온농도(pH)​</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.ph} format="number"/>}</TableCell> </TableRow> <TableRow> <TableCell colSpan={1} className={TABLE_STYLES.header}>염화물(mgCL/L)​</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.clIonQnt} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.clIonQnt ?? ''} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>전기전도도(㎲/cm)​</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.cdtnv} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.cdtnv ?? ''} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>비고</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{sdmApplyDetail?.rmrk}</TableCell> </TableRow> diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx index 33da31f12..4efc58cb5 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx @@ -1,7 +1,7 @@ "use client"; import { useDmStore } from "@/app/store"; -import type { DemandCompType } from "@/app/store/sdmMng"; +import type { DemandCompBizType } from "@/app/store/sdmMng"; import { SdmInfoRegForm } from "@/components/ui/sdmInfoInsertForm/component"; import { UiModal } from "@common/business_components/ui/dialog_ui"; import { Button } from "@common/components/ui"; @@ -14,121 +14,99 @@ import * as z from "zod"; import SdmLogin from "../sdmLogin/page"; const sdmInfoFormSchema = z.object({ - sdmNm: z.string() - .min(1, "수요처를 선택해주세요"), + sdmNm: z.string().min(1, "수요처를 선택해주세요"), demandYmd: z.date().optional(), - demandQnty: z.string() + demandQnty: z + .string() .min(1, "요구수량을 입력해주세요") .regex(/^[0-9]+$/, "요구수량은 숫자만 입력 가능합니다"), usePurpose: z.string().optional(), - money: z.string() - .regex(/^[0-9]+$/, "희망단가는 숫자만 입력 가능합니다"), + money: z.string().regex(/^[0-9]+$/, "희망단가는 숫자만 입력 가능합니다"), /**요구수질 */ reqWaterQlty: z.string().optional(), - grssClfg: z.string() - .min(1, "총대장균군수를 입력해주세요") - .regex(/^[0-9]+$/, "총대장균군수는 숫자만 입력 가능합니다"), - tn: z.string() - .min(1, "총질소를 입력해주세요") - .regex(/^[0-9]+$/, "총질소는 숫자만 입력 가능합니다"), - combResdch: z.string() - .min(1, "결합잔류염소를 입력해주세요") - .regex(/^[0-9]+$/, "결합잔류염소는 숫자만 입력 가능합니다"), - tph: z.string() - .min(1, "총인산물을 입력해주세요") - .regex(/^[0-9]+$/, "총인산물은 숫자만 입력 가능합니다"), - trbt: z.string() - .min(1, "탁도를 입력해주세요") - .regex(/^[0-9]+$/, "탁도는 숫자만 입력 가능합니다"), - ph: z.string() - .min(1, "수소이온농도를 입력해주세요") - .regex(/^[0-9]+$/, "수소이온농도는 숫자만 입력 가능합니다"), - bod: z.string() - .min(1, "생물학적 산소요구량을 입력해주세요") - .regex(/^[0-9]+$/, "생물학적 산소요구량은 숫자만 입력 가능합니다"), + grssClfg: z.number().nullish(), + tn: z.number().nullish(), + combResdch: z.number().nullish(), + tph: z.number().nullish(), + trbt: z.number().nullish(), + ph: z.string().nullish(), + bod: z.number().nullish(), /**컬럼확인필요 */ - clIonQnt: z.string() - .min(1, "염화물을 입력해주세요") - .regex(/^[0-9]+$/, "염화물은 숫자만 입력 가능합니다"), - smll: z.string() - .min(1, "냄새를 입력해주세요") - .regex(/^[0-9]+$/, "냄새는 숫자만 입력 가능합니다"), - cdtnv: z.string() - .min(1, "전기전도도를 입력해주세요") - .regex(/^[0-9]+$/, "전기전도도는 숫자만 입력 가능합니다"), - crmty: z.string() - .min(1, "색도를 입력해주세요") - .regex(/^[0-9]+$/, "색도는 숫자만 입력 가능합니다"), + clIonQnt: z.number().nullish(), + smll: z.string(), + cdtnv: z.number().nullish(), + crmty: z.number().nullish(), rmrk: z.string().optional(), /* telno: z.string() .min(8, "전화번호는 8자 이상 입력해주세요") .max(12, "전화번호는 12자 이내로 입력해주세요") .regex(/^[0-9-]+$/, "전화번호는 숫자와 하이픈(-)만 입력 가능합니다"), */ - candidateTitle: z.array(z.object({ - ruseUtztnFcltNm: z.string(), - ruseUtztnFcltCd: z.string(), - })).optional(), + candidateTitle: z + .array( + z.object({ + ruseUtztnFcltNm: z.string(), + ruseUtztnFcltCd: z.string(), + }) + ) + .optional(), }); export type FormData = z.infer<typeof sdmInfoFormSchema>; function SdmInfoInsertContent() { const searchParams = useSearchParams(); - const {insertSdmInfoData, Sdm ,setSubsInfo} = useDmStore((state) => state); + const { insertSdmInfoData, Sdm, setSubsInfo } = useDmStore((state) => state); const { successLogin } = Sdm; const [ismoneyDisabled, setIsmoneyDisabled] = useState(false); const formSchema = sdmInfoFormSchema; const [isLogin, setIsLogin] = useState(false); - const [facilityName, setFacilityName] = useState<string>(''); - const [defaultValues, setDefaultValues] = useState<FormData>({ - sdmNm: '', + const [facilityName, setFacilityName] = useState<string>(""); + const [defaultValues, setDefaultValues] = useState<FormData>({ + sdmNm: "", demandYmd: new Date(), - demandQnty: '', - usePurpose: '', - money: '', + demandQnty: "", + usePurpose: "", + money: "", /**요구수질 */ - reqWaterQlty: '', - grssClfg: '', - tn: '', - combResdch: '', - tph: '', - trbt: '', - ph: '', - bod: '', + reqWaterQlty: "", + grssClfg: null, + tn: null, + combResdch: null, + tph: null, + trbt: null, + ph: null, + bod: null, /**컬럼확인필요 */ - clIonQnt: '', - smll: '', - cdtnv: '', - crmty: '', - rmrk: '', + clIonQnt: null, + smll: "", + cdtnv: null, + crmty: null, + rmrk: "", /** 후보지 제목 */ candidateTitle: [ - { ruseUtztnFcltNm: '', ruseUtztnFcltCd: '' }, - { ruseUtztnFcltNm: '', ruseUtztnFcltCd: '' }, - { ruseUtztnFcltNm: '', ruseUtztnFcltCd: '' }, + { ruseUtztnFcltNm: "", ruseUtztnFcltCd: "" }, + { ruseUtztnFcltNm: "", ruseUtztnFcltCd: "" }, + { ruseUtztnFcltNm: "", ruseUtztnFcltCd: "" }, ], }); - - useEffect(() => { - const user = sessionStorage.getItem('user'); - setDefaultValues(prev => ({ + const user = sessionStorage.getItem("user"); + setDefaultValues((prev) => ({ ...prev, - sdmNm: searchParams.get('dmdCoNm') || '', + sdmNm: searchParams.get("dmdCoNm") || "", })); - sessionStorage.setItem('user', searchParams.get('dmdInfoId') || ''); - setIsLogin(user || searchParams.get('dmdInfoId') ? true : false); + sessionStorage.setItem("user", searchParams.get("dmdInfoId") || ""); + setIsLogin(user || searchParams.get("dmdInfoId") ? true : false); }, [searchParams]); useEffect(() => { - const local = localStorage.getItem('user'); + const local = localStorage.getItem("user"); if (local) { setIsLogin(true); } setSubsInfo([]); // subsInfo 초기화 후보지 처음 화면 들어올때 초기화(스토어내 객체만) - }, [setSubsInfo]); const form = useForm<FormData>({ @@ -139,99 +117,138 @@ function SdmInfoInsertContent() { const subsInfo = useDmStore((state) => state.map.subsInfo); - useEffect(()=>{ + useEffect(() => { //map.subsInfo 스토어 객체 값이 변경되면 해당 값이 폼에 자동으로 들어가도록 수정 대신 값이 있어야함 - if(subsInfo.length>0){ - form.setValue('candidateTitle', subsInfo) + if (subsInfo.length > 0) { + form.setValue("candidateTitle", subsInfo); } + }, [subsInfo, form]); -},[subsInfo, form]) + //후보지 팝업 값 셋팅 + useEffect(() => { + const handleMessage = (event: MessageEvent) => { + if (event.data?.type === "SET_SUBS_INFO") { + setSubsInfo(event.data.payload); // zustand로 값 설정 + } + }; -//후보지 팝업 값 셋팅 -useEffect(() => { - const handleMessage = (event: MessageEvent) => { - if (event.data?.type === 'SET_SUBS_INFO') { - setSubsInfo(event.data.payload); // zustand로 값 설정 - } - }; - - window.addEventListener('message', handleMessage); - return () => window.removeEventListener('message', handleMessage); -}, [setSubsInfo]); + window.addEventListener("message", handleMessage); + return () => window.removeEventListener("message", handleMessage); + }, [setSubsInfo]); // 로그인 성공 시 모달 닫기 및 폼 업데이트 useEffect(() => { if (successLogin) { setIsLogin(true); - const user = sessionStorage.getItem('user'); + const user = sessionStorage.getItem("user"); if (user) { try { const userData = JSON.parse(user); if (userData.dmdCoNm) { setFacilityName(userData.dmdCoNm); - form.setValue('sdmNm', userData.dmdCoNm); + form.setValue("sdmNm", userData.dmdCoNm); } - } catch (error) { - console.error('Error parsing user data:', error); + console.error("Error parsing user data:", error); } } } }, [successLogin, form]); + useEffect(() => { + console.log("Form state changed:", form.formState.errors); + }, [form.formState.errors]); + // 체크박스 상태가 변경될 때마다 money 필드의 에러를 초기화 useEffect(() => { if (ismoneyDisabled) { - form.clearErrors('money'); - form.setValue('money', ''); + form.clearErrors("money"); + form.setValue("money", ""); } }, [ismoneyDisabled, form]); -const router = useRouter(); + const router = useRouter(); const onSubmit: SubmitHandler<FormData> = async (data) => { try { - // Transform form data to match API schema - const apiData: DemandCompType = { - dmdInfoId: JSON.parse(sessionStorage.getItem('user') || '{}')?.dmdInfoId?.toString() || searchParams.get('dmdInfoId') + (searchParams.get('bznmTotNo') || ''), - dmdInfoFk: JSON.parse(sessionStorage.getItem('user') || '{}')?.dmdInfoId?.toString() || searchParams.get('dmdInfoId') + (searchParams.get('bznmTotNo') || ''), - dmdCoNm: JSON.parse(sessionStorage.getItem('user') || '{}')?.dmdCoNm?.toString() || data.sdmNm, - ctpvCd: JSON.parse(sessionStorage.getItem('user') || '{}')?.ctpvCd?.toString() || searchParams.get('ctpvCd') || '', - sggCd: JSON.parse(sessionStorage.getItem('user') || '{}')?.sggCd?.toString() || searchParams.get('sggCd') || '', - daddr: JSON.parse(sessionStorage.getItem('user') || '{}')?.daddr?.toString() || searchParams.get('daddr') || '', - telno: JSON.parse(sessionStorage.getItem('user') || '{}')?.telno?.toString() || searchParams.get('telno') || '', - rgtr: JSON.parse(sessionStorage.getItem('user') || '{}')?.rgtr?.toString() || searchParams.get('rgtr') || '', - useUsgCd: data.usePurpose || '', - wntUntprc: Number(data.money) || '', + const dmSubsInfo = data.candidateTitle?.filter( + (item) => item.ruseUtztnFcltNm && item.ruseUtztnFcltCd + ); + // Transform form data to madata.candidateTitletch API schema + const apiData: DemandCompBizType = { + dmdInfoId: + JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.dmdInfoId?.toString() || + searchParams.get("dmdInfoId") + (searchParams.get("bznmTotNo") || ""), + dmdInfoFk: + JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.dmdInfoId?.toString() || + searchParams.get("dmdInfoId") + (searchParams.get("bznmTotNo") || ""), + dmdCoNm: + JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.dmdCoNm?.toString() || data.sdmNm, + ctpvCd: + JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.ctpvCd?.toString() || + searchParams.get("ctpvCd") || + "", + sggCd: + JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.sggCd?.toString() || + searchParams.get("sggCd") || + "", + daddr: + JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.daddr?.toString() || + searchParams.get("daddr") || + "", + telno: + JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.telno?.toString() || + searchParams.get("telno") || + "", + rgtr: + JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.rgtr?.toString() || + searchParams.get("rgtr") || + "", + useUsgCd: data.usePurpose || "", + wntUntprc: Number(data.money) || "", dmndTmng: data.demandYmd || new Date(), - dmndQty: Number(data.demandQnty) || '', - grssClfg: Number(data.grssClfg) || '', - tn: Number(data.tn) || '', - combResdch: Number(data.combResdch) || '', - tph: Number(data.tph) || '', - trbt: Number(data.trbt) || '', - ph: Number(data.ph) || '', - blgcOxygDmndQnt: Number(data.bod) || '', - clIonQnt: Number(data.clIonQnt) || '', - smll: Number(data.smll) || '', - cdtnv: Number(data.cdtnv) || '', - crmty: Number(data.crmty) || '', - rmrk: data.rmrk || '', + dmndQty: Number(data.demandQnty) || "", + grssClfg: data.grssClfg === null ? -1 : Number(data.grssClfg), + tn: Number(data.tn) || null, + combResdch: Number(data.combResdch) || null, + tph: Number(data.tph) || null, + trbt: Number(data.trbt) || null, + ph: data.ph || "", + blgcOxygDmndQnt: Number(data.bod) ||null, + clIonQnt: Number(data.clIonQnt) || null, + smll: data.smll || "", + cdtnv: Number(data.cdtnv) ||null, + crmty: Number(data.crmty) || null, + rmrk: data.rmrk || "", bzmnNo: "", dmdAplyPswd: "", index: 0, reqDt: "", dmdInfoSn: 0, - status: '', - splyCndtList: data.candidateTitle || [], + status: "", + splyCndtList: dmSubsInfo || [], }; - - await insertSdmInfoData(apiData); setTimeout(() => { router.push("/"); - }, 1000); + }, 1000); // 0.8초 후 이동 } catch (error) { - console.error('Error submitting form:', error); + console.error("Error submitting form:", error); } }; @@ -254,8 +271,10 @@ const router = useRouter(); /> <div className="flex justify-center pt-4"> <Button + color="sky" + size={"sm"} type="submit" - className="w-40 bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition-colors duration-200" + className="w-40 font-semibold py-2 px-4 rounded-md " > 수요정보 등록 </Button> @@ -265,7 +284,7 @@ const router = useRouter(); </div> </div> - {!isLogin ? + {!isLogin ? ( <UiModal body={<SdmLogin />} bodyClass="sm:max-w-[90vw] w-[530px] h-[430px]" @@ -278,7 +297,7 @@ const router = useRouter(); noHeaderYn="Y" noFooterYn="Y" /> - : null} + ) : null} <Toaster position="top-center" /> </div> ); @@ -291,4 +310,3 @@ export default function MyForm() { </Suspense> ); } - diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx index a0aa35145..9a68510e3 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx @@ -1,8 +1,8 @@ "use client"; import { useDmStore } from "@/app/store"; -import type { DemandCompType } from "@/app/store/sdmMng"; import SdmInsertForm from '@common/business_components/ui/sdmInsertForm'; +import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useRef } from "react"; import { Toaster } from "react-hot-toast"; @@ -44,15 +44,9 @@ export default function MyForm() { const handleInsertSdmData = useCallback((data: import('@common/business_components/ui/sdmInsertForm/form').DemandCompType) => { const convertedData: DemandCompType = { ...data, - dmdInfoSn: '', - dmdInfoFk: '', - status: '', - splyCndtList: [] }; formDataRef.current = convertedData; - insertSdmData(convertedData).catch(error => { - console.error('Error inserting SDM data:', error); - }); + insertSdmData(convertedData) }, [insertSdmData]); return ( @@ -70,4 +64,3 @@ export default function MyForm() { </div> ); } - diff --git a/apps/dm/app/store/sdmMng/index.tsx b/apps/dm/app/store/sdmMng/index.tsx index 65fadc714..2199aa7b6 100644 --- a/apps/dm/app/store/sdmMng/index.tsx +++ b/apps/dm/app/store/sdmMng/index.tsx @@ -2,12 +2,13 @@ import type { SelectDataType } from "@common/business_components/ui"; import type { TableUpperProps } from "../common"; import toast from "react-hot-toast"; +import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; /**==================================== * Store - 타입 =====================================*/ // 수요처 정보 -export type DemandCompType = { +export type DemandCompBizType = { index : number | ''; dmdInfoId : string | ''; dmdInfoFk : string | ''; @@ -23,17 +24,17 @@ export type DemandCompType = { wntUntprc : number | ''; dmndTmng : Date | ''; dmndQty : number | ''; - grssClfg : number | ''; - tn : number | ''; - combResdch : number | ''; - tph : number | ''; - trbt : number | ''; - ph : number | ''; - blgcOxygDmndQnt : number | ''; - clIonQnt : number | ''; - smll : number | ''; - cdtnv : number | ''; - crmty : number | ''; + grssClfg : number | null //총대장균군수 + tn : number | null //총질소 + combResdch : number | null //결합잔류염소 + tph : number | null //총인 + trbt : number | null //탁도 + ph : string | ''; //수소이온농 + blgcOxygDmndQnt : number | null //생물학적산소요구량 + clIonQnt : number | null //염화물 + smll : string | ''; //냄새 + cdtnv : number | null; //전기전도도 + crmty : number | null //색도 rmrk : string | ''; reqDt : string | ''; dmdInfoSn : number | ''; @@ -152,7 +153,8 @@ export type MoneyDetailType = { clclnBrdnCst : string | ''; clclnEcqrRt : string | ''; } - +// 규칙 타입 선언 +export type DisableRulesType = Record<string, string[]>; /**==================================== * Store - 데이터 ====================================*/ @@ -160,6 +162,131 @@ export type MoneyDetailType = { const demandCompList : DemandCompType[] = [ ] + +const disableRules: DisableRulesType = { + "청소": ["tn", 'tph',"clIonQnt" ,'cdtnv'], + "화장실용수": ["tn", 'tph',"clIonQnt" ,'cdtnv'], + "세척": ["combResdch", "crmty",'tph',"clIonQnt",'cdtnv'], + "살수": ["combResdch", "crmty",'tph',"clIonQnt",'cdtnv'], + "조경": ["combResdch", "crmty",'tph','cdtnv'], + "농업_간접용수": ["combResdch", "crmty",'tn','tph','clIonQnt'], + "농업_직접용수": ["combResdch", "crmty",'tn','tph','clIonQnt'], + "하천": ["combResdch", "trbt", "crmty" ,'clIonQnt','cdtnv'], + '공업':[] + }; + + const defaultRules: Record<string, object> = { + "청소": { + grssClfg: null, + combResdch:0.2, + trbt: 2, + bod: 5, + crmty: 20, + ph: "5.8~8.5", + tn:null, + tph: null, + clIonQnt: null, + cdtnv: null, + + }, + "화장실용수": { + grssClfg: null, + combResdch:0.2, + trbt: 2, + bod: 5, + crmty: 20, + ph: "5.8~8.5", + tn:null, + tph: null, + clIonQnt: null, + cdtnv: null, + }, + "세척": { + grssClfg: null, + trbt: 2, + bod: 5, + ph: "5.8~8.5", + combResdch:null, + crmty:null, + tn:null, + tph: null, + clIonQnt: null, + cdtnv: null, + }, + "살수": { + grssClfg: null, + trbt: 2, + bod: 5, + ph: "5.8~8.5", + combResdch:null, + crmty:null, + tn:null, + tph: null, + clIonQnt: null, + cdtnv: null, + }, + "조경": { + grssClfg: null, + trbt: 2, + bod: 5, + ph: "5.8~8.5", + clIonQnt:250, + combResdch:null, + crmty:null, + tn:null, + tph: null, + cdtnv: null, + }, + "농업_간접용수": { + grssClfg: null, + trbt: 5, + bod: 8, + ph: "5.8~8.5", + cdtnv:2000, + combResdch:null, + crmty:null, + tn:null, + tph: null, + clIonQnt: null, + }, + "농업_직접용수": { + grssClfg: null, + trbt: 2, + bod: 8, + ph: "5.8~8.5", + cdtnv:700, + combResdch:null, + crmty:null, + tn:null, + tph: null, + clIonQnt: null, + }, + "하천": { + grssClfg: 1000, + bod: 5, + tn: 10, + tph: 0.5, + ph: "5.8~8.5", + combResdch:null, + trbt:null, + crmty:null, + clIonQnt: null, + cdtnv:null + }, + 공업:{ + grssClfg: null, + bod: null, + tn: null, + tph: null, + ph: "~", + combResdch:null, + trbt:null, + crmty:null, + clIonQnt: null, + cdtnv:null + } + }; + // 사용용도 const useTypesData : SelectDataType[] = [ { text: '청소', val: "청소" }, @@ -167,7 +294,8 @@ const useTypesData : SelectDataType[] = [ { text: "세척", val: "세척" }, { text: "살수", val: "살수" }, { text: "조경", val: "조경" }, - { text: "농업", val: "농업" }, + { text: "농업_간접용수", val: "농업_간접용수" }, + { text: "농업_직접용수", val: "농업_직접용수" }, { text: "하천", val: "하천" }, { text: "지하수", val: "지하수" }, { text: "공업", val: "공업" }, @@ -280,7 +408,7 @@ const postSdmData = async (data: DemandCompType) => { } // 수요정보 등록 -const postSdmInfoData = async (data: DemandCompType) => { +const postSdmInfoData = async (data: DemandCompBizType) => { try { await postFetch("/reuseFc/request/demandInfo", data); toast.success('수요 정보 등록에 성공하였습니다.'); @@ -360,7 +488,7 @@ const fetchGetSimilarCaseList = async (part: string, value: number|string) => { export type SdmActions = { initializeSdmData: () => void; insertSdmData: (data: DemandCompType) => Promise<void>; - insertSdmInfoData: (data: DemandCompType) => Promise<void>; + insertSdmInfoData: (data: DemandCompBizType) => Promise<void>; getDemandCompList: (searchText: string, pageCnt: number) => Promise<void>; actionLogin: (data: DemandCompType) => Promise<void>; getSdmInfoStatusList: (dmdInfoId: string) => Promise<void>; @@ -378,9 +506,10 @@ export type SdmType ={ moneyDetailList: MoneyDetailType[]; // 수요정보 신청현황 - sdmInfoStatusList: DemandCompType[]; + sdmInfoStatusList: DemandCompBizType[]; upHeadSdmInfoStatus: TableUpperProps[]; - + defaultRules: Record<string, object> + disableRules: DisableRulesType; // 로그인관련 successNextPage : boolean; successLogin: boolean; @@ -398,7 +527,10 @@ export const SdmState: SdmType = { moneyDetailList: moneyDetailList, // 수요정보 신청현황 sdmInfoStatusList: [], + defaultRules:defaultRules, + disableRules:disableRules, upHeadSdmInfoStatus: upHeadSdmInfoStatus, + // 로그인관련 successNextPage : false, successLogin: false, @@ -428,7 +560,7 @@ export const SdmReducer = (set: any): SdmActions => { } }, - insertSdmInfoData: async (data: DemandCompType) => { + insertSdmInfoData: async (data: DemandCompBizType) => { postSdmInfoData(data); }, @@ -465,9 +597,9 @@ export const SdmReducer = (set: any): SdmActions => { })) return; } - const sdmInfoStatusList : DemandCompType[] = []; + const sdmInfoStatusList : DemandCompBizType[] = []; - res.demandInfo.map((el: DemandCompType, index: number) => { + res.demandInfo.map((el: DemandCompBizType, index: number) => { const matchingInfo = res.demandMatchings.find((matching: DemandMatchings) => matching.dmdInfoSn === el.dmdInfoSn); const status = matchingInfo?.splyFcltFlg?.trim() === 'Sub' ? '검토' : matchingInfo?.splyFcltFlg?.trim() === 'matched' ? '매칭완료' : '신청'; diff --git a/apps/dm/components/ui/common/searhDemandInput/component.tsx b/apps/dm/components/ui/common/searhDemandInput/component.tsx index 5598129a7..70ccf9efc 100644 --- a/apps/dm/components/ui/common/searhDemandInput/component.tsx +++ b/apps/dm/components/ui/common/searhDemandInput/component.tsx @@ -1,5 +1,5 @@ import { useDmStore } from '@/app/store'; -import type { DemandCompType } from '@/app/store/sdmMng'; +import type { DemandCompType } from '@common/business_components/ui/sdmInsertForm/form'; import React, { useCallback, useEffect, useRef, useState } from 'react'; type thisProps = { diff --git a/apps/dm/components/ui/common/utils/utils.ts b/apps/dm/components/ui/common/utils/utils.ts new file mode 100644 index 000000000..f66437553 --- /dev/null +++ b/apps/dm/components/ui/common/utils/utils.ts @@ -0,0 +1,18 @@ +export const sanitizeInput = (val: string, allowDecimal = false) => { + if (val === "") return ""; // 빈 값 허용 + + // 소숫점 허용 + if (allowDecimal) { + // 숫자와 점만 허용 + let cleaned = val.replace(/[^0-9.]/g, ""); + // 소숫점 여러 개일 경우 첫 번째만 유지 + const parts = cleaned.split("."); + if (parts.length > 2) { + cleaned = parts[0] + "." + parts.slice(1).join(""); + } + return cleaned; + } + + // 정수만 허용 + return val.replace(/[^0-9]/g, ""); +}; \ No newline at end of file diff --git a/apps/dm/components/ui/sdmInfoInsertForm/AccessibleFormField.tsx b/apps/dm/components/ui/sdmInfoInsertForm/AccessibleFormField.tsx index 93ec4e353..c37270df8 100644 --- a/apps/dm/components/ui/sdmInfoInsertForm/AccessibleFormField.tsx +++ b/apps/dm/components/ui/sdmInfoInsertForm/AccessibleFormField.tsx @@ -1,27 +1,29 @@ import { FormUi } from "@common/business_components/ui"; -import type { ReactNode } from "react"; import type { ControllerRenderProps, FieldError, FieldValues, Path } from "react-hook-form"; - - +import { cloneElement, isValidElement } from "react"; interface AccessibleFormFieldProps<T extends FieldValues> { field: ControllerRenderProps<T, Path<T>>; label: string; required?: boolean; error?: FieldError; - inputComponent: ReactNode; + isDisabled?: boolean; + inputComponent: React.ReactElement<{ disabled?: boolean }>; } export function AccessibleFormField<T extends FieldValues>({ label, required = false, inputComponent, + isDisabled = false, }: AccessibleFormFieldProps<T>) { const labelText = required ? `${label} *` : label; return ( - <FormUi label={labelText}> + <FormUi label={labelText} isDisabled={isDisabled}> <div> - {inputComponent} + {isValidElement(inputComponent) + ? cloneElement(inputComponent, { disabled: isDisabled } as Partial<typeof inputComponent.props>) + : inputComponent} {/* {error?.message && Array.from(new Set(error.message.split('\n'))).map((msg, idx) => ( <p key={idx} className="text-red-500 text-sm mt-1" role="alert"> {msg} diff --git a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx index 63638c43e..478c61ac3 100644 --- a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx +++ b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx @@ -11,13 +11,20 @@ import { UiModal, } from "@common/business_components"; import { Input } from "@common/components"; -import { Button } from "@common/components/ui"; +import { Button, Label } from "@common/components/ui"; import { useSearchParams } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; import { AccessibleFormField } from "./AccessibleFormField"; -import type { ControllerRenderProps, UseFormReturn, FieldError } from "react-hook-form"; +import { + type ControllerRenderProps, + type UseFormReturn, + type FieldError, + useWatch, +} from "react-hook-form"; import SearchInputText from "../common/searhDemandInput/component"; -import type { DemandCompType } from "@/app/store/sdmMng"; +import React from "react"; +import { sanitizeInput } from "../common/utils/utils"; +import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; export interface SdmInfoRegFormProps { form: UseFormReturn<FormData>; @@ -35,12 +42,15 @@ export function SdmInfoRegForm({ facilityName, }: SdmInfoRegFormProps) { const { initializeCommonData, Sdm } = useDmStore((state) => state); - const { useTypesData } = Sdm; + const { useTypesData, defaultRules, disableRules } = Sdm; const searchParams = useSearchParams(); const [sdmNmValue, setSdmNmValue] = useState<string>( searchParams.get("dmdCoNm") || "" ); + + const [frontPHdata, setFrontPHdata] = useState("0"); + const [backPHdata, setBackPHdata] = useState("0"); const [isBznmTotNoDisabled, setIsBznmTotNoDisabled] = useState(false); const [isSimilarCase, setIsSimilarCase] = useState(false); const [key, setKey] = useState(0); @@ -58,6 +68,7 @@ export function SdmInfoRegForm({ useEffect(() => { console.log("candidateTitle ", form.getValues("candidateTitle")); }, [form.getValues]); + useEffect(() => { if (useTypesData && useTypesData.length > 0) { setTimeout(() => { @@ -109,6 +120,54 @@ export function SdmInfoRegForm({ onCheckboxChange?.(checked); }; + useEffect(() => { + const watchPH = form.getValues(`ph`); + if (watchPH && watchPH !== undefined) { + const [frontPH, backPH] = watchPH.split("~"); + if (frontPH) { + setFrontPHdata(frontPH); + } + if (backPH) { + setBackPHdata(backPH); + } + } else { + setFrontPHdata("0"); + setBackPHdata("0"); + } + }, [form.getValues(`ph`)]); + + const isSetPh = useCallback( + (typePHdata: string, index: number) => { + const PHdata = form.getValues("ph"); + const isPH = PHdata?.split("~")[index] || ""; + if (index == 0) { + form.setValue("ph", `${isPH}~${typePHdata}`); + setBackPHdata(typePHdata); + } else if (index == 1) { + form.setValue("ph", `${typePHdata}~${isPH}`); + setFrontPHdata(typePHdata); + } + }, + [form] + ); + + const usePurpose = useWatch({ control: form.control, name: "usePurpose" }); + + useEffect(() => { + if (!usePurpose) return; + + // Apply default values + const defaults = defaultRules[usePurpose]; + + console.log("usePurpose", usePurpose); + console.log("defaults", defaults); + if (defaults) { + Object.entries(defaults).forEach(([field, value]) => { + form.setValue(field as keyof FormData, value, { shouldDirty: true }); + }); + } + }, [form.getValues("usePurpose")]); + // 시설 선택시 이벤트 const selectComp = useCallback( (rtnData: DemandCompType[]) => { @@ -178,7 +237,6 @@ export function SdmInfoRegForm({ required: true, component: (field) => ( <Input - placeholder="요구수량(㎥/d)을 입력하세요" type="text" className="!text-xl text-gray-600" value={field.value as string} @@ -222,7 +280,6 @@ export function SdmInfoRegForm({ isDisabled={isBznmTotNoDisabled} > <Input - placeholder="희망단가(원/㎥)을 입력하세요" type="text" className="!text-xl text-gray-600" value={field.value as string} @@ -244,221 +301,7 @@ export function SdmInfoRegForm({ required: true, component: undefined, }, - { - name: "grssClfg", - label: "총대장균군수(개/100ml)", - formIndex: 1, - required: true, - component: (field) => ( - <Input - placeholder="총대장균군수를 입력하세요" - type="text" - className="!text-xl text-gray-600" - value={field.value as string} - onChange={field.onChange} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - ), - }, - { - name: "tn", - label: "총질소(T-N)(mg/L)", - formIndex: 1, - required: true, - component: (field) => ( - <Input - placeholder="총질소를 입력하세요" - type="text" - className="!text-xl text-gray-600" - value={field.value as string} - onChange={field.onChange} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - ), - }, - { - name: "combResdch", - label: "결합잔류염소(mg/L)", - formIndex: 1, - required: true, - component: (field) => ( - <Input - placeholder="결합잔류염소를 입력하세요" - type="text" - className="!text-xl text-gray-600" - value={field.value as string} - onChange={field.onChange} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - ), - }, - { - name: "tph", - label: "총인(T-P)(mg/L)", - formIndex: 1, - required: true, - component: (field) => ( - <Input - placeholder="총인을 입력하세요" - type="text" - className="!text-xl text-gray-600" - value={field.value as string} - onChange={field.onChange} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - ), - }, - { - name: "trbt", - label: "탁도(NTU)", - formIndex: 1, - required: true, - component: (field) => ( - <Input - placeholder="탁도를 입력하세요" - type="text" - className="!text-xl text-gray-600" - value={field.value as string} - onChange={field.onChange} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - ), - }, - { - name: "ph", - label: "수소이온농도(pH)", - formIndex: 1, - required: true, - component: (field) => ( - <Input - placeholder="수소이온농도를 입력하세요" - type="text" - className="!text-xl text-gray-600" - value={field.value as string} - onChange={field.onChange} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - ), - }, - { - name: "bod", - label: "생물학적 산소요구량(BOD)(mg/L)", - formIndex: 1, - required: true, - component: (field) => ( - <Input - placeholder="생물학적 산소요구량을 입력하세요" - type="text" - className="!text-xl text-gray-600" - value={field.value as string} - onChange={field.onChange} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - ), - }, - { - name: "clIonQnt", - label: "염화물(mgCL/L)", - formIndex: 1, - required: true, - component: (field) => ( - <Input - placeholder="염화물을 입력하세요" - type="text" - className="!text-xl text-gray-600" - value={field.value as string} - onChange={field.onChange} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - ), - }, - { - name: "smll", - label: "냄새", - formIndex: 1, - required: true, - component: (field) => ( - <Input - placeholder="냄새를 입력하세요" - type="text" - className="!text-xl text-gray-600" - value={field.value as string} - onChange={field.onChange} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - ), - }, - { - name: "cdtnv", - label: "전기전도도(㎲/cm)", - formIndex: 1, - required: true, - component: (field) => ( - <Input - placeholder="전기전도도를 입력하세요" - type="text" - className="!text-xl text-gray-600" - value={field.value as string} - onChange={field.onChange} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - ), - }, - { - name: "crmty", - label: "색도(도)", - formIndex: 1, - required: true, - component: (field) => ( - <Input - placeholder="색도를 입력하세요" - type="text" - className="!text-xl text-gray-600" - value={field.value as string} - onChange={field.onChange} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - ), - }, - { - name: "rmrk", - label: "비고", - formIndex: 1, - component: (field) => ( - <Input - placeholder="비고를 입력하세요" - type="text" - className="!text-xl text-gray-600" - value={field.value as string} - onChange={field.onChange} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - ), - }, + { name: "candidateTitle", label: CANDIDATE || "공급처 후보지 선택(필수아님)", @@ -466,10 +309,20 @@ export function SdmInfoRegForm({ component: () => ( <div> <div className="-mt-14 float-right flex gap-10"> - <Button className={"bg-gray-200"} onClick={searchCandidate}> + <Button + className="rounded-md" + color="sky" + size={"sm"} + onClick={searchCandidate} + > 후보지 조회 </Button> - <Button className={"bg-gray-200"} onClick={searchSimilarCase}> + <Button + className="rounded-md" + color="sky" + size={"sm"} + onClick={searchSimilarCase} + > 유사사례 </Button> </div> @@ -495,9 +348,9 @@ export function SdmInfoRegForm({ const normalFields = formFields.filter( (f) => f.formIndex === 0 && f.component ); - const groupFields = formFields.filter( - (f) => f.formIndex === 1 && f.component - ); + // const groupFields = formFields.filter( + // (f) => f.formIndex === 1 && f.component + // ); const candidateFields = formFields.filter( (f) => f.formIndex === 2 && f.component ); @@ -512,17 +365,15 @@ export function SdmInfoRegForm({ name={name} render={({ field }) => name === "money" ? ( - component(field) + // component(field) + <FormUi label={label + "*"}>{component(field)}</FormUi> ) : ( - // <FormUi label={label}> - // {component(field)} - // </FormUi> <AccessibleFormField field={field} label={label} required={required} error={ - typeof form.formState.errors[name]?.message === 'string' + typeof form.formState.errors[name]?.message === "string" ? (form.formState.errors[name] as FieldError) : undefined } @@ -534,37 +385,527 @@ export function SdmInfoRegForm({ ) : null )} - {groupFields.length > 0 && ( - <div className="mt-10"> - <FormLabel className="text-xl">요구수질</FormLabel> - <div className="flex flex-wrap justify-start gap-10 mt-3 mb-3"> - {groupFields.map(({ name, label, component, required }) => ( - <div key={name} className="min-w-[180px]"> - <FormField - control={form.control} - name={name} - render={({ field }) => ( - // <FormUi label={label}> - // {component?.(field)} - // </FormUi> - <AccessibleFormField - field={field} - label={label} - required={required} - error={ - typeof form.formState.errors[name]?.message === 'string' - ? (form.formState.errors[name] as FieldError) - : undefined - } - inputComponent={component?.(field)} - /> - )} - /> - </div> - ))} + <div className="mt-10"> + <FormLabel className="text-xl">요구수질</FormLabel> + <div className="grid grid-cols-2 gap-x-10 gap-y-6 mt-3 mb-3"> + <FormField + control={form.control} + name={`grssClfg`} + render={({ field }) => { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + <FormUi + label={ + !isDisabled ? `총대장균군수(개/mg)*` : "총대장균군수(개/mg)" + } + > + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + type="text" + className="w-full pr-16 border py-2 text-right rounded-md" + placeholder="" + onChange={(e) => { + const inputValue = e.target.value.replace( + /[^0-9]/g, + "" + ); // 정수만 + field.onChange( + inputValue === "" ? null : Number(inputValue) + ); + }} + value={ + field.value !== undefined && field.value !== null + ? `${field.value}` + : "불검출" + } + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + {/* TODO: 총대장균군수 확인 필요*/} + {field.value !== null ? ( + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + ) : ( + <></> + )} + </div> + </div> + </FormUi> + ); + }} + /> + <FormField + control={form.control} + name={`tn`} + render={({ field }) => { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + <FormUi + label={ + !isDisabled ? `총질소(T-N)(mg/L)*` : "총질소(T-N)(mg/L)" + } + > + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + type="text" + className="w-full pr-16 border py-2 text-right rounded-md" + placeholder="" + onChange={(e) => { + const inputValue = e.target.value.replace( + /[^0-9]/g, + "" + ); // 정수만 + field.onChange( + inputValue === "" ? null : Number(inputValue) + ); + }} + value={ + field.value !== undefined && field.value !== null + ? `${field.value}` + : "" + } + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> + </FormUi> + ); + }} + /> + <FormField + control={form.control} + name={`combResdch`} + render={({ field }) => { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + <FormUi + label={ + !isDisabled ? `결합잔류염소(mg/L)*` : "결합잔류염소(mg/L)" + } + > + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + type="text" + className="w-full pr-16 border py-2 text-right rounded-md" + placeholder="" + onChange={(e) => { + const cleanValue = sanitizeInput( + e.target.value, + true + ); + field.onChange( + cleanValue === "" ? null : Number(cleanValue) + ); + }} + value={field.value ?? ""} + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이상 + </span> + </div> + </div> + </FormUi> + ); + }} + /> + <FormField + control={form.control} + name={`tph`} + render={({ field }) => { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + <FormUi + label={!isDisabled ? `총인(T-P)(mg/L)*` : "총인(T-P)(mg/L)"} + > + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + type="text" + className="w-full pr-16 border py-2 text-right rounded-md" + placeholder="" + onChange={(e) => { + const cleanValue = sanitizeInput( + e.target.value, + true + ); + field.onChange( + cleanValue === "" ? null : Number(cleanValue) + ); + }} + value={field.value ?? ""} + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> + </FormUi> + ); + }} + /> + <FormField + control={form.control} + name={`trbt`} + render={({ field }) => { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + <FormUi label={!isDisabled ? `탁도(NTU)*` : "탁도(NTU)"}> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + type="text" + className="w-full pr-16 border py-2 text-right rounded-md" + placeholder="" + onChange={(e) => { + const inputValue = e.target.value.replace( + /[^0-9]/g, + "" + ); // 정수만 + field.onChange( + inputValue === "" ? null : Number(inputValue) + ); + }} + value={ + field.value !== undefined && field.value !== null + ? `${field.value}` + : "" + } + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> + </FormUi> + ); + }} + /> + <div className="col-span-1"> + <Label className="text-xl">수소이온농도(PH)</Label> + <div className="flex flex-row items-center gap-2"> + <Input + className="!text-xl w-36" + placeholder="" + type="text" + onChange={(e) => { + const cleanValue = sanitizeInput(e.target.value, true); + isSetPh(cleanValue, 1); + }} + value={frontPHdata ?? ""} + maxLength={5} + /> + <span className="text-xl mx-2">~</span> + <Input + className="!text-xl w-36 " + placeholder="" + type="text" + onChange={(e) => { + const cleanValue = sanitizeInput(e.target.value, true); + isSetPh(cleanValue, 0); + }} + value={backPHdata ?? ""} + maxLength={5} + /> + </div> </div> + <FormField + control={form.control} + name={`bod`} + render={({ field }) => { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + <FormUi + label={ + !isDisabled + ? `생물학적 산소요구량(BOD)(mg/L)*` + : "생물학적 산소요구량(BOD)(mg/L)" + } + > + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + type="text" + className="w-full pr-16 border py-2 text-right rounded-md" + placeholder="" + onChange={(e) => { + const inputValue = e.target.value.replace( + /[^0-9]/g, + "" + ); // 정수만 + field.onChange( + inputValue === "" ? null : Number(inputValue) + ); + }} + value={ + field.value !== undefined && field.value !== null + ? `${field.value}` + : "" + } + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> + </FormUi> + ); + }} + /> + <FormField + control={form.control} + name={`clIonQnt`} + render={({ field }) => { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + <FormUi + label={!isDisabled ? `염화물(mgCL/L)*` : "염화물(mgCL/L)"} + > + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + type="text" + className="w-full pr-16 border py-2 text-right rounded-md" + placeholder="" + onChange={(e) => { + const inputValue = e.target.value.replace( + /[^0-9]/g, + "" + ); // 정수만 + field.onChange( + inputValue === "" ? null : Number(inputValue) + ); + }} + value={ + field.value !== undefined && field.value !== null + ? `${field.value}` + : "" + } + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> + </FormUi> + ); + }} + /> + <FormField + control={form.control} + name={`smll`} + render={({ field }) => { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + <FormUi label={!isDisabled ? `냄새*` : "냄새"}> + <SelectBox + className="!text-2xl rounded-md" + label={""} + selectClass={undefined} + selectArray={[ + { text: "불쾌하지않을것", val: "불쾌하지않을것" }, + { text: "해당사항없음", val: "해당사항없음" }, + ]} + onSelectValue={(val: string | number) => { + field.onChange(val.toString()); + }} + value={field.value || "불쾌하지않을것"} + readOnly={isDisabled} + onBlur={field.onBlur} + ref={field.ref} + /> + </FormUi> + ); + }} + /> + <FormField + control={form.control} + name={`cdtnv`} + render={({ field }) => { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + <FormUi + label={ + !isDisabled ? `전기전도도(㎲/cm)*` : "전기전도도(㎲/cm)" + } + > + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + type="text" + className="w-full pr-16 border py-2 text-right rounded-md" + placeholder="" + onChange={(e) => { + const inputValue = e.target.value.replace( + /[^0-9]/g, + "" + ); // 정수만 + field.onChange( + inputValue === "" ? null : Number(inputValue) + ); + }} + value={ + field.value !== undefined && field.value !== null + ? `${field.value}` + : "" + } + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> + </FormUi> + ); + }} + /> + <FormField + control={form.control} + name={`crmty`} + render={({ field }) => { + const disabledFields = usePurpose ? disableRules[usePurpose] : []; + const isDisabled = disabledFields + ? disabledFields.includes(field.name) + : true; + return ( + <FormUi label={!isDisabled ? `색도(도)*` : "색도(도)"}> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + type="text" + className="w-full pr-16 border py-2 text-right rounded-md" + placeholder="" + onChange={(e) => { + const inputValue = e.target.value.replace( + /[^0-9]/g, + "" + ); // 정수만 + field.onChange( + inputValue === "" ? null : Number(inputValue) + ); + }} + value={ + field.value !== undefined && field.value !== null + ? `${field.value}` + : "" + } + maxLength={6} + disabled={isDisabled} + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> + </FormUi> + ); + }} + /> + <FormField + control={form.control} + name={`rmrk`} + render={({ field }) => { + return ( + <FormUi label={"비고"}> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + type="text" + className="w-full pr-16 border py-2 text-right rounded-md" + placeholder="" + onChange={(e) => { + field.onChange(e.target.value); + }} + value={ + field.value !== undefined && field.value !== "" + ? `${field.value}` + : "" + } + onBlur={field.onBlur} + name={field.name} + ref={field.ref} + /> + </div> + </div> + </FormUi> + ); + }} + /> </div> - )} + </div> {candidateFields.map(({ name, label, component, required }, index) => component ? ( @@ -581,7 +922,7 @@ export function SdmInfoRegForm({ label={label} required={required} error={ - typeof form.formState.errors[name]?.message === 'string' + typeof form.formState.errors[name]?.message === "string" ? (form.formState.errors[name] as FieldError) : undefined } diff --git a/apps/dm/components/ui/sdmLoginForm/component.tsx b/apps/dm/components/ui/sdmLoginForm/component.tsx index 204b12308..dd764844b 100644 --- a/apps/dm/components/ui/sdmLoginForm/component.tsx +++ b/apps/dm/components/ui/sdmLoginForm/component.tsx @@ -1,6 +1,6 @@ import { useDmStore } from "@/app/store"; -import type { DemandCompType } from "@/app/store/sdmMng"; import { BZNM_TOT_NO, FormField, FormUi } from "@common/business_components"; +import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; import { Button, Input } from "@common/components"; import { zodResolver } from "@hookform/resolvers/zod"; import { sha512 } from 'js-sha512'; From 4cfa032be2b61a631dbe902ae71bfdc77a2c79ee Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Wed, 30 Jul 2025 20:31:13 +0900 Subject: [PATCH 20/71] =?UTF-8?q?feat:=20=EC=98=A4=EC=88=98=EC=A0=84?= =?UTF-8?q?=ED=99=98=EC=9C=A8=20=EC=B4=88=EA=B9=83=EA=B0=92=200.8=EB=A1=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(with-base-layout)/ii/irsetting/page.tsx | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx b/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx index 3adf8c233..99542f5bf 100644 --- a/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx @@ -47,7 +47,7 @@ interface ApiSelectOption { } const IrSetting = () => { - const [input1, setInput1] = useState(''); + const [input1, setInput1] = useState('0.8'); // 오수전환율 초기값 const [input2, setInput2] = useState(''); const [select1, setSelect1] = useState('0'); // 환경청코드 const [select2, setSelect2] = useState('0'); // 시도코드 @@ -387,10 +387,7 @@ const IrSetting = () => { accept={".xls, .xlsx, .xlsm, .xltx"} fileIdx={2} /> </FormSection> - <FormSection - name='inputdate' - form={form} - titleNm='▷ 상수사용량 적용월'> + <FormSection name='inputdate' form={form} titleNm='▷ 상수사용량 적용월'> {/* 연월 선택 라이브러리로 수정 필요 */} <DateMonthPicker field={{ @@ -403,10 +400,7 @@ const IrSetting = () => { startYear={1900} // 시작 연도 설정 /> </FormSection> - <FormSection - name='rtkSource' - form={form} - titleNm='▷ 오수전환율'> + <FormSection name='rtkSource' form={form} titleNm='▷ 오수전환율'> <Input value={input1} onChange={(e) => { @@ -434,10 +428,7 @@ const IrSetting = () => { placeholder="예: 0.75" /> </FormSection> - <FormSection - name='analysis information' - form={form} - titleNm='▷ 분석 정보'> + <FormSection name='analysis information' form={form} titleNm='▷ 분석 정보'> <Input value={input2} onChange={(e) => setInput2(e.target.value)} From b5c7f09bc94b48e6ca193db0719f98b62d2a4190 Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Wed, 30 Jul 2025 20:55:57 +0900 Subject: [PATCH 21/71] =?UTF-8?q?feat:=20=EC=8B=9C=EB=8F=84,=20=EC=8B=9C?= =?UTF-8?q?=EA=B5=B0=EA=B5=AC=20zustand=20Store=EB=A1=9C=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20=ED=9B=84=20SearchModal.tsx=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/(map-layout)/map/page.tsx | 7 +++ .../map/searchModal/swagePrcsFclt.tsx | 57 ------------------- .../map/searchModal/swagePrcsZone.tsx | 16 +++--- .../components/ui/SidoSigunguSelectBox.tsx | 40 ++----------- apps/sn/app/store/useStore.ts | 41 +++++++++++++ 5 files changed, 62 insertions(+), 99 deletions(-) create mode 100644 apps/sn/app/store/useStore.ts diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index b2ed8ecd2..367bf421e 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -6,6 +6,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import GisApp from "@common/business_components/ui/map/lib/gis.core"; import SearchModal from "./searchModal/searchModal"; import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup"; +import useStore from "../../store/useStore"; // 타입 정의 추가 export type LyrStngProps = { @@ -38,6 +39,12 @@ const Map = () => { const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태 const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터 + const { fetchCtpvData, fetchSggData } = useStore(); + + useEffect(() => { + fetchCtpvData(); + fetchSggData(); + }, [fetchCtpvData, fetchSggData]); const fetchData = async () => { try { diff --git a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx index 85b32c9ef..53fd312dc 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx @@ -29,19 +29,13 @@ interface SwagePrcsFcltProps { } const SwagePrcsFclt: React.FC<SwagePrcsFcltProps> = ({ - var1, start1, - start2, end1, - end2, city, sgg, yearFilter, - setVar1, setStart1, - setStart2, setEnd1, - setEnd2, setCity, setCtpvName, setSgg, @@ -114,57 +108,6 @@ const SwagePrcsFclt: React.FC<SwagePrcsFcltProps> = ({ } }} /> - {/* 처리구역 면적 */} - {/* <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">처리구역 면적(m²)</Label> - <Input - className="w-[60px] !text-xl placeholder:text-center" - placeholder="입력" - value={start2} - onChange={(e) => { - // 숫자만 입력 허용 - const val = e.target.value.replace(/[^0-9]/g, ""); - setStart2(val); - }} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); // Enter 키 기본 동작 방지 - handleSearch(); - } - }} - /> - <span className="mx-2 text-xl font-semibold">~</span> - <Input - className="mr-8 w-[60px] !text-xl placeholder:text-center" - placeholder="입력" - value={end2} - onChange={(e) => { - // 숫자만 입력 허용 - const val = e.target.value.replace(/[^0-9]/g, ""); - setEnd2(val); - }} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); // Enter 키 기본 동작 방지 - handleSearch(); - } - }} - /> */} - {/* 하수처리시설명 */} - {/* <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">하수처리시설명</Label> - <Input - className="mr-8 w-[100px] !text-xl placeholder:text-center" - placeholder="입력" - value={var1} - onChange={(e) => { - setVar1(e.target.value); - }} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); // Enter 키 기본 동작 방지 - handleSearch(); - } - }} - /> */} {/* 조회 버튼 */} <div className="flex-1 flex justify-end"> <Button className="text-base py-2" onClick={handleSearch}>조회</Button> diff --git a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsZone.tsx b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsZone.tsx index a9065fb35..abeec54b9 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsZone.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsZone.tsx @@ -40,13 +40,13 @@ const swagePrcsZone: React.FC<swagePrcsZoneProps> = ({ }} onSigunguChange={setSgg} /> - <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">하수처리구역명</Label> + <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">하수처리시설명</Label> <Input - className="mr-8 w-[100px] !text-xl placeholder:text-center" + className='!text-xl w-[120px] placeholder:text-center mr-8' placeholder="입력" - value={var1} + value={var2} onChange={(e) => { - setVar1(e.target.value); + setVar2(e.target.value); }} onKeyDown={(e) => { if (e.key === 'Enter') { @@ -55,13 +55,13 @@ const swagePrcsZone: React.FC<swagePrcsZoneProps> = ({ } }} /> - <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">하수처리시설명</Label> + <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">하수처리구역명</Label> <Input - className='!text-xl w-[120px] placeholder:text-center mr-8' + className="mr-8 w-[100px] !text-xl placeholder:text-center" placeholder="입력" - value={var2} + value={var1} onChange={(e) => { - setVar2(e.target.value); + setVar1(e.target.value); }} onKeyDown={(e) => { if (e.key === 'Enter') { diff --git a/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx b/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx index 96f7841b4..a3a138259 100644 --- a/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx +++ b/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx @@ -1,7 +1,7 @@ import React, { useState, useMemo, useEffect } from "react"; import { SelectBox } from '@common/business_components/ui' import { Label } from '@common/components'; -import { getGeoData } from '@/components/lib/api'; +import useStore from '../../store/useStore'; interface Option { id: number; @@ -28,38 +28,10 @@ export function SidoSigunguSelectBox({ className = "", }: SidoSigunguSelectBoxProps) { - const [ctpvList, setCtpvList] = useState<Option[]>([]); - const [sggList, setSggList] = useState<Option[]>([]); - - // 선택된 시도에 따라 시군구 리스트를 가져옴 - const fetchCtpvData = useMemo(() => ( - async () => { - const ctpvData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_ctpv&outputFormat=application%2Fjson'); - const ctpvList: Option[] = ctpvData.features - .map((feature: any) => ({ - id: feature.properties.mctpv_cd.trim(), - name: feature.properties.mctpv_korn_nm - })) - .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); // 가나다순 정렬 - setCtpvList(ctpvList); - } - ), []); - - // 선택된 시도에 따라 시군구 리스트를 가져옴 - const fetchSggData = useMemo(() => ( - async () => { - const sggData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_sgg&outputFormat=application%2Fjson'); - const sggList: Option[] = sggData.features - .map((feature: any) => ({ id: feature.properties.sgg_cd, name: feature.properties.sgg_korn_nm })) - .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); // 가나다순 정렬 - setSggList(sggList); - } - ), []); + const { ctpv, sgg} = useStore(); useEffect(() => { (async () => { - await fetchCtpvData(); - await fetchSggData(); onSidoChange(0); onSigunguChange(0); if (setCtpvName && setSggName) { @@ -79,13 +51,13 @@ export function SidoSigunguSelectBox({ label="" selectArray={ [{ val: 0, text: '전체' }, - ...ctpvList.map(opt => ({ val: opt.id, text: opt.name })) + ...ctpv.map(opt => ({ val: opt.id, text: opt.name })) ] } value={sido} onSelectValue={val => { onSidoChange(Number(val)); - if (setCtpvName) setCtpvName(ctpvList.find(opt => Number(opt.id) === Number(val))?.name || ""); + if (setCtpvName) setCtpvName(ctpv.find(opt => Number(opt.id) === Number(val))?.name || ""); }} selectContentClass="z-[101]" /> @@ -96,7 +68,7 @@ export function SidoSigunguSelectBox({ label="" selectArray={ [{ val: 0, text: '전체' }, - ...sggList + ...sgg .filter(opt => String(opt.id).slice(0, 2) === String(sido)) .map(opt => ({ val: opt.id, text: opt.name })) ] @@ -104,7 +76,7 @@ export function SidoSigunguSelectBox({ value={sigungu} onSelectValue={val => { onSigunguChange(Number(val)) - if (setSggName) setSggName(sggList.find(opt => Number(opt.id) === Number(val))?.name || ""); + if (setSggName) setSggName(sgg.find(opt => Number(opt.id) === Number(val))?.name || ""); }} selectContentClass="z-[101]" /> diff --git a/apps/sn/app/store/useStore.ts b/apps/sn/app/store/useStore.ts new file mode 100644 index 000000000..518cce66b --- /dev/null +++ b/apps/sn/app/store/useStore.ts @@ -0,0 +1,41 @@ +import { useMemo } from "react"; +import { create } from "zustand"; +import { getGeoData } from "@/components/lib/api"; +import { get } from "http"; + +interface Option { id: number, name: string } + +interface SnStoreState { + ctpv: Option[]; + sgg: Option[]; + fetchCtpvData: () => Promise<void>; + fetchSggData: () => Promise<void>; +} + +const useStore = create<SnStoreState>((set) => ({ + // Define your store state and actions here + ctpv: [], + sgg: [], + fetchCtpvData: async () => { + const ctpvData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_ctpv&outputFormat=application%2Fjson'); + const ctpvList: Option[] = ctpvData.features + .map((feature: any) => ({ + id: feature.properties.mctpv_cd.trim(), + name: feature.properties.mctpv_korn_nm + })) + .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); + set({ ctpv: ctpvList }); + }, + fetchSggData: async () => { + const sggData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_sgg&outputFormat=application%2Fjson'); + const sggList: Option[] = sggData.features + .map((feature: any) => ({ + id: feature.properties.sgg_cd, + name: feature.properties.sgg_korn_nm + })) + .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); + set({ sgg: sggList }); + } +})); + +export default useStore; \ No newline at end of file From c23fe686754eac1bfd6b53673589726327c04125 Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Wed, 30 Jul 2025 21:35:16 +0900 Subject: [PATCH 22/71] =?UTF-8?q?refactor:=20=EC=8B=9C=EB=8F=84,=20?= =?UTF-8?q?=EC=8B=9C=EA=B5=B0=EA=B5=AC=20=ED=98=B8=EC=B6=9C=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/(map-layout)/map/page.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index 367bf421e..15c6586eb 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -41,6 +41,7 @@ const Map = () => { const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터 const { fetchCtpvData, fetchSggData } = useStore(); + // 시도, 시군구 호출 useEffect(() => { fetchCtpvData(); fetchSggData(); From 2e7b06d970ef9235fd890b9983d97c867372519e Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Wed, 30 Jul 2025 21:37:40 +0900 Subject: [PATCH 23/71] =?UTF-8?q?feat:=20=EC=A4=91=EC=A0=90=EC=9A=B0?= =?UTF-8?q?=EC=88=98=ED=86=A0=EC=8B=A4=20=ED=8E=98=EC=9D=B4=EC=A7=95=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20(=ED=85=8C=EC=9D=B4=EB=B8=94=20=EB=8F=99?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=EC=B6=94=EA=B0=80=20=EC=9E=91=EC=97=85=20?= =?UTF-8?q?=ED=95=84=EC=9A=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../map/components/popupComponent/Ssc.tsx | 209 +++++++++++------- 1 file changed, 131 insertions(+), 78 deletions(-) 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 52fc5523d..4542e56c9 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx @@ -12,20 +12,114 @@ const inputColumn = [ { id: "swtrmtZoneCd", title: "처리구역", type: "input" }, ]; -const Ssc = ({ featureData, isMainSsc, rainMeasureData, onSaveClick }: { +const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: { featureData: any, isMainSsc?: boolean, - rainMeasureData?: any, + chmbrXcnts?: string, + chmbrYdnts?: string, onSaveClick?: () => void }) => { const [isModalOpen, setIsModalOpen] = useState(false); const [isDataLoaded, setIsDataLoaded] = useState(false); const [formData, setFormData] = useState<any>({}); - const [mntrngsData, setMntrngsData] = useState<any[]>([]); // 수량/수질 측정 정보 + const [rainMeasureData, setRainMeasureData] = useState<any>({}); // 강우, 측정 정보 + const [mntrngsData, setMntrngsData] = useState<any[]>([]); // 수량/수질측정 정보 + const [totalCount, setTotalCount] = useState<number>(0); + const [totalDetailCount, setTotalDetailCount] = useState<number>(0); const [selectedRowIndex, setSelectedRowIndex] = useState<number>(-1); + const [curPage, setCurPage] = useState<number>(1); + const [curDetailPage, setCurDetailPage] = useState<number>(1); + const [isResetPage, setIsResetPage] = useState<boolean>(false); + const [isResetDetailPage, setIsResetDetailPage] = useState<boolean>(false); + const [isResetSelection, setIsResetSelection] = useState<boolean>(false); + const [curIdx, setCurIdx] = useState<number>(0); - // console.log('featureData data received:', featureData); // 추가 데이터 확인용 - // console.log('rainMeasureData received:', rainMeasureData); // 추가 데이터 확인용 + // 강우, 측정 정보 API 호출 함수 + const fetchRainMeasureData = async (chmbrXcnts: string, chmbrYdnts: string) => { + if (!chmbrXcnts || !chmbrYdnts) { + console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { chmbrXcnts, chmbrYdnts }); + return []; + } + + try { + const queryParams = new URLSearchParams({ + chmbrXcnts: chmbrXcnts, + chmbrYdnts: chmbrYdnts, + page: `${curPage - 1}`, + size: '3', + }); + + console.log('queryParams', queryParams.toString()); + console.log(`추가 데이터 API 호출 시작: v1/onsemiro/getSwgRainsByGeomety?${queryParams.toString()}`); + + const result = await getData(`v1/onsemiro/getSwgRainsByGeomety?${queryParams.toString()}`); + + console.log('추가 데이터 API 호출 성공:', result); + + const processedDataArray: any[] = result.content.map((data: any) => { + // 🎯 formatDisplayData로 기본 포맷팅 적용 + const formattedItem = formatDisplayData(data); + + // 🎯 추가 데이터 조합 + return { + ...formattedItem, + flwmtrCombined: `${data.flwmtrMesureTpEtc || '-'} / ${data.flwmtrKnd || '-'}`, + }; + }); + setRainMeasureData(processedDataArray); + setTotalCount(result?.totalElements || 0); + } catch (error) { + console.error('추가 데이터 로딩 실패:', error); + return []; + } + }; + + // 수량/수질측정 정보 API 호출 함수 + const fetchMntrngsData = async (adstDstrCd: string, manageNo: string) => { + if (!adstDstrCd || !manageNo) { + console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { adstDstrCd, manageNo }); + return []; + } + + try { + const queryParams = new URLSearchParams({ + page: `${curDetailPage - 1}`, + size: '3', + }); + + console.log(`SSC 추가 데이터 API 호출 시작: v1/onsemiro/swgRain?${queryParams.toString()}`); + + const result = await getData(`v1/onsemiro/swgRain/${adstDstrCd}/${manageNo}/mntrngs?${queryParams.toString()}`); + + console.log('SSC 추가 데이터 API 호출 성공:', result); + + // 각 아이템 전처리 + const processedDataArray = result.content.map((data: any) => { + // 기본 포맷팅 적용 + const formattedItem = formatDisplayData(data); + return { + ...formattedItem, + }; + }); + + console.log('processedDataArray', processedDataArray); + + setMntrngsData(processedDataArray); + setTotalDetailCount(result?.totalElements || 0); + } catch (error) { + console.error('추가 데이터 로딩 실패:', error); + return []; + } + }; + + + // 🎯 추가 데이터는 별도 useEffect로 비동기 로딩 + useEffect(() => { + // 강우/측정 정보 데이터 호출 + if (isMainSsc && chmbrXcnts && chmbrYdnts) { + fetchRainMeasureData(chmbrXcnts, chmbrYdnts) + } + }, [chmbrXcnts, chmbrYdnts]); const openModal = () => { setIsModalOpen(true); @@ -46,6 +140,19 @@ const Ssc = ({ featureData, isMainSsc, rainMeasureData, onSaveClick }: { } }, [featureData]); + // featureData가 변경될 때 formData 초기화 + useEffect(() => { + // showTable이 true일 때만 fetch (최초 렌더 방지) + setIsResetPage(false); + setIsResetDetailPage(true); + if (chmbrXcnts && chmbrYdnts) fetchRainMeasureData(chmbrXcnts, chmbrYdnts); + }, [curPage]); + + // 페이지 변경 시마다 데이터 fetch + useEffect(() => { + if (chmbrXcnts && chmbrYdnts) fetchMntrngsData(chmbrXcnts, chmbrYdnts); + }, [curDetailPage]); + // 폼 데이터가 변경될 때 호출될 핸들러 const handleFormDataChange = (newFormData: any) => { setFormData(newFormData); @@ -62,91 +169,39 @@ const Ssc = ({ featureData, isMainSsc, rainMeasureData, onSaveClick }: { // 테이블 셀 클릭 핸들러 추가 const handleCellClick = async (index: number | undefined, headId: string) => { if (index !== undefined && rainMeasureData) { - const data = await fetchMntrngsData(rainMeasureData[index].adstDstrCd, rainMeasureData[index].manageNo); - - setMntrngsData(data); + await fetchMntrngsData(rainMeasureData[index].adstDstrCd, rainMeasureData[index].manageNo); setSelectedRowIndex(index); } }; - // 🎯 추가 데이터 API 호출 함수 (독립적으로 실행) - const fetchMntrngsData = useCallback(async (adstDstrCd: string, manageNo: string) => { - if (!adstDstrCd || !manageNo) { - console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { adstDstrCd, manageNo }); - return []; - } - - try { - const queryParams = new URLSearchParams({ - pageable: `{}`, - }); - - console.log(`SSC 추가 데이터 API 호출 시작: v1/onsemiro/swgRain?${queryParams.toString()}`); - - const result = await getData(`v1/onsemiro/swgRain/${adstDstrCd}/${manageNo}/mntrngs?${queryParams.toString()}`); - - console.log('SSC 추가 데이터 API 호출 성공:', result); - - // 🎯 result가 페이지네이션 객체인 경우 content 배열 추출 - let dataArray: any[] = []; - if (result && typeof result === 'object' && 'content' in result && Array.isArray(result.content)) { - dataArray = result.content; - } else if (Array.isArray(result)) { - dataArray = result; - } else { - console.warn('예상치 못한 API 응답 형식:', result); - return []; - } - - // 🎯 각 아이템을 전처리하여 반환 - const processedDataArray = dataArray.map((data: any) => { - // 🎯 formatDisplayData로 기본 포맷팅 적용 - const formattedItem = formatDisplayData(data); - - // 🎯 추가 데이터 조합 - return { - ...formattedItem, - }; - }); - - console.log('processedDataArray', processedDataArray); - - return processedDataArray; - } catch (error) { - console.error('추가 데이터 로딩 실패:', error); - return []; - } - }, []); - const renderModalBody = () => ( <div className='grid grid-cols-1 gap-4 p-4'> {/* 강우정보 테이블 */} <UiTable headName='강우정보' - tableData={Array.isArray(rainMeasureData) ? rainMeasureData : []} + tableData={rainMeasureData} headlist={untrtSwgRainCol3} - pageSize={10} - total={Array.isArray(rainMeasureData) ? rainMeasureData.length : 0} + pageSize={3} + total={0} cellClick={handleCellClick} - onPageChange={function (page: number): void { - console.log('Rain data page change:', page); - }} + onPageChange={setCurPage} + resetPageNo={isResetPage} + resetSelection={isResetSelection} + index={curIdx} /> {/* 측정정보 테이블 */} <UiTable headName='측정정보' - tableData={Array.isArray(rainMeasureData) ? rainMeasureData : []} + tableData={rainMeasureData} headlist={untrtSwgRainCol4} - pageSize={10} - total={Array.isArray(rainMeasureData) ? rainMeasureData.length : 0} + pageSize={3} + total={totalCount} cellClick={handleCellClick} - // onPageChange={setCurPage} - onPageChange={function (page: number): void { - console.log('Rain data page change:', page); - }} - resetPageNo={false} - // index={curPage} + onPageChange={setCurPage} + resetPageNo={isResetPage} + resetSelection={isResetSelection} + index={curIdx} > {/* 커스텀 헤더 */} <TableRow> @@ -168,14 +223,12 @@ const Ssc = ({ featureData, isMainSsc, rainMeasureData, onSaveClick }: { {/* 수량/수질측정 정보 테이블 */} <UiTable headName='수량/수질측정 정보' - tableData={Array.isArray(mntrngsData) ? mntrngsData : []} + tableData={mntrngsData} headlist={untrtSwgRainCol5} - pageSize={10} - total={Array.isArray(mntrngsData) ? mntrngsData.length : 0} + pageSize={3} + total={totalDetailCount} cellClick={undefined} - onPageChange={function (page: number): void { - console.log('Quality data page change:', page); - }} + onPageChange={setCurDetailPage} /> </div> ) From 80156bd349f3c658cf2aa71c4807699771105f8f Mon Sep 17 00:00:00 2001 From: JIeunhuh <entks301@naver.com> Date: Thu, 31 Jul 2025 10:02:21 +0900 Subject: [PATCH 24/71] =?UTF-8?q?=ED=8C=9D=EC=97=85=20=ED=98=84=ED=99=A9?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?(=EC=8B=9C=EC=84=A4=EB=AC=BC=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C?= =?UTF-8?q?=20api=20=EB=AA=A8=EB=91=90=20=EC=A1=B0=ED=9A=8C=20=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=B6=80=EB=B6=84=20=EC=9E=91=EC=97=85=20=EC=A4=91?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wr/app/map/mainMap/page.tsx | 27 ++-- apps/wr/app/map/newWindowPopup.tsx | 206 +++++++++++++++-------------- 2 files changed, 126 insertions(+), 107 deletions(-) diff --git a/apps/wr/app/map/mainMap/page.tsx b/apps/wr/app/map/mainMap/page.tsx index cc71e69aa..c9d58c422 100644 --- a/apps/wr/app/map/mainMap/page.tsx +++ b/apps/wr/app/map/mainMap/page.tsx @@ -11,7 +11,7 @@ const Map = () => { const gisMaps = useRef<any | null>(null); const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 const [isSearchModalOpen, setIsSearchModalOpen] = useState(true); // SearchModal 열림 상태 - const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터 + const [featureData, setFeatureData] = useState<any>(null); // 선택된 피쳐 데이터 const [checkedLayers, setCheckedLayers] = useState<string[]>([]); const [checkedLayersData, setCheckedLayersData] = useState<any[]>([]); @@ -114,7 +114,9 @@ const Map = () => { } else { console.warn("OpenLayers Feature를 찾을 수 없습니다:", data); } - + const currentId = data?.fid || data?.id || data?.ftrIdn; + const lastId = featureData?.fid || featureData?.id || featureData?.ftrIdn; + if (featureData && currentId === lastId) return; // 이전과 같으면 무시 setFeatureData(data); setIsOpen(true); setIsSearchModalOpen(false); @@ -130,7 +132,17 @@ const Map = () => { // /> // ); // }; - + const renderDetailView = useCallback( + (props: any) => ( + <NewWindowPopup + onClose={handlePopupClose} + checkedLayers={checkedLayers} + checkedLayersData={checkedLayersData} + {...props} + /> + ), + [handlePopupClose, checkedLayers, checkedLayersData] // 필요 시 의존성 넣기 + ); return ( <div className="bg-container"> <Gis @@ -139,14 +151,7 @@ const Map = () => { gisMaps={gisMaps} isMiniMap={false} searchView={undefined} - detailView={(props: any) => ( - <NewWindowPopup - onClose={handlePopupClose} // 메모이제이션된 함수 사용 - checkedLayers={checkedLayers} - checkedLayersData={checkedLayersData} - {...props} - /> - )} + detailView={renderDetailView} onFeatureSelect={onFeatureSelect} setIsOpen={setIsOpen} setFeatureData={setFeatureData} diff --git a/apps/wr/app/map/newWindowPopup.tsx b/apps/wr/app/map/newWindowPopup.tsx index 76529b31b..ea13d85de 100644 --- a/apps/wr/app/map/newWindowPopup.tsx +++ b/apps/wr/app/map/newWindowPopup.tsx @@ -15,7 +15,7 @@ import { useGisContext } from "@common/business_components/ui/map/Gis"; import type { DemandCompType } from '@common/business_components/ui/sdmInsertForm/form'; import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; import moment from 'moment'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; interface NewWindowPopupProps { featureData: any; @@ -87,7 +87,7 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ pbnst, pubHasuInit, } = useWrStore((state) => state); - const { pubFacHeadList, pbnstHasuRes } = pbnst; + const { pubFacHeadList, pbnstHasuRes, pbnstHasuResCount } = pbnst; const [hasuList, setHasuList] = useState<pubHasuType[]>(pbnstHasuRes || []); const [isRuseReg, setIsRuseReg] = useState<{ data: any | undefined; @@ -100,11 +100,6 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ coSttn, coSttnInit, coSttnSearch, - // getDemandCompList, - // regDemandCompany, - // updateDemandCompany, - // coSttnTxtSearch, - // demandCompList, } = useWrStore((state) => state); const [mngTable, setMngTable] = useState<DemandCompType[]>(); const [mngPage, setMngPage] = useState(1); @@ -112,11 +107,11 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ const [isDmdModal, setIsDmdModal] = useState<{ isMoal: boolean; data: any | undefined; - }>({ isMoal: false, data: undefined}); + }>({ isMoal: false, data: undefined }); const [mngList, setMngList] = useState<DemandCompType[]>(coSttnRes || []); ///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 /////////////////////////////////////////////// const getSelectedLayers = () => { - console.log(1) + console.trace('호출확인',1) let result = null; const layers = gisMap ? gisMap.getLayers().getArray().filter((l: any) => l.getVisible()) : []; // gisMap이 null인지 확인 후 레이어 가져오기 let featureLayer: { get: (key: string) => any } = { get: () => null }; @@ -128,8 +123,6 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ } }); result = featureLayer.get('lyrNm'); - console.log(result) - return result; }; const fetchpbnstDetail = async (featureCd: string) => { @@ -143,7 +136,7 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ }; const fetchHasuDetail = async (featureCd: string, data: string) => { try { - const rainRes = await postFectch(`/prcsFclt/ruseInfo/1/10/A/0/0/1/SW/${featureCd}`, [data], fetchType.COMMON) + const rainRes = await postFectch(`/prcsFclt/ruseInfo/1/20/A/0/0/1/SW/${featureCd}`, [data], fetchType.COMMON) setHasuList( rainRes.data.map((item: any) => { return { @@ -166,7 +159,7 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ const res = await getFetch(fetchType.WR, `/reuseFc/waterreuseFc/1/20/0/${featureCd}`) setRswtrList(res) } - else if(layerNm =='수요처'){ //수요처 + else if (layerNm == '수요처') { //수요처 const res = await getFetch(fetchType.WR, `/reuseFc/request/1/20/0/${featureCd}`) setMngList(res) } @@ -190,36 +183,43 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ if (htdflList.length > 0) setHtdflDetailModal({ isOpen: true, data: htdflList[0] }); }, [htdflList]) useEffect(() => { - if(mngList.length > 0) setIsDmdModal({ isMoal: true, data: [mngList[0]] }); - },[mngList]) + if (mngList.length > 0) setIsDmdModal({ isMoal: true, data: [mngList[0]] }); + }, [mngList]) + const lastCalledIdRef = useRef<string | null>(null); + const [layerId, setLayerId] = useState<string|null>(); +useEffect(() => { + const layers = getSelectedLayers(); + setLayerId(layers); +}, []); // 최초 렌더링 시 호출 - const layerId = getSelectedLayers(); - console.log(layerId) - if (featureData && typeof featureData.getProperties === "function") { - const selectedVal = featureData.getProperties(); - let featureCd = layerId == '하수재이용시설' ? selectedVal?.swtrmt_fclt_cd : layerId == '빗물이용시설' || layerId == '중수도시설' || layerId == '온배수시설' || layerId == '수요처' ? selectedVal?.bdst_nm : layerId == '하수처리시설' ? selectedVal?.swtrmt_fclt_nm : null; - if (featureCd && featureCd !== fcltPk) { - if (layerId == '하수재이용시설') { - featureCd = selectedVal?.swtrmt_fclt_cd; - setFcltPk(featureCd); // 현재 호출된 featureCd 저장 - setActiveTab(featureCd) - fetchpbnstDetail(featureCd); // 상세정보 - } - else if (layerId == '하수처리시설') { - featureCd = selectedVal?.swtrmt_fclt_nm; - setFcltPk(featureCd); // 현재 호출된 featureCd 저장 - fetchHasuDetail(featureCd, selectedVal?.swtrmt_fclt_cd); - } - else { - featureCd = selectedVal?.bdst_nm; - setFcltPk(featureCd) - if (layerId) { - detainSttn(featureCd, layerId); - } - } - setViewMode("detail") + useEffect(() => { + if (!featureData || typeof featureData.getProperties !== "function" || featureData.getProperties() == '') return; + const selectedProps = featureData.getProperties(); + const currentId = featureData.getId?.() || selectedProps?.id || selectedProps?.fid; + // 유효하지 않은 ID는 아예 무시 + if (!currentId || typeof currentId !== 'string') return; + // 이전과 동일하면 무시 + if (!currentId || currentId === lastCalledIdRef.current) return; + lastCalledIdRef.current = currentId // 새로운 피처일 때만 API 호출 + const layerId = getSelectedLayers?.(); + if (!layerId || typeof layerId !== 'string') return; + const featureCd = + layerId === '하수재이용시설' ? selectedProps?.swtrmt_fclt_cd : + ['빗물이용시설', '중수도시설', '온배수시설', '수요처'].includes(layerId) ? selectedProps?.bdst_nm : + layerId === '하수처리시설' ? selectedProps?.swtrmt_fclt_nm : + null; + if (!featureCd || featureCd === fcltPk) return; + if (layerId === '하수재이용시설') { + fetchpbnstDetail(featureCd); + } else if (layerId === '하수처리시설') { + fetchHasuDetail(featureCd, selectedProps?.swtrmt_fclt_cd); + } else { + detainSttn(featureCd, layerId); } - } else if (layerId === null && seletectedLayerNm) { setViewMode("list") } + setFcltPk(featureCd); + setViewMode("detail"); + setActiveTab(featureCd); + }, [featureData]); ///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 /////////////////////////////////////////////// ///////////////////////////////////////////// 하수재이용시설 ////////////////////////////////////////////////// @@ -238,12 +238,14 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ // 재이용 현황 초기값 useEffect(() => { - if (!pbnstSttn.isInit) { + if (!pbnstSttn.isInit && seletectedLayerNm === '하수재이용시설') { getFlcts() } }, [seletectedLayerNm, pbnstSttn.isInit, getFlcts]); useEffect(() => { - pbnSttnInit("1", "15", { + if(seletectedLayerNm !== '하수재이용시설') return; + console.trace('check', 1) + pbnSttnInit("1", "20", { partValue: "A", sidoValue: '0', sigunValue: "0", @@ -254,7 +256,7 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ sidoText: "", sigunText: "" }, String(new Date().getFullYear()), pbnstSttn.fclts); - }, [fclts, pbnSttnInit, pbnstSttn.fclts]) + }, [fclts, pbnSttnInit, pbnstSttn.fclts, seletectedLayerNm]) useEffect(() => { setBdstTable(pbnstSttn.pbnstSttnRes); }, [seletectedLayerNm, pbnstSttn.pbnstSttnRes]); @@ -271,10 +273,11 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ // 재이용시설 조회 // 재이용시설 페이징처리 관련 함수 const onPageChangeHandler = useCallback((page: number) => { + if(seletectedLayerNm!== '하수재이용시설') return; pbnstSttnSearch( false, String(page), - String(13), + String(20), { partValue: "A", sidoValue: "0", @@ -318,6 +321,7 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ useEffect(() => { + if(seletectedLayerNm !== '빗물이용시설') return; if (!rainSttn.isInit) { rainSttnInit(false, 1, "20", "0", '', code.cityCd, code.sggCd); } @@ -355,6 +359,7 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ }, [code, rainSttnRes, rainSttn.rainSttHeadList]); useEffect(() => { + if(seletectedLayerNm !== '빗물이용시설') return; rainSttnSearch(false, rainPage, "20", '', '', code.cityCd, code.sggCd); }, [code.cityCd, code.sggCd, rainPage, rainSttnSearch]); ///////////////////////////////////////////// 빗물 재이용 ////////////////////////////////////////////////// @@ -370,12 +375,14 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ } } useEffect(() => { + if(seletectedLayerNm !== '중수도시설') return; if (!rswtSttn.isInit) { rswtSttnInit(false, 1, "20", "0", '', code.cityCd, code.sggCd); } }, [code.cityCd, code.sggCd, rswtSttn.isInit, rswtSttnInit]); useEffect(() => { + if(seletectedLayerNm !== '중수도시설') return; const data = tableData(rswtSttnRes, { code }); const chData = data .map((item: customRswtBdst) => { @@ -409,6 +416,7 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ // }, [rswtSttn.excelList]) useEffect(() => { + if(seletectedLayerNm !== '중수도시설') return; rswtSttnSearch(false, rswtrPage, "20", '', '', code.cityCd, code.sggCd); }, [code.cityCd, code.sggCd, rswtSttnSearch, rswtrPage]); ///////////////////////////////////////////// 중수도 시설 ////////////////////////////////////////////////// @@ -422,12 +430,14 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ } }; useEffect(() => { + if(seletectedLayerNm !== '온배수시설') return; if (!htdflSttn.isInit) { initHtdfl(false, 1, "20", "0", '', code.cityCd, code.sggCd); } }, [code.cityCd, code.sggCd, htdflSttn.isInit, initHtdfl]); useEffect(() => { + if(seletectedLayerNm !== '온배수시설') return; const data = tableData(htdflSttnRes, { code }); const chData = data .map((item: any) => ({ @@ -441,6 +451,7 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ }, [code, htdflSttnRes, htdflSttn.htdflSttHeadList]); useEffect(() => { + if(seletectedLayerNm !== '온배수시설') return; htdflSttnSearch(true, htdflPage, "20", '', '', code.cityCd, code.sggCd); }, [code.cityCd, code.sggCd, htdflPage, htdflSttnSearch]); ///////////////////////////////////////////// 온배수 시설 ////////////////////////////////////////////////// @@ -452,6 +463,7 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ } }, [code, pbnstHasuRes]); useEffect(() => { + if(seletectedLayerNm !== '하수처리시설') return; if (!pbnst.isInit) { pubHasuInit("1", "0"); } @@ -470,8 +482,9 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ ///////////////////////////////////////////// 수요처 관리 ////////////////////////////////////////////////// useEffect(() => { + if(seletectedLayerNm !== '수요처') return; if (!coSttn.isInit) { - coSttnInit("1", "13", "0"); + coSttnInit("1", "20", "0"); } }, [coSttn.isInit, coSttnInit]); const coMngCellClick = (index: number | undefined) => { @@ -481,6 +494,7 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ } }; useEffect(() => { + if(seletectedLayerNm !== '수요처') return; if (coSttnRes) { if (coSttnRes?.length > 0 && code.cityCd.length > 0) { const data = tableData(coSttnRes, { code }); @@ -496,21 +510,21 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ } }, [coSttnRes, code]); useEffect(() => { - coSttnSearch(String(mngPage), "13", String(0)); + if(seletectedLayerNm !== '수요처') return; + coSttnSearch(String(mngPage), "20", String(0)); }, [coSttnSearch, mngPage]); ///////////////////////////////////////////// 수요처 관리 ////////////////////////////////////////////////// // 체크된 레이어에 따른 조건부 컴포넌트 렌더링 함수 const renderConditionalContent = () => { const actTabData = tabData.find((tab) => tab.id === activeTab); - let layerId: string | undefined | null = getSelectedLayers(); if (layerId === null || layerId === undefined) { - layerId = seletectedLayerNm || ''; + setLayerId(seletectedLayerNm || ''); } //하수처리시설 const isPrcsFclt = layerId == '하수처리시설' // 재이용시설 - const isPbnst = layerId == '재이용시설'; + const isPbnst = layerId == '하수재이용시설'; // 빗물이용시설 const isRain = layerId == '빗물이용시설'; // 중수도시설 @@ -518,7 +532,7 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ // 온배수시설 const isHtdfl = layerId == '온배수시설'; // 수요처관리 - const isReuseMng = layerId == '수요처관리'; + const isReuseMng = layerId == '수요처'; if (isPrcsFclt) { if (viewMode == 'list') { return ( @@ -530,8 +544,8 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ <UiTable tableData={hasuTable || []} headlist={pubFacHeadList} - pageSize={10} - total={5000} + pageSize={11} + total={pbnstHasuResCount || 0} headName={""} cellClick={hasuCellClick} onPageChange={setHasuPage} @@ -730,51 +744,51 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ } if (isReuseMng) { if (viewMode == 'detail') { - return ( - <div style={{ overflowX: 'hidden' }}> - <div className="flex justify-between items-center mb-4"> - <Title title="수요처 상세정보" /> - </div> - {/* 높이 조정 */} - <UiTable - tableData={isDmdModal?.data || []} - headlist={coSttHeadList.slice(0, 3)} - pageSize={1} - total={0} - headName={"위치정보"} - cellClick={() => { }} - onPageChange={() => { }} - /> - <UiTable - tableData={isDmdModal?.data || []} - headlist={coSttHeadList.slice(3)} - pageSize={1} - total={0} - headName={"기본정보"} - cellClick={() => { }} - onPageChange={() => { }} - /> + return ( + <div style={{ overflowX: 'hidden' }}> + <div className="flex justify-between items-center mb-4"> + <Title title="수요처 상세정보" /> </div> - ) - } else if(viewMode == 'list') { - return ( <div> + {/* 높이 조정 */} + <UiTable + tableData={isDmdModal?.data || []} + headlist={coSttHeadList.slice(0, 3)} + pageSize={1} + total={0} + headName={"위치정보"} + cellClick={() => { }} + onPageChange={() => { }} + /> + <UiTable + tableData={isDmdModal?.data || []} + headlist={coSttHeadList.slice(3)} + pageSize={1} + total={0} + headName={"기본정보"} + cellClick={() => { }} + onPageChange={() => { }} + /> + </div> + ) + } else if (viewMode == 'list') { + return (<div> <div className="flex justify-between items-center"> - <Title title="수요처 현황" /> - </div> - <UiTable - headName={[]} - tableData={mngTable || []} - headlist={coSttHeadList} - pageSize={20} - total={coSttnResCount + 10 || 0} - cellClick={(index: number | undefined) => { - coMngCellClick(index); - // setUpdateData(coSttnRes[index as number]); - }} - onPageChange={(page) => { - setMngPage(page); - }} - ></UiTable></div>) + <Title title="수요처 현황" /> + </div> + <UiTable + headName={[]} + tableData={mngTable || []} + headlist={coSttHeadList} + pageSize={20} + total={coSttnResCount + 10 || 0} + cellClick={(index: number | undefined) => { + coMngCellClick(index); + // setUpdateData(coSttnRes[index as number]); + }} + onPageChange={(page) => { + setMngPage(page); + }} + ></UiTable></div>) } } return null; // 조건에 맞는 콘텐츠가 없을 경우 From c94b33b1b8314cbddfd31b5a52d3722a896181bc Mon Sep 17 00:00:00 2001 From: leedj312 <dlehd467@gmail.com> Date: Thu, 31 Jul 2025 11:05:05 +0900 Subject: [PATCH 25/71] =?UTF-8?q?=EB=8C=80=EB=AF=BC=20=EC=8B=A0=EC=B2=AD?= =?UTF-8?q?=ED=98=84=ED=99=A9=20=EB=A7=A4=EC=B9=AD=EB=90=9C=20=EA=B3=B5?= =?UTF-8?q?=EA=B8=89=EC=B2=98=20=EB=B3=B4=EC=9D=B4=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(base-layout)/pages/sdmMng/sdmApplyList/page.tsx | 12 +++++++----- apps/dm/app/store/sdmMng/index.tsx | 7 ++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx index fb81197d6..ee86a9df5 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx @@ -63,7 +63,7 @@ export default function SdmApplyList({}: SdmApplyProps) { const newTransformedList = sdmInfoStatusList.map((item: DemandCompBizType) => ({ ...item, ctpvCd: selectSidoData.find(sido => sido.val === item.ctpvCd?.trim())?.text || '', - sggCd: selectSigunData.find(sigun => sigun.val === item.sggCd?.trim())?.text || '' + sggCd: selectSigunData.find(sigun => sigun.val === item.sggCd?.trim())?.text || '', })); setTransformedList(newTransformedList); } @@ -73,6 +73,8 @@ export default function SdmApplyList({}: SdmApplyProps) { if (index !== undefined) { const selectedStatus = transformedList[index]; if (selectedStatus) { + + setSdmApplyDetail(selectedStatus); } } @@ -156,11 +158,11 @@ export default function SdmApplyList({}: SdmApplyProps) { </TableRow> <TableRow> <TableCell colSpan={1} className={TABLE_STYLES.header}>후보지1</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}></TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{ sdmApplyDetail?.splyCndtList?.[0]?.ruseUtztnFcltNm ?? ""}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>후보지2</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}></TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{sdmApplyDetail?.splyCndtList?.[1]?.ruseUtztnFcltNm ?? ""}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>후보지3</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}></TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{sdmApplyDetail?.splyCndtList?.[2]?.ruseUtztnFcltNm ?? ""}</TableCell> </TableRow> </TableBody> </Table> @@ -177,7 +179,7 @@ export default function SdmApplyList({}: SdmApplyProps) { <TableCell colSpan={1} className={TABLE_STYLES.header}>진행상태</TableCell> <TableCell colSpan={2} className={TABLE_STYLES.cell}>{sdmApplyDetail?.status?.toString()}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>공급처매칭결과</TableCell> - <TableCell colSpan={2} className={TABLE_STYLES.cell}></TableCell> + <TableCell colSpan={2} className={TABLE_STYLES.cell}>{sdmApplyDetail?.ruseUtztnFcltNm??''}</TableCell> </TableRow> </TableBody> </Table> diff --git a/apps/dm/app/store/sdmMng/index.tsx b/apps/dm/app/store/sdmMng/index.tsx index 2199aa7b6..d68a34e2f 100644 --- a/apps/dm/app/store/sdmMng/index.tsx +++ b/apps/dm/app/store/sdmMng/index.tsx @@ -40,6 +40,7 @@ export type DemandCompBizType = { dmdInfoSn : number | ''; status : string | ''; splyCndtList:SupplyCandidate[]|[], + ruseUtztnFcltNm?:string|'' } @@ -602,6 +603,8 @@ export const SdmReducer = (set: any): SdmActions => { res.demandInfo.map((el: DemandCompBizType, index: number) => { const matchingInfo = res.demandMatchings.find((matching: DemandMatchings) => matching.dmdInfoSn === el.dmdInfoSn); const status = matchingInfo?.splyFcltFlg?.trim() === 'Sub' ? '검토' : matchingInfo?.splyFcltFlg?.trim() === 'matched' ? '매칭완료' : '신청'; + const ruseUtztnFcltNm = matchingInfo?.ruseUtztnFcltNm ? matchingInfo.ruseUtztnFcltNm : ''; + const tmpData = { ...el, @@ -617,7 +620,9 @@ export const SdmReducer = (set: any): SdmActions => { useUsgCd: el.useUsgCd, dmndTmng: el.dmndTmng, dmdInfoSn : el.dmdInfoSn, - status : status + status : status, + splyCndtList: el.splyCndtList, + ruseUtztnFcltNm: ruseUtztnFcltNm || '' } sdmInfoStatusList.push(tmpData); }) From 637929ea26c3f87ce942af9dd82953e7b3f9e365 Mon Sep 17 00:00:00 2001 From: yschoi <yschoi> Date: Thu, 31 Jul 2025 13:15:22 +0900 Subject: [PATCH 26/71] =?UTF-8?q?=EC=A7=80=EB=8F=84=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/dm/app/(popup-layout)/map/popup/page.tsx | 22 ++- apps/fo/app/pages/map/page.tsx | 28 ++- apps/sn/app/(map-layout)/map/page.tsx | 2 + .../(map-layout)/map/searchBar/searchBar.tsx | 83 +++++++++ apps/wr/app/map/mainMap/page.tsx | 11 +- apps/wr/app/map/popup/page.tsx | 11 +- .../business_components/src/ui/map/Gis.js | 176 +++++------------- .../src/ui/map/GisContent.js | 121 ++++++------ .../business_components/src/ui/map/GisTab.js | 84 --------- 9 files changed, 217 insertions(+), 321 deletions(-) create mode 100644 apps/sn/app/(map-layout)/map/searchBar/searchBar.tsx delete mode 100644 packages/common/business_components/src/ui/map/GisTab.js diff --git a/apps/dm/app/(popup-layout)/map/popup/page.tsx b/apps/dm/app/(popup-layout)/map/popup/page.tsx index dc4e404ce..ea71862d8 100644 --- a/apps/dm/app/(popup-layout)/map/popup/page.tsx +++ b/apps/dm/app/(popup-layout)/map/popup/page.tsx @@ -62,10 +62,9 @@ const Map = () => { Object.keys(item).length > 0 && !prev.some((f) => f.pbnstRuseFcltId === item.pbnstRuseFcltId) ); - console.log(newItems) + console.log(newItems); return [...prev, ...newItems]; }); - } catch (error) { console.error("데이터 불러오기 실패:", error); } @@ -232,14 +231,6 @@ const Map = () => { return ( <> - <SearchBar - setHasuPk={(hasuPk: any) => { - if (hasuPk) { - setSelectDataPk(hasuPk); - } - }} - onApply={onApply} - /> <div className="bg-container"> <Gis treeData={treeData} @@ -257,6 +248,17 @@ const Map = () => { isSearchModalOpen={isSearchModalOpen} onCheckedLayersChange={onCheckedLayersChange} imgVisble={false} + searchBar={(props: any) => ( + <SearchBar + setHasuPk={(hasuPk: any) => { + if (hasuPk) { + setSelectDataPk(hasuPk); + } + }} + onApply={onApply} + {...props} + /> + )} /> </div> <SelectedFcltList diff --git a/apps/fo/app/pages/map/page.tsx b/apps/fo/app/pages/map/page.tsx index cbbe0dd11..2c27869b0 100644 --- a/apps/fo/app/pages/map/page.tsx +++ b/apps/fo/app/pages/map/page.tsx @@ -13,7 +13,11 @@ interface FeatureData { } interface MapInstance { - getLayers: () => { getArray: () => Array<{ getSource?: () => { getFeatureById?: (id: string) => FeatureData | null } }> }; + getLayers: () => { + getArray: () => Array<{ + getSource?: () => { getFeatureById?: (id: string) => FeatureData | null }; + }>; + }; getView: () => { fit: (extent: number[], options: object) => void }; } @@ -26,20 +30,20 @@ const Map = () => { const fetchData = async () => { try { - const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map` + const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map`; const reqUrl = `${API_URL}/lyrStngs/FO_NETWORK`; const response = await fetch(reqUrl, { method: "GET", headers: { accept: "application/json", - } + }, }); if (!response.ok) { throw new Error(`HTTP 오류! 상태: ${response.status}`); } - const data = await response.json(); // 여기서 json 파싱 + const data = await response.json(); // 여기서 json 파싱 setTreeData(data); // 데이터 설정 } catch (error) { console.error("데이터 불러오기 실패:", error); @@ -60,7 +64,12 @@ const Map = () => { } // 항상 같은 select 객체 사용 - const select = (GisApp.Module.select as unknown) as { resetSelectedFeature: () => void; selectFeature: (feature: FeatureData, selected: boolean) => void } | undefined; + const select = GisApp.Module.select as unknown as + | { + resetSelectedFeature: () => void; + selectFeature: (feature: FeatureData, selected: boolean) => void; + } + | undefined; if (!select) return; // 1. 기존 선택 초기화 @@ -115,9 +124,12 @@ const Map = () => { }; // onCheckedLayersChange handler - const handleCheckedLayersChange = (checkedIds: string[], checkedNodesData: unknown[]) => { + const handleCheckedLayersChange = ( + checkedIds: string[], + checkedNodesData: unknown[] + ) => { // Handle checked layers change if needed - console.log('Checked layers changed:', checkedIds, checkedNodesData); + console.log("Checked layers changed:", checkedIds, checkedNodesData); }; return ( @@ -137,6 +149,8 @@ const Map = () => { isOpen={isOpen} isSearchModalOpen={isSearchModalOpen} onCheckedLayersChange={handleCheckedLayersChange} + imgVisble={false} + searchBar={undefined} /> </div> ); diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index 2dbbcdd1a..40b034199 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -6,6 +6,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import GisApp from "@common/business_components/ui/map/lib/gis.core"; import SearchModal from "./searchModal/searchModal"; import FeatureClickEventPopup from "./featureClickEventPopup"; +import { SearchBar } from "./searchBar/searchBar"; // 타입 정의 추가 export type LyrStngProps = { @@ -130,6 +131,7 @@ const Map = () => { isOpen={isOpen} isSearchModalOpen={isSearchModalOpen} onCheckedLayersChange={undefined} + searchBar={SearchBar} /> </div> ); diff --git a/apps/sn/app/(map-layout)/map/searchBar/searchBar.tsx b/apps/sn/app/(map-layout)/map/searchBar/searchBar.tsx new file mode 100644 index 000000000..b57e66c24 --- /dev/null +++ b/apps/sn/app/(map-layout)/map/searchBar/searchBar.tsx @@ -0,0 +1,83 @@ +"use client"; +import { + SearchDiv, + SearchFormLeft, + SelectBox, + type SelectDataType, +} from "@common/business_components/ui"; +import { useCallback, useEffect, useState } from "react"; + +export function SearchBar() { + const [sidoValue, setSidoValue] = useState<string>(""); + const [sigunValue, setSigunValue] = useState<string>(""); + const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([ + { text: "시도 선택", val: "0" }, + ]); + + const initializeSigunData = useCallback((sidoCd: string) => { + if (sidoCd === "0") { + setSigunValue("0"); + setFilteredSigunData([{ text: "시도 선택", val: "0" }]); + } else { + const newFilteredData = [ + { text: "데이터", val: "0" }, + // ...Common.selectSigunData.filter( + // (item: SelectDataType) => item.val.substring(0, 2) === sidoCd, + // ), + ]; + setSigunValue("0"); + setFilteredSigunData(newFilteredData); + } + }, []); + const onSelectValue = useCallback( + (val: string, type: string) => { + switch (type) { + case "ctpvCd": + initializeSigunData(val); + setSidoValue(val); + break; + case "sggCd": + setSigunValue(val); + } + }, + [initializeSigunData], + ); + + return ( + <> + <div className="grid grid-cols-4 gap-4"> + <div className="col-span-4"> + <div className="card card-border flex"> + <div className="col-span-10 flex flex-row"> + <SelectBox + className="mr-10 w-[200px]" + selectClass="rounded-lg bg-white" + label="시도" + selectArray={/* Common.selectSidoData || */ []} + onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")} + value={sidoValue || /* Common.selectSidoData[0]?.val || */ ""} + /> + <SelectBox + className="mr-10 w-[200px]" + selectClass="rounded-lg bg-white" + label="시군구" + selectArray={filteredSigunData || []} + onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")} + value={sigunValue} + /> + <SelectBox + className="mr-3 w-[250px]" + selectClass="rounded-lg bg-white" + labelClass="w-[200px]" + label="하수처리시설명" + selectArray={filteredSigunData || []} + onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")} + value={sigunValue} + /> + </div> + </div> + </div> + </div> + </> + ); +} diff --git a/apps/wr/app/map/mainMap/page.tsx b/apps/wr/app/map/mainMap/page.tsx index 1e9afd74b..1f466f1cd 100644 --- a/apps/wr/app/map/mainMap/page.tsx +++ b/apps/wr/app/map/mainMap/page.tsx @@ -136,16 +136,6 @@ const Map = () => { gisMaps={gisMaps} isMiniMap={false} searchView={undefined} - // searchView={(props: any) => ( - // <MapSearchBar - // searchType="공급처 후보지 조회" - // showApplyButton={false} - // checkedLayers={checkedLayers} - // checkedLayersData={checkedLayersData} - // {...props} - // /> - // ) - // } detailView={(props: any) => ( <NewWindowPopup onClose={handlePopupClose} // 메모이제이션된 함수 사용 @@ -163,6 +153,7 @@ const Map = () => { isSearchModalOpen={isSearchModalOpen} onCheckedLayersChange={handleCheckedLayersChange} imgVisble={true} + searchBar={undefined} /> </div> ); diff --git a/apps/wr/app/map/popup/page.tsx b/apps/wr/app/map/popup/page.tsx index 80830aa90..56ec8863b 100644 --- a/apps/wr/app/map/popup/page.tsx +++ b/apps/wr/app/map/popup/page.tsx @@ -123,16 +123,6 @@ const Map = () => { gisMaps={gisMaps} isMiniMap={false} searchView={undefined} - // searchView={(props: any) => ( - // <MapSearchBar - // searchType="공급처 후보지 조회" - // showApplyButton={false} - // checkedLayers={checkedLayers} - // checkedLayersData={checkedLayersData} - // {...props} - // /> - // ) - // } detailView={(props: any) =>( <NewWindowPopup onClose={handlePopupClose} // 메모이제이션된 함수 사용 @@ -150,6 +140,7 @@ const Map = () => { isSearchModalOpen={isSearchModalOpen} onCheckedLayersChange={handleCheckedLayersChange} imgVisble={true} + searchBar={undefined} /> </div> ); diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index f73b428c5..eefb08da6 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -98,7 +98,8 @@ function Gis({ detailView: DetailView, onFeatureSelect, setIsSearchModalOpen, - onCheckedLayersChange + onCheckedLayersChange, + searchBar: SearchBar }) { const mapRef = useRef(); const [mapObject, setMapObject] = useState({}); @@ -127,102 +128,7 @@ function Gis({ // Feature Click Event const handleFeatureSelect = useCallback((data) => { - console.log(data); - // gisMap = mapInstanceRef.current; - // gisMap = mapInstanceRef.current; - - // if (!gisMap) { - // console.warn('지도가 초기화되지 않았습니다.'); - // return; - // } - // if (!gisMap) { - // console.warn('지도가 초기화되지 않았습니다.'); - // return; - // } - - // // 항상 같은 select 객체 사용 - // const select = GisApp.Module.select; - // if (!select) return; - // // 항상 같은 select 객체 사용 - // const select = GisApp.Module.select; - // if (!select) return; - - // // 1. 기존 선택 초기화 - // select.resetSelectedFeature(); - - // // 2. data가 OpenLayers Feature 인스턴스인지 확인 - // let foundFeature = null; - // if (data && typeof data.getGeometry === 'function' && typeof data.setStyle === 'function') { - // // 이미 Feature 인스턴스라면 그대로 사용 - // foundFeature = data; - // } else { - // // 아니면 featureId로 맵에서 Feature 인스턴스를 찾아서 사용 - // let featureId = data.fid; - // const layerName = layerNmRef.current - // if (layerName === '하수재이용시설') { - // featureId = data.searchPk; - // } else if (layerName === '빗물이용시설' || layerName == '중수도시설') { - // featureId = data.bdstInfoSn; - // } else if (layerName === '하수처리시설') { - // featureId = data.bscFctCd; - // } else if (layerName === '온배수시설') { - // featureId = data.bdstRuseFcltSn; - // } else if(layerName == '수요처'){ - // featureId = data.dmdInfoId.replaceAll('-','') - // } - // else { - // console.warn('해당 레이어에 대한 featureId 매핑 없음', layerNm); - // featureId = data.fid || data.id || data.ftrIdn; - // } - // // gisMap.getLayers().getArray().forEach(layer => { - // // const source = layer.getSource?.(); - // // if (source?.getFeatureById) { - // // const feature = source.getFeatureById(String(featureId).trim()); - // // if (feature) foundFeature = feature; - // // } - // // }); - // // const [layerType, id] = String(featureId).split('.'); - // gisMap.getLayers().getArray().forEach(layer => { - // const source = layer.getSource?.(); - // if (source?.getFeatures) { - // const allFeatures = source.getFeatures(); - // for (const f of allFeatures) { - // const id = String(f.getId()); - // // console.log('id', id, featureId) - // if (id && id.split('.')[1] === String(featureId)) { - // foundFeature = f; - // break; - // } - // } - // } - // }); - // } - - // // 3. 피처 선택 - // if (foundFeature) { - // let geometry = foundFeature.getGeometry(); - // if (geometry) { - // let extent = geometry.getExtent(); - - // gisMap.getView().fit(extent, { - // duration: 300, - // padding: [50, 50, 50, 50], - // maxZoom: 16, - // constrainResolution: true, - // nearest: true, - // }); - // } - // select.selectFeature(foundFeature, true); - // } else { - // console.warn('OpenLayers Feature를 찾을 수 없습니다:', data); - // } - - // setFeatureData(data); - // setIsOpen(true); - // setIsSearchModalOpen(false); - onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출 - console.log(data) }, [mapInstanceRef]); // 지도 생성 및 초기화 @@ -347,44 +253,48 @@ function Gis({ <> <GisContext.Provider value={{ gisCore, gisMap: mapInstanceRef.current, treeData, checkedLayerIds }}> <div className="h-[calc(100vh-10rem)] flex relative"> - <div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} /> - <GisTree - visible={tabVisible} - setVisible={setTabVisible} - onCheckedChange={handleCheckedLayersChange} - onLayerIconClick={(layerNm) => { - layerNmRef.current = layerNm - setLayerNm(layerNm); setIsOpen(true); const dummyFeature = { - getProperties: () => ({}), // 최소 구조 - }; - onFeatureSelect(dummyFeature); - }} - imageVisible={imgVisble} - /> - - {/* <GisContent onFeatureSelect={handleFeatureSelect} isSearch={isSearch} isSearchModalOpen={isSearchModalOpen} setIsSearchModalOpen={setIsSearchModalOpen} /> */} - - {/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} - {isOpen && !isSearchModalOpen && ( - <DetailView - featureData={featureData || { getProperties: () => ({}) }} - seletectedLayerNm={layerNm} - onClose={closePopup} // 닫기 콜백 전달 - onFeatureSelect={handleFeatureSelect} + <div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} > + <GisTree + visible={tabVisible} + setVisible={setTabVisible} + onCheckedChange={handleCheckedLayersChange} + onLayerIconClick={(layerNm) => { + layerNmRef.current = layerNm + setLayerNm(layerNm); setIsOpen(true); const dummyFeature = { + getProperties: () => ({}), // 최소 구조 + }; + onFeatureSelect(dummyFeature); + }} + imageVisible={imgVisble} /> - )} - {/* SearchView가 존재할 때만 렌더링 */} - {SearchView && ( - <SearchView - open={isSearchModalOpen} - onOpenChange={setIsSearchModalOpen} - onFeatureSelect={handleFeatureSelect} - /> - )} - {/* <GisLonLat /> */} - <GisToolBar /> - <GisMapToggle /> - <GisSelect onFeatureSelect={handleFeatureSelect} /> + + {/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} + {isOpen && !isSearchModalOpen && ( + <DetailView + featureData={featureData || { getProperties: () => ({}) }} + seletectedLayerNm={layerNm} + onClose={closePopup} // 닫기 콜백 전달 + onFeatureSelect={handleFeatureSelect} + /> + )} + {/* SearchView가 존재할 때만 렌더링 */} + {SearchView && ( + <SearchView + open={isSearchModalOpen} + onOpenChange={setIsSearchModalOpen} + onFeatureSelect={handleFeatureSelect} + /> + )} + {/* 시도/시군구 조회 영역 */} + {SearchBar && ( + <div className="absolute top-4 left-1/2 -translate-x-1/2 z-30 bg-gray-300 rounded " style={{ maxWidth: 'calc(100% - 2rem)' }}> + <SearchBar/> + </div> + )} + <GisToolBar /> + <GisMapToggle /> + <GisSelect onFeatureSelect={handleFeatureSelect} /> + </div> </div> {isSearch && ( diff --git a/packages/common/business_components/src/ui/map/GisContent.js b/packages/common/business_components/src/ui/map/GisContent.js index d2594ba95..45b44c882 100644 --- a/packages/common/business_components/src/ui/map/GisContent.js +++ b/packages/common/business_components/src/ui/map/GisContent.js @@ -1,74 +1,61 @@ -import { Search } from 'lucide-react'; -import { useCallback, useEffect, useRef, useState } from 'react'; -import { useGisContext } from './Gis'; -import './lib/css/gis.edit.css'; -import GisApp from './lib/gis.core'; +import { Search } from "lucide-react"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { useGisContext } from "./Gis"; +import "./lib/css/gis.edit.css"; +import GisApp from "./lib/gis.core"; -export default function GisContent({ edit = false, onFeatureSelect, isSearch, setIsSearchModalOpen }) { - const { gisMap, gisCore } = useGisContext(); - const [visibleComponents, setVisibleComponents] = useState({}); - const addressResultLayer = useRef(null); - const [playShow, setPlayShow] = useState({ show: false }); +export default function GisContent({ + edit = false, + onFeatureSelect, + isSearch, + setIsSearchModalOpen, +}) { + const { gisMap, gisCore } = useGisContext(); - //셀렉트 버튼 이벤트 - // const clickFeature = useCallback(feature => { - // if (feature) { - // const layer = feature.getLayer(); - // setVisibleComponents(prevState => ({ - // ...prevState, - // })); - // } - // }, []); + //셀렉트 버튼 이벤트 + const selectRslt = useCallback((feature) => { + GisApp.Module.layer.highLightFeature( + [feature], + { + id: GisApp.hlLayerId, + zIndex: 10, + opacity: 1, + selectable: true, + }, + { width: 30 }, + ); + clickFeature(feature); + }, []); - const selectRslt = useCallback(feature => { - GisApp.Module.layer.highLightFeature( - [feature], - { - id: GisApp.hlLayerId, - zIndex: 10, - opacity: 1, - selectable: true, - }, - { width: 30 } - ); - clickFeature(feature); - }, []); + useEffect(() => { + if (gisMap) { + setVisibleComponents((prevState) => ({ + ...prevState, + })); + } + }, [gisMap]); - useEffect(() => { - if (gisMap) { - setVisibleComponents(prevState => ({ - ...prevState, - })); - } - }, [gisMap]); + if (!gisMap) return null; - if (!gisMap) return null; + return ( + <> + <GisTree /> + <GisMapToggle /> + <GisLonLat /> + <GisToolBar /> + <GisSelect onFeatureSelect={onFeatureSelect} /> - return ( - <> - {/*<GisTree />*/} - {/* <GisMapToggle /> - <GisLonLat /> - <GisToolBar /> */} - {/* <GisSelect onFeatureSelect={onFeatureSelect} /> */} - - {/* 검색 모달 */} - {isSearch && ( - <div className="absolute right-8 top-[254px] z-20"> - <button - onClick={() => setIsSearchModalOpen(true)} - className="overflow-hidden w-[40px] h-[40px] bg-white - border border-[#9aa7b8] - rounded - shadow-[0_0_5px_#999] - flex items-center justify-center - p-0 - " - > - <Search size={24} className="text-gray-700" /> - </button> - </div> - )} - </> - ); + {/* 검색 모달 */} + {isSearch && ( + <div className="absolute right-8 top-[254px] z-20"> + <button + onClick={() => setIsSearchModalOpen(true)} + className="flex h-[40px] w-[40px] items-center justify-center overflow-hidden rounded border border-[#9aa7b8] bg-white p-0 shadow-[0_0_5px_#999]" + > + <Search size={24} className="text-gray-700" /> + </button> + </div> + )} + </> + ); } diff --git a/packages/common/business_components/src/ui/map/GisTab.js b/packages/common/business_components/src/ui/map/GisTab.js deleted file mode 100644 index 57d8a48e8..000000000 --- a/packages/common/business_components/src/ui/map/GisTab.js +++ /dev/null @@ -1,84 +0,0 @@ -// import React, { useState } from 'react'; -// import { Tabs, TabsList, TabsTrigger } from '@common/components/ui/tabs'; -// import GisTree from './GisTree'; -// import GisSearch from './GisSearch'; -// import { ChevronLeft, ChevronRight, Search } from 'lucide-react'; -// import SearchModal from '@/map/SearchModal'; - - -// function GisTab({ visible, setVisible, isSearch, isSearchModalOpen, setIsSearchModalOpen, onFeatureSelect }) { -// const [activeTab, setActiveTab] = useState('layers'); - -// return ( -// <> -// <div -// className={`absolute top-0 left-0 w-[250px] h-[calc(100vh-10rem)] bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-40 -// ${visible ? 'translate-x-0 opacity-100' : '-translate-x-full opacity-0 pointer-events-none'}`} -// > -// <Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col overflow-hidden"> -// <TabsList className="flex-none mt-4 border-none rounded-none"> -// <TabsTrigger value="layers" className="flex-1 h-10">레이어</TabsTrigger> -// {/* {isSearch && ( -// <button -// onClick={() => setIsSearchModalOpen(true)} -// className="h-10 w-10 flex items-center justify-center px-0" -// > -// <Search size={18} className="text-gray-700" /> -// </button> -// // <TabsTrigger value="search" className="flex-1 h-10 flex items-center justify-center"> -// // <Search size={18} /> -// // </TabsTrigger> -// )} */} -// </TabsList> - -// {/* GisTree는 항상 마운트되며, 탭 상태에 따라 보여주기만 함 */} -// <div -// className={`grow overflow-y-auto p-2 pt-0 border-none shadow-none ${activeTab === 'layers' ? 'block' : 'hidden' -// }`} -// > -// <GisTree /> -// </div> - -// {/* GisSearch도 동일하게 처리 */} -// {/* {isSearch && ( -// <div -// className={`grow overflow-y-auto p-2 pt-0 border-none shadow-none ${ -// activeTab === 'search' ? 'block' : 'hidden' -// }`} -// > -// <GisSearch /> -// </div> -// )} */} - - -// {/* 🔍 검색 모달 */} -// {/* <SearchModal -// open={isSearchModalOpen} -// onOpenChange={setIsSearchModalOpen} -// onFeatureSelect={onFeatureSelect} -// /> */} -// </Tabs> - -// {/* 접기 버튼 */} -// <button -// onClick={() => setVisible(false)} -// className="absolute top-1/2 right-[-3px] transform -translate-y-1/2 z-50 bg-white border border-l border-gray-300 rounded-l-none rounded-r px-1 py-2 shadow-sm" -// > -// <ChevronLeft size={16} /> -// </button> -// </div> - -// {/* 열기 버튼 */} -// {!visible && ( -// <button -// onClick={() => setVisible(true)} -// className="absolute top-1/2 left-[-3px] transform -translate-y-1/2 z-50 bg-white border border-r border-gray-300 rounded-r px-1 py-2 shadow-sm" -// > -// <ChevronRight size={16} /> -// </button> -// )} -// </> -// ); -// } - -// export default React.memo(GisTab); From 0c16b9d14df0f523bbdd6908536b40f9e88e6d56 Mon Sep 17 00:00:00 2001 From: leedj312 <dlehd467@gmail.com> Date: Thu, 31 Jul 2025 13:28:21 +0900 Subject: [PATCH 27/71] =?UTF-8?q?=EC=A4=91=EC=88=98=EB=8F=84=20=EC=A7=80?= =?UTF-8?q?=EB=8F=84=EC=A0=90=EA=B2=80=20=EC=97=91=EC=85=80=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EC=98=A4=EB=A5=98=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../prfmncReg/chkgdnReg/rswrtChkgdn/excel.tsx | 56 +++++++++++++------ 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/excel.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/excel.tsx index 262b1cb30..aa550dcd1 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/excel.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/excel.tsx @@ -1,5 +1,10 @@ import type { SelectDataType } from "@common/business_components/ui"; -import { useTypes, type Buildingtype } from "../rainChkgdn/excel"; +import { + chkMngType, + chkSpwtPositnType, + useTypes, + type Buildingtype, +} from "../rainChkgdn/excel"; import { TEST_UUID } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; import type { rswtChkZodType } from "."; import { @@ -171,14 +176,14 @@ export const excelToRswtGuideData = ( .filter((el: any) => el !== undefined); if (rswtChkgdData.length > 0) { - rswtExcelDataCheck(rswtChkgdData,issues) + rswtExcelDataCheck(rswtChkgdData, issues); } if (issues.length > 0) { return { data: [], issues }; } - return { data:rswtChkgdData, issues: [] }; + return { data: rswtChkgdData, issues: [] }; }; export const excelToRswtPolution = (excelData: any, issues: string[]) => { @@ -248,25 +253,25 @@ export const excelToRswtPolution = (excelData: any, issues: string[]) => { }) .filter((el: any) => el !== undefined); if (rswtChkgdData.length > 0) { - rswtExcelDataCheck(rswtChkgdData,issues) + rswtExcelDataCheck(rswtChkgdData, issues); } if (issues.length > 0) { return { data: [], issues }; } - return { data:rswtChkgdData, issues: [] }; + return { data: rswtChkgdData, issues: [] }; }; - -const rswtExcelDataCheck=(rswtChkgdData:rswtChkZodType[], issues:string[]) =>{ +const rswtExcelDataCheck = ( + rswtChkgdData: rswtChkZodType[], + issues: string[] +) => { rswtChkgdData.forEach((rswt: any) => { const defaultMessage = `*중수도 수질 [NO: ${rswt.bdstRuseFcltSn}, 점검일: ${rswt.chckYmd}, 이용용도: ${rswt.utztnUsgCd}] 항목에서 `; if (rswt.chckYmd && isInvalidDate(rswt.chckYmd)) { - issues.push( - defaultMessage + `점검일: 날짜 형식 아님 (${rswt.chckYmd})` - ); + issues.push(defaultMessage + `점검일: 날짜 형식 아님 (${rswt.chckYmd})`); } if (rswt.anlOperCst.length > 2 && isInvalidNumber(rswt.anlOperCst)) { issues.push( @@ -279,6 +284,25 @@ const rswtExcelDataCheck=(rswtChkgdData:rswtChkZodType[], issues:string[]) =>{ defaultMessage + ` 처리용량(m³): 숫자 형식 아님 (${rswt.prcsCpct})` ); } + if ( + rswt.oilqMeasMthd.length > 1 && + !chkMngType.includes(rswt.oilqMeasMthd) + ) { + issues.push( + defaultMessage + + `유량측정방법: 위탁 혹은 자체 만 허용) /입력값:${rswt.oilqMeasMthd}` + ); + } + if ( + rswt.spwtPositn.length > 1 && + !chkSpwtPositnType.includes(rswt.spwtPositn) + ) { + issues.push( + defaultMessage + + `측정위치: 유량계, 수위계, 펌프적산전력계, 기타 중 하나만 허용) /입력값:${rswt.spwtPositn}` + ); + } + if (rswt?.rswtrPollutions && rswt?.rswtrPollutions.length > 0) { rswt.rswtrPollutions.forEach((pol) => { if (isInvalidNumber(pol.grssClfgUswtr)) { @@ -301,8 +325,7 @@ const rswtExcelDataCheck=(rswtChkgdData:rswtChkZodType[], issues:string[]) =>{ } if (isInvalidNumber(pol.bod)) { issues.push( - defaultMessage + - `${pol.node}분기_BOD: 숫자 형식 아님 (${pol.bod})` + defaultMessage + `${pol.node}분기_BOD: 숫자 형식 아님 (${pol.bod})` ); } if (isInvalidNumber(pol.smll)) { @@ -319,8 +342,7 @@ const rswtExcelDataCheck=(rswtChkgdData:rswtChkZodType[], issues:string[]) =>{ } if (isInvalidNumber(pol.tph)) { issues.push( - defaultMessage + - `${pol.node}분기_T-P: 숫자 형식 아님 (${pol.tph})` + defaultMessage + `${pol.node}분기_T-P: 숫자 형식 아님 (${pol.tph})` ); } if (isInvalidNumber(pol.tn)) { @@ -358,7 +380,5 @@ const rswtExcelDataCheck=(rswtChkgdData:rswtChkZodType[], issues:string[]) =>{ } }); } - }) - - -} \ No newline at end of file + }); +}; From 5712bcedaac74eabc4c4d70e8abf189254a15499 Mon Sep 17 00:00:00 2001 From: Your Name <mind-003@mind-one.co.kr> Date: Thu, 31 Jul 2025 13:38:27 +0900 Subject: [PATCH 28/71] =?UTF-8?q?=EC=9B=90=EC=95=A4=EC=98=AC=20DB=EB=B0=94?= =?UTF-8?q?=EB=9D=BC=EB=B3=B4=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/fo/src/shared/config/env.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/fo/src/shared/config/env.ts b/apps/fo/src/shared/config/env.ts index 8baf1b0f0..cc516daf1 100644 --- a/apps/fo/src/shared/config/env.ts +++ b/apps/fo/src/shared/config/env.ts @@ -2,7 +2,8 @@ export const SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "FO"; export const PORT = process.env.NEXT_PUBLIC_PORT || "3000"; export const PROXY_HOST = process.env.NEXT_PUBLIC_PROXY_HOST || `http://localhost:${PORT}`; export const SERVER_API_WR_URL = process.env.NEXT_PUBLIC_WR_SERVER_API_URL ||'http://con.mind-one.co.kr:11080/api/v1'; -export const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL||' http://con.mind-one.co.kr:11082/api/v1'; +//export const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL||' http://con.mind-one.co.kr:11082/api/v1'; +export const SERVER_API_URL = process.env.NEXT_PUBLIC_CS_SERVER_API_URL||' http://con.mind-one.co.kr:11082/api/v1'; export const SERVER_API_CS_URL = process.env.NEXT_PUBLIC_CS_SERVER_API_URL||'http://dev.onasys.co.kr:8000'; export const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization"; export const ACCESS_TOKEN = process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token"; From 8a2bbfe2202f5766de75756ccce86b8b5e0b3b88 Mon Sep 17 00:00:00 2001 From: Your Name <mind-003@mind-one.co.kr> Date: Thu, 31 Jul 2025 13:52:52 +0900 Subject: [PATCH 29/71] =?UTF-8?q?=EC=9B=90=EC=95=A4=EC=98=AC=20API=20URL?= =?UTF-8?q?=20=EC=9E=AC=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/fo/.env.development | 3 ++- apps/fo/.env.local | 3 ++- apps/fo/.env.production | 3 ++- apps/fo/src/shared/config/env.ts | 3 +-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/fo/.env.development b/apps/fo/.env.development index 94488f0cd..de8976c69 100644 --- a/apps/fo/.env.development +++ b/apps/fo/.env.development @@ -1,7 +1,8 @@ NEXT_PUBLIC_SITE_ID = FO NEXT_PUBLIC_PORT = 11300 NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11300 -NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1 +#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 diff --git a/apps/fo/.env.local b/apps/fo/.env.local index 76fb868cd..ac8a22c06 100644 --- a/apps/fo/.env.local +++ b/apps/fo/.env.local @@ -1,7 +1,8 @@ NEXT_PUBLIC_SITE_ID = FO NEXT_PUBLIC_PORT = 3000 NEXT_PUBLIC_PROXY_HOST = http://localhost:3000 -NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1 +#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 diff --git a/apps/fo/.env.production b/apps/fo/.env.production index 16675eaec..5e11cef61 100644 --- a/apps/fo/.env.production +++ b/apps/fo/.env.production @@ -14,7 +14,8 @@ NEXT_PUBLIC_SITE_ID = FO NEXT_PUBLIC_PORT = 11300 NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11300 -NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1 +# 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 diff --git a/apps/fo/src/shared/config/env.ts b/apps/fo/src/shared/config/env.ts index cc516daf1..8baf1b0f0 100644 --- a/apps/fo/src/shared/config/env.ts +++ b/apps/fo/src/shared/config/env.ts @@ -2,8 +2,7 @@ export const SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "FO"; export const PORT = process.env.NEXT_PUBLIC_PORT || "3000"; export const PROXY_HOST = process.env.NEXT_PUBLIC_PROXY_HOST || `http://localhost:${PORT}`; export const SERVER_API_WR_URL = process.env.NEXT_PUBLIC_WR_SERVER_API_URL ||'http://con.mind-one.co.kr:11080/api/v1'; -//export const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL||' http://con.mind-one.co.kr:11082/api/v1'; -export const SERVER_API_URL = process.env.NEXT_PUBLIC_CS_SERVER_API_URL||' http://con.mind-one.co.kr:11082/api/v1'; +export const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL||' http://con.mind-one.co.kr:11082/api/v1'; export const SERVER_API_CS_URL = process.env.NEXT_PUBLIC_CS_SERVER_API_URL||'http://dev.onasys.co.kr:8000'; export const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization"; export const ACCESS_TOKEN = process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token"; From 4c66dacdb03187496e86b5f8d81ea89fe3308564 Mon Sep 17 00:00:00 2001 From: Your Name <mind-003@mind-one.co.kr> Date: Thu, 31 Jul 2025 14:31:59 +0900 Subject: [PATCH 30/71] =?UTF-8?q?=EC=9B=90=EB=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/fo/.env.development | 4 ++-- apps/fo/.env.local | 4 ++-- apps/fo/.env.production | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/fo/.env.development b/apps/fo/.env.development index de8976c69..44fe06736 100644 --- a/apps/fo/.env.development +++ b/apps/fo/.env.development @@ -1,8 +1,8 @@ NEXT_PUBLIC_SITE_ID = FO NEXT_PUBLIC_PORT = 11300 NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11300 -#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_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 diff --git a/apps/fo/.env.local b/apps/fo/.env.local index ac8a22c06..e125bed3d 100644 --- a/apps/fo/.env.local +++ b/apps/fo/.env.local @@ -1,8 +1,8 @@ NEXT_PUBLIC_SITE_ID = FO NEXT_PUBLIC_PORT = 3000 NEXT_PUBLIC_PROXY_HOST = http://localhost:3000 -#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_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 diff --git a/apps/fo/.env.production b/apps/fo/.env.production index 5e11cef61..52534bd8e 100644 --- a/apps/fo/.env.production +++ b/apps/fo/.env.production @@ -14,8 +14,8 @@ NEXT_PUBLIC_SITE_ID = FO NEXT_PUBLIC_PORT = 11300 NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11300 -# 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_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 From 7ee7a0fc69891b0e62589f9fb7fb0406f4c4ff0d Mon Sep 17 00:00:00 2001 From: JIeunhuh <entks301@naver.com> Date: Thu, 31 Jul 2025 14:38:14 +0900 Subject: [PATCH 31/71] =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=E3=85=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=B6=84=EB=A6=AC=EC=A4=91=20..?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wr/app/map/components/hasuFacility.tsx | 89 +++++ apps/wr/app/map/components/reuseFacility.tsx | 147 ++++++++ apps/wr/app/map/mainMap/page.tsx | 3 +- apps/wr/app/map/newWindowPopup.tsx | 356 +++---------------- apps/wr/app/map/util.ts | 58 +++ 5 files changed, 344 insertions(+), 309 deletions(-) create mode 100644 apps/wr/app/map/components/hasuFacility.tsx create mode 100644 apps/wr/app/map/components/reuseFacility.tsx create mode 100644 apps/wr/app/map/util.ts diff --git a/apps/wr/app/map/components/hasuFacility.tsx b/apps/wr/app/map/components/hasuFacility.tsx new file mode 100644 index 000000000..d93d4c30a --- /dev/null +++ b/apps/wr/app/map/components/hasuFacility.tsx @@ -0,0 +1,89 @@ +import { useWrStore } from "@/app/store"; +import type { pubHasuType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; +import { tableData } from "@/components/lib/func/commom"; +import { Title, UiTable } from "@common/business_components/ui"; +import { useEffect, useState } from "react"; +interface Props { + seletectedLayerNm: string; + viewMode: 'list' | 'detail' | 'nothing'; + onFeatureSelect: (feature: any) => void; + onClose: () => void; + isRuseReg: { data: any | undefined }; +} +const HasuFacility = ({ seletectedLayerNm, viewMode, onClose, onFeatureSelect, isRuseReg }: Props) => { + const { + code, + pbnst, + pubHasuInit, + } = useWrStore((state) => state); + const { pubFacHeadList, pbnstHasuRes, pbnstHasuResCount } = pbnst; + const [hasuTable, setHasuTable] = useState<pubHasuType[]>(); + const [hasuPage, setHasuPage] = useState<number>(1); + useEffect(() => { + if (code.sggCd.length && pbnstHasuRes.length > 0) { + setHasuTable(tableData(pbnstHasuRes, { code })); + } + }, [code, pbnstHasuRes]); + useEffect(() => { + if (seletectedLayerNm !== '하수처리시설') return; + if (!pbnst.isInit) { + pubHasuInit("1", "0"); + } + }, [pbnst.isInit, pubHasuInit]); + useEffect(() => { + if (seletectedLayerNm !== '하수처리시설') return; + pubHasuInit(String(hasuPage), "0"); + }, [hasuPage, pubHasuInit]); + + const hasuCellClick = (index: number | undefined) => { + if (index !== undefined && pbnstHasuRes !== undefined) { + onFeatureSelect(pbnstHasuRes[index]); + onClose(); + } + }; + return (viewMode == 'list' ? ( + <div style={{ overflowY: 'auto' }}> + <div className="flex justify-between items-center mb-4"> + <Title title="하수처리시설 현황" /> + </div> + {/* 높이 조정 */} + <UiTable + tableData={hasuTable || []} + headlist={pubFacHeadList} + pageSize={11} + total={pbnstHasuResCount || 0} + headName={""} + cellClick={hasuCellClick} + onPageChange={setHasuPage} + /> + </div> + ) : + ( + <div style={{ overflowX: 'hidden' }}> + <div className="flex justify-between items-center mb-4"> + <Title title="하수처리시설 상세정보" /> + </div> + {/* 높이 조정 */} + <UiTable + tableData={isRuseReg?.data || []} + headlist={pubFacHeadList.slice(0, 4)} + pageSize={1} + total={0} + headName={"위치정보"} + cellClick={() => { }} + onPageChange={() => { }} + /> + <UiTable + tableData={isRuseReg?.data || []} + headlist={pubFacHeadList.slice(4)} + pageSize={1} + total={0} + headName={"기본정보"} + cellClick={() => { }} + onPageChange={() => { }} + /> + </div> + ) + ) +} +export default HasuFacility; \ No newline at end of file diff --git a/apps/wr/app/map/components/reuseFacility.tsx b/apps/wr/app/map/components/reuseFacility.tsx new file mode 100644 index 000000000..dd18a657e --- /dev/null +++ b/apps/wr/app/map/components/reuseFacility.tsx @@ -0,0 +1,147 @@ +import { useWrStore } from "@/app/store"; +import type { customPbnst } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; +import type { WaterReuseFacility } from "@/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type"; +import { PbnstSttnModal } from "@/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal"; +import { Title, UITab, UiTable } from "@common/business_components/ui"; +import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; +import { useState, useEffect, useCallback } from "react"; +interface Props { + seletectedLayerNm: string; + viewMode: 'list' | 'detail' | 'nothing'; + setFcltPk: (pk: string) => void; + onFeatureSelect: (feature: any) => void; + onClose: () => void; + actTabData: { id: string; name: string; data: any } | undefined; + tabData: { id: string; name: string; data: any }[]; +} +const ResueFacility = ({ viewMode, seletectedLayerNm, tabData, actTabData, setFcltPk, onFeatureSelect, onClose}: Props) => { + const [activeTab, setActiveTab] = useState<string>(''); // 현재 활성화된 탭 + // 하수재이용시설 변수 + const { + pbnstSttn, + pbnstSttnSearch, + pbnSttnInit, + getFlcts + } = useWrStore((state) => state); + const { fclts, pbnstSttnResCount } = pbnstSttn + const [bdstTable, setBdstTable] = useState<WaterReuseFacility[]>() + // 재이용시설 cellClick 이벤트 (상세정보) + const pbnstCellClick = (index: number | undefined) => { + if ( + index !== undefined && + pbnstSttn.pbnstSttnRes && + pbnstSttn.pbnstSttnRes[index] + ) { + setFcltPk(pbnstSttn.pbnstSttnRes[index].pbnstRuseFcltId); + onFeatureSelect(pbnstSttn.pbnstSttnRes[index]); + onClose() + } + }; + + // 재이용 현황 초기값 + useEffect(() => { + console.log('재이용시설 초기값 호출', viewMode) + if (seletectedLayerNm !== '하수재이용시설') return; + if (!pbnstSttn.isInit) { + getFlcts() + } + }, [seletectedLayerNm, pbnstSttn.isInit]); + useEffect(() => { + if (seletectedLayerNm !== '하수재이용시설') return; + pbnSttnInit("1", "20", { + partValue: "A", + sidoValue: '0', + sigunValue: "0", + regionValue: "", + unitWaterShedValue: "", + middleRegionValue: "", + envPartValue: "", + sidoText: "", + sigunText: "" + }, String(new Date().getFullYear()), pbnstSttn.fclts); + }, [fclts, pbnSttnInit, pbnstSttn.fclts, seletectedLayerNm]) + useEffect(() => { + setBdstTable(pbnstSttn.pbnstSttnRes); + }, [seletectedLayerNm, pbnstSttn.pbnstSttnRes]); + + // 재이용시설 조회 + // 재이용시설 페이징처리 관련 함수 + const onPageChangeHandler = useCallback((page: number) => { + if (seletectedLayerNm !== '하수재이용시설') return; + pbnstSttnSearch( + false, + String(page), + String(20), + { + partValue: "A", + sidoValue: "0", + sigunValue: "0", + regionValue: "", + unitWaterShedValue: "", + middleRegionValue: "", + envPartValue: "", + sidoText: "", + sigunText: "", + }, + "", + String(new Date().getFullYear()), fclts + ); + }, [fclts, pbnstSttnSearch]); + // FIXME: 윤선화차장님 요구사항으로 엑셀 다운로드 삭제. + // const excelClick = useCallback(() => { + // // if (codeData) pbnstSttnSearch(1, "10000", codeData); + // excelDownload( + // [[]], + // "하수재이용시설 현황", + // "sample_pbnstSttn.xlsx", + // "A1:U2" + // ); + // }, [pbnstSttnSearch]); + return ( + viewMode == 'detail' ? + // 재이용시설 상세 + <> + <div className="flex justify-between items-center mb-4"> + <Title title="하수재이용시설 상세정보" /> + </div> + <div className="flex justify-between items-center mb-8"> + <UITab + defaultValue={activeTab} + tabList={tabData.map((tab) => ({ + value: tab.id, + label: tab.name, + onClick: () => setActiveTab(tab.id), + }))} + /> + </div> + <PbnstSttnModal + data={actTabData?.data[0] || ({} as customPbnst)} + titleVisible={false} + /> + </> : + <div style={{ overflowY: 'auto' }}> + <div className="flex justify-between items-center"> + <Title title="하수재이용시설 현황" /> + {/* <div className="flex gap-2 mt-8"> + <Button className={btnClass} color="primary" onClick={excelClick}> + 엑셀다운로드 + </Button> + </div> */} + </div> + <UiTable + headName={[]} + tableData={bdstTable || []} + headlist={pbnstSttn.pbnstSttHeadList} + pageSize={20} + total={pbnstSttnResCount || 0} + cellClick={(index: number | undefined) => { + pbnstCellClick(index); + }} + onPageChange={onPageChangeHandler} + > + <HeadMakeColSpan upHeadList={pbnstSttn.pbnstSttNodeList} /> + </UiTable> + </div> + ) +} +export default ResueFacility; \ No newline at end of file diff --git a/apps/wr/app/map/mainMap/page.tsx b/apps/wr/app/map/mainMap/page.tsx index c9d58c422..56c9e823d 100644 --- a/apps/wr/app/map/mainMap/page.tsx +++ b/apps/wr/app/map/mainMap/page.tsx @@ -135,13 +135,14 @@ const Map = () => { const renderDetailView = useCallback( (props: any) => ( <NewWindowPopup + key={featureData?.fid || featureData?.id || featureData?.ftrIdn} onClose={handlePopupClose} checkedLayers={checkedLayers} checkedLayersData={checkedLayersData} {...props} /> ), - [handlePopupClose, checkedLayers, checkedLayersData] // 필요 시 의존성 넣기 + [checkedLayers, checkedLayersData] // 필요 시 의존성 넣기 ); return ( <div className="bg-container"> diff --git a/apps/wr/app/map/newWindowPopup.tsx b/apps/wr/app/map/newWindowPopup.tsx index ea13d85de..d8e6b6338 100644 --- a/apps/wr/app/map/newWindowPopup.tsx +++ b/apps/wr/app/map/newWindowPopup.tsx @@ -2,20 +2,20 @@ import NewWindowPortal from '@/app/map/newWindowPortal'; import { useWrStore } from "@/app/store"; import type { customHtdflBdst } from "@/app/store/ruseFcltMng/fcltReg/htdflReg"; -import type { customPbnst, pubHasuType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; +import type { pubHasuType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; import type { customRainBdst } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; import type { customRswtBdst } from "@/app/store/ruseFcltMng/fcltReg/rswtReg"; -import type { WaterReuseFacility } from "@/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type"; -import { fetchType, getFetch, postFectch } from "@/components/lib/api"; import { formatPhoneNumber, tableData } from "@/components/lib/func/commom"; -import { PbnstSttnModal } from "@/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal"; import SttnDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal"; -import { Title, UITab, UiTable } from '@common/business_components'; +import { Title, UiTable } from '@common/business_components'; import { useGisContext } from "@common/business_components/ui/map/Gis"; import type { DemandCompType } from '@common/business_components/ui/sdmInsertForm/form'; import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; import moment from 'moment'; import React, { useCallback, useEffect, useRef, useState } from 'react'; +import ResueFacility from './components/reuseFacility'; +import { detainSttn, fetchHasuDetail, fetchpbnstDetail } from './util'; +import HasuFacility from './components/hasuFacility'; interface NewWindowPopupProps { featureData: any; @@ -44,15 +44,7 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ }, [onClose]); // 하수재이용시설 변수 - const { - code, - pbnstSttn, - pbnstSttnSearch, - pbnSttnInit, - getFlcts - } = useWrStore((state) => state); - const { fclts, pbnstSttnResCount } = pbnstSttn - const [bdstTable, setBdstTable] = useState<WaterReuseFacility[]>() + const {code} = useWrStore((state) => state); // 빗물재이용시설 변수 const { rainSttn, rainSttnSearch, rainSttnInit } = useWrStore( @@ -82,18 +74,11 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ const [htdflPage, setHtdflPage] = useState(1) const [htdflList, setHtdflList] = useState<customHtdflBdst[]>(htdflSttnRes || []); - //하수처리시설 변수 - const { - pbnst, - pubHasuInit, - } = useWrStore((state) => state); - const { pubFacHeadList, pbnstHasuRes, pbnstHasuResCount } = pbnst; - const [hasuList, setHasuList] = useState<pubHasuType[]>(pbnstHasuRes || []); + // 하수처리시설 변수 + const [hasuList, setHasuList] = useState<pubHasuType[]>([]); const [isRuseReg, setIsRuseReg] = useState<{ data: any | undefined; }>({ data: undefined }); - const [hasuTable, setHasuTable] = useState<pubHasuType[]>(); - const [hasuPage, setHasuPage] = useState<number>(1); // 수요처관리 변수 const { @@ -111,7 +96,7 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ const [mngList, setMngList] = useState<DemandCompType[]>(coSttnRes || []); ///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 /////////////////////////////////////////////// const getSelectedLayers = () => { - console.trace('호출확인',1) + console.trace('호출확인', 1) let result = null; const layers = gisMap ? gisMap.getLayers().getArray().filter((l: any) => l.getVisible()) : []; // gisMap이 null인지 확인 후 레이어 가져오기 let featureLayer: { get: (key: string) => any } = { get: () => null }; @@ -125,50 +110,6 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ result = featureLayer.get('lyrNm'); return result; }; - const fetchpbnstDetail = async (featureCd: string) => { - try { - const response = await getFetch("wr", `/reuseFc/pubFc/${featureCd}`); - // 중복 데이터 추가 방지 - setTabDataFunc(response, featureCd); - } catch (error) { - console.error("Error fetching data:", error); - } - }; - const fetchHasuDetail = async (featureCd: string, data: string) => { - try { - const rainRes = await postFectch(`/prcsFclt/ruseInfo/1/20/A/0/0/1/SW/${featureCd}`, [data], fetchType.COMMON) - setHasuList( - rainRes.data.map((item: any) => { - return { - ...item, - fctCpc: Number(item.fctCpc) || 0, - ruse: item.ruse === false ? "N" : "Y", - }; - }) - ) - } catch (error) { - console.error("Error fetching data:", error); - } - }; - const detainSttn = async (featureCd: string, layerNm: string) => { - if (layerNm == '빗물이용시설') { //빗물이용시설 - const res = await getFetch(fetchType.WR, `/reuseFc/rainyInfoList/1/20/0/${featureCd}`) - setRainList(res) - } - else if (layerNm == '중수도시설') { //중수도 시설 - const res = await getFetch(fetchType.WR, `/reuseFc/waterreuseFc/1/20/0/${featureCd}`) - setRswtrList(res) - } - else if (layerNm == '수요처') { //수요처 - const res = await getFetch(fetchType.WR, `/reuseFc/request/1/20/0/${featureCd}`) - setMngList(res) - } - else { - const res = await getFetch(fetchType.WR, `/drain/facilityList/1/20/0/${featureCd}`) - setHtdflList(res) - setHtdflDetailModal({ isOpen: true, data: htdflSttnRes[0] }); - } - } // 상세 목록 데이터 상태 처리 useEffect(() => { if (hasuList.length > 0) setIsRuseReg({ data: [hasuList[0]] }); @@ -186,12 +127,11 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ if (mngList.length > 0) setIsDmdModal({ isMoal: true, data: [mngList[0]] }); }, [mngList]) const lastCalledIdRef = useRef<string | null>(null); - const [layerId, setLayerId] = useState<string|null>(); -useEffect(() => { - const layers = getSelectedLayers(); - setLayerId(layers); -}, []); // 최초 렌더링 시 호출 - + const [layerId, setLayerId] = useState<string | null>(); + useEffect(() => { + const layers = getSelectedLayers(); + setLayerId(layers); + }, []); // 최초 렌더링 시 호출 useEffect(() => { if (!featureData || typeof featureData.getProperties !== "function" || featureData.getProperties() == '') return; const selectedProps = featureData.getProperties(); @@ -205,106 +145,28 @@ useEffect(() => { if (!layerId || typeof layerId !== 'string') return; const featureCd = layerId === '하수재이용시설' ? selectedProps?.swtrmt_fclt_cd : - ['빗물이용시설', '중수도시설', '온배수시설', '수요처'].includes(layerId) ? selectedProps?.bdst_nm : - layerId === '하수처리시설' ? selectedProps?.swtrmt_fclt_nm : - null; + ['빗물이용시설', '중수도시설', '온배수시설', '수요처'].includes(layerId) ? selectedProps?.bdst_nm : + layerId === '하수처리시설' ? selectedProps?.swtrmt_fclt_nm : + null; if (!featureCd || featureCd === fcltPk) return; - if (layerId === '하수재이용시설') { - fetchpbnstDetail(featureCd); - } else if (layerId === '하수처리시설') { - fetchHasuDetail(featureCd, selectedProps?.swtrmt_fclt_cd); - } else { - detainSttn(featureCd, layerId); - } setFcltPk(featureCd); - setViewMode("detail"); setActiveTab(featureCd); + setViewMode("detail"); + if (layerId === '하수재이용시설') { + fetchpbnstDetail(featureCd, setTabData); + } else if (layerId === '하수처리시설') { + fetchHasuDetail(featureCd, selectedProps?.swtrmt_fclt_cd).then((data) => { setHasuList(data) }) + } else { + detainSttn(featureCd, layerId).then((res) => { + if (layerId === '빗물이용시설') setRainList(res); + else if (layerId === '중수도시설') setRswtrList(res); + else if (layerId === '온배수시설'){ setHtdflList(res); setHtdflDetailModal({ isOpen: true, data: htdflSttnRes[0] });} + else if (layerId === '수요처') setMngList(res); + }); + } }, [featureData]); ///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 /////////////////////////////////////////////// - ///////////////////////////////////////////// 하수재이용시설 ////////////////////////////////////////////////// - // 재이용시설 cellClick 이벤트 (상세정보) - const pbnstCellClick = (index: number | undefined) => { - if ( - index !== undefined && - pbnstSttn.pbnstSttnRes && - pbnstSttn.pbnstSttnRes[index] - ) { - setFcltPk(pbnstSttn.pbnstSttnRes[index].pbnstRuseFcltId); - onFeatureSelect(pbnstSttn.pbnstSttnRes[index]); - onClose() - } - }; - - // 재이용 현황 초기값 - useEffect(() => { - if (!pbnstSttn.isInit && seletectedLayerNm === '하수재이용시설') { - getFlcts() - } - }, [seletectedLayerNm, pbnstSttn.isInit, getFlcts]); - useEffect(() => { - if(seletectedLayerNm !== '하수재이용시설') return; - console.trace('check', 1) - pbnSttnInit("1", "20", { - partValue: "A", - sidoValue: '0', - sigunValue: "0", - regionValue: "", - unitWaterShedValue: "", - middleRegionValue: "", - envPartValue: "", - sidoText: "", - sigunText: "" - }, String(new Date().getFullYear()), pbnstSttn.fclts); - }, [fclts, pbnSttnInit, pbnstSttn.fclts, seletectedLayerNm]) - useEffect(() => { - setBdstTable(pbnstSttn.pbnstSttnRes); - }, [seletectedLayerNm, pbnstSttn.pbnstSttnRes]); - - // 재이용시설 상세정보 - const setTabDataFunc = (res: any, pk: string) => { - setTabData((prev) => { - if (prev.some((tab) => tab.id === pk)) { - return prev; // 이미 존재하는 탭이면 추가하지 않음 - } - return [...prev, { id: pk, name: `재이용시설 ${prev.length + 1}`, data: res }]; - }); - } - // 재이용시설 조회 - // 재이용시설 페이징처리 관련 함수 - const onPageChangeHandler = useCallback((page: number) => { - if(seletectedLayerNm!== '하수재이용시설') return; - pbnstSttnSearch( - false, - String(page), - String(20), - { - partValue: "A", - sidoValue: "0", - sigunValue: "0", - regionValue: "", - unitWaterShedValue: "", - middleRegionValue: "", - envPartValue: "", - sidoText: "", - sigunText: "", - }, - "", - String(new Date().getFullYear()), fclts - ); - }, [fclts, pbnstSttnSearch]); - // FIXME: 윤선화차장님 요구사항으로 엑셀 다운로드 삭제. - // const excelClick = useCallback(() => { - // // if (codeData) pbnstSttnSearch(1, "10000", codeData); - // excelDownload( - // [[]], - // "하수재이용시설 현황", - // "sample_pbnstSttn.xlsx", - // "A1:U2" - // ); - // }, [pbnstSttnSearch]); - ///////////////////////////////////////////// 하수재이용시설 ////////////////////////////////////////////////// - ///////////////////////////////////////////// 빗물 재이용 ////////////////////////////////////////////////// // 빗물재이용시설 cellClick 이벤트 (상세정보) const rainCellClick = (index: number | undefined, headId: string) => { @@ -321,7 +183,7 @@ useEffect(() => { useEffect(() => { - if(seletectedLayerNm !== '빗물이용시설') return; + if (seletectedLayerNm !== '빗물이용시설') return; if (!rainSttn.isInit) { rainSttnInit(false, 1, "20", "0", '', code.cityCd, code.sggCd); } @@ -359,7 +221,7 @@ useEffect(() => { }, [code, rainSttnRes, rainSttn.rainSttHeadList]); useEffect(() => { - if(seletectedLayerNm !== '빗물이용시설') return; + if (seletectedLayerNm !== '빗물이용시설') return; rainSttnSearch(false, rainPage, "20", '', '', code.cityCd, code.sggCd); }, [code.cityCd, code.sggCd, rainPage, rainSttnSearch]); ///////////////////////////////////////////// 빗물 재이용 ////////////////////////////////////////////////// @@ -375,14 +237,14 @@ useEffect(() => { } } useEffect(() => { - if(seletectedLayerNm !== '중수도시설') return; + if (seletectedLayerNm !== '중수도시설') return; if (!rswtSttn.isInit) { rswtSttnInit(false, 1, "20", "0", '', code.cityCd, code.sggCd); } }, [code.cityCd, code.sggCd, rswtSttn.isInit, rswtSttnInit]); useEffect(() => { - if(seletectedLayerNm !== '중수도시설') return; + if (seletectedLayerNm !== '중수도시설') return; const data = tableData(rswtSttnRes, { code }); const chData = data .map((item: customRswtBdst) => { @@ -416,7 +278,7 @@ useEffect(() => { // }, [rswtSttn.excelList]) useEffect(() => { - if(seletectedLayerNm !== '중수도시설') return; + if (seletectedLayerNm !== '중수도시설') return; rswtSttnSearch(false, rswtrPage, "20", '', '', code.cityCd, code.sggCd); }, [code.cityCd, code.sggCd, rswtSttnSearch, rswtrPage]); ///////////////////////////////////////////// 중수도 시설 ////////////////////////////////////////////////// @@ -430,14 +292,14 @@ useEffect(() => { } }; useEffect(() => { - if(seletectedLayerNm !== '온배수시설') return; + if (seletectedLayerNm !== '온배수시설') return; if (!htdflSttn.isInit) { initHtdfl(false, 1, "20", "0", '', code.cityCd, code.sggCd); } }, [code.cityCd, code.sggCd, htdflSttn.isInit, initHtdfl]); useEffect(() => { - if(seletectedLayerNm !== '온배수시설') return; + if (seletectedLayerNm !== '온배수시설') return; const data = tableData(htdflSttnRes, { code }); const chData = data .map((item: any) => ({ @@ -451,38 +313,14 @@ useEffect(() => { }, [code, htdflSttnRes, htdflSttn.htdflSttHeadList]); useEffect(() => { - if(seletectedLayerNm !== '온배수시설') return; + if (seletectedLayerNm !== '온배수시설') return; htdflSttnSearch(true, htdflPage, "20", '', '', code.cityCd, code.sggCd); }, [code.cityCd, code.sggCd, htdflPage, htdflSttnSearch]); ///////////////////////////////////////////// 온배수 시설 ////////////////////////////////////////////////// - ///////////////////////////////////////////// 하수처리 시설 ////////////////////////////////////////////////// - useEffect(() => { - if (code.sggCd.length && pbnstHasuRes.length > 0) { - setHasuTable(tableData(pbnstHasuRes, { code })); - } - }, [code, pbnstHasuRes]); - useEffect(() => { - if(seletectedLayerNm !== '하수처리시설') return; - if (!pbnst.isInit) { - pubHasuInit("1", "0"); - } - }, [pbnst.isInit, pubHasuInit]); - useEffect(() => { - pubHasuInit(String(hasuPage), "0"); - }, [hasuPage, pubHasuInit]); - - const hasuCellClick = (index: number | undefined) => { - if (index !== undefined && pbnstHasuRes !== undefined) { - onFeatureSelect(pbnstHasuRes[index]); - onClose(); - } - }; - ///////////////////////////////////////////// 하수처리 시설 ////////////////////////////////////////////////// - ///////////////////////////////////////////// 수요처 관리 ////////////////////////////////////////////////// useEffect(() => { - if(seletectedLayerNm !== '수요처') return; + if (seletectedLayerNm !== '수요처') return; if (!coSttn.isInit) { coSttnInit("1", "20", "0"); } @@ -494,7 +332,7 @@ useEffect(() => { } }; useEffect(() => { - if(seletectedLayerNm !== '수요처') return; + if (seletectedLayerNm !== '수요처') return; if (coSttnRes) { if (coSttnRes?.length > 0 && code.cityCd.length > 0) { const data = tableData(coSttnRes, { code }); @@ -510,7 +348,7 @@ useEffect(() => { } }, [coSttnRes, code]); useEffect(() => { - if(seletectedLayerNm !== '수요처') return; + if (seletectedLayerNm !== '수요처') return; coSttnSearch(String(mngPage), "20", String(0)); }, [coSttnSearch, mngPage]); ///////////////////////////////////////////// 수요처 관리 ////////////////////////////////////////////////// @@ -521,10 +359,6 @@ useEffect(() => { if (layerId === null || layerId === undefined) { setLayerId(seletectedLayerNm || ''); } - //하수처리시설 - const isPrcsFclt = layerId == '하수처리시설' - // 재이용시설 - const isPbnst = layerId == '하수재이용시설'; // 빗물이용시설 const isRain = layerId == '빗물이용시설'; // 중수도시설 @@ -533,108 +367,14 @@ useEffect(() => { const isHtdfl = layerId == '온배수시설'; // 수요처관리 const isReuseMng = layerId == '수요처'; - if (isPrcsFclt) { - if (viewMode == 'list') { - return ( - <div style={{ overflowY: 'auto' }}> - <div className="flex justify-between items-center mb-4"> - <Title title="하수처리시설 현황" /> - </div> - {/* 높이 조정 */} - <UiTable - tableData={hasuTable || []} - headlist={pubFacHeadList} - pageSize={11} - total={pbnstHasuResCount || 0} - headName={""} - cellClick={hasuCellClick} - onPageChange={setHasuPage} - /> - </div> - ) - } else { - return ( - <div style={{ overflowX: 'hidden' }}> - <div className="flex justify-between items-center mb-4"> - <Title title="하수처리시설 상세정보" /> - </div> - {/* 높이 조정 */} - <UiTable - tableData={isRuseReg?.data || []} - headlist={pubFacHeadList.slice(0, 4)} - pageSize={1} - total={0} - headName={"위치정보"} - cellClick={() => { }} - onPageChange={() => { }} - /> - <UiTable - tableData={isRuseReg?.data || []} - headlist={pubFacHeadList.slice(4)} - pageSize={1} - total={0} - headName={"기본정보"} - cellClick={() => { }} - onPageChange={() => { }} - /> - </div> - ) - } + switch (layerId) { + case '하수재이용시설': + return (<ResueFacility viewMode={viewMode} seletectedLayerNm={layerId} tabData={tabData} actTabData={actTabData} setFcltPk={setFcltPk} onFeatureSelect={onFeatureSelect} onClose={onClose} />) + case '하수처리시설': + return(<HasuFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} isRuseReg={isRuseReg}/>) + default: + break; } - if (isPbnst) { - if (viewMode == 'detail') { - // 재이용시설 상세 - return ( - <> - <div className="flex justify-between items-center mb-4"> - <Title title="하수재이용시설 상세정보" /> - </div> - <div className="flex justify-between items-center mb-8"> - <UITab - defaultValue={activeTab} - tabList={tabData.map((tab) => ({ - value: tab.id, - label: tab.name, - onClick: () => setActiveTab(tab.id), - }))} - /> - </div> - <PbnstSttnModal - data={actTabData?.data[0] || ({} as customPbnst)} - titleVisible={false} - /> - </> - ); - } else { - // 재이용시설 목록 - return ( - <div style={{ overflowY: 'auto' }}> - <div className="flex justify-between items-center"> - <Title title="하수재이용시설 현황" /> - {/* <div className="flex gap-2 mt-8"> - <Button className={btnClass} color="primary" onClick={excelClick}> - 엑셀다운로드 - </Button> - </div> */} - </div> - <UiTable - headName={[]} - tableData={bdstTable || []} - headlist={pbnstSttn.pbnstSttHeadList} - pageSize={20} - total={pbnstSttnResCount || 0} - cellClick={(index: number | undefined) => { - pbnstCellClick(index); - }} - onPageChange={onPageChangeHandler} - > - <HeadMakeColSpan upHeadList={pbnstSttn.pbnstSttNodeList} /> - </UiTable> - </div> - ); - } - } - if (isRain) { if (viewMode == 'detail') { // 빗물이용시설 상세 diff --git a/apps/wr/app/map/util.ts b/apps/wr/app/map/util.ts new file mode 100644 index 000000000..e3428b300 --- /dev/null +++ b/apps/wr/app/map/util.ts @@ -0,0 +1,58 @@ +import { getFetch, postFectch, fetchType } from "@/components/lib/api"; + +const fetchpbnstDetail = async (featureCd: string, setTabData: any) => { + try { + const response = await getFetch("wr", `/reuseFc/pubFc/${featureCd}`); + // 중복 데이터 추가 방지 + setTabDataFunc(response, featureCd, setTabData); + } catch (error) { + console.error("Error fetching data:", error); + } +}; +const fetchHasuDetail = async (featureCd: string, data: string) => { + try { + const rainRes = await postFectch(`/prcsFclt/ruseInfo/1/20/A/0/0/1/SW/${featureCd}`, [data], fetchType.COMMON) + // setHasuList + const filteredData = rainRes.data.map((item: any) => { + return { + ...item, + fctCpc: Number(item.fctCpc) || 0, + ruse: item.ruse === false ? "N" : "Y", + }; + }) + return filteredData + } catch (error) { + console.error("Error fetching data:", error); + } +}; +// 재이용시설 상세정보 +const setTabDataFunc = (res: any, pk: string, setTabData: any) => { + setTabData((prev) => { + if (prev.some((tab) => tab.id === pk)) { + return prev; // 이미 존재하는 탭이면 추가하지 않음 + } + return [...prev, { id: pk, name: `재이용시설 ${prev.length + 1}`, data: res }]; + }); +} + +const detainSttn = async (featureCd: string, layerNm: string) => { + if (layerNm == '빗물이용시설') { //빗물이용시설 + const res = await getFetch(fetchType.WR, `/reuseFc/rainyInfoList/1/20/0/${featureCd}`) + return res + } + else if (layerNm == '중수도시설') { //중수도 시설 + const res = await getFetch(fetchType.WR, `/reuseFc/waterreuseFc/1/20/0/${featureCd}`) + return res + } + else if (layerNm == '수요처') { //수요처 + const res = await getFetch(fetchType.WR, `/reuseFc/request/1/20/0/${featureCd}`) + return res + } + else { + const res = await getFetch(fetchType.WR, `/drain/facilityList/1/20/0/${featureCd}`) + return res + // setHtdflDetailModal({ isOpen: true, data: htdflSttnRes[0] }); + } +} + +export { fetchHasuDetail, fetchpbnstDetail, detainSttn }; \ No newline at end of file From 33ad044ed34fe78bfe832c09c1128385a3fc285e Mon Sep 17 00:00:00 2001 From: jyha00 <jyha@mind-one.co.kr> Date: Thu, 31 Jul 2025 15:01:36 +0900 Subject: [PATCH 32/71] =?UTF-8?q?=EB=8C=80=EB=AF=BC=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/(base-layout)/pages/wrAbout/page.tsx | 4 ++-- apps/dm/app/layout.tsx | 3 +++ apps/dm/public/img/wrAbout/img_02_1.png | Bin 72043 -> 35450 bytes 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx b/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx index 04dfa58df..da3324ffc 100644 --- a/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx +++ b/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx @@ -131,7 +131,7 @@ export default function WrAboutPage() { 물재이용 필요성 </h2> <section aria-labelledby="card2-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] "> - <h1 id="card2-desc" className="font-NotoSansKR text-[#ED5534] text-5xl font-semibold mb-8">물 재이용, 왜 필요한가요?</h1> + <h1 id="card2-desc" className="font-NotoSansKR text-[#D63F1F] text-5xl font-semibold mb-8">물 재이용, 왜 필요한가요?</h1> <p className="font-NotoSansKR text-2xl font-normal mb-10 leading-relaxed"> 빗물, 중수, 하수처리수는 지속적으로 이용 가능한 순환자원입니다.<br /> 물 재이용을 통한 효율적인 수자원 활용은 물 스트레스국가인 우리나라에서<br />가뭄대응능력 향상 및 하천수질보전을 위해 반드시 필요한 일입니다. @@ -237,7 +237,7 @@ export default function WrAboutPage() { 하수 재이용 </h2> <section aria-labelledby="card5-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] "> - <h1 id="card5-desc" className="font-NotoSansKR leading-snug text-[#0099A4] text-5xl font-bold mb-6">아직 할 일이 많은 하수처리수,<br />풍족하게 재이용할 수 있어요!</h1> + <h1 id="card5-desc" className="font-NotoSansKR leading-snug text-[#00818A] text-5xl font-bold mb-6">아직 할 일이 많은 하수처리수,<br />풍족하게 재이용할 수 있어요!</h1> <p className="font-NotoSansKR text-2xl mb-10 leading-relaxed">사용하고 버려져 공공하수처리장으로 모여든 물은<br />용도에 맞는 처리 후 재이용 할 수 있습니다.</p> <Image src="/img/wrAbout/img_05_1.png" diff --git a/apps/dm/app/layout.tsx b/apps/dm/app/layout.tsx index af4dcaf1d..152a069f2 100644 --- a/apps/dm/app/layout.tsx +++ b/apps/dm/app/layout.tsx @@ -8,6 +8,9 @@ export default function RootLayout({ }>) { return ( <html lang="ko"> + <head> + <title>물재이용 대민 + {children} diff --git a/apps/dm/public/img/wrAbout/img_02_1.png b/apps/dm/public/img/wrAbout/img_02_1.png index 3d61c6e5db7ef6ef3945d06b9f009328a66d057d..aa1c8ee5ad583defcd4465034474b9cfb4d8760a 100644 GIT binary patch literal 35450 zcmbSygYKp#Y=7xJaMEggHR!;(sv)PsyHI7w0Zu-VZlL^z>%h;>M%4r-vn3l zhX#84kS6id#q>#k3~g3JJ#h9>{ZR4_LWKfB!DR*PiTy2@v_&U?LzRJ`eC%-Ras9dN zCH~(vsQZh}0>PI8Jm_I{0HynEs+cS3$@SOrU%Ys4dj~2q&03)&Xx_S*)I~RYcjE1@ z=;IH^4OI61REbL*t%cFFLSo0aJr2YBMFk!rh`Il&UCne?1+(1t>ezRpI&pwk{C1|M z04}0KfM<6M+n4~3o#`v>f-YqkrRZbxnswsYJxK}@7Lw{5U*18;qGrA2)*~Sf=C+^q z%i)&aCQ~B4Bi-}+{F0Ywt58wP*t%OJB=8>$~o+0RfprNXZ!xook z6M00-@BLPNv-d9-NPqvG3>j_z+_Mu?M?A&~{3a$>biL1yj1V+L`jRaSKa$dS&Fw37 zF-2@!j~(Uy*AgHPiy~51vX&A;DtzF#ns|Cj@bx zryG+7`z<|dfeJ7=T$O3_U1TR?DlgT

^=pr-o{^Z*Xs##f6V^IQ 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 Date: Thu, 31 Jul 2025 15:48:38 +0900 Subject: [PATCH 33/71] =?UTF-8?q?feat,=20remove:=20-=20=EA=B4=80=20?= =?UTF-8?q?=EC=9A=A9=EB=8F=84,=20=EA=B4=80=20=EC=9E=AC=EC=A7=88=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=20=EC=BD=94=EB=93=9C=20Store=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20-=20I/I=20=EB=B6=84=EC=84=9D=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=8B=9C=EB=8F=84,=20=EC=8B=9C=EA=B5=B0=EA=B5=AC?= =?UTF-8?q?=20=EA=B3=B5=ED=86=B5=20=EC=BD=94=EB=93=9C=20=EC=A0=81=EC=9A=A9?= =?UTF-8?q?=20-=20I/I=20=EB=B6=84=EC=84=9D=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=20=ED=99=98=EA=B2=BD=EC=B2=AD=20=EC=82=AD=EC=A0=9C=20-=20commo?= =?UTF-8?q?nStore.ts=20=EC=82=AD=EC=A0=9C=20-=20Store=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=ED=98=B8=EC=B6=9C=20=EB=A1=9C=EC=A7=81=EC=9D=84=20?= =?UTF-8?q?layout.tsx=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/(map-layout)/layout.tsx | 13 ++ apps/sn/app/(map-layout)/map/commonStore.ts | 25 ---- .../components/popupComponent/MsSwagePpl.tsx | 18 +-- apps/sn/app/(map-layout)/map/page.tsx | 12 +- .../map/searchModal/operationTab.tsx | 37 ++--- .../map/searchModal/searchModal.tsx | 15 +- .../app/(with-base-layout)/ii/BaseLayout.tsx | 138 +++++++----------- .../(with-base-layout)/ii/irsetting/page.tsx | 47 +++--- .../app/(with-base-layout)/ii/rdii/page.tsx | 46 +++--- .../(with-base-layout)/ii/rdiiResult/page.tsx | 38 ++--- .../app/(with-base-layout)/ii/result/page.tsx | 42 ++---- apps/sn/app/(with-base-layout)/layout.tsx | 11 ++ .../selectionValidation.tsx | 32 +--- .../components/ui/SidoSigunguSelectBox.tsx | 4 - apps/sn/app/store/index.ts | 64 ++++++++ apps/sn/app/store/useStore.ts | 44 +++--- 16 files changed, 245 insertions(+), 341 deletions(-) delete mode 100644 apps/sn/app/(map-layout)/map/commonStore.ts create mode 100644 apps/sn/app/store/index.ts diff --git a/apps/sn/app/(map-layout)/layout.tsx b/apps/sn/app/(map-layout)/layout.tsx index e245cfdf1..4337ee048 100644 --- a/apps/sn/app/(map-layout)/layout.tsx +++ b/apps/sn/app/(map-layout)/layout.tsx @@ -1,10 +1,12 @@ "use client"; +import React, { useEffect } from 'react'; import { usePathname, useRouter } from "next/navigation"; import { useQueryClient } from "@tanstack/react-query"; import { BaseLayout } from "@common/components"; import type { MenuItem } from "@/entities/menu"; import { useMenuStore } from "@/shared/lib"; +import useStore from '../store/useStore'; export default function MapLayout({ children, @@ -17,6 +19,17 @@ export default function MapLayout({ const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo); + const { fetchCtpvData, fetchSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } = useStore(); + + + // 시도, 시군구 호출 + useEffect(() => { + fetchCtpvData(); + fetchSggData(); + fetchBIZ_SE_CD(); + fetchBIZ_TY_CD(); + }, []); + const handleMenuClick = (item: MenuItem) => { setCurrentMenuInfo(item); router.push(item.urlPath); diff --git a/apps/sn/app/(map-layout)/map/commonStore.ts b/apps/sn/app/(map-layout)/map/commonStore.ts deleted file mode 100644 index ff903e1e7..000000000 --- a/apps/sn/app/(map-layout)/map/commonStore.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { create } from 'zustand'; - -export type SelectDataType = { - text: string; - val: string; - sido?: string; // 시도 코드 (선택 사항) -} - -interface CityCdStore { - cityCd: string; - setCityCd: (cityCd: string) => void; - sidos: SelectDataType[]; - setSidos: (sidos: SelectDataType[]) => void; - siguns : SelectDataType[]; - setSiguns: (siguns: SelectDataType[]) => void; -} - -export const useCityCdStore = create((set) => ({ - cityCd: '', - setCityCd: (cityCd: string) => set({ cityCd }), - sidos: [], - setSidos: (sidos: SelectDataType[]) => set({ sidos }), - siguns: [], - setSiguns: (siguns: SelectDataType[]) => set({ siguns }), -})); 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 08443c73f..ad067383a 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/MsSwagePpl.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/MsSwagePpl.tsx @@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react' import InputSection from '../InputSection'; import { handleInputChange } from '../common'; import { getData } from "@/components/lib/api"; -// import { useBizByFcltStore } from "../../../../store/StoreProvider"; +import useStore from '../../../../store/useStore'; // 하수관로 const inputColumn = [ @@ -30,21 +30,7 @@ const MsSwagePpl = ({ featureData, onSaveClick }: { featureData: any, onSaveClic inspection: { data: inspectionData, setData: setInspectionData }, }; - - // const BIZ_SE_CD = useBizByFcltStore(state => state.BIZ_SE_CD); - const BIZ_SE_CD = [ - { lwpoCd: "100", lwpoCdNm: "관로기술진단" }, - { lwpoCd: "200", lwpoCdNm: "개대체" }, - { lwpoCd: "201", lwpoCdNm: "유지보수" }, - { lwpoCd: "203", lwpoCdNm: "청소, 준설" }, - { lwpoCd: "204", lwpoCdNm: "단순보수" }, - ]; - // const BIZ_TY_CD = useBizByFcltStore(state => state.BIZ_TY_CD); - const BIZ_TY_CD = [ - { lwpoCd: "SV", lwpoCdNm: "용역" }, - { lwpoCd: "SF", lwpoCdNm: "자체" }, - { lwpoCd: "SC", lwpoCdNm: "공사" }, - ]; + const { BIZ_SE_CD, BIZ_TY_CD } = useStore(); // 마운트 및 업데이트 시 API 호출 diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index 15c6586eb..77b89cfd0 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -6,7 +6,6 @@ import { useCallback, useEffect, useRef, useState } from "react"; import GisApp from "@common/business_components/ui/map/lib/gis.core"; import SearchModal from "./searchModal/searchModal"; import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup"; -import useStore from "../../store/useStore"; // 타입 정의 추가 export type LyrStngProps = { @@ -39,13 +38,11 @@ const Map = () => { const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태 const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터 - const { fetchCtpvData, fetchSggData } = useStore(); - // 시도, 시군구 호출 + useEffect(() => { - fetchCtpvData(); - fetchSggData(); - }, [fetchCtpvData, fetchSggData]); + fetchData(); + }, []); const fetchData = async () => { try { @@ -56,9 +53,6 @@ const Map = () => { } }; - useEffect(() => { - fetchData(); - }, []); // SearchModal, GisContent 등에서 호출 const onFeatureSelect = (data: any) => { let map = gisMaps.current; diff --git a/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx index 9a46e2bf8..dfd71a39d 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx @@ -1,40 +1,15 @@ +import React from 'react'; import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox'; import { SelectBox } from '@common/business_components/ui'; import { Button, Input, Label } from '@common/components'; -import React from 'react'; -//import { useDcTree, flattenFolders, useSggCd, useCityCd } from './components/searchModalReportApi'; -// import { useBizByFcltStore } from "../../../store/StoreProvider"; +import useStore from '../../../store/useStore'; // 운영정보 '구분' 선택 목록 -// const WORK_TYPES = [ -// { val: "maintenance", text: "보수 및 갱신" }, -// { val: "cleaning", text: "청소 및 준설" }, -// { val: "inspection", text: "점검 및 진단" }, -// ]; export const WORK_TYPES = [ { val: "['200', '201', '204']", text: "보수 및 갱신" }, { val: "['100']", text: "점검 및 진단" }, { val: "['203']", text: "청소 및 준설" }, ]; -// const BIZ_SE_CD = useBizByFcltStore(state => state.BIZ_SE_CD); -export const BIZ_SE_CD = [ - { lwpoCd: "100", lwpoCdNm: "관로기술진단" }, - { lwpoCd: "200", lwpoCdNm: "개대체" }, - { lwpoCd: "201", lwpoCdNm: "유지보수" }, - { lwpoCd: "203", lwpoCdNm: "청소, 준설" }, - { lwpoCd: "204", lwpoCdNm: "단순보수" }, -]; -// const BIZ_TY_CD = useBizByFcltStore(state => state.BIZ_TY_CD); -export const BIZ_TY_CD = [ - { lwpoCd: "0", lwpoCdNm: "전체" }, - { lwpoCd: "SV", lwpoCdNm: "용역" }, - { lwpoCd: "SF", lwpoCdNm: "자체" }, - { lwpoCd: "SC", lwpoCdNm: "공사" }, -]; -export const BIZ_TY_CD_SELECT_ARRAY = BIZ_TY_CD.map(item => ({ - val: item.lwpoCd, - text: item.lwpoCdNm, -})); // 운영정보 - 사업 현황 export const operationTableHeadlist = [ { id: 'rowNumber', title: '일련번호' }, @@ -110,6 +85,14 @@ const OperationTab: React.FC = ({ setVar2, setYearFilter, }) => { + + const { BIZ_TY_CD } = useStore(); + + const BIZ_TY_CD_SELECT_ARRAY = BIZ_TY_CD.map(item => ({ + val: item.lwpoCd, + text: item.lwpoCdNm, + })); + return (

diff --git a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx index b453ba20f..7076b621c 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx @@ -3,24 +3,22 @@ import { UiModal, UITab, UiTable } from '@common/business_components/ui'; import axios from 'axios'; import React, { useEffect, useMemo, useState } from 'react'; import { ExclnSewagePumpCol2, MsSwageMhlCol2, MsSwagePplCol2, stofcbrCol2, SwtrmtFcltCol2, SwtrmtPrcsZoneCol2 } from '../components/common/table'; -import { type SelectDataType } from '../commonStore'; import { MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES, MS_SWAGE_MHL_CODES } from '../components/common/table'; import { getData } from "@/components/lib/api"; import BasicTab, { BASIC_TYPES } from './basicTab'; -import OperationTab, { BIZ_TY_CD, operationCleaningTableHeadlist, operationInspectionTableHeadlist, operationMaintenanceTableHeadlist, operationTableHeadlist, WORK_TYPES } from './operationTab'; -import ReportTab, { reportTableHeadlist } from './reportTab'; +import OperationTab, { operationCleaningTableHeadlist, operationInspectionTableHeadlist, operationMaintenanceTableHeadlist, operationTableHeadlist, WORK_TYPES } from './operationTab'; +import ReportTab from './reportTab'; import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; +import useStore from '../../../store/useStore'; const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`; -// 탭 const TABS = [ { val: "basic", text: "기본정보" }, { val: "operation", text: "운영정보" }, { val: "report", text: "하수도정비기본계획보고서" }, ]; - // 날짜 조합 함수 const getDateString = ( year: string, @@ -67,10 +65,6 @@ const SearchModal: React.FC = ({ const [ctpvName, setCtpvName] = useState(''); const [sgg, setSgg] = useState(0); const [sggName, setSggName] = useState(''); - const [ctpvCd, setCtpvCd] = useState('0'); // 하수정비기본계획 report - const [sigungu, setSigungu] = useState('0'); // 하수정비기본계획 report - const [gubun, setGubun] = useState(""); // 하수정비기본계획 report - const [reportGubun, setReportGubun] = useState("0"); // 하수정비기본계획 report const [start1, setStart1] = useState(""); const [start2, setStart2] = useState(""); const [end1, setEnd1] = useState(""); @@ -81,7 +75,8 @@ const SearchModal: React.FC = ({ const [var4, setVar4] = useState(""); // 시작, 종료 const [yearFilter, setYearFilter] = useState({ start: "", end: "" }); - const [filteredSiguns, setFilteredSiguns] = useState([{ val: '0', text: '전체', sido: '0' }]); + + const { BIZ_TY_CD } = useStore(); // 오늘 날짜 구하기 diff --git a/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx b/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx index d825b34fa..9eab30712 100644 --- a/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx +++ b/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx @@ -1,12 +1,12 @@ 'use client' import { getData } from '@/components/lib/api' -import ContentLayout from '@/components/ui/contentLayout' import { GovSidoSigunguSearchForm } from '@/components/ui/govSidoSigunguSearchForm' -import { DatePicker, DateMonthPicker, Form, SearchFormRight, SelectBox } from '@common/business_components/ui' +import { DateMonthPicker, Form, SelectBox } from '@common/business_components/ui' import ListDiv from '@common/business_components/ui/list_div' import { Button, Label } from '@common/components/ui' import { useEffect, useState, type ReactNode } from 'react' import LoadingOverlay from '@/components/ui/LoadingBar' +import { SidoSigunguSelectBox } from '@/components/ui/SidoSigunguSelectBox' type TitleName = { mainTitle: string @@ -40,12 +40,10 @@ type BaseLayout = { firstDivBtn?: boolean selectBox?: SelectBox showDatePicker?: boolean; - enoCd?: string | number - setEnoCd?: (enoCd: string) => void - ctpvCd?: string | number - setCtpvCd?: (ctpvCd: string) => void + ctpvCd: number; + sggCd: number; + sggName?: string; sigungu?: ApiSelectOption - setSigungu?: (sigungu: ApiSelectOption) => void selectedSwageTreatmentPlant?: string | number; onSwageTreatmentPlantChange?: (value: string | number) => void; selectedSwageProcessingZone?: string | number; @@ -54,23 +52,16 @@ type BaseLayout = { setAplcnYmFrom?: (aplcnYmFrom: string) => void; aplcnYmTo?: string setAplcnYmTo?: (aplcnYmTo: string) => void; + setCtpvCd: (ctpv: number) => void; + setSggCd: (sgg: number) => void; + setSggName?: (sggName: string) => void; isLoading?: boolean; - // Excel 다운로드 관련 props 추가 onExcelDownload?: () => void; onDeleteFile?: () => void; onAnalysis?: () => void; selectedRowData?: any; } -// TODO: 나중에 이벤트 추가되면 파라미터 추가 필요 ? -/** - * - * @param children - * @param titleName : 메인 타이틀, 첫번째 요소 타이틀, 두번째 요소 타이틀 - * @param form : zod 스키마 - * @param onSubmitEvent : 폼 이벤트 처리 - * @param graphBtn : 하단에 그래프 버튼 유무 처리 - * @returns - */ + const BaseLayout = ({ children, titleName, @@ -80,42 +71,36 @@ const BaseLayout = ({ analysisBtn, selectBox, showDatePicker = false, - enoCd, - setEnoCd, ctpvCd, - setCtpvCd, + sggCd, + sggName, sigungu, - setSigungu, selectedSwageTreatmentPlant, onSwageTreatmentPlantChange, - selectedSwageProcessingZone, - onSwageProcessingZoneChange, aplcnYmFrom, setAplcnYmFrom, aplcnYmTo, setAplcnYmTo, + setCtpvCd, + setSggCd, + setSggName, isLoading, - // Excel 다운로드 관련 props 추가 onExcelDownload, onDeleteFile, onAnalysis, - selectedRowData }: BaseLayout) => { const defaultOption: ApiSelectOption = { val: '0', text: '전체' }; - const [processingZoneOptions, setProcessingZoneOptions] = useState([]); const [treatmentFacilityOptions, setTreatmentFacilityOptions] = useState([]); const [isLoadingAreaData, setIsLoadingAreaData] = useState(isLoading || false); - const [atflId, setAtflId] = useState('') - const [fileNm, setFileNm] = useState('') - const [dcrpPplMtncBizSn, setDcrpPplMtncBizSn] = useState('') useEffect(() => { + onSwageTreatmentPlantChange?.(0); + const fetchData = async () => { - if (!sigungu || sigungu.val === '0' || !sigungu.val) { + if (!sggName) { console.log("BaseLayout: Skipping fetchData because sigungu.val is:", sigungu?.val); - setProcessingZoneOptions([defaultOption]); setTreatmentFacilityOptions([defaultOption]); setIsLoadingAreaData(false); return; @@ -123,20 +108,6 @@ const BaseLayout = ({ setIsLoadingAreaData(true); try { - // const queryParamsZone = new URLSearchParams({ - // lyrTypeNm: 'tb_ms_swage_prcs_zone', - // page: '0', - // size: '100000', - // sort: 'string', - // }); - // const cqlFiltersZone: string[] = []; - // if (sigungu.val && sigungu.val !== '0') { - // // console.log("시군구 코드", sigungu.val); - // cqlFiltersZone.push(`sgg_cd='${sigungu.val}'`); - // } - // if (cqlFiltersZone.length > 0) { - // queryParamsZone.append('cqlFiter', cqlFiltersZone.join(' and ')); - // } const queryParamsFacility = new URLSearchParams({ lyrTypeNm: 'tb_il_swage_prcs_fclt', @@ -145,39 +116,26 @@ const BaseLayout = ({ sort: 'string', }); const cqlFiltersFacility: string[] = []; - if (sigungu.text && sigungu.val !== '0') { - cqlFiltersFacility.push(`sgg_nm ILIKE '%${sigungu.text}%'`); - } - if (cqlFiltersFacility.length > 0) { - queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and ')); // API 명세에 따라 cqlFilter 또는 cqlFiter - } + if (sggName) cqlFiltersFacility.push(`sgg_nm ILIKE '%${sggName}%'`); + if (cqlFiltersFacility.length > 0) queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and ')); - // console.log("BaseLayout API URL for Zones:", `v1/fclts?${queryParamsZone.toString()}`); console.log("BaseLayout API URL for Facilities:", `v1/fclts?${queryParamsFacility.toString()}`); - // const [zoneRes, facilityRes] = await Promise.all([ - // getData(`v1/fclts?${queryParamsZone.toString()}`), - // getData(`v1/fclts?${queryParamsFacility.toString()}`) - // ]); const facilityRes = await getData(`v1/fclts?${queryParamsFacility.toString()}`); - // const zones = (zoneRes.data || []).map((item: any) => ({ - // text: item.swtrmtZoneNm || item.name || `구역 ${item.id || item.gid || item.objectid}`, - // val: item.fid || item.id || item.gid || item.objectid - // })); - // setProcessingZoneOptions([defaultOption, ...zones]); - // console.log("BaseLayout Fetched Processing Zones:", [defaultOption, ...zones]); + console.log('facilityRes.data', facilityRes.data); - const facilities = (facilityRes.data || []).map((item: any) => ({ - text: item.swtrmtFcltNm || item.name || `시설 ${item.id || item.gid || item.objectid}`, - val: item.fid || item.id || item.gid || item.objectid - })); + const facilities = (facilityRes.data || []) + .map((item: any) => ({ + text: item.swtrmtFcltNm, + val: item.fid + })) + .sort((a: ApiSelectOption, b: ApiSelectOption) => a.text.localeCompare(b.text, 'ko')); setTreatmentFacilityOptions([defaultOption, ...facilities]); console.log("BaseLayout Fetched Treatment Facilities:", [defaultOption, ...facilities]); } catch (error) { console.error("BaseLayout: 지역/시설 데이터 조회 중 오류 발생:", error); - setProcessingZoneOptions([defaultOption]); setTreatmentFacilityOptions([defaultOption]); } finally { setIsLoadingAreaData(false); @@ -185,7 +143,7 @@ const BaseLayout = ({ }; fetchData(); - }, [sigungu]); // sigungu 상태가 변경될 때마다 실행 + }, [sggName]); // sgg 상태가 변경될 때마다 실행 // 파일 다운로드 const isChckFile = () => { @@ -215,10 +173,10 @@ const BaseLayout = ({

{selectBox.title}

)} -
+
{showDatePicker && ( // showDatePicker 값에 따라 DatePicker 렌더링 <> - + { // aplcnYm이 없으면 현재 연월을 기본값으로 설정 @@ -253,24 +211,26 @@ const BaseLayout = ({ /> )} - setEnoCd?.(val)} - setRtnEnvPartSidoValue={(val) => setCtpvCd?.(val)} - setRtnEnvPartSigunValue={setSigungu} // 선택된 시군구 값을 받을 콜백 함수 전달 - > - - onSwageTreatmentPlantChange?.(val)} - selectContentClass="z-[101]" - /> - {/* onSwageProcessingZoneChange?.(val)} selectContentClass="z-[101]" /> */} - + { + setCtpvCd(sidoId); + setSggCd(0); + }} + onSigunguChange={setSggCd} + setSggName={setSggName} + /> + + onSwageTreatmentPlantChange?.(val)} + selectContentClass="z-[101]" + /> {selectBox?.searchChk && (
diff --git a/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx b/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx index 99542f5bf..e2a51676e 100644 --- a/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx @@ -49,13 +49,12 @@ interface ApiSelectOption { const IrSetting = () => { const [input1, setInput1] = useState('0.8'); // 오수전환율 초기값 const [input2, setInput2] = useState(''); - const [select1, setSelect1] = useState('0'); // 환경청코드 - const [select2, setSelect2] = useState('0'); // 시도코드 - const [select3, setSelect3] = useState({ val: '0', text: '전체' }); // 시군구 상태를 객체로 변경 - const [select4, setSelect4] = useState('0'); // 하수처리장코드 - const [select5, setSelect5] = useState('0'); // 하수처리구역코드 + const [select1, setSelect1] = useState('0'); // 하수처리장코드 const [formResetKey, setFormResetKey] = useState(0); // FileUploadForm 리셋을 위한 key 상태 const [isLoadingResult, setIsLoadingResult] = useState(false); // 로딩 상태 + const [ctpvCd, setCtpvCd] = useState(0); // 시도 코드 상태 + const [sggCd, setSggCd] = useState(0); // 시군구 코드 상태 + const [sggName, setSggName] = useState(''); // 시군구 이름 상태 const { data: userInfo } = useQuery({ queryKey: [ENDPOINT.USER_SERVICE.USERS], @@ -115,11 +114,7 @@ const IrSetting = () => { setSelectedDate(`${year}${month}`); setInput1(''); // 입력 필드 초기화 setInput2(''); // 입력 필드 초기화 - setSelect1('0'); // 선택 필드 초기화 - setSelect2('0'); - setSelect3({ val: '0', text: '전체' }); - setSelect4(0); - setSelect5(0); + setSelect1(0); setFormResetKey(prevKey => prevKey + 1); // FileUploadForm 재마운트를 위해 key 변경 }; @@ -164,7 +159,7 @@ const IrSetting = () => { setIsLoadingResult(true); // 로딩 상태 시작 // 기본 선택 항목들 검증 (환경청, 시도, 시군구, 하수처리장) - if (!validateBasicSelectionsWithAlert(select1, select2, select3, select4, { + if (!validateBasicSelectionsWithAlert(select1, { requireEnv: true, requireSido: true, requireSigungu: true, @@ -299,15 +294,11 @@ const IrSetting = () => { // 새로운 JSON 데이터 생성 const combinedAnalysisData = { - enoCd: (select1 && select1 !== '0') ? select1.toString() : null, // 환경청코드 (선택값) - ctpvCd: (select2 && select2 !== '0') ? select2.toString() : null, // 시도코드 (선택값) - sggCd: (select3.val && select3.val !== '0') ? select3.val.toString() : null, // 시군구코드 (선택값) - swageTmplntCd: (select4 && select4 !== '0' && select4 !== 0) - ? select4.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용 + ctpvCd: (ctpvCd ? String(ctpvCd) : null), + sggCd: (sggCd ? String(sggCd) : null), // 시군구코드 (선택값) + swageTmplntCd: (select1 && select1 !== '0' && select1 !== 0) + ? select1.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용 : null, // 하수처리장코드 (선택값) - swtrmtZoneCd: (select5 && select5 !== '0' && select5 !== 0) - ? select5.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용 - : null, // 하수처리구역코드 (선택값) aplcnYm: selectedDate, // 적용연월 drtwSwtcRt: parseFloat(input1) || null, // 오수전환율 (숫자로 변환) anlsInfo: input2, // 분석정보 @@ -346,16 +337,14 @@ const IrSetting = () => { graphBtn={false} analysisBtn={'침입수분석 실행'} selectBox={{ title: '분석 대상지역 선택' }} - enoCd={select1} - setEnoCd={setSelect1} - ctpvCd={select2} - setCtpvCd={setSelect2} - sigungu={select3} - setSigungu={setSelect3} - selectedSwageTreatmentPlant={select4} - onSwageTreatmentPlantChange={setSelect4} - selectedSwageProcessingZone={select5} - onSwageProcessingZoneChange={setSelect5} + ctpvCd={ctpvCd} + sggCd={sggCd} + sggName={sggName} + selectedSwageTreatmentPlant={select1} + onSwageTreatmentPlantChange={setSelect1} + setCtpvCd={setCtpvCd} + setSggCd={setSggCd} + setSggName={setSggName} isLoading={isLoadingResult} onAnalysis={handleAnalysisClick} > diff --git a/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx b/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx index 9bf381b97..491d2ae9d 100644 --- a/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx @@ -114,11 +114,10 @@ export default function RTKForm() { initialFileInfo, // 2: 강우자료 ]); - const [select1, setSelect1] = useState('0'); // 환경청코드 (enoCd) - const [select2, setSelect2] = useState('0'); // 시도코드 (ctpvCd) - const [select3, setSelect3] = useState({ val: '0', text: '전체' }); // 시군구 (sggCd) - const [select4, setSelect4] = useState('0'); // 하수처리장 (swageTmplntCd) - const [select5, setSelect5] = useState('0'); // 하수처리구역 (swtrmtZoneCd) + const [select1, setSelect1] = useState('0'); // 하수처리장 (swageTmplntCd) + const [ctpvCd, setCtpvCd] = useState(0); // 시도 코드 상태 + const [sggCd, setSggCd] = useState(0); // 시군구 코드 상태 + const [sggName, setSggName] = useState(''); // 시군구 이름 상태 const { data: userInfo } = useQuery({ queryKey: [ENDPOINT.USER_SERVICE.USERS], @@ -142,10 +141,6 @@ export default function RTKForm() { ]); setFormResetKey(prevKey => prevKey + 1); // FileUploadForm 재마운트를 위해 key 변경 setSelect1('0'); - setSelect2('0'); - setSelect3({ val: '0', text: '전체' }); - setSelect4('0'); - setSelect5('0'); }; // FileUploadForm의 onFileSelect prop으로 전달될 콜백 함수 @@ -182,7 +177,7 @@ export default function RTKForm() { setIsLoadingResult(true); // 로딩 상태 시작 // 기본 선택 항목들 검증 (환경청, 시도, 시군구, 하수처리장) - if (!validateBasicSelectionsWithAlert(select1, select2, select3, select4, { + if (!validateBasicSelectionsWithAlert(select1, { requireEnv: true, requireSido: true, requireSigungu: true, @@ -295,16 +290,12 @@ export default function RTKForm() { // 새로운 JSON 데이터 생성 const combinedAnalysisData = { - enoCd: (select1 && select1 !== '0') ? select1.toString() : null, // 환경청코드 (선택값) aplcnYm: formData.date, // 적용연월 (YYYYMM 형식) - ctpvCd: (select2 && select2 !== '0') ? select2.toString() : null, // 시도코드 (선택값) - sggCd: (select3.val && select3.val !== '0') ? select3.val.toString() : null, // 시군구코드 (선택값) - swageTmplntCd: (select4 && select4 !== '0' && select4 !== 0) - ? select4.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용 + ctpvCd: ctpvCd ? String(ctpvCd) : null, // 시도코드 (선택값) + sggCd: sggCd ? String(sggCd) : null, // 시군구코드 (선택값) + swageTmplntCd: (select1 && select1 !== '0' && select1 !== 0) + ? select1.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용 : null, // 하수처리장코드 (선택값) - swtrmtZoneCd: (select5 && select5 !== '0' && select5 !== 0) - ? select5.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용 - : null, // 하수처리구역코드 (선택값) atflCd: file1?.atflCd, // 첨부파일CD (오수관망도) atflId: file1?.atflId, // 첨부파일ID (오수관망도) atflInpNm: file1?.file?.name || "", // 오수관망도 파일명 @@ -395,7 +386,6 @@ export default function RTKForm() { } return ( - // { }} graphBtn={false} > { }} // BaseLayout의 form 태그에 연결될 onSubmit graphBtn={false} analysisBtn={'RDII분석 실행'} - enoCd={select1} - setEnoCd={setSelect1} - ctpvCd={select2} - setCtpvCd={setSelect2} - sigungu={select3} - setSigungu={setSelect3} - selectedSwageTreatmentPlant={select4} - onSwageTreatmentPlantChange={setSelect4} - selectedSwageProcessingZone={select5} - onSwageProcessingZoneChange={setSelect5} + ctpvCd={ctpvCd} + sggCd={sggCd} + sggName={sggName} + selectedSwageTreatmentPlant={select1} + onSwageTreatmentPlantChange={setSelect1} + setCtpvCd={setCtpvCd} + setSggCd={setSggCd} + setSggName={setSggName} isLoading={isLoadingResult} onAnalysis={handleAnalysisClick} > diff --git a/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx b/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx index 08c7a8146..d705728c5 100644 --- a/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx @@ -1,5 +1,5 @@ 'use client' -import { deleteData, getData, useCookieAuth } from '@/components/lib/api' +import { deleteData, getData } from '@/components/lib/api' import { UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui' import { zodResolver } from '@hookform/resolvers/zod' import { useState, useEffect } from 'react' @@ -11,12 +11,6 @@ import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; const formSchema = z.object({ - enoCd: z.string().default('0'), // 환경청코드 - ctpvCd: z.string().default('0'), // 시도코드 - sggCd: z.object({ // 시군구 - val: z.string().default('0'), - text: z.string().default('전체'), - }).default({ val: '0', text: '전체' }), swageTmplntCd: z.string().default('0'), // 하수처리장 swtrmtZoneCd: z.string().default('0'), // 하수처리구역 aplcnYmFrom: z.string().default(() => { // Date에서 string(yyyyMM 형식)으로 변경 @@ -54,14 +48,10 @@ const tableColumn = [ ] const Result = () => { - const AUTH = useCookieAuth(); const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { - enoCd: '0', - ctpvCd: '0', - sggCd: { val: '0', text: '전체' }, swageTmplntCd: '0', swtrmtZoneCd: '0', aplcnYmFrom: (() => { // 현재 연월을 yyyyMM 형식으로 설정 @@ -88,6 +78,9 @@ const Result = () => { const [resetSelection, setResetSelection] = useState(false); const [isResetPage, setIsResetPage] = useState(false); const [curPage, setCurPage] = useState(1); // 현재 페이지 + const [ctpvCd, setCtpvCd] = useState(0); // 시도 코드 상태 + const [sggCd, setSggCd] = useState(0); // 시군구 코드 상태 + const [sggName, setSggName] = useState(''); // 시군구 이름 상태 useEffect(() => { // setCurPage(1); @@ -127,11 +120,9 @@ const Result = () => { let inftwtAnlsSearchDto = { "aplcnYmFrom": formValues.aplcnYmFrom && formValues.aplcnYmFrom !== '' ? formValues.aplcnYmFrom : null, "aplcnYmTo": formValues.aplcnYmTo && formValues.aplcnYmTo !== '' ? formValues.aplcnYmTo : null, - "enoCd": formValues.enoCd === '0' ? null : formValues.enoCd, - "ctpvCd": formValues.ctpvCd === '0' ? null : formValues.ctpvCd, - "sggCd": formValues.sggCd?.val === '0' ? null : formValues.sggCd?.val, + "ctpvCd": ctpvCd === 0 ? null : ctpvCd, + "sggCd": sggCd === 0 ? null : sggCd, "swageTmplntCd": processedSwageTmplntCd, - "auth": AUTH, }; console.log("Search DTO:", inftwtAnlsSearchDto); @@ -163,7 +154,8 @@ const Result = () => { rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가 scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경 }; - }); + }) + .filter((item: any) => item.scsYn === 'Y'); // 선택된 행 초기화 setSelectedRowData(null); @@ -289,20 +281,18 @@ const Result = () => { onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit graphBtn={true} showDatePicker={true} - enoCd={form.watch("enoCd")} - setEnoCd={(val) => form.setValue("enoCd", val)} - ctpvCd={form.watch("ctpvCd")} - setCtpvCd={(val) => form.setValue("ctpvCd", val)} - sigungu={form.watch("sggCd")} - setSigungu={(val) => form.setValue("sggCd", val)} + ctpvCd={ctpvCd} + sggCd={sggCd} + sggName={sggName} selectedSwageTreatmentPlant={form.watch("swageTmplntCd")} onSwageTreatmentPlantChange={(val) => form.setValue("swageTmplntCd", String(val))} - selectedSwageProcessingZone={form.watch("swtrmtZoneCd")} - onSwageProcessingZoneChange={(val) => form.setValue("swtrmtZoneCd", String(val))} aplcnYmFrom={form.watch("aplcnYmFrom")} setAplcnYmFrom={(val: string) => { form.setValue("aplcnYmFrom", val) }} aplcnYmTo={form.watch("aplcnYmTo")} setAplcnYmTo={(val: string) => { form.setValue("aplcnYmTo", val) }} + setCtpvCd={setCtpvCd} + setSggCd={setSggCd} + setSggName={setSggName} isLoading={isLoadingResult} // Excel 다운로드 핸들러 추가 onExcelDownload={handleExcelDownload} diff --git a/apps/sn/app/(with-base-layout)/ii/result/page.tsx b/apps/sn/app/(with-base-layout)/ii/result/page.tsx index 974f6fa5f..c27110223 100644 --- a/apps/sn/app/(with-base-layout)/ii/result/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/result/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { deleteData, getData, useCookieAuth } from '@/components/lib/api' -import { UiTable, downloadExcel, downloadExcelWithKoreanHeaders } from '@common/business_components/ui' +import { deleteData, getData } from '@/components/lib/api' +import { UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui' import { zodResolver } from '@hookform/resolvers/zod' import { useState, useEffect } from 'react' import { useForm } from 'react-hook-form' @@ -11,12 +11,6 @@ import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; const formSchema = z.object({ - enoCd: z.string().default('0'), // 환경청코드 - ctpvCd: z.string().default('0'), // 시도코드 - sggCd: z.object({ // 시군구 - val: z.string().default('0'), - text: z.string().default('전체'), - }).default({ val: '0', text: '전체' }), swageTmplntCd: z.string().default('0'), // 하수처리장 swtrmtZoneCd: z.string().default('0'), // 하수처리구역 aplcnYmFrom: z.string().default(() => { // Date에서 string(yyyyMM 형식)으로 변경 @@ -55,9 +49,6 @@ const Result = () => { const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { - enoCd: '0', - ctpvCd: '0', - sggCd: { val: '0', text: '전체' }, swageTmplntCd: '0', swtrmtZoneCd: '0', aplcnYmFrom: (() => { // 현재 연월을 yyyyMM 형식으로 설정 @@ -76,8 +67,6 @@ const Result = () => { } }) - const AUTH = useCookieAuth(); - const [tableData, setTableData] = useState([]); // API 조회 결과 저장 상태 const [totalData, setTotalData] = useState(0) //총 데이터 수l const [isLoadingResult, setIsLoadingResult] = useState(false); // 로딩 상태 @@ -85,6 +74,9 @@ const Result = () => { const [resetSelection, setResetSelection] = useState(false); const [isResetPage, setIsResetPage] = useState(false); const [curPage, setCurPage] = useState(1); // 현재 페이지 + const [ctpvCd, setCtpvCd] = useState(0); // 시도 코드 상태 + const [sggCd, setSggCd] = useState(0); // 시군구 코드 상태 + const [sggName, setSggName] = useState(''); // 시군구 이름 상태 useEffect(() => { // setCurPage(1); @@ -124,12 +116,9 @@ const Result = () => { let inftwtAnlsSearchDto = { "aplcnYmFrom": formValues.aplcnYmFrom && formValues.aplcnYmFrom !== '' ? formValues.aplcnYmFrom : null, "aplcnYmTo": formValues.aplcnYmTo && formValues.aplcnYmTo !== '' ? formValues.aplcnYmTo : null, - "enoCd": formValues.enoCd === '0' ? null : formValues.enoCd, - "ctpvCd": formValues.ctpvCd === '0' ? null : formValues.ctpvCd, - "sggCd": formValues.sggCd?.val === '0' ? null : formValues.sggCd?.val, + "ctpvCd": ctpvCd === 0 ? null : ctpvCd, + "sggCd": sggCd === 0 ? null : sggCd, "swageTmplntCd": processedSwageTmplntCd, - // "swtrmtZoneCd": formValues.swtrmtZoneCd === '0' ? null : formValues.swtrmtZoneCd, - "auth": AUTH, }; console.log("Search DTO:", inftwtAnlsSearchDto); @@ -162,7 +151,8 @@ const Result = () => { rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가 scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경 }; - }); + }) + .filter((item: any) => item.scsYn === 'Y'); // 선택된 행 초기화 setSelectedRowData(null); @@ -289,20 +279,18 @@ const Result = () => { onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit graphBtn={true} showDatePicker={true} - enoCd={form.watch("enoCd")} - setEnoCd={(val) => form.setValue("enoCd", val)} - ctpvCd={form.watch("ctpvCd")} - setCtpvCd={(val) => form.setValue("ctpvCd", val)} - sigungu={form.watch("sggCd")} - setSigungu={(val) => form.setValue("sggCd", val)} + ctpvCd={ctpvCd} + sggCd={sggCd} + sggName={sggName} selectedSwageTreatmentPlant={form.watch("swageTmplntCd")} onSwageTreatmentPlantChange={(val) => form.setValue("swageTmplntCd", String(val))} - selectedSwageProcessingZone={form.watch("swtrmtZoneCd")} - onSwageProcessingZoneChange={(val) => form.setValue("swtrmtZoneCd", String(val))} aplcnYmFrom={form.watch("aplcnYmFrom")} setAplcnYmFrom={(val: string) => { form.setValue("aplcnYmFrom", val) }} aplcnYmTo={form.watch("aplcnYmTo")} setAplcnYmTo={(val: string) => { form.setValue("aplcnYmTo", val) }} + setCtpvCd={setCtpvCd} + setSggCd={setSggCd} + setSggName={setSggName} isLoading={isLoadingResult} // 로딩 상태 전달 // Excel 다운로드 핸들러 추가 onExcelDownload={handleExcelDownload} diff --git a/apps/sn/app/(with-base-layout)/layout.tsx b/apps/sn/app/(with-base-layout)/layout.tsx index 3c1a527d7..8c72ecb0c 100644 --- a/apps/sn/app/(with-base-layout)/layout.tsx +++ b/apps/sn/app/(with-base-layout)/layout.tsx @@ -1,11 +1,13 @@ "use client"; +import React, { useEffect} from 'react'; import { usePathname, useRouter } from "next/navigation"; import { useQueryClient } from "@tanstack/react-query"; import { BaseLayout } from "@common/components"; import type { MenuItem } from "@/entities/menu"; import { useMenuStore } from "@/shared/lib"; import { PageLayout } from "@/shared/ui/page-layout"; +import useStore from '../store/useStore'; export default function WithBaseLayout({ children, @@ -18,6 +20,15 @@ export default function WithBaseLayout({ const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo); + const { fetchCtpvData, fetchSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } = useStore(); + + useEffect(() => { + fetchCtpvData(); + fetchSggData(); + fetchBIZ_SE_CD(); + fetchBIZ_TY_CD(); + }, []); + const handleMenuClick = (item: MenuItem) => { setCurrentMenuInfo(item); router.push(item.urlPath); diff --git a/apps/sn/app/(with-base-layout)/selectionValidation.tsx b/apps/sn/app/(with-base-layout)/selectionValidation.tsx index 8cc506d0e..e5cdb77ad 100644 --- a/apps/sn/app/(with-base-layout)/selectionValidation.tsx +++ b/apps/sn/app/(with-base-layout)/selectionValidation.tsx @@ -87,10 +87,7 @@ export const validateMultipleSelectionsWithAlert = ( * @returns 검증 결과 */ export const validateBasicSelections = ( - select1: string, - select2: string, - select3: ApiSelectOption, - select4: string | number, + select1: string | number, options: { requireEnv?: boolean; requireSido?: boolean; @@ -108,21 +105,6 @@ export const validateBasicSelections = ( const validationConfigs: SelectionValidationConfig[] = [ { value: select1, - description: '환경청', - required: requireEnv - }, - { - value: select2, - description: '시도', - required: requireSido - }, - { - value: select3, - description: '시군구', - required: requireSigungu - }, - { - value: select4, description: '하수처리장', required: requireTreatmentPlant } @@ -133,18 +115,12 @@ export const validateBasicSelections = ( /** * 기본 선택 항목들 검증 및 alert 표시 - * @param select1 환경청코드 - * @param select2 시도코드 - * @param select3 시군구 객체 - * @param select4 하수처리장코드 + * @param select1 하수처리장코드 * @param options 각 항목의 필수 여부 설정 * @returns 검증 결과 */ export const validateBasicSelectionsWithAlert = ( - select1: string, - select2: string, - select3: ApiSelectOption, - select4: string | number, + select1: string | number, options: { requireEnv?: boolean; requireSido?: boolean; @@ -152,7 +128,7 @@ export const validateBasicSelectionsWithAlert = ( requireTreatmentPlant?: boolean; } = {} ): boolean => { - const result = validateBasicSelections(select1, select2, select3, select4, options); + const result = validateBasicSelections(select1, options); if (!result.isValid && result.errorMessage) { alert(result.errorMessage); return false; diff --git a/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx b/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx index a3a138259..fc9962e75 100644 --- a/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx +++ b/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx @@ -3,10 +3,6 @@ import { SelectBox } from '@common/business_components/ui' import { Label } from '@common/components'; import useStore from '../../store/useStore'; -interface Option { - id: number; - name: string; -} interface SidoSigunguSelectBoxProps { sido: number; diff --git a/apps/sn/app/store/index.ts b/apps/sn/app/store/index.ts new file mode 100644 index 000000000..b20466d77 --- /dev/null +++ b/apps/sn/app/store/index.ts @@ -0,0 +1,64 @@ +import { getGeoData, getCmsData } from "@/components/lib/api"; + + +export interface Option { id: number, name: string } + +export type BIZ_CD_TYPE = { + lwpoCd: string, + upCd: string, + lwpoCdNm: string, + unit: string, + prntLwpoCd: null +}; + +export const fetchCtpvData = async (set: (state: any) => void) => { + try { + const ctpvData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_ctpv&outputFormat=application%2Fjson'); + const ctpvList: Option[] = ctpvData.features + .map((feature: any) => ({ + id: feature.properties.mctpv_cd.trim(), + name: feature.properties.mctpv_korn_nm + })) + .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); + set({ ctpv: ctpvList }); + } catch (e: any) { + alert("시도 데이터 로드 실패: " + e.message); + console.error("CTPV 데이터 로드 실패:", e); + } +}; + +export const fetchSggData = async (set: (state: any) => void) => { + try { + const sggData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_sgg&outputFormat=application%2Fjson'); + const sggList: Option[] = sggData.features + .map((feature: any) => ({ + id: feature.properties.sgg_cd, + name: feature.properties.sgg_korn_nm + })) + .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); + set({ sgg: sggList }); + } catch (e: any) { + alert("시군구 데이터 로드 실패: " + e.message); + console.error("SGG 데이터 로드 실패:", e); + } +}; + +export const fetchBIZ_SE_CD = async (set: (state: any) => void) => { + try { + const res = await getCmsData(`/api/v1/code/cache/BIZSECD`); + set({ BIZ_SE_CD: res }); + } catch (e: any) { + alert("BIZ_SE_CD 데이터 로드 실패: " + e.message); + console.error("BIZ_SE_CD 데이터 로드 실패:", e); + } +}; + +export const fetchBIZ_TY_CD = async (set: (state: any) => void) => { + try { + const res = await getCmsData(`/api/v1/code/cache/BIZTYCD`); + set({ BIZ_TY_CD: res }); + } catch (e: any) { + alert("BIZ_TY_CD 데이터 로드 실패: " + e.message); + console.error("BIZ_TY_CD 데이터 로드 실패:", e); + } +}; \ No newline at end of file diff --git a/apps/sn/app/store/useStore.ts b/apps/sn/app/store/useStore.ts index 518cce66b..7775ca61e 100644 --- a/apps/sn/app/store/useStore.ts +++ b/apps/sn/app/store/useStore.ts @@ -1,41 +1,37 @@ -import { useMemo } from "react"; import { create } from "zustand"; -import { getGeoData } from "@/components/lib/api"; -import { get } from "http"; +import { getGeoData, getCmsData } from "@/components/lib/api"; +import { fetchCtpvData, fetchSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } from "./index"; interface Option { id: number, name: string } +export type BIZ_CD_TYPE = { + lwpoCd: string, + upCd: string, + lwpoCdNm: string, + unit: string, + prntLwpoCd: null +}; + interface SnStoreState { ctpv: Option[]; sgg: Option[]; + BIZ_SE_CD: BIZ_CD_TYPE[]; + BIZ_TY_CD: BIZ_CD_TYPE[]; fetchCtpvData: () => Promise; fetchSggData: () => Promise; + fetchBIZ_SE_CD: () => Promise; + fetchBIZ_TY_CD: () => Promise; } const useStore = create((set) => ({ - // Define your store state and actions here ctpv: [], sgg: [], - fetchCtpvData: async () => { - const ctpvData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_ctpv&outputFormat=application%2Fjson'); - const ctpvList: Option[] = ctpvData.features - .map((feature: any) => ({ - id: feature.properties.mctpv_cd.trim(), - name: feature.properties.mctpv_korn_nm - })) - .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); - set({ ctpv: ctpvList }); - }, - fetchSggData: async () => { - const sggData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_sgg&outputFormat=application%2Fjson'); - const sggList: Option[] = sggData.features - .map((feature: any) => ({ - id: feature.properties.sgg_cd, - name: feature.properties.sgg_korn_nm - })) - .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); - set({ sgg: sggList }); - } + BIZ_SE_CD: [], + BIZ_TY_CD: [], + fetchCtpvData: () => fetchCtpvData(set), + fetchSggData: () => fetchSggData(set), + fetchBIZ_SE_CD: () => fetchBIZ_SE_CD(set), + fetchBIZ_TY_CD: () => fetchBIZ_TY_CD(set), })); export default useStore; \ No newline at end of file From 6808430c9ae74890d0f0e84f20a0ffe5409de157 Mon Sep 17 00:00:00 2001 From: swcho Date: Thu, 31 Jul 2025 16:09:36 +0900 Subject: [PATCH 34/71] =?UTF-8?q?feat:=20UITable=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=EC=9D=98=20index=20props=EB=A5=BC=20?= =?UTF-8?q?=ED=86=B5=ED=95=B4=20=EC=99=B8=EB=B6=80=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=82=B4=EB=B6=80=20select=20idx=EB=A5=BC=20=EC=A0=9C=EC=96=B4?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/ui/table/index.tsx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/common/business_components/src/ui/table/index.tsx b/packages/common/business_components/src/ui/table/index.tsx index c3f5f8a60..fa7c9b7fe 100644 --- a/packages/common/business_components/src/ui/table/index.tsx +++ b/packages/common/business_components/src/ui/table/index.tsx @@ -78,6 +78,10 @@ export function UiTable({ } }, [tableData, headlist]); + useEffect(() => { + setIdx(index); + }, [index]) + const handlePageChange = (page: number) => { setCur(page); onPageChange(page); @@ -120,10 +124,10 @@ export function UiTable({ (boldArray.includes(index) ? ( handleCellClick(index, head.id)} @@ -151,10 +155,10 @@ export function UiTable({ ) : ( handleCellClick(index, head.id)} key={`cell-${head.id}-${index}`} From d6cab7b35cff8beaa220415adbdfa0c1f1e89e35 Mon Sep 17 00:00:00 2001 From: swcho Date: Thu, 31 Jul 2025 16:13:52 +0900 Subject: [PATCH 35/71] =?UTF-8?q?refactor:=20=ED=98=B8=EC=B6=9C=20API=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/components/lib/api/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/sn/app/components/lib/api/index.ts b/apps/sn/app/components/lib/api/index.ts index 6574567ad..df1d9f509 100644 --- a/apps/sn/app/components/lib/api/index.ts +++ b/apps/sn/app/components/lib/api/index.ts @@ -41,8 +41,6 @@ export const postData = async (url: string, data: object): Promise => { body: JSON.stringify(data), }; const res = await fetch(`${BASE_URL}/${url}`, options); - console.log('res', res); - console.log('res.status', res.status); if (res.ok) { const resData = await res.json(); return resData; @@ -99,7 +97,6 @@ export const getDcData = async (url: string, data?: object): Promise => { // CMS API 호출 export const getCmsData = async (url: string, data?: object): Promise => { - console.log(url); const res = await fetch('/cms-service' + url, { method: "GET", }); From 62a7dc74518d6df57191f4367e629dfae0b599e5 Mon Sep 17 00:00:00 2001 From: swcho Date: Thu, 31 Jul 2025 16:43:59 +0900 Subject: [PATCH 36/71] =?UTF-8?q?feat,=20fix:=20-=20=EC=A4=91=EC=A0=90?= =?UTF-8?q?=EC=9A=B0=EC=88=98=ED=86=A0=EC=8B=A4=20=EA=B0=95=EC=9A=B0?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=ED=85=8C=EC=9D=B4=EB=B8=94,=20=EC=B8=A1?= =?UTF-8?q?=EC=A0=95=EC=A0=95=EB=B3=B4=20=ED=85=8C=EC=9D=B4=EB=B8=94=20sel?= =?UTF-8?q?ected=20index,=20page=20=EB=8F=99=EA=B8=B0=ED=99=94=20-=20?= =?UTF-8?q?=EC=88=98=EB=9F=89/=EC=88=98=EC=A7=88=EC=B8=A1=EC=A0=95=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../map/components/popupComponent/Ssc.tsx | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) 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 4542e56c9..801164eb8 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx @@ -29,13 +29,16 @@ const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: { const [selectedRowIndex, setSelectedRowIndex] = useState(-1); const [curPage, setCurPage] = useState(1); const [curDetailPage, setCurDetailPage] = useState(1); - const [isResetPage, setIsResetPage] = useState(false); - const [isResetDetailPage, setIsResetDetailPage] = useState(false); + const [isResetPage, setIsResetPage] = useState(true); + const [isResetDetailPage, setIsResetDetailPage] = useState(true); const [isResetSelection, setIsResetSelection] = useState(false); - const [curIdx, setCurIdx] = useState(0); + + console.log('featureData', featureData); // 강우, 측정 정보 API 호출 함수 const fetchRainMeasureData = async (chmbrXcnts: string, chmbrYdnts: string) => { + setIsResetDetailPage(true); + setIsResetSelection(true); if (!chmbrXcnts || !chmbrYdnts) { console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { chmbrXcnts, chmbrYdnts }); return []; @@ -76,6 +79,7 @@ const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: { // 수량/수질측정 정보 API 호출 함수 const fetchMntrngsData = async (adstDstrCd: string, manageNo: string) => { + setIsResetDetailPage(true); if (!adstDstrCd || !manageNo) { console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { adstDstrCd, manageNo }); return []; @@ -87,7 +91,7 @@ const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: { size: '3', }); - console.log(`SSC 추가 데이터 API 호출 시작: v1/onsemiro/swgRain?${queryParams.toString()}`); + console.log(`SSC 추가 데이터 API 호출 시작: v1/onsemiro/swgRain/${adstDstrCd}/${manageNo}/mntrngs?${queryParams.toString()}`); const result = await getData(`v1/onsemiro/swgRain/${adstDstrCd}/${manageNo}/mntrngs?${queryParams.toString()}`); @@ -143,14 +147,14 @@ const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: { // featureData가 변경될 때 formData 초기화 useEffect(() => { // showTable이 true일 때만 fetch (최초 렌더 방지) - setIsResetPage(false); - setIsResetDetailPage(true); + setIsResetDetailPage(false); + setSelectedRowIndex(-1); if (chmbrXcnts && chmbrYdnts) fetchRainMeasureData(chmbrXcnts, chmbrYdnts); }, [curPage]); // 페이지 변경 시마다 데이터 fetch useEffect(() => { - if (chmbrXcnts && chmbrYdnts) fetchMntrngsData(chmbrXcnts, chmbrYdnts); + if (chmbrXcnts && chmbrYdnts && selectedRowIndex !== -1) fetchMntrngsData(rainMeasureData[selectedRowIndex].adstDstrCd, rainMeasureData[selectedRowIndex].manageNo); }, [curDetailPage]); // 폼 데이터가 변경될 때 호출될 핸들러 @@ -187,7 +191,7 @@ const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: { onPageChange={setCurPage} resetPageNo={isResetPage} resetSelection={isResetSelection} - index={curIdx} + index={selectedRowIndex} /> {/* 측정정보 테이블 */} @@ -201,7 +205,7 @@ const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: { onPageChange={setCurPage} resetPageNo={isResetPage} resetSelection={isResetSelection} - index={curIdx} + index={selectedRowIndex} > {/* 커스텀 헤더 */} @@ -229,6 +233,7 @@ const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: { total={totalDetailCount} cellClick={undefined} onPageChange={setCurDetailPage} + resetPageNo={isResetDetailPage} />
) From a780c3699166da4a2a711e559e096a53094f8532 Mon Sep 17 00:00:00 2001 From: ljpark Date: Thu, 31 Jul 2025 16:56:21 +0900 Subject: [PATCH 37/71] =?UTF-8?q?=ED=95=98=EC=88=98=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=EC=8B=9C=EC=84=A4=20=EA=B8=B0=EB=B3=B8=EA=B3=84=ED=9A=8D?= =?UTF-8?q?=EB=8B=A4=EC=9A=B4=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/.env.local | 2 +- .../map/components/common/index.ts | 3 +- .../components/popupComponent/SwtrmtFclt.tsx | 60 +++++++++++++++---- .../popupComponent/featureClickEventPopup.tsx | 1 + .../map/searchModal/searchModalReportApi.ts | 12 +++- 5 files changed, 63 insertions(+), 15 deletions(-) diff --git a/apps/sn/.env.local b/apps/sn/.env.local index 253ed7528..2e22382c5 100644 --- a/apps/sn/.env.local +++ b/apps/sn/.env.local @@ -5,7 +5,7 @@ NEXT_PUBLIC_ADDR_URL = http://con.mind-one.co.kr:11082 NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver -NEXT_PUBLIC_DC_URL = https://apigateway.rsonesoft.com #자료관 데이터 +NEXT_PUBLIC_DC_URL = https://apigateway.apps.rsonesoft.com #자료관 데이터 NEXT_PUBLIC_CLAIM_NAME = Authorization NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_ACCESS_TOKEN = access-token diff --git a/apps/sn/app/(map-layout)/map/components/common/index.ts b/apps/sn/app/(map-layout)/map/components/common/index.ts index 9860b211a..d1cbaa4f0 100644 --- a/apps/sn/app/(map-layout)/map/components/common/index.ts +++ b/apps/sn/app/(map-layout)/map/components/common/index.ts @@ -12,6 +12,7 @@ type LayerComponentProps = { rainMeasureData?: any; onSaveClick?: () => void; onFormDataChange?: (data: any) => void; // 추가 + gisObject?: Record; // GIS 객체 추가 }; type LayerContentMap = { [key: string]: { @@ -22,7 +23,7 @@ type LayerContentMap = { // 레이어 이름과 컴포넌트를 매핑 // 레이어 선택시 변경되는 팝업 컨텐츠 동적으로 변경하도록 작업 const layerContentMap: LayerContentMap = { - // "하수처리시설": { component: SwtrmtFclt, }, // 디지털 자료관 연계정보가 없으니 일단 주석 처리 + "하수처리시설": { component: SwtrmtFclt, }, // 디지털 자료관 연계정보가 없으니 일단 주석 처리 "하수처리구역": {}, "우수토실": { component: Ssc, }, "중점우수토실": { component: Ssc, }, // 중점우수토실 여부 설정 diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/SwtrmtFclt.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/SwtrmtFclt.tsx index 50d6d5606..6d182072b 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/SwtrmtFclt.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/SwtrmtFclt.tsx @@ -1,13 +1,15 @@ +'use client' +import React, { useState,useEffect } from 'react' import Modal from '@/components/ui/modal'; import { VerticalTable } from '@/components/ui/verticalTable'; import { UiTable } from '@common/business_components/ui'; -import { Button, TableCell } from '@common/components/ui'; -import React, { useState } from 'react' +import { Button } from '@common/components/ui'; +import { useDcTree, flattenFolders, getFileDownload, addFileName, type FlattenedFolder} from '../../searchModal/searchModalReportApi'; // 하수처리시설 const headList = [ { id: "title", title: "하수도정비 기본계획보고서" }, - { id: "cad", title: "하수도대장 평면도" },] +]; const modalHeadList = [ { id: "no", title: "번호" }, { id: "year", title: "승인연도" }, @@ -16,9 +18,10 @@ const modalHeadList = [ { id: "date", title: "입력시간" }, ] -const SwtrmtFclt = () => { +const SwtrmtFclt = ({ gisObject }: { gisObject: any }) => { const [isModalOpen, setIsModalOpen] = useState(false); const [modalTitle, setModalTitle] = useState(''); + const [dcDataOne, setDcDataOne] = useState({}); const openModal = (title: string) => { setIsModalOpen(true); setModalTitle(title) @@ -26,22 +29,59 @@ const SwtrmtFclt = () => { const closeModal = () => { setIsModalOpen(false); }; + const ctpvNm = gisObject?.ctpv_nm; + //const reportOriginData = useDcTree(ctpvNm); + const reportOriginData = useDcTree("강원특별자치도"); + useEffect(() => { + const fetch = async () => { + + if (!reportOriginData || reportOriginData.length === 0) { + return; + } + const reportData = flattenFolders(reportOriginData); + let filterReportData = [...reportData]; + if (gisObject && gisObject.sgg_nm !== undefined && gisObject.sgg_nm !== "") { // 시군구검색 + //const sggNm = gisObject?.sgg_nm; + const sggNm = "강릉시"; + filterReportData = filterReportData.filter(item => item.sigungu === sggNm); + } + addFileName(filterReportData).then(data => { + filterReportData = data.filter(item => item.filePath !== undefined && item.filePath !== ""); + if (filterReportData !== undefined && filterReportData.length > 0) { + setDcDataOne(filterReportData[0]); + } + }); + console.log("filterReportData", filterReportData); + }; + fetch(); + }, [reportOriginData]); + + // 하수도정비기본계획 filedownload 핸들러 + const handleFileDownloadClick = () => { + if (dcDataOne && dcDataOne !== undefined && dcDataOne.fileSn !== undefined && dcDataOne.fileSn !== "" + && dcDataOne.filePath !== undefined && dcDataOne.filePath !== "" && dcDataOne.fileName !== undefined && dcDataOne.fileName !== "" ) { + console.log("dcDataOne", dcDataOne); + getFileDownload(String(dcDataOne.fileSn), String(dcDataOne.fileName), String(dcDataOne.filePath)); + } + }; + + //cad: , return (
- openModal('하수도정비 기본계획보고서')}>보고서, - cad: , + handleFileDownloadClick()}>다운로드 }} headName='디지털 자료관 연계정보' tableData={[]} headlist={headList} pageSize={20} total={0} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void { throw new Error("Function not implemented."); }}> - + {/*
-
-
+
*/}
) } 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 679122d6b..2d13b46d4 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx @@ -481,6 +481,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, onSaveClick={handleSaveClick} chmbrXcnts={chmbrXcnts} chmbrYdnts={chmbrYdnts} + gisObject={featureData.values_} /> }
diff --git a/apps/sn/app/(map-layout)/map/searchModal/searchModalReportApi.ts b/apps/sn/app/(map-layout)/map/searchModal/searchModalReportApi.ts index c12021499..9610b4264 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/searchModalReportApi.ts +++ b/apps/sn/app/(map-layout)/map/searchModal/searchModalReportApi.ts @@ -79,7 +79,11 @@ export function flattenFolders(data: Folder[]): FlattenedFolder[] { } } - data.forEach((root) => traverse(root, {}, 0)); + if (data && data.length > 0 && data[0].clsfSystmNm == "기본계획") { // data가 비어있지 않고 children이 있는 경우에만 처리 + data.forEach((root) => traverse(root, {}, 0)); + } else { + data.forEach((root) => traverse(root, {}, 1)); + } return result; } @@ -96,13 +100,15 @@ export const useDcTree = (clsfSystmNm:string) => { if (result) { setData(result); } + console.log("useDcTree호출"); + console.log("clsfSystmNm:", clsfSystmNm, "data:", result); }; fetch(); - console.log("useDcTree호출"); }, [clsfSystmNm]); return data; }; + export const getDcFile = async (clsfSystmSn:string) => { const queryString = 'clsfSystmSn='+clsfSystmSn; const result = await getDcData(`/api/v1/dc/file${queryString ? `?${queryString}` : ''}`); @@ -119,7 +125,7 @@ export const addFileName = async (data:FlattenedFolder[]) => { }) ); return fileReportData; -} +}; export const getDcFileprcs = async (fileSn:string) => { const queryString = 'fileSn='+fileSn; const result = await getDcData(`/api/v1/dc/fileprcs${queryString ? `?${queryString}` : ''}`); From ad3f404c44608d4858e76dc35e51a6b0c1ac4dc1 Mon Sep 17 00:00:00 2001 From: ljpark Date: Thu, 31 Jul 2025 16:57:57 +0900 Subject: [PATCH 38/71] =?UTF-8?q?=EC=9E=90=EB=A3=8C=EA=B4=80=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=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 --- apps/sn/.env.development | 2 +- apps/sn/.env.production | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/sn/.env.development b/apps/sn/.env.development index f12d2c38e..b63add651 100644 --- a/apps/sn/.env.development +++ b/apps/sn/.env.development @@ -5,7 +5,7 @@ NEXT_PUBLIC_ADDR_URL = http://con.mind-one.co.kr:11082 NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver #TODO 운영에서 바꿔야 함 -NEXT_PUBLIC_DC_URL = https://apigateway.rsonesoft.com #자료관 데이터 +NEXT_PUBLIC_DC_URL = https://apigateway.apps.rsonesoft.com #자료관 데이터 NEXT_PUBLIC_CLAIM_NAME = Authorization NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_ACCESS_TOKEN = access-token diff --git a/apps/sn/.env.production b/apps/sn/.env.production index fd98438d7..acfd68b81 100644 --- a/apps/sn/.env.production +++ b/apps/sn/.env.production @@ -9,5 +9,5 @@ NEXT_PUBLIC_SERVER_API_URL = http://apigateway.iipbs-dev.svc.cluster.local:8000 NEXT_PUBLIC_ADDR_URL = http://con.mind-one.co.kr:11082 NEXT_PUBLIC_GEOSERVER_URL = http://172.29.143.87:8080/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl -NEXT_PUBLIC_DC_URL = https://apigateway.rsonesoft.com #자료관 데이터 +NEXT_PUBLIC_DC_URL = https://apigateway.apps.rsonesoft.com #자료관 데이터 NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api From 765ca59e35713af3ed9df9fe38b0bdd219ed6da2 Mon Sep 17 00:00:00 2001 From: JIeunhuh Date: Thu, 31 Jul 2025 17:00:08 +0900 Subject: [PATCH 39/71] =?UTF-8?q?=ED=8C=9D=EC=97=85=EB=B3=84=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20?= =?UTF-8?q?=EC=8B=9C=EC=84=A4=20=EC=84=A0=ED=83=9D=EC=8B=9C=20=ED=95=B4?= =?UTF-8?q?=EB=8B=B9=20api=EB=A7=8C=20=ED=98=B8=EC=B6=9C=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ => layerPopup}/hasuFacility.tsx | 4 +- .../components/layerPopup/htdflFacility.tsx | 83 +++ .../components/layerPopup/rainFacility.tsx | 105 ++++ .../{ => layerPopup}/reuseFacility.tsx | 7 +- .../layerPopup/reuseMngFacility.tsx | 103 ++++ .../components/layerPopup/rswtFacility.tsx | 107 ++++ apps/wr/app/map/components/newWindowPopup.tsx | 186 ++++++ .../map/{ => components}/newWindowPortal.tsx | 0 apps/wr/app/map/mainMap/page.tsx | 17 +- apps/wr/app/map/newWindowPopup.tsx | 552 ------------------ apps/wr/app/map/popup/page.tsx | 2 +- 11 files changed, 597 insertions(+), 569 deletions(-) rename apps/wr/app/map/components/{ => layerPopup}/hasuFacility.tsx (94%) create mode 100644 apps/wr/app/map/components/layerPopup/htdflFacility.tsx create mode 100644 apps/wr/app/map/components/layerPopup/rainFacility.tsx rename apps/wr/app/map/components/{ => layerPopup}/reuseFacility.tsx (95%) create mode 100644 apps/wr/app/map/components/layerPopup/reuseMngFacility.tsx create mode 100644 apps/wr/app/map/components/layerPopup/rswtFacility.tsx create mode 100644 apps/wr/app/map/components/newWindowPopup.tsx rename apps/wr/app/map/{ => components}/newWindowPortal.tsx (100%) delete mode 100644 apps/wr/app/map/newWindowPopup.tsx diff --git a/apps/wr/app/map/components/hasuFacility.tsx b/apps/wr/app/map/components/layerPopup/hasuFacility.tsx similarity index 94% rename from apps/wr/app/map/components/hasuFacility.tsx rename to apps/wr/app/map/components/layerPopup/hasuFacility.tsx index d93d4c30a..5a10ce3ed 100644 --- a/apps/wr/app/map/components/hasuFacility.tsx +++ b/apps/wr/app/map/components/layerPopup/hasuFacility.tsx @@ -25,13 +25,13 @@ const HasuFacility = ({ seletectedLayerNm, viewMode, onClose, onFeatureSelect, i } }, [code, pbnstHasuRes]); useEffect(() => { - if (seletectedLayerNm !== '하수처리시설') return; + if (seletectedLayerNm !== '하수처리시설' || viewMode != 'list') return; if (!pbnst.isInit) { pubHasuInit("1", "0"); } }, [pbnst.isInit, pubHasuInit]); useEffect(() => { - if (seletectedLayerNm !== '하수처리시설') return; + if (seletectedLayerNm !== '하수처리시설' || viewMode != 'list') return; pubHasuInit(String(hasuPage), "0"); }, [hasuPage, pubHasuInit]); diff --git a/apps/wr/app/map/components/layerPopup/htdflFacility.tsx b/apps/wr/app/map/components/layerPopup/htdflFacility.tsx new file mode 100644 index 000000000..fe4bef25a --- /dev/null +++ b/apps/wr/app/map/components/layerPopup/htdflFacility.tsx @@ -0,0 +1,83 @@ +import { useWrStore } from '@/app/store'; +import type { customHtdflBdst } from '@/app/store/ruseFcltMng/fcltReg/htdflReg'; +import { tableData } from '@/components/lib/func/commom'; +import SttnDetailModal from '@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal'; +import { Title, UiTable } from '@common/business_components/ui'; +import { HeadMakeColSpan } from '@common/business_components/ui/table/HeadMakeColSpan'; +import React, { useEffect, useState } from 'react' +interface Props { + seletectedLayerNm: string; + viewMode: 'list' | 'detail' | 'nothing'; + onFeatureSelect: (feature: any) => void; + onClose: () => void; + htdflDetailModal: { isOpen: boolean; data: customHtdflBdst | undefined }; +} +const HtdflFacility = ({ seletectedLayerNm, viewMode, onFeatureSelect, onClose, htdflDetailModal }: Props) => { + const { code, htdflSttn, htdflSttnSearch, initHtdfl } = useWrStore((state) => state); + const { htdflSttnRes, htdflSttnResCount } = htdflSttn; + const [htdflTable, setHtdflTable] = useState(); + const [htdflPage, setHtdflPage] = useState(1) + // 온배수시설 cellClick 이벤트 (상세정보) + const htdflCellClick = (index: number | undefined) => { + if (index !== undefined && htdflTable !== undefined) { + onFeatureSelect(htdflTable[index]); + onClose(); + } + }; + useEffect(() => { + if (seletectedLayerNm !== '온배수시설' || viewMode != 'list') return; + if (!htdflSttn.isInit) { + initHtdfl(false, 1, "20", "0", '', code.cityCd, code.sggCd); + } + }, [code.cityCd, code.sggCd, htdflSttn.isInit, initHtdfl]); + + useEffect(() => { + if (seletectedLayerNm !== '온배수시설' || viewMode != 'list') return; + const data = tableData(htdflSttnRes, { code }); + const chData = data + .map((item: any) => ({ + ...item, + ...item.reuseBuilding[0], + ...item.reuseBuilding[0].htdflObj, + chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N", + })) + .filter((item: any) => item.htdflObj !== undefined); + setHtdflTable(chData); + }, [code, htdflSttnRes, htdflSttn.htdflSttHeadList]); + + useEffect(() => { + if (seletectedLayerNm !== '온배수시설' || viewMode != 'list') return; + htdflSttnSearch(true, htdflPage, "20", '', '', code.cityCd, code.sggCd); + }, [code.cityCd, code.sggCd, htdflPage, htdflSttnSearch]); + return ( + viewMode == 'detail' ? ( + + ) : ( +
+
+ + {/* <div className="flex gap-2 mt-8"> + <Button className={btnClass} color="primary" onClick={excelClick}> + 엑셀다운로드 + </Button> + </div> */} + </div> + <UiTable + headName={[]} + tableData={htdflTable || []} + headlist={htdflSttn.htdflSttHeadList} + pageSize={20} + total={htdflSttnResCount || 0} + cellClick={(index: number | undefined) => { + htdflCellClick(index ?? undefined); + }} + onPageChange={setHtdflPage} + > + <HeadMakeColSpan upHeadList={htdflSttn.htdflSttNodeList} /> + </UiTable> + </div> + ) + ) +} + +export default HtdflFacility \ No newline at end of file diff --git a/apps/wr/app/map/components/layerPopup/rainFacility.tsx b/apps/wr/app/map/components/layerPopup/rainFacility.tsx new file mode 100644 index 000000000..54b51e8a6 --- /dev/null +++ b/apps/wr/app/map/components/layerPopup/rainFacility.tsx @@ -0,0 +1,105 @@ +import { useWrStore } from "@/app/store"; +import type { customRainBdst } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; +import { tableData } from "@/components/lib/func/commom"; +import SttnDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal"; +import { Title, UiTable } from "@common/business_components/ui"; +import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; +import { useEffect, useState } from "react"; +interface Props { + seletectedLayerNm: string; + viewMode: 'list' | 'detail' | 'nothing'; + onFeatureSelect: (feature: any) => void; + onClose: () => void; + rainDetailModal: { isOpen: boolean; data: customRainBdst | undefined }; +} +const RainFacility = ({seletectedLayerNm,viewMode,onFeatureSelect,onClose,rainDetailModal}:Props) => { + // 빗물재이용시설 변수 + const { code, rainSttn, rainSttnSearch, rainSttnInit } = useWrStore( + (state) => state + ); + const { rainSttnRes, rainSttnResCount } = rainSttn; + const [rainTable, setRainTable] = useState<customRainBdst[]>(); + const [rainPage, setRainPage] = useState(1) + + // 빗물재이용시설 cellClick 이벤트 (상세정보) + const rainCellClick = (index: number | undefined, headId: string) => { + if ( + index !== undefined && + rainTable !== undefined && + headId !== 'chBuildingYn') { + onFeatureSelect(rainTable[index]); + onClose(); + } + } + useEffect(() => { + if (seletectedLayerNm !== '빗물이용시설' || viewMode != 'list') return; + console.log('빗물이용시설 초기값 호출', viewMode) + if (!rainSttn.isInit) { + rainSttnInit(false, 1, "20", "0", '', code.cityCd, code.sggCd); + } + }, [code.cityCd, code.sggCd, rainSttn.isInit, rainSttnInit]); + + useEffect(() => { + const data = tableData(rainSttnRes, { code }); + const chData = data + .map((item: any) => { + return { + ...item, + ...item.reuseBuilding[0], + ...item.reuseBuilding[0].rainyObj, + dtfcltCpct: item.reuseBuilding[0].rainyObj.dtfcltCpct === -1 + ? "확인불가" + : item.reuseBuilding[0].rainyObj.dtfcltCpct, + instlCst: item.reuseBuilding[0].instlCst === -1 + ? "확인불가" + : item.reuseBuilding[0].instlCst, + instlCstSprtAmt: item.reuseBuilding[0].instlCstSprtAmt === -1 + ? "확인불가" + : item.reuseBuilding[0].instlCstSprtAmt, + chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N", + pntfColArea: item.reuseBuilding[0].rainyObj.pntfColArea === -1 + ? "확인불가" + : item.reuseBuilding[0].rainyObj.pntfColArea, + filtrTrtmfctCpct: item.reuseBuilding[0].rainyObj.filtrTrtmfctCpct === -1 + ? "확인불가" + : item.reuseBuilding[0].rainyObj.filtrTrtmfctCpct, + archArea: Math.floor(item.archArea) === -1 ? "확인불가" : item.archArea, + }; + }) + .filter((item: any) => item.rainyObj !== undefined); + setRainTable(chData); + }, [code, rainSttnRes, rainSttn.rainSttHeadList]); + + useEffect(() => { + if (seletectedLayerNm !== '빗물이용시설' || viewMode != 'list') return; + rainSttnSearch(false, rainPage, "20", '', '', code.cityCd, code.sggCd); + }, [code.cityCd, code.sggCd, rainPage, rainSttnSearch]); + return (viewMode == 'detail' ? + (<SttnDetailModal type={"RNB"} formObj={rainDetailModal.data as customRainBdst} />) + : ( + <> + <div className="flex justify-between items-center"> + <Title title="빗물이용시설 현황" /> + {/* <div className="flex gap-2 mt-8"> + <Button className={btnClass} color="primary" onClick={excelClick}> + 엑셀다운로드 + </Button> + </div> */} + </div> + <UiTable + headName={[]} + tableData={rainTable || []} + headlist={rainSttn.rainSttHeadList} + pageSize={20} + total={rainSttnResCount} + cellClick={(index: number | undefined, headId: string) => { + rainCellClick(index ?? undefined, headId); + }} + onPageChange={setRainPage} + > + <HeadMakeColSpan upHeadList={rainSttn.rainSttNodeList} /> + </UiTable> + </> + )) +} +export default RainFacility; \ No newline at end of file diff --git a/apps/wr/app/map/components/reuseFacility.tsx b/apps/wr/app/map/components/layerPopup/reuseFacility.tsx similarity index 95% rename from apps/wr/app/map/components/reuseFacility.tsx rename to apps/wr/app/map/components/layerPopup/reuseFacility.tsx index dd18a657e..152d37999 100644 --- a/apps/wr/app/map/components/reuseFacility.tsx +++ b/apps/wr/app/map/components/layerPopup/reuseFacility.tsx @@ -40,14 +40,13 @@ const ResueFacility = ({ viewMode, seletectedLayerNm, tabData, actTabData, setFc // 재이용 현황 초기값 useEffect(() => { - console.log('재이용시설 초기값 호출', viewMode) - if (seletectedLayerNm !== '하수재이용시설') return; + if (seletectedLayerNm !== '하수재이용시설' || viewMode !='list') return; if (!pbnstSttn.isInit) { getFlcts() } }, [seletectedLayerNm, pbnstSttn.isInit]); useEffect(() => { - if (seletectedLayerNm !== '하수재이용시설') return; + if (seletectedLayerNm !== '하수재이용시설' || viewMode !='list') return; pbnSttnInit("1", "20", { partValue: "A", sidoValue: '0', @@ -67,7 +66,7 @@ const ResueFacility = ({ viewMode, seletectedLayerNm, tabData, actTabData, setFc // 재이용시설 조회 // 재이용시설 페이징처리 관련 함수 const onPageChangeHandler = useCallback((page: number) => { - if (seletectedLayerNm !== '하수재이용시설') return; + if (seletectedLayerNm !== '하수재이용시설' || viewMode !='list') return; pbnstSttnSearch( false, String(page), diff --git a/apps/wr/app/map/components/layerPopup/reuseMngFacility.tsx b/apps/wr/app/map/components/layerPopup/reuseMngFacility.tsx new file mode 100644 index 000000000..9030493ff --- /dev/null +++ b/apps/wr/app/map/components/layerPopup/reuseMngFacility.tsx @@ -0,0 +1,103 @@ +import { useWrStore } from '@/app/store'; +import { tableData, formatPhoneNumber } from '@/components/lib/func/commom'; +import { Title, UiTable } from '@common/business_components/ui'; +import type { DemandCompType } from '@common/business_components/ui/sdmInsertForm/form'; +import moment from 'moment'; +import React, { useEffect, useState } from 'react' +interface Props { + seletectedLayerNm: string; + viewMode: 'list' | 'detail' | 'nothing'; + onFeatureSelect: (feature: any) => void; + onClose: () => void; + isDmdModal: { data: any | undefined }; +} +const ReuseMngFacility = ({seletectedLayerNm, viewMode, onFeatureSelect, onClose, isDmdModal}:Props) => { + const { code, + coSttn, + coSttnInit, + coSttnSearch, + } = useWrStore((state) => state); + const [mngTable, setMngTable] = useState<DemandCompType[]>(); + const [mngPage, setMngPage] = useState(1); + const { coSttnRes, coSttnResCount, coSttHeadList } = coSttn; + useEffect(() => { + if (seletectedLayerNm !== '수요처' || viewMode != 'list') return; + if (!coSttn.isInit) { + coSttnInit("1", "20", "0"); + } + }, [coSttn.isInit, coSttnInit]); + const coMngCellClick = (index: number | undefined) => { + if (index !== undefined && mngTable !== undefined) { + onFeatureSelect(mngTable[index]); + onClose(); + } + }; + useEffect(() => { + if (seletectedLayerNm !== '수요처' || viewMode!='list') return; + if (coSttnRes) { + if (coSttnRes?.length > 0 && code.cityCd.length > 0) { + const data = tableData(coSttnRes, { code }); + const chData = data.map((item: any) => ({ + ...item, + telno: formatPhoneNumber(item.telno || undefined), + regDt: moment(item.regDt).format("YYYY-MM-DD"), + dmdInfoId: `${item.dmdInfoId.slice(0, 3)}-${item.dmdInfoId.slice(3, 5)}-${item.dmdInfoId.slice(5, 10)}-${item.dmdInfoId.slice(10)}` + })); + // .filter((item: any) => item !== undefined); + setMngTable(chData); + } + } + }, [coSttnRes, code]); + useEffect(() => { + if (seletectedLayerNm !== '수요처' || viewMode!='list') return; + coSttnSearch(String(mngPage), "20", String(0)); + }, [coSttnSearch, mngPage]); + return ( + viewMode == 'detail' + ? ( + <div style={{ overflowX: 'hidden' }}> + <div className="flex justify-between items-center mb-4"> + <Title title="수요처 상세정보" /> + </div> + {/* 높이 조정 */} + <UiTable + tableData={isDmdModal?.data || []} + headlist={coSttHeadList.slice(0, 3)} + pageSize={1} + total={0} + headName={"위치정보"} + cellClick={() => { }} + onPageChange={() => { }} + /> + <UiTable + tableData={isDmdModal?.data || []} + headlist={coSttHeadList.slice(3)} + pageSize={1} + total={0} + headName={"기본정보"} + cellClick={() => { }} + onPageChange={() => { }} + /> + </div> + ) : (<div> + <div className="flex justify-between items-center"> + <Title title="수요처 현황" /> + </div> + <UiTable + headName={[]} + tableData={mngTable || []} + headlist={coSttHeadList} + pageSize={20} + total={coSttnResCount + 10 || 0} + cellClick={(index: number | undefined) => { + coMngCellClick(index); + // setUpdateData(coSttnRes[index as number]); + }} + onPageChange={(page) => { + setMngPage(page); + }} + ></UiTable></div>) + ) +} + +export default ReuseMngFacility \ No newline at end of file diff --git a/apps/wr/app/map/components/layerPopup/rswtFacility.tsx b/apps/wr/app/map/components/layerPopup/rswtFacility.tsx new file mode 100644 index 000000000..dc2537c95 --- /dev/null +++ b/apps/wr/app/map/components/layerPopup/rswtFacility.tsx @@ -0,0 +1,107 @@ +import { useWrStore } from "@/app/store"; +import type { customRswtBdst } from "@/app/store/ruseFcltMng/fcltReg/rswtReg"; +import { tableData } from "@/components/lib/func/commom"; +import SttnDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal"; +import { Title, UiTable } from "@common/business_components/ui"; +import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; +import { useEffect, useState } from "react"; +interface Props { + seletectedLayerNm: string; + viewMode: 'list' | 'detail' | 'nothing'; + onFeatureSelect: (feature: any) => void; + onClose: () => void; + rswtrDetailModal: { isOpen: boolean; data: customRswtBdst | undefined }; +} +const RswtFacility = ({ seletectedLayerNm, viewMode, onFeatureSelect, onClose, rswtrDetailModal }: Props) => { + const { code, rswtSttn, rswtSttnSearch, rswtSttnInit } = useWrStore( + (state) => state + ); + const { rswtSttnRes, rswtSttnResCount } = rswtSttn; + const [rswtrTable, setRswtrTable] = useState<customRswtBdst[]>() + const [rswtrPage, setRswtrPage] = useState(1); + // 중수도시설 cellClick 이벤트 (상세정보) + const rswtCellClick = (index: number | undefined, headId: string) => { + if (headId !== 'chBuildingYn' && index !== undefined && rswtrTable !== undefined) { + // setRswtrDetailModal({ isOpen: true, data: rswtrTable[index] }); + // setViewMode('detail') + onFeatureSelect(rswtrTable[index]); + onClose(); + } + } + useEffect(() => { + if (seletectedLayerNm !== '중수도시설' || viewMode != 'list') return; + if (!rswtSttn.isInit) { + rswtSttnInit(false, 1, "20", "0", '', code.cityCd, code.sggCd); + } + }, [code.cityCd, code.sggCd, rswtSttn.isInit, rswtSttnInit]); + + useEffect(() => { + if (seletectedLayerNm !== '중수도시설' || viewMode != 'list') return; + const data = tableData(rswtSttnRes, { code }); + const chData = data + .map((item: customRswtBdst) => { + return { + ...item, + ...item.reuseBuilding[0], + ...item.reuseBuilding[0]?.rswtrObj, + chBuildingYn: item?.chgBuilding[0]?.bdstChgNm !== null && item?.chgBuilding.length > 0 ? 'Y' : 'N', + instlCst: item.reuseBuilding[0]?.instlCst === -1 ? "확인불가" : item.reuseBuilding[0]?.instlCst, + instlCstSprtAmt: item.reuseBuilding[0]?.instlCstSprtAmt === -1 ? "확인불가" : item.reuseBuilding[0]?.instlCstSprtAmt, + archArea: item?.archArea.split('.')[0] !== '-1' ? item?.archArea.split('.')[0] : '확인불가' + } + }) + .filter( + (item: customRswtBdst) => + item.reuseBuilding[0]?.rswtrObj !== undefined + ); + setRswtrTable(chData); + }, [code, rswtSttnRes]); + // FIXME: 윤선화 차장님 요구사항으로 엑셀 다운로드 삭제. + // const rswtSttnExcelDownload = useCallback(() => { + // if (rswtSttn.excelList.length > 0) { + // if (rswtSttn.excelList.length === 0) return; + // excelDownload( + // rswtSttn.excelList, + // "중수도시설 현황", + // "sample_rswtSttn.xlsx", + // "A1:W3", + // ); + // } + // }, [rswtSttn.excelList]) + + useEffect(() => { + if (seletectedLayerNm !== '중수도시설' || viewMode != 'list') return; + rswtSttnSearch(false, rswtrPage, "20", '', '', code.cityCd, code.sggCd); + }, [code.cityCd, code.sggCd, rswtSttnSearch, rswtrPage]); + return ( + viewMode == 'detail' ? + // 중수도시설 상세 + ( + <SttnDetailModal type={"RST"} formObj={rswtrDetailModal.data as customRswtBdst} /> + ) : ( + <div style={{ overflowY: 'auto' }}> + <div className="flex justify-between items-center"> + <Title title="중수도시설 현황" /> + {/* <div className="flex gap-2 mt-8"> + <Button className={btnClass} color="primary" onClick={rswtSttnExcelDownload}> + 엑셀다운로드 + </Button> + </div> */} + </div> + <UiTable + headName={[]} + tableData={rswtrTable || []} + headlist={rswtSttn.rswtSttHeadList} + pageSize={20} + total={rswtSttnResCount} + cellClick={(index: number | undefined, headId: string) => { + rswtCellClick(index, headId); + }} + onPageChange={setRswtrPage} + > + <HeadMakeColSpan upHeadList={rswtSttn.rswtSttNodeList} /> + </UiTable> + </div> + )) +} +export default RswtFacility; \ No newline at end of file diff --git a/apps/wr/app/map/components/newWindowPopup.tsx b/apps/wr/app/map/components/newWindowPopup.tsx new file mode 100644 index 000000000..bcc0e6ec3 --- /dev/null +++ b/apps/wr/app/map/components/newWindowPopup.tsx @@ -0,0 +1,186 @@ +"use client" +import NewWindowPortal from '@/app/map/components/newWindowPortal'; +import type { customHtdflBdst } from "@/app/store/ruseFcltMng/fcltReg/htdflReg"; +import type { pubHasuType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; +import type { customRainBdst } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; +import type { customRswtBdst } from "@/app/store/ruseFcltMng/fcltReg/rswtReg"; +import { useGisContext } from "@common/business_components/ui/map/Gis"; +import type { DemandCompType } from '@common/business_components/ui/sdmInsertForm/form'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import ResueFacility from './layerPopup/reuseFacility'; +import { detainSttn, fetchHasuDetail, fetchpbnstDetail } from '../util'; +import HasuFacility from './layerPopup/hasuFacility'; +import RainFacility from './layerPopup/rainFacility'; +import RswtFacility from './layerPopup/rswtFacility'; +import HtdflFacility from './layerPopup/htdflFacility'; +import ReuseMngFacility from './layerPopup/reuseMngFacility'; + +interface NewWindowPopupProps { + featureData: any; + seletectedLayerNm?: string + viewMode?: 'list' | 'detail' | 'nothing' + onFeatureSelect: (data: any) => void; + onClose: () => void; +} + +const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ + featureData, + seletectedLayerNm, + onFeatureSelect, + onClose +}) => { + const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject<number[]>; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; }; + const [tabData, setTabData] = useState<{ id: string; name: string; data: any }[]>([]); + const [activeTab, setActiveTab] = useState<string>('0'); + const [viewMode, setViewMode] = useState<'list' | 'detail' | 'nothing'>('list'); + const [fcltPk, setFcltPk] = useState<string>(''); + + + // onClose 함수를 useCallback으로 메모이제이션 + const memoizedOnClose = useCallback(() => { + onClose(); + }, [onClose]); + + // 빗물재이용시설 변수 + const [rainDetailModal, setRainDetailModal] = useState<{ isOpen: boolean, data: customRainBdst | undefined }>({ data: undefined, isOpen: false }); + const [rainList, setRainList] = useState<customRainBdst[]>([]); + + // 중수도시설 변수 + const [rswtrDetailModal, setRswtrDetailModal] = useState<{ isOpen: boolean, data: customRswtBdst | undefined }>({ data: undefined, isOpen: false }); + const [rswtrList, setRswtrList] = useState<customRswtBdst[]>([]); + + // 온배수시설 변수 + const [htdflDetailModal, setHtdflDetailModal] = useState<{ isOpen: boolean, data: customHtdflBdst | undefined }>({ data: undefined, isOpen: false }); + const [htdflList, setHtdflList] = useState<customHtdflBdst[]>([]); + + // 하수처리시설 변수 + const [hasuList, setHasuList] = useState<pubHasuType[]>([]); + const [isRuseReg, setIsRuseReg] = useState<{ + data: any | undefined; + }>({ data: undefined }); + + // 수요처관리 변수 + const [isDmdModal, setIsDmdModal] = useState<{ + isMoal: boolean; + data: any | undefined; + }>({ isMoal: false, data: undefined }); + const [mngList, setMngList] = useState<DemandCompType[]>([]); + + ///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 /////////////////////////////////////////////// + const getSelectedLayers = () => { + console.trace('호출확인', 1) + let result = null; + const layers = gisMap ? gisMap.getLayers().getArray().filter((l: any) => l.getVisible()) : []; // gisMap이 null인지 확인 후 레이어 가져오기 + let featureLayer: { get: (key: string) => any } = { get: () => null }; + layers.forEach((layer: any) => { + const source = layer.getSource(); + if (source && source.hasFeature && featureData + && typeof featureData.getId === 'function' && source.hasFeature(featureData)) { + featureLayer = layer; // 피처가 속한 레이어 찾기) + } + }); + result = featureLayer.get('lyrNm'); + return result; + }; + // 상세 목록 데이터 상태 처리 + useEffect(() => { + if (hasuList.length > 0) setIsRuseReg({ data: [hasuList[0]] }); + }, [hasuList]) + useEffect(() => { + if (rainList.length > 0) setRainDetailModal({ isOpen: true, data: rainList[0] }); + }, [rainList]) + useEffect(() => { + if (rswtrList.length > 0) setRswtrDetailModal({ isOpen: true, data: rswtrList[0] }); + }, [rswtrList]) + useEffect(() => { + if (htdflList.length > 0) setHtdflDetailModal({ isOpen: true, data: htdflList[0] }); + }, [htdflList]) + useEffect(() => { + if (mngList.length > 0) setIsDmdModal({ isMoal: true, data: [mngList[0]] }); + }, [mngList]) + + const lastCalledIdRef = useRef<string | null>(null); + const [layerId, setLayerId] = useState<string | null>(); + + useEffect(() => { + const layers = getSelectedLayers(); + setLayerId(layers); + }, []); // 최초 렌더링 시 호출 + + // 피쳐 데이터가 변경될 때마다 호출 + useEffect(() => { + if (!featureData || typeof featureData.getProperties !== "function" || featureData.getProperties() == '') return; + const selectedProps = featureData.getProperties(); + const currentId = featureData.getId?.() || selectedProps?.id || selectedProps?.fid; + // 유효하지 않은 ID는 아예 무시 + if (!currentId || typeof currentId !== 'string') return; + // 이전과 동일하면 무시 + if (!currentId || currentId === lastCalledIdRef.current) return; + lastCalledIdRef.current = currentId // 새로운 피처일 때만 API 호출 + const layerId = getSelectedLayers?.(); + if (!layerId || typeof layerId !== 'string') return; + const featureCd = + layerId === '하수재이용시설' ? selectedProps?.swtrmt_fclt_cd : + ['빗물이용시설', '중수도시설', '온배수시설', '수요처'].includes(layerId) ? selectedProps?.bdst_nm : + layerId === '하수처리시설' ? selectedProps?.swtrmt_fclt_nm : + null; + if (!featureCd || featureCd === fcltPk) return; + setFcltPk(featureCd); + setActiveTab(featureCd); + setViewMode("detail"); + if (layerId === '하수재이용시설') { + fetchpbnstDetail(featureCd, setTabData); + } else if (layerId === '하수처리시설') { + fetchHasuDetail(featureCd, selectedProps?.swtrmt_fclt_cd).then((data) => { setHasuList(data) }) + } else { + detainSttn(featureCd, layerId).then((res) => { + if (layerId === '빗물이용시설') setRainList(res); + else if (layerId === '중수도시설') setRswtrList(res); + else if (layerId === '온배수시설') { setHtdflList(res); setHtdflDetailModal({ isOpen: true, data: res[0] }); } + else if (layerId === '수요처') setMngList(res); + }); + } + }, [featureData]); + ///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 /////////////////////////////////////////////// + + // 체크된 레이어에 따른 조건부 컴포넌트 렌더링 함수 + const renderConditionalContent = () => { + const actTabData = tabData.find((tab) => tab.id === activeTab); + if (layerId === null || layerId === undefined) { + setLayerId(seletectedLayerNm || ''); + } + switch (layerId) { + case '하수재이용시설': + return (<ResueFacility viewMode={viewMode} seletectedLayerNm={layerId} tabData={tabData} actTabData={actTabData} setFcltPk={setFcltPk} onFeatureSelect={onFeatureSelect} onClose={onClose} />) + case '하수처리시설': + return (<HasuFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} isRuseReg={isRuseReg} />) + case '빗물이용시설': + return (<RainFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} rainDetailModal={rainDetailModal} />) + case '중수도시설': + return (<RswtFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} rswtrDetailModal={rswtrDetailModal} />) + case '온배수시설': + return (<HtdflFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} htdflDetailModal={htdflDetailModal} />) + case '수요처': + return(<ReuseMngFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} isDmdModal={isDmdModal} />) + default: + break; + } + return null; // 조건에 맞는 콘텐츠가 없을 경우 + }; + + return ( + viewMode == 'nothing' ? <></> : + <NewWindowPortal + onClose={memoizedOnClose} + title="시설 상세 정보" + width={viewMode == 'list' ? window.screen.availWidth : 1100} + height={viewMode == 'list' ? window.screen.availHeight : layerId == '하수처리시설' && viewMode == 'detail' ? 450 : 900} + scrollbars='yes' + resizable='no'> + {/* 체크된 레이어에 따른 조건부 렌더링 */} + {renderConditionalContent()} + </NewWindowPortal> + ); +}; + +export default NewWindowPopup; \ No newline at end of file diff --git a/apps/wr/app/map/newWindowPortal.tsx b/apps/wr/app/map/components/newWindowPortal.tsx similarity index 100% rename from apps/wr/app/map/newWindowPortal.tsx rename to apps/wr/app/map/components/newWindowPortal.tsx diff --git a/apps/wr/app/map/mainMap/page.tsx b/apps/wr/app/map/mainMap/page.tsx index 56c9e823d..96785f8b3 100644 --- a/apps/wr/app/map/mainMap/page.tsx +++ b/apps/wr/app/map/mainMap/page.tsx @@ -3,7 +3,7 @@ import Gis from "@common/business_components/ui/map/Gis"; import { useEffect, useRef, useState, useCallback } from "react"; import { mapService } from "../service"; -import NewWindowPopup from "../newWindowPopup"; +import NewWindowPopup from "../components/newWindowPopup"; import GisApp from "@common/business_components/ui/map/lib/gis.core"; const Map = () => { @@ -61,30 +61,27 @@ const Map = () => { // 2. data가 OpenLayers Feature 인스턴스인지 확인 let foundFeature: any = null; - console.log(data) - //console.log(data.getId()) let featureId = data.fid; const layerName = data.layerNm; - - if (layerName === "재이용시설") { + console.log("선택된 레이어 이름:", data, layerName); + if (layerName === "하수재이용시설") { // 재이용시설 featureId = `${layerName}.${data.searchPk}`; } else if ( //빗물이용시설, 중수도시설 layerName === "빗물이용시설" || - layerName == "tb_wr_rswtr_fclt" + layerName == "중수도시설" ) { featureId = `${layerName}.${data.bdstInfoSn}`; - } else if (layerName === "tb_il_swage_prcs_fclt") { //하수처리시설 + } else if (layerName === "하수처리시설") { //하수처리시설 featureId = `${layerName}.${data.bscFctCd}`; - } else if (layerName === "tb_wr_htdfl_fclt") { //온배수시설 + } else if (layerName === "온배수시설") { //온배수시설 featureId = `${layerName}.${data.bdstRuseFcltSn}`; - } else if (layerName == "tb_wr_ruse_fclt_dmd_co") { //수요처 + } else if (layerName == "수요처") { //수요처 featureId = `${layerName}.${data.dmdInfoId.replaceAll("-", "")}`; } else { console.warn("해당 레이어에 대한 featureId 매핑 없음", layerName); featureId = `${layerName}.${data.fid || data.id || data.ftrIdn}`; } - map .getLayers() .getArray() diff --git a/apps/wr/app/map/newWindowPopup.tsx b/apps/wr/app/map/newWindowPopup.tsx deleted file mode 100644 index d8e6b6338..000000000 --- a/apps/wr/app/map/newWindowPopup.tsx +++ /dev/null @@ -1,552 +0,0 @@ -"use client" -import NewWindowPortal from '@/app/map/newWindowPortal'; -import { useWrStore } from "@/app/store"; -import type { customHtdflBdst } from "@/app/store/ruseFcltMng/fcltReg/htdflReg"; -import type { pubHasuType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; -import type { customRainBdst } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; -import type { customRswtBdst } from "@/app/store/ruseFcltMng/fcltReg/rswtReg"; -import { formatPhoneNumber, tableData } from "@/components/lib/func/commom"; -import SttnDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal"; -import { Title, UiTable } from '@common/business_components'; -import { useGisContext } from "@common/business_components/ui/map/Gis"; -import type { DemandCompType } from '@common/business_components/ui/sdmInsertForm/form'; -import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; -import moment from 'moment'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import ResueFacility from './components/reuseFacility'; -import { detainSttn, fetchHasuDetail, fetchpbnstDetail } from './util'; -import HasuFacility from './components/hasuFacility'; - -interface NewWindowPopupProps { - featureData: any; - seletectedLayerNm?: string - viewMode?: 'list' | 'detail' | 'nothing' - onFeatureSelect: (data: any) => void; - onClose: () => void; -} - -const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ - featureData, - seletectedLayerNm, - onFeatureSelect, - onClose -}) => { - const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject<number[]>; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; }; - const [tabData, setTabData] = useState<{ id: string; name: string; data: any }[]>([]); - const [activeTab, setActiveTab] = useState<string>('0'); - const [viewMode, setViewMode] = useState<'list' | 'detail' | 'nothing'>('list'); - const [fcltPk, setFcltPk] = useState<string>(''); - - - // onClose 함수를 useCallback으로 메모이제이션 - const memoizedOnClose = useCallback(() => { - onClose(); - }, [onClose]); - - // 하수재이용시설 변수 - const {code} = useWrStore((state) => state); - - // 빗물재이용시설 변수 - const { rainSttn, rainSttnSearch, rainSttnInit } = useWrStore( - (state) => state - ); - const { rainSttnRes, rainSttnResCount } = rainSttn; - const [rainTable, setRainTable] = useState<customRainBdst[]>(); - const [rainPage, setRainPage] = useState(1) - const [rainDetailModal, setRainDetailModal] = useState<{ isOpen: boolean, data: customRainBdst | undefined }>({ data: undefined, isOpen: false }); - const [rainList, setRainList] = useState<customRainBdst[]>(rainSttnRes || []); - - // 중수도시설 변수 - const { rswtSttn, rswtSttnSearch, rswtSttnInit } = useWrStore( - (state) => state - ); - const { rswtSttnRes, rswtSttnResCount } = rswtSttn; - const [rswtrTable, setRswtrTable] = useState<customRswtBdst[]>(); - const [rswtrDetailModal, setRswtrDetailModal] = useState<{ isOpen: boolean, data: customRswtBdst | undefined }>({ data: undefined, isOpen: false }); - const [rswtrPage, setRswtrPage] = useState(1); - const [rswtrList, setRswtrList] = useState<customRswtBdst[]>(rswtSttnRes || []); - - // 온배수시설 변수 - const { htdflSttn, htdflSttnSearch, initHtdfl } = useWrStore((state) => state); - const { htdflSttnRes, htdflSttnResCount } = htdflSttn; - const [htdflTable, setHtdflTable] = useState<customHtdflBdst[]>(); - const [htdflDetailModal, setHtdflDetailModal] = useState<{ isOpen: boolean, data: customHtdflBdst | undefined }>({ data: undefined, isOpen: false }); - const [htdflPage, setHtdflPage] = useState(1) - const [htdflList, setHtdflList] = useState<customHtdflBdst[]>(htdflSttnRes || []); - - // 하수처리시설 변수 - const [hasuList, setHasuList] = useState<pubHasuType[]>([]); - const [isRuseReg, setIsRuseReg] = useState<{ - data: any | undefined; - }>({ data: undefined }); - - // 수요처관리 변수 - const { - coSttn, - coSttnInit, - coSttnSearch, - } = useWrStore((state) => state); - const [mngTable, setMngTable] = useState<DemandCompType[]>(); - const [mngPage, setMngPage] = useState(1); - const { coSttnRes, coSttnResCount, coSttHeadList } = coSttn; - const [isDmdModal, setIsDmdModal] = useState<{ - isMoal: boolean; - data: any | undefined; - }>({ isMoal: false, data: undefined }); - const [mngList, setMngList] = useState<DemandCompType[]>(coSttnRes || []); - ///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 /////////////////////////////////////////////// - const getSelectedLayers = () => { - console.trace('호출확인', 1) - let result = null; - const layers = gisMap ? gisMap.getLayers().getArray().filter((l: any) => l.getVisible()) : []; // gisMap이 null인지 확인 후 레이어 가져오기 - let featureLayer: { get: (key: string) => any } = { get: () => null }; - layers.forEach((layer: any) => { - const source = layer.getSource(); - if (source && source.hasFeature && featureData - && typeof featureData.getId === 'function' && source.hasFeature(featureData)) { - featureLayer = layer; // 피처가 속한 레이어 찾기) - } - }); - result = featureLayer.get('lyrNm'); - return result; - }; - // 상세 목록 데이터 상태 처리 - useEffect(() => { - if (hasuList.length > 0) setIsRuseReg({ data: [hasuList[0]] }); - }, [hasuList]) - useEffect(() => { - if (rainList.length > 0) setRainDetailModal({ isOpen: true, data: rainList[0] }); - }, [rainList]) - useEffect(() => { - if (rswtrList.length > 0) setRswtrDetailModal({ isOpen: true, data: rswtrList[0] }); - }, [rswtrList]) - useEffect(() => { - if (htdflList.length > 0) setHtdflDetailModal({ isOpen: true, data: htdflList[0] }); - }, [htdflList]) - useEffect(() => { - if (mngList.length > 0) setIsDmdModal({ isMoal: true, data: [mngList[0]] }); - }, [mngList]) - const lastCalledIdRef = useRef<string | null>(null); - const [layerId, setLayerId] = useState<string | null>(); - useEffect(() => { - const layers = getSelectedLayers(); - setLayerId(layers); - }, []); // 최초 렌더링 시 호출 - useEffect(() => { - if (!featureData || typeof featureData.getProperties !== "function" || featureData.getProperties() == '') return; - const selectedProps = featureData.getProperties(); - const currentId = featureData.getId?.() || selectedProps?.id || selectedProps?.fid; - // 유효하지 않은 ID는 아예 무시 - if (!currentId || typeof currentId !== 'string') return; - // 이전과 동일하면 무시 - if (!currentId || currentId === lastCalledIdRef.current) return; - lastCalledIdRef.current = currentId // 새로운 피처일 때만 API 호출 - const layerId = getSelectedLayers?.(); - if (!layerId || typeof layerId !== 'string') return; - const featureCd = - layerId === '하수재이용시설' ? selectedProps?.swtrmt_fclt_cd : - ['빗물이용시설', '중수도시설', '온배수시설', '수요처'].includes(layerId) ? selectedProps?.bdst_nm : - layerId === '하수처리시설' ? selectedProps?.swtrmt_fclt_nm : - null; - if (!featureCd || featureCd === fcltPk) return; - setFcltPk(featureCd); - setActiveTab(featureCd); - setViewMode("detail"); - if (layerId === '하수재이용시설') { - fetchpbnstDetail(featureCd, setTabData); - } else if (layerId === '하수처리시설') { - fetchHasuDetail(featureCd, selectedProps?.swtrmt_fclt_cd).then((data) => { setHasuList(data) }) - } else { - detainSttn(featureCd, layerId).then((res) => { - if (layerId === '빗물이용시설') setRainList(res); - else if (layerId === '중수도시설') setRswtrList(res); - else if (layerId === '온배수시설'){ setHtdflList(res); setHtdflDetailModal({ isOpen: true, data: htdflSttnRes[0] });} - else if (layerId === '수요처') setMngList(res); - }); - } - }, [featureData]); - ///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 /////////////////////////////////////////////// - - ///////////////////////////////////////////// 빗물 재이용 ////////////////////////////////////////////////// - // 빗물재이용시설 cellClick 이벤트 (상세정보) - const rainCellClick = (index: number | undefined, headId: string) => { - if ( - index !== undefined && - rainTable !== undefined && - headId !== 'chBuildingYn') { - // setRainDetailModal({ isOpen: true, data: rainTable[index] }) - // setViewMode('detail') - onFeatureSelect(rainTable[index]); - onClose(); - } - } - - - useEffect(() => { - if (seletectedLayerNm !== '빗물이용시설') return; - if (!rainSttn.isInit) { - rainSttnInit(false, 1, "20", "0", '', code.cityCd, code.sggCd); - } - }, [code.cityCd, code.sggCd, rainSttn.isInit, rainSttnInit]); - - useEffect(() => { - const data = tableData(rainSttnRes, { code }); - const chData = data - .map((item: any) => { - return { - ...item, - ...item.reuseBuilding[0], - ...item.reuseBuilding[0].rainyObj, - dtfcltCpct: item.reuseBuilding[0].rainyObj.dtfcltCpct === -1 - ? "확인불가" - : item.reuseBuilding[0].rainyObj.dtfcltCpct, - instlCst: item.reuseBuilding[0].instlCst === -1 - ? "확인불가" - : item.reuseBuilding[0].instlCst, - instlCstSprtAmt: item.reuseBuilding[0].instlCstSprtAmt === -1 - ? "확인불가" - : item.reuseBuilding[0].instlCstSprtAmt, - chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N", - pntfColArea: item.reuseBuilding[0].rainyObj.pntfColArea === -1 - ? "확인불가" - : item.reuseBuilding[0].rainyObj.pntfColArea, - filtrTrtmfctCpct: item.reuseBuilding[0].rainyObj.filtrTrtmfctCpct === -1 - ? "확인불가" - : item.reuseBuilding[0].rainyObj.filtrTrtmfctCpct, - archArea: Math.floor(item.archArea) === -1 ? "확인불가" : item.archArea, - }; - }) - .filter((item: any) => item.rainyObj !== undefined); - setRainTable(chData); - }, [code, rainSttnRes, rainSttn.rainSttHeadList]); - - useEffect(() => { - if (seletectedLayerNm !== '빗물이용시설') return; - rainSttnSearch(false, rainPage, "20", '', '', code.cityCd, code.sggCd); - }, [code.cityCd, code.sggCd, rainPage, rainSttnSearch]); - ///////////////////////////////////////////// 빗물 재이용 ////////////////////////////////////////////////// - - ///////////////////////////////////////////// 중수도 시설 ////////////////////////////////////////////////// - // 중수도시설 cellClick 이벤트 (상세정보) - const rswtCellClick = (index: number | undefined, headId: string) => { - if (headId !== 'chBuildingYn' && index !== undefined && rswtrTable !== undefined) { - // setRswtrDetailModal({ isOpen: true, data: rswtrTable[index] }); - // setViewMode('detail') - onFeatureSelect(rswtrTable[index]); - onClose(); - } - } - useEffect(() => { - if (seletectedLayerNm !== '중수도시설') return; - if (!rswtSttn.isInit) { - rswtSttnInit(false, 1, "20", "0", '', code.cityCd, code.sggCd); - } - }, [code.cityCd, code.sggCd, rswtSttn.isInit, rswtSttnInit]); - - useEffect(() => { - if (seletectedLayerNm !== '중수도시설') return; - const data = tableData(rswtSttnRes, { code }); - const chData = data - .map((item: customRswtBdst) => { - return { - ...item, - ...item.reuseBuilding[0], - ...item.reuseBuilding[0]?.rswtrObj, - chBuildingYn: item?.chgBuilding[0]?.bdstChgNm !== null && item?.chgBuilding.length > 0 ? 'Y' : 'N', - instlCst: item.reuseBuilding[0]?.instlCst === -1 ? "확인불가" : item.reuseBuilding[0]?.instlCst, - instlCstSprtAmt: item.reuseBuilding[0]?.instlCstSprtAmt === -1 ? "확인불가" : item.reuseBuilding[0]?.instlCstSprtAmt, - archArea: item?.archArea.split('.')[0] !== '-1' ? item?.archArea.split('.')[0] : '확인불가' - } - }) - .filter( - (item: customRswtBdst) => - item.reuseBuilding[0]?.rswtrObj !== undefined - ); - setRswtrTable(chData); - }, [code, rswtSttnRes]); - // FIXME: 윤선화 차장님 요구사항으로 엑셀 다운로드 삭제. - // const rswtSttnExcelDownload = useCallback(() => { - // if (rswtSttn.excelList.length > 0) { - // if (rswtSttn.excelList.length === 0) return; - // excelDownload( - // rswtSttn.excelList, - // "중수도시설 현황", - // "sample_rswtSttn.xlsx", - // "A1:W3", - // ); - // } - // }, [rswtSttn.excelList]) - - useEffect(() => { - if (seletectedLayerNm !== '중수도시설') return; - rswtSttnSearch(false, rswtrPage, "20", '', '', code.cityCd, code.sggCd); - }, [code.cityCd, code.sggCd, rswtSttnSearch, rswtrPage]); - ///////////////////////////////////////////// 중수도 시설 ////////////////////////////////////////////////// - - ///////////////////////////////////////////// 온배수 시설 ////////////////////////////////////////////////// - // 온배수시설 cellClick 이벤트 (상세정보) - const htdflCellClick = (index: number | undefined) => { - if (index !== undefined && htdflTable !== undefined) { - onFeatureSelect(htdflTable[index]); - onClose(); - } - }; - useEffect(() => { - if (seletectedLayerNm !== '온배수시설') return; - if (!htdflSttn.isInit) { - initHtdfl(false, 1, "20", "0", '', code.cityCd, code.sggCd); - } - }, [code.cityCd, code.sggCd, htdflSttn.isInit, initHtdfl]); - - useEffect(() => { - if (seletectedLayerNm !== '온배수시설') return; - const data = tableData(htdflSttnRes, { code }); - const chData = data - .map((item: any) => ({ - ...item, - ...item.reuseBuilding[0], - ...item.reuseBuilding[0].htdflObj, - chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N", - })) - .filter((item: any) => item.htdflObj !== undefined); - setHtdflTable(chData); - }, [code, htdflSttnRes, htdflSttn.htdflSttHeadList]); - - useEffect(() => { - if (seletectedLayerNm !== '온배수시설') return; - htdflSttnSearch(true, htdflPage, "20", '', '', code.cityCd, code.sggCd); - }, [code.cityCd, code.sggCd, htdflPage, htdflSttnSearch]); - ///////////////////////////////////////////// 온배수 시설 ////////////////////////////////////////////////// - - ///////////////////////////////////////////// 수요처 관리 ////////////////////////////////////////////////// - useEffect(() => { - if (seletectedLayerNm !== '수요처') return; - if (!coSttn.isInit) { - coSttnInit("1", "20", "0"); - } - }, [coSttn.isInit, coSttnInit]); - const coMngCellClick = (index: number | undefined) => { - if (index !== undefined && mngTable !== undefined) { - onFeatureSelect(mngTable[index]); - onClose(); - } - }; - useEffect(() => { - if (seletectedLayerNm !== '수요처') return; - if (coSttnRes) { - if (coSttnRes?.length > 0 && code.cityCd.length > 0) { - const data = tableData(coSttnRes, { code }); - const chData = data.map((item: any) => ({ - ...item, - telno: formatPhoneNumber(item.telno || undefined), - regDt: moment(item.regDt).format("YYYY-MM-DD"), - dmdInfoId: `${item.dmdInfoId.slice(0, 3)}-${item.dmdInfoId.slice(3, 5)}-${item.dmdInfoId.slice(5, 10)}-${item.dmdInfoId.slice(10)}` - })); - // .filter((item: any) => item !== undefined); - setMngTable(chData); - } - } - }, [coSttnRes, code]); - useEffect(() => { - if (seletectedLayerNm !== '수요처') return; - coSttnSearch(String(mngPage), "20", String(0)); - }, [coSttnSearch, mngPage]); - ///////////////////////////////////////////// 수요처 관리 ////////////////////////////////////////////////// - - // 체크된 레이어에 따른 조건부 컴포넌트 렌더링 함수 - const renderConditionalContent = () => { - const actTabData = tabData.find((tab) => tab.id === activeTab); - if (layerId === null || layerId === undefined) { - setLayerId(seletectedLayerNm || ''); - } - // 빗물이용시설 - const isRain = layerId == '빗물이용시설'; - // 중수도시설 - const isRswt = layerId == '중수도시설'; - // 온배수시설 - const isHtdfl = layerId == '온배수시설'; - // 수요처관리 - const isReuseMng = layerId == '수요처'; - switch (layerId) { - case '하수재이용시설': - return (<ResueFacility viewMode={viewMode} seletectedLayerNm={layerId} tabData={tabData} actTabData={actTabData} setFcltPk={setFcltPk} onFeatureSelect={onFeatureSelect} onClose={onClose} />) - case '하수처리시설': - return(<HasuFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} isRuseReg={isRuseReg}/>) - default: - break; - } - if (isRain) { - if (viewMode == 'detail') { - // 빗물이용시설 상세 - return ( - <SttnDetailModal type={"RNB"} formObj={rainDetailModal.data as customRainBdst} /> - ); - } else { - // 빗물이용시설 목록 - return ( - <> - <div className="flex justify-between items-center"> - <Title title="빗물이용시설 현황" /> - {/* <div className="flex gap-2 mt-8"> - <Button className={btnClass} color="primary" onClick={excelClick}> - 엑셀다운로드 - </Button> - </div> */} - </div> - <UiTable - headName={[]} - tableData={rainTable || []} - headlist={rainSttn.rainSttHeadList} - pageSize={20} - total={rainSttnResCount} - cellClick={(index: number | undefined, headId: string) => { - rainCellClick(index ?? undefined, headId); - }} - onPageChange={setRainPage} - > - <HeadMakeColSpan upHeadList={rainSttn.rainSttNodeList} /> - </UiTable> - </> - ); - } - } - - if (isRswt) { - if (viewMode == 'detail') { - // 중수도시설 상세 - return ( - <SttnDetailModal type={"RST"} formObj={rswtrDetailModal.data as customRswtBdst} /> - ); - } else { - // 중수도시설 목록 - return ( - <div style={{ overflowY: 'auto' }}> - <div className="flex justify-between items-center"> - <Title title="중수도시설 현황" /> - {/* <div className="flex gap-2 mt-8"> - <Button className={btnClass} color="primary" onClick={rswtSttnExcelDownload}> - 엑셀다운로드 - </Button> - </div> */} - </div> - <UiTable - headName={[]} - tableData={rswtrTable || []} - headlist={rswtSttn.rswtSttHeadList} - pageSize={20} - total={rswtSttnResCount} - cellClick={(index: number | undefined, headId: string) => { - rswtCellClick(index, headId); - }} - onPageChange={setRswtrPage} - > - <HeadMakeColSpan upHeadList={rswtSttn.rswtSttNodeList} /> - </UiTable> - </div> - ); - } - } - - if (isHtdfl) { - if (viewMode == 'detail') { - // 온배수시설 상세 - return ( - <SttnDetailModal type={"HTB"} formObj={htdflDetailModal.data as customHtdflBdst} /> - ); - } else { - // 온배수시설 목록 - return ( - <div style={{ overflowY: 'auto' }}> - <div className="flex justify-between items-center"> - <Title title="온배수시설 현황" /> - {/* <div className="flex gap-2 mt-8"> - <Button className={btnClass} color="primary" onClick={excelClick}> - 엑셀다운로드 - </Button> - </div> */} - </div> - <UiTable - headName={[]} - tableData={htdflTable || []} - headlist={htdflSttn.htdflSttHeadList} - pageSize={20} - total={htdflSttnResCount || 0} - cellClick={(index: number | undefined) => { - htdflCellClick(index ?? undefined); - }} - onPageChange={setHtdflPage} - > - <HeadMakeColSpan upHeadList={htdflSttn.htdflSttNodeList} /> - </UiTable> - </div> - ); - } - } - if (isReuseMng) { - if (viewMode == 'detail') { - return ( - <div style={{ overflowX: 'hidden' }}> - <div className="flex justify-between items-center mb-4"> - <Title title="수요처 상세정보" /> - </div> - {/* 높이 조정 */} - <UiTable - tableData={isDmdModal?.data || []} - headlist={coSttHeadList.slice(0, 3)} - pageSize={1} - total={0} - headName={"위치정보"} - cellClick={() => { }} - onPageChange={() => { }} - /> - <UiTable - tableData={isDmdModal?.data || []} - headlist={coSttHeadList.slice(3)} - pageSize={1} - total={0} - headName={"기본정보"} - cellClick={() => { }} - onPageChange={() => { }} - /> - </div> - ) - } else if (viewMode == 'list') { - return (<div> - <div className="flex justify-between items-center"> - <Title title="수요처 현황" /> - </div> - <UiTable - headName={[]} - tableData={mngTable || []} - headlist={coSttHeadList} - pageSize={20} - total={coSttnResCount + 10 || 0} - cellClick={(index: number | undefined) => { - coMngCellClick(index); - // setUpdateData(coSttnRes[index as number]); - }} - onPageChange={(page) => { - setMngPage(page); - }} - ></UiTable></div>) - } - } - return null; // 조건에 맞는 콘텐츠가 없을 경우 - }; - - return ( - viewMode == 'nothing' ? <></> : - <NewWindowPortal - onClose={memoizedOnClose} - title="시설 상세 정보" - width={viewMode == 'list' ? window.screen.availWidth : 1100} - height={viewMode == 'list' ? window.screen.availHeight : layerId == '하수처리시설' && viewMode == 'detail' ? 450 : 900} - scrollbars='yes' - resizable='no'> - {/* 체크된 레이어에 따른 조건부 렌더링 */} - {renderConditionalContent()} - </NewWindowPortal> - ); -}; - -export default NewWindowPopup; \ No newline at end of file diff --git a/apps/wr/app/map/popup/page.tsx b/apps/wr/app/map/popup/page.tsx index 80830aa90..c7b23461f 100644 --- a/apps/wr/app/map/popup/page.tsx +++ b/apps/wr/app/map/popup/page.tsx @@ -3,7 +3,7 @@ import Gis from "@common/business_components/ui/map/Gis"; import { useEffect, useRef, useState, useCallback } from "react"; import { mapService } from "../service"; -import NewWindowPopup from "../newWindowPopup"; +import NewWindowPopup from "../components/newWindowPopup"; import GisApp from "@common/business_components/ui/map/lib/gis.core"; const Map = () => { From 416677dcf444a02a26067816bc36f64f35f0def2 Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Thu, 31 Jul 2025 17:08:28 +0900 Subject: [PATCH 40/71] =?UTF-8?q?feat,=20refactor:=20-=20=EC=A4=91?= =?UTF-8?q?=EC=A0=90=EC=9A=B0=EC=88=98=ED=86=A0=EC=8B=A4=20=EC=9C=84?= =?UTF-8?q?=EA=B2=BD=EB=8F=84=20=EC=86=8C=EC=88=98=EC=A0=90=20=EC=95=84?= =?UTF-8?q?=EB=9E=98=20=EB=84=A4=EC=9E=90=EB=A6=AC=EA=B9=8C=EC=A7=80?= =?UTF-8?q?=EB=A7=8C=20=ED=91=9C=EC=8B=9C=20-=20=EC=A4=91=EC=A0=90?= =?UTF-8?q?=EC=9A=B0=EC=88=98=ED=86=A0=EC=8B=A4=20=EC=9C=84=EA=B2=BD?= =?UTF-8?q?=EB=8F=84=20=EB=B3=80=EC=88=98=20=ED=83=80=EC=9E=85=20=EC=84=A0?= =?UTF-8?q?=EC=96=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/(map-layout)/map/components/common/index.ts | 2 ++ .../app/(map-layout)/map/components/common/table.ts | 3 ++- .../popupComponent/featureClickEventPopup.tsx | 11 +++-------- .../app/components/lib/utils/formatDisplayValue.tsx | 13 +++++++++---- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/apps/sn/app/(map-layout)/map/components/common/index.ts b/apps/sn/app/(map-layout)/map/components/common/index.ts index 9860b211a..e328cf983 100644 --- a/apps/sn/app/(map-layout)/map/components/common/index.ts +++ b/apps/sn/app/(map-layout)/map/components/common/index.ts @@ -12,6 +12,8 @@ type LayerComponentProps = { rainMeasureData?: any; onSaveClick?: () => void; onFormDataChange?: (data: any) => void; // 추가 + chmbrXcnts: string; + chmbrYdnts: string; }; type LayerContentMap = { [key: string]: { 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 51ef93ddf..5604d1b55 100644 --- a/apps/sn/app/(map-layout)/map/components/common/table.ts +++ b/apps/sn/app/(map-layout)/map/components/common/table.ts @@ -686,5 +686,6 @@ export const numberFields = [ 'swrPplEqpAr', 'swrTrtPopl', 'weirHg', 'weirLt', 'precdngDry', 'rainFall', 'cntncTime', 'rainIn', 'mesureIntrvl', 'qltwtrCo', 'rnysnFvlc', 'clarFvlc', - 'bizAmt', 'count' + 'bizAmt', 'count', + 'chmbr_xcnts', 'chmbr_ydnts' ]; \ No newline at end of file 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 679122d6b..cf0dd303b 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx @@ -21,7 +21,6 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, const [layerNm, setLayerNm] = useState<string>(""); // 레이어 이름 상태 const [layerSpecificNm, setLayerSpecificNm] = useState<string>(""); // 레이어 이름 상태 const [tableData, setTableData] = useState<any>(null); // 피처 데이터 - const [rainMeasureData, setRainMeasureData] = useState<any[]>([]); // 강우/측정 측정 정보 const [headlist, setHeadlist] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태 const [headlist2, setHeadlist2] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태 const [buttonChk, setButtonChk] = useState(true) @@ -35,8 +34,8 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, const { setIsOpen: setIsModalRdii } = useRdiiChartModalStore(); const [isClicked, setIsClicked] = useState(true); // 클릭 여부 상태 const [isLoading, setIsLoading] = useState(false); // 로딩 상태 - const [chmbrXcnts, setChmbrXcnts] = useState(); // 로딩 상태 - const [chmbrYdnts, setChmbrYdnts] = useState(); // 로딩 상태 + const [chmbrXcnts, setChmbrXcnts] = useState<string>(''); // 로딩 상태 + const [chmbrYdnts, setChmbrYdnts] = useState<string>(''); // 로딩 상태 // 🎯 관로관리번호 추출 const pplMngNo = featureData?.values_?.fclt_mng_no || ""; @@ -406,7 +405,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, </h3> </div> <div className="flex items-center space-x-3"> - {/* 🎯 하수관로일 때만 Infil/RDII 버튼 표시 */} + {/* 하수관로일 때만 Infil/RDII 버튼 표시 */} {layerNm === '하수관로' && ( <> <Button onClick={() => setIsModalInfil(true)} @@ -442,9 +441,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, </div> <Button onClick={onClose} - // className="text-white hover:text-gray-300 focus:outline-none" className="flex items-center space-x-4 text-white hover:text-gray-300 focus:outline-none" - // className="absolute top-8 right-4 text-white hover:text-gray-300 focus:outline-none" size="xs" > ✕ @@ -477,7 +474,6 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, featureData={fetchData} isMainSsc={isMainSsc} isDrtwSewagePump={isDrtwSewagePump} - // rainMeasureData={rainMeasureData} onSaveClick={handleSaveClick} chmbrXcnts={chmbrXcnts} chmbrYdnts={chmbrYdnts} @@ -485,7 +481,6 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, } </div> - {/* 🎯 InfilChart 및 RdiiChart 컴포넌트 추가 */} <InfilChart pplMngNo={pplMngNo} /> <RdiiChart pplMngNo={pplMngNo} /> </div> diff --git a/apps/sn/app/components/lib/utils/formatDisplayValue.tsx b/apps/sn/app/components/lib/utils/formatDisplayValue.tsx index 289b05b97..55cdc278f 100644 --- a/apps/sn/app/components/lib/utils/formatDisplayValue.tsx +++ b/apps/sn/app/components/lib/utils/formatDisplayValue.tsx @@ -34,7 +34,7 @@ export const formatDisplayData = (data: { [key: string]: any }): { [key: string] // yyyy-mm-dd hh:mm:ss 형식 확인 const dateTimeRegex = /^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})$/; const match = dateTimeStr.match(dateTimeRegex); - + if (match && match.length >= 6) { const year = match[1]; const month = match[2]; @@ -42,7 +42,7 @@ export const formatDisplayData = (data: { [key: string]: any }): { [key: string] const hour = match[4]; const minute = match[5]; const second = match[6]; - + // 🎯 undefined 체크 후 parseInt 적용 if (year && month && day && hour && minute) { // const formattedMonth = parseInt(month); @@ -61,7 +61,7 @@ export const formatDisplayData = (data: { [key: string]: any }): { [key: string] // 🎯 기존 날짜 포맷팅 (yyyymmdd -> yyyy년 m월 d일) if (dateFields.includes(fieldId)) { const dateStr = String(value).trim(); - + // 숫자 타입이거나 숫자로만 구성된 문자열인지 확인 if (/^\d+$/.test(dateStr)) { formattedData[fieldId] = formatDateString(dateStr); @@ -75,7 +75,12 @@ export const formatDisplayData = (data: { [key: string]: any }): { [key: string] if (numberFields.includes(fieldId)) { const numValue = Number(value); if (!isNaN(numValue) && isFinite(numValue)) { - formattedData[fieldId] = numValue.toLocaleString('ko-KR'); + // 소수점이 있는 경우 4자리까지 표시 + if (!Number.isInteger(numValue)) { + formattedData[fieldId] = numValue.toLocaleString('ko-KR', { maximumFractionDigits: 4 }); + } else { + formattedData[fieldId] = numValue.toLocaleString('ko-KR'); + } } else { formattedData[fieldId] = String(value); } From 96448b642785afd83dbc00a3ab58fc6ec7593fd6 Mon Sep 17 00:00:00 2001 From: jyha00 <jyha@mind-one.co.kr> Date: Thu, 31 Jul 2025 17:50:05 +0900 Subject: [PATCH 41/71] =?UTF-8?q?=EC=88=98=EC=9A=94=EC=B2=98=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ui/sdmLoginForm/component.tsx | 147 ++++++++++-------- 1 file changed, 84 insertions(+), 63 deletions(-) diff --git a/apps/dm/components/ui/sdmLoginForm/component.tsx b/apps/dm/components/ui/sdmLoginForm/component.tsx index dd764844b..6f4e2c959 100644 --- a/apps/dm/components/ui/sdmLoginForm/component.tsx +++ b/apps/dm/components/ui/sdmLoginForm/component.tsx @@ -4,8 +4,9 @@ import type { DemandCompType } from "@common/business_components/ui/sdmInsertFor import { Button, Input } from "@common/components"; import { zodResolver } from "@hookform/resolvers/zod"; import { sha512 } from 'js-sha512'; -import { useEffect, useState, useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import { FormProvider, useForm, type ControllerRenderProps } from "react-hook-form"; +import toast from "react-hot-toast"; import * as z from "zod"; export function SdmLoginForm() { @@ -15,10 +16,10 @@ export function SdmLoginForm() { const formatBusinessNumber = (value: string) => { // 숫자만 추출 const digitsOnly = value.replace(/\D/g, ""); - + // 10자리까지만 처리 const limitedDigits = digitsOnly.slice(0, 10); - + // 3-2-5 형식으로 포맷팅 if (limitedDigits.length <= 3) { return limitedDigits; @@ -28,18 +29,18 @@ export function SdmLoginForm() { return `${limitedDigits.slice(0, 3)}-${limitedDigits.slice(3, 5)}-${limitedDigits.slice(5)}`; } }; - + // 스키마를 useMemo로 최적화 const sdmLoginFormSchema = useMemo(() => z.object({ dmdInfoId: z.string() .min(1, "사업자등록번호를 입력해주세요") .regex(/^\d{10}$/, "사업자등록번호는 10자리 숫자만 입력 가능합니다"), - bznmTotNo: isBznmTotNoDisabled + bznmTotNo: isBznmTotNoDisabled ? z.string().optional() : z.string().length(4, "종사업자등록번호는 4자리 숫자만 입력 가능합니다"), password: z.string() .min(1, "비밀번호를 입력해주세요") - .min(8, "비밀번호는 8자 이상 입력해주세요") + .min(8, "비밀번호는 8자 이상 입력해주세요") .max(16, "비밀번호는 16자 이내로 입력해주세요"), rgtr: z.string().optional(), dmdAplyPswd: z.string().optional(), @@ -47,7 +48,7 @@ export function SdmLoginForm() { type FormData = z.infer<typeof sdmLoginFormSchema>; type FieldComponent = (field: ControllerRenderProps<FormData, keyof FormData>) => JSX.Element; - + const formSchema = sdmLoginFormSchema; useEffect(() => { @@ -72,6 +73,23 @@ export function SdmLoginForm() { setIsBznmTotNoDisabled(checked); }; + const initPassword = () => { + toast( + () => ( + <div className="flex flex-col items-center"> + <div className="text-2xl mb-2"> + 관리자(031-0000-0000)에게 비밀번호 초기화 요청을 유선으로 하시기 바랍니다. + </div> + </div> + ), + { + id: "pwd-reset", + duration: 5000, + position: "top-center", + } + ); + } + const handleLogin = () => { const data = form.getValues(); const passwordStr = sha512.hex(data.password) @@ -82,61 +100,61 @@ export function SdmLoginForm() { } actionLogin(param as unknown as DemandCompType); }; - + const formFields: Array<{ name: keyof FormData; label: string; component: FieldComponent | undefined; }> = [ - { - name: "dmdInfoId", - label: "사업자등록번호", - component: (field) => ( - <Input - placeholder="사업자등록번호를 입력하세요" - type="text" - className="!mt-5 !mb-5 !text-xl text-gray-600" - {...field} - value={formatBusinessNumber(field.value as string)} - onChange={(e) => { - const dmdId = e.target.value.replace(/\D/g, "").slice(0, 10); - field.onChange(dmdId); - }} - /> - ) - }, - { - name: "bznmTotNo", - label: BZNM_TOT_NO || "종사업자등록번호", - component: (field) => ( - <FormUi - label="종사업자등록번호" - onCheckboxChange={handleCheckboxChange} - isDisabled={isBznmTotNoDisabled} - > - <Input - placeholder="여러사업장이 있을 경우 입력해주세요" + { + name: "dmdInfoId", + label: "사업자등록번호", + component: (field) => ( + <Input + placeholder="사업자등록번호를 입력하세요" type="text" - className="!mt-5 !text-xl text-gray-600" - disabled={isBznmTotNoDisabled} + className="!mt-5 !mb-5 !text-xl text-gray-600" + {...field} + value={formatBusinessNumber(field.value as string)} + onChange={(e) => { + const dmdId = e.target.value.replace(/\D/g, "").slice(0, 10); + field.onChange(dmdId); + }} + /> + ) + }, + { + name: "bznmTotNo", + label: BZNM_TOT_NO || "종사업자등록번호", + component: (field) => ( + <FormUi + label="종사업자등록번호" + onCheckboxChange={handleCheckboxChange} + isDisabled={isBznmTotNoDisabled} + > + <Input + placeholder="여러사업장이 있을 경우 입력해주세요" + type="text" + className="!mt-5 !text-xl text-gray-600" + disabled={isBznmTotNoDisabled} + {...field} + /> + </FormUi> + ) + }, + { + name: "password", + label: "비밀번호", + component: (field) => ( + <Input + placeholder="비밀번호를 입력하세요" + type="password" + className="!mt-5 !mb-5 !text-xl text-gray-600" {...field} /> - </FormUi> - ) - }, - { - name: "password", - label: "비밀번호", - component: (field) => ( - <Input - placeholder="비밀번호를 입력하세요" - type="password" - className="!mt-5 !mb-5 !text-xl text-gray-600" - {...field} - /> - ) - } - ]; + ) + } + ]; return ( <> @@ -148,11 +166,11 @@ export function SdmLoginForm() { control={form.control} name={name} render={({ field }) => ( - label==='종사업자등록번호' ? - <> + label === '종사업자등록번호' ? + <> {component && component(field)} </> - : <FormUi label={label}> + : <FormUi label={label}> {component && component(field)} </FormUi> )} @@ -160,12 +178,15 @@ export function SdmLoginForm() { ))} </FormProvider> <div className="flex justify-center pt-4"> - <Button - onClick={handleLogin} - className="w-32 bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition-colors duration-200" - > - 로그인 - </Button> + <div className="flex flex-col items-center"> + <Button + onClick={handleLogin} + className="w-32 bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition-colors duration-200" + > + 로그인 + </Button> + <div className="text-xl mt-2 text-gray-400" onClick={initPassword}>비밀번호 초기화 요청</div> + </div> </div> </div> </> From 47faed0ff7877adb41d4f3f1f122f3c3471bd74f Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Thu, 31 Jul 2025 17:56:46 +0900 Subject: [PATCH 42/71] =?UTF-8?q?refactor:=20=EC=8A=A4=ED=86=A0=EC=96=B4?= =?UTF-8?q?=20fetch=20=EC=8B=A4=ED=8C=A8=20alert=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/store/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/sn/app/store/index.ts b/apps/sn/app/store/index.ts index b20466d77..875f0489d 100644 --- a/apps/sn/app/store/index.ts +++ b/apps/sn/app/store/index.ts @@ -22,7 +22,7 @@ export const fetchCtpvData = async (set: (state: any) => void) => { .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); set({ ctpv: ctpvList }); } catch (e: any) { - alert("시도 데이터 로드 실패: " + e.message); + // alert("시도 데이터 로드 실패: " + e.message); console.error("CTPV 데이터 로드 실패:", e); } }; @@ -38,7 +38,7 @@ export const fetchSggData = async (set: (state: any) => void) => { .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); set({ sgg: sggList }); } catch (e: any) { - alert("시군구 데이터 로드 실패: " + e.message); + // alert("시군구 데이터 로드 실패: " + e.message); console.error("SGG 데이터 로드 실패:", e); } }; @@ -48,7 +48,7 @@ export const fetchBIZ_SE_CD = async (set: (state: any) => void) => { const res = await getCmsData(`/api/v1/code/cache/BIZSECD`); set({ BIZ_SE_CD: res }); } catch (e: any) { - alert("BIZ_SE_CD 데이터 로드 실패: " + e.message); + // alert("BIZ_SE_CD 데이터 로드 실패: " + e.message); console.error("BIZ_SE_CD 데이터 로드 실패:", e); } }; @@ -58,7 +58,7 @@ export const fetchBIZ_TY_CD = async (set: (state: any) => void) => { const res = await getCmsData(`/api/v1/code/cache/BIZTYCD`); set({ BIZ_TY_CD: res }); } catch (e: any) { - alert("BIZ_TY_CD 데이터 로드 실패: " + e.message); + // alert("BIZ_TY_CD 데이터 로드 실패: " + e.message); console.error("BIZ_TY_CD 데이터 로드 실패:", e); } }; \ No newline at end of file From fb9dacd60c090a561d27a545183b93cfa761c1b8 Mon Sep 17 00:00:00 2001 From: JIeunhuh <entks301@naver.com> Date: Thu, 31 Jul 2025 17:58:33 +0900 Subject: [PATCH 43/71] =?UTF-8?q?=ED=8C=9D=EC=97=85=20=ED=81=AC=EA=B8=B0?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wr/app/map/components/newWindowPopup.tsx | 11 ++++------- .../wr/app/map/components/newWindowPortal.tsx | 19 ++++++++++++++----- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/apps/wr/app/map/components/newWindowPopup.tsx b/apps/wr/app/map/components/newWindowPopup.tsx index bcc0e6ec3..21d31b984 100644 --- a/apps/wr/app/map/components/newWindowPopup.tsx +++ b/apps/wr/app/map/components/newWindowPopup.tsx @@ -18,7 +18,6 @@ import ReuseMngFacility from './layerPopup/reuseMngFacility'; interface NewWindowPopupProps { featureData: any; seletectedLayerNm?: string - viewMode?: 'list' | 'detail' | 'nothing' onFeatureSelect: (data: any) => void; onClose: () => void; } @@ -125,9 +124,6 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ layerId === '하수처리시설' ? selectedProps?.swtrmt_fclt_nm : null; if (!featureCd || featureCd === fcltPk) return; - setFcltPk(featureCd); - setActiveTab(featureCd); - setViewMode("detail"); if (layerId === '하수재이용시설') { fetchpbnstDetail(featureCd, setTabData); } else if (layerId === '하수처리시설') { @@ -140,6 +136,9 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ else if (layerId === '수요처') setMngList(res); }); } + setFcltPk(featureCd); + setActiveTab(featureCd); + setViewMode("detail"); }, [featureData]); ///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 /////////////////////////////////////////////// @@ -167,15 +166,13 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({ } return null; // 조건에 맞는 콘텐츠가 없을 경우 }; - return ( viewMode == 'nothing' ? <></> : <NewWindowPortal onClose={memoizedOnClose} title="시설 상세 정보" - width={viewMode == 'list' ? window.screen.availWidth : 1100} - height={viewMode == 'list' ? window.screen.availHeight : layerId == '하수처리시설' && viewMode == 'detail' ? 450 : 900} scrollbars='yes' + viewMode={viewMode} resizable='no'> {/* 체크된 레이어에 따른 조건부 렌더링 */} {renderConditionalContent()} diff --git a/apps/wr/app/map/components/newWindowPortal.tsx b/apps/wr/app/map/components/newWindowPortal.tsx index b8da17d7b..119e3a52b 100644 --- a/apps/wr/app/map/components/newWindowPortal.tsx +++ b/apps/wr/app/map/components/newWindowPortal.tsx @@ -31,9 +31,10 @@ interface NewWindowPortalProps { height?: number; resizable?: string; scrollbars?: string; + viewMode?: 'list' | 'detail' | 'nothing'; } -const NewWindowPortal: React.FC<NewWindowPortalProps> = ({ children, onClose, title = "", width = 900, height = 800, resizable = "yes", scrollbars = "yes" }) => { +const NewWindowPortal: React.FC<NewWindowPortalProps> = ({ children, onClose, title = "", resizable = "yes", scrollbars = "yes", viewMode }) => { const [container, setContainer] = useState<HTMLElement | null>(null); const newWindowRef = useRef<Window | null>(null); const onCloseRef = useRef(onClose); @@ -46,7 +47,15 @@ const NewWindowPortal: React.FC<NewWindowPortalProps> = ({ children, onClose, ti const handleUnload = useCallback(() => { onCloseRef.current(); }, []); - + useEffect(() => { + if (newWindowRef.current) { + if (viewMode == 'detail') { + newWindowRef.current.resizeTo(1100, 700); + } else { + newWindowRef.current.resizeTo(screen.availWidth, screen.availHeight); + } + } + }, [viewMode]); useEffect(() => { // 이미 창이 열려있으면 새로 열지 않음 if (newWindowRef.current && !newWindowRef.current.closed) return; @@ -57,11 +66,11 @@ const NewWindowPortal: React.FC<NewWindowPortalProps> = ({ children, onClose, ti const screenHeight = window.innerHeight ?? document.documentElement.clientHeight; // 중앙 위치 계산 - const left = screenLeft + (screenWidth - width) / 2; - const top = screenTop + (screenHeight - height) / 2; + const left = screenLeft + (screenWidth - window.screen.width) / 2; + const top = screenTop + (screenHeight - window.screen.height) / 2; // 1. 새 창을 열고 참조를 저장합니다. - const popupOptions = `width=${width},height=${height},left=${left}, top=${top}, resizable=${resizable},scrollbars=${scrollbars}`; + const popupOptions = `width=${window.screen.width},height=${window.screen.height},left=${left}, top=${top}, resizable=${resizable},scrollbars=${scrollbars}`; newWindowRef.current = window.open('', title, popupOptions); if (newWindowRef.current) { From d23f928ddb56271fb9db7cca57c00c82d66b319f Mon Sep 17 00:00:00 2001 From: jyha00 <jyha@mind-one.co.kr> Date: Thu, 31 Jul 2025 19:53:17 +0900 Subject: [PATCH 44/71] =?UTF-8?q?=EC=88=98=EC=9A=94=EC=B2=98=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wr/app/matMng/coMng/coSttn/page.tsx | 35 ++++-- apps/wr/app/store/matDmd/coSttn/index.tsx | 135 +++++++++++++--------- apps/wr/public/locales/ko/message.json | 24 ++-- 3 files changed, 118 insertions(+), 76 deletions(-) diff --git a/apps/wr/app/matMng/coMng/coSttn/page.tsx b/apps/wr/app/matMng/coMng/coSttn/page.tsx index 8e2705450..1dbf511eb 100644 --- a/apps/wr/app/matMng/coMng/coSttn/page.tsx +++ b/apps/wr/app/matMng/coMng/coSttn/page.tsx @@ -5,6 +5,7 @@ import { btnClass } from "@/app/store/common/code"; import { excelCoSttnParsing } from "@/app/store/matDmd/coSttn/excel"; import { fetchType, getFetch } from "@/components/lib/api"; import { + confirmToast, excelDownload, formatPhoneNumber, getCity, @@ -30,6 +31,7 @@ import { Button } from "@common/components/ui"; import moment from "moment"; import { useCallback, useEffect, useState } from "react"; import toast, { Toaster } from "react-hot-toast"; +import { useTranslation } from "react-i18next"; export default function CoSttn() { const { @@ -43,6 +45,7 @@ export default function CoSttn() { updateDemandCompany, coSttnTxtSearch, demandCompList, + coSttnPwInit, } = useWrStore((state) => state); const { regAuthrtYn, mdfcnAuthrtYn } = auth; @@ -103,9 +106,8 @@ export default function CoSttn() { ...item, telno: formatPhoneNumber(item.telno || undefined), regDt: moment(item.regDt).format("YYYY-MM-DD"), - dmdInfoId: `${item.dmdInfoId.slice(0, 3)}-${item.dmdInfoId.slice(3, 5)}-${item.dmdInfoId.slice(5,10)}-${item.dmdInfoId.slice(10)}` - })); - console.log("chData:", chData) + dmdInfoId: `${item.dmdInfoId.slice(0, 3)}-${item.dmdInfoId.slice(3, 5)}-${item.dmdInfoId.slice(5, 10)}-${item.dmdInfoId.slice(10)}` + })); // .filter((item: any) => item !== undefined); setBdstTable(chData); } else if (coSttnRes?.length == 0) { @@ -156,6 +158,18 @@ export default function CoSttn() { } } }; + const { t } = useTranslation('base'); + const initPassword = () => { + if (upadateData) { + const dmdInfoId = upadateData.dmdInfoId; + const handleConfirm = () => { + coSttnPwInit(dmdInfoId); + }; + confirmToast(t('isInitPw'), handleConfirm); + } else { + toast.error("비밀번호 초기화를 원하는 행을 선택해주세요.") + } + } return ( <> @@ -167,7 +181,7 @@ export default function CoSttn() { <SearchFormLeft> <div className="col-span-10 flex flex-row"> <SelectBox - className="col-span-2 w-1/6 h-full text-2xl" + className="col-span-3 w-1/6 h-full text-2xl" label="시도" selectClass={""} selectArray={code.cityCd || []} @@ -178,7 +192,7 @@ export default function CoSttn() { value={objSearch.city} /> <SelectBox - className="col-span-2 w-1/6 h-full text-2xl" + className="col-span-3 w-1/6 h-full text-2xl" label="시군구" selectClass={""} selectArray={selectSgg || []} @@ -195,7 +209,7 @@ export default function CoSttn() { /> </div> </SearchFormLeft> - <SearchFormRight className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 "> + <SearchFormRight className="w-3/12 mx-5 flex flex-col items-center justify-center space-x-2 "> <div className="flex flex-row space-x-2"> <Button className="mt-2" size={"sm"} onClick={searchClick}> 조회 @@ -236,6 +250,13 @@ export default function CoSttn() { > 다운로드 </Button> + <Button + size={"sm"} + className={`${btnClass}`} + onClick={(initPassword)} + > + 비밀번호 초기화 + </Button> </div> </SearchFormRight> </SearchDiv> @@ -286,9 +307,7 @@ export default function CoSttn() { objSearch.sgg && objSearch.sgg !== "0" ? objSearch.sgg : objSearch.city; - console.log("data ", data); if (upadateData?.dmdInfoId) { - console.log("data ", data); await updateDemandCompany(data as DemandCompType); coSttnInit("1", "13", String(con)); setIsDmdModal({ diff --git a/apps/wr/app/store/matDmd/coSttn/index.tsx b/apps/wr/app/store/matDmd/coSttn/index.tsx index 1e20e8a76..1f7b2c54b 100644 --- a/apps/wr/app/store/matDmd/coSttn/index.tsx +++ b/apps/wr/app/store/matDmd/coSttn/index.tsx @@ -1,7 +1,8 @@ import type { CommonTableProps } from "@/app/store/common/type/header"; -import { fetchType, getFetchWithCount } from "@/components/lib/api"; +import { fetchType, getFetchWithCount, putFectch } from "@/components/lib/api"; import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; import { coSttHeadListExport, coSttNodeListExport } from "./headList"; +import toast from "react-hot-toast"; // 테이블 헤드 리스트 const coSttNodeList = coSttNodeListExport; @@ -21,9 +22,10 @@ export type coSttnState = { }; type coSttnActions = { - coSttnInit:(pageNo:string, pageCnt:string, con: string) => void; - coSttnSearch: (pageNo:string, pageCnt:string, con: string) => void; - coSttnTxtSearch:(pageNo:string, pageCnt:string, con: string,txt:string) => void; + coSttnInit: (pageNo: string, pageCnt: string, con: string) => void; + coSttnSearch: (pageNo: string, pageCnt: string, con: string) => void; + coSttnTxtSearch: (pageNo: string, pageCnt: string, con: string, txt: string) => void; + coSttnPwInit: (dmdInfoId: string) => void; }; export type coSttnType = coSttnState & coSttnActions; @@ -44,7 +46,7 @@ export const coSttnActionsExport: (set: any) => coSttnActions = ( set: any ) => { return { - coSttnInit: async (pageNo:string, pageCnt:string, con: string) => { + coSttnInit: async (pageNo: string, pageCnt: string, con: string) => { try { const coSttnRes = await getFetchWithCount( @@ -67,56 +69,81 @@ export const coSttnActionsExport: (set: any) => coSttnActions = ( console.log(e) } }, - coSttnSearch: async (pageNo:string, pageCnt:string, con: string) => { - try { - const coSttnRes = await getFetchWithCount( - fetchType.WR, - `/reuseFc/request/${pageNo}/${pageCnt}/${con}` - ); - if (Number(pageCnt) >= 10000) { - set((state: coSttnState) => { - return { - ...state, - coSttn: { - ...state.coSttn, - excelList: coSttnRes.data, - //isInit: false, - }, - }; - }); - } else - set((state: coSttnState) => { - return { - ...state, - coSttn: { - ...state.coSttn, - coSttnRes: coSttnRes.data, - isInit: false, - coSttnResCount : coSttnRes.count, - }, - }; - }); - } catch (erTxt) { - console.log("error", erTxt); - } - }, - coSttnTxtSearch: async (pageNo:string, pageCnt:string, con: string, txt: string) => { - try { - const coSttnRes = await getFetchWithCount( - fetchType.WR, - `/reuseFc/request/${pageNo}/${pageCnt}/${con}/${txt}`) - set((state: coSttnState) => { - return { - ...state, - coSttn: { - ...state.coSttn, - coSttnRes: coSttnRes.data, - coSttnResCount: coSttnRes.count, - isInit: false, - }}}) - } catch (erTxt) { - console.log("error", erTxt); + coSttnSearch: async (pageNo: string, pageCnt: string, con: string) => { + try { + const coSttnRes = await getFetchWithCount( + fetchType.WR, + `/reuseFc/request/${pageNo}/${pageCnt}/${con}` + ); + if (Number(pageCnt) >= 10000) { + set((state: coSttnState) => { + return { + ...state, + coSttn: { + ...state.coSttn, + excelList: coSttnRes.data, + //isInit: false, + }, + }; + }); + } else + set((state: coSttnState) => { + return { + ...state, + coSttn: { + ...state.coSttn, + coSttnRes: coSttnRes.data, + isInit: false, + coSttnResCount: coSttnRes.count, + }, + }; + }); + } catch (erTxt) { + console.log("error", erTxt); + } + }, + coSttnTxtSearch: async (pageNo: string, pageCnt: string, con: string, txt: string) => { + try { + const coSttnRes = await getFetchWithCount( + fetchType.WR, + `/reuseFc/request/${pageNo}/${pageCnt}/${con}/${txt}`) + set((state: coSttnState) => { + return { + ...state, + coSttn: { + ...state.coSttn, + coSttnRes: coSttnRes.data, + coSttnResCount: coSttnRes.count, + isInit: false, + } } + }) + } catch (erTxt) { + console.log("error", erTxt); + } + }, + coSttnPwInit: async (dmdInfoId: string) => { + try { + const response = await putFectch(`/reuseFc/request/chgPassword/${dmdInfoId}`,[]) + if (response.status === 200) { + toast.success('비밀번호가 초기화 되었습니다.') + } else { + toast.error('비밀번호 초기화에 실패하였습니다') } + set((state: coSttnState) => { + return { + ...state, + coSttn: { + ...state.coSttn, + isInit: false, + } + } + }); + } catch (err) { + console.log(err); + toast.error('비밀번호 초기화에 실패하였습니다.') + + } + } } } \ No newline at end of file diff --git a/apps/wr/public/locales/ko/message.json b/apps/wr/public/locales/ko/message.json index d3c55de5e..859df00cf 100644 --- a/apps/wr/public/locales/ko/message.json +++ b/apps/wr/public/locales/ko/message.json @@ -1,10 +1,10 @@ { - "deleteMsg" : "{{name}}가 삭제되었습니다.", + "deleteMsg": "{{name}}가 삭제되었습니다.", "insertMsg": "{{name}}가 저장되었습니다.", "updateMsg": "{{name}}가 수정되었습니다.", - "errorMsg": "{{name}} 중 오류가 발생했습니다.", + "errorMsg": "{{name}} 중 오류가 발생했습니다.", "fileErrMsg": "빈 파일 또는 파일 오류입니다.", - "selectErrMsg": "{{name}}을(를) 선택해주세요.", + "selectErrMsg": "{{name}}을(를) 선택해주세요.", "noChangedErrMsg": "변경사항이 없습니다.", "dateErrMsg": "시작년도를 종료년도 이후로 설정할 수 없습니다.", "fileDateErrMsg": "파일업로드 이전에 등록년도를 작성해주세요.", @@ -12,19 +12,15 @@ "isUpdateMsg": "수정한 정보를 저장하시겠습니까?", "isInsertMsg": "등록하시겠습니까?", "isUpdatetMsg": "수정하시겠습니까?", - "matchFinishMsg" : "{{name}}이(가) 완료되었습니다.", + "matchFinishMsg": "{{name}}이(가) 완료되었습니다.", "excelDownErrMsg": "다운로드할 데이터가 없습니다.", - "isMatchFinishMsg":" 매칭완료를 진행하시겠습니까?", - "isNotMatchFinishMsg":"검토완료중 매칭완료 여부를 체크한 항목이 없습니다.", - "canNotChangeMatStateMsg":"매칭완료된 항목은 상태를 변경할 수 없습니다.", - - - + "isMatchFinishMsg": " 매칭완료를 진행하시겠습니까?", + "isNotMatchFinishMsg": "검토완료중 매칭완료 여부를 체크한 항목이 없습니다.", + "canNotChangeMatStateMsg": "매칭완료된 항목은 상태를 변경할 수 없습니다.", + "isInitPw": "비밀번호를 초기화하시겠습니까?", "alreadyInsertedErrMsg": "사업자번호 {{num}}(은)는 이미 등록되었습니다. 다시 등록해주세요.", "saveRowErrMsg": "수정을 원하는 행을 선택하고 저장버튼을 눌러주세요.", - "costCalcErrMsg":"공사비 계산 진행 후 저장을 눌러주세요", + "costCalcErrMsg": "공사비 계산 진행 후 저장을 눌러주세요", "maxYearErrMsg": "최대 {{year}}년까지 선택가능합니다.", "checkQtyMsg": "요구수량(m³/일)을 확인해주세요 계산할 수 없습니다." - -} - +} \ No newline at end of file From 96a50e4f5a48311f7a3b34560ae846e81111f873 Mon Sep 17 00:00:00 2001 From: jyha00 <jyha@mind-one.co.kr> Date: Thu, 31 Jul 2025 20:01:25 +0900 Subject: [PATCH 45/71] =?UTF-8?q?=EC=88=98=EC=9A=94=EC=B2=98=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=EC=9A=94=EC=B2=AD=20cursor-point?= =?UTF-8?q?er=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/dm/components/ui/sdmLoginForm/component.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dm/components/ui/sdmLoginForm/component.tsx b/apps/dm/components/ui/sdmLoginForm/component.tsx index 6f4e2c959..75852a4da 100644 --- a/apps/dm/components/ui/sdmLoginForm/component.tsx +++ b/apps/dm/components/ui/sdmLoginForm/component.tsx @@ -185,7 +185,7 @@ export function SdmLoginForm() { > 로그인 </Button> - <div className="text-xl mt-2 text-gray-400" onClick={initPassword}>비밀번호 초기화 요청</div> + <div className="text-xl mt-2 text-gray-400 cursor-pointer" onClick={initPassword}>비밀번호 초기화 요청</div> </div> </div> </div> From cb0f3160230da1d5a70e0e527e62bfe0af0d6d04 Mon Sep 17 00:00:00 2001 From: jyha00 <jyha@mind-one.co.kr> Date: Thu, 31 Jul 2025 20:11:10 +0900 Subject: [PATCH 46/71] =?UTF-8?q?=EC=88=98=EC=9A=94=EC=B2=98=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=EC=B6=94=EA=B0=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wr/app/matMng/coMng/coSttn/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/wr/app/matMng/coMng/coSttn/page.tsx b/apps/wr/app/matMng/coMng/coSttn/page.tsx index 1dbf511eb..75e1ecff7 100644 --- a/apps/wr/app/matMng/coMng/coSttn/page.tsx +++ b/apps/wr/app/matMng/coMng/coSttn/page.tsx @@ -192,7 +192,7 @@ export default function CoSttn() { value={objSearch.city} /> <SelectBox - className="col-span-3 w-1/6 h-full text-2xl" + className="col-span-3 w-1/6 h-full text-2xl ml-4" label="시군구" selectClass={""} selectArray={selectSgg || []} @@ -200,7 +200,7 @@ export default function CoSttn() { value={objSearch.sgg} /> <SearchInput - className={"!text-3xl"} + className={"!text-3xl ml-4"} label="회사명" textValue={seartchTxt ?? ""} setText={(e) => { From 8eea3c75aa82327ae5d670640afc242933766332 Mon Sep 17 00:00:00 2001 From: leedj312 <dlehd467@gmail.com> Date: Thu, 31 Jul 2025 20:16:38 +0900 Subject: [PATCH 47/71] =?UTF-8?q?=EC=88=98=EC=9A=94=EB=A7=A4=EC=B9=AD=20?= =?UTF-8?q?=EA=B2=80=ED=86=A0=20=EC=9D=B4=EC=83=81=20=EC=9D=B4=ED=95=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wr/app/matMng/coMat/coMatRvw/page.tsx | 307 +++++++++++++-------- 1 file changed, 185 insertions(+), 122 deletions(-) diff --git a/apps/wr/app/matMng/coMat/coMatRvw/page.tsx b/apps/wr/app/matMng/coMat/coMatRvw/page.tsx index ff35b95c5..996b624d8 100644 --- a/apps/wr/app/matMng/coMat/coMatRvw/page.tsx +++ b/apps/wr/app/matMng/coMat/coMatRvw/page.tsx @@ -376,87 +376,116 @@ export default function CoMatRvw() { <div className="col-span-12 w-full"> <h1 className={formH1Class}>요구수질</h1> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="grssClfg" render={({ field }) => ( - <FormUi label="총대장균군수 (개/100ml)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <FormUi label={"총대장균군수(개/mg)"}> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + type="text" + className="w-full pr-16 border py-2 text-right rounded-md" + placeholder="" + value={ + field.value != -1 + ? `${field.value}` + : "불검출" + } + readOnly + /> + {/* TODO: 총대장균군수 확인 필요*/} + {field.value != -1 ? ( + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + ) : ( + <></> + )} + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="combResdch" render={({ field }) => ( - <FormUi label="결합잔류염소(mg/L)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <FormUi label={"결합잔류염소(mg/L)"}> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + type="text" + className="w-full pr-16 border py-2 text-right rounded-md" + placeholder="" + value={field.value ?? ""} + readOnly + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이상 + </span> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="trbt" render={({ field }) => ( <FormUi label="탁도(NTU)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + placeholder="" + className="w-full pr-16 border py-2 text-right rounded-md" + type="text" + value={field.value ?? 0} + readOnly + /> + <span className=" text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="blgcOxygDmndQnt" render={({ field }) => ( <FormUi label="생물학적 산소요구량(BOD)(mg/L)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + placeholder="" + type="text" + className="w-full pr-16 border py-2 text-right rounded-md" + step="0.1" + onChange={(e) => { + field.onChange(Number(e.target.value)); + }} + value={field.value ?? ""} + readOnly + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="smll" @@ -464,138 +493,172 @@ export default function CoMatRvw() { <FormUi label="냄새"> <Input placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} + type="text" + value={field.value ?? ""} readOnly /> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="crmty" render={({ field }) => ( <FormUi label="색도(도)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + placeholder="" + className="w-full pr-16 border py-2 text-right rounded-md" + type="text" + step="0.1" + value={field.value ?? ""} + readOnly + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="tn" render={({ field }) => ( - <FormUi label="총질소 (T-N)(mg/L)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <FormUi label="총질소(T-N)(mg/L)"> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + placeholder="" + className="w-full pr-16 border py-2 text-right rounded-md" + type="text" + onChange={(e) => { + field.onChange(Number(e.target.value)); + }} + value={field.value ?? 0} + readOnly + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="tph" render={({ field }) => ( <FormUi label="총인(T-P)(mg/L)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + placeholder="" + className="w-full pr-16 border py-2 text-right rounded-md" + type="text" + onChange={(e) => { + field.onChange(Number(e.target.value)); + }} + value={field.value ?? 0} + readOnly + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="ph" render={({ field }) => ( - <FormUi label="수소이온농도(pH)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <FormUi label="수소이온농도(PH)"> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + placeholder="" + className="w-full pr-16 border py-2 text-right rounded-md" + type="text" + onChange={(e) => { + field.onChange(Number(e.target.value)); + }} + value={field.value ?? 0} + readOnly + /> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="clIonQnt" render={({ field }) => ( <FormUi label="염화물(mgCL/L)"> - <Input - placeholder="" - type="number" - step="0.1" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + placeholder="" + className="w-full pr-16 border py-2 text-right rounded-md" + type="text" + onChange={(e) => { + field.onChange(Number(e.target.value)); + }} + value={field.value ?? 0} + readOnly + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="cdtnv" render={({ field }) => ( <FormUi label="전기전도도(㎲/cm)"> - <Input - placeholder="" - type="number" - onChange={(e) => { - field.onChange(Number(e.target.value)); - }} - value={field.value ?? 0} - readOnly - /> + <div className="flex items-center gap-2"> + <div className="relative flex-1 min-w-0"> + <Input + placeholder="" + className="w-full pr-16 border py-2 text-right rounded-md" + type="text" + onChange={(e) => { + field.onChange(Number(e.target.value)); + }} + value={field.value ?? 0} + readOnly + /> + <span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none"> + 이하 + </span> + </div> + </div> </FormUi> )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <FormField control={form.control} name="rmrk" @@ -648,7 +711,7 @@ export default function CoMatRvw() { form={form} type={"mat"} selectionVersion={selectionVersion} - readonly={regAuthrtYn ==="N"} + readonly={regAuthrtYn === "N"} /> </ListDiv> {/* </ScrollArea> */} From e54a0d0ef8e2c27aa710860cd1b4bbe8c1055da7 Mon Sep 17 00:00:00 2001 From: leedj312 <dlehd467@gmail.com> Date: Fri, 1 Aug 2025 11:37:23 +0900 Subject: [PATCH 48/71] =?UTF-8?q?=EC=88=98=EC=9A=94=EC=8B=A0=EC=B2=AD?= =?UTF-8?q?=ED=98=84=ED=99=A9=20=EC=9D=B4=EC=83=81/=EC=9D=B4=ED=95=98=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/sdmMng/sdmApplyList/page.tsx | 20 +++++++++---------- apps/dm/app/store/sdmMng/index.tsx | 1 - .../ui/sdmInfoInsertForm/component.tsx | 2 -- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx index ee86a9df5..9c2f6201e 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx @@ -123,33 +123,33 @@ export default function SdmApplyList({}: SdmApplyProps) { </TableRow> <TableRow> <TableCell colSpan={1} className={TABLE_STYLES.header}>총대장균군수(개/100ml)</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.grssClfg ?? ''} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{ sdmApplyDetail?.grssClfg!=-1?<TransValueFormat value={sdmApplyDetail?.grssClfg ?? ''} format="number"/>:''} {sdmApplyDetail?.grssClfg?sdmApplyDetail.grssClfg!=-1? '이하':'불검출':'-'} </TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>결합잔류염소(mg/L)​</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.combResdch ?? ''} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.combResdch ?? ''} format="number"/>} {sdmApplyDetail?.combResdch?'이하':'-'}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>탁도(NTU)</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.trbt ?? ''} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.trbt ?? ''} format="number"/>} {sdmApplyDetail?.trbt?'이하':'-'}</TableCell> </TableRow> <TableRow> <TableCell colSpan={1} className={TABLE_STYLES.header}>생물학적 산소요구량(BOD)(mg/L)</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.blgcOxygDmndQnt ?? ''} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.blgcOxygDmndQnt ?? ''} format="number"/>} {sdmApplyDetail?.blgcOxygDmndQnt?'이하':'-'}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>냄새</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.smll} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.smll} format="number"/>} {sdmApplyDetail?.smll?'이하':'-'}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>색도(도)</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.crmty ?? ''} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.crmty ?? ''} format="number"/>} {sdmApplyDetail?.crmty?'이하':'-'}</TableCell> </TableRow> <TableRow> <TableCell colSpan={1} className={TABLE_STYLES.header}>총질소(T-N)(mg/L)</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tn ?? ''} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tn ?? ''} format="number"/>} {sdmApplyDetail?.tn?'이하':'-'}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>총인(T-P)(mg/L)​</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tph ?? ''} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tph ?? ''} format="number"/>} {sdmApplyDetail?.tph?'이하':'-'}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>수소이온농도(pH)​</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.ph} format="number"/>}</TableCell> </TableRow> <TableRow> <TableCell colSpan={1} className={TABLE_STYLES.header}>염화물(mgCL/L)​</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.clIonQnt ?? ''} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.clIonQnt ?? ''} format="number"/>} {sdmApplyDetail?.clIonQnt?'이하':'-'}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>전기전도도(㎲/cm)​</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.cdtnv ?? ''} format="number"/>}</TableCell> + <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.cdtnv ?? ''} format="number"/>} {sdmApplyDetail?.cdtnv?'이하':'-'}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>비고</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{sdmApplyDetail?.rmrk}</TableCell> </TableRow> diff --git a/apps/dm/app/store/sdmMng/index.tsx b/apps/dm/app/store/sdmMng/index.tsx index d68a34e2f..6a6628339 100644 --- a/apps/dm/app/store/sdmMng/index.tsx +++ b/apps/dm/app/store/sdmMng/index.tsx @@ -298,7 +298,6 @@ const useTypesData : SelectDataType[] = [ { text: "농업_간접용수", val: "농업_간접용수" }, { text: "농업_직접용수", val: "농업_직접용수" }, { text: "하천", val: "하천" }, - { text: "지하수", val: "지하수" }, { text: "공업", val: "공업" }, ]; diff --git a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx index 478c61ac3..7d6c41f24 100644 --- a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx +++ b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx @@ -159,8 +159,6 @@ export function SdmInfoRegForm({ // Apply default values const defaults = defaultRules[usePurpose]; - console.log("usePurpose", usePurpose); - console.log("defaults", defaults); if (defaults) { Object.entries(defaults).forEach(([field, value]) => { form.setValue(field as keyof FormData, value, { shouldDirty: true }); From d4c115d7ca22241e0654c8ed52c84c9c575777a1 Mon Sep 17 00:00:00 2001 From: yschoi <yschoi> Date: Fri, 1 Aug 2025 14:13:19 +0900 Subject: [PATCH 49/71] =?UTF-8?q?=EC=A7=80=EB=8F=84=20=EC=9E=91=EC=97=85?= =?UTF-8?q?=20=EC=A4=911?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/dm/app/(popup-layout)/map/popup/page.tsx | 1 + .../components/ui/map/searchBar/searchBar.tsx | 125 ++-- .../images/map/layericon/BML_CBAS_PS.png | Bin 1547 -> 919 bytes .../images/map/layericon/BML_STMS_PS.png | Bin 1542 -> 810 bytes .../images/map/layericon/RDL_ETCT_PS.png | Bin 1016 -> 896 bytes .../images/map/layericon/RDL_OCUP_PS.png | Bin 1061 -> 837 bytes .../images/map/layericon/RDL_SEPC_PS.png | Bin 1068 -> 790 bytes .../images/map/layericon/RDL_STAT_PS.png | Bin 1075 -> 855 bytes .../images/map/layericon/RDL_TSCT_PS.png | Bin 1700 -> 923 bytes apps/fo/app/pages/map/page.tsx | 16 +- apps/fo/app/pages/map/searchBar/searchBar.tsx | 158 +++++ .../images/map/layericon/BML_CBAS_PS.png | Bin 1547 -> 919 bytes .../images/map/layericon/BML_STMS_PS.png | Bin 1542 -> 810 bytes .../images/map/layericon/RDL_ETCT_PS.png | Bin 1016 -> 896 bytes .../images/map/layericon/RDL_OCUP_PS.png | Bin 1061 -> 837 bytes .../images/map/layericon/RDL_SEPC_PS.png | Bin 1068 -> 790 bytes .../images/map/layericon/RDL_STAT_PS.png | Bin 1075 -> 855 bytes .../images/map/layericon/RDL_TSCT_PS.png | Bin 1700 -> 923 bytes .../images/map/layericon/BML_CBAS_PS copy.png | Bin 0 -> 919 bytes .../images/map/layericon/BML_STMS_PS copy.png | Bin 0 -> 810 bytes .../images/map/layericon/RDL_ETCT_PS copy.png | Bin 0 -> 896 bytes .../images/map/layericon/RDL_OCUP_PS copy.png | Bin 0 -> 837 bytes .../images/map/layericon/RDL_SEPC_PS copy.png | Bin 0 -> 790 bytes .../images/map/layericon/RDL_STAT_PS copy.png | Bin 0 -> 855 bytes .../images/map/layericon/RDL_TSCT_PS copy.png | Bin 0 -> 923 bytes .../images/map/layericon/BML_CBAS_PS.png | Bin 1547 -> 919 bytes .../images/map/layericon/BML_STMS_PS.png | Bin 1542 -> 810 bytes .../images/map/layericon/RDL_ETCT_PS.png | Bin 1016 -> 896 bytes .../images/map/layericon/RDL_OCUP_PS.png | Bin 1061 -> 837 bytes .../images/map/layericon/RDL_SEPC_PS.png | Bin 1068 -> 790 bytes .../images/map/layericon/RDL_STAT_PS.png | Bin 1075 -> 855 bytes .../images/map/layericon/RDL_TSCT_PS.png | Bin 1700 -> 923 bytes .../business_components/src/ui/map/Gis.js | 599 +++++++++--------- .../business_components/src/ui/map/GisTree.js | 2 +- 34 files changed, 533 insertions(+), 368 deletions(-) create mode 100644 apps/fo/app/pages/map/searchBar/searchBar.tsx create mode 100644 apps/sn/public/images/map/layericon/BML_CBAS_PS copy.png create mode 100644 apps/sn/public/images/map/layericon/BML_STMS_PS copy.png create mode 100644 apps/sn/public/images/map/layericon/RDL_ETCT_PS copy.png create mode 100644 apps/sn/public/images/map/layericon/RDL_OCUP_PS copy.png create mode 100644 apps/sn/public/images/map/layericon/RDL_SEPC_PS copy.png create mode 100644 apps/sn/public/images/map/layericon/RDL_STAT_PS copy.png create mode 100644 apps/sn/public/images/map/layericon/RDL_TSCT_PS copy.png diff --git a/apps/dm/app/(popup-layout)/map/popup/page.tsx b/apps/dm/app/(popup-layout)/map/popup/page.tsx index ea71862d8..c0d38a5c5 100644 --- a/apps/dm/app/(popup-layout)/map/popup/page.tsx +++ b/apps/dm/app/(popup-layout)/map/popup/page.tsx @@ -233,6 +233,7 @@ const Map = () => { <> <div className="bg-container"> <Gis + height='h-screen' treeData={treeData} isSearch={false} gisMaps={gisMaps} diff --git a/apps/dm/components/ui/map/searchBar/searchBar.tsx b/apps/dm/components/ui/map/searchBar/searchBar.tsx index f2beeff82..7ca64bbce 100644 --- a/apps/dm/components/ui/map/searchBar/searchBar.tsx +++ b/apps/dm/components/ui/map/searchBar/searchBar.tsx @@ -1,8 +1,6 @@ "use client"; import { useDmStore } from "@/app/store"; import { - SearchDiv, - SearchFormLeft, SelectBox, type SelectDataType, } from "@common/business_components/ui"; @@ -48,6 +46,7 @@ export function SearchBar({ }, [Common.selectSigunData] ); + const onSelectValue = useCallback( (val: string, type: string) => { switch (type) { @@ -62,78 +61,58 @@ export function SearchBar({ [initializeSigunData] ); - // useEffect(() => { - // console.log("map.ruseFcltList ", map.ruseFcltList); - // }, [map.ruseFcltList]); - return ( - <> - <div className="grid grid-cols-4 gap-4 "> - <div className="col-span-4"> - <SearchDiv> - <SearchFormLeft> - <div className="col-span-10 flex flex-row"> - <SelectBox - className="col-span-2 w-1/6 h-full text-2xl" - label="시도" - selectClass={""} - selectArray={Common.selectSidoData || []} - onSelectValue={(val) => - onSelectValue(val.toString(), "ctpvCd") - } - value={sidoValue || Common.selectSidoData[0]?.val || ""} - /> - <SelectBox - className="col-span-2 w-1/6 h-full text-2xl ml-10" - label="시군구" - selectClass={""} - selectArray={filteredSigunData || []} - onSelectValue={(val) => - onSelectValue(val.toString(), "sggCd") - } - value={sigunValue} - /> - <div className="col-span-6 flex flex-row items-center min-w-[27rem] ml-10"> - <Label className="text-xl justify-self-center mt-2 mx-3"> - 하수재이용시설 - </Label> - <SearchInputText - label={"시설명"} - initValue={undefined} - setValue={(searchNm?: string) => { - let sggOrCity = ""; - if (searchNm) { - if (sigunValue === "0" || sigunValue === "") { - sggOrCity = sidoValue; - } else { - sggOrCity = sigunValue; - } - findRuseFclt(sggOrCity, searchNm); - } - }} - setRtnData={(rtnData: any) => { - setHasuPk(rtnData); - }} - searchList={map.ruseFcltList || []} - /> - </div> - <div className="flex flex-row space-x-2"> - <Button - color="sky" - className="mt-2" - size={"sm"} - onClick={() => { - onApply(); - }} - > - 적용 - </Button> - </div> - </div> - </SearchFormLeft> - </SearchDiv> - </div> + <div className="flex justify-center w-full px-4 py-3"> + <div className="flex items-center w-full max-w-screen-lg rounded-xl bg-white shadow-md px-6 py-4 space-x-3"> + <SelectBox + labelClass="w-[40px]" + className="w-[200px]" + label="시도" + selectClass="rounded-lg bg-white" + selectArray={Common.selectSidoData || []} + onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")} + value={sidoValue || Common.selectSidoData[0]?.val || ""} + /> + <SelectBox + labelClass="w-[65px]" + className="w-[200px]" + label="시군구" + selectClass="rounded-lg bg-white" + selectArray={filteredSigunData || []} + onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")} + value={sigunValue} + /> + <Label className="text-gray-600">하수재이용시설</Label> + <SearchInputText + label="시설명" + initValue={undefined} + setValue={(searchNm?: string) => { + let sggOrCity = ""; + if (searchNm) { + if (sigunValue === "0" || sigunValue === "") { + sggOrCity = sidoValue; + } else { + sggOrCity = sigunValue; + } + findRuseFclt(sggOrCity, searchNm); + } + }} + setRtnData={(rtnData: any) => { + setHasuPk(rtnData); + }} + searchList={map.ruseFcltList || []} + /> + <Button + color="sky" + className="ml-2 text-white" + size="sm" + onClick={() => { + onApply(); + }} + > + 적용 + </Button> </div> - </> + </div> ); } diff --git a/apps/dm/public/images/map/layericon/BML_CBAS_PS.png b/apps/dm/public/images/map/layericon/BML_CBAS_PS.png index 5ff4add3ef67899ca56ef065952b919b8426d086..0e70939baeed2a0221089ebdf2ed19921495c8e9 100644 GIT binary patch delta 897 zcmV-{1AhFA43`IxBYy*4Nkl<Zc${TZT}+#07(LJXeue(PMzF0Gl#aAibkPX~Gt`K4 zL@G!^veC@NWm!z-#iEIq3o1VsCd(F0%obo?Ng^0((6}Yj5Qqgqkrb^I$|$Tb3bYjJ zr0lEf&-Z#^F$<o{ljofC<m7n>SqLGBhyXxHk_xF*(hA@J0Dl?45)q{yniG3EdYI&# zLzd+;Q{z9LUkL`EeefW0AU8*S-)JhjeYnEjXEGIc5s}>1*2X$IIurnqgb>u;-cCXY z?)Q1GyLvxA=l6M$PG`V4M>>_#sU)c~7?`g-QeD%yu@OI2WXj*%3nd{0V~i<*g}F~| z4Gz3NKRc7kQGaXLOLY!b|9TUJ*Vn|gD<3QJZd@s~SXD3AzcD=R-E6*aq2)dRFd|YE zMLF`*@X&iR-rq8uOI(we&y}1U+S!f6Vk@VH<`!-=6-j}>LV9-k*W*=HC8x7J5CZ_O zXR2j+DZq)C01?w^b-3f3!GwDR08rP^1hqy>fS|x_zki5^Lnllolh)~Uk^}(JNVsBa zGXlmqoqgwBR6bvW-s@NK`{d8CmOY77GKrPo9l@AH+uPf;^Jna-nwnz<m#c480)SK| ztx3o-)Y?3lORazqIApP5@T)Hoy1R@gtTt4?T1V4kZt+)a8<})QmFA=d@b3m1tuB~R zGOxUNtbY!V7!2^w`S6XaAIBYyXlOo(rQi|{S<4_P0z=;o3a(OxCr8JU|8-y}E*UnL zmeIuMh=|8`5RPrZWH#gUx%05t%JEu#BWxBM0&_m0H$F;St@|Ysxf}24=^+wAP*+!9 zfw@>S@<sP&&lQ`^se^i>YI$KE<xf9@6K}qSv44>vY(=A)vEd(h%bB;M-;Yk5y>;Wp zL@XAIQ}!UJx8HS?CGPd#{JO6s6j~K(wORmB@(&i!dT5my^ag4=d1`k{PJPgN@#43& zwY3qq+x<5Iz`(!&cRHQ?^5svDmY0=$^vBd>?fTk^l#pd`&hfap6o)ISeE!Ad?hjjA zhkERGd&J}M>;Mq7_Y_%^900~km#(~EFcefvjOml<Ok!;<GJCcAnkTxk5whFu_x8lt z4<Q5r$OVuGKo6h@Krw*F02DeLj@<nk+D~MSn8)Mc<KyFOad8oag@u{U&d#(D{}25K XcgcgJw%X%T00000NkvXXu0mjfo@TMS literal 1547 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`<jAm(=3qqRfJl%=|nBkco*&`K2Yc zN<f!FypUS}H#V;r8ce_d)2~P@&^OdG0C@?f(AO8g(%f9Q>f(~1RD^r68eAMwS&*t9 zlv<o$T9gcoxHM&uRRvbgMX8A;`9&f5`8lvq49LhY$uG{xFHmp}HdD|D&&*57FE7>v ztM~P_^2{qPNz6-5^>ndS0-B(gnVDi`<Z5p0Xk_kaV&V)8bypKdGh;(%XE$dj6H6Bh zS7&3GUYGpj(%jU%5}4i;gkC3{dO=Acw*Y9fOKMSOS!#+~QGTuh*vnR#xZPrb(>$o& z6x?pH#Hm*w=oo!a#3DsBObD2IKumbD1#;jCKQ#}S+KYh6+H&5{UIqrHdQTU}kcwM# zCdYbA4wN|lzxuh^-KyftM`9}$b|eHj#ydx=Nojj7R_fibWOhtktBRwzr;AGL6JLcX zP9asojgm^Nvyv>YoZNEAS66w8U5Los;>%~ht$SX1yNy%n%YpY#=RBYD{eR7zVv*&a zHg7yw)KPTEN8<3l%tLM_g0*X7YcHPdDrJgV)VE?sNS)uzi4)u2J~Hz-$nY#Z{b5O+ zn|F2jglp2BSwbJzieC`a_ZJb3;`&^C;m;P%xwd6GyY5cNP>d5PEO@UsWuC6v_uqA2 ztlg|m)V8_oF?{%<)YbL4?(SgT9XV^8jz4d=GUMYsCQ?~=c0bqc2kI7fy9%~0;^Ao3 z`RnX=Zd=fT{>}|zGDZ)d-L3kexg^7*Ma_@fQ6Qz_jGbq|BgbUpPoIh+uUG9-IKJeJ zkN3)jv-tO9Wgb5Agg?Wzc8g<qUWk*ktB=p$NzV<nOl#ugr-?nAn0{}$#OxW9ESQU{ zvW%{psx03-^`qSJ4;-6}E>~tflT*nqwc24D_r^eK(M07myPQdht9O1E(DK!JCiZNB zzeiH|raMOx65h_&*cMm)<@t$<#hNS+^COS+R2N!$F45U>`_2aQz0QG*+fLXNDYaDy zde2LpcWy(^SyL;u`NavPsi);W%v-$krbKb?sgs)yJid0_cHw8$;{vP29>hoX<vp8q z-TnF23PV0|y=f0zqr*G?r!wkodiZv?V@RxKv%=bg&c~m`e{`IrA}P1oe|eYV+qJJV z@}GBiu<hzBQ`CvGHL$s7`0uUyyZigtKL4|wQ1{!={BLZVtG}4s!NtF#ZrEE~y!kqz z^_8!DaAsknk#V-VkkHqFRaZagRDOK=T4kP1^}d(MzurIp<KDo?pc>2aQPz!58&o`d My85}Sb4q9e0Czo2EdT%j diff --git a/apps/dm/public/images/map/layericon/BML_STMS_PS.png b/apps/dm/public/images/map/layericon/BML_STMS_PS.png index 423f2e8fcb87d6c1b9192d1547a4cf97c3bb7292..bf4357181e9891bf71753cc3cd75aff9262de4a2 100644 GIT binary patch delta 788 zcmV+v1MB>T45|i@BYy(%Nkl<Zc${^TO-N&R6vfZ|f8?3iTChpuMs(1P77NAf#6nO6 zp<Rr$6|(KLiw3)xaUU(Sp00&1hS^P+nN88f*dZ&oQc#p;p++&OJ|p49<o~{JH_0$q zde--M@45FL;GURS03aa*+U@oWW6aM0P^nbZ{o4Pi2ZNcl<9|3W_V@RBcXv1Neg7u_ zD3{BUndw7DeE>@$ikLaoY&Kus+}wP9d3hP0pPx%9<yN=b{VbJAJuQ_=f|)@?3;=Ww z&NQ3NSM_@RtLy9QFp45otJT=^ym)GAO6GF8XX$kMNf?I2%tS=7DwoTB_`%iH)$2y1 z@uh9sp=DVr2!8@FGbIuUaddPP20`$+Uax<e$z&c8QOL~HcksDw+kez*wFi#lgj#C_ z0Iuue;^G25%Wz#c92*-`xm@l|Hk&OFkw?t@MYUS3oSd8tM^O|KkpciBf-weXXJ=q$ z0Duqzp63PGY*x?D&%c|Tocu-sXb2(R-QM1QSF6=vS$`Io8A1qjIvpGzAHQz^AU4LR zxw*MGl}h~yAXG$TTbA|x>FMe3dwY97RIAl+U|>M@58QXd%(3gbVsUXXEEbDD647%2 zB$$~z&!f@N(MJml3%||G%qU|_{QgZuh@vR=eP1juFT2HJag~Uk_kzWWh?tpaV`D=M z4-Y?f9Dk=JgxEYdIA9{;APAU<#LCJ_yHF^6OGIxgm5R#e^C19;-V_FaQmLedhK7D! zT3Y&Lc6OGGF_cUu#p>#6qfjU;6VcnPtu39;=cE6Pvk;=6%!k|C+h%8H=Wk}7?5}fQ zR4SE(<2Y8U)%pm)$5P56t@S5|hlgf;ef^<r+kY43a`~@`i3#nxZVNygK>P0Q?rwT| z+M~6#HOu$?bQDD+%$(I)XW}?cODQw0R*SUODw#|MVHny{%9|*PY$CdnQvNeCGGbG2 zci}jW<v31KDV0nl5(B>PTUu)k0G&=J5JLDt5SYQi!EU$PHRI#s{^sUp{4d9PpR{5; S2Mx6V0000<MNUMnLSTa3;)7@a literal 1542 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`<jAm(=3qqRfJl%=|nBkco*&`K2Yc zN<f!FypUS}H#V;r8ce_d)2~P@&^OdG0C@?f(AO8g(%f9Q>f(~1RD^r68eAMwS&*t9 zlv<o$T9gcoxHM&uRRvbgMX8A;`9&f5`8lvq49LhY$uG{xFHmp}HdD|D&&*57FE7>v ztM~P_^2{qPNz6-5^>ndS0-B(gnVDkc?Br<f<mO^%V&Y_MXy|I<XyN4I>}X<S=w$9< zX=Y&w)9aF-T$-DjR|3<Ug3xP(Q!gkf<Q4#Jc1bPDEK5ysE6UGR0DIXg6Q^68-Ef)* z)tiFbEe1IC>H{644~kf%h=vIPQxAv<Pqsh~JmIJ20aJSsFj-$;(cR9#z*Obw;uunK zt0c(RUpP?2_Vdk!+ghhNaj#`{R_sz!IkwbLsHd$}+C}UiyZF(i(!#>!Z9mxW?6};@ zEo2|D%REmdk7w!IEnAsXx3Fee%g-}U<hb@J?cVOs@AeeG+xy(^WNGXd>76loozD!P z&)&Lq>vfBCkG(7`X&G_Lf*+s096UWhW9E%h;$|<RRWGLLH{NOgQfXzo&1P25;|Ci$ z7v4}Xnt3JmSNzx04(~eU7Vnhr<vmy^>vw%~()q&4|I^Q@J~*G4D0Fh-ibWa?D}1IL z51*v6Bq*7&YTk!CH|nJ>%zXC!xsRIdY>y*~lO3j=&^@SJFq1VhVC9tnohe>l^nO*X zP<L6){rU6en{P~fHotk;b~y3HEWXvZkH)QLJ-w~)>nWL4S3e!)m$$clI?+9yv+1q9 z*{4e3_h$DN<(4m4J!?M0E|$|ij}FdN)#zfs_*bZgY4=sDBp2q})uC5rm1=ByboJdl z4oOayi!VzOt?$;mghbZQ`F8Kc3kw;yU3SLpZofGuJ+6AWS}R$@h)0*f*HnPlo#mZa zVdSe1!6_edtg0)s3{(zoGqu^X_eG->i}{RD4GRY41&6okY|)*q%AvciH|O7ve@!<; z9XA-woavCdymN6H^VUyh{3qjfojsf!BVmxh!!o0{)g_C`O{3{bg_KIf2h)PO>--B| zvZ<+0iiof8uQ}MVj8kwWqkQ*7VP(TL&+-z!A2_srZf)b%KiyAuP3k&0q0y_9{rFox zrd@uHp_4S`nU?gb-qe_7I&I14c^MYBSwknL=H!MamP>c=x+`vEeq{D5w?u~hVSvEz zuU4gr?rloTWkpWE+rB>DZ+(f4+~>6Yfm-4_#s0os7x9nFfMHhRx5p)mI~zeIv!|<{ J%Q~loCIHG1Mo<6% diff --git a/apps/dm/public/images/map/layericon/RDL_ETCT_PS.png b/apps/dm/public/images/map/layericon/RDL_ETCT_PS.png index 29c0b77b4ee74276df8de9416fe284e4c8bc15ec..e5a83f998a8ea4f36918482e08ec93f13fb6c40e 100644 GIT binary patch delta 874 zcmV-w1C{*v2Y?5VBYy)&Nkl<Zc${sMQD{>K7=_P&?@ex!Cb6k8%U0JFmZ}x(=At;Z zhq(C?nHyowhz<*T2|_^_+mJG7Ko~`bLx!++WwL=VsAG&FExNT6+p^(I5v^veuB~ZJ zG+mS2rb+L;x%d8iSVh#G$HV`f!~bxARWUPxK>%P!9hf=98h^jSDsSRmwmo+Dq#l|Q zWN1e9(D~x$Bd4<ODaW(B`Be7Z<^F}P)Vz}Zh<n)u0Lf%hcnnT*QebB3YH8ri2_?!0 zW^Ek%3;gy?a4Zdu&wxKIY~!K)cf;ii8~{BkC1w@`lB|is=ojA_9UqQo#%o^5DTpKr z7Bd78ELc|AtbbZB1RHG!%#%Zlhu(<oyA-GZfIvi+r5P{%Sp4$H@YJ~J#adC*1+iGw zsHO{0R&=VWEPAZ9()H<k#zp>SSM%|^2Z@LQkOTmi^8@>GJS84&X*b&HH;YSp89&Yc zhN0XQ+V0<s{Y_E$oj#hYWa)>6Upeg@+q(yev+;9rO@9Ii_tfdF|5QqFdF#n<uSc`} zS*+g}gkcD1Z`q9vaucdpK}oGrd0C^pkZax8+_LUu{F6Be0APvE*<t~mnKRhx5Aps` zgm$dkgpSs|aEnfi7H;6&-<NSEKM7T+U|UeK73I8R^#z7(dS~1e*r}TZT+L=^Ja-m5 zf*bhbmw#i_GkFo`Xa9s@nQ)3S+%6YOs^|Xb<o7xvAOQeE>XxBqc?%UPD$DEv3<qDj zdz;cq78ho2p{9v&uop^2<AC!;@;!Lwk11R)_a5vefthKdZ*Fjx^POvJ+(Bt^d8K9= zcCxb@5^S(rE?9;GN~LC)k}U4Dy|pm%_3uCX;D4(n5(!fPfcL-JqqxRA9UlY_XMAfK zZTFQWE|xV*uhq;_MK^QG5=%~x)c$;8Ss%n_eS>|s+9GWl5m^KP{r&x7JRTQMA3gnQ zeXOBtxHJ-(R%azoorqFZN0V(mBHq_076<Y@o!y<kgu~$nsZ?qSz@uTThS(i&1iHJr zUt_Cl^=wx~!!K#PQc4$Z_MGTRWsJ;xI2<lLg#Uk3A;<vS0Q>-+1JDQ{2%sSvjml3l zPfzo|AR(1XiDUUO;m++lXlQFNkG*-!V1ByvU-_e+CpFW^EdT%j07*qoM6N<$f?+_V AZvX%Q literal 1016 zcmaJ=OK8+k6b&tw(vJ!*{1ogXU8vB>XPTs)II*3XOznWFWjfFSH)fL8PUs{rO<tX} zn--yn3!xS_T?m4pE=1_Um4buR-%fF(?(9ZzBO-1Di7(U6)P-Y6@;;n%?s@m#cYSK| zd~er@E{dXha}#QwjEAGIvxEHme}p<2j^k_*Por5})jde1OjLp(XX|q?4|Q{C;SEeu zR6A`Iinyp<kPT!rdW2yD+a+v@N{$AuZp=drN^s6{6#CnfXEd-(g`N>LPIEJ`Y)veC zaC&*NU@XrY36mZ@2a<tI1Z;?P5ZD#Rmji{a>&j#vZL>6}L-4#p@0u!VQy_yp2t;Nm zW^g<YB#Ge#QIhyU5a;+f%aK=#@q#Q$a(o!<Jv52tnX__U9p8&ZRtjCl*p*qfTCFnG zAqIJKET2duB8_-FMi4Q7$-#OMbNqpZf(m`Zvs`Q;2Skc`2`yrUCQf&cV7pCO$KOj6 zNf;aGF3U4qbfgAQ)BX>&?IzmCdARTQpTd4&$%Skl`e@NJNa1D&qEN1!@t}^8S3s!J z$l_EPVdR&Q3o_FZI6Y%ICaU^p>Ud3)bB>R7$ACFip@{-xS*ARm&19rhd{__#k>|5Q zN)$#$QrT=;Oi1Y=Dc#_z$XK+YgBx6PpF7f$8?}P%lE^CbtSitQ_mB<hN0zPDUWk)c zUUs==YcEnuE=$a?QDOfobYqKjPc&_|Em<@hA3CJlJ<`@6+h6XGD^ba*sY39nv^Bc@ zD}4Vr>=~;C%<tj$t=ea<uh`$c@nPle=0mlwcK=1^z0LLQV_WaWX84`;SKR90YNfVP z5#C-37hWIvav&+(e!6<?<j?1KzPEJ?r#5c&r8W;fIJB`AhT+AVN2#_RYP~~TJ9`6t PjqYMDJE=ZOU%vVWRx?RH diff --git a/apps/dm/public/images/map/layericon/RDL_OCUP_PS.png b/apps/dm/public/images/map/layericon/RDL_OCUP_PS.png index 8a1ac0074ec2fb6ede26d23e927b6fca22809356..aeae3e26680a9415a77f0d9b310eb18b3e5f6c58 100644 GIT binary patch delta 815 zcmV+~1JL}X2*n1FBYy)7Nkl<Zc${sLT}a(^7{@=)@BecCo^#xhEi1weE2P?(GLto8 z!*y{c>rH6t=wcT|8WdD?9q2*`293yzvFajvAvhRF7aLP*qs7|9(uF2la}H0j>B^&> z7d_|y@^rB|g#27Se7|3KzRv@9QB_e804$Fc)kh1eyKbvK)_<yMuh+2-SsRLoVmXvc zg(LRFP`CMWs*y@571>@<@2kHO>}>n$n=N%GUJ+Tnrw43R75e%_RZVd9;wSddd#`fk z5|mcaFvPf40@rP}Zd_`9x#^|n>QoPdEUtx!sv0p?<j;(MXrKA$-C%mm7bhjsp0b$p zCAwHrNl2uXLVsjGw9P5)f8$J3>OlJ+4O9S(h(wD~*eK@*-wrO1g#<O0v?grR;)&T; zg?Y*ol45C)wEdacAU<)r?X=8x-Ce;LfU3!#JFFX%R#b9ST3lmV6R@ICZDHCsVGgCh zi9`bruKuQ*ua9=o+H2F-rv|{xXLs?}0@TLDI#>;k5PuH;OL#U9jvx=aSe+>p%dn6W zZ}H|1+V-a8=+Qgp(IBj1xdPkcu-PX1sX#Ee1PO!Q4ls>XnB8&AFDo#dC-g%%Zs(j5 zhyf_O$?Moj)m#N42u>{%`H^Zl!XH|qGO&Q&3gCXD;AuHloh^O-S*3TagZYNWVd@(s zyzsZ`Tz`Oh!b2AW(Lx9@P+uVw{LviLZk4$$b?5kfxbVWslOn1rM{W@x82|QM&A_pi zVxr{7y0<wxvx1f*%!3Z39Q=u;z&o{M=U;pCX7_yM`3nQbr?c5?8Pz*QW~OI$7QZ-g zO%LwXd}F;9UfdBBp55-}pLsmUKmACg`<k`*@qY))pJoOR12vtUo$mTqHX;(e|NZ%< z*DJaHO+Wr{z`Hi<vQh*u#%kSG3a$G`zYgtV?{svWO{df4k&%(&y#RnrCS!GVbqS=B z$G`h>_a34>@knb6QCR%7?2o_I-#^CG)IaHT+FvvO4^@>X!2xU_26#XmNIU?Eo}M0O tLnz}$L8@v`W-@N3x7SrwgUE*3{{XBoeqw|U;4}aL002ovPDHLkV1mCili~mX literal 1061 zcmaJ=O=#0#7>-tp>_^m#%!?s|Img;0{mI&}x^+$K0(R84*dBH;O}?(#mV7b!y0*iE z8*?alQ&5?^$i#!92*ZQF8;I!1o2VDBDvTW_3R9S0y0-IR4N1Nqp67Yr?|q-|a$@9U zd&`j)f*{((VIhgf1NOJiiT_78wj?}splBAQU>=nu6A)nq<^U;b(ilhrNvWKB3;GGd zL8<91%EnLeGSu9Xjd7PX1G5RDf1qqg@&rI+4veXKh+2R8k|I?lL}mPOCT>JPK^>kp zL27y=El*F#L4_JPLH3t<ET91-k!5XCxA=01s_OE1Zg0~RS%sj95Vf6DHl83O&;+F4 z?RCiv%aR=DW_^B+V|z&t!+K~2znqKp@qUi?^pUlP!qH46&nJagEf!vdr~*O;Pt&DR z$zAfgp*cpg!C=tV@OWGp;j$_^lFBaK>Z~gWz>-bXKq}NpTT#lvA_`GB)9oW@MnhJ& zYGuL&qsx*(vu?&dQXLqN{~xMp4YY-lU?<;y3R~%l0q7*KV9}Iu<MN$$D1(ofKtj+= zLpWKlVxj;Mv<lE5BPovT&ZxQqOV;r!J|5>q-9nNs15pT3Si!BT3h$3{0UyhReSU9@ zWuv};j|mL<W1g_bi>vMp*SP|eiyF{TovZ9{hnjM2D`*CeEC5rT0!qw;8d*IuuQtym z&{VI+Rhs7#Y|5o^GPK>;zZzZN!tSxB4cp>H!}vhQZa1;54?CBG_)6Rqg>bt3Dc9T6 zv-xhY>&x02P<F9PckYPqe}Dg)V%oWtdyi%mNB-{8tlD+s!mIoHoj<;f-u}oCQ>%|R zoz92LEzabJwgk=0?mnH#TvzDP7w0<;UZN<f?bpxsV?1+lZZ7ehaEy<S@9}?rQf&3M z8R8+w{6gy>8$Q$ftg<mbb8DICBd&G7*?VPc>+0gdDnYIg;-c_cm>i7R7gvmq2oHwN G&i(<yMpM!N diff --git a/apps/dm/public/images/map/layericon/RDL_SEPC_PS.png b/apps/dm/public/images/map/layericon/RDL_SEPC_PS.png index 087b4e94149966fd4edc1dd5b27ac50e40a4641f..b3db8e0a41bfd9e32d796bfa4035bde320607b2f 100644 GIT binary patch delta 767 zcmV<b0s#H22$lwrBYy(jNkl<Zc${rfOK6-`7(L&2??0JInuN9)XDBXGQ)!J<5D6lJ z;zkg3(M1*ANvjJXU07Wy9irkw7lIoph?If_i?1SXtaU)~1rcLyiosH`t?fKwI{D}E z|MSni_qb?NlHl2%2j_5rO(3ZNB?3_MJl@LO3jk0ht!x(mzkeAZ8C~%_{+=h_s;vwX z=bi_E{iGqeA;+#W8xip&hcEc>(T_$Szu4@!#p|&NQSNB$c-xrB8$v2chzM^5tTPvv z0zUrM=tH&Dz8hAt!yvz@HT^*J^?&+<t#_y%mJo!bib$|0Z<&*wX9GU@UU6^Zdg=<+ z*fxLwkx`+3cYn3!k{zWN&n5lsyMTit;z)|zY;gDQz!z0wXQ!RHNYx5}ZkFNv^1mPe z1EB4_yQ@^P&u+<mI-N7mh)7>Z-gCO!ub%eUrfKF>#gc%Epp*5nSZ{y;K)?vlbzx90 zPDC$_=g&N_b?gZX;9AayGxc_||9qo?F;#fn2xzA%et-GvZ;%84C=i14_OU@4wiS1O z4Zv9uX^t3u^5=H%{dcu=VE$U&jT<!pHr!=^;C;}icTc$!Gkc#Ckz*i5NlMpwxo<ds z@YU_3?>ut*4%_lRC~h<t5g>%%2|ci9Dm^i?_bCxMR;gC)+La(FAXNc0((K49wH40% z`bW6`r+>2nAAauT<d;1B+fq#OQ2?k^s}`G4sZ{jgn?#ajvv1Z{c;^obEPVDAk1YOq zmE?l}aCCk?+B|OqM0KszzSl0-leaHiS|Yh?J-0z$sZ=zM<DpiowFSVaF=i}^3gb)X z7Lu0_9oo0tXk7l_)TxC%Q&UlzrY!(%0PRkv(|DPlp6<)+?CemMWfR`}GRaaDMa2-p zgt4~RT3waMT3g8H2hO>sF=p9&-xQI$F{V~7mz%P-KaS&}IF1X}+Cnau%V$|O6h%=4 x0NrkPpsLwmFi1v5MtZ$oFPWU2%;x6i!hdSPmcBW5^N0Wd002ovPDHLkV1lGLdV>G} literal 1068 zcmaJ=TSyd97#_94Y=wl1?4`4#fk8X7v%9mG$<1_UH<wzobwOVWXJ?K&=**dBj_xSE zBuI!o^bkG;N<BmrLI_0{$d^RBKJ-*jgb>t&Bt>Dcv7J#@duSVG=3Mx`@BhyK|7X0t z?L>9efhvlks*|l^nvA=huW|?ZAN*dB$WV*pUAP1F;GASas#!r<2$GuA3)4_ihR-j; zMvAJS)l3)eN}Up9q%o3%VQkGHY>H}ZvJFY@hZtmGuc}AsuaBP4Kvkl2C!g}Bj2P@w zTSrXTG18WiNBZT6LN^@)jkZ7pG>9c&YXiC^*im{-S0Hm|o29`T1oubjbyHobb`V1* z1U%#S$X<>E!63s0_+XGb0(@T1$9l;d^l$-z4+_2z*m!6X%~X1Xw3yh4MOIO|4`V}M z*<3Ei<opaWds!|Li8vZQpNAkk*07Ex+oM}`MFkOBvZ)$aMLKX4r7Rl6QJOeiKZ0g# z%Ielenn=P}TQXRV@j6E;0#m8~Lp5y^ZQ(TB^7~I=D>H0BHVrK_Xv(B;J#|hfLx`DB z!pO`ZG*HZ9dmqBc>O%&Ibp$~}r>ZL`XB}R{r&2;vx3HwkFeyf9qQI!CB8VZLWVyv3 z;Qb=U#RCyB;g5N}VQ)Ab3Uh&Ykt-s3P=h)ya+NJ^pd{C61<fFlMQEx+P)V3b18YYX z)Y4u^*-Lp@=PISW_)2nFVup1J`&XfhTcmrO>1NxK#b)C}opifN+B&OUY9Uu*Iw>}1 z?04CGq0m&}YB>6{E*zeF|9kB6!i9axtNW*4*S2nN*da#p@XF-m{H@h5Z*HzuQ)O<q z`<aU>tEs7R-3_4+-Tc+@i!F1bCztYXXN`Mz9yhjCtURQq64zaJe#QOk9GD-v#?0+q z5+8j0_UZYar{y<hW|$Y#1rS^~4qmoS?&{uIHFdk)^?7-DY<6N|qSjbE!yhW&wx8-h V%kh;z>g%2Rn~b-KGcDbte*gsmT|@u? diff --git a/apps/dm/public/images/map/layericon/RDL_STAT_PS.png b/apps/dm/public/images/map/layericon/RDL_STAT_PS.png index 13d8ea6899c02aa0d0917b630ca9f697783fd6ff..9a0b11673abfd8ad6b85c32dcae55f5e3f7664b4 100644 GIT binary patch delta 833 zcmV-H1HSyT2-gOXBYy)PNkl<Zc${rhU1%I-6g}TJGrP01*-abMRBHUgpb3-|yA(?i zqaqTpDup(JBGi|K(wAT#N_}v_A1XzJA{tF44}I`q1s_UfEmngug>>mcY)EUft!dh{ z={BZjXEL+1GvCbdp$(<2_u*c++;h(5au0APND6=g04xCHa(@?_|86E1yLl#W+&gz) ztTvMXJk@;n>wnZA@684E9hVh%&jl-o7Q#xk?(E~Sf&~DDLP6dGE|8KO)qlC=kG!=+ zTIWe?GGOOfpQ*_v&2!`{HS+r#?r%JCv;#ui-Gro+08TG?Uu>Hv@w*djo$}PEqvClL zbJS7s)+tY6%zp`<nI(&}ODAi6>sm;PyCluprrp)1N}Y1m$5;G#+K*|IyrxK(<b)e> z*LjcnSUr4mmMl-4%CCw807e(;pL9-=2mWq^G0AyF&MER*bh}<xq)&2i##NcJiigf# zJ+U3Ib`_~f0pL<YJiB1W=+Geuz|w$L4KO5-)qu2i`+vZXCN<&I196>Z&vQ?h>wXvz z)B(T`<5V(=@#Tz%c3r}dK*o^hvLvc*j0=uJLJE9U^`I#OK}3T|!vuVx6F@d<&ORj5 zFV5Wxv8QZfOS^$HO95ZoV2Ym?ns_Rs;i4143nOGQCeyxs<M;(9Kwm*f0NCtb{dJ=W z5jA7BNq^!%w*@Vtc;|5wiHP{1JB^KLiO>uAq?HhzwV8=B;C%b`>q3$gmyZmVcAATm z*~}W<_EebC1Q5n((FLGl7!vUPP(g3eb{MtA^6+QJ)?#g#&*vfu0NaPg>+8pl?%mhn zTxrd=8HfM0X}du@GF^}AZWs+u*<2P`ea~a@ihora`t+O9*ty=m-lh;D1^^U`MGZjW zyZ!kWMkcB!-ze3>O+PtY|D(&E<12i%w8+Ebm!>}7Ke!jb)45!(6@b8MS|u467)Y!E zIu3sL{x;F`{1zjXZg03NjmBT6r;i*wRGCqj&*gH<l}g11!2dT@S63213xIY28vr~4 z;5uO-+t%OTpS&OQpf&Hj=y0W?`8RLMi`6RHdV8afUVcgOK~4MzMZ?7h!F%vn00000 LNkvXXu0mjf>vELe literal 1075 zcmaJ=&rj1(9512<`H{#)kqa+fOw_P--MZ=;1{vKZi<T^7ksUbDwGY-n`%3%91`#q1 zh#tHc{|C89NHoNRgNvFN<53T4gcyl2(SuQ=s3FW(VAF%MY5U&y<@5P`f4*yp{<AHO z?Tr*gwZ!`(NwOYxzJ`P3-|@99ljRtWrttt8!Fkz&R9HnB2;#at3X@P)rzW1jE{dwB zwNx6XrSpP<bdT&{JO$k(Y>MjYDVVY{1~JIMQOyX^pH}bFKvP5XU_kOprU<iI-?Rk> zru$RM^q3M<>7H)TRS<}P4zUah`nX{Wg%G`?E0Dc&%+O#5g2zJiZcu3{0Yqd$5b*fj zikD>p&wE%d!1HV;@OfDu<0UWeW;r3i3%=7}@1cn|OC1rCk=ULuVuk1|#-_k9`F!4! z_j`~v%CNy;(9!Vu+yvparwl9?+=lI{C`6#GSel77WB^A|&Y&C)(InE{6m+vHYuI~b zA_Zd#vdOR>uai;*C`tc^>UtGz<0SkO?>~j@)RYOCB(zb^Qb^-QT#hSK5G^QUWTg-q zuT(LSMHtyxWCC%32PXzKLq&P}<PKhvgt%d2*-&6S5~7KMN7Gb6<ilZ(3j{b$<XAS! z5krhcqyB&x=#9p>GZk(GDLEY)xWZNcaQT{CXB2dkc#c3zn}ll2LOR$<S<q_7;;*T< z$5m^`!qwz5BpAkN>|c$p9Fgg9w$-sEMs@I^L8jdzV}1OY-y&DyRy-0;6<%ew%H^JV zzVupXjlM;>ZWo{HyvOCIS2I^-|6J?Z0Wkk`VB^ijLzBFB=EJsl<;(3=cId&}xjPGs zpsi_Q`Qf|pQtxK_1*XV#|N6FYsjW08nU6nin%`GDdOo)o{msL(3qL#dFVEMxR<e(d zG`-+9xZ+J*qI}fR?%D0F^3wWx`NwlFn`nr&lv=N^&CH}XR_-n~xK7pWYo>;76pH}7 QeD7T2c(gyV)H{6nH~m6mg#Z8m diff --git a/apps/dm/public/images/map/layericon/RDL_TSCT_PS.png b/apps/dm/public/images/map/layericon/RDL_TSCT_PS.png index ffc22515996bacdfbece7d30e3d5afcfd20269d2..3776f6c0493ee2fb6717ff03adecb0f228bf8673 100644 GIT binary patch delta 901 zcmV;01A6?V4Vwp$BYy*8Nkl<Zc${TZYiL__96i7P&Fdy<+H7{U-DXp*Wn`VMs}ncJ zx~U+H>0Hs^2dQXj!PgXLQTF8~RVIGYfiSCxE$GAsKdi`F)Y+}`F$a5KX&|m$TbI_R zY5K_H-rU^W`}2d{gnB+6&iNhA4-R1!%uGZ8KrA}}wZQcN%6|b}1DxYTRQyjze9K2` zNmbR`zWgrHaW%GhpQ>3t*-7fX0eAe#eO0IG>t;?8ZO<!;A|jE91pqc?rf@h+%q*#z z{%MCY`oj53voMSTOw)uA!tHRln$G^7ZE8E{KbT7nKkKb}WbRfdF|)8qWDWdq{ln^- znm4YD-YgwGK7T@fpDX|gLX^M^YdK?Bf!b>Ov5t+yoOti>*laKx4u@F~kySGFrqgHt zdTsABUzpn(e4^*w7AccSli)e1szI7&NEKzKZDM?)^zHYl{Z(6DdTzA>2>|$UWb)~& zV@b&djSsbdE&LVb82Y>c=TG!f?>i?^;hu+@*Wq;;bbsmE6pxNut<86%JQRxldth>A zdHd{K3T3iEODlxI)30FX4j(o({ffsAx8lfC1IXkUlF(@>y+D7b3R`yW+ID9&8e%~J zjH2!~^c>8R3d^#%#y<(*9P*|IzysJ+xd_uxiA@cLk%ws&?OM?#0}z4%cw9W~b%Qlc zh1;H{zJH+y5x;l}9<v>@u{ZH?bO-Dr3ti8{?PT`YjrpH1#*EuP*cx#Cw6%U6WmYom zwC6FIkomx?yU^Np1CPGgjFD^A@Hn!_X0lwn(MNty{_@1le5R+T2ZEXD+=bquLyy&s zuJ^lbD@)6U$C;<pk{tsh_al*%;dAAn=CdWRDSth&|E|Rg!_hNdueZ?I*=Y&@=<50; zZ7arKKhpkiqVkS1dn%b?U0bl6V%9YD1#3Pz&kl!Vd-?f2s+PWd;+yE;AB~NTIU=$M z0Q&p;rBEp3Jg&U8x3X^cvEg5??~YF-rA)Q}*=5H)bsMnnq541Prhn^u|AUVPgTdf* zEPfVSyuAegP!z=$i9}?t1DlV&-L<D;U2U_F9P5ilp^!>W#lPzN?8;0sdm|VOCS$SK zG5`x}p(u&~AOr9N*bJZ^Km&j}02^9bT3l;Aw3b+1rwB)*HjIslsi`Rh0)bL@cXx@I b|EK*2JXN2b<c_UL00000NkvXXu0mjfL6OAJ literal 1700 zcmaJ?c~BE~7!4kP+-gKctlNlFwUP@GASK~Qf}|xDLd58lYK{#FklmPF$N^e~ieg(~ zP;3<usnDt@BPuP3f?BR3YFkB(1}$=VK}IM?P_P>W+CNHncK3J8d++<c-}iPlGjiQp zX9o`l91iEq3+D*1(arkU+hXr!{cpwCuo&fv&}dkWYQ-857b1ftAiz_J<3Ir@mg%?t z3I^hEGYLwe2o>=o7*be86k9Pwol1?dak#)BomwnS08u~!#wj5t!CX~C0F*K&VFQg% z=BwGDLK$w*fYF9^La8A^8XzMCv4B7w0~1hzs2I?x5+Q`4V-m)78Q9#qO(Fo}5Hx{F zc$buj9|^Ew4G7SPKHgF?g#ys&M2atsPN%E^sALM2M8<BqH^rAhqcf;}z{EwsqG@Du zhJX_`5er)}2?`WdGe{(@R!h|S5MfOmi4qVHVAY^fy)lG0qK8ni&Kp9OPbzRgM5<A$ zQ6&rkRz<M{PC}UkEYo)-sMJ%k5HfL0*nyFBVl|0EBwI_G1oHX+4^^qA&<H93|H=11 zg%P1%4Uz;P0w-ytSmWf&t)bKmwgwcVuto^OiIb-osen-!QNU_|9Zd&TZcsupSc|M0 z$Mg9N9)zG`NDA^eOai7rR4QeR05**p%B6?-`m#eP6t1s-s2`0>_6zk3poCKWLTQs+ z4lGSlfe<>$l}&N|$kTGIR#2(2$Q)3kOa^6P8dwF4m&{O3p9^PNy$P;t+Fa<|X}KgU z8IrZJ|7!H)7Umx7bjr5aV#@d+gt=XV*_sLU$g!_w5swoh)IA6k#40}H;>T{9-nPDk z^6DR$io9uMF|)VLSzRp(NnBP0h`4K5_Pf4Ugq%pTXYFw)v!8uZqzHa|>VEJxvg_3k z(!a@kRhYK^PUP*JqRj7F^KbNCPBDAlGQ}?S_d(=0hw{zMi<=fo@XMQ5@4480GKa-) z!5{Iz5`(*I^ILMAc4vf*?d>gwjc$BLBt_x5qj1%tr2Dp8Z`%pFzi{hz4|jL6Dah6f zU#=(vo4VJoxw@jFYa``~c!^i=^PN?v3XJm_n}*@k_Xm$$ADnl>h|7s+`lPnUE~mAr zzO42M`Im5|sj6h)<Fm~X=Q;B5sNT;vSDKIA<sI;v6CpRmS7Z}CO-C!L#`0bO`)khw z?lbas4WE9{BC0fus(boevug5-AEq29?^rg{c+M$5c}Yph3(@9LDKe5?-oIV(++wf@ z3U_2|y1X|H(igJ?^A<eccR0PZ%8n9Q0`Q|Rc=Y|LoH<fZN_c$0tz&T=cm7ChBs249 zQ!wGfTZor(#?S2wX8q)u664gHR~K#}!rWsT2VLUDLomLwVswcAMlapip>_P4V%wAK z(6BYJ@`tl$+)VagHk{*?_zxW4Z7y)kZQ{XpiVJv;!?JSQr!Ivp$zUHFvYqP~QZvf~ zZ;9^EuN`VVACaXZ8m^axX{*w68ok<&8Oisd&Xoa1wbAv_>?H>qf+RunJHL8WwD)Oq zuF;+KI;?$XiqEC~cxQcV+nKDqit>1M*RV_Aa3fA$dO_Xx!lhoa(423YQ*)Xn>`QV{ znIxA6tLr-tIhFa$y0cr|J44(j$w;Wt?HLl5Sw3pZMVuqPxOeSplxsB_|E#;iZMb2y zAHEdn{b(>VGu!fdW@`WS%YmY(I%9LkdtC+fmK)t#{Mi2abALpnZ|-e)t$Ul_8+edd zpOC)3E;r{%-KWy7Itt4oEVkeNsu9=ULaC<Hu61Tt;N3{6R6*?#MojJLwx{ghGZO!L z78if`+xmf*M)A^-qW0K*V*~wB`L|k&vzVLypmz<lufO5gKJqXtquiWRdO*>>*~S)^ X+MJ>?k7n6e{~J8+I!<ZmmbAYC)BCkP diff --git a/apps/fo/app/pages/map/page.tsx b/apps/fo/app/pages/map/page.tsx index 2c27869b0..1a84449a8 100644 --- a/apps/fo/app/pages/map/page.tsx +++ b/apps/fo/app/pages/map/page.tsx @@ -3,6 +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 { SearchBar } from "./searchBar/searchBar"; interface FeatureData { fid?: string; @@ -123,22 +124,13 @@ const Map = () => { setIsSearchModalOpen(false); }; - // onCheckedLayersChange handler - const handleCheckedLayersChange = ( - checkedIds: string[], - checkedNodesData: unknown[] - ) => { - // Handle checked layers change if needed - console.log("Checked layers changed:", checkedIds, checkedNodesData); - }; - return ( <div className="bg-container"> <Gis treeData={treeData} isSearch={false} gisMaps={gisMaps} - isMiniMap={true} + isMiniMap={false} searchView={null} detailView={null} onFeatureSelect={onFeatureSelect} @@ -148,9 +140,9 @@ const Map = () => { setIsSearchModalOpen={setIsSearchModalOpen} isOpen={isOpen} isSearchModalOpen={isSearchModalOpen} - onCheckedLayersChange={handleCheckedLayersChange} + onCheckedLayersChange={undefined} imgVisble={false} - searchBar={undefined} + searchBar={(props:any) => <SearchBar onFeatureSelect={onFeatureSelect} gisMaps={gisMaps} />} /> </div> ); diff --git a/apps/fo/app/pages/map/searchBar/searchBar.tsx b/apps/fo/app/pages/map/searchBar/searchBar.tsx new file mode 100644 index 000000000..9b0800399 --- /dev/null +++ b/apps/fo/app/pages/map/searchBar/searchBar.tsx @@ -0,0 +1,158 @@ +"use client"; +import { SelectBox, type SelectDataType } from "@common/business_components/ui"; +import { Button } from "@common/components/ui"; +import { useCallback, useEffect, useState } from "react"; + +interface SearchBarProps { + onFeatureSelect: (feature: any) => void; + gisMaps: React.RefObject<any>; +} + +export function SearchBar({ onFeatureSelect, gisMaps }: SearchBarProps) { + const [sidoValue, setSidoValue] = useState("0"); + const [sigunValue, setSigunValue] = useState("0"); + const [sidoData, setSidoData] = useState<SelectDataType[]>([{ text: "선택", val: "0" }]); + const [sigunData, setSigunData] = useState<SelectDataType[]>([{ text: "선택", val: "0" }]); + const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([{ text: "선택", val: "0" }]); + const [facilityData, setFacilityData] = useState<SelectDataType[]>([{ text: "선택", val: "0" }]); + const [facilityValue, setFacilityValue] = useState("0"); + + const fetchSido = async () => { + const url = `http://rsone.iptime.org:10010/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_ctpv&outputFormat=application/json&propertyName=mctpv_korn_nm,mctpv_cd`; + const res = await fetch(url); + const json = await res.json(); + + const list = json.features.map((f: any) => ({ + text: f.properties.mctpv_korn_nm, + val: f.properties.mctpv_cd, + })); + setSidoData([{ text: "선택", val: "0" }, ...list]); + }; + + const fetchSigun = async () => { + const url = `http://rsone.iptime.org:10010/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_sgg&outputFormat=application/json&propertyName=sgg_cd,sgg_korn_nm`; + const res = await fetch(url); + const json = await res.json(); + + const list = json.features.map((f: any) => ({ + text: f.properties.sgg_korn_nm, + val: f.properties.sgg_cd, + })); + setSigunData(list); + }; + + const fetchFacilitiesBySggNm = async (sggNm: string) => { + const encodedSggNm = encodeURIComponent(sggNm); + const url = `http://rsone.iptime.org:10010/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_il_swage_prcs_fclt&outputFormat=application/json&cql_filter=sgg_nm='${encodedSggNm}'`; + const res = await fetch(url); + const json = await res.json(); + + const list = json.features.map((f: any) => ({ + text: f.properties.swtrmt_fclt_nm ?? "(이름없음)", + val: f.id, // f.properties.swtrmt_fclt_cd 도 가능 + })); + setFacilityData([{ text: "선택", val: "0" }, ...list]); + setFacilityValue("0"); + }; + + useEffect(() => { + fetchSido(); + fetchSigun(); + }, []); + + const initializeSigunData = useCallback( + (sidoCd: string) => { + if (sidoCd === "0") { + setFilteredSigunData([{ text: "선택", val: "0" }]); + setSigunValue("0"); + return; + } + const prefix = sidoCd.substring(0, 2); + const filtered = sigunData.filter((item) => item.val.startsWith(prefix)); + setFilteredSigunData([{ text: "선택", val: "0" }, ...filtered]); + setSigunValue("0"); + }, + [sigunData] + ); + + const onSelectValue = useCallback( + (val: string, type: string) => { + if (type === "ctpvCd") { + setSidoValue(val); + initializeSigunData(val); + setFacilityData([{ text: "선택", val: "0" }]); + } else if (type === "sggCd") { + setSigunValue(val); + const sggNm = sigunData.find((item) => item.val === val)?.text || ""; + if (sggNm) fetchFacilitiesBySggNm(sggNm); + } else if (type === "fcltCd") { + setFacilityValue(val); + } + }, + [initializeSigunData, sigunData] + ); + + const handleSearch = () => { + if (!gisMaps.current || facilityValue === "0") return; + + const layers = gisMaps.current.getLayers().getArray(); + for (const layer of layers) { + const source = layer.getSource?.(); + const feature = source?.getFeatureById?.(facilityValue); + if (feature) { + onFeatureSelect(feature); + return; + } + } + + alert("해당 처리시설을 지도에서 찾을 수 없습니다."); + }; + + return ( + <div className="grid grid-cols-4 gap-4"> + <div className="col-span-4"> + <div className="card card-border flex"> + <div className="col-span-10 flex flex-row"> + <SelectBox + labelClass="w-[40px]" + className="mr-10 w-[200px]" + selectClass="rounded-lg bg-white" + label="시도" + selectArray={sidoData} + onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")} + value={sidoValue} + /> + <SelectBox + labelClass="w-[65px]" + className="mr-10 w-[200px]" + selectClass="rounded-lg bg-white" + label="시군구" + selectArray={filteredSigunData} + onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")} + value={sigunValue} + /> + <SelectBox + labelClass="w-[140px]" + className="mr-3 w-[250px]" + selectClass="rounded-lg bg-white" + label="하수처리시설" + selectArray={facilityData} + onSelectValue={(val) => onSelectValue(val.toString(), "fcltCd")} + value={facilityValue} + /> + </div> + <div className="flex flex-row space-x-2"> + <Button + color="primary" + className="mx-2 mt-1 ml-10" + size={"sm"} + onClick={handleSearch} + > + 조회 + </Button> + </div> + </div> + </div> + </div> + ); +} diff --git a/apps/fo/public/images/map/layericon/BML_CBAS_PS.png b/apps/fo/public/images/map/layericon/BML_CBAS_PS.png index 5ff4add3ef67899ca56ef065952b919b8426d086..0e70939baeed2a0221089ebdf2ed19921495c8e9 100644 GIT binary patch delta 897 zcmV-{1AhFA43`IxBYy*4Nkl<Zc${TZT}+#07(LJXeue(PMzF0Gl#aAibkPX~Gt`K4 zL@G!^veC@NWm!z-#iEIq3o1VsCd(F0%obo?Ng^0((6}Yj5Qqgqkrb^I$|$Tb3bYjJ zr0lEf&-Z#^F$<o{ljofC<m7n>SqLGBhyXxHk_xF*(hA@J0Dl?45)q{yniG3EdYI&# zLzd+;Q{z9LUkL`EeefW0AU8*S-)JhjeYnEjXEGIc5s}>1*2X$IIurnqgb>u;-cCXY z?)Q1GyLvxA=l6M$PG`V4M>>_#sU)c~7?`g-QeD%yu@OI2WXj*%3nd{0V~i<*g}F~| z4Gz3NKRc7kQGaXLOLY!b|9TUJ*Vn|gD<3QJZd@s~SXD3AzcD=R-E6*aq2)dRFd|YE zMLF`*@X&iR-rq8uOI(we&y}1U+S!f6Vk@VH<`!-=6-j}>LV9-k*W*=HC8x7J5CZ_O zXR2j+DZq)C01?w^b-3f3!GwDR08rP^1hqy>fS|x_zki5^Lnllolh)~Uk^}(JNVsBa zGXlmqoqgwBR6bvW-s@NK`{d8CmOY77GKrPo9l@AH+uPf;^Jna-nwnz<m#c480)SK| ztx3o-)Y?3lORazqIApP5@T)Hoy1R@gtTt4?T1V4kZt+)a8<})QmFA=d@b3m1tuB~R zGOxUNtbY!V7!2^w`S6XaAIBYyXlOo(rQi|{S<4_P0z=;o3a(OxCr8JU|8-y}E*UnL zmeIuMh=|8`5RPrZWH#gUx%05t%JEu#BWxBM0&_m0H$F;St@|Ysxf}24=^+wAP*+!9 zfw@>S@<sP&&lQ`^se^i>YI$KE<xf9@6K}qSv44>vY(=A)vEd(h%bB;M-;Yk5y>;Wp zL@XAIQ}!UJx8HS?CGPd#{JO6s6j~K(wORmB@(&i!dT5my^ag4=d1`k{PJPgN@#43& zwY3qq+x<5Iz`(!&cRHQ?^5svDmY0=$^vBd>?fTk^l#pd`&hfap6o)ISeE!Ad?hjjA zhkERGd&J}M>;Mq7_Y_%^900~km#(~EFcefvjOml<Ok!;<GJCcAnkTxk5whFu_x8lt z4<Q5r$OVuGKo6h@Krw*F02DeLj@<nk+D~MSn8)Mc<KyFOad8oag@u{U&d#(D{}25K XcgcgJw%X%T00000NkvXXu0mjfo@TMS literal 1547 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`<jAm(=3qqRfJl%=|nBkco*&`K2Yc zN<f!FypUS}H#V;r8ce_d)2~P@&^OdG0C@?f(AO8g(%f9Q>f(~1RD^r68eAMwS&*t9 zlv<o$T9gcoxHM&uRRvbgMX8A;`9&f5`8lvq49LhY$uG{xFHmp}HdD|D&&*57FE7>v ztM~P_^2{qPNz6-5^>ndS0-B(gnVDi`<Z5p0Xk_kaV&V)8bypKdGh;(%XE$dj6H6Bh zS7&3GUYGpj(%jU%5}4i;gkC3{dO=Acw*Y9fOKMSOS!#+~QGTuh*vnR#xZPrb(>$o& z6x?pH#Hm*w=oo!a#3DsBObD2IKumbD1#;jCKQ#}S+KYh6+H&5{UIqrHdQTU}kcwM# zCdYbA4wN|lzxuh^-KyftM`9}$b|eHj#ydx=Nojj7R_fibWOhtktBRwzr;AGL6JLcX zP9asojgm^Nvyv>YoZNEAS66w8U5Los;>%~ht$SX1yNy%n%YpY#=RBYD{eR7zVv*&a zHg7yw)KPTEN8<3l%tLM_g0*X7YcHPdDrJgV)VE?sNS)uzi4)u2J~Hz-$nY#Z{b5O+ zn|F2jglp2BSwbJzieC`a_ZJb3;`&^C;m;P%xwd6GyY5cNP>d5PEO@UsWuC6v_uqA2 ztlg|m)V8_oF?{%<)YbL4?(SgT9XV^8jz4d=GUMYsCQ?~=c0bqc2kI7fy9%~0;^Ao3 z`RnX=Zd=fT{>}|zGDZ)d-L3kexg^7*Ma_@fQ6Qz_jGbq|BgbUpPoIh+uUG9-IKJeJ zkN3)jv-tO9Wgb5Agg?Wzc8g<qUWk*ktB=p$NzV<nOl#ugr-?nAn0{}$#OxW9ESQU{ zvW%{psx03-^`qSJ4;-6}E>~tflT*nqwc24D_r^eK(M07myPQdht9O1E(DK!JCiZNB zzeiH|raMOx65h_&*cMm)<@t$<#hNS+^COS+R2N!$F45U>`_2aQz0QG*+fLXNDYaDy zde2LpcWy(^SyL;u`NavPsi);W%v-$krbKb?sgs)yJid0_cHw8$;{vP29>hoX<vp8q z-TnF23PV0|y=f0zqr*G?r!wkodiZv?V@RxKv%=bg&c~m`e{`IrA}P1oe|eYV+qJJV z@}GBiu<hzBQ`CvGHL$s7`0uUyyZigtKL4|wQ1{!={BLZVtG}4s!NtF#ZrEE~y!kqz z^_8!DaAsknk#V-VkkHqFRaZagRDOK=T4kP1^}d(MzurIp<KDo?pc>2aQPz!58&o`d My85}Sb4q9e0Czo2EdT%j diff --git a/apps/fo/public/images/map/layericon/BML_STMS_PS.png b/apps/fo/public/images/map/layericon/BML_STMS_PS.png index 423f2e8fcb87d6c1b9192d1547a4cf97c3bb7292..bf4357181e9891bf71753cc3cd75aff9262de4a2 100644 GIT binary patch delta 788 zcmV+v1MB>T45|i@BYy(%Nkl<Zc${^TO-N&R6vfZ|f8?3iTChpuMs(1P77NAf#6nO6 zp<Rr$6|(KLiw3)xaUU(Sp00&1hS^P+nN88f*dZ&oQc#p;p++&OJ|p49<o~{JH_0$q zde--M@45FL;GURS03aa*+U@oWW6aM0P^nbZ{o4Pi2ZNcl<9|3W_V@RBcXv1Neg7u_ zD3{BUndw7DeE>@$ikLaoY&Kus+}wP9d3hP0pPx%9<yN=b{VbJAJuQ_=f|)@?3;=Ww z&NQ3NSM_@RtLy9QFp45otJT=^ym)GAO6GF8XX$kMNf?I2%tS=7DwoTB_`%iH)$2y1 z@uh9sp=DVr2!8@FGbIuUaddPP20`$+Uax<e$z&c8QOL~HcksDw+kez*wFi#lgj#C_ z0Iuue;^G25%Wz#c92*-`xm@l|Hk&OFkw?t@MYUS3oSd8tM^O|KkpciBf-weXXJ=q$ z0Duqzp63PGY*x?D&%c|Tocu-sXb2(R-QM1QSF6=vS$`Io8A1qjIvpGzAHQz^AU4LR zxw*MGl}h~yAXG$TTbA|x>FMe3dwY97RIAl+U|>M@58QXd%(3gbVsUXXEEbDD647%2 zB$$~z&!f@N(MJml3%||G%qU|_{QgZuh@vR=eP1juFT2HJag~Uk_kzWWh?tpaV`D=M z4-Y?f9Dk=JgxEYdIA9{;APAU<#LCJ_yHF^6OGIxgm5R#e^C19;-V_FaQmLedhK7D! zT3Y&Lc6OGGF_cUu#p>#6qfjU;6VcnPtu39;=cE6Pvk;=6%!k|C+h%8H=Wk}7?5}fQ zR4SE(<2Y8U)%pm)$5P56t@S5|hlgf;ef^<r+kY43a`~@`i3#nxZVNygK>P0Q?rwT| z+M~6#HOu$?bQDD+%$(I)XW}?cODQw0R*SUODw#|MVHny{%9|*PY$CdnQvNeCGGbG2 zci}jW<v31KDV0nl5(B>PTUu)k0G&=J5JLDt5SYQi!EU$PHRI#s{^sUp{4d9PpR{5; S2Mx6V0000<MNUMnLSTa3;)7@a literal 1542 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`<jAm(=3qqRfJl%=|nBkco*&`K2Yc zN<f!FypUS}H#V;r8ce_d)2~P@&^OdG0C@?f(AO8g(%f9Q>f(~1RD^r68eAMwS&*t9 zlv<o$T9gcoxHM&uRRvbgMX8A;`9&f5`8lvq49LhY$uG{xFHmp}HdD|D&&*57FE7>v ztM~P_^2{qPNz6-5^>ndS0-B(gnVDkc?Br<f<mO^%V&Y_MXy|I<XyN4I>}X<S=w$9< zX=Y&w)9aF-T$-DjR|3<Ug3xP(Q!gkf<Q4#Jc1bPDEK5ysE6UGR0DIXg6Q^68-Ef)* z)tiFbEe1IC>H{644~kf%h=vIPQxAv<Pqsh~JmIJ20aJSsFj-$;(cR9#z*Obw;uunK zt0c(RUpP?2_Vdk!+ghhNaj#`{R_sz!IkwbLsHd$}+C}UiyZF(i(!#>!Z9mxW?6};@ zEo2|D%REmdk7w!IEnAsXx3Fee%g-}U<hb@J?cVOs@AeeG+xy(^WNGXd>76loozD!P z&)&Lq>vfBCkG(7`X&G_Lf*+s096UWhW9E%h;$|<RRWGLLH{NOgQfXzo&1P25;|Ci$ z7v4}Xnt3JmSNzx04(~eU7Vnhr<vmy^>vw%~()q&4|I^Q@J~*G4D0Fh-ibWa?D}1IL z51*v6Bq*7&YTk!CH|nJ>%zXC!xsRIdY>y*~lO3j=&^@SJFq1VhVC9tnohe>l^nO*X zP<L6){rU6en{P~fHotk;b~y3HEWXvZkH)QLJ-w~)>nWL4S3e!)m$$clI?+9yv+1q9 z*{4e3_h$DN<(4m4J!?M0E|$|ij}FdN)#zfs_*bZgY4=sDBp2q})uC5rm1=ByboJdl z4oOayi!VzOt?$;mghbZQ`F8Kc3kw;yU3SLpZofGuJ+6AWS}R$@h)0*f*HnPlo#mZa zVdSe1!6_edtg0)s3{(zoGqu^X_eG->i}{RD4GRY41&6okY|)*q%AvciH|O7ve@!<; z9XA-woavCdymN6H^VUyh{3qjfojsf!BVmxh!!o0{)g_C`O{3{bg_KIf2h)PO>--B| zvZ<+0iiof8uQ}MVj8kwWqkQ*7VP(TL&+-z!A2_srZf)b%KiyAuP3k&0q0y_9{rFox zrd@uHp_4S`nU?gb-qe_7I&I14c^MYBSwknL=H!MamP>c=x+`vEeq{D5w?u~hVSvEz zuU4gr?rloTWkpWE+rB>DZ+(f4+~>6Yfm-4_#s0os7x9nFfMHhRx5p)mI~zeIv!|<{ J%Q~loCIHG1Mo<6% diff --git a/apps/fo/public/images/map/layericon/RDL_ETCT_PS.png b/apps/fo/public/images/map/layericon/RDL_ETCT_PS.png index 29c0b77b4ee74276df8de9416fe284e4c8bc15ec..e5a83f998a8ea4f36918482e08ec93f13fb6c40e 100644 GIT binary patch delta 874 zcmV-w1C{*v2Y?5VBYy)&Nkl<Zc${sMQD{>K7=_P&?@ex!Cb6k8%U0JFmZ}x(=At;Z zhq(C?nHyowhz<*T2|_^_+mJG7Ko~`bLx!++WwL=VsAG&FExNT6+p^(I5v^veuB~ZJ zG+mS2rb+L;x%d8iSVh#G$HV`f!~bxARWUPxK>%P!9hf=98h^jSDsSRmwmo+Dq#l|Q zWN1e9(D~x$Bd4<ODaW(B`Be7Z<^F}P)Vz}Zh<n)u0Lf%hcnnT*QebB3YH8ri2_?!0 zW^Ek%3;gy?a4Zdu&wxKIY~!K)cf;ii8~{BkC1w@`lB|is=ojA_9UqQo#%o^5DTpKr z7Bd78ELc|AtbbZB1RHG!%#%Zlhu(<oyA-GZfIvi+r5P{%Sp4$H@YJ~J#adC*1+iGw zsHO{0R&=VWEPAZ9()H<k#zp>SSM%|^2Z@LQkOTmi^8@>GJS84&X*b&HH;YSp89&Yc zhN0XQ+V0<s{Y_E$oj#hYWa)>6Upeg@+q(yev+;9rO@9Ii_tfdF|5QqFdF#n<uSc`} zS*+g}gkcD1Z`q9vaucdpK}oGrd0C^pkZax8+_LUu{F6Be0APvE*<t~mnKRhx5Aps` zgm$dkgpSs|aEnfi7H;6&-<NSEKM7T+U|UeK73I8R^#z7(dS~1e*r}TZT+L=^Ja-m5 zf*bhbmw#i_GkFo`Xa9s@nQ)3S+%6YOs^|Xb<o7xvAOQeE>XxBqc?%UPD$DEv3<qDj zdz;cq78ho2p{9v&uop^2<AC!;@;!Lwk11R)_a5vefthKdZ*Fjx^POvJ+(Bt^d8K9= zcCxb@5^S(rE?9;GN~LC)k}U4Dy|pm%_3uCX;D4(n5(!fPfcL-JqqxRA9UlY_XMAfK zZTFQWE|xV*uhq;_MK^QG5=%~x)c$;8Ss%n_eS>|s+9GWl5m^KP{r&x7JRTQMA3gnQ zeXOBtxHJ-(R%azoorqFZN0V(mBHq_076<Y@o!y<kgu~$nsZ?qSz@uTThS(i&1iHJr zUt_Cl^=wx~!!K#PQc4$Z_MGTRWsJ;xI2<lLg#Uk3A;<vS0Q>-+1JDQ{2%sSvjml3l zPfzo|AR(1XiDUUO;m++lXlQFNkG*-!V1ByvU-_e+CpFW^EdT%j07*qoM6N<$f?+_V AZvX%Q literal 1016 zcmaJ=OK8+k6b&tw(vJ!*{1ogXU8vB>XPTs)II*3XOznWFWjfFSH)fL8PUs{rO<tX} zn--yn3!xS_T?m4pE=1_Um4buR-%fF(?(9ZzBO-1Di7(U6)P-Y6@;;n%?s@m#cYSK| zd~er@E{dXha}#QwjEAGIvxEHme}p<2j^k_*Por5})jde1OjLp(XX|q?4|Q{C;SEeu zR6A`Iinyp<kPT!rdW2yD+a+v@N{$AuZp=drN^s6{6#CnfXEd-(g`N>LPIEJ`Y)veC zaC&*NU@XrY36mZ@2a<tI1Z;?P5ZD#Rmji{a>&j#vZL>6}L-4#p@0u!VQy_yp2t;Nm zW^g<YB#Ge#QIhyU5a;+f%aK=#@q#Q$a(o!<Jv52tnX__U9p8&ZRtjCl*p*qfTCFnG zAqIJKET2duB8_-FMi4Q7$-#OMbNqpZf(m`Zvs`Q;2Skc`2`yrUCQf&cV7pCO$KOj6 zNf;aGF3U4qbfgAQ)BX>&?IzmCdARTQpTd4&$%Skl`e@NJNa1D&qEN1!@t}^8S3s!J z$l_EPVdR&Q3o_FZI6Y%ICaU^p>Ud3)bB>R7$ACFip@{-xS*ARm&19rhd{__#k>|5Q zN)$#$QrT=;Oi1Y=Dc#_z$XK+YgBx6PpF7f$8?}P%lE^CbtSitQ_mB<hN0zPDUWk)c zUUs==YcEnuE=$a?QDOfobYqKjPc&_|Em<@hA3CJlJ<`@6+h6XGD^ba*sY39nv^Bc@ zD}4Vr>=~;C%<tj$t=ea<uh`$c@nPle=0mlwcK=1^z0LLQV_WaWX84`;SKR90YNfVP z5#C-37hWIvav&+(e!6<?<j?1KzPEJ?r#5c&r8W;fIJB`AhT+AVN2#_RYP~~TJ9`6t PjqYMDJE=ZOU%vVWRx?RH diff --git a/apps/fo/public/images/map/layericon/RDL_OCUP_PS.png b/apps/fo/public/images/map/layericon/RDL_OCUP_PS.png index 8a1ac0074ec2fb6ede26d23e927b6fca22809356..aeae3e26680a9415a77f0d9b310eb18b3e5f6c58 100644 GIT binary patch delta 815 zcmV+~1JL}X2*n1FBYy)7Nkl<Zc${sLT}a(^7{@=)@BecCo^#xhEi1weE2P?(GLto8 z!*y{c>rH6t=wcT|8WdD?9q2*`293yzvFajvAvhRF7aLP*qs7|9(uF2la}H0j>B^&> z7d_|y@^rB|g#27Se7|3KzRv@9QB_e804$Fc)kh1eyKbvK)_<yMuh+2-SsRLoVmXvc zg(LRFP`CMWs*y@571>@<@2kHO>}>n$n=N%GUJ+Tnrw43R75e%_RZVd9;wSddd#`fk z5|mcaFvPf40@rP}Zd_`9x#^|n>QoPdEUtx!sv0p?<j;(MXrKA$-C%mm7bhjsp0b$p zCAwHrNl2uXLVsjGw9P5)f8$J3>OlJ+4O9S(h(wD~*eK@*-wrO1g#<O0v?grR;)&T; zg?Y*ol45C)wEdacAU<)r?X=8x-Ce;LfU3!#JFFX%R#b9ST3lmV6R@ICZDHCsVGgCh zi9`bruKuQ*ua9=o+H2F-rv|{xXLs?}0@TLDI#>;k5PuH;OL#U9jvx=aSe+>p%dn6W zZ}H|1+V-a8=+Qgp(IBj1xdPkcu-PX1sX#Ee1PO!Q4ls>XnB8&AFDo#dC-g%%Zs(j5 zhyf_O$?Moj)m#N42u>{%`H^Zl!XH|qGO&Q&3gCXD;AuHloh^O-S*3TagZYNWVd@(s zyzsZ`Tz`Oh!b2AW(Lx9@P+uVw{LviLZk4$$b?5kfxbVWslOn1rM{W@x82|QM&A_pi zVxr{7y0<wxvx1f*%!3Z39Q=u;z&o{M=U;pCX7_yM`3nQbr?c5?8Pz*QW~OI$7QZ-g zO%LwXd}F;9UfdBBp55-}pLsmUKmACg`<k`*@qY))pJoOR12vtUo$mTqHX;(e|NZ%< z*DJaHO+Wr{z`Hi<vQh*u#%kSG3a$G`zYgtV?{svWO{df4k&%(&y#RnrCS!GVbqS=B z$G`h>_a34>@knb6QCR%7?2o_I-#^CG)IaHT+FvvO4^@>X!2xU_26#XmNIU?Eo}M0O tLnz}$L8@v`W-@N3x7SrwgUE*3{{XBoeqw|U;4}aL002ovPDHLkV1mCili~mX literal 1061 zcmaJ=O=#0#7>-tp>_^m#%!?s|Img;0{mI&}x^+$K0(R84*dBH;O}?(#mV7b!y0*iE z8*?alQ&5?^$i#!92*ZQF8;I!1o2VDBDvTW_3R9S0y0-IR4N1Nqp67Yr?|q-|a$@9U zd&`j)f*{((VIhgf1NOJiiT_78wj?}splBAQU>=nu6A)nq<^U;b(ilhrNvWKB3;GGd zL8<91%EnLeGSu9Xjd7PX1G5RDf1qqg@&rI+4veXKh+2R8k|I?lL}mPOCT>JPK^>kp zL27y=El*F#L4_JPLH3t<ET91-k!5XCxA=01s_OE1Zg0~RS%sj95Vf6DHl83O&;+F4 z?RCiv%aR=DW_^B+V|z&t!+K~2znqKp@qUi?^pUlP!qH46&nJagEf!vdr~*O;Pt&DR z$zAfgp*cpg!C=tV@OWGp;j$_^lFBaK>Z~gWz>-bXKq}NpTT#lvA_`GB)9oW@MnhJ& zYGuL&qsx*(vu?&dQXLqN{~xMp4YY-lU?<;y3R~%l0q7*KV9}Iu<MN$$D1(ofKtj+= zLpWKlVxj;Mv<lE5BPovT&ZxQqOV;r!J|5>q-9nNs15pT3Si!BT3h$3{0UyhReSU9@ zWuv};j|mL<W1g_bi>vMp*SP|eiyF{TovZ9{hnjM2D`*CeEC5rT0!qw;8d*IuuQtym z&{VI+Rhs7#Y|5o^GPK>;zZzZN!tSxB4cp>H!}vhQZa1;54?CBG_)6Rqg>bt3Dc9T6 zv-xhY>&x02P<F9PckYPqe}Dg)V%oWtdyi%mNB-{8tlD+s!mIoHoj<;f-u}oCQ>%|R zoz92LEzabJwgk=0?mnH#TvzDP7w0<;UZN<f?bpxsV?1+lZZ7ehaEy<S@9}?rQf&3M z8R8+w{6gy>8$Q$ftg<mbb8DICBd&G7*?VPc>+0gdDnYIg;-c_cm>i7R7gvmq2oHwN G&i(<yMpM!N diff --git a/apps/fo/public/images/map/layericon/RDL_SEPC_PS.png b/apps/fo/public/images/map/layericon/RDL_SEPC_PS.png index 087b4e94149966fd4edc1dd5b27ac50e40a4641f..b3db8e0a41bfd9e32d796bfa4035bde320607b2f 100644 GIT binary patch delta 767 zcmV<b0s#H22$lwrBYy(jNkl<Zc${rfOK6-`7(L&2??0JInuN9)XDBXGQ)!J<5D6lJ z;zkg3(M1*ANvjJXU07Wy9irkw7lIoph?If_i?1SXtaU)~1rcLyiosH`t?fKwI{D}E z|MSni_qb?NlHl2%2j_5rO(3ZNB?3_MJl@LO3jk0ht!x(mzkeAZ8C~%_{+=h_s;vwX z=bi_E{iGqeA;+#W8xip&hcEc>(T_$Szu4@!#p|&NQSNB$c-xrB8$v2chzM^5tTPvv z0zUrM=tH&Dz8hAt!yvz@HT^*J^?&+<t#_y%mJo!bib$|0Z<&*wX9GU@UU6^Zdg=<+ z*fxLwkx`+3cYn3!k{zWN&n5lsyMTit;z)|zY;gDQz!z0wXQ!RHNYx5}ZkFNv^1mPe z1EB4_yQ@^P&u+<mI-N7mh)7>Z-gCO!ub%eUrfKF>#gc%Epp*5nSZ{y;K)?vlbzx90 zPDC$_=g&N_b?gZX;9AayGxc_||9qo?F;#fn2xzA%et-GvZ;%84C=i14_OU@4wiS1O z4Zv9uX^t3u^5=H%{dcu=VE$U&jT<!pHr!=^;C;}icTc$!Gkc#Ckz*i5NlMpwxo<ds z@YU_3?>ut*4%_lRC~h<t5g>%%2|ci9Dm^i?_bCxMR;gC)+La(FAXNc0((K49wH40% z`bW6`r+>2nAAauT<d;1B+fq#OQ2?k^s}`G4sZ{jgn?#ajvv1Z{c;^obEPVDAk1YOq zmE?l}aCCk?+B|OqM0KszzSl0-leaHiS|Yh?J-0z$sZ=zM<DpiowFSVaF=i}^3gb)X z7Lu0_9oo0tXk7l_)TxC%Q&UlzrY!(%0PRkv(|DPlp6<)+?CemMWfR`}GRaaDMa2-p zgt4~RT3waMT3g8H2hO>sF=p9&-xQI$F{V~7mz%P-KaS&}IF1X}+Cnau%V$|O6h%=4 x0NrkPpsLwmFi1v5MtZ$oFPWU2%;x6i!hdSPmcBW5^N0Wd002ovPDHLkV1lGLdV>G} literal 1068 zcmaJ=TSyd97#_94Y=wl1?4`4#fk8X7v%9mG$<1_UH<wzobwOVWXJ?K&=**dBj_xSE zBuI!o^bkG;N<BmrLI_0{$d^RBKJ-*jgb>t&Bt>Dcv7J#@duSVG=3Mx`@BhyK|7X0t z?L>9efhvlks*|l^nvA=huW|?ZAN*dB$WV*pUAP1F;GASas#!r<2$GuA3)4_ihR-j; zMvAJS)l3)eN}Up9q%o3%VQkGHY>H}ZvJFY@hZtmGuc}AsuaBP4Kvkl2C!g}Bj2P@w zTSrXTG18WiNBZT6LN^@)jkZ7pG>9c&YXiC^*im{-S0Hm|o29`T1oubjbyHobb`V1* z1U%#S$X<>E!63s0_+XGb0(@T1$9l;d^l$-z4+_2z*m!6X%~X1Xw3yh4MOIO|4`V}M z*<3Ei<opaWds!|Li8vZQpNAkk*07Ex+oM}`MFkOBvZ)$aMLKX4r7Rl6QJOeiKZ0g# z%Ielenn=P}TQXRV@j6E;0#m8~Lp5y^ZQ(TB^7~I=D>H0BHVrK_Xv(B;J#|hfLx`DB z!pO`ZG*HZ9dmqBc>O%&Ibp$~}r>ZL`XB}R{r&2;vx3HwkFeyf9qQI!CB8VZLWVyv3 z;Qb=U#RCyB;g5N}VQ)Ab3Uh&Ykt-s3P=h)ya+NJ^pd{C61<fFlMQEx+P)V3b18YYX z)Y4u^*-Lp@=PISW_)2nFVup1J`&XfhTcmrO>1NxK#b)C}opifN+B&OUY9Uu*Iw>}1 z?04CGq0m&}YB>6{E*zeF|9kB6!i9axtNW*4*S2nN*da#p@XF-m{H@h5Z*HzuQ)O<q z`<aU>tEs7R-3_4+-Tc+@i!F1bCztYXXN`Mz9yhjCtURQq64zaJe#QOk9GD-v#?0+q z5+8j0_UZYar{y<hW|$Y#1rS^~4qmoS?&{uIHFdk)^?7-DY<6N|qSjbE!yhW&wx8-h V%kh;z>g%2Rn~b-KGcDbte*gsmT|@u? diff --git a/apps/fo/public/images/map/layericon/RDL_STAT_PS.png b/apps/fo/public/images/map/layericon/RDL_STAT_PS.png index 13d8ea6899c02aa0d0917b630ca9f697783fd6ff..9a0b11673abfd8ad6b85c32dcae55f5e3f7664b4 100644 GIT binary patch delta 833 zcmV-H1HSyT2-gOXBYy)PNkl<Zc${rhU1%I-6g}TJGrP01*-abMRBHUgpb3-|yA(?i zqaqTpDup(JBGi|K(wAT#N_}v_A1XzJA{tF44}I`q1s_UfEmngug>>mcY)EUft!dh{ z={BZjXEL+1GvCbdp$(<2_u*c++;h(5au0APND6=g04xCHa(@?_|86E1yLl#W+&gz) ztTvMXJk@;n>wnZA@684E9hVh%&jl-o7Q#xk?(E~Sf&~DDLP6dGE|8KO)qlC=kG!=+ zTIWe?GGOOfpQ*_v&2!`{HS+r#?r%JCv;#ui-Gro+08TG?Uu>Hv@w*djo$}PEqvClL zbJS7s)+tY6%zp`<nI(&}ODAi6>sm;PyCluprrp)1N}Y1m$5;G#+K*|IyrxK(<b)e> z*LjcnSUr4mmMl-4%CCw807e(;pL9-=2mWq^G0AyF&MER*bh}<xq)&2i##NcJiigf# zJ+U3Ib`_~f0pL<YJiB1W=+Geuz|w$L4KO5-)qu2i`+vZXCN<&I196>Z&vQ?h>wXvz z)B(T`<5V(=@#Tz%c3r}dK*o^hvLvc*j0=uJLJE9U^`I#OK}3T|!vuVx6F@d<&ORj5 zFV5Wxv8QZfOS^$HO95ZoV2Ym?ns_Rs;i4143nOGQCeyxs<M;(9Kwm*f0NCtb{dJ=W z5jA7BNq^!%w*@Vtc;|5wiHP{1JB^KLiO>uAq?HhzwV8=B;C%b`>q3$gmyZmVcAATm z*~}W<_EebC1Q5n((FLGl7!vUPP(g3eb{MtA^6+QJ)?#g#&*vfu0NaPg>+8pl?%mhn zTxrd=8HfM0X}du@GF^}AZWs+u*<2P`ea~a@ihora`t+O9*ty=m-lh;D1^^U`MGZjW zyZ!kWMkcB!-ze3>O+PtY|D(&E<12i%w8+Ebm!>}7Ke!jb)45!(6@b8MS|u467)Y!E zIu3sL{x;F`{1zjXZg03NjmBT6r;i*wRGCqj&*gH<l}g11!2dT@S63213xIY28vr~4 z;5uO-+t%OTpS&OQpf&Hj=y0W?`8RLMi`6RHdV8afUVcgOK~4MzMZ?7h!F%vn00000 LNkvXXu0mjf>vELe literal 1075 zcmaJ=&rj1(9512<`H{#)kqa+fOw_P--MZ=;1{vKZi<T^7ksUbDwGY-n`%3%91`#q1 zh#tHc{|C89NHoNRgNvFN<53T4gcyl2(SuQ=s3FW(VAF%MY5U&y<@5P`f4*yp{<AHO z?Tr*gwZ!`(NwOYxzJ`P3-|@99ljRtWrttt8!Fkz&R9HnB2;#at3X@P)rzW1jE{dwB zwNx6XrSpP<bdT&{JO$k(Y>MjYDVVY{1~JIMQOyX^pH}bFKvP5XU_kOprU<iI-?Rk> zru$RM^q3M<>7H)TRS<}P4zUah`nX{Wg%G`?E0Dc&%+O#5g2zJiZcu3{0Yqd$5b*fj zikD>p&wE%d!1HV;@OfDu<0UWeW;r3i3%=7}@1cn|OC1rCk=ULuVuk1|#-_k9`F!4! z_j`~v%CNy;(9!Vu+yvparwl9?+=lI{C`6#GSel77WB^A|&Y&C)(InE{6m+vHYuI~b zA_Zd#vdOR>uai;*C`tc^>UtGz<0SkO?>~j@)RYOCB(zb^Qb^-QT#hSK5G^QUWTg-q zuT(LSMHtyxWCC%32PXzKLq&P}<PKhvgt%d2*-&6S5~7KMN7Gb6<ilZ(3j{b$<XAS! z5krhcqyB&x=#9p>GZk(GDLEY)xWZNcaQT{CXB2dkc#c3zn}ll2LOR$<S<q_7;;*T< z$5m^`!qwz5BpAkN>|c$p9Fgg9w$-sEMs@I^L8jdzV}1OY-y&DyRy-0;6<%ew%H^JV zzVupXjlM;>ZWo{HyvOCIS2I^-|6J?Z0Wkk`VB^ijLzBFB=EJsl<;(3=cId&}xjPGs zpsi_Q`Qf|pQtxK_1*XV#|N6FYsjW08nU6nin%`GDdOo)o{msL(3qL#dFVEMxR<e(d zG`-+9xZ+J*qI}fR?%D0F^3wWx`NwlFn`nr&lv=N^&CH}XR_-n~xK7pWYo>;76pH}7 QeD7T2c(gyV)H{6nH~m6mg#Z8m diff --git a/apps/fo/public/images/map/layericon/RDL_TSCT_PS.png b/apps/fo/public/images/map/layericon/RDL_TSCT_PS.png index ffc22515996bacdfbece7d30e3d5afcfd20269d2..3776f6c0493ee2fb6717ff03adecb0f228bf8673 100644 GIT binary patch delta 901 zcmV;01A6?V4Vwp$BYy*8Nkl<Zc${TZYiL__96i7P&Fdy<+H7{U-DXp*Wn`VMs}ncJ zx~U+H>0Hs^2dQXj!PgXLQTF8~RVIGYfiSCxE$GAsKdi`F)Y+}`F$a5KX&|m$TbI_R zY5K_H-rU^W`}2d{gnB+6&iNhA4-R1!%uGZ8KrA}}wZQcN%6|b}1DxYTRQyjze9K2` zNmbR`zWgrHaW%GhpQ>3t*-7fX0eAe#eO0IG>t;?8ZO<!;A|jE91pqc?rf@h+%q*#z z{%MCY`oj53voMSTOw)uA!tHRln$G^7ZE8E{KbT7nKkKb}WbRfdF|)8qWDWdq{ln^- znm4YD-YgwGK7T@fpDX|gLX^M^YdK?Bf!b>Ov5t+yoOti>*laKx4u@F~kySGFrqgHt zdTsABUzpn(e4^*w7AccSli)e1szI7&NEKzKZDM?)^zHYl{Z(6DdTzA>2>|$UWb)~& zV@b&djSsbdE&LVb82Y>c=TG!f?>i?^;hu+@*Wq;;bbsmE6pxNut<86%JQRxldth>A zdHd{K3T3iEODlxI)30FX4j(o({ffsAx8lfC1IXkUlF(@>y+D7b3R`yW+ID9&8e%~J zjH2!~^c>8R3d^#%#y<(*9P*|IzysJ+xd_uxiA@cLk%ws&?OM?#0}z4%cw9W~b%Qlc zh1;H{zJH+y5x;l}9<v>@u{ZH?bO-Dr3ti8{?PT`YjrpH1#*EuP*cx#Cw6%U6WmYom zwC6FIkomx?yU^Np1CPGgjFD^A@Hn!_X0lwn(MNty{_@1le5R+T2ZEXD+=bquLyy&s zuJ^lbD@)6U$C;<pk{tsh_al*%;dAAn=CdWRDSth&|E|Rg!_hNdueZ?I*=Y&@=<50; zZ7arKKhpkiqVkS1dn%b?U0bl6V%9YD1#3Pz&kl!Vd-?f2s+PWd;+yE;AB~NTIU=$M z0Q&p;rBEp3Jg&U8x3X^cvEg5??~YF-rA)Q}*=5H)bsMnnq541Prhn^u|AUVPgTdf* zEPfVSyuAegP!z=$i9}?t1DlV&-L<D;U2U_F9P5ilp^!>W#lPzN?8;0sdm|VOCS$SK zG5`x}p(u&~AOr9N*bJZ^Km&j}02^9bT3l;Aw3b+1rwB)*HjIslsi`Rh0)bL@cXx@I b|EK*2JXN2b<c_UL00000NkvXXu0mjfL6OAJ literal 1700 zcmaJ?c~BE~7!4kP+-gKctlNlFwUP@GASK~Qf}|xDLd58lYK{#FklmPF$N^e~ieg(~ zP;3<usnDt@BPuP3f?BR3YFkB(1}$=VK}IM?P_P>W+CNHncK3J8d++<c-}iPlGjiQp zX9o`l91iEq3+D*1(arkU+hXr!{cpwCuo&fv&}dkWYQ-857b1ftAiz_J<3Ir@mg%?t z3I^hEGYLwe2o>=o7*be86k9Pwol1?dak#)BomwnS08u~!#wj5t!CX~C0F*K&VFQg% z=BwGDLK$w*fYF9^La8A^8XzMCv4B7w0~1hzs2I?x5+Q`4V-m)78Q9#qO(Fo}5Hx{F zc$buj9|^Ew4G7SPKHgF?g#ys&M2atsPN%E^sALM2M8<BqH^rAhqcf;}z{EwsqG@Du zhJX_`5er)}2?`WdGe{(@R!h|S5MfOmi4qVHVAY^fy)lG0qK8ni&Kp9OPbzRgM5<A$ zQ6&rkRz<M{PC}UkEYo)-sMJ%k5HfL0*nyFBVl|0EBwI_G1oHX+4^^qA&<H93|H=11 zg%P1%4Uz;P0w-ytSmWf&t)bKmwgwcVuto^OiIb-osen-!QNU_|9Zd&TZcsupSc|M0 z$Mg9N9)zG`NDA^eOai7rR4QeR05**p%B6?-`m#eP6t1s-s2`0>_6zk3poCKWLTQs+ z4lGSlfe<>$l}&N|$kTGIR#2(2$Q)3kOa^6P8dwF4m&{O3p9^PNy$P;t+Fa<|X}KgU z8IrZJ|7!H)7Umx7bjr5aV#@d+gt=XV*_sLU$g!_w5swoh)IA6k#40}H;>T{9-nPDk z^6DR$io9uMF|)VLSzRp(NnBP0h`4K5_Pf4Ugq%pTXYFw)v!8uZqzHa|>VEJxvg_3k z(!a@kRhYK^PUP*JqRj7F^KbNCPBDAlGQ}?S_d(=0hw{zMi<=fo@XMQ5@4480GKa-) z!5{Iz5`(*I^ILMAc4vf*?d>gwjc$BLBt_x5qj1%tr2Dp8Z`%pFzi{hz4|jL6Dah6f zU#=(vo4VJoxw@jFYa``~c!^i=^PN?v3XJm_n}*@k_Xm$$ADnl>h|7s+`lPnUE~mAr zzO42M`Im5|sj6h)<Fm~X=Q;B5sNT;vSDKIA<sI;v6CpRmS7Z}CO-C!L#`0bO`)khw z?lbas4WE9{BC0fus(boevug5-AEq29?^rg{c+M$5c}Yph3(@9LDKe5?-oIV(++wf@ z3U_2|y1X|H(igJ?^A<eccR0PZ%8n9Q0`Q|Rc=Y|LoH<fZN_c$0tz&T=cm7ChBs249 zQ!wGfTZor(#?S2wX8q)u664gHR~K#}!rWsT2VLUDLomLwVswcAMlapip>_P4V%wAK z(6BYJ@`tl$+)VagHk{*?_zxW4Z7y)kZQ{XpiVJv;!?JSQr!Ivp$zUHFvYqP~QZvf~ zZ;9^EuN`VVACaXZ8m^axX{*w68ok<&8Oisd&Xoa1wbAv_>?H>qf+RunJHL8WwD)Oq zuF;+KI;?$XiqEC~cxQcV+nKDqit>1M*RV_Aa3fA$dO_Xx!lhoa(423YQ*)Xn>`QV{ znIxA6tLr-tIhFa$y0cr|J44(j$w;Wt?HLl5Sw3pZMVuqPxOeSplxsB_|E#;iZMb2y zAHEdn{b(>VGu!fdW@`WS%YmY(I%9LkdtC+fmK)t#{Mi2abALpnZ|-e)t$Ul_8+edd zpOC)3E;r{%-KWy7Itt4oEVkeNsu9=ULaC<Hu61Tt;N3{6R6*?#MojJLwx{ghGZO!L z78if`+xmf*M)A^-qW0K*V*~wB`L|k&vzVLypmz<lufO5gKJqXtquiWRdO*>>*~S)^ X+MJ>?k7n6e{~J8+I!<ZmmbAYC)BCkP diff --git a/apps/sn/public/images/map/layericon/BML_CBAS_PS copy.png b/apps/sn/public/images/map/layericon/BML_CBAS_PS copy.png new file mode 100644 index 0000000000000000000000000000000000000000..0e70939baeed2a0221089ebdf2ed19921495c8e9 GIT binary patch literal 919 zcmV;I18Dq-P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV000ACNkl<Zc${TZ zT}+#07(LJXeue(PMzF0Gl#aAibkPX~Gt`K4L@G!^veC@NWm!z-#iEIq3o1VsCd(F0 z%obo?Ng^0((6}Yj5Qqgqkrb^I$|$Tb3bYjJr0lEf&-Z#^F$<o{ljofC<m7n>SqLGB zhyXxHk_xF*(hA@J02#m%5v3lQ6MH&(nB<&8mgO^3<3FBX2?n2i@E~y@H%EQnXezpW zxWe9NG8K0bk=)kS#yUDW6abKf5Y*n@PC^Lo_j#|odOtts_j!>{XTUf|I+fC?B&jkO zn6EriUDLR+5kFOA%HP}zB_RZ3j46SIxle8l4!l1<JCn*$YuHP54p#qq6NT5;#I-9Q zEAnn!DYaNtFW0{@Jnh|VzHp)CJ^(NxQWQlw^3(9pdo$kOGMr0Xlb6qxoE+NOjl*Iq zr-tShZZs81fxtp~cKX-jRaGUYvpo<40Iz4NWqB#UiI@Np(`j|M<D0>RdjtSb*U$vD zMoWO8z-_;XhC?SzCX?3bbdm%B(MY&rYcm4IIGugxT~t0_gWl^`@%!Y@u$DcER5FQ` z;2pu3MBCfjwDV`|shXN&2A8XERRVxiCap=xGSu2Um`kmI5IAJ9VeqRj5xTpKC#*J9 zzgkDrV{Y+RY#W($MwRBI2Jr6&8m%svQ8KT*c&rYO7!2^w`S6XaAIBYyXlOo(rQi|{ zS<4_P0z=;o3a(OxCr8JU|8-y}E*UnLmeIuMh=|8`5RPrZWH#gUx%05t%JEu#BWxBM z0&_m0H$F;St@|Ysxf}24=^+wAP*+!9fw@>S@<sP&&lQ`^se^i>YI$KE<xf9@6K}qS zv5_HcMWdOq;U9R*nYW|gk4~Jub>qfFEEbDX_8_RY-*uEF?)BgNy00V@S`})wS^!Y; z4;IjRXq6fC25LHaYIjRceb9RG;<vT6wGp@5{Wk%?z`y`^I-UIT<xh{6mz8|<$JAu) z`r3+=kY#Yr@wmAZhbyXl{>A0)4_jM@>~?#^<MHeO5VZFcS(6+9#!Hv3ykIaCR7;HM zlj%%iZ7ni;wfmYUy0H<m+wJ%E#MloZ1OdnekOx2ypa?)QfX4t7IvkGN{TkX&WQ~}| t<Kg4u<7{zp5ru_?na<A6v=ILf{Rel+gQK?E<5B<s002ovPDHLkV1k61vD*Lu literal 0 HcmV?d00001 diff --git a/apps/sn/public/images/map/layericon/BML_STMS_PS copy.png b/apps/sn/public/images/map/layericon/BML_STMS_PS copy.png new file mode 100644 index 0000000000000000000000000000000000000000..bf4357181e9891bf71753cc3cd75aff9262de4a2 GIT binary patch literal 810 zcmV+_1J(SAP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0008<Nkl<Zc${^T zO-N&R6vfZ|f8?3iTChpuMs(1P77NAf#6nO6p<Rr$6|(KLiw3)xaUU(Sp00&1hS^P+ znN88f*dZ&oQc#p;p++&OJ|p49<o~{JH_0$qde--M@45FL;GURS03aa*+U@oWW6aM0 zP^nbZ{o4Pi2ZNcl<2Wz&_xE{scQ^2T|0e(_m&=lw=|e_+081i@m^syKHecS{+<bj` zc^RIcpGzs_R=3;zER{+<EtN`wnL$Jh0CW$|G@H#=^?LoQ>+9<<iXv63)!6gAcxq}& z=5o1b>2&%@7>2~mL`1PFm&<+l!PV8(>qevTrES}xWmzf+0x&Zr5(#m1bQA_b@VH*D zf11f;9uZN<%+z=Axoz8j)M~W{j^l({YXtzV>*C_#0zJ!cT{j#X8&kPl?oBqEEfA4M z%=|^QTCJR%oD4@%6cUjF03w1h24`nyU}gY-5CWd(1=(y?&(F`lo1C2dMgeFDA>Q5I z-hNlB)nHi`m>EI{bUGazA0NMO03bHTsJXehIF(BM2_RHNWLuW?{psoH?|XZDKUAyL zaA06S_7B{5!_2Ykx?*v0F)S8~KN8V%03?{1JkO)i(a}c>3k$!^%*-fbO#J>$M2Mm& z_I+P0FE6{rVsVv-p7(;qiint*X=7tU3=a=Kb{wZAgxEYdIA9{;APAU<#LCJ_yHF^6 zOGIxgm5R#e^C19;-V_FaQmLedhK7D!T3Y&Lc6OGGF_cUu#p>#6qfjU;6VcnPtu39; z=cE6Pvk;=6%!k|C+h%8H=Wk}7?5}fQR4SE(<2Y8U)%pm)$5P56t@S5|hlgf;ef^<r z+ZW|>`LBtI3GKRW3qTt{`|j@UZhCs!qqVg)%lG|s6h$M<oYh)q;y6xADKo8Bi?r4% znM?*@7}`?Gn<$EGBD#`N{xdQ%VpDH-;W&=vI8IV2l}scO1HSKDT5AmeolYkZLij-t on8CrpZnxVt<KyH0=H_PnFUNYHv|>C5wEzGB07*qoM6N<$f<P^U=>Px# literal 0 HcmV?d00001 diff --git a/apps/sn/public/images/map/layericon/RDL_ETCT_PS copy.png b/apps/sn/public/images/map/layericon/RDL_ETCT_PS copy.png new file mode 100644 index 0000000000000000000000000000000000000000..e5a83f998a8ea4f36918482e08ec93f13fb6c40e GIT binary patch literal 896 zcmV-`1AqL9P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0009=Nkl<Zc${sM zQD{>K7=_P&?@ex!Cb6k8%U0JFmZ}x(=At;Zhq(C?nHyowhz<*T2|_^_+mJG7Ko~`b zLx!++WwL=VsAG&FExNT6+p^(I5v^veuB~ZJG+mS2rb+L;x%d8iSVh#G$HV`f!~bxA zRWUPxK>%P!9hf=98o$CSZ{l9IJ$Cn`9-0zlXh!wW`Qqmzr?T%U$FsZnRQBEF{)Mj8 zypsNid)Wm5$z)P^3{G-VU}ouRY2eHWCCUe8Z5;ax{Ps<7EDes&fIls4<DvX_!{rMc z06i)tW)=jJtck+t7vCBkAC70nYhKAIh$IRYGXxMUSXSArS}z0}Z3oPgLyL#ri0!)+ zr~rUKM3$u)Fa22j^2qSixaq}OQPTynSk<Ve3s6>cs;Vq{thLhh>3haS{$^M6@w*3! zhyjoU0GIOv`*J)b9&Kqi+UhrpOL`eU&Hsj>+!fmH-;DiDQTUxcnyX~#hlO7`?Ht>? z2Z^)sb8$@q2=~<Kt^ZU?aCz&=Z?8wQ{aLKv7=&R6Xm8n#4RRBzSV2jxQh8aUypU_% z*xa)2Wc-sk2>@V;&e>uCotZP(>JRb$P=t1@+k}qRy>N?8j23R-+~1dRB|iyOs9;-A zvK8gLWAz1wY<g$h6xgYo1zgQ$XgqfoJAxbd<CkO9GkFo`Xa9s@nQ)3S+%6YOs^|Xb z<o7xvAOQeE>XxBqc?%UPD$DEv3<qDjdz;cq78ho2p{9v&uop^2<AC!;@;!Lwk11R) z_a5vefthKdZ*Fjx^POvJ+(Bt^d8K9=cCxb@5^S(rE?9;GN~LC)k}U4Dy|pm%_3uCX z;HxDP2~z-o_rKbsxW+sk9|R9)d}|tQ_mw3smNiSS)yz^wH*?AoOHPl}{(NFtAH-*U zgMGK!B5fKGSp)$6{rzG*9v4p^J^gBZtf6bTG!mIsXC+Uah*DKYlWjdB-q$A<2l73g z-JQRL!{G<1RB8#pqhYLu*d1^Ly1Tkxt84XaS4G1wX}nTO7jO2Q=t*Ua%zQW;E<J?* ze^epJ0Neol0G<QT2p|ZcAsUU!PclzW^S>Y=l}d?Y`7z<n?K@~_YcP+!dCXvby7XW9 Wqn;-<)5t9V0000<MNUMnLSTaaMWt2% literal 0 HcmV?d00001 diff --git a/apps/sn/public/images/map/layericon/RDL_OCUP_PS copy.png b/apps/sn/public/images/map/layericon/RDL_OCUP_PS copy.png new file mode 100644 index 0000000000000000000000000000000000000000..aeae3e26680a9415a77f0d9b310eb18b3e5f6c58 GIT binary patch literal 837 zcmV-L1G@Z)P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0009FNkl<Zc${sL zT}a(^7{@=)@BecCo^#xhEi1weE2P?(GLto8!*y{c>rH6t=wcT|8WdD?9q2*`293yz zvFajvAvhRF7aLP*qs7|9(uF2la}H0j>B^&>7d_|y@^rB|g#27Se7|3KzRv@9QB_e8 z04$Fc)kh1eyKbvK)~af+*Rc*+8;XcxIh0F<Blg5lxA}9bkxD5Q*<MlatG^QLZ2Rh) zEp;be5m~*b2W(Xp`uapwO>p(%C-%^LuX5!QlvdF&#JE-h*KM|LTxx!~>80oDR1bqJ zu7!xI8ZlPn&y0U)pZVzBV0z3KCneIJvY7KFx>!<4NTiiQWIwdcDeZsbOjGJW`yUNd z0E~!4i&5Ap=Lg>oE{}x-HI}p{Y}4Y2*;j>m$`g`eX^^!2nb{ydak}la%y!*f!5Dz5 z$)7u{8<SR4a#UJeV_FliqEKyN+BabirND_q0}rnLrkk&icG22v)7Pg4z|3cN@z(;> z#>6^U4UP~F|4Vo_4~`%YyI7qm6w9!X6L0b64%+sn<mk~m=g}anVz~m_<FMH#`l&!L zxC9A<-VQL0RhZpz%r7f2oG0`{H*V*g5{LmPyUFX=N!45hA_z_`6Zw&9Il>=WqB5|6 z-U{G;qu^;dR-G+<{#m7Wt%Lc7#$oCkB)ssq>Rf<%!b2AW(Lx9@P+uVw{LviLZk4$$ zb?5kfxbVWslOn1rM{W@x82|QM&A_piVxr{7y0<wxvx1f*%!3Z39Q=u;z&o{M=U;pC zX7_yM`3nQbr?c5?8Pz*QW~OI$7QZ-gO%LwXd}F;9UfdBBp55-}pLsmUKmACg`<k`* z@dwMFW(E%fHJzQE?)q0YA`-p-{rRTXE4ltnKmKsQyEf~xQUou?YTZ@}t@}s64((&_ zbab3er_<$;k&)uP0Dw#;V|8_P38a$8zx#6c9-=+*NNWpGSp2o@kH6L5KgQJ5Kk0PZ zUo-y?Rh1{f0c;=!ct9LTJOGKFo*rjI<3>TMYENb|Zl<@_RaJw?hT8uCtT29Jgbv^| P00000NkvXXu0mjfj{B4n literal 0 HcmV?d00001 diff --git a/apps/sn/public/images/map/layericon/RDL_SEPC_PS copy.png b/apps/sn/public/images/map/layericon/RDL_SEPC_PS copy.png new file mode 100644 index 0000000000000000000000000000000000000000..b3db8e0a41bfd9e32d796bfa4035bde320607b2f GIT binary patch literal 790 zcmV+x1L^#UP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0008rNkl<Zc${rf zOK6-`7(L&2??0JInuN9)XDBXGQ)!J<5D6lJ;zkg3(M1*ANvjJXU07Wy9irkw7lIop zh?If_i?1SXtaU)~1rcLyiosH`t?fKwI{D}E|MSni_qb?NlHl2%2j_5rO(3ZNB?3_M zJl@LO3jk0ht!x(mzZoDIUGY5ro+sa`tqc<9o(F*aq#?N>$F4IQ5%DC4FZl4$k47KA z*zCB)>#+$@?r7|I+nC53LMlm!2yX?fGZ&WvKK|C|L$%eu8&<KyAit?K{Xq2ffBJ*1 zcc>ng5QL<PNU$ewnUkGo13vj)ac|>#>I&A_Hh=(;QK5c!wdRr?r5Dd7{q4JegCgQc zirj2)_wT?LRbpqSow-QW3V?2w;r#NyAOHiP?Y+CJRI<-*$$dJVGtY=fUr63_y4$aw z_SmLr=2XR!fQq1#^|4rQfB-<i2+(z5P%chHFOBEVJg{}_2@Bv_&WAJgcC!C`qk%D1 zc-#nRrzw8<>u-<*04NZG^Y*bp8nzX8e+|G{5owMXee&ma@BMeRbYT8k-HjVH05;rZ zfZ%=5r*}`e6El0C6Om&eMM+B6dAV;mfAH1qqwhR&`wrXkJ}7Q97ZD(Y;0ZmjXDU50 zv-c?xIaaAw?b?+fDIiq=G}7$IE43BQ{Q5_@|EIG7AAauT<d;1B+fq#OQ2?k^s}`G4 zsZ{jgn?#ajvv1Z{c;^obEPVDAk1YOqmE?l}aCCk?+B|OqM0KszzSl0-leaHiS|Yh? zJ-0z$sZ=zM<DpiowFSVaF=i}^3gb)X7Lu0_9oo0tXk7l_)TxC%Q&UlzrY!(%0PRkv z)0v*0?#t}#>`<0v6W;qW$x;+W#Sp@Tv9{P+U6sgMTgc}J&bg*BX4!k+6p^|yrdBSO zo3ge)j^m*?jtkb>LN1rfXIVBBMNtF*-EMcFs@Y&LNJd6Rdc9sRnVg)==H}+Ye`>*& UzBzXDhyVZp07*qoM6N<$f^J@W3IG5A literal 0 HcmV?d00001 diff --git a/apps/sn/public/images/map/layericon/RDL_STAT_PS copy.png b/apps/sn/public/images/map/layericon/RDL_STAT_PS copy.png new file mode 100644 index 0000000000000000000000000000000000000000..9a0b11673abfd8ad6b85c32dcae55f5e3f7664b4 GIT binary patch literal 855 zcmV-d1E~CoP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0009XNkl<Zc${rh zU1%I-6g}TJGrP01*-abMRBHUgpb3-|yA(?iqaqTpDup(JBGi|K(wAT#N_}v_A1XzJ zA{tF44}I`q1s_UfEmngug>>mcY)EUft!dh{={BZjXEL+1GvCbdp$(<2_u*c++;h(5 zau0APND6=g04xCHau=KbZYCGIc_we%J9l5KHj@B6)qMBsf7BoE%?0%xmlb!<1uKUZ z!b-L7?BlV51ptLYLEZx{kdhqLf4Sw4ytPDH=SgcaVCPw%smUhIbL1;E^7|X^Z#;3d z147*0grt-JPA_?1Y?~+XyAy1k^3<rK;&~Nw)KT%)DNkX{37(lHi?d57YklilNQ%28 z&Dy5j)uu|Fa@EII{CL`rX_LIBNSEY<8*$fpkNQ|Wd~=p8Pn^oHiUR;f7wVsMPLc=y zZiF$(c}31C@>+DeURR_~a&X2~nX-z9&R#vS9k6y4sYwCgQbRnuV8`gtAqc?IfL0AK zB#_mBw08Tzk0v$Y)B|yyX3uj^nCpHR5Yz#{593rait*))hjv}UkU++e=&~fLZj1|# zLP83BRrR1L13^TCNy7wup%XwhYR*0+(=X253bChbV@tb%GfM$q+hB^H7n*n~qv4_x z!3!f~GA7f$edG8ACqQ38NC4REU;TBX2@y48wn^eZw*@Vtc;|5wiHP{1JB^KLiO>uA zq?HhzwV8=B;C%b`>q3$gmyZmVcAATm*~}W<_EebC1Q5n((FLGl7!vUPP(g3eb{MtA z^6+QJ)?#g#&*vfu0NaPg>+8pl?%mhnTxrd=8HfM0X}du@GF^}AZWs+u*<2P`ea~a@ zid7o=^qbPyx!%6srVt_q02GTw4M5_%{rMM0CaNdjDAmGEKRI0gqsyM-D}1%I$iw58 zras?4xEH|Fxm>OlfWT^6B^ekPNUQ-m4u1IlHqrC^79*8zZ@4Rs#$TtWj~qNynNgU} z<#NlFO2r1i|2I`vR}w%AfOY^I06YTVVIbSq-`}6SAM>C!@4V=6rK0&aZ_10+D%yH` hqmN#GN%28V{0BwD#RtKA@K^u<002ovPDHLkV1j>(l-&RT literal 0 HcmV?d00001 diff --git a/apps/sn/public/images/map/layericon/RDL_TSCT_PS copy.png b/apps/sn/public/images/map/layericon/RDL_TSCT_PS copy.png new file mode 100644 index 0000000000000000000000000000000000000000..3776f6c0493ee2fb6717ff03adecb0f228bf8673 GIT binary patch literal 923 zcmV;M17!S(P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV000AGNkl<Zc${TZ zYiL__96i7P&Fdy<+H7{U-DXp*Wn`VMs}ncJx~U+H>0Hs^2dQXj!PgXLQTF8~RVIGY zfiSCxE$GAsKdi`F)Y+}`F$a5KX&|m$TbI_RY5K_H-rU^W`}2d{gnB+6&iNhA4-R1! z%uGZ8KrA}}wZQcN$^l&ioa01P{7*-G%SUTTRn^<R{4UXPHMV%4s#!kSN$R};cl^nH zRj2CfW=;}q&nt=|B9Vv%05)c(a5zlNEUB9QX@@fU!ud<HFpL6B(}WPh?Qpo7&i<Zl zYCGsZm`e^n>#cfZ?p7!<v#?2I4g7HZ!|IxvH?EA{EFC>QLVlkt00}~rzzl0SV_1RO zYWuN{jl-OH@9@}cFdGhsSrCy`GWDj@Xa9O_?=xSR+ZueL=iL@5lSz}{IjE{Znr289 zWu|Rne4_O2_o@9=TV8r@wE_tM_;F<N>8oQ&$p(!NwSO)A73CQEyaDG=^iuCTCsE;^ zhnm;nbs2Q&+7yqDTdmD^qdXLf{(E3@W_kPUTnc5fK}#!y!PBo`=MEn>HT{am54YmT zQv=B47?RLwDZM~{rwUtk?%H-|G#X+-0F0vUHuN0Kk_yYRxW+#T;2iR%2fzc^RJjP# zP>D?qhLMM9742HlB?AzG0eD<I?RA4SO@-T@roN#E5x;l}9<v>@u{ZH?bO-Dr3ti8{ z?PT`YjrpH1#*EuP*cx#Cw6%U6WmYomwC6FIkomx?yU^Np1CPGgjFD^A@Hn!_X0lwn z(MNty{_@1le5R+T2ZEXD+=bquLyy&suJ^lbD@)6U$C;<pk{tsh_al*%;dAAn=CdWR zDLt|OuEh(((KBAJx6s+yX$k=7>iQ&YE5=_x(*AIw@{TflDw$$kTd<sB)-?15Yd$&8 z4u@oW`T0GnmcD%Ao9N&ljg5^tBC-eo`uqE(P$=X)uDrFkvTpaW;a{%rj!z_|Ott{o zWyd{r8?f)8`akEUf9reygO3J-!QgZ(7F)c%1prVK#TJP~WUm98kG|ctr(#`gvydF? zi$<Z4N>0VU>ig`<Ofq{T7z`$3vDh*I3u~b$iU1%3@B`QkpdLU2fI0vhT3T9MYdy4< xSY4+GN24~3jfttLDFgz6Qg?TEiJAYW{Rcc%pPuB7tw{g?002ovPDHLkV1iM2#LoZ# literal 0 HcmV?d00001 diff --git a/apps/wr/public/images/map/layericon/BML_CBAS_PS.png b/apps/wr/public/images/map/layericon/BML_CBAS_PS.png index 5ff4add3ef67899ca56ef065952b919b8426d086..0e70939baeed2a0221089ebdf2ed19921495c8e9 100644 GIT binary patch delta 897 zcmV-{1AhFA43`IxBYy*4Nkl<Zc${TZT}+#07(LJXeue(PMzF0Gl#aAibkPX~Gt`K4 zL@G!^veC@NWm!z-#iEIq3o1VsCd(F0%obo?Ng^0((6}Yj5Qqgqkrb^I$|$Tb3bYjJ zr0lEf&-Z#^F$<o{ljofC<m7n>SqLGBhyXxHk_xF*(hA@J0Dl?45)q{yniG3EdYI&# zLzd+;Q{z9LUkL`EeefW0AU8*S-)JhjeYnEjXEGIc5s}>1*2X$IIurnqgb>u;-cCXY z?)Q1GyLvxA=l6M$PG`V4M>>_#sU)c~7?`g-QeD%yu@OI2WXj*%3nd{0V~i<*g}F~| z4Gz3NKRc7kQGaXLOLY!b|9TUJ*Vn|gD<3QJZd@s~SXD3AzcD=R-E6*aq2)dRFd|YE zMLF`*@X&iR-rq8uOI(we&y}1U+S!f6Vk@VH<`!-=6-j}>LV9-k*W*=HC8x7J5CZ_O zXR2j+DZq)C01?w^b-3f3!GwDR08rP^1hqy>fS|x_zki5^Lnllolh)~Uk^}(JNVsBa zGXlmqoqgwBR6bvW-s@NK`{d8CmOY77GKrPo9l@AH+uPf;^Jna-nwnz<m#c480)SK| ztx3o-)Y?3lORazqIApP5@T)Hoy1R@gtTt4?T1V4kZt+)a8<})QmFA=d@b3m1tuB~R zGOxUNtbY!V7!2^w`S6XaAIBYyXlOo(rQi|{S<4_P0z=;o3a(OxCr8JU|8-y}E*UnL zmeIuMh=|8`5RPrZWH#gUx%05t%JEu#BWxBM0&_m0H$F;St@|Ysxf}24=^+wAP*+!9 zfw@>S@<sP&&lQ`^se^i>YI$KE<xf9@6K}qSv44>vY(=A)vEd(h%bB;M-;Yk5y>;Wp zL@XAIQ}!UJx8HS?CGPd#{JO6s6j~K(wORmB@(&i!dT5my^ag4=d1`k{PJPgN@#43& zwY3qq+x<5Iz`(!&cRHQ?^5svDmY0=$^vBd>?fTk^l#pd`&hfap6o)ISeE!Ad?hjjA zhkERGd&J}M>;Mq7_Y_%^900~km#(~EFcefvjOml<Ok!;<GJCcAnkTxk5whFu_x8lt z4<Q5r$OVuGKo6h@Krw*F02DeLj@<nk+D~MSn8)Mc<KyFOad8oag@u{U&d#(D{}25K XcgcgJw%X%T00000NkvXXu0mjfo@TMS literal 1547 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`<jAm(=3qqRfJl%=|nBkco*&`K2Yc zN<f!FypUS}H#V;r8ce_d)2~P@&^OdG0C@?f(AO8g(%f9Q>f(~1RD^r68eAMwS&*t9 zlv<o$T9gcoxHM&uRRvbgMX8A;`9&f5`8lvq49LhY$uG{xFHmp}HdD|D&&*57FE7>v ztM~P_^2{qPNz6-5^>ndS0-B(gnVDi`<Z5p0Xk_kaV&V)8bypKdGh;(%XE$dj6H6Bh zS7&3GUYGpj(%jU%5}4i;gkC3{dO=Acw*Y9fOKMSOS!#+~QGTuh*vnR#xZPrb(>$o& z6x?pH#Hm*w=oo!a#3DsBObD2IKumbD1#;jCKQ#}S+KYh6+H&5{UIqrHdQTU}kcwM# zCdYbA4wN|lzxuh^-KyftM`9}$b|eHj#ydx=Nojj7R_fibWOhtktBRwzr;AGL6JLcX zP9asojgm^Nvyv>YoZNEAS66w8U5Los;>%~ht$SX1yNy%n%YpY#=RBYD{eR7zVv*&a zHg7yw)KPTEN8<3l%tLM_g0*X7YcHPdDrJgV)VE?sNS)uzi4)u2J~Hz-$nY#Z{b5O+ zn|F2jglp2BSwbJzieC`a_ZJb3;`&^C;m;P%xwd6GyY5cNP>d5PEO@UsWuC6v_uqA2 ztlg|m)V8_oF?{%<)YbL4?(SgT9XV^8jz4d=GUMYsCQ?~=c0bqc2kI7fy9%~0;^Ao3 z`RnX=Zd=fT{>}|zGDZ)d-L3kexg^7*Ma_@fQ6Qz_jGbq|BgbUpPoIh+uUG9-IKJeJ zkN3)jv-tO9Wgb5Agg?Wzc8g<qUWk*ktB=p$NzV<nOl#ugr-?nAn0{}$#OxW9ESQU{ zvW%{psx03-^`qSJ4;-6}E>~tflT*nqwc24D_r^eK(M07myPQdht9O1E(DK!JCiZNB zzeiH|raMOx65h_&*cMm)<@t$<#hNS+^COS+R2N!$F45U>`_2aQz0QG*+fLXNDYaDy zde2LpcWy(^SyL;u`NavPsi);W%v-$krbKb?sgs)yJid0_cHw8$;{vP29>hoX<vp8q z-TnF23PV0|y=f0zqr*G?r!wkodiZv?V@RxKv%=bg&c~m`e{`IrA}P1oe|eYV+qJJV z@}GBiu<hzBQ`CvGHL$s7`0uUyyZigtKL4|wQ1{!={BLZVtG}4s!NtF#ZrEE~y!kqz z^_8!DaAsknk#V-VkkHqFRaZagRDOK=T4kP1^}d(MzurIp<KDo?pc>2aQPz!58&o`d My85}Sb4q9e0Czo2EdT%j diff --git a/apps/wr/public/images/map/layericon/BML_STMS_PS.png b/apps/wr/public/images/map/layericon/BML_STMS_PS.png index 423f2e8fcb87d6c1b9192d1547a4cf97c3bb7292..bf4357181e9891bf71753cc3cd75aff9262de4a2 100644 GIT binary patch delta 788 zcmV+v1MB>T45|i@BYy(%Nkl<Zc${^TO-N&R6vfZ|f8?3iTChpuMs(1P77NAf#6nO6 zp<Rr$6|(KLiw3)xaUU(Sp00&1hS^P+nN88f*dZ&oQc#p;p++&OJ|p49<o~{JH_0$q zde--M@45FL;GURS03aa*+U@oWW6aM0P^nbZ{o4Pi2ZNcl<9|3W_V@RBcXv1Neg7u_ zD3{BUndw7DeE>@$ikLaoY&Kus+}wP9d3hP0pPx%9<yN=b{VbJAJuQ_=f|)@?3;=Ww z&NQ3NSM_@RtLy9QFp45otJT=^ym)GAO6GF8XX$kMNf?I2%tS=7DwoTB_`%iH)$2y1 z@uh9sp=DVr2!8@FGbIuUaddPP20`$+Uax<e$z&c8QOL~HcksDw+kez*wFi#lgj#C_ z0Iuue;^G25%Wz#c92*-`xm@l|Hk&OFkw?t@MYUS3oSd8tM^O|KkpciBf-weXXJ=q$ z0Duqzp63PGY*x?D&%c|Tocu-sXb2(R-QM1QSF6=vS$`Io8A1qjIvpGzAHQz^AU4LR zxw*MGl}h~yAXG$TTbA|x>FMe3dwY97RIAl+U|>M@58QXd%(3gbVsUXXEEbDD647%2 zB$$~z&!f@N(MJml3%||G%qU|_{QgZuh@vR=eP1juFT2HJag~Uk_kzWWh?tpaV`D=M z4-Y?f9Dk=JgxEYdIA9{;APAU<#LCJ_yHF^6OGIxgm5R#e^C19;-V_FaQmLedhK7D! zT3Y&Lc6OGGF_cUu#p>#6qfjU;6VcnPtu39;=cE6Pvk;=6%!k|C+h%8H=Wk}7?5}fQ zR4SE(<2Y8U)%pm)$5P56t@S5|hlgf;ef^<r+kY43a`~@`i3#nxZVNygK>P0Q?rwT| z+M~6#HOu$?bQDD+%$(I)XW}?cODQw0R*SUODw#|MVHny{%9|*PY$CdnQvNeCGGbG2 zci}jW<v31KDV0nl5(B>PTUu)k0G&=J5JLDt5SYQi!EU$PHRI#s{^sUp{4d9PpR{5; S2Mx6V0000<MNUMnLSTa3;)7@a literal 1542 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`<jAm(=3qqRfJl%=|nBkco*&`K2Yc zN<f!FypUS}H#V;r8ce_d)2~P@&^OdG0C@?f(AO8g(%f9Q>f(~1RD^r68eAMwS&*t9 zlv<o$T9gcoxHM&uRRvbgMX8A;`9&f5`8lvq49LhY$uG{xFHmp}HdD|D&&*57FE7>v ztM~P_^2{qPNz6-5^>ndS0-B(gnVDkc?Br<f<mO^%V&Y_MXy|I<XyN4I>}X<S=w$9< zX=Y&w)9aF-T$-DjR|3<Ug3xP(Q!gkf<Q4#Jc1bPDEK5ysE6UGR0DIXg6Q^68-Ef)* z)tiFbEe1IC>H{644~kf%h=vIPQxAv<Pqsh~JmIJ20aJSsFj-$;(cR9#z*Obw;uunK zt0c(RUpP?2_Vdk!+ghhNaj#`{R_sz!IkwbLsHd$}+C}UiyZF(i(!#>!Z9mxW?6};@ zEo2|D%REmdk7w!IEnAsXx3Fee%g-}U<hb@J?cVOs@AeeG+xy(^WNGXd>76loozD!P z&)&Lq>vfBCkG(7`X&G_Lf*+s096UWhW9E%h;$|<RRWGLLH{NOgQfXzo&1P25;|Ci$ z7v4}Xnt3JmSNzx04(~eU7Vnhr<vmy^>vw%~()q&4|I^Q@J~*G4D0Fh-ibWa?D}1IL z51*v6Bq*7&YTk!CH|nJ>%zXC!xsRIdY>y*~lO3j=&^@SJFq1VhVC9tnohe>l^nO*X zP<L6){rU6en{P~fHotk;b~y3HEWXvZkH)QLJ-w~)>nWL4S3e!)m$$clI?+9yv+1q9 z*{4e3_h$DN<(4m4J!?M0E|$|ij}FdN)#zfs_*bZgY4=sDBp2q})uC5rm1=ByboJdl z4oOayi!VzOt?$;mghbZQ`F8Kc3kw;yU3SLpZofGuJ+6AWS}R$@h)0*f*HnPlo#mZa zVdSe1!6_edtg0)s3{(zoGqu^X_eG->i}{RD4GRY41&6okY|)*q%AvciH|O7ve@!<; z9XA-woavCdymN6H^VUyh{3qjfojsf!BVmxh!!o0{)g_C`O{3{bg_KIf2h)PO>--B| zvZ<+0iiof8uQ}MVj8kwWqkQ*7VP(TL&+-z!A2_srZf)b%KiyAuP3k&0q0y_9{rFox zrd@uHp_4S`nU?gb-qe_7I&I14c^MYBSwknL=H!MamP>c=x+`vEeq{D5w?u~hVSvEz zuU4gr?rloTWkpWE+rB>DZ+(f4+~>6Yfm-4_#s0os7x9nFfMHhRx5p)mI~zeIv!|<{ J%Q~loCIHG1Mo<6% diff --git a/apps/wr/public/images/map/layericon/RDL_ETCT_PS.png b/apps/wr/public/images/map/layericon/RDL_ETCT_PS.png index 29c0b77b4ee74276df8de9416fe284e4c8bc15ec..e5a83f998a8ea4f36918482e08ec93f13fb6c40e 100644 GIT binary patch delta 874 zcmV-w1C{*v2Y?5VBYy)&Nkl<Zc${sMQD{>K7=_P&?@ex!Cb6k8%U0JFmZ}x(=At;Z zhq(C?nHyowhz<*T2|_^_+mJG7Ko~`bLx!++WwL=VsAG&FExNT6+p^(I5v^veuB~ZJ zG+mS2rb+L;x%d8iSVh#G$HV`f!~bxARWUPxK>%P!9hf=98h^jSDsSRmwmo+Dq#l|Q zWN1e9(D~x$Bd4<ODaW(B`Be7Z<^F}P)Vz}Zh<n)u0Lf%hcnnT*QebB3YH8ri2_?!0 zW^Ek%3;gy?a4Zdu&wxKIY~!K)cf;ii8~{BkC1w@`lB|is=ojA_9UqQo#%o^5DTpKr z7Bd78ELc|AtbbZB1RHG!%#%Zlhu(<oyA-GZfIvi+r5P{%Sp4$H@YJ~J#adC*1+iGw zsHO{0R&=VWEPAZ9()H<k#zp>SSM%|^2Z@LQkOTmi^8@>GJS84&X*b&HH;YSp89&Yc zhN0XQ+V0<s{Y_E$oj#hYWa)>6Upeg@+q(yev+;9rO@9Ii_tfdF|5QqFdF#n<uSc`} zS*+g}gkcD1Z`q9vaucdpK}oGrd0C^pkZax8+_LUu{F6Be0APvE*<t~mnKRhx5Aps` zgm$dkgpSs|aEnfi7H;6&-<NSEKM7T+U|UeK73I8R^#z7(dS~1e*r}TZT+L=^Ja-m5 zf*bhbmw#i_GkFo`Xa9s@nQ)3S+%6YOs^|Xb<o7xvAOQeE>XxBqc?%UPD$DEv3<qDj zdz;cq78ho2p{9v&uop^2<AC!;@;!Lwk11R)_a5vefthKdZ*Fjx^POvJ+(Bt^d8K9= zcCxb@5^S(rE?9;GN~LC)k}U4Dy|pm%_3uCX;D4(n5(!fPfcL-JqqxRA9UlY_XMAfK zZTFQWE|xV*uhq;_MK^QG5=%~x)c$;8Ss%n_eS>|s+9GWl5m^KP{r&x7JRTQMA3gnQ zeXOBtxHJ-(R%azoorqFZN0V(mBHq_076<Y@o!y<kgu~$nsZ?qSz@uTThS(i&1iHJr zUt_Cl^=wx~!!K#PQc4$Z_MGTRWsJ;xI2<lLg#Uk3A;<vS0Q>-+1JDQ{2%sSvjml3l zPfzo|AR(1XiDUUO;m++lXlQFNkG*-!V1ByvU-_e+CpFW^EdT%j07*qoM6N<$f?+_V AZvX%Q literal 1016 zcmaJ=OK8+k6b&tw(vJ!*{1ogXU8vB>XPTs)II*3XOznWFWjfFSH)fL8PUs{rO<tX} zn--yn3!xS_T?m4pE=1_Um4buR-%fF(?(9ZzBO-1Di7(U6)P-Y6@;;n%?s@m#cYSK| zd~er@E{dXha}#QwjEAGIvxEHme}p<2j^k_*Por5})jde1OjLp(XX|q?4|Q{C;SEeu zR6A`Iinyp<kPT!rdW2yD+a+v@N{$AuZp=drN^s6{6#CnfXEd-(g`N>LPIEJ`Y)veC zaC&*NU@XrY36mZ@2a<tI1Z;?P5ZD#Rmji{a>&j#vZL>6}L-4#p@0u!VQy_yp2t;Nm zW^g<YB#Ge#QIhyU5a;+f%aK=#@q#Q$a(o!<Jv52tnX__U9p8&ZRtjCl*p*qfTCFnG zAqIJKET2duB8_-FMi4Q7$-#OMbNqpZf(m`Zvs`Q;2Skc`2`yrUCQf&cV7pCO$KOj6 zNf;aGF3U4qbfgAQ)BX>&?IzmCdARTQpTd4&$%Skl`e@NJNa1D&qEN1!@t}^8S3s!J z$l_EPVdR&Q3o_FZI6Y%ICaU^p>Ud3)bB>R7$ACFip@{-xS*ARm&19rhd{__#k>|5Q zN)$#$QrT=;Oi1Y=Dc#_z$XK+YgBx6PpF7f$8?}P%lE^CbtSitQ_mB<hN0zPDUWk)c zUUs==YcEnuE=$a?QDOfobYqKjPc&_|Em<@hA3CJlJ<`@6+h6XGD^ba*sY39nv^Bc@ zD}4Vr>=~;C%<tj$t=ea<uh`$c@nPle=0mlwcK=1^z0LLQV_WaWX84`;SKR90YNfVP z5#C-37hWIvav&+(e!6<?<j?1KzPEJ?r#5c&r8W;fIJB`AhT+AVN2#_RYP~~TJ9`6t PjqYMDJE=ZOU%vVWRx?RH diff --git a/apps/wr/public/images/map/layericon/RDL_OCUP_PS.png b/apps/wr/public/images/map/layericon/RDL_OCUP_PS.png index 8a1ac0074ec2fb6ede26d23e927b6fca22809356..aeae3e26680a9415a77f0d9b310eb18b3e5f6c58 100644 GIT binary patch delta 815 zcmV+~1JL}X2*n1FBYy)7Nkl<Zc${sLT}a(^7{@=)@BecCo^#xhEi1weE2P?(GLto8 z!*y{c>rH6t=wcT|8WdD?9q2*`293yzvFajvAvhRF7aLP*qs7|9(uF2la}H0j>B^&> z7d_|y@^rB|g#27Se7|3KzRv@9QB_e804$Fc)kh1eyKbvK)_<yMuh+2-SsRLoVmXvc zg(LRFP`CMWs*y@571>@<@2kHO>}>n$n=N%GUJ+Tnrw43R75e%_RZVd9;wSddd#`fk z5|mcaFvPf40@rP}Zd_`9x#^|n>QoPdEUtx!sv0p?<j;(MXrKA$-C%mm7bhjsp0b$p zCAwHrNl2uXLVsjGw9P5)f8$J3>OlJ+4O9S(h(wD~*eK@*-wrO1g#<O0v?grR;)&T; zg?Y*ol45C)wEdacAU<)r?X=8x-Ce;LfU3!#JFFX%R#b9ST3lmV6R@ICZDHCsVGgCh zi9`bruKuQ*ua9=o+H2F-rv|{xXLs?}0@TLDI#>;k5PuH;OL#U9jvx=aSe+>p%dn6W zZ}H|1+V-a8=+Qgp(IBj1xdPkcu-PX1sX#Ee1PO!Q4ls>XnB8&AFDo#dC-g%%Zs(j5 zhyf_O$?Moj)m#N42u>{%`H^Zl!XH|qGO&Q&3gCXD;AuHloh^O-S*3TagZYNWVd@(s zyzsZ`Tz`Oh!b2AW(Lx9@P+uVw{LviLZk4$$b?5kfxbVWslOn1rM{W@x82|QM&A_pi zVxr{7y0<wxvx1f*%!3Z39Q=u;z&o{M=U;pCX7_yM`3nQbr?c5?8Pz*QW~OI$7QZ-g zO%LwXd}F;9UfdBBp55-}pLsmUKmACg`<k`*@qY))pJoOR12vtUo$mTqHX;(e|NZ%< z*DJaHO+Wr{z`Hi<vQh*u#%kSG3a$G`zYgtV?{svWO{df4k&%(&y#RnrCS!GVbqS=B z$G`h>_a34>@knb6QCR%7?2o_I-#^CG)IaHT+FvvO4^@>X!2xU_26#XmNIU?Eo}M0O tLnz}$L8@v`W-@N3x7SrwgUE*3{{XBoeqw|U;4}aL002ovPDHLkV1mCili~mX literal 1061 zcmaJ=O=#0#7>-tp>_^m#%!?s|Img;0{mI&}x^+$K0(R84*dBH;O}?(#mV7b!y0*iE z8*?alQ&5?^$i#!92*ZQF8;I!1o2VDBDvTW_3R9S0y0-IR4N1Nqp67Yr?|q-|a$@9U zd&`j)f*{((VIhgf1NOJiiT_78wj?}splBAQU>=nu6A)nq<^U;b(ilhrNvWKB3;GGd zL8<91%EnLeGSu9Xjd7PX1G5RDf1qqg@&rI+4veXKh+2R8k|I?lL}mPOCT>JPK^>kp zL27y=El*F#L4_JPLH3t<ET91-k!5XCxA=01s_OE1Zg0~RS%sj95Vf6DHl83O&;+F4 z?RCiv%aR=DW_^B+V|z&t!+K~2znqKp@qUi?^pUlP!qH46&nJagEf!vdr~*O;Pt&DR z$zAfgp*cpg!C=tV@OWGp;j$_^lFBaK>Z~gWz>-bXKq}NpTT#lvA_`GB)9oW@MnhJ& zYGuL&qsx*(vu?&dQXLqN{~xMp4YY-lU?<;y3R~%l0q7*KV9}Iu<MN$$D1(ofKtj+= zLpWKlVxj;Mv<lE5BPovT&ZxQqOV;r!J|5>q-9nNs15pT3Si!BT3h$3{0UyhReSU9@ zWuv};j|mL<W1g_bi>vMp*SP|eiyF{TovZ9{hnjM2D`*CeEC5rT0!qw;8d*IuuQtym z&{VI+Rhs7#Y|5o^GPK>;zZzZN!tSxB4cp>H!}vhQZa1;54?CBG_)6Rqg>bt3Dc9T6 zv-xhY>&x02P<F9PckYPqe}Dg)V%oWtdyi%mNB-{8tlD+s!mIoHoj<;f-u}oCQ>%|R zoz92LEzabJwgk=0?mnH#TvzDP7w0<;UZN<f?bpxsV?1+lZZ7ehaEy<S@9}?rQf&3M z8R8+w{6gy>8$Q$ftg<mbb8DICBd&G7*?VPc>+0gdDnYIg;-c_cm>i7R7gvmq2oHwN G&i(<yMpM!N diff --git a/apps/wr/public/images/map/layericon/RDL_SEPC_PS.png b/apps/wr/public/images/map/layericon/RDL_SEPC_PS.png index 087b4e94149966fd4edc1dd5b27ac50e40a4641f..b3db8e0a41bfd9e32d796bfa4035bde320607b2f 100644 GIT binary patch delta 767 zcmV<b0s#H22$lwrBYy(jNkl<Zc${rfOK6-`7(L&2??0JInuN9)XDBXGQ)!J<5D6lJ z;zkg3(M1*ANvjJXU07Wy9irkw7lIoph?If_i?1SXtaU)~1rcLyiosH`t?fKwI{D}E z|MSni_qb?NlHl2%2j_5rO(3ZNB?3_MJl@LO3jk0ht!x(mzkeAZ8C~%_{+=h_s;vwX z=bi_E{iGqeA;+#W8xip&hcEc>(T_$Szu4@!#p|&NQSNB$c-xrB8$v2chzM^5tTPvv z0zUrM=tH&Dz8hAt!yvz@HT^*J^?&+<t#_y%mJo!bib$|0Z<&*wX9GU@UU6^Zdg=<+ z*fxLwkx`+3cYn3!k{zWN&n5lsyMTit;z)|zY;gDQz!z0wXQ!RHNYx5}ZkFNv^1mPe z1EB4_yQ@^P&u+<mI-N7mh)7>Z-gCO!ub%eUrfKF>#gc%Epp*5nSZ{y;K)?vlbzx90 zPDC$_=g&N_b?gZX;9AayGxc_||9qo?F;#fn2xzA%et-GvZ;%84C=i14_OU@4wiS1O z4Zv9uX^t3u^5=H%{dcu=VE$U&jT<!pHr!=^;C;}icTc$!Gkc#Ckz*i5NlMpwxo<ds z@YU_3?>ut*4%_lRC~h<t5g>%%2|ci9Dm^i?_bCxMR;gC)+La(FAXNc0((K49wH40% z`bW6`r+>2nAAauT<d;1B+fq#OQ2?k^s}`G4sZ{jgn?#ajvv1Z{c;^obEPVDAk1YOq zmE?l}aCCk?+B|OqM0KszzSl0-leaHiS|Yh?J-0z$sZ=zM<DpiowFSVaF=i}^3gb)X z7Lu0_9oo0tXk7l_)TxC%Q&UlzrY!(%0PRkv(|DPlp6<)+?CemMWfR`}GRaaDMa2-p zgt4~RT3waMT3g8H2hO>sF=p9&-xQI$F{V~7mz%P-KaS&}IF1X}+Cnau%V$|O6h%=4 x0NrkPpsLwmFi1v5MtZ$oFPWU2%;x6i!hdSPmcBW5^N0Wd002ovPDHLkV1lGLdV>G} literal 1068 zcmaJ=TSyd97#_94Y=wl1?4`4#fk8X7v%9mG$<1_UH<wzobwOVWXJ?K&=**dBj_xSE zBuI!o^bkG;N<BmrLI_0{$d^RBKJ-*jgb>t&Bt>Dcv7J#@duSVG=3Mx`@BhyK|7X0t z?L>9efhvlks*|l^nvA=huW|?ZAN*dB$WV*pUAP1F;GASas#!r<2$GuA3)4_ihR-j; zMvAJS)l3)eN}Up9q%o3%VQkGHY>H}ZvJFY@hZtmGuc}AsuaBP4Kvkl2C!g}Bj2P@w zTSrXTG18WiNBZT6LN^@)jkZ7pG>9c&YXiC^*im{-S0Hm|o29`T1oubjbyHobb`V1* z1U%#S$X<>E!63s0_+XGb0(@T1$9l;d^l$-z4+_2z*m!6X%~X1Xw3yh4MOIO|4`V}M z*<3Ei<opaWds!|Li8vZQpNAkk*07Ex+oM}`MFkOBvZ)$aMLKX4r7Rl6QJOeiKZ0g# z%Ielenn=P}TQXRV@j6E;0#m8~Lp5y^ZQ(TB^7~I=D>H0BHVrK_Xv(B;J#|hfLx`DB z!pO`ZG*HZ9dmqBc>O%&Ibp$~}r>ZL`XB}R{r&2;vx3HwkFeyf9qQI!CB8VZLWVyv3 z;Qb=U#RCyB;g5N}VQ)Ab3Uh&Ykt-s3P=h)ya+NJ^pd{C61<fFlMQEx+P)V3b18YYX z)Y4u^*-Lp@=PISW_)2nFVup1J`&XfhTcmrO>1NxK#b)C}opifN+B&OUY9Uu*Iw>}1 z?04CGq0m&}YB>6{E*zeF|9kB6!i9axtNW*4*S2nN*da#p@XF-m{H@h5Z*HzuQ)O<q z`<aU>tEs7R-3_4+-Tc+@i!F1bCztYXXN`Mz9yhjCtURQq64zaJe#QOk9GD-v#?0+q z5+8j0_UZYar{y<hW|$Y#1rS^~4qmoS?&{uIHFdk)^?7-DY<6N|qSjbE!yhW&wx8-h V%kh;z>g%2Rn~b-KGcDbte*gsmT|@u? diff --git a/apps/wr/public/images/map/layericon/RDL_STAT_PS.png b/apps/wr/public/images/map/layericon/RDL_STAT_PS.png index 13d8ea6899c02aa0d0917b630ca9f697783fd6ff..9a0b11673abfd8ad6b85c32dcae55f5e3f7664b4 100644 GIT binary patch delta 833 zcmV-H1HSyT2-gOXBYy)PNkl<Zc${rhU1%I-6g}TJGrP01*-abMRBHUgpb3-|yA(?i zqaqTpDup(JBGi|K(wAT#N_}v_A1XzJA{tF44}I`q1s_UfEmngug>>mcY)EUft!dh{ z={BZjXEL+1GvCbdp$(<2_u*c++;h(5au0APND6=g04xCHa(@?_|86E1yLl#W+&gz) ztTvMXJk@;n>wnZA@684E9hVh%&jl-o7Q#xk?(E~Sf&~DDLP6dGE|8KO)qlC=kG!=+ zTIWe?GGOOfpQ*_v&2!`{HS+r#?r%JCv;#ui-Gro+08TG?Uu>Hv@w*djo$}PEqvClL zbJS7s)+tY6%zp`<nI(&}ODAi6>sm;PyCluprrp)1N}Y1m$5;G#+K*|IyrxK(<b)e> z*LjcnSUr4mmMl-4%CCw807e(;pL9-=2mWq^G0AyF&MER*bh}<xq)&2i##NcJiigf# zJ+U3Ib`_~f0pL<YJiB1W=+Geuz|w$L4KO5-)qu2i`+vZXCN<&I196>Z&vQ?h>wXvz z)B(T`<5V(=@#Tz%c3r}dK*o^hvLvc*j0=uJLJE9U^`I#OK}3T|!vuVx6F@d<&ORj5 zFV5Wxv8QZfOS^$HO95ZoV2Ym?ns_Rs;i4143nOGQCeyxs<M;(9Kwm*f0NCtb{dJ=W z5jA7BNq^!%w*@Vtc;|5wiHP{1JB^KLiO>uAq?HhzwV8=B;C%b`>q3$gmyZmVcAATm z*~}W<_EebC1Q5n((FLGl7!vUPP(g3eb{MtA^6+QJ)?#g#&*vfu0NaPg>+8pl?%mhn zTxrd=8HfM0X}du@GF^}AZWs+u*<2P`ea~a@ihora`t+O9*ty=m-lh;D1^^U`MGZjW zyZ!kWMkcB!-ze3>O+PtY|D(&E<12i%w8+Ebm!>}7Ke!jb)45!(6@b8MS|u467)Y!E zIu3sL{x;F`{1zjXZg03NjmBT6r;i*wRGCqj&*gH<l}g11!2dT@S63213xIY28vr~4 z;5uO-+t%OTpS&OQpf&Hj=y0W?`8RLMi`6RHdV8afUVcgOK~4MzMZ?7h!F%vn00000 LNkvXXu0mjf>vELe literal 1075 zcmaJ=&rj1(9512<`H{#)kqa+fOw_P--MZ=;1{vKZi<T^7ksUbDwGY-n`%3%91`#q1 zh#tHc{|C89NHoNRgNvFN<53T4gcyl2(SuQ=s3FW(VAF%MY5U&y<@5P`f4*yp{<AHO z?Tr*gwZ!`(NwOYxzJ`P3-|@99ljRtWrttt8!Fkz&R9HnB2;#at3X@P)rzW1jE{dwB zwNx6XrSpP<bdT&{JO$k(Y>MjYDVVY{1~JIMQOyX^pH}bFKvP5XU_kOprU<iI-?Rk> zru$RM^q3M<>7H)TRS<}P4zUah`nX{Wg%G`?E0Dc&%+O#5g2zJiZcu3{0Yqd$5b*fj zikD>p&wE%d!1HV;@OfDu<0UWeW;r3i3%=7}@1cn|OC1rCk=ULuVuk1|#-_k9`F!4! z_j`~v%CNy;(9!Vu+yvparwl9?+=lI{C`6#GSel77WB^A|&Y&C)(InE{6m+vHYuI~b zA_Zd#vdOR>uai;*C`tc^>UtGz<0SkO?>~j@)RYOCB(zb^Qb^-QT#hSK5G^QUWTg-q zuT(LSMHtyxWCC%32PXzKLq&P}<PKhvgt%d2*-&6S5~7KMN7Gb6<ilZ(3j{b$<XAS! z5krhcqyB&x=#9p>GZk(GDLEY)xWZNcaQT{CXB2dkc#c3zn}ll2LOR$<S<q_7;;*T< z$5m^`!qwz5BpAkN>|c$p9Fgg9w$-sEMs@I^L8jdzV}1OY-y&DyRy-0;6<%ew%H^JV zzVupXjlM;>ZWo{HyvOCIS2I^-|6J?Z0Wkk`VB^ijLzBFB=EJsl<;(3=cId&}xjPGs zpsi_Q`Qf|pQtxK_1*XV#|N6FYsjW08nU6nin%`GDdOo)o{msL(3qL#dFVEMxR<e(d zG`-+9xZ+J*qI}fR?%D0F^3wWx`NwlFn`nr&lv=N^&CH}XR_-n~xK7pWYo>;76pH}7 QeD7T2c(gyV)H{6nH~m6mg#Z8m diff --git a/apps/wr/public/images/map/layericon/RDL_TSCT_PS.png b/apps/wr/public/images/map/layericon/RDL_TSCT_PS.png index ffc22515996bacdfbece7d30e3d5afcfd20269d2..3776f6c0493ee2fb6717ff03adecb0f228bf8673 100644 GIT binary patch delta 901 zcmV;01A6?V4Vwp$BYy*8Nkl<Zc${TZYiL__96i7P&Fdy<+H7{U-DXp*Wn`VMs}ncJ zx~U+H>0Hs^2dQXj!PgXLQTF8~RVIGYfiSCxE$GAsKdi`F)Y+}`F$a5KX&|m$TbI_R zY5K_H-rU^W`}2d{gnB+6&iNhA4-R1!%uGZ8KrA}}wZQcN%6|b}1DxYTRQyjze9K2` zNmbR`zWgrHaW%GhpQ>3t*-7fX0eAe#eO0IG>t;?8ZO<!;A|jE91pqc?rf@h+%q*#z z{%MCY`oj53voMSTOw)uA!tHRln$G^7ZE8E{KbT7nKkKb}WbRfdF|)8qWDWdq{ln^- znm4YD-YgwGK7T@fpDX|gLX^M^YdK?Bf!b>Ov5t+yoOti>*laKx4u@F~kySGFrqgHt zdTsABUzpn(e4^*w7AccSli)e1szI7&NEKzKZDM?)^zHYl{Z(6DdTzA>2>|$UWb)~& zV@b&djSsbdE&LVb82Y>c=TG!f?>i?^;hu+@*Wq;;bbsmE6pxNut<86%JQRxldth>A zdHd{K3T3iEODlxI)30FX4j(o({ffsAx8lfC1IXkUlF(@>y+D7b3R`yW+ID9&8e%~J zjH2!~^c>8R3d^#%#y<(*9P*|IzysJ+xd_uxiA@cLk%ws&?OM?#0}z4%cw9W~b%Qlc zh1;H{zJH+y5x;l}9<v>@u{ZH?bO-Dr3ti8{?PT`YjrpH1#*EuP*cx#Cw6%U6WmYom zwC6FIkomx?yU^Np1CPGgjFD^A@Hn!_X0lwn(MNty{_@1le5R+T2ZEXD+=bquLyy&s zuJ^lbD@)6U$C;<pk{tsh_al*%;dAAn=CdWRDSth&|E|Rg!_hNdueZ?I*=Y&@=<50; zZ7arKKhpkiqVkS1dn%b?U0bl6V%9YD1#3Pz&kl!Vd-?f2s+PWd;+yE;AB~NTIU=$M z0Q&p;rBEp3Jg&U8x3X^cvEg5??~YF-rA)Q}*=5H)bsMnnq541Prhn^u|AUVPgTdf* zEPfVSyuAegP!z=$i9}?t1DlV&-L<D;U2U_F9P5ilp^!>W#lPzN?8;0sdm|VOCS$SK zG5`x}p(u&~AOr9N*bJZ^Km&j}02^9bT3l;Aw3b+1rwB)*HjIslsi`Rh0)bL@cXx@I b|EK*2JXN2b<c_UL00000NkvXXu0mjfL6OAJ literal 1700 zcmaJ?c~BE~7!4kP+-gKctlNlFwUP@GASK~Qf}|xDLd58lYK{#FklmPF$N^e~ieg(~ zP;3<usnDt@BPuP3f?BR3YFkB(1}$=VK}IM?P_P>W+CNHncK3J8d++<c-}iPlGjiQp zX9o`l91iEq3+D*1(arkU+hXr!{cpwCuo&fv&}dkWYQ-857b1ftAiz_J<3Ir@mg%?t z3I^hEGYLwe2o>=o7*be86k9Pwol1?dak#)BomwnS08u~!#wj5t!CX~C0F*K&VFQg% z=BwGDLK$w*fYF9^La8A^8XzMCv4B7w0~1hzs2I?x5+Q`4V-m)78Q9#qO(Fo}5Hx{F zc$buj9|^Ew4G7SPKHgF?g#ys&M2atsPN%E^sALM2M8<BqH^rAhqcf;}z{EwsqG@Du zhJX_`5er)}2?`WdGe{(@R!h|S5MfOmi4qVHVAY^fy)lG0qK8ni&Kp9OPbzRgM5<A$ zQ6&rkRz<M{PC}UkEYo)-sMJ%k5HfL0*nyFBVl|0EBwI_G1oHX+4^^qA&<H93|H=11 zg%P1%4Uz;P0w-ytSmWf&t)bKmwgwcVuto^OiIb-osen-!QNU_|9Zd&TZcsupSc|M0 z$Mg9N9)zG`NDA^eOai7rR4QeR05**p%B6?-`m#eP6t1s-s2`0>_6zk3poCKWLTQs+ z4lGSlfe<>$l}&N|$kTGIR#2(2$Q)3kOa^6P8dwF4m&{O3p9^PNy$P;t+Fa<|X}KgU z8IrZJ|7!H)7Umx7bjr5aV#@d+gt=XV*_sLU$g!_w5swoh)IA6k#40}H;>T{9-nPDk z^6DR$io9uMF|)VLSzRp(NnBP0h`4K5_Pf4Ugq%pTXYFw)v!8uZqzHa|>VEJxvg_3k z(!a@kRhYK^PUP*JqRj7F^KbNCPBDAlGQ}?S_d(=0hw{zMi<=fo@XMQ5@4480GKa-) z!5{Iz5`(*I^ILMAc4vf*?d>gwjc$BLBt_x5qj1%tr2Dp8Z`%pFzi{hz4|jL6Dah6f zU#=(vo4VJoxw@jFYa``~c!^i=^PN?v3XJm_n}*@k_Xm$$ADnl>h|7s+`lPnUE~mAr zzO42M`Im5|sj6h)<Fm~X=Q;B5sNT;vSDKIA<sI;v6CpRmS7Z}CO-C!L#`0bO`)khw z?lbas4WE9{BC0fus(boevug5-AEq29?^rg{c+M$5c}Yph3(@9LDKe5?-oIV(++wf@ z3U_2|y1X|H(igJ?^A<eccR0PZ%8n9Q0`Q|Rc=Y|LoH<fZN_c$0tz&T=cm7ChBs249 zQ!wGfTZor(#?S2wX8q)u664gHR~K#}!rWsT2VLUDLomLwVswcAMlapip>_P4V%wAK z(6BYJ@`tl$+)VagHk{*?_zxW4Z7y)kZQ{XpiVJv;!?JSQr!Ivp$zUHFvYqP~QZvf~ zZ;9^EuN`VVACaXZ8m^axX{*w68ok<&8Oisd&Xoa1wbAv_>?H>qf+RunJHL8WwD)Oq zuF;+KI;?$XiqEC~cxQcV+nKDqit>1M*RV_Aa3fA$dO_Xx!lhoa(423YQ*)Xn>`QV{ znIxA6tLr-tIhFa$y0cr|J44(j$w;Wt?HLl5Sw3pZMVuqPxOeSplxsB_|E#;iZMb2y zAHEdn{b(>VGu!fdW@`WS%YmY(I%9LkdtC+fmK)t#{Mi2abALpnZ|-e)t$Ul_8+edd zpOC)3E;r{%-KWy7Itt4oEVkeNsu9=ULaC<Hu61Tt;N3{6R6*?#MojJLwx{ghGZO!L z78if`+xmf*M)A^-qW0K*V*~wB`L|k&vzVLypmz<lufO5gKJqXtquiWRdO*>>*~S)^ X+MJ>?k7n6e{~J8+I!<ZmmbAYC)BCkP diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index eefb08da6..079506aca 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -1,321 +1,356 @@ -import { Search } from 'lucide-react'; -import OlMap from 'ol/Map'; -import View from 'ol/View'; -import { defaults as defaultControls, OverviewMap } from 'ol/control'; -import { defaults } from 'ol/interaction'; -import * as olProj from 'ol/proj'; -import { register } from 'ol/proj/proj4'; -import proj4 from 'proj4'; -import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; -import GisMapToggle from './GisMapToggle'; -import GisSelect from './GisSelect'; -import GisToolBar from './GisToolBar'; -import GisTree from './GisTree'; -import './lib/css/gis.common.css'; -import GisApp from './lib/gis.core'; +import { Search } from "lucide-react"; +import OlMap from "ol/Map"; +import View from "ol/View"; +import { defaults as defaultControls, OverviewMap } from "ol/control"; +import { defaults } from "ol/interaction"; +import * as olProj from "ol/proj"; +import { register } from "ol/proj/proj4"; +import proj4 from "proj4"; +import React, { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import GisMapToggle from "./GisMapToggle"; +import GisSelect from "./GisSelect"; +import GisToolBar from "./GisToolBar"; +import GisTree from "./GisTree"; +import "./lib/css/gis.common.css"; +import GisApp from "./lib/gis.core"; -export const GisContext = createContext({ gisCore: null, gisMap: null, treeData: null, checkedLayerIds: null }); +export const GisContext = createContext({ + gisCore: null, + gisMap: null, + treeData: null, + checkedLayerIds: null, +}); export const useGisContext = () => useContext(GisContext); export const searchUpperPpls = async (fcltMngNo) => { - const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ''; + const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ""; + const res = await fetch(`${BASE_URL}/v1/fcltmhl/upperPpls/${fcltMngNo}`, { + method: "GET", + }); - const res = await fetch(`${BASE_URL}/v1/fcltmhl/upperPpls/${fcltMngNo}`, { - method: "GET", - }); + let data = null; + if (res.ok) { + try { + // 응답이 비어있는지 확인 + const contentLength = res.headers.get("content-length"); + if (contentLength === "0") + alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); - let data = null; - if (res.ok) { - try { - // 응답이 비어있는지 확인 - const contentLength = res.headers.get('content-length'); - if (contentLength === '0') alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); + // 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인 + const text = await res.text(); + if (!text || text.trim() === "") + alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); - // 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인 - const text = await res.text(); - if (!text || text.trim() === '') alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); + // JSON 파싱 시도 + data = JSON.parse(text); + } catch (error) { + console.error("JSON parsing error:", error); + alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); + } + } - // JSON 파싱 시도 - data = JSON.parse(text); - } catch (error) { - console.error('JSON parsing error:', error); - alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); - } - } + const select = GisApp.Module.select; + const gisMap = GisApp.Module.core?.map; + let foundFeature = null; - const select = GisApp.Module.select; + data.forEach((item) => { + const pplMngNo = item.ppl_mng_no.trim(); - const gisMap = GisApp.Module.core?.map; + gisMap + .getLayers() + .getArray() + .filter((e) => e.get("lyrTyNm") == "tb_ms_swage_ppl") + .forEach((layer) => { + const source = layer.getSource?.(); + if (source?.getFeatures) { + const features = source.getFeatures(); + for (const f of features) { + if (f.values_.fclt_mng_no.trim() === pplMngNo) { + foundFeature = f; + break; + } + } + } + }); - let foundFeature = null; + if (foundFeature) select.selectMultiFeature(foundFeature, true); + }); - data.forEach(item => { - const pplMngNo = item.ppl_mng_no.trim(); + if (!foundFeature) alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); - gisMap.getLayers().getArray().filter(e => e.get("lyrTyNm") == 'tb_ms_swage_ppl').forEach(layer => { - const source = layer.getSource?.(); - if (source?.getFeatures) { - const features = source.getFeatures(); - for (const f of features) { - if (f.values_.fclt_mng_no.trim() === pplMngNo) { - foundFeature = f; - break; - } - } - } - }); + let geometry = foundFeature?.getGeometry(); + if (geometry) { + let extent = geometry.getExtent(); - if (foundFeature) select.selectMultiFeature(foundFeature, true); - }); - - if (!foundFeature) alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); - - let geometry = foundFeature?.getGeometry(); - if (geometry) { - let extent = geometry.getExtent(); - - gisMap.getView().fit(extent, { - duration: 300, - padding: [50, 50, 50, 50], - maxZoom: 14, - constrainResolution: true, - nearest: true, - }); - } + gisMap.getView().fit(extent, { + duration: 300, + padding: [50, 50, 50, 50], + maxZoom: 14, + constrainResolution: true, + nearest: true, + }); + } }; function Gis({ - isOpen, //우측 상세 레이어 팝업 - gisMaps, - isSearchModalOpen, - treeData = [], - isSearch = false, - isMiniMap = false, - setIsOpen, - setFeatureData, - featureData, - imgVisble = false, - searchView: SearchView, - detailView: DetailView, - onFeatureSelect, - setIsSearchModalOpen, - onCheckedLayersChange, - searchBar: SearchBar + isOpen, //우측 상세 레이어 팝업 + gisMaps, + isSearchModalOpen, + treeData = [], + isSearch = false, + isMiniMap = false, + setIsOpen, + setFeatureData, + featureData, + imgVisble = false, + searchView: SearchView, + detailView: DetailView, + onFeatureSelect, + setIsSearchModalOpen, + onCheckedLayersChange, + searchBar: SearchBar, + height:height="h-[calc(100vh-10rem)]", }) { - const mapRef = useRef(); - const [mapObject, setMapObject] = useState({}); - const [tabVisible, setTabVisible] = useState(true); - const [layerNm, setLayerNm] = useState('') - // const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 - // const [featureData, setFeatureData] = useState(null) // 선택된 피쳐 데이터 - // const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태 - const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []); - const mapInstanceRef = useRef(null); - const checkedLayerIds = useRef([]); - const layerNmRef = useRef('') + const mapRef = useRef(); + const [mapObject, setMapObject] = useState({}); + const [tabVisible, setTabVisible] = useState(true); + const [layerNm, setLayerNm] = useState(""); + // const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 + // const [featureData, setFeatureData] = useState(null) // 선택된 피쳐 데이터 + // const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태 + const gisCore = useMemo( + () => new GisApp.core({ targetId: mapRef.current }), + [], + ); + const mapInstanceRef = useRef(null); + const checkedLayerIds = useRef([]); + const layerNmRef = useRef(""); - // 팝업 데이터를 저장할 상태와 오버레이 참조를 저장할 ref 추가 - // const [facilityInfoPopupData, setFacilityInfoPopupData] = useState([]); - // const facilityOverlaysRef = useRef([]); - // 체크된 레이어 상태 관리 - const [checkedLayers, setCheckedLayers] = useState([]); - const [checkedLayersData, setCheckedLayersData] = useState([]); + // 팝업 데이터를 저장할 상태와 오버레이 참조를 저장할 ref 추가 + // const [facilityInfoPopupData, setFacilityInfoPopupData] = useState([]); + // const facilityOverlaysRef = useRef([]); + // 체크된 레이어 상태 관리 + const [checkedLayers, setCheckedLayers] = useState([]); + const [checkedLayersData, setCheckedLayersData] = useState([]); - let gisMap = null; - const closePopup = () => { - setIsOpen(false); // 팝업 닫기 - setFeatureData(null) - }; + let gisMap = null; + const closePopup = () => { + setIsOpen(false); // 팝업 닫기 + setFeatureData(null); + }; - // Feature Click Event - const handleFeatureSelect = useCallback((data) => { - onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출 - }, [mapInstanceRef]); + // Feature Click Event + const handleFeatureSelect = useCallback( + (data) => { + onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출 + }, + [mapInstanceRef], + ); - // 지도 생성 및 초기화 - useEffect(() => { - if (!mapRef.current) return; - if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단 + // 지도 생성 및 초기화 + useEffect(() => { + if (!mapRef.current) return; + if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단 - // EPSG:5179 등록 - proj4.defs([ - [ - 'EPSG:5179', - '+proj=tmerc +lat_0=38 +lon_0=127.5 +k=0.9996 +x_0=1000000 +y_0=2000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs', - ], - ]); - register(proj4); + // EPSG:5179 등록 + proj4.defs([ + [ + "EPSG:5179", + "+proj=tmerc +lat_0=38 +lon_0=127.5 +k=0.9996 +x_0=1000000 +y_0=2000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs", + ], + ]); + register(proj4); - // OpenLayers 지도 객체 생성 - const map = new OlMap({ - layers: [gisCore.baseLayers['base'], gisCore.baseLayers['satellite']], - renderer: 'webgl', - target: mapRef.current, - interactions: defaults({ doubleClickZoom: false }), - controls: defaultControls({ - zoom: false, - rotate: false, - attributionOptions: { - collapsible: false, - }, - }).extend([]), - view: new View({ - projection: GisApp.TO_PROJ, - zoom: gisCore.defaults.zoom, - center: olProj.transform( - [127.4875, 37.4916], - GisApp.FROM_PROJ, - GisApp.TO_PROJ - ), - minZoom: gisCore.defaults.minZoom, - maxZoom: gisCore.defaults.maxZoom, - }), - }); + // OpenLayers 지도 객체 생성 + const map = new OlMap({ + layers: [gisCore.baseLayers["base"], gisCore.baseLayers["satellite"]], + renderer: "webgl", + target: mapRef.current, + interactions: defaults({ doubleClickZoom: false }), + controls: defaultControls({ + zoom: false, + rotate: false, + attributionOptions: { + collapsible: false, + }, + }).extend([]), + view: new View({ + projection: GisApp.TO_PROJ, + zoom: gisCore.defaults.zoom, + center: olProj.transform( + [127.4875, 37.4916], + GisApp.FROM_PROJ, + GisApp.TO_PROJ, + ), + minZoom: gisCore.defaults.minZoom, + maxZoom: gisCore.defaults.maxZoom, + }), + }); - gisMaps.current = map + gisMaps.current = map; - // 미니맵 추가 - if (isMiniMap) { - const overviewMap = new OverviewMap({ - layers: [GisApp.Module.core.baseMiniMapLayers['base']], - view: new View({ - projection: GisApp.TO_PROJ, - minZoom: 9, - }), - collapsed: false, - collapseLabel: '\u00BB', - label: '\u00AB', - }); - map.addControl(overviewMap); - } + // 미니맵 추가 + if (isMiniMap) { + const overviewMap = new OverviewMap({ + layers: [GisApp.Module.core.baseMiniMapLayers["base"]], + view: new View({ + projection: GisApp.TO_PROJ, + minZoom: 9, + }), + collapsed: false, + collapseLabel: "\u00BB", + label: "\u00AB", + }); + map.addControl(overviewMap); + } - // 스케일 컨트롤 및 베이스 레이어 설정 - gisCore.__proto__.map = map; - gisCore.scaleControl(); - gisCore.changeBaseLayer('base'); - setMapObject({ map: map }); - mapInstanceRef.current = map; // 생성 완료 플래그 설정 + // 스케일 컨트롤 및 베이스 레이어 설정 + gisCore.__proto__.map = map; + gisCore.scaleControl(); + gisCore.changeBaseLayer("base"); + setMapObject({ map: map }); + mapInstanceRef.current = map; // 생성 완료 플래그 설정 + }, []); - }, []); + // treeData 변경 시 레이어 코드 생성 + useEffect(() => { + if (!treeData || treeData.length === 0) return; - // treeData 변경 시 레이어 코드 생성 - useEffect(() => { - if (!treeData || treeData.length === 0) return; + // 기존 오버레이 및 레이어 모두 삭제 + gisCore.deleteAllOverlay(); + gisCore.deleteAllLayer(); - // 기존 오버레이 및 레이어 모두 삭제 - gisCore.deleteAllOverlay(); - gisCore.deleteAllLayer(); + // 트리 데이터를 레이어 코드 객체로 변환 + const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, "lyrSn"); + GisApp.layerCode = layerCode; // 전역 레이어 코드 설정 - // 트리 데이터를 레이어 코드 객체로 변환 - const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, 'lyrSn'); - GisApp.layerCode = layerCode; // 전역 레이어 코드 설정 + //setTreeData(data); + // 편집 가능한 레이어 목록 생성 + //const layerItems = getLayerItems(layerCode); + //setEditLayerItems(prev => [...layerItems]) + }, [treeData]); - //setTreeData(data); - // 편집 가능한 레이어 목록 생성 - //const layerItems = getLayerItems(layerCode); - //setEditLayerItems(prev => [...layerItems]) - }, [treeData]); + const getLayerItems = (layerCode) => { + const layerItems = Object.entries(layerCode) + .filter(([key, obj], idx) => obj?.updtPsblYn === "Y") + .map(([key, obj]) => ({ + ...obj, + name: obj.lyrNm, + label: obj.lyrNm, + value: obj.lyrSn + "", + })) + .sort((a, b) => { + if (a.sortOrdr > b.sortOrdr) return 1; + if (a.sortOrdr < b.sortOrdr) return -1; + return 0; + }); + return layerItems; + }; - const getLayerItems = (layerCode) => { - const layerItems = Object.entries(layerCode) - .filter(([key, obj], idx) => obj?.updtPsblYn === 'Y') - .map(([key, obj]) => ({ - ...obj, - name: obj.lyrNm, - label: obj.lyrNm, - value: obj.lyrSn + '', - })) - .sort((a, b) => { - if (a.sortOrdr > b.sortOrdr) return 1; - if (a.sortOrdr < b.sortOrdr) return -1; - return 0; - }); - return layerItems; - }; + if (mapObject.map) { + try { + gisMap = mapObject.map; + } catch (e) { + console.error("오류 발생:", e); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력 + } + } - if (mapObject.map) { - try { - gisMap = mapObject.map; - } catch (e) { - console.error('오류 발생:', e); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력 - } - } + // 체크된 레이어 정보를 처리하는 함수 + const handleCheckedLayersChange = useCallback( + (checkedIds, checkedNodesData) => { + setTimeout(() => { + setCheckedLayers(checkedIds); + setCheckedLayersData(checkedNodesData); + onCheckedLayersChange?.(checkedIds, checkedNodesData); + }, 1000); + }, + [onCheckedLayersChange], + ); - // 체크된 레이어 정보를 처리하는 함수 - const handleCheckedLayersChange = useCallback((checkedIds, checkedNodesData) => { - setTimeout(() => { - setCheckedLayers(checkedIds); - setCheckedLayersData(checkedNodesData); - onCheckedLayersChange?.(checkedIds, checkedNodesData); - }, 1000); - }, [onCheckedLayersChange]); + return ( + <> + <GisContext.Provider + value={{ + gisCore, + gisMap: mapInstanceRef.current, + treeData, + checkedLayerIds, + }} + > + <div className={`relative flex ${height}`}> + <div + ref={mapRef} + className="gis-map relative flex-1" + style={{ height: "100%", padding: 0 }} + > + <GisTree + visible={tabVisible} + setVisible={setTabVisible} + onCheckedChange={handleCheckedLayersChange} + onLayerIconClick={(layerNm) => { + layerNmRef.current = layerNm; + setLayerNm(layerNm); + setIsOpen(true); + const dummyFeature = { + getProperties: () => ({}), // 최소 구조 + }; + onFeatureSelect(dummyFeature); + }} + imageVisible={imgVisble} + /> - return ( - <> - <GisContext.Provider value={{ gisCore, gisMap: mapInstanceRef.current, treeData, checkedLayerIds }}> - <div className="h-[calc(100vh-10rem)] flex relative"> - <div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} > - <GisTree - visible={tabVisible} - setVisible={setTabVisible} - onCheckedChange={handleCheckedLayersChange} - onLayerIconClick={(layerNm) => { - layerNmRef.current = layerNm - setLayerNm(layerNm); setIsOpen(true); const dummyFeature = { - getProperties: () => ({}), // 최소 구조 - }; - onFeatureSelect(dummyFeature); - }} - imageVisible={imgVisble} - /> + {/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} + {isOpen && !isSearchModalOpen && ( + <DetailView + featureData={featureData || { getProperties: () => ({}) }} + seletectedLayerNm={layerNm} + onClose={closePopup} // 닫기 콜백 전달 + onFeatureSelect={handleFeatureSelect} + /> + )} + {/* SearchView가 존재할 때만 렌더링 */} + {SearchView && ( + <SearchView + open={isSearchModalOpen} + onOpenChange={setIsSearchModalOpen} + onFeatureSelect={handleFeatureSelect} + /> + )} + {/* 시도/시군구 조회 영역 */} + {SearchBar && ( + <div + className="absolute left-1/2 top-4 z-30 -translate-x-1/2 rounded" + style={{ maxWidth: "calc(100% - 2rem)" }} + > + <SearchBar /> + </div> + )} + <GisToolBar /> + <GisMapToggle /> + <GisSelect onFeatureSelect={handleFeatureSelect} /> + </div> + </div> - {/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} - {isOpen && !isSearchModalOpen && ( - <DetailView - featureData={featureData || { getProperties: () => ({}) }} - seletectedLayerNm={layerNm} - onClose={closePopup} // 닫기 콜백 전달 - onFeatureSelect={handleFeatureSelect} - /> - )} - {/* SearchView가 존재할 때만 렌더링 */} - {SearchView && ( - <SearchView - open={isSearchModalOpen} - onOpenChange={setIsSearchModalOpen} - onFeatureSelect={handleFeatureSelect} - /> - )} - {/* 시도/시군구 조회 영역 */} - {SearchBar && ( - <div className="absolute top-4 left-1/2 -translate-x-1/2 z-30 bg-gray-300 rounded " style={{ maxWidth: 'calc(100% - 2rem)' }}> - <SearchBar/> - </div> - )} - <GisToolBar /> - <GisMapToggle /> - <GisSelect onFeatureSelect={handleFeatureSelect} /> - </div> - </div> - - {isSearch && ( - <div className="absolute right-8 top-[305px] z-20"> - <button - onClick={() => setIsSearchModalOpen(true)} - className="overflow-hidden w-[40px] h-[40px] bg-white - border border-[#9aa7b8] - rounded - shadow-[0_0_5px_#999] - flex items-center justify-center - p-0 - " - > - <Search size={24} className="text-gray-700" /> - </button> - </div> - )} - </GisContext.Provider> - </> - ); + {isSearch && ( + <div className="absolute right-8 top-[305px] z-20"> + <button + onClick={() => setIsSearchModalOpen(true)} + className="flex h-[40px] w-[40px] items-center justify-center overflow-hidden rounded border border-[#9aa7b8] bg-white p-0 shadow-[0_0_5px_#999]" + > + <Search size={24} className="text-gray-700" /> + </button> + </div> + )} + </GisContext.Provider> + </> + ); } export default React.memo(Gis); diff --git a/packages/common/business_components/src/ui/map/GisTree.js b/packages/common/business_components/src/ui/map/GisTree.js index 335b1b119..47e86d4dc 100644 --- a/packages/common/business_components/src/ui/map/GisTree.js +++ b/packages/common/business_components/src/ui/map/GisTree.js @@ -196,7 +196,7 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes return ( <> <div - className={`absolute top-0 left-0 w-[250px] h-[calc(100vh-10rem)] bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-40 + className={`absolute top-0 left-0 w-[250px] h-full bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-40 ${visible ? 'translate-x-0 opacity-100' : '-translate-x-full opacity-0 pointer-events-none'}`} > <div From def2867c289ec84a77aeeaa7fc9ec47c1c95ab0a Mon Sep 17 00:00:00 2001 From: jyha00 <jyha@mind-one.co.kr> Date: Fri, 1 Aug 2025 14:34:19 +0900 Subject: [PATCH 50/71] =?UTF-8?q?=EC=88=98=EC=9A=94=EC=B2=98=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx | 3 ++- apps/dm/components/ui/sdmLoginForm/component.tsx | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx index 4efc58cb5..f4f156d2a 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx @@ -9,7 +9,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useRouter, useSearchParams } from "next/navigation"; import { Suspense, useEffect, useState } from "react"; import { FormProvider, useForm, type SubmitHandler } from "react-hook-form"; -import { Toaster } from "react-hot-toast"; +import toast, { Toaster } from "react-hot-toast"; import * as z from "zod"; import SdmLogin from "../sdmLogin/page"; @@ -291,6 +291,7 @@ function SdmInfoInsertContent() { open={!isLogin} onOpenChange={(open) => { if (!open) { + toast.dismiss(); router.push("/"); } }} diff --git a/apps/dm/components/ui/sdmLoginForm/component.tsx b/apps/dm/components/ui/sdmLoginForm/component.tsx index 75852a4da..a69c87541 100644 --- a/apps/dm/components/ui/sdmLoginForm/component.tsx +++ b/apps/dm/components/ui/sdmLoginForm/component.tsx @@ -75,16 +75,19 @@ export function SdmLoginForm() { const initPassword = () => { toast( - () => ( + (t) => ( <div className="flex flex-col items-center"> <div className="text-2xl mb-2"> 관리자(031-0000-0000)에게 비밀번호 초기화 요청을 유선으로 하시기 바랍니다. </div> + <Button size={"sm"} onClick={() => toast.dismiss(t.id)}> + 확인 + </Button> </div> ), { id: "pwd-reset", - duration: 5000, + duration: Infinity, position: "top-center", } ); From ca9f2da83cacff3756a6ef43f85affae0cd57d30 Mon Sep 17 00:00:00 2001 From: leedj312 <dlehd467@gmail.com> Date: Fri, 1 Aug 2025 14:38:38 +0900 Subject: [PATCH 51/71] =?UTF-8?q?=EC=88=98=EC=9A=94=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EC=88=98=EC=A7=88=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20validation=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/sdmMng/sdmInfoInsert/page.tsx | 49 +------- apps/dm/app/store/sdmMng/index.tsx | 109 +++++++++++++++++- .../ui/sdmInfoInsertForm/component.tsx | 25 ++-- .../components/ui/sdmLoginForm/component.tsx | 10 +- 4 files changed, 130 insertions(+), 63 deletions(-) diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx index 4efc58cb5..56ec3bc00 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx @@ -1,7 +1,7 @@ "use client"; import { useDmStore } from "@/app/store"; -import type { DemandCompBizType } from "@/app/store/sdmMng"; +import { sdmInfoFormSchema, type DemandCompBizType, type SdmInfoFormData } from "@/app/store/sdmMng"; import { SdmInfoRegForm } from "@/components/ui/sdmInfoInsertForm/component"; import { UiModal } from "@common/business_components/ui/dialog_ui"; import { Button } from "@common/components/ui"; @@ -10,48 +10,9 @@ import { useRouter, useSearchParams } from "next/navigation"; import { Suspense, useEffect, useState } from "react"; import { FormProvider, useForm, type SubmitHandler } from "react-hook-form"; import { Toaster } from "react-hot-toast"; -import * as z from "zod"; import SdmLogin from "../sdmLogin/page"; -const sdmInfoFormSchema = z.object({ - sdmNm: z.string().min(1, "수요처를 선택해주세요"), - demandYmd: z.date().optional(), - demandQnty: z - .string() - .min(1, "요구수량을 입력해주세요") - .regex(/^[0-9]+$/, "요구수량은 숫자만 입력 가능합니다"), - usePurpose: z.string().optional(), - money: z.string().regex(/^[0-9]+$/, "희망단가는 숫자만 입력 가능합니다"), - /**요구수질 */ - reqWaterQlty: z.string().optional(), - grssClfg: z.number().nullish(), - tn: z.number().nullish(), - combResdch: z.number().nullish(), - tph: z.number().nullish(), - trbt: z.number().nullish(), - ph: z.string().nullish(), - bod: z.number().nullish(), - /**컬럼확인필요 */ - clIonQnt: z.number().nullish(), - smll: z.string(), - cdtnv: z.number().nullish(), - crmty: z.number().nullish(), - rmrk: z.string().optional(), - /* telno: z.string() - .min(8, "전화번호는 8자 이상 입력해주세요") - .max(12, "전화번호는 12자 이내로 입력해주세요") - .regex(/^[0-9-]+$/, "전화번호는 숫자와 하이픈(-)만 입력 가능합니다"), */ - candidateTitle: z - .array( - z.object({ - ruseUtztnFcltNm: z.string(), - ruseUtztnFcltCd: z.string(), - }) - ) - .optional(), -}); -export type FormData = z.infer<typeof sdmInfoFormSchema>; function SdmInfoInsertContent() { const searchParams = useSearchParams(); @@ -62,7 +23,7 @@ function SdmInfoInsertContent() { const [isLogin, setIsLogin] = useState(false); const [facilityName, setFacilityName] = useState<string>(""); - const [defaultValues, setDefaultValues] = useState<FormData>({ + const [defaultValues, setDefaultValues] = useState<SdmInfoFormData>({ sdmNm: "", demandYmd: new Date(), demandQnty: "", @@ -109,10 +70,10 @@ function SdmInfoInsertContent() { setSubsInfo([]); // subsInfo 초기화 후보지 처음 화면 들어올때 초기화(스토어내 객체만) }, [setSubsInfo]); - const form = useForm<FormData>({ + const form = useForm<SdmInfoFormData>({ resolver: zodResolver(formSchema), defaultValues, - mode: "onChange", + mode: "onSubmit", }); const subsInfo = useDmStore((state) => state.map.subsInfo); @@ -167,7 +128,7 @@ function SdmInfoInsertContent() { } }, [ismoneyDisabled, form]); const router = useRouter(); - const onSubmit: SubmitHandler<FormData> = async (data) => { + const onSubmit: SubmitHandler<SdmInfoFormData> = async (data) => { try { const dmSubsInfo = data.candidateTitle?.filter( (item) => item.ruseUtztnFcltNm && item.ruseUtztnFcltCd diff --git a/apps/dm/app/store/sdmMng/index.tsx b/apps/dm/app/store/sdmMng/index.tsx index 6a6628339..1b63c399d 100644 --- a/apps/dm/app/store/sdmMng/index.tsx +++ b/apps/dm/app/store/sdmMng/index.tsx @@ -3,10 +3,105 @@ import type { SelectDataType } from "@common/business_components/ui"; import type { TableUpperProps } from "../common"; import toast from "react-hot-toast"; import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; +import { z } from "zod"; /**==================================== * Store - 타입 =====================================*/ + +export const sdmInfoFormSchema = z.object({ + sdmNm: z.string().min(1, "수요처를 선택해주세요"), + demandYmd: z.date().optional(), + demandQnty: z + .string() + .min(1, "요구수량을 입력해주세요") + .regex(/^[0-9]+$/, "요구수량은 숫자만 입력 가능합니다"), + usePurpose: z.string().optional(), + money: z.string().regex(/^[0-9]+$/, "희망단가는 숫자만 입력 가능합니다"), + /**요구수질 */ + reqWaterQlty: z.string().optional(), + grssClfg: z.number().nullish(), + tn: z.number().nullish(), + combResdch: z.number().nullish(), + tph: z.number().nullish(), + trbt: z.number().nullish(), + ph: z.string().nullish(), + bod: z.number().nullish(), + /**컬럼확인필요 */ + clIonQnt: z.number().nullish(), + smll: z.string(), + cdtnv: z.number().nullish(), + crmty: z.number().nullish(), + rmrk: z.string().optional(), + /* telno: z.string() + .min(8, "전화번호는 8자 이상 입력해주세요") + .max(12, "전화번호는 12자 이내로 입력해주세요") + .regex(/^[0-9-]+$/, "전화번호는 숫자와 하이픈(-)만 입력 가능합니다"), */ + candidateTitle: z + .array( + z.object({ + ruseUtztnFcltNm: z.string(), + ruseUtztnFcltCd: z.string(), + }) + ) + .optional(), +}).superRefine((data, ctx) => { + const disabledFields = disableRules[data.usePurpose ?? ""] || []; + // 모든 수질 관련 필드 + // 총대장균군수는 null 값을 불검출로 표시하기때문에 해당 검사에서 제외함. + const allFields: (keyof typeof data)[] = [ + "tn", "combResdch", "tph", + "trbt", "ph", "bod", "clIonQnt", + "smll", "cdtnv", "crmty", + ]; + + const allFieldsName: Record<string, string> = { + tn: "총질소(T-N)(mg/L)", + combResdch: "결합잔류염소(mg/L)", + tph: "총인(T-P)(mg/L)", + trbt: "탁도(NTU)", + ph: "수소이온농도(pH)", + bod: "생물학적 산소요구량(BOD)(mg/L)", + clIonQnt: "염화물(mgCL/L)", + smll: "냄새", + cdtnv: "전기전도도(㎲/cm)", + crmty: "색도(도)", + }; + allFields.forEach((field) => { + const isDisabled = disabledFields.includes(field); + if (!isDisabled) { + // disabled가 아닌 필드는 반드시 값이 있어야 함 + const value = data[field]; + if (typeof value === "string") { + if (value.trim().length === 0) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: [field], + message: `${allFieldsName[field]} 을 입력해주세요.`, + }); + } + } else if (typeof value === "number") { + if (value <= 0 || Number.isNaN(value)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: [field], + message: `${allFieldsName[field]} 을 입력해주세요.`, + }); + } + } else if (value == null) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: [field], + message: `${allFieldsName[field]} 을 입력해주세요.`, + }); + } + } + }); +}); + +export type SdmInfoFormData = z.infer<typeof sdmInfoFormSchema>; + + // 수요처 정보 export type DemandCompBizType = { index : number | ''; @@ -167,8 +262,8 @@ const demandCompList : DemandCompType[] = [ const disableRules: DisableRulesType = { "청소": ["tn", 'tph',"clIonQnt" ,'cdtnv'], "화장실용수": ["tn", 'tph',"clIonQnt" ,'cdtnv'], - "세척": ["combResdch", "crmty",'tph',"clIonQnt",'cdtnv'], - "살수": ["combResdch", "crmty",'tph',"clIonQnt",'cdtnv'], + "세척": ["combResdch", "crmty",'tph','tn',"clIonQnt",'cdtnv'], + "살수": ["combResdch", "crmty",'tph','tn',"clIonQnt",'cdtnv'], "조경": ["combResdch", "crmty",'tph','cdtnv'], "농업_간접용수": ["combResdch", "crmty",'tn','tph','clIonQnt'], "농업_직접용수": ["combResdch", "crmty",'tn','tph','clIonQnt'], @@ -184,6 +279,7 @@ const disableRules: DisableRulesType = { bod: 5, crmty: 20, ph: "5.8~8.5", + smll:'불쾌하지않을것', tn:null, tph: null, clIonQnt: null, @@ -196,6 +292,7 @@ const disableRules: DisableRulesType = { trbt: 2, bod: 5, crmty: 20, + smll:'불쾌하지않을것', ph: "5.8~8.5", tn:null, tph: null, @@ -208,6 +305,7 @@ const disableRules: DisableRulesType = { bod: 5, ph: "5.8~8.5", combResdch:null, + smll:'불쾌하지않을것', crmty:null, tn:null, tph: null, @@ -219,6 +317,7 @@ const disableRules: DisableRulesType = { trbt: 2, bod: 5, ph: "5.8~8.5", + smll:'불쾌하지않을것', combResdch:null, crmty:null, tn:null, @@ -231,6 +330,7 @@ const disableRules: DisableRulesType = { trbt: 2, bod: 5, ph: "5.8~8.5", + smll:'불쾌하지않을것', clIonQnt:250, combResdch:null, crmty:null, @@ -254,6 +354,7 @@ const disableRules: DisableRulesType = { grssClfg: null, trbt: 2, bod: 8, + smll:'불쾌하지않을것', ph: "5.8~8.5", cdtnv:700, combResdch:null, @@ -267,6 +368,7 @@ const disableRules: DisableRulesType = { bod: 5, tn: 10, tph: 0.5, + smll:'불쾌하지않을것', ph: "5.8~8.5", combResdch:null, trbt:null, @@ -274,13 +376,14 @@ const disableRules: DisableRulesType = { clIonQnt: null, cdtnv:null }, - 공업:{ + '공업':{ grssClfg: null, bod: null, tn: null, tph: null, ph: "~", combResdch:null, + smll:'불쾌하지않을것', trbt:null, crmty:null, clIonQnt: null, diff --git a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx index 7d6c41f24..c007645bb 100644 --- a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx +++ b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx @@ -1,5 +1,4 @@ import ModalSimilarPage from "@/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page"; -import type { FormData } from "@/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page"; import { useDmStore } from "@/app/store"; import { CANDIDATE, @@ -25,15 +24,16 @@ import SearchInputText from "../common/searhDemandInput/component"; import React from "react"; import { sanitizeInput } from "../common/utils/utils"; import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; +import type { SdmInfoFormData } from "@/app/store/sdmMng"; export interface SdmInfoRegFormProps { - form: UseFormReturn<FormData>; + form: UseFormReturn<SdmInfoFormData>; onCheckboxChange?: (checked: boolean) => void; facilityName?: string; } type FieldComponent = ( - field: ControllerRenderProps<FormData, keyof FormData> + field: ControllerRenderProps<SdmInfoFormData, keyof SdmInfoFormData> ) => JSX.Element; export function SdmInfoRegForm({ @@ -161,7 +161,7 @@ export function SdmInfoRegForm({ if (defaults) { Object.entries(defaults).forEach(([field, value]) => { - form.setValue(field as keyof FormData, value, { shouldDirty: true }); + form.setValue(field as keyof SdmInfoFormData, value, { shouldDirty: true }); }); } }, [form.getValues("usePurpose")]); @@ -176,7 +176,7 @@ export function SdmInfoRegForm({ ); const formFields: Array<{ - name: keyof FormData; + name: keyof SdmInfoFormData; label: string; component: FieldComponent | undefined; formIndex: number; @@ -346,9 +346,7 @@ export function SdmInfoRegForm({ const normalFields = formFields.filter( (f) => f.formIndex === 0 && f.component ); - // const groupFields = formFields.filter( - // (f) => f.formIndex === 1 && f.component - // ); + const candidateFields = formFields.filter( (f) => f.formIndex === 2 && f.component ); @@ -363,8 +361,13 @@ export function SdmInfoRegForm({ name={name} render={({ field }) => name === "money" ? ( - // component(field) - <FormUi label={label + "*"}>{component(field)}</FormUi> + <div> + <Label className="text-black" > + 희망단가* + </Label> + { component(field)} + </div> + // <FormUi label={label + "*"}>{component(field)}</FormUi> ) : ( <AccessibleFormField field={field} @@ -769,7 +772,7 @@ export function SdmInfoRegForm({ onSelectValue={(val: string | number) => { field.onChange(val.toString()); }} - value={field.value || "불쾌하지않을것"} + value={field.value } readOnly={isDisabled} onBlur={field.onBlur} ref={field.ref} diff --git a/apps/dm/components/ui/sdmLoginForm/component.tsx b/apps/dm/components/ui/sdmLoginForm/component.tsx index 75852a4da..9664c52a2 100644 --- a/apps/dm/components/ui/sdmLoginForm/component.tsx +++ b/apps/dm/components/ui/sdmLoginForm/component.tsx @@ -46,8 +46,8 @@ export function SdmLoginForm() { dmdAplyPswd: z.string().optional(), }), [isBznmTotNoDisabled]); - type FormData = z.infer<typeof sdmLoginFormSchema>; - type FieldComponent = (field: ControllerRenderProps<FormData, keyof FormData>) => JSX.Element; + type SdmLoginForm = z.infer<typeof sdmLoginFormSchema>; + type FieldComponent = (field: ControllerRenderProps<SdmLoginForm, keyof SdmLoginForm>) => JSX.Element; const formSchema = sdmLoginFormSchema; @@ -55,7 +55,7 @@ export function SdmLoginForm() { initializeCommonData(); }, [initializeCommonData]); - const defaultValues: FormData = { + const defaultValues: SdmLoginForm = { dmdInfoId: '', bznmTotNo: '', password: '', @@ -63,7 +63,7 @@ export function SdmLoginForm() { dmdAplyPswd: '', }; - const form = useForm<FormData>({ + const form = useForm<SdmLoginForm>({ resolver: zodResolver(formSchema), defaultValues, mode: "onChange", @@ -102,7 +102,7 @@ export function SdmLoginForm() { }; const formFields: Array<{ - name: keyof FormData; + name: keyof SdmLoginForm; label: string; component: FieldComponent | undefined; }> = [ From 59e3dbbe97905603320e9f4c472537e53c386aae Mon Sep 17 00:00:00 2001 From: jyha00 <jyha@mind-one.co.kr> Date: Fri, 1 Aug 2025 14:59:36 +0900 Subject: [PATCH 52/71] =?UTF-8?q?=EC=A4=91=EC=88=98=EB=8F=84=20=EC=8B=A0?= =?UTF-8?q?=EA=B7=9C=EB=93=B1=EB=A1=9D=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx b/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx index 59ce18d93..055d0372f 100644 --- a/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx @@ -158,7 +158,7 @@ export default function RswtrRegForm({ </h1> <div className="grid grid-cols-12 flex items-center card-header"> <h1 className="text-2xl font-medium text-label col-span-10"> - 건축물 운영정보 + 건축물 정보 </h1> {!readonly && ( <Button From 8653335aa9619712fc11cb0294798bd2e3cd6cb6 Mon Sep 17 00:00:00 2001 From: leedj312 <dlehd467@gmail.com> Date: Fri, 1 Aug 2025 15:26:54 +0900 Subject: [PATCH 53/71] =?UTF-8?q?=EC=88=98=EC=9A=94=EC=8B=A0=EC=B2=AD=20?= =?UTF-8?q?=EC=82=AC=EC=97=85=EC=9E=90=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=B4=88=EA=B8=B0=20=ED=99=94=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wr/app/matMng/coMng/coSttn/page.tsx | 3 ++- apps/wr/components/lib/func/commom.tsx | 4 ++-- apps/wr/public/locales/ko/message.json | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/wr/app/matMng/coMng/coSttn/page.tsx b/apps/wr/app/matMng/coMng/coSttn/page.tsx index 75e1ecff7..5c4f85849 100644 --- a/apps/wr/app/matMng/coMng/coSttn/page.tsx +++ b/apps/wr/app/matMng/coMng/coSttn/page.tsx @@ -162,10 +162,11 @@ export default function CoSttn() { const initPassword = () => { if (upadateData) { const dmdInfoId = upadateData.dmdInfoId; + const dmdInfoSliceId = `${dmdInfoId.slice(0, 3)}-${dmdInfoId.slice(3, 5)}-${dmdInfoId.slice(5, 10)}-${dmdInfoId.slice(10)}` const handleConfirm = () => { coSttnPwInit(dmdInfoId); }; - confirmToast(t('isInitPw'), handleConfirm); + confirmToast( t('isInitPw', { dmdInfoId: dmdInfoSliceId }), handleConfirm, 'text-2xl'); } else { toast.error("비밀번호 초기화를 원하는 행을 선택해주세요.") } diff --git a/apps/wr/components/lib/func/commom.tsx b/apps/wr/components/lib/func/commom.tsx index 4902ccf3d..fef689f46 100644 --- a/apps/wr/components/lib/func/commom.tsx +++ b/apps/wr/components/lib/func/commom.tsx @@ -201,11 +201,11 @@ export const formatPhoneNumber = (tel: string) => { } }; -export const confirmToast = (content: string, saveFunc: () => void) => { +export const confirmToast = (content: string, saveFunc: () => void, contentClass?:string) => { toast( (t) => ( <span className="flex flex-col items-center"> - <div className="mb-6">{content}</div> + <div className={`mb-6 ${contentClass}`} >{content}</div> <div className="flex flex-row space-x-2"> <Button size={"sm"} diff --git a/apps/wr/public/locales/ko/message.json b/apps/wr/public/locales/ko/message.json index 859df00cf..fb7af9ed2 100644 --- a/apps/wr/public/locales/ko/message.json +++ b/apps/wr/public/locales/ko/message.json @@ -17,7 +17,7 @@ "isMatchFinishMsg": " 매칭완료를 진행하시겠습니까?", "isNotMatchFinishMsg": "검토완료중 매칭완료 여부를 체크한 항목이 없습니다.", "canNotChangeMatStateMsg": "매칭완료된 항목은 상태를 변경할 수 없습니다.", - "isInitPw": "비밀번호를 초기화하시겠습니까?", + "isInitPw":"사업자번호 {{dmdInfoId}} 의 비밀번호를 초기화하시겠습니까? (초기화비밀번호는 해당 사업자의 사업자 번호입니다)", "alreadyInsertedErrMsg": "사업자번호 {{num}}(은)는 이미 등록되었습니다. 다시 등록해주세요.", "saveRowErrMsg": "수정을 원하는 행을 선택하고 저장버튼을 눌러주세요.", "costCalcErrMsg": "공사비 계산 진행 후 저장을 눌러주세요", From cb83b7f92b1e4a74934ba8037de8a148bb313fe8 Mon Sep 17 00:00:00 2001 From: jyha00 <jyha@mind-one.co.kr> Date: Fri, 1 Aug 2025 15:50:44 +0900 Subject: [PATCH 54/71] =?UTF-8?q?=ED=95=98=EC=88=98=EC=9E=AC=EC=9D=B4?= =?UTF-8?q?=EC=9A=A9=EC=8B=9C=EC=84=A4=20=ED=98=84=ED=99=A9=20=EC=97=91?= =?UTF-8?q?=EC=85=80=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=97=86=EC=9D=84=20?= =?UTF-8?q?=EC=8B=9C=20toast=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruseFcltMng/fcltSttn/pbnstSttn/page.tsx | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/apps/wr/app/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx b/apps/wr/app/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx index 8aea16c00..21f72f449 100644 --- a/apps/wr/app/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx @@ -21,6 +21,8 @@ import ListDiv from "@common/business_components/ui/list_div"; import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; import { Button, Label } from "@common/components/ui"; import { useCallback, useEffect, useRef, useState } from "react"; +import toast, { Toaster } from "react-hot-toast"; +import { useTranslation } from "react-i18next"; export default function PbnstSttn() { const { @@ -46,10 +48,11 @@ export default function PbnstSttn() { isModal: boolean; data: customPbnst; }>({ isModal: false, data: {} as customPbnst }); - + const isDownloadTriggered = useRef(false); const [pbnRuseNm, setPbnRuseNm] = useState<string>(""); - + const { t } = useTranslation('base'); const excelClick = useCallback(() => { + isDownloadTriggered.current = true; pbnstSttnSearch( true, String(1), @@ -77,9 +80,9 @@ export default function PbnstSttn() { }, [pbnstSttn.isInit]); useEffect(() => { - pbnSttnInit("1", "15", ref.current, searchYear, fclts); + pbnSttnInit("1", "15", ref.current, searchYear, fclts); }, [fclts]); - + useEffect(() => { if (pbnstSttnRes && pbnstSttnRes.length > 0) { setBdstTable(pbnstSttnRes); @@ -90,7 +93,10 @@ export default function PbnstSttn() { useEffect(() => { - if (pbnstSttn.excelList.length >= 0) { + if (!isDownloadTriggered.current) { + return; + } + if (pbnstSttn.excelList.length > 0) { excelDownload( pbnstSttn.excelList, "하수재이용시설 현황", @@ -98,7 +104,10 @@ export default function PbnstSttn() { "A1:U2", 3 ); + } else { + toast.error(t('excelDownErrMsg')); } + isDownloadTriggered.current = false; }, [pbnstSttn.excelList]); @@ -162,8 +171,8 @@ export default function PbnstSttn() { /> </div> } - queryClick={ ()=> { - // pbnSearch(returnType); + queryClick={() => { + // pbnSearch(returnType); //setTableIndex(0) setIsReset(true); @@ -202,7 +211,7 @@ export default function PbnstSttn() { ); setIsReset(false); }} - + resetPageNo={isReset} > <HeadMakeColSpan upHeadList={pbnstSttn.pbnstSttNodeList} /> @@ -242,6 +251,7 @@ export default function PbnstSttn() { }} /> </ContentsDiv> + <Toaster position="top-center" /> </> ); } From 9bdf01e7f3272aec4aed2c9031bc59851730b111 Mon Sep 17 00:00:00 2001 From: jyha00 <jyha@mind-one.co.kr> Date: Fri, 1 Aug 2025 16:16:50 +0900 Subject: [PATCH 55/71] =?UTF-8?q?=EB=AA=A8=EB=8B=AC=EC=B0=BD=20=EC=82=AC?= =?UTF-8?q?=EC=9D=B4=EC=A6=88=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wr/app/matMng/coMng/coSttn/page.tsx | 4 +- .../sttn/sttnDetailModal/coDatailModal.tsx | 37 +++++++++---------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/apps/wr/app/matMng/coMng/coSttn/page.tsx b/apps/wr/app/matMng/coMng/coSttn/page.tsx index 75e1ecff7..4a6c74951 100644 --- a/apps/wr/app/matMng/coMng/coSttn/page.tsx +++ b/apps/wr/app/matMng/coMng/coSttn/page.tsx @@ -129,7 +129,7 @@ export default function CoSttn() { coExcel as [], "수요처 관리", "sample_coSttn.xlsx", - "A1:H1", + "A1:G1", 2 ); } @@ -288,7 +288,7 @@ export default function CoSttn() { </div> <UiModal bodyClass={ - "w-[40vw] max-w-none h-[80vh] " // +upadateData?.dmdInfoId?'h-[60vh]':'h-[90vh]' + "w-[40vw] max-w-none h-[83vh] " // +upadateData?.dmdInfoId?'h-[60vh]':'h-[90vh]' } open={isDmdModal.isMoal} onOpenChange={() => { diff --git a/apps/wr/components/ui/modal/sttn/sttnDetailModal/coDatailModal.tsx b/apps/wr/components/ui/modal/sttn/sttnDetailModal/coDatailModal.tsx index cb38717a3..aef145cc1 100644 --- a/apps/wr/components/ui/modal/sttn/sttnDetailModal/coDatailModal.tsx +++ b/apps/wr/components/ui/modal/sttn/sttnDetailModal/coDatailModal.tsx @@ -48,22 +48,21 @@ export default function CoDetailModal({ return ( <div> - <ScrollArea className="h-[85vh] mx-8"> - <Form {...form}> - <form onSubmit={form.handleSubmit(onFormSubmit)}> - <div className="grid grid-cols-12 items-center card-header"> - <h1 className="text-2xl font-medium text-label col-span-10"> - 잠재수요처 상세정보 - </h1> - <Button type="submit" - className="col-start-12 col-span-1" - size={"sm"} - disabled={readonly} - > - 저장 - </Button> - </div> - + <Form {...form}> + <form onSubmit={form.handleSubmit(onFormSubmit)}> + <div className="grid grid-cols-12 items-center card-header"> + <h1 className="text-2xl font-medium text-label col-span-10"> + 잠재수요처 상세정보 + </h1> + <Button type="submit" + className="col-start-12 col-span-1" + size={"sm"} + disabled={readonly} + > + 저장 + </Button> + </div> + <ScrollArea className="h-[79vh] mx-8"> <FormField control={form.control} name="fctryMngSn" @@ -865,9 +864,9 @@ export default function CoDetailModal({ /> </div> </div> - </form> - </Form> - </ScrollArea> + </ScrollArea> + </form> + </Form> </div> ); } From 406826a11b9522bb7f66b87cb2f6317dec5fcb42 Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Fri, 1 Aug 2025 16:44:08 +0900 Subject: [PATCH 56/71] =?UTF-8?q?feat:=20Store=20SN=20Root=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=EC=97=90=EC=84=9C=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=EC=A4=91=20(=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=9E=91=EC=97=85=20=ED=95=84=EC=9A=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/store/index.ts | 42 +++++---- apps/sn/app/store/useStore.ts | 8 +- apps/sn/src/shared/lib/index.ts | 1 + apps/sn/src/shared/lib/use-data-store.ts | 14 +++ .../src/shared/providers/store-provider.tsx | 3 + apps/sn/src/shared/store/data-store.ts | 90 +++++++++++++++++++ apps/sn/src/shared/store/index.ts | 1 + 7 files changed, 132 insertions(+), 27 deletions(-) create mode 100644 apps/sn/src/shared/lib/use-data-store.ts create mode 100644 apps/sn/src/shared/store/data-store.ts diff --git a/apps/sn/app/store/index.ts b/apps/sn/app/store/index.ts index 875f0489d..ea5171b44 100644 --- a/apps/sn/app/store/index.ts +++ b/apps/sn/app/store/index.ts @@ -1,4 +1,4 @@ -import { getGeoData, getCmsData } from "@/components/lib/api"; +import { getCmsData } from "@/components/lib/api"; export interface Option { id: number, name: string } @@ -11,35 +11,33 @@ export type BIZ_CD_TYPE = { prntLwpoCd: null }; -export const fetchCtpvData = async (set: (state: any) => void) => { +export const fetchCtpvSggData = async (set: (state: any) => void) => { try { - const ctpvData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_ctpv&outputFormat=application%2Fjson'); - const ctpvList: Option[] = ctpvData.features + const ctpvData = await getCmsData('/api/v1/code/cache/ADSTDSTRCD'); + console.log("CTPV Data:", ctpvData); + const ctpvList: Option[] = ctpvData + .filter((item: any) => typeof item.lwpoCd === 'string' && item.lwpoCd.length === 2) .map((feature: any) => ({ - id: feature.properties.mctpv_cd.trim(), - name: feature.properties.mctpv_korn_nm + id: feature.lwpoCd, + name: feature.lwpoCdNm })) .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); - set({ ctpv: ctpvList }); - } catch (e: any) { - // alert("시도 데이터 로드 실패: " + e.message); - console.error("CTPV 데이터 로드 실패:", e); - } -}; -export const fetchSggData = async (set: (state: any) => void) => { - try { - const sggData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_sgg&outputFormat=application%2Fjson'); - const sggList: Option[] = sggData.features - .map((feature: any) => ({ - id: feature.properties.sgg_cd, - name: feature.properties.sgg_korn_nm + const sggList: Option[] = ctpvData + .filter((item: any) => typeof item.lwpoCd === 'string' && item.lwpoCd.length !== 2) + .map((item: any) => ({ + id: item.lwpoCd, + name: item.lwpoCdNm })) .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); - set({ sgg: sggList }); + + console.log("CTPV List:", ctpvList); + console.log("SGG List:", sggList); + + set({ ctpv: ctpvList, sgg: sggList }); } catch (e: any) { - // alert("시군구 데이터 로드 실패: " + e.message); - console.error("SGG 데이터 로드 실패:", e); + // alert("시도, 시군구 데이터 로드 실패: " + e.message); + console.error("CTPV 데이터 로드 실패:", e); } }; diff --git a/apps/sn/app/store/useStore.ts b/apps/sn/app/store/useStore.ts index 7775ca61e..b2ae014fa 100644 --- a/apps/sn/app/store/useStore.ts +++ b/apps/sn/app/store/useStore.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; import { getGeoData, getCmsData } from "@/components/lib/api"; -import { fetchCtpvData, fetchSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } from "./index"; +import { fetchCtpvSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } from "./index"; interface Option { id: number, name: string } @@ -17,8 +17,7 @@ interface SnStoreState { sgg: Option[]; BIZ_SE_CD: BIZ_CD_TYPE[]; BIZ_TY_CD: BIZ_CD_TYPE[]; - fetchCtpvData: () => Promise<void>; - fetchSggData: () => Promise<void>; + fetchCtpvSggData: () => Promise<void>; fetchBIZ_SE_CD: () => Promise<void>; fetchBIZ_TY_CD: () => Promise<void>; } @@ -28,8 +27,7 @@ const useStore = create<SnStoreState>((set) => ({ sgg: [], BIZ_SE_CD: [], BIZ_TY_CD: [], - fetchCtpvData: () => fetchCtpvData(set), - fetchSggData: () => fetchSggData(set), + fetchCtpvSggData: () => fetchCtpvSggData(set), fetchBIZ_SE_CD: () => fetchBIZ_SE_CD(set), fetchBIZ_TY_CD: () => fetchBIZ_TY_CD(set), })); diff --git a/apps/sn/src/shared/lib/index.ts b/apps/sn/src/shared/lib/index.ts index b8b4c52c0..061346946 100644 --- a/apps/sn/src/shared/lib/index.ts +++ b/apps/sn/src/shared/lib/index.ts @@ -2,5 +2,6 @@ export { useMutationWrapper } from "./use-mutation-wrapper"; export { useAlertStore } from "./use-alert-store"; export { useMenuStore } from "./use-menu-store"; export { useUserStore } from "./use-user-store"; +export { useDataStore } from "./use-data-store"; export { usePrompt } from "./use-prompt"; export { getCookie, setCookie, removeCookie } from "./cookie"; diff --git a/apps/sn/src/shared/lib/use-data-store.ts b/apps/sn/src/shared/lib/use-data-store.ts new file mode 100644 index 000000000..fc88ad3bb --- /dev/null +++ b/apps/sn/src/shared/lib/use-data-store.ts @@ -0,0 +1,14 @@ +import { useStore } from "zustand"; +import { useContext } from "react"; +import type { DataStore } from "../store/data-store"; +import { StoreContext } from "../providers/store-provider"; + +export const useDataStore = <T>(selector: (store: DataStore) => T): T => { + const storeContext = useContext(StoreContext); + + if (!storeContext) { + throw new Error("useDataStore must be used within StoreProvider"); + } + + return useStore(storeContext.dataStore, selector); +}; diff --git a/apps/sn/src/shared/providers/store-provider.tsx b/apps/sn/src/shared/providers/store-provider.tsx index a0ec17b61..db01d81b4 100644 --- a/apps/sn/src/shared/providers/store-provider.tsx +++ b/apps/sn/src/shared/providers/store-provider.tsx @@ -6,6 +6,7 @@ import { createAttachmentStore, createMenuStore, createUserStore, + createDataStore, } from "../store"; type StoreContextType = { @@ -13,6 +14,7 @@ type StoreContextType = { attachmentStore: ReturnType<typeof createAttachmentStore>; menuStore: ReturnType<typeof createMenuStore>; userStore: ReturnType<typeof createUserStore>; + dataStore: ReturnType<typeof createDataStore>; }; export const StoreContext = createContext<StoreContextType | undefined>( @@ -27,6 +29,7 @@ export const StoreProvider = ({ children }: { children: React.ReactNode }) => { attachmentStore: createAttachmentStore(), menuStore: createMenuStore(), userStore: createUserStore(), + dataStore: createDataStore(), }; } diff --git a/apps/sn/src/shared/store/data-store.ts b/apps/sn/src/shared/store/data-store.ts new file mode 100644 index 000000000..fec870234 --- /dev/null +++ b/apps/sn/src/shared/store/data-store.ts @@ -0,0 +1,90 @@ +import { create } from "zustand"; +import { getCmsData } from "@/components/lib/api"; + +export interface Option { id: number, name: string } + +export type BIZ_CD_TYPE = { + lwpoCd: string, + upCd: string, + lwpoCdNm: string, + unit: string, + prntLwpoCd: null +}; + +export type DataState = { + ctpv: Option[]; + sgg: Option[]; + BIZ_SE_CD: BIZ_CD_TYPE[]; + BIZ_TY_CD: BIZ_CD_TYPE[]; +} + +export type FetchData = { + fetchCtpvSggData: () => Promise<void>; + fetchBIZ_SE_CD: () => Promise<void>; + fetchBIZ_TY_CD: () => Promise<void>; +}; + +export type DataStore = DataState & FetchData; + +export const defaultInitState: DataState = { + ctpv: [], + sgg: [], + BIZ_SE_CD: [], + BIZ_TY_CD: [], +}; + +const fetchCtpvSggData = async (set: (state: any) => void) => { + try { + const ctpvData = await getCmsData('/api/v1/code/cache/ADSTDSTRCD'); + console.log("CTPV Data:", ctpvData); + const ctpvList: Option[] = ctpvData + .filter((item: any) => typeof item.lwpoCd === 'string' && item.lwpoCd.length === 2) + .map((feature: any) => ({ + id: feature.lwpoCd, + name: feature.lwpoCdNm + })) + .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); + + const sggList: Option[] = ctpvData + .filter((item: any) => typeof item.lwpoCd === 'string' && item.lwpoCd.length !== 2) + .map((item: any) => ({ + id: item.lwpoCd, + name: item.lwpoCdNm + })) + .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); + + set({ ctpv: ctpvList, sgg: sggList }); + } catch (e: any) { + // alert("시도, 시군구 데이터 로드 실패: " + e.message); + console.error("CTPV 데이터 로드 실패:", e); + } +}; + +const fetchBIZ_SE_CD = async (set: (state: any) => void) => { + try { + const res = await getCmsData(`/api/v1/code/cache/BIZSECD`); + set({ BIZ_SE_CD: res }); + } catch (e: any) { + // alert("BIZ_SE_CD 데이터 로드 실패: " + e.message); + console.error("BIZ_SE_CD 데이터 로드 실패:", e); + } +}; + +const fetchBIZ_TY_CD = async (set: (state: any) => void) => { + try { + const res = await getCmsData(`/api/v1/code/cache/BIZTYCD`); + set({ BIZ_TY_CD: res }); + } catch (e: any) { + // alert("BIZ_TY_CD 데이터 로드 실패: " + e.message); + console.error("BIZ_TY_CD 데이터 로드 실패:", e); + } +}; + +export const createDataStore = (initState: DataState = defaultInitState) => { + return create<DataStore>((set) => ({ + ...initState, + fetchCtpvSggData: () => fetchCtpvSggData(set), + fetchBIZ_SE_CD: () => fetchBIZ_SE_CD(set), + fetchBIZ_TY_CD: () => fetchBIZ_TY_CD(set), + })); +}; \ No newline at end of file diff --git a/apps/sn/src/shared/store/index.ts b/apps/sn/src/shared/store/index.ts index a41d6f928..8928b7269 100644 --- a/apps/sn/src/shared/store/index.ts +++ b/apps/sn/src/shared/store/index.ts @@ -2,3 +2,4 @@ export { createAlertStore } from "./alert-store"; export { createAttachmentStore } from "./attachment-store"; export { createMenuStore, type BbsType } from "./menu-store"; export { createUserStore, type IUserInfo } from "./user-store"; +export { createDataStore, type DataStore } from "./data-store"; \ No newline at end of file From f9fff06f62a59c2c58070345ca57cd49c06a554d Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Fri, 1 Aug 2025 16:55:21 +0900 Subject: [PATCH 57/71] =?UTF-8?q?feat:=20-=20SidoSigunguSelectoBox.tsx=20?= =?UTF-8?q?=EB=AA=85=EC=B9=AD=20ctpvSggPrcsFcltSelectBox.tsx=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=ED=95=98=EC=88=98=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EC=8B=9C=EC=84=A4=20SelectBox=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=ED=8F=AC=ED=95=A8=20-=20ctpvSggPrcsFcltSe?= =?UTF-8?q?lectBox.tsx=20=EB=B3=80=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20-=20=EC=A4=91=EC=A0=90=EC=9A=B0=EC=88=98=ED=86=A0=EC=8B=A4?= =?UTF-8?q?=20=EC=9A=B4=EC=98=81=EC=A0=95=EB=B3=B4=20=EB=AA=A8=EB=8B=AC=20?= =?UTF-8?q?=EC=83=81=EB=8B=A8=20=EB=9D=BC=EB=B2=A8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/(map-layout)/layout.tsx | 7 +- .../map/components/popupComponent/Ssc.tsx | 88 +++++----- .../(map-layout)/map/searchModal/basicTab.tsx | 12 -- .../map/searchModal/operationTab.tsx | 10 +- .../map/searchModal/swagePrcsFclt.tsx | 4 +- .../map/searchModal/swagePrcsZone.tsx | 6 +- .../app/(with-base-layout)/ii/BaseLayout.tsx | 90 ++-------- .../(with-base-layout)/ii/irsetting/page.tsx | 6 +- .../app/(with-base-layout)/ii/rdii/page.tsx | 6 +- .../(with-base-layout)/ii/rdiiResult/page.tsx | 6 +- .../app/(with-base-layout)/ii/result/page.tsx | 6 +- apps/sn/app/(with-base-layout)/layout.tsx | 5 +- .../components/ui/SidoSigunguSelectBox.tsx | 81 --------- .../ui/ctpvSggPrcsFcltSelectBox.tsx | 161 ++++++++++++++++++ 14 files changed, 242 insertions(+), 246 deletions(-) delete mode 100644 apps/sn/app/components/ui/SidoSigunguSelectBox.tsx create mode 100644 apps/sn/app/components/ui/ctpvSggPrcsFcltSelectBox.tsx diff --git a/apps/sn/app/(map-layout)/layout.tsx b/apps/sn/app/(map-layout)/layout.tsx index 4337ee048..e7ab407c3 100644 --- a/apps/sn/app/(map-layout)/layout.tsx +++ b/apps/sn/app/(map-layout)/layout.tsx @@ -19,13 +19,10 @@ export default function MapLayout({ const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo); - const { fetchCtpvData, fetchSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } = useStore(); + const { fetchCtpvSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } = useStore(); - - // 시도, 시군구 호출 useEffect(() => { - fetchCtpvData(); - fetchSggData(); + fetchCtpvSggData(); fetchBIZ_SE_CD(); fetchBIZ_TY_CD(); }, []); 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 801164eb8..0ab04eea5 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx @@ -6,6 +6,7 @@ import { untrtSwgRainCol3, untrtSwgRainCol4, untrtSwgRainCol5 } from '../common/ import { VerticalTable } from '@/components/ui/verticalTable'; import { getData } from "@/components/lib/api"; import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; +import { Label } from '@radix-ui/react-label'; const inputColumn = [ { id: "prrtymngYn", title: "중점여부", type: "select", options: ["우수토실", "중점우수토실"] }, @@ -180,49 +181,52 @@ const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: { const renderModalBody = () => ( <div className='grid grid-cols-1 gap-4 p-4'> - {/* 강우정보 테이블 */} - <UiTable - headName='강우정보' - tableData={rainMeasureData} - headlist={untrtSwgRainCol3} - pageSize={3} - total={0} - cellClick={handleCellClick} - onPageChange={setCurPage} - resetPageNo={isResetPage} - resetSelection={isResetSelection} - index={selectedRowIndex} - /> + <div> + <Label className='text-3xl font-medium text-label '>강우/측정 정보</Label> + {/* 강우정보 테이블 */} + <UiTable + headName='강우정보' + tableData={rainMeasureData} + headlist={untrtSwgRainCol3} + pageSize={3} + total={0} + cellClick={handleCellClick} + onPageChange={setCurPage} + resetPageNo={isResetPage} + resetSelection={isResetSelection} + index={selectedRowIndex} + /> - {/* 측정정보 테이블 */} - <UiTable - headName='측정정보' - tableData={rainMeasureData} - headlist={untrtSwgRainCol4} - pageSize={3} - total={totalCount} - cellClick={handleCellClick} - onPageChange={setCurPage} - resetPageNo={isResetPage} - resetSelection={isResetSelection} - index={selectedRowIndex} - > - {/* 커스텀 헤더 */} - <TableRow> - <TableHead colSpan={5} className="text-center bg-blue-100 font-bold">수량</TableHead> - <TableHead colSpan={3} className="text-center bg-blue-100 font-bold">수질</TableHead> - </TableRow> - <TableRow> - <TableHead className="text-center">측정시작일시</TableHead> - <TableHead className="text-center">측정종료일시</TableHead> - <TableHead className="text-center">측정간격(분)</TableHead> - <TableHead className="text-center">측정기기설치위치</TableHead> - <TableHead className="text-center">유량계 종류 및 모델명</TableHead> - <TableHead className="text-center">채수시작일시</TableHead> - <TableHead className="text-center">채수종료일시</TableHead> - <TableHead className="text-center">채수횟수</TableHead> - </TableRow> - </UiTable> + {/* 측정정보 테이블 */} + <UiTable + headName='측정정보' + tableData={rainMeasureData} + headlist={untrtSwgRainCol4} + pageSize={3} + total={totalCount} + cellClick={handleCellClick} + onPageChange={setCurPage} + resetPageNo={isResetPage} + resetSelection={isResetSelection} + index={selectedRowIndex} + > + {/* 커스텀 헤더 */} + <TableRow> + <TableHead colSpan={5} className="text-center bg-blue-100 font-bold">수량</TableHead> + <TableHead colSpan={3} className="text-center bg-blue-100 font-bold">수질</TableHead> + </TableRow> + <TableRow> + <TableHead className="text-center">측정시작일시</TableHead> + <TableHead className="text-center">측정종료일시</TableHead> + <TableHead className="text-center">측정간격(분)</TableHead> + <TableHead className="text-center">측정기기설치위치</TableHead> + <TableHead className="text-center">유량계 종류 및 모델명</TableHead> + <TableHead className="text-center">채수시작일시</TableHead> + <TableHead className="text-center">채수종료일시</TableHead> + <TableHead className="text-center">채수횟수</TableHead> + </TableRow> + </UiTable> + </div> {/* 수량/수질측정 정보 테이블 */} <UiTable diff --git a/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx index 57afc22c7..018f67883 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx @@ -1,6 +1,4 @@ import React from 'react'; -import { MS_SWAGE_MHL_USE_CODES, MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES } from '../components/common/table'; -import { SidoSigunguSelectBox } from '@/components/ui/SidoSigunguSelectBox'; import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox'; import { SelectBox } from '@common/business_components/ui'; import { Button, Input, Label } from '@common/components'; @@ -21,7 +19,6 @@ export const BASIC_TYPES = [ ]; interface BasicTabProps { - open?: boolean; basicType: string; city: number; sgg: number; @@ -33,10 +30,6 @@ interface BasicTabProps { var2: string; yearFilter: { start: string; end: string; }; handleSearch: () => void; - // onOpenChange?: (open: boolean) => void; - // onFeatureSelect?: (row: any) => void; - // setTableData?: (data: any[]) => void; - // setTableCount?: (count: number) => void; setBasicType: (type: string) => void; setCity: (city: number) => void; setCtpvName: (name: string) => void; @@ -52,7 +45,6 @@ interface BasicTabProps { } const BasicTab: React.FC<BasicTabProps> = ({ - // open, basicType, city, sgg, @@ -64,10 +56,6 @@ const BasicTab: React.FC<BasicTabProps> = ({ var2, yearFilter, handleSearch, - // onOpenChange, - // onFeatureSelect, - // setTableData, - // setTableCount, setBasicType, setCity, setCtpvName, diff --git a/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx index dfd71a39d..c52a4ddab 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx @@ -55,15 +55,11 @@ export const operationInspectionTableHeadlist = [ ]; interface OperationTabProps { - open: boolean; workType: string; var1: string; var2: string; yearFilter: { start: string; end: string; }; handleSearch: () => void; - // onOpenChange: (open: boolean) => void; - // setTableData: (data: any[]) => void; - // setTableCount: (count: number) => void; setWorkType: (type: string) => void; setYearFilter: (filter: { start: string; end: string }) => void; setVar1: (value: string) => void; @@ -71,15 +67,11 @@ interface OperationTabProps { } const OperationTab: React.FC<OperationTabProps> = ({ - open, workType, var1, var2, yearFilter, handleSearch, - // onOpenChange, - // setTableData, - // setTableCount, setWorkType, setVar1, setVar2, @@ -98,7 +90,7 @@ const OperationTab: React.FC<OperationTabProps> = ({ <div className="flex items-center mb-3"> <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">업무구분</Label> <SelectBox - className="w-80 mr-8" + className="w-40 mr-8" selectClass="rounded-lg bg-white text-xl" label="" selectArray={WORK_TYPES.map((w) => ({ diff --git a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx index 53fd312dc..8779f0fa5 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox'; import { Button, Input, Label } from '@common/components'; -import { SidoSigunguSelectBox } from '@/components/ui/SidoSigunguSelectBox'; +import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox'; interface SwagePrcsFcltProps { @@ -47,7 +47,7 @@ const SwagePrcsFclt: React.FC<SwagePrcsFcltProps> = ({ return ( <div className="mb-3"> <div className="mb-2 flex items-center"> - <SidoSigunguSelectBox + <CtpvSggPrcsFcltSelectBox sido={city} sigungu={sgg} onSidoChange={(sidoId) => { diff --git a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsZone.tsx b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsZone.tsx index abeec54b9..8cb304d2a 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsZone.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsZone.tsx @@ -1,6 +1,6 @@ // 하수처리구역 -import { SidoSigunguSelectBox } from '@/components/ui/SidoSigunguSelectBox'; +import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox'; import { Button, Input, Label } from '@common/components'; import React from 'react'; @@ -31,10 +31,10 @@ const swagePrcsZone: React.FC<swagePrcsZoneProps> = ({ return ( <div className="mb-3"> <div className="mb-2 flex items-center"> - <SidoSigunguSelectBox + <CtpvSggPrcsFcltSelectBox sido={city} sigungu={sgg} - onSidoChange={(sidoId) => { + onSidoChange={(sidoId: number) => { setCity(sidoId); setSgg(0); }} diff --git a/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx b/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx index 9eab30712..c83d56232 100644 --- a/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx +++ b/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx @@ -1,12 +1,9 @@ 'use client' -import { getData } from '@/components/lib/api' -import { GovSidoSigunguSearchForm } from '@/components/ui/govSidoSigunguSearchForm' import { DateMonthPicker, Form, SelectBox } from '@common/business_components/ui' import ListDiv from '@common/business_components/ui/list_div' import { Button, Label } from '@common/components/ui' -import { useEffect, useState, type ReactNode } from 'react' -import LoadingOverlay from '@/components/ui/LoadingBar' -import { SidoSigunguSelectBox } from '@/components/ui/SidoSigunguSelectBox' +import { useState, type ReactNode } from 'react' +import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox' type TitleName = { mainTitle: string @@ -44,10 +41,8 @@ type BaseLayout = { sggCd: number; sggName?: string; sigungu?: ApiSelectOption - selectedSwageTreatmentPlant?: string | number; - onSwageTreatmentPlantChange?: (value: string | number) => void; - selectedSwageProcessingZone?: string | number; - onSwageProcessingZoneChange?: (value: string | number) => void; + selectedSwagePrcsFclt?: string | number; + onSwagePrcsFcltChange?: (value: string | number) => void; aplcnYmFrom?: string setAplcnYmFrom?: (aplcnYmFrom: string) => void; aplcnYmTo?: string @@ -55,7 +50,7 @@ type BaseLayout = { setCtpvCd: (ctpv: number) => void; setSggCd: (sgg: number) => void; setSggName?: (sggName: string) => void; - isLoading?: boolean; + setIsLoading?: (isLoading: boolean) => void; onExcelDownload?: () => void; onDeleteFile?: () => void; onAnalysis?: () => void; @@ -75,8 +70,8 @@ const BaseLayout = ({ sggCd, sggName, sigungu, - selectedSwageTreatmentPlant, - onSwageTreatmentPlantChange, + selectedSwagePrcsFclt, + onSwagePrcsFcltChange, aplcnYmFrom, setAplcnYmFrom, aplcnYmTo, @@ -84,7 +79,7 @@ const BaseLayout = ({ setCtpvCd, setSggCd, setSggName, - isLoading, + setIsLoading, onExcelDownload, onDeleteFile, onAnalysis, @@ -93,57 +88,6 @@ const BaseLayout = ({ const defaultOption: ApiSelectOption = { val: '0', text: '전체' }; const [treatmentFacilityOptions, setTreatmentFacilityOptions] = useState<ApiSelectOption[]>([]); - const [isLoadingAreaData, setIsLoadingAreaData] = useState<boolean>(isLoading || false); - - useEffect(() => { - onSwageTreatmentPlantChange?.(0); - - const fetchData = async () => { - if (!sggName) { - console.log("BaseLayout: Skipping fetchData because sigungu.val is:", sigungu?.val); - setTreatmentFacilityOptions([defaultOption]); - setIsLoadingAreaData(false); - return; - } - - setIsLoadingAreaData(true); - try { - - const queryParamsFacility = new URLSearchParams({ - lyrTypeNm: 'tb_il_swage_prcs_fclt', - page: '0', - size: '100000', // 요청하신 URL에 맞게 50으로 변경 가능 - sort: 'string', - }); - const cqlFiltersFacility: string[] = []; - if (sggName) cqlFiltersFacility.push(`sgg_nm ILIKE '%${sggName}%'`); - if (cqlFiltersFacility.length > 0) queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and ')); - - console.log("BaseLayout API URL for Facilities:", `v1/fclts?${queryParamsFacility.toString()}`); - - const facilityRes = await getData(`v1/fclts?${queryParamsFacility.toString()}`); - - console.log('facilityRes.data', facilityRes.data); - - const facilities = (facilityRes.data || []) - .map((item: any) => ({ - text: item.swtrmtFcltNm, - val: item.fid - })) - .sort((a: ApiSelectOption, b: ApiSelectOption) => a.text.localeCompare(b.text, 'ko')); - setTreatmentFacilityOptions([defaultOption, ...facilities]); - console.log("BaseLayout Fetched Treatment Facilities:", [defaultOption, ...facilities]); - - } catch (error) { - console.error("BaseLayout: 지역/시설 데이터 조회 중 오류 발생:", error); - setTreatmentFacilityOptions([defaultOption]); - } finally { - setIsLoadingAreaData(false); - } - }; - - fetchData(); - }, [sggName]); // sgg 상태가 변경될 때마다 실행 // 파일 다운로드 const isChckFile = () => { @@ -165,7 +109,6 @@ const BaseLayout = ({ return ( <Form {...form} className="min-h-screen flex flex-col"> - <LoadingOverlay isLoading={isLoading ?? false} /> <ListDiv> {/* searchBox.title이 있으면 h3 태그로 제목을 표시합니다. */} {selectBox?.title && ( @@ -211,25 +154,18 @@ const BaseLayout = ({ /> </> )} - <SidoSigunguSelectBox + <CtpvSggPrcsFcltSelectBox sido={ctpvCd} sigungu={sggCd} - onSidoChange={(sidoId) => { + onSidoChange={(sidoId: number) => { setCtpvCd(sidoId); setSggCd(0); }} onSigunguChange={setSggCd} setSggName={setSggName} - /> - <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-4">하수처리장</Label> - <SelectBox - label={''} - value={selectedSwageTreatmentPlant} - selectArray={treatmentFacilityOptions} - className="w-[160px]" - selectClass="rounded-lg bg-white truncate" - onSelectValue={(val) => onSwageTreatmentPlantChange?.(val)} - selectContentClass="z-[101]" + selectedSwagePrcsFclt={selectedSwagePrcsFclt} + onSwagePrcsFcltChange={onSwagePrcsFcltChange} + setIsLoading={setIsLoading} /> {selectBox?.searchChk && ( <div className='flex flex-1 justify-end mr-6'> diff --git a/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx b/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx index e2a51676e..7bb508e50 100644 --- a/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx @@ -340,12 +340,12 @@ const IrSetting = () => { ctpvCd={ctpvCd} sggCd={sggCd} sggName={sggName} - selectedSwageTreatmentPlant={select1} - onSwageTreatmentPlantChange={setSelect1} + selectedSwagePrcsFclt={select1} + onSwagePrcsFcltChange={setSelect1} setCtpvCd={setCtpvCd} setSggCd={setSggCd} setSggName={setSggName} - isLoading={isLoadingResult} + setIsLoading={setIsLoadingResult} onAnalysis={handleAnalysisClick} > {[<div key="irsetting-layout-key" className="grid gap-4"> diff --git a/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx b/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx index 491d2ae9d..a8b0a2b84 100644 --- a/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx @@ -396,12 +396,12 @@ export default function RTKForm() { ctpvCd={ctpvCd} sggCd={sggCd} sggName={sggName} - selectedSwageTreatmentPlant={select1} - onSwageTreatmentPlantChange={setSelect1} + selectedSwagePrcsFclt={select1} + onSwagePrcsFcltChange={setSelect1} setCtpvCd={setCtpvCd} setSggCd={setSggCd} setSggName={setSggName} - isLoading={isLoadingResult} + setIsLoading={setIsLoadingResult} onAnalysis={handleAnalysisClick} > {[<div className="space-y-[1.2rem] overflow-x-auto pt-4 pb-4 px-2" key="form-section"> diff --git a/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx b/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx index d705728c5..66b8095db 100644 --- a/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx @@ -284,8 +284,8 @@ const Result = () => { ctpvCd={ctpvCd} sggCd={sggCd} sggName={sggName} - selectedSwageTreatmentPlant={form.watch("swageTmplntCd")} - onSwageTreatmentPlantChange={(val) => form.setValue("swageTmplntCd", String(val))} + selectedSwagePrcsFclt={form.watch("swageTmplntCd")} + onSwagePrcsFcltChange={(val) => form.setValue("swageTmplntCd", String(val))} aplcnYmFrom={form.watch("aplcnYmFrom")} setAplcnYmFrom={(val: string) => { form.setValue("aplcnYmFrom", val) }} aplcnYmTo={form.watch("aplcnYmTo")} @@ -293,7 +293,7 @@ const Result = () => { setCtpvCd={setCtpvCd} setSggCd={setSggCd} setSggName={setSggName} - isLoading={isLoadingResult} + setIsLoading={setIsLoadingResult} // Excel 다운로드 핸들러 추가 onExcelDownload={handleExcelDownload} onDeleteFile={handleFileDelete} diff --git a/apps/sn/app/(with-base-layout)/ii/result/page.tsx b/apps/sn/app/(with-base-layout)/ii/result/page.tsx index c27110223..7c7e4721a 100644 --- a/apps/sn/app/(with-base-layout)/ii/result/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/result/page.tsx @@ -282,8 +282,8 @@ const Result = () => { ctpvCd={ctpvCd} sggCd={sggCd} sggName={sggName} - selectedSwageTreatmentPlant={form.watch("swageTmplntCd")} - onSwageTreatmentPlantChange={(val) => form.setValue("swageTmplntCd", String(val))} + selectedSwagePrcsFclt={form.watch("swageTmplntCd")} + onSwagePrcsFcltChange={(val) => form.setValue("swageTmplntCd", String(val))} aplcnYmFrom={form.watch("aplcnYmFrom")} setAplcnYmFrom={(val: string) => { form.setValue("aplcnYmFrom", val) }} aplcnYmTo={form.watch("aplcnYmTo")} @@ -291,7 +291,7 @@ const Result = () => { setCtpvCd={setCtpvCd} setSggCd={setSggCd} setSggName={setSggName} - isLoading={isLoadingResult} // 로딩 상태 전달 + setIsLoading={setIsLoadingResult} // Excel 다운로드 핸들러 추가 onExcelDownload={handleExcelDownload} onDeleteFile={handleFileDelete} diff --git a/apps/sn/app/(with-base-layout)/layout.tsx b/apps/sn/app/(with-base-layout)/layout.tsx index 8c72ecb0c..f8e046717 100644 --- a/apps/sn/app/(with-base-layout)/layout.tsx +++ b/apps/sn/app/(with-base-layout)/layout.tsx @@ -20,11 +20,10 @@ export default function WithBaseLayout({ const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo); - const { fetchCtpvData, fetchSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } = useStore(); + const { fetchCtpvSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } = useStore(); useEffect(() => { - fetchCtpvData(); - fetchSggData(); + fetchCtpvSggData(); fetchBIZ_SE_CD(); fetchBIZ_TY_CD(); }, []); diff --git a/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx b/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx deleted file mode 100644 index fc9962e75..000000000 --- a/apps/sn/app/components/ui/SidoSigunguSelectBox.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React, { useState, useMemo, useEffect } from "react"; -import { SelectBox } from '@common/business_components/ui' -import { Label } from '@common/components'; -import useStore from '../../store/useStore'; - - -interface SidoSigunguSelectBoxProps { - sido: number; - sigungu: number; - onSidoChange: (id: number) => void; - setCtpvName?: (name: string) => void; - onSigunguChange: (id: number) => void; - setSggName?: (name: string) => void; - className?: string; -} - -export function SidoSigunguSelectBox({ - sido, - sigungu, - onSidoChange, - setCtpvName, - onSigunguChange, - setSggName, - className = "", -}: SidoSigunguSelectBoxProps) { - - const { ctpv, sgg} = useStore(); - - useEffect(() => { - (async () => { - onSidoChange(0); - onSigunguChange(0); - if (setCtpvName && setSggName) { - setCtpvName(""); - setSggName(""); - } - })(); - }, []); - - - return ( - <div className={`flex items-center justify-center ${className} mr-8`}> - <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시도</Label> - <SelectBox - className="w-60 mr-8" - selectClass="rounded-lg bg-white" - label="" - selectArray={ - [{ val: 0, text: '전체' }, - ...ctpv.map(opt => ({ val: opt.id, text: opt.name })) - ] - } - value={sido} - onSelectValue={val => { - onSidoChange(Number(val)); - if (setCtpvName) setCtpvName(ctpv.find(opt => Number(opt.id) === Number(val))?.name || ""); - }} - selectContentClass="z-[101]" - /> - <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시군구</Label> - <SelectBox - className="w-48" - selectClass="w-48 rounded-lg bg-white truncate" - label="" - selectArray={ - [{ val: 0, text: '전체' }, - ...sgg - .filter(opt => String(opt.id).slice(0, 2) === String(sido)) - .map(opt => ({ val: opt.id, text: opt.name })) - ] - } - value={sigungu} - onSelectValue={val => { - onSigunguChange(Number(val)) - if (setSggName) setSggName(sgg.find(opt => Number(opt.id) === Number(val))?.name || ""); - }} - selectContentClass="z-[101]" - /> - </div> - ); -} \ No newline at end of file diff --git a/apps/sn/app/components/ui/ctpvSggPrcsFcltSelectBox.tsx b/apps/sn/app/components/ui/ctpvSggPrcsFcltSelectBox.tsx new file mode 100644 index 000000000..8e6f9d4eb --- /dev/null +++ b/apps/sn/app/components/ui/ctpvSggPrcsFcltSelectBox.tsx @@ -0,0 +1,161 @@ +import React, { useState, useMemo, useEffect } from "react"; +import { SelectBox } from '@common/business_components/ui' +import { Label } from '@common/components'; +import useStore from '../../store/useStore'; +import { getData } from '@/components/lib/api/index'; + + +interface ApiSelectOption { + text: string; + val: string; +} +interface CtpvSggPrcsFcltSelectBoxProps { + sido: number; + sigungu: number; + sggName?: string; + onSidoChange: (id: number) => void; + onSigunguChange: (id: number) => void; + setCtpvName?: (name: string) => void; + setSggName?: (name: string) => void; + className?: string; + selectedSwagePrcsFclt?: string | number; + onSwagePrcsFcltChange?: (value: string | number) => void; + setIsLoading?: (isLoading: boolean) => void; +} + +const defaultOption: ApiSelectOption = { val: '0', text: '전체' }; + +export function CtpvSggPrcsFcltSelectBox({ + sido, + sigungu, + sggName, + onSidoChange, + setCtpvName, + onSigunguChange, + setSggName, + className = "", + selectedSwagePrcsFclt, + onSwagePrcsFcltChange, + setIsLoading, +}: CtpvSggPrcsFcltSelectBoxProps) { + + const [prcsFcltOptions, setPrcsFcltOptions] = useState<ApiSelectOption[]>([]); + const { ctpv, sgg } = useStore(); + + useEffect(() => { + (async () => { + onSidoChange(0); + onSigunguChange(0); + if (setCtpvName && setSggName) { + setCtpvName(""); + setSggName(""); + } + })(); + }, []); + + // useEffect(() => { + // onSwagePrcsFcltChange?.('0'); + + // const fetchData = async () => { + // if (!sggName) { + // console.log("BaseLayout: Skipping fetchData because sigungu.val is:", sggName); + // setPrcsFcltOptions([defaultOption]); + // if (setIsLoading) setIsLoading(false); + // return; + // } + + // if (setIsLoading) setIsLoading(true); + // try { + + // const queryParamsFacility = new URLSearchParams({ + // lyrTypeNm: 'tb_il_swage_prcs_fclt', + // page: '0', + // size: '100000', // 요청하신 URL에 맞게 50으로 변경 가능 + // sort: 'string', + // }); + // const cqlFiltersFacility: string[] = []; + // if (sggName) cqlFiltersFacility.push(`sgg_nm ILIKE '%${sggName}%'`); + // if (cqlFiltersFacility.length > 0) queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and ')); + + // console.log("BaseLayout API URL for Facilities:", `v1/fclts?${queryParamsFacility.toString()}`); + + // const facilityRes = await getData(`v1/fclts?${queryParamsFacility.toString()}`); + + // console.log('facilityRes.data', facilityRes.data); + + // const facilities = (facilityRes.data || []) + // .map((item: any) => ({ + // text: item.swtrmtFcltNm, + // val: item.fid + // })) + // .sort((a: ApiSelectOption, b: ApiSelectOption) => a.text.localeCompare(b.text, 'ko')); + // setPrcsFcltOptions(facilities); + // console.log("BaseLayout Fetched Treatment Facilities:", prcsFcltOptions); + + // } catch (error) { + // console.error("BaseLayout: 지역/시설 데이터 조회 중 오류 발생:", error); + // setPrcsFcltOptions([]); + // } finally { + // if (setIsLoading) setIsLoading(false); + // } + // }; + + // fetchData(); + // }, [sggName]); + + + return ( + <div className={`flex items-center justify-center ${className} mr-8`}> + <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시도</Label> + <SelectBox + className="w-60 mr-8" + selectClass="rounded-lg bg-white" + label="" + selectArray={ + [{ val: 0, text: '전체' }, + ...ctpv.map(opt => ({ val: opt.id, text: opt.name })) + ] + } + value={sido} + onSelectValue={val => { + onSidoChange(Number(val)); + if (setCtpvName) setCtpvName(ctpv.find(opt => Number(opt.id) === Number(val))?.name || ""); + }} + selectContentClass="z-[101]" + /> + <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시군구</Label> + <SelectBox + className="w-48" + selectClass="w-48 rounded-lg bg-white truncate" + label="" + selectArray={ + [{ val: 0, text: '전체' }, + ...sgg + .filter(opt => String(opt.id).slice(0, 2) === String(sido)) + .map(opt => ({ val: opt.id, text: opt.name })) + ] + } + value={sigungu} + onSelectValue={val => { + onSigunguChange(Number(val)) + if (setSggName) setSggName(sgg.find(opt => Number(opt.id) === Number(val))?.name || ""); + }} + selectContentClass="z-[101]" + /> + {selectedSwagePrcsFclt && onSwagePrcsFcltChange && ( + <> + <Label className="text-xl text-gray-600 font-medium whitespace-nowrap ml-8 mr-4">하수처리시설명</Label> + <SelectBox + label={''} + value={selectedSwagePrcsFclt} + selectArray={[defaultOption, ...prcsFcltOptions]} + className="w-[160px]" + selectClass="rounded-lg bg-white truncate" + onSelectValue={(val) => onSwagePrcsFcltChange?.(val)} + selectContentClass="z-[101]" + /> + </> + )} + </div> + ); +} \ No newline at end of file From 8568e10b3febf09998d50adae63a0eb2619ed8c1 Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Fri, 1 Aug 2025 17:08:26 +0900 Subject: [PATCH 58/71] =?UTF-8?q?fix:=20searchModal.tsx=20=EC=97=90?= =?UTF-8?q?=EC=84=9C=20open=20props=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx index 7076b621c..898b9a963 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx @@ -562,7 +562,6 @@ const SearchModal: React.FC<SearchModalProps> = ({ {/* 기본정보 탭 */} {activeTab === 'basic' && ( <BasicTab - open={open} basicType={basicType} city={city} sgg={sgg} @@ -591,7 +590,6 @@ const SearchModal: React.FC<SearchModalProps> = ({ {/* 운영정보 탭 */} {activeTab === 'operation' && ( <OperationTab - open={open} workType={workType} var1={var1} var2={var2} From 79b038657a05a92640a2c4de7f23db38b7100bc8 Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Fri, 1 Aug 2025 17:50:59 +0900 Subject: [PATCH 59/71] =?UTF-8?q?feat:=20json=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20fid=20->=20fcltMngNo=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../map/components/popupComponent/featureClickEventPopup.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 26bf2191b..f50595c10 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx @@ -39,12 +39,10 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, // 🎯 관로관리번호 추출 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); // 🎯 데이터 전처리 메모이제이션 const processedData = useMemo(() => { @@ -355,7 +353,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, else if (layerNm === '하수펌프장') apiEndpoint = 'v1/fcltpump'; else if (layerNm === '하수맨홀') apiEndpoint = 'v1/fcltmhl'; - bodyData['fid'] = featureId.toString().split('.').pop() || null; + bodyData['fcltMngNo'] = featureData.values_.fclt_mng_no.toString() || null; // 빈 키 제거 if (bodyData[""] !== undefined) delete bodyData[""]; From 5e9a9c82ff82b00a25afd8900c2c40088b28d9b1 Mon Sep 17 00:00:00 2001 From: RSONE001 <rsone001@gmail.com> Date: Fri, 1 Aug 2025 17:54:30 +0900 Subject: [PATCH 60/71] =?UTF-8?q?=EC=97=B0=EA=B3=84=20=EC=86=8C=EC=8A=A4?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/src/pages/link/ui/link-form.tsx | 4 ++-- apps/cs/src/pages/linkParams/ui/link-params-page.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/cs/src/pages/link/ui/link-form.tsx b/apps/cs/src/pages/link/ui/link-form.tsx index 05a3da0f1..172215662 100644 --- a/apps/cs/src/pages/link/ui/link-form.tsx +++ b/apps/cs/src/pages/link/ui/link-form.tsx @@ -52,9 +52,9 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, if (await confirm(confirmMsg)) { if (linkSn) { updateLinkInfo?.(data); - } else { + } else { handleInsertLink?.(data); - } + } } }); }; diff --git a/apps/cs/src/pages/linkParams/ui/link-params-page.tsx b/apps/cs/src/pages/linkParams/ui/link-params-page.tsx index d46a9d863..4c811ce13 100644 --- a/apps/cs/src/pages/linkParams/ui/link-params-page.tsx +++ b/apps/cs/src/pages/linkParams/ui/link-params-page.tsx @@ -66,7 +66,7 @@ function LinkParamsPage() { const userUniqId = getCookie(AUTH_USER_ID) ?? ""; const { setMessage: alert } = useAlertStore((state) => state); - const dbIdList = ['link0001', 'link0002']; + const dbIdList = ['link0001', 'link0002', 'link0007']; // Link 목록 쿼리 const query = useQuery({ From 7c7ce750f720c9c14de90693b527c0047d8de7a9 Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Fri, 1 Aug 2025 19:13:37 +0900 Subject: [PATCH 61/71] =?UTF-8?q?feat:=20-=20=EC=8B=9C=EB=8F=84,=20?= =?UTF-8?q?=EC=8B=9C=EA=B5=B0=EA=B5=AC,=20=ED=95=98=EC=88=98=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EC=9E=A5=20SelectBox=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=ED=98=84=20-=20searchBar=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20UI=20=EA=B0=9C=EB=B0=9C=20(zoom?= =?UTF-8?q?=20=EB=B0=8F=20=EC=9D=B4=EB=8F=99=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=ED=95=84=EC=9A=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(map-layout)/map/searchBar/searchBar.tsx | 95 +++++------------- .../app/(with-base-layout)/ii/BaseLayout.tsx | 5 +- .../ui/ctpvSggPrcsFcltSelectBox.tsx | 98 +++++++++---------- apps/sn/app/store/index.ts | 3 - 4 files changed, 71 insertions(+), 130 deletions(-) diff --git a/apps/sn/app/(map-layout)/map/searchBar/searchBar.tsx b/apps/sn/app/(map-layout)/map/searchBar/searchBar.tsx index b57e66c24..43ceb14de 100644 --- a/apps/sn/app/(map-layout)/map/searchBar/searchBar.tsx +++ b/apps/sn/app/(map-layout)/map/searchBar/searchBar.tsx @@ -1,82 +1,31 @@ "use client"; -import { - SearchDiv, - SearchFormLeft, - SelectBox, - type SelectDataType, -} from "@common/business_components/ui"; -import { useCallback, useEffect, useState } from "react"; + +import { useState } from "react"; +import { CtpvSggPrcsFcltSelectBox } from "@/components/ui/ctpvSggPrcsFcltSelectBox"; + export function SearchBar() { - const [sidoValue, setSidoValue] = useState<string>(""); - const [sigunValue, setSigunValue] = useState<string>(""); - const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([ - { text: "시도 선택", val: "0" }, - ]); - - const initializeSigunData = useCallback((sidoCd: string) => { - if (sidoCd === "0") { - setSigunValue("0"); - setFilteredSigunData([{ text: "시도 선택", val: "0" }]); - } else { - const newFilteredData = [ - { text: "데이터", val: "0" }, - // ...Common.selectSigunData.filter( - // (item: SelectDataType) => item.val.substring(0, 2) === sidoCd, - // ), - ]; - setSigunValue("0"); - setFilteredSigunData(newFilteredData); - } - }, []); - const onSelectValue = useCallback( - (val: string, type: string) => { - switch (type) { - case "ctpvCd": - initializeSigunData(val); - setSidoValue(val); - break; - case "sggCd": - setSigunValue(val); - } - }, - [initializeSigunData], - ); + const [ctpv, setCtpv] = useState<number>(0); + const [sgg, setSgg] = useState<number>(0); + const [sggName, setSggName] = useState<string>(''); + const [selectedSwagePrcsFclt, setSelectedSwagePrcsFclt] = useState<string | number>('0'); return ( <> - <div className="grid grid-cols-4 gap-4"> - <div className="col-span-4"> - <div className="card card-border flex"> - <div className="col-span-10 flex flex-row"> - <SelectBox - className="mr-10 w-[200px]" - selectClass="rounded-lg bg-white" - label="시도" - selectArray={/* Common.selectSidoData || */ []} - onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")} - value={sidoValue || /* Common.selectSidoData[0]?.val || */ ""} - /> - <SelectBox - className="mr-10 w-[200px]" - selectClass="rounded-lg bg-white" - label="시군구" - selectArray={filteredSigunData || []} - onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")} - value={sigunValue} - /> - <SelectBox - className="mr-3 w-[250px]" - selectClass="rounded-lg bg-white" - labelClass="w-[200px]" - label="하수처리시설명" - selectArray={filteredSigunData || []} - onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")} - value={sigunValue} - /> - </div> - </div> - </div> + <div className="flex items-center gap-4 bg-white min-h-[48px] min-w-[600px] px-5 py-3 rounded-lg shadow"> + <CtpvSggPrcsFcltSelectBox + sido={ctpv} + sigungu={sgg} + sggName={sggName} + onSidoChange={(sidoId) => { + setCtpv(sidoId); + setSgg(0); + }} + onSigunguChange={setSgg} + setSggName={setSggName} + selectedSwagePrcsFclt={selectedSwagePrcsFclt} + onSwagePrcsFcltChange={setSelectedSwagePrcsFclt} + /> </div> </> ); diff --git a/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx b/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx index c83d56232..5d5443db2 100644 --- a/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx +++ b/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx @@ -85,10 +85,6 @@ const BaseLayout = ({ onAnalysis, }: BaseLayout) => { - const defaultOption: ApiSelectOption = { val: '0', text: '전체' }; - - const [treatmentFacilityOptions, setTreatmentFacilityOptions] = useState<ApiSelectOption[]>([]); - // 파일 다운로드 const isChckFile = () => { // Excel 다운로드 함수가 있으면 Excel 다운로드 실행 @@ -157,6 +153,7 @@ const BaseLayout = ({ <CtpvSggPrcsFcltSelectBox sido={ctpvCd} sigungu={sggCd} + sggName={sggName} onSidoChange={(sidoId: number) => { setCtpvCd(sidoId); setSggCd(0); diff --git a/apps/sn/app/components/ui/ctpvSggPrcsFcltSelectBox.tsx b/apps/sn/app/components/ui/ctpvSggPrcsFcltSelectBox.tsx index 8e6f9d4eb..7e401ef3a 100644 --- a/apps/sn/app/components/ui/ctpvSggPrcsFcltSelectBox.tsx +++ b/apps/sn/app/components/ui/ctpvSggPrcsFcltSelectBox.tsx @@ -53,55 +53,53 @@ export function CtpvSggPrcsFcltSelectBox({ })(); }, []); - // useEffect(() => { - // onSwagePrcsFcltChange?.('0'); + useEffect(() => { + onSwagePrcsFcltChange?.('0'); - // const fetchData = async () => { - // if (!sggName) { - // console.log("BaseLayout: Skipping fetchData because sigungu.val is:", sggName); - // setPrcsFcltOptions([defaultOption]); - // if (setIsLoading) setIsLoading(false); - // return; - // } + const fetchData = async () => { + if (!sggName) { + console.log("BaseLayout: Skipping fetchData because sigungu.val is:", sggName); + setPrcsFcltOptions([]); + if (setIsLoading) setIsLoading(false); + return; + } - // if (setIsLoading) setIsLoading(true); - // try { + if (setIsLoading) setIsLoading(true); + try { - // const queryParamsFacility = new URLSearchParams({ - // lyrTypeNm: 'tb_il_swage_prcs_fclt', - // page: '0', - // size: '100000', // 요청하신 URL에 맞게 50으로 변경 가능 - // sort: 'string', - // }); - // const cqlFiltersFacility: string[] = []; - // if (sggName) cqlFiltersFacility.push(`sgg_nm ILIKE '%${sggName}%'`); - // if (cqlFiltersFacility.length > 0) queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and ')); + const queryParamsFacility = new URLSearchParams({ + lyrTypeNm: 'tb_il_swage_prcs_fclt', + page: '0', + size: '100000', // 요청하신 URL에 맞게 50으로 변경 가능 + sort: 'string', + }); + const cqlFiltersFacility: string[] = []; + if (sggName) cqlFiltersFacility.push(`sgg_nm ILIKE '%${sggName}%'`); + if (cqlFiltersFacility.length > 0) queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and ')); - // console.log("BaseLayout API URL for Facilities:", `v1/fclts?${queryParamsFacility.toString()}`); + console.log("BaseLayout API URL for Facilities:", `v1/fclts?${queryParamsFacility.toString()}`); - // const facilityRes = await getData(`v1/fclts?${queryParamsFacility.toString()}`); + const facilityRes = await getData(`v1/fclts?${queryParamsFacility.toString()}`); - // console.log('facilityRes.data', facilityRes.data); + console.log('facilityRes.data', facilityRes.data); - // const facilities = (facilityRes.data || []) - // .map((item: any) => ({ - // text: item.swtrmtFcltNm, - // val: item.fid - // })) - // .sort((a: ApiSelectOption, b: ApiSelectOption) => a.text.localeCompare(b.text, 'ko')); - // setPrcsFcltOptions(facilities); - // console.log("BaseLayout Fetched Treatment Facilities:", prcsFcltOptions); + const facilities = (facilityRes.data || []) + .map((item: any) => ({ + text: item.swtrmtFcltNm, + val: item.fid + })) + .sort((a: ApiSelectOption, b: ApiSelectOption) => a.text.localeCompare(b.text, 'ko')); + setPrcsFcltOptions(facilities); + } catch (error) { + console.error("BaseLayout: 지역/시설 데이터 조회 중 오류 발생:", error); + setPrcsFcltOptions([]); + } finally { + if (setIsLoading) setIsLoading(false); + } + }; - // } catch (error) { - // console.error("BaseLayout: 지역/시설 데이터 조회 중 오류 발생:", error); - // setPrcsFcltOptions([]); - // } finally { - // if (setIsLoading) setIsLoading(false); - // } - // }; - - // fetchData(); - // }, [sggName]); + fetchData(); + }, [sggName]); return ( @@ -112,8 +110,8 @@ export function CtpvSggPrcsFcltSelectBox({ selectClass="rounded-lg bg-white" label="" selectArray={ - [{ val: 0, text: '전체' }, - ...ctpv.map(opt => ({ val: opt.id, text: opt.name })) + [defaultOption, + ...ctpv.map(opt => ({ val: opt.id, text: opt.name })) ] } value={sido} @@ -129,10 +127,10 @@ export function CtpvSggPrcsFcltSelectBox({ selectClass="w-48 rounded-lg bg-white truncate" label="" selectArray={ - [{ val: 0, text: '전체' }, - ...sgg - .filter(opt => String(opt.id).slice(0, 2) === String(sido)) - .map(opt => ({ val: opt.id, text: opt.name })) + [defaultOption, + ...sgg + .filter(opt => String(opt.id).slice(0, 2) === String(sido)) + .map(opt => ({ val: opt.id, text: opt.name })) ] } value={sigungu} @@ -146,11 +144,11 @@ export function CtpvSggPrcsFcltSelectBox({ <> <Label className="text-xl text-gray-600 font-medium whitespace-nowrap ml-8 mr-4">하수처리시설명</Label> <SelectBox - label={''} - value={selectedSwagePrcsFclt} - selectArray={[defaultOption, ...prcsFcltOptions]} className="w-[160px]" selectClass="rounded-lg bg-white truncate" + label="" + selectArray={[defaultOption, ...prcsFcltOptions]} + value={selectedSwagePrcsFclt} onSelectValue={(val) => onSwagePrcsFcltChange?.(val)} selectContentClass="z-[101]" /> diff --git a/apps/sn/app/store/index.ts b/apps/sn/app/store/index.ts index ea5171b44..e173c7509 100644 --- a/apps/sn/app/store/index.ts +++ b/apps/sn/app/store/index.ts @@ -31,9 +31,6 @@ export const fetchCtpvSggData = async (set: (state: any) => void) => { })) .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); - console.log("CTPV List:", ctpvList); - console.log("SGG List:", sggList); - set({ ctpv: ctpvList, sgg: sggList }); } catch (e: any) { // alert("시도, 시군구 데이터 로드 실패: " + e.message); From de8276851e576687d7677f51e75ddf0dd8dd65f9 Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Fri, 1 Aug 2025 19:14:19 +0900 Subject: [PATCH 62/71] =?UTF-8?q?feat:=20=EC=B9=A8=EC=88=98=EC=8B=AC=20?= =?UTF-8?q?=EB=B2=94=EB=A1=80=20UI=20=EA=B5=AC=ED=98=84=20(=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80=20=EA=B5=AC=ED=98=84=20=ED=95=84?= =?UTF-8?q?=EC=9A=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/(map-layout)/map/page.tsx | 4 ++ .../map/waterDepth/waterDepth.tsx | 62 ++++++++++++++++++ .../images/map/toolbar/pause_button.png | Bin 0 -> 479 bytes .../map/toolbar/play_button_arrowhead.png | Bin 0 -> 774 bytes 4 files changed, 66 insertions(+) create mode 100644 apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx create mode 100644 apps/sn/public/images/map/toolbar/pause_button.png create mode 100644 apps/sn/public/images/map/toolbar/play_button_arrowhead.png diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index d80b3d851..5ff30009c 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -7,6 +7,7 @@ import GisApp from "@common/business_components/ui/map/lib/gis.core"; import SearchModal from "./searchModal/searchModal"; import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup"; import { SearchBar } from "./searchBar/searchBar"; +import { WaterDepth } from "./waterDepth/waterDepth"; // 타입 정의 추가 export type LyrStngProps = { @@ -116,6 +117,8 @@ const Map = () => { setIsOpen(true); setIsSearchModalOpen(false); }; + + return ( <div className="bg-container"> <Gis @@ -134,6 +137,7 @@ const Map = () => { isSearchModalOpen={isSearchModalOpen} onCheckedLayersChange={undefined} searchBar={SearchBar} + waterDepth={WaterDepth} /> </div> ); diff --git a/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx b/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx new file mode 100644 index 000000000..39e375a4b --- /dev/null +++ b/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx @@ -0,0 +1,62 @@ +"use client"; + +import React, { useRef, useEffect, useState } from "react"; +import { Label } from "@common/components/ui/label"; + +export function WaterDepth({ onClose }: { onClose?: () => void }) { + const [isPlaying, setIsPlaying] = useState<boolean>(false); + + return ( + <> + <div className="fixed top-8 left-8 z-50 bg-white rounded-lg shadow-lg border border-gray-200 w-[160px] px-4 py-3"> + <div className="card-header flex items-center justify-between"> + <Label className="text-3xl text-gray-900 font-bold">침수심 범례</Label> + <button + type="button" + className="ml-2" + onClick={() => setIsPlaying((prev) => !prev)} + > + {isPlaying ? ( + <img + src="/images/map/toolbar/pause_button.png" + alt="pause" + className="w-6 h-6 object-contain" + /> + ) : ( + <img + src="/images/map/toolbar/play_button_arrowhead.png" + alt="play" + className="w-6 h-6 object-contain" + /> + )} + </button> + </div> + <div className="border-b border-gray-300 mb-2" /> + <div className="w-[140px] mx-auto flex flex-col items-center"> + <div className="bg-blue-900 text-white text-2xl text-center py-1 mb-2 rounded w-full">침 수 심</div> + <div className="space-y-1 w-full"> + <LegendItem color="#0000ff" label="0.3~0.5m 미만" /> + <LegendItem color="#00e8fe" label="0.5~1.0m 미만" /> + <LegendItem color="#00fdb7" label="1.0~1.5m 미만" /> + <LegendItem color="#00fe4e" label="1.5~2.0m 미만" /> + <LegendItem color="#fce800" label="2.0~3.0m 미만" /> + <LegendItem color="#fe0000" label="3.0m 이상" /> + </div> + <div className="border-t-4 border-gray-900 mt-2 mx-auto w-full" /> + </div> + </div> + </> + ); +} + +function LegendItem({ color, label }: { color: string; label: string }) { + return ( + <div className="flex items-center gap-2 w-full"> + <span + className="inline-block w-16 h-6" + style={{ backgroundColor: color, border: "1px solid #ccc" }} + /> + <span className="text-xl text-gray-900 font-bold">{label}</span> + </div> + ); +} \ No newline at end of file diff --git a/apps/sn/public/images/map/toolbar/pause_button.png b/apps/sn/public/images/map/toolbar/pause_button.png new file mode 100644 index 0000000000000000000000000000000000000000..05f51ac1362afeecb4c18fe3aa7329473b437289 GIT binary patch literal 479 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!n2Vh}LpV4%Za?&Y0OWEOctjR6 zFbKPVFyo^4{!4*^k|nMYCBgY=CFO}lsSM@i<$9TU*~Q6;1*v-ZMd`EO*+>Bu<plVI zxc>kDAIPK+s40s-2((GLB*-tAfsvJqn^(!m%E8GaJRv==pkd;a=`&}|nY(TO$us9) ze)|07-=F2CZ9wJQJY5_^A`ZWuep0B(K!BB5Xo76Y@eR-Z|G%iU{mg>S_K?b1<v#wu z?)AuuT}=|rJz-b2Ze8~Z<utkcC$-g26AN~|;lEUHxjuc$dfpj4`36>Oj(S&vID#08 z{??Z>mm8}XGt6Es$|7_w{D8Z{1umnK=^7IhCS<az?$Q>~5YX^+U=o_pz@nnS$ms!M zKKRe{`pL#Vz1MFx^ZEb&lDg;ek?rBOk9R+||7H7b`TL*p+mhz~`thTNdGf92bIT59 Qz6S-Zr>mdKI;Vst0M|{ELjV8( literal 0 HcmV?d00001 diff --git a/apps/sn/public/images/map/toolbar/play_button_arrowhead.png b/apps/sn/public/images/map/toolbar/play_button_arrowhead.png new file mode 100644 index 0000000000000000000000000000000000000000..7948b16e587e5aaebaeb85f9a98ac9dabb04cf5f GIT binary patch literal 774 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!n2Vh}LpV4%Za?&Y0OWEOctjR6 zFtBETFynX8=><SR$r9IylHmNblJdl&REF~Ma=pyF?Be9af>gcyqV(DCY@~pS{sj1h zxc>kDAIKb;Aac>C(?FjGmjw9*GcYo-vaxe;a&hwsiHJ!^%gQTi>FOI8n_1a=c>4zg zheyYyq!$*KRMa%Ibo5V}Hf!FZWm|UaIdJgsk>jT>-MD@4!Q&@SU%q|+@zduo-@gC) z{pasL-!HN93=E92o-U3d5r>anKOV;9DB$+cETM0PvfGh{u4KLrNnxe$fA1fidR%$e z%bna=_V*Vp-@WRefD^~U+P8lm8b+RJnR2z3gW>9>46B6p_f_}b`F^a8@14IZKC_!G z?Put8<BbmUmU+27YFHxqAa}Jo+m1*1clF$5Txb4&l@!Hf!q+Ctuzq$dU&Cxw&I326 zuw+bWlVQ+r?O|RpX)?ncAFqb<e;wH!N*7;dEZD2SXdyS9;p<*T_5+uz+L;&F=rdYK zO=gha{g<y{Hz(%@8+FE4tA!R!o66wIJ!hMY#<m{<Z}Yc^d&Z^yp7Er38n5d%liMMw z(bJn<`^>Cko>olfZi^{=GfDNj&CZNdq1W|p3%=gRCu8;L#f7EG^ZDjk{rdGG_tAgx X7w4|026+{p0Hr`rS3j3^P6<r_Y0ceF literal 0 HcmV?d00001 From 2644e771da54098c0ee269a504040b87a26ac8b6 Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Fri, 1 Aug 2025 19:14:38 +0900 Subject: [PATCH 63/71] =?UTF-8?q?feat:=20=EC=B9=A8=EC=88=98=EC=8B=AC=20?= =?UTF-8?q?=EB=B2=94=EB=A1=80=20UI=20=EA=B5=AC=ED=98=84=20(=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80=20=EA=B5=AC=ED=98=84=20=ED=95=84?= =?UTF-8?q?=EC=9A=94)2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/common/business_components/src/ui/map/Gis.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index eefb08da6..89b7e982b 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -99,7 +99,8 @@ function Gis({ onFeatureSelect, setIsSearchModalOpen, onCheckedLayersChange, - searchBar: SearchBar + searchBar: SearchBar, + waterDepth: WaterDepth, }) { const mapRef = useRef(); const [mapObject, setMapObject] = useState({}); @@ -294,6 +295,7 @@ function Gis({ <GisToolBar /> <GisMapToggle /> <GisSelect onFeatureSelect={handleFeatureSelect} /> + {typeof WaterDepth === "function" && <WaterDepth onClose={() => setIsOpen(false)} />} </div> </div> From f6333d0b290e38013ca5c9c46fd438e8b6330919 Mon Sep 17 00:00:00 2001 From: swcho <swcho@mind-one.co.kr> Date: Fri, 1 Aug 2025 19:16:16 +0900 Subject: [PATCH 64/71] =?UTF-8?q?refactor:=20=EC=B9=A8=EC=88=98=EC=8B=AC?= =?UTF-8?q?=20=EB=B2=94=EB=A1=80=20=EC=A3=BC=EC=84=9D=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/(map-layout)/map/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index 5ff30009c..6105bc818 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -137,7 +137,7 @@ const Map = () => { isSearchModalOpen={isSearchModalOpen} onCheckedLayersChange={undefined} searchBar={SearchBar} - waterDepth={WaterDepth} + // waterDepth={WaterDepth} /> </div> ); From 71aacf4c7679e77fddff81adc8c42300d53d43e8 Mon Sep 17 00:00:00 2001 From: yschoi <yschoi> Date: Sun, 3 Aug 2025 14:27:49 +0900 Subject: [PATCH 65/71] =?UTF-8?q?=EB=AC=BC=EC=9E=AC=EC=9D=B4=EC=9A=A9=20?= =?UTF-8?q?=EC=A7=80=EB=8F=84=EB=B3=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/fo/app/pages/map/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/fo/app/pages/map/page.tsx b/apps/fo/app/pages/map/page.tsx index 1a84449a8..edd7b70d2 100644 --- a/apps/fo/app/pages/map/page.tsx +++ b/apps/fo/app/pages/map/page.tsx @@ -131,8 +131,8 @@ const Map = () => { isSearch={false} gisMaps={gisMaps} isMiniMap={false} - searchView={null} - detailView={null} + searchView={undefined} + detailView={undefined} onFeatureSelect={onFeatureSelect} setIsOpen={setIsOpen} setFeatureData={setFeatureData} From d45ce537a39d5abbae9204e1e0fe8d7bc5a22e66 Mon Sep 17 00:00:00 2001 From: yschoi <yschoi> Date: Sun, 3 Aug 2025 14:29:11 +0900 Subject: [PATCH 66/71] =?UTF-8?q?=EB=AC=BC=EC=9E=AC=EC=9D=B4=EC=9A=A9=20?= =?UTF-8?q?=EC=A7=80=EB=8F=84=EB=B3=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/fo/app/pages/map/searchBar/searchBar.tsx | 7 +- apps/wr/.env.local | 2 + apps/wr/app/map/mainMap/page.tsx | 47 +-- apps/wr/app/map/searchBar/searchBar.tsx | 299 ++++++++++++++++++ apps/wr/middleware.ts | 11 + .../business_components/src/ui/map/Gis.js | 3 +- .../src/ui/map/lib/gis.layer.js | 10 +- 7 files changed, 351 insertions(+), 28 deletions(-) create mode 100644 apps/wr/app/map/searchBar/searchBar.tsx diff --git a/apps/fo/app/pages/map/searchBar/searchBar.tsx b/apps/fo/app/pages/map/searchBar/searchBar.tsx index 9b0800399..e9084870a 100644 --- a/apps/fo/app/pages/map/searchBar/searchBar.tsx +++ b/apps/fo/app/pages/map/searchBar/searchBar.tsx @@ -2,6 +2,7 @@ import { SelectBox, type SelectDataType } from "@common/business_components/ui"; import { Button } from "@common/components/ui"; import { useCallback, useEffect, useState } from "react"; +import { GEOSERVER_URL } from "@/src/shared/config/env"; interface SearchBarProps { onFeatureSelect: (feature: any) => void; @@ -18,7 +19,7 @@ export function SearchBar({ onFeatureSelect, gisMaps }: SearchBarProps) { const [facilityValue, setFacilityValue] = useState("0"); const fetchSido = async () => { - const url = `http://rsone.iptime.org:10010/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_ctpv&outputFormat=application/json&propertyName=mctpv_korn_nm,mctpv_cd`; + const url = `/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_ctpv&outputFormat=application/json&propertyName=mctpv_korn_nm,mctpv_cd`; const res = await fetch(url); const json = await res.json(); @@ -30,7 +31,7 @@ export function SearchBar({ onFeatureSelect, gisMaps }: SearchBarProps) { }; const fetchSigun = async () => { - const url = `http://rsone.iptime.org:10010/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_sgg&outputFormat=application/json&propertyName=sgg_cd,sgg_korn_nm`; + const url = `/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_sgg&outputFormat=application/json&propertyName=sgg_cd,sgg_korn_nm`; const res = await fetch(url); const json = await res.json(); @@ -43,7 +44,7 @@ export function SearchBar({ onFeatureSelect, gisMaps }: SearchBarProps) { const fetchFacilitiesBySggNm = async (sggNm: string) => { const encodedSggNm = encodeURIComponent(sggNm); - const url = `http://rsone.iptime.org:10010/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_il_swage_prcs_fclt&outputFormat=application/json&cql_filter=sgg_nm='${encodedSggNm}'`; + const url = `/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_il_swage_prcs_fclt&outputFormat=application/json&cql_filter=sgg_nm='${encodedSggNm}'`; const res = await fetch(url); const json = await res.json(); diff --git a/apps/wr/.env.local b/apps/wr/.env.local index 2543a96ad..4d00d1cab 100644 --- a/apps/wr/.env.local +++ b/apps/wr/.env.local @@ -10,6 +10,8 @@ NEXT_PUBLIC_CLAIM_NAME = Authorization NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_ACCESS_TOKEN = access-token NEXT_PUBLIC_REFRESH_TOKEN = refresh-token +NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver +NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver diff --git a/apps/wr/app/map/mainMap/page.tsx b/apps/wr/app/map/mainMap/page.tsx index 8cfb27e2f..420dfdfb0 100644 --- a/apps/wr/app/map/mainMap/page.tsx +++ b/apps/wr/app/map/mainMap/page.tsx @@ -5,6 +5,7 @@ import { useEffect, useRef, useState, useCallback } from "react"; import { mapService } from "../service"; import NewWindowPopup from "../newWindowPopup"; import GisApp from "@common/business_components/ui/map/lib/gis.core"; +import { SearchBar } from "../searchBar/searchBar"; const Map = () => { const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정 @@ -61,24 +62,28 @@ const Map = () => { // 2. data가 OpenLayers Feature 인스턴스인지 확인 let foundFeature: any = null; - console.log(data) + console.log(data); //console.log(data.getId()) let featureId = data.fid; const layerName = data.layerNm; - + if (layerName === "재이용시설") { // 재이용시설 featureId = `${layerName}.${data.searchPk}`; - } else if ( //빗물이용시설, 중수도시설 + } else if ( + //빗물이용시설, 중수도시설 layerName === "빗물이용시설" || layerName == "tb_wr_rswtr_fclt" ) { featureId = `${layerName}.${data.bdstInfoSn}`; - } else if (layerName === "tb_il_swage_prcs_fclt") { //하수처리시설 + } else if (layerName === "tb_il_swage_prcs_fclt") { + //하수처리시설 featureId = `${layerName}.${data.bscFctCd}`; - } else if (layerName === "tb_wr_htdfl_fclt") { //온배수시설 + } else if (layerName === "tb_wr_htdfl_fclt") { + //온배수시설 featureId = `${layerName}.${data.bdstRuseFcltSn}`; - } else if (layerName == "tb_wr_ruse_fclt_dmd_co") { //수요처 + } else if (layerName == "tb_wr_ruse_fclt_dmd_co") { + //수요처 featureId = `${layerName}.${data.dmdInfoId.replaceAll("-", "")}`; } else { console.warn("해당 레이어에 대한 featureId 매핑 없음", layerName); @@ -120,17 +125,6 @@ const Map = () => { setIsSearchModalOpen(false); }; - // MapSearchBar 래퍼 컴포넌트 - // const CustomMapSearchBar = (props: any) => { - // return ( - // <MapSearchBar - // {...props} - // searchType="공급처 후보지 조회" - // showApplyButton={false} - // /> - // ); - // }; - return ( <div className="bg-container"> <Gis @@ -156,7 +150,24 @@ const Map = () => { isSearchModalOpen={isSearchModalOpen} onCheckedLayersChange={handleCheckedLayersChange} imgVisble={true} - searchBar={undefined} + searchBar={(props: any) => { + const facilityTypes = [ + { text: "하수처리시설", val: "tb_il_swage_prcs_fclt" }, + { text: "하수재이용시설", val: "tb_wr_bdst_ruse_fclt" }, + { text: "빗물이용시설", val: "tb_wr_rnwtr_utztn_fclt" }, + { text: "중수도시설", val: "tb_wr_rswtr_fclt" }, + { text: "온배수시설", val: "tb_wr_htdfl_fclt" }, + { text: "수요처", val: "tb_wr_ruse_fclt_dmd_co" }, + { text: "잠재수요처", val: "tb_wr_tmpr_dmd_co" }, + ]; + return ( + <SearchBar + onFeatureSelect={onFeatureSelect} + gisMaps={gisMaps} + facilityTypes={facilityTypes} + /> + ); + }} /> </div> ); diff --git a/apps/wr/app/map/searchBar/searchBar.tsx b/apps/wr/app/map/searchBar/searchBar.tsx new file mode 100644 index 000000000..7502daf94 --- /dev/null +++ b/apps/wr/app/map/searchBar/searchBar.tsx @@ -0,0 +1,299 @@ +"use client"; +import { SelectBox, type SelectDataType } from "@common/business_components/ui"; +import { Button } from "@common/components/ui"; +import { useCallback, useEffect, useState } from "react"; +import { SERVER_API_WR_URL } from "@/src/shared/config"; + +const geoserverUrl = + "/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&outputFormat=application/json"; + +const FACILITY_TYPE_URL_MAP: Record<string, string> = { + tb_il_swage_prcs_fclt: geoserverUrl, //하수처리시설 + tb_wr_bdst_ruse_fclt: + "/reuse-service/reuseFc/pubDetailList/{year}/{sggCd}/1/1000", //재이용시설 post + tb_wr_rnwtr_utztn_fclt: "/reuse-service/reuseFc/rainyInfoList/1/1000/{sggCd}", //빗물이용시설 get + tb_wr_rswtr_fclt: "/reuse-service/reuseFc/waterreuseFc/1/1000/{sggCd}", //중수도시설 get + tb_wr_htdfl_fclt: "/reuse-service/drain/facilityList/1/1000/{sggCd}", //온배수시설 get + tb_wr_ruse_fclt_dmd_co: "/reuse-service/reuseFc/request/1/1000/{sggCd}", //수요처 get + tb_wr_tmpr_dmd_co: + "/reuse-service/reuseFc/request/temp/1/1000/{sidoNm} {sggNm}", //잠재수요처 get +}; + +interface SearchBarProps { + onFeatureSelect: (feature: any) => void; + gisMaps: React.RefObject<any>; + facilityTypes: { text: string; val: string }[]; +} + +export function SearchBar({ + onFeatureSelect, + gisMaps, + facilityTypes, +}: SearchBarProps) { + const [sidoValue, setSidoValue] = useState("0"); + const [sigunValue, setSigunValue] = useState("0"); + const [sidoData, setSidoData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + const [sigunData, setSigunData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + + const [facilityData, setFacilityData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + const [facilityValue, setFacilityValue] = useState("0"); + + const [facilityType, setFacilityType] = useState( + facilityTypes?.length === 1 && facilityTypes[0] ? facilityTypes[0].val : "0" + ); + + //시도, 시군구 조회 + const fetchSido = async () => { + const url = `/cms-service/api/v1/code/cache/ADSTDSTRCD/Y`; + const res = await fetch(url); + const json = await res.json(); + const sidoList = json + .filter( + (item: any) => + typeof item.lwpoCd === "string" && item.lwpoCd.length === 2 + ) + .map((feature: any) => ({ + val: feature.lwpoCd, + text: feature.lwpoCdNm, + })); + setSidoData([{ text: "선택", val: "0" }, ...sidoList]); + + const sigunList = json + .filter( + (item: any) => + typeof item.lwpoCd === "string" && item.lwpoCd.length !== 2 + ) + .map((feature: any) => ({ + val: feature.lwpoCd, + text: feature.lwpoCdNm, + })); + setSigunData(sigunList); + }; + + //시설명 콤보박스 조회 + const fetchFacilitiesBySggNm = async ( + sggCd: string, + sggNm: string, + sidoNm: string, + currentFacilityType?: string + ) => { + const type = currentFacilityType ?? facilityType; + if (!type || type === "0") return; + + const baseUrlTemplate = FACILITY_TYPE_URL_MAP[type]; + if (!baseUrlTemplate) return; + + const year = new Date().getFullYear().toString(); + let url = baseUrlTemplate + .replace("{year}", year) + .replace("{sggCd}", sggCd) + .replace("{sidoNm}", encodeURIComponent(sidoNm)) + .replace("{sggNm}", encodeURIComponent(sggNm)); + + let list: SelectDataType[] = []; + + if ( + type === "tb_il_swage_prcs_fclt" //하수처리시설 + ) { + const cqlFilter = `ctpv_nm='${encodeURIComponent(sidoNm)}' AND sgg_nm='${encodeURIComponent(sggNm)}'`; + url = `${url}&typeName=hasudo:${type}&cql_filter=${cqlFilter}`; + const res = await fetch(url); + const json = await res.json(); + list = json.features.map((f: any) => ({ + text: f.properties.swtrmt_fclt_nm ?? "(이름없음)", + val: f.id, + })); + } else if ( + type === "tb_wr_bdst_ruse_fclt" // 재이용시설 + ) { + //재용에서 참조하는 하수처리시설 목록 + const paramsRes = await fetch("/reuse-service/reuseFc/pubFc"); + const paramsJson = await paramsRes.json(); + const params = paramsJson.data; + + const res = await fetch(url, { + method: "POST", + headers: { + accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(params), + }); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.ruseUtztnFcltNm ?? "(이름없음)", + val: item.pbnstRuseFcltId, + })); + } 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 방식 + const res = await fetch(url); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.dmdCoNm ?? "(이름없음)", + val: item.fctryMngSn, + })); + } else { + // GET 방식 + const res = await fetch(url); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.bdstNm ?? "(이름없음)", + val: item.bdstInfoSn, + })); + } + + setFacilityData([{ text: "선택", val: "0" }, ...list]); + setFacilityValue("0"); + }; + + //시도, 시군구 초기 데이터 조회 + useEffect(() => { + fetchSido(); + }, []); + + //시군구 데이터 초기화 + const initializeSigunData = useCallback( + (sidoCd: string) => { + if (sidoCd === "0") { + setFilteredSigunData([{ text: "선택", val: "0" }]); + setSigunValue("0"); + return; + } + const prefix = sidoCd.substring(0, 2); + const filtered = sigunData.filter((item) => item.val.startsWith(prefix)); + setFilteredSigunData([{ text: "선택", val: "0" }, ...filtered]); + setSigunValue("0"); + }, + [sigunData] + ); + + //콤보박스 선택시 연관 콤보박스 처리 + const onSelectValue = useCallback( + (val: string, type: string) => { + if (type === "ctpvCd") { + setSidoValue(val); + initializeSigunData(val); + setFacilityData([{ text: "선택", val: "0" }]); + setFacilityValue("0"); + } else if (type === "sggCd") { + setSigunValue(val); + const sidoNm = + sidoData.find((item) => item.val === sidoValue)?.text || ""; + const sggNm = sigunData.find((item) => item.val === val)?.text || ""; + const sggCd = sigunData.find((item) => item.val === val)?.val || ""; + if (sggNm && sidoNm) + fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, facilityType); + } else if (type === "facilityType") { + setFacilityType(val); + setFacilityValue("0"); + setFacilityData([{ text: "선택", val: "0" }]); + const sidoNm = + sidoData.find((item) => item.val === sidoValue)?.text || ""; + const sggNm = + sigunData.find((item) => item.val === sigunValue)?.text || ""; + const sggCd = + sigunData.find((item) => item.val === sigunValue)?.val || ""; + if (sggNm && sidoNm) fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, val); + } else if (type === "fcltCd") { + setFacilityValue(val); + } + }, + [initializeSigunData, sigunData, sigunValue, sidoData, sidoValue] + ); + + //조회 버튼 클릭 + const handleSearch = () => { + if (!gisMaps.current || facilityValue === "0") return; + const layers = gisMaps.current.getLayers().getArray(); + for (const layer of layers) { + const source = layer.getSource?.(); + const feature = source?.getFeatureById?.(facilityValue); + if (feature) { + onFeatureSelect(feature); + return; + } + } + alert("해당 처리시설을 지도에서 찾을 수 없습니다."); + }; + + return ( + <div className="grid grid-cols-4 gap-4"> + <div className="col-span-4"> + <div className="card card-border flex"> + <div className="col-span-10 flex flex-row"> + {facilityTypes.length > 1 && ( + <SelectBox + labelClass="w-[120px]" + className="mr-10 w-[190px]" + selectClass="rounded-lg bg-white" + label="시설물유형" + selectArray={[{ text: "선택", val: "0" }, ...facilityTypes]} + onSelectValue={(val) => + onSelectValue(val.toString(), "facilityType") + } + value={facilityType} + /> + )} + <SelectBox + labelClass="w-[40px]" + className="mr-10 w-[150px]" + selectClass="rounded-lg bg-white" + label="시도" + selectArray={sidoData} + onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")} + value={sidoValue} + /> + <SelectBox + labelClass="w-[65px]" + className="mr-10 w-[150px]" + selectClass="rounded-lg bg-white" + label="시군구" + selectArray={filteredSigunData} + onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")} + value={sigunValue} + /> + <SelectBox + labelClass="w-[60px]" + className="mr-3 w-[180px]" + selectClass="rounded-lg bg-white" + label="시설명" + selectArray={facilityData} + onSelectValue={(val) => onSelectValue(val.toString(), "fcltCd")} + value={facilityValue} + /> + </div> + <div className="flex flex-row space-x-2"> + <Button + color="primary" + className="mx-2 mt-1 ml-5" + size={"sm"} + onClick={handleSearch} + > + 조회 + </Button> + </div> + </div> + </div> + </div> + ); +} diff --git a/apps/wr/middleware.ts b/apps/wr/middleware.ts index 61f41d634..1a90a5591 100644 --- a/apps/wr/middleware.ts +++ b/apps/wr/middleware.ts @@ -8,6 +8,7 @@ const serverApiUrl = CS_URL; const GEOSERVER_URL = process.env.NEXT_PUBLIC_GEOSERVER_URL || ""; const LX_GEOSERVER_URL = process.env.NEXT_PUBLIC_LX_GEOSERVER_URL || ""; const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ""; +const REUSE_API_URL = process.env.NEXT_PUBLIC_WR_SERVER_API_URL || ""; export function middleware(req: NextRequest) { const pathname = req.nextUrl.pathname; @@ -135,6 +136,15 @@ export function middleware(req: NextRequest) { return NextResponse.rewrite(new URL(newUrl)); } + if (pathname.startsWith("/reuse-service")) { + const queryParams = searchParams.toString(); + const newUrl = `${REUSE_API_URL}${pathname.replace("/reuse-service", "")}${ + queryParams ? `?${queryParams}` : "" + }`; + console.log(`[Middleware] Rewriting from '/reuse-service' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl)); + } + return NextResponse.next(); } @@ -146,6 +156,7 @@ export const config = { "/attach-service/:path*", "/geoserver/:path*", "/sewerage-service/:path*", + "/reuse-service/:path*", "/" ], }; diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index 079506aca..013288df4 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -326,8 +326,7 @@ function Gis({ {/* 시도/시군구 조회 영역 */} {SearchBar && ( <div - className="absolute left-1/2 top-4 z-30 -translate-x-1/2 rounded" - style={{ maxWidth: "calc(100% - 2rem)" }} + className="absolute left-1/2 top-4 z-30 -translate-x-1/2 rounded w-max" > <SearchBar /> </div> diff --git a/packages/common/business_components/src/ui/map/lib/gis.layer.js b/packages/common/business_components/src/ui/map/lib/gis.layer.js index 412b0d8b9..f7bb7b3ac 100644 --- a/packages/common/business_components/src/ui/map/lib/gis.layer.js +++ b/packages/common/business_components/src/ui/map/lib/gis.layer.js @@ -143,11 +143,11 @@ GisOlLayer.prototype = { let source; let lyrTyNm = option.lyrTyNm; - if(lyrTyNm == 'tb_ms_flood_dngr_map' - || lyrTyNm == 'tb_ms_sbmrg_dngr_map' - || lyrTyNm == 'tb_ms_swage_mhl' - || lyrTyNm == 'tb_ms_swage_ppl' - || lyrTyNm == 'tb_ms_swage_pmstn' + if(lyrTyNm == 'tb_ms_flood_dngr_map' //침수위험지도 + || lyrTyNm == 'tb_ms_sbmrg_dngr_map' //도시침수지도 + || lyrTyNm == 'tb_ms_swage_mhl' //맨홀 + || lyrTyNm == 'tb_ms_swage_ppl' //관로 + || lyrTyNm == 'tb_ms_swage_pmstn' //펌프장 ) { source = this.getVectorSourceBbox(option); }else { From 8eede7432b6c14a01d2139719e434110f2c73a23 Mon Sep 17 00:00:00 2001 From: yschoi <yschoi> Date: Sun, 3 Aug 2025 14:30:16 +0900 Subject: [PATCH 67/71] =?UTF-8?q?=EC=8B=9C=EB=8F=84=EC=8B=9C=EA=B5=B0?= =?UTF-8?q?=EA=B5=AC=20=EC=A0=95=EB=A0=AC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/store/index.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/sn/app/store/index.ts b/apps/sn/app/store/index.ts index e173c7509..40e094480 100644 --- a/apps/sn/app/store/index.ts +++ b/apps/sn/app/store/index.ts @@ -13,23 +13,20 @@ export type BIZ_CD_TYPE = { export const fetchCtpvSggData = async (set: (state: any) => void) => { try { - const ctpvData = await getCmsData('/api/v1/code/cache/ADSTDSTRCD'); - console.log("CTPV Data:", ctpvData); + const ctpvData = await getCmsData('/api/v1/code/cache/ADSTDSTRCD/Y'); const ctpvList: Option[] = ctpvData .filter((item: any) => typeof item.lwpoCd === 'string' && item.lwpoCd.length === 2) .map((feature: any) => ({ id: feature.lwpoCd, name: feature.lwpoCdNm - })) - .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); + })); const sggList: Option[] = ctpvData .filter((item: any) => typeof item.lwpoCd === 'string' && item.lwpoCd.length !== 2) .map((item: any) => ({ id: item.lwpoCd, name: item.lwpoCdNm - })) - .sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko')); + })); set({ ctpv: ctpvList, sgg: sggList }); } catch (e: any) { From e2171843b2b78a902bf8d913924457f8dd4c4355 Mon Sep 17 00:00:00 2001 From: yschoi <yschoi> Date: Sun, 3 Aug 2025 14:35:58 +0900 Subject: [PATCH 68/71] =?UTF-8?q?=EB=B3=91=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/common/business_components/src/ui/map/Gis.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index 92a64ed44..41774fc59 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -116,7 +116,7 @@ function Gis({ setIsSearchModalOpen, onCheckedLayersChange, searchBar: SearchBar, - waterDepth: WaterDepth,, + waterDepth: WaterDepth, height:height="h-[calc(100vh-10rem)]", }) { const mapRef = useRef(); From 937cccb2240880e106518aa5787809eea370bee1 Mon Sep 17 00:00:00 2001 From: yschoi <yschoi> Date: Mon, 4 Aug 2025 08:16:49 +0900 Subject: [PATCH 69/71] =?UTF-8?q?=EC=A7=80=EB=8F=84=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wr/app/map/searchBar/SearchInputText.tsx | 124 +++++++ apps/wr/app/map/searchBar/comboSearchBar.tsx | 299 +++++++++++++++++ apps/wr/app/map/searchBar/procSearchBar.tsx | 299 +++++++++++++++++ apps/wr/app/map/searchBar/searchBar.tsx | 316 +++++++++--------- apps/wr/middleware.ts | 11 + 5 files changed, 893 insertions(+), 156 deletions(-) create mode 100644 apps/wr/app/map/searchBar/SearchInputText.tsx create mode 100644 apps/wr/app/map/searchBar/comboSearchBar.tsx create mode 100644 apps/wr/app/map/searchBar/procSearchBar.tsx diff --git a/apps/wr/app/map/searchBar/SearchInputText.tsx b/apps/wr/app/map/searchBar/SearchInputText.tsx new file mode 100644 index 000000000..1df92fd16 --- /dev/null +++ b/apps/wr/app/map/searchBar/SearchInputText.tsx @@ -0,0 +1,124 @@ +// SearchInputText.tsx +"use client"; +import { Input } from "@common/components/ui"; +import { useEffect, useRef, useState } from "react"; + +interface SearchInputTextProps { + label?: string; + initValue?: string; + setValue: (val: string) => void; + setRtnData: (val: { bscFctNm: string; bscFctCd: string }) => void; + searchList: { bscFctNm: string; bscFctCd: string }[]; + onEnter?: () => void; + className?: string; +} + +export default function SearchInputText({ + label, + initValue, + setValue, + setRtnData, + searchList, + onEnter, + className, +}: SearchInputTextProps) { + const [showResults, setShowResults] = useState(false); + const [text, setText] = useState(initValue ?? ""); + const [highlightIndex, setHighlightIndex] = useState(-1); + const wrapperRef = useRef(null); + + useEffect(() => { + setText(initValue ?? ""); + }, [initValue]); + + useEffect(() => { + if (searchList.length > 0) { + setShowResults(true); + } else { + setShowResults(false); + } + setHighlightIndex(-1); + }, [searchList]); + + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if ( + wrapperRef.current && + !(wrapperRef.current as any).contains(event.target) + ) { + setShowResults(false); + } + } + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [wrapperRef]); + + const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { + setText(e.target.value); + setValue(e.target.value); + setHighlightIndex(-1); + }; + + const handleSelect = (item: { bscFctNm: string; bscFctCd: string }) => { + setText(item.bscFctNm); + setValue(item.bscFctNm); + setRtnData(item); + setShowResults(false); + }; + + const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { + if (e.key === "Enter") { + if (highlightIndex >= 0 && highlightIndex < searchList.length) { + const selected = searchList[highlightIndex]; + if (selected) handleSelect(selected); + } else if (onEnter) { + onEnter(); + } + } else if (e.key === "ArrowDown") { + e.preventDefault(); + setHighlightIndex((prev) => + prev < searchList.length - 1 ? prev + 1 : prev + ); + } else if (e.key === "ArrowUp") { + e.preventDefault(); + setHighlightIndex((prev) => (prev > 0 ? prev - 1 : -1)); + } + }; + + return ( + <div className="relative" ref={wrapperRef}> + <Input + value={text} + onChange={handleChange} + onKeyDown={handleKeyDown} + placeholder="시설명을 입력하세요" + className={ + className + " rounded-lg border px-2 py-1 text-sm h-[29.5px]" + } + /> + {showResults && ( + <div className="absolute z-10 mt-1 max-h-200 w-full overflow-auto rounded border bg-white shadow"> + {searchList.length > 0 ? ( + searchList.map((item, index) => ( + <div + key={index} + className={`cursor-pointer px-3 py-1 hover:bg-gray-100 ${ + highlightIndex === index ? "bg-blue-100" : "" + }`} + onClick={() => handleSelect(item)} + > + {item.bscFctNm} + </div> + )) + ) : ( + <div className="px-3 py-2 text-sm text-gray-500"> + 검색 결과가 없습니다. + </div> + )} + </div> + )} + </div> + ); +} diff --git a/apps/wr/app/map/searchBar/comboSearchBar.tsx b/apps/wr/app/map/searchBar/comboSearchBar.tsx new file mode 100644 index 000000000..7502daf94 --- /dev/null +++ b/apps/wr/app/map/searchBar/comboSearchBar.tsx @@ -0,0 +1,299 @@ +"use client"; +import { SelectBox, type SelectDataType } from "@common/business_components/ui"; +import { Button } from "@common/components/ui"; +import { useCallback, useEffect, useState } from "react"; +import { SERVER_API_WR_URL } from "@/src/shared/config"; + +const geoserverUrl = + "/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&outputFormat=application/json"; + +const FACILITY_TYPE_URL_MAP: Record<string, string> = { + tb_il_swage_prcs_fclt: geoserverUrl, //하수처리시설 + tb_wr_bdst_ruse_fclt: + "/reuse-service/reuseFc/pubDetailList/{year}/{sggCd}/1/1000", //재이용시설 post + tb_wr_rnwtr_utztn_fclt: "/reuse-service/reuseFc/rainyInfoList/1/1000/{sggCd}", //빗물이용시설 get + tb_wr_rswtr_fclt: "/reuse-service/reuseFc/waterreuseFc/1/1000/{sggCd}", //중수도시설 get + tb_wr_htdfl_fclt: "/reuse-service/drain/facilityList/1/1000/{sggCd}", //온배수시설 get + tb_wr_ruse_fclt_dmd_co: "/reuse-service/reuseFc/request/1/1000/{sggCd}", //수요처 get + tb_wr_tmpr_dmd_co: + "/reuse-service/reuseFc/request/temp/1/1000/{sidoNm} {sggNm}", //잠재수요처 get +}; + +interface SearchBarProps { + onFeatureSelect: (feature: any) => void; + gisMaps: React.RefObject<any>; + facilityTypes: { text: string; val: string }[]; +} + +export function SearchBar({ + onFeatureSelect, + gisMaps, + facilityTypes, +}: SearchBarProps) { + const [sidoValue, setSidoValue] = useState("0"); + const [sigunValue, setSigunValue] = useState("0"); + const [sidoData, setSidoData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + const [sigunData, setSigunData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + + const [facilityData, setFacilityData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + const [facilityValue, setFacilityValue] = useState("0"); + + const [facilityType, setFacilityType] = useState( + facilityTypes?.length === 1 && facilityTypes[0] ? facilityTypes[0].val : "0" + ); + + //시도, 시군구 조회 + const fetchSido = async () => { + const url = `/cms-service/api/v1/code/cache/ADSTDSTRCD/Y`; + const res = await fetch(url); + const json = await res.json(); + const sidoList = json + .filter( + (item: any) => + typeof item.lwpoCd === "string" && item.lwpoCd.length === 2 + ) + .map((feature: any) => ({ + val: feature.lwpoCd, + text: feature.lwpoCdNm, + })); + setSidoData([{ text: "선택", val: "0" }, ...sidoList]); + + const sigunList = json + .filter( + (item: any) => + typeof item.lwpoCd === "string" && item.lwpoCd.length !== 2 + ) + .map((feature: any) => ({ + val: feature.lwpoCd, + text: feature.lwpoCdNm, + })); + setSigunData(sigunList); + }; + + //시설명 콤보박스 조회 + const fetchFacilitiesBySggNm = async ( + sggCd: string, + sggNm: string, + sidoNm: string, + currentFacilityType?: string + ) => { + const type = currentFacilityType ?? facilityType; + if (!type || type === "0") return; + + const baseUrlTemplate = FACILITY_TYPE_URL_MAP[type]; + if (!baseUrlTemplate) return; + + const year = new Date().getFullYear().toString(); + let url = baseUrlTemplate + .replace("{year}", year) + .replace("{sggCd}", sggCd) + .replace("{sidoNm}", encodeURIComponent(sidoNm)) + .replace("{sggNm}", encodeURIComponent(sggNm)); + + let list: SelectDataType[] = []; + + if ( + type === "tb_il_swage_prcs_fclt" //하수처리시설 + ) { + const cqlFilter = `ctpv_nm='${encodeURIComponent(sidoNm)}' AND sgg_nm='${encodeURIComponent(sggNm)}'`; + url = `${url}&typeName=hasudo:${type}&cql_filter=${cqlFilter}`; + const res = await fetch(url); + const json = await res.json(); + list = json.features.map((f: any) => ({ + text: f.properties.swtrmt_fclt_nm ?? "(이름없음)", + val: f.id, + })); + } else if ( + type === "tb_wr_bdst_ruse_fclt" // 재이용시설 + ) { + //재용에서 참조하는 하수처리시설 목록 + const paramsRes = await fetch("/reuse-service/reuseFc/pubFc"); + const paramsJson = await paramsRes.json(); + const params = paramsJson.data; + + const res = await fetch(url, { + method: "POST", + headers: { + accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(params), + }); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.ruseUtztnFcltNm ?? "(이름없음)", + val: item.pbnstRuseFcltId, + })); + } 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 방식 + const res = await fetch(url); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.dmdCoNm ?? "(이름없음)", + val: item.fctryMngSn, + })); + } else { + // GET 방식 + const res = await fetch(url); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.bdstNm ?? "(이름없음)", + val: item.bdstInfoSn, + })); + } + + setFacilityData([{ text: "선택", val: "0" }, ...list]); + setFacilityValue("0"); + }; + + //시도, 시군구 초기 데이터 조회 + useEffect(() => { + fetchSido(); + }, []); + + //시군구 데이터 초기화 + const initializeSigunData = useCallback( + (sidoCd: string) => { + if (sidoCd === "0") { + setFilteredSigunData([{ text: "선택", val: "0" }]); + setSigunValue("0"); + return; + } + const prefix = sidoCd.substring(0, 2); + const filtered = sigunData.filter((item) => item.val.startsWith(prefix)); + setFilteredSigunData([{ text: "선택", val: "0" }, ...filtered]); + setSigunValue("0"); + }, + [sigunData] + ); + + //콤보박스 선택시 연관 콤보박스 처리 + const onSelectValue = useCallback( + (val: string, type: string) => { + if (type === "ctpvCd") { + setSidoValue(val); + initializeSigunData(val); + setFacilityData([{ text: "선택", val: "0" }]); + setFacilityValue("0"); + } else if (type === "sggCd") { + setSigunValue(val); + const sidoNm = + sidoData.find((item) => item.val === sidoValue)?.text || ""; + const sggNm = sigunData.find((item) => item.val === val)?.text || ""; + const sggCd = sigunData.find((item) => item.val === val)?.val || ""; + if (sggNm && sidoNm) + fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, facilityType); + } else if (type === "facilityType") { + setFacilityType(val); + setFacilityValue("0"); + setFacilityData([{ text: "선택", val: "0" }]); + const sidoNm = + sidoData.find((item) => item.val === sidoValue)?.text || ""; + const sggNm = + sigunData.find((item) => item.val === sigunValue)?.text || ""; + const sggCd = + sigunData.find((item) => item.val === sigunValue)?.val || ""; + if (sggNm && sidoNm) fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, val); + } else if (type === "fcltCd") { + setFacilityValue(val); + } + }, + [initializeSigunData, sigunData, sigunValue, sidoData, sidoValue] + ); + + //조회 버튼 클릭 + const handleSearch = () => { + if (!gisMaps.current || facilityValue === "0") return; + const layers = gisMaps.current.getLayers().getArray(); + for (const layer of layers) { + const source = layer.getSource?.(); + const feature = source?.getFeatureById?.(facilityValue); + if (feature) { + onFeatureSelect(feature); + return; + } + } + alert("해당 처리시설을 지도에서 찾을 수 없습니다."); + }; + + return ( + <div className="grid grid-cols-4 gap-4"> + <div className="col-span-4"> + <div className="card card-border flex"> + <div className="col-span-10 flex flex-row"> + {facilityTypes.length > 1 && ( + <SelectBox + labelClass="w-[120px]" + className="mr-10 w-[190px]" + selectClass="rounded-lg bg-white" + label="시설물유형" + selectArray={[{ text: "선택", val: "0" }, ...facilityTypes]} + onSelectValue={(val) => + onSelectValue(val.toString(), "facilityType") + } + value={facilityType} + /> + )} + <SelectBox + labelClass="w-[40px]" + className="mr-10 w-[150px]" + selectClass="rounded-lg bg-white" + label="시도" + selectArray={sidoData} + onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")} + value={sidoValue} + /> + <SelectBox + labelClass="w-[65px]" + className="mr-10 w-[150px]" + selectClass="rounded-lg bg-white" + label="시군구" + selectArray={filteredSigunData} + onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")} + value={sigunValue} + /> + <SelectBox + labelClass="w-[60px]" + className="mr-3 w-[180px]" + selectClass="rounded-lg bg-white" + label="시설명" + selectArray={facilityData} + onSelectValue={(val) => onSelectValue(val.toString(), "fcltCd")} + value={facilityValue} + /> + </div> + <div className="flex flex-row space-x-2"> + <Button + color="primary" + className="mx-2 mt-1 ml-5" + size={"sm"} + onClick={handleSearch} + > + 조회 + </Button> + </div> + </div> + </div> + </div> + ); +} diff --git a/apps/wr/app/map/searchBar/procSearchBar.tsx b/apps/wr/app/map/searchBar/procSearchBar.tsx new file mode 100644 index 000000000..7502daf94 --- /dev/null +++ b/apps/wr/app/map/searchBar/procSearchBar.tsx @@ -0,0 +1,299 @@ +"use client"; +import { SelectBox, type SelectDataType } from "@common/business_components/ui"; +import { Button } from "@common/components/ui"; +import { useCallback, useEffect, useState } from "react"; +import { SERVER_API_WR_URL } from "@/src/shared/config"; + +const geoserverUrl = + "/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&outputFormat=application/json"; + +const FACILITY_TYPE_URL_MAP: Record<string, string> = { + tb_il_swage_prcs_fclt: geoserverUrl, //하수처리시설 + tb_wr_bdst_ruse_fclt: + "/reuse-service/reuseFc/pubDetailList/{year}/{sggCd}/1/1000", //재이용시설 post + tb_wr_rnwtr_utztn_fclt: "/reuse-service/reuseFc/rainyInfoList/1/1000/{sggCd}", //빗물이용시설 get + tb_wr_rswtr_fclt: "/reuse-service/reuseFc/waterreuseFc/1/1000/{sggCd}", //중수도시설 get + tb_wr_htdfl_fclt: "/reuse-service/drain/facilityList/1/1000/{sggCd}", //온배수시설 get + tb_wr_ruse_fclt_dmd_co: "/reuse-service/reuseFc/request/1/1000/{sggCd}", //수요처 get + tb_wr_tmpr_dmd_co: + "/reuse-service/reuseFc/request/temp/1/1000/{sidoNm} {sggNm}", //잠재수요처 get +}; + +interface SearchBarProps { + onFeatureSelect: (feature: any) => void; + gisMaps: React.RefObject<any>; + facilityTypes: { text: string; val: string }[]; +} + +export function SearchBar({ + onFeatureSelect, + gisMaps, + facilityTypes, +}: SearchBarProps) { + const [sidoValue, setSidoValue] = useState("0"); + const [sigunValue, setSigunValue] = useState("0"); + const [sidoData, setSidoData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + const [sigunData, setSigunData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + + const [facilityData, setFacilityData] = useState<SelectDataType[]>([ + { text: "선택", val: "0" }, + ]); + const [facilityValue, setFacilityValue] = useState("0"); + + const [facilityType, setFacilityType] = useState( + facilityTypes?.length === 1 && facilityTypes[0] ? facilityTypes[0].val : "0" + ); + + //시도, 시군구 조회 + const fetchSido = async () => { + const url = `/cms-service/api/v1/code/cache/ADSTDSTRCD/Y`; + const res = await fetch(url); + const json = await res.json(); + const sidoList = json + .filter( + (item: any) => + typeof item.lwpoCd === "string" && item.lwpoCd.length === 2 + ) + .map((feature: any) => ({ + val: feature.lwpoCd, + text: feature.lwpoCdNm, + })); + setSidoData([{ text: "선택", val: "0" }, ...sidoList]); + + const sigunList = json + .filter( + (item: any) => + typeof item.lwpoCd === "string" && item.lwpoCd.length !== 2 + ) + .map((feature: any) => ({ + val: feature.lwpoCd, + text: feature.lwpoCdNm, + })); + setSigunData(sigunList); + }; + + //시설명 콤보박스 조회 + const fetchFacilitiesBySggNm = async ( + sggCd: string, + sggNm: string, + sidoNm: string, + currentFacilityType?: string + ) => { + const type = currentFacilityType ?? facilityType; + if (!type || type === "0") return; + + const baseUrlTemplate = FACILITY_TYPE_URL_MAP[type]; + if (!baseUrlTemplate) return; + + const year = new Date().getFullYear().toString(); + let url = baseUrlTemplate + .replace("{year}", year) + .replace("{sggCd}", sggCd) + .replace("{sidoNm}", encodeURIComponent(sidoNm)) + .replace("{sggNm}", encodeURIComponent(sggNm)); + + let list: SelectDataType[] = []; + + if ( + type === "tb_il_swage_prcs_fclt" //하수처리시설 + ) { + const cqlFilter = `ctpv_nm='${encodeURIComponent(sidoNm)}' AND sgg_nm='${encodeURIComponent(sggNm)}'`; + url = `${url}&typeName=hasudo:${type}&cql_filter=${cqlFilter}`; + const res = await fetch(url); + const json = await res.json(); + list = json.features.map((f: any) => ({ + text: f.properties.swtrmt_fclt_nm ?? "(이름없음)", + val: f.id, + })); + } else if ( + type === "tb_wr_bdst_ruse_fclt" // 재이용시설 + ) { + //재용에서 참조하는 하수처리시설 목록 + const paramsRes = await fetch("/reuse-service/reuseFc/pubFc"); + const paramsJson = await paramsRes.json(); + const params = paramsJson.data; + + const res = await fetch(url, { + method: "POST", + headers: { + accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(params), + }); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.ruseUtztnFcltNm ?? "(이름없음)", + val: item.pbnstRuseFcltId, + })); + } 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 방식 + const res = await fetch(url); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.dmdCoNm ?? "(이름없음)", + val: item.fctryMngSn, + })); + } else { + // GET 방식 + const res = await fetch(url); + const json = await res.json(); + list = (json.data || []).map((item: any) => ({ + text: item.bdstNm ?? "(이름없음)", + val: item.bdstInfoSn, + })); + } + + setFacilityData([{ text: "선택", val: "0" }, ...list]); + setFacilityValue("0"); + }; + + //시도, 시군구 초기 데이터 조회 + useEffect(() => { + fetchSido(); + }, []); + + //시군구 데이터 초기화 + const initializeSigunData = useCallback( + (sidoCd: string) => { + if (sidoCd === "0") { + setFilteredSigunData([{ text: "선택", val: "0" }]); + setSigunValue("0"); + return; + } + const prefix = sidoCd.substring(0, 2); + const filtered = sigunData.filter((item) => item.val.startsWith(prefix)); + setFilteredSigunData([{ text: "선택", val: "0" }, ...filtered]); + setSigunValue("0"); + }, + [sigunData] + ); + + //콤보박스 선택시 연관 콤보박스 처리 + const onSelectValue = useCallback( + (val: string, type: string) => { + if (type === "ctpvCd") { + setSidoValue(val); + initializeSigunData(val); + setFacilityData([{ text: "선택", val: "0" }]); + setFacilityValue("0"); + } else if (type === "sggCd") { + setSigunValue(val); + const sidoNm = + sidoData.find((item) => item.val === sidoValue)?.text || ""; + const sggNm = sigunData.find((item) => item.val === val)?.text || ""; + const sggCd = sigunData.find((item) => item.val === val)?.val || ""; + if (sggNm && sidoNm) + fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, facilityType); + } else if (type === "facilityType") { + setFacilityType(val); + setFacilityValue("0"); + setFacilityData([{ text: "선택", val: "0" }]); + const sidoNm = + sidoData.find((item) => item.val === sidoValue)?.text || ""; + const sggNm = + sigunData.find((item) => item.val === sigunValue)?.text || ""; + const sggCd = + sigunData.find((item) => item.val === sigunValue)?.val || ""; + if (sggNm && sidoNm) fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, val); + } else if (type === "fcltCd") { + setFacilityValue(val); + } + }, + [initializeSigunData, sigunData, sigunValue, sidoData, sidoValue] + ); + + //조회 버튼 클릭 + const handleSearch = () => { + if (!gisMaps.current || facilityValue === "0") return; + const layers = gisMaps.current.getLayers().getArray(); + for (const layer of layers) { + const source = layer.getSource?.(); + const feature = source?.getFeatureById?.(facilityValue); + if (feature) { + onFeatureSelect(feature); + return; + } + } + alert("해당 처리시설을 지도에서 찾을 수 없습니다."); + }; + + return ( + <div className="grid grid-cols-4 gap-4"> + <div className="col-span-4"> + <div className="card card-border flex"> + <div className="col-span-10 flex flex-row"> + {facilityTypes.length > 1 && ( + <SelectBox + labelClass="w-[120px]" + className="mr-10 w-[190px]" + selectClass="rounded-lg bg-white" + label="시설물유형" + selectArray={[{ text: "선택", val: "0" }, ...facilityTypes]} + onSelectValue={(val) => + onSelectValue(val.toString(), "facilityType") + } + value={facilityType} + /> + )} + <SelectBox + labelClass="w-[40px]" + className="mr-10 w-[150px]" + selectClass="rounded-lg bg-white" + label="시도" + selectArray={sidoData} + onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")} + value={sidoValue} + /> + <SelectBox + labelClass="w-[65px]" + className="mr-10 w-[150px]" + selectClass="rounded-lg bg-white" + label="시군구" + selectArray={filteredSigunData} + onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")} + value={sigunValue} + /> + <SelectBox + labelClass="w-[60px]" + className="mr-3 w-[180px]" + selectClass="rounded-lg bg-white" + label="시설명" + selectArray={facilityData} + onSelectValue={(val) => onSelectValue(val.toString(), "fcltCd")} + value={facilityValue} + /> + </div> + <div className="flex flex-row space-x-2"> + <Button + color="primary" + className="mx-2 mt-1 ml-5" + size={"sm"} + onClick={handleSearch} + > + 조회 + </Button> + </div> + </div> + </div> + </div> + ); +} diff --git a/apps/wr/app/map/searchBar/searchBar.tsx b/apps/wr/app/map/searchBar/searchBar.tsx index 7502daf94..7511169b4 100644 --- a/apps/wr/app/map/searchBar/searchBar.tsx +++ b/apps/wr/app/map/searchBar/searchBar.tsx @@ -1,22 +1,25 @@ +// SearchBar.tsx "use client"; import { SelectBox, type SelectDataType } from "@common/business_components/ui"; import { Button } from "@common/components/ui"; import { useCallback, useEffect, useState } from "react"; -import { SERVER_API_WR_URL } from "@/src/shared/config"; - -const geoserverUrl = - "/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&outputFormat=application/json"; +import SearchInputText from "./SearchInputText"; const FACILITY_TYPE_URL_MAP: Record<string, string> = { - tb_il_swage_prcs_fclt: geoserverUrl, //하수처리시설 + tb_il_swage_prcs_fclt: + "/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_il_swage_prcs_fclt&outputFormat=application/json&propertyName=swtrmt_fclt_cd,swtrmt_fclt_nm&cql_filter=", tb_wr_bdst_ruse_fclt: - "/reuse-service/reuseFc/pubDetailList/{year}/{sggCd}/1/1000", //재이용시설 post - tb_wr_rnwtr_utztn_fclt: "/reuse-service/reuseFc/rainyInfoList/1/1000/{sggCd}", //빗물이용시설 get - tb_wr_rswtr_fclt: "/reuse-service/reuseFc/waterreuseFc/1/1000/{sggCd}", //중수도시설 get - tb_wr_htdfl_fclt: "/reuse-service/drain/facilityList/1/1000/{sggCd}", //온배수시설 get - tb_wr_ruse_fclt_dmd_co: "/reuse-service/reuseFc/request/1/1000/{sggCd}", //수요처 get + "/reuse-service/reuseFc/pubDetailList/{year}/{sggCd}/1/1000/", + tb_wr_rnwtr_utztn_fclt: + "/reuse-service/reuseFc/rainyInfoList/1/1000/{sggCd}/{fcltyNm}", + tb_wr_rswtr_fclt: + "/reuse-service/reuseFc/waterreuseFc/1/1000/{sggCd}/{fcltyNm}", + tb_wr_htdfl_fclt: + "/reuse-service/drain/facilityList/1/1000/{sggCd}/{fcltyNm}", + tb_wr_ruse_fclt_dmd_co: + "/reuse-service/reuseFc/request/1/1000/{sggCd}/{fcltyNm}", tb_wr_tmpr_dmd_co: - "/reuse-service/reuseFc/request/temp/1/1000/{sidoNm} {sggNm}", //잠재수요처 get + "/reuse-service/reuseFc/request/temp/1/1000/{sidoNm} {sggNm}/{fcltyNm}", }; interface SearchBarProps { @@ -32,80 +35,100 @@ export function SearchBar({ }: SearchBarProps) { const [sidoValue, setSidoValue] = useState("0"); const [sigunValue, setSigunValue] = useState("0"); - const [sidoData, setSidoData] = useState<SelectDataType[]>([ - { text: "선택", val: "0" }, - ]); - const [sigunData, setSigunData] = useState<SelectDataType[]>([ - { text: "선택", val: "0" }, - ]); - const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([ - { text: "선택", val: "0" }, - ]); - - const [facilityData, setFacilityData] = useState<SelectDataType[]>([ - { text: "선택", val: "0" }, - ]); - const [facilityValue, setFacilityValue] = useState("0"); - const [facilityType, setFacilityType] = useState( facilityTypes?.length === 1 && facilityTypes[0] ? facilityTypes[0].val : "0" ); + const [fcltyNm, setFcltyNm] = useState(""); + const [searchList, setSearchList] = useState< + { bscFctNm: string; bscFctCd: string }[] + >([]); + + const [sidoData, setSidoData] = useState<SelectDataType[]>([ + { text: "시도", val: "0" }, + ]); + const [sigunData, setSigunData] = useState<SelectDataType[]>([ + { text: "시군구", val: "0" }, + ]); + const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([ + { text: "시군구", val: "0" }, + ]); - //시도, 시군구 조회 const fetchSido = async () => { - const url = `/cms-service/api/v1/code/cache/ADSTDSTRCD/Y`; - const res = await fetch(url); + const res = await fetch("/cms-service/api/v1/code/cache/ADSTDSTRCD/Y"); const json = await res.json(); const sidoList = json - .filter( - (item: any) => - typeof item.lwpoCd === "string" && item.lwpoCd.length === 2 - ) - .map((feature: any) => ({ - val: feature.lwpoCd, - text: feature.lwpoCdNm, - })); - setSidoData([{ text: "선택", val: "0" }, ...sidoList]); - + .filter((item: any) => item.lwpoCd?.length === 2) + .map((item: any) => ({ val: item.lwpoCd, text: item.lwpoCdNm })); const sigunList = json - .filter( - (item: any) => - typeof item.lwpoCd === "string" && item.lwpoCd.length !== 2 - ) - .map((feature: any) => ({ - val: feature.lwpoCd, - text: feature.lwpoCdNm, - })); + .filter((item: any) => item.lwpoCd?.length > 2) + .map((item: any) => ({ val: item.lwpoCd, text: item.lwpoCdNm })); + setSidoData([{ text: "시도", val: "0" }, ...sidoList]); setSigunData(sigunList); }; - //시설명 콤보박스 조회 - const fetchFacilitiesBySggNm = async ( - sggCd: string, - sggNm: string, - sidoNm: string, - currentFacilityType?: string - ) => { - const type = currentFacilityType ?? facilityType; - if (!type || type === "0") return; + const initializeSigunData = useCallback( + (sidoCd: string) => { + if (sidoCd === "0") { + setFilteredSigunData([{ text: "시도", val: "0" }]); + setSigunValue("0"); + return; + } + const prefix = sidoCd.substring(0, 2); + const filtered = sigunData.filter((item) => item.val.startsWith(prefix)); + setFilteredSigunData([{ text: "시군구", val: "0" }, ...filtered]); + setSigunValue("0"); + }, + [sigunData] + ); + useEffect(() => { + fetchSido(); + }, []); + + const onSelectValue = useCallback( + (val: string, type: string) => { + if (type === "ctpvCd") { + setSidoValue(val); + initializeSigunData(val); + setSearchList([]); + setFcltyNm(""); + } else if (type === "sggCd") { + setSigunValue(val); + setSearchList([]); + setFcltyNm(""); + } else if (type === "facilityType") { + setFacilityType(val); + setSearchList([]); + setFcltyNm(""); + } + }, + [initializeSigunData] + ); + + const handleSearch = async () => { + if (!fcltyNm) return alert("시설명을 입력하세요."); + + const sidoNm = sidoData.find((item) => item.val === sidoValue)?.text || ""; + const sggNm = sigunData.find((item) => item.val === sigunValue)?.text || ""; + + const type = facilityType; const baseUrlTemplate = FACILITY_TYPE_URL_MAP[type]; if (!baseUrlTemplate) return; + let url = baseUrlTemplate; const year = new Date().getFullYear().toString(); - let url = baseUrlTemplate + url = url .replace("{year}", year) - .replace("{sggCd}", sggCd) + .replace("{sggCd}", sigunValue) .replace("{sidoNm}", encodeURIComponent(sidoNm)) - .replace("{sggNm}", encodeURIComponent(sggNm)); + .replace("{sggNm}", encodeURIComponent(sggNm)) + .replace("{fcltyNm}", encodeURIComponent(fcltyNm)); let list: SelectDataType[] = []; - if ( - type === "tb_il_swage_prcs_fclt" //하수처리시설 - ) { - const cqlFilter = `ctpv_nm='${encodeURIComponent(sidoNm)}' AND sgg_nm='${encodeURIComponent(sggNm)}'`; - url = `${url}&typeName=hasudo:${type}&cql_filter=${cqlFilter}`; + if (type === "tb_il_swage_prcs_fclt") { + const cqlFilter = `ctpv_nm='${sidoNm}' and sgg_nm='${sggNm}' and swtrmt_fclt_nm like '%${fcltyNm}%'`; + url += encodeURIComponent(cqlFilter); const res = await fetch(url); const json = await res.json(); list = json.features.map((f: any) => ({ @@ -115,18 +138,43 @@ export function SearchBar({ } else if ( type === "tb_wr_bdst_ruse_fclt" // 재이용시설 ) { - //재용에서 참조하는 하수처리시설 목록 - const paramsRes = await fetch("/reuse-service/reuseFc/pubFc"); - const paramsJson = await paramsRes.json(); - const params = paramsJson.data; + //재이용에서 참조하는 하수처리시설 목록 + const procRes = await fetch("/reuse-service/reuseFc/pubFc"); + const procJson = await procRes.json(); + const procData = procJson.data; + const procResult = procData + .filter((code: string) => code.slice(0, 5) === sigunValue) + .map((code: string) => { + return code; + }); + //처리시설명으로 처리시설코드 조회 + let procUrl2 = + "/operation-service/prcsFclt/ruseInfo/1/1000/A/{sggCd}/0/1/SW/{fcltyNm}"; + procUrl2 = procUrl2 + .replace("{sggCd}", sigunValue) + .replace("{fcltyNm}", encodeURIComponent(fcltyNm)); + const procRes2 = await fetch(procUrl2, { + method: "POST", + headers: { + accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(procResult), + }); + const procJson2 = await procRes2.json(); + const procResult2 = (procJson2.data || []).map( + (item: any) => item.bscFctCd + ); + + //최종 처리시설코드로 재이용시설조회 const res = await fetch(url, { method: "POST", headers: { accept: "application/json", "Content-Type": "application/json", }, - body: JSON.stringify(params), + body: JSON.stringify(procResult2), }); const json = await res.json(); list = (json.data || []).map((item: any) => ({ @@ -162,78 +210,19 @@ export function SearchBar({ })); } - setFacilityData([{ text: "선택", val: "0" }, ...list]); - setFacilityValue("0"); - }; - - //시도, 시군구 초기 데이터 조회 - useEffect(() => { - fetchSido(); - }, []); - - //시군구 데이터 초기화 - const initializeSigunData = useCallback( - (sidoCd: string) => { - if (sidoCd === "0") { - setFilteredSigunData([{ text: "선택", val: "0" }]); - setSigunValue("0"); - return; - } - const prefix = sidoCd.substring(0, 2); - const filtered = sigunData.filter((item) => item.val.startsWith(prefix)); - setFilteredSigunData([{ text: "선택", val: "0" }, ...filtered]); - setSigunValue("0"); - }, - [sigunData] - ); - - //콤보박스 선택시 연관 콤보박스 처리 - const onSelectValue = useCallback( - (val: string, type: string) => { - if (type === "ctpvCd") { - setSidoValue(val); - initializeSigunData(val); - setFacilityData([{ text: "선택", val: "0" }]); - setFacilityValue("0"); - } else if (type === "sggCd") { - setSigunValue(val); - const sidoNm = - sidoData.find((item) => item.val === sidoValue)?.text || ""; - const sggNm = sigunData.find((item) => item.val === val)?.text || ""; - const sggCd = sigunData.find((item) => item.val === val)?.val || ""; - if (sggNm && sidoNm) - fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, facilityType); - } else if (type === "facilityType") { - setFacilityType(val); - setFacilityValue("0"); - setFacilityData([{ text: "선택", val: "0" }]); - const sidoNm = - sidoData.find((item) => item.val === sidoValue)?.text || ""; - const sggNm = - sigunData.find((item) => item.val === sigunValue)?.text || ""; - const sggCd = - sigunData.find((item) => item.val === sigunValue)?.val || ""; - if (sggNm && sidoNm) fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, val); - } else if (type === "fcltCd") { - setFacilityValue(val); - } - }, - [initializeSigunData, sigunData, sigunValue, sidoData, sidoValue] - ); - - //조회 버튼 클릭 - const handleSearch = () => { - if (!gisMaps.current || facilityValue === "0") return; - const layers = gisMaps.current.getLayers().getArray(); - for (const layer of layers) { - const source = layer.getSource?.(); - const feature = source?.getFeatureById?.(facilityValue); - if (feature) { - onFeatureSelect(feature); - return; - } + if (list.length === 0) { + alert("검색 결과가 없습니다."); + setSearchList([]); + setFcltyNm(""); + return; } - alert("해당 처리시설을 지도에서 찾을 수 없습니다."); + + const newList = list.map((item: any) => { + const code = item.val; + const name = item.text; + return { bscFctNm: name, bscFctCd: code }; + }); + setSearchList(newList); }; return ( @@ -244,10 +233,13 @@ export function SearchBar({ {facilityTypes.length > 1 && ( <SelectBox labelClass="w-[120px]" - className="mr-10 w-[190px]" + className="mr-5 w-[150px]" selectClass="rounded-lg bg-white" - label="시설물유형" - selectArray={[{ text: "선택", val: "0" }, ...facilityTypes]} + label="" + selectArray={[ + { text: "시설 구분", val: "0" }, + ...facilityTypes, + ]} onSelectValue={(val) => onSelectValue(val.toString(), "facilityType") } @@ -256,37 +248,49 @@ export function SearchBar({ )} <SelectBox labelClass="w-[40px]" - className="mr-10 w-[150px]" + className="mr-5 w-[150px]" selectClass="rounded-lg bg-white" - label="시도" + label="" selectArray={sidoData} onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")} value={sidoValue} /> <SelectBox labelClass="w-[65px]" - className="mr-10 w-[150px]" + className="mr-5 w-[150px]" selectClass="rounded-lg bg-white" - label="시군구" + label="" selectArray={filteredSigunData} onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")} value={sigunValue} /> - <SelectBox - labelClass="w-[60px]" - className="mr-3 w-[180px]" - selectClass="rounded-lg bg-white" + <SearchInputText label="시설명" - selectArray={facilityData} - onSelectValue={(val) => onSelectValue(val.toString(), "fcltCd")} - value={facilityValue} + initValue={fcltyNm} + setValue={(val: string = "") => { + setFcltyNm(val); + }} + setRtnData={(selected: { + bscFctNm: string; + bscFctCd: string; + }) => { + // 선택된 항목이 있을 경우 onFeatureSelect 실행 + const type = facilityType; + onFeatureSelect({ + layerNm: type, + ...selected, + }); + }} + searchList={searchList} + onEnter={handleSearch} + className="mr-5 w-[150px]" /> </div> <div className="flex flex-row space-x-2"> <Button color="primary" - className="mx-2 mt-1 ml-5" - size={"sm"} + className="mx-2 mt-1" + size="sm" onClick={handleSearch} > 조회 diff --git a/apps/wr/middleware.ts b/apps/wr/middleware.ts index 1a90a5591..d068f6aa5 100644 --- a/apps/wr/middleware.ts +++ b/apps/wr/middleware.ts @@ -9,6 +9,7 @@ const GEOSERVER_URL = process.env.NEXT_PUBLIC_GEOSERVER_URL || ""; const LX_GEOSERVER_URL = process.env.NEXT_PUBLIC_LX_GEOSERVER_URL || ""; const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ""; const REUSE_API_URL = process.env.NEXT_PUBLIC_WR_SERVER_API_URL || ""; +const OPERATION_API_URL = process.env.NEXT_PUBLIC_FO_SERVER_API_URL || ""; export function middleware(req: NextRequest) { const pathname = req.nextUrl.pathname; @@ -145,6 +146,15 @@ export function middleware(req: NextRequest) { return NextResponse.rewrite(new URL(newUrl)); } + if (pathname.startsWith("/operation-service")) { + const queryParams = searchParams.toString(); + const newUrl = `${OPERATION_API_URL}${pathname.replace("/operation-service", "")}${ + queryParams ? `?${queryParams}` : "" + }`; + console.log(`[Middleware] Rewriting from '/operation-service' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl)); + } + return NextResponse.next(); } @@ -157,6 +167,7 @@ export const config = { "/geoserver/:path*", "/sewerage-service/:path*", "/reuse-service/:path*", + "/operation-service/:path*", "/" ], }; From 7a9d568021fe6b55a1ef9d0a0633c48e72098218 Mon Sep 17 00:00:00 2001 From: yschoi <yschoi> Date: Mon, 4 Aug 2025 08:57:13 +0900 Subject: [PATCH 70/71] =?UTF-8?q?gis=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/dm/app/(popup-layout)/map/popup/page.tsx | 1 + apps/fo/app/pages/map/page.tsx | 1 + apps/sn/app/(map-layout)/map/page.tsx | 1 + apps/wr/app/map/mainMap/page.tsx | 1 + 4 files changed, 4 insertions(+) diff --git a/apps/dm/app/(popup-layout)/map/popup/page.tsx b/apps/dm/app/(popup-layout)/map/popup/page.tsx index c0d38a5c5..8547c7ea8 100644 --- a/apps/dm/app/(popup-layout)/map/popup/page.tsx +++ b/apps/dm/app/(popup-layout)/map/popup/page.tsx @@ -260,6 +260,7 @@ const Map = () => { {...props} /> )} + waterDepth={undefined} /> </div> <SelectedFcltList diff --git a/apps/fo/app/pages/map/page.tsx b/apps/fo/app/pages/map/page.tsx index edd7b70d2..d1dca94ae 100644 --- a/apps/fo/app/pages/map/page.tsx +++ b/apps/fo/app/pages/map/page.tsx @@ -143,6 +143,7 @@ const Map = () => { onCheckedLayersChange={undefined} imgVisble={false} searchBar={(props:any) => <SearchBar onFeatureSelect={onFeatureSelect} gisMaps={gisMaps} />} + waterDepth={undefined} /> </div> ); diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index 6105bc818..b45b41936 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -138,6 +138,7 @@ const Map = () => { onCheckedLayersChange={undefined} searchBar={SearchBar} // waterDepth={WaterDepth} + waterDepth={undefined} /> </div> ); diff --git a/apps/wr/app/map/mainMap/page.tsx b/apps/wr/app/map/mainMap/page.tsx index feed1f6ab..4a488201c 100644 --- a/apps/wr/app/map/mainMap/page.tsx +++ b/apps/wr/app/map/mainMap/page.tsx @@ -179,6 +179,7 @@ const Map = () => { /> ); }} + waterDepth={undefined} /> </div> ); From 4a1cb73c50d24cf8c09ddcd9a2ead1c1c730001a Mon Sep 17 00:00:00 2001 From: yschoi <yschoi> Date: Mon, 4 Aug 2025 10:29:37 +0900 Subject: [PATCH 71/71] =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/fo/app/pages/map/page.tsx | 2 +- apps/fo/app/pages/map/searchBar/searchBar.tsx | 1 - apps/wr/app/map/mainMap/page.tsx | 3 +-- apps/wr/app/map/popup/page.tsx | 1 + apps/wr/app/map/searchBar/SearchInputText.tsx | 2 -- apps/wr/app/map/searchBar/comboSearchBar.tsx | 1 - apps/wr/app/map/searchBar/procSearchBar.tsx | 1 - apps/wr/app/map/searchBar/searchBar.tsx | 5 ++--- 8 files changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/fo/app/pages/map/page.tsx b/apps/fo/app/pages/map/page.tsx index d1dca94ae..a388d80ca 100644 --- a/apps/fo/app/pages/map/page.tsx +++ b/apps/fo/app/pages/map/page.tsx @@ -142,7 +142,7 @@ const Map = () => { isSearchModalOpen={isSearchModalOpen} onCheckedLayersChange={undefined} imgVisble={false} - searchBar={(props:any) => <SearchBar onFeatureSelect={onFeatureSelect} gisMaps={gisMaps} />} + searchBar={() => <SearchBar onFeatureSelect={onFeatureSelect} gisMaps={gisMaps} />} waterDepth={undefined} /> </div> diff --git a/apps/fo/app/pages/map/searchBar/searchBar.tsx b/apps/fo/app/pages/map/searchBar/searchBar.tsx index e9084870a..0b1315465 100644 --- a/apps/fo/app/pages/map/searchBar/searchBar.tsx +++ b/apps/fo/app/pages/map/searchBar/searchBar.tsx @@ -2,7 +2,6 @@ import { SelectBox, type SelectDataType } from "@common/business_components/ui"; import { Button } from "@common/components/ui"; import { useCallback, useEffect, useState } from "react"; -import { GEOSERVER_URL } from "@/src/shared/config/env"; interface SearchBarProps { onFeatureSelect: (feature: any) => void; diff --git a/apps/wr/app/map/mainMap/page.tsx b/apps/wr/app/map/mainMap/page.tsx index 4a488201c..7b1d46c88 100644 --- a/apps/wr/app/map/mainMap/page.tsx +++ b/apps/wr/app/map/mainMap/page.tsx @@ -161,7 +161,7 @@ const Map = () => { isSearchModalOpen={isSearchModalOpen} onCheckedLayersChange={handleCheckedLayersChange} imgVisble={true} - searchBar={(props: any) => { + searchBar={() => { const facilityTypes = [ { text: "하수처리시설", val: "tb_il_swage_prcs_fclt" }, { text: "하수재이용시설", val: "tb_wr_bdst_ruse_fclt" }, @@ -174,7 +174,6 @@ const Map = () => { return ( <SearchBar onFeatureSelect={onFeatureSelect} - gisMaps={gisMaps} facilityTypes={facilityTypes} /> ); diff --git a/apps/wr/app/map/popup/page.tsx b/apps/wr/app/map/popup/page.tsx index 6c1cc47d0..c7e091bb4 100644 --- a/apps/wr/app/map/popup/page.tsx +++ b/apps/wr/app/map/popup/page.tsx @@ -141,6 +141,7 @@ const Map = () => { onCheckedLayersChange={handleCheckedLayersChange} imgVisble={true} searchBar={undefined} + waterDepth={undefined} /> </div> ); diff --git a/apps/wr/app/map/searchBar/SearchInputText.tsx b/apps/wr/app/map/searchBar/SearchInputText.tsx index 1df92fd16..caf4c9f2a 100644 --- a/apps/wr/app/map/searchBar/SearchInputText.tsx +++ b/apps/wr/app/map/searchBar/SearchInputText.tsx @@ -4,7 +4,6 @@ import { Input } from "@common/components/ui"; import { useEffect, useRef, useState } from "react"; interface SearchInputTextProps { - label?: string; initValue?: string; setValue: (val: string) => void; setRtnData: (val: { bscFctNm: string; bscFctCd: string }) => void; @@ -14,7 +13,6 @@ interface SearchInputTextProps { } export default function SearchInputText({ - label, initValue, setValue, setRtnData, diff --git a/apps/wr/app/map/searchBar/comboSearchBar.tsx b/apps/wr/app/map/searchBar/comboSearchBar.tsx index 7502daf94..15736d710 100644 --- a/apps/wr/app/map/searchBar/comboSearchBar.tsx +++ b/apps/wr/app/map/searchBar/comboSearchBar.tsx @@ -2,7 +2,6 @@ import { SelectBox, type SelectDataType } from "@common/business_components/ui"; import { Button } from "@common/components/ui"; import { useCallback, useEffect, useState } from "react"; -import { SERVER_API_WR_URL } from "@/src/shared/config"; const geoserverUrl = "/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&outputFormat=application/json"; diff --git a/apps/wr/app/map/searchBar/procSearchBar.tsx b/apps/wr/app/map/searchBar/procSearchBar.tsx index 7502daf94..15736d710 100644 --- a/apps/wr/app/map/searchBar/procSearchBar.tsx +++ b/apps/wr/app/map/searchBar/procSearchBar.tsx @@ -2,7 +2,6 @@ import { SelectBox, type SelectDataType } from "@common/business_components/ui"; import { Button } from "@common/components/ui"; import { useCallback, useEffect, useState } from "react"; -import { SERVER_API_WR_URL } from "@/src/shared/config"; const geoserverUrl = "/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&outputFormat=application/json"; diff --git a/apps/wr/app/map/searchBar/searchBar.tsx b/apps/wr/app/map/searchBar/searchBar.tsx index 7511169b4..7760ee0b8 100644 --- a/apps/wr/app/map/searchBar/searchBar.tsx +++ b/apps/wr/app/map/searchBar/searchBar.tsx @@ -24,13 +24,13 @@ const FACILITY_TYPE_URL_MAP: Record<string, string> = { interface SearchBarProps { onFeatureSelect: (feature: any) => void; - gisMaps: React.RefObject<any>; + // gisMaps: React.RefObject<any>; facilityTypes: { text: string; val: string }[]; } export function SearchBar({ onFeatureSelect, - gisMaps, + // gisMaps, facilityTypes, }: SearchBarProps) { const [sidoValue, setSidoValue] = useState("0"); @@ -265,7 +265,6 @@ export function SearchBar({ value={sigunValue} /> <SearchInputText - label="시설명" initValue={fcltyNm} setValue={(val: string = "") => { setFcltyNm(val);