/** * 📄 useReportTab Hook * 검색 모달 보고서 탭 훅 * * - 계층적 폴더 구조 평탄화 및 데이터 변환 * - 파일 목록 조회 및 페이징 처리 * - 파일 다운로드 기능 * - 셀 클릭 이벤트 처리 * - Zustand를 통한 상태 관리 및 로딩 처리 * - 데이터 매핑 및 코드 변환 (시도/시군구, 보고서 유형 등) * * 🔗 사용 API * - 보고서 파일 조회: GET /api/v1/file (dc-service) * - 파일 다운로드: GET /api/v1/fileDownload (dc-service) */ import { getDcData } from '@/components/lib/api'; import { fileDownload } from '@/utils'; import { useReportTabStore } from '@/store/useReportTabStore'; import useSearchModalStore from '@/store/useSearchModalStore'; import { useAlertStore } from '@/shared/lib'; export type Folder = { clsfSystmSn: string; upClsfSystmSn: string; clsfSystmNm: string; children: Folder[]; }; export type FlattenedFolder = { [key: string]: string | number | undefined; }; export type SelectDataType = { text: string; val: string; sido?: string; // 시도 코드 (선택 사항) } export type ReportDataFile = { no: number; fileTitle: string; regDt: string; fileSn: string; filePath: string; } const getStringValue = (value: string | number | undefined) => String(value ?? ''); const buildDisplayFileName = (files: ReportDataFile[]) => { if (files.length === 0) return '-'; if (files.length === 1) return files[0]?.fileTitle ?? '-'; return `${files[0]?.fileTitle ?? '대표 파일'} 외 ${files.length - 1}건`; }; export default function useReportTab() { const alert = useAlertStore((state) => state.setMessage); const REPORT_TYPES = [ { val: '0', text: '전체' }, { val: '1', text: '보고서' }, { val: '2', text: '요약보고서' }, { val: '3', text: '부록' }, { val: '4', text: '기타' } ]; const { curPage, setIsLoading } = useSearchModalStore(); const { dcFiles: filesOrigin, files, setFiles } = useReportTabStore(); /** * Flatten a hierarchical folder structure into a flat array. * @param data - The hierarchical folder data to flatten. * @returns An array of flattened folders with their paths and levels. */ const 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: 'plantZoneGubun', 5: 'reportGubun', 7: 'fileName', 8: 'regDate' }; const traverse = ( node: Folder, path: { [key: string]: string | number }, level: number ) => { const isYear = (value: string): boolean => { return /^[0-9]{4}년$/.test(value); }; 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 && exceptNodeNm.includes(node.clsfSystmNm)) { level--; // 제외할 노드 이름인 경우, 현재의 레벨에 하위값이 써지도록 함. } node.children.forEach((child) => { traverse(child, newPath, level + 1); }); } }; if (data && data.length > 0 && data[0]?.clsfSystmNm == '기본계획') { data.forEach((root) => traverse(root, {}, 0)); } else { data.forEach((root) => traverse(root, {}, 1)); } return result; }; const getDcFiles = async (clsfSystmSn: string): Promise => { if (!clsfSystmSn) { console.error('clsfSystmSn is required to get files.'); return []; } const queryString = 'clsfSystmSn=' + clsfSystmSn; const result = await getDcData(`/api/v1/file${queryString ? `?${queryString}` : ''}`); return result; }; // 파일 이름 추가 const addFileName = async (data: FlattenedFolder[]) => { const fileReportData = await Promise.all( data.map(async (item) => { const fileArr: ReportDataFile[] = await getDcFiles(String(item.clsfSystmSn)); if (fileArr.length === 0) { return { ...item, fileName: '-', regDate: '', fileSn: '', filePath: '', fileCount: 0, downloadFileTitle: '', }; } const primaryFile = fileArr[0]; return { ...item, fileName: buildDisplayFileName(fileArr), regDate: primaryFile?.regDt ?? '', fileSn: primaryFile?.fileSn ?? '', filePath: primaryFile?.filePath ?? '', fileCount: fileArr.length, downloadFileTitle: primaryFile?.fileTitle ?? '', }; }) ); return fileReportData; }; const getDcFileDownload = async (fileSn: string, fileName: string, filePath: string) => { if (filePath === '') { alert(`filePath 정보가 없으므로 다운로드가 불가합니다.\nfilesn: ${fileSn}`); return; } const queryString = 'filePath=' + encodeURIComponent(filePath); const responseBlob = await getDcData(`/api/v1/fileDownload${queryString ? `?${queryString}` : ''}`, 'blob'); fileDownload(responseBlob, fileName); }; const downloadReportFile = async (item: FlattenedFolder) => { const fileSn = getStringValue(item.fileSn); const filePath = getStringValue(item.filePath); const downloadFileTitle = getStringValue(item.downloadFileTitle || item.fileName); if (!fileSn || !filePath || !downloadFileTitle || downloadFileTitle === '-') { return; } await getDcFileDownload(fileSn, downloadFileTitle, filePath); }; // 파일 목록 조회 const getFiles = async () => { setIsLoading(true); // 로딩 시작 try { let data = filesOrigin; data = data?.slice((curPage - 1) * 10, curPage * 10); // paging data = data?.map((item, index) => ({ ...item, no: (curPage - 1) * 10 + index + 1 })); setFiles(data); } catch (error) { console.error('파일 목록 조회 에러:', error); } finally { setIsLoading(false); // 로딩 종료 } }; // cellClick 핸들러 const handleCellClick = (index: number | undefined) => { if (typeof index === 'number' && files[index]) { const fileName = files[index].fileTitle; const filePath = files[index].filePath; if (files[index].fileSn !== '') { getDcFileDownload(files[index].fileSn, fileName, filePath); } } }; return { REPORT_TYPES, flattenFolders, addFileName, getDcFiles, getDcFileDownload, downloadReportFile, getFiles, handleCellClick }; }