From c6838839e67457ff45f9ed28c4b987fee5c66091 Mon Sep 17 00:00:00 2001 From: ljpark Date: Wed, 8 Apr 2026 16:01:13 +0900 Subject: [PATCH 01/18] =?UTF-8?q?=EC=9A=B4=EC=98=81=EC=97=90=EB=8A=94=20?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=EC=97=90=20=EC=84=A4=EC=A0=95=ED=96=88?= =?UTF-8?q?=EC=9D=8C.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/fo/.env.production | 1 - apps/sn/.env.production | 1 - apps/wr/.env.production | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/fo/.env.production b/apps/fo/.env.production index bd0d0f04b..d6940b0e9 100644 --- a/apps/fo/.env.production +++ b/apps/fo/.env.production @@ -17,4 +17,3 @@ NEXT_PUBLIC_DC_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/dc-s NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service NEXT_PUBLIC_NGII_URL = http://172.29.143.87/poiact NEXT_PUBLIC_NGII_PARAM = BDE2BEF39DAFEC293AB6730150F222AC&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={y}&tilerow={x} -NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login \ No newline at end of file diff --git a/apps/sn/.env.production b/apps/sn/.env.production index 4a7c15edc..0b8189a78 100644 --- a/apps/sn/.env.production +++ b/apps/sn/.env.production @@ -15,4 +15,3 @@ NEXT_PUBLIC_NGII_URL = http://172.29.143.87/poiact NEXT_PUBLIC_NGII_PARAM = BDE2BEF39DAFEC293AB6730150F222AC&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={y}&tilerow={x} NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 NEXT_PUBLIC_DC_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 -NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login diff --git a/apps/wr/.env.production b/apps/wr/.env.production index a159d1aa4..934f1e6a5 100644 --- a/apps/wr/.env.production +++ b/apps/wr/.env.production @@ -20,5 +20,4 @@ NEXT_PUBLIC_NGII_PARAM = BDE2BEF39DAFEC293AB6730150F222AC&service=WMTS&request=G NEXT_PUBLIC_HASUDOINFO = https://www.hasudoinfo.or.kr NEXT_PUBLIC_HASUDOINFO_IN = http://10.101.163.102:8889 NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do -NEXT_PRIVATE_WR_ADDRESS_KEY = U01TXOFVVEgyMDI1MDgyODE0MTAxNzExNjEyMjA= -NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login \ No newline at end of file +NEXT_PRIVATE_WR_ADDRESS_KEY = U01TXOFVVEgyMDI1MDgyODE0MTAxNzExNjEyMjA= \ No newline at end of file From c5df40a9ef49629dbf7041ca4c4abfd5fbf00621 Mon Sep 17 00:00:00 2001 From: ljpark Date: Thu, 9 Apr 2026 18:12:09 +0900 Subject: [PATCH 02/18] =?UTF-8?q?wr:=20=EC=A7=80=EB=8F=84=EC=A0=90?= =?UTF-8?q?=EA=B2=80=EA=B2=B0=EA=B3=BC=20=EC=A4=91=EC=88=98=EB=8F=84=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 --- .../ruseFcltMng/prfmncReg/chkgdnReg/page.tsx | 97 ++++------- apps/wr/app/store/common/code/index.ts | 158 ++++++++++++------ .../prfmncReg/chkgdnReg/rswrtChkgdn/excel.tsx | 116 ++++++------- .../prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx | 28 +--- apps/wr/components/lib/func/TableData.tsx | 1 + .../src/ui/excel/index.tsx | 2 +- 6 files changed, 196 insertions(+), 206 deletions(-) diff --git a/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx b/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx index ba0d34f44..5575a1860 100644 --- a/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx +++ b/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx @@ -65,7 +65,8 @@ export default function Page() { rswtChkDelete, // 중수도시설 지도점검 선택 건 삭제 rswtrChkExcelDownload, // 중수도시설 지도점검 엑셀 양식/데이터 조회 rswtChkHeadChange, // 중수도시설 테이블 헤더 선택 상태 변경 - getLaeSeCdCheck // 사용자 관할 기관 코드 조회 + getLaeSeCdCheck, // 사용자 관할 기관 코드 조회 + getCommonCodesByKeys, // 지도점검 화면에서 필요한 공통코드 보강 조회 } = useWrStore((state) => { return state; }); @@ -93,8 +94,15 @@ export default function Page() { useEffect(() => { getLaeSeCdCheck(); + const hasRequiredCodes = + code.rainRswtMng.length > 0 && + code.rainFcltFlowRate.length > 0 && + code.dmdUszCd.length > 0; + if (!hasRequiredCodes) { + void getCommonCodesByKeys(["WR014", "WR015", "WR016"]); + } useInitRef.current = true; // getLaeSeCdCheck()를 호출한 뒤 true로 바뀜 - }, []); + }, [getLaeSeCdCheck, getCommonCodesByKeys, code.rainRswtMng, code.rainFcltFlowRate, code.dmdUszCd]); @@ -102,75 +110,46 @@ export default function Page() { if (searchYear) if (selFcltDiv === "RNB") { // 빗물 if (fcltNm) { - rainChkNmSearch( - rainPageNation, - rainPageSize, - searchYear, - laeSeCd, - fcltNm - ); + rainChkNmSearch( rainPageNation, rainPageSize, searchYear, laeSeCd, fcltNm ); setRainPageNation(1); } else { rainChkInit(rainPageNation, rainPageSize, searchYear, laeSeCd); } } else { // 중수도 if (fcltNm) { - rswtChkNmSearch( - rswtPageNation, - rswtPageSize, - searchYear, - laeSeCd, - fcltNm - ); + rswtChkNmSearch( rswtPageNation, rswtPageSize, searchYear, laeSeCd, fcltNm ); setRswtPageNation(1); } else { rswtChkgdnInit(rswtPageNation, rswtPageSize, searchYear, laeSeCd); } } }; + const rainPageChange = (pageNo: number) => { if (pageNo > 0) { setRainPageNation(pageNo); if (fcltNm) { - rainChkNmSearch( - pageNo, - rainPageSize, - searchYear, - laeSeCd, - fcltNm - ); + rainChkNmSearch( pageNo, rainPageSize, searchYear, laeSeCd, fcltNm ); } else { rainChkInit(pageNo, rainPageSize, searchYear, laeSeCd); } } }; + const rswtPageChange = (pageNo: number) => { if (pageNo > 0) { setRswtPageNation(pageNo); if (fcltNm) { - rswtChkNmSearch( - pageNo, - rswtPageSize, - searchYear, - laeSeCd, - fcltNm - ); + rswtChkNmSearch( pageNo, rswtPageSize, searchYear, laeSeCd, fcltNm ); } else { rswtChkgdnInit(pageNo, rswtPageSize, searchYear, laeSeCd); } } }; - - useEffect(() => { if (useInitRef.current == true && laeSeCd !== undefined&& !rainChkgdn.isInit) { - rainChkInit( - 1, - rainPageSize, - new Date().getFullYear().toString(), - laeSeCd - ); + rainChkInit( 1, rainPageSize, new Date().getFullYear().toString(), laeSeCd ); isRainSttn(false); setIsModal({ data: {}, open: false }); setIsRegExcel(false); @@ -179,12 +158,7 @@ export default function Page() { useEffect(() => { if (useInitRef.current == true && laeSeCd !== undefined&&!rswtChkgdn.isInit) { - rswtChkgdnInit( - 1, - rswtPageSize, - new Date().getFullYear().toString(), - laeSeCd - ); + rswtChkgdnInit( 1, rswtPageSize, new Date().getFullYear().toString(), laeSeCd ); setIsModal({ data: {}, open: false }); setIsRegExcel(false); } @@ -192,38 +166,24 @@ export default function Page() { useEffect(() => { if (rswtChkgdn.excelList[0] && rswtChkgdn.excelList[0]?.data.length > 1) { - excelArrayDownload( - "중수도시설 지도점검", - "sample_rs_chk.xlsx", - rswtChkgdn.excelList - ); + excelArrayDownload( "중수도시설 지도점검", "sample_rs_chk.xlsx", rswtChkgdn.excelList ); } }, [rswtChkgdn.excelList]); useEffect(() => { excelDownload( - rainChkgdn.excelList, - "빗물이용시설 지도점검", - "sample_rainy.xlsx", - "A1:AN1", - 2 - ); + rainChkgdn.excelList, "빗물이용시설 지도점검", "sample_rainy.xlsx", "A1:AN1", 2 ); }, [rainChkgdn.excelList]); const excelClick = useCallback(() => { - if (selFcltDiv === "RNB") { - rainChkExcelDownload(code.cityCd, code.sggCd,laeSeCd); - } else { - rswtrChkExcelDownload(code.cityCd, code.sggCd,laeSeCd); - } - }, [ - rainChkExcelDownload, - rswtrChkExcelDownload, - code.cityCd, - code.sggCd, - selFcltDiv, - laeSeCd - ]); + if (selFcltDiv === "RNB") { + rainChkExcelDownload(code.cityCd, code.sggCd,laeSeCd); + } else { + rswtrChkExcelDownload(code.cityCd, code.sggCd,laeSeCd); + } + }, + [ rainChkExcelDownload, rswtrChkExcelDownload, code.cityCd, code.sggCd, selFcltDiv, laeSeCd ] + ); const handleClick = useCallback( (data: any) => { @@ -264,6 +224,7 @@ export default function Page() { } } }; + const chkUpdateTable = async (selFcltDiv: string) => { if (selFcltDiv === "RNB") { if (updateRainDetail && updateRainDetail.length > 0) { diff --git a/apps/wr/app/store/common/code/index.ts b/apps/wr/app/store/common/code/index.ts index fb400de2c..adcff57f2 100644 --- a/apps/wr/app/store/common/code/index.ts +++ b/apps/wr/app/store/common/code/index.ts @@ -40,7 +40,6 @@ const codeMApping = { }; export const getCommonUpCd = async (set: any) => { try { - let lwCdCode: Record = {}; const upCd = await getFetchWithCount( fetchType.CS, "/cms-service/api/v1/up-codes?keyword=&upCd=&cdSe=&useYn=Y&siteId=WR&page=0&size=100&sort=string" @@ -50,24 +49,9 @@ export const getCommonUpCd = async (set: any) => { .filter((item: any) => item.upCd.slice(0, 2) === "WR") .map((cd: any) => cd.upCd); - await Promise.all( - upWrCode.map(async (cd) => { - const mapping = codeMApping[cd as keyof typeof codeMApping]; - if (!mapping) return; + await fetchCommonCodesByKeys(upWrCode); - const lwCd = await getFetchWithCount( - fetchType.CS, - `/cms-service/api/v1/lwpo-cds/up-cd/${cd}` - ); - - const list = Array.isArray(lwCd) ? lwCd : (lwCd.content ?? []); - lwCds[mapping] = list.map((it: any) => ({ - text: it.lwpoCdNm, - val: it.lwpoCd, - })); - }) - ); - lwCdCode = lwCds; + const lwCdCode = lwCds; set((state: comCodeState) => ({ code: { ...state.code, @@ -78,6 +62,8 @@ export const getCommonUpCd = async (set: any) => { pplLenght: lwCdCode["pplLenght"] ?? [], dmdUszCd: lwCdCode["dmdUszCd"] ?? [], sewageOutFclttUse: lwCdCode["sewageOutFclttUse"] ?? [], + rainRswtMng: lwCdCode["rainRswtMng"] ?? [], + rainFcltFlowRate: lwCdCode["rainFcltFlowRate"] ?? [], ruseMngMtd: lwCdCode["ruseMngMtd"] ?? [], rusePrcsMthdCd: lwCdCode["rusePrcsMthdCd"] ?? [], erctUswtrPrcsMeth: lwCdCode["erctUswtrPrcsMeth"] ?? [], @@ -93,6 +79,30 @@ export const getCommonUpCd = async (set: any) => { console.log("com code error ", e); } }; + +const fetchCommonCodesByKeys = async (keys: string[]) => { + const targets = [...new Set(keys)].filter( + (cd) => Boolean(codeMApping[cd as keyof typeof codeMApping]) + ); + + await Promise.all( + targets.map(async (cd) => { + const mapping = codeMApping[cd as keyof typeof codeMApping]; + if (!mapping) return; + + const lwCd = await getFetchWithCount( + fetchType.CS, + `/cms-service/api/v1/lwpo-cds/up-cd/${cd}` + ); + + const list = Array.isArray(lwCd) ? lwCd : (lwCd.content ?? []); + lwCds[mapping] = list.map((it: any) => ({ + text: it.lwpoCdNm, + val: it.lwpoCd, + })); + }) + ); +}; //물재이용 주소 코드: devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM= export const buildingType = { RST: "RST", @@ -219,41 +229,42 @@ export const getEnvPartCd = async (set: any) => { export type comCodeState = { code: { - isInit: boolean; - chBuCd: SelectDataType[]; - rainFcltCd: SelectDataType[]; - bdstUsgCd: SelectDataType[]; - cityCd: SelectDataType[]; - sggCd: SelectDataType[]; - rvbsnCd: SelectDataType[]; - mdmbsnCd: SelectDataType[]; - lgbsnCd: SelectDataType[]; - lclgvrnmntCd: EnvPartSelectDataType[]; - orgwtKndCd: SelectDataType[]; - yn: SelectDataType[]; - pplQulity: SelectDataType[]; - pplLenght: SelectDataType[]; - dmdUszCd: SelectDataType[]; - sewageOutFclttUse: SelectDataType[]; + isInit: boolean; // 초기화 여부 + chBuCd: SelectDataType[]; // 변경사업코드 + rainFcltCd: SelectDataType[]; // 빗물시설코드 + bdstUsgCd: SelectDataType[]; // 건물용도코드 + cityCd: SelectDataType[]; // 시도코드 + sggCd: SelectDataType[]; // 시군구코드 + rvbsnCd: SelectDataType[]; // 대권역코드 + mdmbsnCd: SelectDataType[]; // 중권역코드 + lgbsnCd: SelectDataType[]; // 단위유역(대영역)코드 + lclgvrnmntCd: EnvPartSelectDataType[]; // 지방환경청코드 + orgwtKndCd: SelectDataType[]; // 원수종류코드 + yn: SelectDataType[]; // 여부코드 + pplQulity: SelectDataType[]; // 관저질 + pplLenght: SelectDataType[]; // 관연장 + dmdUszCd: SelectDataType[]; // 용도별코드 + sewageOutFclttUse: SelectDataType[]; // 하수재이용 장외코드 + rainRswtMng: SelectDataType[]; // 운영관리형태코드 + rainFcltFlowRate: SelectDataType[]; // 유량측정방법코드 buildingType: { - RST: string; - RNB: string; - HTB: string; - }; - ruseMngMtd: SelectDataType[]; - ruseSeCd: SelectDataType[]; - ruseBizMthdCd: SelectDataType[]; - rusePrcsMthdCd: SelectDataType[]; - erctUswtrPrcsMeth: SelectDataType[]; - bdstFilterYn: SelectDataType[]; - bdstIncenYn: SelectDataType[]; - bdstGrntchClass: SelectDataType[]; - // rswtPrscCdMthdCd: SelectDataType[]; - htdPowerMthdCd: SelectDataType[]; - htdUtztnUsgCd: SelectDataType[]; - rswtBdstType: SelectDataType[]; - bdstTotalUsgType: SelectDataType[]; - userSiSggCd: string; + RST: string; // 중수도시설 구분 + RNB: string; // 빗물이용시설 구분 + HTB: string; // 온배수시설 구분 + }; // 시설구분 + ruseMngMtd: SelectDataType[]; // 재이용관리방식코드 + ruseSeCd: SelectDataType[]; // 재이용구분코드 + ruseBizMthdCd: SelectDataType[]; // 재이용사업방식코드 + rusePrcsMthdCd: SelectDataType[]; // 재이용처리공법코드 + erctUswtrPrcsMeth: SelectDataType[]; // 하수처리공법코드 + bdstFilterYn: SelectDataType[]; // 여과시설여부코드 + bdstIncenYn: SelectDataType[]; // 인센티브여부코드 + bdstGrntchClass: SelectDataType[]; // 보조금지원대상분류코드 + htdPowerMthdCd: SelectDataType[]; // 온배수발전방식코드 + htdUtztnUsgCd: SelectDataType[]; // 온배수이용용도코드 + rswtBdstType: SelectDataType[]; // 중수도건물유형코드 + bdstTotalUsgType: SelectDataType[]; // 전체건물용도유형코드 + userSiSggCd: string; // 사용자 시군구코드 }; }; @@ -277,6 +288,8 @@ export const comCodeStateExport: comCodeState = { yn: yn, dmdUszCd: lwCds["dmdUszCd"] ?? [], sewageOutFclttUse: lwCds["sewageOutFclttUse"] ?? [], + rainRswtMng: lwCds["rainRswtMng"] ?? [], + rainFcltFlowRate: lwCds["rainFcltFlowRate"] ?? [], buildingType: buildingType, ruseMngMtd: lwCds["ruseMngMtd"] ?? [], ruseSeCd: ruseSeCd, @@ -298,10 +311,51 @@ export const comCodeStateExport: comCodeState = { export type comCodeActions = { returnCodeText: (val: string, codeName: string) => string; returnTextCode: (text: string, codeName: string) => string; + getCommonCodesByKeys: (keys: string[]) => Promise; }; export const codeActionExport: (set: any, get:any) => comCodeActions = (set: any, get:any) => { return { + getCommonCodesByKeys: async (keys: string[]) => { + try { + await fetchCommonCodesByKeys(keys); + const lwCdCode = lwCds; + + set((state: comCodeState) => ({ + code: { + ...state.code, + rainFcltCd: lwCdCode["rainFcltCd"] ?? state.code.rainFcltCd, + bdstUsgCd: lwCdCode["bdstUsgCd"] ?? state.code.bdstUsgCd, + orgwtKndCd: lwCdCode["orgwtKndCd"] ?? state.code.orgwtKndCd, + pplQulity: lwCdCode["pplQulity"] ?? state.code.pplQulity, + pplLenght: lwCdCode["pplLenght"] ?? state.code.pplLenght, + dmdUszCd: lwCdCode["dmdUszCd"] ?? state.code.dmdUszCd, + sewageOutFclttUse: + lwCdCode["sewageOutFclttUse"] ?? state.code.sewageOutFclttUse, + rainRswtMng: lwCdCode["rainRswtMng"] ?? state.code.rainRswtMng, + rainFcltFlowRate: + lwCdCode["rainFcltFlowRate"] ?? state.code.rainFcltFlowRate, + ruseMngMtd: lwCdCode["ruseMngMtd"] ?? state.code.ruseMngMtd, + rusePrcsMthdCd: + lwCdCode["rusePrcsMthdCd"] ?? state.code.rusePrcsMthdCd, + erctUswtrPrcsMeth: + lwCdCode["erctUswtrPrcsMeth"] ?? state.code.erctUswtrPrcsMeth, + bdstGrntchClass: + lwCdCode["bdstGrntchClass"] ?? state.code.bdstGrntchClass, + htdPowerMthdCd: + lwCdCode["htdPowerMthdCd"] ?? state.code.htdPowerMthdCd, + htdUtztnUsgCd: + lwCdCode["htdUtztnUsgCd"] ?? state.code.htdUtztnUsgCd, + rswtBdstType: lwCdCode["rswtBdstType"] ?? state.code.rswtBdstType, + bdstTotalUsgType: + lwCdCode["bdstUsgCd"]?.concat(lwCdCode["rswtBdstType"] || []) ?? + state.code.bdstTotalUsgType, + }, + })); + } catch (e) { + console.log("selected common code error ", e); + } + }, returnCodeText: (val: string, codeName: string) => { const codeList = get().code[codeName] || []; diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/excel.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/excel.tsx index 6e1114781..cc5109000 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/excel.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/excel.tsx @@ -270,35 +270,25 @@ export const rswtExcelDataCheck = ( issues: string[] ) => { // const chkMngType = { "01": "위탁", "02": "자체" }; - + //rainRswtMng: SelectDataType[]; // 운영관리형태코드 + // console.log(rswtChkgdData); rswtChkgdData.forEach((rswt: any) => { const defaultMessage = `*중수도 수질 [NO: ${rswt.bdstRuseFcltSn}, 점검일: ${rswt.chckYmd}, 이용용도: ${codeToText(rswt.utztnUsgCd ,"dmdUszCd")??rswt.utztnUsgCd}] 항목에서 `; if (rswt.chckYmd && isInvalidDate(rswt.chckYmd)) { issues.push(defaultMessage + `점검일: 날짜 형식 아님 (${rswt.chckYmd})`); } - // if (rswt.operMngCd?.length > 1 && !chkMngType[rswt.operMngCd]) { - // issues.push( - // defaultMessage + - // `운영관리형태: 위탁 혹은 자체만 허용 (${rswt.operMngCd})` - // ); - // } - // if (rswt.utztnUsgCd?.length > 2 && !codeToText(rswt.utztnUsgCd, "dmdUszCd")) { - // issues.push( - // defaultMessage + `이용용도: 허용되지 않은 값 (${rswt.utztnUsgCd})` - // ); - // } + if (rswt.operMngCd?.length > 1 && !codeToText(rswt.operMngCd, "rainRswtMng")) { + issues.push( defaultMessage + `운영관리형태: 위탁 혹은 자체만 허용 (${rswt.operMngCd})` ); + } + if (rswt.utztnUsgCd?.length > 2 && !codeToText(rswt.utztnUsgCd, "dmdUszCd")) { + issues.push( defaultMessage + `이용용도: 허용되지 않은 값 (${rswt.utztnUsgCd})` ); + } if (rswt.anlOperCst.length > 2 && isInvalidNumber(rswt.anlOperCst)) { - - issues.push( - defaultMessage + - ` 연간운영비(원/년): 숫자 형식 아님 (${rswt.anlOperCst})` - ); + issues.push( defaultMessage + ` 연간운영비(원/년): 숫자 형식 아님 (${rswt.anlOperCst})` ); } if (rswt.prcsCpct.length > 2 && isInvalidNumber(rswt.prcsCpct)) { - issues.push( - defaultMessage + ` 처리용량(m³): 숫자 형식 아님 (${rswt.prcsCpct})` - ); + issues.push( defaultMessage + ` 처리용량(m³): 숫자 형식 아님 (${rswt.prcsCpct})` ); } // if ( // rswt.spwtPositn.length > 1 && @@ -313,55 +303,31 @@ export const rswtExcelDataCheck = ( if (rswt?.rswtrPollutions && rswt?.rswtrPollutions.length > 0) { rswt.rswtrPollutions.forEach((pol) => { if (isInvalidNumber(pol.grssClfgUswtr)) { - issues.push( - defaultMessage + - `${pol.node}분기_총대장균군수: 숫자 형식 아님 (${pol.grssClfgUswtr})` - ); + issues.push( defaultMessage + `${pol.node}분기_총대장균군수: 숫자 형식 아님 (${pol.grssClfgUswtr})` ); } if (isInvalidNumber(pol.combResdch)) { - issues.push( - defaultMessage + - `${pol.node}분기_결합잔류염소: 숫자 형식 아님 (${pol.combResdch})` - ); + issues.push( defaultMessage + `${pol.node}분기_결합잔류염소: 숫자 형식 아님 (${pol.combResdch})` ); } if (isInvalidNumber(pol.trbt)) { - issues.push( - defaultMessage + - `${pol.node}분기_탁도: 숫자 형식 아님 (${pol.trbt})` - ); + issues.push( defaultMessage + `${pol.node}분기_탁도: 숫자 형식 아님 (${pol.trbt})` ); } if (isInvalidNumber(pol.bod)) { - issues.push( - defaultMessage + `${pol.node}분기_BOD: 숫자 형식 아님 (${pol.bod})` - ); + issues.push( defaultMessage + `${pol.node}분기_BOD: 숫자 형식 아님 (${pol.bod})` ); } if (isInvalidNumber(pol.crmty)) { - issues.push( - defaultMessage + - `${pol.node}분기_색도: 숫자 형식 아님 (${pol.crmty})` - ); + issues.push( defaultMessage + `${pol.node}분기_색도: 숫자 형식 아님 (${pol.crmty})` ); } if (isInvalidNumber(pol.tph)) { - issues.push( - defaultMessage + `${pol.node}분기_T-P: 숫자 형식 아님 (${pol.tph})` - ); + issues.push( defaultMessage + `${pol.node}분기_T-P: 숫자 형식 아님 (${pol.tph})` ); } if (isInvalidNumber(pol.tn)) { - issues.push( - defaultMessage + `${pol.node}분기_T-N: 숫자 형식 아님 (${pol.tn})` - ); + issues.push( defaultMessage + `${pol.node}분기_T-N: 숫자 형식 아님 (${pol.tn})` ); } - if (pol.ph?.length > 1 && isInvalidNumber(pol.ph)) { - issues.push( - defaultMessage + `${pol.node}분기_PH: 숫자 형식 아님 (${pol.ph})` - ); + if (isInvalidNumber(pol.ph)) { + issues.push( defaultMessage + `${pol.node}분기_PH: 숫자 형식 아님 (${pol.ph})` ); } - - if (isInvalidNumber(pol.clIonQnt)) { - issues.push( - defaultMessage + - `${pol.node}분기_염소이온량: 숫자 형식 아님 (${pol.clIonQnt})` - ); + if (isInvalidNumber(pol.clIonQnt)) { + issues.push( defaultMessage + `${pol.node}분기_염소이온량: 숫자 형식 아님 (${pol.clIonQnt})` ); } }); } @@ -369,19 +335,43 @@ export const rswtExcelDataCheck = ( if (rswt?.rswtrDetails && rswt?.rswtrDetails.length > 0) { rswt?.rswtrDetails.forEach((key) => { if (key.levyAmt?.length > 1 && isInvalidNumber(key.levyAmt)) { - issues.push( - defaultMessage + - `과태료부과금액(천원): 숫자 형식 아님 (${key.levyAmt})` - ); + issues.push( defaultMessage + `과태료부과금액(천원): 숫자 형식 아님 (${key.levyAmt})` ); } if (key.vltnDt?.length > 2 && isInvalidDate(key.vltnDt)) { - issues.push( - defaultMessage + - `과태료 부과일(위반일): 날짜 형식 아님 (${key.vltnDt})` - ); + issues.push( defaultMessage + `과태료 부과일(위반일): 날짜 형식 아님 (${key.vltnDt})` ); } }); } + + const polutionUseCodes = new Set( + (rswt?.rswtrPollutions ?? []) + .map((pol) => pol.utztnUsgCd) + .filter((code): code is string => Boolean(code)) + ); + const guideDetailCodes = new Set( + (rswt?.guideDetails ?? []) + .map((detail) => detail.utztnUsgCd) + .filter((code): code is string => Boolean(code)) + ); + + polutionUseCodes.forEach((code) => { + if (!guideDetailCodes.has(code)) { + issues.push( + defaultMessage + + `중수도_점검결과_수질 sheet의 이용용도에 대응하는 중수도_점검결과_이용량현황sheet의 이용용도가 없습니다.` + ); + } + }); + + guideDetailCodes.forEach((code) => { + if (!polutionUseCodes.has(code)) { + issues.push( + defaultMessage + + `중수도_점검결과_이용량현황sheet의 이용용도에 대응하는 중수도_점검결과_수질sheet의 이용용도가 없습니다.` + ); + } + }); + }); }; diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx index 3ce6cc2ff..102d44d75 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx @@ -53,29 +53,14 @@ export type rswtChkgdnState = { isInit: boolean; // 초기 목록 조회 완료 여부 isSttn: boolean; // 테이블 선택/상세 상태 제어 플래그 isLoading:boolean; // 등록/업로드 등 비동기 처리 진행 여부 - rswtChkCount: number; // 메인 목록 전체 건수 + rswtChkCount: number; // 메인 목록 전체 건수 : 시설의 건수임 }; }; type rswtChkgdnActions = { - rswtChkgdnInit: ( - pageNo: number, - cnt: number, - year: string, - sggCd: string - ) => void; - rswtChkNmSearch: ( - pageNo: number, - cnt: number, - year: string, - sggCd: string, - txt: string - ) => void; - rswtrChkExcelDownload( - sidoCode: SelectDataType[], - sggCode: SelectDataType[], - sggCd: string, - ): Promise; + rswtChkgdnInit: ( pageNo: number, cnt: number, year: string, sggCd: string ) => void; + rswtChkNmSearch: ( pageNo: number, cnt: number, year: string, sggCd: string, txt: string ) => void; + rswtrChkExcelDownload( sidoCode: SelectDataType[], sggCode: SelectDataType[], sggCd: string, ): Promise; rswtChkExceDataUpload: (excelData: rswtChkZodType[],laeSeCd:string) => Promise; rswtChkDelete: (chkgdnSns: number[] ,year:string) => Promise; rswtChkDetilsUpdate: (rswtChkDetails: rswtrChkDetailsZodType[] ,year:string) => Promise; @@ -189,8 +174,7 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( // set((state: rswtChkgdnType) => ({ rswtChkgdn: { ...state.rswtChkgdn, isLoading: true } })); try { const rswtChkRes = await initFetch(pageNo, cnt, year, sggCd); - - + // console.log(rswtChkRes); set((state: rswtChkgdnState) => ({ ...state, rswtChkgdn: { @@ -246,7 +230,7 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( rswtChkExceDataUpload: async (excelData: rswtChkZodType[],laeSeCd:string) => { const successMessage = "지도점검 등록이 완료되었습니다."; const failMessage = "지도점검 등록이 실패되었습니다."; - console.log(excelData); + // console.log(excelData); try { set((state: rswtChkgdnType) => ( { rswtChkgdn: { ...state.rswtChkgdn, isLoading: true } })); diff --git a/apps/wr/components/lib/func/TableData.tsx b/apps/wr/components/lib/func/TableData.tsx index 89d347c24..f17af3a7f 100644 --- a/apps/wr/components/lib/func/TableData.tsx +++ b/apps/wr/components/lib/func/TableData.tsx @@ -112,6 +112,7 @@ export const chRowspanTable = ( // rowspan된 셀 렌더링 여부 추적용 const renderedMap = new Map>(); + // console.log(makeTableData); return ( <> {/* {makeTableData.map((row, rowIndex) => ( */} diff --git a/packages/common/business_components/src/ui/excel/index.tsx b/packages/common/business_components/src/ui/excel/index.tsx index 23dfd0fea..54e4fe6a8 100644 --- a/packages/common/business_components/src/ui/excel/index.tsx +++ b/packages/common/business_components/src/ui/excel/index.tsx @@ -246,7 +246,7 @@ export const filePickedArray=(oFile: File, headerNum:number=0):Promise Date: Fri, 10 Apr 2026 11:46:49 +0900 Subject: [PATCH 03/18] =?UTF-8?q?wr:=20=EC=A4=91=EC=88=98=EB=8F=84=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20modal=20=EB=83=84=EC=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wr/components/lib/func/TableData.tsx | 74 +++++++++++++------ .../ui/modal/chkgd/chkgdExcel/chkgdExcel.tsx | 2 +- 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/apps/wr/components/lib/func/TableData.tsx b/apps/wr/components/lib/func/TableData.tsx index f17af3a7f..b2411eea4 100644 --- a/apps/wr/components/lib/func/TableData.tsx +++ b/apps/wr/components/lib/func/TableData.tsx @@ -314,6 +314,25 @@ const handleChange = ( ); }; +const handleTextChange = ( + rowIndex: number, + field: any, + value: string, + setInputData: (data: any) => void +) => { + setInputData((prevData: any) => + prevData?.map((row: any, index: number) => { + if (index === rowIndex) { + return { + ...row, + [field]: value, + }; + } + return row; + }) + ); +}; + // 재이용실적등록 > 지도점검결과 등록 - 상세 modal의 body export const InputTableBody = ( @@ -350,17 +369,7 @@ export const InputTableBody = ( return ( {headList.map((head, headIndex) => { - //냄새면 계산 못하게 - 로 수정 - // if (["smll"].includes(head.id)) { - // return ( - // - // - - // - // ); - // } + const isTextColumn = head.id === "구분" || head.id === "utztnUsgCdText"; const textAlignClass = isTextColumn ? "text-center" @@ -419,22 +428,39 @@ export const InputTableBody = ( key={head.id + headIndex} className={`w-36 ${bgClass}`} > - - handleCommaNumberChange(e, (val) => - handleChange( + {head.id === "smll" ? ( + + handleTextChange( dataIndex, head.id, - String(val), + e.target.value, setInputData ) - ) - } - value={formatCommaNumber(data[head.id as keyof any]) ?? ""} - maxLength={numSize} - /> + } + value={String(data[head.id as keyof any] ?? "")} + maxLength={numSize} + /> + ) : ( + + handleCommaNumberChange(e, (val) => + handleChange( + dataIndex, + head.id, + String(val), + setInputData + ) + ) + } + value={formatCommaNumber(data[head.id as keyof any]) ?? ""} + maxLength={numSize} + /> + )} ) : ( ); -}; \ No newline at end of file +}; diff --git a/apps/wr/components/ui/modal/chkgd/chkgdExcel/chkgdExcel.tsx b/apps/wr/components/ui/modal/chkgd/chkgdExcel/chkgdExcel.tsx index 595cff55e..0e4152db6 100644 --- a/apps/wr/components/ui/modal/chkgd/chkgdExcel/chkgdExcel.tsx +++ b/apps/wr/components/ui/modal/chkgd/chkgdExcel/chkgdExcel.tsx @@ -192,7 +192,7 @@ export default function ChkgdExcel({ } }) .finally(() => setRswtChkLoading(false)); - console.log("업로드파일 점검 완료"); + // console.log("업로드파일 점검 완료"); } } }, [file]); From 2de065ceb0bb29351521635962a83b751dbbdf4e Mon Sep 17 00:00:00 2001 From: ljpark Date: Fri, 10 Apr 2026 18:01:12 +0900 Subject: [PATCH 04/18] =?UTF-8?q?wr:=20jwt=20=EB=82=A8=EC=9D=80=EC=8B=9C?= =?UTF-8?q?=EA=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wr/app/(with-layout)/jwttest/page.tsx | 164 ++++++++++++++++++ .../prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx | 5 +- apps/wr/components/lib/api/index.ts | 41 +++++ .../wr/src/entities/auth/lib/token-manager.ts | 50 ++++++ 4 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 apps/wr/app/(with-layout)/jwttest/page.tsx diff --git a/apps/wr/app/(with-layout)/jwttest/page.tsx b/apps/wr/app/(with-layout)/jwttest/page.tsx new file mode 100644 index 000000000..25d47c1d2 --- /dev/null +++ b/apps/wr/app/(with-layout)/jwttest/page.tsx @@ -0,0 +1,164 @@ +/** + * JWT를 decoding하면 만료시간을 읽을 수 있다. + * JWT는 3부분으로 구성 : Header.Payload.Signature + * Payload 부분에 만료시간(exp)이 있다. + */ +"use client"; + +import { useEffect, useState } from "react"; +import { CLAIM_NAME } from "@/src/shared/config"; +import { getCookie } from "@/src/shared/lib"; + +interface JwtPayload { + sub?: string; // 토큰 subject. 일반적으로 사용자 식별자나 주체 정보 + exp?: number; // 만료 시각(Unix timestamp, 초 단위) + iat?: number; // 토큰 발급 시각(Unix timestamp, 초 단위) + [key: string]: unknown; // 그 외 JWT payload에 포함될 수 있는 추가 클레임 +} + +interface JwtState { + token: string; // 쿠키에서 읽어온 JWT 원본 문자열 + payload: JwtPayload | null; // JWT를 파싱한 payload 객체 + exp: number | null; // payload에서 추출한 exp 값 + expDateLocal: string | null; // exp를 로컬 시간 문자열로 변환한 값 + remainingMs: number | null; // 현재 시각 기준 남은 시간(ms) + error: string | null; // 쿠키 없음, 파싱 실패 등 오류 메시지 +} + + +function parseJwt(token: string): JwtPayload { + const parts = token.split("."); + if (parts.length !== 3) { + throw new Error("유효한 JWT 형식이 아닙니다."); + } + + const base64Url = parts[1] ?? ""; // Payload를 가져온다. + const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); + const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "="); + const jsonPayload = decodeURIComponent( + atob(padded) + .split("") + .map((char) => `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`) + .join("") + ); + + return JSON.parse(jsonPayload) as JwtPayload; +} + +function formatRemainingMs(remainingMs: number | null): string { + if (remainingMs === null) return "-"; + if (remainingMs <= 0) return "만료됨"; + + const totalSeconds = Math.floor(remainingMs / 1000); + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + + return `${hours}시간 ${minutes}분 ${seconds}초`; +} + +function readJwtState(): JwtState { + const token = getCookie(CLAIM_NAME) ?? ""; + + if (!token) { + return { + token: "", + payload: null, + exp: null, + expDateLocal: null, + remainingMs: null, + error: `${CLAIM_NAME} 쿠키가 없습니다.`, + }; + } + + try { + const payload = parseJwt(token); + + if (typeof payload.exp !== "number") { + return { + token, + payload, + exp: null, + expDateLocal: null, + remainingMs: null, + error: "JWT payload에 exp 값이 없습니다.", + }; + } + + const expMs = payload.exp * 1000; + + return { + token, + payload, + exp: payload.exp, + expDateLocal: new Date(expMs).toLocaleString(), + remainingMs: Math.max(expMs - Date.now(), 0), + error: null, + }; + } catch (error) { + return { + token, + payload: null, + exp: null, + expDateLocal: null, + remainingMs: null, + error: + error instanceof Error + ? error.message + : "JWT 파싱 중 오류가 발생했습니다.", + }; + } +} + +export default function Page() { + const [jwtState, setJwtState] = useState(() => readJwtState()); + + useEffect(() => { + const syncJwtState = () => { + setJwtState(readJwtState()); + }; + + syncJwtState(); + const timer = window.setInterval(syncJwtState, 1000); + + return () => window.clearInterval(timer); + }, []); + + return ( +
+

