diff --git a/packages/common/business_components/src/ui/index.ts b/packages/common/business_components/src/ui/index.ts index eab3a7eab..8604f80d5 100644 --- a/packages/common/business_components/src/ui/index.ts +++ b/packages/common/business_components/src/ui/index.ts @@ -14,6 +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 "./select"; export * from "./tab"; export * from "./table"; 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 new file mode 100644 index 000000000..f56ab2370 --- /dev/null +++ b/packages/common/business_components/src/ui/search_input_address/findAddress.ts @@ -0,0 +1,86 @@ +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[]; + }; + } + + 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[] 주소 배열 + */ +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 []; + } + +} + + 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 new file mode 100644 index 000000000..bacd767e4 --- /dev/null +++ b/packages/common/business_components/src/ui/search_input_address/index.tsx @@ -0,0 +1,108 @@ +import { Input } from "@common/components/ui"; +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { findAddress, type addressType } from "./findAddress"; + +type thisProps = { + initValue: string | undefined; + setRtnData: (rtnData: addressType) => void; +}; + +/** + * + * @param initValue : 초기값 ? + * @returns setRtnData => 선택된 주소 데이터 반환 + */ +export default function SearchInputAddress({ + initValue = undefined, + setRtnData = () => {}, +}: thisProps) { + + const [isOpen, setIsOpen] = useState(false); + const [textValue, setTextValue] = useState(initValue || ""); + const [searchList, setSearchList] = useState([]); + + + const wrapperRef = useRef(null); + const handleInputChange = useCallback( + (e: React.ChangeEvent) => { + const value = e.target.value; + setTextValue(value); + findAddress(value).then((data) => { + setSearchList(data); + }) + 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] + ); + + 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); + }; + }, []); + + return ( +
+ setIsOpen(true)} */ + className="w-full h-[3.2rem] !text-xl pl-3 text-gray-600 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder={`주소를 입력하세요`} + /> + {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} +
+ )) + ) : ( +
+ 검색 결과가 없습니다. +
+ )} +
+
+ )} +
+ ); +}