diff --git a/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx b/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx index 220ff76f3..0e5feb399 100644 --- a/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx @@ -3,18 +3,15 @@ import { useWrStore } from "@/app/store"; import { fcltDiv, - type chHeadType, - type rainChkZodType, + type rainChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; -import type { TableUpperProps } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; import { excelDownload } from "@/components/lib/func/commom"; import ChkgdExcel from "@/components/ui/modal/chkgdExcel/chkgdExcel"; -import ChkgdModal from "@/components/ui/modal/chkgdModal/chkgdModal"; import RainChkgdTable from "@/components/ui/table/chkgdTable/rainChkgd/rainChkgdTable"; +import RainChkgdTableModal from "@/components/ui/table/chkgdTable/rainChkgd/rainChkgdTableModal"; import RswtChkgdTable from "@/components/ui/table/chkgdTable/rswtChkgd/rswtChkgdTable"; import { ContentsDiv, - filePickedBlob, SearchDiv, SearchFormLeft, SearchFormRight, @@ -37,7 +34,10 @@ export default function Page() { const [searchTxt, setSearchTxt] = useState(); const [updateRainDetail, setUpdateRainDetail] = useState([]); const [selFcltDiv, setSelFcltDiv] = useState("RNB"); - + const [isModal, setIsModal] = useState({ + open: false, + data: {}, + }); const cellClick = () => { if(searchTxt) rainChkInit(searchTxt,'41') @@ -54,13 +54,15 @@ export default function Page() { useEffect(() => { - excelDownload(rainChkgdn.excelList ,'빗물이용시설 지도점검','sample_rainy.xlsx'); + excelDownload(rainChkgdn.excelList ,'빗물이용시설 지도점검','sample_rainy.xlsx', 'A1:AN1'); }, [rainChkgdn.excelList]); const excelClick= useCallback(()=>{ rainChkExcelDownload(code.cityCd, code.sggCd); - }, [excelDownload, code.cityCd, code.sggCd]); - + }, [rainChkExcelDownload, code.cityCd, code.sggCd]); + const handleClick = useCallback((data:any) => { + if(data) setIsModal({ open: true, data: data }); + }, [isModal]); return ( @@ -122,12 +124,12 @@ export default function Page() {
- {selFcltDiv === "RNB" ? : } + {selFcltDiv === "RNB" ? : }
- + { // 1. chkgdnFk + utztnUsgCd 기준 누계 정보 만들기 - const maketotal = rainChk.map((item: any) => { + const maketotal = rainChk.map((item: rainChkZodType) => { const selectTotal = item.guideDetails.reduce((acc: any, cur: any) => { const code = cur.utztnUsgCd; const fk = cur.chkgdnFk; @@ -23,6 +23,8 @@ export const rightTableData = (rainChk: rainChkZodType[]) => { return selectTotal; // 모든 키: `${chkgdnFk}_${utztnUsgCd}` 형태 }); + console.log("maketotal", maketotal); + // 2. 누계 정보들을 하나의 객체로 합치기 (평탄화) const flatTotalMap = Object.assign({}, ...maketotal); @@ -30,8 +32,8 @@ export const rightTableData = (rainChk: rainChkZodType[]) => { // 3. rainyDetails에 누계 정보 주입 const updatedRainyDetails: any[] = []; - rainChk.forEach((rd: any) => { - const details = rd.rainyDetails.forEach((rainyItem: any) => { + rainChk.forEach((rd: rainChkZodType) => { + rd.rainyDetails.forEach((rainyItem: any) => { const key = `${rainyItem.chkgdnFk}_${rainyItem.utztnUsgCd}`; const totals = flatTotalMap[key] || { totalSplyUswtrUtztnQnt: 0, @@ -51,10 +53,6 @@ export const rightTableData = (rainChk: rainChkZodType[]) => { return updatedRainyDetails; }; - - - - export const chRowspanTable = ( makeTableData: any[], headList: TableUpperProps[], @@ -67,10 +65,7 @@ export const chRowspanTable = ( selectedIds: number[], handleCellClick: (index: number, headId: string) => void ) => { - - - - + console.log('chRowspanTable', makeTableData); // rowspan된 셀 렌더링 여부 추적용 const renderedMap = new Map>(); return ( diff --git a/apps/wr/components/lib/func/commom.tsx b/apps/wr/components/lib/func/commom.tsx index 57e885d38..0e53c981f 100644 --- a/apps/wr/components/lib/func/commom.tsx +++ b/apps/wr/components/lib/func/commom.tsx @@ -41,7 +41,6 @@ export const getCity = (val: string, sggCd: SelectDataType[]) => { returnData= { ...item, sggText: sggText, cityText: cityText}; } - // console.log(" returnData ", returnData) return returnData; }); @@ -49,7 +48,7 @@ export const getCity = (val: string, sggCd: SelectDataType[]) => { - export const excelDownload =(excelList: [][], fileNm:string ,sampleFile:string ) =>{ + export const excelDownload =(excelList: [][], fileNm:string ,sampleFile:string, headerRange='A1:V3') =>{ if (excelList.length === 0) return; fetch(`/${sampleFile}`, {}) .then(async (response) => { @@ -59,7 +58,7 @@ export const getCity = (val: string, sggCd: SelectDataType[]) => { // 받은 파일 바인너리 처리 const blobRet = await response.arrayBuffer() // 버퍼에 있는 엑셀데이터 전달 후 json으로 변환 - await filePickedBlob(blobRet, excelList,fileNm, 4) + await filePickedBlob(blobRet, excelList,fileNm, 4,headerRange) }) } diff --git a/apps/wr/components/ui/table/chkgdTable/rainChkgd/rainChkgdTable.tsx b/apps/wr/components/ui/table/chkgdTable/rainChkgd/rainChkgdTable.tsx index 19a1164bd..e9bc79108 100644 --- a/apps/wr/components/ui/table/chkgdTable/rainChkgd/rainChkgdTable.tsx +++ b/apps/wr/components/ui/table/chkgdTable/rainChkgd/rainChkgdTable.tsx @@ -1,41 +1,29 @@ "use client"; +import { headMakeprfmnc } from "@/app/ruseFcltMng/prfmncReg/chkgdnReg/TableHead"; import { useWrStore } from "@/app/store"; import type { rainChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; import { tableData } from "@/components/lib/func/commom"; -import { UiModal } from "@common/business_components/ui"; +import { + chRowspanTable, + rightTableData +} from "@/components/lib/func/TableData"; import { Table, TableBody, TableHeader, } from "@common/components/ui"; -import { useEffect, useState } from "react"; -import ChkgdModal from "../../../modal/chkgdModal/chkgdModal"; -import { headMakeprfmnc } from "@/app/ruseFcltMng/prfmncReg/chkgdnReg/TableHead"; -import { - chRowspanTable, - rightTableData, -} from "@/components/lib/func/TableData"; +import { useCallback, useEffect, useState } from "react"; export default function RainChkgdTable({ - detailUp, + detailUp, handleClick=() => {} }: { detailUp: (data: rainChkZodType[]) => void; + handleClick?: (data:any) => void; }) { const { code, rainChkgdn } = useWrStore((state) => state); const [makeTableData, setMakeTableData] = useState([]); const { rainChk } = rainChkgdn; const [selectedIds, setSelectedIds] = useState([]); const [upRaintDetail, setUpRaintDetail] = useState([]); - const [isModal, setIsModal] = useState({ open: false, data: {} }); - - const handleCellClick = (index: number, headId: string) => { - if ( - headId === "totalSplyUswtrUtztnQnt" || - headId === "totalRuseFcltUtztnQnt" - ) { - setIsModal({ open: true, data: makeTableData[index] }); - } - }; - const toggleSelection = (pk: number, checked: boolean) => { setSelectedIds((prev) => checked ? [...prev, pk] : prev.filter((item) => item !== pk) @@ -53,6 +41,15 @@ export default function RainChkgdTable({ ); }; + const handleCellClick= useCallback((index: number, headId: string) => { + if ( + headId === "totalSplyUswtrUtztnQnt" || + headId === "totalRuseFcltUtztnQnt" + ) { + handleClick(makeTableData[index]) + } + },[makeTableData, handleClick]); + useEffect(() => { @@ -65,8 +62,8 @@ export default function RainChkgdTable({ rowspan: item.rainyDetails?.length, }; }); - - setMakeTableData(rightTableData(rainChg)); + const tableTotalData = rightTableData(rainChg) + setMakeTableData(tableTotalData); }, [rainChk, code]); useEffect(() => { @@ -117,35 +114,24 @@ export default function RainChkgdTable({ detailUp(upRaintDetail); }, [upRaintDetail]); return ( - <> - - - {headMakeprfmnc( - rainChkgdn?.rainChkgdnHeadList, - rainChkgdn?.rainHeadNm - )} - +
+ + {headMakeprfmnc( + rainChkgdn?.rainChkgdnHeadList, + rainChkgdn?.rainHeadNm + )} + - - {chRowspanTable( - makeTableData, - rainChkgdn.rainChkgdnHeadList, - handleChange, - toggleSelection, - selectedIds, - handleCellClick - )} - -
- - { - setIsModal({ open: !open, data: {} }); - }} - bodyClass={"w-[80vw] max-w-none"} - body={} - > - + + {chRowspanTable( + makeTableData, + rainChkgdn.rainChkgdnHeadList, + handleChange, + toggleSelection, + selectedIds, + handleCellClick + )} + + ); } diff --git a/apps/wr/components/ui/table/chkgdTable/rainChkgd/rainChkgdTableModal.tsx b/apps/wr/components/ui/table/chkgdTable/rainChkgd/rainChkgdTableModal.tsx new file mode 100644 index 000000000..3cc5bdbbb --- /dev/null +++ b/apps/wr/components/ui/table/chkgdTable/rainChkgd/rainChkgdTableModal.tsx @@ -0,0 +1,13 @@ +import ChkgdModal from "@/components/ui/modal/chkgdModal/chkgdModal"; +import { UiModal } from "@common/business_components"; + +export default function RainChkgdTableModal({ isModal, setIsModal }: { isModal: { open: boolean; data: any }; setIsModal: (value: { open: boolean; data: any }) => void }) { + return ( { + setIsModal({ open: !open, data: {} }); + }} + bodyClass={"w-[80vw] max-w-none"} + body={} + >) +} \ No newline at end of file diff --git a/packages/common/business_components/package.json b/packages/common/business_components/package.json index 1f02a708e..a23f31058 100644 --- a/packages/common/business_components/package.json +++ b/packages/common/business_components/package.json @@ -43,6 +43,7 @@ "sonner": "^2.0.1", "tailwind-merge": "^3.0.1", "xlsx": "^0.18.5", + "xlsx-populate": "^1.21.0", "zod": "^3.24.2" }, "devDependencies": { @@ -58,6 +59,7 @@ "@storybook/test": "^8.5.3", "@storybook/theming": "^8.5.5", "@vitejs/plugin-react": "^4.3.4", + "@xlsx/xlsx-populate": "^0.0.8", "autoprefixer": "^10.4.20", "eslint": "^9.17.0", "eslint-plugin-react-hooks": "^5.0.0", diff --git a/packages/common/business_components/src/ui/excel/excelStyle.tsx b/packages/common/business_components/src/ui/excel/excelStyle.tsx new file mode 100644 index 000000000..2475d1453 --- /dev/null +++ b/packages/common/business_components/src/ui/excel/excelStyle.tsx @@ -0,0 +1,24 @@ +import * as XlsxPopulate from '@xlsx/xlsx-populate'; +export const excelStyle = async (data: ArrayBuffer, range: string = "A1:V3", fileNm:string) => { + console.log("excelStyle", data, range, fileNm); + const workbook = await XlsxPopulate.Workbook.fromData(data); + const rangeCells = workbook.activeSheet(0)?.sheet(0)?.range(range); + console.log(rangeCells); + rangeCells?.style((styleQ)=>{ + console.log(styleQ); + styleQ.fill({ type: "solid", color: {rgb:"9bc1e7"} }); + styleQ.borderEach( { style: "thin", color: { rgb: "FFFFFF" } } ); + styleQ.wrapText(true); + // styleQ.fontColor({rgb:"FFFFFF"}); + styleQ.fontSize(15); + styleQ.horizontalAlignment("center"); + styleQ.verticalAlignment("center"); + styleQ.bold(true); + }); + const outputData = await workbook.output("array-buffer"); + const blob = new Blob([outputData], { type: "application/octet-stream" }); + const link = document.createElement("a"); + link.href = URL.createObjectURL(blob); + link.download = fileNm+".xlsx"; + link.click(); +} \ No newline at end of file diff --git a/packages/common/business_components/src/ui/excel_download/index.tsx b/packages/common/business_components/src/ui/excel/index.tsx similarity index 81% rename from packages/common/business_components/src/ui/excel_download/index.tsx rename to packages/common/business_components/src/ui/excel/index.tsx index 58ecf8a52..13a814114 100644 --- a/packages/common/business_components/src/ui/excel_download/index.tsx +++ b/packages/common/business_components/src/ui/excel/index.tsx @@ -1,18 +1,18 @@ import * as XLSX from 'xlsx'; +import { excelStyle } from './excelStyle'; type ExcelDownloadProps = { data: JSON | JSON[]; fileName: string; sheetNameArray: string[]; + headerRange: string; } -export const downloadExcel = ({ data, fileName = new Date().getTime().toString() + '.xlsx', sheetNameArray = ["Sheet1"] }: ExcelDownloadProps) => { +export const downloadExcel = ({ data, fileName = new Date().getTime().toString() + '.xlsx', sheetNameArray = ["Sheet1"], headerRange='A1:V3'}: ExcelDownloadProps) => { const worksheet = XLSX.utils.json_to_sheet(Array.isArray(data) ? data : [data]); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, sheetNameArray[0]); - //let buffer = XLSX.write(workbook, { bookType: "xlsx", type: "buffer" }); - //XLSX.write(workbook, { bookType: "xlsx", type: "binary" }); - XLSX.writeFile(workbook, fileName + '.xlsx'); + excelStyle(XLSX.write(workbook, { bookType: "xlsx", type: "buffer" }), headerRange, fileName); }; @@ -59,13 +59,12 @@ export const filePicked=(oFile: File, headerNum:number=0):Promise =>{ - // Create A File Reader HTML5 +export const filePickedBlob=(oFile: ArrayBuffer, data:[][],fileNm:string, headerNum:number=0, headerRange='A1:V3'):Promise =>{ // Create A File Reader HTML5 return new Promise(() => { // Ready The Event For When A File Gets Selected const wb = XLSX.read(oFile, { type: 'binary' }); // Loop Over Each Sheet - wb.SheetNames.forEach(function (sheetName, index) { + wb.SheetNames.forEach(async function (sheetName, index) { // Obtain The Current Row As CSV if(index==0){ const sheet = wb.Sheets[sheetName]; @@ -74,7 +73,7 @@ export const filePickedBlob=(oFile: ArrayBuffer, data:[][],fileNm:string, header // const sheet = XLSX.utils.json_to_sheet(data); XLSX.utils.sheet_add_aoa(sheet, data, { origin: 'A'+headerNum }); // XLSX.utils.book_append_sheet(wb, sheet, 'test'); - XLSX.writeFile(wb, `${fileNm}.xlsx`); + await excelStyle(XLSX.write(wb, { bookType: "xlsx", type: "buffer" }), headerRange, fileNm) } else { console.error(`Sheet "${sheetName}" is undefined.`); } diff --git a/packages/common/business_components/src/ui/index.ts b/packages/common/business_components/src/ui/index.ts index e591be5b5..90bf029c0 100644 --- a/packages/common/business_components/src/ui/index.ts +++ b/packages/common/business_components/src/ui/index.ts @@ -1,7 +1,7 @@ export * from "./chart"; export * from "./date_picker"; export * from "./dialog_ui"; -export * from "./excel_download"; +export * from "./excel"; export * from "./file-upload"; export * from "./form"; export * from "./form_ui"; @@ -19,3 +19,4 @@ export * from "./tab"; export * from "./table"; export * from "./table_div"; export * from "./title"; +