JWT Exp Test

+ +
+
쿠키명
+
{CLAIM_NAME}
+
+ +
+
exp
+
{jwtState.exp ?? "-"}
+
+ +
+
만료시각
+
{jwtState.expDateLocal ?? "-"}
+
+ +
+
남은시간
+
{formatRemainingMs(jwtState.remainingMs)}
+
+ +
+
상태
+
{jwtState.error ?? "정상"}
+
+ +
+
payload
+
+          {jwtState.payload ? JSON.stringify(jwtState.payload, null, 2) : "-"}
+        
+
+
+ ); +} diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx index 102d44d75..71ae80eda 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx @@ -1,4 +1,7 @@ -import { buildingType } from "@/app/store/common/code"; +/** + * 중수도 지도점검 store + */ +import { buildingType } from "@/app/store/common/code"; import type { TableUpperProps } from "@/app/store/common/type/header"; import { TEST_UUID, diff --git a/apps/wr/components/lib/api/index.ts b/apps/wr/components/lib/api/index.ts index 6ff75f5a6..aa257a71a 100644 --- a/apps/wr/components/lib/api/index.ts +++ b/apps/wr/components/lib/api/index.ts @@ -1,4 +1,6 @@ import { DEV_URL_FO, DEV_URL_WR, LOCAL_URL } from "../config/"; +import { CLAIM_NAME } from "@/src/shared/config"; +import { tokenManager } from "@/src/entities/auth/lib/token-manager"; export const fetchType = { COMMON: "common", @@ -7,6 +9,28 @@ export const fetchType = { CS: "CS", }; +const LOGIN_PAGE_URL = + process.env.NEXT_PUBLIC_CM_LOGIN_PAGE_URL || "/auth/login"; + +const handleUnauthorizedResponse = () => { + if (typeof window === "undefined") return; + + const remainingMs = tokenManager.getRemainingMsFromToken(CLAIM_NAME); + const isExpired = remainingMs === null || remainingMs <= 0; + + if (!isExpired) return; + + tokenManager.clearAuthToken(); + + const isLoginPage = + window.location.pathname === "/auth/login" || + window.location.pathname.startsWith("/auth/login/"); + + if (!isLoginPage) { + window.location.assign(LOGIN_PAGE_URL); + } +}; + export const getFetch = async (type: string, url: string) => { const div = type === fetchType.COMMON @@ -24,6 +48,10 @@ export const getFetch = async (type: string, url: string) => { }, }); + if (response.status === 401) { + handleUnauthorizedResponse(); + } + if (!response.ok) { console.log(`오류 발생: ${response.status}`); } @@ -72,6 +100,10 @@ export const getFetchWithCount = async (type: string, url: string) => { }, }); + if (response.status === 401) { + handleUnauthorizedResponse(); + } + if (!response.ok) { console.log(`오류 발생: ${response.status}`); } @@ -114,6 +146,9 @@ export const postFectch = async (url: string, params: any, type?: string) => { }, body: JSON.stringify(params), }); + if (response.status === 401) { + handleUnauthorizedResponse(); + } if (!response.ok) { throw new Error(`오류 발생: ${response.status}`); } @@ -138,6 +173,9 @@ export const putFectch = async (url: string, params: any, type?: string) => { }, body: JSON.stringify(params), }); + if (response.status === 401) { + handleUnauthorizedResponse(); + } if (!response.ok) { throw new Error(`오류 발생: ${response.status}`); } @@ -166,6 +204,9 @@ export const deleteFectch = async ( }, body: params ? JSON.stringify(params) : undefined, }); + if (response.status === 401) { + handleUnauthorizedResponse(); + } if (!response.ok) { // 에러 응답 본문 파싱 const errorText = await response.text(); diff --git a/apps/wr/src/entities/auth/lib/token-manager.ts b/apps/wr/src/entities/auth/lib/token-manager.ts index 40d32f33e..919386622 100644 --- a/apps/wr/src/entities/auth/lib/token-manager.ts +++ b/apps/wr/src/entities/auth/lib/token-manager.ts @@ -1,6 +1,13 @@ import { AUTH_USER_ID, CLAIM_NAME } from "@/src/shared/config"; import { getCookie, removeCookie, setCookie } from "@/src/shared/lib/cookie"; +type JwtPayload = { + sub?: string; // 토큰 subject + exp?: number; // 만료 시각(초 단위) + iat?: number; // 발급 시각(초 단위) + [key: string]: unknown; +}; + export class TokenManager { private static instance: TokenManager; private refreshTimer: NodeJS.Timeout | null = null; @@ -18,6 +25,49 @@ export class TokenManager { return getCookie(key); } + private parseJwt(token: string): JwtPayload { + const parts = token.split("."); + if (parts.length !== 3) { + throw new Error("유효한 JWT 형식이 아닙니다."); + } + + const base64Url = parts[1] ?? ""; // Payload를 가져온다. + const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); + const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "="); + const jsonPayload = decodeURIComponent( + atob(padded) + .split("") + .map((char) => `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`) + .join("") + ); + + return JSON.parse(jsonPayload) as JwtPayload; + } + + public getRemainingMsFromToken(key: string = CLAIM_NAME): number | null { + const token = this.getToken(key); + if (!token) { + return null; + } + + try { + const payload = this.parseJwt(token); + if (typeof payload.exp !== "number") { + return null; + } + + return payload.exp * 1000 - Date.now(); + } catch (error) { + console.error("JWT 파싱 오류:", error); + return null; + } + } + + public isExpiredToken(key: string = CLAIM_NAME): boolean { + const remainingMs = this.getRemainingMsFromToken(key); + return remainingMs === null || remainingMs <= 0; + } + public setToken(key: string, value: string): void { setCookie(key, value, { secure: window.location.protocol === "https:", From 7359f19e7db70512297c8257760317d12fb1ba8b Mon Sep 17 00:00:00 2001 From: ljpark Date: Mon, 13 Apr 2026 18:18:19 +0900 Subject: [PATCH 05/18] =?UTF-8?q?wr:=20=ED=95=98=EC=88=98=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EC=8B=9C=EC=84=A4=ED=98=84=ED=99=A9=20=EC=A7=80?= =?UTF-8?q?=EC=9E=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sn/src/entities/auth/api/login-service.ts | 1 + apps/wr/app/(map-layout)/map/mainMap/page.tsx | 3 +- .../ruseFcltMng/fcltReg/pbnstReg/page.tsx | 2 +- .../ruseFcltMng/fcltSttn/pbnstSttn/page.tsx | 83 ++++++++++++++++--- apps/wr/app/store/common/auth/index.tsx | 11 +-- .../ruseFcltMng/fcltReg/pbnstReg/index.tsx | 8 +- packages/common/network/utils.ts | 10 +-- 7 files changed, 91 insertions(+), 27 deletions(-) diff --git a/apps/sn/src/entities/auth/api/login-service.ts b/apps/sn/src/entities/auth/api/login-service.ts index c1968fe36..dd5a49f68 100644 --- a/apps/sn/src/entities/auth/api/login-service.ts +++ b/apps/sn/src/entities/auth/api/login-service.ts @@ -23,6 +23,7 @@ export class LoginApiService implements LoginService { public async login(payload: ILogin): Promise { try { + tokenManager.clearAuthToken(); const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, { method: "POST", body: JSON.stringify({ diff --git a/apps/wr/app/(map-layout)/map/mainMap/page.tsx b/apps/wr/app/(map-layout)/map/mainMap/page.tsx index a2addf109..13798ff4f 100644 --- a/apps/wr/app/(map-layout)/map/mainMap/page.tsx +++ b/apps/wr/app/(map-layout)/map/mainMap/page.tsx @@ -21,7 +21,8 @@ import WrOverLay from "@/components/ui/map/wrOverLay/wrOverLay"; const Map = () => { - const { code, comMap, rainMapDetile, rswtMapDetile, getRuseMaplListReturn, pbnMapDetaile, ldrmntMapDetile, htdfMapDetile, coSttnMapDetail } = useWrStore((state) => state); + const { code, comMap, rainMapDetile, rswtMapDetile, getRuseMaplListReturn + , pbnMapDetaile, ldrmntMapDetile, htdfMapDetile, coSttnMapDetail } = useWrStore((state) => state); const { isFeatured } = comMap; const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정 const gisMaps = useRef(null); diff --git a/apps/wr/app/(with-layout)/ruseFcltMng/fcltReg/pbnstReg/page.tsx b/apps/wr/app/(with-layout)/ruseFcltMng/fcltReg/pbnstReg/page.tsx index 0a243a41f..5cf8fc0d5 100644 --- a/apps/wr/app/(with-layout)/ruseFcltMng/fcltReg/pbnstReg/page.tsx +++ b/apps/wr/app/(with-layout)/ruseFcltMng/fcltReg/pbnstReg/page.tsx @@ -287,7 +287,7 @@ export default function PublicReuseFaciltyInsert() { }, [pbnstRes, append, selectRow]); useEffect(() => { - pubHasuInit(String(pageNation), laeSeCd); + pubHasuInit(String(pageNation), laeSeCd); // 해당 지자체의 하수처리시설 조회 isResetPage(false); setSelectRow(undefined); setSelectedRowIndex(-1); diff --git a/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx b/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx index 3e085ea89..23d7bb4e8 100644 --- a/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx +++ b/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx @@ -36,6 +36,7 @@ import { useTranslation } from "react-i18next"; export default function PbnstSttn() { const { + auth, code, pbnstSttn, pbnstSttnSearch, @@ -43,9 +44,11 @@ export default function PbnstSttn() { getPbnStnDetail, getFlcts, resetPbnstDetail, + getLaeSeCdCheck, } = useWrStore((state) => state); const { pbnstSttnRes, fclts, pbnstSttnResCount } = pbnstSttn; + const { laeSeCd } = auth; const [isReset, setIsReset] = useState(false); const ref = useRef({} as ReturnType); @@ -59,21 +62,53 @@ export default function PbnstSttn() { data: customPbnst; }>({ isModal: false, data: {} as customPbnst }); const isDownloadTriggered = useRef(false); + const useInitRef = useRef(false); const [pbnRuseNm, setPbnRuseNm] = useState(""); const { t } = useTranslation("base"); const alert = useAlertStore((state) => state.setMessage); + const isAllAreaUser = laeSeCd === "0"; + const fixedSidoValue = + !isAllAreaUser && laeSeCd?.length ? laeSeCd.slice(0, 2) : ""; + const fixedSigunValue = + !isAllAreaUser && laeSeCd?.length === 5 ? laeSeCd : "0"; + const getFixedSearchValue = useCallback( + (baseValue: ReturnType): ReturnType => { + if (isAllAreaUser) { + return baseValue; + } + + const fixedSidoText = + code.cityCd.find((item) => item.val === fixedSidoValue)?.text ?? ""; + const fixedSigunText = + fixedSigunValue !== "0" + ? code.sggCd.find((item) => item.val === fixedSigunValue)?.text ?? "" + : ""; + + return { + ...baseValue, + partValue: "A", + sidoValue: fixedSidoValue, + sigunValue: fixedSigunValue, + sidoText: fixedSidoText, + sigunText: fixedSigunText, + }; + }, + [isAllAreaUser, fixedSidoValue, fixedSigunValue, code.cityCd, code.sggCd] + ); + const excelClick = useCallback(() => { isDownloadTriggered.current = true; + const fixedSearchValue = getFixedSearchValue(ref.current); pbnstSttnSearch( true, String(1), "100000", - ref.current, + fixedSearchValue, pbnRuseNm, searchYear, fclts ); - }, [pbnstSttnSearch, fclts, pbnRuseNm]); + }, [pbnstSttnSearch, fclts, pbnRuseNm, searchYear, getFixedSearchValue]); const cellCLick = useCallback( (index: number | undefined) => { @@ -84,14 +119,21 @@ export default function PbnstSttn() { }, [getPbnStnDetail, pbnstSttnRes] ); + useEffect(() => { + getLaeSeCdCheck(); + useInitRef.current = true; + }, []); + useEffect(() => { if (!pbnstSttn.isInit) { getFlcts(); } }, [pbnstSttn.isInit]); useEffect(() => { - pbnSttnInit("1", "15", ref.current, searchYear, fclts); - }, [fclts]); + if (useInitRef.current === true && laeSeCd !== undefined && fclts.length > 0) { + pbnSttnInit("1", "15", getFixedSearchValue(ref.current), searchYear, fclts); + } + }, [fclts, laeSeCd, searchYear, pbnSttnInit, getFixedSearchValue]); useEffect(() => { if (pbnstSttnRes && pbnstSttnRes.length > 0) { @@ -139,8 +181,18 @@ export default function PbnstSttn() {
item.val === fixedSidoValue) + : code.cityCd + } + selectSigunData={ + !isAllAreaUser && laeSeCd?.length === 5 + ? code.sggCd.filter((item) => item.val === laeSeCd) + : !isAllAreaUser && laeSeCd?.length === 2 + ? code.sggCd.filter((item) => item.val.slice(0, 2) === laeSeCd) + : code.sggCd + } selectRegionData={code.rvbsnCd} selectUnitWaterShedData={code.lgbsnCd} selectMiddleRegionData={code.mdmbsnCd} @@ -185,9 +237,16 @@ export default function PbnstSttn() {
} queryClick={() => { - // pbnSearch(returnType); - - //setTableIndex(0) + const fixedSearchValue = getFixedSearchValue(ref.current); + pbnstSttnSearch( + false, + "1", + String(prfRegPageSize), + fixedSearchValue, + pbnRuseNm, + searchYear, + fclts + ); setIsReset(true); }} excelClick={excelClick} @@ -196,7 +255,8 @@ export default function PbnstSttn() { }} btnClass={btnClass} ref={ref} - isAdmin={false} + isAdmin={!isAllAreaUser} + initSidoValue={fixedSidoValue} />
@@ -215,11 +275,12 @@ export default function PbnstSttn() { cellCLick(index); }} onPageChange={(page: number) => { + const fixedSearchValue = getFixedSearchValue(ref.current); pbnstSttnSearch( false, String(page), String(prfRegPageSize), - ref.current, + fixedSearchValue, pbnRuseNm, searchYear, fclts diff --git a/apps/wr/app/store/common/auth/index.tsx b/apps/wr/app/store/common/auth/index.tsx index e213bafa3..feca57c80 100644 --- a/apps/wr/app/store/common/auth/index.tsx +++ b/apps/wr/app/store/common/auth/index.tsx @@ -12,7 +12,7 @@ export type authState = { mdfcnAuthrtYn: string; delAuthrtYn: string; laeSeCd: string; // 지자체구분코드 - myUUID?: string; + myUUID?: string; }; }; @@ -67,17 +67,18 @@ export const authActionsExport: (set: any, get: any) => authActions = ( redirect("/"); } }, + // 로그인 사용자 지역코드 laeSeCd를 조회 후 상태 update getLaeSeCdCheck: async () => { try { const current = get().auth.laeSeCd; if (current && current !== "") { return; } - const infoMe = await getUserInfo(); - //console.log(infoMe); + const infoMe = await getUserInfo(); // 로그인 사용자의 정보 조회 + // console.log(infoMe); const isLaeSeCd = - infoMe.laeSeCd.slice(0, 2) === "G3" ? infoMe.laeSeCd.slice(2) : "0"; - + infoMe.laeSeCd.slice(0, 2) === "G3" ? infoMe.laeSeCd.slice(2) : "0"; //G341830 + // console.log("isLaeSeCd",isLaeSeCd); set((state: authState) => ({ ...state, auth: { diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx index 35458abd5..b1ae27e1b 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx @@ -99,18 +99,18 @@ export const pubFacStateExport: pubFacState = { pbnstHasuResCount: 1, }, }; - +// 재사용시설에 대한 정보 조회 export const checkWrRegList = async () => { const wrRegList = await getFetch("wr", `/reuseFc/pubFc`); return wrRegList; }; - +// 하수처리시설 정보 조회 const hasuListFetch = async ( page: string, - sggOrCity: string, + sggOrCity: string, // 해당 지자체의 하수처리시설 조회 searchTxt?: string ) => { - const checkWrRegListRes = await checkWrRegList(); + const checkWrRegListRes = await checkWrRegList(); // 재사용시설에 대한 정보 조회 let url = `/prcsFclt/ruseInfo/${page ? page : 1}/11/A/${sggOrCity ? sggOrCity : "11"}/0/1/SW`; diff --git a/packages/common/network/utils.ts b/packages/common/network/utils.ts index 010bf2fe2..ac92db852 100644 --- a/packages/common/network/utils.ts +++ b/packages/common/network/utils.ts @@ -7,14 +7,14 @@ export function getAuthLoginRedirectUrl(): string { } export function isAtLoginRedirectUrl(loginRedirectUrl: string): boolean { - if (typeof window === "undefined") return false; + if (typeof window === "undefined") return false; // 서버 환경에서는 현재 브라우저 URL을 알 수 없으므로 false 반환 try { - const target = new URL(loginRedirectUrl, window.location.href); + const target = new URL(loginRedirectUrl, window.location.href); // 상대경로/절대경로 모두 현재 주소 기준의 완전한 URL로 변환 return ( - target.origin === window.location.origin && - target.pathname === window.location.pathname + target.origin === window.location.origin && // 도메인(프로토콜 + 호스트 + 포트)이 같은지 확인 + target.pathname === window.location.pathname // 현재 페이지 경로가 로그인 이동 경로와 같은지 확인 ); } catch { - return false; + return false; // 잘못된 URL 문자열이면 비교할 수 없으므로 false 반환 } } From 0411b2a89460ed66e1fbc3a86c8742f3388af77d Mon Sep 17 00:00:00 2001 From: ljpark Date: Tue, 14 Apr 2026 14:57:26 +0900 Subject: [PATCH 06/18] =?UTF-8?q?wr:=20=EC=9E=AC=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?=EC=8B=9C=EC=84=A4=ED=98=84=ED=99=A9=20=EC=A7=80=EC=B0=A8?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruseFcltMng/fcltSttn/htdflSttn/page.tsx | 113 +++++++-- .../ruseFcltMng/fcltSttn/pbnstSttn/page.tsx | 7 +- .../ruseFcltMng/fcltSttn/rainSttn/page.tsx | 220 ++++++++++++------ .../ruseFcltMng/fcltSttn/rswtSttn/page.tsx | 116 +++++++-- .../prfmncSttn/pbnstPrfmncSttn/page.tsx | 111 +++++++-- .../ruseFcltMng/fcltSttn/pbnstSttn/index.tsx | 11 + .../src/ui/search_base_form/index.tsx | 15 +- .../src/ui/search_form_left/index.tsx | 44 +++- 8 files changed, 500 insertions(+), 137 deletions(-) diff --git a/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/htdflSttn/page.tsx b/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/htdflSttn/page.tsx index 2f31e616c..257b224aa 100644 --- a/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/htdflSttn/page.tsx +++ b/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/htdflSttn/page.tsx @@ -37,17 +37,20 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { useAlertStore } from "@/src/shared/lib"; import { useTranslation } from "react-i18next"; +const defaultSigunOptions: SelectDataType[] = [{ text: "전체", val: "0" }]; + export default function HtdflSttn() { - const { auth, code, htdflSttn, htdflSttnSearch, initHtdfl } = useWrStore((state) => state); + const { auth, code, htdflSttn, htdflSttnSearch, initHtdfl, getLaeSeCdCheck } = useWrStore((state) => state); const { laeSeCd } = auth; // 지자체구분코드 const { htdflSttnRes, htdflSttnResCount } = htdflSttn; const isDownloadTriggered = useRef(false); + const skipNextSearchRef = useRef(true); const { t } = useTranslation('base'); const [pageNation, setPageNation] = useState(1); const [isChHead, setIsChHead] = useState(false); const [resetPage, isResetPage] = useState(false); const [bdstTable, setBdstTable] = useState(); - const [selectSgg, setSelectSgg] = useState() + const [selectSgg, setSelectSgg] = useState(defaultSigunOptions) const [objSearch, setObjSearch] = useState({ sgg: "", city: "" }); const [detailModal, setDetailModal] = useState<{ isOpen: boolean; @@ -59,22 +62,23 @@ export default function HtdflSttn() { // }>({ isOpen: false, chBuilding: [] }); const [fcltNm, setFcltNm] = useState(""); const alert = useAlertStore((state) => state.setMessage); - - console.log("laeSeCd : ", laeSeCd); - useEffect(() => { - htdflSttnSearch(false, pageNation, htdflSttnSize, String(getConValue()), fcltNm, code.cityCd, code.sggCd); - isResetPage(false) - }, [pageNation]); + const isAllAreaUser = laeSeCd === "0"; + const isLaeSeCdReady = + laeSeCd === "0" || laeSeCd.length === 2 || laeSeCd.length === 5; + const fixedSidoValue = + !isAllAreaUser && laeSeCd?.length ? laeSeCd.slice(0, 2) : ""; + const fixedSigunValue = + !isAllAreaUser && laeSeCd?.length === 5 ? laeSeCd : "0"; const getSelCity = (val: string | number) => { const cityValue = String(val); if (cityValue === "0") { - setSelectSgg([]); + setSelectSgg(defaultSigunOptions); setObjSearch({ city: cityValue, sgg: "" }); } else { const data = getCity(cityValue, code.sggCd); - setSelectSgg(data); + setSelectSgg(data.length > 0 ? data : defaultSigunOptions); const firstSggVal = data.find(item => item.val === "0")?.val || (data[0]?.val ?? ""); setObjSearch({ city: cityValue, sgg: firstSggVal }); @@ -85,16 +89,24 @@ export default function HtdflSttn() { setObjSearch({ ...objSearch, sgg: String(val) }) }; - const getConValue = () => { + const getConValue = (): string => { if (!objSearch.city || objSearch.city === "0") { return "0"; } if (objSearch.sgg && objSearch.sgg !== "0") { - return objSearch.sgg; + return String(objSearch.sgg); } - return objSearch.city; + return String(objSearch.city); }; + const getFixedConValue = useCallback((): string => { + if (laeSeCd === undefined || laeSeCd === "0") { + return getConValue(); + } + + return laeSeCd.length === 5 ? laeSeCd : laeSeCd.slice(0, 2); + }, [laeSeCd, objSearch]); + const cellClick = (index: number | undefined, headId: string) => { if ( headId !== "chBuildingYn" && @@ -106,9 +118,37 @@ export default function HtdflSttn() { }; const excelClick = useCallback(() => { + if (!isLaeSeCdReady) { + return; + } + isDownloadTriggered.current = true; - htdflSttnSearch(true, 1, htdflSttnResCount, String(getConValue()), fcltNm, code.cityCd, code.sggCd); - }, [htdflSttnSearch, objSearch, code.cityCd, code.sggCd, fcltNm, htdflSttnResCount]); + htdflSttnSearch(true, 1, htdflSttnResCount, String(getFixedConValue()), fcltNm, code.cityCd, code.sggCd); + }, [htdflSttnSearch, fcltNm, htdflSttnResCount, code.cityCd, code.sggCd, getFixedConValue, isLaeSeCdReady]); + + useEffect(() => { + getLaeSeCdCheck(); + }, [getLaeSeCdCheck]); + + useEffect(() => { + if (!isLaeSeCdReady) { + return; + } + + if (laeSeCd === "0") { + setSelectSgg(defaultSigunOptions); + setObjSearch({ city: "0", sgg: "" }); + return; + } + + const cityValue = laeSeCd.slice(0, 2); + const sigunOptions = getCity(cityValue, code.sggCd); + setSelectSgg(sigunOptions.length > 0 ? sigunOptions : defaultSigunOptions); + setObjSearch({ + city: cityValue, + sgg: laeSeCd.length === 5 ? laeSeCd : "0", + }); + }, [laeSeCd, code.sggCd, isLaeSeCdReady]); useEffect(() => { if (!isDownloadTriggered.current) { @@ -135,10 +175,27 @@ export default function HtdflSttn() { }, [htdflSttn.excelList, code]); useEffect(() => { - if (!htdflSttn.isInit) { - initHtdfl(false, 1, htdflSttnSize, "0", fcltNm, code.cityCd, code.sggCd); + if (!isLaeSeCdReady || htdflSttn.isInit) { + return; } - }, [htdflSttn.isInit, initHtdfl]); + + skipNextSearchRef.current = true; + initHtdfl(false, 1, htdflSttnSize, String(getFixedConValue()), fcltNm, code.cityCd, code.sggCd); + }, [htdflSttn.isInit, initHtdfl, fcltNm, code.cityCd, code.sggCd, getFixedConValue, isLaeSeCdReady]); + + useEffect(() => { + if (!isLaeSeCdReady) { + return; + } + + if (skipNextSearchRef.current) { + skipNextSearchRef.current = false; + return; + } + + htdflSttnSearch(false, pageNation, htdflSttnSize, String(getFixedConValue()), fcltNm, code.cityCd, code.sggCd); + isResetPage(false) + }, [pageNation, htdflSttnSearch, fcltNm, code.cityCd, code.sggCd, getFixedConValue, isLaeSeCdReady]); useEffect(() => { const data = tableData(htdflSttnRes, { code }); @@ -171,15 +228,24 @@ export default function HtdflSttn() { className="col-span-2 w-1/6 h-full text-2xl" label="시도" selectClass={""} - selectArray={code.cityCd} + selectArray={ + !isAllAreaUser && fixedSidoValue + ? code.cityCd.filter((item) => item.val === fixedSidoValue) + : code.cityCd + } + value={objSearch.city || "0"} onSelectValue={getSelCity} /> item.val === "0" || item.val === fixedSigunValue) + : selectSgg || defaultSigunOptions + } + value={objSearch.sgg || "0"} onSelectValue={getSgg} />
@@ -201,7 +267,10 @@ export default function HtdflSttn() {
diff --git a/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/rainSttn/page.tsx b/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/rainSttn/page.tsx index 9de317a08..dae95f113 100644 --- a/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/rainSttn/page.tsx +++ b/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/rainSttn/page.tsx @@ -35,18 +35,21 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { useAlertStore } from "@/src/shared/lib"; import { useTranslation } from "react-i18next"; +const defaultSigunOptions: SelectDataType[] = [{ text: "전체", val: "0" }]; + export default function RainSttn() { - const { code, rainSttn, rainSttnSearch, rainSttnInit } = useWrStore( + const { auth, code, rainSttn, rainSttnSearch, rainSttnInit, getLaeSeCdCheck } = useWrStore( (state) => state ); const { rainSttnRes, rainSttnResCount } = rainSttn; + const { laeSeCd } = auth; const [pageNation, setPageNation] = useState(1); const [isChHead, setIsChHead] = useState(false); const [bdstTable, setBdstTable] = useState(); - const [selectSgg, setSelectSgg] = useState(); + const [selectSgg, setSelectSgg] = useState(defaultSigunOptions); const [resetPage, isResetPage] = useState(false); - const isDownloadTriggered = useRef(false); + const [isDownloadTriggered, setIsDownloadTriggered] = useState(false); const [objSearch, setObjSearch] = useState({ sgg: "", city: "", @@ -59,6 +62,14 @@ export default function RainSttn() { const [fcltNm, setFcltNm] = useState(""); const { t } = useTranslation("base"); const alert = useAlertStore((state) => state.setMessage); + const skipNextSearchRef = useRef(true); + const isAllAreaUser = laeSeCd === "0"; + const isLaeSeCdReady = + laeSeCd === "0" || laeSeCd.length === 2 || laeSeCd.length === 5; + const fixedSidoValue = + !isAllAreaUser && laeSeCd?.length ? laeSeCd.slice(0, 2) : ""; + const fixedSigunValue = + !isAllAreaUser && laeSeCd?.length === 5 ? laeSeCd : "0"; const cellClick = (index: number | undefined) => { if (index !== undefined && bdstTable !== undefined) { @@ -69,11 +80,11 @@ export default function RainSttn() { const getSelCity = (val: string | number) => { const cityValue = String(val); if (cityValue === "0") { - setSelectSgg([]); + setSelectSgg(defaultSigunOptions); setObjSearch({ city: cityValue, sgg: "" }); } else { const data = getCity(cityValue, code.sggCd); - setSelectSgg(data); + setSelectSgg(data.length > 0 ? data : defaultSigunOptions); const firstSggVal = data.find((item) => item.val === "0")?.val || (data[0]?.val ?? ""); setObjSearch({ city: cityValue, sgg: firstSggVal }); @@ -84,21 +95,135 @@ export default function RainSttn() { setObjSearch({ ...objSearch, sgg: String(val) }); }; - const getConValue = () => { + const getConValue = (): string => { if (!objSearch.city || objSearch.city === "0") { return "0"; } if (objSearch.sgg && objSearch.sgg !== "0") { - return objSearch.sgg; + return String(objSearch.sgg); } - return objSearch.city; + return String(objSearch.city); }; - useEffect(() => { - if (!rainSttn.isInit) { - rainSttnInit(false, 1, rainSttnSize, "0", fcltNm, code as unknown as Record); + const getFixedConValue = useCallback((): string => { + if (laeSeCd === undefined || laeSeCd === "0") { + return getConValue(); } - }, [rainSttn.isInit, rainSttnInit]); + + return laeSeCd.length === 5 ? laeSeCd : laeSeCd.slice(0, 2); + }, [laeSeCd, objSearch]); + + useEffect(() => { + getLaeSeCdCheck(); + }, [getLaeSeCdCheck]); + + useEffect(() => { + if (!isLaeSeCdReady) { + return; + } + + if (laeSeCd === "0") { + setSelectSgg(defaultSigunOptions); + setObjSearch({ city: "0", sgg: "" }); + return; + } + + const cityValue = laeSeCd.slice(0, 2); + const sigunOptions = getCity(cityValue, code.sggCd); + setSelectSgg(sigunOptions.length > 0 ? sigunOptions : defaultSigunOptions); + setObjSearch({ + city: cityValue, + sgg: laeSeCd.length === 5 ? laeSeCd : "0", + }); + }, [laeSeCd, code.sggCd, isLaeSeCdReady]); + + useEffect(() => { + if (!isLaeSeCdReady || rainSttn.isInit) { + return; + } + + skipNextSearchRef.current = true; + rainSttnInit( + false, + 1, + rainSttnSize, + getFixedConValue(), + fcltNm, + code as unknown as Record + ); + }, [rainSttn.isInit, rainSttnInit, fcltNm, code, getFixedConValue, isLaeSeCdReady]); + + useEffect(() => { + if (!isLaeSeCdReady) { + return; + } + + if (skipNextSearchRef.current) { + skipNextSearchRef.current = false; + return; + } + + rainSttnSearch( + false, + pageNation, + rainSttnSize, + String(getFixedConValue()), + fcltNm, + code as unknown as Record + ); + isResetPage(false); + }, [pageNation, rainSttnSearch, fcltNm, code, getFixedConValue, isLaeSeCdReady]); + + useEffect(() => { + if (!isLaeSeCdReady) { + return; + } + + if (!isDownloadTriggered) { + return; + } + if (rainSttn.excelList.length > 0) { + const sheetArray: SheetParameter[] = [{ + data: rainSttn.excelList, + headerNum: 3, + headerRange: "A1:W3", + sheetName: 'sheet1', + startCol: 'A', + decimalArray0: [6,10,12,14,16] // 0부터 시작 + }]; + excelDownloadNumStyle( + "빗물이용시설 현황", + "sample_rainSttn.xlsx", // public 폴더에 있는 template 파일 경로 + sheetArray + ); + } else { + alert(t("excelDownErrMsg")); + } + setIsDownloadTriggered(false); + }, [rainSttn.excelList, isDownloadTriggered, alert, t, isLaeSeCdReady]); + + const excelClick = useCallback(() => { + if (!isLaeSeCdReady) { + return; + } + + setIsDownloadTriggered(true); + rainSttnSearch( + true, + 1, + rainSttnResCount, + String(getFixedConValue()), + fcltNm, + code as unknown as Record + ); + }, [ + rainSttnSearch, + fcltNm, + rainSttnResCount, + code, + getFixedConValue, + isLaeSeCdReady, + ]); useEffect(() => { const data = tableData(rainSttnRes, { code }); @@ -143,60 +268,6 @@ export default function RainSttn() { setBdstTable(chData); }, [code, rainSttnRes]); - useEffect(() => { - rainSttnSearch( - false, - pageNation, - rainSttnSize, - String(getConValue()), - fcltNm, - code as unknown as Record - ); - isResetPage(false); - }, [pageNation]); - - useEffect(() => { - if (!isDownloadTriggered.current) { - return; - } - if (rainSttn.excelList.length > 0) { - const sheetArray: SheetParameter[] = [{ - data: rainSttn.excelList, - headerNum: 3, - headerRange: "A1:W3", - sheetName: 'sheet1', - startCol: 'A', - decimalArray0: [6,10,12,14,16] // 0부터 시작 - }]; - excelDownloadNumStyle( - "빗물이용시설 현황", - "sample_rainSttn.xlsx", // public 폴더에 있는 template 파일 경로 - sheetArray - ); - } else { - alert(t("excelDownErrMsg")); - } - isDownloadTriggered.current = false; - }, [rainSttn.excelList]); - - const excelClick = useCallback(() => { - isDownloadTriggered.current = true; - rainSttnSearch( - true, - 1, - rainSttnResCount, - String(getConValue()), - fcltNm, - code as unknown as Record - ); - }, [ - rainSttnSearch, - objSearch, - code.cityCd, - code.sggCd, - fcltNm, - rainSttnResCount, - ]); return ( <> @@ -210,15 +281,24 @@ export default function RainSttn() { className="col-span-2 w-1/6 h-full text-2xl" label="시도" selectClass={""} - selectArray={code.cityCd} + selectArray={ + !isAllAreaUser && fixedSidoValue + ? code.cityCd.filter((item) => item.val === fixedSidoValue) + : code.cityCd + } + value={objSearch.city || "0"} onSelectValue={getSelCity} /> item.val === "0" || item.val === fixedSigunValue) + : selectSgg || defaultSigunOptions + } + value={objSearch.sgg || "0"} onSelectValue={getSgg} />
@@ -243,7 +323,7 @@ export default function RainSttn() { false, 1, rainSttnSize, - String(getConValue()), + String(getFixedConValue()), fcltNm, code as unknown as Record ); diff --git a/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/rswtSttn/page.tsx b/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/rswtSttn/page.tsx index c95c7aafe..912cce28d 100644 --- a/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/rswtSttn/page.tsx +++ b/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/rswtSttn/page.tsx @@ -35,24 +35,35 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { useAlertStore } from "@/src/shared/lib"; import { useTranslation } from "react-i18next"; +const defaultSigunOptions: SelectDataType[] = [{ text: "전체", val: "0" }]; + export default function RswtSttn() { - const { code, rswtSttn, rswtSttnSearch, rswtSttnInit } = useWrStore( + const { auth, code, rswtSttn, rswtSttnSearch, rswtSttnInit, getLaeSeCdCheck } = useWrStore( (state) => state ); const [pageNation, setPageNation] = useState(1); const { rswtSttnRes, rswtSttnResCount } = rswtSttn; + const { laeSeCd } = auth; const [isChHead, setIsChHead] = useState(false); const [resetPage, isResetPage] = useState(false); const [bdstTable, setBdstTable] = useState(); const isDownloadTriggered = useRef(false); - const [selectSgg, setSelectSgg] = useState() + const skipNextSearchRef = useRef(true); + const [selectSgg, setSelectSgg] = useState(defaultSigunOptions) const [objSearch, setObjSearch] = useState({ sgg: "", city: "" }); const [detailModal, setDetailModal] = useState<{ isOpen: boolean, data: customRswtBdst | undefined }>({ data: undefined, isOpen: false }) const [fcltNm, setFcltNm] = useState(""); const { t } = useTranslation('base'); const alert = useAlertStore((state) => state.setMessage); + const isAllAreaUser = laeSeCd === "0"; + const isLaeSeCdReady = + laeSeCd === "0" || laeSeCd.length === 2 || laeSeCd.length === 5; + const fixedSidoValue = + !isAllAreaUser && laeSeCd?.length ? laeSeCd.slice(0, 2) : ""; + const fixedSigunValue = + !isAllAreaUser && laeSeCd?.length === 5 ? laeSeCd : "0"; const cellClick = (index: number | undefined) => { if ( index !== undefined && bdstTable !== undefined) { @@ -64,11 +75,11 @@ export default function RswtSttn() { const cityValue = String(val); if (cityValue === "0") { - setSelectSgg([]); + setSelectSgg(defaultSigunOptions); setObjSearch({ city: cityValue, sgg: "" }); } else { const data = getCity(cityValue, code.sggCd); - setSelectSgg(data); + setSelectSgg(data.length > 0 ? data : defaultSigunOptions); const firstSggVal = data.find(item => item.val === "0")?.val || (data[0]?.val ?? ""); setObjSearch({ city: cityValue, sgg: firstSggVal }); @@ -79,21 +90,63 @@ export default function RswtSttn() { setObjSearch({ ...objSearch, sgg: String(val) }) }; - const getConValue = () => { + const getConValue = (): string => { if (!objSearch.city || objSearch.city === "0") { return "0"; } if (objSearch.sgg && objSearch.sgg !== "0") { - return objSearch.sgg; + return String(objSearch.sgg); } - return objSearch.city; + return String(objSearch.city); }; - useEffect(() => { - if (!rswtSttn.isInit&&code) { - rswtSttnInit(false, 1, rswtSttnSize, code.userSiSggCd, fcltNm, code as unknown as Record); + const getFixedConValue = useCallback((): string => { + if (laeSeCd === undefined || laeSeCd === "0") { + return getConValue(); } - }, [rswtSttn.isInit, rswtSttnInit]); + + return laeSeCd.length === 5 ? laeSeCd : laeSeCd.slice(0, 2); + }, [laeSeCd, objSearch]); + + useEffect(() => { + getLaeSeCdCheck(); + }, [getLaeSeCdCheck]); + + useEffect(() => { + if (!isLaeSeCdReady) { + return; + } + + if (laeSeCd === "0") { + setSelectSgg(defaultSigunOptions); + setObjSearch({ city: "0", sgg: "" }); + return; + } + + const cityValue = laeSeCd.slice(0, 2); + const sigunOptions = getCity(cityValue, code.sggCd); + setSelectSgg(sigunOptions.length > 0 ? sigunOptions : defaultSigunOptions); + setObjSearch({ + city: cityValue, + sgg: laeSeCd.length === 5 ? laeSeCd : "0", + }); + }, [laeSeCd, code.sggCd, isLaeSeCdReady]); + + useEffect(() => { + if (!isLaeSeCdReady || rswtSttn.isInit) { + return; + } + + skipNextSearchRef.current = true; + rswtSttnInit( + false, + 1, + rswtSttnSize, + getFixedConValue(), + fcltNm, + code as unknown as Record + ); + }, [rswtSttn.isInit, rswtSttnInit, fcltNm, code, getFixedConValue, isLaeSeCdReady]); useEffect(() => { const data = tableData(rswtSttnRes, { code }); @@ -150,14 +203,27 @@ export default function RswtSttn() { }, [rswtSttn.excelList]); const excelClick = useCallback(() => { + if (!isLaeSeCdReady) { + return; + } + isDownloadTriggered.current = true; - rswtSttnSearch(true, 1, rswtSttnResCount, String(getConValue()), fcltNm, code as unknown as Record); - }, [rswtSttnSearch, objSearch, code.cityCd, code.sggCd, fcltNm, rswtSttnResCount]); + rswtSttnSearch(true, 1, rswtSttnResCount, String(getFixedConValue()), fcltNm, code as unknown as Record); + }, [rswtSttnSearch, fcltNm, rswtSttnResCount, code, getFixedConValue, isLaeSeCdReady]); useEffect(() => { - rswtSttnSearch(false, pageNation, rswtSttnSize, String(getConValue()), fcltNm, code as unknown as Record); + if (!isLaeSeCdReady) { + return; + } + + if (skipNextSearchRef.current) { + skipNextSearchRef.current = false; + return; + } + + rswtSttnSearch(false, pageNation, rswtSttnSize, String(getFixedConValue()), fcltNm, code as unknown as Record); isResetPage(false) - }, [pageNation]); + }, [pageNation, rswtSttnSearch, fcltNm, code, getFixedConValue, isLaeSeCdReady]); return ( <> @@ -171,7 +237,12 @@ export default function RswtSttn() { className="col-span-2 w-1/6 h-full text-2xl" label="시도" selectClass={""} - selectArray={code.cityCd} + selectArray={ + !isAllAreaUser && fixedSidoValue + ? code.cityCd.filter((item) => item.val === fixedSidoValue) + : code.cityCd + } + value={objSearch.city || "0"} onSelectValue={ getSelCity } @@ -180,8 +251,12 @@ export default function RswtSttn() { className="col-span-2 w-1/6 h-full text-2xl ml-4" label="시군구" selectClass={""} - selectArray={selectSgg || []} - value={objSearch.sgg} // value props 추가 + selectArray={ + !isAllAreaUser && laeSeCd?.length === 5 + ? (selectSgg || defaultSigunOptions).filter((item) => item.val === "0" || item.val === fixedSigunValue) + : selectSgg || defaultSigunOptions + } + value={objSearch.sgg || "0"} onSelectValue={ getSgg } @@ -205,7 +280,10 @@ export default function RswtSttn() {
diff --git a/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx index 9e2c47a04..ee94ada00 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx @@ -112,6 +112,12 @@ export const hasuListFetch = async ( year: string, flcts: string[] ) => { + + // 시설 코드가 없으면 FO 조회를 호출하지 않고 빈 결과 반환 + if (flcts.length === 0) { + return { data: [], count: 0 }; + } + let url = ``; let filteredFclt = flcts; const filterData = @@ -163,6 +169,11 @@ export const hasuListFetch = async ( url += "/" + searchNm; } + // 시설 코드가 없으면 FO 조회를 호출하지 않고 빈 결과 반환 + if (filteredFclt.length === 0) { + return { data: [], count: 0 }; + } + const pbnList = await postFectch( `/prcsFclt/ruseInfo/1/${filteredFclt.length}/` + url, filteredFclt, diff --git a/packages/common/business_components/src/ui/search_base_form/index.tsx b/packages/common/business_components/src/ui/search_base_form/index.tsx index 9cef315c3..e4fef1e1e 100644 --- a/packages/common/business_components/src/ui/search_base_form/index.tsx +++ b/packages/common/business_components/src/ui/search_base_form/index.tsx @@ -26,6 +26,7 @@ type SearchBaseType = { leftSelectClassName?: string; buttonOrder?: ("query" | "init" | "excel")[]; initSidoValue?: string | ''; + initSigunValue?: string | ''; excelDisabled?: boolean; } export type ReturnType = { @@ -61,11 +62,12 @@ function SearchBaseForm ({ leftSelectClassName, buttonOrder, initSidoValue='', + initSigunValue='', excelDisabled=false, }: SearchBaseType) { const [partValue, setPartValue] = useState("A"); const [sidoValue, setSidoValue] = useState(initSidoValue?initSidoValue:"0"); - const [sigunValue, setSigunValue] = useState("0"); + const [sigunValue, setSigunValue] = useState(initSigunValue?initSigunValue:"0"); const [sidoText, setSidoText] = useState(""); const [sigunText, setSigunText] = useState(""); const [regionValue, setRegionValue] = useState("0"); @@ -115,18 +117,24 @@ function SearchBaseForm ({ if(initSidoValue){ setSidoValue(initSidoValue); } + if(initSigunValue){ + setSigunValue(initSigunValue); + } setResetTrigger(true); initClick(); - }, [initSidoValue, setResetTrigger, initClick]); + }, [initSidoValue, initSigunValue, setResetTrigger, initClick]); useEffect(() => { if (resetTrigger) { if(initSidoValue!=""){ setSidoValue(initSidoValue); } + if(initSigunValue!=""){ + setSigunValue(initSigunValue); + } setResetTrigger(false); } - }, [resetTrigger]); + }, [resetTrigger, initSidoValue, initSigunValue]); const excelDownload = () => { excelClick({ partValue: partValue, @@ -179,6 +187,7 @@ function SearchBaseForm ({ leftClassName={leftClassName} leftSelectClassName={leftSelectClassName} initSidoValue={initSidoValue} + initSigunValue={initSigunValue} > {leftChildren} diff --git a/packages/common/business_components/src/ui/search_form_left/index.tsx b/packages/common/business_components/src/ui/search_form_left/index.tsx index 04537ec5e..0b04562b4 100644 --- a/packages/common/business_components/src/ui/search_form_left/index.tsx +++ b/packages/common/business_components/src/ui/search_form_left/index.tsx @@ -47,6 +47,7 @@ type SearchFormLeftProps = { leftClassName?: string; leftSelectClassName?: string; initSidoValue?: string; + initSigunValue?: string; } export type EnvPartSelectDataType = SelectDataType & { @@ -59,12 +60,12 @@ const selectPartData:SelectDataType[] = [ { text: "환경청별", val: 'G' }, ]; -function SearchFormLeft({ isShowPart = false, children, selectSidoData = [], selectSigunData = [], selectRegionData = [], selectUnitWaterShedData = [], selectMiddleRegionData = [], selectEnvPartData = [], setRtnPartValue, setRtnSidoValue, setRtnSidoText, setRtnSigunValue, setRtnSigunText, setRtnRegionValue, setRtnUnitWaterShedValue, setRtnMiddleRegionValue, setRtnEnvPartValue, setRtnEnvPartSidoValue, setRtnEnvPartSigunValue, resetTrigger = false, isAdmin = false, leftClassName, leftSelectClassName, initSidoValue }: SearchFormLeftProps) { +function SearchFormLeft({ isShowPart = false, children, selectSidoData = [], selectSigunData = [], selectRegionData = [], selectUnitWaterShedData = [], selectMiddleRegionData = [], selectEnvPartData = [], setRtnPartValue, setRtnSidoValue, setRtnSidoText, setRtnSigunValue, setRtnSigunText, setRtnRegionValue, setRtnUnitWaterShedValue, setRtnMiddleRegionValue, setRtnEnvPartValue, setRtnEnvPartSidoValue, setRtnEnvPartSigunValue, resetTrigger = false, isAdmin = false, leftClassName, leftSelectClassName, initSidoValue, initSigunValue }: SearchFormLeftProps) { /******************선택된 데이터******************/ const [partValue, setPartValue] = useState('A'); //행정별 시도 시군구 const [sidoValue, setSidoValue] = useState(initSidoValue?initSidoValue : '0'); - const [sigunValue, setSigunValue] = useState('0'); + const [sigunValue, setSigunValue] = useState(initSigunValue?initSigunValue : '0'); const [sidoText, setSidoText] = useState(''); const [sigunText, setSigunText] = useState(''); //유역별 @@ -87,6 +88,39 @@ function SearchFormLeft({ isShowPart = false, children, selectSidoData = [], sel const [filteredEnvPartSigunData, setFilteredEnvPartSigunData] = useState([{ text: "전체", val: '0' }]); const [initTrigger, setInitTrigger] = useState(false); + + useEffect(() => { + if (!initSidoValue) return; + if (!selectSidoData.some((item) => item.val === initSidoValue)) return; + + const selectedSido = selectSidoData.find((item) => item.val === initSidoValue); + setSidoValue(initSidoValue); + setSidoText(selectedSido?.text || ""); + if (setRtnSidoValue) setRtnSidoValue(initSidoValue); + if (setRtnSidoText) setRtnSidoText(selectedSido?.text || ""); + initializeSigunData(initSidoValue); + }, [initSidoValue, selectSidoData]); + + useEffect(() => { + if (!initSidoValue || !initSigunValue) return; + + const initializedSigunData = [ + { text: "전체", val: '0' }, + ...selectSigunData.filter( + (item: SelectDataType) => item.val.substring(0, 2) === initSidoValue + ) + ]; + const selectedSigun = initializedSigunData.find((item) => item.val === initSigunValue); + + if (!selectedSigun) return; + + setFilteredSigunData(initializedSigunData); + setSigunValue(initSigunValue); + setSigunText(selectedSigun.text || ""); + if (setRtnSigunValue) setRtnSigunValue(initSigunValue); + if (setRtnSigunText) setRtnSigunText(selectedSigun.text || ""); + }, [initSidoValue, initSigunValue, selectSigunData, setRtnSigunText, setRtnSigunValue]); + // 시군구 세팅 const initializeSigunData = (sidoCd: string) => { if (sidoCd === '0') { @@ -204,7 +238,7 @@ function SearchFormLeft({ isShowPart = false, children, selectSidoData = [], sel setRtnSidoText(selectedSido?.text || ''); } initializeSigunData(val); - onSelectValue('0', 'sigun'); + onSelectValue(val === initSidoValue && initSigunValue ? initSigunValue : '0', 'sigun'); break; case 'sigun': if (setRtnSigunValue) { @@ -276,7 +310,7 @@ function SearchFormLeft({ isShowPart = false, children, selectSidoData = [], sel const resetAllValues = () => { setPartValue('A'); setSidoValue(initSidoValue?initSidoValue:'0'); - setSigunValue('0'); + setSigunValue(initSigunValue?initSigunValue:'0'); setRegionValue('0'); setUnitWaterShedValue('0'); setMiddleRegionValue('0'); @@ -291,7 +325,7 @@ function SearchFormLeft({ isShowPart = false, children, selectSidoData = [], sel if (setRtnPartValue) setRtnPartValue('A'); if (setRtnSidoValue) setRtnSidoValue(initSidoValue?initSidoValue:'0'); - if (setRtnSigunValue) setRtnSigunValue('0'); + if (setRtnSigunValue) setRtnSigunValue(initSigunValue?initSigunValue:'0'); if (setRtnRegionValue) setRtnRegionValue('0'); if (setRtnUnitWaterShedValue) setRtnUnitWaterShedValue('0'); if (setRtnMiddleRegionValue) setRtnMiddleRegionValue('0'); From 0e57bbc4e842a9a516f419d4f5afc5641c625032 Mon Sep 17 00:00:00 2001 From: ljpark Date: Tue, 14 Apr 2026 15:45:20 +0900 Subject: [PATCH 07/18] =?UTF-8?q?wr:=20=ED=95=98=EC=88=98=EC=9E=AC?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=EC=8B=A4=EC=A0=81=ED=98=84=ED=99=A9=20?= =?UTF-8?q?=EC=A7=80=EC=9E=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruseFcltMng/fcltSttn/htdflSttn/page.tsx | 2 +- .../ruseFcltMng/fcltSttn/pbnstSttn/page.tsx | 2 +- .../ruseFcltMng/fcltSttn/rainSttn/page.tsx | 2 +- .../ruseFcltMng/fcltSttn/rswtSttn/page.tsx | 2 +- .../prfmncSttn/chkgdnSttn/page.tsx | 170 ++++++++++++++++-- .../src/ui/search_input/index.tsx | 2 +- 6 files changed, 161 insertions(+), 19 deletions(-) diff --git a/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/htdflSttn/page.tsx b/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/htdflSttn/page.tsx index 257b224aa..5705faabc 100644 --- a/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/htdflSttn/page.tsx +++ b/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/htdflSttn/page.tsx @@ -248,7 +248,7 @@ export default function HtdflSttn() { value={objSearch.sgg || "0"} onSelectValue={getSgg} /> -
+
diff --git a/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx b/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx index cd243cdaf..2fd1c2d55 100644 --- a/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx +++ b/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx @@ -225,7 +225,7 @@ export default function PbnstSttn() { setSearchYear(String(val)) } /> -