From f8c902a1384612f941abcb7287de300b86a197f5 Mon Sep 17 00:00:00 2001 From: ljpark Date: Sun, 27 Jul 2025 21:55:24 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B8=B0=EB=B3=B8=EA=B3=84=ED=9A=8D=20componen?= =?UTF-8?q?t=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../map/searchModal/reportTab.tsx | 238 +++++++++++++----- .../map/searchModal/searchModal.tsx | 97 +------ .../map/searchModal/searchModalReportApi.ts | 19 +- apps/sn/app/components/lib/api/index.ts | 1 - 4 files changed, 185 insertions(+), 170 deletions(-) diff --git a/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx index bdf718578..48399ac22 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx @@ -1,13 +1,15 @@ +'use client'; +import React, { useEffect, useState } from 'react'; +import { Button } from '@common/components'; import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox'; -import { SelectBox } from '@common/business_components/ui'; -import { Button, Input } from '@common/components'; -import React from 'react'; -import { reportGubuns } from './searchModalReportApi'; +import { SelectBox, UiTable } from '@common/business_components/ui'; +import LoadingOverlay from '@/components/ui/LoadingBar'; + +import { addFileName, flattenFolders, getFileDownload, useCityCd, useDcTree, useSggCd, reportGubuns } from './searchModalReportApi'; import { type SelectDataType } from '../commonStore'; -import { useCityCd, useSggCd } from '../components/searchModalReportApi'; -// // 하수도정비 기본계획 보고서 +// 하수도정비 기본계획 보고서 export const reportTableHeadlist = [ { id: "no", title: "NO" }, { id: "sido", title: "시도" }, @@ -22,58 +24,159 @@ export const reportTableHeadlist = [ interface BasicTabProps { open?: boolean; - gubun: string; - sidos: SelectDataType[]; - ctpvCd: string | number; - sigungu: string; - filteredSiguns: SelectDataType[]; - reportGubun: string; - setReportGubun: (gubun: string) => void; - yearFilter: { start: string; end: string; }; - handleSearch: () => void; - handleCtpvCdChange: (val: string | number) => void; - setYearFilter: (filter: { start: string; end: string }) => void; - setGubun: (gubun: string) => void; - setSigungu: (sigungu: string) => void; } -const ReportTab: React.FC = ({ - open, - gubun, - sidos, - ctpvCd, - sigungu, - filteredSiguns = [{ val: '0', text: '전체', sido: '0' }], - yearFilter, - reportGubun, - setReportGubun, - handleSearch, - handleCtpvCdChange, - setYearFilter, - setGubun, - setSigungu, -}) => { +const ReportTab: React.FC = ({ open }) => { + + const [isLoading, setIsLoading] = useState(false); + const [isResetPage, setIsResetPage] = useState(false); + const [isResetSelection, setIsResetSelection] = useState(false); + const [showTable, setShowTable] = useState(false); + const [tableData, setTableData] = useState([]); + const [tableCount, setTableCount] = useState(0); + const [curPage, setCurPage] = useState(1); + const [yearFilter, setYearFilter] = useState({ start: "", end: "" }); + const [ctpvCd, setCtpvCd] = useState('0'); // 하수정비기본계획 report + const [sigungu, setSigungu] = useState('0'); // 하수정비기본계획 report + const [filteredSiguns, setFilteredSiguns] = useState([{ val: '0', text: '전체', sido: '0' }]); + const [gubun, setGubun] = useState(""); // 하수정비기본계획 report + const [reportGubun, setReportGubun] = useState("0"); // 하수정비기본계획 report + + + // 하수정비기본계획 report + const sidos = useCityCd(); + const siguns = useSggCd(); + const reportOriginData = useDcTree(""); //:Folder[] + + const dynamicHeadlist: { id: string, title: string }[] = reportTableHeadlist; + + useEffect(() => { + // filterParams 초기화 + setCtpvCd('0'); + setSigungu('0'); + setReportGubun('0'); + setYearFilter({ start: "", end: "" }); + setShowTable(false); + setTableData([]); + setTableCount(0); + setCurPage(1); // 페이지를 1로 초기화 + setIsResetPage(true); // 페이지를 초기화 + setIsResetSelection(true); // 선택 초기화 + }, [open]); + + // 페이지 변경 시마다 데이터 fetch + useEffect(() => { + // showTable이 true일 때만 fetch (최초 렌더 방지) + setIsResetPage(false); + setIsResetSelection(true); // 선택 초기화 + if (showTable) fetchFilteredData(); + }, [curPage]); + + const handleCtpvCdChange = (val: string | number) => { // 하수도정비기본계획.시도 + const sidoOption = sidos.find((n) => n.val === val); + setCtpvCd(String(val)); + 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({ start: "", end: "", }); + } + + + // 조회 버튼 클릭 시 1페이지로 이동 및 데이터 초기화 + const handleSearch = () => { + setCurPage(1); // 페이지를 1로 초기화 + fetchFilteredData(); + setIsResetPage(true); // 페이지를 초기화 + setIsResetSelection(false); // 선택 초기화 + setShowTable(true); + }; + + // 하수도정비기본계획 테이블 cellClick 핸들러 + const handleReportCellClick = (index: number | undefined ) => { + console.log(index); + if (typeof index === "number" && tableData[index]) { + const reportData = tableData[index]; + const fileName = reportData.fileName; + const filePath = reportData.filePath; + if (reportData.fileSn !== "") { + getFileDownload(reportData.fileSn, fileName, filePath); + } + } + }; + + // 검색 API 호출 함수 + const fetchFilteredData = async () => { + setIsLoading(true); // 로딩 시작 + setIsResetSelection(true); // 선택 초기화 + + try { + //const reportOriginData = await getDcTree('경기도'); + const reportData = flattenFolders(reportOriginData); + let filterReportData = reportData; + if (ctpvCd !== "0") { // 시도검색 + const sidoOption = sidos.find((n) => n.val === ctpvCd); + filterReportData = filterReportData.filter(item => item.sido === sidoOption?.text); + } + if (sigungu !== "0") { // 시군구검색 + const sigunguOption = filteredSiguns.find((n) => n.val === sigungu); + filterReportData = filterReportData.filter(item => item.sigungu === sigunguOption?.text); + } + if (reportGubun !== "0") { // 보고서구분검색 + const reportGubunOption = reportGubuns.find((n) => n.val === reportGubun); + filterReportData = filterReportData.filter(item => item.reportGubun === reportGubunOption?.text); + } + + if (yearFilter.start != "" && yearFilter.end == "") //승인년도검색 + filterReportData = filterReportData.filter(item => (item.approvalYear) ? item.approvalYear >= yearFilter.start + "년" : null); + if (yearFilter.start == "" && yearFilter.end != "") + filterReportData = filterReportData.filter(item => (item.approvalYear) ? item.approvalYear <= yearFilter.end + "년" : null); + if (yearFilter.start != "" && yearFilter.end != "") { + filterReportData = filterReportData.filter(item => { + return (item.approvalYear) ? item.approvalYear >= yearFilter.start + "년" && item.approvalYear <= yearFilter.end + "년" : null; + }); + } + 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 || []); + + setShowTable(true); + } catch (err) { + console.error('API ERROR:', err); + setTableData([]); + } finally { + setIsLoading(false); // 로딩 종료 + } + }; return ( + <>
- - setSigungu(String(val))} + label="시군구" selectArray={filteredSiguns} + value={sigungu} onSelectValue={val => setSigungu(String(val))} selectContentClass="z-[101]" />
@@ -85,23 +188,16 @@ const ReportTab: React.FC = ({ value={gubun} onChange={(e) => { setGubun(e.target.value); }} /> */} - setReportGubun(String(val))} - selectContentClass="z-[101]" - labelClass='whitespace-nowrap text-xl text-gray-600 pr-2' + label="보고서구분" selectArray={[...reportGubuns]} + value={reportGubun} onSelectValue={val => setReportGubun(String(val))} + selectContentClass="z-[101]" labelClass='whitespace-nowrap text-xl text-gray-600 pr-2' /> {/* 승인년도 필터 */} - setYearFilter({ ...yearFilter, start: range.start, end: range.end })} - startLabel="승인년도" - endLabel="" + startLabel="승인년도" endLabel="" /> {/* 조회 버튼 */}
@@ -109,6 +205,28 @@ const ReportTab: React.FC = ({
+ +
+ {/* 테이블2 영역 (가로/세로 스크롤 가능) */} +
+ + { + return (<>조회 결과 ({tableCount.toLocaleString('ko-KR')})건); + })() + } + tableData={tableData} + headlist={dynamicHeadlist} + pageSize={10} + total={tableCount} + cellClick={handleReportCellClick} + onPageChange={setCurPage} + resetPageNo={isResetPage} + resetSelection={isResetSelection} + /> +
+
+ ); }; diff --git a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx index 9e2b35caf..622fbd63f 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx @@ -3,11 +3,8 @@ import { UiModal, UITab, UiTable } from '@common/business_components/ui'; import axios from 'axios'; import React, { useEffect, useMemo, useState } from 'react'; import { ExclnSewagePumpCol2, MsSwageMhlCol2, MsSwagePplCol2, stofcbrCol2, SwtrmtFcltCol2, SwtrmtPrcsZoneCol2 } from '../components/common/table'; -//import { useDcTree, flattenFolders, useSggCd, useCityCd } from './components/searchModalReportApi'; import { type SelectDataType } from '../commonStore'; import { MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES, MS_SWAGE_MHL_CODES } from '../components/common/table'; -import { addFileName, flattenFolders, getFileDownload, useCityCd, useDcTree, useSggCd, reportGubuns } from './searchModalReportApi'; -// import { useBizByFcltStore } from "../../../store/StoreProvider"; import { getData } from "@/components/lib/api"; import BasicTab, { BASIC_TYPES } from './basicTab'; import OperationTab, { BIZ_TY_CD, operationCleaningTableHeadlist, operationInspectionTableHeadlist, operationMaintenanceTableHeadlist, operationTableHeadlist, WORK_TYPES } from './operationTab'; @@ -84,11 +81,7 @@ const SearchModal: React.FC = ({ const [yearFilter, setYearFilter] = useState({ start: "", end: "" }); const [filteredSiguns, setFilteredSiguns] = useState([{ val: '0', text: '전체', sido: '0' }]); - // 하수정비기본계획 report - const sidos = useCityCd(); - const siguns = useSggCd(); - const reportOriginData = useDcTree(""); //:Folder[] - + // 오늘 날짜 구하기 const today = new Date(); const todayYear = today.getFullYear().toString(); @@ -107,7 +100,6 @@ const SearchModal: React.FC = ({ return []; } if (activeTab === 'operation') return operationTableHeadlist; - if (activeTab === 'report') return reportTableHeadlist; return []; }, [activeTab, basicType, workType]); @@ -136,9 +128,6 @@ const SearchModal: React.FC = ({ setVar4(""); setCity(0); setSgg(0); - setCtpvCd('0'); - setSigungu('0'); - setReportGubun('0'); setYearFilter({ start: "", end: "", @@ -284,46 +273,7 @@ const SearchModal: React.FC = ({ setDetailTableData([]); setDetailTableCount(0); - } else if (activeTab === 'report') { - //const reportOriginData = await getDcTree('경기도'); - const reportData = flattenFolders(reportOriginData); - let filterReportData = reportData; - if (ctpvCd !== "0") { // 시도검색 - const sidoOption = sidos.find((n) => n.val === ctpvCd); - filterReportData = filterReportData.filter(item => item.sido === sidoOption?.text); - } - if (sigungu !== "0") { // 시군구검색 - const sigunguOption = filteredSiguns.find((n) => n.val === sigungu); - filterReportData = filterReportData.filter(item => item.sigungu === sigunguOption?.text); - } - if (reportGubun !== "0") { // 보고서구분검색 - const reportGubunOption = reportGubuns.find((n) => n.val === reportGubun); - filterReportData = filterReportData.filter(item => item.reportGubun === reportGubunOption?.text); - } - - if (yearFilter.start != "" && yearFilter.end == "") //승인년도검색 - filterReportData = filterReportData.filter(item => (item.approvalYear) ? item.approvalYear >= yearFilter.start + "년" : null); - if (yearFilter.start == "" && yearFilter.end != "") - filterReportData = filterReportData.filter(item => (item.approvalYear) ? item.approvalYear <= yearFilter.end + "년" : null); - if (yearFilter.start != "" && yearFilter.end != "") { - filterReportData = filterReportData.filter(item => { - return (item.approvalYear) ? item.approvalYear >= yearFilter.start + "년" && item.approvalYear <= yearFilter.end + "년" : null; - }); - } - 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 || []); - } + } setShowTable(true); } catch (err) { @@ -596,31 +546,7 @@ const SearchModal: React.FC = ({ } } }; - // 하수도정비기본계획 테이블 cellClick 핸들러 - const handleReportCellClick = (index: number | undefined, headId?: String) => { - console.log(index); - if (typeof index === "number" && tableData[index]) { - const reportData = tableData[index]; - const fileName = reportData.fileName; - const filePath = reportData.filePath; - if (reportData.fileSn !== "") { - getFileDownload(reportData.fileSn, fileName, filePath); - } - } - }; - - const handleCtpvCdChange = (val: string | number) => { // 하수도정비기본계획.시도 - const sidoOption = sidos.find((n) => n.val === val); - setCtpvCd(String(val)); - 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({ start: "", end: "", }); - } + const renderBody = () => ( <> @@ -681,19 +607,6 @@ const SearchModal: React.FC = ({ {activeTab === 'report' && ( )} {/* */} @@ -701,7 +614,7 @@ const SearchModal: React.FC = ({ {/* 📋 결과 리스트 */} {/* 기본정보 탭, 하수도정비기본계획보고서 탭 */} - {(activeTab === 'basic' || activeTab === 'report') && ( + {(activeTab === 'basic' ) && (
{/* 테이블2 영역 (가로/세로 스크롤 가능) */}
@@ -716,7 +629,7 @@ const SearchModal: React.FC = ({ headlist={dynamicHeadlist} pageSize={10} total={tableCount} - cellClick={activeTab === 'report' ? handleReportCellClick : handleBasicCellClick} + cellClick={handleBasicCellClick} onPageChange={setCurPage} resetPageNo={isResetPage} resetSelection={isResetSelection} diff --git a/apps/sn/app/(map-layout)/map/searchModal/searchModalReportApi.ts b/apps/sn/app/(map-layout)/map/searchModal/searchModalReportApi.ts index bcee09fdd..c12021499 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/searchModalReportApi.ts +++ b/apps/sn/app/(map-layout)/map/searchModal/searchModalReportApi.ts @@ -80,15 +80,10 @@ export function flattenFolders(data: Folder[]): FlattenedFolder[] { } 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; }; @@ -118,13 +113,8 @@ export const addFileName = async (data:FlattenedFolder[]) => { data.map(async (item) => { const filenameArr:ReportDataFile[] = await getDcFile(String(item.clsfSystmSn)); const f = filenameArr[0]; - //console.log("clsfSystmSn:", item.clsfSystmSn, "f:", f); const result = {...item, fileName:f?.fileTitle, regDate:f?.regDt, fileSn:f?.fileSn, filePath:f?.filePath}; - // if (f !== undefined && f.fileSn !== "") { - // const fprcs = await getDcFileprcs(f?.fileSn); - // if (fprcs.length>0) - // result.filePath = fprcs[0].filePath; - // } + return result; }) ); @@ -132,7 +122,6 @@ export const addFileName = async (data:FlattenedFolder[]) => { } 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; }; @@ -159,9 +148,6 @@ export const fileDownload = (data:Blob, fileName:string) => { // Blob데이터 window.URL.revokeObjectURL(url); } export const getFileDownload = async (fileSn:string, fileName:string, filePath:string) => { - //const fileprcsData = await getDcFileprcs(fileSn); // filePath 조회 - //if (fileprcsData?.length === 0 ) { alert("filePath 조회 오류"); } - //if (fileprcsData[0].filePath === "") alert("filePath 정보 없으므로 다운로드 불가"); if (filePath === "") alert("filePath 정보 없으므로 다운로드 불가:filesn"+ fileSn); const blobData = await getDcFileDownload(filePath); // 실제 다운로드 API fileDownload(blobData, fileName); @@ -179,7 +165,6 @@ export const getCityCd = async (): Promise => { })); citys = citys.sort((a:SelectDataType, b:SelectDataType) => a.text.localeCompare(b.text)); citys.unshift({ val: '0', text: '전체' }); - console.log("citys:", citys); return citys; } catch (error) { console.error('시도 데이터를 가져오는데 실패했습니다:', error); @@ -210,7 +195,7 @@ export const getSggCd = async (): Promise => { if (!response.ok) throw new Error(`시군구 데이터 오류: ${response.status}`); const res = await response.json(); const sgg = res.data; - const sggCd = sgg.map((item: any) => ({ + const sggCd = sgg.map((item: {adstDstrCd:string, sido:string, gugun:string}) => ({ val: item.adstDstrCd, sido: item.sido, text: item.gugun, diff --git a/apps/sn/app/components/lib/api/index.ts b/apps/sn/app/components/lib/api/index.ts index 0c0c88973..ab8936329 100644 --- a/apps/sn/app/components/lib/api/index.ts +++ b/apps/sn/app/components/lib/api/index.ts @@ -87,7 +87,6 @@ export const deleteData = async (url: string, data?: object): Promise => { // RSONE DC API 호출 export const getDcData = async (url: string, data?: object): Promise => { - console.log(url); const res = await fetch('/dc-service' + url, { method: "GET", });