Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend
This commit is contained in:
@@ -58,7 +58,7 @@ export default function MyForm() {
|
||||
dataList={demandCompList}
|
||||
rtnValue={callback}
|
||||
rtnInputData={handleInsertSdmData}
|
||||
openNetwork={false}
|
||||
openNetwork={true}
|
||||
/>
|
||||
<Toaster position="top-center" />
|
||||
</div>
|
||||
|
||||
@@ -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
@@ -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};
|
||||
}
|
||||
|
||||
/**====================================
|
||||
|
||||
@@ -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>
|
||||
) : (
|
||||
|
||||
@@ -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}
|
||||
<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}
|
||||
// <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>;
|
||||
Reference in New Issue
Block a user