diff --git a/packages/common/business_components/package.json b/packages/common/business_components/package.json
index 1f02a708e..a338556dc 100644
--- a/packages/common/business_components/package.json
+++ b/packages/common/business_components/package.json
@@ -31,6 +31,7 @@
"@radix-ui/react-scroll-area": "^1.2.3",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-slot": "^1.1.2",
+ "axios": "1.8.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
diff --git a/packages/common/business_components/src/ui/map/GisTab.js b/packages/common/business_components/src/ui/map/GisTab.js
index baf268879..b63b774bb 100644
--- a/packages/common/business_components/src/ui/map/GisTab.js
+++ b/packages/common/business_components/src/ui/map/GisTab.js
@@ -2,10 +2,13 @@ import React, { useState } from 'react';
import { Tabs, TabsList, TabsTrigger } from '@common/components/ui/tabs';
import GisTree from './GisTree';
import GisSearch from './GisSearch';
-import { ChevronLeft, ChevronRight } from 'lucide-react';
+import { ChevronLeft, ChevronRight, Search } from 'lucide-react';
+import SearchModal from './SearchModal';
+
function GisTab({ visible, setVisible, isSearch }) {
const [activeTab, setActiveTab] = useState('layers');
+ const [isSearchModalOpen, setIsSearchModalOpen] = useState(false);
return (
<>
@@ -17,21 +20,29 @@ function GisTab({ visible, setVisible, isSearch }) {
레이어
{isSearch && (
- 검색
+
+ //
+ //
+ //
)}
{/* GisTree는 항상 마운트되며, 탭 상태에 따라 보여주기만 함 */}
{/* GisSearch도 동일하게 처리 */}
- {isSearch && (
+ {/* {isSearch && (
- )}
+ )} */}
+
+
+ {/* 🔍 검색 모달 */}
+
{/* 접기 버튼 */}
diff --git a/packages/common/business_components/src/ui/map/SearchModal.tsx b/packages/common/business_components/src/ui/map/SearchModal.tsx
new file mode 100644
index 000000000..434ca99c8
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/SearchModal.tsx
@@ -0,0 +1,377 @@
+import React from 'react';
+import axios from 'axios';
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogClose,
+} from '@common/components/ui/dialog';
+import { Button } from '@common/components';
+import { UiTable } from '../../ui';
+
+
+const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`;
+
+const TABS = [
+ { key: 'basic', label: '기본정보' },
+ { key: 'operation', label: '운영정보' },
+ { key: 'report', label: '하수도정비기본계획보고서' },
+];
+
+const WORK_TYPES = [
+ { key: 'maintenance', label: '보수 및 갱신' },
+ { key: 'cleaning', label: '청소 및 준설' },
+ { key: 'inspection', label: '점검 및 진단' },
+];
+
+const YEARS = Array.from({ length: 150 }, (_, i) => `${2025 - i}`);
+
+const tableHeadlist = [
+ { id: 'no', title: 'NO' },
+ { id: 'fileNm', title: '파일명' },
+ { id: 'manageYear', title: '년도' },
+ { id: 'bungi', title: '분기' },
+ { id: 'registDt', title: '등록 일자' },
+ { id: 'registUsr', title: '등록자' }
+]
+
+const tableData = [
+ { 'no': '1', 'fileNm': '파일명', 'manageYear': '2023', 'registDt': '2025-04-16', 'registUsr': 'me', 'bungi': '1분기' },
+ { 'no': '2', 'fileNm': '파일명', 'manageYear': '2024', 'registDt': '2025-04-16', 'registUsr': 'me2', 'bungi': '1분기' },
+ { 'no': '3', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
+ { 'no': '4', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
+ { 'no': '5', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
+ { 'no': '6', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
+ { 'no': '7', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
+ { 'no': '8', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
+ { 'no': '9', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
+ { 'no': '10', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' }
+]
+
+const BASIC_TYPES = [
+ { key: 'SEWER_PIPE', label: '하수관로' },
+ { key: 'TREATMENT_FACILITY', label: '하수처리시설' },
+ { key: 'TREATMENT_AREA', label: '하수처리구역' },
+ { key: 'MANHOLE', label: '하수맨홀' },
+ { key: 'WASTEWATER_PUMP', label: '오수펌프' },
+ { key: 'RAINWATER_PUMP', label: '빗물펌프' },
+ { key: 'OUTFALL', label: '우수토실' },
+ { key: 'RETENTION_FACILITY', label: '하수저류시설' },
+ { key: 'PRIORITY_OUTFALL', label: '중점우수토실' },
+];
+
+const sidoList = ['선택', '서울', '경기', '부산'];
+const sigunguList = ['선택', '강남구', '서초구', '수원시'];
+const 처리구역List = ['선택', 'A', 'B'];
+const 하수관용도List = ['선택', '우수', '오수'];
+const 관재질List = ['선택', 'PVC', 'PE'];
+const 설치년도List = ['선택', ...YEARS];
+const 경과년도List = ['선택', '1', '2', '3'];
+
+// tableHeadlist, tableData 타입 예시 (실제 타입에 맞게 수정)
+type TableHead = {
+ key: string;
+ label: string;
+};
+type TableRow = Record;
+
+interface SearchModalProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+const SearchModal: React.FC = ({ open, onOpenChange }) => {
+ const [activeTab, setActiveTab] = React.useState('operation');
+ const [workType, setWorkType] = React.useState(WORK_TYPES[0]?.key ?? '');
+ const [startYear, setStartYear] = React.useState('');
+ const [endYear, setEndYear] = React.useState('');
+ const [showTable, setShowTable] = React.useState(false);
+ const [curPage, setCurPage] = React.useState(1);
+ const [basicType, setBasicType] = React.useState(BASIC_TYPES[0]?.key ?? '');
+ const [관리번호, set관리번호] = React.useState('');
+ const [시도, set시도] = React.useState('');
+ const [시군구, set시군구] = React.useState('');
+ const [처리구역, set처리구역] = React.useState('');
+ const [하수관용도, set하수관용도] = React.useState('');
+ const [관재질, set관재질] = React.useState('');
+ const [설치년도1, set설치년도1] = React.useState('');
+ const [설치년도2, set설치년도2] = React.useState('');
+ const [경과년도, set경과년도] = React.useState('');
+ const [연장1, set연장1] = React.useState('');
+ const [연장2, set연장2] = React.useState('');
+
+ // cellClick 함수 타입 지정
+ const handleCellClick = (row: TableRow, col: TableHead) => {
+ // 원하는 동작 구현
+ console.log(row, col);
+ };
+
+ const modalMinWidth = 'min-w-[900px]';
+ const modalMaxWidth = 'max-w-6xl';
+
+ // 모달 높이 동적 계산
+ const contentRef = React.useRef(null);
+ const [dynamicHeight, setDynamicHeight] = React.useState(undefined);
+
+ // API 호출 테스트 (axios 사용)
+ React.useEffect(() => {
+ // API_URL root 경로 확인 테스트
+ console.log('API_URL:', API_URL);
+
+ // lyrTypeNm과 cql_filter에 따른 API 호출 테스트
+ axios.get(`${API_URL}/v1/map/features?lyrTypeNm=tb_ms_swage_prcs_zone&cql_filter=ctpv_nm='전라북도'`)
+ .then(res => {
+ console.log('API DATA:', res.data); // 실제 JSON 데이터 출력
+ })
+ .catch(err => {
+ console.error('API ERROR:', err);
+ });
+ }, []);
+
+ React.useEffect(() => {
+ if (contentRef.current) {
+ setDynamicHeight(contentRef.current.scrollHeight + 60); // 여유 padding
+ }
+ }, [activeTab, showTable, curPage, tableData.length]);
+
+ return (
+
+ );
+}
+
+export default React.memo(SearchModal);
diff --git a/packages/common/business_components/src/ui/search_icon/index.tsx b/packages/common/business_components/src/ui/search_icon/index.tsx
new file mode 100644
index 000000000..0ef0e17e3
--- /dev/null
+++ b/packages/common/business_components/src/ui/search_icon/index.tsx
@@ -0,0 +1,18 @@
+export default function SearchIcon() {
+ return (
+
+ );
+ }
\ No newline at end of file