Files
frontend/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx
T
swcho cb1bcd6061 feat:
- searchModal 화면 반응형 크기 수정
- 침입수 분석 결과, RDII 분석 결과 표 세로 스크롤 삭제
2025-12-19 18:06:47 +09:00

272 lines
10 KiB
TypeScript

/**
* ➡️ 경로 : 하수관망도 메인 페이지 (오른쪽 돋보기 모양 클릭) - 검색 모달 창 내 하수도정비기본계획보고서 탭
*
* 📄 Report Tab
* Next.js App Router (app/) 기반 검색 모달 보고서 탭 컴포넌트
*
* - 하수도정비 기본계획 보고서 목록 표시 및 필터링
* - 시도/시군구/보고서 구분/승인년도 기반 검색 기능
* - 테이블 데이터 표시 및 페이지네이션 지원
* - 보고서 클릭 시 파일 목록 모달 열기
* - Zustand를 통한 모달 상태 및 데이터 관리
* - 로딩 오버레이 및 선택 초기화 기능
*/
'use client';
import React, { useEffect, useState } from 'react';
import { Button, Label } from '@common/components';
import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox';
import { SelectBox, UiTable } from '@common/business_components/ui';
import LoadingOverlay from '@/components/ui/LoadingBar';
import useReportTab from './useReportTab';
import useStore from '@/store/useStore';
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
import useSearchModalStore from '@/store/useSearchModalStore';
import { useReportTabStore } from '@/store/useReportTabStore';
import ReportFilesModal from './reportFilesModal'; // 파일목록모달
// 하수도정비 기본계획 보고서
export const reportTableHeadlist = [
{ id: "no", title: "NO" },
{ id: "sido", title: "시도" },
{ id: "sigungu", title: "시군구" },
{ id: "approvalYear", title: "승인년도" },
{ id: "plantZoneGubun", title: "구분" },
{ id: "reportGubun", title: "보고서구분" },
{ id: "fileName", title: "파일명" },
{ id: "regDate", title: "등록일자" },
];
interface ReportTabProps {
open?: boolean;
isSearch?: boolean;
ctpvNm?: string;
sggNm?: string;
}
const ReportTab: React.FC<ReportTabProps> = ({ open, isSearch = true, ctpvNm, sggNm }) => {
// const [isLoading, setIsLoading] = useState(false);
const [isResetPage, setIsResetPage] = useState<boolean>(false);
const [isResetSelection, setIsResetSelection] = useState<boolean>(false);
const [showTable, setShowTable] = useState(false);
// const [tableData, setTableData] = useState<any[]>([]);
// const [tableCount, setTableCount] = useState(0);
// const [curPage, setCurPage] = useState(1);
// const [yearFilter, setYearFilter] = useState({ start: "", end: "" });
const [ctpvCd, setCtpvCd] = useState('0');
const [sggCd, setSggCd] = useState('0');
const [reportGubun, setReportGubun] = useState("0");
const [clsfSystmSn, setClsfSystmSn] = useState(''); // 자료관 기본계획 일련번호
const {
tableData,
tableCount,
isLoading,
yearFilter,
curPage,
setTableData,
setTableCount,
setIsLoading,
setYearFilter,
setCurPage
} = useSearchModalStore();
const { ctpv, sgg } = useStore();
const {
isOpen: isReportFilesModalOpen,
setIsOpen: setIsReportFilesModalOpen,
dcTree: reportOriginData,
fetchDcTree
} = useReportTabStore(); // 모달 상태 관리 스토어
const { REPORT_TYPES, flattenFolders, addFileName } = useReportTab();
useEffect(() => {
fetchDcTree("기본계획"); // 하수정비기본계획 report
}, []);
useEffect(() => {
// filterParams 초기화
setCtpvCd('0');
setSggCd('0');
setReportGubun('0');
setYearFilter({ start: "", end: "" });
setShowTable(false);
setTableData([]);
setTableCount(0);
setCurPage(1); // 페이지를 1로 초기화
setIsResetPage(true); // 페이지를 초기화
setIsResetSelection(true); // 선택 초기화
}, [open]);
useEffect(() => {
const newCtpvCd = ctpv.find((n) => n.text === ctpvNm)?.val || '0';
const newSggCd = sgg.find((n) => n.text === sggNm)?.val || '0';
setCtpvCd(newCtpvCd);
setSggCd(newSggCd);
}, [ctpvNm, sggNm]);
// ctpvCd, sggCd가 업데이트된 후에 fetchFilteredData 실행
useEffect(() => {
if (!isSearch && ctpvCd && sggCd) {
fetchFilteredData();
}
}, [reportOriginData, ctpvCd, sggCd, isSearch]);
// 페이지 변경 시마다 데이터 fetch
useEffect(() => {
// showTable이 true일 때만 fetch (최초 렌더 방지)
setIsResetPage(false);
if (showTable) fetchFilteredData();
}, [curPage]);
const handleCtpvCdChange = (sidoId: string) => {
setCtpvCd(sidoId);
setSggCd('0');
}
// 조회 버튼 클릭 시 1페이지로 이동 및 데이터 초기화
const handleSearch = () => {
setCurPage(1); // 페이지를 1로 초기화
fetchFilteredData();
setIsResetPage(true); // 페이지를 초기화
setIsResetSelection(false); // 선택 초기화
setShowTable(true);
};
// 하수도정비기본계획 테이블 cellClick 핸들러
const handleReportCellClick = (index: number | undefined) => {
if (typeof index === "number" && tableData[index]) {
const reportData = tableData[index];
if (reportData.fileSn !== "") {
setClsfSystmSn(reportData.clsfSystmSn); // 자료관 기본계획 일련번호 설정
setIsReportFilesModalOpen(true); // 모달 열기
}
}
};
// 검색 API 호출 함수
const fetchFilteredData = async () => {
setIsLoading(true); // 로딩 시작
setIsResetSelection(true); // 선택 초기화
try {
const reportData = flattenFolders(reportOriginData);
let filterReportData = reportData;
if (ctpvCd !== '0') { // 시도검색
const sidoOption = ctpv.find((n) => n.val.trim() === ctpvCd.trim());
filterReportData = filterReportData.filter(item => item.sido === sidoOption?.text);
}
if (sggCd !== '0') { // 시군구검색
const sigunguOption = sgg.find((n) => String(n.val) === String(sggCd));
filterReportData = filterReportData.filter(item => item.sigungu === sigunguOption?.text);
}
if (reportGubun !== "0") { // 보고서구분검색
const reportGubunOption = REPORT_TYPES.find((n) => n.val === reportGubun);
filterReportData = filterReportData.filter(item => item.reportGubun === reportGubunOption?.text);
}
const { start, end } = yearFilter;
if (start && !end) //승인년도검색
filterReportData = filterReportData.filter(item => (item.approvalYear) ? item.approvalYear >= yearFilter.start + "년" : null);
else if (!start && end)
filterReportData = filterReportData.filter(item => (item.approvalYear) ? item.approvalYear <= yearFilter.end + "년" : null);
else if (start && end) {
filterReportData = filterReportData.filter(item => {
return (item.approvalYear) ? item.approvalYear >= yearFilter.start + "년" && item.approvalYear <= yearFilter.end + "년" : null;
});
}
// if (gubun !== "") { // 구분검색 (추후 사용할 수도 있을 것 같아 남겨둠)
// filterReportData = filterReportData.filter(item => (item.plantZoneGubun) ? String(item.plantZoneGubun).includes(String(gubun)) : null);
// }
setTableCount(filterReportData.length || 0);
filterReportData = filterReportData.slice((curPage - 1) * 10, curPage * 10); // paging
filterReportData = filterReportData?.map((item, index) => ({ ...item, no: (curPage - 1) * 10 + index + 1 }));
addFileName(filterReportData).then(data => {
filterReportData = data;
setTableData(filterReportData || []);
setIsResetSelection(false); // 데이터 설정 후 선택 초기화 해제
});
setTableData(filterReportData || []);
setShowTable(true);
} catch (err) {
console.error('API ERROR:', err);
setTableData([]);
setIsResetSelection(false); // 에러 시에도 선택 초기화 해제
} finally {
setIsLoading(false); // 로딩 종료
}
};
return (
<>
<div className={`relative flex flex-col border rounded-lg pt-5 px-5 w-full ${isSearch ? "min-h-[10vh]" : "min-h-[6vh]"}`}>
{isSearch && (
<>
<div className="flex items-center mb-3">
<CtpvSggPrcsFcltSelectBox
sido={ctpvCd}
sigungu={sggCd}
onSidoChange={(sidoId: string) => { handleCtpvCdChange(sidoId) }}
onSigunguChange={setSggCd}
setIsLoading={setIsLoading}
/>
</div>
</>
)}
<div className="flex items-center mb-6">
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">보고서 구분</Label>
<SelectBox className="w-44 mr-8"
selectClass="rounded-lg bg-white"
label=""
selectArray={[...REPORT_TYPES]}
value={reportGubun} onSelectValue={val => setReportGubun(String(val))}
selectContentClass="z-[101]" labelClass='whitespace-nowrap text-xl text-gray-600 pr-2'
/>
{/* 승인년도 필터 */}
<YearRangeSelectBox
start={yearFilter.start}
end={yearFilter.end}
onChange={range => setYearFilter({ ...yearFilter, start: range.start, end: range.end })}
startLabel="승인년도" endLabel=""
/>
{/* 조회 버튼 */}
<div className="flex-1 flex justify-end">
<Button className="text-base py-2" onClick={handleSearch}>조회</Button>
</div>
</div>
</div>
<div className="h-[79vh] w-full overflow-hidden rounded-lg border">
{/* 테이블2 영역 (가로/세로 스크롤 가능) */}
<div className="flex-1 px-2 pb-2">
<LoadingOverlay isLoading={isLoading} />
<UiTable
headName={(() => {
return (<>조회 결과 ({tableCount.toLocaleString('ko-KR')})건</>);
})()
}
tableData={tableData}
headlist={reportTableHeadlist}
pageSize={10}
total={tableCount}
cellClick={handleReportCellClick}
onPageChange={setCurPage}
resetPageNo={isResetPage}
resetSelection={isResetSelection}
/>
</div>
</div>
{/* 모달 컴포넌트 */}
{isReportFilesModalOpen && <ReportFilesModal clsfSystmSn={clsfSystmSn} />}
</>
);
};
export default React.memo(ReportTab);