엑셀업로드 기능 추가 테스트 필요

This commit is contained in:
leedj312
2025-04-29 19:41:09 +09:00
parent 00b342ed85
commit 945e21c6a5
8 changed files with 268 additions and 195 deletions
@@ -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;
@@ -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<string>();
const [testObj, setTestObj] = useState({});
const [updateRainDetail, setUpdateRainDetail] = useState<rainChkZodType[]>([]);
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() {
</Button>
</div>
<div className="flex flex-row space-x-2 pt-3">
<Button size={"sm"} onClick={() => {}}>
<Button size={"sm"} onClick={() => {setIsRegExcel(!isRegExcel)}}>
결과등록
</Button>
<Button
@@ -126,17 +126,11 @@ export default function Page() {
</ListDiv>
</div>
</div>
<UiModal
open={isDetail}
onOpenChange={setIsDetail}
bodyClass={"w-[40vw] max-w-none"}
body={<ChkgdModal type={selFcltDiv} obj={testObj}></ChkgdModal>}
/>
<UiModal
open={isExcel}
onOpenChange={setIsExcel}
open={isRegExcel}
onOpenChange={setIsRegExcel}
bodyClass={"w-[40vw] max-w-none"}
body={<ChkgdExcel type={selFcltDiv}></ChkgdExcel>}
/>
@@ -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;
}
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;
};
@@ -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<typeof rainChkZod>;
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<void>;
rainChkGuidUpdate(rainChk: rainChkZodType): Promise<void>;
excelExportAction(sidoCode: SelectDataType[],sggCode: SelectDataType[]): Promise<void>
rainChkExcelDownload(sidoCode: SelectDataType[],sggCode: SelectDataType[]): Promise<void>
excelDataUPload(excelData:rainChkZodType[]) : Promise<void>
};
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);
@@ -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
};
};
+16 -1
View File
@@ -61,4 +61,19 @@ export const getCity = (val: string, sggCd: SelectDataType[]) => {
// 버퍼에 있는 엑셀데이터 전달 후 json으로 변환
await filePickedBlob(blobRet, excelList,fileNm, 4)
})
}
}
//엑셀은 날짜가 숫자로 나와서 만든 함수
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"
};
@@ -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<File[] | null>(null);
const [excelData , setExcelData] = useState<rainChkZodType[]>()
const defaultValues: z.infer<typeof rainRegExcel> = 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<typeof rainRegExcel>)=> {
if (excelData) {
excelDataUPload(excelData);
}
}, [files])
}
return (
<>
<div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div>
<Title
title={
@@ -57,25 +73,22 @@ export default function ChkgdExcel({ type }: { type: string }) {
{"○점검정보 입력"}
</h3>
<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>
</>
@@ -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(