feat: I/I 분석 화면 엑셀 파일 업로드 시 Json 변환 기능 구현 및 title 수정

This commit is contained in:
swcho
2025-05-23 15:28:28 +09:00
parent 525fd5bdb1
commit 0183b91c39
5 changed files with 234 additions and 76 deletions
@@ -0,0 +1,63 @@
export const TEST_UUID= '2d4b151d-1828-41e0-91d9-4cd28ea1cc7e'
import { z } from "zod";
// 수용가-맨홀 매칭결과 (아직 안함)
export const MatchingResultZod = z.object({
anls_sn: z.number(),
mhl_mng_no: z.string(), // 일련번호 0 포함인지? number? string?
rcptr_no: z.string(),
});
export type MatchingResultZodType = z.infer<typeof MatchingResultZod>;
export const excelToMatchingResultData = (excelData: any) => {
const MatchingResultData: MatchingResultZodType[] = excelData.map((item: any) => {
return {
anls_sn: Number(item["NO"]),
mhl_mng_no: String(item["FTR_IDN"]), // 임시값이라 추후 수정 필요
rcptr_no: String(item["수용가"]),
};
}).filter((el: any) => el !== undefined);
return MatchingResultData;
};
// 시간패턴
export const timePatternZod = z.object({
hr_id: z.number(),
rt: z.number().nullish(),
});
export type timePatternZodType = z.infer<typeof timePatternZod>;
export const excelToTimePatternData = (excelData: any) => {
const timePatternData: timePatternZodType[] = excelData.map((item: any) => {
return {
hr_id: Number(item["시간"]),
rt: Number(item["비율"]),
};
}).filter((el: any) => el !== undefined);
return timePatternData;
};
// 유량자료
export const flowRateZod = z.object({
ppl_mng_no: z.string(),
mtrng_dt: z.number(), // type 뭘로 해야할지 Date? timeStamp임
oilq: z.number().nullish(),
});
export type flowRateZodType = z.infer<typeof flowRateZod>;
export const excelToflowRateData = (excelData: any) => {
const flowRateData: flowRateZodType[] = excelData.map((item: any) => {
return {
ppl_mng_no: String(item["관로번호"]),
mtrng_dt: new Date(item["시간"]).toISOString(), // type 뭘로 해야할지 Date? timeStamp임
oilq: Number(item["유량(㎥/h)"]),
};
}).filter((el: any) => el !== undefined);
return flowRateData;
};
+41 -21
View File
@@ -4,6 +4,8 @@ import { SearchDiv, SearchFormLeft, SelectBox, SearchFormRight, Form, UiTable }
import ListDiv from '@common/business_components/ui/list_div'
import { Button } from '@common/components/ui'
import React, { type ReactNode } from 'react'
import { SelectBox2 } from '@common/business_components/ui/select/index-2'
type TitleName = {
mainTitle: string
firstCardTitle: string
@@ -12,6 +14,7 @@ type TitleName = {
type SelectBox = {
labelNm?: Label
searchChk?: boolean
title?: string // SearchDiv의 제목 (선택 사항)
}
type Label = {
first: string
@@ -41,15 +44,30 @@ type BaseLayout = {
const BaseLayout = ({ children, titleName, form, onSubmitEvent, graphBtn = false, searchBox }: BaseLayout) => {
return (
<ContentLayout title={titleName.mainTitle}>
<SearchDiv>
<SearchFormLeft>
<SelectBox className="col-span-2" label={searchBox?.labelNm?.first ?? ''} selectArray={[{ text: '한강 유역청', val: '1' }]} onSelectValue={(val) => { }} selectClass={undefined} />
<SelectBox label={searchBox?.labelNm?.second ?? ''} selectArray={[{ text: '경기도', val: '1' }]} className="col-span-2" onSelectValue={() => { }} selectClass='' />
<SelectBox label={searchBox?.labelNm?.third ?? ''} selectArray={[{ text: '양평군', val: '1' }]} className="col-span-3" onSelectValue={() => { }} selectClass='' />
<SelectBox label={searchBox?.labelNm?.fourth ?? ''} selectArray={[{ text: '양평하수처리장', val: '1' }]} className="col-span-3" onSelectValue={() => { }} selectClass='' />
</SearchFormLeft>
{searchBox?.searchChk ? <SearchFormRight><Button>조회</Button></SearchFormRight> : null}
</SearchDiv>
<ListDiv>
{/* searchBox.title이 있으면 h3 태그로 제목을 표시합니다. */}
{searchBox?.title && (
<div className="card-header">
<h3 className="text-2xl font-medium">{searchBox.title}</h3>
<div>
{graphBtn ? <Button className="!mr-[1.2rem]">다운로드</Button> : null}
{/* 그래프 있을 시 */}
{graphBtn ? <Button className="!mr-[1.2rem]">삭제</Button> : null}
</div>
</div>
)}
<div className="flex items-center w-full">
<div className="flex-1">
<SearchFormLeft>
<SelectBox2 label={searchBox?.labelNm?.first ?? ''} selectArray={[{ text: '한강 유역청', val: '1' }]} className="col-span-2" onSelectValue={(val) => { }} selectClass={undefined} />
<SelectBox2 label={searchBox?.labelNm?.second ?? ''} selectArray={[{ text: '경기도', val: '1' }]} className="col-span-2" onSelectValue={() => { }} selectClass='' />
<SelectBox2 label={searchBox?.labelNm?.third ?? ''} selectArray={[{ text: '양평군', val: '1' }]} className="col-span-3" onSelectValue={() => { }} selectClass='' />
<SelectBox2 label={searchBox?.labelNm?.fourth ?? ''} selectArray={[{ text: '양평하수처리장', val: '1' }]} className="col-span-3" onSelectValue={() => { }} selectClass='' />
</SearchFormLeft>
</div>
{searchBox?.searchChk ? <SearchFormRight><Button>조회</Button></SearchFormRight> : null}
</div>
</ListDiv>
<ListDiv>
<div className="card-header">
<h3 className="text-2xl font-medium">{titleName.firstCardTitle}</h3>
@@ -68,18 +86,20 @@ const BaseLayout = ({ children, titleName, form, onSubmitEvent, graphBtn = false
</form>
</Form>
</ListDiv>
{children?.[1] != null ? <ListDiv>
<div className="card-header flex items-center justify-between">
<h3 className="text-2xl font-medium">{titleName?.secondCardTitle}</h3>
<div>
{graphBtn ? <Button className="!mr-[1.2rem]">다운로드</Button> : null}
{/* 그래프 있을 시 */}
{graphBtn ? <Button className="!mr-[1.2rem]">삭제</Button> : null}
</div>
</div>
<div className="card !m-[1.2rem]">{children?.[1]}</div>
</ListDiv> : null}
</ContentLayout>
{
children?.[1] != null ? <ListDiv>
<div className="card-header flex items-center justify-between">
<h3 className="text-2xl font-medium">{titleName?.secondCardTitle}</h3>
<div>
{graphBtn ? <Button className="!mr-[1.2rem]">다운로드</Button> : null}
{/* 그래프 있을 시 */}
{graphBtn ? <Button className="!mr-[1.2rem]">삭제</Button> : null}
</div>
</div>
<div className="card !m-[1.2rem]">{children?.[1]}</div>
</ListDiv> : null
}
</ContentLayout >
)
}
+103 -29
View File
@@ -1,12 +1,15 @@
'use client'
import React, { useState } from 'react'
import { FormField, SelectBox } from '@common/business_components/ui'
import React, { useRef, useState } from 'react'
import { Button, DatePicker, Input } from '@common/components/ui'
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import FormSection from '@/components/ui/formSection'
import BaseLayout from '../BaseLayout'
import { filePicked, FileUpload } from '@common/business_components/ui'
import { excelToflowRateData, excelToMatchingResultData, excelToTimePatternData } from '../../components/lib/utils/excelToJson'
import { postData } from '@/components/lib/api'
const formSchema = z.object({
managenm: z.string(),
region: z.string(),
@@ -14,7 +17,7 @@ const formSchema = z.object({
inputdate: z.coerce.date(),
rtkSource: z.string(),
suyeongaPlace: z.string(),
pattern : z.string()
pattern: z.string()
})
const region: { text: string, val: number }[] = [{ text: '양평', val: 1 }, { text: '강남', val: 2 }, { text: 'test', val: 3 }]
const IrSetting = () => {
@@ -30,35 +33,106 @@ const IrSetting = () => {
pattern: ""
}
})
const [file1, setFile1] = useState<File | null>(null);
const [file2, setFile2] = useState<File | null>(null);
const [file3, setFile3] = useState<File | null>(null);
const [file4, setFile4] = useState<File | null>(null);
const [selectedDate, setSelectedDate] = useState(new Date())
const fileInputRef1 = useRef<HTMLInputElement>(null)
const fileInputRef2 = useRef<HTMLInputElement>(null)
const fileInputRef3 = useRef<HTMLInputElement>(null)
const fileInputRef4 = useRef<HTMLInputElement>(null)
// 버튼 클릭 핸들러
const handleAnalysisClick = async () => {
const file1 = fileInputRef1.current?.files?.[0]
const file2 = fileInputRef2.current?.files?.[0]
const file3 = fileInputRef3.current?.files?.[0]
if (!file1 || !file2 || !file3) {
alert('모든 파일을 선택해주세요.')
return
}
let json2: any = null;
let json3: any = null;
await filePicked(file2, 0).then((res) => {
if (res !== undefined) json2 = excelToMatchingResultData(res);
});
await filePicked(file3, 0).then((res) => {
if (res !== undefined) json3 = excelToTimePatternData(res);
});
console.log(file1)
console.log(json2)
console.log(json3)
}
// 버튼 클릭 핸들러
const handleUploadClick = async () => {
const file4 = fileInputRef4.current?.files?.[0];
if (!file4) {
alert('파일을 선택해주세요.')
return
}
let json4: any = null;
await filePicked(file4, 0).then((res) => {
console.log(res);
if (res !== undefined) json4 = excelToflowRateData(res);
});
console.log(json4);
// json4가 존재하면 postData로 백엔드에 전송
if (json4) {
try {
const response = await postData('your-backend-endpoint', json4); // 'your-backend-endpoint'를 실제 API 경로로 변경
console.log('업로드 결과:', response);
alert('업로드가 완료되었습니다.');
} catch (error) {
console.error('업로드 실패:', error);
alert('업로드에 실패했습니다.');
}
}
}
return (
<BaseLayout titleName={{ mainTitle: '침입수 산정', firstCardTitle: '실측자료 등록', secondCardTitle: '침입수 분석자료 입력' }} form={form} onSubmitEvent={() => { }} graphBtn={false}>
<div className="grid gap-4">
<FormSection name='suyeongaPlace' form={form} titleNm='○ 오수관망도'><div className="w-[150px]">
<Input id="fileNm" type="file" />
</div></FormSection>
<FormSection name='suyeonga' form={form} titleNm='○ 수용가-맨홀 매칭결과'><div className="w-[150px]">
<Input id="fileNm" type="file" />
</div></FormSection>
<FormSection name='pattern' form={form} titleNm='○ 오수량 시간패턴'><div className="w-[150px]">
<Input id="fileNm" type="file" />
</div></FormSection>
<FormSection name='inputdate' form={form} titleNm='○ 상수사용량 적용기간'>
<DatePicker selected={selectedDate} onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date) }} mode={"single"} />
</FormSection>
<FormSection name='rtkSource' form={form} titleNm='○ 오수전환율'><Input className="h-[3.2rem] text-center text-xl w-[150px]" /></FormSection>
<div className="flex justify-end mt-1">
<Button type="submit">침입수분석 실행</Button>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<FormSection name='region' form={form} titleNm='○ 파일위치'>
<div className="w-[150px]">
<Input id="fileNm" type="file" />
<>
<BaseLayout titleName={{ mainTitle: '침입수 분석', firstCardTitle: '침입수 분석 자료 입력', secondCardTitle: '실측자료 등록' }} form={form} onSubmitEvent={() => { }} graphBtn={false} searchBox={{ title: '분석 대장지역 선택' }} >
<div className="grid gap-4">
<FormSection name='suyeongaPlace' form={form} titleNm='▷ 오수관망도'><div className="">
<Input id="fileNm" ref={fileInputRef1} type="file" />
</div></FormSection>
<FormSection name='suyeonga' form={form} titleNm='▷ 수용가-맨홀 매칭결과'><div className="w-[150px]">
<Input id="fileNm" ref={fileInputRef2} type="file" accept=".xls, .xlsx, .xlsm, .xltx" />
</div></FormSection>
<FormSection name='pattern' form={form} titleNm='▷ 오수량 시간패턴'><div className="w-[150px]">
<Input id="fileNm" ref={fileInputRef3} type="file" accept=".xls, .xlsx, .xlsm, .xltx" />
</div></FormSection>
<FormSection name='inputdate' form={form} titleNm='▷ 상수사용량 적용기간'>
<DatePicker selected={selectedDate} onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date) }} mode={"single"} />
</FormSection>
<FormSection name='rtkSource' form={form} titleNm='▷ 오수전환율'><Input className="h-[3.2rem] text-center text-xl w-[150px]" /></FormSection>
<FormSection name='analysis information' form={form} titleNm='▷ 분석 정보'><Input className="h-[3.2rem] text-center text-xl w-[150px]" /></FormSection>
<div className="flex justify-center mt-1">
<Button type="submit" onClick={handleAnalysisClick}>침입수분석 실행</Button>
</div>
</FormSection>
</div>
</BaseLayout>
</div>
<div className="flex items-center gap-24">
<FormSection name='region' form={form} titleNm='▷ 유량자료 등록'>
<div className="w-[150px]">
<Input id="fileNm" ref={fileInputRef4} type="file" accept=".xls, .xlsx, .xlsm, .xltx" />
</div>
</FormSection>
<div className='mt-1'>
<Button type="submit" onClick={handleUploadClick}>자료 업로드</Button>
</div>
</div>
</BaseLayout>
</>
)
}
+26 -25
View File
@@ -88,17 +88,17 @@ export default function RTKForm() {
}
const [selectedDate, setSelectedDate] = useState(new Date())
return (
<BaseLayout titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력' }} form={form} onSubmitEvent={() => { }} graphBtn={false}>
<BaseLayout searchBox={{ title: '분석 대장지역 선택' }} titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력', secondCardTitle: '실측자료 등록' }} form={form} onSubmitEvent={() => { }} graphBtn={false} >
{[<div className="space-y-[1.2rem]" key="form-section">
<div className="grid grid-cols-2 gap-4">
<FormSection name="rtkSource" form={form} titleNm="○ RTK계수 출처"><Input
className="h-10 text-center text-xl w-[150px]"
placeholder=""
/></FormSection>
<FormSection name="rdiiArea" form={form} titleNm="○ RDII 기여면적(ha)"><Input
className="h-10 text-center text-xl w-[150px]"
placeholder=""
/></FormSection></div>
<FormSection name="rtkSource" form={form} titleNm="○ RTK계수 출처"><Input
className="h-10 text-center text-xl w-[150px]"
placeholder=""
/></FormSection>
<FormSection name="rdiiArea" form={form} titleNm="○ RDII 기여면적(ha)"><Input
className="h-10 text-center text-xl w-[150px]"
placeholder=""
/></FormSection></div>
<div className="grid grid-cols-2">
<div>
<div className="mb-[1.2rem] text-xl font-medium">○ 단위도 계수</div>
@@ -132,23 +132,24 @@ export default function RTKForm() {
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<FormSection name="suyeonga" form={form} titleNm="○ 오수관망도"><div className="w-[150px]">
<Input id="fileNm" type="file" />
</div></FormSection>
<FormSection name="regopm" form={form} titleNm="○ 수용가-맨홀 매칭결과"><div className="w-[150px]">
<Input id="fileNm2" type="file" />
</div></FormSection>
<FormSection name='date' form={form} titleNm='○ 상수사용량 적용기간'>
<DatePicker selected={selectedDate} onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date) }} mode={"single"} />
</FormSection>
<FormSection name='rate' form={form} titleNm='○ 오수전환율'><Input className="h-[3.2rem] text-center text-xl w-[150px]" /></FormSection>
<FormSection name="rain" form={form} titleNm="○ 강우자료"><div className="w-[150px]">
<Input id="fileNm3" type="file" />
</div></FormSection>
<FormSection name="suyeonga" form={form} titleNm="○ 오수관망도"><div className="w-[150px]">
<Input id="fileNm" type="file" />
</div></FormSection>
<FormSection name="regopm" form={form} titleNm="○ 수용가-맨홀 매칭결과"><div className="w-[150px]">
<Input id="fileNm2" type="file" />
</div></FormSection>
<FormSection name='date' form={form} titleNm='○ 상수사용량 적용기간'>
<DatePicker selected={selectedDate} onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date) }} mode={"single"} />
</FormSection>
<FormSection name='rate' form={form} titleNm='○ 오수전환율'><Input className="h-[3.2rem] text-center text-xl w-[150px]" /></FormSection>
<FormSection name="rain" form={form} titleNm="○ 강우자료"><div className="w-[150px]">
<Input id="fileNm3" type="file" />
</div></FormSection>
<FormSection name='rate' form={form} titleNm='○ 분석 정보'><Input className="h-[3.2rem] text-center text-xl w-[150px]" /></FormSection>
</div>
<div className="flex justify-end mt-1">
<Button type="submit">RDII분석 실행</Button>
</div>
<div className="flex justify-end mt-1">
<Button type="submit">RDII분석 실행</Button>
</div>
</div>
]}
</BaseLayout >
+1 -1
View File
@@ -57,7 +57,7 @@ const Result = () => {
}
})
return (
<BaseLayout searchBox={{labelNm: {first: "분석기간",second: "침입수",third: "처리장",fourth: "처리구역",},searchChk: true,}} titleName={{ mainTitle: '분석결과', firstCardTitle: '분석이력', secondCardTitle:'RDII 산정결과 현황'}} form={form} onSubmitEvent={() => { }} graphBtn={true}>
<BaseLayout searchBox={{labelNm: {first: "분석기간",second: "분석구분",third: "하수처리장",fourth: "하수처리구역",},searchChk: true,}} titleName={{ mainTitle: '분석결과', firstCardTitle: '분석이력', secondCardTitle:'RDII 산정결과 현황'}} form={form} onSubmitEvent={() => { }} graphBtn={true}>
{[<div key='table1'>
<div className="mb-[1.2rem] text-xl font-medium">○ 침입수 산정결과 현황</div>
<UiTable