Files
frontend/apps/sn/app/ii/irsetting/page.tsx
T
swcho 761c3f8c4c
iipbs_sn / build (push) Has been cancelled
feat:
- 침입수 분석, RDII 분석 버튼 위치 변경 및 레이아웃 변경
- RDII 분석 null 값 입력 문제 수정
2025-06-10 14:10:17 +09:00

380 lines
17 KiB
TypeScript

'use client'
import { postData } from '@/components/lib/api'
import FileUploadForm from '@/components/ui/fileUploadForm'
import FormSection from '@/components/ui/formSection'
import { DatePicker, filePicked } from '@common/business_components/ui'
import ListDiv from '@common/business_components/ui/list_div'
import { Input } from '@common/components/ui'
import { zodResolver } from '@hookform/resolvers/zod'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import { excelToFlowRateData, excelToMatchingResultData, excelToTimePatternData } from '../../components/lib/utils/excelToJson'
import BaseLayout from '../BaseLayout'
const formSchema = z.object({
managenm: z.string(),
region: z.string(),
suyeonga: z.number(),
inputdate: z.coerce.date(),
rtkSource: z.string(),
suyeongaPlace: z.string(),
pattern: z.string()
})
// 각 파일 업로드 폼의 데이터를 저장할 인터페이스 정의
interface UploadedFileInfo {
file: File | null;
atflCd: string | null;
atflId: string | null;
// 필요하다면 파일 이름 등 추가 정보 저장 가능
// fileName: string;
}
// 초기 파일 정보 상태
const initialFileInfo: UploadedFileInfo = { file: null, atflCd: null, atflId: null };
interface ApiSelectOption {
text: string;
val: string;
}
const IrSetting = () => {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
managenm: "",
region: "",
suyeonga: 0,
inputdate: new Date(),
rtkSource: "",
suyeongaPlace: "",
pattern: ""
}
})
const [uploadedFilesData, setUploadedFilesData] = useState<UploadedFileInfo[]>([
initialFileInfo, // 0: 오수관망도
initialFileInfo, // 1: 수용가-맨홀 매칭결과
initialFileInfo, // 2: 오수량 시간패턴
initialFileInfo // 3: 유량자료 등록
]);
const [selectedDate, setSelectedDate] = useState(new Date());
const [input1, setInput1] = useState('');
const [input2, setInput2] = useState('');
const [select1, setSelect1] = useState('0'); // 환경청코드
const [select2, setSelect2] = useState('0'); // 시도코드
const [select3, setSelect3] = useState<ApiSelectOption>({ val: '0', text: '전체' }); // 시군구 상태를 객체로 변경
const [select4, setSelect4] = useState<string | number>('0'); // 하수처리장코드
const [select5, setSelect5] = useState<string | number>('0'); // 하수처리구역코드
const [formResetKey, setFormResetKey] = useState(0); // FileUploadForm 리셋을 위한 key 상태
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
// 폼 초기화 함수
const resetAllForms = () => {
form.reset({ // react-hook-form 초기화
managenm: "",
region: "",
suyeonga: 0,
inputdate: new Date(),
rtkSource: "",
suyeongaPlace: "",
pattern: ""
});
setUploadedFilesData([ // 파일 업로드 상태 초기화
initialFileInfo,
initialFileInfo,
initialFileInfo,
initialFileInfo
]);
setSelectedDate(new Date()); // 날짜 선택 초기화
setInput1(''); // 입력 필드 초기화
setInput2(''); // 입력 필드 초기화
setSelect1('0'); // 선택 필드 초기화
setSelect2('0');
setSelect3({ val: '0', text: '전체' });
setSelect4(0);
setSelect5(0);
setFormResetKey(prevKey => prevKey + 1); // FileUploadForm 재마운트를 위해 key 변경
};
// FileUploadForm의 onFileSelect prop으로 전달될 콜백 함수
const handleFileSelection = (
index: number, // FileUploadForm의 fileIdx 값
selectedFile: File | null,
cd: string | null,
id: string | null
) => {
setUploadedFilesData(prevData => {
const newData = [...prevData];
newData[index] = {
file: selectedFile,
atflCd: cd,
atflId: id,
};
return newData;
});
console.log(`Page: File data received for form index ${index}`, { fileName: selectedFile?.name, cd, id });
};
const registFile = async (analysisData: any) => {
setIsLoadingResult(true); // 로딩 상태 시작
console.log("Submitting combined analysis data to backend:", analysisData);
// 추후 API 엔드포인트에 맞게 URL 수정 필요
const data = await postData('v1/inftwtAnls', analysisData);
console.log("Response from backend:", data);
if (data) {
alert("분석 데이터가 성공적으로 제출되었습니다.");
resetAllForms(); // 성공 시 폼 초기화 함수 호출
} else alert("분석 데이터 제출 중 오류가 발생했습니다.");
setIsLoadingResult(false); // 로딩 상태 시작
};
// 침입수 분석 실행 버튼 클릭 핸들러
const handleAnalysisClick = async () => {
setIsLoadingResult(true); // 로딩 상태 시작
console.log(uploadedFilesData);
const file1 = uploadedFilesData[0]; // 오수관망도
const file2 = uploadedFilesData[1]; // 수용가-맨홀 매칭결과
const file3 = uploadedFilesData[2]; // 오수량 시간패턴
const file4 = uploadedFilesData[3]; // 유량자료
console.log("오수관망도 정보:", file1?.file);
console.log("수용가-맨홀 매칭결과 정보:", file2);
console.log("오수량 시간패턴 정보:", file3);
console.log("유량자료 정보:", file4);
// 모든 필수 파일이 선택되었는지 확인 (UploadedFileInfo 객체와 그 안의 file 객체 모두 확인)
// 유량자료는 선택값
if (!file1?.file || !file2?.file || !file3?.file) {
setIsLoadingResult(false);
alert('모든 분석용 파일을 선택해주세요.');
return;
}
type MatchingResult = { anlsSn: number; mhlMngNo: string; rcptrNo: string; };
type TimePattern = { hrId: number; rt?: number | null | undefined; };
type FlowRate = { pplMngNo: string; mtrngDt: string; oilq?: number | null | undefined; };
const filePickers: {
file: UploadedFileInfo;
parser: (excelData: any) => MatchingResult[] | TimePattern[] | FlowRate[];
}[] = [
{ file: file2, parser: excelToMatchingResultData },
{ file: file3, parser: excelToTimePatternData }
];
if (file4?.file) {
filePickers.push({ file: file4, parser: excelToFlowRateData });
}
try {
const parsedResults = await Promise.all(
filePickers.map(async ({ file, parser }) => {
if (!file || !file.file) return null;
const jsonData = await filePicked(file.file, 0);
if (jsonData === undefined) {
console.warn(`Warning: filePicked returned undefined for ${file.file.name}`);
return {
fileName: file.file.name,
atflCd: file.atflCd,
atflId: file.atflId,
parsedData: null
};
}
// jsonData를 각 parser 함수로 처리
const parsedDataBySpecificFunction = parser(jsonData);
return {
fileName: file.file.name,
atflCd: file.atflCd,
atflId: file.atflId,
parsedData: parsedDataBySpecificFunction
};
})
);
// 각 파싱 결과 접근
const matchingResultData = parsedResults.find(r => r && r.fileName === file2.file?.name)?.parsedData;
const timePatternData = parsedResults.find(r => r && r.fileName === file3.file?.name)?.parsedData;
const flowRateData = file4?.file
? parsedResults.find(r => r && r.fileName === file4.file?.name)?.parsedData
: null;
// 새로운 JSON 데이터 생성
const combinedAnalysisData = {
enoCd: select1.toString(), // 환경청코드
ctpvCd: select2.toString(), // 시도코드
sggCd: select3.val.toString(), // 시군구코드
swageTmplntCd: select4.toString(), // 하수처리장코드
swtrmtZoneCd: select5.toString(), // 하수처리구역코드
aplcnYm: new Date(selectedDate).toISOString().substring(0, 7).replace("-", ""), // 적용연월
drtwSwtcRt: parseFloat(input1) || null, // 오수전환율 (숫자로 변환)
anlsInfo: input2, // 분석정보
atflCd: file1?.atflCd, // 첨부파일CD (오수관망도)
atflId: file1?.atflId, // 첨부파일ID (오수관망도)
atflInpNm: file1?.file?.name, // 오수관망도 파일명
atflHrPttnNm: file3?.file?.name, // 오수량 시간패턴 파일명
atflMpngNm: file2?.file?.name, // 수용가-맨홀 매칭결과 파일명
atflOilqNm: file4?.file?.name || null, // 유량자료 파일명 (없을 수 있음)
tbSnRcptrMhlMpng: matchingResultData, // 수용가-맨홀 매칭결과
tbSnHrPttn: timePatternData, // 오수량 시간패턴
tbSnOilqData: flowRateData, // 유량자료 (없을 수 있음)
};
console.log("통합 분석 데이터 (JSON):", combinedAnalysisData);
// 수정된 registFile 함수를 호출하여 combinedAnalysisData를 전송합니다.
await registFile(combinedAnalysisData);
setIsLoadingResult(false);
} catch (error) {
setIsLoadingResult(false);
console.error("파일 파싱 중 오류 발생:", error);
alert("파일 처리 중 오류가 발생했습니다.");
return;
}
}
return (
<>
<BaseLayout
titleName={{ mainTitle: '침입수 분석', firstCardTitle: '침입수 분석 자료 입력' }}
form={form}
onSubmitEvent={() => { }}
graphBtn={false}
analysisBtn={'침입수분석 실행'}
selectBox={{ title: '분석 대상지역 선택' }}
enoCd={select1}
setEnoCd={setSelect1}
ctpvCd={select2}
setCtpvCd={setSelect2}
sigungu={select3}
setSigungu={setSelect3}
selectedSwageTreatmentPlant={select4}
onSwageTreatmentPlantChange={setSelect4}
selectedSwageProcessingZone={select5}
onSwageProcessingZoneChange={setSelect5}
isLoading={isLoadingResult}
onAnalysis={handleAnalysisClick}
>
{[<div className="grid gap-4">
<FormSection name='suyeongaPlace' form={form} titleNm='▷ 오수관망도'>
<FileUploadForm
key={`file-upload-0-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(0, file, cd, id)}
showButton={false}
showPreview={true}
accept={""}
fileIdx={0} />
</FormSection>
<FormSection name='suyeonga' form={form} titleNm='▷ 수용가-맨홀 매칭결과'>
<FileUploadForm
key={`file-upload-1-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(1, file, cd, id)}
showButton={false}
showPreview={true}
accept={".xls, .xlsx, .xlsm, .xltx"}
fileIdx={1} />
</FormSection>
<FormSection name='pattern' form={form} titleNm='▷ 오수량 시간패턴'>
<FileUploadForm
key={`file-upload-2-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(2, file, cd, id)}
showButton={false}
showPreview={true}
accept={".xls, .xlsx, .xlsm, .xltx"}
fileIdx={2} />
</FormSection>
<FormSection
name='inputdate'
form={form}
titleNm='▷ 상수사용량 적용기간'>
<DatePicker
mode={"single"}
field={{
value: selectedDate, // 현재 페이지의 selectedDate 상태 사용
onChange: (date: Date | undefined) => {
if (date) {
setSelectedDate(date);
form.setValue('inputdate', date, { shouldValidate: true });
}
}
}}
selectClass="ml-5 date-picker-trigger" // date-picker.css의 스타일 적용
/>
</FormSection>
<FormSection
name='rtkSource'
form={form}
titleNm='▷ 오수전환율'>
<Input
value={input1}
onChange={(e) => {
const newValue = e.target.value;
if (newValue === "") {
setInput1(newValue);
return;
}
// 숫자와 '.'만 허용하고, '.'은 최대 하나인지 확인
const isValidChars = /^[0-9]*\.?[0-9]*$/.test(newValue);
if (!isValidChars) return; // 유효하지 않은 문자 또는 여러 개의 '.' 포함
// '.' 기호가 포함된 경우, 처음이나 끝에 오면 안 됨
if (newValue.includes('.') && newValue.startsWith('.')) return; // '.'이 처음에는 올 수 없음
setInput1(newValue);
}}
className="ml-5 h-[3.2rem] text-center text-xl w-[150px]"
placeholder="예: 0.75" />
</FormSection>
<FormSection
name='analysis information'
form={form}
titleNm='▷ 분석 정보'>
<Input
value={input2}
onChange={(e) => setInput2(e.target.value)}
className="ml-5 h-[3.2rem] text-center text-xl w-[150px]" />
</FormSection>
<ListDiv>
<div className="card !m-[1.2rem]">
<FormSection name='region' form={form} titleNm='▷ 유량자료 등록'>
<FileUploadForm
key={`file-upload-3-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(3, file, cd, id)}
showButton={false}
showPreview={true}
accept={".xls, .xlsx, .xlsm, .xltx"}
fileIdx={3}
/>
</FormSection>
</div>
</ListDiv>
</div>]}
{/* <div className="flex items-center gap-4">
<FormSection
name='region' form={form} titleNm='▷ 유량자료 등록'>
<FileUploadForm
key={`file-upload-3-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(3, file, cd, id)}
showButton={false}
showPreview={true}
accept={".xls, .xlsx, .xlsm, .xltx"}
fileIdx={3} />
</FormSection>
<div className="flex justify-center mt-1">
<Button size="sm" type="submit" onClick={handleAnalysisClick}>침입수분석 실행</Button>
</div>
</div> */}
</BaseLayout>
</>
)
}
export default IrSetting