Merge commit '4999cc72d66e82ce4ec6379801a6133fa38b9b24' into iipbs/wr/form_fuction

This commit is contained in:
leedj312
2025-05-02 15:47:15 +09:00
11 changed files with 118 additions and 96 deletions
@@ -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<string>();
const [updateRainDetail, setUpdateRainDetail] = useState<rainChkZodType[]>([]);
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 (
<ContentsDiv>
@@ -122,12 +124,12 @@ export default function Page() {
<div className="grid grid-cols-6 gap-4 ">
<div className="col-span-6">
<ListDiv>
{selFcltDiv === "RNB" ? <RainChkgdTable detailUp={setUpdateRainDetail} /> : <RswtChkgdTable />}
{selFcltDiv === "RNB" ? <RainChkgdTable detailUp={setUpdateRainDetail} handleClick={handleClick} /> : <RswtChkgdTable />}
</ListDiv>
</div>
</div>
<RainChkgdTableModal isModal={isModal} setIsModal={setIsModal}/>
<UiModal
open={isRegExcel}
onOpenChange={setIsRegExcel}
@@ -9,6 +9,15 @@ import { buildingToExcelType, excelToRainChkData } from "./excel";
import toast from "react-hot-toast";
import { ToastFormUi } from "@/components/ui/toast/toastFromUi";
const guideDetail=z.object({
chkgdnFk: z.number().nullable(),
utztnUsgCd: z.string(),
chckYmd: z.string(),
ym: z.string(),
splyUswtrUtztnQnt: z.number(),
ruseFcltUtztnQnt: z.number(),
flg: z.string(),
})
export const rainChkZod = z.object({
chkgdnPk: z.number().nullable(),
bdstRuseFcltFk: z.number(),
@@ -21,15 +30,7 @@ export const rainChkZod = z.object({
mdfr: z.string().nullable(),
flg: z.string().nullable(),
guideDetails: z.array(
z.object({
chkgdnFk: z.number().nullable(),
utztnUsgCd: z.string(),
chckYmd: z.string(),
ym: z.string(),
splyUswtrUtztnQnt: z.number(),
ruseFcltUtztnQnt: z.number(),
flg: z.string(),
})
guideDetail
),
building: z.object({
bdstInfoPk: z.number(),
+2 -2
View File
@@ -1,14 +1,14 @@
const LOCAL_PORT = "3000";
const DEV_PORT = "11080";
const DEV_PORT_NEW="8882"
const DEV_PORT_NEW="11082"
export const LOCAL_URL_WR = `http://localhost:${LOCAL_PORT}/api/v1`;
export const DEV_URL_WR = `http://con.mind-one.co.kr:${DEV_PORT}/api/v1`;
//export const DEV_URL_FO = `http://con.mind-one.co.kr:${DEV_PORT_FO}/api/v1`;
export const DEV_URL_FO = `http://192.168.1.193:${DEV_PORT_NEW}/api/v1`;
export const DEV_URL_FO = `http://con.mind-one.co.kr:${DEV_PORT_NEW}/api/v1`;
+6 -11
View File
@@ -4,7 +4,7 @@ import { Checkbox, Input, TableCell, TableRow } from "@common/components/ui";
export const rightTableData = (rainChk: rainChkZodType[]) => {
// 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<string, Set<number>>();
return (
+2 -3
View File
@@ -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)
})
}
@@ -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<any[]>([]);
const { rainChk } = rainChkgdn;
const [selectedIds, setSelectedIds] = useState<number[]>([]);
const [upRaintDetail, setUpRaintDetail] = useState<rainChkZodType[]>([]);
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 (
<>
<Table variant="secondary">
<TableHeader className="font-bold">
{headMakeprfmnc(
rainChkgdn?.rainChkgdnHeadList,
rainChkgdn?.rainHeadNm
)}
</TableHeader>
<Table variant="secondary">
<TableHeader className="font-bold">
{headMakeprfmnc(
rainChkgdn?.rainChkgdnHeadList,
rainChkgdn?.rainHeadNm
)}
</TableHeader>
<TableBody>
{chRowspanTable(
makeTableData,
rainChkgdn.rainChkgdnHeadList,
handleChange,
toggleSelection,
selectedIds,
handleCellClick
)}
</TableBody>
</Table>
<UiModal
open={isModal.open}
onOpenChange={() => {
setIsModal({ open: !open, data: {} });
}}
bodyClass={"w-[80vw] max-w-none"}
body={<ChkgdModal type={"RNB"} obj={isModal.data} />}
></UiModal>
</>
<TableBody>
{chRowspanTable(
makeTableData,
rainChkgdn.rainChkgdnHeadList,
handleChange,
toggleSelection,
selectedIds,
handleCellClick
)}
</TableBody>
</Table>
);
}
@@ -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 (<UiModal
open={isModal.open}
onOpenChange={() => {
setIsModal({ open: !open, data: {} });
}}
bodyClass={"w-[80vw] max-w-none"}
body={<ChkgdModal type={"RNB"} obj={isModal.data} />}
></UiModal>)
}
@@ -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",
@@ -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();
}
@@ -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<JSON | undefin
export const filePickedBlob=(oFile: ArrayBuffer, data:[][],fileNm:string, headerNum:number=0):Promise<JSON | undefined> =>{
// Create A File Reader HTML5
export const filePickedBlob=(oFile: ArrayBuffer, data:[][],fileNm:string, headerNum:number=0, headerRange='A1:V3'):Promise<JSON | undefined> =>{ // Create A File Reader HTML5
return new Promise<JSON | undefined>(() => {
// 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.`);
}
@@ -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";