feat:
- 도시침수예방사업 엑셀 보기 모달 구현 - 노후 하수관로 정비사업 엑셀 보기 모달 구현 (엑셀 양식이 확정 안돼서 수정 필요)
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import { TableHead, TableRow } from "@common/components/ui";
|
||||
|
||||
|
||||
export type TableUpperProps = {
|
||||
id: string;
|
||||
title: string;
|
||||
upName?: string;
|
||||
upSequnce?: number;
|
||||
edit?: boolean;
|
||||
visible?: boolean;
|
||||
rowSpan?: number;
|
||||
colSpan?: number;
|
||||
type?: string;
|
||||
selectData?: string; // type 이 seelct 일경우 selectarray 정하기 위해
|
||||
row?: number;
|
||||
mergeCell?: boolean // 셀을 합칠경우
|
||||
maxLength?: number
|
||||
readonly?: boolean
|
||||
};
|
||||
type chHeadType = {
|
||||
headNm: string;
|
||||
text: string;
|
||||
length: number;
|
||||
rows?: number;
|
||||
};
|
||||
|
||||
export const HeadMakeFloodPre = (
|
||||
upHeadList: TableUpperProps[],
|
||||
makeHeadNm: chHeadType[]
|
||||
) => {
|
||||
return (
|
||||
<>
|
||||
<TableRow>
|
||||
{upHeadList
|
||||
.filter((data) => data.visible != false)
|
||||
.map((head: TableUpperProps) =>
|
||||
!head.upSequnce ? (
|
||||
<TableHead
|
||||
className={`border border-gray-600 px-2 py-1 `}
|
||||
rowSpan={2}
|
||||
key={head.id}
|
||||
>
|
||||
{head.title}
|
||||
</TableHead>
|
||||
) : head.upSequnce === 1 ? (
|
||||
<TableHead
|
||||
className={`border border-gray-600 px-2 py-1 `}
|
||||
key={head.id}
|
||||
rowSpan={1}
|
||||
colSpan={
|
||||
makeHeadNm?.find((ch) => ch.headNm === head.upName)?.length
|
||||
}
|
||||
>
|
||||
{makeHeadNm?.find((ch) => ch.headNm === head.upName)?.text}
|
||||
</TableHead>
|
||||
) : null
|
||||
)}
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
{upHeadList
|
||||
.filter((data) => data.visible != false)
|
||||
.map((head: TableUpperProps) =>
|
||||
makeHeadNm?.find((ch) => ch.headNm === head.upName) ? (
|
||||
<TableHead
|
||||
className={`border border-gray-600 px-2 py-1 `}
|
||||
key={head.id}
|
||||
>
|
||||
{head.title}
|
||||
</TableHead>
|
||||
) : null
|
||||
)}
|
||||
</TableRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,200 @@
|
||||
import type { TableUpperProps } from "./headMakeFloodPre";
|
||||
import { Input, TableCell, TableRow } from "@common/components/ui";
|
||||
|
||||
|
||||
// 숫자입력시 , 삽입
|
||||
const formatCommaNumber = (
|
||||
value: number | string | undefined | null
|
||||
): string => {
|
||||
if (value === undefined || value === null || value === "") return "";
|
||||
const num = Number(value);
|
||||
// 숫자로 변환할 수 없는 값이 들어올 경우 빈 문자열을 반환하여 예상치 못한 "NaN" 출력을 방지합니다.
|
||||
if (isNaN(num)) {
|
||||
return "";
|
||||
}
|
||||
return num.toLocaleString();
|
||||
};
|
||||
|
||||
// onchange 텍스트를 숫자로 and ,제거
|
||||
const handleCommaNumberChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
onChange: (value: number | "") => void
|
||||
) => {
|
||||
const raw = e.target.value.replace(/,/g, "");
|
||||
if (!/^\d*$/.test(raw)) return;
|
||||
onChange(raw === "" ? "" : Number(raw));
|
||||
};
|
||||
|
||||
const handleChange = (
|
||||
rowIndex: number,
|
||||
field: any,
|
||||
value: string,
|
||||
setInputData: (data: any) => void
|
||||
) => {
|
||||
if (Number(value) < 0 || !Number(value)) {
|
||||
value = "0";
|
||||
}
|
||||
setInputData((prevData: any) =>
|
||||
prevData?.map((row: any, index: number) => {
|
||||
if (index === rowIndex) {
|
||||
return {
|
||||
...row,
|
||||
[field]: Number(value.replace(/,/g, "")),
|
||||
};
|
||||
}
|
||||
return row;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const InputTableBody = (
|
||||
chgRes: any[] | [],
|
||||
headList: TableUpperProps[],
|
||||
setInputData: (data: any[]) => void,
|
||||
isEditable: boolean = true
|
||||
) => {
|
||||
// mergeCell 처리용
|
||||
const getRowSpan = (data: any[], key: string, rowIndex: number) => {
|
||||
if (rowIndex > 0 && data[rowIndex - 1][key] && String(data[rowIndex][key]).trim() === "") {
|
||||
return 0; // 이전과 같으면 셀을 그리지 않음
|
||||
}
|
||||
let span = 1;
|
||||
for (let i = rowIndex + 1; i < data.length; i++) {
|
||||
if (data[i][key] === data[rowIndex][key]) span++;
|
||||
else break;
|
||||
}
|
||||
return span;
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{chgRes.map((data, dataIndex) => {
|
||||
// 소계/총합계 행 판별
|
||||
const isSubTotal = data["용수명"] === "소계";
|
||||
const isTotal = data["구분"] === 0;
|
||||
|
||||
const bgClass = isTotal
|
||||
? "bg-[#d5e1eb] font-bold"
|
||||
: isSubTotal
|
||||
? "bg-[#e6eef5] font-semibold"
|
||||
: "";
|
||||
|
||||
return (
|
||||
<TableRow key={dataIndex + "row"}>
|
||||
{headList.map((head, headIndex) => {
|
||||
// console.log('head, head.id', head, head.id);
|
||||
const isTextColumn = head.id === "__EMPTY_3";
|
||||
const textAlignClass = isTextColumn
|
||||
? "text-center"
|
||||
: "text-right";
|
||||
|
||||
// // 총합계 행이면 "구분"에서 colSpan=2로 병합
|
||||
// if (
|
||||
// (data["구분"] === 0 || data["용수명"] === 0) &&
|
||||
// head.id === "구분"
|
||||
// ) {
|
||||
// return (
|
||||
// <TableCell
|
||||
// key={head.id + headIndex}
|
||||
// colSpan={2}
|
||||
// className="font-bold text-center bg-[#d5e1eb]"
|
||||
// >
|
||||
// 총합계
|
||||
// </TableCell>
|
||||
// );
|
||||
// }
|
||||
// // 총합계(합계) 행이면 "용수명" 컬럼은 렌더링하지 않음
|
||||
// if (
|
||||
// (data["구분"] === 0 || data["용수명"] === 0) &&
|
||||
// head.id === "용수명"
|
||||
// ) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// // 총합계 행의 나머지 셀에도 색상 적용
|
||||
// if (data["구분"] === 0 || data["용수명"] === 0) {
|
||||
// return (
|
||||
// <TableCell
|
||||
// key={head.id + headIndex}
|
||||
// className="bg-[#d5e1eb] font-bold text-right"
|
||||
// >
|
||||
// {Number(data[head.id])
|
||||
// ? formatCommaNumber(data[head.id as keyof any])
|
||||
// : (data[head.id as keyof any] ?? "")}
|
||||
// </TableCell>
|
||||
// );
|
||||
// }
|
||||
// mergeCell 처리
|
||||
if (head.mergeCell === true) {
|
||||
const rowSpan = getRowSpan(chgRes, head.id, dataIndex);
|
||||
if (rowSpan === 0) return null;
|
||||
return (
|
||||
<TableCell
|
||||
key={head.id + headIndex}
|
||||
rowSpan={rowSpan}
|
||||
className={`w-36 align-middle text-center whitespace-nowrap ${bgClass}`}
|
||||
>
|
||||
{Number(data[head.id])
|
||||
? formatCommaNumber(data[head.id as keyof any])
|
||||
: (data[head.id as keyof any] ?? "")}
|
||||
</TableCell>
|
||||
);
|
||||
}
|
||||
// 기본 셀
|
||||
// return head.edit === true && data.edit !== false ? (
|
||||
return isEditable && head.edit === true && data.edit !== false ? (
|
||||
<TableCell
|
||||
key={head.id + headIndex}
|
||||
className={`w-36 ${bgClass}`}
|
||||
>
|
||||
<Input
|
||||
// className={`${head.id !== "utztnUsgCd" ? "text-right" : "text-center"} !text-xl h-10 w-full`}
|
||||
className={`${textAlignClass} !text-xl h-10 w-full`}
|
||||
onChange={(e) =>
|
||||
handleCommaNumberChange(e, (val) =>
|
||||
handleChange(
|
||||
dataIndex,
|
||||
head.id,
|
||||
String(val),
|
||||
setInputData
|
||||
)
|
||||
)
|
||||
}
|
||||
value={formatCommaNumber(data[head.id as keyof any]) ?? ""}
|
||||
maxLength={12}
|
||||
/>
|
||||
</TableCell>
|
||||
) : (
|
||||
<TableCell
|
||||
key={head.id + headIndex}
|
||||
className={`w-36 ${bgClass}`}
|
||||
>
|
||||
<div
|
||||
// className={`
|
||||
// ${head.id !== "utztnUsgCd" ? "text-right" : "text-center"}
|
||||
// !text-xl w-full h-10
|
||||
// `}
|
||||
className={`${textAlignClass} !text-xl w-full h-10`}
|
||||
>
|
||||
{Number(data[head.id])
|
||||
? formatCommaNumber(data[head.id as keyof any])
|
||||
: (data[head.id as keyof any] ?? "")}
|
||||
</div>
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
{chgRes.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={headList.length}
|
||||
className="h-24 text-center text-gray-500"
|
||||
>
|
||||
조회된 결과가 없습니다.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -2,7 +2,10 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import LoadingOverlay from '@/components/ui/LoadingBar'
|
||||
import { UITab, UiTable } from '@common/business_components/ui'
|
||||
import { Table, TableBody, TableHeader } from "@common/components/ui";
|
||||
import * as XLSX from 'xlsx';
|
||||
import { HeadMakeFloodPre } from './headMakeFloodPre';
|
||||
import { InputTableBody } from './inputTableBody';
|
||||
|
||||
const TABS = [
|
||||
{ val: "management", text: "관리SHEET" },
|
||||
@@ -19,7 +22,7 @@ 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 [resultTableHeader, setResultTableHeader] = useState<{ id: string; title: string, mergeCell?: boolean }[]>([]) //테이블 헤더
|
||||
const [resultTableData, setResultTableData] = useState<any>([{}]) //테이블 데이터
|
||||
const [isLoading, setIsLoading] = useState(false) //로딩 상태
|
||||
|
||||
@@ -53,13 +56,12 @@ const TableModal: React.FC<TableModalProps> = ({ fileNm, atflId }) => {
|
||||
|
||||
// 관리 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 (0 < index && index < 47) || (73 < index);
|
||||
}
|
||||
return false;
|
||||
})
|
||||
@@ -81,17 +83,20 @@ const TableModal: React.FC<TableModalProps> = ({ fileNm, atflId }) => {
|
||||
|
||||
// 운영관리현황 점검결과
|
||||
const resultHeader = jsonDataBySheet['운영관리현황 점검결과']?.[2] || [];
|
||||
const dynamicInspectionHeader = Object.entries(resultHeader)
|
||||
const dynamicResultHeader = Object.entries(resultHeader)
|
||||
.map(([key, value]) => ({
|
||||
id: key,
|
||||
title: String(value).replace(/\r\n/g, '\n')
|
||||
title: String(value).replace(/\r\n/g, '\n'),
|
||||
mergeCell: false,
|
||||
...(key === '__EMPTY_3' && { mergeCell: true }) // __EMPTY_3인 경우 mergeCell: true 추가
|
||||
}));
|
||||
dynamicInspectionHeader.splice(1, 0, {
|
||||
dynamicResultHeader.splice(1, 0, {
|
||||
id: "__EMPTY_2",
|
||||
title: ''
|
||||
title: '',
|
||||
mergeCell: false
|
||||
});
|
||||
setResultTableHeader(dynamicInspectionHeader);
|
||||
console.log('resultTableHeader', dynamicInspectionHeader);
|
||||
setResultTableHeader(dynamicResultHeader);
|
||||
console.log('resultTableHeader', dynamicResultHeader);
|
||||
|
||||
const resultData = Array.isArray(jsonDataBySheet['운영관리현황 점검결과'])
|
||||
? jsonDataBySheet['운영관리현황 점검결과'].slice(3)
|
||||
@@ -238,6 +243,14 @@ const TableModal: React.FC<TableModalProps> = ({ fileNm, atflId }) => {
|
||||
cellClick={undefined}
|
||||
onPageChange={() => { }}
|
||||
/>
|
||||
{/* <Table variant="secondary">
|
||||
<TableHeader className="font-bold">
|
||||
{HeadMakeFloodPre(resultTableHeader, [])}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{InputTableBody(resultTableData ?? [], resultTableHeader, setResultTableData, false)}
|
||||
</TableBody>
|
||||
</Table> */}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import { deleteData, postData, useCookieAuth } from '@/components/lib/api'
|
||||
import { searchData } from '@/components/lib/common/apiHelpers'
|
||||
import { formatDate, handleDownload, yearOptions } from '@/components/lib/utils'
|
||||
import { formatDate, handleDownload } from '@/components/lib/utils'
|
||||
import LoadingOverlay from '@/components/ui/LoadingBar'
|
||||
import { SearchDiv, SearchFormLeft, SearchFormRight, UiTable, UiModal } from '@common/business_components/ui'
|
||||
import ListDiv from '@common/business_components/ui/list_div'
|
||||
@@ -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 = () => {
|
||||
@@ -30,7 +31,8 @@ const Page = () => {
|
||||
const [curPage, setCurPage] = useState(1)
|
||||
const [totalData, setTotalData] = useState(0) //총 데이터 수
|
||||
const [isLoading, setIsLoading] = useState(true) //로딩 상태
|
||||
const [isModalOpen, setIsModalOpen] = useState(false) // 모달 상태
|
||||
const [isModalOpen, setIsModalOpen] = useState(false) // 등록 모달 상태
|
||||
const [isTableModalOpen, setIsTableModalOpen] = useState(false) // 테이블 모달 상태
|
||||
const [resetSelection, setResetSelection] = useState<boolean>(false); // 선택 초기화 상태
|
||||
const [isResetPage, setIsResetPage] = useState<boolean>(false);
|
||||
|
||||
@@ -223,6 +225,7 @@ const Page = () => {
|
||||
setFileNm(fileName);
|
||||
setAtflId(id);
|
||||
setDcrpPplMtncBizSn(tableData[index]?.dcrpPplMtncBizSn);
|
||||
setIsTableModalOpen(true);
|
||||
}
|
||||
}}
|
||||
onPageChange={setCurPage}
|
||||
@@ -245,6 +248,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,465 @@
|
||||
'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';
|
||||
import { HeadMakeColSpan } from '@common/business_components/ui/table/HeadMakeColSpan';
|
||||
import { oldPpTable2HeadList, oldPpTable2NodeList, oldPpTable6HeadList, oldPpTable6NodeList } from './headList';
|
||||
|
||||
const TABS = [
|
||||
{ val: "1", text: "row data" },
|
||||
{ val: "2", text: "1.정밀조사" },
|
||||
{ val: "3", text: "2.정밀조사_정비사업연계" },
|
||||
{ val: "4", text: " 3.정비사업" },
|
||||
{ val: "5", text: "4.정비사업요약" },
|
||||
{ val: "6", text: "4.정비사업최종" },
|
||||
];
|
||||
|
||||
interface TableModalProps {
|
||||
fileNm: string;
|
||||
atflId: string;
|
||||
}
|
||||
|
||||
const TableModal: React.FC<TableModalProps> = ({ fileNm, atflId }) => {
|
||||
|
||||
const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? "1");
|
||||
const [tableHeader1, setTableHeader1] = useState<{ id: string; title: string }[]>([])
|
||||
const [tableHeader2, setTableHeader2] = useState<{ id: string; title: string }[]>([])
|
||||
const [tableHeader4, setTableHeader4] = useState<{ id: string; title: string }[]>([])
|
||||
const [tableHeader5, setTableHeader5] = useState<{ id: string; title: string }[]>([])
|
||||
const [tableData1, setTableData1] = useState<any>([{}])
|
||||
const [tableData2, setTableData2] = useState<any>([{}])
|
||||
const [tableData3, setTableData3] = useState<any>([{}])
|
||||
const [tableData4, setTableData4] = useState<any>([{}])
|
||||
const [tableData5, setTableData5] = useState<any>([{}])
|
||||
const [tableData6, setTableData6] = 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 = ['row data', '1.정밀조사', '2.정밀조사_정비사업연계', ' 3.정비사업', '4.정비사업요약', '4.정비사업최종'];
|
||||
const jsonDataBySheet = await filePickedSelectedSheets(file, 0, targetSheets);
|
||||
|
||||
const header1 = jsonDataBySheet['row data']?.[1] || [];
|
||||
const header2 = jsonDataBySheet['1.정밀조사']?.[1] || [];
|
||||
const header4 = jsonDataBySheet[' 3.정비사업']?.[1] || [];
|
||||
const header5 = jsonDataBySheet['4.정비사업요약']?.[2] || [];
|
||||
|
||||
// row data
|
||||
const allEntries1 = Object.entries(header1);
|
||||
const filteredHeader1 = [
|
||||
allEntries1[0],
|
||||
allEntries1[1],
|
||||
allEntries1[2],
|
||||
allEntries1[3],
|
||||
allEntries1[5],
|
||||
allEntries1[6],
|
||||
allEntries1[7],
|
||||
allEntries1[8],
|
||||
allEntries1[9],
|
||||
allEntries1[10],
|
||||
allEntries1[11],
|
||||
allEntries1[12],
|
||||
allEntries1[13],
|
||||
allEntries1[14],
|
||||
allEntries1[15]
|
||||
].filter((e): e is [string, any] => Boolean(e))
|
||||
.map(([key, value]) => ({
|
||||
id: key,
|
||||
title: String(value).replace(/\r\n/g, '\n')
|
||||
}));
|
||||
filteredHeader1.splice(0, 0, {
|
||||
id: "rowNumber",
|
||||
title: '일련번호'
|
||||
});
|
||||
setTableHeader1(filteredHeader1);
|
||||
|
||||
const data1 = Array.isArray(jsonDataBySheet['row data'])
|
||||
? jsonDataBySheet['row data'].slice(2)
|
||||
: [];
|
||||
const filteredData1 = filterEmptyRows(data1).map((item: any, index: number) => ({
|
||||
...item,
|
||||
rowNumber: index + 1,
|
||||
__EMPTY_1: item.__EMPTY_1 || '-'
|
||||
}));
|
||||
setTableData1(filteredData1);
|
||||
|
||||
// 1.정밀조사
|
||||
const allEntries2 = Object.entries(header2);
|
||||
let filteredHeader2 = [
|
||||
allEntries2[0],
|
||||
allEntries2[1],
|
||||
allEntries2[2],
|
||||
allEntries2[3],
|
||||
allEntries2[5],
|
||||
allEntries2[6],
|
||||
allEntries2[7],
|
||||
allEntries2[8],
|
||||
allEntries2[9],
|
||||
allEntries2[10],
|
||||
allEntries2[11],
|
||||
allEntries2[12],
|
||||
allEntries2[13],
|
||||
allEntries2[14],
|
||||
allEntries2[15]
|
||||
].filter((e): e is [string, any] => Boolean(e))
|
||||
.map(([key, value]) => ({
|
||||
id: key,
|
||||
title: String(value).replace(/\r\n/g, '\n')
|
||||
}));
|
||||
filteredHeader2.splice(0, 0, {
|
||||
id: "rowNumber",
|
||||
title: '일련번호'
|
||||
});
|
||||
setTableHeader2(filteredHeader2);
|
||||
|
||||
const data2 = Array.isArray(jsonDataBySheet['1.정밀조사'])
|
||||
? jsonDataBySheet['1.정밀조사'].slice(2)
|
||||
: [];
|
||||
const filteredData2 = filterEmptyRows(data2).map((item: any, index: number) => ({
|
||||
...item,
|
||||
rowNumber: index + 1
|
||||
}));
|
||||
setTableData2(filteredData2);
|
||||
|
||||
// 2.정밀조사_정비사업연계 (헤더는 import)
|
||||
const data3 = Array.isArray(jsonDataBySheet['2.정밀조사_정비사업연계'])
|
||||
? jsonDataBySheet['2.정밀조사_정비사업연계'].slice(5)
|
||||
: [];
|
||||
const filteredData3 = filterEmptyRows(data3).map((item: any, index: number) => {
|
||||
const newItem = { ...item };
|
||||
// 매핑 에러로 인해 특정 키를 "__EMPTY_0"으로 변경
|
||||
const targetKey = "*1차 출처: 공단자료\r\n*2차 출처: 정산서, 정밀조사보고서 등.\r\n (파랑) 2024년말 기준 실적 제출 자료. 준공 후 재확인 필요\r\n (노랑셀) 준공(또는 정산) 후에 재확인 필요";
|
||||
if (newItem[targetKey] !== undefined) {
|
||||
newItem["__EMPTY_0"] = newItem[targetKey];
|
||||
delete newItem[targetKey];
|
||||
}
|
||||
return {
|
||||
...newItem,
|
||||
__EMPTY_4: newItem?.__EMPTY_4 || '-',
|
||||
rowNumber: index + 1
|
||||
};
|
||||
});
|
||||
setTableData3(filteredData3);
|
||||
|
||||
// 3.정비사업
|
||||
const allEntries4 = Object.entries(header4);
|
||||
const filteredHeader4 = allEntries4.filter((e): e is [string, any] => Boolean(e))
|
||||
.map(([key, value]) => ({
|
||||
id: key,
|
||||
title: String(value).replace(/\r\n/g, '\n')
|
||||
}));
|
||||
|
||||
filteredHeader4.splice(0, 0, {
|
||||
id: "rowNumber",
|
||||
title: '일련번호'
|
||||
});
|
||||
setTableHeader4(filteredHeader4);
|
||||
|
||||
const data4 = Array.isArray(jsonDataBySheet[' 3.정비사업'])
|
||||
? jsonDataBySheet[' 3.정비사업'].slice(2)
|
||||
: [];
|
||||
const filteredData4 = filterEmptyRows(data4).map((item: any, index: number) => ({
|
||||
...item,
|
||||
rowNumber: index + 1,
|
||||
__EMPTY_2: item?.__EMPTY_2 || '-',
|
||||
__EMPTY_11: item?.__EMPTY_11 || '-',
|
||||
}));
|
||||
setTableData4(filteredData4);
|
||||
|
||||
// 4.정비사업요약
|
||||
const allEntries5 = Object.entries(header5);
|
||||
const excludeKeys5 = [
|
||||
": \"정비사업\"sheet에서 사업코드 ctrl+c, Ctrl+v 한 다음 중복된 데이터 제거실시",
|
||||
"__EMPTY",
|
||||
"__EMPTY_8",
|
||||
"__EMPTY_15"
|
||||
];
|
||||
const filteredHeader5 = allEntries5
|
||||
.filter(([key]) => !excludeKeys5.includes(key))
|
||||
.filter((e): e is [string, any] => Boolean(e))
|
||||
.map(([key, value]) => ({
|
||||
id: key,
|
||||
title: String(value).replace(/\r\n/g, '\n'),
|
||||
}));
|
||||
filteredHeader5.splice(0, 0, {
|
||||
id: "rowNumber",
|
||||
title: '일련번호'
|
||||
});
|
||||
setTableHeader5(filteredHeader5);
|
||||
|
||||
const data5 = Array.isArray(jsonDataBySheet['4.정비사업요약'])
|
||||
? jsonDataBySheet['4.정비사업요약'].slice(3)
|
||||
: [];
|
||||
const filteredData5 =
|
||||
filterEmptyRows(data5).map((item: any, index: number) => ({
|
||||
...item,
|
||||
rowNumber: index + 1,
|
||||
}));
|
||||
setTableData5(filteredData5);
|
||||
|
||||
// 4.정비사업최종 (헤더는 import)
|
||||
const data6 = Array.isArray(jsonDataBySheet['4.정비사업최종'])
|
||||
? jsonDataBySheet['4.정비사업최종'].slice(6)
|
||||
: [];
|
||||
const filteredData6 =
|
||||
filterEmptyRows(data6).map((item: any, index: number) => ({
|
||||
...item,
|
||||
rowNumber: index + 1,
|
||||
}));
|
||||
setTableData6(filteredData6);
|
||||
} 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 &&
|
||||
String(value).trim() !== '' &&
|
||||
String(value).trim() !== '-' &&
|
||||
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) {
|
||||
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)}
|
||||
/>
|
||||
{/* row data 탭 */}
|
||||
{activeTab === '1' && (
|
||||
<div className="h-[79vh] w-full overflow-auto rounded-lg border">
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<div className="min-w-[150%]">
|
||||
<UiTable
|
||||
headName={'row data'}
|
||||
tableData={tableData1}
|
||||
headlist={tableHeader1}
|
||||
pageSize={10}
|
||||
total={0}
|
||||
cellClick={undefined}
|
||||
onPageChange={() => { }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 1.정밀조사 탭 */}
|
||||
{activeTab === '2' && (
|
||||
<div className="h-[79vh] w-full overflow-auto rounded-lg border">
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<div className="min-w-[150%]">
|
||||
<UiTable
|
||||
headName={'1.정밀조사'}
|
||||
tableData={tableData2}
|
||||
headlist={tableHeader2}
|
||||
pageSize={10}
|
||||
total={0}
|
||||
className={"overflow-x-auto overflow-y-auto"}
|
||||
cellClick={undefined}
|
||||
onPageChange={() => { }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 2.정밀조사_정비사업연계 탭 */}
|
||||
{activeTab === '3' && (
|
||||
<div className="h-[79vh] w-full overflow-auto rounded-lg border">
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<div className="min-w-[550%]">
|
||||
<UiTable
|
||||
headName={'2.정밀조사_정비사업연계'}
|
||||
tableData={tableData3}
|
||||
headlist={oldPpTable2HeadList}
|
||||
pageSize={10}
|
||||
total={0}
|
||||
className={"overflow-x-auto overflow-y-auto"}
|
||||
cellClick={undefined}
|
||||
onPageChange={() => { }}
|
||||
>
|
||||
<HeadMakeColSpan upHeadList={oldPpTable2NodeList} />
|
||||
</UiTable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 3.정비사업 탭 */}
|
||||
{activeTab === '4' && (
|
||||
<div className="h-[79vh] w-full overflow-auto rounded-lg border">
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<div className="min-w-[160%]">
|
||||
<UiTable
|
||||
headName={'3.정비사업'}
|
||||
tableData={tableData4}
|
||||
headlist={tableHeader4}
|
||||
pageSize={10}
|
||||
total={0}
|
||||
className={"overflow-x-auto overflow-y-auto"}
|
||||
cellClick={undefined}
|
||||
onPageChange={() => { }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 4.정비사업요약 탭 */}
|
||||
{activeTab === '5' && (
|
||||
<div className="h-[79vh] w-full overflow-auto rounded-lg border">
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<div className="min-w-[130%]">
|
||||
<UiTable
|
||||
headName={'4.정비사업요약'}
|
||||
tableData={tableData5}
|
||||
headlist={tableHeader5}
|
||||
pageSize={10}
|
||||
total={0}
|
||||
className={"overflow-x-auto overflow-y-auto"}
|
||||
cellClick={undefined}
|
||||
onPageChange={() => { }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 4.정비사업최종 탭 */}
|
||||
{activeTab === '6' && (
|
||||
<div className="h-[79vh] w-full overflow-auto rounded-lg border">
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<div className="min-w-[200%]">
|
||||
<UiTable
|
||||
headName={'4.정비사업최종'}
|
||||
tableData={tableData6}
|
||||
headlist={oldPpTable6HeadList}
|
||||
pageSize={10}
|
||||
total={0}
|
||||
className={"overflow-x-auto overflow-y-auto"}
|
||||
cellClick={undefined}
|
||||
onPageChange={() => { }}
|
||||
>
|
||||
<HeadMakeColSpan upHeadList={oldPpTable6NodeList} />
|
||||
</UiTable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export default TableModal;
|
||||
Reference in New Issue
Block a user