diff --git a/apps/sn/app/components/ui/fileUploadForm.tsx b/apps/sn/app/components/ui/fileUploadForm.tsx index 9bb740277..939399bef 100644 --- a/apps/sn/app/components/ui/fileUploadForm.tsx +++ b/apps/sn/app/components/ui/fileUploadForm.tsx @@ -1,13 +1,20 @@ "use client" import { SelectBox } from '@common/business_components/ui'; -import { Button } from '@common/components/ui'; -import React, { useState } from 'react' +import { Button, Input } from '@common/components/ui'; +import React, { useState, useId } from 'react' import type { HttpResponse, Upload } from "tus-js-client"; import { FileUploader } from "@common/components"; import { handleUpload } from "@utils"; import { set } from 'react-hook-form'; -const FileUploadForm = ({ title, onSubmit }: { title: string, onSubmit: (files: string, year: string, quarter: string, code:string, id:string) => void }) => { +interface FileUploadFormProps { + onSubmit: (files: string, year: string, quarter: string, code: string, id: string) => void; + showButton?: boolean; // 버튼 표시 여부 (기본값 true) + showPreview?: boolean; // 파일명 프리뷰 표시 여부 + accept?: string; // 파일 형식 지정 +} + +const FileUploadForm = ({ onSubmit, showButton = true, showPreview = false, accept = ".jpeg,.jpg,.png", }: FileUploadFormProps) => { const [atflCd, setAtflCd] = useState(null); const [atflId, setAtflId] = useState(null); const handleUploadResponse = (file: File) => async (res: HttpResponse) => { @@ -30,76 +37,66 @@ const FileUploadForm = ({ title, onSubmit }: { title: string, onSubmit: (files: const [selectedYear, setSelectedYear] = useState(yearOptions[0]?.val || ''); const [selectedQuarter, setSelectedQuarter] = useState(quater[0]?.val || ''); const [upload, setUpload] = useState(null); + const uniqueId = useId(); // 각 FileUploadForm 인스턴스에 대한 고유 ID 생성 return ( -
-
-

{title}

-
-
-
-
- setSelectedYear(String(value))} - /> setSelectedQuarter(String(value))} +
{/* 컴포넌트 루트 div */} +
{/* 카드 스타일링 */} +
{/* FileUploader와 Button을 정렬하는 메인 flex 컨테이너 */} +
{/* FileUploader의 너비와 오른쪽 여백을 제어하는 컨테이너 */} + { + const selectedFiles = e.target.files; + if (!selectedFiles) return; + + setFiles(selectedFiles); + if (selectedFiles[0]) { + handleUpload(selectedFiles, { + handleUploadStart: setUpload, + handleSuccess: setUpload, + handleResponse: handleUploadResponse(selectedFiles[0]), + }); + } + }} + customPreview={ + showPreview ? ( +
+ {files && files.length > 0 + ? Array.from(files).map((file) => file.name).join(", ") + : "파일을 선택하세요."} +
+ ) : null + } /> -
-
- { - const selectedFiles = e.target.files; - if (!selectedFiles) return; - - setFiles(selectedFiles); - if (selectedFiles[0]) { - handleUpload(selectedFiles, { - handleUploadStart: setUpload, - handleSuccess: setUpload, - handleResponse: handleUploadResponse(selectedFiles[0]), - }); - } - - }} - customPreview={<>} - /> -
- -
+ {showButton ? ( + + ) : null}
diff --git a/apps/sn/app/components/ui/operFileUploadForm.tsx b/apps/sn/app/components/ui/operFileUploadForm.tsx new file mode 100644 index 000000000..7de737e9b --- /dev/null +++ b/apps/sn/app/components/ui/operFileUploadForm.tsx @@ -0,0 +1,109 @@ +"use client" +import { SelectBox } from '@common/business_components/ui'; +import { Button } from '@common/components/ui'; +import React, { useState } from 'react' +import type { HttpResponse, Upload } from "tus-js-client"; +import { FileUploader } from "@common/components"; +import { handleUpload } from "@utils"; +import { set } from 'react-hook-form'; + +const operFileUploadForm = ({ title, onSubmit }: { title: string, onSubmit: (files: string, year: string, quarter: string, code:string, id:string) => void }) => { + const [atflCd, setAtflCd] = useState(null); + const [atflId, setAtflId] = useState(null); + const handleUploadResponse = (file: File) => async (res: HttpResponse) => { + const fileInfo = res.getHeader("X-File-Info"); + if (!fileInfo) return; + const { atflCd, atflId } = JSON.parse(fileInfo) ?? {}; + const orgnlFileNm = file.name; + console.log('orgnlFileNm', orgnlFileNm, atflCd, atflId); + setAtflCd(atflCd); + setAtflId(atflId); + }; + + // 현재 년도부터 10년 전까지의 년도 옵션 생성 + const yearOptions = Array.from({ length: 10 }, (_, i) => { + const year = new Date().getFullYear() - i; + return { text: `${year}년`, val: year.toString() }; + }); + const quater = [{ text: '1분기', val: '1' }, { text: '2분기', val: '2' }, { text: '3분기', val: '3' }, { text: '4분기', val: '4' }] + const [files, setFiles] = useState(null); + const [selectedYear, setSelectedYear] = useState(yearOptions[0]?.val || ''); + const [selectedQuarter, setSelectedQuarter] = useState(quater[0]?.val || ''); + const [upload, setUpload] = useState(null); + + return ( +
+
+

{title}

+
+
+
+
+ setSelectedYear(String(value))} + /> setSelectedQuarter(String(value))} + /> +
+
+ { + const selectedFiles = e.target.files; + if (!selectedFiles) return; + + setFiles(selectedFiles); + if (selectedFiles[0]) { + handleUpload(selectedFiles, { + handleUploadStart: setUpload, + handleSuccess: setUpload, + handleResponse: handleUploadResponse(selectedFiles[0]), + }); + } + + }} + customPreview={<>} + /> +
+ +
+
+
+
+
+ ) +} + +export default operFileUploadForm \ No newline at end of file diff --git a/apps/sn/app/ii/irsetting/page.tsx b/apps/sn/app/ii/irsetting/page.tsx index 17410e3cd..125c58883 100644 --- a/apps/sn/app/ii/irsetting/page.tsx +++ b/apps/sn/app/ii/irsetting/page.tsx @@ -1,6 +1,6 @@ 'use client' import React, { useRef, useState } from 'react' -import { Button, DatePicker, Input } from '@common/components/ui' +import { Button, DatePicker, Input, FileUploader } from '@common/components/ui' import { z } from 'zod' import { zodResolver } from '@hookform/resolvers/zod' import { useForm } from 'react-hook-form' @@ -8,7 +8,10 @@ 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' +import { handleUpload } from '../../components/lib/utils/fileUploadUtil' +import type { HttpResponse, Upload } from "tus-js-client"; +import FileUploadForm from '@/components/ui/fileUploadForm' +import { deleteData, getData, postData } from '@/components/lib/api' const formSchema = z.object({ managenm: z.string(), @@ -33,104 +36,199 @@ const IrSetting = () => { pattern: "" } }) - const [file1, setFile1] = useState(null); - const [file2, setFile2] = useState(null); - const [file3, setFile3] = useState(null); - const [file4, setFile4] = useState(null); - const [selectedDate, setSelectedDate] = useState(new Date()) - const fileInputRef1 = useRef(null) - const fileInputRef2 = useRef(null) - const fileInputRef3 = useRef(null) - const fileInputRef4 = useRef(null) + + const handleUploadResponse = (file: File) => async (res: HttpResponse) => { + const fileInfo = res.getHeader("X-File-Info"); + if (!fileInfo) return; + const { atflCd, atflId } = JSON.parse(fileInfo) ?? {}; + const orgnlFileNm = file.name; + console.log('orgnlFileNm', orgnlFileNm, atflCd, atflId); + setAtflCd(atflCd); + setAtflId(atflId); + }; + + // const fileInputRef1 = useRef(null); + // const fileInputRef2 = useRef(null); + // const fileInputRef3 = useRef(null); + // const fileInputRef4 = useRef(null); + const [fileInfos, setFileInfos] = useState([ + { atflId: null, atflCd: null }, // 파일1 + { atflId: null, atflCd: null }, // 파일2 + { atflId: null, atflCd: null }, // 파일3 + { atflId: null, atflCd: null }, // 파일4 + ]); + const fileInputRef1 = useRef(null); + const fileInputRef2 = useRef(null); + const fileInputRef3 = useRef(null); + const [atflCd, setAtflCd] = useState(null); + const [atflId, setAtflId] = useState(null); + const [upload, setUpload] = useState(null); + const [selectedDate, setSelectedDate] = useState(new Date()); + const [input1, setInput1] = useState(''); + const [input2, setInput2] = useState(''); + const [select1, setSelect1] = useState(''); + const [select2, setSelect2] = useState(''); + const [select3, setSelect3] = useState(''); + const [select4, setSelect4] = useState(''); + + const registFile = async ( + fileNm: string, + year: string, + quater: string, + cd: string, + id: string, + fileIdx: number // 추가: 파일 구분용 인덱스 + ) => { + const data = await postData('v1/sbmrgPrevnt', { + atflNm: fileNm, + mngYr: year, + mngQu: quater, + atflCd: cd, + atflId: id + }); + if (data.ok) { + setFileInfos(prev => + prev.map((info, idx) => + idx === fileIdx + ? { atflId: data.atflId, atflCd: data.atflCd } + : info + ) + ); + } + }; // 버튼 클릭 핸들러 const handleAnalysisClick = async () => { - const file1 = fileInputRef1.current?.files?.[0] - const file2 = fileInputRef2.current?.files?.[0] - const file3 = fileInputRef3.current?.files?.[0] + const file1 = fileInputRef1.current?.files?.[0]; + const fileRefs = [fileInputRef1, fileInputRef2, fileInputRef3]; + const files = fileRefs.map(ref => ref.current?.files?.[0]); + const [file2, file3, file4] = files; - if (!file1 || !file2 || !file3) { - alert('모든 파일을 선택해주세요.') - return - } + // if (!fileInputRef1.current?.files?.[0] || files.some(f => !f)) { + // alert('모든 파일을 select해주세요.'); + // return; + // } - let json2: any = null; - let json3: any = null; + const filePickers = [ + { file: file2, parser: excelToMatchingResultData }, + { file: file3, parser: excelToTimePatternData }, + { file: file4, parser: excelToflowRateData } + ]; - 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(file2); + console.log(file3); + console.log(file4); + + const [json2, json3, json4] = await Promise.all( + filePickers.map(async ({ file, parser }) => { + if (!file) return null; + const res = await filePicked(file, 0); + return res !== undefined ? parser(res) : null; + }) + ); - 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('업로드에 실패했습니다.'); - } - } + // console.log(file1); + // console.log(json2); + // console.log(json3); + // console.log(json4); + console.log(`선택된 날짜: ${selectedDate}`); + console.log(`오수전환율: ${input1}`); + console.log(`분석 정보: ${input2}`); } return ( <> - { }} graphBtn={false} searchBox={{ title: '분석 대장지역 선택' }} > + { }} + graphBtn={false} + searchBox={{ title: '분석 대장지역 선택' }} >
-
- -
-
- -
-
- -
- - { if (date) setSelectedDate(date) }} mode={"single"} /> + + { await registFile(file, year, quater, cd, id, 0) }} + showButton={false} + showPreview={true} + accept={""} /> + + + {/* { await registFile(file, year, quater, cd, id, 1) }} + showButton={false} + showPreview={true} + accept={".xls, .xlsx, .xlsm, .xltx"} /> */} + + + + {/* { await registFile(file, year, quater, cd, id, 2) }} + showButton={false} + showPreview={true} + accept={".xls, .xlsx, .xlsm, .xltx"} /> */} + + + + { if (date) setSelectedDate(date) }} + mode={"single"} /> + + + { + 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" /> + + + setInput2(e.target.value)} + className="ml-5 h-[3.2rem] text-center text-xl w-[150px]" /> + +
+
+ + {/* { await registFile(file, year, quater, cd, id, 3) }} + showButton={false} + showPreview={true} + accept={".xls, .xlsx, .xlsm, .xltx"} /> */} + - -
-
- -
- -
-
-
- -
-
) diff --git a/apps/sn/app/oper/floodPre/page.tsx b/apps/sn/app/oper/floodPre/page.tsx index 98b14cd62..661326090 100644 --- a/apps/sn/app/oper/floodPre/page.tsx +++ b/apps/sn/app/oper/floodPre/page.tsx @@ -2,7 +2,7 @@ import { SearchDiv, SearchFormLeft, SearchFormRight, UiTable } from '@common/business_components/ui' import { Button, DatePicker, Input, Label } from '@common/components/ui' import React, { useEffect, useState } from 'react' -import FileUploadForm from '../../components/ui/fileUploadForm' +import OperFileUploadForm from '../../components/ui/operFileUploadForm' import ListDiv from '@common/business_components/ui/list_div' import ContentLayout from '@/components/ui/contentLayout' import { searchData } from '@/components/lib/common/apiHelpers' @@ -136,7 +136,7 @@ const Page = () => { { if (index != null) { const fileName = tableData[index]?.fileNm ?? ""; setFileNm(fileName); setSbmrgPrevntBizSn(tableData[index]?.sbmrgPrevntBizSn) } }} onPageChange={async (page:number)=>{setCurPage(page); await fetchData()}}> - { await registFile(file, year, quater, cd, id) }} /> + { await registFile(file, year, quater, cd, id) }} /> ) diff --git a/apps/sn/app/oper/oldPp/page.tsx b/apps/sn/app/oper/oldPp/page.tsx index b9f68bc1a..f7ca87ca5 100644 --- a/apps/sn/app/oper/oldPp/page.tsx +++ b/apps/sn/app/oper/oldPp/page.tsx @@ -2,7 +2,7 @@ import { SearchDiv, SearchFormLeft, SearchFormRight, UiTable } from '@common/business_components/ui' import { Button, DatePicker, Input, Label } from '@common/components/ui' import React, { useEffect, useState } from 'react' -import FileUploadForm from '../../components/ui/fileUploadForm' +import OperFileUploadForm from '../../components/ui/operFileUploadForm' import ListDiv from '@common/business_components/ui/list_div' import ContentLayout from '@/components/ui/contentLayout' import { tableHeadlist } from '..' @@ -140,7 +140,7 @@ const Page = () => { { if (index != null) { const fileName = tableData[index]?.fileNm ?? ""; setFileNm(fileName); setSbmrgPrevntBizSn(tableData[index]?.sbmrgPrevntBizSn) } }} onPageChange={async (page:number)=>{setCurPage(page); await fetchData()}}> - { await registFile(file, year, quater, cd, id) }} /> + { await registFile(file, year, quater, cd, id) }} /> )