From 92ddd91331894461e35ac1617b9da4ed89898014 Mon Sep 17 00:00:00 2001 From: swcho Date: Fri, 9 May 2025 15:38:19 +0900 Subject: [PATCH] =?UTF-8?q?feat,=20fix=20:=20=ED=95=98=EC=88=98=EA=B4=80?= =?UTF-8?q?=EB=A1=9C-=ED=95=98=EC=88=98=EC=B2=98=EB=A6=AC=EC=8B=9C?= =?UTF-8?q?=EC=84=A4=20=EB=AA=A8=EB=8B=AC=EA=B3=BC=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC,=20=EB=B0=B1=EC=97=94=EB=93=9C=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=95=20=EC=B2=98=EB=A6=AC=20=EC=97=86=EB=8A=94=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EC=83=9D=EC=84=B1,=20SearchModal.tsx=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=9C=84=EC=B9=98=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?(sn/app/map)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../business_components/src/ui/map/Gis.js | 59 +-- .../business_components/src/ui/map/GisTab.js | 5 +- .../src/ui/map/SearchModal.tsx | 377 ------------------ .../src/ui/table/index-2.tsx | 111 ++++++ .../common/business_components/tsconfig.json | 2 +- 5 files changed, 148 insertions(+), 406 deletions(-) delete mode 100644 packages/common/business_components/src/ui/map/SearchModal.tsx create mode 100644 packages/common/business_components/src/ui/table/index-2.tsx diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index 3f2d4eb2f..97918d5b7 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -25,19 +25,28 @@ function Gis({ treeData = [], isSearch = false }) { const [tabVisible, setTabVisible] = useState(true); const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 const [featureData,setFeatureData] = useState(null) // 선택된 피쳐 데이터 + const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태 const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []); //const [treeData, setTreeData] = useState([]); const checkedLayerIds = useRef([]); let gisMap = null; const closePopup = () => { - setIsOpen(false); // 팝업 닫기 + setIsOpen(false); // 팝업 닫기 setFeatureData(null) - }; + }; const handleFeatureSelect = (data) => { console.log('check',data) - setFeatureData(data); // 선택된 피처 데이터 저장 - setIsOpen(true); // 팝업 열기 - }; + setFeatureData(data); // 선택된 피처 데이터 저장 + setIsOpen(true); // 팝업 열기 + }; + + // SearchModal이 열릴 때 featureClickEventPopup 닫기 + useEffect(() => { + if (isSearchModalOpen && isOpen) { + setIsOpen(false); + setFeatureData(null); + } + }, [isSearchModalOpen]); useEffect(() => { if (!mapRef.current) return; @@ -50,7 +59,7 @@ function Gis({ treeData = [], isSearch = false }) { ], ]); register(proj4); - + //맵 생성성 const map = new OlMap({ layers: [gisCore.baseLayers['base'], gisCore.baseLayers['satellite']], @@ -76,7 +85,7 @@ function Gis({ treeData = [], isSearch = false }) { maxZoom: gisCore.defaults.maxZoom, }), }); - + //미니맵 const overviewMap = new OverviewMap({ layers: [GisApp.Module.core.baseMiniMapLayers['base']], @@ -103,10 +112,10 @@ function Gis({ treeData = [], isSearch = false }) { gisCore.deleteAllOverlay(); gisCore.deleteAllLayer(); - + const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, 'lyrSn'); GisApp.layerCode = layerCode; - + //setTreeData(data); const layerItems = getLayerItems(layerCode); //setEditLayerItems(prev => [...layerItems]) @@ -140,22 +149,22 @@ function Gis({ treeData = [], isSearch = false }) { return ( <> - -
- - - {isOpen && featureData && ( - - )} -
- - - -
- + +
+ + + {isOpen && featureData && ( + + )} +
+ + + +
+ ); } diff --git a/packages/common/business_components/src/ui/map/GisTab.js b/packages/common/business_components/src/ui/map/GisTab.js index b63b774bb..d658c0bfb 100644 --- a/packages/common/business_components/src/ui/map/GisTab.js +++ b/packages/common/business_components/src/ui/map/GisTab.js @@ -3,12 +3,11 @@ import { Tabs, TabsList, TabsTrigger } from '@common/components/ui/tabs'; import GisTree from './GisTree'; import GisSearch from './GisSearch'; import { ChevronLeft, ChevronRight, Search } from 'lucide-react'; -import SearchModal from './SearchModal'; +import SearchModal from '@/map/SearchModal'; -function GisTab({ visible, setVisible, isSearch }) { +function GisTab({ visible, setVisible, isSearch, isSearchModalOpen, setIsSearchModalOpen }) { const [activeTab, setActiveTab] = useState('layers'); - const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); return ( <> diff --git a/packages/common/business_components/src/ui/map/SearchModal.tsx b/packages/common/business_components/src/ui/map/SearchModal.tsx deleted file mode 100644 index 434ca99c8..000000000 --- a/packages/common/business_components/src/ui/map/SearchModal.tsx +++ /dev/null @@ -1,377 +0,0 @@ -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 ( - - - - 검색 - - - {/* 탭 영역 */} -
- {TABS.map(tab => ( - - ))} -
- - {/* 기본정보 탭 */} - {activeTab === 'basic' && ( -
- {/* 구분 */} -
- - -
- {/* 관리번호, 시도, 시군구 한 줄 */} -
- - set관리번호(e.target.value)} /> - - - - -
- {/* 처리구역, 하수관용도, 관재질 한 줄 */} -
- - - - - - -
- {/* 설치년도, 경과년도 한 줄 */} -
- - - ~ - - - -
- {/* 연장 한 줄 */} -
- - set연장1(e.target.value)} /> - ~ - set연장2(e.target.value)} /> -
- {/* 조회 버튼 중앙 정렬 */} -
- -
-
- )} - - {/* 운영정보 탭 */} - {activeTab === 'operation' && ( -
-
- - - - - - ~ - -
-
- -
-
- )} - - {/* 하수도정비기본계획보고서서 탭 */} - {activeTab === 'report' && ( -
- {/* 시도, 시군구, 하수처리구역 한 줄 */} -
- - - - - - - -
- {/* 승인년도 한 줄 */} -
- - - ~ - -
- {/* 조회 버튼 중앙 정렬 */} -
- -
-
- )} - - {/* 📋 결과 리스트 */} -
- {/* 선택된 탭의 label */} -
- {TABS.find(tab => tab.key === activeTab)?.label} -
- {/* "▶" + 선택된 탭 label + " 조회 결과" */} -
- ▶ {TABS.find(tab => tab.key === activeTab)?.label} 조회 결과 -
- { }} onPageChange={function (page: number): void { - throw new Error('Function not implemented.') - }}> - -
-
-
- ); -} - -export default React.memo(SearchModal); diff --git a/packages/common/business_components/src/ui/table/index-2.tsx b/packages/common/business_components/src/ui/table/index-2.tsx new file mode 100644 index 000000000..f4f4163f8 --- /dev/null +++ b/packages/common/business_components/src/ui/table/index-2.tsx @@ -0,0 +1,111 @@ +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; +import { useEffect, useState } from "react"; +import UiPageNations from "./uiPageNations"; + +type UiTableProps = { + headName: React.ReactNode | undefined; + tableData: { [key: string]: string | number | boolean | object }[]; + headlist: UiHeadProps[]; + pageSize: number; + total: number; + children: React.ReactNode | undefined; + cellClick: ((index: number | undefined, headId: string) => void) | undefined; + onPageChange: (page: number) => void; + className?: string; + resetPageNo?: boolean; + cur?: number; +} + +type UiHeadProps = { + id: string; + title: string; + align?: string; +} + +export function UiTable2({ tableData, headlist, pageSize, total, children, headName, cellClick, onPageChange, className = '', resetPageNo = false, cur }: UiTableProps) { + const [idx, setIdx] = useState(-1); + + const [curState, setCurState] = useState(cur ?? 1); + useEffect(() => { + setCurState(cur ?? 1); + }, [cur]); + + useEffect(() => { + if (resetPageNo) { + setCurState(1); + onPageChange(1); + } + }, [onPageChange, resetPageNo]); + + const handlePageChange = (page: number) => { + setCurState(page); + onPageChange(page); + } + + const handleCellClick = (index: number, headId: string) => { + if (cellClick) { + cellClick(index, headId); + setIdx(index); + } + }; + + const startIdx = (curState - 1) * pageSize; + const endIdx = startIdx + pageSize; + const pageData = tableData.slice(startIdx, endIdx); + + return ( + <> + {headName ? ( +
+

{headName}

+
+ ) : null} +
+
+ + {/* 스크롤 관련 div 제거 */} + + {children ? ( + <>{children} + ) : ( + + {headlist?.map((head) => ( + + {head.title} + + ))} + + )} + + + {pageData.map((el: { [key: string]: string | number | boolean | object }, index) => ( + + {headlist?.map((head) => ( + handleCellClick(startIdx + index, head.id)} + key={head.id + index} + > + {typeof el[head.id] === 'number' ? String(el[head.id]).replace(/\B(?=(\d{3})+(?!\d))/g, ",") : String(el[head.id] ?? '')} + + ))} + + ))} + +
+
+ {/* 페이지네이션을 sticky 하단에 고정 */} + {total > 0 && ( +
+ +
+ )} +
+ + ); +} diff --git a/packages/common/business_components/tsconfig.json b/packages/common/business_components/tsconfig.json index 8b065d297..20f5a6648 100644 --- a/packages/common/business_components/tsconfig.json +++ b/packages/common/business_components/tsconfig.json @@ -30,5 +30,5 @@ "**/*.mjs", "**/*.jsx", "**/*.json" - ] +, "../../../apps/sn/app/map/SearchModal.tsx" ] }