From b66b3a9b557734ef7ddbddf3185cae0572ca0da4 Mon Sep 17 00:00:00 2001 From: ljpark Date: Mon, 14 Jul 2025 12:52:45 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9D=BC=EB=8B=A8=20=EB=8B=A4=EC=9A=B4?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EC=84=B1=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/(map-layout)/map/SearchModal.tsx | 33 ++- .../map/components/searchModalReportApi.ts | 255 ++++++++++-------- 2 files changed, 171 insertions(+), 117 deletions(-) diff --git a/apps/sn/app/(map-layout)/map/SearchModal.tsx b/apps/sn/app/(map-layout)/map/SearchModal.tsx index 82e1485fc..610803a4f 100644 --- a/apps/sn/app/(map-layout)/map/SearchModal.tsx +++ b/apps/sn/app/(map-layout)/map/SearchModal.tsx @@ -11,7 +11,7 @@ import { SidoSigunguSelectBox } from '@/components/ui/SidoSigunguSelectBox'; import { MsSwagePplCol2, SwtrmtFcltCol2, SwtrmtPrcsZoneCol2, MsSwageMhlCol2, ExclnSewagePumpCol2, stofcbrCol2 } from './components/common/table'; import LoadingOverlay from '@/components/ui/LoadingBar'; //import { useDcTree, flattenFolders, useSggCd, useCityCd } from './components/searchModalReportApi'; -import { getDcTree,useDcTree,flattenFolders, useSggCd, useCityCd,getDcFile } from './components/searchModalReportApi'; +import { getDcTree,useDcTree,flattenFolders, useSggCd, useCityCd,getDcFile,addFileName,getDcFileDownload } from './components/searchModalReportApi'; import { useCityCdStore, type SelectDataType } from './commonStore'; const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`; @@ -130,7 +130,7 @@ const SearchModal: React.FC = ({ const sidos = useCityCd(); const siguns = useSggCd(); - const reportOriginData = useDcTree(""); + const reportOriginData = useDcTree(""); //:Folder[] // 오늘 날짜 구하기 const today = new Date(); @@ -179,6 +179,26 @@ const SearchModal: React.FC = ({ onOpenChange(false); // SearchModal 닫기 } }; + // 하수도정비기본계획.table cellClick + const handleReportCellClick = (index: number, headId?: string) => { + console.log(index); + const reportData = tableData[index]; + console.log(reportData); + const fileName = reportData.fileName; + if (reportData.filePath) { + console.log(reportData.filePath); + getDcFileDownload(reportData.filePath).then((data) => { + const url = window.URL.createObjectURL(data); + const link = document.createElement('a'); + link.href = url; + link.download = fileName; // 원하는 파일명 지정 + document.body.appendChild(link); + link.click(); + link.remove(); + window.URL.revokeObjectURL(url); + }); + } + }; const handleCtpvCdChange = (val: string|number) => { // 하수도정비기본계획.시도 const sidoOption = sidos.find((n) => n.val === val); @@ -286,9 +306,12 @@ const SearchModal: React.FC = ({ setTableCount(filterReportData.length || 0); filterReportData = filterReportData.slice((curPage-1)*10, curPage*10); // paging filterReportData = filterReportData?.map((item, index)=>({...item, no:(curPage-1)*10+index+1})); + //const fileList = await getDcFile("CLS20250530000041"); + addFileName(filterReportData).then(data => { + filterReportData = data; + setTableData(filterReportData || []); + }); setTableData(filterReportData || []); - const fileList = await getDcFile("CLS20250530000041"); - console.log(fileList); } else { console.log('API URL:', `${API_URL}/v1/fclts?${queryParams.toString()}`); const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`); @@ -1117,7 +1140,7 @@ const SearchModal: React.FC = ({ pageSize={tableData.length < 10 ? tableData.length : 10} total={tableCount} children={undefined} - cellClick={handleCellClick} + cellClick={(activeTab!=='report')?handleCellClick:handleReportCellClick} onPageChange={setCurPage} resetPageNo={false} cur={curPage} diff --git a/apps/sn/app/(map-layout)/map/components/searchModalReportApi.ts b/apps/sn/app/(map-layout)/map/components/searchModalReportApi.ts index 93c0b6b13..f26f6cbb2 100644 --- a/apps/sn/app/(map-layout)/map/components/searchModalReportApi.ts +++ b/apps/sn/app/(map-layout)/map/components/searchModalReportApi.ts @@ -1,18 +1,17 @@ import {useState, useEffect} from 'react'; import { getDcData } from '@/components/lib/api'; -import { json } from 'stream/consumers'; const ADDR_URL = process.env.NEXT_PUBLIC_ADDR_URL; -type Folder = { +export type Folder = { clsfSystmSn: string; upClsfSystmSn: string; clsfSystmNm: string; children: Folder[]; }; -type FlattenedFolder = { - [key: string]: string|number; +export type FlattenedFolder = { + [key: string]: string|number|undefined; }; type SelectDataType = { @@ -21,27 +20,126 @@ type SelectDataType = { sido?: string; // 시도 코드 (선택 사항) } - // 시군구 데이터 -export const getSggCd = async (): Promise => { - try { - const response = await fetch(`${ADDR_URL}/api/v1/addr/sgg`, { method: "GET", headers: { accept: "application/json" } }); - if (!response.ok) throw new Error(`시군구 데이터 오류: ${response.status}`); - const res = await response.json(); - const sgg = res.data; - const sggCd = sgg.map((item: any) => ({ - val: item.adstDstrCd, - sido: item.sido, - text: item.gugun, - })); - sggCd.unshift({ val: '0', text: '전체' }); // "전체" 옵션 추가 - return sggCd; - } catch (error) { - console.error('시군구 데이터를 가져오는데 실패했습니다:', error); - return [{ val: '0', text: '전체', sido: '0' }]; - } - }; +type ReportDataFile = { + no : number; + fileTitle : string; + regDt : string; + fileSn : string; + filePath : string; +} +const isYear = (value: string): boolean => { + return /^[0-9]{4}년$/.test(value); +}; - // 시도 데이터 +export function flattenFolders(data: Folder[]): FlattenedFolder[] { + const result: FlattenedFolder[] = []; + const exceptNodeNm = ["부분","통합","전체"]; + const addNodeNm = ["보고서","요약보고서","부록","기타"]; // level5 + const columnNms: { [key: number]: string } = { 0: "root", 1:"sido", 2:"sigungu", 3:"approvalYear" + ,4:"plantZoneGugun", 5:"reportGubun", 7:"fileName", 8:"regDate" }; + + function traverse( + node: Folder, + path: { [key: string]: string| number }, + level: number + ) { + if (level === 2 && isYear(node.clsfSystmNm)) { + level++; + } + if (level === 4 && addNodeNm.includes(node.clsfSystmNm)) { + level++; // 레벨을 증가시켜 하위값에 추가되도록 함. + } + + const newPath = { + ...path, + [`${columnNms[level]}`]: node.clsfSystmNm, + }; + + if (node.children.length === 0) { + newPath.no = result.length + 1; + newPath.clsfSystmSn = node.clsfSystmSn; // 파일 조회를 위한 고유번호 + result.push(newPath); + } else { + if (level === 4) { + if (exceptNodeNm.includes(node.clsfSystmNm)) { + level--; // 제외할 노드 이름인 경우, 현재의 레벨에 하위값이 써지도록 함. + } + } + node.children.forEach((child) => { + traverse(child, newPath, level + 1); + }); + } + } + + data.forEach((root) => traverse(root, {}, 0)); + console.log(result.slice(0, 10)); + return result; +} + +export const getDcTree = async (clsfSystmNm:string) => { + //const cityName = encodeURIComponent(clsfSystmNm); + const queryString = 'clsfSystmNm='+clsfSystmNm; + console.log("getDcTree queryString:", queryString); + //const result = await getDcData(`/api/v1/dc/clsf${queryString ? `?${queryString}` : ''}`); + const result = await getDcData("/api/v1/dc/clsf?clsfSystmNm=" + clsfSystmNm); + return result; +}; +export const useDcTree = (clsfSystmNm:string) => { + const [data, setData] = useState([]); + + useEffect(() => { + const fetch = async () => { + const result = await getDcTree(clsfSystmNm); + if (result) { + setData(result); + } + }; + fetch(); + console.log("useDcTree호출"); + }, [clsfSystmNm]); + + return data; +}; +export const getDcFile = async (clsfSystmSn:string) => { + const queryString = 'clsfSystmSn='+clsfSystmSn; + const result = await getDcData(`/api/v1/dc/file${queryString ? `?${queryString}` : ''}`); + return result; +}; +export const getDcFileprcs = async (fileSn:string) => { + const queryString = 'fileSn='+fileSn; + console.log("getDcTree queryString:", queryString); + const result = await getDcData(`/api/v1/dc/fileprcs${queryString ? `?${queryString}` : ''}`); + return result; +}; + +export const addFileName = async (data:FlattenedFolder[]) => { + const fileReportData = await Promise.all( + data.map(async (item) => { + const filenameArr:ReportDataFile[] = await getDcFile(String(item.clsfSystmSn)); + const f = filenameArr[0]; + const result = {...item, fileName:f?.fileTitle, regDate:f?.regDt, fileSn:f?.fileSn, filePath:""}; + if (f !== undefined && f.fileSn !== "") { + const fprcs = await getDcFileprcs(f?.fileSn); + if (fprcs.length>0) + result.filePath = fprcs[0].filePath; + } + return result; + }) + ); + return fileReportData; +} +export const getDcFileDownload = async (filePath:string) => { + const queryString = 'filePath='+ encodeURIComponent(filePath); + const response = await fetch(`/dc-service/api/v1/dc/file/download?${queryString}`, { + method: 'GET', + }); + if (!response.ok) { + throw new Error(`File download failed: ${response.status}`); + } + const blob = await response.blob(); // PDF, image 등은 blob으로 받아야 함 + return blob; +}; +// 시도 데이터 export const getCityCd = async (): Promise => { try { const response = await fetch(`${ADDR_URL}/api/v1/addr/city`, { method: "GET", headers: { accept: "application/json" } }); @@ -75,6 +173,27 @@ export const useSggCd = () => { }, []); return data; }; + +// 시군구 데이터 +export const getSggCd = async (): Promise => { + try { + const response = await fetch(`${ADDR_URL}/api/v1/addr/sgg`, { method: "GET", headers: { accept: "application/json" } }); + if (!response.ok) throw new Error(`시군구 데이터 오류: ${response.status}`); + const res = await response.json(); + const sgg = res.data; + const sggCd = sgg.map((item: any) => ({ + val: item.adstDstrCd, + sido: item.sido, + text: item.gugun, + })); + sggCd.unshift({ val: '0', text: '전체' }); // "전체" 옵션 추가 + return sggCd; + } catch (error) { + console.error('시군구 데이터를 가져오는데 실패했습니다:', error); + return [{ val: '0', text: '전체', sido: '0' }]; + } +}; + export const useCityCd = () => { const [data, setData] = useState([]); @@ -91,91 +210,3 @@ export const useCityCd = () => { return data; }; -const isYear = (value: string): boolean => { - return /^[0-9]{4}년$/.test(value); -}; - - export function flattenFolders(data: Folder[]): FlattenedFolder[] { - const result: FlattenedFolder[] = []; - const exceptNodeNm = ["부분","통합","전체"]; - const addNodeNm = ["보고서","요약보고서","부록","기타"]; // level5 - const columnNms: { [key: number]: string } = { 0: "root", 1:"sido", 2:"sigungu", 3:"approvalYear" - ,4:"plantZoneGugun", 5:"reportGubun", 7:"fileName", 8:"regDate" }; - - function traverse( - node: Folder, - path: { [key: string]: string| number }, - level: number - ) { - if (level === 2 && isYear(node.clsfSystmNm)) { - level++; - } - if (level === 4 && addNodeNm.includes(node.clsfSystmNm)) { - level++; // 레벨을 증가시켜 하위값에 추가되도록 함. - } - - const newPath = { - ...path, - [`${columnNms[level]}`]: node.clsfSystmNm, - }; - - if (node.children.length === 0) { - newPath.no = result.length + 1; - newPath.clsfSystmSn = node.clsfSystmSn; // 파일 조회를 위한 고유번호 - result.push(newPath); - } else { - if (level === 4) { - if (exceptNodeNm.includes(node.clsfSystmNm)) { - level--; // 제외할 노드 이름인 경우, 현재의 레벨에 하위값이 써지도록 함. - } - } - node.children.forEach((child) => { - traverse(child, newPath, level + 1); - }); - } - } - - data.forEach((root) => traverse(root, {}, 0)); - console.log(result.slice(0, 10)); - return result; - } - - -export const getDcTree = async (clsfSystmNm:string) => { - const cityName = encodeURIComponent(clsfSystmNm); - const queryString = 'clsfSystmNm='+cityName; - console.log("getDcTree queryString:", queryString); - //const result = await getDcData(`/api/v1/dc/clsf${queryString ? `?${queryString}` : ''}`); - const result = await getDcData("/api/v1/dc/clsf?clsfSystmNm=" + clsfSystmNm); - return result; -}; -export const useDcTree = (clsfSystmNm:string) => { - const [data, setData] = useState([]); - - useEffect(() => { - const fetch = async () => { - const result = await getDcTree(clsfSystmNm); - if (result) { - setData(result); - } - }; - fetch(); - }, [clsfSystmNm]); - - return data; -}; -export const getDcFile = async (clsfSystmSn:string) => { - const queryString = 'clsfSystmNm='+clsfSystmSn; - console.log("getDcTree queryString:", queryString); - const result = await getDcData(`/api/v1/dc/file${queryString ? `?${queryString}` : ''}`); - return result; -}; -export const addDcFileName = async(data: any) => { - const updated = await Promise.all( - data.map(async (item) => { - const fileName = await getDcFile(item.systmSn); - return { ...item, fileName }; - }) - ); - return updated; -}