+
{status === "checking" &&
접속 IP 확인 중...
}
{status === "redirect-internal" &&
내부망으로 이동합니다...
}
{status === "redirect-root" &&
외부에서 접속하여 루트로 이동합니다...
}
{status === "error-fallback" && (
- 문제가 발생하여 루트로 이동합니다. 이동하지 않으면 여기를 클릭하세요.
+ 문제가 발생하여 루트로 이동합니다. 이동하지 않으면 여기를 클릭하세요.
)}
diff --git a/apps/wr/app/store/common/auth/index.tsx b/apps/wr/app/store/common/auth/index.tsx
index 7b647d5e7..209b1fb8a 100644
--- a/apps/wr/app/store/common/auth/index.tsx
+++ b/apps/wr/app/store/common/auth/index.tsx
@@ -10,7 +10,7 @@ export type authState = {
regAuthrtYn: string;
mdfcnAuthrtYn: string;
delAuthrtYn: string;
- laeSeCd: string;
+ laeSeCd: string; // 지자체구분코드
myUUID?: string;
};
};
@@ -72,6 +72,7 @@ export const authActionsExport: (set: any, get: any) => authActions = (
return;
}
const infoMe = await getUserInfo();
+ console.log(infoMe);
const isLaeSeCd =
infoMe.laeSeCd.slice(0, 2) === "G3" ? infoMe.laeSeCd.slice(2) : "0";
diff --git a/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx b/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx
index 8ed65ee7d..c201f5d28 100644
--- a/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx
+++ b/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx
@@ -11,14 +11,14 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [
},
{
id: "bizBgngDate",
- title: "사업시작일",
+ title: "사업시작년",
visible: true,
row: 0,
rowSpan: 2,
},
{
id: "bizEndDate",
- title: "사업종료일",
+ title: "사업종료년",
visible: true,
row: 0,
rowSpan: 2,
diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx
index b1e0daf82..d595a0624 100644
--- a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx
+++ b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx
@@ -118,7 +118,7 @@ const hasuListFetch = async (
if (searchTxt) {
url = url + `/${searchTxt}`;
}
- const rainRes = await postFectch(url, data, fetchType.COMMON);
+ const rainRes = await postFectch(url, data, fetchType.COMMON); // fo
const hasuList = rainRes.data.map((item: any) => {
return {
@@ -176,7 +176,7 @@ export const pubFacActionsExport: (set: any) => pubFacActions = (set: any) => {
// 하수처리시설을 조회한다(운영실태)
pubHasuInit: async (page: string, sggOrCity: string) => {
try {
- const pbnstHasuRes = await hasuListFetch(page, sggOrCity);
+ const pbnstHasuRes = await hasuListFetch(page, sggOrCity); // /prcsFclt/ruseInfo
return set((state: pubFacState) => ({
...state,
pbnst: {
diff --git a/apps/wr/components/lib/func/mapUtil.ts b/apps/wr/components/lib/func/mapUtil.ts
index a39bf17d7..c49dea69f 100644
--- a/apps/wr/components/lib/func/mapUtil.ts
+++ b/apps/wr/components/lib/func/mapUtil.ts
@@ -1,22 +1,22 @@
import { formatCommaNumber, formatPhoneNumber } from "./commom";
const wrLayerList = [
- { text: "tb_il_swage_prcs_fclt", val: "bscFctCd" },
- { text: "tb_wr_bdst_ruse_fclt", val: "pbnstRuseFcltId" },
- { text: "tb_wr_rnwtr_utztn_fclt", val: "rainyObj" },
- { text: "tb_wr_rswtr_fclt", val: "rswtrObj" },
- { text: "tb_wr_htdfl_fclt", val: "htdflObj" },
- { text: "tb_wr_ruse_fclt_dmd_co", val: "dmdInfoId" },
- { text: "tb_wr_tmpr_dmd_co", val: "fctryMngSn" },
+ { text: "tb_il_swage_prcs_fclt", val: "bscFctCd" }, //하수처리시설
+ { text: "tb_wr_bdst_ruse_fclt", val: "pbnstRuseFcltId" }, //하수재이용시설
+ { text: "tb_wr_rnwtr_utztn_fclt", val: "rainyObj" }, //빗물이용시설
+ { text: "tb_wr_rswtr_fclt", val: "rswtrObj" }, //중수도시설
+ { text: "tb_wr_htdfl_fclt", val: "htdflObj" }, //온배수시설
+ { text: "tb_wr_ruse_fclt_dmd_co", val: "dmdInfoId" }, //수요처
+ { text: "tb_wr_tmpr_dmd_co", val: "fctryMngSn" }, //잠재수요처
];
const wrFeaturePK = [
- { text: "bscFctCd", val: "tb_il_swage_prcs_fclt" },
- { text: "searchPk", val: "tb_wr_bdst_ruse_fclt" },
- { text: "bdstInfoSn", val: "tb_wr_rnwtr_utztn_fclt" },
- { text: "bdstInfoSn", val: "tb_wr_rswtr_fclt" },
- { text: "bdstInfoSn", val: "tb_wr_htdfl_fclt" },
- { text: "dmdInfoId", val: "tb_wr_ruse_fclt_dmd_co" },
- { text: "fctryMngSn", val: "tb_wr_tmpr_dmd_co" },
+ { text: "bscFctCd", val: "tb_il_swage_prcs_fclt" }, //하수처리시설
+ { text: "searchPk", val: "tb_wr_bdst_ruse_fclt" }, //하수재이용시설
+ { text: "bdstInfoSn", val: "tb_wr_rnwtr_utztn_fclt" }, //빗물이용시설
+ { text: "bdstInfoSn", val: "tb_wr_rswtr_fclt" }, //중수도시설
+ { text: "bdstInfoSn", val: "tb_wr_htdfl_fclt" }, //온배수시설
+ { text: "dmdInfoId", val: "tb_wr_ruse_fclt_dmd_co" }, //수요처
+ { text: "fctryMngSn", val: "tb_wr_tmpr_dmd_co" }, //잠재수요처
];
// { text: "하수처리시설", val: "tb_il_swage_prcs_fclt" },
@@ -29,18 +29,18 @@ const wrFeaturePK = [
export const getFeatureCd = (featurePropertise: any, layerNm: string) => {
const featureCd =
- layerNm === "tb_wr_bdst_ruse_fclt"
+ layerNm === "tb_wr_bdst_ruse_fclt" //하수재이용시설
? featurePropertise?.swtrmt_fclt_cd
: [
- "tb_wr_rswtr_fclt",
- "tb_wr_htdfl_fclt",
- "tb_wr_rnwtr_utztn_fclt",
- "tb_wr_tmpr_dmd_co",
- ].includes(layerNm)
+ "tb_wr_rswtr_fclt", // 중수도시설
+ "tb_wr_htdfl_fclt", // 온배수시설
+ "tb_wr_rnwtr_utztn_fclt", // 빗물이용시설
+ "tb_wr_tmpr_dmd_co", // 잠재수요처
+ ].includes(layerNm)
? featurePropertise?.bdst_nm
- : layerNm === "tb_il_swage_prcs_fclt"
+ : layerNm === "tb_il_swage_prcs_fclt" //하수처리시설
? featurePropertise?.swtrmt_fclt_cd
- : layerNm === "tb_wr_ruse_fclt_dmd_co"
+ : layerNm === "tb_wr_ruse_fclt_dmd_co" //수요처
? featurePropertise?.bdst_info_sn
: null;
@@ -71,9 +71,9 @@ export const getSelectedLayers = (isFeature: any, gisMaps: any) => {
let result = null;
const layers = map
? map
- .getLayers()
- .getArray()
- .filter((l: any) => l.getVisible())
+ .getLayers()
+ .getArray()
+ .filter((l: any) => l.getVisible())
: []; // gisMap이 null인지 확인 후 레이어 가져오기
let featureLayer: { get: (key: string) => any } = { get: () => null };
@@ -119,12 +119,12 @@ export const getClickFeatureId = (data: any) => {
} else if (layerName == "수요처") {
//수요처
featureId = `${layerName}.${data.dmdInfoId.replaceAll("-", "")}`;
- }else if (layerName == "잠재수요처") {
+ } else if (layerName == "잠재수요처") {
//잠재수요처
featureId = data.id_
- } else {
-
+ } else {
+
featureId = data.id_
}
@@ -160,7 +160,7 @@ export function featurePopUpOpen(
);
}
-export const getOverLayObj = (fclType: string, data: any,code?:any) => {
+export const getOverLayObj = (fclType: string, data: any, code?: any) => {
let newObj = {};
let title = "";
@@ -169,11 +169,11 @@ export const getOverLayObj = (fclType: string, data: any,code?:any) => {
if (fclType === "하수재이용시설") {
newObj = {
- "시설용량(톤/일)": formatCommaNumber(data?.fcltCpct) ??'확인불가',
- "재이용률(%)": (data?.fcltCpct / data?.bscFctCpc)||0,
+ "시설용량(톤/일)": formatCommaNumber(data?.fcltCpct) ?? '확인불가',
+ "재이용률(%)": (data?.fcltCpct / data?.bscFctCpc) || 0,
"장내용수(m³/년)": formatCommaNumber(data?.inUsage) ?? '확인불가',
- "장외용수(m3/년)":formatCommaNumber(data?.outUsage) ?? '확인불가',
- // 수요처: data?.reuseBuilding[0]?.rainyObj.pntfColArea ?? '확인불가',
+ "장외용수(m3/년)": formatCommaNumber(data?.outUsage) ?? '확인불가',
+ // 수요처: data?.reuseBuilding[0]?.rainyObj.pntfColArea ?? '확인불가',
};
title = data?.bdstNm ?? '확인불가';
} else if (fclType === "하수처리시설") {
@@ -184,9 +184,9 @@ export const getOverLayObj = (fclType: string, data: any,code?:any) => {
"재이용시설 등록여부": data?.ruse ?? '확인불가',
};
title = data?.bscFctNm ?? '확인불가';
- } else if (fclType === "빗물이용시설" ) {
+ } else if (fclType === "빗물이용시설") {
newObj = {
- 건물용도: code?.bdstUsgCd.find((item) => item.val=== String(data?.reuseBuilding[0].bdstUsgCd) )?.text ?? '확인불가',
+ 건물용도: code?.bdstUsgCd.find((item) => item.val === String(data?.reuseBuilding[0].bdstUsgCd))?.text ?? '확인불가',
설치완료일: data?.reuseBuilding[0]?.instlCmptnDt ?? '확인불가',
처리시설유무: data?.reuseBuilding[0]?.rainyObj.filtrTrtmfctYn ?? '확인불가',
"처리시설용량(m³/일)": formatCommaNumber(data?.reuseBuilding[0]?.rainyObj.filtrTrtmfctCpct) ?? '확인불가',
@@ -195,25 +195,25 @@ export const getOverLayObj = (fclType: string, data: any,code?:any) => {
title = data?.bdstNm ?? '확인불가';
} else if (fclType === "중수도시설") {
newObj = {
- 건물용도: code?.bdstUsgCd.find((item) => item.val===String(data?.reuseBuilding[0].bdstUsgCd) )?.text ?? '확인불가',
+ 건물용도: code?.bdstUsgCd.find((item) => item.val === String(data?.reuseBuilding[0].bdstUsgCd))?.text ?? '확인불가',
설치완료일: data?.reuseBuilding[0]?.instlCmptnDt ?? '확인불가',
- "주요 처리공정": data?.reuseBuilding[0]?.rswtrObj.filtrTrtmfctProcs ?? '확인불가',
- 원수종류: code?.orgwtKndCd.find((item) => item.val === data?.reuseBuilding[0]?.rswtrObj.orgwtKndCd)?.text ?? '확인불가',
+ "주요 처리공정": data?.reuseBuilding[0]?.rswtrObj.filtrTrtmfctProcs ?? '확인불가',
+ 원수종류: code?.orgwtKndCd.find((item) => item.val === data?.reuseBuilding[0]?.rswtrObj.orgwtKndCd)?.text ?? '확인불가',
//이용용도: isCode.filtrTrtmfctUsgCd data?.reuseBuilding[0]?.rswtrObj.altUswtrKndCd ?? '확인불가',
};
title = data?.bdstNm ?? '확인불가';
} else if (fclType === "온배수시설") {
newObj = {
- '발전유형': data?.reuseBuilding[0]?.htdflObj.etgtType??'',
- '설비용량': data?.reuseBuilding[0]?.htdflObj.fclCpct??'',
- '담수화 여부': data?.reuseBuilding[0]?.htdflObj.plwtYn??'',
+ '발전유형': data?.reuseBuilding[0]?.htdflObj.etgtType ?? '',
+ '설비용량': data?.reuseBuilding[0]?.htdflObj.fclCpct ?? '',
+ '담수화 여부': data?.reuseBuilding[0]?.htdflObj.plwtYn ?? '',
};
title = data?.bdstNm ?? '확인불가';
} else if (fclType === "수요처") {
newObj = {
- '사업자등록번호': data?.dmdInfoId??'',
- '주소': data?.daddr??'',
- '전화번호': formatPhoneNumber(data?.telno)??'',
+ '사업자등록번호': data?.dmdInfoId ?? '',
+ '주소': data?.daddr ?? '',
+ '전화번호': formatPhoneNumber(data?.telno) ?? '',
};
title = data?.dmdCoNm ?? '확인불가';
diff --git a/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm.tsx b/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm.tsx
index 6fd110297..d4e7fe940 100644
--- a/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm.tsx
+++ b/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm.tsx
@@ -35,10 +35,11 @@ export type FilesType = {
file: File;
};
+const diagramFileTypes = ".pdf,.jpg,.jpeg,.png,.bmp";
/**
* 재이용시설 등록 - 하수재이용 시설등록
- * 재이용시설 등록 - 하수재이용 시설현황
+ * 재이용시설 등록 - 하수재이용 시설현황
* - layout.tsx에 의해 공통 레이아웃이 적용됨
* - with-layout 적용
* - 하수처리시설에 하수재이용 시설 시설 정보를 등록한다.
@@ -459,7 +460,7 @@ export function PbnstMngForm({
setFiles={(files: File[] | null) => {
if (files) fileHandler(files, "relation");
}}
- fileTypes=".pdf"
+ fileTypes={diagramFileTypes}
onFileClick={(index) => {
fileDownLoad(
relationfiles?.find(
@@ -497,7 +498,7 @@ export function PbnstMngForm({
setFiles={(files: File[] | null) => {
if (files) fileHandler(files, "plan");
}}
- fileTypes=".pdf"
+ fileTypes={diagramFileTypes}
onFileClick={(index) => {
fileDownLoad(
planFiles?.find(
diff --git a/packages/common/business_components/src/lib/utils.ts b/packages/common/business_components/src/lib/utils.ts
index a5ef19350..1a87df14d 100644
--- a/packages/common/business_components/src/lib/utils.ts
+++ b/packages/common/business_components/src/lib/utils.ts
@@ -2,5 +2,5 @@ import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs));
+ return twMerge(clsx(inputs)); //clsx 결과 (단순 합치기):twMerge 결과 (Tailwind 충돌 제거)
}
diff --git a/packages/common/business_components/src/ui/excel/index.tsx b/packages/common/business_components/src/ui/excel/index.tsx
index aedb32256..048a0cc42 100644
--- a/packages/common/business_components/src/ui/excel/index.tsx
+++ b/packages/common/business_components/src/ui/excel/index.tsx
@@ -6,6 +6,7 @@ type ExcelDownloadProps = {
fileName: string;
sheetNameArray: string[];
headerRange?: string;
+ headers?: readonly string[]|undefined;
decimalArray0? : number[];
decimalArray1? : number[];
decimalArray2? : number[];
@@ -19,6 +20,15 @@ export const downloadExcel = ({ data, fileName = new Date().getTime().toString()
excelStyle(XLSX.write(workbook, { bookType: "xlsx", type: "buffer" }), headerRange, fileName);
};
+export const downloadExcelHeaders = ({ data, fileName = new Date().getTime().toString() + '.xlsx', sheetNameArray = ["Sheet1"], headerRange='A1:V3', headers}: ExcelDownloadProps) => {
+ const dataArray = Array.isArray(data) ? data : [data];
+ // headers가 제공되면 열 순서를 강제함
+ const worksheet = XLSX.utils.json_to_sheet(dataArray, headers ? { header: [...headers] } : undefined);
+ const workbook = XLSX.utils.book_new();
+ XLSX.utils.book_append_sheet(workbook, worksheet, sheetNameArray[0]);
+ excelStyle(XLSX.write(workbook, { bookType: "xlsx", type: "buffer" }), headerRange, fileName);
+};
+
export const downloadExcelFormat = ({ data, fileName = new Date().getTime().toString() + '.xlsx', sheetNameArray = ["Sheet1"], headerRange='A1:V3', decimalArray0=[], decimalArray1=[], decimalArray2=[], decimalArray3=[]}: ExcelDownloadProps) => {
const worksheet = XLSX.utils.json_to_sheet(Array.isArray(data) ? data : [data]);
const workbook = XLSX.utils.book_new();
diff --git a/packages/common/business_components/src/ui/file-upload/lib/file_upload.tsx b/packages/common/business_components/src/ui/file-upload/lib/file_upload.tsx
index 42662ab1d..030ddda95 100644
--- a/packages/common/business_components/src/ui/file-upload/lib/file_upload.tsx
+++ b/packages/common/business_components/src/ui/file-upload/lib/file_upload.tsx
@@ -93,6 +93,7 @@ export const FileUploader = forwardRef<
".jpeg": "image/jpeg",
".png": "image/png",
".gif": "image/gif",
+ ".bmp": "image/bmp",
".doc": "application/msword",
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".xls": "application/vnd.ms-excel",
diff --git a/packages/common/business_components/src/ui/index.ts b/packages/common/business_components/src/ui/index.ts
index db1132b58..ae419bd83 100644
--- a/packages/common/business_components/src/ui/index.ts
+++ b/packages/common/business_components/src/ui/index.ts
@@ -15,6 +15,7 @@ export * from "./search_form_right";
export * from "./search_input";
export * from "./search_input_select";
// export * from "./search_input_address";
+export * from "./search_address";
export * from "./select";
export * from "./tab";
export * from "./table";
@@ -24,4 +25,4 @@ export * from "./sdmInsertForm";
export * from "./popover_ui";
export * from "./date_month_picker";
export * from "./date_picker_range"
-export * from "./map/GisOverlay";
\ No newline at end of file
+export * from "./map/GisOverlay";
diff --git a/packages/common/business_components/src/ui/sdmInsertForm/form.tsx b/packages/common/business_components/src/ui/sdmInsertForm/form.tsx
index 6f0cbff2b..b2499ab43 100644
--- a/packages/common/business_components/src/ui/sdmInsertForm/form.tsx
+++ b/packages/common/business_components/src/ui/sdmInsertForm/form.tsx
@@ -1,4 +1,4 @@
-import { Button, Input, Label } from "@common/components";
+import { Button, Input } from "@common/components";
import { AsYouType } from "libphonenumber-js";
import React, { useCallback, useEffect, useState } from "react";
import type { ControllerRenderProps, UseFormReturn } from "react-hook-form";
@@ -6,10 +6,7 @@ import { FormField } from "../form";
import { FormUi } from "../form_ui";
import { SelectBox } from "../select";
import { SdmSearchForm } from "./sdmNameBox";
-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";
+import { SearchAddressModal } from "../search_address/SearchAddressModal";
// 수요처 정보
export type DemandCompType = {
@@ -91,7 +88,6 @@ export function SdmRegForm({
readOnly,
openNetwork,
}: SdmRegFormProps) {
- const PAGE_SIZE = 10;
const [sidoValue, setSidoValue] = useState
("");
const [sigunValue, setSigunValue] = useState("");
const [filteredSigunData, setFilteredSigunData] = useState([
@@ -104,30 +100,6 @@ export function SdmRegForm({
const [zipId, setZipId] = useState(undefined);
const [address, setAddress] = useState("");
const [detailedAddress, setDetailedAddress] = useState("");
- const [curPage, setCurPage] = useState(1);
- // 우편번호 검색 관련 상태
- const [zipSearchResults, setZipSearchResults] = useState([]);
- const [zipTotalElements, setZipTotalElements] = useState(-1);
- const [searchQuery, setSearchQuery] = useState({
- roadNm: "",
- bldgNoMno: null,
- bldgNoSno: null,
- page: curPage,
- size: PAGE_SIZE
- });
- const [isResetPage, setIsResetPage] = useState(false);
- const [isResetSelection, setIsResetSelection] = useState(false);
- const [selectedRowIndex, setSelectedRowIndex] = useState(undefined);
- const [selectedZip, setSelectedZip] = useState();
- const [isSelectedZip, setIsSelectedZip] = useState(false);
-
- const handleCellClick = (index: any, headId: any) => {
- console.log('index', index);
- console.log('headId', headId);
- setSelectedZip(zipSearchResults[index]);
- setIsSelectedZip(true);
- console.log('selectedZip', zipSearchResults[index]);
- };
const formatPhoneNumber = (value: string) => {
const input = value;
@@ -293,17 +265,6 @@ 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);
@@ -325,16 +286,6 @@ 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에 설정
@@ -344,105 +295,6 @@ export function SdmRegForm({
}
}, [address, detailedAddress, form]);
- const renderBody = () => (
- <>
-
-
- {/* 검색 결과 부분 */}
- {zipTotalElements > 0 && (
-
- )}
- {zipTotalElements === 0 && (
-
-
- 조회된 데이터가 없습니다.
-
- )}
- >
- );
-
const formFields: Array<{
name: keyof FormSchema;
@@ -725,35 +577,14 @@ export function SdmRegForm({
/>
);
})}
- -1 ? 'h-[80vh]' : 'h-[30vh]'}
- w-[30vw]
- min-w-[500px]
- max-w-[90vw]
- max-h-[85vh]
- overflow-y-auto
- !fixed
- !top-[50%]
- !left-[50%]
- !-translate-x-1/2
- !-translate-y-1/2
- z-[101]
- flex
- flex-col
- `}
- body={renderBody()}
- footerContent={
-
-
-
-
- }
- useDirectRender={true}
+ onSelect={(zip) => {
+ setZipId(zip.zipId);
+ setAddress(zip.zipAddr || "");
+ setDetailedAddress("");
+ }}
/>
>
);
diff --git a/packages/common/business_components/src/ui/search_address/SearchAddressModal.tsx b/packages/common/business_components/src/ui/search_address/SearchAddressModal.tsx
new file mode 100644
index 000000000..699d3cc50
--- /dev/null
+++ b/packages/common/business_components/src/ui/search_address/SearchAddressModal.tsx
@@ -0,0 +1,221 @@
+import { Button, Input, Label, cn } from "@common/components";
+import React, { useState, useEffect } from "react";
+import { UiModal } from "../dialog_ui";
+import { UiTable } from "../table";
+import type { Zip, ZipSearchParams } from "./zipService";
+import { findAddress } from "./findAddress";
+
+interface SearchAddressModalProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ onSelect: (zip: Zip) => void;
+}
+
+export function SearchAddressModal({ open, onOpenChange, onSelect }: SearchAddressModalProps) {
+ const PAGE_SIZE = 10;
+ const [curPage, setCurPage] = useState(1);
+ const [zipSearchResults, setZipSearchResults] = useState([]);
+ const [zipTotalElements, setZipTotalElements] = useState(-1);
+ const [searchQuery, setSearchQuery] = useState({
+ roadNm: "",
+ bldgNoMno: null,
+ bldgNoSno: null,
+ page: 1,
+ size: PAGE_SIZE
+ });
+ const [isResetPage, setIsResetPage] = useState(false);
+ const [isResetSelection] = useState(false);
+ const [selectedRowIndex, setSelectedRowIndex] = useState(undefined);
+ const [selectedZip, setSelectedZip] = useState();
+ const [isSelectedZip, setIsSelectedZip] = useState(false);
+
+ // Initialize/Reset state when modal opens
+ useEffect(() => {
+ if (open) {
+ setCurPage(1);
+ setZipSearchResults([]);
+ setZipTotalElements(-1);
+ setSearchQuery({
+ roadNm: "",
+ bldgNoMno: null,
+ bldgNoSno: null,
+ page: 0,
+ size: PAGE_SIZE
+ });
+ setSelectedZip(undefined);
+ setIsSelectedZip(false);
+ setSelectedRowIndex(undefined);
+ setIsResetPage(true);
+ setTimeout(() => setIsResetPage(false), 100);
+ }
+ }, [open]);
+
+ // Pagination Effect
+ useEffect(() => {
+ if (!open || zipTotalElements === -1) return;
+
+ // Correctly update query for pagination without mutation
+ const nextPage = curPage - 1;
+ if (searchQuery.page !== nextPage) {
+ const newQuery = { ...searchQuery, page: nextPage };
+ setSearchQuery(newQuery);
+
+ const fetchAddress = async () => {
+ const result = await findAddress(newQuery);
+ setZipSearchResults(result?.content || []);
+ };
+ fetchAddress();
+ }
+ }, [curPage, open, zipTotalElements]);
+
+ const handleCellClick = (index: any) => {
+ setSelectedZip(zipSearchResults[index]);
+ setIsSelectedZip(true);
+ setSelectedRowIndex(index);
+ };
+
+ const handleSearch = async (e: React.FormEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const query = { ...searchQuery, page: 0 };
+ setSearchQuery(query);
+ setCurPage(1);
+ setIsResetPage(true);
+
+ const result = await findAddress(query);
+ setZipSearchResults(result?.content || []);
+ setZipTotalElements(result?.totalElements || 0);
+
+ setTimeout(() => setIsResetPage(false), 100);
+ };
+
+ const handleConfirm = () => {
+ if (selectedZip) {
+ onSelect(selectedZip);
+ onOpenChange(false);
+ }
+ };
+
+ return (
+ -1 ? 'h-[80vh]' : 'h-[30vh]'}
+ w-[30vw]
+ min-w-[500px]
+ max-w-[90vw]
+ min-h-[235px]
+ overflow-y-auto
+ !fixed
+ !top-[50%]
+ !left-[50%]
+ !-translate-x-1/2
+ !-translate-y-1/2
+ z-[101]
+ flex
+ flex-col
+ `}
+ body={
+ <>
+
+
+ {zipTotalElements > 0 && (
+
+ )}
+ {zipTotalElements === 0 && (
+
+
+ 조회된 데이터가 없습니다.
+
+ )}
+ >
+ }
+ footerContent={
+
+
+
+
+ }
+ useDirectRender={true}
+ />
+ );
+}
diff --git a/packages/common/business_components/src/ui/search_address/findAddress.ts b/packages/common/business_components/src/ui/search_address/findAddress.ts
new file mode 100644
index 000000000..5e5ba4360
--- /dev/null
+++ b/packages/common/business_components/src/ui/search_address/findAddress.ts
@@ -0,0 +1,118 @@
+
+// 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 = {
+ results: {
+ common: {
+ errorMessage: string;
+ countPerPage: string;
+ totalCount: string;
+ errorCode: string;
+ currentPage: string;
+ };
+ juso: addressType[];
+ };
+}
+
+export type addressType = {
+ detBdNmList: string;
+ engAddr: string;
+ rn: string;
+ emdNm: string;
+ zipNo: string;
+ roadAddrPart2: string;
+ emdNo: string;
+ sggNm: string;
+ jibunAddr: string;
+ siNm: string;
+ roadAddrPart1: string;
+ bdNm: string;
+ admCd: string;
+ udrtYn: string;
+ lnbrMnnm: string;
+ roadAddr: string;
+ lnbrSlno: string;
+ buldMnnm: string;
+ bdKdcd: string;
+ liNm: string;
+ rnMgtSn: string;
+ mtYn: string;
+ bdMgtSn: string;
+ buldSlno: string;
+}
+
+/**
+ * 주소에 따른 검색
+ * @param search : 검색어
+ * @returns addressType[] 주소 배열
+ */
+export const findAddress = async (searchQuery: ZipSearchParams): Promise => {
+ if (!searchQuery) return [];
+
+ // 쿼리 파라미터 생성
+ const params = new URLSearchParams();
+ if (searchQuery.roadNm) params.append('roadNm', searchQuery.roadNm);
+ if (searchQuery.bldgNoMno != null) params.append('bldgNoMno', searchQuery.bldgNoMno.toString());
+ if (searchQuery.bldgNoSno != null) params.append('bldgNoSno', searchQuery.bldgNoSno.toString());
+ if (searchQuery.page != null) params.append('page', searchQuery.page.toString());
+ if (searchQuery.size != null) params.append('size', searchQuery.size.toString());
+
+ const url = `/cms-service/api/v1/zips?${params.toString()}`;
+ try {
+ const response = await fetch(url);
+ if (!response.ok) throw new Error('Network response was not ok');
+
+ const data = await response.json();
+ if (data) return data
+ else return [];
+ } catch (error) {
+ console.error('Error fetching address data:', error);
+ return [];
+ }
+
+}
+
+
+
+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"
+ ]
+
+ 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;
+
+
+
+}
diff --git a/packages/common/business_components/src/ui/search_address/index.ts b/packages/common/business_components/src/ui/search_address/index.ts
new file mode 100644
index 000000000..b790bb492
--- /dev/null
+++ b/packages/common/business_components/src/ui/search_address/index.ts
@@ -0,0 +1,3 @@
+export * from "./SearchAddressModal";
+export * from "./findAddress";
+export * from "./zipService";
diff --git a/packages/common/business_components/src/ui/search_address/zipService.ts b/packages/common/business_components/src/ui/search_address/zipService.ts
new file mode 100644
index 000000000..03e4a8ccc
--- /dev/null
+++ b/packages/common/business_components/src/ui/search_address/zipService.ts
@@ -0,0 +1,66 @@
+interface SortConfig {
+ empty: boolean;
+ sorted: boolean;
+ unsorted: boolean;
+}
+
+interface PageableConfig {
+ sort: SortConfig;
+ offset: number;
+ pageNumber: number;
+ pageSize: number;
+ paged: boolean;
+ unpaged: boolean;
+}
+
+export interface PageableList {
+ content: T[];
+ pageable: PageableConfig;
+ sort: SortConfig;
+ totalPages: number;
+ totalElements: number;
+ number: number;
+ numberOfElements: number;
+ size: number;
+ first: boolean;
+ last: boolean;
+ empty: boolean;
+}
+
+export interface PageableParams {
+ page?: number;
+ size?: number;
+ sort?: string;
+}
+
+
+export interface ZipSearchParams extends PageableParams {
+ zoneNo?: string; // 구역번호
+ ctpvNm?: string; // 시도명
+ sggNm?: string; // 시군구명
+ emdNm?: string; // 읍면동명
+ roadNm?: string; // 도로명
+ stdgNm?: string; // 법정동명
+ dongNm?: string; // 행정동명
+ bldgNoMno?: number | null; // 빌딩본번
+ bldgNoSno?: number | null; // 빌딩딩부번
+ lotnoMno?: number | null; // 지번본번
+ lotnoSno?: number | null; // 지번부번
+}
+
+export interface Zip {
+ zipId: number;
+ zoneNo: string;
+ ctpvNm: string;
+ sggNm: string;
+ emdNm: string;
+ roadNm: string;
+ bldgNoMno: number;
+ bldgNoSno: number;
+ lotnoMno: number;
+ lotnoSno: number;
+ lotno: string;
+ zipAddr: string;
+}
+
+export type ZipList = PageableList;
diff --git a/packages/common/business_components/src/ui/table/index.tsx b/packages/common/business_components/src/ui/table/index.tsx
index a0b421db8..1a0fc57c0 100644
--- a/packages/common/business_components/src/ui/table/index.tsx
+++ b/packages/common/business_components/src/ui/table/index.tsx
@@ -119,7 +119,6 @@ export function UiTable({
};
const leafCols = getLeafCols(headlist);
-
return (
<>
{headName ? (
@@ -263,7 +262,11 @@ export function UiTable({
item.visible != false).length -
- headlist.filter((item) => item.visible != false && item.colSpan).length
+ headlist.filter(
+ (item) =>
+ item.visible != false &&
+ (item.colSpan || ["A", "B", "C"].includes(item.id)),
+ ).length
}
className="h-24 text-center text-gray-500"
>
diff --git a/packages/common/components/src/ui/header/index.tsx b/packages/common/components/src/ui/header/index.tsx
index d1675d6f1..b9f7fa628 100644
--- a/packages/common/components/src/ui/header/index.tsx
+++ b/packages/common/components/src/ui/header/index.tsx
@@ -274,10 +274,10 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) {
{mounted ? (
-
{
if (logoRef.current) {
@@ -288,7 +288,7 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) {
) : (
)}
- 0 ? `${(logoWidth - 120) / (siteInfo?.siteNm?.length || 1)}px` : '0.05em'