"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Button, Table, TableBody, TableCell, TableHeader, TableHead, TableRow, } from "@common/components"; import { StandardPopupLayout } from "@common/business_components"; import UiPageNations from "@common/business_components/ui/table/uiPageNations"; import { downloadExcelWithKoreanHeaders } from "@/shared/lib/download-excel"; import { getData } from "@/components/lib/api"; import LoadingOverlay from "@/components/ui/LoadingBar"; import useStore from "@/store/useStore"; import { useAlertStore } from "@/shared/lib"; import { useUserStore } from "@/shared/lib/use-user-store"; import { openWindowPopup } from "@/features/windowPopup/lib/openWindowPopup"; import { windowPopupDefinitions } from "@/features/windowPopup/model/popupDefinitions"; import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable"; import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel"; import { PIPE_ANALYSIS_SUMMARY_DEFAULT_FILTERS, PIPE_ANALYSIS_TYPE_OPTIONS, applyScopeToSummaryFilters, buildPipeAnalysisFileDate, buildPipeAnalysisYearMaterialGroups, buildPipeAnalysisScope, buildPipeAnalysisSummaryDownloadHeaders, buildPipeAnalysisSummaryHeadList, buildPipeAnalysisSummaryViewRows, fetchPipeAnalysisZoneOptions, resolvePipeAnalysisTypeLabel, type PipeAnalysisMaterialColumn, type PipeAnalysisSummaryFilters, type PipeAnalysisSummaryResponse, type PipeAnalysisSummaryViewRow, type PipeAnalysisYearMaterialGroup, } from "./pipeAnalysisUtils"; const SUMMARY_FILE_PREFIX: Record = { YEAR: "관로분석_경과년도별", MATERIAL: "관로분석_관재질별", YEAR_MATERIAL: "관로분석_경과년도별_관재질별", }; const PIPE_ANALYSIS_SUMMARY_PAGE_SIZE = 10; const toExcelColumnName = (index: number) => { let current = index; let result = ""; while (current > 0) { const remainder = (current - 1) % 26; result = String.fromCharCode(65 + remainder) + result; current = Math.floor((current - 1) / 26); } return result || "A"; }; const formatGridNumber = (value: unknown) => { const num = Number(value ?? 0); if (!Number.isFinite(num)) { return String(value ?? ""); } return num.toLocaleString("ko-KR", { minimumFractionDigits: 0, maximumFractionDigits: 3, }); }; const resolveErrorMessage = (error: unknown, fallbackMessage: string) => error instanceof Error && error.message.trim() ? error.message : fallbackMessage; export default function PipeAnalysisPopupPage() { const alert = useAlertStore((state) => state.setMessage); const { ctpv, sgg, fetchCtpv, fetchSgg } = useStore(); const userInfo = useUserStore((state) => state.userInfo); const scope = useMemo(() => buildPipeAnalysisScope(userInfo), [userInfo]); const [draftFilters, setDraftFilters] = useState( () => applyScopeToSummaryFilters(PIPE_ANALYSIS_SUMMARY_DEFAULT_FILTERS, scope), ); const [appliedFilters, setAppliedFilters] = useState(() => applyScopeToSummaryFilters(PIPE_ANALYSIS_SUMMARY_DEFAULT_FILTERS, scope), ); const [materialColumns, setMaterialColumns] = useState< PipeAnalysisMaterialColumn[] >([]); const [rows, setRows] = useState([]); const [zoneOptions, setZoneOptions] = useState< Array<{ value: string; label: string }> >([]); const [summaryCurrentPage, setSummaryCurrentPage] = useState(1); const [isLoading, setIsLoading] = useState(false); const initializedRef = useRef(false); useEffect(() => { if (ctpv.length === 0) fetchCtpv(); if (sgg.length === 0) fetchSgg(); }, [ctpv.length, fetchCtpv, fetchSgg, sgg.length]); useEffect(() => { setDraftFilters((current) => applyScopeToSummaryFilters(current, scope)); setAppliedFilters((current) => applyScopeToSummaryFilters(current, scope)); }, [scope]); useEffect(() => { let mounted = true; (async () => { try { const zones = await fetchPipeAnalysisZoneOptions({ ctpvCd: draftFilters.ctpvCd, sggCd: draftFilters.sggCd, scopeRegionCode: scope.regionCode, }); if (!mounted) return; setZoneOptions([{ value: "0", label: "전체" }, ...zones]); } catch (error) { console.error("처리구역 목록 조회 중 오류:", error); if (!mounted) return; setZoneOptions([{ value: "0", label: "전체" }]); } })(); return () => { mounted = false; }; }, [draftFilters.ctpvCd, draftFilters.sggCd, scope.regionCode]); const fetchSummary = useCallback( async (nextFilters: PipeAnalysisSummaryFilters) => { const params = new URLSearchParams({ analysisType: nextFilters.analysisType, }); if (nextFilters.ctpvCd !== "0") params.set("ctpvCd", nextFilters.ctpvCd); if (nextFilters.sggCd !== "0") params.set("sggCd", nextFilters.sggCd); if (nextFilters.swtrmtZoneNm !== "0") params.set("swtrmtZoneNm", nextFilters.swtrmtZoneNm); if (nextFilters.scopeRegionCode) params.set("scopeRegionCode", nextFilters.scopeRegionCode); const response = (await getData( `v1/fclts/ppl-analysis/summaries?${params.toString()}`, )) as PipeAnalysisSummaryResponse; const nextMaterialColumns = Array.isArray(response?.materialColumns) ? response.materialColumns : []; setMaterialColumns(nextMaterialColumns); setRows(buildPipeAnalysisSummaryViewRows(response, ctpv, sgg)); }, [ctpv, sgg], ); const handleSearch = useCallback( async (nextFilters = draftFilters) => { setIsLoading(true); try { const scopedFilters = applyScopeToSummaryFilters(nextFilters, scope); setDraftFilters(scopedFilters); setAppliedFilters(scopedFilters); setSummaryCurrentPage(1); await fetchSummary(scopedFilters); } catch (error) { console.error("관로분석 집계 조회 중 오류:", error); alert( resolveErrorMessage( error, "관로분석 집계 조회 중 오류가 발생했습니다.", ), ); setRows([]); setMaterialColumns([]); } finally { setIsLoading(false); } }, [alert, draftFilters, fetchSummary, scope], ); useEffect(() => { if (initializedRef.current) return; if (ctpv.length === 0 || sgg.length === 0) return; initializedRef.current = true; void handleSearch(applyScopeToSummaryFilters(draftFilters, scope)); }, [ctpv.length, draftFilters, handleSearch, scope, sgg.length]); const handleDownload = async () => { if (rows.length === 0) { alert("다운로드할 데이터가 없습니다."); return; } try { const exportHeadList = buildPipeAnalysisSummaryHeadList( appliedFilters.analysisType, materialColumns, ); const headerRange = `A1:${toExcelColumnName(exportHeadList.length)}1`; const mergeRanges = appliedFilters.analysisType === "YEAR_MATERIAL" ? (() => { const ranges: string[] = []; let currentRow = 2; orderedYearMaterialGroups.forEach((group) => { const rowSpan = group.rows.length; if (rowSpan > 1) { ["A", "B", "C", "D", "E"].forEach((column) => { ranges.push( `${column}${currentRow}:${column}${currentRow + rowSpan - 1}`, ); }); } currentRow += rowSpan; }); return ranges; })() : []; const exportRows = appliedFilters.analysisType === "YEAR_MATERIAL" ? orderedYearMaterialGroups.flatMap((group) => group.rows.map((yearRow, rowIndex) => { const exportRow: Record = { rowNo: rowIndex === 0 ? group.rowNo : "", ctpvNm: rowIndex === 0 ? group.ctpvNm : "", sggNm: rowIndex === 0 ? group.sggNm : "", swtrmtZoneNm: rowIndex === 0 ? group.swtrmtZoneNm : "", totalLengthKm: rowIndex === 0 ? group.totalLengthKm : "", elapsedYearLabel: yearRow.elapsedYearLabel, }; materialColumns.forEach((column) => { exportRow[column.key] = yearRow.metrics[column.key] ?? 0; }); return exportRow; }), ) : rows.map((row) => { const exportRow: Record = {}; exportHeadList.forEach((head) => { exportRow[head.id] = row[head.id]; }); return exportRow; }); await downloadExcelWithKoreanHeaders({ data: exportRows as unknown as JSON[], fileName: `${SUMMARY_FILE_PREFIX[appliedFilters.analysisType]}_${buildPipeAnalysisFileDate()}`, sheetNameArray: ["관로분석 현황"], headerRange, headerMapping: buildPipeAnalysisSummaryDownloadHeaders( appliedFilters.analysisType, materialColumns, ), mergeRanges, }); } catch (error) { console.error("관로분석 집계 다운로드 중 오류:", error); alert( resolveErrorMessage( error, "관로분석 집계 다운로드 중 오류가 발생했습니다.", ), ); } }; const handleCellClick = ( row: PipeAnalysisSummaryViewRow, columnId: string, ) => { if (row.__isNoZone || row.__isSummaryTotal) return; const cellFilter = row.__cellFilters[columnId]; if (!cellFilter) return; const popup = openWindowPopup("pipeAnalysisDetailPopup", { analysisType: appliedFilters.analysisType, ctpvCd: String(row.__base.ctpvCd || appliedFilters.ctpvCd), sggCd: String(row.__base.sggCd || appliedFilters.sggCd || "0"), swtrmtZoneNm: String( row.__base.swtrmtZoneNm || appliedFilters.swtrmtZoneNm, ), elapsedYearBucket: cellFilter.elapsedYearBucket ?? "", materialCode: cellFilter.materialCode ?? "", scopeRegionCode: scope.regionCode, }); if (!popup) { alert("팝업이 차단되었습니다. 브라우저 팝업 허용 설정을 확인해주세요."); } }; const handleYearMaterialCellClick = ( group: PipeAnalysisYearMaterialGroup, row: PipeAnalysisYearMaterialGroup["rows"][number], columnId: string, ) => { const cellFilter = row.cellFilters[columnId]; if (!cellFilter) return; const popup = openWindowPopup("pipeAnalysisDetailPopup", { analysisType: appliedFilters.analysisType, ctpvCd: String(group.base.ctpvCd || appliedFilters.ctpvCd), sggCd: String(group.base.sggCd || appliedFilters.sggCd || "0"), swtrmtZoneNm: String( group.base.swtrmtZoneNm || appliedFilters.swtrmtZoneNm, ), elapsedYearBucket: cellFilter.elapsedYearBucket ?? "", materialCode: cellFilter.materialCode ?? "", scopeRegionCode: scope.regionCode, }); if (!popup) { alert("팝업이 차단되었습니다. 브라우저 팝업 허용 설정을 확인해주세요."); } }; const headList = useMemo( () => buildPipeAnalysisSummaryHeadList( appliedFilters.analysisType, materialColumns, ), [appliedFilters.analysisType, materialColumns], ); const yearMaterialGroups = useMemo( () => appliedFilters.analysisType === "YEAR_MATERIAL" ? buildPipeAnalysisYearMaterialGroups(rows, materialColumns) : [], [appliedFilters.analysisType, materialColumns, rows], ); const orderedYearMaterialGroups = useMemo( () => yearMaterialGroups.map((group, index) => ({ ...group, rowNo: yearMaterialGroups.length - index, })), [yearMaterialGroups], ); const useHorizontalScrollTable = appliedFilters.analysisType !== "YEAR"; const summaryPageOffset = (summaryCurrentPage - 1) * PIPE_ANALYSIS_SUMMARY_PAGE_SIZE; const pagedRows = useMemo( () => appliedFilters.analysisType === "YEAR_MATERIAL" ? rows : rows.slice( summaryPageOffset, summaryPageOffset + PIPE_ANALYSIS_SUMMARY_PAGE_SIZE, ), [appliedFilters.analysisType, rows, summaryPageOffset], ); const clickableHeadIds = useMemo( () => headList .filter((head) => rows.some((row) => Boolean(row.__cellFilters[head.id])), ) .map((head) => head.id), [headList, rows], ); const visibleYearMaterialGroups = useMemo( () => orderedYearMaterialGroups.slice( summaryPageOffset, summaryPageOffset + PIPE_ANALYSIS_SUMMARY_PAGE_SIZE, ), [orderedYearMaterialGroups, summaryPageOffset], ); const sggOptions = useMemo( () => draftFilters.ctpvCd === "0" ? sgg : sgg.filter((item) => String(item.val).startsWith(draftFilters.ctpvCd.slice(0, 2)), ), [draftFilters.ctpvCd, sgg], ); return (
{appliedFilters.analysisType === "YEAR_MATERIAL" ? (

{resolvePipeAnalysisTypeLabel(appliedFilters.analysisType)} 목록

{headList.map((head) => ( {head.id === "rowNo" ? "순번" : head.title} ))} {visibleYearMaterialGroups.length > 0 ? ( visibleYearMaterialGroups.flatMap((group) => group.rows.map((yearRow, rowIndex) => ( {rowIndex === 0 ? ( <> {group.rowNo} {group.ctpvNm} {group.sggNm} {group.swtrmtZoneNm} {formatGridNumber(group.totalLengthKm)} ) : null} {yearRow.elapsedYearLabel} {materialColumns.map((column) => { const value = Number( yearRow.metrics[column.key] ?? 0, ); const isClickable = value > 0 && Boolean(yearRow.cellFilters[column.key]); return ( { if (isClickable) { handleYearMaterialCellClick( group, yearRow, column.key, ); } }} > {formatGridNumber(value)} ); })} )), ) ) : ( 조회 결과가 없습니다. )}
{orderedYearMaterialGroups.length > PIPE_ANALYSIS_SUMMARY_PAGE_SIZE ? (
) : null}
) : null} {appliedFilters.analysisType !== "YEAR_MATERIAL" ? ( > } headlist={headList} title={`${resolvePipeAnalysisTypeLabel(appliedFilters.analysisType)} 목록`} total={rows.length} page={summaryCurrentPage} pageSize={PIPE_ANALYSIS_SUMMARY_PAGE_SIZE} onPageChange={setSummaryCurrentPage} scrollMode={useHorizontalScrollTable ? "horizontal" : "none"} minTableWidth={useHorizontalScrollTable ? "160rem" : "100%"} clickableHeadIds={clickableHeadIds} onCellClick={(row, _rowIndex, columnId) => handleCellClick(row, columnId) } rowId={(row, index) => `${String(row.__base.ctpvCd)}-${String(row.__base.sggCd)}-${String(row.__base.swtrmtZoneNm)}-${index}` } actions={ } /> ) : null}
); }