From 0506464a979821a796f135bd3e5cb9d045370af5 Mon Sep 17 00:00:00 2001 From: swcho Date: Tue, 2 Sep 2025 17:49:22 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20searchModal=20UI,=20=EB=B9=84?= =?UTF-8?q?=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../map/searchModal/searchModal.tsx | 1013 +---------------- .../map/searchModal/useSearchModal.tsx | 1000 ++++++++++++++++ 2 files changed, 1035 insertions(+), 978 deletions(-) create mode 100644 apps/sn/app/(map-layout)/map/searchModal/useSearchModal.tsx diff --git a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx index fdb52a1c2..b9eb29a10 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx @@ -1,95 +1,49 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import LoadingOverlay from '@/components/ui/LoadingBar'; -import { UiModal, UITab, UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui'; +import { UiModal, UITab, UiTable } from '@common/business_components/ui'; import { Button } from '@common/components/ui'; import axios from 'axios'; import { ExclnSewagePumpCol2, MsSwageMhlCol2, MsSwagePplCol2, stofcbrCol2, SwtrmtFcltCol2, SwtrmtPrcsZoneCol2, - MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES, MS_SWAGE_MHL_CODES, MS_SWAGE_MHL_USE_CODES, - MsSwagePplExcelCol, SwtrmtFcltExcelCol, SwtrmtPrcsZoneExcelCol, MsSwageMhlExcelCol, ExclnSewagePumpExcelCol, stofcbrExcelCol + MS_SWAGE_PPL_USE_CODES, MS_SWAGE_MHL_CODES, MS_SWAGE_MHL_USE_CODES, } from '../components/common/table'; import { getData } from "@/components/lib/api"; import BasicTab, { BASIC_TYPES } from './basicTab'; import OperationTab, { operationCleaningTableHeadlist, operationInspectionTableHeadlist, operationMaintenanceTableHeadlist, operationTableHeadlist, WORK_TYPES } from './operationTab'; import ReportTab from './reportTab'; import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; -import useStore from '../../../store/useStore'; -import GisApp from "@common/business_components/ui/map/lib/gis.core"; -import { confirmToast } from '@/components/ui/toast' -import { toast, Toaster } from "react-hot-toast"; +import { Toaster } from "react-hot-toast"; +import { useSearchModal, OPERATION_TABLE_SIZE, type SearchModalProps } from './useSearchModal'; -const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`; - -const TABS = [ - { val: "basic", text: "기본정보" }, - { val: "operation", text: "운영정보" }, - { val: "report", text: "하수도정비기본계획보고서" }, -]; - -const OPERATION_TABLE_SIZE = 5; - -// 날짜 조합 함수 -const getDateString = ( - year: string, - month: string, - day: string, - defaultDate: string, -) => { - if (!year && !month && !day) return defaultDate; - const y = year || defaultDate.slice(0, 4); - const m = month || defaultDate.slice(4, 6); - const d = day || defaultDate.slice(6, 8); - return `${y}${m}${d}`; -}; - -interface SearchModalProps { - open: boolean; - onOpenChange: (open: boolean) => void; - onFeatureSelect: (row: any) => void; -} - -const SearchModal: React.FC = ({ - open, - onOpenChange, - onFeatureSelect, -}) => { - const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? "basic"); - const [basicType, setBasicType] = useState(BASIC_TYPES[0]?.val ?? ""); - const [workType, setWorkType] = useState(WORK_TYPES[0]?.val ?? ""); - const [showTable, setShowTable] = useState(false); - const [tableData, setTableData] = useState([]); - const [detailTableData, setDetailTableData] = useState([]); - const [tableCount, setTableCount] = useState(0); - const [detailTableCount, setDetailTableCount] = useState(0); - const [curPage, setCurPage] = useState(1); - const [detailCurPage, setDetailCurPage] = useState(1); - const [isLoading, setIsLoading] = useState(false); - const [isDetailLoading, setIsDetailLoading] = useState(false); - const [isResetPage, setIsResetPage] = useState(false); - const [isResetDetailPage, setIsResetDetailPage] = useState(false); - const [isResetSelection, setIsResetSelection] = useState(false); - const [isResetDetailSelection, setIsResetDetailSelection] = useState(false); - const [detailBizId, setDetailBizId] = useState(0); - const [ctpvCd, setCtpvCd] = useState("0"); - const [ctpvName, setCtpvName] = useState(''); - const [sggCd, setSggCd] = useState("0"); - const [sggName, setSggName] = useState(''); - const [start1, setStart1] = useState(""); - const [start2, setStart2] = useState(""); - const [end1, setEnd1] = useState(""); - const [end2, setEnd2] = useState(""); - const [var1, setVar1] = useState(""); - const [var2, setVar2] = useState(""); - const [yearFilter, setYearFilter] = useState({ start: "", end: "" }); - - const { ctpv, sgg, BIZ_TY_CD } = useStore(); - - - // 오늘 날짜 구하기 - const today = new Date(); - const todayYear = today.getFullYear().toString(); - const todayMonth = (today.getMonth() + 1).toString().padStart(2, "0"); - const todayDate = today.getDate().toString().padStart(2, "0"); +const SearchModal: React.FC = (props) => { + const { + open, onOpenChange, + TABS, + activeTab, setActiveTab, + basicType, setBasicType, + workType, setWorkType, + tableData, detailTableData, + tableCount, detailTableCount, + curPage, setCurPage, + detailCurPage, setDetailCurPage, + isLoading, isDetailLoading, + isResetPage, isResetDetailPage, + isResetSelection, isResetDetailSelection, + ctpvCd, setCtpvCd, setCtpvName, + sggCd, setSggCd, setSggName, + start1, setStart1, start2, setStart2, + end1, setEnd1, end2, setEnd2, + var1, setVar1, var2, setVar2, + yearFilter, setYearFilter, + handleSearch, + handleBasicCellClick, + handleOperationCellClick, + handleOperationResultCellClick, + basicExcelDownload, + operationExcelDownload, + operationResultExcelDownload, + viewMultipleFeature, + } = useSearchModal(props); const dynamicHeadlist: { id: string, title: string }[] = useMemo(() => { if (activeTab === 'basic') { @@ -117,910 +71,13 @@ const SearchModal: React.FC = ({ return []; }, [activeTab, workType]); - useEffect(() => { - // filterParams 초기화 - setStart1(""); - setStart2(""); - setEnd1(""); - setEnd2(""); - setVar1(""); - setVar2(""); - setCtpvCd("0"); - setSggCd("0"); - setYearFilter({ - start: "", - end: "", - }); - setShowTable(false); - setTableData([]); - setDetailTableData([]); - setTableCount(0); - setDetailTableCount(0); - setCurPage(1); // 페이지를 1로 초기화 - setDetailCurPage(1); // 페이지를 1로 초기화 - setIsResetPage(true); // 페이지를 초기화 - setIsResetDetailPage(true); // 디테일 페이지를 초기화 - setIsResetSelection(true); // 선택 초기화 - setIsResetDetailSelection(true); // 선택 초기화 - setDetailBizId(0); - // queryParams는 fetchFilteredData 내에서 매번 새로 생성되므로 별도 상태 초기화는 필요 없음 - }, [activeTab, basicType, workType]); - - // 페이지 변경 시마다 데이터 fetch - useEffect(() => { - // showTable이 true일 때만 fetch (최초 렌더 방지) - setIsResetPage(false); - setIsResetDetailPage(true); - setDetailBizId(0); - if (showTable) fetchFilteredData(); - }, [curPage]); - - // 페이지 변경 시마다 데이터 fetch - useEffect(() => { - setIsResetDetailSelection(false); - // showTable이 true일 때만 fetch (최초 렌더 방지) - if (showTable) { - handleOperationPageNation(Number(detailBizId)); - } - }, [detailCurPage]); - - - // 검색 API 호출 함수 - const fetchFilteredData = async () => { - setIsLoading(true); // 로딩 시작 - setIsResetSelection(true); // 선택 초기화 - setIsResetDetailSelection(true); // 선택 초기화 - - try { - let cqlFilters: string[] = []; - if (activeTab === 'basic') { - const queryParams = new URLSearchParams({ - lyrTypeNm: basicType, - page: `${curPage - 1}`, - size: '10', - sort: 'string', - }); - - if (basicType === 'tb_ms_swage_prcs_zone') cqlFilters = zoneCqlFilters(); // 하수처리구역 - else if (basicType === 'tb_il_swage_prcs_fclt') cqlFilters = facilityCqlFilters(); // 하수처리시설 - else if (basicType === 'tb_ms_swage_ppl') cqlFilters = pplCqlFilters(); // 하수관로 - else if (basicType === 'tb_ms_swage_mhl') cqlFilters = mhCqlFilters(); // 하수맨홀 - else if (basicType === 'tb_ms_swage_pmstn') cqlFilters = psmtnCqlFilters(); // 하수펌프장 - else if (basicType === 'tb_ms_stofcbr') cqlFilters = otflCqlFilters(); // 우수토실 - - if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and ')); - - const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`); - - // 🎯 각 아이템을 개별적으로 포맷팅하면서 일련번호 추가 - const dataWithRowNumber = (res.data.data || []).map((item: any, index: number) => { - // 🎯 먼저 각 아이템을 포맷팅 - const formattedItem = formatDisplayData(item); - - let ctpvCd = item?.ctpvNm ?? formattedItem?.ctpvNm ?? ''; - let sggCd = item?.sggNm ?? formattedItem?.sggNm ?? ''; - - if (!ctpvCd || !sggCd) { - const code = String(item?.pbadmsEmdCd ?? formattedItem?.pbadmsEmdCd ?? '')?.trim(); - if (!ctpvCd && code) ctpvCd = code.slice(0, 2); - if (!sggCd && code) sggCd = code.slice(0, 5); - } - - const matchedMhlCode = MS_SWAGE_MHL_CODES.find(code => code.val === item?.mqltCd?.trim()); - const matchedMhlUsgCode = MS_SWAGE_MHL_USE_CODES.find(code => code.val === item?.usgCd); - const ctpvNm = ctpv.find(code => code.val.trim() === ctpvCd)?.text || ctpvCd; - const sggNm = sgg.find(code => code.val.trim() === sggCd)?.text || '-'; - const usgCdObj = MS_SWAGE_PPL_USE_CODES.find(code => code.val === item?.usgCd?.trim()); - - // 추가 처리 및 일련번호 추가 - return { - ...formattedItem, - rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가 - scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item?.scsYn, // scsYn이 null이면 'N'으로 변경 - mqltCd: matchedMhlCode ? matchedMhlCode.text : item?.mqltCd, - usgCd: matchedMhlUsgCode ? matchedMhlUsgCode.text : (usgCdObj ? usgCdObj.text : item?.usgCd), - ctpvNm, - sggNm, - swtrmt_zone_nm: item.swtrmtZoneNm || item.swtrmt_zone_nm || '-', - }; - }); - - setTableData(dataWithRowNumber); - setTableCount(res.data.count || []); - - } else if (activeTab === 'operation') { - const RequestDTO: Record = { - page: `${curPage - 1}`, - size: `${OPERATION_TABLE_SIZE}`, - }; - if (var1) RequestDTO.bizInfoNm = var1; - if (var2 != '0' && var2) RequestDTO.bizTypeCd = var2; - // 수행기간 범위 - const startDate = getDateString( - yearFilter.start, - '', - '', - "19000101", - ); - const endDate = getDateString( - yearFilter.end, - '', - '', - `${todayYear}${todayMonth}${todayDate}`, - ); - RequestDTO.fromflfmtEndYmd = startDate; - RequestDTO.toflfmtEndYmd = endDate; - - // JSON.parse 사용으로 문자열 -> 배열 변환 - RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"')); - - const params = new URLSearchParams(RequestDTO).toString(); - - const res = await getData(`v1/bizInfo?${params}`); - - const mappedContent = (res.content || []).map((item: any, index: number) => { - const formattedItem = formatDisplayData(item); - const matched = BIZ_TY_CD.find(code => code.lwpoCd === item.bizTypeCd); - return { - ...formattedItem, - rowNumber: (curPage - 1) * OPERATION_TABLE_SIZE + index + 1, // 1부터 시작하는 일련번호 추가 - bizTypeCd: matched ? matched.lwpoCdNm : item.bizTypeCd, - }; - }); - - setTableData(mappedContent || []); - setTableCount(res.totalElements || 0); - setDetailTableData([]); - setDetailTableCount(0); - } - - setShowTable(true); - setIsResetSelection(false); - } catch (err) { - console.error('API ERROR:', err); - setTableData([]); - setIsResetSelection(false); - } finally { - setIsLoading(false); // 로딩 종료 - setIsResetSelection(false); - // setIsResetDetailSelection(false); - } - }; - - // 하수관로용 CQL 필터 생성 - const pplCqlFilters = () => { - const cqlFilters: string[] = []; - // 추후 작성 예정 - if (var1) cqlFilters.push(`usg_cd='${var1}'`); - if (var2) cqlFilters.push(`mqlt_cd='${var2}'`); - if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); - else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); - // 설치년도 범위 - const startDate = getDateString( - yearFilter.start, - '', - '', - "19000101", - ); - const endDate = getDateString( - yearFilter.end, - '', - '', - `${todayYear}${todayMonth}${todayDate}`, - ); - cqlFilters.push( - `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, - ); - // 관경(mm) 범위 - if (start1 && end1) cqlFilters.push(`pdmt_vl BETWEEN ${start1} AND ${end1}`); - else if (start1) cqlFilters.push(`pdmt_vl >= ${start1}`); - else if (end1) cqlFilters.push(`pdmt_vl <= ${end1}`); - // 연장(m) 범위 - if (start2 && end2) cqlFilters.push(`prlg_vl BETWEEN ${start2} AND ${end2}`); - else if (start2) cqlFilters.push(`prlg_vl >= ${start2}`); - else if (end2) cqlFilters.push(`prlg_vl <= ${end2}`); - - return cqlFilters; - }; - // 하수처리시설용 CQL 필터 생성 - const facilityCqlFilters = () => { - const cqlFilters: string[] = []; - // 가동개시일자 범위 - const startDate = getDateString( - yearFilter.start, - '', - '', - "19000101", - ); - const endDate = getDateString( - yearFilter.end, - '', - '', - `${todayYear}${todayMonth}${todayDate}`, - ); - cqlFilters.push( - `oprtng_strt_ymd >= '${startDate}' and oprtng_strt_ymd <= '${endDate}'`, - ); - // 시설용량 범위 - if (start1 && end1) - cqlFilters.push(`fclt_cpct BETWEEN ${start1} AND ${end1}`); - else if (start1) cqlFilters.push(`fclt_cpct >= ${start1}`); - else if (end1) cqlFilters.push(`fclt_cpct <= ${end1}`); - - // 시도 - if (ctpvName) cqlFilters.push(`ctpv_nm ILIKE '%${ctpvName}%'`); - if (sggName) cqlFilters.push(`sgg_nm ILIKE '%${sggName}%'`); - // // 처리구역 면적 - // if (start2 && end2) - // cqlFilters.push(`prcsZoneAreaSum BETWEEN ${start2} AND ${end2}`); - // else if (start2) cqlFilters.push(`prcsZoneAreaSum >= ${start2}`); - // else if (end2) cqlFilters.push(`prcsZoneAreaSum <= ${end2}`); - // // 하수처리시설명 - // if (var1) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${var1}%'`); - return cqlFilters; - }; - // 하수처리구역용 CQL 필터 생성 - const zoneCqlFilters = () => { - const cqlFilters: string[] = []; - if (ctpvCd !== '' && ctpvCd !== '0') cqlFilters.push(`ctpv_cd='${ctpvCd?.trim()}'`); - if (sggCd !== '' && sggCd !== '0') cqlFilters.push(`sgg_cd='${sggCd}'`); - if (var1) cqlFilters.push(`swtrmt_zone_nm ILIKE '%${var1}%'`); - if (var2) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${var2}%'`); - return cqlFilters; - }; - // 하수맨홀용 CQL 필터 생성 - const mhCqlFilters = () => { - const cqlFilters: string[] = []; - // 시도, 시군구 - if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); - else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); - // // 설치년도 범위 - const startDate = getDateString( - yearFilter.start, - '', - '', - "19000101", - ); - const endDate = getDateString( - yearFilter.end, - '', - '', - `${todayYear}${todayMonth}${todayDate}`, - ); - cqlFilters.push( - `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, - ); - if (var1) cqlFilters.push(`usg_cd='${var1}'`); - // 구경(mm) 범위 - if (start1 && end1) cqlFilters.push(`swage_mhL_diam_vl BETWEEN ${start1} AND ${end1}`); - else if (start1) cqlFilters.push(`swage_mhL_diam_vl >= ${start1}`); - else if (end1) cqlFilters.push(`swage_mhL_diam_vl <= ${end1}`); - - return cqlFilters; - }; - // 하수펌프장용 CQL 필터 생성 - const psmtnCqlFilters = () => { - const cqlFilters: string[] = []; - // 시도, 시군구 - if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); - else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); - // 설치년도 범위 - const startDate = getDateString( - yearFilter.start, - '', - '', - "19000101", - ); - const endDate = getDateString( - yearFilter.end, - '', - '', - `${todayYear}${todayMonth}${todayDate}`, - ); - cqlFilters.push( - `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, - ); - // 일일처리용량 범위 - if (start1 && end1) - cqlFilters.push(`daily_prcs_cpct BETWEEN ${start1} AND ${end1}`); - else if (start1) cqlFilters.push(`daily_prcs_cpct >= ${start1}`); - else if (end1) cqlFilters.push(`daily_prcs_cpct <= ${end1}`); - // 양수능력 범위 - if (start2 && end2) - cqlFilters.push(`excln_wpmp_ablt_vl BETWEEN ${start2} AND ${end2}`); - else if (start2) cqlFilters.push(`excln_wpmp_ablt_vl >= ${start2}`); - else if (end2) cqlFilters.push(`excln_wpmp_ablt_vl <= ${end2}`); - - return cqlFilters; - }; - // 우수토실용 CQL 필터 생성 - const otflCqlFilters = () => { - const cqlFilters: string[] = []; - // 시도, 시군구 - if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); - else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); - // 설치년도 범위 - const startDate = getDateString( - yearFilter.start, - '', - '', - "19000101", - ); - const endDate = getDateString( - yearFilter.end, - '', - '', - `${todayYear}${todayMonth}${todayDate}`, - ); - cqlFilters.push( - `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, - ); - - return cqlFilters; - }; - - // 조회 버튼 클릭 시 1페이지로 이동 및 데이터 초기화 - const handleSearch = () => { - setCurPage(1); // 페이지를 1로 초기화 - fetchFilteredData(); - setIsResetPage(true); // 페이지를 초기화 - setIsResetDetailPage(true); // 디테일 페이지를 초기화 - setIsResetSelection(false); // 선택 초기화 - setIsResetDetailSelection(false); // 선택 초기화 - setShowTable(true); - }; - - // 기본정보 테이블 cellClick 핸들러 - const handleBasicCellClick = async (index?: number | undefined, headId?: String) => { - if (typeof index === "number") { - setIsLoading(true); - - const row = tableData[index]; - const layerNm = row.lyrTypeNm; - const fid = row.fid; - const core = GisApp.Module.layer as any; - await core.showSearchResultsFromGeoJSON(layerNm, fid); - setIsLoading(false); - onOpenChange(false); // SearchModal 닫기 - } - }; - // 운영정보 테이블 cellClick 핸들러 - const handleOperationCellClick = async (index?: number | undefined, headId?: String) => { - setIsResetDetailPage(true); // 페이지를 1로 초기화 - setIsResetDetailSelection(true); // 선택 초기화 - setIsResetDetailPage(true); // 디테일 페이지를 초기화 - - if (typeof index === "number") { - setIsDetailLoading(true); - setDetailBizId(tableData[index].bizId); - setDetailCurPage(1); // 상세 페이지 초기화 - try { - const RequestDTO: Record = { - page: `${detailCurPage - 1}`, - size: `${OPERATION_TABLE_SIZE}`, - bizId: tableData[index % OPERATION_TABLE_SIZE].bizId, - }; - - RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"')); - - const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`); - - const mappedData = (res.data || []).map((item: any, detailIndex: number) => { - const formattedItem = formatDisplayData(item); - - const usgCdObj = MS_SWAGE_PPL_USE_CODES.find(code => code.val === item?.usgCd?.trim()); - const mqltCdObj = MS_SWAGE_PPL_CODES.find(code => code.val === item?.mqltCd?.trim()); - return { - ...formattedItem, - rowNumber: (detailCurPage - 1) * OPERATION_TABLE_SIZE + detailIndex + 1, - usgCd: usgCdObj ? usgCdObj.text : item.usgCd, - mqltCd: mqltCdObj ? mqltCdObj.text : item.mqltCd, - swtrmt_zone_nm: item.swtrmt_zone_nm ? item.swtrmt_zone_nm : '-', - }; - }); - - setDetailTableData(mappedData); - setDetailTableCount(res.count || 0); - } catch (error) { - console.error('운영정보 cellClick 에러:', error); - } finally { - setIsDetailLoading(false); - setIsResetDetailPage(false); - setIsResetDetailSelection(false); - } - } - }; - // 운영정보 디테일 테이블 페이지네이션 핸들러 - const handleOperationPageNation = async (index?: number | undefined) => { - if (typeof index === "number") { - setIsDetailLoading(true); - try { - const RequestDTO: Record = { - page: `${detailCurPage - 1}`, - size: `${OPERATION_TABLE_SIZE}`, - bizId: String(detailBizId), - }; - const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`); - - const mappedData = (res.data || []).map((item: any, detailIndex: number) => { - const formattedItem = formatDisplayData(item); - - const usgCdObj = MS_SWAGE_PPL_USE_CODES.find(code => code.val === item?.usgCd?.trim()); - const mqltCdObj = MS_SWAGE_PPL_CODES.find(code => code.val === item?.mqltCd?.trim()); - return { - ...formattedItem, - rowNumber: (detailCurPage - 1) * OPERATION_TABLE_SIZE + detailIndex + 1, - usgCd: usgCdObj ? usgCdObj.text : item.usgCd, - mqltCd: mqltCdObj ? mqltCdObj.text : item.mqltCd, - swtrmt_zone_nm: item.swtrmt_zone_nm ? item.swtrmt_zone_nm : '-', - }; - }); - - setDetailTableData(mappedData); - setDetailTableCount(res.count || 0); - } catch (error) { - console.error('운영정보 페이지네이션 에러:', error); - } finally { - setIsDetailLoading(false); - } - } - }; - - const handleOperationResultCellClick = async (index?: number | undefined, headId?: String) => { - if (typeof index === "number") { - setIsLoading(true); - - const row = detailTableData[index]; - const layerNm = row.lyrTypeNm; - const fid = row.fid; - const core = GisApp.Module.layer as any; - await core.showSearchResultsFromGeoJSON(layerNm, fid); - setIsLoading(false); - onOpenChange(false); // SearchModal 닫기 - } - }; - - // 기본정보 Excel 다운로드 핸들러 - const basicExcelDownload = async () => { - if (tableData.length === 0) { - alert("다운로드 할 Excel 파일이 없습니다.\n조회 후 다시 시도해주세요."); - return; - } - - toast.dismiss("global-confirm-toast"); - - confirmToast( - "파일 다운로드에는 2분 내외의 시간이 소요될 수 있습니다.\n파일 다운로드를 진행하시겠습니까?", - onAnalysis ?? (() => { }), - "text-xl font-medium text-gray-800" - ); - - async function onAnalysis() { - setIsLoading(true); - - try { - const queryParams = new URLSearchParams({ - lyrTypeNm: basicType, - size: '10000', - }); - - let cqlFilters: string[] = []; - let headerMapping: { [key: string]: string } = {}; - let headerRange: string = "A1:F1"; - - switch (basicType) { - case 'tb_ms_swage_ppl': { - // 하수관로 - cqlFilters = pplCqlFilters() - headerMapping = MsSwagePplExcelCol; - headerRange = "A1:M1"; - break; - } - case 'tb_il_swage_prcs_fclt': { - // 하수처리시설 - cqlFilters = facilityCqlFilters(); - headerMapping = SwtrmtFcltExcelCol; - headerRange = "A1:J1"; - break; - } - case 'tb_ms_swage_prcs_zone': { - // 하수처리구역 - cqlFilters = zoneCqlFilters(); - headerMapping = SwtrmtPrcsZoneExcelCol; - headerRange = "A1:D1"; - break; - } - case 'tb_ms_swage_mhl': { - // 하수맨홀 - cqlFilters = mhCqlFilters(); - headerMapping = MsSwageMhlExcelCol; - headerRange = "A1:J1"; - break; - } - case 'tb_ms_swage_pmstn': { - // 하수펌프장 - cqlFilters = psmtnCqlFilters(); - headerMapping = ExclnSewagePumpExcelCol; - headerRange = "A1:J1"; - break; - } - case 'tb_ms_stofcbr': { - // 우수토실 - cqlFilters = otflCqlFilters(); - headerMapping = stofcbrExcelCol; - headerRange = "A1:H1"; - break; - } - } - - if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and ')); - - const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`); - - - - - // const mhlCdMap = new Map(MS_SWAGE_MHL_CODES.map(code => [code.val?.trim(), code.text])); - const mhlUsgCdMap = new Map(MS_SWAGE_MHL_USE_CODES.map(code => [code.val?.trim(), code.text])); - const pplUsgCdMap = new Map(MS_SWAGE_PPL_USE_CODES.map(code => [code.val?.trim(), code.text])); - const ctpvMap = new Map(ctpv.map(code => [code.val?.trim(), code.text])); - const sggMap = new Map(sgg.map(code => [code.val?.trim(), code.text])); - - const mappedContent = (res.data.data || []).map((item: any) => { - const formattedItem = formatDisplayData(item); - - let ctpvCd = item?.ctpvCd ?? formattedItem?.ctpvCd ?? ''; - let sggCd = item?.sggCd ?? formattedItem?.sggCd ?? ''; - - if (!ctpvCd || !sggCd) { - const code = String(item?.pbadmsEmdCd ?? formattedItem?.pbadmsEmdCd ?? '')?.trim(); - if (!ctpvCd && code) ctpvCd = code.slice(0, 2); - if (!sggCd && code) sggCd = code.slice(0, 5); - } - - // trim 한 번만 수행 - const trimmedUsgCd = item?.usgCd?.trim(); - // const trimmedMqltCd = item?.mqltCd?.trim(); - - switch (basicType) { - case 'tb_ms_swage_ppl': { - // 하수관로 - return { - fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '-', - instMngNo: formattedItem.instMngNo ?? item.instMngNo ?? '-', - ctpvNm: ctpvMap.get(ctpvCd) ?? item.ctpvNm ?? '-', - sggNm: sggMap.get(sggCd) ?? item.sggNm ?? '-', - swtrmt_zone_nm: formattedItem.swtrmt_zone_nm ?? item.swtrmt_zone_nm ?? '-', - instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '-', - usgCd: pplUsgCdMap.get(trimmedUsgCd) || item.usgCd || formattedItem.usgCd || '-', - pdmtVl: formattedItem.pdmtVl ?? item.pdmtVl ?? '-', - prlgVl: formattedItem.prlgVl ?? item.prlgVl ?? '-', - tmptDpth: formattedItem.tmptDpth ?? item.tmptDpth ?? '-', - tmptMhlMngNo: formattedItem.tmptMhlMngNo ?? item.tmptMhlMngNo ?? '-', - scendDpth: formattedItem.scendDpth ?? item.scendDpth ?? '-', - scendMhlMngNo: formattedItem.scendMhlMngNo ?? item.scendMhlMngNo ?? '-', - }; - } - case 'tb_il_swage_prcs_fclt': { - // 하수처리시설 - return { - swtrmtFcltNm: formattedItem.swtrmtFcltNm ?? item.swtrmtFcltNm ?? '-', - fcltCpct: formattedItem.fcltCpct ?? item.fcltCpct ?? '-', - rgnExpln: formattedItem.rgnExpln ?? item.rgnExpln ?? '-', - oprtngStrtYmd: formattedItem.oprtngStrtYmd ?? item.oprtngStrtYmd ?? '-', - laeNm: formattedItem.laeNm ?? item.laeNm ?? '-', - sareaNm: formattedItem.sareaNm ?? item.sareaNm ?? '-', - rvrNm: formattedItem.rvrNm ?? item.rvrNm ?? '-', - dschgWtstNm: formattedItem.dschgWtstNm ?? item.dschgWtstNm ?? '-', - swtrmtPpltnCnt: formattedItem.swtrmtPpltnCnt ?? item.swtrmtPpltnCnt ?? '-', - rprsMsrcCn: formattedItem.rprsMsrcCn ?? item.rprsMsrcCn ?? '-' - }; - } - case 'tb_ms_swage_prcs_zone': { - // 하수처리구역 - return { - swtrmtZoneNm: formattedItem.swtrmtZoneNm ?? item.swtrmtZoneNm ?? '-', - ctpvNm: ctpvMap.get(ctpvCd) ?? formattedItem.ctpvNm ?? '-', - sggNm: sggMap.get(sggCd) ?? formattedItem.sggNm ?? '-', - swtrmtFcltNm: formattedItem.swtrmtFcltNm ?? item.swtrmtFcltNm ?? '-' - }; - } - case 'tb_ms_swage_mhl': { - // 하수맨홀 - return { - fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '-', - ctpvNm: ctpvMap.get(ctpvCd) ?? formattedItem.ctpvNm ?? '-', - sggNm: sggMap.get(sggCd) ?? formattedItem.sggNm ?? '-', - instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '-', - usgCd: mhlUsgCdMap.get(trimmedUsgCd) || item.usgCd || formattedItem.usgCd || '-', - swageMhlDiamVl: formattedItem.swageMhlDiamVl ?? item.swageMhlDiamVl ?? '-', - wdthLen: formattedItem.wdthLen ?? item.wdthLen ?? '-', - vrtcLen: formattedItem.vrtcLen ?? item.vrtcLen ?? '-', - altdVl: formattedItem.altdVl ?? item.altdVl ?? '-', - dpthlVl: formattedItem.dpthlVl ?? item.dpthlVl ?? '-' - }; - } - case 'tb_ms_swage_pmstn': { - // 하수펌프장 - return { - fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '-', - ctpvNm: ctpvMap.get(ctpvCd) ?? formattedItem.ctpvNm ?? '-', - sggNm: sggMap.get(sggCd) ?? formattedItem.sggNm ?? '-', - swagePmstnNm: formattedItem.swagePmstnNm ?? item.swagePmstnNm ?? '-', - instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '-', - dailyPrcsCpct: formattedItem.dailyPrcsCpct ?? item.dailyPrcsCpct ?? '-', - maxImpndCpct: formattedItem.maxImpndCpct ?? item.maxImpndCpct ?? '-', - altdVl: formattedItem.altdVl ?? item.altdVl ?? '-', - wtlvVl: formattedItem.wtlvVl ?? item.wtlvVl ?? '-', - exclnWpmpAbltVl: formattedItem.exclnWpmpAbltVl ?? item.exclnWpmpAbltVl ?? '-' - }; - } - case 'tb_ms_stofcbr': { - // 우수토실 - return { - fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '-', - mapshtNo: formattedItem.mapshtNo ?? item.mapshtNo ?? '-', - ctpvNm: ctpvMap.get(ctpvCd) ?? formattedItem.ctpvNm ?? '-', - sggNm: sggMap.get(sggCd) ?? formattedItem.sggNm ?? '-', - instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '-', - worflwWallHgt: formattedItem.worflwWallHgt ?? item.worflwWallHgt ?? '-', - swageMhlMngNo: formattedItem.swageMhlMngNo ?? item.swageMhlMngNo ?? '-', - drtnAngl: formattedItem.drtnAngl ?? item.drtnAngl ?? '-' - }; - } - } - }); - - // 선택된 행 데이터를 Excel 다운로드 형식으로 변환 - const excelData = mappedContent; // 배열로 감싸서 전달 - - // 파일명 생성 (분석번호 기반) - const label = BASIC_TYPES.find((type) => type.val === basicType)?.text - const fileName = `${label} 조회 결과_${todayYear}${todayMonth}${todayDate}`; - - await downloadExcelWithKoreanHeaders({ - data: excelData as JSON[], - fileName: fileName, - sheetNameArray: [fileName], - headerRange: headerRange, - headerMapping: headerMapping - }); - setIsLoading(false); - } catch (error) { - setIsLoading(false); - console.error("Excel 다운로드 중 오류 발생:", error); - alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요."); - } - } - }; - - // 운영정보 Excel 다운로드 핸들러 추가 - const operationExcelDownload = async () => { - if (tableData.length === 0) { - alert("다운로드 할 Excel 파일이 없습니다.\n조회 후 다시 시도해주세요."); - return; - } - - toast.dismiss("global-confirm-toast"); - - confirmToast( - "파일 다운로드에는 2분 내외의 시간이 소요될 수 있습니다.\n파일 다운로드를 진행하시겠습니까?", - onAnalysis ?? (() => { }), - "text-xl font-medium text-gray-800" - ); - - async function onAnalysis() { - setIsLoading(true); - - try { - const RequestDTO: Record = { - size: '10000', - }; - if (var1) RequestDTO.bizInfoNm = var1; - if (var2 != '0' && var2) RequestDTO.bizTypeCd = var2; - // 수행기간 범위 - const startDate = getDateString( - yearFilter.start, - '', - '', - "19000101", - ); - const endDate = getDateString( - yearFilter.end, - '', - '', - `${todayYear}${todayMonth}${todayDate}`, - ); - RequestDTO.fromflfmtEndYmd = startDate; - RequestDTO.toflfmtEndYmd = endDate; - - // JSON.parse 사용으로 문자열 -> 배열 변환 - RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"')); - - const params = new URLSearchParams(RequestDTO).toString(); - const res = await getData(`v1/bizInfo?${params}`); - - // 헤더 매핑 정의 - const headerMapping: { [key: string]: string } = { - 'bizInfoNm': '사업명', - 'flfmtBgngYmd': '수행시작일자', - 'flfmtEndYmd': '수행종료일자', - 'flfmtYr': '수행년도', - 'bizTypeCd': '사업유형', - 'bizAmt': '사업금액(원)', - }; - - const bizTyCdMap = new Map(BIZ_TY_CD.map(code => [code.lwpoCd, code.lwpoCdNm])); - - const mappedContent = (res.content || []).map((item: any) => { - const formattedItem = formatDisplayData(item); - - return { - bizInfoNm: formattedItem.bizInfoNm ?? item.bizInfoNm ?? '', - flfmtBgngYmd: formattedItem.flfmtBgngYmd ?? item.flfmtBgngYmd ?? '', - flfmtEndYmd: formattedItem.flfmtEndYmd ?? item.flfmtEndYmd ?? '', - flfmtYr: formattedItem.flfmtYr ?? item.flfmtYr ?? '', - bizTypeCd: bizTyCdMap.get(item.bizTypeCd) || item.bizTypeCd || formattedItem.bizTypeCd || '', - bizAmt: ((v) => (v === 0 || v === '0') ? '0' : (v ?? ''))(formattedItem.bizAmt ?? item.bizAmt), - }; - }); - - // 선택된 행 데이터를 Excel 다운로드 형식으로 변환 - const excelData = mappedContent; // 배열로 감싸서 전달 - - // 파일명 생성 (분석번호 기반) - const label = WORK_TYPES.find((type) => type.val === workType)?.text - const fileName = `${label} 사업 현황_${todayYear}${todayMonth}${todayDate}`; - - await downloadExcelWithKoreanHeaders({ - data: excelData as JSON[], - fileName: fileName, - sheetNameArray: [fileName], - headerRange: "A1:F1", - headerMapping: headerMapping - }); - setIsLoading(false); - } catch (error) { - setIsLoading(false); - console.error("Excel 다운로드 중 오류 발생:", error); - alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요."); - } - } - }; - - // 운영정보 Excel 다운로드 핸들러 추가 - const operationResultExcelDownload = async () => { - if (detailTableData.length === 0) { - alert("다운로드 할 Excel 파일이 없습니다.\n조회 후 다시 시도해주세요."); - return; - } - - toast.dismiss("global-confirm-toast"); - - confirmToast( - "파일 다운로드에는 2분 내외의 시간이 소요될 수 있습니다.\n파일 다운로드를 진행하시겠습니까?", - onAnalysis ?? (() => { }), - "text-xl font-medium text-gray-800" - ); - - async function onAnalysis() { - - setIsLoading(true); - - try { - const RequestDTO: Record = { - size: '10000', - bizId: String(detailBizId), - }; - - const apiStartTime = performance.now(); - const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`); - const apiEndTime = performance.now(); - - const mappingStartTime = performance.now(); - // 헤더 매핑 정의 - const headerMapping: { [key: string]: string } = { - 'fcltMngNo': '관리번호', - 'swtrmt_zone_nm': '처리구역', - 'usgCd': '용도', - 'mqltCd': '재질', - 'pdmtVl': '관경(mm)', - 'prlgVl': '연장(m)', - 'instlYmd': '설치일자', - 'strc_stts_grd': '구조적 상태등급', - 'oper_stts_grd': '운영적 상태등급', - }; - - const usgCdMap = new Map(MS_SWAGE_PPL_USE_CODES.map(code => [code.val, code.text])); - const mqltCdMap = new Map(MS_SWAGE_PPL_CODES.map(code => [code.val, code.text])); - - // 🎯 단일 반복문으로 최적화 - 각 아이템마다 필요한 필드만 직접 매핑 - const mappedData = (res.data || []).map((item: any) => { - const formattedItem = formatDisplayData(item); - - // trim 한 번만 수행 - const trimmedUsgCd = item?.usgCd?.trim(); - const trimmedMqltCd = item?.mqltCd?.trim(); - - // zone 값 한 번만 계산 - const zone = item.swtrmtZoneNm ?? formattedItem.swtrmt_zone_nm ?? item.swtrmt_zone_nm ?? ''; - - // 직접 매핑 - 반복문 없이 한 번에 객체 생성 - return { - fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '', - swtrmt_zone_nm: (zone === null || zone === '') ? '-' : zone, - usgCd: usgCdMap.get(trimmedUsgCd) || item.usgCd || formattedItem.usgCd || '', - mqltCd: mqltCdMap.get(trimmedMqltCd) || item.mqltCd || formattedItem.mqltCd || '', - pdmtVl: formattedItem.pdmtVl ?? item.pdmtVl ?? '', - prlgVl: formattedItem.prlgVl ?? item.prlgVl ?? '', - instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '', - strc_stts_grd: ((v) => (v === 0 || v === '0') ? '0' : (v ?? ''))(formattedItem.strc_stts_grd ?? item.strc_stts_grd), - oper_stts_grd: ((v) => (v === 0 || v === '0') ? '0' : (v ?? ''))(formattedItem.oper_stts_grd ?? item.oper_stts_grd), - }; - }); - const mappingEndTime = performance.now(); - - // 선택된 행 데이터를 Excel 다운로드 형식으로 변환 - const excelData = mappedData; // 배열로 감싸서 전달 - - const excelStartTime = performance.now(); - // 파일명 생성 (분석번호 기반) - const label = WORK_TYPES.find((type) => type.val === workType)?.text - const fileName = `${label} 결과표_${todayYear}${todayMonth}${todayDate}`; - - await downloadExcelWithKoreanHeaders({ - data: excelData as JSON[], - fileName: fileName, - sheetNameArray: [fileName], - headerRange: "A1:I1", - headerMapping: headerMapping - }); - const excelEndTime = performance.now(); - - setIsLoading(false); - } catch (error) { - setIsLoading(false); - console.error("Excel 다운로드 중 오류 발생:", error); - alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요."); - } - } - }; - - const viewMultipleFeature = async () => { - if (activeTab === 'basic') { - if (!(tableData.length === 0)) { - setIsLoading(true); - - const row = tableData[0]; - const layerNm = row.lyrTypeNm; - const fids = tableData.map((item) => item.fid); - const core = GisApp.Module.layer as any; - await core.showMultipleSearchResultsFromGeoJSON(layerNm, fids); - setIsLoading(false); - onOpenChange(false); - } - } else if (activeTab === 'operation') { - if (!(detailTableData.length === 0)) { - setIsLoading(true); - - const row = detailTableData[0]; - const layerNm = row.lyrTypeNm; - const fids = detailTableData.map((item) => item.fid); - const core = GisApp.Module.layer as any; - await core.showMultipleSearchResultsFromGeoJSON(layerNm, fids); - setIsLoading(false); - onOpenChange(false); - } - } - } - const renderBody = () => ( <> {/* 탭 영역 */} ({ + tabList={TABS.map((tab: any) => ({ value: tab.val, label: tab.text }))} diff --git a/apps/sn/app/(map-layout)/map/searchModal/useSearchModal.tsx b/apps/sn/app/(map-layout)/map/searchModal/useSearchModal.tsx new file mode 100644 index 000000000..35ab4d09c --- /dev/null +++ b/apps/sn/app/(map-layout)/map/searchModal/useSearchModal.tsx @@ -0,0 +1,1000 @@ +import { useEffect, useState } from 'react'; +import { downloadExcelWithKoreanHeaders } from '@common/business_components/ui'; +import axios from 'axios'; +import { + MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES, MS_SWAGE_MHL_CODES, MS_SWAGE_MHL_USE_CODES, + MsSwagePplExcelCol, SwtrmtFcltExcelCol, SwtrmtPrcsZoneExcelCol, MsSwageMhlExcelCol, ExclnSewagePumpExcelCol, stofcbrExcelCol +} from '../components/common/table'; +import { getData } from "@/components/lib/api"; +import { BASIC_TYPES } from './basicTab'; +import { WORK_TYPES } from './operationTab'; +import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; +import useStore from '../../../store/useStore'; +import GisApp from "@common/business_components/ui/map/lib/gis.core"; +import { confirmToast } from '@/components/ui/toast' +import { toast } from "react-hot-toast"; + +const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`; +export const OPERATION_TABLE_SIZE = 5; + +// 날짜 조합 함수 +const getDateString = ( + year: string, + month: string, + day: string, + defaultDate: string, +) => { + if (!year && !month && !day) return defaultDate; + const y = year || defaultDate.slice(0, 4); + const m = month || defaultDate.slice(4, 6); + const d = day || defaultDate.slice(6, 8); + return `${y}${m}${d}`; +}; + +export interface SearchModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onFeatureSelect: (row: any) => void; +} + +export function useSearchModal({ open, onOpenChange, onFeatureSelect }: SearchModalProps) { + const TABS = [ + { val: "basic", text: "기본정보" }, + { val: "operation", text: "운영정보" }, + { val: "report", text: "하수도정비기본계획보고서" }, + ]; + const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? "basic"); + const [basicType, setBasicType] = useState(BASIC_TYPES[0]?.val ?? ""); + const [workType, setWorkType] = useState(WORK_TYPES[0]?.val ?? ""); + const [showTable, setShowTable] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [isDetailLoading, setIsDetailLoading] = useState(false); + const [tableData, setTableData] = useState([]); + const [detailTableData, setDetailTableData] = useState([]); + const [tableCount, setTableCount] = useState(0); + const [detailTableCount, setDetailTableCount] = useState(0); + const [curPage, setCurPage] = useState(1); + const [detailCurPage, setDetailCurPage] = useState(1); + const [isResetPage, setIsResetPage] = useState(false); + const [isResetDetailPage, setIsResetDetailPage] = useState(false); + const [isResetSelection, setIsResetSelection] = useState(false); + const [isResetDetailSelection, setIsResetDetailSelection] = useState(false); + const [detailBizId, setDetailBizId] = useState(0); + const [ctpvCd, setCtpvCd] = useState("0"); + const [ctpvName, setCtpvName] = useState(''); + const [sggCd, setSggCd] = useState("0"); + const [sggName, setSggName] = useState(''); + const [start1, setStart1] = useState(""); + const [start2, setStart2] = useState(""); + const [end1, setEnd1] = useState(""); + const [end2, setEnd2] = useState(""); + const [var1, setVar1] = useState(""); + const [var2, setVar2] = useState(""); + const [yearFilter, setYearFilter] = useState({ start: "", end: "" }); + + const { ctpv, sgg, BIZ_TY_CD } = useStore(); + + // 오늘 날짜 구하기 + const today = new Date(); + const todayYear = today.getFullYear().toString(); + const todayMonth = (today.getMonth() + 1).toString().padStart(2, "0"); + const todayDate = today.getDate().toString().padStart(2, "0"); + + + useEffect(() => { + // filterParams 초기화 + setStart1(""); + setStart2(""); + setEnd1(""); + setEnd2(""); + setVar1(""); + setVar2(""); + setCtpvCd("0"); + setSggCd("0"); + setYearFilter({ start: "", end: "" }); + setShowTable(false); + setTableData([]); + setDetailTableData([]); + setTableCount(0); + setDetailTableCount(0); + setCurPage(1); + setDetailCurPage(1); + setIsResetPage(true); + setIsResetDetailPage(true); + setIsResetSelection(true); + setIsResetDetailSelection(true); + setDetailBizId(0); + }, [activeTab, basicType, workType]); + + // 페이지 변경 시마다 데이터 fetch + useEffect(() => { + setIsResetPage(false); + setIsResetDetailPage(true); + setDetailBizId(0); + if (showTable) fetchFilteredData(); + }, [curPage]); + + // 페이지 변경 시마다 데이터 fetch + useEffect(() => { + setIsResetDetailSelection(false); + if (showTable) handleOperationPageNation(Number(detailBizId)); + }, [detailCurPage]); + + + // 검색 API 호출 함수 + const fetchFilteredData = async () => { + setIsLoading(true); // 로딩 시작 + setIsResetSelection(true); // 선택 초기화 + setIsResetDetailSelection(true); // 선택 초기화 + + try { + let cqlFilters: string[] = []; + if (activeTab === 'basic') { + const queryParams = new URLSearchParams({ + lyrTypeNm: basicType, + page: `${curPage - 1}`, + size: '10', + sort: 'string', + }); + + if (basicType === 'tb_ms_swage_prcs_zone') cqlFilters = zoneCqlFilters(); // 하수처리구역 + else if (basicType === 'tb_il_swage_prcs_fclt') cqlFilters = facilityCqlFilters(); // 하수처리시설 + else if (basicType === 'tb_ms_swage_ppl') cqlFilters = pplCqlFilters(); // 하수관로 + else if (basicType === 'tb_ms_swage_mhl') cqlFilters = mhCqlFilters(); // 하수맨홀 + else if (basicType === 'tb_ms_swage_pmstn') cqlFilters = psmtnCqlFilters(); // 하수펌프장 + else if (basicType === 'tb_ms_stofcbr') cqlFilters = otflCqlFilters(); // 우수토실 + + if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and ')); + + const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`); + + // 🎯 각 아이템을 개별적으로 포맷팅하면서 일련번호 추가 + const dataWithRowNumber = (res.data.data || []).map((item: any, index: number) => { + // 🎯 먼저 각 아이템을 포맷팅 + const formattedItem = formatDisplayData(item); + + let ctpvCd = item?.ctpvNm ?? formattedItem?.ctpvNm ?? ''; + let sggCd = item?.sggNm ?? formattedItem?.sggNm ?? ''; + + if (!ctpvCd || !sggCd) { + const code = String(item?.pbadmsEmdCd ?? formattedItem?.pbadmsEmdCd ?? '')?.trim(); + if (!ctpvCd && code) ctpvCd = code.slice(0, 2); + if (!sggCd && code) sggCd = code.slice(0, 5); + } + + const matchedMhlCode = MS_SWAGE_MHL_CODES.find(code => code.val === item?.mqltCd?.trim()); + const matchedMhlUsgCode = MS_SWAGE_MHL_USE_CODES.find(code => code.val === item?.usgCd); + const ctpvNm = ctpv.find(code => code.val.trim() === ctpvCd)?.text || ctpvCd; + const sggNm = sgg.find(code => code.val.trim() === sggCd)?.text || '-'; + const usgCdObj = MS_SWAGE_PPL_USE_CODES.find(code => code.val === item?.usgCd?.trim()); + + // 추가 처리 및 일련번호 추가 + return { + ...formattedItem, + rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가 + scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item?.scsYn, // scsYn이 null이면 'N'으로 변경 + mqltCd: matchedMhlCode ? matchedMhlCode.text : item?.mqltCd, + usgCd: matchedMhlUsgCode ? matchedMhlUsgCode.text : (usgCdObj ? usgCdObj.text : item?.usgCd), + ctpvNm, + sggNm, + swtrmt_zone_nm: item.swtrmtZoneNm || item.swtrmt_zone_nm || '-', + }; + }); + + setTableData(dataWithRowNumber); + setTableCount(res.data.count || []); + + } else if (activeTab === 'operation') { + const RequestDTO: Record = { + page: `${curPage - 1}`, + size: `${OPERATION_TABLE_SIZE}`, + }; + if (var1) RequestDTO.bizInfoNm = var1; + if (var2 != '0' && var2) RequestDTO.bizTypeCd = var2; + // 수행기간 범위 + const startDate = getDateString( + yearFilter.start, + '', + '', + "19000101", + ); + const endDate = getDateString( + yearFilter.end, + '', + '', + `${todayYear}${todayMonth}${todayDate}`, + ); + RequestDTO.fromflfmtEndYmd = startDate; + RequestDTO.toflfmtEndYmd = endDate; + + // JSON.parse 사용으로 문자열 -> 배열 변환 + RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"')); + + const params = new URLSearchParams(RequestDTO).toString(); + + const res = await getData(`v1/bizInfo?${params}`); + + const mappedContent = (res.content || []).map((item: any, index: number) => { + const formattedItem = formatDisplayData(item); + const matched = BIZ_TY_CD.find(code => code.lwpoCd === item.bizTypeCd); + return { + ...formattedItem, + rowNumber: (curPage - 1) * OPERATION_TABLE_SIZE + index + 1, // 1부터 시작하는 일련번호 추가 + bizTypeCd: matched ? matched.lwpoCdNm : item.bizTypeCd, + }; + }); + + setTableData(mappedContent || []); + setTableCount(res.totalElements || 0); + setDetailTableData([]); + setDetailTableCount(0); + } + + setShowTable(true); + setIsResetSelection(false); + } catch (err) { + console.error('API ERROR:', err); + setTableData([]); + setIsResetSelection(false); + } finally { + setIsLoading(false); // 로딩 종료 + setIsResetSelection(false); + // setIsResetDetailSelection(false); + } + }; + + // 하수관로용 CQL 필터 생성 + const pplCqlFilters = () => { + const cqlFilters: string[] = []; + // 추후 작성 예정 + if (var1) cqlFilters.push(`usg_cd='${var1}'`); + if (var2) cqlFilters.push(`mqlt_cd='${var2}'`); + if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); + else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); + // 설치년도 범위 + const startDate = getDateString( + yearFilter.start, + '', + '', + "19000101", + ); + const endDate = getDateString( + yearFilter.end, + '', + '', + `${todayYear}${todayMonth}${todayDate}`, + ); + cqlFilters.push( + `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, + ); + // 관경(mm) 범위 + if (start1 && end1) cqlFilters.push(`pdmt_vl BETWEEN ${start1} AND ${end1}`); + else if (start1) cqlFilters.push(`pdmt_vl >= ${start1}`); + else if (end1) cqlFilters.push(`pdmt_vl <= ${end1}`); + // 연장(m) 범위 + if (start2 && end2) cqlFilters.push(`prlg_vl BETWEEN ${start2} AND ${end2}`); + else if (start2) cqlFilters.push(`prlg_vl >= ${start2}`); + else if (end2) cqlFilters.push(`prlg_vl <= ${end2}`); + + return cqlFilters; + }; + // 하수처리시설용 CQL 필터 생성 + const facilityCqlFilters = () => { + const cqlFilters: string[] = []; + // 가동개시일자 범위 + const startDate = getDateString( + yearFilter.start, + '', + '', + "19000101", + ); + const endDate = getDateString( + yearFilter.end, + '', + '', + `${todayYear}${todayMonth}${todayDate}`, + ); + cqlFilters.push( + `oprtng_strt_ymd >= '${startDate}' and oprtng_strt_ymd <= '${endDate}'`, + ); + // 시설용량 범위 + if (start1 && end1) + cqlFilters.push(`fclt_cpct BETWEEN ${start1} AND ${end1}`); + else if (start1) cqlFilters.push(`fclt_cpct >= ${start1}`); + else if (end1) cqlFilters.push(`fclt_cpct <= ${end1}`); + + // 시도 + if (ctpvName) cqlFilters.push(`ctpv_nm ILIKE '%${ctpvName}%'`); + if (sggName) cqlFilters.push(`sgg_nm ILIKE '%${sggName}%'`); + // // 처리구역 면적 + // if (start2 && end2) + // cqlFilters.push(`prcsZoneAreaSum BETWEEN ${start2} AND ${end2}`); + // else if (start2) cqlFilters.push(`prcsZoneAreaSum >= ${start2}`); + // else if (end2) cqlFilters.push(`prcsZoneAreaSum <= ${end2}`); + // // 하수처리시설명 + // if (var1) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${var1}%'`); + return cqlFilters; + }; + // 하수처리구역용 CQL 필터 생성 + const zoneCqlFilters = () => { + const cqlFilters: string[] = []; + if (ctpvCd !== '' && ctpvCd !== '0') cqlFilters.push(`ctpv_cd='${ctpvCd?.trim()}'`); + if (sggCd !== '' && sggCd !== '0') cqlFilters.push(`sgg_cd='${sggCd}'`); + if (var1) cqlFilters.push(`swtrmt_zone_nm ILIKE '%${var1}%'`); + if (var2) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${var2}%'`); + return cqlFilters; + }; + // 하수맨홀용 CQL 필터 생성 + const mhCqlFilters = () => { + const cqlFilters: string[] = []; + // 시도, 시군구 + if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); + else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); + // // 설치년도 범위 + const startDate = getDateString( + yearFilter.start, + '', + '', + "19000101", + ); + const endDate = getDateString( + yearFilter.end, + '', + '', + `${todayYear}${todayMonth}${todayDate}`, + ); + cqlFilters.push( + `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, + ); + if (var1) cqlFilters.push(`usg_cd='${var1}'`); + // 구경(mm) 범위 + if (start1 && end1) cqlFilters.push(`swage_mhL_diam_vl BETWEEN ${start1} AND ${end1}`); + else if (start1) cqlFilters.push(`swage_mhL_diam_vl >= ${start1}`); + else if (end1) cqlFilters.push(`swage_mhL_diam_vl <= ${end1}`); + + return cqlFilters; + }; + // 하수펌프장용 CQL 필터 생성 + const psmtnCqlFilters = () => { + const cqlFilters: string[] = []; + // 시도, 시군구 + if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); + else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); + // 설치년도 범위 + const startDate = getDateString( + yearFilter.start, + '', + '', + "19000101", + ); + const endDate = getDateString( + yearFilter.end, + '', + '', + `${todayYear}${todayMonth}${todayDate}`, + ); + cqlFilters.push( + `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, + ); + // 일일처리용량 범위 + if (start1 && end1) + cqlFilters.push(`daily_prcs_cpct BETWEEN ${start1} AND ${end1}`); + else if (start1) cqlFilters.push(`daily_prcs_cpct >= ${start1}`); + else if (end1) cqlFilters.push(`daily_prcs_cpct <= ${end1}`); + // 양수능력 범위 + if (start2 && end2) + cqlFilters.push(`excln_wpmp_ablt_vl BETWEEN ${start2} AND ${end2}`); + else if (start2) cqlFilters.push(`excln_wpmp_ablt_vl >= ${start2}`); + else if (end2) cqlFilters.push(`excln_wpmp_ablt_vl <= ${end2}`); + + return cqlFilters; + }; + // 우수토실용 CQL 필터 생성 + const otflCqlFilters = () => { + const cqlFilters: string[] = []; + // 시도, 시군구 + if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); + else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); + // 설치년도 범위 + const startDate = getDateString( + yearFilter.start, + '', + '', + "19000101", + ); + const endDate = getDateString( + yearFilter.end, + '', + '', + `${todayYear}${todayMonth}${todayDate}`, + ); + cqlFilters.push( + `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, + ); + + return cqlFilters; + }; + + // 조회 버튼 클릭 시 1페이지로 이동 및 데이터 초기화 + const handleSearch = () => { + setCurPage(1); // 페이지를 1로 초기화 + fetchFilteredData(); + setIsResetPage(true); // 페이지를 초기화 + setIsResetDetailPage(true); // 디테일 페이지를 초기화 + setIsResetSelection(false); // 선택 초기화 + setIsResetDetailSelection(false); // 선택 초기화 + setShowTable(true); + }; + + // 기본정보 테이블 cellClick 핸들러 + const handleBasicCellClick = async (index?: number | undefined, headId?: String) => { + if (typeof index === "number") { + setIsLoading(true); + + const row = tableData[index]; + const layerNm = row.lyrTypeNm; + const fid = row.fid; + const core = GisApp.Module.layer as any; + await core.showSearchResultsFromGeoJSON(layerNm, fid); + setIsLoading(false); + onOpenChange(false); // SearchModal 닫기 + } + }; + // 운영정보 테이블 cellClick 핸들러 + const handleOperationCellClick = async (index?: number | undefined, headId?: String) => { + setIsResetDetailPage(true); // 페이지를 1로 초기화 + setIsResetDetailSelection(true); // 선택 초기화 + setIsResetDetailPage(true); // 디테일 페이지를 초기화 + + if (typeof index === "number") { + setIsDetailLoading(true); + setDetailBizId(tableData[index].bizId); + setDetailCurPage(1); // 상세 페이지 초기화 + try { + const RequestDTO: Record = { + page: `${detailCurPage - 1}`, + size: `${OPERATION_TABLE_SIZE}`, + bizId: tableData[index % OPERATION_TABLE_SIZE].bizId, + }; + + RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"')); + + const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`); + + const mappedData = (res.data || []).map((item: any, detailIndex: number) => { + const formattedItem = formatDisplayData(item); + + const usgCdObj = MS_SWAGE_PPL_USE_CODES.find(code => code.val === item?.usgCd?.trim()); + const mqltCdObj = MS_SWAGE_PPL_CODES.find(code => code.val === item?.mqltCd?.trim()); + return { + ...formattedItem, + rowNumber: (detailCurPage - 1) * OPERATION_TABLE_SIZE + detailIndex + 1, + usgCd: usgCdObj ? usgCdObj.text : item.usgCd, + mqltCd: mqltCdObj ? mqltCdObj.text : item.mqltCd, + swtrmt_zone_nm: item.swtrmt_zone_nm ? item.swtrmt_zone_nm : '-', + }; + }); + + setDetailTableData(mappedData); + setDetailTableCount(res.count || 0); + } catch (error) { + console.error('운영정보 cellClick 에러:', error); + } finally { + setIsDetailLoading(false); + setIsResetDetailPage(false); + setIsResetDetailSelection(false); + } + } + }; + // 운영정보 디테일 테이블 페이지네이션 핸들러 + const handleOperationPageNation = async (index?: number | undefined) => { + if (typeof index === "number") { + setIsDetailLoading(true); + try { + const RequestDTO: Record = { + page: `${detailCurPage - 1}`, + size: `${OPERATION_TABLE_SIZE}`, + bizId: String(detailBizId), + }; + const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`); + + const mappedData = (res.data || []).map((item: any, detailIndex: number) => { + const formattedItem = formatDisplayData(item); + + const usgCdObj = MS_SWAGE_PPL_USE_CODES.find(code => code.val === item?.usgCd?.trim()); + const mqltCdObj = MS_SWAGE_PPL_CODES.find(code => code.val === item?.mqltCd?.trim()); + return { + ...formattedItem, + rowNumber: (detailCurPage - 1) * OPERATION_TABLE_SIZE + detailIndex + 1, + usgCd: usgCdObj ? usgCdObj.text : item.usgCd, + mqltCd: mqltCdObj ? mqltCdObj.text : item.mqltCd, + swtrmt_zone_nm: item.swtrmt_zone_nm ? item.swtrmt_zone_nm : '-', + }; + }); + + setDetailTableData(mappedData); + setDetailTableCount(res.count || 0); + } catch (error) { + console.error('운영정보 페이지네이션 에러:', error); + } finally { + setIsDetailLoading(false); + } + } + }; + + const handleOperationResultCellClick = async (index?: number | undefined, headId?: String) => { + if (typeof index === "number") { + setIsLoading(true); + + const row = detailTableData[index]; + const layerNm = row.lyrTypeNm; + const fid = row.fid; + const core = GisApp.Module.layer as any; + await core.showSearchResultsFromGeoJSON(layerNm, fid); + setIsLoading(false); + onOpenChange(false); // SearchModal 닫기 + } + }; + + // 기본정보 Excel 다운로드 핸들러 + const basicExcelDownload = async () => { + if (tableData.length === 0) { + alert("다운로드 할 Excel 파일이 없습니다.\n조회 후 다시 시도해주세요."); + return; + } + + toast.dismiss("global-confirm-toast"); + + confirmToast( + "파일 다운로드에는 2분 내외의 시간이 소요될 수 있습니다.\n파일 다운로드를 진행하시겠습니까?", + onAnalysis ?? (() => { }), + "text-xl font-medium text-gray-800" + ); + + async function onAnalysis() { + setIsLoading(true); + + try { + const queryParams = new URLSearchParams({ + lyrTypeNm: basicType, + size: '10000', + }); + + let cqlFilters: string[] = []; + let headerMapping: { [key: string]: string } = {}; + let headerRange: string = "A1:F1"; + + switch (basicType) { + case 'tb_ms_swage_ppl': { + // 하수관로 + cqlFilters = pplCqlFilters() + headerMapping = MsSwagePplExcelCol; + headerRange = "A1:M1"; + break; + } + case 'tb_il_swage_prcs_fclt': { + // 하수처리시설 + cqlFilters = facilityCqlFilters(); + headerMapping = SwtrmtFcltExcelCol; + headerRange = "A1:J1"; + break; + } + case 'tb_ms_swage_prcs_zone': { + // 하수처리구역 + cqlFilters = zoneCqlFilters(); + headerMapping = SwtrmtPrcsZoneExcelCol; + headerRange = "A1:D1"; + break; + } + case 'tb_ms_swage_mhl': { + // 하수맨홀 + cqlFilters = mhCqlFilters(); + headerMapping = MsSwageMhlExcelCol; + headerRange = "A1:J1"; + break; + } + case 'tb_ms_swage_pmstn': { + // 하수펌프장 + cqlFilters = psmtnCqlFilters(); + headerMapping = ExclnSewagePumpExcelCol; + headerRange = "A1:J1"; + break; + } + case 'tb_ms_stofcbr': { + // 우수토실 + cqlFilters = otflCqlFilters(); + headerMapping = stofcbrExcelCol; + headerRange = "A1:H1"; + break; + } + } + + if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and ')); + + const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`); + + + + + // const mhlCdMap = new Map(MS_SWAGE_MHL_CODES.map(code => [code.val?.trim(), code.text])); + const mhlUsgCdMap = new Map(MS_SWAGE_MHL_USE_CODES.map(code => [code.val?.trim(), code.text])); + const pplUsgCdMap = new Map(MS_SWAGE_PPL_USE_CODES.map(code => [code.val?.trim(), code.text])); + const ctpvMap = new Map(ctpv.map(code => [code.val?.trim(), code.text])); + const sggMap = new Map(sgg.map(code => [code.val?.trim(), code.text])); + + const mappedContent = (res.data.data || []).map((item: any) => { + const formattedItem = formatDisplayData(item); + + let ctpvCd = item?.ctpvCd ?? formattedItem?.ctpvCd ?? ''; + let sggCd = item?.sggCd ?? formattedItem?.sggCd ?? ''; + + if (!ctpvCd || !sggCd) { + const code = String(item?.pbadmsEmdCd ?? formattedItem?.pbadmsEmdCd ?? '')?.trim(); + if (!ctpvCd && code) ctpvCd = code.slice(0, 2); + if (!sggCd && code) sggCd = code.slice(0, 5); + } + + // trim 한 번만 수행 + const trimmedUsgCd = item?.usgCd?.trim(); + // const trimmedMqltCd = item?.mqltCd?.trim(); + + switch (basicType) { + case 'tb_ms_swage_ppl': { + // 하수관로 + return { + fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '-', + instMngNo: formattedItem.instMngNo ?? item.instMngNo ?? '-', + ctpvNm: ctpvMap.get(ctpvCd) ?? item.ctpvNm ?? '-', + sggNm: sggMap.get(sggCd) ?? item.sggNm ?? '-', + swtrmt_zone_nm: formattedItem.swtrmt_zone_nm ?? item.swtrmt_zone_nm ?? '-', + instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '-', + usgCd: pplUsgCdMap.get(trimmedUsgCd) || item.usgCd || formattedItem.usgCd || '-', + pdmtVl: formattedItem.pdmtVl ?? item.pdmtVl ?? '-', + prlgVl: formattedItem.prlgVl ?? item.prlgVl ?? '-', + tmptDpth: formattedItem.tmptDpth ?? item.tmptDpth ?? '-', + tmptMhlMngNo: formattedItem.tmptMhlMngNo ?? item.tmptMhlMngNo ?? '-', + scendDpth: formattedItem.scendDpth ?? item.scendDpth ?? '-', + scendMhlMngNo: formattedItem.scendMhlMngNo ?? item.scendMhlMngNo ?? '-', + }; + } + case 'tb_il_swage_prcs_fclt': { + // 하수처리시설 + return { + swtrmtFcltNm: formattedItem.swtrmtFcltNm ?? item.swtrmtFcltNm ?? '-', + fcltCpct: formattedItem.fcltCpct ?? item.fcltCpct ?? '-', + rgnExpln: formattedItem.rgnExpln ?? item.rgnExpln ?? '-', + oprtngStrtYmd: formattedItem.oprtngStrtYmd ?? item.oprtngStrtYmd ?? '-', + laeNm: formattedItem.laeNm ?? item.laeNm ?? '-', + sareaNm: formattedItem.sareaNm ?? item.sareaNm ?? '-', + rvrNm: formattedItem.rvrNm ?? item.rvrNm ?? '-', + dschgWtstNm: formattedItem.dschgWtstNm ?? item.dschgWtstNm ?? '-', + swtrmtPpltnCnt: formattedItem.swtrmtPpltnCnt ?? item.swtrmtPpltnCnt ?? '-', + rprsMsrcCn: formattedItem.rprsMsrcCn ?? item.rprsMsrcCn ?? '-' + }; + } + case 'tb_ms_swage_prcs_zone': { + // 하수처리구역 + return { + swtrmtZoneNm: formattedItem.swtrmtZoneNm ?? item.swtrmtZoneNm ?? '-', + ctpvNm: ctpvMap.get(ctpvCd) ?? formattedItem.ctpvNm ?? '-', + sggNm: sggMap.get(sggCd) ?? formattedItem.sggNm ?? '-', + swtrmtFcltNm: formattedItem.swtrmtFcltNm ?? item.swtrmtFcltNm ?? '-' + }; + } + case 'tb_ms_swage_mhl': { + // 하수맨홀 + return { + fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '-', + ctpvNm: ctpvMap.get(ctpvCd) ?? formattedItem.ctpvNm ?? '-', + sggNm: sggMap.get(sggCd) ?? formattedItem.sggNm ?? '-', + instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '-', + usgCd: mhlUsgCdMap.get(trimmedUsgCd) || item.usgCd || formattedItem.usgCd || '-', + swageMhlDiamVl: formattedItem.swageMhlDiamVl ?? item.swageMhlDiamVl ?? '-', + wdthLen: formattedItem.wdthLen ?? item.wdthLen ?? '-', + vrtcLen: formattedItem.vrtcLen ?? item.vrtcLen ?? '-', + altdVl: formattedItem.altdVl ?? item.altdVl ?? '-', + dpthlVl: formattedItem.dpthlVl ?? item.dpthlVl ?? '-' + }; + } + case 'tb_ms_swage_pmstn': { + // 하수펌프장 + return { + fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '-', + ctpvNm: ctpvMap.get(ctpvCd) ?? formattedItem.ctpvNm ?? '-', + sggNm: sggMap.get(sggCd) ?? formattedItem.sggNm ?? '-', + swagePmstnNm: formattedItem.swagePmstnNm ?? item.swagePmstnNm ?? '-', + instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '-', + dailyPrcsCpct: formattedItem.dailyPrcsCpct ?? item.dailyPrcsCpct ?? '-', + maxImpndCpct: formattedItem.maxImpndCpct ?? item.maxImpndCpct ?? '-', + altdVl: formattedItem.altdVl ?? item.altdVl ?? '-', + wtlvVl: formattedItem.wtlvVl ?? item.wtlvVl ?? '-', + exclnWpmpAbltVl: formattedItem.exclnWpmpAbltVl ?? item.exclnWpmpAbltVl ?? '-' + }; + } + case 'tb_ms_stofcbr': { + // 우수토실 + return { + fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '-', + mapshtNo: formattedItem.mapshtNo ?? item.mapshtNo ?? '-', + ctpvNm: ctpvMap.get(ctpvCd) ?? formattedItem.ctpvNm ?? '-', + sggNm: sggMap.get(sggCd) ?? formattedItem.sggNm ?? '-', + instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '-', + worflwWallHgt: formattedItem.worflwWallHgt ?? item.worflwWallHgt ?? '-', + swageMhlMngNo: formattedItem.swageMhlMngNo ?? item.swageMhlMngNo ?? '-', + drtnAngl: formattedItem.drtnAngl ?? item.drtnAngl ?? '-' + }; + } + } + }); + + // 선택된 행 데이터를 Excel 다운로드 형식으로 변환 + const excelData = mappedContent; // 배열로 감싸서 전달 + + // 파일명 생성 (분석번호 기반) + const label = BASIC_TYPES.find((type) => type.val === basicType)?.text + const fileName = `${label} 조회 결과_${todayYear}${todayMonth}${todayDate}`; + + await downloadExcelWithKoreanHeaders({ + data: excelData as JSON[], + fileName: fileName, + sheetNameArray: [fileName], + headerRange: headerRange, + headerMapping: headerMapping + }); + setIsLoading(false); + } catch (error) { + setIsLoading(false); + console.error("Excel 다운로드 중 오류 발생:", error); + alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요."); + } + } + }; + + // 운영정보 Excel 다운로드 핸들러 추가 + const operationExcelDownload = async () => { + if (tableData.length === 0) { + alert("다운로드 할 Excel 파일이 없습니다.\n조회 후 다시 시도해주세요."); + return; + } + + toast.dismiss("global-confirm-toast"); + + confirmToast( + "파일 다운로드에는 2분 내외의 시간이 소요될 수 있습니다.\n파일 다운로드를 진행하시겠습니까?", + onAnalysis ?? (() => { }), + "text-xl font-medium text-gray-800" + ); + + async function onAnalysis() { + setIsLoading(true); + + try { + const RequestDTO: Record = { + size: '10000', + }; + if (var1) RequestDTO.bizInfoNm = var1; + if (var2 != '0' && var2) RequestDTO.bizTypeCd = var2; + // 수행기간 범위 + const startDate = getDateString( + yearFilter.start, + '', + '', + "19000101", + ); + const endDate = getDateString( + yearFilter.end, + '', + '', + `${todayYear}${todayMonth}${todayDate}`, + ); + RequestDTO.fromflfmtEndYmd = startDate; + RequestDTO.toflfmtEndYmd = endDate; + + // JSON.parse 사용으로 문자열 -> 배열 변환 + RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"')); + + const params = new URLSearchParams(RequestDTO).toString(); + const res = await getData(`v1/bizInfo?${params}`); + + // 헤더 매핑 정의 + const headerMapping: { [key: string]: string } = { + 'bizInfoNm': '사업명', + 'flfmtBgngYmd': '수행시작일자', + 'flfmtEndYmd': '수행종료일자', + 'flfmtYr': '수행년도', + 'bizTypeCd': '사업유형', + 'bizAmt': '사업금액(원)', + }; + + const bizTyCdMap = new Map(BIZ_TY_CD.map(code => [code.lwpoCd, code.lwpoCdNm])); + + const mappedContent = (res.content || []).map((item: any) => { + const formattedItem = formatDisplayData(item); + + return { + bizInfoNm: formattedItem.bizInfoNm ?? item.bizInfoNm ?? '', + flfmtBgngYmd: formattedItem.flfmtBgngYmd ?? item.flfmtBgngYmd ?? '', + flfmtEndYmd: formattedItem.flfmtEndYmd ?? item.flfmtEndYmd ?? '', + flfmtYr: formattedItem.flfmtYr ?? item.flfmtYr ?? '', + bizTypeCd: bizTyCdMap.get(item.bizTypeCd) || item.bizTypeCd || formattedItem.bizTypeCd || '', + bizAmt: ((v) => (v === 0 || v === '0') ? '0' : (v ?? ''))(formattedItem.bizAmt ?? item.bizAmt), + }; + }); + + // 선택된 행 데이터를 Excel 다운로드 형식으로 변환 + const excelData = mappedContent; // 배열로 감싸서 전달 + + // 파일명 생성 (분석번호 기반) + const label = WORK_TYPES.find((type) => type.val === workType)?.text + const fileName = `${label} 사업 현황_${todayYear}${todayMonth}${todayDate}`; + + await downloadExcelWithKoreanHeaders({ + data: excelData as JSON[], + fileName: fileName, + sheetNameArray: [fileName], + headerRange: "A1:F1", + headerMapping: headerMapping + }); + setIsLoading(false); + } catch (error) { + setIsLoading(false); + console.error("Excel 다운로드 중 오류 발생:", error); + alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요."); + } + } + }; + + // 운영정보 Excel 다운로드 핸들러 추가 + const operationResultExcelDownload = async () => { + if (detailTableData.length === 0) { + alert("다운로드 할 Excel 파일이 없습니다.\n조회 후 다시 시도해주세요."); + return; + } + + toast.dismiss("global-confirm-toast"); + + confirmToast( + "파일 다운로드에는 2분 내외의 시간이 소요될 수 있습니다.\n파일 다운로드를 진행하시겠습니까?", + onAnalysis ?? (() => { }), + "text-xl font-medium text-gray-800" + ); + + async function onAnalysis() { + + setIsLoading(true); + + try { + const RequestDTO: Record = { + size: '10000', + bizId: String(detailBizId), + }; + + const apiStartTime = performance.now(); + const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`); + const apiEndTime = performance.now(); + + const mappingStartTime = performance.now(); + // 헤더 매핑 정의 + const headerMapping: { [key: string]: string } = { + 'fcltMngNo': '관리번호', + 'swtrmt_zone_nm': '처리구역', + 'usgCd': '용도', + 'mqltCd': '재질', + 'pdmtVl': '관경(mm)', + 'prlgVl': '연장(m)', + 'instlYmd': '설치일자', + 'strc_stts_grd': '구조적 상태등급', + 'oper_stts_grd': '운영적 상태등급', + }; + + const usgCdMap = new Map(MS_SWAGE_PPL_USE_CODES.map(code => [code.val, code.text])); + const mqltCdMap = new Map(MS_SWAGE_PPL_CODES.map(code => [code.val, code.text])); + + // 🎯 단일 반복문으로 최적화 - 각 아이템마다 필요한 필드만 직접 매핑 + const mappedData = (res.data || []).map((item: any) => { + const formattedItem = formatDisplayData(item); + + // trim 한 번만 수행 + const trimmedUsgCd = item?.usgCd?.trim(); + const trimmedMqltCd = item?.mqltCd?.trim(); + + // zone 값 한 번만 계산 + const zone = item.swtrmtZoneNm ?? formattedItem.swtrmt_zone_nm ?? item.swtrmt_zone_nm ?? ''; + + // 직접 매핑 - 반복문 없이 한 번에 객체 생성 + return { + fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '', + swtrmt_zone_nm: (zone === null || zone === '') ? '-' : zone, + usgCd: usgCdMap.get(trimmedUsgCd) || item.usgCd || formattedItem.usgCd || '', + mqltCd: mqltCdMap.get(trimmedMqltCd) || item.mqltCd || formattedItem.mqltCd || '', + pdmtVl: formattedItem.pdmtVl ?? item.pdmtVl ?? '', + prlgVl: formattedItem.prlgVl ?? item.prlgVl ?? '', + instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '', + strc_stts_grd: ((v) => (v === 0 || v === '0') ? '0' : (v ?? ''))(formattedItem.strc_stts_grd ?? item.strc_stts_grd), + oper_stts_grd: ((v) => (v === 0 || v === '0') ? '0' : (v ?? ''))(formattedItem.oper_stts_grd ?? item.oper_stts_grd), + }; + }); + + // 선택된 행 데이터를 Excel 다운로드 형식으로 변환 + const excelData = mappedData; // 배열로 감싸서 전달 + + // 파일명 생성 (분석번호 기반) + const label = WORK_TYPES.find((type) => type.val === workType)?.text + const fileName = `${label} 결과표_${todayYear}${todayMonth}${todayDate}`; + + await downloadExcelWithKoreanHeaders({ + data: excelData as JSON[], + fileName: fileName, + sheetNameArray: [fileName], + headerRange: "A1:I1", + headerMapping: headerMapping + }); + + setIsLoading(false); + } catch (error) { + setIsLoading(false); + console.error("Excel 다운로드 중 오류 발생:", error); + alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요."); + } + } + }; + + const viewMultipleFeature = async () => { + if (activeTab === 'basic') { + if (!(tableData.length === 0)) { + setIsLoading(true); + + const row = tableData[0]; + const layerNm = row.lyrTypeNm; + const fids = tableData.map((item) => item.fid); + const core = GisApp.Module.layer as any; + await core.showMultipleSearchResultsFromGeoJSON(layerNm, fids); + setIsLoading(false); + onOpenChange(false); + } + } else if (activeTab === 'operation') { + if (!(detailTableData.length === 0)) { + setIsLoading(true); + + const row = detailTableData[0]; + const layerNm = row.lyrTypeNm; + const fids = detailTableData.map((item) => item.fid); + const core = GisApp.Module.layer as any; + await core.showMultipleSearchResultsFromGeoJSON(layerNm, fids); + setIsLoading(false); + onOpenChange(false); + } + } + } + + return { + open, onOpenChange, onFeatureSelect, + TABS, + activeTab, setActiveTab, + basicType, setBasicType, + workType, setWorkType, + tableData, detailTableData, + tableCount, detailTableCount, + curPage, setCurPage, + detailCurPage, setDetailCurPage, + isLoading, isDetailLoading, + isResetPage, isResetDetailPage, + isResetSelection, isResetDetailSelection, + detailBizId, setDetailBizId, + ctpvCd, setCtpvCd, ctpvName, setCtpvName, + sggCd, setSggCd, sggName, setSggName, + start1, setStart1, start2, setStart2, + end1, setEnd1, end2, setEnd2, + var1, setVar1, var2, setVar2, + yearFilter, setYearFilter, + handleSearch, + handleBasicCellClick, + handleOperationCellClick, + handleOperationPageNation, + handleOperationResultCellClick, + basicExcelDownload, + operationExcelDownload, + operationResultExcelDownload, + viewMultipleFeature, + }; +};