From 945e21c6a57af81cc54c0ece0439e9d5b9344608 Mon Sep 17 00:00:00 2001 From: leedj312 Date: Tue, 29 Apr 2025 19:41:09 +0900 Subject: [PATCH] =?UTF-8?q?=EC=97=91=EC=85=80=EC=97=85=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=95=84=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruseFcltMng/fcltSttn/rainSttn/page.tsx | 2 +- .../ruseFcltMng/prfmncReg/chkgdnReg/page.tsx | 28 +- .../prfmncReg/chkgdnReg/rainChkgdn/excel.tsx | 244 ++++++++++-------- .../prfmncReg/chkgdnReg/rainChkgdn/index.tsx | 90 ++++--- .../store/ruseFcltMng/fcltSttn/sttn/index.tsx | 4 +- apps/wr/components/lib/func/commom.tsx | 17 +- .../ui/modal/chkgdExcel/chkgdExcel.tsx | 76 +++--- .../ui/modal/chkgdModal/chkgdModal.tsx | 2 +- 8 files changed, 268 insertions(+), 195 deletions(-) diff --git a/apps/wr/app/ruseFcltMng/fcltSttn/rainSttn/page.tsx b/apps/wr/app/ruseFcltMng/fcltSttn/rainSttn/page.tsx index 84fbdbd5c..371e03075 100644 --- a/apps/wr/app/ruseFcltMng/fcltSttn/rainSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltSttn/rainSttn/page.tsx @@ -18,7 +18,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { headMakeTriple } from "./TableHead"; export default function RainSttn() { - const { code, sttnCommon, excelExportAction, rainSttnSearch } = + const { code, sttnCommon, rainSttnSearch } = useWrStore((state) => state); const { rainRes } = sttnCommon; diff --git a/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx b/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx index 5d8889db1..cd915e33b 100644 --- a/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx @@ -30,13 +30,11 @@ import { useCallback, useEffect, useState } from "react"; //----------------------- export default function Page() { - const { code, rainChkgdn, rainChkInit,rainChkDetilUpdate, excelExportAction } = useWrStore((state) => { + const { code, rainChkgdn, rainChkInit,rainChkDetilUpdate, rainChkExcelDownload } = useWrStore((state) => { return state; }); - const [isDetail, setIsDetail] = useState(false); - const [isExcel, setIsExcel] = useState(false); + const [isRegExcel, setIsRegExcel] = useState(false); const [searchTxt, setSearchTxt] = useState(); - const [testObj, setTestObj] = useState({}); const [updateRainDetail, setUpdateRainDetail] = useState([]); const [selFcltDiv, setSelFcltDiv] = useState("RNB"); @@ -53,13 +51,15 @@ export default function Page() { }, [rainChkgdn.isInit, rainChkInit]); + + useEffect(() => { - excelDownload(rainChkgdn.excelList); + excelDownload(rainChkgdn.excelList ,'빗물이용시설 지도점검','sample_rainy.xlsx'); }, [rainChkgdn.excelList]); const excelClick= useCallback(()=>{ - excelExportAction(code.cityCd, code.sggCd); - }, [excelExportAction, code.cityCd, code.sggCd]); + rainChkExcelDownload(code.cityCd, code.sggCd); + }, [excelDownload, code.cityCd, code.sggCd]); return ( @@ -103,7 +103,7 @@ export default function Page() {
-
+ } - /> - - } /> diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx index ead156cb2..24c8cdebc 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx @@ -1,117 +1,133 @@ -import type { SelectDataType } from "@common/business_components" +import type { SelectDataType } from "@common/business_components"; +import type { rainChkZodType } from "."; +import { TEST_UUID } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; +import { excelSerialToDateString } from "@/components/lib/func/commom"; const useTypes = [ - "조경용수", - "청소화장실용수", - "세척살수용수", - "농업용수", - "공업용수", - "친수용수", - "기타" -] -export type ExcelExportType = { - "NO": string, - "시도": string, - "시군구": string, - "상세주소": string, - "시설명": string, - "운영관리형태": string, - "유량측정방법": string, - "연간운영비(원/년)": string, - "점검일": string, - "이용용도": string, - "보급_1월": string, - "보급_2월": string, - "보급_3월": string, - "보급_4월": string, - "보급_5월": string, - "보급_6월": string, - "보급_7월": string, - "보급_8월": string, - "보급_9월": string, - "보급_10월": string, - "보급_11월": string, - "보급_12월": string, - "빗물_1월": string, - "빗물_2월": string, - "빗물_3월": string, - "빗물_4월": string, - "빗물_5월": string, - "빗물_6월": string, - "빗물_7월": string, - "빗물_8월": string, - "빗물_9월": string, - "빗물_10월": string, - "빗물_11월": string, - "빗물_12월": string, - "PH": string, - "탁도": string, - "총대장균군수": string, - "과태료 부과일(위반일)": string, - "위반사항": string, - "과태료부과금액(천원)": string -} - + "조경용수", + "청소화장실용수", + "세척살수용수", + "농업용수", + "공업용수", + "친수용수", + "기타", +]; export type Buildingtype = { - bdstInfoPk: number, - ctpvCd: 1, - sggCd: 1001, - daddr: "string3", - bdstNm: "string", - frstArchPrmsnDt: "2025-03-25", - archArea: 0, - regDt: "2025-04-16", - rgtr: null, - mdfcnDt: "2025-04-16", - mdfr: null -} -export const buildingToExcelType = (buildings: Buildingtype[], sidoCode: SelectDataType[], sggCdoe: SelectDataType[]) => { - const excelData: ExcelExportType[] = []; - buildings.forEach((building) => { - useTypes.forEach((useType) => { - excelData.push({ - NO: building.bdstInfoPk.toString(), - 시도: sidoCode.find((sido) => sido.val === building.ctpvCd.toString())?.text || "", - 시군구: sggCdoe.find((sido) => sido.val === building.sggCd.toString())?.text || "", - 상세주소: building.daddr, - 시설명: building.bdstNm, - 운영관리형태: "", - 유량측정방법: "", - '연간운영비(원/년)': "", - 점검일: "", - 이용용도: useType, - 보급_1월: "", - 보급_2월: "", - 보급_3월: "", - 보급_4월: "", - 보급_5월: "", - 보급_6월: "", - 보급_7월: "", - 보급_8월: "", - 보급_9월: "", - 보급_10월: "", - 보급_11월: "", - 보급_12월: "", - 빗물_1월: "", - 빗물_2월: "", - 빗물_3월: "", - 빗물_4월: "", - 빗물_5월: "", - 빗물_6월: "", - 빗물_7월: "", - 빗물_8월: "", - 빗물_9월: "", - 빗물_10월: "", - 빗물_11월: "", - 빗물_12월: "", - PH: "", - 탁도: "", - 총대장균군수: "", - "과태료 부과일(위반일)": "", - 위반사항: "", - "과태료부과금액(천원)": "" - }) - }) - }) - return excelData; -} \ No newline at end of file + bdstInfoPk: number; + ctpvCd: 1; + sggCd: 1001; + daddr: "string3"; + bdstNm: "string"; + frstArchPrmsnDt: "2025-03-25"; + archArea: 0; + regDt: "2025-04-16"; + rgtr: null; + mdfcnDt: "2025-04-16"; + mdfr: null; +}; + +export const buildingToExcelType = ( + buildings: Buildingtype[], + sidoCode: SelectDataType[], + sggCdoe: SelectDataType[] +) => { + const excelData: string[][] = []; + buildings.forEach((building) => { + useTypes.forEach((useType) => { + excelData.push([ + building.bdstInfoPk.toString(), + sidoCode.find((sido) => sido.val === building.ctpvCd.toString()) + ?.text || "", + sggCdoe.find((sido) => sido.val === building.sggCd.toString())?.text || + "", + building.daddr, + building.bdstNm, + "", + "", + "", + "", + useType, + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ]); + }); + }); + return excelData; +}; + +export const excelToRainChkData = ( + excelData: any, + year: string, +) => { + + const rainChkgdData: rainChkZodType[] = excelData.map((item: any) => { + const makeGuidDetail = Array.from({ length: 12 }, (_, i) => { + let month = `${i + 1}`; + const splyData = item[`보급_${month}월`]; + const ruseData = item[`빗물_${month}월`]; + if (month.length < 2) month = "0" + month; + return { + utztnUsgCd: item["이용용도"], + ym: year + month, + chckYmd: excelSerialToDateString(item["점검일"]) , + splyUswtrUtztnQnt: splyData !== undefined ? splyData : 0, + ruseFcltUtztnQnt: ruseData !== undefined ? ruseData : 0, + flg: "RNB", + }; + }); + + return { + bdstRuseFcltFk: Number(item["NO"]), + chckYmd: excelSerialToDateString(item["점검일"]) , + spwtPositn: item["유량측정방법"], + rgtr: TEST_UUID, + oilqMeasMthd: item["운영관리형태"], + anlOperCst: Number(item["연간운영비(원/년)"])??0, + operMngCd: item["유량측정방법"], + guideDetails: makeGuidDetail, + rainyDetails: [ + { + utztnUsgCd: item["이용용도"], + ph: Number(item["PH"])??0, + trbt: Number(item["탁도"])??0, + grssClfg: Number(item["총대장균군수"])??0, + etc: item["기타"] ?? "", // 필드 누락 방지 + vltnDt: item["과태료 부과일(위반일)"], + vltnMttr: item["위반사항"], + levyAmt: Number(item["과태료부과금액(천원)"])??0, + rgtr: TEST_UUID, + }, + ], + }; + }); + + return rainChkgdData; +}; diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/index.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/index.tsx index f8832a220..5a74d104d 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/index.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/index.tsx @@ -5,22 +5,22 @@ import { import { fetchType, getFetch, postFectch, putFectch } from "@/components/lib/api"; import type { SelectDataType } from "@common/business_components/ui"; import { z } from "zod"; -import { buildingToExcelType, type ExcelExportType } from "./excel"; +import { buildingToExcelType, excelToRainChkData } from "./excel"; export const rainChkZod = z.object({ - chkgdnPk: z.number(), + chkgdnPk: z.number().nullable(), bdstRuseFcltFk: z.number(), chckYmd: z.string(), - prcsCpct: z.number(), - spwtPositn: z.string(), - regDt: z.string(), - rgtr: z.string(), + prcsCpct: z.number().nullable(), + spwtPositn: z.string().nullable(), + regDt: z.string().nullable(), + rgtr: z.string().nullable(), mdfcnDt: z.string().nullable(), mdfr: z.string().nullable(), - flg: z.string(), + flg: z.string().nullable(), guideDetails: z.array( z.object({ - chkgdnFk: z.number(), + chkgdnFk: z.number().nullable(), utztnUsgCd: z.string(), chckYmd: z.string(), ym: z.string(), @@ -41,20 +41,20 @@ export const rainChkZod = z.object({ rgtr: z.string().nullable(), mdfcnDt: z.string().nullable(), mdfr: z.string().nullable(), - }), + }).nullable() , rainyDetails: z.array( z.object({ - chkgdnFk: z.number(), + chkgdnFk: z.number().nullable(), utztnUsgCd: z.string(), - ph: z.number(), - trbt: z.number(), - grssClfg: z.number(), - etc: z.string(), - vltnDt: z.string(), - vltnMttr: z.string(), - levyAmt: z.number(), - regDt: z.string(), - rgtr: z.string(), + ph: z.number().nullable(), + trbt: z.number().nullable(), + grssClfg: z.number().nullable(), + etc: z.string().nullable(), + vltnDt: z.string().nullable(), + vltnMttr: z.string().nullable(), + levyAmt: z.number().nullable(), + regDt: z.string().nullable(), + rgtr: z.string().nullable(), mdfcnDt: z.string().nullable(), mdfr: z.string().nullable(), }) @@ -65,12 +65,22 @@ export type rainChkZodType = z.infer; export const rainRegExcel = z.object({ bdstInfoPk: z.number().default(111111111), - chckYr: z .string().default("") - .refine((year) =>year.length ===0 || /^\d{4}$/.test(year),{ - message:'4자리 연도만 입력하세요' - }), - file: z.instanceof(File).optional(), -}); + chckYr: z.string() + .default("") + .refine( + (year) => year.length === 0 || /^\d{4}$/.test(year), + { message: "4자리 연도만 입력하세요" } + ) + .refine( + (year) => { + if (year.length === 0) return true; // 빈 문자열은 통과 + const numYear = Number(year); + return !isNaN(numYear) && numYear <= new Date().getFullYear(); + }, + { message: "현재 년도보다 높은 수를 입력할 수 없습니다." } + ), +file: z.instanceof(File).optional(), +}); export const fcltDiv = [ { val: "RST", text: "중수도시설" }, @@ -288,7 +298,7 @@ export type rainChkgdnState = { splHeadList: TableUpperProps[]; rswtDetailHeadList: TableUpperProps[]; isInit: boolean; - excelList:JSON[]; + excelList:[]; }; }; @@ -296,7 +306,8 @@ type rainChkgdnActions = { rainChkInit: (sggCd: string, searchTxt: string) => void; rainChkDetilUpdate(rainChk: rainChkZodType[]): Promise; rainChkGuidUpdate(rainChk: rainChkZodType): Promise; - excelExportAction(sidoCode: SelectDataType[],sggCode: SelectDataType[]): Promise + rainChkExcelDownload(sidoCode: SelectDataType[],sggCode: SelectDataType[]): Promise + excelDataUPload(excelData:rainChkZodType[]) : Promise }; export type rainChkgdnType = rainChkgdnState & rainChkgdnActions; @@ -348,9 +359,11 @@ export const rainChkgdnActionsExport: (set: any) => rainChkgdnActions = ( set: any ) => { return { - excelExportAction:async(sidoCode:SelectDataType[], sggCode:SelectDataType[])=>{ + rainChkExcelDownload:async(sidoCode:SelectDataType[], sggCode:SelectDataType[])=>{ const data = await getFetch(fetchType.WR, "/reuseFc/building/RNB/0") - const excelList = buildingToExcelType(data, sidoCode, sggCode); + // const excelList = buildingToExcelType(data, sidoCode, sggCode); + + const excelList = buildingToExcelType(data, sidoCode, sggCode); set((state: rainChkgdnState) => ({ rainChkgdn: { ...state.rainChkgdn, @@ -358,6 +371,25 @@ export const rainChkgdnActionsExport: (set: any) => rainChkgdnActions = ( }, })); }, + excelDataUPload: async(excelData:rainChkZodType[] ) =>{ + try{ + const res = await postFectch("/reuseChk/rainyExcel", excelData); + if (res.ok) { + alert(" 데이터 입력 완료"); + } + const rainChkRes = await initFetch('2024', '0'); + return set((state: rainChkgdnState) => ({ + ...state, + rainChkgdn: { + ...state.rainChkgdn, + rainChk: rainChkRes, + }, + })); + }catch(error){ + console.log(error); + alert("데이터 입력을 실패하였습니다."); + } + }, rainChkInit: async (searchTxt: string, sggCd: string) => { try { const rainChkRes = await initFetch(searchTxt, sggCd); diff --git a/apps/wr/app/store/ruseFcltMng/fcltSttn/sttn/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltSttn/sttn/index.tsx index 590c4574b..d0dd648e7 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltSttn/sttn/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltSttn/sttn/index.tsx @@ -5,7 +5,7 @@ import { type TableTripleProps, type TableUpperProps, } from "../../fcltReg/rainReg"; -import { excelSttnParsing, type ExcelSttnRainType } from "./excel"; +import { excelSttnParsing, } from "./excel"; const pbnSttHeadNm: chHeadType[] = [ { headNm: "catchment", text: "유역", length: 3 }, @@ -309,7 +309,7 @@ export type sttnState = { pbnSttHeadNm: chHeadType[]; rainSttHeadList: TableTripleProps[]; rainSttHeadNm: chHeadType[]; - excelList:ExcelSttnRainType[]; + excelList:[]; isInit:boolean }; }; diff --git a/apps/wr/components/lib/func/commom.tsx b/apps/wr/components/lib/func/commom.tsx index d9282a9a6..a25ea0278 100644 --- a/apps/wr/components/lib/func/commom.tsx +++ b/apps/wr/components/lib/func/commom.tsx @@ -61,4 +61,19 @@ export const getCity = (val: string, sggCd: SelectDataType[]) => { // 버퍼에 있는 엑셀데이터 전달 후 json으로 변환 await filePickedBlob(blobRet, excelList,fileNm, 4) }) -} \ No newline at end of file +} + + + +//엑셀은 날짜가 숫자로 나와서 만든 함수 +export const excelSerialToDateString = (excelDate: number|undefined): string => { + const excelEpoch = new Date(1899, 11, 30); // Excel 기준일 (1900-01-01보다 하루 전) + let date + if(excelDate){ + date = new Date(excelEpoch.getTime() + excelDate * 86400000); + }else{ + date =new Date() + } + return date.toISOString().split("T")[0] || ""; // "YYYY-MM-DD" +}; + diff --git a/apps/wr/components/ui/modal/chkgdExcel/chkgdExcel.tsx b/apps/wr/components/ui/modal/chkgdExcel/chkgdExcel.tsx index 28aebe0f4..c57953781 100644 --- a/apps/wr/components/ui/modal/chkgdExcel/chkgdExcel.tsx +++ b/apps/wr/components/ui/modal/chkgdExcel/chkgdExcel.tsx @@ -1,4 +1,6 @@ -import { rainRegExcel } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; +import { useWrStore } from "@/app/store"; +import { rainChkZod, rainRegExcel, type rainChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; +import { excelToRainChkData } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel"; import { selectClass } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; import { DatePicker, @@ -9,16 +11,18 @@ import { FormUi, Title, } from "@common/business_components/ui"; -import { Button } from "@common/components/ui"; +import { Button, Input } from "@common/components/ui"; import { zodResolver } from "@hookform/resolvers/zod"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import type { z } from "zod"; export default function ChkgdExcel({ type }: { type: string }) { - const [files, setFiles] = useState(null); - - const [dmdDate, setDmdDate] = useState(new Date()); + const { excelDataUPload } = useWrStore((state) => { + return state; + }); + const [files, setFiles] = useState(null); + const [excelData , setExcelData] = useState() const defaultValues: z.infer = rainRegExcel.parse({}); @@ -27,22 +31,34 @@ export default function ChkgdExcel({ type }: { type: string }) { defaultValues: defaultValues, }); - const onChangeDate = (val: Date | undefined) => { - if (val) setDmdDate(val); - }; - useEffect(() => { - if (files) { - const file = files[0]; - if(file) filePicked(file,1).then((res) => console.log(res)); - }else{ - + if (files) { + const file = files[0]; + + if(form.getValues("chckYr").length<1 ){ + alert("등록년도를 입력해주세요"); + return + } + if (file) + filePicked(file, 0).then((res) => { + if (res !== undefined) + + setExcelData(excelToRainChkData(res, form.getValues("chckYr"))) + }); + } + }, [files]); + + const onSubmit =(values: z.infer)=> { + if (excelData) { + excelDataUPload(excelData); } - }, [files]) + } + return ( <>
+
- <Button className="">저장</Button> + <Button className="" type="submit"> + 저장 + </Button> </div> <div> <FormField control={form.control} - name="chckYmd" + name="chckYr" render={({ field }) => ( - <FormUi label="점검일자"> - <DatePicker - mode="single" - field={{ - value: - field.value && !isNaN(new Date(field.value).getTime()) - ? new Date(field.value) - : new Date(), // Provide a default Date value - onChange: (date) => field.onChange(date), - }} - selectClass={selectClass} + <FormUi label="등록년도"> + <Input + className={`text-3xl`} + type="text" + value={field.value} + onChange={field.onChange} /> </FormUi> )} @@ -87,14 +100,17 @@ export default function ChkgdExcel({ type }: { type: string }) { render={({ field }) => ( <FormUi label="점검결과 파일"> <FileUpload - files={field.value} - setFiles={setFiles} + files={files || []} + setFiles={(files: File[] | null ) => { + if(files) setFiles(files) + } } fileTypes="xlsx" ></FileUpload> </FormUi> )} /> </div> + </form> </Form> </div> </> diff --git a/apps/wr/components/ui/modal/chkgdModal/chkgdModal.tsx b/apps/wr/components/ui/modal/chkgdModal/chkgdModal.tsx index 96b324b3c..c07a84341 100644 --- a/apps/wr/components/ui/modal/chkgdModal/chkgdModal.tsx +++ b/apps/wr/components/ui/modal/chkgdModal/chkgdModal.tsx @@ -1,10 +1,10 @@ -import { headMakeprfmnc } from "@/app/ruseFcltMng/prfmncReg/chkgdnReg/page"; import { useWrStore } from "@/app/store"; import { ScrollArea, Title, UiTable } from "@common/business_components/ui"; import { Button } from "@common/components/ui"; import { useEffect, useState } from "react"; import { GuideDetailsTable } from "../../table/chkgdTable/guideDetailTable/guideDetailTable"; import type { splRuseWaterType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; +import { headMakeprfmnc } from "@/app/ruseFcltMng/prfmncReg/chkgdnReg/TableHead"; export default function ChkgdModal({ type, obj }: { type: string; obj: any }) { const { rainChkgdn, rainChkDetilUpdate, rainChkGuidUpdate } = useWrStore(