/** * ➡️ 경로 : 하수관망도 메인 페이지 (오른쪽 돋보기 모양 클릭) - 검색 모달 창 내 하수도정비기본계획보고서 탭 * * 📄 Report Tab * Next.js App Router (app/) 기반 검색 모달 보고서 탭 컴포넌트 * * - 하수도정비 기본계획 보고서 목록 표시 및 필터링 * - 시도/시군구/구분/보고서 구분/승인년도 기반 검색 기능 * - 테이블 데이터 표시 및 페이지네이션 지원 * - 파일명 클릭 시 대표 파일 바로 다운로드 * - 탭 진입 시 기본 조건 자동 조회 지원 * - Zustand를 통한 모달 상태 및 데이터 관리 * - 로딩 오버레이 및 선택 초기화 기능 */ 'use client'; import React, { useEffect, useMemo, useRef, 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'; // 하수도정비 기본계획 보고서 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 normalizeYear = (value: string) => { if (!value || value === '0' || value === '9999') return ''; return value; }; const getApprovalYearNumber = (value: string | number | undefined) => { const normalized = String(value ?? '').replace('년', '').trim(); const parsed = Number(normalized); return Number.isFinite(parsed) ? parsed : null; }; const ReportTab: React.FC = ({ open, isSearch = true, ctpvNm, sggNm }) => { const [isResetPage, setIsResetPage] = useState(false); const [isResetSelection, setIsResetSelection] = useState(false); const [showTable, setShowTable] = useState(false); const [ctpvCd, setCtpvCd] = useState('0'); const [sggCd, setSggCd] = useState('0'); const [plantZoneGubun, setPlantZoneGubun] = useState('0'); const [reportGubun, setReportGubun] = useState('0'); const hasAutoFetchedRef = useRef(false); const { tableData, tableCount, isLoading, yearFilter, curPage, setTableData, setTableCount, setIsLoading, setYearFilter, setCurPage } = useSearchModalStore(); const { ctpv, sgg } = useStore(); const { dcTree: reportOriginData, fetchDcTree } = useReportTabStore(); const { REPORT_TYPES, flattenFolders, addFileName, downloadReportFile } = useReportTab(); const flattenedReportData = useMemo(() => flattenFolders(reportOriginData), [reportOriginData]); const plantZoneOptions = useMemo(() => { const uniqueOptions = Array.from( new Set( flattenedReportData .map((item) => String(item.plantZoneGubun ?? '').trim()) .filter((item) => item.length > 0) ) ).sort((a, b) => a.localeCompare(b, 'ko')); return [ { val: '0', text: '전체' }, ...uniqueOptions.map((item) => ({ val: item, text: item })), ]; }, [flattenedReportData]); useEffect(() => { void fetchDcTree('기본계획'); }, [fetchDcTree]); useEffect(() => { hasAutoFetchedRef.current = false; setCtpvCd('0'); setSggCd('0'); setPlantZoneGubun('0'); setReportGubun('0'); setYearFilter({ start: '', end: '' }); setShowTable(false); setTableData([]); setTableCount(0); setCurPage(1); setIsResetPage(true); setIsResetSelection(true); }, [open, setCurPage, setTableCount, setTableData, setYearFilter]); 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); }, [ctpv, ctpvNm, sgg, sggNm]); useEffect(() => { if (!open || flattenedReportData.length === 0 || hasAutoFetchedRef.current) return; hasAutoFetchedRef.current = true; void fetchFilteredData(1); }, [flattenedReportData.length, open]); useEffect(() => { setIsResetPage(false); if (showTable) { void fetchFilteredData(); } }, [curPage]); const handleCtpvCdChange = (sidoId: string) => { setCtpvCd(sidoId); setSggCd('0'); }; const handleSearch = () => { hasAutoFetchedRef.current = true; setCurPage(1); void fetchFilteredData(1); setIsResetPage(true); setIsResetSelection(false); setShowTable(true); }; const handleReportCellClick = (index: number | undefined, headId: string) => { if (headId !== 'fileName') return; if (typeof index !== 'number' || !tableData[index]) return; const reportData = tableData[index]; if (!reportData.fileSn) return; void (async () => { setIsLoading(true); try { await downloadReportFile(reportData); } finally { setIsLoading(false); } })(); }; const fetchFilteredData = async (page = curPage) => { setIsLoading(true); setIsResetSelection(true); try { let filterReportData = flattenedReportData; if (ctpvCd !== '0') { const sidoOption = ctpv.find((n) => String(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 (plantZoneGubun !== '0') { filterReportData = filterReportData.filter((item) => String(item.plantZoneGubun ?? '') === plantZoneGubun); } if (reportGubun !== '0') { const reportGubunOption = REPORT_TYPES.find((n) => n.val === reportGubun); filterReportData = filterReportData.filter((item) => item.reportGubun === reportGubunOption?.text); } const normalizedStartYear = normalizeYear(yearFilter.start); const normalizedEndYear = normalizeYear(yearFilter.end); if (normalizedStartYear || normalizedEndYear) { filterReportData = filterReportData.filter((item) => { const approvalYear = getApprovalYearNumber(item.approvalYear); if (approvalYear === null) return false; if (normalizedStartYear && approvalYear < Number(normalizedStartYear)) return false; if (normalizedEndYear && approvalYear > Number(normalizedEndYear)) return false; return true; }); } setTableCount(filterReportData.length || 0); const pagedReportData = filterReportData .slice((page - 1) * 10, page * 10) .map((item, index) => ({ ...item, no: (page - 1) * 10 + index + 1 })); const reportRows = await addFileName(pagedReportData); setTableData(reportRows || []); setShowTable(true); setIsResetSelection(false); } catch (err) { console.error('API ERROR:', err); setTableData([]); setIsResetSelection(false); } finally { setIsLoading(false); } }; return ( <>
{isSearch && ( { handleCtpvCdChange(sidoId); }} onSigunguChange={setSggCd} setIsLoading={setIsLoading} /> )} setYearFilter({ start: normalizeYear(range.start), end: normalizeYear(range.end), })} startLabel="승인년도" endLabel="" /> setPlantZoneGubun(String(val))} selectContentClass="z-[101]" /> setReportGubun(String(val))} selectContentClass="z-[101]" />
조회 결과} tableData={tableData} headlist={reportTableHeadlist} pageSize={10} total={tableCount} cellClick={handleReportCellClick} cellClassName={(row, headId) => { if (headId !== 'fileName') return ''; if (!row.fileSn) return '!text-gray-400'; return '!text-left !text-blue-600 underline underline-offset-4 hover:!text-blue-800'; }} onPageChange={setCurPage} resetPageNo={isResetPage} resetSelection={isResetSelection} />
); }; export default React.memo(ReportTab);