From 5aec4b957c073e9559cb16e558980e20869cfadb Mon Sep 17 00:00:00 2001 From: swcho Date: Mon, 25 Aug 2025 13:46:53 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=8F=84=EC=8B=9C=EC=B9=A8=EC=88=98?= =?UTF-8?q?=EC=98=88=EB=B0=A9=EC=82=AC=EC=97=85=20=EC=97=91=EC=85=80=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=AA=A8=EB=8B=AC=EB=A1=9C=20?= =?UTF-8?q?=EB=9D=84=EC=9A=B0=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(=EC=85=80=20=EC=84=B8=EB=A1=9C=20merge=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=ED=95=84=EC=9A=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(with-base-layout)/oper/floodPre/page.tsx | 28 +- .../oper/floodPre/tableModal.tsx | 244 ++++++++++++++++++ 2 files changed, 267 insertions(+), 5 deletions(-) create mode 100644 apps/sn/app/(with-base-layout)/oper/floodPre/tableModal.tsx diff --git a/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx b/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx index 98edf6eb7..a23f3330f 100644 --- a/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx +++ b/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx @@ -14,6 +14,7 @@ import { AUTH_USER_ID } from "@/shared/config"; import { useQuery } from "@tanstack/react-query"; import { ENDPOINT } from "@common/components/config"; import { getUserInfo } from "@common/components/api"; +import TableModal from './tableModal'; const Page = () => { @@ -32,7 +33,8 @@ const Page = () => { const [curPage, setCurPage] = useState(1) //현재 페이지 const [totalData, setTotalData] = useState(0) //총 데이터 수 const [isLoading, setIsLoading] = useState(true) //로딩 상태 - const [isModalOpen, setIsModalOpen] = useState(false) // 모달 상태 + const [isUploadModalOpen, setIsUploadModalOpen] = useState(false) // 등록 모달 상태 + const [isTableModalOpen, setIsTableModalOpen] = useState(false) // 테이블 모달 상태 const [resetSelection, setResetSelection] = useState(false); // 선택 초기화 상태 const [isResetPage, setIsResetPage] = useState(false); @@ -135,14 +137,14 @@ const Page = () => { const data = await postData('v1/sbmrgPrevnt', json) if (data) { alert('파일이 등록되었습니다.'); - setIsModalOpen(false); + setIsUploadModalOpen(false); handleSearch(); } } // 등록 버튼 클릭 핸들러 const handleRegisterClick = () => { - setIsModalOpen(true); + setIsUploadModalOpen(true); } @@ -228,6 +230,7 @@ const Page = () => { setFileNm(fileName); setAtflId(id); setSbmrgPrevntBizSn(tableData[index]?.sbmrgPrevntBizSn); + setIsTableModalOpen(true); } }} onPageChange={setCurPage} @@ -237,8 +240,8 @@ const Page = () => { {/* 파일 업로드 모달 */} { /> } /> + {/* 엑셀 데이터 테이블 모달 */} + } + useDirectRender={true} + /> ) } diff --git a/apps/sn/app/(with-base-layout)/oper/floodPre/tableModal.tsx b/apps/sn/app/(with-base-layout)/oper/floodPre/tableModal.tsx new file mode 100644 index 000000000..547b46610 --- /dev/null +++ b/apps/sn/app/(with-base-layout)/oper/floodPre/tableModal.tsx @@ -0,0 +1,244 @@ +'use client' +import { useState, useEffect } from 'react' +import LoadingOverlay from '@/components/ui/LoadingBar' +import { UITab, UiTable } from '@common/business_components/ui' +import * as XLSX from 'xlsx'; + +const TABS = [ + { val: "management", text: "관리SHEET" }, + { val: "Inspection results", text: "운영관리현황 점검결과" }, +]; + +interface TableModalProps { + fileNm: string; + atflId: string; +} + +const TableModal: React.FC = ({ fileNm, atflId }) => { + + const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? "management"); + const [managementTableHeader, setManagementTableHeader] = useState<{ id: string; title: string }[]>([]) //테이블 헤더 + const [managementTableData, setManagementTableData] = useState([{}]) //테이블 데이터 + const [resultTableHeader, setResultTableHeader] = useState<{ id: string; title: string }[]>([]) //테이블 헤더 + const [resultTableData, setResultTableData] = useState([{}]) //테이블 데이터 + const [isLoading, setIsLoading] = useState(false) //로딩 상태 + + useEffect(() => { + isChckFile(); + }, []); + + // 파일 다운로드 + const isChckFile = async () => { + setIsLoading(true); + try { + if (fileNm === '' || fileNm === undefined) { + alert('파일을 선택해주세요') + setIsLoading(false); + return false + } + + const response = await fetch(`/attach-service/api/v1/download/${atflId}`); + if (!response.ok) { + throw new Error('파일을 가져오는 중 오류가 발생했습니다.'); + } + + const blob = await response.blob(); + + const file = new File([blob], fileNm, { + type: blob.type || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + }); + + const targetSheets = ['관리SHEET', '운영관리현황 점검결과']; + const jsonDataBySheet = await filePickedSelectedSheets(file, 0, targetSheets); + + // 관리 SHEET + const managementHeader = jsonDataBySheet['관리SHEET']?.[7] || []; + // 1번째~47번째, 74번째~80번째 데이터만 필터링 + const filteredManagementHeader = Object.entries(managementHeader) + .filter(([key, value]) => { + const match = key.match(/^__EMPTY_(\d+)$/); + if (match) { + const index = parseInt(match[1] ?? '', 10); + return (index > 0 && index < 47) || (index > 73); + } + return false; + }) + .map(([key, value]) => ({ + id: key, + title: String(value).replace(/\r\n/g, '\n') + })); + + setManagementTableHeader(filteredManagementHeader); + console.log('managementTableHeader', filteredManagementHeader); + + const managementData = Array.isArray(jsonDataBySheet['관리SHEET']) + ? jsonDataBySheet['관리SHEET'].slice(8) + : []; + // console.log('변환 후:', excelToManagementData(managementData)); + const filteredManagementData = filterEmptyRows(managementData); + setManagementTableData(filteredManagementData); + console.log('managementTableData', filteredManagementData); + + // 운영관리현황 점검결과 + const resultHeader = jsonDataBySheet['운영관리현황 점검결과']?.[2] || []; + const dynamicInspectionHeader = Object.entries(resultHeader) + .map(([key, value]) => ({ + id: key, + title: String(value).replace(/\r\n/g, '\n') + })); + setResultTableHeader(dynamicInspectionHeader); + console.log('resultTableHeader', dynamicInspectionHeader); + + const resultData = Array.isArray(jsonDataBySheet['운영관리현황 점검결과']) + ? jsonDataBySheet['운영관리현황 점검결과'].slice(3) + : []; + const filteredResultData = filterEmptyRows(resultData); + setResultTableData(filteredResultData); + console.log('resultTableData', filteredResultData); + } catch (error) { + console.error('파일 처리 중 오류 발생:', error); + alert('파일 처리 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } + } + + function filterEmptyRows(data: any[]) { + return data + .filter(row => { + return Object.entries(row).some(([key, value]) => { + const match = key.match(/^__EMPTY_(\d+)$/); + if (!match || parseInt(match[1] ?? '', 10) < 2) return false; + + return value !== undefined && + value !== null && + value !== '' && + value !== '-' && + String(value).trim() !== ''; + }); + }) + .map((row) => { + const processedRow = { ...row }; + const keys = Object.keys(processedRow); + + keys.forEach((key, keyIndex) => { + // 마지막 속성(비고)이 아닌 경우에만 빈 값을 '-'으로 변경 + if (keyIndex !== keys.length - 1) { + const value = processedRow[key]; + if (value === undefined || + value === null || + value === '' || + String(value).trim() === '') { + processedRow[key] = '-'; + } + } + }); + + return processedRow; + }); + }; + + // 특정 시트만 선택적으로 JSON으로 변환하는 함수 + function filePickedSelectedSheets(file: File, headerNum: number = 0, sheetNames: string[]): Promise> { + const reader = new FileReader(); + + return new Promise((resolve, reject) => { + reader.onload = function (e) { + const data = e.target?.result; + if (!data) { + console.error("Failed to read file: e.target.result is null"); + reject(new Error("Failed to read file")); + return; + } + + // Excel 파일 읽기 + const wb = XLSX.read(data, { type: 'binary', cellDates: true }); + const result: Record = {}; + + // 지정된 시트 이름만 찾아서 처리 + sheetNames.forEach(targetSheetName => { + // 대소문자 구분 없이 시트 이름 찾기 + const sheetName = wb.SheetNames.find(name => + name.toLowerCase() === targetSheetName.toLowerCase() + ); + + if (sheetName) { + const sheet = wb.Sheets[sheetName]; + if (sheet) { + console.log(`Found sheet: "${sheetName}"`); + const jsonData = XLSX.utils.sheet_to_json(sheet, { range: headerNum, raw: false }); + result[targetSheetName] = jsonData; + } + } else { + console.warn(`Sheet "${targetSheetName}" not found in workbook`); + result[targetSheetName] = []; + } + }); + + resolve(result); + }; + + reader.onerror = function () { + console.error("Error reading file"); + reject(new Error("Error reading file")); + }; + + // 파일 읽기 시작 + reader.readAsBinaryString(file); + }); + }; + + + return ( + <> + {/* 탭 영역 */} + ({ + value: tab.val, + label: tab.text + }))} + onValueChange={(value: string) => setActiveTab(value)} + /> + {/* 관리SHEET 탭 */} + {activeTab === 'management' && ( +
+
+ +
+ { }} + /> +
+
+
+ )} + {/* 운영관리현황 점검결과 탭 */} + {activeTab === 'Inspection results' && ( +
+
+ + { }} + /> +
+
+ )} + + ) +}; + +export default TableModal; \ No newline at end of file