diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx index a75b3acd7..79eb07830 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx @@ -58,7 +58,7 @@ export default function MyForm() { dataList={demandCompList} rtnValue={callback} rtnInputData={handleInsertSdmData} - openNetwork={false} + openNetwork={true} /> diff --git a/apps/dm/app/store/sdmMng/index.tsx b/apps/dm/app/store/sdmMng/index.tsx index 35dd3d2a9..b5b258839 100644 --- a/apps/dm/app/store/sdmMng/index.tsx +++ b/apps/dm/app/store/sdmMng/index.tsx @@ -501,6 +501,7 @@ export const upHeadSdmInfoStatus: TableUpperProps[] = [ const postSdmData = async (data: DemandCompType) => { try { await postFetch("/reuseFc/request", data); + sessionStorage.setItem("user", JSON.stringify(data) ); toast.success("수요처 등록에 성공하였습니다."); return true; } catch (error) { diff --git a/apps/dm/middleware.ts b/apps/dm/middleware.ts index ee26a9767..0b2b21fbb 100644 --- a/apps/dm/middleware.ts +++ b/apps/dm/middleware.ts @@ -42,9 +42,8 @@ export function middleware(req: NextRequest) { .map(([key, value]) => `${key}=${decodeURIComponent(value)}`) .join("&"); - const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${ - decodedParams ? `?${decodedParams}` : "" - }`; + const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${decodedParams ? `?${decodedParams}` : "" + }`; return NextResponse.rewrite(new URL(newUrl)); } @@ -55,33 +54,25 @@ export function middleware(req: NextRequest) { if (pathname.startsWith("/attach-service")) { const requestHeaders = new Headers(req.headers); - // const token = - // "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhMGVjN2EwNy03NTJhLTRmN2YtODlhNi04NTRlNjNkMmUzYTAiLCJhdXRob3JpdGllcyI6IlJPTEVfQURNSU4sUk9MRV9BTk9OWU1PVVMiLCJtbmdyWW4iOiJZIiwiZXhwIjoxNzUyMTM1OTk3fQ.itGY6M6mC2iwKvEpXw2KS6354T_B_OFz4hD26ResIgwJhpwiDJCMuSXD-rZ3AarmAIKd0udpCuJR7UlvFMN2ww"; - // const userId = "a0ec7a07-752a-4f7f-89a6-854e63d2e3a0"; const token = req.cookies.get(CLAIM_NAME)?.value; const userId = req.cookies.get(AUTH_USER_ID)?.value; const newUrl = `${serverApiUrl}${pathname}`; - - //wr:dev: pathname /attach-service/api/v1/tus/upload/5e60b70a-956e-40c3-be5a-cdd3100bf8ad - //http://dev.onasys.co.kr:8000/attach-service/api/v1/tus/upload/5e60b70a-956e-40c3-be5a-cdd3100bf8ad if (token) requestHeaders.set(CLAIM_NAME, token); if (userId) requestHeaders.set(AUTH_USER_ID, userId); - console.log( - `[Middleware] Rewriting from '/attach-service' path to: ${newUrl}` - ); + console.log(`[Middleware] Rewriting from '/attach-service' path to: ${newUrl}`); return NextResponse.rewrite(new URL(newUrl), { headers: requestHeaders, }); } - if (pathname.startsWith("/user-service")) { + if (pathname.startsWith("/user-service") + || pathname.startsWith("/cms-service")) { + let queryParams = searchParams.toString(); const requestHeaders = new Headers(req.headers); - const newUrl = `${serverApiUrl}${pathname}`; + const newUrl = `${serverApiUrl}${pathname}${queryParams ? `?${queryParams}` : ""}`; - console.log( - `[Middleware] Rewriting from '/user-service' path to: ${newUrl}` - ); + console.log(`[Middleware] Rewriting from '/user-service' path to: ${newUrl}`); return NextResponse.rewrite(new URL(newUrl), { headers: requestHeaders, }); @@ -130,27 +121,24 @@ export function middleware(req: NextRequest) { if (pathname.startsWith("/reuse-service")) { const queryParams = searchParams.toString(); - const newUrl = `${SERVER_API_WR_URL}${pathname.replace("/reuse-service", "")}${ - queryParams ? `?${queryParams}` : "" - }`; + const newUrl = `${SERVER_API_WR_URL}${pathname.replace("/reuse-service", "")}${queryParams ? `?${queryParams}` : "" + }`; console.log(`[Middleware] Rewriting from '/reuse-service' path to: ${newUrl}`); return NextResponse.rewrite(new URL(newUrl)); } if (pathname.startsWith("/operation-service")) { const queryParams = searchParams.toString(); - const newUrl = `${SERVER_API_FO_URL}${pathname.replace("/operation-service", "")}${ - queryParams ? `?${queryParams}` : "" - }`; + const newUrl = `${SERVER_API_FO_URL}${pathname.replace("/operation-service", "")}${queryParams ? `?${queryParams}` : "" + }`; console.log(`[Middleware] Rewriting from '/operation-service' path to: ${newUrl}`); return NextResponse.rewrite(new URL(newUrl)); } if (pathname.startsWith("/sewerage-service")) { const queryParams = searchParams.toString(); - const newUrl = `${SEWERAGE_API_URL}${pathname.replace("/sewerage-service", "")}${ - queryParams ? `?${queryParams}` : "" - }`; + const newUrl = `${SEWERAGE_API_URL}${pathname.replace("/sewerage-service", "")}${queryParams ? `?${queryParams}` : "" + }`; console.log(`[Middleware] Rewriting from '/sewerage-service' path to: ${newUrl}`); return NextResponse.rewrite(new URL(newUrl)); } @@ -174,9 +162,7 @@ export function middleware(req: NextRequest) { 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}` - ); + console.log(`[Middleware] Rewriting from '/business.juso.go.kr' path to: ${newUrl}`); return NextResponse.rewrite(new URL(newUrl)); } @@ -187,6 +173,7 @@ export const config = { matcher: [ "/server/:path*", "/user-service/:path*", + "/cms-service/:path*", "/attach-service/:path*", "/geoserver/:path*", "/operation-service/:path*", diff --git a/apps/fo/app/store/facilityPrecipitation/index.tsx b/apps/fo/app/store/facilityPrecipitation/index.tsx index f7a716349..39bc35220 100644 --- a/apps/fo/app/store/facilityPrecipitation/index.tsx +++ b/apps/fo/app/store/facilityPrecipitation/index.tsx @@ -1,5 +1,5 @@ -import { getFetch } from "@/app/components/api"; -import CalcRainEffect from "@/app/components/common/CalcRainEffect"; +import { getFetch, postFetch } from "@/app/components/api"; +//import CalcRainEffect from "@/app/components/common/CalcRainEffect"; import { roundNumber } from "@/app/components/common/DataForm"; import type { TableUpperProps } from "../dashboard"; export type { TableUpperProps }; @@ -130,47 +130,109 @@ const upHeadListPrecipitationDetail:TableUpperProps[] = [ const gridListPrecipitationDetail:PrecipitationPropsDetailProps[] = [ ] +export interface FacilityRainDetailProps { + bscFctCd: string, // 시설코드 + rainDt: string, // 측정일 + stnKo: string, // 관측소명 + infoRnDay: number | string | '', // 강수량 (mm/일) + rainDay: number, // 강우일 (일) + rainEffectDay: number, // 강우영향일 (일) +} + /**==================================== * Store - api ====================================*/ +// 시설정보 목록 [] +export const fetchFacilityRainDetailList = async (searchYear: string|undefined, fcltCdList: string[]): Promise => { + try { + const res = await postFetch('fo', '/fclt/rain/detail/'+searchYear, fcltCdList); + return res; + } catch (error) { + console.error('시설정보 목록 데이터를 가져오는데 실패했습니다:', error); + return null; + } +}; const fetchFacilityPrecipitationList = async (pageNo: number, pageSize: number, searchYear: string, part?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => { const tailStr = '/'+pageNo + '/' + pageSize + '/' + searchYear + (part?'/' + part:'' ) + (nameCd?'/' + nameCd:'' ) + (fcltCpct?'/' + fcltCpct:'' ) + (operationValue?'/' + operationValue:'' ) + (fcltyNm && fcltyNm !== '' ? '/' + fcltyNm : '') ; const url = '/fclt/rain' + tailStr; const res = await getFetch('fo', url); - const tmpData:PrecipitationPropsDetailProps[] = []; - if(res!= null && res.data.length > 0){ - for(let idx=0; idx=3?1:0; + - tmpData.push({ - bscFctCd : bscFctCd, - rainDt : rainDt, - infoRnDay : infoRnDay, - rainDay : rainDay, - rainEffectDay : rainDay - }) + const fcltCdList : string[]= []; + res?.data.forEach(item => { + fcltCdList.push(item.bscFctCd); + }); + + const rainDetailListRes: Record = {}; + const promises: Promise[] = []; + for (let idx = 0; idx < fcltCdList.length; idx += 100) { + const slicedItems = fcltCdList.slice(idx, idx + 100); + // 각 fetch 호출을 Promise 배열에 담기 + promises.push(fetchFacilityRainDetailList(searchYear, slicedItems)); + } + // 모든 요청을 병렬로 실행 + const results = await Promise.all(promises); + //console.log(results); + // 결과를 합치기 + for (const res of results) { + if (res.status === 200 && res.data) { + for (const [key, value] of Object.entries(res.data)) { + const keyStr = String(key); + rainDetailListRes[keyStr] = [...(rainDetailListRes[keyStr] || []), ...(value as any[])]; } } - - res.data.map((item: FacilityPrecipitationProps) => { - const filteredList = tmpData.filter((gridItem) => { - return gridItem.bscFctCd === item.bscFctCd; - }); - - const rtnData = CalcRainEffect(filteredList); - item.rainEffectDay = rtnData.reduce((acc, curr) => acc + curr.rainEffectDay, 0); - item.totalYearRain = rtnData.reduce((acc, curr) => acc + Number(curr.infoRnDay), 0); - item.infoRnDay = filteredList.reduce((acc, curr) => acc + curr.rainDay, 0); - }); - } + //console.log(rainDetailListRes); - return { data: res.data, count: res.count, detailData: tmpData }; + const tmpData:PrecipitationPropsDetailProps[] = []; + + for (const item of res?.data) { + const rainDetailList = rainDetailListRes[item.bscFctCd]; + + let rainDayCnt = 0; // 강우일수 + let rainEffectDayCnt = 0; // 강우영향일수 + let totalRain = 0; // 총강수량 + if (rainDetailList) { + item.stnKo = rainDetailList[0]?.stnKo || ''; // 관측소명 + + let rainEffectDayEval = 0; // 강우영향일 평가 변수 + rainDetailList.forEach(item2 => { + + if (item2.infoRnDay == null) item2.infoRnDay = 0; + if (item2.infoRnDay >= 3) { // 강우량이 3mm 이상인 경우 강우일 + rainEffectDayEval = 2; + item2.rainDay = 1; + rainDayCnt++; // 강우일수 증가 + } else { + rainEffectDayEval--; + item2.rainDay = 0; + } + + if (rainEffectDayEval < 0) { // 청천일 + item2.rainEffectDay = 0; + } else { // 강우영향일 + item2.rainEffectDay = 1; + rainEffectDayCnt++; // 강우영향일수 증가 + } + totalRain += Number(item2.infoRnDay); + item2.rainDt = item2.rainDt; + tmpData.push({ + bscFctCd: item.bscFctCd, + rainDt: item2.rainDt.substring(0,10), + infoRnDay: item2.infoRnDay, + rainEffectDay: item2.rainEffectDay, + rainDay: item2.rainDay + }); + }); + item.infoRnDay = rainDayCnt; + item.rainEffectDay = rainEffectDayCnt; + item.totalYearRain = totalRain; + item.rainDetailList = rainDetailList; + } + } + + return { data: res.data, count: res.count, detailData: tmpData}; } /**==================================== diff --git a/apps/fo/app/store/rdi/index.tsx b/apps/fo/app/store/rdi/index.tsx index 6fd287432..67c6efa1e 100644 --- a/apps/fo/app/store/rdi/index.tsx +++ b/apps/fo/app/store/rdi/index.tsx @@ -17,7 +17,7 @@ export type { TableUpperProps }; export type RdiProps = { bscFctCd : string, sido : string, - sigungo : string, + gugun : string, bscFctNm : string, lclgvrnmntNm : string, daddr : string, @@ -40,7 +40,7 @@ export type RdiProps = { export interface RdiInterface { bscFctCd : string, sido : string, - sigungo : string, + gugun : string, bscFctNm : string, lclgvrnmntNm : string, daddr : string, @@ -103,7 +103,7 @@ export interface ExcelType { const upHeadList1:TableUpperProps[] = [ { id: "A", title: "행정구역", row:0, colSpan:2 }, { id: "sido", title: "시도", row:1, widthPercent: 6 }, - { id: "sigungo", title: "시군구", row:1, widthPercent: 6 }, + { id: "gugun", title: "시군구", row:1, widthPercent: 6 }, { id: "bscFctNm", title: "시설명", row:0, rowSpan:2, widthPercent: 7 }, { id: "daddr", title: "주소", row:0, rowSpan:2 }, { id: "fctCpc", title: "시설용량\n(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 7 }, @@ -147,7 +147,7 @@ const upHeadList3:TableUpperProps[] = [ { id: "rgnNm", title: "환경청", row:0, rowSpan:2, widthPercent: 6 }, { id: "B", title: "행정구역", row:0, colSpan:2 }, { id: "sido", title: "시도", upName:"upChangeRe2", upSequnce:1, row:1, widthPercent: 4 }, - { id: "sigungo", title: "시군구", upName:"upChangeRe2",upSequnce:2, row:1, widthPercent: 4 }, + { id: "gugun", title: "시군구", upName:"upChangeRe2",upSequnce:2, row:1, widthPercent: 4 }, { id: "bscFctNm", title: "시설명", row:0, rowSpan:2, widthPercent: 6 }, { id: "daddr", title: "주소", row:0, rowSpan:2 }, { id: "fctCpc", title: "시설용량\n(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 5 }, @@ -184,8 +184,10 @@ const fetchRdiList = async (pageNo: number, pageSize: number, searchYear: string item.rdiiValue = ''; item.infoRnDay = ''; item.properYn = ''; - item.sido = item.lclgvrnmntNm.split(' ')[0]?.toString() || ''; - item.sigungo = item.lclgvrnmntNm.split(' ')[1]?.toString() || ''; + // item.sido = item.lclgvrnmntNm.split(' ')[0]?.toString() || ''; + // item.gugun = item.lclgvrnmntNm.split(' ')[1]?.toString() || ''; + item.sido = item.sido; + item.gugun = item.gugun; }); return { data: res.data, count: res.count } }catch(error){ @@ -307,8 +309,10 @@ export const rdiReducer:(set:SetStateFunction)=>RdiActions=(set: SetStateFunctio const rtnData = res.data.map((item: RdiProps) => { return { ...item, - sido: item.lclgvrnmntNm.split(' ')[0]?.toString() || '', - sigungo: item.lclgvrnmntNm.split(' ')[1]?.toString() || '', + //sido: item.lclgvrnmntNm.split(' ')[0]?.toString() || '', + //gugun: item.lclgvrnmntNm.split(' ')[1]?.toString() || '', + sido: item.sido, + gugun : item.gugun, inflAmnt: item.inflAmnt?Number(item.inflAmnt).toFixed(1): '' // 유입유량 (강우량) } }) @@ -427,7 +431,7 @@ export const rdiReducer:(set:SetStateFunction)=>RdiActions=(set: SetStateFunctio 환경청: item.rgnNm, 시설명: item.bscFctNm, 시도: item.sido, - 시군구: item.sigungo, + 시군구: item.gugun, 주소: item.daddr, "시설용량(m³/일)": item.fctCpc, "청천시유입하수량(m³/일)": item.chungchunFlow, diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx index e334d7d30..c824600c7 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx @@ -231,11 +231,12 @@ const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: { const renderModalBody = () => (
-
- +
+ +
{/* 강우정보 테이블 */}
-
+
{/* 수량/수질측정 정보 테이블 */} - + +
)} diff --git a/apps/wr/app/(with-layout)/matMng/coMat/coMatRvw/page.tsx b/apps/wr/app/(with-layout)/matMng/coMat/coMatRvw/page.tsx index d4e16b676..248f8f5e0 100644 --- a/apps/wr/app/(with-layout)/matMng/coMat/coMatRvw/page.tsx +++ b/apps/wr/app/(with-layout)/matMng/coMat/coMatRvw/page.tsx @@ -723,6 +723,12 @@ export default function CoMatRvw() { ))}
+ + + +
+

표준공사비 산정

+
{ @@ -350,7 +350,7 @@ export default function CoSttn() { }} bodyClassNm={"w-full mx-auto bg-white rounded-lg shadow-md p-6"} updateList={updateData} - openNetwork={false} + openNetwork={true} > } > diff --git a/apps/wr/app/(with-layout)/matMng/coMng/drgtSttn/page.tsx b/apps/wr/app/(with-layout)/matMng/coMng/drgtSttn/page.tsx index c27d418e6..b34a1eeba 100644 --- a/apps/wr/app/(with-layout)/matMng/coMng/drgtSttn/page.tsx +++ b/apps/wr/app/(with-layout)/matMng/coMng/drgtSttn/page.tsx @@ -68,6 +68,15 @@ export default function DrgtSttn() { sggNm: code.sggCd.find(sgg => sgg.text == item.sggNm)?.text } }) + .sort((a, b) => { + // ctpvCd 기준 1차 정렬 + const ctpvCompare = (a.ctpvCd || '').localeCompare(b.ctpvCd || ''); + if (ctpvCompare !== 0) return ctpvCompare; + + // sggNm 기준 2차 정렬 + return (a.sggNm || '').localeCompare(b.sggNm || ''); + }); + const data = Array.isArray(resData) ? resData.map((item: customDrgt) => { return { diff --git a/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx b/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx index dcb0f0b65..186e0bb77 100644 --- a/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx +++ b/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx @@ -301,12 +301,12 @@ export default function ConCostForm({ return ( <> -
+
-

+ {/*

표준공사비 산정 -

-
+ */} +

처리시설 표준공사비 산정

) : ( diff --git a/apps/wr/middleware.ts b/apps/wr/middleware.ts index a5956ee0b..d1e96dacc 100644 --- a/apps/wr/middleware.ts +++ b/apps/wr/middleware.ts @@ -60,7 +60,8 @@ export function middleware(req: NextRequest) { } if (pathname.startsWith("/cms-service")) { - const newUrl = `${serverApiUrl}${pathname}`; + let queryParams = searchParams.toString(); + const newUrl = `${serverApiUrl}${pathname}${queryParams ? `?${queryParams}` : ""}`; console.log(`[Middleware] Rewriting from '/cms-service' path to: ${newUrl}`); const rewriteRequest = rewriteWith(newUrl, req, true); return rewriteRequest; diff --git a/packages/common/business_components/src/ui/dialog_ui/index.tsx b/packages/common/business_components/src/ui/dialog_ui/index.tsx index 4201cbdfa..67b2a3c53 100644 --- a/packages/common/business_components/src/ui/dialog_ui/index.tsx +++ b/packages/common/business_components/src/ui/dialog_ui/index.tsx @@ -31,19 +31,21 @@ export function UiModal({ <> - {noHeaderYn === 'N' && ( - - - {headerTitle} - - - )} - {(open && body) ? - useDirectRender - ? body - : <>{body} - : <> - } + {noHeaderYn === 'N' && ( + + + {headerTitle} + + + )} +
+ {(open && body) ? + useDirectRender + ? body + : <>{body} + : <> + } +
{noFooterYn === 'N' && ( {footerContent} diff --git a/packages/common/business_components/src/ui/index.ts b/packages/common/business_components/src/ui/index.ts index 4686e3a99..db1132b58 100644 --- a/packages/common/business_components/src/ui/index.ts +++ b/packages/common/business_components/src/ui/index.ts @@ -14,7 +14,7 @@ export * from "./search_form_left"; export * from "./search_form_right"; export * from "./search_input"; export * from "./search_input_select"; -export * from "./search_input_address"; +// export * from "./search_input_address"; export * from "./select"; export * from "./tab"; export * from "./table"; diff --git a/packages/common/business_components/src/ui/sdmInsertForm/form.tsx b/packages/common/business_components/src/ui/sdmInsertForm/form.tsx index 6d4bf8acf..2883413c2 100644 --- a/packages/common/business_components/src/ui/sdmInsertForm/form.tsx +++ b/packages/common/business_components/src/ui/sdmInsertForm/form.tsx @@ -1,4 +1,4 @@ -import { Input } from "@common/components"; +import { Button, Input, Label } from "@common/components"; import { AsYouType } from "libphonenumber-js"; import React, { useCallback, useEffect, useState } from "react"; import type { ControllerRenderProps, UseFormReturn } from "react-hook-form"; @@ -6,8 +6,10 @@ import { FormField } from "../form"; import { FormUi } from "../form_ui"; import { SelectBox } from "../select"; import { SdmSearchForm } from "./sdmNameBox"; -import SearchInputAddress from "../search_input_address"; -import type { addressType } from "../search_input_address/findAddress"; +import { UiModal, UiTable } from '../../ui'; +import type { Zip, ZipSearchParams } from "../search_input_address/zipService"; +import { findAddress } from "../search_input_address/findAddress"; +import { cn } from "@common/components"; // 수요처 정보 export type DemandCompType = { @@ -18,7 +20,7 @@ export type DemandCompType = { daddr: string | ""; telno: string | ""; rgtr: string | ""; - mdfr?:string ; + mdfr?: string; useUsgCd: string | ""; ctpvCd: string | ""; sggCd: string | ""; @@ -89,6 +91,7 @@ export function SdmRegForm({ readOnly, openNetwork, }: SdmRegFormProps) { + const PAGE_SIZE = 10; const [sidoValue, setSidoValue] = useState(""); const [sigunValue, setSigunValue] = useState(""); const [filteredSigunData, setFilteredSigunData] = useState([ @@ -97,6 +100,34 @@ export function SdmRegForm({ const [isBznmTotNoDisabled, setIsBznmTotNoDisabled] = useState(true); const [orgBznmNo, setOrgBznmNo] = useState(""); const [updateCoNm, setUpdateNm] = useState(""); + const [isZipModalOpen, setIsZipModalOpen] = useState(false); + const [zipId, setZipId] = useState(undefined); + const [address, setAddress] = useState(""); + const [detailedAddress, setDetailedAddress] = useState(""); + const [curPage, setCurPage] = useState(1); + // 우편번호 검색 관련 상태 + const [zipSearchResults, setZipSearchResults] = useState([]); + const [zipTotalElements, setZipTotalElements] = useState(-1); + const [searchQuery, setSearchQuery] = useState({ + roadNm: "", + bldgNoMno: null, + bldgNoSno: null, + page: curPage, + size: PAGE_SIZE + }); + const [isResetPage, setIsResetPage] = useState(false); + const [isResetSelection, setIsResetSelection] = useState(false); + const [selectedRowIndex, setSelectedRowIndex] = useState(undefined); + const [selectedZip, setSelectedZip] = useState(); + const [isSelectedZip, setIsSelectedZip] = useState(false); + + const handleCellClick = (index: any, headId: any) => { + console.log('index', index); + console.log('headId', headId); + setSelectedZip(zipSearchResults[index]); + setIsSelectedZip(true); + console.log('selectedZip', zipSearchResults[index]); + }; const formatPhoneNumber = (value: string) => { const input = value; @@ -262,6 +293,17 @@ export function SdmRegForm({ [orgBznmNo, setIsDuplicate], ); + // 우편번호 선택 함수 + const handleSelectAddress = () => { + setZipId(selectedZip?.zipId); + setAddress(selectedZip?.zipAddr || ""); + setDetailedAddress(""); + setIsZipModalOpen(false); + setSelectedZip(undefined); + setIsSelectedZip(false); + setSelectedRowIndex(undefined); + }; + useEffect(() => { if (updateList?.dmdInfoId) { form.reset(updateList); @@ -283,199 +325,344 @@ export function SdmRegForm({ console.log("form.formState.errors ", form.formState.errors); }, [form.formState.errors]); + useEffect(() => { + console.log("curPage ", curPage); + searchQuery.page = curPage - 1; + const fetchAddress = async () => { + const result = await findAddress(searchQuery); + setZipSearchResults(result?.content); + }; + fetchAddress(); + }, [curPage]); + + // 폼 제출 전에 주소 합치기 + useEffect(() => { + // address와 detailedAddress를 합쳐서 daddr에 설정 + if (address) { + const fullAddress = `${address || ""} ${detailedAddress || ""}`.trim(); + form.setValue("daddr", fullAddress, {shouldValidate: true }); + } + }, [address, detailedAddress, form]); + + const renderBody = () => ( + <> + { + e.preventDefault(); + e.stopPropagation(); + const result = await findAddress(searchQuery); + setZipSearchResults(result?.content); + setZipTotalElements(result?.totalElements); + }} + className="mb-4 space-y-2" + > + {/* 검색 조건 부분 */} +
+
+
+ + + setSearchQuery({ + ...searchQuery, + roadNm: e.target.value, + }) + } + placeholder="예: 테헤란로" + className="!text-[1.3rem]" + /> +
+ +
+ + { + const value = e.target.value + ? parseInt(e.target.value) + : null; + setSearchQuery({ + ...searchQuery, + bldgNoMno: value, + }); + }} + placeholder="예: 123" + className="!text-[1.3rem]" + /> +
+ +
+ + { + const value = e.target.value + ? parseInt(e.target.value) + : null; + setSearchQuery({ + ...searchQuery, + bldgNoSno: value, + }); + }} + placeholder="예: 45" + className="!text-[1.3rem]" + /> +
+
+ +
+ +
+
+ + + {/* 검색 결과 부분 */} + {zipTotalElements > 0 && ( + + )} + {zipTotalElements === 0 && ( +
+ + 조회된 데이터가 없습니다. +
+ )} + + ); + + const formFields: Array<{ name: keyof FormSchema; label: string; component: FieldComponent; }> = [ - { - name: "ctpvCd", - label: "시도", - component: (field) => - updateList?.dmdInfoId ? ( - { - field.onChange(e.target.value); - }} - value={ - selectSidoData.find((item) => item.val === field.value?.trim()) - ?.text ?? "" - } - readOnly={!!updateList?.dmdInfoId} - /> - ) : ( - - onSelectValue(val.toString(), "ctpvCd", field ?? "") - } - className="" - value={sidoValue} + { + name: "ctpvCd", + label: "시도", + component: (field) => + updateList?.dmdInfoId ? ( + { + field.onChange(e.target.value); + }} + value={ + selectSidoData.find((item) => item.val === field.value?.trim()) + ?.text ?? "" + } + readOnly={!!updateList?.dmdInfoId} + /> + ) : ( + + onSelectValue(val.toString(), "ctpvCd", field ?? "") + } + className="" + value={sidoValue} + /> + ), + }, + { + name: "sggCd", + label: "시군구", + component: (field) => + updateList?.dmdInfoId ? ( + { + field.onChange(e.target.value); + }} + value={ + selectSigunData.find((item) => item.val === field.value?.trim()) + ?.text ?? "" + } + readOnly={!!updateList?.dmdInfoId} + /> + ) : ( + + onSelectValue(val.toString(), "sggCd", field) + } + className="" + value={sigunValue} + /> + ), + }, + { + name: "dmdCoNm", + label: "회사명", + component: (field) => ( + ), - }, - { - name: "sggCd", - label: "시군구", - component: (field) => - updateList?.dmdInfoId ? ( + }, + { + name: "dmdInfoId", + label: "사업자등록번호", + component: (field) => ( { - field.onChange(e.target.value); - }} - value={ - selectSigunData.find((item) => item.val === field.value?.trim()) - ?.text ?? "" - } + onChange={(e) => bznNoChange(e, "dmdInfoId", field)} + value={formatBusinessNumber(field.value ?? "")} readOnly={!!updateList?.dmdInfoId} /> - ) : ( - - onSelectValue(val.toString(), "sggCd", field) - } - className="" - value={sigunValue} - /> ), - }, - { - name: "dmdCoNm", - label: "회사명", - component: (field) => ( - - ), - }, - { - name: "dmdInfoId", - label: "사업자등록번호", - component: (field) => ( - bznNoChange(e, "dmdInfoId", field)} - value={formatBusinessNumber(field.value ?? "")} - readOnly={!!updateList?.dmdInfoId} - /> - ), - }, - { - name: "bznmTotNo", - label: "", - component: (field) => ( - + }, + { + name: "bznmTotNo", + label: "", + component: (field) => ( + + bznNoChange(e, "bznmTotNo", field)} + // {...field} + value={field.value ?? ""} + readOnly={!!updateList?.dmdInfoId} + /> + + ), + }, + { + name: "daddr", + label: "사업자주소", + component: (field) => + openNetwork ? ( +
+
+ {/* 우편번호 */} + + +
+ {/* 주소 */} + + {/* 상세 주소 */} + { + const value = e.target.value; + setDetailedAddress(value); + }} + value={detailedAddress ?? ""} + /> +
+ ) : ( + + ), + }, + { + name: "telno", + label: "전화번호", + component: (field) => { + const formattedValue = formatPhoneNumber(field.value ?? ""); // 초기 렌더링 시에도 포맷 적용 + + return ( + { + const value = e.target.value.replace(/\D/g, ""); // 숫자만 저장 + field.onChange(value); + }} + /> + ); + }, + }, + + { + name: "dmdAplyPswd", + label: "패스워드", + component: (field) => ( bznNoChange(e, "bznmTotNo", field)} - // {...field} - value={field.value ?? ""} - readOnly={!!updateList?.dmdInfoId} - /> -
- ), - }, - { - name: "daddr", - label: "사업자주소", - component: (field) => - openNetwork ? ( - { - field.onChange(rtnData.roadAddrPart1); - }} - /> - ) : ( - ), - }, - { - name: "telno", - label: "전화번호", - component: (field) => { - const formattedValue = formatPhoneNumber(field.value ?? ""); // 초기 렌더링 시에도 포맷 적용 - - return ( - { - const value = e.target.value.replace(/\D/g, ""); // 숫자만 저장 - field.onChange(value); - }} - /> - ); }, - }, - - { - name: "dmdAplyPswd", - label: "패스워드", - component: (field) => ( - - ), - }, - { - name: "dmdAplyPswdConfirm", - label: "패스워드확인", - component: (field) => ( - - ), - }, - ]; + { + name: "dmdAplyPswdConfirm", + label: "패스워드확인", + component: (field) => ( + + ), + }, + ]; return ( <> @@ -538,6 +725,36 @@ export function SdmRegForm({ /> ); })} + -1 ? 'h-[80vh]' : 'h-[30vh]'} + w-[30vw] + min-w-[500px] + max-w-[90vw] + max-h-[85vh] + overflow-y-auto + !fixed + !top-[50%] + !left-[50%] + !-translate-x-1/2 + !-translate-y-1/2 + z-[101] + flex + flex-col + `} + body={renderBody()} + footerContent={ +
+ + +
+ } + useDirectRender={true} + /> ); } diff --git a/packages/common/business_components/src/ui/sdmInsertForm/index.tsx b/packages/common/business_components/src/ui/sdmInsertForm/index.tsx index 1b9aed0a0..ef9dbeed2 100644 --- a/packages/common/business_components/src/ui/sdmInsertForm/index.tsx +++ b/packages/common/business_components/src/ui/sdmInsertForm/index.tsx @@ -39,7 +39,7 @@ const createFormSchema = (isBznmTotNoDisabled: boolean, isDuplicate: boolean ,up .refine(() => !isDuplicate, "이미 등록된 사업자등록번호입니다"), daddr: z.string() .min(1, "주소를 입력해주세요") - .min(6, "주소는 6자 이상 입력해주세요") + // .min(6, "주소는 6자 이상 입력해주세요") .max(40, "주소는 40자 이내로 입력해주세요"), telno: z.string() .min(8, "전화번호는 8자 이상 입력해주세요") 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 c458b80c4..33cd14d13 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 @@ -2,11 +2,13 @@ // const Key = 'U01TX0FVVEgyMDI1MDgyODE0MTAxNzExNjEyMjA' // const addresssKey = 'devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM='; //인증키 만료일은 2025-11-23 +import type { Zip, ZipSearchParams } from "./zipService"; + /** * 주소 API 응답 타입 * https://business.juso.go.kr/addrlink/main.do 해당 사이트 참조 */ -export type AddressApiResponse ={ +export type AddressApiResponse = { results: { common: { errorMessage: string; @@ -19,7 +21,7 @@ export type AddressApiResponse ={ }; } -export type addressType ={ +export type addressType = { detBdNmList: string; engAddr: string; rn: string; @@ -46,79 +48,70 @@ export type addressType ={ buldSlno: string; } - - - - /** * 주소에 따른 검색 * @param search : 검색어 * @returns addressType[] 주소 배열 */ -export const findAddress = async (search: string) => { - 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 []; - } +export const findAddress = async (searchQuery: ZipSearchParams): Promise => { + if (!searchQuery) return []; + + // 쿼리 파라미터 생성 + const params = new URLSearchParams(); + if (searchQuery.roadNm) params.append('roadNm', searchQuery.roadNm); + if (searchQuery.bldgNoMno != null) params.append('bldgNoMno', searchQuery.bldgNoMno.toString()); + if (searchQuery.bldgNoSno != null) params.append('bldgNoSno', searchQuery.bldgNoSno.toString()); + if (searchQuery.page != null) params.append('page', searchQuery.page.toString()); + if (searchQuery.size != null) params.append('size', searchQuery.size.toString()); + + const url = `/cms-service/api/v1/zips?${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) return data + else return []; } catch (error) { - console.error('Error fetching address data:', error); - return []; + console.error('Error fetching address data:', error); + return []; } } -export const searchFilter=(search:string)=>{ +export const searchFilter = (search: string) => { - if(search.length >0){ - //특수문자 제거 - const expText = /[%=><]/ ; - if(expText.test(search) == true){ - alert("특수문자를 입력 할수 없습니다.") ; - search = search.split(expText).join(""); - return false; - } - - //특정문자열(sql예약어의 앞뒤공백포함) 제거 - const sqlArray = [ - //sql 예약어 - "OR", "SELECT", "INSERT", "DELETE", "UPDATE", "CREATE", "DROP", "EXEC", - "UNION", "FETCH", "DECLARE", "TRUNCATE" + if (search.length > 0) { + //특수문자 제거 + const expText = /[%=><]/; + if (expText.test(search) == true) { + alert("특수문자를 입력 할수 없습니다."); + search = search.split(expText).join(""); + return false; + } + + //특정문자열(sql예약어의 앞뒤공백포함) 제거 + const sqlArray = [ + //sql 예약어 + "OR", "SELECT", "INSERT", "DELETE", "UPDATE", "CREATE", "DROP", "EXEC", + "UNION", "FETCH", "DECLARE", "TRUNCATE" ] - - let regex; - for(let i=0; i void; - readonly?: boolean; -}; +// type thisProps = { +// initValue: string | undefined; +// setRtnData: (rtnData: addressType) => void; +// readonly?: boolean; +// }; -/** - * - * @param initValue : 초기값 ? - * @returns setRtnData => 선택된 주소 데이터 반환 - */ -export default function SearchInputAddress({ - initValue, - setRtnData = () => {}, - readonly = false, -}: thisProps) { - const [isOpen, setIsOpen] = useState(false); - const [textValue, setTextValue] = useState(initValue || ""); - const [searchList, setSearchList] = useState([]); +// /** +// * +// * @param initValue : 초기값 ? +// * @returns setRtnData => 선택된 주소 데이터 반환 +// */ +// export default function SearchInputAddress({ +// initValue, +// setRtnData = () => {}, +// readonly = false, +// }: thisProps) { +// const [isOpen, setIsOpen] = useState(false); +// const [textValue, setTextValue] = useState(initValue || ""); +// const [searchList, setSearchList] = useState([]); - useEffect(() => { - if ( - initValue !== textValue && - initValue !== undefined && - initValue !== null && - initValue !== "" - ) { - setTextValue(initValue); - } - }, [initValue]); - const wrapperRef = useRef(null); +// useEffect(() => { +// if ( +// initValue !== textValue && +// initValue !== undefined && +// initValue !== null && +// initValue !== "" +// ) { +// setTextValue(initValue); +// } +// }, [initValue]); +// const wrapperRef = useRef(null); - const handleInputChange = useCallback( - (e: React.ChangeEvent) => { - const value = e.target.value; +// const handleInputChange = useCallback( +// (e: React.ChangeEvent) => { +// const value = e.target.value; - if (searchFilter(value)) { - setTextValue(value); - findAddress(value).then((data) => { - setSearchList(data); - }); - }else{ - setTextValue(""); - setSearchList([]); - } - if (value) { - setIsOpen(true); - } else { - setIsOpen(false); - } - }, - [setSearchList], - ); +// if (searchFilter(value)) { +// setTextValue(value); +// findAddress(value).then((data) => { +// setSearchList(data); +// }); +// }else{ +// setTextValue(""); +// setSearchList([]); +// } +// if (value) { +// setIsOpen(true); +// } else { +// setIsOpen(false); +// } +// }, +// [setSearchList], +// ); - const handleSuggestionClick = useCallback( - (roadAddrPart1: string) => { - setTextValue(roadAddrPart1); - setIsOpen(false); - setRtnData( - searchList?.find((item) => item.roadAddrPart1 === roadAddrPart1) || - ({} as addressType), - ); - }, - [searchList, setRtnData], - ); +// const handleSuggestionClick = useCallback( +// (roadAddrPart1: string) => { +// setTextValue(roadAddrPart1); +// setIsOpen(false); +// setRtnData( +// searchList?.find((item) => item.roadAddrPart1 === roadAddrPart1) || +// ({} as addressType), +// ); +// }, +// [searchList, setRtnData], +// ); - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - wrapperRef.current && - !wrapperRef.current.contains(event.target as Node) - ) { - setIsOpen(false); - } - }; +// useEffect(() => { +// const handleClickOutside = (event: MouseEvent) => { +// if ( +// wrapperRef.current && +// !wrapperRef.current.contains(event.target as Node) +// ) { +// setIsOpen(false); +// } +// }; - document.addEventListener("mousedown", handleClickOutside); - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, []); +// document.addEventListener("mousedown", handleClickOutside); +// return () => { +// document.removeEventListener("mousedown", handleClickOutside); +// }; +// }, []); - return ( -
- setIsOpen(true)} */ - className="h-[3.2rem] w-full rounded-md border border-gray-300 pl-3 !text-xl text-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500" - placeholder={`주소를 입력하세요`} - readOnly={readonly} - /> - {isOpen && ( -
-
- {searchList && searchList.length > 0 ? ( - searchList.map((item: addressType, index: number) => ( -
handleSuggestionClick(item.roadAddrPart1)} - role="button" - tabIndex={0} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - handleSuggestionClick(item.roadAddrPart1); - } - }} - > - {item.zipNo}  - {item.roadAddrPart1} -
- )) - ) : ( -
- 검색 결과가 없습니다. -
- )} -
-
- )} -
- ); -} +// return ( +//
+// setIsOpen(true)} */ +// className="h-[3.2rem] w-full rounded-md border border-gray-300 pl-3 !text-xl text-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500" +// placeholder={`주소를 입력하세요`} +// readOnly={readonly} +// /> +// {isOpen && ( +//
+//
+// {searchList && searchList.length > 0 ? ( +// searchList.map((item: addressType, index: number) => ( +//
handleSuggestionClick(item.roadAddrPart1)} +// role="button" +// tabIndex={0} +// onKeyDown={(e) => { +// if (e.key === "Enter" || e.key === " ") { +// handleSuggestionClick(item.roadAddrPart1); +// } +// }} +// > +// {item.zipNo}  +// {item.roadAddrPart1} +//
+// )) +// ) : ( +//
+// 검색 결과가 없습니다. +//
+// )} +//
+//
+// )} +//
+// ); +// } diff --git a/packages/common/business_components/src/ui/search_input_address/zipService.ts b/packages/common/business_components/src/ui/search_input_address/zipService.ts new file mode 100644 index 000000000..04f0b85b2 --- /dev/null +++ b/packages/common/business_components/src/ui/search_input_address/zipService.ts @@ -0,0 +1,66 @@ +interface SortConfig { + empty: boolean; + sorted: boolean; + unsorted: boolean; +} + +interface PageableConfig { + sort: SortConfig; + offset: number; + pageNumber: number; + pageSize: number; + paged: boolean; + unpaged: boolean; +} + +export interface PageableList { + content: T[]; + pageable: PageableConfig; + sort: SortConfig; + totalPages: number; + totalElements: number; + number: number; + numberOfElements: number; + size: number; + first: boolean; + last: boolean; + empty: boolean; +} + +export interface PageableParams { + page?: number; + size?: number; + sort?: string; +} + + +export interface ZipSearchParams extends PageableParams { + zoneNo?: string; // 구역번호 + ctpvNm?: string; // 시도명 + sggNm?: string; // 시군구명 + emdNm?: string; // 읍면동명 + roadNm?: string; // 도로명 + stdgNm?: string; // 법정동명 + dongNm?: string; // 행정동명 + bldgNoMno?: number | null; // 빌딩본번 + bldgNoSno?: number | null; // 빌딩딩부번 + lotnoMno?: number | null; // 지번본번 + lotnoSno?: number | null; // 지번부번 +} + +export interface Zip { + zipId: number; + zoneNo: string; + ctpvNm: string; + sggNm: string; + emdNm: string; + roadNm: string; + bldgNoMno: number; + bldgNoSno: number; + lotnoMno: number; + lotnoSno: number; + lotno: string; + zipAddr: string; +} + +export type ZipList = PageableList;