feat, fix : SearchModal 및 GisTab에서 onFeatureSelect prop 추가 및 관련 상태 관리 개선
This commit is contained in:
+206
-266
@@ -1,19 +1,32 @@
|
||||
import React from 'react';
|
||||
import FeatureClickEventPopup from './featureClickEventPopup';
|
||||
import React, { useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import { UiModal } from '@common/business_components/ui/dialog_ui';
|
||||
import { Button } from '@common/components';
|
||||
import { UiTable2 } from '@common/business_components/ui/table/index-2';
|
||||
import { CommonTableColumn } from "./table";
|
||||
|
||||
|
||||
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`;
|
||||
|
||||
// 탭
|
||||
const TABS = [
|
||||
{ key: 'basic', label: '기본정보' },
|
||||
{ key: 'operation', label: '운영정보' },
|
||||
{ key: 'report', label: '하수도정비기본계획보고서' },
|
||||
];
|
||||
|
||||
// 기본정보 '구분' 선택 목록
|
||||
const BASIC_TYPES = [
|
||||
{ key: 'SEWER_PIPE', label: '하수관로' },
|
||||
{ key: 'tb_il_swage_prcs_fclt', label: '하수처리시설' },
|
||||
{ key: 'tb_ms_swage_prcs_zone', label: '하수처리구역' },
|
||||
{ key: 'MANHOLE', label: '하수맨홀' },
|
||||
{ key: 'WASTEWATER_PUMP', label: '오수펌프' },
|
||||
{ key: 'RAINWATER_PUMP', label: '빗물펌프' },
|
||||
{ key: 'OUTFALL', label: '우수토실' },
|
||||
{ key: 'RETENTION_FACILITY', label: '하수저류시설' },
|
||||
{ key: 'PRIORITY_OUTFALL', label: '중점우수토실' },
|
||||
];
|
||||
// 운영정보 '구분' 선택 목록
|
||||
const WORK_TYPES = [
|
||||
{ key: 'maintenance', label: '보수 및 갱신' },
|
||||
{ key: 'cleaning', label: '청소 및 준설' },
|
||||
@@ -70,19 +83,6 @@ const swageFcltTableHeadlist = [
|
||||
{ id: 'bsnNm', title: '유역명' },
|
||||
];
|
||||
|
||||
// '구분' 선택 목록
|
||||
const BASIC_TYPES = [
|
||||
{ key: 'SEWER_PIPE', label: '하수관로' },
|
||||
{ key: 'tb_il_swage_prcs_fclt', label: '하수처리시설' },
|
||||
{ key: 'tb_ms_swage_prcs_zone', 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 = [
|
||||
{ id: 0, name: '선택' },
|
||||
{ id: 26, name: '부산광역시' },
|
||||
@@ -96,8 +96,6 @@ const sidoList = [
|
||||
{ id: 47, name: '경상북도' },
|
||||
{ id: 48, name: '경상남도' },
|
||||
];
|
||||
|
||||
// 시도별 시군구 리스트 매핑
|
||||
const sigunguMap: Record<number, { id: number; name: string }[]> = {
|
||||
26: [
|
||||
{ id: 0, name: '선택' },
|
||||
@@ -200,16 +198,10 @@ const sigunguMap: Record<number, { id: number; name: string }[]> = {
|
||||
const 처리구역List = ['선택', 'A', 'B'];
|
||||
const 설치년도List = ['선택', ...YEARS];
|
||||
|
||||
// swageZoneTableHeadlist, tableData 타입 예시 (실제 타입에 맞게 수정)
|
||||
type TableHead = {
|
||||
key: string;
|
||||
label: string;
|
||||
};
|
||||
type TableRow = Record<string, any>;
|
||||
|
||||
interface SearchModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onFeatureSelect: (row: any) => void;
|
||||
}
|
||||
|
||||
// filterParams 타입 정의 추가
|
||||
@@ -221,14 +213,6 @@ type FilterParams = {
|
||||
// 추가 필터 속성들...
|
||||
};
|
||||
|
||||
// 해당 년/월의 마지막 일 구하는 함수
|
||||
function getLastDayOfMonth(year: string, month: string) {
|
||||
if (!year || !month) return 31;
|
||||
const y = parseInt(year, 10);
|
||||
const m = parseInt(month, 10);
|
||||
if (isNaN(y) || isNaN(m)) return 31;
|
||||
return new Date(y, m, 0).getDate();
|
||||
}
|
||||
// 날짜 조합 함수
|
||||
const getDateString = (year: string, month: string, day: string, defaultDate: string) => {
|
||||
if (!year && !month && !day) return defaultDate;
|
||||
@@ -238,31 +222,34 @@ const getDateString = (year: string, month: string, day: string, defaultDate: st
|
||||
return `${y}${m}${d}`;
|
||||
};
|
||||
|
||||
const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
const [activeTab, setActiveTab] = React.useState('basic');
|
||||
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(0);
|
||||
const [시군구, set시군구] = React.useState(0);
|
||||
const [처리구역, set처리구역] = React.useState('');
|
||||
const [설치년도1, set설치년도1] = React.useState('');
|
||||
const [설치년도2, set설치년도2] = React.useState('');
|
||||
const [tableData, setTableData] = React.useState<any[]>([]);
|
||||
const [시설용량최소, set시설용량최소] = React.useState('');
|
||||
const [시설용량최대, set시설용량최대] = React.useState('');
|
||||
const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeatureSelect }) => {
|
||||
const [activeTab, setActiveTab] = useState('basic');
|
||||
const [workType, setWorkType] = useState(WORK_TYPES[0]?.key ?? '');
|
||||
const [startYear, setStartYear] = useState('');
|
||||
const [endYear, setEndYear] = useState('');
|
||||
const [showTable, setShowTable] = useState(false);
|
||||
const [curPage, setCurPage] = useState(1);
|
||||
const [basicType, setBasicType] = useState(BASIC_TYPES[0]?.key ?? '');
|
||||
const [하수처리구역명, set하수처리구역명] = useState('');
|
||||
const [하수처리시설명, set하수처리시설명] = useState('');
|
||||
const [시도, set시도] = useState(0);
|
||||
const [시군구, set시군구] = useState(0);
|
||||
const [처리구역, set처리구역] = useState('');
|
||||
const [설치년도1, set설치년도1] = useState('');
|
||||
const [설치년도2, set설치년도2] = useState('');
|
||||
const [tableData, setTableData] = useState<any[]>([]);
|
||||
const [시설용량최소, set시설용량최소] = useState('');
|
||||
const [시설용량최대, set시설용량최대] = useState('');
|
||||
// 날짜 상태 선언
|
||||
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 [가동개시_시작_년, set가동개시_시작_년] = useState('');
|
||||
const [가동개시_시작_월, set가동개시_시작_월] = useState('');
|
||||
const [가동개시_시작_일, set가동개시_시작_일] = useState('');
|
||||
const [가동개시_종료_년, set가동개시_종료_년] = useState('');
|
||||
const [가동개시_종료_월, set가동개시_종료_월] = useState('');
|
||||
const [가동개시_종료_일, set가동개시_종료_일] = useState('');
|
||||
|
||||
// 상세 정보 조회를 위한 상태 추가
|
||||
const [popupFeature, setPopupFeature] = useState<any>(null);
|
||||
|
||||
// 오늘 날짜 구하기
|
||||
const today = new Date();
|
||||
@@ -278,9 +265,6 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
dynamicHeadlist = swageFcltTableHeadlist; // 하수처리시설
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 선택된 시도에 따라 시군구 리스트 가져오기
|
||||
const selectedSigunguList = sigunguMap[시도] || [{ id: 0, name: '선택' }];
|
||||
|
||||
@@ -292,12 +276,17 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
};
|
||||
|
||||
// 선택된 필터 값을 저장할 상태 추가
|
||||
const [filterParams, setFilterParams] = React.useState<FilterParams>({});
|
||||
const [filterParams, setFilterParams] = useState<FilterParams>({});
|
||||
|
||||
// cellClick 함수 타입 지정
|
||||
const handleCellClick = (row: TableRow, col: TableHead) => {
|
||||
const handleCellClick = (index: number | undefined, headId: string) => {
|
||||
// 원하는 동작 구현
|
||||
console.log(row, col);
|
||||
console.log('Cell clicked:', index, headId);
|
||||
if (typeof index === "number") {
|
||||
console.log('Selected row data:', tableData[index]);
|
||||
onFeatureSelect(tableData[index]);
|
||||
onOpenChange(false); // SearchModal 닫기
|
||||
}
|
||||
};
|
||||
|
||||
const modalMinWidth = 'min-w-[900px]';
|
||||
@@ -305,7 +294,7 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
|
||||
// 모달 높이 동적 계산
|
||||
const contentRef = React.useRef<HTMLDivElement>(null);
|
||||
const [dynamicHeight, setDynamicHeight] = React.useState<number | undefined>(undefined);
|
||||
const [dynamicHeight, setDynamicHeight] = useState<number | undefined>(undefined);
|
||||
|
||||
// 구분(basicType)이 바뀔 때마다 queryParams, filterParams 초기화
|
||||
React.useEffect(() => {
|
||||
@@ -369,73 +358,62 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
}, [activeTab, showTable, curPage, tableData.length]);
|
||||
|
||||
|
||||
// 하수처리구역용 CQL 필터 생성
|
||||
const buildZoneCqlFilters = (params: FilterParams) => {
|
||||
const cqlFilters: string[] = [];
|
||||
if (params.ctpv_cd) cqlFilters.push(`ctpv_cd='${params.ctpv_cd}'`);
|
||||
if (params.sgg_cd) cqlFilters.push(`sgg_cd='${params.sgg_cd}'`);
|
||||
if (params.swtrmt_zone_nm) cqlFilters.push(`swtrmt_zone_nm ILIKE '%${params.swtrmt_zone_nm}%'`);
|
||||
if (params.swtrmt_fclt_nm) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${params.swtrmt_fclt_nm}%'`);
|
||||
return cqlFilters;
|
||||
};
|
||||
// 하수처리시설용 CQL 필터 생성
|
||||
const buildFacilityCqlFilters = (params: FilterParams) => {
|
||||
const cqlFilters: string[] = [];
|
||||
// 가동개시일자 범위
|
||||
const startDate = getDateString(가동개시_시작_년, 가동개시_시작_월, 가동개시_시작_일, '19000101');
|
||||
const endDate = getDateString(가동개시_종료_년, 가동개시_종료_월, 가동개시_종료_일, `${todayYear}${todayMonth}${todayDate}`);
|
||||
cqlFilters.push(`oprtng_strt_ymd >= '${startDate}' and oprtng_strt_ymd <= '${endDate}'`);
|
||||
// 시설용량 범위
|
||||
if (시설용량최소 && 시설용량최대) cqlFilters.push(`fclt_cpct BETWEEN ${시설용량최소} AND ${시설용량최대}`);
|
||||
else if (시설용량최소) cqlFilters.push(`fclt_cpct >= ${시설용량최소}`);
|
||||
else if (시설용량최대) cqlFilters.push(`fclt_cpct <= ${시설용량최대}`);
|
||||
// 하수처리시설명
|
||||
if (params.swtrmt_fclt_nm) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${params.swtrmt_fclt_nm}%'`);
|
||||
return cqlFilters;
|
||||
};
|
||||
// 기타 구분용 CQL 필터 생성
|
||||
const buildDefaultCqlFilters = (params: FilterParams) => {
|
||||
const cqlFilters: string[] = [];
|
||||
if (params.swtrmt_fclt_nm) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${params.swtrmt_fclt_nm}%'`);
|
||||
return cqlFilters;
|
||||
};
|
||||
|
||||
// 검색 API 호출 함수
|
||||
const fetchFilteredData = () => {
|
||||
const queryParams = new URLSearchParams({
|
||||
lyrTypeNm: basicType
|
||||
});
|
||||
|
||||
const cqlFilters: string[] = [];
|
||||
let cqlFilters: string[] = [];
|
||||
if (basicType === 'tb_ms_swage_prcs_zone') cqlFilters = buildZoneCqlFilters(filterParams);
|
||||
else if (basicType === 'tb_il_swage_prcs_fclt') cqlFilters = buildFacilityCqlFilters(filterParams);
|
||||
else cqlFilters = buildDefaultCqlFilters(filterParams);
|
||||
// 탭 + 구분 별로 cqlFilter 추가 예정
|
||||
|
||||
// 구분별 분기 처리
|
||||
if (basicType === 'tb_ms_swage_prcs_zone') {
|
||||
// 하수처리구역
|
||||
// 시도
|
||||
if (filterParams.ctpv_cd) {
|
||||
cqlFilters.push(`ctpv_cd='${filterParams.ctpv_cd}'`);
|
||||
}
|
||||
// 시군구
|
||||
if (filterParams.sgg_cd) {
|
||||
cqlFilters.push(`sgg_cd='${filterParams.sgg_cd}'`);
|
||||
}
|
||||
// 하수처리구역명
|
||||
if (filterParams.swtrmt_zone_nm) {
|
||||
cqlFilters.push(`swtrmt_zone_nm ILIKE '%${filterParams.swtrmt_zone_nm}%'`);
|
||||
}
|
||||
// 하수처리시설명
|
||||
if (filterParams.swtrmt_fclt_nm) {
|
||||
cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${filterParams.swtrmt_fclt_nm}%'`);
|
||||
}
|
||||
} else if (basicType === 'tb_il_swage_prcs_fclt') {
|
||||
// 하수처리시설
|
||||
// 가동개시일자 범위 필터 추가
|
||||
// 기본값: 19000101 ~ 오늘
|
||||
const startDate = getDateString(가동개시_시작_년, 가동개시_시작_월, 가동개시_시작_일, '19000101');
|
||||
const endDate = getDateString(가동개시_종료_년, 가동개시_종료_월, 가동개시_종료_일, `${todayYear}${todayMonth}${todayDate}`);
|
||||
cqlFilters.push(`oprtng_strt_ymd >= '${startDate}' and oprtng_strt_ymd <= '${endDate}'`);
|
||||
// 시설용량 범위(fclt_cpct) 필터 추가
|
||||
if (시설용량최소 && 시설용량최대) cqlFilters.push(`fclt_cpct BETWEEN ${시설용량최소} AND ${시설용량최대}`);
|
||||
else if (시설용량최소) cqlFilters.push(`fclt_cpct >= ${시설용량최소}`);
|
||||
else if (시설용량최대) cqlFilters.push(`fclt_cpct <= ${시설용량최대}`);
|
||||
// 하수처리시설명
|
||||
if (filterParams.swtrmt_fclt_nm) {
|
||||
cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${filterParams.swtrmt_fclt_nm}%'`);
|
||||
}
|
||||
} else {
|
||||
// 기타 구분(예: 하수관로, 맨홀 등) - 필요시 추가
|
||||
// 예시: 이름으로 검색
|
||||
if (filterParams.swtrmt_fclt_nm) {
|
||||
cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${filterParams.swtrmt_fclt_nm}%'`);
|
||||
}
|
||||
}
|
||||
|
||||
// 파라미터 이름 오타 주의! (Swagger와 동일하게)
|
||||
if (cqlFilters.length > 0) {
|
||||
queryParams.append('cqlFiter', cqlFilters.join(' and '));
|
||||
}
|
||||
if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and '));
|
||||
|
||||
console.log('API URL:', `${API_URL}/v1/map/features?${queryParams.toString()}`);
|
||||
axios.get(`${API_URL}/v1/map/features?${queryParams.toString()}`)
|
||||
.then(res => {
|
||||
// 결과값을 표에 사용할 수 있도록 상태에 저장
|
||||
console.log('필터링된 API 결과:', res.data.length);
|
||||
console.log('필터링된 API 결과:', res.data);
|
||||
setTableData(res.data || []); // res.data 구조에 따라 조정 필요
|
||||
setTableData(res.data || []);
|
||||
setShowTable(true);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('API ERROR:', err);
|
||||
setTableData([]); // 에러 시 빈 배열로 초기화
|
||||
setTableData([]);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -484,36 +462,9 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/* 분기처리: 하수처리구역 */}
|
||||
{basicType === 'tb_ms_swage_prcs_zone' && (
|
||||
<div className="mb-3">
|
||||
<div className="flex items-center mb-2">
|
||||
<label className="font-semibold text-xl mr-2">시도</label>
|
||||
<select className="border rounded text-xl px-2 py-1 mr-4" value={시도} onChange={e => set시도(parseInt(e.target.value))}>
|
||||
{sidoList.map(opt => (
|
||||
<option key={opt.id} value={opt.id}>{opt.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold text-xl mr-2">시군구</label>
|
||||
<select className="border rounded text-xl px-2 py-1 mr-4" value={시군구} onChange={e => set시군구(parseInt(e.target.value))}>
|
||||
{selectedSigunguList.map(opt => (
|
||||
<option key={opt.id} value={opt.id}>{opt.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<label className="font-semibold text-xl mr-2">하수처리구역명</label>
|
||||
<input className="border rounded text-xl px-2 py-1 mr-4" value={하수처리구역명} onChange={e => set하수처리구역명(e.target.value)} />
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<label className="font-semibold text-xl mr-2">하수처리시설명</label>
|
||||
<input className="border rounded text-xl px-2 py-1 mr-4" value={하수처리시설명} onChange={e => set하수처리시설명(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 분기처리: 하수처리시설 */}
|
||||
{basicType === 'tb_il_swage_prcs_fclt' && (
|
||||
<div className="mb-3">
|
||||
<div className="mb-6">
|
||||
{/* 가동개시일자 */}
|
||||
<div className="flex items-center mb-2">
|
||||
<label className="font-semibold text-xl mr-2">가동개시일자</label>
|
||||
@@ -529,32 +480,10 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
return <option key={y} value={y}>{y}</option>;
|
||||
})}
|
||||
</select>
|
||||
{/* <select
|
||||
className="border rounded text-xl px-2 py-1 mr-1"
|
||||
value={가동개시_시작_월}
|
||||
onChange={e => set가동개시_시작_월(e.target.value)}
|
||||
>
|
||||
<option value="">월</option>
|
||||
{[...Array(12)].map((_, i) => {
|
||||
const m = (i + 1).toString().padStart(2, '0');
|
||||
return <option key={m} value={m}>{m}</option>;
|
||||
})}
|
||||
</select>
|
||||
<select
|
||||
className="border rounded text-xl px-2 py-1 mr-4"
|
||||
value={가동개시_시작_일}
|
||||
onChange={e => set가동개시_시작_일(e.target.value)}
|
||||
>
|
||||
<option value="">일</option>
|
||||
{[...Array(31)].map((_, i) => {
|
||||
const d = (i + 1).toString().padStart(2, '0');
|
||||
return <option key={d} value={d}>{d}</option>;
|
||||
})}
|
||||
</select> */}
|
||||
<span className="mx-1">~</span>
|
||||
<span className="font-semibold text-xl mx-2">~</span>
|
||||
{/* 종료일자 */}
|
||||
<select
|
||||
className="border rounded text-xl px-2 py-1 mr-1"
|
||||
className="border rounded text-xl px-2 py-1 mr-8"
|
||||
value={가동개시_종료_년}
|
||||
onChange={e => set가동개시_종료_년(e.target.value)}
|
||||
>
|
||||
@@ -566,58 +495,7 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
return <option key={y} value={y}>{y}</option>;
|
||||
})}
|
||||
</select>
|
||||
{/* <select
|
||||
className="border rounded text-xl px-2 py-1 mr-1"
|
||||
value={가동개시_종료_월}
|
||||
onChange={e => set가동개시_종료_월(e.target.value)}
|
||||
>
|
||||
<option value="">월</option>
|
||||
{[...Array(12)].map((_, i) => {
|
||||
const m = (i + 1).toString().padStart(2, '0');
|
||||
// 시작년/월이 선택된 경우, 종료년이 시작년과 같으면 시작월 이상만 표시
|
||||
if (
|
||||
가동개시_시작_년 &&
|
||||
가동개시_종료_년 === 가동개시_시작_년 &&
|
||||
가동개시_시작_월 &&
|
||||
parseInt(m) < parseInt(가동개시_시작_월)
|
||||
) return null;
|
||||
return <option key={m} value={m}>{m}</option>;
|
||||
})}
|
||||
</select>
|
||||
<select
|
||||
className="border rounded text-xl px-2 py-1 mr-4"
|
||||
value={가동개시_종료_일}
|
||||
onChange={e => set가동개시_종료_일(e.target.value)}
|
||||
>
|
||||
<option value="">일</option>
|
||||
{[...Array(31)].map((_, i) => {
|
||||
const d = (i + 1).toString().padStart(2, '0');
|
||||
// 시작년/월/일이 선택된 경우, 종료년/월이 시작년/월과 같으면 시작일 이상만 표시
|
||||
if (
|
||||
가동개시_시작_년 &&
|
||||
가동개시_종료_년 === 가동개시_시작_년 &&
|
||||
가동개시_시작_월 &&
|
||||
가동개시_종료_월 === 가동개시_시작_월 &&
|
||||
가동개시_시작_일 &&
|
||||
parseInt(d) < parseInt(가동개시_시작_일)
|
||||
) return null;
|
||||
// 시작년/월이 선택된 경우, 종료년/월이 시작년/월과 같고 시작일이 선택되었으면 시작일 이상만 표시
|
||||
if (
|
||||
가동개시_시작_년 &&
|
||||
가동개시_종료_년 === 가동개시_시작_년 &&
|
||||
가동개시_시작_월 &&
|
||||
가동개시_종료_월 === 가동개시_시작_월 &&
|
||||
가동개시_시작_일 &&
|
||||
parseInt(d) < parseInt(가동개시_시작_일)
|
||||
) return null;
|
||||
// 시작년/월만 선택된 경우, 종료년/월이 시작년/월과 같으면 모든 일 표시(위 조건에 포함)
|
||||
// 시작년만 선택된 경우, 종료년이 시작년과 같으면 모든 월/일 표시(위 조건에 포함)
|
||||
return <option key={d} value={d}>{d}</option>;
|
||||
})}
|
||||
</select> */}
|
||||
</div>
|
||||
{/* 시설용량 */}
|
||||
<div className="flex items-center mb-2">
|
||||
{/* 시설용량 */}
|
||||
<label className="font-semibold text-xl mr-2">시설용량</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -633,7 +511,7 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
<span className="text-xl mr-2">~</span>
|
||||
<input
|
||||
type="text"
|
||||
className="border rounded text-xl px-2 py-1 mr-4 w-40 placeholder:text-center"
|
||||
className="border rounded text-xl px-2 py-1 mr-8 w-40 placeholder:text-center"
|
||||
placeholder="최대"
|
||||
value={시설용량최대}
|
||||
onChange={e => {
|
||||
@@ -642,14 +520,36 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
set시설용량최대(val);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* 하수처리시설명 */}
|
||||
<div className="flex items-center">
|
||||
{/* 하수처리시설명 */}
|
||||
<label className="font-semibold text-xl mr-2">하수처리시설명</label>
|
||||
<input className="border rounded text-xl px-2 py-1 mr-4" value={하수처리시설명} onChange={e => set하수처리시설명(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 분기처리: 하수처리구역 */}
|
||||
{basicType === 'tb_ms_swage_prcs_zone' && (
|
||||
<div className="mb-3">
|
||||
<div className="flex items-center mb-2">
|
||||
<label className="font-semibold text-xl mr-2">시도</label>
|
||||
<select className="border rounded text-xl px-2 py-1 mr-8" value={시도} onChange={e => set시도(parseInt(e.target.value))}>
|
||||
{sidoList.map(opt => (
|
||||
<option key={opt.id} value={opt.id}>{opt.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold text-xl mr-2">시군구</label>
|
||||
<select className="border rounded text-xl px-2 py-1 mr-8" value={시군구} onChange={e => set시군구(parseInt(e.target.value))}>
|
||||
{selectedSigunguList.map(opt => (
|
||||
<option key={opt.id} value={opt.id}>{opt.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold text-xl mr-2">하수처리구역명</label>
|
||||
<input className="border rounded text-xl px-2 py-1 mr-8" value={하수처리구역명} onChange={e => set하수처리구역명(e.target.value)} />
|
||||
<label className="font-semibold text-xl mr-2">하수처리시설명</label>
|
||||
<input className="border rounded text-xl px-2 py-1 mr-8" value={하수처리시설명} onChange={e => set하수처리시설명(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 조회 버튼 중앙 정렬 */}
|
||||
<div className="flex justify-center">
|
||||
<Button className="w-40 text-base py-2" onClick={handleSearch}>조회</Button>
|
||||
@@ -660,10 +560,10 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
{/* 운영정보 탭 */}
|
||||
{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>
|
||||
<div className="flex items-center mb-6">
|
||||
<label className="w-24 font-semibold text-xl">업무구분</label>
|
||||
<select
|
||||
className="border rounded px-3 py-2 text-base"
|
||||
className="border rounded px-3 py-2 text-xl mr-8"
|
||||
value={workType}
|
||||
onChange={e => setWorkType(e.target.value)}
|
||||
style={{ minWidth: 140 }}
|
||||
@@ -673,9 +573,9 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
))}
|
||||
</select>
|
||||
|
||||
<label className="w-16 text-center font-semibold text-base">년도</label>
|
||||
<label className="w-16 text-center font-semibold text-xl">년도</label>
|
||||
<select
|
||||
className="border rounded px-3 py-2 text-base"
|
||||
className="border rounded px-3 py-2 text-xl"
|
||||
value={startYear}
|
||||
onChange={e => setStartYear(e.target.value)}
|
||||
style={{ minWidth: 100 }}
|
||||
@@ -685,9 +585,9 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
<option key={y} value={y}>{y}</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="mx-2 text-lg">~</span>
|
||||
<span className="font-semibold mx-2 text-xl">~</span>
|
||||
<select
|
||||
className="border rounded px-3 py-2 text-base"
|
||||
className="border rounded px-3 py-2 text-xl"
|
||||
value={endYear}
|
||||
onChange={e => setEndYear(e.target.value)}
|
||||
style={{ minWidth: 100 }}
|
||||
@@ -698,49 +598,45 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* 조회 버튼 중앙 정렬 */}
|
||||
<div className="flex justify-center">
|
||||
<Button className="w-40 text-base py-2" onClick={() => setShowTable(true)}>
|
||||
조회
|
||||
</Button>
|
||||
<Button className="w-40 text-base py-2" onClick={handleSearch}>조회</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)}>
|
||||
<div className="flex items-center mb-6">
|
||||
<label className="font-semibold text-xl mr-2">시도</label>
|
||||
<select className="border rounded px-2 py-1 text-xl mr-8" value={시도} onChange={e => set시도(e.target.value)}>
|
||||
{sidoList.map(opt => (
|
||||
<option key={opt.id} value={opt.name}>{opt.id}</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)}>
|
||||
<label className="font-semibold text-xl mr-2">시군구</label>
|
||||
<select className="border rounded px-2 py-1 text-xl mr-8" value={시군구} onChange={e => set시군구(e.target.value)}>
|
||||
{/* {sigunguMap.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)}>
|
||||
<label className="font-semibold text-xl mr-2">하수처리구역</label>
|
||||
<select className="border rounded px-2 text-xl 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)}>
|
||||
<input className="border rounded text-xl px-2 py-1 w-24 mr-8" placeholder="0000" />
|
||||
<label className="font-semibold text-xl mr-2">승인년도</label>
|
||||
<select className="border rounded text-xl 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)}>
|
||||
<select className="border rounded text-xl px-2 py-1" value={설치년도2} onChange={e => set설치년도2(e.target.value)}>
|
||||
{설치년도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
@@ -748,48 +644,92 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
</div>
|
||||
{/* 조회 버튼 중앙 정렬 */}
|
||||
<div className="flex justify-center">
|
||||
<Button className="w-40 text-base py-2">조회</Button>
|
||||
<Button className="w-40 text-base py-2" onClick={handleSearch}>조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 📋 결과 리스트 */}
|
||||
<div className="border-t pt-6 space-y-2" style={{ maxWidth: '100vw', maxHeight: '60vh', overflowX: 'auto'}}>
|
||||
<div
|
||||
className="border-t pt-6 space-y-2 max-w-full max-h-[60vh] overflow-auto"
|
||||
onWheel={e => {
|
||||
// Shift+Wheel은 브라우저가 기본적으로 가로 스크롤을 처리하지만,
|
||||
// 세로 스크롤이 최상단/최하단에 도달했을 때 가로 스크롤이 막히는 현상을 해결
|
||||
const container = e.currentTarget;
|
||||
const isShift = e.shiftKey;
|
||||
if (!isShift) return;
|
||||
|
||||
// 세로 스크롤 위치
|
||||
const atTop = container.scrollTop === 0;
|
||||
const atBottom = Math.ceil(container.scrollTop + container.clientHeight) >= container.scrollHeight;
|
||||
|
||||
// 세로 스크롤이 최상단/최하단에 도달한 경우에만 가로 스크롤 수동 처리
|
||||
if ((atTop && e.deltaY < 0) || (atBottom && e.deltaY > 0)) {
|
||||
container.scrollLeft += e.deltaY;
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* 선택된 탭의 label */}
|
||||
<div className="text-2xl font-bold mb-2">
|
||||
<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>
|
||||
{/* 조회 결과 라벨 및 건수 표시 */}
|
||||
{
|
||||
(() => {
|
||||
// 조회 건수 변수 선언
|
||||
const resultCount = tableData.length;
|
||||
// 탭별 라벨
|
||||
const label =
|
||||
activeTab === 'basic'
|
||||
? BASIC_TYPES.find(type => type.key === basicType)?.label
|
||||
: activeTab === 'operation'
|
||||
? WORK_TYPES.find(type => type.key === workType)?.label
|
||||
: activeTab === 'report'
|
||||
? '하수도정비기본계획보고서'
|
||||
: TABS.find(tab => tab.key === activeTab)?.label;
|
||||
return (
|
||||
<div className="text-lg font-semibold mb-6">
|
||||
▶ {label} 조회 결과
|
||||
<span className="ml-2 text-base text-gray-500">({resultCount.toLocaleString()}건)</span>
|
||||
</div>
|
||||
);
|
||||
})()
|
||||
}
|
||||
<UiTable2 headName='' tableData={tableData} headlist={dynamicHeadlist}
|
||||
pageSize={tableData.length < 10 ? tableData.length : 10}
|
||||
total={tableData.length} children={undefined}
|
||||
cellClick={() => { }} onPageChange={setCurPage}
|
||||
cellClick={handleCellClick} onPageChange={setCurPage}
|
||||
resetPageNo={false}
|
||||
cur={curPage}
|
||||
/>
|
||||
{/* FeatureClickEventPopup 표시 */}
|
||||
{
|
||||
popupFeature && (
|
||||
<FeatureClickEventPopup
|
||||
featureData={popupFeature}
|
||||
onClose={() => setPopupFeature(null)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
{/* </DialogContent>
|
||||
</Dialog>
|
||||
); */}
|
||||
|
||||
return (
|
||||
<UiModal
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
bodyClass={`
|
||||
<UiModal
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
bodyClass={`
|
||||
${modalMaxWidth} ${modalMinWidth}
|
||||
transition-all duration-300
|
||||
w-full overflow-hidden
|
||||
z-[200]
|
||||
`}
|
||||
body={renderBody()}
|
||||
headerTitle="검색"
|
||||
// 필요시 footerContent 등 추가
|
||||
/>
|
||||
body={renderBody()}
|
||||
headerTitle="검색"
|
||||
// 필요시 footerContent 등 추가
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,20 +34,15 @@ function Gis({ treeData = [], isSearch = false }) {
|
||||
setIsOpen(false); // 팝업 닫기
|
||||
setFeatureData(null)
|
||||
};
|
||||
|
||||
// SearchModal, GisContent 등에서 호출
|
||||
const handleFeatureSelect = (data) => {
|
||||
console.log('check',data)
|
||||
setFeatureData(data); // 선택된 피처 데이터 저장
|
||||
setIsOpen(true); // 팝업 열기
|
||||
setIsSearchModalOpen(false); // SearchModal 닫기
|
||||
};
|
||||
|
||||
// SearchModal이 열릴 때 featureClickEventPopup 닫기
|
||||
useEffect(() => {
|
||||
if (isSearchModalOpen && isOpen) {
|
||||
setIsOpen(false);
|
||||
setFeatureData(null);
|
||||
}
|
||||
}, [isSearchModalOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mapRef.current) return;
|
||||
|
||||
@@ -151,9 +146,10 @@ function Gis({ treeData = [], isSearch = false }) {
|
||||
<>
|
||||
<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} />
|
||||
<GisTab visible={tabVisible} setVisible={setTabVisible} isSearch={isSearch} isSearchModalOpen={isSearchModalOpen} setIsSearchModalOpen={setIsSearchModalOpen} onFeatureSelect={handleFeatureSelect}/>
|
||||
<GisContent onFeatureSelect={handleFeatureSelect}/>
|
||||
{isOpen && featureData && (
|
||||
{/* isSearchModalOpen이 true면 visible=false로 팝업 숨김 */}
|
||||
{isOpen && featureData && !isSearchModalOpen && (
|
||||
<FeatureClickEventPopup
|
||||
featureData={featureData}
|
||||
onClose={closePopup} // 닫기 콜백 전달
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ChevronLeft, ChevronRight, Search } from 'lucide-react';
|
||||
import SearchModal from '@/map/SearchModal';
|
||||
|
||||
|
||||
function GisTab({ visible, setVisible, isSearch, isSearchModalOpen, setIsSearchModalOpen }) {
|
||||
function GisTab({ visible, setVisible, isSearch, isSearchModalOpen, setIsSearchModalOpen, onFeatureSelect }) {
|
||||
const [activeTab, setActiveTab] = useState('layers');
|
||||
|
||||
return (
|
||||
@@ -53,7 +53,11 @@ function GisTab({ visible, setVisible, isSearch, isSearchModalOpen, setIsSearchM
|
||||
|
||||
|
||||
{/* 🔍 검색 모달 */}
|
||||
<SearchModal open={isSearchModalOpen} onOpenChange={setIsSearchModalOpen} />
|
||||
<SearchModal
|
||||
open={isSearchModalOpen}
|
||||
onOpenChange={setIsSearchModalOpen}
|
||||
onFeatureSelect={onFeatureSelect}
|
||||
/>
|
||||
</Tabs>
|
||||
|
||||
{/* 접기 버튼 */}
|
||||
|
||||
Reference in New Issue
Block a user