feat: 도시침수예방사업 엑셀 데이터 모달로 띄우기 기능 구현 (셀 세로 merge 처리 필요)
This commit is contained in:
@@ -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<boolean>(false); // 선택 초기화 상태
|
||||
const [isResetPage, setIsResetPage] = useState<boolean>(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 = () => {
|
||||
</ListDiv>
|
||||
{/* 파일 업로드 모달 */}
|
||||
<UiModal
|
||||
open={isModalOpen}
|
||||
onOpenChange={setIsModalOpen}
|
||||
open={isUploadModalOpen}
|
||||
onOpenChange={setIsUploadModalOpen}
|
||||
bodyClass="w-auto max-w-[90vw] min-w-[600px] h-auto"
|
||||
noHeaderYn="N"
|
||||
noFooterYn="Y"
|
||||
@@ -251,6 +254,21 @@ const Page = () => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{/* 엑셀 데이터 테이블 모달 */}
|
||||
<UiModal
|
||||
open={isTableModalOpen}
|
||||
onOpenChange={setIsTableModalOpen}
|
||||
bodyClass={`
|
||||
h-[100dvh]
|
||||
w-[90dvw]
|
||||
max-w-[100dvw]
|
||||
max-h-[100dvh]
|
||||
overflow-hidden
|
||||
z-[101]
|
||||
`}
|
||||
body={<TableModal fileNm={fileNm} atflId={atflId} />}
|
||||
useDirectRender={true}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<TableModalProps> = ({ fileNm, atflId }) => {
|
||||
|
||||
const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? "management");
|
||||
const [managementTableHeader, setManagementTableHeader] = useState<{ id: string; title: string }[]>([]) //테이블 헤더
|
||||
const [managementTableData, setManagementTableData] = useState<any>([{}]) //테이블 데이터
|
||||
const [resultTableHeader, setResultTableHeader] = useState<{ id: string; title: string }[]>([]) //테이블 헤더
|
||||
const [resultTableData, setResultTableData] = useState<any>([{}]) //테이블 데이터
|
||||
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<Record<string, any[]>> {
|
||||
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<string, any[]> = {};
|
||||
|
||||
// 지정된 시트 이름만 찾아서 처리
|
||||
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 (
|
||||
<>
|
||||
{/* 탭 영역 */}
|
||||
<UITab
|
||||
defaultValue={activeTab}
|
||||
tabList={TABS.map((tab) => ({
|
||||
value: tab.val,
|
||||
label: tab.text
|
||||
}))}
|
||||
onValueChange={(value: string) => setActiveTab(value)}
|
||||
/>
|
||||
{/* 관리SHEET 탭 */}
|
||||
{activeTab === 'management' && (
|
||||
<div className="h-[79vh] w-full overflow-auto rounded-lg border">
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<div className="w-[400%]">
|
||||
<UiTable
|
||||
headName={'관리SHEET'}
|
||||
tableData={managementTableData}
|
||||
headlist={managementTableHeader}
|
||||
pageSize={10}
|
||||
total={0}
|
||||
cellClick={undefined}
|
||||
onPageChange={() => { }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 운영관리현황 점검결과 탭 */}
|
||||
{activeTab === 'Inspection results' && (
|
||||
<div className="h-[79vh] w-full overflow-auto rounded-lg border">
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<UiTable
|
||||
headName={'운영관리현황 점검결과'}
|
||||
tableData={resultTableData}
|
||||
headlist={resultTableHeader}
|
||||
pageSize={10}
|
||||
total={0}
|
||||
className={"overflow-x-auto overflow-y-auto"}
|
||||
cellClick={undefined}
|
||||
onPageChange={() => { }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export default TableModal;
|
||||
Reference in New Issue
Block a user