From ec5cd9e13cd01dadbd587809d5644b37b3325405 Mon Sep 17 00:00:00 2001 From: swcho Date: Mon, 26 May 2025 13:18:18 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20fileUploadForm.tsx=20=EC=B2=A8?= =?UTF-8?q?=EB=B6=80=ED=8C=8C=EC=9D=BC=EB=AA=85=20=ED=91=9C=EC=8B=9C=20?= =?UTF-8?q?=EB=B0=8F=20SELECTBOX=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/components/ui/fileUploadForm.tsx | 135 +++++---- .../app/components/ui/operFileUploadForm.tsx | 109 ++++++++ apps/sn/app/ii/irsetting/page.tsx | 262 ++++++++++++------ apps/sn/app/oper/floodPre/page.tsx | 4 +- apps/sn/app/oper/oldPp/page.tsx | 4 +- 5 files changed, 359 insertions(+), 155 deletions(-) create mode 100644 apps/sn/app/components/ui/operFileUploadForm.tsx 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) }} /> ) From 6d853919825c30a9bcdc42f8c645e5060bba58c1 Mon Sep 17 00:00:00 2001 From: swcho Date: Mon, 26 May 2025 13:44:13 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20fileUploadFrom.tsx=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EB=AA=85=20width=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/components/ui/fileUploadForm.tsx | 2 +- .../app/components/ui/operFileUploadForm.tsx | 53 +++---------------- 2 files changed, 7 insertions(+), 48 deletions(-) diff --git a/apps/sn/app/components/ui/fileUploadForm.tsx b/apps/sn/app/components/ui/fileUploadForm.tsx index 939399bef..f4c7292e9 100644 --- a/apps/sn/app/components/ui/fileUploadForm.tsx +++ b/apps/sn/app/components/ui/fileUploadForm.tsx @@ -43,7 +43,7 @@ const FileUploadForm = ({ onSubmit, showButton = true, showPreview = false, acce
{/* 컴포넌트 루트 div */}
{/* 카드 스타일링 */}
{/* FileUploader와 Button을 정렬하는 메인 flex 컨테이너 */} -
{/* FileUploader의 너비와 오른쪽 여백을 제어하는 컨테이너 */} +
{/* FileUploader의 너비와 오른쪽 여백을 제어하는 컨테이너 */} void }) => { const [atflCd, setAtflCd] = useState(null); @@ -52,53 +53,11 @@ const operFileUploadForm = ({ title, onSubmit }: { title: string, onSubmit: (fil selectArray={quater} onSelectValue={(value: string | number) => 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={<>} - /> -
- -
+
From 92710d1ac79ecea57d48d2fe3c0ac078b4fa926d Mon Sep 17 00:00:00 2001 From: JIeunhuh Date: Mon, 26 May 2025 17:05:41 +0900 Subject: [PATCH 3/8] =?UTF-8?q?=EB=8F=84=EC=8B=9C=EC=B9=A8=EC=88=98?= =?UTF-8?q?=EC=98=88=EB=B0=A9=EC=82=AC=EC=97=85=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C/=EB=8B=A4=EC=9A=B4/=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/components/lib/api/index.ts | 6 ++-- apps/sn/app/components/ui/fileUploadForm.tsx | 7 +++-- apps/sn/app/oper/floodPre/page.tsx | 32 ++++++++++---------- apps/sn/app/oper/index.ts | 14 +++++++-- 4 files changed, 35 insertions(+), 24 deletions(-) diff --git a/apps/sn/app/components/lib/api/index.ts b/apps/sn/app/components/lib/api/index.ts index a171844a1..e2f5cbf95 100644 --- a/apps/sn/app/components/lib/api/index.ts +++ b/apps/sn/app/components/lib/api/index.ts @@ -55,7 +55,7 @@ export const deleteData = async (url: string, data?: object): Promise => { }, body: data ? JSON.stringify(data) : undefined, }; - const res = await fetch(`${BASE_URL}/${url}`, options); - const resData = await res.json(); - return resData; + const res = await fetch(url.startsWith('/') ? `${url}` : `${BASE_URL}/${url}`, options); + if(res.status === 204) return true + return res; }; \ No newline at end of file diff --git a/apps/sn/app/components/ui/fileUploadForm.tsx b/apps/sn/app/components/ui/fileUploadForm.tsx index f4c7292e9..7a4fbcff2 100644 --- a/apps/sn/app/components/ui/fileUploadForm.tsx +++ b/apps/sn/app/components/ui/fileUploadForm.tsx @@ -17,12 +17,13 @@ interface FileUploadFormProps { const FileUploadForm = ({ onSubmit, showButton = true, showPreview = false, accept = ".jpeg,.jpg,.png", }: FileUploadFormProps) => { const [atflCd, setAtflCd] = useState(null); const [atflId, setAtflId] = useState(null); + const [fileName, setFileName] = useState(''); 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); + setFileName(orgnlFileNm); setAtflCd(atflCd); setAtflId(atflId); }; @@ -65,7 +66,7 @@ const FileUploadForm = ({ onSubmit, showButton = true, showPreview = false, acce showPreview ? (
{files && files.length > 0 - ? Array.from(files).map((file) => file.name).join(", ") + ? fileName : "파일을 선택하세요."}
) : null @@ -92,6 +93,8 @@ const FileUploadForm = ({ onSubmit, showButton = true, showPreview = false, acce } const fileNm = files?.[0]?.name || ''; onSubmit(fileNm, selectedYear, selectedQuarter, atflCd || '', atflId || ''); + setFiles(null); // 파일 업로드 후 파일 목록 초기화 + setFileName(''); // 파일명 초기화 }} > 등록 diff --git a/apps/sn/app/oper/floodPre/page.tsx b/apps/sn/app/oper/floodPre/page.tsx index 661326090..ec0608fd7 100644 --- a/apps/sn/app/oper/floodPre/page.tsx +++ b/apps/sn/app/oper/floodPre/page.tsx @@ -12,18 +12,19 @@ import { formatDate } from '@/components/lib/utils/date' import { deleteData, getData, postData } from '@/components/lib/api' const Page = () => { + // 조회조건 const [fromDate, setFromDate] = useState(new Date()) const [toDate, setToDate] = useState(new Date()) const [manageYear, setManageYear] = useState('') const [mngQu, setMngQu] = useState('') const [usr, setUsr] = useState('') const [pjNm, setpjNm] = useState('') - const [fileNm, setFileNm] = useState('') - const [atflId, setAtflId] = useState('') - const [atflCd, setAtflCd] = useState('') - const [sbmrgPrevntBizSn, setSbmrgPrevntBizSn] = useState('') - const [tableData, setTableData] = useState([{}]) - const [curPage, setCurPage] = useState(1) + // 파일업로드 관련 상태 + const [fileNm, setFileNm] = useState('') //파일명 + const [atflId, setAtflId] = useState('') //파일 ID + const [sbmrgPrevntBizSn, setSbmrgPrevntBizSn] = useState('') //파일 일련번호 + const [tableData, setTableData] = useState([{}]) //테이블 데이터 + const [curPage, setCurPage] = useState(1) //현재 페이지 const fetchData = async () => { const data = await searchData('v1/sbmrgPrevnt', { fromRegDt: formatDate(fromDate), @@ -49,30 +50,29 @@ const Page = () => { handleDownload(`/attach-service/api/v1/download/${atflId}`, fileNm) } const isDeleteFile = async () => { - if (fileNm === '' || fileNm === undefined) { + if (sbmrgPrevntBizSn === '' || sbmrgPrevntBizSn === undefined) { alert('삭제할 파일을 선택해주세요') return false } - const data = await getData(`/attach-service/api/v1/atfl/attachments/${atflId}`) + const data = await deleteData(`/attach-service/api/v1/atfl/attachments/${atflId}`) if (data) { const delData = await deleteData(`v1/sbmrgPrevnt/${sbmrgPrevntBizSn}`) - if (delData) { + if (delData.status === 200) { alert('삭제되었습니다.') fetchData() - } else { - alert('삭제에 실패했습니다.') } } } - const registFile = async (fileNm: string, year: string, quater: string, cd: string, id: string) => { + const registFile = async (fileName: string, year: string, quater: string, cd: string, id: string) => { const data = await postData('v1/sbmrgPrevnt', { - atflNm: fileNm, + atflNm: fileName, mngYr: year, mngQu: quater, atflCd: cd, - atflId: id + atflId: id, + sbmrgPrevntBizNm:fileName }) - if (data.ok) { setAtflId(data.atflId); setAtflCd(data.atflCd) } + if (data) {alert('파일이 등록되었습니다.'); fetchData() } } return ( @@ -133,7 +133,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()}}> + { if (index != null) {const fileName = tableData[index]?.sbmrgPrevntBizNm ?? ""; setFileNm(fileName); const id = tableData[index]?.atflId ?? ''; setAtflId(id); setSbmrgPrevntBizSn(tableData[index]?.sbmrgPrevntBizSn) } }} onPageChange={async (page:number)=>{setCurPage(page); await fetchData()}}> { await registFile(file, year, quater, cd, id) }} /> diff --git a/apps/sn/app/oper/index.ts b/apps/sn/app/oper/index.ts index f4ebe7eeb..54ba00374 100644 --- a/apps/sn/app/oper/index.ts +++ b/apps/sn/app/oper/index.ts @@ -1,9 +1,17 @@ export const tableHeadlist = [ { id: 'sbmrgPrevntBizSn', title: '일련번호' }, - { id: 'fileNm', title: '파일명' }, + { id: 'sbmrgPrevntBizNm', title: '파일명' }, { id: 'mngYr', title: '년도' }, { id: 'mngQu', title: '분기' }, - { id: 'registDt', title: '등록 일자' }, - { id: 'registUsr', title: '등록자' } + { id: 'regDt', title: '등록 일자' }, + { id: 'rgtr', title: '등록자' } +] +export const tableHeadlistOldPp = [ + { id: 'dcrpPplMtncBizSn', title: '일련번호' }, + { id: 'dcrpPplMtncBizNm', title: '파일명' }, + { id: 'mngYr', title: '년도' }, + { id: 'mngQu', title: '분기' }, + { id: 'regDt', title: '등록 일자' }, + { id: 'rgtr', title: '등록자' } ] \ No newline at end of file From 3d3a5858e3a4f91db3265c9fbd1b359b5a9a6ff3 Mon Sep 17 00:00:00 2001 From: swcho Date: Tue, 27 May 2025 09:48:21 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20fileUploadForm.tsx=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EB=B2=84=ED=8A=BC=20=EC=97=86=EC=9D=B4=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=A0=80=EC=9E=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84,=20page.tsx=20JSON=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EB=B0=B1=EC=97=94=EB=93=9C=20?= =?UTF-8?q?API=20=EC=97=B0=EA=B2=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/components/ui/fileUploadForm.tsx | 108 +++++-- apps/sn/app/ii/irsetting/page.tsx | 296 +++++++++++++------ 2 files changed, 278 insertions(+), 126 deletions(-) diff --git a/apps/sn/app/components/ui/fileUploadForm.tsx b/apps/sn/app/components/ui/fileUploadForm.tsx index f4c7292e9..58712b130 100644 --- a/apps/sn/app/components/ui/fileUploadForm.tsx +++ b/apps/sn/app/components/ui/fileUploadForm.tsx @@ -8,36 +8,67 @@ import { handleUpload } from "@utils"; import { set } from 'react-hook-form'; interface FileUploadFormProps { - onSubmit: (files: string, year: string, quarter: string, code: string, id: string) => void; + onSubmit?: (files: string, year: string, quarter: string, code: string, id: string) => void; + onFileSelect?: (file: File | null, atflCd: string | null, atflId: string | null) => void; // 파일 선택 시 호출될 콜백 추가 showButton?: boolean; // 버튼 표시 여부 (기본값 true) showPreview?: boolean; // 파일명 프리뷰 표시 여부 accept?: string; // 파일 형식 지정 + fileIdx?: number; // 부모가 어떤 FileUploadForm인지 구분하기 위한 인덱스 } -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) => { - 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 FileUploadForm = ({ + onSubmit, + onFileSelect, + showButton = true, + showPreview = false, + accept = ".jpeg,.jpg,.png", + fileIdx // props에서 fileIdx 받기 +}: FileUploadFormProps) => { + const [internalAtflCd, setInternalAtflCd] = useState(null); + const [internalAtflId, setInternalAtflId] = useState(null); + const [currentFile, setCurrentFile] = useState(null); // 현재 선택된 파일을 저장할 상태 + const [files, setFiles] = useState(null); + const [upload, setUpload] = useState(null); + const uniqueId = useId(); // 각 FileUploadForm 인스턴스에 대한 고유 ID 생성 + const quater = [{ text: '1분기', val: '1' }, { text: '2분기', val: '2' }, { text: '3분기', val: '3' }, { text: '4분기', val: '4' }]; // 현재 년도부터 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); - const uniqueId = useId(); // 각 FileUploadForm 인스턴스에 대한 고유 ID 생성 + + // const handleUploadResponse = (file: File) => async (res: HttpResponse) => { + // const fileInfo = res.getHeader("X-File-Info"); + // if (!fileInfo) return; + // const { atflCd: newAtflCd, atflId: newAtflId } = JSON.parse(fileInfo) ?? {}; + // const orgnlFileNm = file.name; + // console.log('orgnlFileNm', orgnlFileNm, atflCd, atflId); + // setAtflCd(newAtflCd); + // setAtflId(newAtflId); + // // 파일 정보가 업데이트되면 onFileSelect 호출 + // if (onFileSelect) onFileSelect(file, newAtflCd, newAtflId); + // }; + + const internalHandleUploadResponse = (file: File) => async (res: HttpResponse) => { + const fileInfo = res.getHeader("X-File-Info"); + if (!fileInfo) { + if (onFileSelect) onFileSelect(file, null, null); + return; + } + const { atflCd: newAtflCd, atflId: newAtflId } = JSON.parse(fileInfo) ?? {}; + const orgnlFileNm = file.name; + console.log(`FileUploadForm (idx: ${fileIdx ?? 'N/A'}) - orgnlFileNm:`, orgnlFileNm, 'New AtflCd:', newAtflCd, 'New AtflId:', newAtflId); + setInternalAtflCd(newAtflCd); + setInternalAtflId(newAtflId); + + if (onFileSelect) { + onFileSelect(file, newAtflCd, newAtflId); + } + }; + return (
{/* 컴포넌트 루트 div */} @@ -50,16 +81,34 @@ const FileUploadForm = ({ onSubmit, showButton = true, showPreview = false, acce accept={accept} onUpload={(e) => { const selectedFiles = e.target.files; - if (!selectedFiles) return; + if (!selectedFiles || selectedFiles.length === 0) { + setFiles(null); + setCurrentFile(null); + setInternalAtflCd(null); + setInternalAtflId(null); + if (onFileSelect) { + onFileSelect(null, null, null); // 파일 선택 취소 시 null 전달 + } + return; + } + + const firstFile = selectedFiles[0]; + if (!firstFile) return; // 타입스크립트 undefined 가능성 제거 setFiles(selectedFiles); - if (selectedFiles[0]) { - handleUpload(selectedFiles, { - handleUploadStart: setUpload, - handleSuccess: setUpload, - handleResponse: handleUploadResponse(selectedFiles[0]), - }); + setCurrentFile(firstFile); + + if (onFileSelect) { + onFileSelect(firstFile, internalAtflCd, internalAtflId); } + + handleUpload(selectedFiles, { + handleUploadStart: setUpload, + handleSuccess: setUpload, + // handleResponse는 tus-js-client의 Upload 인스턴스 콜백과 연동 필요 + // 여기서는 handleUpload 유틸리티가 응답을 직접 처리한다고 가정 + handleResponse: internalHandleUploadResponse(firstFile), + }); }} customPreview={ showPreview ? ( @@ -72,13 +121,13 @@ const FileUploadForm = ({ onSubmit, showButton = true, showPreview = false, acce } />
- {showButton ? ( + {showButton ? ( // showButton이 true일 때만 버튼 렌더링 From 83845d686029d87a67df3ecc86fc4420036ec9c9 Mon Sep 17 00:00:00 2001 From: JIeunhuh Date: Tue, 27 May 2025 13:18:55 +0900 Subject: [PATCH 5/8] =?UTF-8?q?=EB=85=B8=ED=9B=84=20=ED=95=98=EC=88=98?= =?UTF-8?q?=EA=B4=80=EB=A1=9C=20=EA=B5=AC=ED=98=84=20=EC=9E=91=EC=97=85=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/components/lib/api/index.ts | 9 +++++++-- apps/sn/app/oper/floodPre/page.tsx | 2 +- apps/sn/app/oper/oldPp/page.tsx | 26 ++++++++++++------------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/apps/sn/app/components/lib/api/index.ts b/apps/sn/app/components/lib/api/index.ts index e2f5cbf95..0045047e1 100644 --- a/apps/sn/app/components/lib/api/index.ts +++ b/apps/sn/app/components/lib/api/index.ts @@ -9,8 +9,13 @@ export const postData = async (url: string, data: object): Promise => { body: JSON.stringify(data), }; const res = await fetch(`${BASE_URL}/${url}`, options); - const resData = await res.json(); - return resData; + if (res.ok) { + const text = await res.text(); + if (text) { + return JSON.parse(text); // JSON 데이터가 있으면 파싱하여 반환 + } + return true; // 응답 본문이 비어 있으면 true 반환 + } }; export const postMutiPart = async (url: string, data: FormData): Promise => { diff --git a/apps/sn/app/oper/floodPre/page.tsx b/apps/sn/app/oper/floodPre/page.tsx index ec0608fd7..0a619b2bb 100644 --- a/apps/sn/app/oper/floodPre/page.tsx +++ b/apps/sn/app/oper/floodPre/page.tsx @@ -13,7 +13,7 @@ import { deleteData, getData, postData } from '@/components/lib/api' const Page = () => { // 조회조건 - const [fromDate, setFromDate] = useState(new Date()) + const [fromDate, setFromDate] = useState(()=>{const date = new Date(); date.setDate(date.getDate()-7); return date}) const [toDate, setToDate] = useState(new Date()) const [manageYear, setManageYear] = useState('') const [mngQu, setMngQu] = useState('') diff --git a/apps/sn/app/oper/oldPp/page.tsx b/apps/sn/app/oper/oldPp/page.tsx index f7ca87ca5..8f241621a 100644 --- a/apps/sn/app/oper/oldPp/page.tsx +++ b/apps/sn/app/oper/oldPp/page.tsx @@ -5,13 +5,13 @@ import React, { useEffect, useState } from 'react' import OperFileUploadForm from '../../components/ui/operFileUploadForm' import ListDiv from '@common/business_components/ui/list_div' import ContentLayout from '@/components/ui/contentLayout' -import { tableHeadlist } from '..' +import { tableHeadlistOldPp } from '..' import { handleDownload } from '@/components/lib/utils/fileDownload' import { searchData } from '@/components/lib/common/apiHelpers' import { formatDate } from '@/components/lib/utils/date' import { deleteData, getData, postData } from '@/components/lib/api' const Page = () => { - const [fromDate, setFromDate] = useState(new Date()); + const [fromDate, setFromDate] = useState(()=>{const date = new Date(); date.setDate(date.getDate()-7); return date}); const [toDate, setToDate] = useState(new Date()); const [manageYear, setManageYear] = useState('') const [mngQu, setMngQu] = useState('') @@ -19,8 +19,7 @@ const Page = () => { const [pjNm, setpjNm] = useState('') const [fileNm, setFileNm] = useState('') const [atflId, setAtflId] = useState('') - const [atflCd, setAtflCd] = useState('') - const [sbmrgPrevntBizSn, setSbmrgPrevntBizSn] = useState('') + const [dcrpPplMtncBizSn, setDcrpPplMtncBizSn] = useState('') const [tableData, setTableData] = useState([{}]) const [curPage, setCurPage] = useState(1) // 데이터 조회 @@ -30,7 +29,7 @@ const Page = () => { fromMngYr: manageYear, toMngYr: mngQu, toRegDt: formatDate(toDate), - atflNm: pjNm, + dcrpPplMtncBizNm: pjNm, rgtrNm: usr, page: curPage - 1, size: '10' @@ -52,14 +51,14 @@ const Page = () => { } // 파일 삭제 const isDeleteFile = async () => { - if (fileNm === '' || fileNm === undefined) { + if (dcrpPplMtncBizSn === '' || dcrpPplMtncBizSn === undefined) { alert('삭제할 파일을 선택해주세요') return false } - const data = await getData(`/attach-service/api/v1/atfl/attachments/${atflId}`) + const data = await deleteData(`/attach-service/api/v1/atfl/attachments/${atflId}`) if (data) { - const delData = await deleteData(`v1/sbmrgPrevnt/${sbmrgPrevntBizSn}`) - if (delData) { + const delData = await deleteData(`v1/dcrpPpl/${dcrpPplMtncBizSn}`) + if (delData.status == 200) { alert('삭제되었습니다.') fetchData() } else { @@ -74,9 +73,10 @@ const Page = () => { mngYr: year, mngQu: quater, atflCd: cd, - atflId: id + atflId: id, + dcrpPplMtncBizNm: fileNm }) - if (data.ok) { setAtflId(data.atflId); setAtflCd(data.atflCd) } + if (data) {alert('파일이 등록되었습니다.');fetchData() } } return ( @@ -119,7 +119,7 @@ const Page = () => { setpjNm(e.target.value)} + onChange={(e) => {setpjNm(e.target.value)}} />
@@ -137,7 +137,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()}}> + { if (index != null) { const fileName = tableData[index]?.dcrpPplMtncBizNm ?? ""; setFileNm(fileName); const id = tableData[index]?.atflId ?? ''; setAtflId(id); setDcrpPplMtncBizSn(tableData[index]?.dcrpPplMtncBizSn) } }} onPageChange={async (page:number)=>{setCurPage(page); await fetchData()}}> { await registFile(file, year, quater, cd, id) }} /> From 5395969952627f9a7381522a8d322584a6e22add Mon Sep 17 00:00:00 2001 From: JIeunhuh Date: Tue, 27 May 2025 13:41:07 +0900 Subject: [PATCH 6/8] =?UTF-8?q?url=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/oper/oldPp/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sn/app/oper/oldPp/page.tsx b/apps/sn/app/oper/oldPp/page.tsx index 8f241621a..4eea3049e 100644 --- a/apps/sn/app/oper/oldPp/page.tsx +++ b/apps/sn/app/oper/oldPp/page.tsx @@ -46,7 +46,7 @@ const Page = () => { alert('파일을 선택해주세요') return false } - handleDownload(`/attach-service/api/v1/download/${atflId}}`, fileNm) + handleDownload(`/attach-service/api/v1/download/${atflId}`, fileNm) return true } // 파일 삭제 From 2fe86d591333a7a700c6177f5fb6f7d86cbefc10 Mon Sep 17 00:00:00 2001 From: JIeunhuh Date: Tue, 27 May 2025 14:06:26 +0900 Subject: [PATCH 7/8] =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=95=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EA=B0=AF=EC=88=98=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/components/lib/common/apiHelpers.ts | 2 +- apps/sn/app/oper/floodPre/page.tsx | 8 +++++--- apps/sn/app/oper/oldPp/page.tsx | 7 +++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/sn/app/components/lib/common/apiHelpers.ts b/apps/sn/app/components/lib/common/apiHelpers.ts index 508556571..6f205c875 100644 --- a/apps/sn/app/components/lib/common/apiHelpers.ts +++ b/apps/sn/app/components/lib/common/apiHelpers.ts @@ -10,5 +10,5 @@ export const searchData = async>(url: string, para }, {} as Record) ); const data = await getData(`${url}?${params.toString()}`); - return data && data.content + return data && {data : data.content, total : data.totalElements} } diff --git a/apps/sn/app/oper/floodPre/page.tsx b/apps/sn/app/oper/floodPre/page.tsx index 0a619b2bb..a260d530b 100644 --- a/apps/sn/app/oper/floodPre/page.tsx +++ b/apps/sn/app/oper/floodPre/page.tsx @@ -9,7 +9,7 @@ import { searchData } from '@/components/lib/common/apiHelpers' import { tableHeadlist } from '..' import { handleDownload } from '@/components/lib/utils/fileDownload' import { formatDate } from '@/components/lib/utils/date' -import { deleteData, getData, postData } from '@/components/lib/api' +import { deleteData, postData } from '@/components/lib/api' const Page = () => { // 조회조건 @@ -25,8 +25,9 @@ const Page = () => { const [sbmrgPrevntBizSn, setSbmrgPrevntBizSn] = useState('') //파일 일련번호 const [tableData, setTableData] = useState([{}]) //테이블 데이터 const [curPage, setCurPage] = useState(1) //현재 페이지 + const [totalData, setTotalData] = useState(0) //총 데이터 수 const fetchData = async () => { - const data = await searchData('v1/sbmrgPrevnt', { + const {data, total} = await searchData('v1/sbmrgPrevnt', { fromRegDt: formatDate(fromDate), fromMngYr: manageYear, toMngYr: mngQu, @@ -37,6 +38,7 @@ const Page = () => { size: '10' }) setTableData(data) + setTotalData(total) } useEffect(() => { setCurPage(1) @@ -133,7 +135,7 @@ const Page = () => { - { if (index != null) {const fileName = tableData[index]?.sbmrgPrevntBizNm ?? ""; setFileNm(fileName); const id = tableData[index]?.atflId ?? ''; setAtflId(id); setSbmrgPrevntBizSn(tableData[index]?.sbmrgPrevntBizSn) } }} onPageChange={async (page:number)=>{setCurPage(page); await fetchData()}}> + { if (index != null) {const fileName = tableData[index]?.sbmrgPrevntBizNm ?? ""; setFileNm(fileName); const id = tableData[index]?.atflId ?? ''; setAtflId(id); setSbmrgPrevntBizSn(tableData[index]?.sbmrgPrevntBizSn);} }} onPageChange={async (page:number)=>{setCurPage(page); await fetchData()}}> { 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 4eea3049e..cf49a1f18 100644 --- a/apps/sn/app/oper/oldPp/page.tsx +++ b/apps/sn/app/oper/oldPp/page.tsx @@ -22,9 +22,10 @@ const Page = () => { const [dcrpPplMtncBizSn, setDcrpPplMtncBizSn] = useState('') const [tableData, setTableData] = useState([{}]) const [curPage, setCurPage] = useState(1) + const [totalData, setTotalData] = useState(0) //총 데이터 수 // 데이터 조회 const fetchData = async () => { - const data = await searchData('v1/dcrpPpl', { + const {data, total} = await searchData('v1/dcrpPpl', { fromRegDt: formatDate(fromDate), fromMngYr: manageYear, toMngYr: mngQu, @@ -35,6 +36,8 @@ const Page = () => { size: '10' }) setTableData(data) + setTotalData(total) + console.log(data, total) } useEffect(() => { setCurPage(1) @@ -137,7 +140,7 @@ const Page = () => { - { if (index != null) { const fileName = tableData[index]?.dcrpPplMtncBizNm ?? ""; setFileNm(fileName); const id = tableData[index]?.atflId ?? ''; setAtflId(id); setDcrpPplMtncBizSn(tableData[index]?.dcrpPplMtncBizSn) } }} onPageChange={async (page:number)=>{setCurPage(page); await fetchData()}}> + { if (index != null) { const fileName = tableData[index]?.dcrpPplMtncBizNm ?? ""; setFileNm(fileName); const id = tableData[index]?.atflId ?? ''; setAtflId(id); setDcrpPplMtncBizSn(tableData[index]?.dcrpPplMtncBizSn) } }} onPageChange={async (page:number)=>{setCurPage(page); await fetchData()}}> { await registFile(file, year, quater, cd, id) }} /> From e0041cbde4b034d3edbcd39a6b612ca2f58796ea Mon Sep 17 00:00:00 2001 From: JIeunhuh Date: Tue, 27 May 2025 15:54:55 +0900 Subject: [PATCH 8/8] =?UTF-8?q?ngii=20=EC=84=A4=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/ui/map/lib/gis.core.js | 70 ++++++++++++++++--- 1 file changed, 62 insertions(+), 8 deletions(-) diff --git a/packages/common/business_components/src/ui/map/lib/gis.core.js b/packages/common/business_components/src/ui/map/lib/gis.core.js index 4085e2907..1869b766a 100644 --- a/packages/common/business_components/src/ui/map/lib/gis.core.js +++ b/packages/common/business_components/src/ui/map/lib/gis.core.js @@ -14,6 +14,9 @@ import CircleStyle from 'ol/style/Circle'; import VectorLayer from 'ol/layer/Vector'; import { Point } from 'ol/geom'; import { boundingExtent } from 'ol/extent'; +import TileWMTS from 'ol/source/WMTS'; +import WMTS from 'ol/tilegrid/WMTS'; +import { get as getProjection } from 'ol/proj'; import olSourceImageWMS from 'ol/source/ImageWMS'; import { GeoJSON } from 'ol/format'; import GisOlEditLayer from './gis.edit'; @@ -32,20 +35,26 @@ GisApp.GEOSERVER_URL = '/geoserver/hasudo/ows'; GisApp.GEOSERVER_WMS_URL = '/geoserver/hasudo/wms'; //맵 정보 -GisApp.EMAP_URL = '/emap/'; -GisApp.EMAP_SATELLITE_URL = '/emap/satellite/'; -GisApp.VWORLD_URL = 'https://xdworld.vworld.kr/2d/Base/service/{z}/{x}/{y}.png'; -GisApp.VWORLD_SATELLITE_URL = 'https://xdworld.vworld.kr/2d/Satellite/service/{z}/{x}/{y}.jpeg'; GisApp.GOOGLE_API_KEY = 'AIzaSyCf1sgeELFalfPuqlykRKBMj6X9u_DQH6M'; GisApp.GOOGLE_ROADMAP_URL = `https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key=${GisApp.GOOGLE_API_KEY}`; GisApp.GOOGLE_SATELLITE_URL = `https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}&key=${GisApp.GOOGLE_API_KEY}`; GisApp.GOOGLE_HYBRID_URL = `https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}&key=${GisApp.GOOGLE_API_KEY}`; GisApp.GOOGLE_3D_URL = `https://tile.googleapis.com/v1/3dtiles/root.json?key=${GisApp.GOOGLE_API_KEY}`; + +// TODO: NGII 정보 +GisApp.NGII_API_KEY = '305CC242F2920F46BB93BCC13A52DD8A'; // NGII에서 발급받은 API 키 +GisApp.NGII_WMTS_URL = `http://map.ngii.go.kr/openapi/wmts_ngiiMap.js?apikey=${GisApp.NGII_API_KEY}`; + +GisApp.EMAP_URL = '/emap/'; +GisApp.EMAP_SATELLITE_URL = '/emap/satellite/'; +GisApp.VWORLD_URL = 'https://xdworld.vworld.kr/2d/Base/service/{z}/{x}/{y}.png'; +GisApp.VWORLD_SATELLITE_URL = 'https://xdworld.vworld.kr/2d/Satellite/service/{z}/{x}/{y}.jpeg'; GisApp.OPENSTREET_URL = '//tile.openstreetmap.org/{z}/{x}/{y}.png'; GisApp.BASE_MAP_TYPES = []; GisApp.FROM_PROJ = 'EPSG:4326'; -GisApp.TO_PROJ = 'EPSG:3857'; +// GisApp.TO_PROJ = 'EPSG:3857'; +GisApp.TO_PROJ = 'EPSG:5179'; //ngii 좌표계 GisApp.TO_DATA_PROJ = 'EPSG:4326'; GisApp.CONTEXT_LAYER_PATH = '/images/map/layericon/'; GisApp.layerCode = {}; @@ -86,6 +95,7 @@ GisApp.core.prototype = { minZoom: 5, maxZoom: 22, }, + options: { /*1: { zoom: 16, minZoom: 10, maxZoom: 22, centerX: 35.3227711, centerY: 129.1836202, dataProj: 'EPSG:5186' }, 4: { zoom: 16, minZoom: 10, maxZoom: 22, centerX: 35.0140499, centerY: 126.7110757, dataProj: 'EPSG:5186' }, @@ -109,11 +119,15 @@ GisApp.core.prototype = { GisApp.Module.core = this; GisApp.Module.layer = new GisOlLayer(this.defaults); GisApp.Module.edit = new GisOlEditLayer(this.defaults); - + // TODO: vworld layer 정보 (getNGIIbaseMap)활성화 하면 ngii base map으로 변경됨 this.baseMiniMapLayers['base'] = this.getBaseMap('minimap-base'); this.baseLayers['base'] = this.getBaseMap('base'); this.baseLayers['satellite'] = this.getBaseMap('satellite'); this.baseLayers['3d'] = this.getBaseMap('3d'); + // this.baseLayers['3d'] = this.getNGIIBaseMap('3d'); + // this.baseMiniMapLayers['base'] = this.getNGIIBaseMap('minimap-base'); + // this.baseLayers['base'] = this.getNGIIBaseMap('base'); + // this.baseLayers['satellite'] = this.getNGIIBaseMap('satellite'); }, layerType: { point: 'POINT', @@ -126,6 +140,46 @@ GisApp.core.prototype = { this.setCenter(Number(this.defaults.centerX), Number(this.defaults.centerY)); this.setZoom(15); }, + // NGII Base Map + getNGIIBaseMap: function (_id) { + const layers = { + base: 'korean_map', // 일반지도 + satellite: 'satellite_map', // 위성지도 + night: 'night_map', // 야간지도 + }; + // 선택된 레이어 + const layerName = layers[_id] || layers.base; + // EPSG:5179 좌표계 설정 + const projection = getProjection('EPSG:5179'); + const extent = [705680.0000, 1349270.0000,1388291.0000, 2581448.0000]; // NGII 좌표계 범위 + const resolutions = [ + 2088.96, 1044.48, 522.24, 261.12, 130.56, 65.28, 32.64, 16.32, 8.16, 4.08, 2.04, 1.02, 0.51, 0.255, + ]; + const tileGrid = new WMTS({ + extent: extent, + resolutions: resolutions, + matrixIds: resolutions.map((_, index) => index.toString()), // WMTS matrix IDs + }); + + // WMTS 소스 생성 + const wmtsSource = new TileWMTS({ + url: GisApp.NGII_WMTS_URL, + layer: layerName, + matrixSet: 'EPSG:5179', + format: 'image/png', + projection: projection, + tileGrid: tileGrid, + style: 'default', + crossOrigin: 'anonymous', + }); + + // TileLayer 생성 + return new TileLayer({ + source: wmtsSource, + visible: true, + zIndex: 0, + }); + }, getBaseMap: function (_id) { let url = GisApp.VWORLD_URL; if (_id === 'satellite') { @@ -182,7 +236,7 @@ GisApp.core.prototype = { setCenter: function (x, y, transYn) { if (!x || !y) return; let view = this.map.getView(); - + if (!transYn) { // 값이 없으면 let center = olProj.transform([Number(y), Number(x)], GisApp.FROM_PROJ, GisApp.TO_PROJ); @@ -553,7 +607,7 @@ GisApp.core.prototype = { */ convertLayerInfosToLayerCode: function (array, key, parentName) { let initialValue = {}; - + for (let i = 0; i < array.length; i++) { let item = {}; Object.assign(item, array[i]);