diff --git a/apps/dm/.env.development b/apps/dm/.env.development index 581a639c5..66f910fbe 100644 --- a/apps/dm/.env.development +++ b/apps/dm/.env.development @@ -17,3 +17,6 @@ NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/ap #NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정 + +NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do +NEXT_PUBLIC_WR_ADDRESS_KEY = devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM= diff --git a/apps/dm/.env.local b/apps/dm/.env.local index f6000ca82..1c3474d11 100644 --- a/apps/dm/.env.local +++ b/apps/dm/.env.local @@ -20,3 +20,6 @@ NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/ap #NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정 + +NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do +NEXT_PUBLIC_WR_ADDRESS_KEY = devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM= diff --git a/apps/dm/.env.production b/apps/dm/.env.production index 4ce1ba027..358de43c9 100644 --- a/apps/dm/.env.production +++ b/apps/dm/.env.production @@ -16,3 +16,6 @@ NEXT_PUBLIC_NGII_URL = http://10.98.25.94:18080/openapi/Gettile.do?apiKey=BDE2BE NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-dev.svc.cluster.local:8000/sewerage-service/api + +NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do +NEXT_PUBLIC_WR_ADDRESS_KEY = U01TXOFVVEgyMDI1MDgyODE0MTAxNzExNjEyMjA= diff --git a/apps/dm/components/ui/common/utils/utils.ts b/apps/dm/components/ui/common/utils/utils.ts index f66437553..19c92bff2 100644 --- a/apps/dm/components/ui/common/utils/utils.ts +++ b/apps/dm/components/ui/common/utils/utils.ts @@ -15,4 +15,40 @@ export const sanitizeInput = (val: string, allowDecimal = false) => { // 정수만 허용 return val.replace(/[^0-9]/g, ""); -}; \ No newline at end of file +}; + + + +// utils: format Date <-> "YYYY-MM-DD" without moment +export const formatDateToYMD=(value: Date): string => { + if (!value) return ""; + // value can be Date | string ("YYYY-MM-DD") + if (typeof value === "string") { + // assume already in yyyy-mm-dd + if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return value; + const d = new Date(value); + if (isNaN(d.getTime())) return ""; + return toYMD(d); + } + if (value instanceof Date) { + if (isNaN(value.getTime())) return ""; + return toYMD(value); + } + return ""; +} + +const toYMD=(date: Date): string => { + // avoid timezone shift by normalizing to local date parts + const y = date.getFullYear(); + const m = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + return `${y}-${m}-${day}`; +} + +// const parseYMDToDate=(ymd: string): Date | null => { +// if (!/^\d{4}-\d{2}-\d{2}$/.test(ymd)) return null; +// const [y, m, d] = ymd.split("-").map(Number); +// const date = new Date(y, (m - 1), d); +// // validate round-trip +// return (date.getFullYear() === y && date.getMonth() === m - 1 && date.getDate() === d) ? date : null; +// } \ No newline at end of file diff --git a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx index 69514da41..7c6c13b32 100644 --- a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx +++ b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx @@ -2,7 +2,7 @@ import ModalSimilarPage from "@/app/(base-layout)/pages/sdmMng/sdmInfoInsert/mod import { useDmStore } from "@/app/store"; import { CANDIDATE, - DatePicker, + // DatePicker, FormField, FormLabel, FormUi, @@ -10,7 +10,7 @@ import { UiModal, } from "@common/business_components"; import { Input } from "@common/components"; -import { Button, Label } 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"; @@ -22,7 +22,7 @@ import { } from "react-hook-form"; import SearchInputText from "../common/searhDemandInput/component"; import React from "react"; -import { sanitizeInput } from "../common/utils/utils"; +import { formatDateToYMD, sanitizeInput } from "../common/utils/utils"; import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; import type { SdmInfoFormData } from "@/app/store/sdmMng"; @@ -161,7 +161,9 @@ export function SdmInfoRegForm({ if (defaults) { Object.entries(defaults).forEach(([field, value]) => { - form.setValue(field as keyof SdmInfoFormData, value, { shouldDirty: true }); + form.setValue(field as keyof SdmInfoFormData, value, { + shouldDirty: true, + }); }); } }, [form.getValues("usePurpose")]); @@ -208,8 +210,8 @@ export function SdmInfoRegForm({ formIndex: 0, required: true, component: (field) => ( -
- + {/* { @@ -224,8 +226,18 @@ export function SdmInfoRegForm({ }, }} showOutsideDays={true} - selectClass={undefined} - /> + selectClass={undefined} + /> */} + {/* 네이티브 input */} + { + const selectedDate = e.target.value ? new Date(e.target.value) : null; + field.onChange(selectedDate); + }} + value={ formatDateToYMD(field.value as Date) } + />
), }, @@ -363,13 +375,11 @@ export function SdmInfoRegForm({ render={({ field }) => name === "money" ? (
- - { component(field)} + + {component(field)}
- // {component(field)} ) : ( + // {component(field)} { field.onChange(val.toString()); }} - value={field.value } + value={field.value} readOnly={isDisabled} onBlur={field.onBlur} ref={field.ref} diff --git a/apps/dm/middleware.ts b/apps/dm/middleware.ts index 8fcfd4167..6b37ac02e 100644 --- a/apps/dm/middleware.ts +++ b/apps/dm/middleware.ts @@ -7,6 +7,8 @@ const serverApiUrl = process.env.NEXT_PUBLIC_CS_SERVER_API_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 BUSINESS_JUSO_API_URL = process.env.NEXT_PUBLIC_FINDADRESS_URL || ""; +const AdressKey = process.env.NEXT_PUBLIC_WR_ADDRESS_KEY || ""; export function middleware(req: NextRequest) { const pathname = req.nextUrl.pathname; @@ -24,8 +26,8 @@ export function middleware(req: NextRequest) { if (pathname.startsWith("/server")) { const decodedParams = Array.from(searchParams.entries()) - .map(([key, value]) => `${key}=${decodeURIComponent(value)}`) - .join("&"); + .map(([key, value]) => `${key}=${decodeURIComponent(value)}`) + .join("&"); const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${ decodedParams ? `?${decodedParams}` : "" @@ -38,7 +40,6 @@ export function middleware(req: NextRequest) { return NextResponse.redirect(new URL("/pages/wrStatus", req.url)); } - if (pathname.startsWith("/attach-service")) { const requestHeaders = new Headers(req.headers); // const token = @@ -69,7 +70,7 @@ export function middleware(req: NextRequest) { `[Middleware] Rewriting from '/user-service' path to: ${newUrl}` ); return NextResponse.rewrite(new URL(newUrl), { - headers: requestHeaders + headers: requestHeaders, }); } @@ -81,33 +82,31 @@ export function middleware(req: NextRequest) { let newUrl = `${GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`; if ( - typename == "hasudo:tb_il_swage_prcs_fclt" //하수처리시설 - || typename == "hasudo:tb_ms_swage_prcs_zone" //하수처리구역 - || typename == "hasudo:tb_ms_sbmrg_dngr_map" //침수위험지도 - || typename == "hasudo:tb_ms_flood_dngr_map" //도시침수지도 + typename == "hasudo:tb_il_swage_prcs_fclt" || //하수처리시설 + typename == "hasudo:tb_ms_swage_prcs_zone" || //하수처리구역 + typename == "hasudo:tb_ms_sbmrg_dngr_map" || //침수위험지도 + typename == "hasudo:tb_ms_flood_dngr_map" //도시침수지도 ) { newUrl = `${GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`; - } - else if ( - typename == "hasudo:tb_ms_swage_ppl" //관로 - || typename == "hasudo:tb_ms_swage_mhl" //맨홀 - || typename == "hasudo:tb_ms_swage_pmstn" //펌프장 - || typename == "hasudo:tb_ms_stofcbr" //우수토실 + } else if ( + typename == "hasudo:tb_ms_swage_ppl" || //관로 + typename == "hasudo:tb_ms_swage_mhl" || //맨홀 + typename == "hasudo:tb_ms_swage_pmstn" || //펌프장 + typename == "hasudo:tb_ms_stofcbr" //우수토실 ) { newUrl = `${LX_GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`; - } - else if ( - typename == "hasudo:om_dt_untrt_swg_rain" //중점우수토실 - || typename == "hasudo:om_wqlt_station" //관측소 - || typename == "hasudo:tb_wr_bdst_ruse_fclt" //재이용시설 - || typename == "hasudo:tb_wr_rnwtr_utztn_fclt" //빗물이용시설 - || typename == "hasudo:tb_wr_rswtr_fclt" //중수도시설 - || typename == "hasudo:tb_wr_htdfl_fclt" //온배수시설 - || typename == "hasudo:tb_wr_ruse_fclt_dmd_co" //수요처 - || typename == "hasudo:tb_wr_tmpr_dmd_co" //잠재수요처 - ){ + } else if ( + typename == "hasudo:om_dt_untrt_swg_rain" || //중점우수토실 + typename == "hasudo:om_wqlt_station" || //관측소 + typename == "hasudo:tb_wr_bdst_ruse_fclt" || //재이용시설 + typename == "hasudo:tb_wr_rnwtr_utztn_fclt" || //빗물이용시설 + typename == "hasudo:tb_wr_rswtr_fclt" || //중수도시설 + typename == "hasudo:tb_wr_htdfl_fclt" || //온배수시설 + typename == "hasudo:tb_wr_ruse_fclt_dmd_co" || //수요처 + typename == "hasudo:tb_wr_tmpr_dmd_co" //잠재수요처 + ) { const url = req.nextUrl.clone(); - url.searchParams.set('TYPENAME', typename.replace("hasudo:","")); + url.searchParams.set("TYPENAME", typename.replace("hasudo:", "")); queryParams = url.searchParams.toString(); newUrl = `${SEWERAGE_API_URL}/v1/map/geometry${queryParams ? `?${queryParams}` : ""}`; } @@ -118,15 +117,41 @@ export function middleware(req: NextRequest) { if (pathname.startsWith("/sewerage-service")) { const queryParams = searchParams.toString(); - const newUrl = `${SEWERAGE_API_URL}${pathname.replace("/sewerage-service/api", "")}${queryParams ? `?${queryParams}` : "" - }`; + const newUrl = `${SEWERAGE_API_URL}${pathname.replace("/sewerage-service/api", "")}${ + queryParams ? `?${queryParams}` : "" + }`; console.log(`[Middleware] Rewriting from '/geoserver' path to: ${newUrl}`); return NextResponse.rewrite(new URL(newUrl)); } + if (pathname.startsWith("/bussiness-juso-service")) { + console.log("AdressKey ", AdressKey); + const params = new URLSearchParams({ + currentPage: "1", // 원하는 페이지 + countPerPage: "5", // 페이지당 결과 수 + keyword: searchParams.get("keyword") || "", // 검색어(주소) + confmKey: AdressKey, // 인증키 인터넷용 패쇄망용은 따로 발급 필요 즉 middleware에서 치환해서 사용해야할듯 + resultType: "json", // 결과 형식 기본은 xml + }); + const newUrl = `${BUSINESS_JUSO_API_URL}?${params.toString()}`; + console.log(`[Middleware] Request to business.juso.go.kr`); + console.log(req.url); + console.log( + `[Middleware] Rewriting from '/business.juso.go.kr' path to: ${newUrl}` + ); + return NextResponse.rewrite(new URL(newUrl)); + } return NextResponse.next(); } export const config = { - matcher: ["/server/:path*", "/user-service/:path*", "/attach-service/:path*", "/geoserver/:path*", "/sewerage-service/:path*", "/"], + matcher: [ + "/server/:path*", + "/user-service/:path*", + "/attach-service/:path*", + "/geoserver/:path*", + "/sewerage-service/:path*", + "/bussiness-juso-service/:path*", + "/", + ], }; 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 24f9a3f8d..b3db19b1c 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx @@ -12,8 +12,8 @@ import RdiiChart from './chart/rdiiChart'; import { useInfilChartModalStore, useRdiiChartModalStore } from "./chartModalStore"; import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; import { codeMapper } from "../common/codeMapper"; -import { searchUpperPpls } from '@common/business_components/ui/map/Gis'; import LoadingOverlay from '@/components/ui/LoadingBar'; +import { searchUpperPpls } from "./smh"; const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, featureData: any }) => { @@ -58,7 +58,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, const getSelectedLayers = () => { let result = null; const layers = gisMap.getLayers().getArray(); // gisMap에서 모든 레이어 가져오기 - let featureLayer: { get: (key: string) => any }; // 피처가 속한 레이어를 찾기 위한 변수 + let featureLayer: any = null; // 피처가 속한 레이어를 찾기 위한 변수 layers.some((layer: any) => { const source = layer.getSource(); // if (source && featureData && featureData.fid) { @@ -77,98 +77,119 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, setIsClicked(true); console.log('gis data', featureData.values_); const lyrTyNm = featureData.id_.split('.')[0]; - if (lyrTyNm === 'tb_ms_swage_ppl') { - result = '하수관로'; - setHeadlist(MsSwagePplCol); - setLayerSpecificNm(featureData.values_.fclt_mng_no); - } - else if (lyrTyNm === 'tb_ms_swage_mhl') { - result = '하수맨홀'; - setHeadlist(MsSwageMhlCol); - setLayerSpecificNm(featureData.values_.fclt_mng_no); - } - else if (lyrTyNm === 'tb_ms_swage_pmstn') { // 하수펌프장 - const drtwPumpCode = DRTW_SEWAGE_PUMP_USE_CODES.find(code => code.text === '오수펌프장'); - if (drtwPumpCode && featureData.values_.usg_cd.trim() === drtwPumpCode.val) { - setIsDrtwSewagePump(true); - setHeadlist(DrtwSewagePumpCol); + + switch (lyrTyNm) { + case 'tb_ms_swage_ppl': { + result = '하수관로'; + setHeadlist(MsSwagePplCol); + setLayerSpecificNm(featureData.values_.fclt_mng_no); + break; } - else { - setIsDrtwSewagePump(false); - setHeadlist(ExclnSewagePumpCol); + case 'tb_ms_swage_mhl': { + result = '하수맨홀'; + setHeadlist(MsSwageMhlCol); + setLayerSpecificNm(featureData.values_.fclt_mng_no); + break; + } + case 'tb_ms_swage_pmstn': { + const drtwPumpCode = DRTW_SEWAGE_PUMP_USE_CODES.find(code => code.text === '오수펌프장'); + if (drtwPumpCode && featureData.values_.usg_cd.trim() === drtwPumpCode.val) { + setIsDrtwSewagePump(true); + setHeadlist(DrtwSewagePumpCol); + } + else { + setIsDrtwSewagePump(false); + setHeadlist(ExclnSewagePumpCol); + } + setLayerSpecificNm(featureData.values_.fclt_mng_no); + result = '하수펌프장'; + break; + } + case 'tb_il_swage_prcs_fclt': { + result = '하수처리시설'; + setHeadlist(SwtrmtFcltCol); + setLayerSpecificNm(featureData.values_.swtrmt_fclt_cd); + break; + } + case 'tb_ms_swage_prcs_zone': { + result = '하수처리구역'; + setHeadlist(SwtrmtPrcsZoneCol); + setLayerSpecificNm(featureData.values_.swtrmt_fclt_cd); + break; + } + case 'tb_ms_stofcbr': { + result = '우수토실'; + setHeadlist(stofcbrCol); + setLayerSpecificNm(featureData.values_.fclt_mng_no); + break; + } + case 'om_dt_untrt_swg_rain': { + result = '중점우수토실'; + setHeadlist(untrtSwgRainCol1); + setHeadlist2(untrtSwgRainCol2); + setLayerSpecificNm(featureData.values_.manage_no); + break; + } + default: { + result = featureLayer ? featureLayer.get('lyrNm') : null; + break; } - setLayerSpecificNm(featureData.values_.fclt_mng_no); - result = '하수펌프장'; } - else if (lyrTyNm === 'tb_il_swage_prcs_fclt') { - result = '하수처리시설'; - setHeadlist(SwtrmtFcltCol); - setLayerSpecificNm(featureData.values_.swtrmt_fclt_cd); - } - else if (lyrTyNm === 'tb_ms_swage_prcs_zone') { - result = '하수처리구역'; - setHeadlist(SwtrmtPrcsZoneCol); - setLayerSpecificNm(featureData.values_.swtrmt_fclt_cd); - } - else if (lyrTyNm === 'tb_ms_stofcbr') { - result = '우수토실'; - setHeadlist(stofcbrCol); - setLayerSpecificNm(featureData.values_.fclt_mng_no); - } - else if (lyrTyNm === 'om_dt_untrt_swg_rain') { - result = '중점우수토실'; - setHeadlist(untrtSwgRainCol1); - setHeadlist2(untrtSwgRainCol2); - setLayerSpecificNm(featureData.values_.manage_no); - } - else result = featureLayer.get('lyrNm'); } else { // SearchModal 클릭 이벤트 setIsClicked(false); console.log('SearchModal data', featureData); const lyrTyNm = featureData.lyrTypeNm; - if (featureData.lyrTypeNm === 'tb_ms_swage_ppl') { - result = '하수관로'; - setHeadlist(MsSwagePplCol2) - setLayerSpecificNm(featureData.swtrmtFcltCd); - } - else if (featureData.lyrTypeNm === 'tb_ms_swage_mhl') { - result = '하수맨홀'; - setHeadlist(MsSwageMhlCol2) - setLayerSpecificNm(featureData.fcltMngNo); - } - else if (featureData.lyrTypeNm === 'tb_ms_swage_pmstn') { // 하수펌프장 - const drtwPumpCode = DRTW_SEWAGE_PUMP_USE_CODES.find(code => code.text === '오수펌프장'); - if (drtwPumpCode && featureData.usgCd.trim() === drtwPumpCode.val) { - console.log('오수펌프장'); - setIsDrtwSewagePump(false); - setHeadlist(ExclnSewagePumpCol2) + + switch (lyrTyNm) { + case 'tb_ms_swage_ppl': { + result = '하수관로'; + setHeadlist(MsSwagePplCol2) + setLayerSpecificNm(featureData.swtrmtFcltCd); + break; } - else { - console.log('빗물펌프장'); - setIsDrtwSewagePump(true); - setHeadlist(DrtwSewagePumpCol2) + case 'tb_ms_swage_mhl': { + result = '하수맨홀'; + setHeadlist(MsSwageMhlCol2) + setLayerSpecificNm(featureData.fcltMngNo); + break; + } + case 'tb_ms_swage_pmstn': { + const drtwPumpCode = DRTW_SEWAGE_PUMP_USE_CODES.find(code => code.text === '오수펌프장'); + if (drtwPumpCode && featureData.usgCd.trim() === drtwPumpCode.val) { + console.log('오수펌프장'); + setIsDrtwSewagePump(false); + setHeadlist(ExclnSewagePumpCol2) + } + else { + console.log('빗물펌프장'); + setIsDrtwSewagePump(true); + setHeadlist(DrtwSewagePumpCol2) + } + setLayerSpecificNm(featureData.fcltMngNo); + result = '하수펌프장'; + break; + } + case 'tb_il_swage_prcs_fclt': { + result = '하수처리시설'; + setHeadlist(SwtrmtFcltCol2) + setLayerSpecificNm(featureData.swtrmtFcltCd); + break; + } + case 'tb_ms_swage_prcs_zone': { + result = '하수처리구역'; + setHeadlist(SwtrmtPrcsZoneCol2); + setLayerSpecificNm(featureData.swtrmtFcltCd); + break; + } + case 'tb_ms_stofcbr': { + result = '우수토실'; + setHeadlist(stofcbrCol2) + setLayerSpecificNm(featureData.fcltMngNo); + break; } - setLayerSpecificNm(featureData.fcltMngNo); - result = '하수펌프장'; } - else if (featureData.lyrTypeNm === 'tb_il_swage_prcs_fclt') { - result = '하수처리시설'; - setHeadlist(SwtrmtFcltCol2) - setLayerSpecificNm(featureData.swtrmtFcltCd); - } - else if (featureData.lyrTypeNm === 'tb_ms_swage_prcs_zone') { - result = '하수처리구역'; - setHeadlist(SwtrmtPrcsZoneCol2); - setLayerSpecificNm(featureData.swtrmtFcltCd); - } - else if (featureData.lyrTypeNm === 'tb_ms_stofcbr') { - result = '우수토실'; - setHeadlist(stofcbrCol2) - setLayerSpecificNm(featureData.fcltMngNo); - } - // else if (featureData.lyrTypeNm === 'om_dt_untrt_swg_rain') result = '중점우수토실'; } return result; @@ -424,7 +445,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, className="px-2 py-1 text-xl text-gray-600 border border-gray-600 rounded hover:bg-gray-700 mr-4" onClick={() => { setIsLoading(true); - searchUpperPpls(layerSpecificNm).finally(() => setIsLoading(false)); + searchUpperPpls(gisMap, layerSpecificNm).finally(() => setIsLoading(false)); }} > 상류관로 보기 diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/smh.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/smh.tsx index 848343aad..5d5365834 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/smh.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/smh.tsx @@ -1,6 +1,84 @@ // 하수맨홀 import { VerticalTable } from '@/components/ui/verticalTable'; import { useEffect, useState } from 'react'; +import GisApp from "@common/business_components/ui/map/lib/gis.core"; + +// 상류관로 보기 함수 (FeatureClickEventPopup에서 사용) +export async function searchUpperPpls(gisMap: any, fcltMngNo: any) { + const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ""; + const res = await fetch(`${BASE_URL}/v1/fcltmhl/upperPpls/${fcltMngNo}`, { + method: "GET", + }); + + let data: any = null; + if (res.ok) { + try { + // 응답이 비어있는지 확인 + const contentLength = res.headers.get("content-length"); + if (contentLength === "0") { + alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); + return; + } + + // 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인 + const text = await res.text(); + if (!text || text.trim() === "") { + alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); + return; + } + + // JSON 파싱 시도 + data = JSON.parse(text); + } catch (error) { + console.error("JSON parsing error:", error); + alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); + return; + } + } else { + alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); + return; + } + + const select: any = GisApp.Module.select; + let foundFeature: any = null; + + (data || []).forEach((item: any) => { + const pplMngNo = item.ppl_mng_no?.trim?.(); + + gisMap + .getLayers() + .getArray() + .filter((e: any) => e.get("lyrTyNm") == "tb_ms_swage_ppl") + .forEach((layer: any) => { + 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; + } + } + } + }); + + if (foundFeature) select?.selectMultiFeature?.(foundFeature, true); + }); + + if (!foundFeature) alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); + + const geometry = foundFeature?.getGeometry(); + if (geometry) { + const extent = geometry.getExtent(); + gisMap.getView().fit(extent, { + duration: 300, + padding: [50, 50, 50, 50], + maxZoom: 14, + constrainResolution: true, + nearest: true, + }); + } +} const inputColumn = [ { id: "swtrmtZoneNm", title: "처리구역", type: "input" }, diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index 6e6997267..e1bbc45ad 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -6,7 +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 { WaterDepth, WATER_DEPTH_LAYER_TYPES, ORDERED_LAYERS } from "./waterDepth/waterDepth"; +import { WaterDepth, WATER_DEPTH_LAYER_TYPES } from "./waterDepth/waterDepth"; import { ProcSearchBar } from "@common/business_components/ui/map/searchBar"; // 타입 정의 추가 @@ -160,7 +160,6 @@ const Map = () => { isPlaying={isPlaying} setIsPlaying={setIsPlaying as any} targetLayerTypes={WATER_DEPTH_LAYER_TYPES as any} - orderedLayers={ORDERED_LAYERS as any} checkInterval={2500} /> diff --git a/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx index 7fe3bd0f5..d476fedd7 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx @@ -149,7 +149,6 @@ const ReportTab: React.FC = ({ open, isSearch = true, ctpvNm, sg // if (gubun !== "") { // 구분검색 (추후 사용할 수도 있을 것 같아 남겨둠) // filterReportData = filterReportData.filter(item => (item.plantZoneGubun) ? String(item.plantZoneGubun).includes(String(gubun)) : null); // } - console.log('report count:', filterReportData.length); setTableCount(filterReportData.length || 0); diff --git a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx index 3ec23a850..fdb52a1c2 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx @@ -80,12 +80,9 @@ const SearchModal: React.FC = ({ const [end2, setEnd2] = useState(""); const [var1, setVar1] = useState(""); const [var2, setVar2] = useState(""); - const [var3, setVar3] = useState(""); - const [var4, setVar4] = useState(""); - // 시작, 종료 const [yearFilter, setYearFilter] = useState({ start: "", end: "" }); - const { ctpv, sgg, BIZ_TY_CD, BIZ_SE_CD } = useStore(); + const { ctpv, sgg, BIZ_TY_CD } = useStore(); // 오늘 날짜 구하기 @@ -128,8 +125,6 @@ const SearchModal: React.FC = ({ setEnd2(""); setVar1(""); setVar2(""); - setVar3(""); - setVar4(""); setCtpvCd("0"); setSggCd("0"); setYearFilter({ @@ -196,7 +191,6 @@ const SearchModal: React.FC = ({ if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and ')); const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`); - console.log('api:', `${API_URL}/v1/fclts?${queryParams.toString()}`); // 🎯 각 아이템을 개별적으로 포맷팅하면서 일련번호 추가 const dataWithRowNumber = (res.data.data || []).map((item: any, index: number) => { @@ -231,7 +225,6 @@ const SearchModal: React.FC = ({ }; }); - console.log("dataWithRowNumber: ", dataWithRowNumber) setTableData(dataWithRowNumber); setTableCount(res.data.count || []); @@ -264,7 +257,6 @@ const SearchModal: React.FC = ({ const params = new URLSearchParams(RequestDTO).toString(); const res = await getData(`v1/bizInfo?${params}`); - console.log('운영정보 사업 데이터:', res.content); const mappedContent = (res.content || []).map((item: any, index: number) => { const formattedItem = formatDisplayData(item); @@ -512,7 +504,6 @@ const SearchModal: React.FC = ({ RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"')); const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`); - console.log('운영정보 cellClick 데이터:', res.data); const mappedData = (res.data || []).map((item: any, detailIndex: number) => { const formattedItem = formatDisplayData(item); @@ -527,7 +518,6 @@ const SearchModal: React.FC = ({ swtrmt_zone_nm: item.swtrmt_zone_nm ? item.swtrmt_zone_nm : '-', }; }); - console.log('운영정보 cellClick 데이터:', mappedData); setDetailTableData(mappedData); setDetailTableCount(res.count || 0); @@ -565,7 +555,6 @@ const SearchModal: React.FC = ({ swtrmt_zone_nm: item.swtrmt_zone_nm ? item.swtrmt_zone_nm : '-', }; }); - console.log('운영정보 PageNation 데이터:', mappedData); setDetailTableData(mappedData); setDetailTableCount(res.count || 0); @@ -667,7 +656,6 @@ const SearchModal: React.FC = ({ if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and ')); const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`); - console.log('res.data.data', res.data.data); @@ -785,7 +773,6 @@ const SearchModal: React.FC = ({ // 선택된 행 데이터를 Excel 다운로드 형식으로 변환 const excelData = mappedContent; // 배열로 감싸서 전달 - console.log('excelData', excelData); // 파일명 생성 (분석번호 기반) const label = BASIC_TYPES.find((type) => type.val === basicType)?.text @@ -929,8 +916,6 @@ const SearchModal: React.FC = ({ const apiStartTime = performance.now(); const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`); const apiEndTime = performance.now(); - console.log(`🚀 1. API 호출 시간: ${(apiEndTime - apiStartTime).toFixed(2)}ms`); - console.log('운영정보 결과 다운로드.data:', res.data); const mappingStartTime = performance.now(); // 헤더 매핑 정의 @@ -974,13 +959,10 @@ const SearchModal: React.FC = ({ }; }); const mappingEndTime = performance.now(); - console.log(`🔄 2. 데이터 매핑 시간: ${(mappingEndTime - mappingStartTime).toFixed(2)}ms`); // 선택된 행 데이터를 Excel 다운로드 형식으로 변환 const excelData = mappedData; // 배열로 감싸서 전달 - console.log('excelData', excelData); - const excelStartTime = performance.now(); // 파일명 생성 (분석번호 기반) const label = WORK_TYPES.find((type) => type.val === workType)?.text @@ -994,7 +976,6 @@ const SearchModal: React.FC = ({ headerMapping: headerMapping }); const excelEndTime = performance.now(); - console.log(`📊 3. Excel 다운로드 시간: ${(excelEndTime - excelStartTime).toFixed(2)}ms`); setIsLoading(false); } catch (error) { @@ -1023,7 +1004,6 @@ const SearchModal: React.FC = ({ setIsLoading(true); const row = detailTableData[0]; - console.log('row', row); const layerNm = row.lyrTypeNm; const fids = detailTableData.map((item) => item.fid); const core = GisApp.Module.layer as any; @@ -1116,6 +1096,7 @@ const SearchModal: React.FC = ({ onPageChange={setCurPage} resetPageNo={isResetPage} resetSelection={isResetSelection} + curPage={curPage} headButton={
@@ -1147,6 +1128,7 @@ const SearchModal: React.FC = ({ onPageChange={setCurPage} resetPageNo={isResetPage} resetSelection={isResetSelection} + curPage={curPage} headButton={ } @@ -1173,6 +1155,7 @@ const SearchModal: React.FC = ({ onPageChange={setDetailCurPage} resetPageNo={isResetDetailPage} resetSelection={isResetDetailSelection} + curPage={detailCurPage} headButton={
diff --git a/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx b/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx index 78849bda2..a1434d895 100644 --- a/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx +++ b/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx @@ -7,16 +7,6 @@ export const WATER_DEPTH_LAYER_TYPES: string[] = [ 'tb_ms_flood_dngr_map', 'tb_ms_sbmrg_dngr_map' ]; -// 지정된 순서에 따른 lyr_sn 배열 -export const ORDERED_LAYERS: string[] = [ - '27', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 30년 빈도 - '28', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 50년 빈도 - '3', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 80년 빈도 - '4', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 100년 빈도 - '59', // 도시침수지도(tb_ms_flood_dngr_map) - 30년 빈도 - '60', // 도시침수지도(tb_ms_flood_dngr_map) - 50년 빈도 - '58' // 도시침수지도(tb_ms_flood_dngr_map) - 80년 빈도 -]; interface WaterDepthProps { onClose?: () => void; diff --git a/apps/sn/app/(with-base-layout)/oper/floodPre/headMakeFloodPre.tsx b/apps/sn/app/(with-base-layout)/oper/floodPre/headMakeFloodPre.tsx new file mode 100644 index 000000000..f16178003 --- /dev/null +++ b/apps/sn/app/(with-base-layout)/oper/floodPre/headMakeFloodPre.tsx @@ -0,0 +1,76 @@ +import { TableHead, TableRow } from "@common/components/ui"; + + +export type TableUpperProps = { + id: string; + title: string; + upName?: string; + upSequnce?: number; + edit?: boolean; + visible?: boolean; + rowSpan?: number; + colSpan?: number; + type?: string; + selectData?: string; // type 이 seelct 일경우 selectarray 정하기 위해 + row?: number; + mergeCell?: boolean // 셀을 합칠경우 + maxLength?: number + readonly?: boolean +}; +type chHeadType = { + headNm: string; + text: string; + length: number; + rows?: number; +}; + +export const HeadMakeFloodPre = ( + upHeadList: TableUpperProps[], + makeHeadNm: chHeadType[] +) => { + return ( + <> + + {upHeadList + .filter((data) => data.visible != false) + .map((head: TableUpperProps) => + !head.upSequnce ? ( + + {head.title} + + ) : head.upSequnce === 1 ? ( + ch.headNm === head.upName)?.length + } + > + {makeHeadNm?.find((ch) => ch.headNm === head.upName)?.text} + + ) : null + )} + + + + {upHeadList + .filter((data) => data.visible != false) + .map((head: TableUpperProps) => + makeHeadNm?.find((ch) => ch.headNm === head.upName) ? ( + + {head.title} + + ) : null + )} + + + ); +}; \ No newline at end of file diff --git a/apps/sn/app/(with-base-layout)/oper/floodPre/inputTableBody.tsx b/apps/sn/app/(with-base-layout)/oper/floodPre/inputTableBody.tsx new file mode 100644 index 000000000..0058b47d1 --- /dev/null +++ b/apps/sn/app/(with-base-layout)/oper/floodPre/inputTableBody.tsx @@ -0,0 +1,200 @@ +import type { TableUpperProps } from "./headMakeFloodPre"; +import { Input, TableCell, TableRow } from "@common/components/ui"; + + +// 숫자입력시 , 삽입 +const formatCommaNumber = ( + value: number | string | undefined | null +): string => { + if (value === undefined || value === null || value === "") return ""; + const num = Number(value); + // 숫자로 변환할 수 없는 값이 들어올 경우 빈 문자열을 반환하여 예상치 못한 "NaN" 출력을 방지합니다. + if (isNaN(num)) { + return ""; + } + return num.toLocaleString(); +}; + +// onchange 텍스트를 숫자로 and ,제거 +const handleCommaNumberChange = ( + e: React.ChangeEvent, + onChange: (value: number | "") => void +) => { + const raw = e.target.value.replace(/,/g, ""); + if (!/^\d*$/.test(raw)) return; + onChange(raw === "" ? "" : Number(raw)); +}; + +const handleChange = ( + rowIndex: number, + field: any, + value: string, + setInputData: (data: any) => void +) => { + if (Number(value) < 0 || !Number(value)) { + value = "0"; + } + setInputData((prevData: any) => + prevData?.map((row: any, index: number) => { + if (index === rowIndex) { + return { + ...row, + [field]: Number(value.replace(/,/g, "")), + }; + } + return row; + }) + ); +}; + +export const InputTableBody = ( + chgRes: any[] | [], + headList: TableUpperProps[], + setInputData: (data: any[]) => void, + isEditable: boolean = true +) => { + // mergeCell 처리용 + const getRowSpan = (data: any[], key: string, rowIndex: number) => { + if (rowIndex > 0 && data[rowIndex - 1][key] && String(data[rowIndex][key]).trim() === "") { + return 0; // 이전과 같으면 셀을 그리지 않음 + } + let span = 1; + for (let i = rowIndex + 1; i < data.length; i++) { + if (data[i][key] === data[rowIndex][key]) span++; + else break; + } + return span; + }; + return ( + <> + {chgRes.map((data, dataIndex) => { + // 소계/총합계 행 판별 + const isSubTotal = data["용수명"] === "소계"; + const isTotal = data["구분"] === 0; + + const bgClass = isTotal + ? "bg-[#d5e1eb] font-bold" + : isSubTotal + ? "bg-[#e6eef5] font-semibold" + : ""; + + return ( + + {headList.map((head, headIndex) => { + // console.log('head, head.id', head, head.id); + const isTextColumn = head.id === "__EMPTY_3"; + const textAlignClass = isTextColumn + ? "text-center" + : "text-right"; + + // // 총합계 행이면 "구분"에서 colSpan=2로 병합 + // if ( + // (data["구분"] === 0 || data["용수명"] === 0) && + // head.id === "구분" + // ) { + // return ( + // + // 총합계 + // + // ); + // } + // // 총합계(합계) 행이면 "용수명" 컬럼은 렌더링하지 않음 + // if ( + // (data["구분"] === 0 || data["용수명"] === 0) && + // head.id === "용수명" + // ) { + // return null; + // } + + // // 총합계 행의 나머지 셀에도 색상 적용 + // if (data["구분"] === 0 || data["용수명"] === 0) { + // return ( + // + // {Number(data[head.id]) + // ? formatCommaNumber(data[head.id as keyof any]) + // : (data[head.id as keyof any] ?? "")} + // + // ); + // } + // mergeCell 처리 + if (head.mergeCell === true) { + const rowSpan = getRowSpan(chgRes, head.id, dataIndex); + if (rowSpan === 0) return null; + return ( + + {Number(data[head.id]) + ? formatCommaNumber(data[head.id as keyof any]) + : (data[head.id as keyof any] ?? "")} + + ); + } + // 기본 셀 + // return head.edit === true && data.edit !== false ? ( + return isEditable && head.edit === true && data.edit !== false ? ( + + + handleCommaNumberChange(e, (val) => + handleChange( + dataIndex, + head.id, + String(val), + setInputData + ) + ) + } + value={formatCommaNumber(data[head.id as keyof any]) ?? ""} + maxLength={12} + /> + + ) : ( + +
+ {Number(data[head.id]) + ? formatCommaNumber(data[head.id as keyof any]) + : (data[head.id as keyof any] ?? "")} +
+
+ ); + })} +
+ ); + })} + {chgRes.length === 0 && ( + + + 조회된 결과가 없습니다. + + + )} + + ); +}; diff --git a/apps/sn/app/(with-base-layout)/oper/floodPre/tableModal.tsx b/apps/sn/app/(with-base-layout)/oper/floodPre/tableModal.tsx index 80cd94a0e..a4c7939c8 100644 --- a/apps/sn/app/(with-base-layout)/oper/floodPre/tableModal.tsx +++ b/apps/sn/app/(with-base-layout)/oper/floodPre/tableModal.tsx @@ -2,7 +2,10 @@ import { useState, useEffect } from 'react' import LoadingOverlay from '@/components/ui/LoadingBar' import { UITab, UiTable } from '@common/business_components/ui' +import { Table, TableBody, TableHeader } from "@common/components/ui"; import * as XLSX from 'xlsx'; +import { HeadMakeFloodPre } from './headMakeFloodPre'; +import { InputTableBody } from './inputTableBody'; const TABS = [ { val: "management", text: "관리SHEET" }, @@ -19,7 +22,7 @@ const TableModal: React.FC = ({ fileNm, atflId }) => { const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? "management"); const [managementTableHeader, setManagementTableHeader] = useState<{ id: string; title: string }[]>([]) //테이블 헤더 const [managementTableData, setManagementTableData] = useState([{}]) //테이블 데이터 - const [resultTableHeader, setResultTableHeader] = useState<{ id: string; title: string }[]>([]) //테이블 헤더 + const [resultTableHeader, setResultTableHeader] = useState<{ id: string; title: string, mergeCell?: boolean }[]>([]) //테이블 헤더 const [resultTableData, setResultTableData] = useState([{}]) //테이블 데이터 const [isLoading, setIsLoading] = useState(false) //로딩 상태 @@ -53,13 +56,12 @@ const TableModal: React.FC = ({ fileNm, atflId }) => { // 관리 SHEET const managementHeader = jsonDataBySheet['관리SHEET']?.[7] || []; - // 1번째~47번째, 74번째~80번째 데이터만 필터링 const filteredManagementHeader = Object.entries(managementHeader) .filter(([key, value]) => { const match = key.match(/^__EMPTY_(\d+)$/); if (match) { const index = parseInt(match[1] ?? '', 10); - return (index > 0 && index < 47) || (index > 73); + return (0 < index && index < 47) || (73 < index); } return false; }) @@ -81,17 +83,20 @@ const TableModal: React.FC = ({ fileNm, atflId }) => { // 운영관리현황 점검결과 const resultHeader = jsonDataBySheet['운영관리현황 점검결과']?.[2] || []; - const dynamicInspectionHeader = Object.entries(resultHeader) + const dynamicResultHeader = Object.entries(resultHeader) .map(([key, value]) => ({ id: key, - title: String(value).replace(/\r\n/g, '\n') + title: String(value).replace(/\r\n/g, '\n'), + mergeCell: false, + ...(key === '__EMPTY_3' && { mergeCell: true }) // __EMPTY_3인 경우 mergeCell: true 추가 })); - dynamicInspectionHeader.splice(1, 0, { + dynamicResultHeader.splice(1, 0, { id: "__EMPTY_2", - title: '' + title: '', + mergeCell: false }); - setResultTableHeader(dynamicInspectionHeader); - console.log('resultTableHeader', dynamicInspectionHeader); + setResultTableHeader(dynamicResultHeader); + console.log('resultTableHeader', dynamicResultHeader); const resultData = Array.isArray(jsonDataBySheet['운영관리현황 점검결과']) ? jsonDataBySheet['운영관리현황 점검결과'].slice(3) @@ -225,7 +230,7 @@ const TableModal: React.FC = ({ fileNm, atflId }) => { )} {/* 운영관리현황 점검결과 탭 */} {activeTab === 'Inspection results' && ( -
+
= ({ fileNm, atflId }) => { cellClick={undefined} onPageChange={() => { }} /> + {/* + + {HeadMakeFloodPre(resultTableHeader, [])} + + + {InputTableBody(resultTableData ?? [], resultTableHeader, setResultTableData, false)} + +
*/}
)} diff --git a/apps/sn/app/(with-base-layout)/oper/oldPp/headList.ts b/apps/sn/app/(with-base-layout)/oper/oldPp/headList.ts new file mode 100644 index 000000000..516302720 --- /dev/null +++ b/apps/sn/app/(with-base-layout)/oper/oldPp/headList.ts @@ -0,0 +1,1711 @@ +import { z } from "zod"; + +export const oldPpHeadListType = z.array( + z.object({ + id: z.string(), + title: z.string(), + visible: z.boolean(), + }) +); + +type CommonTableProps = { + id: string; + title: string; + upName?: string; + upSequnce?: number; + row?: number; + rowSpan?: number; + colSpan?: number; + visible?: boolean; +}; + +export const oldPpTable2HeadList: z.infer = [ + { + id: "rowNumber", + title: "일련번호", + visible: true, + }, + { + id: "__EMPTY", + title: "연번", + visible: true, + }, + { + id: "__EMPTY_1", + title: "차수", + visible: true, + }, + { + id: "__EMPTY_2", + title: "정밀조사 사업코드", + visible: true, + }, + { + id: "__EMPTY_3", + title: "시도", + visible: true, + }, + { + id: "__EMPTY_4", + title: "시군구", + visible: true, + }, + { + id: "__EMPTY_5", + title: "사업명", + visible: true, + }, + { + id: "__EMPTY_6", + title: "착수", + visible: true, + }, + { + id: "__EMPTY_7", + title: "준공", + visible: true, + }, + { + id: "__EMPTY_8", + title: "내역사업명", + visible: true, + }, + { + id: "__EMPTY_9", + title: "물량\r\n(결산자료)", + visible: true, + }, + { + id: "__EMPTY_0", + title: "실제조사물량\r\n(보고서,실적보고)", + visible: true, + }, + { + id: "__EMPTY_10", + title: "계", + visible: true, + }, + { + id: "__EMPTY_11", + title: "국고", + visible: true, + }, + { + id: "__EMPTY_12", + title: "지방비", + visible: true, + }, + { + id: "__EMPTY_13", + title: " 총사업비 ", + visible: true, + }, + { + id: "__EMPTY_14", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_15", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_16", + title: " 계 ", + visible: true, + }, + { + id: "__EMPTY_17", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_18", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_19", + title: " 총사업비 ", + visible: true, + }, + { + id: "__EMPTY_20", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_21", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_22", + title: " 계 ", + visible: true, + }, + { + id: "__EMPTY_23", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_24", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_25", + title: " 총사업비 ", + visible: true, + }, + { + id: "__EMPTY_26", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_27", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_28", + title: " 계 ", + visible: true, + }, + { + id: "__EMPTY_29", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_30", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_31", + title: " 총사업비 ", + visible: true, + }, + { + id: "__EMPTY_32", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_33", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_34", + title: " 계 ", + visible: true, + }, + { + id: "__EMPTY_35", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_36", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_37", + title: " 총사업비 ", + visible: true, + }, + { + id: "__EMPTY_38", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_39", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_40", + title: " 계 ", + visible: true, + }, + { + id: "__EMPTY_41", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_42", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_43", + title: " 총사업비 ", + visible: true, + }, + { + id: "__EMPTY_44", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_45", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_46", + title: " 계 ", + visible: true, + }, + { + id: "__EMPTY_47", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_48", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_49", + title: " 총사업비 ", + visible: true, + }, + { + id: "__EMPTY_50", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_51", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_52", + title: " 계 ", + visible: true, + }, + { + id: "__EMPTY_53", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_54", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_55", + title: " 총사업비 ", + visible: true, + }, + { + id: "__EMPTY_56", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_57", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_58", + title: " 계 ", + visible: true, + }, + { + id: "__EMPTY_59", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_60", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_61", + title: " 총사업비 ", + visible: true, + }, + { + id: "__EMPTY_62", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_63", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_64", + title: " 계 ", + visible: true, + }, + { + id: "__EMPTY_65", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_66", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_67", + title: " 총사업비 ", + visible: true, + }, + { + id: "__EMPTY_68", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_69", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_70", + title: " 계 ", + visible: true, + }, + { + id: "__EMPTY_71", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_72", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_73", + title: " 총사업비 ", + visible: true, + }, + { + id: "__EMPTY_74", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_75", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_76", + title: " 계 ", + visible: true, + }, + { + id: "__EMPTY_77", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_78", + title: " 지방비 ", + visible: true, + }, +]; + +export const oldPpTable2NodeList: CommonTableProps[] = [ + { + id: "rowNumber", + title: "일련번호", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY", + title: "연번", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_1", + title: "차수", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_2", + title: "정밀조사 사업코드", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_3", + title: "시도", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_4", + title: "시군구", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_5", + title: "사업명", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_6", + title: "착수", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_7", + title: "준공", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_8", + title: "내역사업명", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_9", + title: "물량\r\n(결산자료)", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_0", + title: "실제조사물량\r\n(보고서,실적보고)", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_10", + title: "총계", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 3, + }, + { + id: "2__EMPTY_10", + title: "계", + upName: "__EMPTY_10", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_11", + title: "국고", + upName: "__EMPTY_10", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_12", + title: "지방비", + upName: "__EMPTY_10", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_13", + title: " 2015년 ", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 6, + }, + { + id: "2__EMPTY_13", + title: " 총사업비 ", + upName: "__EMPTY_13", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_14", + title: " 국고 ", + upName: "__EMPTY_13", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_15", + title: " 지방비 ", + upName: "__EMPTY_13", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_16", + title: " 계 ", + upName: "__EMPTY_13", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_17", + title: " 국고 ", + upName: "__EMPTY_13", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_18", + title: " 지방비 ", + upName: "__EMPTY_13", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + + { + id: "__EMPTY_19", + title: " 2016년 ", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 6, + }, + { + id: "2__EMPTY_19", + title: " 총사업비 ", + upName: "__EMPTY_19", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_20", + title: " 국고 ", + upName: "__EMPTY_19", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_21", + title: " 지방비 ", + upName: "__EMPTY_19", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_22", + title: " 계 ", + upName: "__EMPTY_19", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_23", + title: " 국고 ", + upName: "__EMPTY_19", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_24", + title: " 지방비 ", + upName: "__EMPTY_19", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_25", + title: "2017년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 6, + }, + { + id: "2__EMPTY_25", + title: " 총사업비 ", + upName: "__EMPTY_25", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_26", + title: " 국고 ", + upName: "__EMPTY_25", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_27", + title: " 지방비 ", + upName: "__EMPTY_25", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_28", + title: " 계 ", + upName: "__EMPTY_25", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_29", + title: " 국고 ", + upName: "__EMPTY_25", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_30", + title: " 지방비 ", + upName: "__EMPTY_25", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_31", + title: "2018년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 6, + }, + { + id: "2__EMPTY_31", + title: " 총사업비 ", + upName: "__EMPTY_31", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_32", + title: " 국고 ", + upName: "__EMPTY_25", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_33", + title: " 지방비 ", + upName: "__EMPTY_31", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_34", + title: " 계 ", + upName: "__EMPTY_31", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_35", + title: " 국고 ", + upName: "__EMPTY_31", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_36", + title: " 지방비 ", + upName: "__EMPTY_31", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_37", + title: "2019년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 6, + }, + { + id: "2__EMPTY_37", + title: " 총사업비 ", + upName: "__EMPTY_43", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_38", + title: " 국고 ", + upName: "__EMPTY_43", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_39", + title: " 지방비 ", + upName: "__EMPTY_43", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_40", + title: " 계 ", + upName: "__EMPTY_43", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_41", + title: " 국고 ", + upName: "__EMPTY_43", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_42", + title: " 지방비 ", + upName: "__EMPTY_43", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_43", + title: "2020년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 6, + }, + { + id: "2__EMPTY_43", + title: " 총사업비 ", + upName: "__EMPTY_49", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_44", + title: " 국고 ", + upName: "__EMPTY_49", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_45", + title: " 지방비 ", + upName: "__EMPTY_49", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_46", + title: " 계 ", + upName: "__EMPTY_49", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_47", + title: " 국고 ", + upName: "__EMPTY_49", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_48", + title: " 지방비 ", + upName: "__EMPTY_49", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_49", + title: "2021년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 6, + }, + { + id: "2__EMPTY_49", + title: " 총사업비 ", + upName: "__EMPTY_55", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_50", + title: " 국고 ", + upName: "__EMPTY_55", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_51", + title: " 지방비 ", + upName: "__EMPTY_55", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_52", + title: " 계 ", + upName: "__EMPTY_55", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_53", + title: " 국고 ", + upName: "__EMPTY_55", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_54", + title: " 지방비 ", + upName: "__EMPTY_55", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_55", + title: "2022년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 6, + }, + { + id: "2__EMPTY_55", + title: " 총사업비 ", + upName: "__EMPTY_61", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_56", + title: " 국고 ", + upName: "__EMPTY_61", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_57", + title: " 지방비 ", + upName: "__EMPTY_61", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_58", + title: " 계 ", + upName: "__EMPTY_61", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_59", + title: " 국고 ", + upName: "__EMPTY_61", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_60", + title: " 지방비 ", + upName: "__EMPTY_61", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_61", + title: "2023년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 6, + }, + { + id: "2__EMPTY_61", + title: " 총사업비 ", + upName: "__EMPTY_67", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_62", + title: " 국고 ", + upName: "__EMPTY_67", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_63", + title: " 지방비 ", + upName: "__EMPTY_67", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_64", + title: " 계 ", + upName: "__EMPTY_67", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_65", + title: " 국고 ", + upName: "__EMPTY_67", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_66", + title: " 지방비 ", + upName: "__EMPTY_67", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_67", + title: "2024년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 6, + }, + { + id: "2__EMPTY_67", + title: " 총사업비 ", + upName: "__EMPTY_73", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_68", + title: " 국고 ", + upName: "__EMPTY_73", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_69", + title: " 지방비 ", + upName: "__EMPTY_73", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_70", + title: " 계 ", + upName: "__EMPTY_73", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_71", + title: " 국고 ", + upName: "__EMPTY_73", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_72", + title: " 지방비 ", + upName: "__EMPTY_73", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_73", + title: "2025년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 6, + }, + { + id: "2__EMPTY_73", + title: " 총사업비 ", + upName: "__EMPTY_37", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_74", + title: " 국고 ", + upName: "__EMPTY_37", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_75", + title: " 지방비 ", + upName: "__EMPTY_37", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_76", + title: " 계 ", + upName: "__EMPTY_37", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_77", + title: " 국고 ", + upName: "__EMPTY_37", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_78", + title: " 지방비 ", + upName: "__EMPTY_37", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, +]; + +export const oldPpTable6HeadList: z.infer = [ + { + id: "rowNumber", + title: "일련번호", + visible: true, + }, + { + id: "__EMPTY", + title: "차수", + visible: true, + }, + { + id: "__EMPTY_1", + title: "기존관리", + visible: true, + }, + { + id: "__EMPTY_2", + title: "연번", + visible: true, + }, + { + id: "__EMPTY_3", + title: "정비사업 사업코드", + visible: true, + }, + { + id: "__EMPTY_4", + title: "착공년도", + visible: true, + }, + { + id: "__EMPTY_5", + title: "준공년도", + visible: true, + }, + { + id: "__EMPTY_6", + title: "유역청", + visible: true, + }, + { + id: "__EMPTY_7", + title: "시도", + visible: true, + }, + { + id: "__EMPTY_8", + title: "시군구", + visible: true, + }, + { + id: "__EMPTY_9", + title: "사업명", + visible: true, + }, + { + id: "__EMPTY_10", + title: "내역사업명", + visible: true, + }, + { + id: "__EMPTY_11", + title: "계획물량\r\n(결산자료)", + visible: true, + }, + { + id: "__EMPTY_12", + title: " 계 ", + visible: true, + }, + { + id: "__EMPTY_13", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_14", + title: " 지방비 ", + visible: true, + }, + { + id: "__EMPTY_19", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_25", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_31", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_37", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_43", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_49", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_55", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_61", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_67", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_73", + title: " 국고 ", + visible: true, + }, + { + id: "__EMPTY_75", + title: " 총국고 ", + visible: true, + }, + { + id: "__EMPTY_76", + title: " 검토자 ", + visible: true, + }, +]; + +export const oldPpTable6NodeList: CommonTableProps[] = [ + { + id: "rowNumber", + title: "일련번호", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY", + title: "차수", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_1", + title: "기존관리", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_2", + title: "연번", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_3", + title: "정비사업 사업코드", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_4", + title: "착공년도", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_5", + title: "준공년도", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_6", + title: "유역청", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_7", + title: "시도", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_8", + title: "시군구", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_9", + title: "사업명", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_10", + title: "내역사업명", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "2__EMPTY_11", + title: "물량", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_11", + title: "계획물량\r\n(결산자료)", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_12", + title: "총계", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 3, + }, + { + id: "__EMPTY_12", + title: " 계 ", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_13", + title: " 국고 ", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_14", + title: " 지방비 ", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_15", + title: "2016년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_19", + title: " 국고 ", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_21", + title: "2017년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_25", + title: " 국고 ", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_27", + title: "2018년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_31", + title: " 국고 ", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_33", + title: "2019년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_37", + title: " 국고 ", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_39", + title: "2020년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_43", + title: " 국고 ", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_45", + title: "2021년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_49", + title: " 국고 ", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_51", + title: "2022년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_55", + title: " 국고 ", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_57", + title: "2023년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_61", + title: " 국고 ", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_63", + title: "2024년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_67", + title: " 국고 ", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "2__EMPTY_69", + title: "2025년", + visible: true, + row: 1, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_73", + title: " 국고 ", + visible: true, + row: 2, + rowSpan: 1, + colSpan: 1, + }, + { + id: "__EMPTY_75", + title: " 총국고 ", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, + { + id: "__EMPTY_76", + title: " 검토자 ", + visible: true, + row: 1, + rowSpan: 2, + colSpan: 1, + }, +]; \ No newline at end of file diff --git a/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx b/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx index 7165188ca..8fc09b5f4 100644 --- a/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx +++ b/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx @@ -1,7 +1,7 @@ 'use client' import { deleteData, postData, useCookieAuth } from '@/components/lib/api' import { searchData } from '@/components/lib/common/apiHelpers' -import { formatDate, handleDownload, yearOptions } from '@/components/lib/utils' +import { formatDate, handleDownload } from '@/components/lib/utils' import LoadingOverlay from '@/components/ui/LoadingBar' import { SearchDiv, SearchFormLeft, SearchFormRight, UiTable, UiModal } from '@common/business_components/ui' import ListDiv from '@common/business_components/ui/list_div' @@ -14,6 +14,7 @@ import { AUTH_USER_ID } from "@/shared/config"; import { useQuery } from "@tanstack/react-query"; import { ENDPOINT } from "@common/components/config"; import { getUserInfo } from "@common/components/api"; +import TableModal from './tableModal'; const Page = () => { @@ -30,7 +31,8 @@ const Page = () => { const [curPage, setCurPage] = useState(1) const [totalData, setTotalData] = useState(0) //총 데이터 수 const [isLoading, setIsLoading] = useState(true) //로딩 상태 - const [isModalOpen, setIsModalOpen] = useState(false) // 모달 상태 + const [isModalOpen, setIsModalOpen] = useState(false) // 등록 모달 상태 + const [isTableModalOpen, setIsTableModalOpen] = useState(false) // 테이블 모달 상태 const [resetSelection, setResetSelection] = useState(false); // 선택 초기화 상태 const [isResetPage, setIsResetPage] = useState(false); @@ -223,6 +225,7 @@ const Page = () => { setFileNm(fileName); setAtflId(id); setDcrpPplMtncBizSn(tableData[index]?.dcrpPplMtncBizSn); + setIsTableModalOpen(true); } }} onPageChange={setCurPage} @@ -245,6 +248,21 @@ const Page = () => { /> } /> + {/* 엑셀 데이터 테이블 모달 */} + } + useDirectRender={true} + /> ) } diff --git a/apps/sn/app/(with-base-layout)/oper/oldPp/tableModal.tsx b/apps/sn/app/(with-base-layout)/oper/oldPp/tableModal.tsx new file mode 100644 index 000000000..169ae0895 --- /dev/null +++ b/apps/sn/app/(with-base-layout)/oper/oldPp/tableModal.tsx @@ -0,0 +1,465 @@ +'use client' +import { useState, useEffect } from 'react' +import LoadingOverlay from '@/components/ui/LoadingBar' +import { UITab, UiTable } from '@common/business_components/ui' +import * as XLSX from 'xlsx'; +import { HeadMakeColSpan } from '@common/business_components/ui/table/HeadMakeColSpan'; +import { oldPpTable2HeadList, oldPpTable2NodeList, oldPpTable6HeadList, oldPpTable6NodeList } from './headList'; + +const TABS = [ + { val: "1", text: "row data" }, + { val: "2", text: "1.정밀조사" }, + { val: "3", text: "2.정밀조사_정비사업연계" }, + { val: "4", text: " 3.정비사업" }, + { val: "5", text: "4.정비사업요약" }, + { val: "6", text: "4.정비사업최종" }, +]; + +interface TableModalProps { + fileNm: string; + atflId: string; +} + +const TableModal: React.FC = ({ fileNm, atflId }) => { + + const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? "1"); + const [tableHeader1, setTableHeader1] = useState<{ id: string; title: string }[]>([]) + const [tableHeader2, setTableHeader2] = useState<{ id: string; title: string }[]>([]) + const [tableHeader4, setTableHeader4] = useState<{ id: string; title: string }[]>([]) + const [tableHeader5, setTableHeader5] = useState<{ id: string; title: string }[]>([]) + const [tableData1, setTableData1] = useState([{}]) + const [tableData2, setTableData2] = useState([{}]) + const [tableData3, setTableData3] = useState([{}]) + const [tableData4, setTableData4] = useState([{}]) + const [tableData5, setTableData5] = useState([{}]) + const [tableData6, setTableData6] = useState([{}]) + const [isLoading, setIsLoading] = useState(false) + + useEffect(() => { + isChckFile(); + }, []); + + // 파일 다운로드 + const isChckFile = async () => { + setIsLoading(true); + try { + if (fileNm === '' || fileNm === undefined) { + alert('파일을 선택해주세요'); + setIsLoading(false); + return false; + } + + const response = await fetch(`/attach-service/api/v1/download/${atflId}`); + if (!response.ok) { + throw new Error('파일을 가져오는 중 오류가 발생했습니다.'); + } + + const blob = await response.blob(); + + const file = new File([blob], fileNm, { + type: blob.type || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + }); + + const targetSheets = ['row data', '1.정밀조사', '2.정밀조사_정비사업연계', ' 3.정비사업', '4.정비사업요약', '4.정비사업최종']; + const jsonDataBySheet = await filePickedSelectedSheets(file, 0, targetSheets); + + const header1 = jsonDataBySheet['row data']?.[1] || []; + const header2 = jsonDataBySheet['1.정밀조사']?.[1] || []; + const header4 = jsonDataBySheet[' 3.정비사업']?.[1] || []; + const header5 = jsonDataBySheet['4.정비사업요약']?.[2] || []; + + // row data + const allEntries1 = Object.entries(header1); + const filteredHeader1 = [ + allEntries1[0], + allEntries1[1], + allEntries1[2], + allEntries1[3], + allEntries1[5], + allEntries1[6], + allEntries1[7], + allEntries1[8], + allEntries1[9], + allEntries1[10], + allEntries1[11], + allEntries1[12], + allEntries1[13], + allEntries1[14], + allEntries1[15] + ].filter((e): e is [string, any] => Boolean(e)) + .map(([key, value]) => ({ + id: key, + title: String(value).replace(/\r\n/g, '\n') + })); + filteredHeader1.splice(0, 0, { + id: "rowNumber", + title: '일련번호' + }); + setTableHeader1(filteredHeader1); + + const data1 = Array.isArray(jsonDataBySheet['row data']) + ? jsonDataBySheet['row data'].slice(2) + : []; + const filteredData1 = filterEmptyRows(data1).map((item: any, index: number) => ({ + ...item, + rowNumber: index + 1, + __EMPTY_1: item.__EMPTY_1 || '-' + })); + setTableData1(filteredData1); + + // 1.정밀조사 + const allEntries2 = Object.entries(header2); + let filteredHeader2 = [ + allEntries2[0], + allEntries2[1], + allEntries2[2], + allEntries2[3], + allEntries2[5], + allEntries2[6], + allEntries2[7], + allEntries2[8], + allEntries2[9], + allEntries2[10], + allEntries2[11], + allEntries2[12], + allEntries2[13], + allEntries2[14], + allEntries2[15] + ].filter((e): e is [string, any] => Boolean(e)) + .map(([key, value]) => ({ + id: key, + title: String(value).replace(/\r\n/g, '\n') + })); + filteredHeader2.splice(0, 0, { + id: "rowNumber", + title: '일련번호' + }); + setTableHeader2(filteredHeader2); + + const data2 = Array.isArray(jsonDataBySheet['1.정밀조사']) + ? jsonDataBySheet['1.정밀조사'].slice(2) + : []; + const filteredData2 = filterEmptyRows(data2).map((item: any, index: number) => ({ + ...item, + rowNumber: index + 1 + })); + setTableData2(filteredData2); + + // 2.정밀조사_정비사업연계 (헤더는 import) + const data3 = Array.isArray(jsonDataBySheet['2.정밀조사_정비사업연계']) + ? jsonDataBySheet['2.정밀조사_정비사업연계'].slice(5) + : []; + const filteredData3 = filterEmptyRows(data3).map((item: any, index: number) => { + const newItem = { ...item }; + // 매핑 에러로 인해 특정 키를 "__EMPTY_0"으로 변경 + const targetKey = "*1차 출처: 공단자료\r\n*2차 출처: 정산서, 정밀조사보고서 등.\r\n (파랑) 2024년말 기준 실적 제출 자료. 준공 후 재확인 필요\r\n (노랑셀) 준공(또는 정산) 후에 재확인 필요"; + if (newItem[targetKey] !== undefined) { + newItem["__EMPTY_0"] = newItem[targetKey]; + delete newItem[targetKey]; + } + return { + ...newItem, + __EMPTY_4: newItem?.__EMPTY_4 || '-', + rowNumber: index + 1 + }; + }); + setTableData3(filteredData3); + + // 3.정비사업 + const allEntries4 = Object.entries(header4); + const filteredHeader4 = allEntries4.filter((e): e is [string, any] => Boolean(e)) + .map(([key, value]) => ({ + id: key, + title: String(value).replace(/\r\n/g, '\n') + })); + + filteredHeader4.splice(0, 0, { + id: "rowNumber", + title: '일련번호' + }); + setTableHeader4(filteredHeader4); + + const data4 = Array.isArray(jsonDataBySheet[' 3.정비사업']) + ? jsonDataBySheet[' 3.정비사업'].slice(2) + : []; + const filteredData4 = filterEmptyRows(data4).map((item: any, index: number) => ({ + ...item, + rowNumber: index + 1, + __EMPTY_2: item?.__EMPTY_2 || '-', + __EMPTY_11: item?.__EMPTY_11 || '-', + })); + setTableData4(filteredData4); + + // 4.정비사업요약 + const allEntries5 = Object.entries(header5); + const excludeKeys5 = [ + ": \"정비사업\"sheet에서 사업코드 ctrl+c, Ctrl+v 한 다음 중복된 데이터 제거실시", + "__EMPTY", + "__EMPTY_8", + "__EMPTY_15" + ]; + const filteredHeader5 = allEntries5 + .filter(([key]) => !excludeKeys5.includes(key)) + .filter((e): e is [string, any] => Boolean(e)) + .map(([key, value]) => ({ + id: key, + title: String(value).replace(/\r\n/g, '\n'), + })); + filteredHeader5.splice(0, 0, { + id: "rowNumber", + title: '일련번호' + }); + setTableHeader5(filteredHeader5); + + const data5 = Array.isArray(jsonDataBySheet['4.정비사업요약']) + ? jsonDataBySheet['4.정비사업요약'].slice(3) + : []; + const filteredData5 = + filterEmptyRows(data5).map((item: any, index: number) => ({ + ...item, + rowNumber: index + 1, + })); + setTableData5(filteredData5); + + // 4.정비사업최종 (헤더는 import) + const data6 = Array.isArray(jsonDataBySheet['4.정비사업최종']) + ? jsonDataBySheet['4.정비사업최종'].slice(6) + : []; + const filteredData6 = + filterEmptyRows(data6).map((item: any, index: number) => ({ + ...item, + rowNumber: index + 1, + })); + setTableData6(filteredData6); + } catch (error) { + console.error('파일 처리 중 오류 발생:', error); + alert('파일 처리 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } +} + +function filterEmptyRows(data: any[]) { + return data + .filter(row => { + return Object.entries(row).some(([key, value]) => { + const match = key.match(/^__EMPTY_(\d+)$/); + if (!match || parseInt(match[1] ?? '', 10) < 2) return false; + + return value !== undefined && + value !== null && + String(value).trim() !== '' && + String(value).trim() !== '-' && + String(value).trim() !== ''; + }); + }) + .map((row) => { + const processedRow = { ...row }; + const keys = Object.keys(processedRow); + + keys.forEach((key, keyIndex) => { + // 마지막 속성(사업 근거)이 아닌 경우에만 빈 값을 '-'으로 변경 + if (keyIndex !== keys.length - 1) { + const value = processedRow[key]; + if (value === undefined || + value === null || + value === '' || + String(value).trim() === '') { + processedRow[key] = '-'; + } + } + }); + + return processedRow; + }); +}; + +// 특정 시트만 선택적으로 JSON으로 변환하는 함수 +function filePickedSelectedSheets(file: File, headerNum: number = 0, sheetNames: string[]): Promise> { + const reader = new FileReader(); + + return new Promise((resolve, reject) => { + reader.onload = function (e) { + const data = e.target?.result; + if (!data) { + console.error("Failed to read file: e.target.result is null"); + reject(new Error("Failed to read file")); + return; + } + + // Excel 파일 읽기 + const wb = XLSX.read(data, { type: 'binary', cellDates: true }); + const result: Record = {}; + + // 지정된 시트 이름만 찾아서 처리 + sheetNames.forEach(targetSheetName => { + // 대소문자 구분 없이 시트 이름 찾기 + const sheetName = wb.SheetNames.find(name => + name.toLowerCase() === targetSheetName.toLowerCase() + ); + + if (sheetName) { + const sheet = wb.Sheets[sheetName]; + if (sheet) { + const jsonData = XLSX.utils.sheet_to_json(sheet, { range: headerNum, raw: false }); + result[targetSheetName] = jsonData; + } + } else { + console.warn(`Sheet "${targetSheetName}" not found in workbook`); + result[targetSheetName] = []; + } + }); + + resolve(result); + }; + + reader.onerror = function () { + console.error("Error reading file"); + reject(new Error("Error reading file")); + }; + + // 파일 읽기 시작 + reader.readAsBinaryString(file); + }); +}; + + +return ( + <> + {/* 탭 영역 */} + ({ + value: tab.val, + label: tab.text + }))} + onValueChange={(value: string) => setActiveTab(value)} + /> + {/* row data 탭 */} + {activeTab === '1' && ( +
+
+ +
+ { }} + /> +
+
+
+ )} + {/* 1.정밀조사 탭 */} + {activeTab === '2' && ( +
+
+ +
+ { }} + /> +
+
+
+ )} + {/* 2.정밀조사_정비사업연계 탭 */} + {activeTab === '3' && ( +
+
+ +
+ { }} + > + + +
+
+
+ )} + {/* 3.정비사업 탭 */} + {activeTab === '4' && ( +
+
+ +
+ { }} + /> +
+
+
+ )} + {/* 4.정비사업요약 탭 */} + {activeTab === '5' && ( +
+
+ +
+ { }} + /> +
+
+
+ )} + {/* 4.정비사업최종 탭 */} + {activeTab === '6' && ( +
+
+ +
+ { }} + > + + +
+
+
+ )} + +) +}; + +export default TableModal; \ No newline at end of file diff --git a/apps/wr/.env.development b/apps/wr/.env.development index ceaf190df..d3ec10727 100644 --- a/apps/wr/.env.development +++ b/apps/wr/.env.development @@ -27,3 +27,5 @@ NEXT_PUBLIC_ACCESS_TOKEN = access-token NEXT_PUBLIC_REFRESH_TOKEN = refresh-token NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr/openapi/Gettile.do?apiKey=04trYP9_xwLAfALjwZ-B8g NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API +NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do +NEXT_PUBLIC_WR_ADDRESS_KEY = devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM= diff --git a/apps/wr/.env.local b/apps/wr/.env.local index 4d8937ea6..547195433 100644 --- a/apps/wr/.env.local +++ b/apps/wr/.env.local @@ -16,6 +16,12 @@ NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr/openapi/Gettile.do?apiKey=04trYP9_xwLAfALjwZ-B8g + + +NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do +NEXT_PUBLIC_WR_ADDRESS_KEY = devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM= + + NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API #NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성 #NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정 diff --git a/apps/wr/.env.production b/apps/wr/.env.production index 4a0754665..295ba7ea9 100644 --- a/apps/wr/.env.production +++ b/apps/wr/.env.production @@ -16,6 +16,9 @@ NEXT_PUBLIC_NGII_URL = http://10.98.25.94:18080/openapi/Gettile.do?apiKey=BDE2BE NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-dev.svc.cluster.local:8000/sewerage-service/api +NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do +NEXT_PUBLIC_WR_ADDRESS_KEY = U01TXOFVVEgyMDI1MDgyODE0MTAxNzExNjEyMjA= + diff --git a/apps/wr/app/store/common/code/basicCode.tsx b/apps/wr/app/store/common/code/basicCode.tsx index aa5d1152e..8af04c59f 100644 --- a/apps/wr/app/store/common/code/basicCode.tsx +++ b/apps/wr/app/store/common/code/basicCode.tsx @@ -52,7 +52,7 @@ export const ruseBizMthdCd: SelectDataType[] = [ { val: "자체", text: "자체" }, ]; -//공통 건축물 용도 +// 공통 건축물 용도 export const bdstUsgCd: SelectDataType[] = [ { val: "-1", text: "확인불가" }, { val: "1", text: "운동장" }, @@ -71,67 +71,67 @@ export const bdstUsgCd: SelectDataType[] = [ { val: "14", text: "골프장" }, { val: "15", text: "대규모점포" }, { val: "16", text: "기타" }, - { text: "숙박업", val: "17" }, - { text: "목욕장업", val: "18" }, - { text: "공장", val: "19" }, - { text: "발전시설", val: "20" }, - { text: "관광단지개발사업", val: "21" }, - { text: "도시개발사업", val: "22" }, - { text: "산업단지개발사업", val: "23" }, - { text: "택지개발사업", val: "24" }, - { text: "대규모점포(대형마트)", val: "25" }, - { text: "대규모점포(전문점)", val: "26" }, - { text: "대규모점포(백화점)", val: "27" }, - { text: "대규모점포(쇼핑센터)", val: "28" }, - { text: "대규모점포(복합쇼핑몰)", val: "29" }, - { text: "물류시설", val: "30" }, - { text: "운수시설(여객자동차터미널)", val: "31" }, - { text: "운수시설(철도시설)", val: "32" }, - { text: "운수시설(공항시설)", val: "33" }, - { text: "운수시설(항만시설)", val: "34" }, - { text: "업무시설(공공업무시설)", val: "35" }, - { text: "업무시설(금융업소)", val: "36" }, - { text: "업무시설(사무소)", val: "37" }, - { text: "업무시설(결혼상담소 등 소개업소)", val: "38" }, - { text: "업무시설(출판사)", val: "39" }, - { text: "업무시설(신문사)", val: "40" }, - { text: "업무시설(오피스텔)", val: "41" }, - { text: "교정시설", val: "42" }, - { text: "방송국", val: "43" }, - { text: "전신전화국", val: "44" }, - { text: "기타", val: "45" }, + { val: "17", text: "숙박업" }, + { val: "18", text: "목욕장업" }, + { val: "19", text: "공장" }, + { val: "20", text: "발전시설" }, + { val: "21", text: "관광단지개발사업" }, + { val: "22", text: "도시개발사업" }, + { val: "23", text: "산업단지개발사업" }, + { val: "24", text: "택지개발사업" }, + { val: "25", text: "대규모점포(대형마트)" }, + { val: "26", text: "대규모점포(전문점)" }, + { val: "27", text: "대규모점포(백화점)" }, + { val: "28", text: "대규모점포(쇼핑센터)" }, + { val: "29", text: "대규모점포(복합쇼핑몰)" }, + { val: "30", text: "물류시설" }, + { val: "31", text: "운수시설(여객자동차터미널)" }, + { val: "32", text: "운수시설(철도시설)" }, + { val: "33", text: "운수시설(공항시설)" }, + { val: "34", text: "운수시설(항만시설)" }, + { val: "35", text: "업무시설(공공업무시설)" }, + { val: "36", text: "업무시설(금융업소)" }, + { val: "37", text: "업무시설(사무소)" }, + { val: "38", text: "업무시설(결혼상담소 등 소개업소)" }, + { val: "39", text: "업무시설(출판사)" }, + { val: "40", text: "업무시설(신문사)" }, + { val: "41", text: "업무시설(오피스텔)" }, + { val: "42", text: "교정시설" }, + { val: "43", text: "방송국" }, + { val: "44", text: "전신전화국" }, + { val: "45", text: "기타" }, ]; export const rswtBdstType: SelectDataType[] = [ - { text: "숙박업", val: "17" }, - { text: "목욕장업", val: "18" }, - { text: "공장", val: "19" }, - { text: "발전시설", val: "20" }, - { text: "관광단지개발사업", val: "21" }, - { text: "도시개발사업", val: "22" }, - { text: "산업단지개발사업", val: "23" }, - { text: "택지개발사업", val: "24" }, - { text: "대규모점포(대형마트)", val: "25" }, - { text: "대규모점포(전문점)", val: "26" }, - { text: "대규모점포(백화점)", val: "27" }, - { text: "대규모점포(쇼핑센터)", val: "28" }, - { text: "대규모점포(복합쇼핑몰)", val: "29" }, - { text: "물류시설", val: "30" }, - { text: "운수시설(여객자동차터미널)", val: "31" }, - { text: "운수시설(철도시설)", val: "32" }, - { text: "운수시설(공항시설)", val: "33" }, - { text: "운수시설(항만시설)", val: "34" }, - { text: "업무시설(공공업무시설)", val: "35" }, - { text: "업무시설(금융업소)", val: "36" }, - { text: "업무시설(사무소)", val: "37" }, - { text: "업무시설(결혼상담소 등 소개업소)", val: "38" }, - { text: "업무시설(출판사)", val: "39" }, - { text: "업무시설(신문사)", val: "40" }, - { text: "업무시설(오피스텔)", val: "41" }, - { text: "교정시설", val: "42" }, - { text: "방송국", val: "43" }, - { text: "전신전화국", val: "44" }, - { text: "기타", val: "45" }, + { val: "17", text: "숙박업" }, + { val: "18", text: "목욕장업" }, + { val: "19", text: "공장" }, + { val: "20", text: "발전시설" }, + { val: "21", text: "관광단지개발사업" }, + { val: "22", text: "도시개발사업" }, + { val: "23", text: "산업단지개발사업" }, + { val: "24", text: "택지개발사업" }, + { val: "25", text: "대규모점포(대형마트)" }, + { val: "26", text: "대규모점포(전문점)" }, + { val: "27", text: "대규모점포(백화점)" }, + { val: "28", text: "대규모점포(쇼핑센터)" }, + { val: "29", text: "대규모점포(복합쇼핑몰)" }, + { val: "30", text: "물류시설" }, + { val: "31", text: "운수시설(여객자동차터미널)" }, + { val: "32", text: "운수시설(철도시설)" }, + { val: "33", text: "운수시설(공항시설)" }, + { val: "34", text: "운수시설(항만시설)" }, + { val: "35", text: "업무시설(공공업무시설)" }, + { val: "36", text: "업무시설(금융업소)" }, + { val: "37", text: "업무시설(사무소)" }, + { val: "38", text: "업무시설(결혼상담소 등 소개업소)" }, + { val: "39", text: "업무시설(출판사)" }, + { val: "40", text: "업무시설(신문사)" }, + { val: "41", text: "업무시설(오피스텔)" }, + { val: "42", text: "교정시설" }, + { val: "43", text: "방송국" }, + { val: "44", text: "전신전화국" }, + { val: "45", text: "기타" }, ]; export const orgwtKndCd = [ @@ -186,6 +186,7 @@ export const pplLenght = [ ]; export const rainFcltCd = [ + { val: "10", text: "확인불가" }, { val: "01", text: "지붕" }, { val: "02", text: "건물옥상" }, { val: "03", text: "골프장 연못" }, @@ -195,7 +196,6 @@ export const rainFcltCd = [ { val: "07", text: "태양광패널" }, { val: "08", text: "연못" }, { val: "09", text: "기타" }, - { val: "10", text: "확인불가" }, ]; diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/htdflReg/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/htdflReg/index.tsx index add7030fa..28888d042 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/htdflReg/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/htdflReg/index.tsx @@ -17,15 +17,15 @@ const hdtflObjSchema = z.object({ .string() .min(1, { message: validationMessages.required("에너지원 종류") }), fclCpct: z - .number() - .positive({ message: validationMessages.required("시설 용량") }), + .number().nullish(), + //.positive({ message: validationMessages.required("시설 용량") }), plwtYn: z.enum(["Y", "N"]).default("Y"), orgwtWtritSpt: z - .string() - .min(1, { message: validationMessages.required("원수 취수 지점") }), + .string().nullish(), + // .min(1, { message: validationMessages.required("원수 취수 지점") }), orgwtOcrnProcs: z - .string() - .min(1, { message: validationMessages.required("원수 발생 공정") }), + .string().nullish(), + // .min(1, { message: validationMessages.required("원수 발생 공정") }), wtritEntrcTp: z .number() .max(100, { message: "온도는 100이상이 될 수 없습니다." }) @@ -33,12 +33,12 @@ const hdtflObjSchema = z.object({ amtgnr: z.number().default(0), prcsProcsYn: z.enum(["Y", "N"]).default("Y"), prcsFcltCpct: z - .number() - .positive({ message: validationMessages.required("처리시설 용량") }), + .number().nullish(), + // .positive({ message: validationMessages.required("처리시설 용량") }), fmYn: z.string().default(""), utztnCpct: z - .number() - .positive({ message: validationMessages.required("이용 용량") }), + .number().nullish(), + // .positive({ message: validationMessages.required("이용 용량") }), utztnUsg: z .string() .min(1, { message: validationMessages.required("이용 용도") }), diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/type.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/type.tsx index 011d29da4..4bf891d87 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/type.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/type.tsx @@ -19,14 +19,9 @@ export const baseReuseBuildingSchema = z.object({ instlCstSprtYn: z.string().default("Y"), instlCstSprtAmt: z.number().nullish(), grntchBdstCertYn: z.string().default("Y"), - grntchBdstCertYr: z - .string() - .max(4, { message: validationMessages.yearFormat }) - .refine( - (year) => year.length === 0 || /^\d{4}$/.test(year), - { - message: validationMessages.yearFormat, - } + grntchBdstCertYr: z.string().refine( + (val) => val === "" || /^\d{4}$/.test(val), + { message: "연도는 빈값 또는 4자리 숫자여야 합니다." } ), grntchBdstCertNo: z.string().default(""), grntchBdstCertGrd: z.string().default(""), @@ -59,7 +54,7 @@ export const chgBuildingSchema = z.object({ export const htdflReuseBuildingSchema = z.object({ bdstRuseFcltSn: z.number().nullish(), bdstInfoSn: z.number().nullish(), - instlCmptnDt: z.coerce.date().default(() => new Date()), + //instlCmptnDt: z.coerce.date().default(() => new Date()), instlCst: z.number().nullish(), operMngCd: z.string().optional().nullish(), anlOperCst: z.number().default(0), diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/rswtReg/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/rswtReg/index.tsx index 7ef9bf0d9..02ea00b19 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/rswtReg/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/rswtReg/index.tsx @@ -14,7 +14,7 @@ const rswtrObjSchema = z.object({ //wtrUsqty: z.number({ required_error: validationMessages.required("물사용량(m³)") }).positive({ message: validationMessages.required("물사용량(m³)") }), //oblgTrgtWtrUsqty: z.number().nullish().default(0), orgwtKndCd: z.string({ required_error: validationMessages.requiredSelect("원수 종류") }).min(1, { message: validationMessages.requiredSelect("원수 종류") }), - filtrTrtmfctCpct: z.number({ required_error: validationMessages.required("처리시설 용량") }), + filtrTrtmfctCpct: z.number().nullish(), filtrTrtmfctProcs: z.string({ required_error: validationMessages.required("주요처리공정") }).min(1, { message: validationMessages.required("주요처리공정") }), filtrTrtmfctUsgCd: z.string().default("").nullish(), rswtrUsqty: z.number().default(0), diff --git a/apps/wr/components/ui/form/fcltReg/bdstReg/bdstReg.tsx b/apps/wr/components/ui/form/fcltReg/bdstReg/bdstReg.tsx index 90ca4e0f5..0b7be34fa 100644 --- a/apps/wr/components/ui/form/fcltReg/bdstReg/bdstReg.tsx +++ b/apps/wr/components/ui/form/fcltReg/bdstReg/bdstReg.tsx @@ -21,7 +21,7 @@ export default function BdstReg({ form, type }: { form: any; type?: string }) { const { userSiSggCd } = code; const [useCityCd, setUserCityCd] = useState([]); const [useSggCd, setUserSggCd] = useState([]); - + const [initDaddr, setInitDaddr] = useState(); useEffect(() => { if (userSiSggCd.length === 2 && code.cityCd.length > 0) { @@ -56,6 +56,7 @@ export default function BdstReg({ form, type }: { form: any; type?: string }) { form.setValue("ctpvCd", Number(city[0]?.val)); form.setValue("sggCd", Number(sgg[0]?.val)); } + }, [form, userSiSggCd, code.cityCd, code.sggCd]); @@ -67,9 +68,16 @@ export default function BdstReg({ form, type }: { form: any; type?: string }) { return () => subscription?.unsubscribe(); }, [form.form]); + // useEffect(() => { + // console.log("form ", form.watch()); + // }, [form.formState]); + + useEffect(() => { - console.log("form.formState ", form.watch("archArea")); - }, [form.formState]); + const bdstInfoSn = form.getValues("bdstInfoSn"); + if(bdstInfoSn&&bdstInfoSn>0) + setInitDaddr(form.getValues("daddr")); + }, [form.getValues("bdstInfoSn")]); return ( <> @@ -166,7 +174,7 @@ export default function BdstReg({ form, type }: { form: any; type?: string }) { /> */}
{ console.log("rtnData", rtnData); field.onChange(rtnData.roadAddrPart1); diff --git a/apps/wr/components/ui/form/fcltReg/htdflReg/htdflRegForm.tsx b/apps/wr/components/ui/form/fcltReg/htdflReg/htdflRegForm.tsx index dc2c16242..c17dfda04 100644 --- a/apps/wr/components/ui/form/fcltReg/htdflReg/htdflRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/htdflReg/htdflRegForm.tsx @@ -78,6 +78,10 @@ export default function HtdflRegForm({ }; const cleanHtdflData = (raw: any): z.infer => { + const buildings = + raw?.reuseBuilding && raw.reuseBuilding.length > 0 + ? raw.reuseBuilding + : [{}]; return { ...raw, frstArchPrmsnDt: new Date(raw?.frstArchPrmsnDt || null), @@ -92,7 +96,7 @@ export default function HtdflRegForm({ : new Date(item?.archChgPrmsnDt), bdstChgNm: item?.bdstChgNm ?? "", // null → "" })), - reuseBuilding: (raw?.reuseBuilding || []).map((item: any) => ({ + reuseBuilding:buildings.map((item: any) => ({ ...item, instlCmptnDt: new Date(item?.instlCmptnDt), htdflObj: { diff --git a/apps/wr/components/ui/form/fcltReg/rainReg/rainRegForm.tsx b/apps/wr/components/ui/form/fcltReg/rainReg/rainRegForm.tsx index 09acc46e8..fbcae44b0 100644 --- a/apps/wr/components/ui/form/fcltReg/rainReg/rainRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/rainReg/rainRegForm.tsx @@ -84,13 +84,14 @@ export default function RainRegForm({ typeof item?.grntchBdstCertYr === "string" ? item?.grntchBdstCertYr : "", // null → "" + bdstUsgCd: item?.bdstUsgCd ?? Number("-1"), // null → "-1" instlCstSprtYn: item?.instlCstSprtYn ?? "Y", // null → "Y" - grntchBdstCertYn: item?.grntchBdstCertYn ?? "Y", + grntchBdstCertYn: item?.grntchBdstCertYn ?? "D", rainyObj: { ...item?.rainyObj, - pntfColKnd:item.rainyObj?.pntfColKnd??'확인불가', + pntfColKnd:item.rainyObj?.pntfColKnd??rainFcltCd[0]?.val??'', filtrTrtmfctPrs:item.rainyObj?.filtrTrtmfctPrs??'', - filtrTrtmfctYn: item?.rainyObj?.filtrTrtmfctYn ?? "Y", + filtrTrtmfctYn: item?.rainyObj?.filtrTrtmfctYn ?? "D", }, })), }; @@ -98,7 +99,7 @@ export default function RainRegForm({ useEffect(() => { if (grntchBdstCertYn === "N") { - form.setValue("reuseBuilding.0.grntchBdstCertYr", ""); + form.setValue("reuseBuilding.0.grntchBdstCertYr", ''); form.setValue("reuseBuilding.0.grntchBdstCertNo", ""); form.setValue("reuseBuilding.0.grntchBdstCertGrd", ""); } else if (instlCstSprtYn === "N") { @@ -298,7 +299,10 @@ export default function RainRegForm({ selectClass={selectClass} label={""} selectArray={rainFcltCd} - onSelectValue={readonly ? () => { } : field.onChange} + onSelectValue={ (item) =>{ + if(readonly) return; + field.onChange(item) + } } value={field.value ?? rainFcltCd[0]?.val} readOnly={readonly} /> @@ -521,8 +525,7 @@ export default function RainRegForm({ control={form.control} name="reuseBuilding.0.grntchBdstCertYr" render={({ field }) => { - const isUnknown = - field.value === null || field.value === "0000"; // 또는 Number(value) === -1 + const isUnknown = field.value === null || field.value === "0000"; // 또는 Number(value) === -1 return ( { if (readonly) return; if (e.target.value === "확인불가") { diff --git a/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx b/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx index 5ae5cd264..82aa0227c 100644 --- a/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx @@ -83,8 +83,10 @@ export default function RswtrRegForm({ const filtrTrtmfctCpct = form.watch('reuseBuilding.0.rswtrObj.filtrTrtmfctCpct') const cleanRswtData = (raw: any): z.infer => { - - console.log('raw ', raw) + const buildings = + raw?.reuseBuilding && raw.reuseBuilding.length > 0 + ? raw.reuseBuilding + : [{}]; return { ...raw, frstArchPrmsnDt: raw?.frstArchPrmsnDt @@ -93,14 +95,15 @@ export default function RswtrRegForm({ archChgPrmsnDt: raw?.archChgPrmsnDt ? new Date(raw?.archChgPrmsnDt) : null, - reuseBuilding: (raw?.reuseBuilding || []).map((item: any) => ({ + reuseBuilding:buildings.map((item: any) => ({ ...item, instlCmptnDt: item?.instlCmptnDt ? new Date(item.instlCmptnDt) : new Date(), // item?.instlCmptnDt가 유효하지 않으면 현재 날짜로 초기화 operMngCd: item?.operMngCd || "", // null → "" - instlCstSprtYn: item?.instlCstSprtYn || code.yn[0]?.val, // null → "Y" - grntchBdstCertYn: item?.grntchBdstCertYn || code.yn[0]?.val, + instlCstSprtYn: item?.instlCstSprtYn || 'N', // null → "Y" + grntchBdstCertYn: item?.grntchBdstCertYn ||'N', + bdstUsgCd: item?.bdstUsgCd ?? Number("-1"), // null → "-1" rswtrObj: { ...item?.rswtrObj, filtrTrtmfctYn: item?.rainyObj?.filtrTrtmfctYn || code.yn[0]?.val, @@ -145,6 +148,9 @@ export default function RswtrRegForm({ if (rswtZod) form.reset(cleanRswtData(rswtForm)); }, [rswtForm, form]); + + + return (
@@ -291,7 +297,7 @@ export default function RswtrRegForm({ selectClass={selectClass} selectArray={code.orgwtKndCd} onSelectValue={readonly ? () => { } : field.onChange} - value={field.value || code.orgwtKndCd[0]?.val} + value={field.value ?? code.orgwtKndCd[0]?.val} readOnly={readonly} /> diff --git a/apps/wr/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal.tsx b/apps/wr/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal.tsx index 31f9f2d16..a692553af 100644 --- a/apps/wr/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal.tsx +++ b/apps/wr/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal.tsx @@ -49,8 +49,8 @@ export function BdstReg({ const { userSiSggCd } = code; - const [bdstTable, setbdstTable] = useState(); - const [rainBdstForm, setRainBdstForm] = useState( + const [bdstTable, setBdstTable] = useState(); + const [bdstForm, setBdstForm] = useState( {} as BdstTypeMap[T] ); const [searchTxt, setSearchTxt] = useState(); @@ -83,7 +83,7 @@ export function BdstReg({ archArea: archArea, }; }); - setbdstTable(tableData(bdstData, { code })); + setBdstTable(tableData(bdstData, { code })); } }, [bdst]); @@ -118,13 +118,13 @@ export function BdstReg({ if (val !== undefined && bdst?.bdstBase.length > 0) { const data: BdstTypeMap[T] = bdst?.bdstBase[val]; if (data) { - setRainBdstForm(data); + setBdstForm(data); } } }; useEffect(() => { - setbdstTable([]); // 초기화 작업 + setBdstTable([]); // 초기화 작업 }, []); return ( <> @@ -200,13 +200,13 @@ export function BdstReg({
{moType === "RNB" ? ( ) : moType === "RST" ? ( { if (setIsModal && open) { @@ -216,7 +216,7 @@ export function BdstReg({ > ) : ( { if (setIsModal && open) { diff --git a/apps/wr/components/ui/table/pbnstReg/ruseMngTableForm/ruseMngTableForm.tsx b/apps/wr/components/ui/table/pbnstReg/ruseMngTableForm/ruseMngTableForm.tsx index 18c065ee2..0f919a603 100644 --- a/apps/wr/components/ui/table/pbnstReg/ruseMngTableForm/ruseMngTableForm.tsx +++ b/apps/wr/components/ui/table/pbnstReg/ruseMngTableForm/ruseMngTableForm.tsx @@ -8,7 +8,7 @@ import { } from "@/components/lib/func/commom"; import { - DatePicker, + // DatePicker, FormField, FormUi, SelectBox, @@ -24,6 +24,7 @@ import { TableHeader, TableRow, } from "@common/components/ui"; +import moment from "moment"; import React from "react"; type RuseMngTableProps = { @@ -49,14 +50,17 @@ const RuseMngTableForm = React.forwardRef( form, formNm, indexBiz, - readonly= false + readonly = false, }, ref ) => { return ( <> - +
{upHeadList ? ( headMakeprfmnc(headList, upHeadList) @@ -76,20 +80,20 @@ const RuseMngTableForm = React.forwardRef( {tableData.map((item, i) => ( - + {headList.map((head, j) => ( { }} + onClick={() => {}} > = 0 && - typeof indexBiz === "number" && - indexBiz >= 0 + indexMng >= 0 && + typeof indexBiz === "number" && + indexBiz >= 0 ? `items.${indexMng}.fcBizs.${indexBiz}.${formNm}.${i}.${head.id}` : typeof indexMng === "number" && indexMng >= 0 ? `items.${indexMng}.${formNm}.${i}.${head.id}` @@ -99,7 +103,7 @@ const RuseMngTableForm = React.forwardRef( {head.edit === true ? ( { if (head.type === "number") { @@ -113,8 +117,10 @@ const RuseMngTableForm = React.forwardRef( ? formatCommaNumber(field.value) : field.value || "" } - readOnly={readonly|| head.readonly ? true : false} - maxLength={head.maxLength?head.maxLength:15} + readOnly={ + readonly || head.readonly ? true : false + } + maxLength={head.maxLength ? head.maxLength : 15} /> ) : head.type === "select" ? ( ( field.onChange(val); } }} - readOnly={ readonly||head.readonly ? true : false} - // Disabled prop removed as SelectBox does not support it + readOnly={ + readonly || head.readonly ? true : false + } + // Disabled prop removed as SelectBox does not support it > ) : head.type === "date" ? ( - { - console.log("date", date); - field.onChange(date); // readonly 상태가 아닐 때만 값 변경 - }, + // { + // console.log("date", date); + // field.onChange(date); // readonly 상태가 아닐 때만 값 변경 + // }, + // }} + // selectClass={"w-full"} + // readOnly={readonly||head.readonly ? true : false} + // /> + { + // 브라우저에서 선택한 값은 "YYYY-MM-DD" + const date = new Date(e.target.value); // e.g. "2025-06-01" + field.onChange(date); }} - selectClass={"w-full"} + value={ + field.value? moment(field.value, "YYYYMMDD").format( "YYYY-MM-DD"): "" + } + className={"w-full text-gray-600"} readOnly={readonly||head.readonly ? true : false} /> ) : head.type === "checkbox" ? ( @@ -158,22 +179,19 @@ const RuseMngTableForm = React.forwardRef( className="" checked={field.value || false} onCheckedChange={(checked) => { - if(readonly || head.readonly) { + if (readonly || head.readonly) { return; - }else{ + } else { field.onChange(checked); } }} > ) : ( - ( - - { - head.type === "number" + + {head.type === "number" ? formatCommaNumber(field.value) - : field.value || "" - } - ) + : field.value || ""} + )} )} diff --git a/apps/wr/middleware.ts b/apps/wr/middleware.ts index 7758ec5ec..716a851b8 100644 --- a/apps/wr/middleware.ts +++ b/apps/wr/middleware.ts @@ -10,6 +10,8 @@ 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 || ""; +const BUSINESS_JUSO_API_URL = process.env.NEXT_PUBLIC_FINDADRESS_URL || ""; +const AdressKey = process.env.NEXT_PUBLIC_WR_ADDRESS_KEY || ""; export function middleware(req: NextRequest) { const pathname = req.nextUrl.pathname; @@ -148,6 +150,24 @@ export function middleware(req: NextRequest) { return NextResponse.rewrite(new URL(newUrl)); } + + if (pathname.startsWith("/bussiness-juso-service")) { + console.log('AdressKey ', AdressKey) + const params = new URLSearchParams({ + currentPage: "1", // 원하는 페이지 + countPerPage: "5", // 페이지당 결과 수 + keyword: searchParams.get("keyword") || "", // 검색어(주소) + confmKey: AdressKey, // 인증키 인터넷용 패쇄망용은 따로 발급 필요 즉 middleware에서 치환해서 사용해야할듯 + resultType: "json", // 결과 형식 기본은 xml + }); + const newUrl = `${BUSINESS_JUSO_API_URL}?${params.toString()}`; + console.log(`[Middleware] Request to business.juso.go.kr`); + console.log(req.url); + console.log(`[Middleware] Rewriting from '/business.juso.go.kr' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl)); + } + + return NextResponse.next(); } @@ -161,6 +181,7 @@ export const config = { "/sewerage-service/:path*", "/reuse-service/:path*", "/operation-service/:path*", + "/bussiness-juso-service/:path*", "/" ], }; diff --git a/packages/common/business_components/src/ui/chart/LineBarChart.tsx b/packages/common/business_components/src/ui/chart/LineBarChart.tsx index 9f9a4feec..a7319cb01 100644 --- a/packages/common/business_components/src/ui/chart/LineBarChart.tsx +++ b/packages/common/business_components/src/ui/chart/LineBarChart.tsx @@ -7,6 +7,7 @@ import { } from "./lib/chart"; import type { ContentType } from "recharts/types/component/DefaultLegendContent"; import type { set } from "date-fns"; +import React from "react"; // 툴크 커스텀 css /** 차트 기본 스타일 */ @@ -126,6 +127,37 @@ export default function LineBarchart({ monthTicks = getTicks(chartData); } + const CustomAxisText : React.FC<{ viewBox: any, fieldText: string, position:string }> = ({ viewBox, fieldText, position }) => { + const { x, y, width, height } = viewBox; // 축 영역 + const padding = 5; // 차트 바깥으로 띄우기 + + if('left' == position) { + return ( + + {fieldText} + + ); + }else{ + return ( + + {fieldText} + + ); + } + } + return (
{chartTitle && chartTitle !== "" && chartTitle !== "undefined" ? ( @@ -133,7 +165,7 @@ export default function LineBarchart({ {chartTitle}
) : null} -
+
+ typeof value === "number" ? value.toLocaleString() : value + } + orientation="left" + label={(props) => } />} {/* 오른쪽 Y축: barVal */} {yFieldText2 && + typeof value === "number" ? value.toLocaleString() : value + } + label={(props) => } reversed={true} />} } /> diff --git a/packages/common/business_components/src/ui/chart/LineChart.tsx b/packages/common/business_components/src/ui/chart/LineChart.tsx index a76807bae..6fa3c4f64 100644 --- a/packages/common/business_components/src/ui/chart/LineChart.tsx +++ b/packages/common/business_components/src/ui/chart/LineChart.tsx @@ -65,11 +65,11 @@ const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?: return (

{label}

- {payload.map((item, index) => ( + {payload.filter(item => item.value != null && String(item.value) != 'NaN' && item.value != undefined).map((item, index) => (
{/* 색상 박스 추가 (범례 색상과 동일) */}
- {` ${item.name} : ${item.value}`} + {` ${item.name} : ${item.value.toString().replace(',','')}`}
))}
@@ -129,6 +129,37 @@ const CustomLegend = (props: LegendProps) => { ); }; +const CustomAxisText : React.FC<{ viewBox: any, fieldText: string, position:string }> = ({ viewBox, fieldText, position }) => { + const { x, y, width, height } = viewBox; // 축 영역 + const padding = 5; // 차트 바깥으로 띄우기 + + if('left' == position) { + return ( + + {fieldText} + + ); + }else{ + return ( + + {fieldText} + + ); + } +} + type DataType = {label: string; val: string}; type ChartDataType = { @@ -220,7 +251,10 @@ export default function Linechart({ yAxisId="left" className="text-[12px] font-bold" orientation="left" - label={{ value: yFieldText, className:"text-[12px] font-bold", angle: -90, position: 'insideLeft' }} /> + tickFormatter={(value) => + typeof value === "number" ? value.toLocaleString() : value + } + label={(props) => }/> } /> {showLegend && } {Object.keys(chartData).map((key) => { @@ -233,8 +267,10 @@ export default function Linechart({ type="linear" strokeWidth={2} dot={false} + connectNulls={true} name={String(config?.label || "X축")} - stroke={config?.color} + stroke={config?.color?.includes('dash')?config?.color?.replace('dash',''):config?.color} + strokeDasharray={config?.color?.includes("dash")?"4 4":undefined} /> ); })} diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index 276733475..bf8b1d128 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -30,75 +30,6 @@ export const GisContext = createContext({ checkedLayerIds: null, }); export const useGisContext = () => useContext(GisContext); -export const searchUpperPpls = async (fcltMngNo) => { - const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ""; - 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(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); - - // 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인 - const text = await res.text(); - if (!text || text.trim() === "") - 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; - - data.forEach((item) => { - const pplMngNo = item.ppl_mng_no.trim(); - - 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; - } - } - } - }); - - 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, - }); - } -}; function Gis({ isOpen, //우측 상세 레이어 팝업 @@ -121,7 +52,6 @@ function Gis({ isPlaying = false, setIsPlaying = () => { }, targetLayerTypes = [], - orderedLayers = [], checkInterval = 3000, height: height = "h-[calc(100vh-5rem)]", onOpenPopup = (data) => { }, @@ -359,7 +289,6 @@ function Gis({ isPlaying={isPlaying} setIsPlaying={setIsPlaying} targetLayerTypes={targetLayerTypes} - orderedLayers={orderedLayers} checkInterval={checkInterval} onProgressChange={handleProgressChange} /> diff --git a/packages/common/business_components/src/ui/map/GisTree.js b/packages/common/business_components/src/ui/map/GisTree.js index 54baddba5..197827010 100644 --- a/packages/common/business_components/src/ui/map/GisTree.js +++ b/packages/common/business_components/src/ui/map/GisTree.js @@ -39,7 +39,6 @@ export default React.memo(function GisTree({ isPlaying = false, setIsPlaying = () => { }, targetLayerTypes = [], - orderedLayers = [], checkInterval = 3000, onProgressChange = () => { }, }) { @@ -313,14 +312,15 @@ export default React.memo(function GisTree({ // orderedLayers 순서를 우선 적용하면서, 타입 필터에 맞는 타겟 노드만 선별 const getTargetNodesInOrder = () => { - const orderedNodes = []; - orderedLayers.forEach((lyrSn) => { - const node = nodes.find( - (n) => n.id === lyrSn && layerTypes.includes(n.data?.lyrTyNm) - ); - if (node) orderedNodes.push(node); + const target = nodes.filter((n) => n.data?.actlLyrYn === 'N' && layerTypes.includes(n.data?.lyrTyNm)); + + // drawOrdr 값을 기준으로 오름차순 정렬 + target.sort((a, b) => { + const aOrder = a.data?.drawOrdr || 0; + const bOrder = b.data?.drawOrdr || 0; + return aOrder - bOrder; }); - return orderedNodes; + return target; }; const targetNodes = getTargetNodesInOrder(); @@ -510,8 +510,8 @@ export default React.memo(function GisTree({ {/* 사이드 패널 */}
obj.lyrTyNm === lyrTyNm); + // 이전 검색 레이어 완전히 제거 + this.clearSearchLayer(matched); + // 새로운 검색 레이어 생성 + const layer = this.ensureSearchLayer(matched); layer.set("gobjTy", matched.gobjTy); // 새 소스 만들기 - const source = this.getFeatureSourceFromGeoserver(lyrTyNm, featureId); + const source = this.getFeatureSourceFromGeoserver(lyrTyNm, featureId, matched); // 1) 로드 완료시 extent 맞추기 const onLoadEnd = () => { + if (Object.keys(source.nullGeometryFeatures_).length > 0) alert('해당 시설의 위치 정보가 없습니다.'); + const feats = source.getFeatures(); if (feats && feats.length > 0) { const select = GisApp.Module.select; @@ -665,12 +688,13 @@ GisOlLayer.prototype = { * @param {*} featureId * @returns */ - getFeatureSourceFromGeoserver: function (lyrTyNm, featureId) { + getFeatureSourceFromGeoserver: function (lyrTyNm, featureId, option) { const typeName = this.storageNm + lyrTyNm; const srsName = GisApp.TO_PROJ; const baseUrl = GisApp.GEOSERVER_URL; const source = new VectorSource({ + ...option, format: new GeoJSON(), url: () => { const params = new URLSearchParams({ @@ -695,13 +719,14 @@ GisOlLayer.prototype = { * @param {Array|string} featureIds 피처 ID 배열 또는 단일 ID */ showMultipleSearchResultsFromGeoJSON: function (lyrTyNm, featureIds) { - const layer = this.ensureSearchLayer(); - - const prev = layer.getSource(); - if (prev) prev.clear(true); const layerCode = GisApp.layerCode; const matched = Object.values(layerCode).find(obj => obj.lyrTyNm === lyrTyNm); + + // 이전 검색 레이어 완전히 제거 + this.clearSearchLayer(matched); + // 새로운 검색 레이어 생성 + const layer = this.ensureSearchLayer(matched); layer.set("gobjTy", matched.gobjTy); const source = this.getMultiFeatureSourceFromGeoserver(lyrTyNm, featureIds); diff --git a/packages/common/business_components/src/ui/map/lib/gis.select.js b/packages/common/business_components/src/ui/map/lib/gis.select.js index d0209a6ed..ed4f9a2a7 100644 --- a/packages/common/business_components/src/ui/map/lib/gis.select.js +++ b/packages/common/business_components/src/ui/map/lib/gis.select.js @@ -284,6 +284,7 @@ GisOlSelect.prototype = { selectedFeatures.forEach(feature => { selectedFeatures.remove(feature); }); + selectedFeatures.clear(); }, /** * SELECT 이벤트 활성화 diff --git a/packages/common/business_components/src/ui/sdmInsertForm/form.tsx b/packages/common/business_components/src/ui/sdmInsertForm/form.tsx index 5099db92e..d4e4b7a2e 100644 --- a/packages/common/business_components/src/ui/sdmInsertForm/form.tsx +++ b/packages/common/business_components/src/ui/sdmInsertForm/form.tsx @@ -412,10 +412,10 @@ export function SdmRegForm({ // {...field} // /> { field.onChange(rtnData.roadAddrPart1); - console.log("rtnData ", rtnData); }} /> ), diff --git a/packages/common/business_components/src/ui/search_input_address/findAddress.ts b/packages/common/business_components/src/ui/search_input_address/findAddress.ts index f56ab2370..985d20465 100644 --- a/packages/common/business_components/src/ui/search_input_address/findAddress.ts +++ b/packages/common/business_components/src/ui/search_input_address/findAddress.ts @@ -1,85 +1,88 @@ -const addresssKey = 'devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM='; //인증키 만료일은 2025-11-23 + +// const Key = 'U01TX0FVVEgyMDI1MDgyODE0MTAxNzExNjEyMjA' +// const addresssKey = 'devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM='; //인증키 만료일은 2025-11-23 /** * 주소 API 응답 타입 * https://business.juso.go.kr/addrlink/main.do 해당 사이트 참조 */ export type AddressApiResponse ={ - results: { - common: { - errorMessage: string; - countPerPage: string; - totalCount: string; - errorCode: string; - currentPage: string; - }; - juso: addressType[]; + results: { + common: { + errorMessage: string; + countPerPage: string; + totalCount: string; + errorCode: string; + currentPage: string; }; - } - - export type addressType ={ - detBdNmList: string; - engAddr: string; - rn: string; - emdNm: string; - zipNo: string; - roadAddrPart2: string; - emdNo: string; - sggNm: string; - jibunAddr: string; - siNm: string; - roadAddrPart1: string; - bdNm: string; - admCd: string; - udrtYn: string; - lnbrMnnm: string; - roadAddr: string; - lnbrSlno: string; - buldMnnm: string; - bdKdcd: string; - liNm: string; - rnMgtSn: string; - mtYn: string; - bdMgtSn: string; - buldSlno: string; - } + juso: addressType[]; + }; +} + +export type addressType ={ + detBdNmList: string; + engAddr: string; + rn: string; + emdNm: string; + zipNo: string; + roadAddrPart2: string; + emdNo: string; + sggNm: string; + jibunAddr: string; + siNm: string; + roadAddrPart1: string; + bdNm: string; + admCd: string; + udrtYn: string; + lnbrMnnm: string; + roadAddr: string; + lnbrSlno: string; + buldMnnm: string; + bdKdcd: string; + liNm: string; + rnMgtSn: string; + mtYn: string; + bdMgtSn: string; + buldSlno: string; +} - /** - * 주소에 따른 검색 - * @param search : 검색어 - * @returns addressType[] 주소 배열 - */ +/** + * 주소에 따른 검색 + * @param search : 검색어 + * @returns addressType[] 주소 배열 + */ export const findAddress = async (search: string) => { - if (!search) { - return []; - } - const params = new URLSearchParams({ - currentPage: "1", // 원하는 페이지 - countPerPage: "5", // 페이지당 결과 수 - keyword: search, // 검색어(주소) - confmKey: addresssKey, // 인증키 인터넷용 패쇄망용은 따로 발급 필요 즉 middleware에서 치환해서 사용해야할듯 - resultType: "json", // 결과 형식 기본은 xml - }); - const url = `https://business.juso.go.kr/addrlink/addrLinkApi.do?${params.toString()}`; - try { - const response = await fetch(url); - if (!response.ok) { - throw new Error('Network response was not ok'); - } - const data = await response.json(); - if (data.results && data.results.juso) { - return data.results.juso as addressType[]; - } else { - return []; - } - } catch (error) { - console.error('Error fetching address data:', error); - return []; - } + if (!search) { + return []; + } + const params = new URLSearchParams({ + currentPage: "1", // 원하는 페이지 + countPerPage: "5", // 페이지당 결과 수 + keyword: search, // 검색어(주소) + confmKey: '', // 인증키 인터넷용 패쇄망용은 따로 발급 필요 즉 middleware에서 치환해서 사용해야할듯 + resultType: "json", // 결과 형식 기본은 xml + }); + // const url = `https://business.juso.go.kr/addrlink/addrLinkApi.do?${params.toString()}`; + const url = `/bussiness-juso-service/addrlink/addrLinkApi.do?${params.toString()}`; + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error('Network response was not ok'); + } + const data = await response.json(); + if (data.results && data.results.juso) { + return data.results.juso as addressType[]; + } else { + return []; + } + } catch (error) { + console.error('Error fetching address data:', error); + return []; + } } diff --git a/packages/common/business_components/src/ui/search_input_address/index.tsx b/packages/common/business_components/src/ui/search_input_address/index.tsx index fa88cd4b0..15c8f2f43 100644 --- a/packages/common/business_components/src/ui/search_input_address/index.tsx +++ b/packages/common/business_components/src/ui/search_input_address/index.tsx @@ -14,7 +14,7 @@ type thisProps = { * @returns setRtnData => 선택된 주소 데이터 반환 */ export default function SearchInputAddress({ - initValue = undefined, + initValue , setRtnData = () => {}, readonly=false }: thisProps) { @@ -24,6 +24,13 @@ export default function SearchInputAddress({ const [searchList, setSearchList] = useState([]); + useEffect(()=>{ + + if(initValue !== textValue && initValue !== undefined && initValue !== null && initValue !== ''){ + setTextValue(initValue) + } + + },[initValue]) const wrapperRef = useRef(null); const handleInputChange = useCallback( (e: React.ChangeEvent) => { diff --git a/packages/common/business_components/src/ui/table/index.tsx b/packages/common/business_components/src/ui/table/index.tsx index 9ffea89bf..6d5b24c70 100644 --- a/packages/common/business_components/src/ui/table/index.tsx +++ b/packages/common/business_components/src/ui/table/index.tsx @@ -30,6 +30,7 @@ type UiTableProps = { boldArray?: number[]; resetSelection?: boolean; index?: number; + curPage?: number; headButton?: React.ReactNode | undefined; }; @@ -54,9 +55,10 @@ export function UiTable({ boldArray = [], resetSelection = false, index = -1, + curPage = 1, headButton = undefined, }: UiTableProps) { - const [cur, setCur] = useState(1); + const [cur, setCur] = useState(curPage); const [idx, setIdx] = useState(index); const [headerHeight, setHeaderHeight] = useState(0); const headerRef = useRef(null); @@ -84,6 +86,10 @@ export function UiTable({ setIdx(index); }, [index]) + useEffect(() => { + setCur(curPage); + }, [curPage]) + const handlePageChange = (page: number) => { setCur(page); onPageChange(page);