diff --git a/apps/sn/app/(map-layout)/map/components/common/table.ts b/apps/sn/app/(map-layout)/map/components/common/table.ts index 92b0e3c4a..a16e68013 100644 --- a/apps/sn/app/(map-layout)/map/components/common/table.ts +++ b/apps/sn/app/(map-layout)/map/components/common/table.ts @@ -55,6 +55,21 @@ export const MsSwagePplCol2 = [ // { id: 'usgCd', title: '용도코드' }, // { id: 'lyrTypeNm', title: '레이어타입명' }, ] +export const MsSwagePplExcelCol = { + 'fcltMngNo': '시설 관리번호', + 'instMngNo': '기관 관리번호', + 'ctpvNm': '시도', + 'sggNm': '시군구', + 'swtrmt_zone_nm': '처리구역', + 'instlYmd': '설치일자', + 'usgCd': '용도', + 'pdmtVl': '관경(mm)', + 'prlgVl': '연장(m)', + 'tmptDpth': '시점 깊이(m)', + 'tmptMhlMngNo': '시점맨홀 관리번호', + 'scendDpth': '종점 깊이(m)', + 'scendMhlMngNo': '종점맨홀 관리번호', +}; // 하수관로 용도 코드표 export const MS_SWAGE_PPL_USE_CODES = [ { val: "SBA000", text: "미분류" }, @@ -339,6 +354,18 @@ export const SwtrmtFcltCol2 = [ // { id: 'bsnNm', title: '유역명' }, // { id: 'obsrvnSptCd', title: '관측지점코드' }, ]; +export const SwtrmtFcltExcelCol = { + 'swtrmtFcltNm': '하수처리시설명', + 'fcltCpct': '시설 용량(㎥/d)', + 'rgnExpln': '지역설명', + 'oprtngStrtYmd': '가동개시 일자', + 'laeNm': '지자체명', + 'sareaNm': '권역명', + 'rvrNm': '하천명', + 'dschgWtstNm': '방류수계명', + 'swtrmtPpltnCnt': '하수 처리 인구수(인)', + 'rprsMsrcCn': '대표 공법 내용' +}; // 하수처리구역 export const SwtrmtPrcsZoneCol = [ @@ -362,6 +389,12 @@ export const SwtrmtPrcsZoneCol2 = [ { id: 'sggNm', title: '시군구' }, { id: 'swtrmtFcltNm', title: '하수처리시설명' }, ]; +export const SwtrmtPrcsZoneExcelCol = { + 'swtrmtZoneNm': '하수처리구역명', + 'ctpvNm': '시도', + 'sggNm': '시군구', + 'swtrmtFcltNm': '하수처리시설명' +}; // 하수맨홀 export const MsSwageMhlCol = [ @@ -411,6 +444,18 @@ export const MsSwageMhlCol2 = [ { id: 'dpthlVl', title: '심도(m)' }, // { id: 'instMngNo', title: '기관관리번호' } ]; +export const MsSwageMhlExcelCol = { + 'fcltMngNo': '시설관리번호', + 'ctpvNm': '시도', + 'sggNm': '시군구', + 'instlYmd': '설치일자', + 'usgCd': '용도', + 'swageMhlDiamVl': '하수맨홀구경(mm)', + 'wdthLen': '가로길이(m)', + 'vrtcLen': '세로길이(m)', + 'altdVl': '표고(EL.m)', + 'dpthlVl': '심도(m)' +}; // 하수맨홀 뚜껑재질 코드표 export const MS_SWAGE_MHL_CODES = [ { val: "SBC000", text: "미분류" }, @@ -519,6 +564,18 @@ export const ExclnSewagePumpCol2 = [ { id: 'exclnWpmpAbltVl', title: '우수양수능력(m²/d)' }, // { id: 'instMngNo', title: '기관관리번호' }, ] +export const ExclnSewagePumpExcelCol = { + 'fcltMngNo': '시설관리번호', + 'ctpvNm': '시도', + 'sggNm': '시군구', + 'swagePmstnNm': '하수펌프장명', + 'instlYmd': '설치일자', + 'dailyPrcsCpct': '일일처리용량(㎥)', + 'maxImpndCpct': '최대저수용량(㎥)', + 'altdVl': '표고(EL.m)', + 'wtlvVl': '수위(m)', + 'exclnWpmpAbltVl': '우수양수능력(m²/d)' +}; // 오수펌프장 export const DrtwSewagePumpCol = [ // { id: 'fid', title: 'FID' }, @@ -606,6 +663,16 @@ export const stofcbrCol2 = [ { id: 'swageMhlMngNo', title: '하수맨홀관리번호' }, { id: 'drtnAngl', title: '방향각도(°)' }, ] +export const stofcbrExcelCol = { + 'fcltMngNo': '시설관리번호', + 'mapshtNo': '도엽번호', + 'ctpvNm': '시도', + 'sggNm': '시군구', + 'instlYmd': '설치일자', + 'worflwWallHgt': '월류벽높이(m)', + 'swageMhlMngNo': '하수맨홀관리번호', + 'drtnAngl': '방향각도(°)' +}; // 중점우수토실 측정지점 export const untrtSwgRainCol1 = [ diff --git a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx index ce495aa92..8b38d232d 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx @@ -1,9 +1,13 @@ -import LoadingOverlay from '@/components/ui/LoadingBar'; -import { UiModal, UITab, UiTable } from '@common/business_components/ui'; -import axios from 'axios'; import React, { useEffect, useMemo, useState } from 'react'; -import { ExclnSewagePumpCol2, MsSwageMhlCol2, MsSwagePplCol2, stofcbrCol2, SwtrmtFcltCol2, SwtrmtPrcsZoneCol2 } from '../components/common/table'; -import { MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES, MS_SWAGE_MHL_CODES, MS_SWAGE_MHL_USE_CODES } from '../components/common/table'; +import LoadingOverlay from '@/components/ui/LoadingBar'; +import { UiModal, UITab, UiTable, downloadExcelWithKoreanHeaders } 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 +} 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'; @@ -11,6 +15,8 @@ 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"; const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`; @@ -90,12 +96,12 @@ const SearchModal: React.FC = ({ const dynamicHeadlist: { id: string, title: string }[] = useMemo(() => { if (activeTab === 'basic') { - if (basicType === 'tb_ms_swage_ppl') return MsSwagePplCol2; - if (basicType === 'tb_il_swage_prcs_fclt') return SwtrmtFcltCol2; - if (basicType === 'tb_ms_swage_prcs_zone') return SwtrmtPrcsZoneCol2; - if (basicType === 'tb_ms_swage_mhl') return MsSwageMhlCol2; - if (basicType === 'tb_ms_swage_pmstn') return ExclnSewagePumpCol2; - if (basicType === 'tb_ms_stofcbr') return stofcbrCol2; + if (basicType === 'tb_ms_swage_ppl') return MsSwagePplCol2; // 하수관로 + if (basicType === 'tb_il_swage_prcs_fclt') return SwtrmtFcltCol2; // 하수처리시설 + if (basicType === 'tb_ms_swage_prcs_zone') return SwtrmtPrcsZoneCol2; // 하수처리구역 + if (basicType === 'tb_ms_swage_mhl') return MsSwageMhlCol2; // 하수맨홀 + if (basicType === 'tb_ms_swage_pmstn') return ExclnSewagePumpCol2; // 하수펌프장 + if (basicType === 'tb_ms_stofcbr') return stofcbrCol2; // 우수토실 return []; } @@ -141,6 +147,7 @@ const SearchModal: React.FC = ({ setIsResetDetailPage(true); // 디테일 페이지를 초기화 setIsResetSelection(true); // 선택 초기화 setIsResetDetailSelection(true); // 선택 초기화 + setDetailBizId(0); // queryParams는 fetchFilteredData 내에서 매번 새로 생성되므로 별도 상태 초기화는 필요 없음 }, [activeTab, basicType, workType, open]); @@ -152,6 +159,7 @@ const SearchModal: React.FC = ({ setTableData([]); setDetailTableCount(0); setDetailTableData([]); + setDetailBizId(0); }, [open]); // 페이지 변경 시마다 데이터 fetch @@ -159,6 +167,7 @@ const SearchModal: React.FC = ({ // showTable이 true일 때만 fetch (최초 렌더 방지) setIsResetPage(false); setIsResetDetailPage(true); + setDetailBizId(0); if (showTable) fetchFilteredData(); }, [curPage]); @@ -198,6 +207,7 @@ const SearchModal: React.FC = ({ if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and ')); const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`); + console.log('api:', `${API_URL}/v1/fclts?${queryParams.toString()}`); // 🎯 각 아이템을 개별적으로 포맷팅하면서 일련번호 추가 const dataWithRowNumber = (res.data.data || []).map((item: any, index: number) => { @@ -208,7 +218,7 @@ const SearchModal: React.FC = ({ let sggCd = item?.sggNm ?? formattedItem?.sggNm ?? ''; if (!ctpvCd || !sggCd) { - const code = String(item?.pbadmsEmdCd ?? formattedItem?.pbadmsEmdCd ?? '').trim(); + const code = String(item?.pbadmsEmdCd ?? formattedItem?.pbadmsEmdCd ?? '')?.trim(); if (!ctpvCd && code) ctpvCd = code.slice(0, 2); if (!sggCd && code) sggCd = code.slice(0, 5); } @@ -265,6 +275,7 @@ const SearchModal: React.FC = ({ const params = new URLSearchParams(RequestDTO).toString(); const res = await getData(`v1/bizInfo?${params}`); + console.log('운영정보 사업 데이터:', res.content); const mappedContent = (res.content || []).map((item: any, index: number) => { const formattedItem = formatDisplayData(item); @@ -283,9 +294,11 @@ const SearchModal: React.FC = ({ } setShowTable(true); + setIsResetSelection(false); } catch (err) { console.error('API ERROR:', err); setTableData([]); + setIsResetSelection(false); } finally { setIsLoading(false); // 로딩 종료 setIsResetSelection(false); @@ -297,10 +310,10 @@ const SearchModal: React.FC = ({ const pplCqlFilters = () => { const cqlFilters: string[] = []; // 추후 작성 예정 - if (var1 !== '0') cqlFilters.push(`usg_cd='${var1}'`); + 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()}%'`); + else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); // 설치년도 범위 const startDate = getDateString( yearFilter.start, @@ -368,8 +381,8 @@ const SearchModal: React.FC = ({ // 하수처리구역용 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.trim()}'`); + 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; @@ -379,7 +392,7 @@ const SearchModal: React.FC = ({ 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()}%'`); + else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); // // 설치년도 범위 const startDate = getDateString( yearFilter.start, @@ -396,7 +409,7 @@ const SearchModal: React.FC = ({ cqlFilters.push( `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, ); - if (var1 !== '0') cqlFilters.push(`usg_cd='${var1}'`); + 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}`); @@ -409,7 +422,7 @@ const SearchModal: React.FC = ({ 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()}%'`); + else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); // 설치년도 범위 const startDate = getDateString( yearFilter.start, @@ -444,7 +457,7 @@ const SearchModal: React.FC = ({ 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()}%'`); + else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); // 설치년도 범위 const startDate = getDateString( yearFilter.start, @@ -508,18 +521,23 @@ const SearchModal: React.FC = ({ RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"')); const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`); + console.log('운영정보 cellClick 데이터1:', res); + console.log('운영정보 cellClick 데이터2:', res.data); + 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()); + 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 : '-', }; }); + console.log('운영정보 cellClick 데이터:', mappedData); setDetailTableData(mappedData); setDetailTableCount(res.count || 0); @@ -547,15 +565,17 @@ const SearchModal: React.FC = ({ 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()); + 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 : '-', }; }); + console.log('운영정보 PageNation 데이터:', mappedData); setDetailTableData(mappedData); setDetailTableCount(res.count || 0); @@ -567,6 +587,420 @@ const SearchModal: React.FC = ({ } }; + // 기본정보 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()}`); + console.log('res.data.data', res.data.data); + + + + + // 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; // 배열로 감싸서 전달 + console.log('excelData', excelData); + + // 파일명 생성 (분석번호 기반) + 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(); + console.log(`🚀 1. API 호출 시간: ${(apiEndTime - apiStartTime).toFixed(2)}ms`); + console.log('운영정보 결과 다운로드.data:', res.data); + + 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(); + console.log(`🔄 2. 데이터 매핑 시간: ${(mappingEndTime - mappingStartTime).toFixed(2)}ms`); + + // 선택된 행 데이터를 Excel 다운로드 형식으로 변환 + const excelData = mappedData; // 배열로 감싸서 전달 + + console.log('excelData', excelData); + + 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(); + console.log(`📊 3. Excel 다운로드 시간: ${(excelEndTime - excelStartTime).toFixed(2)}ms`); + + setIsLoading(false); + } catch (error) { + setIsLoading(false); + console.error("Excel 다운로드 중 오류 발생:", error); + alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요."); + } + } + }; + const renderBody = () => ( <> @@ -578,9 +1012,7 @@ const SearchModal: React.FC = ({ label: tab.text }))} onValueChange={(value: string) => setActiveTab(value)} - > - - {/* 각각의 탭 영역 */} + /> {/* 기본정보 탭 */} {activeTab === 'basic' && ( = ({ open={open} /> )} - {/* */} {/* 📋 결과 리스트 */} - {/* 하수도정비기본계획보고서 탭 */} + {/* 기본정보 탭 */} {activeTab === 'basic' && (
@@ -652,6 +1083,9 @@ const SearchModal: React.FC = ({ onPageChange={setCurPage} resetPageNo={isResetPage} resetSelection={isResetSelection} + headButton={ + + } />
@@ -677,6 +1111,9 @@ const SearchModal: React.FC = ({ onPageChange={setCurPage} resetPageNo={isResetPage} resetSelection={isResetSelection} + headButton={ + + } /> @@ -700,6 +1137,9 @@ const SearchModal: React.FC = ({ onPageChange={setDetailCurPage} resetPageNo={isResetDetailPage} resetSelection={isResetDetailSelection} + headButton={ + + } /> @@ -709,10 +1149,11 @@ const SearchModal: React.FC = ({ ); return ( - + = ({ overflow-hidden z-[101] `} - body={renderBody()} - useDirectRender={true} - /> + body={renderBody()} + useDirectRender={true} + /> + + ); }; diff --git a/packages/common/business_components/src/ui/table/index.tsx b/packages/common/business_components/src/ui/table/index.tsx index fa7c9b7fe..dcf7f4aa4 100644 --- a/packages/common/business_components/src/ui/table/index.tsx +++ b/packages/common/business_components/src/ui/table/index.tsx @@ -30,6 +30,7 @@ type UiTableProps = { boldArray?: number[]; resetSelection?: boolean; index?: number; + headButton?: React.ReactNode | undefined; }; type UiHeadProps = { @@ -53,6 +54,7 @@ export function UiTable({ boldArray = [], resetSelection = false, index = -1, + headButton = undefined, }: UiTableProps) { const [cur, setCur] = useState(1); const [idx, setIdx] = useState(index); @@ -100,9 +102,16 @@ export function UiTable({ return ( <> {headName ? ( -
-

{headName}

-
+ headButton ? ( +
+

{headName}

+ <>{headButton} +
+ ) : ( +
+

{headName}

+
+ ) ) : ( <> )}