/** * 화면명: 시도별 노후관로현황 * 기준연도별 시도 단위 노후관로 현황을 조회하고 엑셀로 내려받는다. */ "use client"; import { getSnRowNumber } from "@/shared/lib/row-number"; import { useCallback, useEffect, useMemo, useState } from "react"; import { Button, Input } from "@common/components"; import { StandardDataTable, StandardPopupLayout, } from "@common/business_components"; import { getCoreRowModel, getPaginationRowModel, useReactTable, type ColumnDef, type PaginationState, } from "@tanstack/react-table"; import { getRawJson } from "@/shared/api/sewerage-api"; import LoadingOverlay from "@/components/ui/loading-bar"; import { useAlertStore } from "@/shared/lib"; import { windowPopupDefinitions } from "@/features/window-popup/model/popup-definitions"; import { AGED_PIPE_STATUS_BY_CTPV_DEFAULT_ELAPSED_YEARS, buildAgedPipeStatusYearOptions, getAgedPipeStatusByCtpvValidationMessage, normalizeAgedPipeStatusByCtpvResponse, normalizeAgedPipeStatusByCtpvFilters, toAgedPipeStatusByCtpvRows, type AgedPipeStatusByCtpvFilters, type AgedPipeStatusByCtpvResponse, type AgedPipeStatusByCtpvRow, } from "./aged-pipe-status-by-ctpv-utils"; import { downloadAgedPipeStatusByCtpvExcel } from "./aged-pipe-status-by-ctpv-export"; import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel"; // CS 목록과 동일하게 한 페이지에는 최대 10개 시도만 표시한다. const AGED_PIPE_STATUS_PAGE_SIZE = 10; export default function AgedPipeStatusByCtpvPopupPage() { const alert = useAlertStore((state) => state.setMessage); // 조회조건 입력 상태다. 기본값 30년, 최근 연도 기준 단년 조회로 시작한다. const [filters, setFilters] = useState(() => normalizeAgedPipeStatusByCtpvFilters({ elapsedYears: AGED_PIPE_STATUS_BY_CTPV_DEFAULT_ELAPSED_YEARS, }), ); // 화면 목록을 마지막으로 조회한 조건이며 페이지 이동·Excel이 동일하게 사용한다. const [appliedFilters, setAppliedFilters] = useState(() => normalizeAgedPipeStatusByCtpvFilters({ elapsedYears: AGED_PIPE_STATUS_BY_CTPV_DEFAULT_ELAPSED_YEARS, }), ); // 서버 응답을 화면 그리드에 맞게 평탄화한 row 목록이다. const [rows, setRows] = useState([]); // 동적 컬럼 생성을 위한 기준년도 목록이다. const [years, setYears] = useState([]); // 조회/다운로드 중 로딩 overlay를 제어한다. const [isLoading, setIsLoading] = useState(false); // 사용자에게 현재 다운로드 상태를 보여주는 진행 문구다. const [downloadProgressText, setDownloadProgressText] = useState(""); const [currentPage, setCurrentPage] = useState(1); const yearOptions = useMemo(() => buildAgedPipeStatusYearOptions(), []); const columns = useMemo[]>( () => [ { id: "rowNumber", header: "순번", cell: ({ row }) => getSnRowNumber({ total: rows.length, rowIndex: row.index, }), }, { accessorKey: "ctpvNm", header: "시도" }, ...years.map((year) => ({ id: `year_${year}`, header: `${year}년`, columns: [ { accessorKey: `year${year}TotalLengthKm`, header: "전체관로연장(km)", }, { accessorKey: `year${year}AgedLengthKm`, header: "노후관로연장(km)", }, { accessorKey: `year${year}AgedRate`, header: "노후화율(%)", }, ], })), ], [rows.length, years], ); const pagination: PaginationState = { pageIndex: currentPage - 1, pageSize: AGED_PIPE_STATUS_PAGE_SIZE, }; const table = useReactTable({ data: rows, columns, state: { pagination }, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), onPaginationChange: (updater) => { const next = typeof updater === "function" ? updater(pagination) : updater; setCurrentPage(next.pageIndex + 1); }, }); const loadRows = useCallback( async (nextFilters: AgedPipeStatusByCtpvFilters) => { setIsLoading(true); try { const normalized = normalizeAgedPipeStatusByCtpvFilters(nextFilters); const queryParams = new URLSearchParams({ startYear: String(normalized.startYear), endYear: String(normalized.endYear), elapsedYears: String(normalized.elapsedYears), }); const rawResponse = await getRawJson( `v1/swagePpl/agedStatusByCtpv?${queryParams.toString()}`, ); const response = normalizeAgedPipeStatusByCtpvResponse( rawResponse, ) as AgedPipeStatusByCtpvResponse; setYears(Array.isArray(response?.years) ? response.years : []); setRows(toAgedPipeStatusByCtpvRows(response)); setCurrentPage(1); setAppliedFilters(normalized); } catch (error) { console.error("시도별 노후관로현황 조회 중 오류:", error); setYears([]); setRows([]); } finally { setIsLoading(false); } }, [], ); useEffect(() => { const initialFilters = normalizeAgedPipeStatusByCtpvFilters({ elapsedYears: AGED_PIPE_STATUS_BY_CTPV_DEFAULT_ELAPSED_YEARS, }); void loadRows(initialFilters); }, [loadRows]); const handleSearch = async () => { const validationMessage = getAgedPipeStatusByCtpvValidationMessage(filters); if (validationMessage) { alert(validationMessage); return; } const normalized = normalizeAgedPipeStatusByCtpvFilters(filters); setFilters(normalized); await loadRows(normalized); }; const handleDownload = async () => { const validationMessage = getAgedPipeStatusByCtpvValidationMessage(filters); if (validationMessage) { alert(validationMessage); return; } const normalizedDraft = normalizeAgedPipeStatusByCtpvFilters(filters); if (JSON.stringify(normalizedDraft) !== JSON.stringify(appliedFilters)) { alert("조회조건이 변경되었습니다. 조회 후 Excel을 다운로드해주세요."); return; } try { setIsLoading(true); setDownloadProgressText( "시도별 노후관로현황 Excel 파일을 준비하고 있습니다.", ); await downloadAgedPipeStatusByCtpvExcel(appliedFilters); setDownloadProgressText(""); } catch (error) { console.error("시도별 노후관로현황 다운로드 중 오류:", error); setDownloadProgressText(""); alert( error instanceof Error ? error.message : "시도별 노후관로현황 Excel 다운로드 중 오류가 발생했습니다.", ); } finally { setIsLoading(false); } }; return ( {downloadProgressText ? (
{downloadProgressText}
) : null}
0 ? "120rem" : "100%"} actions={ } flushOuterMargin />
); }