This commit is contained in:
2025-11-13 17:27:12 +09:00
19 changed files with 808 additions and 458 deletions
@@ -58,7 +58,7 @@ export default function MyForm() {
dataList={demandCompList}
rtnValue={callback}
rtnInputData={handleInsertSdmData}
openNetwork={false}
openNetwork={true}
/>
<Toaster position="top-center" />
</div>
+1
View File
@@ -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) {
+16 -29
View File
@@ -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*",
@@ -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<FacilityRainDetailProps | null> => {
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<res.data.length; idx++){
for(let idx2=0; idx2<res.data[idx].rainyDayObjList.length; idx2++){
const bscFctCd = res.data[idx].rainyDayObjList[idx2].bscFctCd;
const rainDt = res.data[idx].rainyDayObjList[idx2].rainDt;
const infoRnDay = res.data[idx].rainyDayObjList[idx2].infoRnDay;
const rainDay = res.data[idx].rainyDayObjList[idx2].infoRnDay>=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<string, any[]> = {};
const promises: Promise<any>[] = [];
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};
}
/**====================================
+13 -9
View File
@@ -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,
@@ -231,11 +231,12 @@ const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: {
const renderModalBody = () => (
<div className='grid grid-cols-1 gap-4 p-4 mr-4 mt-4'>
<div className='border rounded-lg p-4 bg-white'>
<Label className='text-3xl font-medium text-label '>강우/측정 정보</Label>
<div className='border rounded-lg pt-4 bg-white'>
<Label className='text-2xl font-bold text-label ml-4'>강우/측정 정보</Label>
<hr className="my-4 ml-4 mr-4 border-t border-gray-300" />
{/* 강우정보 테이블 */}
<UiTable
headName='강우정보'
headName=''
tableData={rainMeasureData}
headlist={untrtSwgRainCol3}
pageSize={3}
@@ -249,7 +250,7 @@ const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: {
{/* 측정정보 테이블 */}
<UiTable
headName='측정정보'
headName=''
tableData={rainMeasureData}
headlist={untrtSwgRainCol4}
pageSize={3}
@@ -278,11 +279,12 @@ const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: {
</UiTable>
</div>
<div className='border rounded-lg p-4 bg-white'>
<div className='border rounded-lg pt-4 bg-white'>
{/* 수량/수질측정 정보 테이블 */}
<Label className='text-3xl font-medium text-label '>수량/수질측정 정보</Label>
<Label className='text-2xl font-bold text-label ml-4'>수량/수질측정 정보</Label>
<hr className="my-4 ml-4 mr-4 border-t border-gray-300" />
<UiTable
headName=' '
headName=''
tableData={mntrngsData}
headlist={untrtSwgRainCol5}
pageSize={3}
@@ -340,8 +342,7 @@ const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: {
`}
body={renderModalBody()}
noHeaderYn='Y'
noFooterYn='N'
// footerContent={renderFooterContent()}
noFooterYn='Y'
/>
</div>
)}
@@ -723,6 +723,12 @@ export default function CoMatRvw() {
))}
</div>
</Form>
</ListDiv>
<ListDiv>
<div className="card-header flex items-center justify-between">
<h3 className="text-2xl font-medium text-label">표준공사비 산정</h3>
</div>
<ConCostForm
form={form}
type={"mat"}
@@ -302,7 +302,7 @@ export default function CoSttn() {
</div>
<UiModal
bodyClass={
"w-[40vw] max-w-none h-[83vh] " // +updateData?.dmdInfoId?'h-[60vh]':'h-[90vh]'
"w-[40vw] max-w-none h-[90vh] overflow-y-auto" // +updateData?.dmdInfoId?'h-[60vh]':'h-[90vh]'
}
open={isDmdModal.isMoal}
onOpenChange={() => {
@@ -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}
></SdmInsertForm>
}
></UiModal>
@@ -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 {
@@ -301,12 +301,12 @@ export default function ConCostForm({
return (
<>
<hr className="mt-8" />
<Form {...form}>
<h3 className="mt-4 ml-2 text-2xl font-medium text-label">
{/* <h3 className="mt-4 ml-2 text-2xl font-medium text-label">
표준공사비 산정
</h3>
<div className="flex items-center mx-3 mb-3">
</h3> */}
<div className="flex items-center mx-3 mb-3 pt-5">
<h1 className="">처리시설 표준공사비 산정</h1>
<Button className="mx-3" size={"sm"} onClick={calculateConstCost}>
@@ -766,7 +766,7 @@ export function PbnstBuseForm({
></UiModal>
<UiModal
bodyClass={"w-[40vw] max-w-none h-[90vh]"}
bodyClass={"w-[40vw] max-w-none h-[100vh] overflow-y-auto"}
open={isDmdModal.isMoal}
onOpenChange={() => {
setIsDmdModal({ isMoal: false, data: {} as DemandCompType });
@@ -787,9 +787,10 @@ export function PbnstBuseForm({
});
}}
bodyClassNm={"w-full mx-auto bg-white rounded-lg shadow-md p-6"}
openNetwork={false}
openNetwork={true}
></SdmInsertForm>
}
noFooterYn={"Y"}
></UiModal>
</div>
) : (
+2 -1
View File
@@ -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;
@@ -31,19 +31,21 @@ export function UiModal({
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className={bodyClass}>
{noHeaderYn === 'N' && (
<DialogHeader>
<DialogDescription className={headerTitleClass}>
{headerTitle}
</DialogDescription>
</DialogHeader>
)}
{(open && body) ?
useDirectRender
? body
: <>{body}</>
: <></>
}
{noHeaderYn === 'N' && (
<DialogHeader>
<DialogDescription className={headerTitleClass}>
{headerTitle}
</DialogDescription>
</DialogHeader>
)}
<div>
{(open && body) ?
useDirectRender
? body
: <>{body}</>
: <></>
}
</div>
{noFooterYn === 'N' && (
<DialogFooter className="sm:justify-start">
{footerContent}
@@ -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";
@@ -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<string>("");
const [sigunValue, setSigunValue] = useState<string>("");
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([
@@ -97,6 +100,34 @@ export function SdmRegForm({
const [isBznmTotNoDisabled, setIsBznmTotNoDisabled] = useState(true);
const [orgBznmNo, setOrgBznmNo] = useState<string>("");
const [updateCoNm, setUpdateNm] = useState<string>("");
const [isZipModalOpen, setIsZipModalOpen] = useState<boolean>(false);
const [zipId, setZipId] = useState<number | undefined>(undefined);
const [address, setAddress] = useState<string | undefined>("");
const [detailedAddress, setDetailedAddress] = useState<string>("");
const [curPage, setCurPage] = useState<number>(1);
// 우편번호 검색 관련 상태
const [zipSearchResults, setZipSearchResults] = useState<Zip[]>([]);
const [zipTotalElements, setZipTotalElements] = useState<number>(-1);
const [searchQuery, setSearchQuery] = useState<ZipSearchParams>({
roadNm: "",
bldgNoMno: null,
bldgNoSno: null,
page: curPage,
size: PAGE_SIZE
});
const [isResetPage, setIsResetPage] = useState<boolean>(false);
const [isResetSelection, setIsResetSelection] = useState<boolean>(false);
const [selectedRowIndex, setSelectedRowIndex] = useState<number | undefined>(undefined);
const [selectedZip, setSelectedZip] = useState<Zip>();
const [isSelectedZip, setIsSelectedZip] = useState<boolean>(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 = () => (
<>
<form
onSubmit={async (e) => {
e.preventDefault();
e.stopPropagation();
const result = await findAddress(searchQuery);
setZipSearchResults(result?.content);
setZipTotalElements(result?.totalElements);
}}
className="mb-4 space-y-2"
>
{/* 검색 조건 부분 */}
<div className="card card-border !m-0 flex flex-col gap-4 p-4">
<div className="grid grid-cols-3 gap-4">
<div className="flex flex-col">
<Label className="mb-1 font-bold text-base">도로명</Label>
<Input
value={searchQuery.roadNm || ""}
onChange={(e) =>
setSearchQuery({
...searchQuery,
roadNm: e.target.value,
})
}
placeholder="예: 테헤란로"
className="!text-[1.3rem]"
/>
</div>
<div className="flex flex-col">
<Label className="mb-1 text-base">건물본번</Label>
<Input
type="number"
value={searchQuery.bldgNoMno ?? ""}
onChange={(e) => {
const value = e.target.value
? parseInt(e.target.value)
: null;
setSearchQuery({
...searchQuery,
bldgNoMno: value,
});
}}
placeholder="예: 123"
className="!text-[1.3rem]"
/>
</div>
<div className="flex flex-col">
<Label className="mb-1 text-base">건물부번</Label>
<Input
type="number"
value={searchQuery.bldgNoSno ?? ""}
onChange={(e) => {
const value = e.target.value
? parseInt(e.target.value)
: null;
setSearchQuery({
...searchQuery,
bldgNoSno: value,
});
}}
placeholder="예: 45"
className="!text-[1.3rem]"
/>
</div>
</div>
<div className="flex justify-end">
<Button className="rounded-md" type="submit">검색</Button>
</div>
</div>
</form>
{/* 검색 결과 부분 */}
{zipTotalElements > 0 && (
<UiTable
headName=''
tableData={zipSearchResults}
headlist={[{ id: 'zoneNo', title: '우편번호' }, { id: 'zipAddr', title: '주소' }]}
pageSize={PAGE_SIZE}
total={zipTotalElements}
cellClick={handleCellClick}
onPageChange={setCurPage}
resetPageNo={isResetPage}
resetSelection={isResetSelection}
index={selectedRowIndex}
/>
)}
{zipTotalElements === 0 && (
<div className={cn("flex h-96 flex-col items-center justify-center gap-4")}>
<i className="diveicon di-info-circle text-3xl" />
조회된 데이터가 없습니다.
</div>
)}
</>
);
const formFields: Array<{
name: keyof FormSchema;
label: string;
component: FieldComponent;
}> = [
{
name: "ctpvCd",
label: "시도",
component: (field) =>
updateList?.dmdInfoId ? (
<Input
placeholder="시도"
type="text"
className="!text-xl text-gray-600"
onChange={(e) => {
field.onChange(e.target.value);
}}
value={
selectSidoData.find((item) => item.val === field.value?.trim())
?.text ?? ""
}
readOnly={!!updateList?.dmdInfoId}
/>
) : (
<SelectBox
selectClass={undefined}
label=""
selectArray={selectSidoData}
onSelectValue={(val) =>
onSelectValue(val.toString(), "ctpvCd", field ?? "")
}
className=""
value={sidoValue}
{
name: "ctpvCd",
label: "시도",
component: (field) =>
updateList?.dmdInfoId ? (
<Input
placeholder="시도"
type="text"
className="!text-xl text-gray-600"
onChange={(e) => {
field.onChange(e.target.value);
}}
value={
selectSidoData.find((item) => item.val === field.value?.trim())
?.text ?? ""
}
readOnly={!!updateList?.dmdInfoId}
/>
) : (
<SelectBox
selectClass={undefined}
label=""
selectArray={selectSidoData}
onSelectValue={(val) =>
onSelectValue(val.toString(), "ctpvCd", field ?? "")
}
className=""
value={sidoValue}
/>
),
},
{
name: "sggCd",
label: "시군구",
component: (field) =>
updateList?.dmdInfoId ? (
<Input
placeholder="시도"
type="text"
className="!text-xl text-gray-600"
onChange={(e) => {
field.onChange(e.target.value);
}}
value={
selectSigunData.find((item) => item.val === field.value?.trim())
?.text ?? ""
}
readOnly={!!updateList?.dmdInfoId}
/>
) : (
<SelectBox
selectClass={"!text-xl text-gray-600"}
label=""
selectArray={filteredSigunData}
onSelectValue={(val) =>
onSelectValue(val.toString(), "sggCd", field)
}
className=""
value={sigunValue}
/>
),
},
{
name: "dmdCoNm",
label: "회사명",
component: (field) => (
<SdmSearchForm
rtnData={setRtnData}
label="수요처명"
initValue={updateCoNm}
dataList={dataList}
rtnValue={setRtnValue}
rtnText={setRtnText}
readOnly={readOnly}
{...field}
/>
),
},
{
name: "sggCd",
label: "시군구",
component: (field) =>
updateList?.dmdInfoId ? (
},
{
name: "dmdInfoId",
label: "사업자등록번호",
component: (field) => (
<Input
placeholder="시도"
placeholder="사업자등록번호를 입력하세요"
type="text"
className="!text-xl text-gray-600"
onChange={(e) => {
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}
/>
) : (
<SelectBox
selectClass={"!text-xl text-gray-600"}
label=""
selectArray={filteredSigunData}
onSelectValue={(val) =>
onSelectValue(val.toString(), "sggCd", field)
}
className=""
value={sigunValue}
/>
),
},
{
name: "dmdCoNm",
label: "회사명",
component: (field) => (
<SdmSearchForm
rtnData={setRtnData}
label="수요처명"
initValue={updateCoNm}
dataList={dataList}
rtnValue={setRtnValue}
rtnText={setRtnText}
readOnly={readOnly}
{...field}
/>
),
},
{
name: "dmdInfoId",
label: "사업자등록번호",
component: (field) => (
<Input
placeholder="사업자등록번호를 입력하세요"
type="text"
className="!text-xl text-gray-600"
onChange={(e) => bznNoChange(e, "dmdInfoId", field)}
value={formatBusinessNumber(field.value ?? "")}
readOnly={!!updateList?.dmdInfoId}
/>
),
},
{
name: "bznmTotNo",
label: "",
component: (field) => (
<FormUi
label="종사업자등록번호"
onCheckboxChange={handleCheckboxChange}
isDisabled={isBznmTotNoDisabled}
>
},
{
name: "bznmTotNo",
label: "",
component: (field) => (
<FormUi
label="종사업자등록번호"
onCheckboxChange={handleCheckboxChange}
isDisabled={isBznmTotNoDisabled}
>
<Input
placeholder="여러사업장이 있을 경우 입력해주세요"
type="text"
className="!mt-5 !text-xl text-gray-600"
disabled={isBznmTotNoDisabled}
onChange={(e) => bznNoChange(e, "bznmTotNo", field)}
// {...field}
value={field.value ?? ""}
readOnly={!!updateList?.dmdInfoId}
/>
</FormUi>
),
},
{
name: "daddr",
label: "사업자주소",
component: (field) =>
openNetwork ? (
<div>
<div className="flex items-center gap-2">
{/* 우편번호 */}
<Input
type="text"
className="!text-xl text-gray-600 w-64"
readOnly={true}
value={zipId?.toString() ?? ""}
placeholder="우편번호"
/>
<Button className="px-4 py-2 rounded-md" onClick={() => setIsZipModalOpen(true)}>우편번호 검색</Button>
</div>
{/* 주소 */}
<Input
type="text"
className="!text-xl text-gray-600"
readOnly={true}
value={address ?? ""}
placeholder="주소"
/>
{/* 상세 주소 */}
<Input
placeholder="상세 주소를 입력하세요"
type="text"
className="!text-xl text-gray-600"
readOnly={readOnly}
onChange={(e) => {
const value = e.target.value;
setDetailedAddress(value);
}}
value={detailedAddress ?? ""}
/>
</div>
) : (
<Input
placeholder="주소를 입력하세요"
type="text"
className="!text-xl text-gray-600"
readOnly={readOnly}
{...field}
value={field.value ?? ""}
/>
),
},
{
name: "telno",
label: "전화번호",
component: (field) => {
const formattedValue = formatPhoneNumber(field.value ?? ""); // 초기 렌더링 시에도 포맷 적용
return (
<Input
placeholder="전화번호를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={formattedValue}
readOnly={readOnly}
onChange={(e) => {
const value = e.target.value.replace(/\D/g, ""); // 숫자만 저장
field.onChange(value);
}}
/>
);
},
},
{
name: "dmdAplyPswd",
label: "패스워드",
component: (field) => (
<Input
placeholder="여러사업장이 있을 경우 입력해주세요"
type="text"
className="!mt-5 !text-xl text-gray-600"
disabled={isBznmTotNoDisabled}
onChange={(e) => bznNoChange(e, "bznmTotNo", field)}
// {...field}
value={field.value ?? ""}
readOnly={!!updateList?.dmdInfoId}
/>
</FormUi>
),
},
{
name: "daddr",
label: "사업자주소",
component: (field) =>
openNetwork ? (
<SearchInputAddress
initValue={updateList?.daddr || ""}
readonly={readOnly ?? false}
setRtnData={(rtnData: addressType) => {
field.onChange(rtnData.roadAddrPart1);
}}
/>
) : (
<Input
placeholder="주소를 입력하세요"
type="text"
placeholder="패스워드를 입력하세요"
type="password"
className="!text-xl text-gray-600"
readOnly={readOnly}
{...field}
value={field.value ?? ""}
/>
),
},
{
name: "telno",
label: "전화번호",
component: (field) => {
const formattedValue = formatPhoneNumber(field.value ?? ""); // 초기 렌더링 시에도 포맷 적용
return (
<Input
placeholder="전화번호를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={formattedValue}
readOnly={readOnly}
onChange={(e) => {
const value = e.target.value.replace(/\D/g, ""); // 숫자만 저장
field.onChange(value);
}}
/>
);
},
},
{
name: "dmdAplyPswd",
label: "패스워드",
component: (field) => (
<Input
placeholder="패스워드를 입력하세요"
type="password"
className="!text-xl text-gray-600"
readOnly={readOnly}
{...field}
value={field.value ?? ""}
/>
),
},
{
name: "dmdAplyPswdConfirm",
label: "패스워드확인",
component: (field) => (
<Input
placeholder="패스워드를 입력하세요"
type="password"
className="!text-xl text-gray-600"
readOnly={readOnly}
{...field}
value={field.value ?? ""}
/>
),
},
];
{
name: "dmdAplyPswdConfirm",
label: "패스워드확인",
component: (field) => (
<Input
placeholder="패스워드를 입력하세요"
type="password"
className="!text-xl text-gray-600"
readOnly={readOnly}
{...field}
value={field.value ?? ""}
/>
),
},
];
return (
<>
@@ -538,6 +725,36 @@ export function SdmRegForm({
/>
);
})}
<UiModal
headerTitle="주소 검색"
headerTitleClass="text-2xl font-bold text-gray-900"
open={isZipModalOpen}
onOpenChange={setIsZipModalOpen}
bodyClass={`
${zipTotalElements > -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={
<div className="w-full flex justify-center items-center gap-4">
<Button className="rounded-md" size="lg" onClick={handleSelectAddress} disabled={!isSelectedZip}>선택</Button>
<Button className="rounded-md" size="lg" onClick={() => setIsZipModalOpen(false)}>취소</Button>
</div>
}
useDirectRender={true}
/>
</>
);
}
@@ -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자 이상 입력해주세요")
@@ -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<any> => {
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<sqlArray.length; i++){
regex = new RegExp( sqlArray[i] as string,"gi") ;
if (regex.test(search) ) {
alert("\"" + sqlArray[i]+"\"와(과) 같은 특정문자로 검색할 수 없습니다.");
search =search.replace(regex, "");
return false;
}
}
}
return true ;
let regex;
for (let i = 0; i < sqlArray.length; i++) {
regex = new RegExp(sqlArray[i] as string, "gi");
if (regex.test(search)) {
alert("\"" + sqlArray[i] + "\"와(과) 같은 특정문자로 검색할 수 없습니다.");
search = search.replace(regex, "");
return false;
}
}
}
return true;
@@ -1,129 +1,129 @@
import { Input } from "@common/components/ui";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { findAddress, searchFilter, type addressType } from "./findAddress";
// import { Input } from "@common/components/ui";
// import React, { useCallback, useEffect, useRef, useState } from "react";
// import { findAddress, searchFilter, type addressType } from "./findAddress";
type thisProps = {
initValue: string | undefined;
setRtnData: (rtnData: addressType) => 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<addressType[]>([]);
// /**
// *
// * @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<addressType[]>([]);
useEffect(() => {
if (
initValue !== textValue &&
initValue !== undefined &&
initValue !== null &&
initValue !== ""
) {
setTextValue(initValue);
}
}, [initValue]);
const wrapperRef = useRef<HTMLDivElement>(null);
// useEffect(() => {
// if (
// initValue !== textValue &&
// initValue !== undefined &&
// initValue !== null &&
// initValue !== ""
// ) {
// setTextValue(initValue);
// }
// }, [initValue]);
// const wrapperRef = useRef<HTMLDivElement>(null);
const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
// const handleInputChange = useCallback(
// (e: React.ChangeEvent<HTMLInputElement>) => {
// 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 (
<div ref={wrapperRef} className="relative">
<Input
type="text"
value={textValue}
onChange={handleInputChange}
/* onFocus={() => 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 && (
<div className="absolute left-0 right-0 z-50 mt-2 max-h-72 overflow-auto rounded-md border border-gray-300 bg-white shadow-lg">
<div className="max-h-72">
{searchList && searchList.length > 0 ? (
searchList.map((item: addressType, index: number) => (
<div
key={index}
className="w-full cursor-pointer overflow-auto px-5 py-3 text-left !text-xl hover:bg-gray-100"
onClick={() => handleSuggestionClick(item.roadAddrPart1)}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
handleSuggestionClick(item.roadAddrPart1);
}
}}
>
{item.zipNo}&nbsp;
<span className="float-right">{item.roadAddrPart1}</span>
</div>
))
) : (
<div className="w-full cursor-pointer overflow-auto px-5 py-3 !text-xl hover:bg-gray-100">
검색 결과가 없습니다.
</div>
)}
</div>
</div>
)}
</div>
);
}
// return (
// <div ref={wrapperRef} className="relative">
// <Input
// type="text"
// value={textValue}
// onChange={handleInputChange}
// /* onFocus={() => 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 && (
// <div className="absolute left-0 right-0 z-50 mt-2 max-h-72 overflow-auto rounded-md border border-gray-300 bg-white shadow-lg">
// <div className="max-h-72">
// {searchList && searchList.length > 0 ? (
// searchList.map((item: addressType, index: number) => (
// <div
// key={index}
// className="w-full cursor-pointer overflow-auto px-5 py-3 text-left !text-xl hover:bg-gray-100"
// onClick={() => handleSuggestionClick(item.roadAddrPart1)}
// role="button"
// tabIndex={0}
// onKeyDown={(e) => {
// if (e.key === "Enter" || e.key === " ") {
// handleSuggestionClick(item.roadAddrPart1);
// }
// }}
// >
// {item.zipNo}&nbsp;
// <span className="float-right">{item.roadAddrPart1}</span>
// </div>
// ))
// ) : (
// <div className="w-full cursor-pointer overflow-auto px-5 py-3 !text-xl hover:bg-gray-100">
// 검색 결과가 없습니다.
// </div>
// )}
// </div>
// </div>
// )}
// </div>
// );
// }
@@ -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<T> {
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<Zip>;