feat: 연도 범위 선택 기능 추가 및 SearchModal 데이터 처리 개선

This commit is contained in:
swcho
2025-05-16 16:46:06 +09:00
parent 7a9038bbe2
commit d11343726f
4 changed files with 127 additions and 162 deletions
@@ -13,6 +13,48 @@ interface YearRangeSelectBoxProps {
selectClass?: string;
}
// 시작년도 selectArray 생성 함수
export function getStartYearArray({
minYear = 1900,
maxYear = new Date().getFullYear(),
placeholder = "년",
endYear,
}: {
minYear?: number;
maxYear?: number;
placeholder?: string;
endYear?: string;
} = {}) {
const years = Array.from({ length: maxYear - minYear + 1 }, (_, i) =>
(maxYear - i).toString()
).filter(y => !endYear || parseInt(y) <= parseInt(endYear));
return [
{ val: "0", text: placeholder },
...years.map(y => ({ val: y, text: y })),
];
}
// 종료년도 selectArray 생성 함수
export function getEndYearArray({
minYear = 1900,
maxYear = new Date().getFullYear(),
placeholder = "년",
startYear,
}: {
minYear?: number;
maxYear?: number;
placeholder?: string;
startYear?: string;
} = {}) {
const years = Array.from({ length: maxYear - minYear + 1 }, (_, i) =>
(maxYear - i).toString()
).filter(y => !startYear || parseInt(y) >= parseInt(startYear));
return [
{ val: "9999", text: placeholder },
...years.map(y => ({ val: y, text: y })),
];
}
export function YearRangeSelectBox({
start,
end,
+83 -159
View File
@@ -38,7 +38,7 @@ const WORK_TYPES = [
const YEARS = Array.from({ length: 100 }, (_, i) => `${2025 - i}`);
// 테이블 헤더 리스트
// 하수관로 (추후 작성 예정)
// 하수관로
const swagePplTableHeadlist = [
{ id: 'pipeNumber', title: '관로번호' },
{ id: 'gisManageNumber', title: '관리번호(GIS)' },
@@ -66,11 +66,8 @@ const swagePplTableHeadlist = [
]
// 하수처리구역
const swageZoneTableHeadlist = [
// { id: 'ctpvCd', title: '시도코드' },
{ id: 'ctpvNm', title: '시도명' },
// { id: 'sggCd', title: '시군구코드' },
{ id: 'sggNm', title: '시군구명' },
// { id: 'swtrmtZoneSn', title: '하수처리구역번호' },
{ id: 'swtrmtZoneNm', title: '하수처리구역명' },
{ id: 'swtrmtFcltCd', title: '하수처리시설번호' },
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
@@ -162,14 +159,13 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? 'basic');
const [workType, setWorkType] = useState(WORK_TYPES[0]?.val ?? '');
const [showTable, setShowTable] = useState(false);
const [curPage, setCurPage] = useState(0);
const [tableData, setTableData] = useState<any[]>([]);
const [tableCount, setTableCount] = useState<number>(0);
const [curPage, setCurPage] = useState(1);
const [basicType, setBasicType] = useState(BASIC_TYPES[0]?.val ?? '');
const [하수처리구역명, set하수처리구역명] = useState('');
const [하수처리시설명, set하수처리시설명] = useState('');
const [시도, set시도] = useState(0);
const [시군구, set시군구] = useState(0);
const [처리구역, set처리구역] = useState('');
const [tableData, setTableData] = useState<any[]>([]);
// 날짜 상태 선언
const [시작1, set시작1] = useState('');
const [시작2, set시작2] = useState('');
@@ -179,10 +175,6 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
const [변수2, set변수2] = useState('');
const [변수3, set변수3] = useState('');
const [변수4, set변수4] = useState('');
// 하수관로 변수
const [하수관로용도코드, set하수관로용도코드] = useState('');
const [하수관로재질코드, set하수관로재질코드] = useState('');
const [행정읍면동코드, set행정읍면동코드] = useState('');
// 시작, 종료, 경과년도
const [yearFilter, setYearFilter] = useState({
시작: '',
@@ -206,7 +198,7 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
const todayDate = today.getDate().toString().padStart(2, '0');
// UiTable2에 넘길 headlist를 BASIC_TYPES 값에 따라 분기 처리
let dynamicHeadlist = swageZoneTableHeadlist;
let dynamicHeadlist = swageZoneTableHeadlist; // default 값
if (activeTab === 'operation') dynamicHeadlist = operationTableHeadlist; // 운영정보
else if (activeTab === 'report') dynamicHeadlist = reportTableHeadlist; // 하수도정비기본계획보고서
else if (basicType === 'tb_ms_swage_ppl') dynamicHeadlist = swagePplTableHeadlist; // 하수관로
@@ -221,27 +213,14 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
setShowTable(true);
};
// 선택된 필터 값을 저장할 상태 추가
// const [filterParams, setFilterParams] = useState<FilterParams>({});
// cellClick 함수 타입 지정
const handleCellClick = (index?: number, headId?: string) => {
// 원하는 동작 구현
console.log('Cell clicked:', index, headId);
if (typeof index === "number") {
console.log('Selected row data:', tableData[index]);
onFeatureSelect(tableData[index]);
onFeatureSelect(tableData[index % 10]); // 10은 page size 하드 코딩한 숫자
onOpenChange(false); // SearchModal 닫기
}
};
const modalMinWidth = 'min-w-[900px]';
const modalMaxWidth = 'max-w-6xl';
// 모달 높이 동적 계산
const contentRef = React.useRef<HTMLDivElement>(null);
const [dynamicHeight, setDynamicHeight] = useState<number | undefined>(undefined);
// 구분(basicType)이 바뀔 때마다 queryParams, filterParams 초기화
React.useEffect(() => {
// filterParams 초기화
@@ -258,8 +237,6 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
set가동개시_종료_월('');
set가동개시_시작_일('');
set가동개시_종료_일('');
set하수처리구역명('');
set하수처리시설명('');
set시도(0);
set시군구(0);
set처리구역('');
@@ -269,57 +246,20 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
경과년도: ''
});
setShowTable(false);
setTableCount(0);
setTableData([]);
// queryParams는 fetchFilteredData 내에서 매번 새로 생성되므로 별도 상태 초기화는 필요 없음
}, [activeTab, basicType]);
// 페이지 변경 시마다 데이터 fetch
React.useEffect(() => {
if (contentRef.current) {
setDynamicHeight(contentRef.current.scrollHeight + 60); // 여유 padding
// showTable이 true일 때만 fetch (최초 렌더 방지)
if (showTable) {
fetchFilteredData(); // 실제 API 사용 시
// fetchDummyData(); // 더미 데이터 사용 시
}
}, [activeTab, showTable, curPage, tableData.length]);
}, [curPage]);
// 시작년도 selectArray 생성 함수
function getStartYearArray({
minYear = 1900,
maxYear = new Date().getFullYear(),
placeholder = '년',
endYear,
}: {
minYear?: number;
maxYear?: number;
placeholder?: string;
endYear?: string;
} = {}) {
const years = Array.from({ length: maxYear - minYear + 1 }, (_, i) =>
(maxYear - i).toString()
).filter(y => !endYear || parseInt(y) <= parseInt(endYear));
return [
{ val: '0', text: placeholder },
...years.map(y => ({ val: y, text: y })),
];
}
// 종료년도 selectArray 생성 함수
function getEndYearArray({
minYear = 1900,
maxYear = new Date().getFullYear(),
placeholder = '년',
startYear,
}: {
minYear?: number;
maxYear?: number;
placeholder?: string;
startYear?: string;
} = {}) {
const years = Array.from({ length: maxYear - minYear + 1 }, (_, i) =>
(maxYear - i).toString()
).filter(y => !startYear || parseInt(y) >= parseInt(startYear));
return [
{ val: '9999', text: placeholder },
...years.map(y => ({ val: y, text: y })),
];
}
// 하수관로용 CQL 필터 생성
const pplCqlFilters = () => {
@@ -446,10 +386,12 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
// 검색 API 호출 함수
const fetchFilteredData = () => {
const queryParams = new URLSearchParams({
lyrTypeNm: basicType
lyrTypeNm: basicType,
page: `${curPage - 1}`,
size: '10',
sort: 'string',
});
// page와 size 사용해서 api 호출하는 걸로 바꾸기
const size = 10;
let cqlFilters: string[] = [];
if (activeTab === 'basic') {
@@ -465,12 +407,13 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
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()}`)
console.log('API URL:', `${API_URL}/v1/fclts?${queryParams.toString()}`);
axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`)
.then(res => {
console.log('필터링된 API 결과:', res.data.length);
console.log('필터링된 API 결과:', res.data);
setTableData(res.data || []);
console.log('필터링된 API 결과(개수):', res.data.count);
console.log('필터링된 API 결과(데이터):', res.data.data);
setTableData(res.data.data || []);
setTableCount(res.data.count || []);
setShowTable(true);
})
.catch(err => {
@@ -507,8 +450,8 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
{/* 분기처리: 하수관로 */}
{basicType === 'tb_ms_swage_ppl' && (
<div className="mb-6">
{/* 설치년도 */}
<div className="flex items-center mb-2">
{/* 설치년도 */}
<YearRangeSelectBox
start={yearFilter.시작}
end={yearFilter.종료}
@@ -521,7 +464,7 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
<div className='flex items-center'>
<Label className="w-auto text-xl text-gray-600 mr-2">경과년도</Label>
<Input
className='!text-xl w-[100px] placeholder:text-center'
className='!text-xl w-[100px] placeholder:text-center mr-8'
placeholder="입력"
value={yearFilter.경과년도}
onChange={(e) => {
@@ -531,6 +474,42 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
}}
/>
</div>
{/* 용도코드 */}
<Label className="w-auto text-xl text-gray-600 mr-2">용도코드</Label>
<Input
className='!text-xl w-[100px] placeholder:text-center mr-8'
placeholder="입력"
value={변수1}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, '');
set변수1(val);
}}
/>
{/* 재질코드 */}
<Label className="w-auto text-xl text-gray-600 mr-2">재질코드</Label>
<Input
className='!text-xl w-[100px] placeholder:text-center mr-8'
placeholder="입력"
value={변수2}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, '');
set변수2(val);
}}
/>
{/* 행정읍면동코드 */}
<Label className="w-auto text-xl text-gray-600 mr-2">행정읍면동코드</Label>
<Input
className='!text-xl w-[100px] placeholder:text-center mr-8'
placeholder="입력"
value={변수3}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, '');
set변수3(val);
}}
/>
</div>
<div className="flex items-center mb-2">
{/* 관경 */}
@@ -580,44 +559,6 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
}}
/>
</div>
<div className="flex items-center mb-2">
{/* 용도코드 */}
<Label className="w-auto text-xl text-gray-600 mr-2">용도코드</Label>
<Input
className='!text-xl w-[100px] placeholder:text-center mr-8'
placeholder="입력"
value={변수1}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, '');
set변수1(val);
}}
/>
{/* 재질코드 */}
<Label className="w-auto text-xl text-gray-600 mr-2">재질코드</Label>
<Input
className='!text-xl w-[100px] placeholder:text-center mr-8'
placeholder="입력"
value={변수2}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, '');
set변수2(val);
}}
/>
{/* 행정읍면동코드 */}
<Label className="w-auto text-xl text-gray-600 mr-2">행정읍면동코드</Label>
<Input
className='!text-xl w-[100px] placeholder:text-center mr-8'
placeholder="입력"
value={변수3}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, '');
set변수3(val);
}}
/>
</div>
</div>
)}
{/* 분기처리: 하수처리시설 */}
@@ -704,29 +645,25 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
<div className="mb-3">
<div className="flex items-center mb-2">
{/* 설치년도 */}
<div className="flex items-center mb-2">
<YearRangeSelectBox
start={yearFilter.시작}
end={yearFilter.종료}
onChange={range => setYearFilter({ ...yearFilter, 시작: range.start, 종료: range.end })}
startLabel="설치년도"
endLabel=""
/>
{/* 경과년도 */}
<Label className="w-auto text-xl text-gray-600 mr-2">경과년도</Label>
<Input
className='!text-xl w-[100px] placeholder:text-center mr-8'
placeholder="입력"
value={yearFilter.경과년도}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, '');
setYearFilter({ ...yearFilter, 경과년도: val });
}}
/>
</div>
</div>
<div className="flex items-center mb-2">
<YearRangeSelectBox
start={yearFilter.시작}
end={yearFilter.종료}
onChange={range => setYearFilter({ ...yearFilter, 시작: range.start, 종료: range.end })}
startLabel="설치년도"
endLabel=""
/>
{/* 경과년도 */}
<Label className="w-auto text-xl text-gray-600 mr-2">경과년도</Label>
<Input
className='!text-xl w-[100px] placeholder:text-center mr-8'
placeholder="입력"
value={yearFilter.경과년도}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, '');
setYearFilter({ ...yearFilter, 경과년도: val });
}}
/>
{/* 용도코드 */}
<Label className="w-auto text-xl text-gray-600 mr-2">용도코드</Label>
<Input
@@ -791,8 +728,6 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
}}
/>
</div>
</div>
<div className="flex items-center mb-2">
{/* 일일처리용량 - 오수맨홀 */}
<Label className="w-auto text-xl text-gray-600 mr-2">일일처리용량</Label>
<Input
@@ -868,8 +803,6 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
}}
/>
</div>
</div>
<div className="flex items-center mb-2">
{/* 하천명 */}
<Label className="w-auto text-xl text-gray-600 mr-2">하천명</Label>
<Input
@@ -1033,7 +966,7 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
// 탭별 라벨
const label =
activeTab === 'basic'
? BASIC_TYPES.find(type => type.val === basicType)?.val
? BASIC_TYPES.find(type => type.val === basicType)?.text
: activeTab === 'operation'
? WORK_TYPES.find(type => type.val === workType)?.text
: activeTab === 'report'
@@ -1042,7 +975,7 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
return (
<div className="text-lg font-semibold mb-6">
▶ {label} 조회 결과
<span className="ml-2 text-base text-gray-500">({resultCount.toLocaleString()}건)</span>
<span className="ml-2 text-base text-gray-500">({tableCount}건)</span>
</div>
);
})()
@@ -1052,22 +985,13 @@ const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange, onFeature
tableData={tableData}
headlist={dynamicHeadlist}
pageSize={tableData.length < 10 ? tableData.length : 10}
total={tableData.length}
total={tableCount}
children={undefined}
cellClick={handleCellClick}
onPageChange={setCurPage}
resetPageNo={false}
cur={curPage}
/>
{/* FeatureClickEventPopup 표시 */}
{
popupFeature && (
<FeatureClickEventPopup
featureData={popupFeature}
onClose={() => setPopupFeature(null)}
/>
)
}
</div >
</div>
</>
+1 -1
View File
@@ -31,7 +31,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
featureLayer = layer; // 피처가 속한 레이어 찾기
}
});
// SearchMoal 클릭 이벤트와 Gis 클릭 이벤트 구분하기
// SearchModal 클릭 이벤트와 Gis 클릭 이벤트 구분하기
if (featureLayer.get('lyrNm') === null) {
if (featureData.lyrTypeNm === 'tb_il_swage_prcs_fclt') result = '하수처리시설';
else if (featureData.lyrTypeNm === 'tb_ms_swage_prcs_zone') result = '하수처리구역';
@@ -51,7 +51,6 @@ export function UiTable2({ tableData, headlist, pageSize, total, children, headN
const startIdx = (curState - 1) * pageSize;
const endIdx = startIdx + pageSize;
const pageData = tableData.slice(startIdx, endIdx);
return (
<>
@@ -81,7 +80,7 @@ export function UiTable2({ tableData, headlist, pageSize, total, children, headN
)}
</TableHeader>
<TableBody className='text-center'>
{pageData.map((el: { [key: string]: string | number | boolean | object }, index) => (
{tableData.map((el: { [key: string]: string | number | boolean | object }, index) => (
<TableRow key={index + "row"}>
{headlist?.map((head) => (
<TableCell