diff --git a/apps/sn/app/(map-layout)/map/SearchModal.tsx b/apps/sn/app/(map-layout)/map/SearchModal.tsx index 96e81da93..74975331e 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 } from './components/searchModalReportApi'; +import { useDcTree,flattenFolders, useSggCd, useCityCd, addFileName, getFileDownload } from './components/searchModalReportApi'; import { useCityCdStore, type SelectDataType } from './commonStore'; const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`; @@ -128,7 +128,7 @@ const SearchModal: React.FC = ({ const sidos = useCityCd(); const siguns = useSggCd(); - const reportOriginData = useDcTree(""); + const reportOriginData = useDcTree(""); //:Folder[] // 오늘 날짜 구하기 const today = new Date(); @@ -177,15 +177,27 @@ const SearchModal: React.FC = ({ onOpenChange(false); // SearchModal 닫기 } }; + // 하수도정비기본계획.table cellClick + const handleReportCellClick = (index: number) => { + console.log(index); + const reportData = tableData[index]; + const fileName = reportData.fileName; + if (reportData.fileSn !== "") { + getFileDownload(reportData.fileSn, fileName); + } + }; - const handleCtpvCdChange = (val: string|number) => { + const handleCtpvCdChange = (val: string|number) => { // 하수도정비기본계획.시도 const sidoOption = sidos.find((n) => n.val === val); setCtpvCd(String(val)); - if (val !== '0' && sidoOption) { - const filterSiguns = siguns.filter(item => item.sido === sidoOption.text); + if (val !== '0') { + const filterSiguns = siguns.filter(item => item.sido === sidoOption?.text); filterSiguns.unshift({ val: '0', text: '전체', sido: '0' }); setFilteredSiguns(filterSiguns); } + setSigungu("0"); + setGubun(""); + setYearFilter({시작: "",종료: "",}); } useEffect(() => { @@ -257,15 +269,15 @@ const SearchModal: React.FC = ({ //const reportOriginData = await getDcTree('경기도'); const reportData = flattenFolders(reportOriginData); let filterReportData = reportData; - if (ctpvCd !== "0") { + if (ctpvCd !== "0") { // 시도검색 const sidoOption = sidos.find((n) => n.val === ctpvCd); filterReportData = filterReportData.filter(item => item.sido === sidoOption?.text); } - if (sigungu !== "0") { + if (sigungu !== "0") { // 시군구검색 const sigunguOption = filteredSiguns.find((n) => n.val === sigungu); filterReportData = filterReportData.filter(item => item.sigungu === sigunguOption?.text); } - if (yearFilter.시작 != "" && yearFilter.종료 == "") + if (yearFilter.시작 != "" && yearFilter.종료 == "") //승인년도검색 filterReportData = filterReportData.filter(item => (item.approvalYear)? item.approvalYear >= yearFilter.시작+"년" : null); if (yearFilter.시작 == "" && yearFilter.종료 != "") filterReportData = filterReportData.filter(item => (item.approvalYear)? item.approvalYear <= yearFilter.종료+"년" : null); @@ -274,15 +286,19 @@ const SearchModal: React.FC = ({ return (item.approvalYear)? item.approvalYear >= yearFilter.시작+"년" && item.approvalYear <= yearFilter.종료+"년": null; }); } - if (gubun !== "") { + if (gubun !== "") { // 구분검색 filterReportData = filterReportData.filter(item => (item.plantZoneGugun)? String(item.plantZoneGugun).includes(String(gubun)) : null); } console.log('report count:', filterReportData.length); 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 || []); - } else { console.log('API URL:', `${API_URL}/v1/fclts?${queryParams.toString()}`); const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`); @@ -984,7 +1000,7 @@ const SearchModal: React.FC = ({ )} {/* 운영정보 탭 */} {activeTab === 'operation' && ( -
+
= ({ )} {/* 하수도정비기본계획보고서 탭 */} {activeTab === 'report' && ( -
+
{/* 시도, 시군구, 하수처리구역 한 줄 */}
= ({ 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 e4af3a127..9d1ae43e0 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,143 @@ 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 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 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 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 fileDownload = (data:Blob, fileName:string) => { // Blob데이터 임시link로 연결 + 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); +} +export const getFileDownload = async (fileSn:string, fileName:string) => { + const fileprcsData = await getDcFileprcs(fileSn); // filePath 조회 + if (fileprcsData?.length === 0 ) { alert("filePath 조회 오류"); } + if (fileprcsData[0].filePath === "") alert("filePath 정보 없으므로 다운로드 불가"); + const blobData = await getDcFileDownload(fileprcsData[0].filePath); // 실제 다운로드 API + fileDownload(blobData, fileName); +} +// 시도 데이터 export const getCityCd = async (): Promise => { try { const response = await fetch(`${ADDR_URL}/api/v1/addr/city`, { method: "GET", headers: { accept: "application/json" } }); @@ -71,10 +186,31 @@ export const useSggCd = () => { } }; fetch(); + console.log("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([]); @@ -86,96 +222,8 @@ export const useCityCd = () => { } }; fetch(); + console.log("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; -} diff --git a/apps/sn/middleware.ts b/apps/sn/middleware.ts index d69ef5513..8fe638ebe 100644 --- a/apps/sn/middleware.ts +++ b/apps/sn/middleware.ts @@ -102,8 +102,11 @@ export function middleware(req: NextRequest) { if (pathname.startsWith("/dc-service")) { const requestHeaders = new Headers(req.headers); - const newUrl = `${RSONE_DC_URL}${pathname}`; - + const decodedParams = Array.from(searchParams.entries()) + .map(([key, value]) => `${key}=${decodeURIComponent(value)}`) + .join("&"); + + const newUrl = `${RSONE_DC_URL}${pathname}${decodedParams ? `?${decodedParams}` : ""}`; console.log( `[Middleware] Rewriting from '/dc-service' path to: ${newUrl}`, );