feat, fix : 하수관로-하수처리시설 모달과 상세조회 이벤트 처리, 백엔드 페이징 처리 없는 테이블 생성, SearchModal.tsx 파일 위치 이동 (sn/app/map)
This commit is contained in:
@@ -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 (
|
||||
<>
|
||||
<GisContext.Provider value={{gisCore, gisMap, treeData, checkedLayerIds}}>
|
||||
<div className="h-[calc(100vh-10rem)] flex relative">
|
||||
<GisTab visible={tabVisible} setVisible={setTabVisible} isSearch={isSearch} />
|
||||
<GisContent onFeatureSelect={handleFeatureSelect}/>
|
||||
{isOpen && featureData && (
|
||||
<FeatureClickEventPopup
|
||||
featureData={featureData}
|
||||
onClose={closePopup} // 닫기 콜백 전달
|
||||
/>
|
||||
)}
|
||||
<div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} />
|
||||
<GisLonLat/>
|
||||
<GisToolBar/>
|
||||
<MapToggleBtn/>
|
||||
</div>
|
||||
</GisContext.Provider>
|
||||
<GisContext.Provider value={{gisCore, gisMap, treeData, checkedLayerIds}}>
|
||||
<div className="h-[calc(100vh-10rem)] flex relative">
|
||||
<GisTab visible={tabVisible} setVisible={setTabVisible} isSearch={isSearch} isSearchModalOpen={isSearchModalOpen} setIsSearchModalOpen={setIsSearchModalOpen} />
|
||||
<GisContent onFeatureSelect={handleFeatureSelect}/>
|
||||
{isOpen && featureData && (
|
||||
<FeatureClickEventPopup
|
||||
featureData={featureData}
|
||||
onClose={closePopup} // 닫기 콜백 전달
|
||||
/>
|
||||
)}
|
||||
<div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} />
|
||||
<GisLonLat/>
|
||||
<GisToolBar/>
|
||||
<MapToggleBtn/>
|
||||
</div>
|
||||
</GisContext.Provider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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<string, any>;
|
||||
|
||||
interface SearchModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const SearchModal: React.FC<SearchModalProps> = ({ 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<HTMLDivElement>(null);
|
||||
const [dynamicHeight, setDynamicHeight] = React.useState<number | undefined>(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 (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className={`
|
||||
${modalMaxWidth} ${modalMinWidth}
|
||||
transition-all duration-300
|
||||
w-full overflow-hidden
|
||||
`}
|
||||
style={{ minHeight: showTable ? 600 : 220 }}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>검색</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{/* 탭 영역 */}
|
||||
<div className="flex gap-2 mb-4">
|
||||
{TABS.map(tab => (
|
||||
<button
|
||||
key={tab.key}
|
||||
className={`px-4 py-2 rounded-t border border-b-0 text-base font-semibold
|
||||
${activeTab === tab.key
|
||||
? 'bg-[#bfcfe6] text-black'
|
||||
: 'bg-[#e6e6e6] text-gray-700'
|
||||
}`}
|
||||
style={{
|
||||
borderColor: '#888',
|
||||
borderBottom: activeTab === tab.key ? '2px solid white' : undefined,
|
||||
minWidth: 140,
|
||||
fontSize: '1.1rem'
|
||||
}}
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 기본정보 탭 */}
|
||||
{activeTab === 'basic' && (
|
||||
<div className="border rounded p-6 mb-4">
|
||||
{/* 구분 */}
|
||||
<div className="flex items-center mb-3">
|
||||
<label className="font-semibold mr-2">구분</label>
|
||||
<select className="border rounded px-2 py-1" value={basicType} onChange={e => setBasicType(e.target.value)}>
|
||||
{BASIC_TYPES.map(opt => (
|
||||
<option key={opt.key} value={opt.key}>{opt.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* 관리번호, 시도, 시군구 한 줄 */}
|
||||
<div className="flex items-center mb-3">
|
||||
<label className="font-semibold mr-2">관리번호</label>
|
||||
<input className="border rounded px-2 py-1 mr-4" value={관리번호} onChange={e => set관리번호(e.target.value)} />
|
||||
<label className="font-semibold mr-2">시도</label>
|
||||
<select className="border rounded px-2 py-1 mr-4" value={시도} onChange={e => set시도(e.target.value)}>
|
||||
{sidoList.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold mr-2">시군구</label>
|
||||
<select className="border rounded px-2 py-1" value={시군구} onChange={e => set시군구(e.target.value)}>
|
||||
{sigunguList.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* 처리구역, 하수관용도, 관재질 한 줄 */}
|
||||
<div className="flex items-center mb-3">
|
||||
<label className="font-semibold mr-2">처리구역</label>
|
||||
<select className="border rounded px-2 py-1 mr-4" value={처리구역} onChange={e => set처리구역(e.target.value)}>
|
||||
{처리구역List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold mr-2">하수관용도</label>
|
||||
<select className="border rounded px-2 py-1 mr-4" value={하수관용도} onChange={e => set하수관용도(e.target.value)}>
|
||||
{하수관용도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold mr-2">관재질</label>
|
||||
<select className="border rounded px-2 py-1" value={관재질} onChange={e => set관재질(e.target.value)}>
|
||||
{관재질List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* 설치년도, 경과년도 한 줄 */}
|
||||
<div className="flex items-center mb-3">
|
||||
<label className="font-semibold mr-2">설치년도</label>
|
||||
<select className="border rounded px-2 py-1 mr-2" value={설치년도1} onChange={e => set설치년도1(e.target.value)}>
|
||||
{설치년도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="mx-1">~</span>
|
||||
<select className="border rounded px-2 py-1 mr-6" value={설치년도2} onChange={e => set설치년도2(e.target.value)}>
|
||||
{설치년도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold mr-2">경과년도</label>
|
||||
<select className="border rounded px-2 py-1" value={경과년도} onChange={e => set경과년도(e.target.value)}>
|
||||
{경과년도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* 연장 한 줄 */}
|
||||
<div className="flex items-center mb-4">
|
||||
<label className="font-semibold mr-2">연장(m)</label>
|
||||
<input className="border rounded px-2 py-1 mr-2" value={연장1} onChange={e => set연장1(e.target.value)} />
|
||||
<span className="mx-1">~</span>
|
||||
<input className="border rounded px-2 py-1" value={연장2} onChange={e => set연장2(e.target.value)} />
|
||||
</div>
|
||||
{/* 조회 버튼 중앙 정렬 */}
|
||||
<div className="flex justify-center">
|
||||
<Button className="w-40 text-base py-2">조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 운영정보 탭 */}
|
||||
{activeTab === 'operation' && (
|
||||
<div className="border rounded p-6 mb-4">
|
||||
<div className="flex items-center gap-6 mb-6">
|
||||
<label className="w-24 font-semibold text-base">업무구분</label>
|
||||
<select
|
||||
className="border rounded px-3 py-2 text-base"
|
||||
value={workType}
|
||||
onChange={e => setWorkType(e.target.value)}
|
||||
style={{ minWidth: 140 }}
|
||||
>
|
||||
{WORK_TYPES.map(w => (
|
||||
<option key={w.key} value={w.key}>{w.label}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<label className="w-16 text-center font-semibold text-base">년도</label>
|
||||
<select
|
||||
className="border rounded px-3 py-2 text-base"
|
||||
value={startYear}
|
||||
onChange={e => setStartYear(e.target.value)}
|
||||
style={{ minWidth: 100 }}
|
||||
>
|
||||
<option value="">선택</option>
|
||||
{YEARS.map(y => (
|
||||
<option key={y} value={y}>{y}</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="mx-2 text-lg">~</span>
|
||||
<select
|
||||
className="border rounded px-3 py-2 text-base"
|
||||
value={endYear}
|
||||
onChange={e => setEndYear(e.target.value)}
|
||||
style={{ minWidth: 100 }}
|
||||
>
|
||||
<option value="">선택</option>
|
||||
{YEARS.map(y => (
|
||||
<option key={y} value={y}>{y}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Button className="w-40 text-base py-2" onClick={() => setShowTable(true)}>
|
||||
조회
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 하수도정비기본계획보고서서 탭 */}
|
||||
{activeTab === 'report' && (
|
||||
<div className="border rounded p-6 mb-4">
|
||||
{/* 시도, 시군구, 하수처리구역 한 줄 */}
|
||||
<div className="flex items-center mb-3">
|
||||
<label className="font-semibold mr-2">시도</label>
|
||||
<select className="border rounded px-2 py-1 mr-4" value={시도} onChange={e => set시도(e.target.value)}>
|
||||
{sidoList.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold mr-2">시군구</label>
|
||||
<select className="border rounded px-2 py-1 mr-4" value={시군구} onChange={e => set시군구(e.target.value)}>
|
||||
{sigunguList.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold mr-2">하수처리구역</label>
|
||||
<select className="border rounded px-2 py-1 mr-2" value={처리구역} onChange={e => set처리구역(e.target.value)}>
|
||||
{처리구역List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<input className="border rounded px-2 py-1 w-24" placeholder="0000" />
|
||||
</div>
|
||||
{/* 승인년도 한 줄 */}
|
||||
<div className="flex items-center mb-4">
|
||||
<label className="font-semibold mr-2">승인년도</label>
|
||||
<select className="border rounded px-2 py-1 mr-2" value={설치년도1} onChange={e => set설치년도1(e.target.value)}>
|
||||
{설치년도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="mx-1">~</span>
|
||||
<select className="border rounded px-2 py-1" value={설치년도2} onChange={e => set설치년도2(e.target.value)}>
|
||||
{설치년도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* 조회 버튼 중앙 정렬 */}
|
||||
<div className="flex justify-center">
|
||||
<Button className="w-40 text-base py-2">조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 📋 결과 리스트 */}
|
||||
<div className="border-t pt-6 space-y-2">
|
||||
{/* 선택된 탭의 label */}
|
||||
<div className="text-2xl font-bold mb-2">
|
||||
{TABS.find(tab => tab.key === activeTab)?.label}
|
||||
</div>
|
||||
{/* "▶" + 선택된 탭 label + " 조회 결과" */}
|
||||
<div className="text-lg font-semibold mb-6">
|
||||
▶ {TABS.find(tab => tab.key === activeTab)?.label} 조회 결과
|
||||
</div>
|
||||
<UiTable headName='' tableData={tableData} headlist={tableHeadlist}
|
||||
pageSize={10} total={20} children={undefined}
|
||||
cellClick={() => { }} onPageChange={function (page: number): void {
|
||||
throw new Error('Function not implemented.')
|
||||
}}>
|
||||
</UiTable>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(SearchModal);
|
||||
@@ -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<number>(-1);
|
||||
|
||||
const [curState, setCurState] = useState<number>(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 ? (
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">{headName}</h3>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={`card !m-[1.2rem] !p-0r w-full relative ${className}`}>
|
||||
<div>
|
||||
<Table variant="secondary" className="w-full">
|
||||
{/* 스크롤 관련 div 제거 */}
|
||||
<TableHeader className="bg-white">
|
||||
{children ? (
|
||||
<>{children}</>
|
||||
) : (
|
||||
<TableRow>
|
||||
{headlist?.map((head) => (
|
||||
<TableHead key={head.id}
|
||||
className="min-w-[120px] max-w-[180px] w-[150px]
|
||||
text-center whitespace-nowrap overflow-hidden
|
||||
text-ellipsis bg-white">
|
||||
{head.title}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
)}
|
||||
</TableHeader>
|
||||
<TableBody className='text-center'>
|
||||
{pageData.map((el: { [key: string]: string | number | boolean | object }, index) => (
|
||||
<TableRow key={index + "row"}>
|
||||
{headlist?.map((head) => (
|
||||
<TableCell
|
||||
className={`${head.align === 'right' ? 'text-right' : head.align === 'center' ? 'text-center' : typeof el[head.id] === 'number' ? 'text-right' : 'text-center'} ${cellClick && idx === index ? '!font-[1000] cursor-pointer' :
|
||||
cellClick ? 'cursor-pointer' : ''
|
||||
}`}
|
||||
onClick={() => 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] ?? '')}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
{/* 페이지네이션을 sticky 하단에 고정 */}
|
||||
{total > 0 && (
|
||||
<div className="sticky left-0 right-0 bottom-0 bg-white z-20 border-t border-gray-200">
|
||||
<UiPageNations total={total} cur={curState} pageSize={pageSize} onPageChange={handlePageChange} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -30,5 +30,5 @@
|
||||
"**/*.mjs",
|
||||
"**/*.jsx",
|
||||
"**/*.json"
|
||||
]
|
||||
, "../../../apps/sn/app/map/SearchModal.tsx" ]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user