diff --git a/apps/sn/app/(with-base-layout)/fileValidation.tsx b/apps/sn/app/(with-base-layout)/fileValidation.tsx
new file mode 100644
index 000000000..c0711a7d2
--- /dev/null
+++ b/apps/sn/app/(with-base-layout)/fileValidation.tsx
@@ -0,0 +1,48 @@
+/**
+ * 파일 확장자 검증 유틸리티
+ */
+
+// 파일 확장자 검증 함수
+export const validateFileExtension = (file: File, allowedExtensions: string[]): boolean => {
+ const fileName = file.name.toLowerCase();
+ return allowedExtensions.some(ext => fileName.endsWith(ext.toLowerCase()));
+};
+
+// 파일 확장자 검증 결과 타입
+export interface FileValidationResult {
+ isValid: boolean;
+ errorMessage?: string;
+}
+
+// 파일 검증 설정 타입
+export interface FileValidationConfig {
+ file: File;
+ allowedExtensions: string[];
+ fileDescription: string;
+}
+
+/**
+ * 여러 파일의 확장자를 한 번에 검증하는 함수
+ * @param validationConfigs 검증할 파일들의 설정 배열
+ * @returns 검증 결과. 실패 시 첫 번째 에러 메시지 반환
+ */
+export const validateMultipleFiles = (validationConfigs: FileValidationConfig[]): FileValidationResult => {
+ for (const config of validationConfigs) {
+ if (!validateFileExtension(config.file, config.allowedExtensions)) {
+ const allowedExtStr = config.allowedExtensions.join(', ');
+ return {
+ isValid: false,
+ errorMessage: `${config.fileDescription} 파일은 ${allowedExtStr} 확장자만 업로드 가능합니다.`
+ };
+ }
+ }
+
+ return { isValid: true };
+};
+
+// 자주 사용되는 확장자 상수
+export const FILE_EXTENSIONS = {
+ INP: ['.inp'],
+ EXCEL: ['.xls', '.xlsx', '.xlsm', '.xltx'],
+ ALL_OFFICE: ['.xls', '.xlsx', '.xlsm', '.xltx', '.doc', '.docx', '.ppt', '.pptx']
+};
\ No newline at end of file
diff --git a/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx b/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx
index edee9b4ce..0e7a5c331 100644
--- a/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx
+++ b/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx
@@ -6,11 +6,13 @@ import { DateMonthPicker, 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, useEffect } from 'react'
+import { useState } from 'react'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import { excelToFlowRateData, excelToMatchingResultData, excelToTimePatternData } from '@/components/lib/utils'
import BaseLayout from '../BaseLayout'
+import { validateMultipleFiles, FILE_EXTENSIONS, type FileValidationConfig } from '../../fileValidation';
+import { validateBasicSelectionsWithAlert } from '../../selectionValidation';
const formSchema = z.object({
managenm: z.string(),
@@ -148,6 +150,17 @@ const IrSetting = () => {
const handleAnalysisClick = async () => {
setIsLoadingResult(true); // 로딩 상태 시작
+ // 기본 선택 항목들 검증 (환경청, 시도, 시군구, 하수처리장)
+ if (!validateBasicSelectionsWithAlert(select1, select2, select3, select4, {
+ requireEnv: true,
+ requireSido: true,
+ requireSigungu: true,
+ requireTreatmentPlant: true
+ })) {
+ setIsLoadingResult(false);
+ return;
+ }
+
console.log(uploadedFilesData);
const file1 = uploadedFilesData[0]; // 오수관망도
const file2 = uploadedFilesData[1]; // 수용가-맨홀 매칭결과
@@ -167,6 +180,42 @@ const IrSetting = () => {
return;
}
+ // 파일 확장자 검증 (분리된 유틸리티 사용)
+ const validationConfigs: FileValidationConfig[] = [
+ {
+ file: file1.file,
+ allowedExtensions: FILE_EXTENSIONS.INP,
+ fileDescription: '오수관망도'
+ },
+ {
+ file: file2.file,
+ allowedExtensions: FILE_EXTENSIONS.EXCEL,
+ fileDescription: '수용가-맨홀 매칭결과'
+ },
+ {
+ file: file3.file,
+ allowedExtensions: FILE_EXTENSIONS.EXCEL,
+ fileDescription: '오수량 시간패턴'
+ }
+ ];
+
+ // 유량자료 파일이 있는 경우 검증 목록에 추가
+ if (file4?.file) {
+ validationConfigs.push({
+ file: file4.file,
+ allowedExtensions: FILE_EXTENSIONS.EXCEL,
+ fileDescription: '유량자료'
+ });
+ }
+
+ // 파일 확장자 검증 실행
+ const validationResult = validateMultipleFiles(validationConfigs);
+ if (!validationResult.isValid) {
+ setIsLoadingResult(false);
+ alert(validationResult.errorMessage);
+ return;
+ }
+
// 오수전환율 값 0 ~ 1 사이인지 검증
if (input1 === '' || input1 === null || input1 === undefined) {
setIsLoadingResult(false);
@@ -394,21 +443,6 @@ const IrSetting = () => {
]}
- {/*
-
- handleFileSelection(3, file, cd, id)}
- showButton={false}
- showPreview={true}
- accept={".xls, .xlsx, .xlsm, .xltx"}
- fileIdx={3} />
-
-
-
-
-
*/}
>
)
diff --git a/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx b/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx
index 0ce49c797..970c6d1a9 100644
--- a/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx
+++ b/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx
@@ -13,6 +13,8 @@ import { useFieldArray, useForm } from "react-hook-form"
import { z } from "zod"
import { excelToMatchingResultData, excelToRainData } from '@/components/lib/utils'
import { EditableFormTable, type EditableTableHeadItem } from '../../../components/ui/EditableFormTable'
+import { validateMultipleFiles, FILE_EXTENSIONS, type FileValidationConfig } from '../../fileValidation';
+import { validateBasicSelectionsWithAlert } from '../../selectionValidation';
// zod 스키마 정의
@@ -167,7 +169,18 @@ export default function RTKForm() {
// 침입수 분석 실행 버튼 클릭 핸들러
const handleAnalysisClick = async () => {
setIsLoadingResult(true); // 로딩 상태 시작
-
+
+ // 기본 선택 항목들 검증 (환경청, 시도, 시군구, 하수처리장)
+ if (!validateBasicSelectionsWithAlert(select1, select2, select3, select4, {
+ requireEnv: true,
+ requireSido: true,
+ requireSigungu: true,
+ requireTreatmentPlant: true
+ })) {
+ setIsLoadingResult(false);
+ return;
+ }
+
const formData = form.getValues();
console.log(uploadedFilesData);
const file1 = uploadedFilesData[0]; // 오수관망도
@@ -185,6 +198,33 @@ export default function RTKForm() {
return;
}
+ // 파일 확장자 검증 (분리된 유틸리티 사용)
+ const validationConfigs: FileValidationConfig[] = [
+ {
+ file: file1.file,
+ allowedExtensions: FILE_EXTENSIONS.INP,
+ fileDescription: '오수관망도'
+ },
+ {
+ file: file2.file,
+ allowedExtensions: FILE_EXTENSIONS.EXCEL,
+ fileDescription: '수용가-맨홀 매칭결과'
+ },
+ {
+ file: file3.file,
+ allowedExtensions: FILE_EXTENSIONS.EXCEL,
+ fileDescription: '오수량 시간패턴'
+ }
+ ];
+
+ // 파일 확장자 검증 실행
+ const validationResult = validateMultipleFiles(validationConfigs);
+ if (!validationResult.isValid) {
+ setIsLoadingResult(false);
+ alert(validationResult.errorMessage);
+ return;
+ }
+
// 오수전환율 값 0 ~ 1 사이인지 검증
const rateValue = form.getValues().rate;
if (rateValue === '' || rateValue === null || rateValue === undefined) {
@@ -193,7 +233,7 @@ export default function RTKForm() {
alert('오수전환율을 입력해주세요.');
return;
}
-
+
const numericInput1 = parseFloat(rateValue);
if (isNaN(numericInput1) || numericInput1 < 0 || numericInput1 > 1) {
setIsLoadingResult(false);
@@ -341,35 +381,6 @@ export default function RTKForm() {
console.log('Page changed:', page)
}
- // 선택된 값을 처리하는 공통 함수
- const handleSelectValueChange = (
- val: string | number,
- setter: (value: string | number) => void
- ) => {
- // val이 0이 아닐 경우 .을 기준으로 슬라이싱해서 두번째 값을 설정
- if (val && val !== '0' && val !== 0) {
- const valString = String(val);
- const parts = valString.split('.');
- if (parts.length > 1 && parts[1] !== undefined) {
- setter(parts[1]); // 두 번째 값 (인덱스 1)
- } else {
- setter(val); // .이 없으면 원래 값 사용
- }
- } else {
- setter('0'); // val이 0이면 '0' 설정
- }
- };
-
- // 하수처리장 변경 핸들러
- const handleSwageTreatmentPlantChange = (val: string | number) => {
- handleSelectValueChange(val, setSelect4);
- };
-
- // 하수처리구역 변경 핸들러
- const handleSwageProcessingZoneChange = (val: string | number) => {
- handleSelectValueChange(val, setSelect5);
- };
-
return (
// { }} graphBtn={false} >
diff --git a/apps/sn/app/(with-base-layout)/selectionValidation.tsx b/apps/sn/app/(with-base-layout)/selectionValidation.tsx
new file mode 100644
index 000000000..8cc506d0e
--- /dev/null
+++ b/apps/sn/app/(with-base-layout)/selectionValidation.tsx
@@ -0,0 +1,161 @@
+/**
+ * 선택 항목 검증 유틸리티
+ */
+
+export interface ApiSelectOption {
+ text: string;
+ val: string;
+}
+
+// 선택 항목 검증 설정 타입
+export interface SelectionValidationConfig {
+ value: string | number | ApiSelectOption;
+ description: string;
+ required: boolean;
+}
+
+// 선택 항목 검증 결과 타입
+export interface SelectionValidationResult {
+ isValid: boolean;
+ errorMessage?: string;
+}
+
+/**
+ * 단일 선택 항목이 유효한지 검증하는 함수
+ * @param value 검증할 값
+ * @param isRequired 필수 선택 여부
+ * @returns 유효한지 여부
+ */
+export const isValidSelection = (
+ value: string | number | ApiSelectOption,
+ isRequired: boolean = true
+): boolean => {
+ if (!isRequired) return true;
+
+ if (typeof value === 'object' && value !== null) {
+ // ApiSelectOption 객체인 경우
+ return value.val !== '0' && value.val !== '' && value.val !== null && value.val !== undefined;
+ }
+
+ // 문자열 또는 숫자인 경우
+ return value !== '0' && value !== 0 && value !== '' && value !== null && value !== undefined;
+};
+
+/**
+ * 여러 선택 항목을 한 번에 검증하는 함수
+ * @param validationConfigs 검증할 선택 항목들의 설정 배열
+ * @returns 검증 결과. 실패 시 첫 번째 에러 메시지 반환
+ */
+export const validateMultipleSelections = (
+ validationConfigs: SelectionValidationConfig[]
+): SelectionValidationResult => {
+ for (const config of validationConfigs) {
+ if (!isValidSelection(config.value, config.required)) {
+ return {
+ isValid: false,
+ errorMessage: `${config.description}을(를) 선택해주세요.`
+ };
+ }
+ }
+
+ return { isValid: true };
+};
+
+/**
+ * 여러 선택 항목을 한 번에 검증하고 alert 표시하는 함수
+ * @param validationConfigs 검증할 선택 항목들의 설정 배열
+ * @returns 검증 결과
+ */
+export const validateMultipleSelectionsWithAlert = (
+ validationConfigs: SelectionValidationConfig[]
+): boolean => {
+ const result = validateMultipleSelections(validationConfigs);
+ if (!result.isValid && result.errorMessage) {
+ alert(result.errorMessage);
+ return false;
+ }
+ return true;
+};
+
+/**
+ * 기본 선택 항목들 검증 (환경청, 시도, 시군구, 하수처리장)
+ * @param select1 환경청코드
+ * @param select2 시도코드
+ * @param select3 시군구 객체
+ * @param select4 하수처리장코드
+ * @param options 각 항목의 필수 여부 설정
+ * @returns 검증 결과
+ */
+export const validateBasicSelections = (
+ select1: string,
+ select2: string,
+ select3: ApiSelectOption,
+ select4: string | number,
+ options: {
+ requireEnv?: boolean;
+ requireSido?: boolean;
+ requireSigungu?: boolean;
+ requireTreatmentPlant?: boolean;
+ } = {}
+): SelectionValidationResult => {
+ const {
+ requireEnv = true,
+ requireSido = true,
+ requireSigungu = true,
+ requireTreatmentPlant = true
+ } = options;
+
+ const validationConfigs: SelectionValidationConfig[] = [
+ {
+ value: select1,
+ description: '환경청',
+ required: requireEnv
+ },
+ {
+ value: select2,
+ description: '시도',
+ required: requireSido
+ },
+ {
+ value: select3,
+ description: '시군구',
+ required: requireSigungu
+ },
+ {
+ value: select4,
+ description: '하수처리장',
+ required: requireTreatmentPlant
+ }
+ ];
+
+ return validateMultipleSelections(validationConfigs);
+};
+
+/**
+ * 기본 선택 항목들 검증 및 alert 표시
+ * @param select1 환경청코드
+ * @param select2 시도코드
+ * @param select3 시군구 객체
+ * @param select4 하수처리장코드
+ * @param options 각 항목의 필수 여부 설정
+ * @returns 검증 결과
+ */
+export const validateBasicSelectionsWithAlert = (
+ select1: string,
+ select2: string,
+ select3: ApiSelectOption,
+ select4: string | number,
+ options: {
+ requireEnv?: boolean;
+ requireSido?: boolean;
+ requireSigungu?: boolean;
+ requireTreatmentPlant?: boolean;
+ } = {}
+): boolean => {
+ const result = validateBasicSelections(select1, select2, select3, select4, options);
+ if (!result.isValid && result.errorMessage) {
+ alert(result.errorMessage);
+ return false;
+ }
+ return true;
+};
\ No newline at end of file