Merge branch 'iipbs/sn/dev'

This commit is contained in:
swcho
2025-09-09 09:59:41 +09:00
30 changed files with 1431 additions and 1468 deletions
@@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react'
import InputSection from '../InputSection'; import InputSection from '../InputSection';
import { handleInputChange } from '../common'; import { handleInputChange } from '../common';
import { getData } from "@/components/lib/api"; import { getData } from "@/components/lib/api";
import useStore from '../../../../store/useStore'; import useStore from '@/store/useStore';
// 하수관로 // 하수관로
const inputColumn = [ const inputColumn = [
+6 -28
View File
@@ -8,37 +8,15 @@ import SearchModal from "./searchModal/searchModal";
import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup"; import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup";
import { WaterDepth, WATER_DEPTH_LAYER_TYPES } from "./waterDepth/waterDepth"; import { WaterDepth, WATER_DEPTH_LAYER_TYPES } from "./waterDepth/waterDepth";
import { ProcSearchBar } from "@common/business_components/ui/map/searchBar"; import { ProcSearchBar } from "@common/business_components/ui/map/searchBar";
import useSearchModalStore from "@/store/useSearchModalStore";
// 타입 정의 추가
export type LyrStngProps = {
lyr_sn: string;
lyr_nm: string;
upper_lyr_sn: string;
drawOrdr: string;
flterSql: string;
gobjTy: string;
lyrTpNm: string;
useYn: string;
minViewSize: string;
iconNm: string;
lblAtrbNm: string;
fontNm: string;
fontIntnColor: string;
fontExtnColor: string;
fontExtnWdth: string;
lnWdth: string;
lnColor: string;
polgnColor: string;
initEprssYn: string;
clickPsblYn: string;
};
const Map = () => { const Map = () => {
const { isSearchModalOpen, setIsSearchModalOpen } = useSearchModalStore();
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정 const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
const [selectedFeature, setSelectedFeature] = useState(null); // 선택된 피처 상태
const gisMaps = useRef<any | null>(null); const gisMaps = useRef<any | null>(null);
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태
const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터 const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터
const [showWaterDepth, setShowWaterDepth] = useState(false); // WaterDepth 표시 여부 const [showWaterDepth, setShowWaterDepth] = useState(false); // WaterDepth 표시 여부
const [isPlaying, setIsPlaying] = useState(false); const [isPlaying, setIsPlaying] = useState(false);
@@ -148,12 +126,12 @@ const Map = () => {
searchView={SearchModal} searchView={SearchModal}
detailView={FeatureClickEventPopup} detailView={FeatureClickEventPopup}
onFeatureSelect={onFeatureSelect} onFeatureSelect={onFeatureSelect}
setIsOpen={setIsOpen}
setFeatureData={setFeatureData}
featureData={featureData} featureData={featureData}
setIsSearchModalOpen={setIsSearchModalOpen} setFeatureData={setFeatureData}
isOpen={isOpen} isOpen={isOpen}
setIsOpen={setIsOpen}
isSearchModalOpen={isSearchModalOpen} isSearchModalOpen={isSearchModalOpen}
setIsSearchModalOpen={setIsSearchModalOpen}
onCheckedLayersChange={handleCheckedLayersChange} onCheckedLayersChange={handleCheckedLayersChange}
searchBar={<ProcSearchBar gisMaps={gisMaps} facilityTypes={facilityTypes} />} searchBar={<ProcSearchBar gisMaps={gisMaps} facilityTypes={facilityTypes} />}
waterDepth={showWaterDepth ? WaterDepth : undefined} waterDepth={showWaterDepth ? WaterDepth : undefined}
@@ -1,15 +1,13 @@
import React from 'react'; import React from 'react';
import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox';
import { SelectBox } from '@common/business_components/ui'; import { SelectBox } from '@common/business_components/ui';
import { Button, Label } from '@common/components'; import { Label } from '@common/components';
import SwagePpl from './swagePpl'; import SwagePpl from './swagePpl';
import SwagePrcsFclt from './swagePrcsFclt'; import SwagePrcsFclt from './swagePrcsFclt';
import SwagePrcsZone from './swagePrcsZone'; import SwagePrcsZone from './swagePrcsZone';
import SwageMhl from './swageMhl'; import SwageMhl from './swageMhl';
import SwagePmstn from './swagePmstn'; import SwagePmstn from './swagePmstn';
import Stofcbr from './stofcbr'; import Stofcbr from './stofcbr';
import { useSearchModal, type SearchModalProps } from './useSearchModal'; import useSearchModalStore from '@/store/useSearchModalStore';
import useSearchModalStore from '../../../store/useSearchModalStore';
// 기본정보 '구분' 선택 목록 // 기본정보 '구분' 선택 목록
@@ -22,7 +20,7 @@ export const BASIC_TYPES = [
{ val: "tb_ms_stofcbr", text: "우수토실" }, { val: "tb_ms_stofcbr", text: "우수토실" },
]; ];
const BasicTab: React.FC<SearchModalProps> = (props) => { const BasicTab: React.FC = () => {
const { basicType, setBasicType } = useSearchModalStore(); const { basicType, setBasicType } = useSearchModalStore();
return ( return (
@@ -45,12 +43,12 @@ const BasicTab: React.FC<SearchModalProps> = (props) => {
</div> </div>
</div> </div>
{/* 조회 조건 */} {/* 조회 조건 */}
{basicType === "tb_ms_swage_ppl" && (<SwagePpl {...props} />)} {/* 하수관로 */} {basicType === "tb_ms_swage_ppl" && (<SwagePpl />)} {/* 하수관로 */}
{basicType === "tb_il_swage_prcs_fclt" && (<SwagePrcsFclt {...props} />)} {/* 하수처리시설 */} {basicType === "tb_il_swage_prcs_fclt" && (<SwagePrcsFclt />)} {/* 하수처리시설 */}
{basicType === "tb_ms_swage_prcs_zone" && (<SwagePrcsZone {...props} />)} {/* 하수처리구역 */} {basicType === "tb_ms_swage_prcs_zone" && (<SwagePrcsZone />)} {/* 하수처리구역 */}
{basicType === "tb_ms_swage_mhl" && (<SwageMhl {...props} />)} {/* 하수맨홀 */} {basicType === "tb_ms_swage_mhl" && (<SwageMhl />)} {/* 하수맨홀 */}
{basicType === "tb_ms_swage_pmstn" && (<SwagePmstn {...props} />)} {/* 하수펌프장 */} {basicType === "tb_ms_swage_pmstn" && (<SwagePmstn />)} {/* 하수펌프장 */}
{basicType === "tb_ms_stofcbr" && (<Stofcbr {...props} />)} {/* 우수토실 */} {basicType === "tb_ms_stofcbr" && (<Stofcbr />)} {/* 우수토실 */}
</div> </div>
); );
}; };
@@ -1,82 +1,22 @@
import React from 'react'; import React, { use } from 'react';
import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox'; import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox';
import { SelectBox } from '@common/business_components/ui'; import { SelectBox } from '@common/business_components/ui';
import { Button, Input, Label } from '@common/components'; import { Button, Input, Label } from '@common/components';
import useStore from '../../../store/useStore'; import useStore from '../../../store/useStore';
import useSearchModalStore from '../../../store/useSearchModalStore'; import useSearchModalStore from '../../../store/useSearchModalStore';
import { useSearchModal, type SearchModalProps } from './useSearchModal'; import useSearchModal from './useSearchModal';
import useOperationTab from './useOperationTab';
// 운영정보 '구분' 선택 목록
export const WORK_TYPES = [
{ val: "['200', '201', '204']", text: "보수 및 갱신" },
{ val: "['100']", text: "점검 및 진단" },
{ val: "['203']", text: "청소 및 준설" },
];
// 운영정보 - 사업 현황
export const operationTableHeadlist = [
{ id: 'rowNumber', title: '일련번호' },
{ id: "bizInfoNm", title: "사업명" },
{ id: "flfmtBgngYmd", title: "수행시작일자" },
{ id: "flfmtEndYmd", title: "수행종료일자" },
{ id: "flfmtYr", title: "수행년도" },
{ id: "bizTypeCd", title: "사업유형" },
{ id: "bizAmt", title: "사업금액" },
];
// 운영정보 - 보수 및 갱신 결과표
export const operationMaintenanceTableHeadlist = [
{ id: 'rowNumber', title: '일련번호' },
{ id: "fcltMngNo", title: "관리번호" },
{ id: "swtrmt_zone_nm", title: "처리구역" },
{ id: "usgCd", title: "용도" },
// { id: "lgalDrblyr", title: "내용연수" },
// { id: "acqsPrmpc", title: "취득금액" },
{ id: "mqltCd", title: "재질" },
{ id: "pdmtVl", title: "관경(mm)" },
{ id: "prlgVl", title: "연장(m)" },
{ id: "instlYmd", title: "설치일자" },
// { id: "instlYmd", title: "사업구분" },
];
// 운영정보 - 청소 및 준설 결과표
export const operationCleaningTableHeadlist = [
{ id: 'rowNumber', title: '일련번호' },
{ id: "fcltMngNo", title: "관리번호" },
{ id: "swtrmt_zone_nm", title: "처리구역" },
{ id: "usgCd", title: "용도" },
// { id: "lgalDrblyr", title: "내용연수" },
// { id: "acqsPrmpc", title: "취득금액" },
{ id: "mqltCd", title: "재질" },
{ id: "pdmtVl", title: "관경(mm)" },
{ id: "prlgVl", title: "연장(m)" },
{ id: "instlYmd", title: "설치일자" },
];
// 운영정보 - 점검 및 진단 결과표
export const operationInspectionTableHeadlist = [
{ id: 'rowNumber', title: '일련번호' },
{ id: "fcltMngNo", title: "관리번호" },
{ id: "swtrmt_zone_nm", title: "처리구역" },
{ id: "usgCd", title: "용도" },
// { id: "riskEval", title: "위험평가" },
// { id: "acqsPrmpc", title: "취득금액" },
// { id: "lgalDrblyr", title: "내용연수" },
// { id: "remLife", title: "잔존수명" },
{ id: "mqltCd", title: "재질" },
{ id: "pdmtVl", title: "관경(mm)" },
{ id: "prlgVl", title: "연장(m)" },
{ id: "instlYmd", title: "설치일자" },
{ id: "strc_stts_grd", title: "구조적 상태등급" },
{ id: "oper_stts_grd", title: "운영적 상태등급" },
];
const OperationTab: React.FC<SearchModalProps> = (props) => { const OperationTab: React.FC = () => {
const { BIZ_TY_CD } = useStore();
const { const {
workType, setWorkType, workType, setWorkType,
var1, setVar1, var2, setVar2, var1, setVar1, var2, setVar2,
yearFilter, setYearFilter, yearFilter, setYearFilter,
} = useSearchModalStore(); } = useSearchModalStore();
const { handleSearch } = useSearchModal(props); const { handleSearch } = useSearchModal();
const { WORK_TYPES } = useOperationTab();
const { BIZ_TY_CD } = useStore();
const BIZ_TY_CD_SELECT_ARRAY = BIZ_TY_CD.map(item => ({ const BIZ_TY_CD_SELECT_ARRAY = BIZ_TY_CD.map(item => ({
val: item.lwpoCd, val: item.lwpoCd,
@@ -1,108 +0,0 @@
'use client'
import React, { useEffect, useState } from 'react';
import LoadingOverlay from '@/components/ui/LoadingBar';
import { getFileDownload, getDcFiles, useDcFiles } from './searchModalReportApi';
import { type ReportDataFile } from './searchModalReportApi';
import { useReportFileModalStore } from './searchModalReportApi'; // 모달 상태 관리 스토어
import { UiTable } from '@common/business_components/ui';
import { UiModal } from "@common/business_components";
type ModalOpenProp = {
clsfSystmSn: string, // 자료관 기본계획 일련번호
}
// 하수도정비 기본계획 보고서 파일목록
export const headlist = [
{ id: "no", title: "NO" },
{ id: "fileTitle", title: "파일명" },
{ id: "regDt", title: "등록일자" },
];
const ReportFiles: React.FC<ModalOpenProp> = ({ clsfSystmSn }: ModalOpenProp) => {
const [isLoading, setIsLoading] = useState(false);
const [isResetPage, setIsResetPage] = useState<boolean>(false);
const [isResetSelection, setIsResetSelection] = useState<boolean>(false);
const { isOpen: isModalOpen, setIsOpen: setIsModalOpen } = useReportFileModalStore(); // 모달 상태 관리 스토어
const [files, setFiles] = useState<ReportDataFile[]>([]);
const [curPage, setCurPage] = useState(1);
const filesOrigin = useDcFiles(clsfSystmSn); // 자료관 기본계획 일련번호에 해당하는 파일 목록을 가져오기 위한 훅
useEffect(() => {
if (isModalOpen === true) {
getFiles(); // 파일 목록 조회
} else {
setFiles([]); // 모달이 닫힐 때 파일 목록 초기화
setCurPage(1); // 페이지를 1로 초기화
setIsResetPage(true); // 페이지를 초기화
setIsResetSelection(true); // 선택 초기화
}
}, [filesOrigin, isModalOpen, curPage]);
// 파일 목록 조회
const getFiles = async () => {
setIsLoading(true); // 로딩 시작
try {
//let data = await getDcFiles(clsfSystmSn);
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 !== "") {
getFileDownload(files[index].fileSn, fileName, filePath);
}
}
};
const renderBody = () => (
<>
<div className="flex-1 px-2 pb-2">
<LoadingOverlay isLoading={isLoading} />
<UiTable
headName={(() => {
return (<>조회 결과 ({filesOrigin.length.toLocaleString('ko-KR')})건</>);
})()
}
tableData={files}
headlist={headlist}
pageSize={10}
total={filesOrigin.length}
cellClick={handleCellClick}
onPageChange={setCurPage}
resetPageNo={isResetPage}
resetSelection={isResetSelection}
/>
</div>
</>
);
return (
<UiModal
open={isModalOpen}
onOpenChange={setIsModalOpen}
headerTitle=""
bodyClass={`
min-h-[50vh] h-[570px]
min-w-[35vw] w-[400px]
max-w-none max-h-none
overflow-visible
z-[5000]
flex items-start
`}
body={renderBody()}
noFooterYn='Y'
/>
);
};
export default React.memo(ReportFiles);
@@ -0,0 +1,96 @@
'use client'
import React, { useEffect, useState } from 'react';
import LoadingOverlay from '@/components/ui/LoadingBar';
import useReportTab from './useReportTab';
import { useReportTabStore } from '@/store/useReportTabStore';
import { UiTable } from '@common/business_components/ui';
import { UiModal } from "@common/business_components";
import useSearchModalStore from '@/store/useSearchModalStore';
type ModalOpenProp = { clsfSystmSn: string } // 자료관 기본계획 일련번호
const ReportFilesModal: React.FC<ModalOpenProp> = ({ clsfSystmSn }: ModalOpenProp) => {
// 하수도정비 기본계획 보고서 파일목록
const REPORT_FILES_HEADLIST = [
{ id: "no", title: "NO" },
{ id: "fileTitle", title: "파일명" },
{ id: "regDt", title: "등록일자" },
];
const {
curPage, setCurPage,
isLoading,
isResetPage,
isResetSelection,
setIsResetPage,
setIsResetSelection
} = useSearchModalStore();
const {
isOpen: isModalOpen,
files,
dcFiles: filesOrigin,
setIsOpen: setIsModalOpen,
setFiles,
fetchDcFiles
} = useReportTabStore();
const { getFiles, handleCellClick } = useReportTab();
useEffect(() => {
fetchDcFiles(clsfSystmSn);
}, [clsfSystmSn]);
useEffect(() => {
if (isModalOpen === true) {
getFiles(); // 파일 목록 조회
} else {
setFiles([]); // 모달이 닫힐 때 파일 목록 초기화
setCurPage(1); // 페이지를 1로 초기화
setIsResetPage(true); // 페이지를 초기화
setIsResetSelection(true); // 선택 초기화
}
}, [filesOrigin, isModalOpen, curPage]);
const renderBody = () => (
<>
<div className="flex-1 px-2 pb-2">
<LoadingOverlay isLoading={isLoading} />
<UiTable
headName={(() => {
return (<>조회 결과 ({filesOrigin.length.toLocaleString('ko-KR')})건</>);
})()
}
tableData={files}
headlist={REPORT_FILES_HEADLIST}
pageSize={10}
total={filesOrigin.length}
cellClick={handleCellClick}
onPageChange={setCurPage}
resetPageNo={isResetPage}
resetSelection={isResetSelection}
/>
</div>
</>
);
return (
<UiModal
open={isModalOpen}
onOpenChange={setIsModalOpen}
headerTitle=""
bodyClass={`
min-h-[50vh] h-[570px]
min-w-[35vw] w-[400px]
max-w-none max-h-none
overflow-visible
z-[5000]
flex items-start
`}
body={renderBody()}
noFooterYn='Y'
/>
);
};
export default React.memo(ReportFilesModal);
@@ -4,11 +4,11 @@ import { Button, Label } from '@common/components';
import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox'; import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox';
import { SelectBox, UiTable } from '@common/business_components/ui'; import { SelectBox, UiTable } from '@common/business_components/ui';
import LoadingOverlay from '@/components/ui/LoadingBar'; import LoadingOverlay from '@/components/ui/LoadingBar';
import { addFileName, flattenFolders, getFileDownload, useDcTree, reportGubuns } from './searchModalReportApi'; import useReportTab from './useReportTab';
import useStore from '../../../store/useStore'; import useStore from '@/store/useStore';
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox'; import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
import { useReportFileModalStore } from './searchModalReportApi'; // 모달 상태 관리 스토어 import { useReportTabStore } from '@/store/useReportTabStore';
import ReportFiles from './reportFiles'; // 파일목록모달 import ReportFilesModal from './reportFilesModal'; // 파일목록모달
// 하수도정비 기본계획 보고서 // 하수도정비 기본계획 보고서
@@ -42,13 +42,20 @@ const ReportTab: React.FC<ReportTabProps> = ({ open, isSearch = true, ctpvNm, sg
const [ctpvCd, setCtpvCd] = useState('0'); const [ctpvCd, setCtpvCd] = useState('0');
const [sggCd, setSggCd] = useState('0'); const [sggCd, setSggCd] = useState('0');
const [reportGubun, setReportGubun] = useState("0"); const [reportGubun, setReportGubun] = useState("0");
// const [gubun, setGubun] = useState("");
const { isOpen: isModalOpen, setIsOpen: setIsModalOpen } = useReportFileModalStore(); // 모달 상태 관리 스토어
const [clsfSystmSn, setClsfSystmSn] = useState(''); // 자료관 기본계획 일련번호 const [clsfSystmSn, setClsfSystmSn] = useState(''); // 자료관 기본계획 일련번호
const { ctpv, sgg } = useStore();
// 하수정비기본계획 report const { ctpv, sgg } = useStore();
const reportOriginData = useDcTree(""); //:Folder[] const {
isOpen: isReportFilesModalOpen,
setIsOpen: setIsReportFilesModalOpen,
dcTree: reportOriginData,
fetchDcTree
} = useReportTabStore(); // 모달 상태 관리 스토어
const { REPORT_TYPES, flattenFolders, addFileName } = useReportTab();
useEffect(() => {
fetchDcTree(""); // 하수정비기본계획 report
}, []);
useEffect(() => { useEffect(() => {
// filterParams 초기화 // filterParams 초기화
@@ -105,12 +112,9 @@ const ReportTab: React.FC<ReportTabProps> = ({ open, isSearch = true, ctpvNm, sg
const handleReportCellClick = (index: number | undefined) => { const handleReportCellClick = (index: number | undefined) => {
if (typeof index === "number" && tableData[index]) { if (typeof index === "number" && tableData[index]) {
const reportData = tableData[index]; const reportData = tableData[index];
const fileName = reportData.fileName;
const filePath = reportData.filePath;
if (reportData.fileSn !== "") { if (reportData.fileSn !== "") {
//getFileDownload(reportData.fileSn, fileName, filePath);
setIsModalOpen(true); // 모달 열기
setClsfSystmSn(reportData.clsfSystmSn); // 자료관 기본계획 일련번호 설정 setClsfSystmSn(reportData.clsfSystmSn); // 자료관 기본계획 일련번호 설정
setIsReportFilesModalOpen(true); // 모달 열기
} }
} }
}; };
@@ -133,15 +137,16 @@ const ReportTab: React.FC<ReportTabProps> = ({ open, isSearch = true, ctpvNm, sg
filterReportData = filterReportData.filter(item => item.sigungu === sigunguOption?.text); filterReportData = filterReportData.filter(item => item.sigungu === sigunguOption?.text);
} }
if (reportGubun !== "0") { // 보고서구분검색 if (reportGubun !== "0") { // 보고서구분검색
const reportGubunOption = reportGubuns.find((n) => n.val === reportGubun); const reportGubunOption = REPORT_TYPES.find((n) => n.val === reportGubun);
filterReportData = filterReportData.filter(item => item.reportGubun === reportGubunOption?.text); filterReportData = filterReportData.filter(item => item.reportGubun === reportGubunOption?.text);
} }
if (yearFilter.start != "" && yearFilter.end == "") //승인년도검색 const { start, end } = yearFilter;
if (start && !end) //승인년도검색
filterReportData = filterReportData.filter(item => (item.approvalYear) ? item.approvalYear >= yearFilter.start + "년" : null); filterReportData = filterReportData.filter(item => (item.approvalYear) ? item.approvalYear >= yearFilter.start + "년" : null);
if (yearFilter.start == "" && yearFilter.end != "") else if (!start && end)
filterReportData = filterReportData.filter(item => (item.approvalYear) ? item.approvalYear <= yearFilter.end + "년" : null); filterReportData = filterReportData.filter(item => (item.approvalYear) ? item.approvalYear <= yearFilter.end + "년" : null);
if (yearFilter.start != "" && yearFilter.end != "") { else if (start && end) {
filterReportData = filterReportData.filter(item => { filterReportData = filterReportData.filter(item => {
return (item.approvalYear) ? item.approvalYear >= yearFilter.start + "년" && item.approvalYear <= yearFilter.end + "년" : null; return (item.approvalYear) ? item.approvalYear >= yearFilter.start + "년" && item.approvalYear <= yearFilter.end + "년" : null;
}); });
@@ -194,7 +199,7 @@ const ReportTab: React.FC<ReportTabProps> = ({ open, isSearch = true, ctpvNm, sg
<SelectBox className="w-44 mr-8" <SelectBox className="w-44 mr-8"
selectClass="rounded-lg bg-white" selectClass="rounded-lg bg-white"
label="" label=""
selectArray={[...reportGubuns]} selectArray={[...REPORT_TYPES]}
value={reportGubun} onSelectValue={val => setReportGubun(String(val))} value={reportGubun} onSelectValue={val => setReportGubun(String(val))}
selectContentClass="z-[101]" labelClass='whitespace-nowrap text-xl text-gray-600 pr-2' selectContentClass="z-[101]" labelClass='whitespace-nowrap text-xl text-gray-600 pr-2'
/> />
@@ -233,7 +238,7 @@ const ReportTab: React.FC<ReportTabProps> = ({ open, isSearch = true, ctpvNm, sg
</div> </div>
</div> </div>
{/* 모달 컴포넌트 */} {/* 모달 컴포넌트 */}
{isModalOpen && <ReportFiles clsfSystmSn={clsfSystmSn} />} {isReportFilesModalOpen && <ReportFilesModal clsfSystmSn={clsfSystmSn} />}
</> </>
); );
}; };
@@ -1,37 +1,24 @@
import React, { useEffect, useMemo } from 'react';
import LoadingOverlay from '@/components/ui/LoadingBar'; import LoadingOverlay from '@/components/ui/LoadingBar';
import { UiModal, UITab, UiTable } from '@common/business_components/ui'; import { UiModal, UITab, UiTable } from '@common/business_components/ui';
import { Button } from '@common/components/ui'; import { Button } from '@common/components/ui';
import axios from 'axios'; import React, { useMemo } from 'react';
import {
ExclnSewagePumpCol2, MsSwageMhlCol2, MsSwagePplCol2, stofcbrCol2, SwtrmtFcltCol2, SwtrmtPrcsZoneCol2,
MS_SWAGE_PPL_USE_CODES, MS_SWAGE_MHL_CODES, MS_SWAGE_MHL_USE_CODES,
} from '../components/common/table';
import { getData } from "@/components/lib/api";
import BasicTab, { BASIC_TYPES } from './basicTab';
import OperationTab, { operationCleaningTableHeadlist, operationInspectionTableHeadlist, operationMaintenanceTableHeadlist, operationTableHeadlist, WORK_TYPES } from './operationTab';
import ReportTab from './reportTab';
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
import { Toaster } from "react-hot-toast"; import { Toaster } from "react-hot-toast";
import { useSearchModal, OPERATION_TABLE_SIZE, type SearchModalProps } from './useSearchModal';
import useSearchModalStore from '../../../store/useSearchModalStore'; import useSearchModalStore from '../../../store/useSearchModalStore';
import {
ExclnSewagePumpCol2, MsSwageMhlCol2, MsSwagePplCol2, stofcbrCol2, SwtrmtFcltCol2, SwtrmtPrcsZoneCol2
} from '../components/common/table';
import BasicTab, { BASIC_TYPES } from './basicTab';
import OperationTab from './operationTab';
import ReportTab from './reportTab';
import useSearchModal from './useSearchModal';
import useBasicTab from './useBasicTab';
import useOperationTab from './useOperationTab';
const SearchModal: React.FC<SearchModalProps> = (props) => { const SearchModal: React.FC = () => {
const {
open, onOpenChange,
TABS,
handleSearch,
handleBasicCellClick,
handleOperationCellClick,
handleOperationResultCellClick,
basicExcelDownload,
operationExcelDownload,
operationResultExcelDownload,
viewMultipleFeature,
} = useSearchModal(props);
const { const {
isSearchModalOpen, setIsSearchModalOpen,
activeTab, basicType, workType, activeTab, basicType, workType,
tableData, detailTableData, tableData, detailTableData,
tableCount, detailTableCount, tableCount, detailTableCount,
curPage, detailCurPage, curPage, detailCurPage,
isLoading, isDetailLoading, isLoading, isDetailLoading,
@@ -40,6 +27,21 @@ const SearchModal: React.FC<SearchModalProps> = (props) => {
setActiveTab, setActiveTab,
setCurPage, setDetailCurPage, setCurPage, setDetailCurPage,
} = useSearchModalStore(); } = useSearchModalStore();
const { TABS, viewMultipleFeature } = useSearchModal();
const { handleBasicCellClick, basicExcelDownload } = useBasicTab();
const {
WORK_TYPES,
OPERATION_TABLE_SIZE,
OPERATION_TABLE_HEADLIST,
MAINTENANACE_TABLE_HEADLIST,
CLEANING_TABLE_HEADLIST,
INSPECTION_TABLE_HEADLIST,
handleOperationCellClick,
handleOperationResultCellClick,
operationExcelDownload,
operationResultExcelDownload
} = useOperationTab();
const dynamicHeadlist: { id: string, title: string }[] = useMemo(() => { const dynamicHeadlist: { id: string, title: string }[] = useMemo(() => {
if (activeTab === 'basic') { // 기본정보 if (activeTab === 'basic') { // 기본정보
@@ -52,16 +54,16 @@ const SearchModal: React.FC<SearchModalProps> = (props) => {
return []; return [];
} }
if (activeTab === 'operation') return operationTableHeadlist; // 운영정보 if (activeTab === 'operation') return OPERATION_TABLE_HEADLIST; // 운영정보
return []; return [];
}, [activeTab, basicType, workType]); }, [activeTab, basicType, workType]);
const dynamicHeadlist2: { id: string, title: string }[] = useMemo(() => { const dynamicHeadlist2: { id: string, title: string }[] = useMemo(() => {
if (activeTab === 'operation') { if (activeTab === 'operation') {
if (workType === WORK_TYPES[0]?.val) return operationMaintenanceTableHeadlist; // 보수 및 갱신 if (workType === WORK_TYPES[0]?.val) return MAINTENANACE_TABLE_HEADLIST; // 보수 및 갱신
if (workType === WORK_TYPES[1]?.val) return operationInspectionTableHeadlist; // 점검 및 진단 if (workType === WORK_TYPES[1]?.val) return CLEANING_TABLE_HEADLIST; // 점검 및 진단
if (workType === WORK_TYPES[2]?.val) return operationCleaningTableHeadlist; // 청소 및 준설 if (workType === WORK_TYPES[2]?.val) return INSPECTION_TABLE_HEADLIST; // 청소 및 준설
} }
return []; return [];
@@ -79,20 +81,9 @@ const SearchModal: React.FC<SearchModalProps> = (props) => {
}))} }))}
onValueChange={(value: string) => setActiveTab(value)} onValueChange={(value: string) => setActiveTab(value)}
/> />
{/* 기본정보 탭 */} {activeTab === 'basic' && (<BasicTab />)} {/* 기본정보 탭 */}
{activeTab === 'basic' && ( {activeTab === 'operation' && (<OperationTab />)} {/* 운영정보 탭 */}
<BasicTab {...props} /> {activeTab === 'report' && (<ReportTab open={isSearchModalOpen} />)} {/* 하수도정비기본계획보고서 탭 */}
)}
{/* 운영정보 탭 */}
{activeTab === 'operation' && (
<OperationTab {...props} />
)}
{/* 하수도정비기본계획보고서 탭 */}
{activeTab === 'report' && (
<ReportTab
open={open}
/>
)}
{/* 📋 결과 리스트 */} {/* 📋 결과 리스트 */}
@@ -192,8 +183,8 @@ const SearchModal: React.FC<SearchModalProps> = (props) => {
return ( return (
<> <>
<UiModal <UiModal
open={open} open={isSearchModalOpen}
onOpenChange={onOpenChange} onOpenChange={setIsSearchModalOpen}
bodyClass={` bodyClass={`
h-[100dvh] h-[100dvh]
w-[60dvw] w-[60dvw]
@@ -1,198 +0,0 @@
import { useState, useEffect } from 'react';
import { getDcData } from '@/components/lib/api';
import { create } from 'zustand';
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 isYear = (value: string): boolean => {
return /^[0-9]{4}년$/.test(value);
};
export const reportGubuns = [{ val: '0', text: '전체' }, { val: '1', text: '보고서' }
, { val: '2', text: '요약보고서' }, { val: '3', text: '부록' }, { val: '4', text: '기타' }];
/**
* 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.
*/
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: "plantZoneGubun", 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);
});
}
}
if (data && data.length > 0 && data[0]?.clsfSystmNm == "기본계획") { // data가 비어있지 않고 children이 있는 경우에만 처리
data.forEach((root) => traverse(root, {}, 0));
} else {
data.forEach((root) => traverse(root, {}, 1));
}
return result;
}
export const getDcTree = async (clsfSystmNm: string) => {
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);
}
console.log("useDcTree호출");
console.log("clsfSystmNm:", clsfSystmNm, "data:", result);
};
fetch();
}, [clsfSystmNm]);
return data;
};
export const getDcFiles = async (clsfSystmSn: string) : Promise<ReportDataFile[]> => {
if (!clsfSystmSn) {
console.error("clsfSystmSn is required to get files.");
return [];
}
const queryString = 'clsfSystmSn=' + clsfSystmSn;
const result = await getDcData(`/api/v1/dc/file${queryString ? `?${queryString}` : ''}`);
return result;
};
export const useDcFiles = (clsfSystmNm: string) => {
const [data, setData] = useState<ReportDataFile[]>([]);
useEffect(() => {
const fetch = async () => {
const result = await getDcFiles(clsfSystmNm);
if (result) {
setData(result);
}
console.log("useDcFiles 호출");
console.log("clsfSystmNm:", clsfSystmNm, "data:", result);
};
fetch();
}, [clsfSystmNm]);
return data;
};
// 파일 이름 추가
export const addFileName = async (data: FlattenedFolder[]) => {
const fileReportData = await Promise.all(
data.map(async (item) => {
const fileArr: ReportDataFile[] = await getDcFiles(String(item.clsfSystmSn));
let result:FlattenedFolder;
if (fileArr.length > 0) {
const f = fileArr[0];
const fCount = fileArr.length;
result = { ...item, fileName: f?.fileTitle+" 외 "+fCount+"건", regDate: f?.regDt, fileSn: f?.fileSn, filePath: f?.filePath };
} else {
result = { ...item, fileName: "", regDate: "", fileSn: "", filePath: "" };
}
return result;
})
);
return fileReportData;
};
export const getDcFileprcs = async (fileSn: string) => {
const queryString = 'fileSn=' + fileSn;
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, filePath: string) => {
if (filePath === "") alert("filePath 정보 없으므로 다운로드 불가:filesn" + fileSn);
const blobData = await getDcFileDownload(filePath); // 실제 다운로드 API
fileDownload(blobData, fileName);
}
interface ModalStore {
isOpen: boolean;
setIsOpen: (open: boolean) => void;
}
export const useReportFileModalStore = create<ModalStore>((set) => ({
isOpen: false,
setIsOpen: (open: boolean) => set({ isOpen: open }),
}));
@@ -5,24 +5,16 @@ import { Button } from '@common/components';
import React from 'react'; import React from 'react';
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox'; import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
import useSearchModalStore from '../../../store/useSearchModalStore'; import useSearchModalStore from '../../../store/useSearchModalStore';
import { useSearchModal, type SearchModalProps } from './useSearchModal'; import useSearchModal from './useSearchModal';
interface stofcbrCqlFiltersParams {
ctpvCd: string; export const stofcbrCqlFilters = () => {
sggCd: string;
yearFilter: { start: string; end: string };
todayYear: string;
todayMonth: string;
todayDate: string;
getDateString: (year: string, month: string, day: string, defaultDate: string) => string;
}
export const stofcbrCqlFilters = (params: stofcbrCqlFiltersParams) => {
const { const {
ctpvCd, sggCd, ctpvCd, sggCd,
yearFilter, yearFilter,
todayYear, todayMonth, todayDate, todayYear, todayMonth, todayDate,
getDateString getDateString
} = params; } = useSearchModalStore.getState();
const cqlFilters: string[] = []; const cqlFilters: string[] = [];
// 시도, 시군구 // 시도, 시군구
@@ -48,13 +40,13 @@ export const stofcbrCqlFilters = (params: stofcbrCqlFiltersParams) => {
return cqlFilters; return cqlFilters;
}; };
const Stofcbr: React.FC<SearchModalProps> = (props) => { const Stofcbr: React.FC = () => {
const { const {
ctpvCd, setCtpvCd, ctpvCd, setCtpvCd,
sggCd, setSggCd, sggCd, setSggCd,
yearFilter, setYearFilter, yearFilter, setYearFilter,
} = useSearchModalStore(); } = useSearchModalStore();
const { handleSearch } = useSearchModal(props); const { handleSearch } = useSearchModal();
return ( return (
@@ -7,21 +7,10 @@ import { MS_SWAGE_MHL_USE_CODES } from '../components/common/table';
import { SelectBox } from '@common/business_components/ui'; import { SelectBox } from '@common/business_components/ui';
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox'; import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
import useSearchModalStore from '../../../store/useSearchModalStore'; import useSearchModalStore from '../../../store/useSearchModalStore';
import { useSearchModal, type SearchModalProps } from './useSearchModal'; import useSearchModal from './useSearchModal';
interface swageMhlCqlFiltersParams {
ctpvCd: string; export const swageMhlCqlFilters = () => {
sggCd: string;
yearFilter: { start: string; end: string };
start1: string;
end1: string;
var1: string;
todayYear: string;
todayMonth: string;
todayDate: string;
getDateString: (year: string, month: string, day: string, defaultDate: string) => string;
}
export const swageMhlCqlFilters = (params: swageMhlCqlFiltersParams) => {
const { const {
ctpvCd, sggCd, ctpvCd, sggCd,
yearFilter, yearFilter,
@@ -29,7 +18,7 @@ export const swageMhlCqlFilters = (params: swageMhlCqlFiltersParams) => {
var1, var1,
todayYear, todayMonth, todayDate, todayYear, todayMonth, todayDate,
getDateString getDateString
} = params; } = useSearchModalStore.getState();
const cqlFilters: string[] = []; const cqlFilters: string[] = [];
// 시도, 시군구 // 시도, 시군구
@@ -60,7 +49,7 @@ export const swageMhlCqlFilters = (params: swageMhlCqlFiltersParams) => {
return cqlFilters; return cqlFilters;
}; };
const SwageMhl: React.FC<SearchModalProps> = (props) => { const SwageMhl: React.FC = () => {
const { const {
var1, setVar1, var1, setVar1,
start1, setStart1, end1, setEnd1, start1, setStart1, end1, setEnd1,
@@ -68,7 +57,7 @@ const SwageMhl: React.FC<SearchModalProps> = (props) => {
sggCd, setSggCd, sggCd, setSggCd,
yearFilter, setYearFilter, yearFilter, setYearFilter,
} = useSearchModalStore(); } = useSearchModalStore();
const { handleSearch } = useSearchModal(props); const { handleSearch } = useSearchModal();
return ( return (
<div className="mb-3"> <div className="mb-3">
@@ -5,29 +5,17 @@ import { Button, Input, Label } from '@common/components';
import React from 'react'; import React from 'react';
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox'; import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
import useSearchModalStore from '../../../store/useSearchModalStore'; import useSearchModalStore from '../../../store/useSearchModalStore';
import { useSearchModal, type SearchModalProps } from './useSearchModal'; import useSearchModal from './useSearchModal';
interface swagePmstnCqlFiltersParams {
ctpvCd: string; export const swagePmstnCqlFilters = () => {
sggCd: string;
yearFilter: { start: string; end: string };
start1: string;
start2: string;
end1: string;
end2: string;
todayYear: string;
todayMonth: string;
todayDate: string;
getDateString: (year: string, month: string, day: string, defaultDate: string) => string;
}
export const swagePmstnCqlFilters = (params: swagePmstnCqlFiltersParams) => {
const { const {
ctpvCd, sggCd, ctpvCd, sggCd,
yearFilter, yearFilter,
start1, start2, end1, end2, start1, start2, end1, end2,
todayYear, todayMonth, todayDate, todayYear, todayMonth, todayDate,
getDateString getDateString
} = params; } = useSearchModalStore.getState();
const cqlFilters: string[] = []; const cqlFilters: string[] = [];
// 시도, 시군구 // 시도, 시군구
@@ -63,7 +51,7 @@ export const swagePmstnCqlFilters = (params: swagePmstnCqlFiltersParams) => {
return cqlFilters; return cqlFilters;
}; };
const SwagePmstn: React.FC<SearchModalProps> = (props) => { const SwagePmstn: React.FC = () => {
const { const {
start1, setStart1, start1, setStart1,
end1, setEnd1, end1, setEnd1,
@@ -71,7 +59,7 @@ const SwagePmstn: React.FC<SearchModalProps> = (props) => {
sggCd, setSggCd, sggCd, setSggCd,
yearFilter, setYearFilter, yearFilter, setYearFilter,
} = useSearchModalStore(); } = useSearchModalStore();
const { handleSearch } = useSearchModal(props); const { handleSearch } = useSearchModal();
return ( return (
<div className="mb-3"> <div className="mb-3">
@@ -4,29 +4,14 @@ import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox';
import { SelectBox } from '@common/business_components/ui'; import { SelectBox } from '@common/business_components/ui';
import { Button, Input, Label } from '@common/components'; import { Button, Input, Label } from '@common/components';
import React from 'react'; import React from 'react';
import { MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES } from '../components/common/table'; import { MS_SWAGE_PPL_USE_CODES } from '../components/common/table';
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox'; import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
import useSearchModalStore from '../../../store/useSearchModalStore'; import useSearchModalStore from '../../../store/useSearchModalStore';
import { useSearchModal, type SearchModalProps } from './useSearchModal'; import useSearchModal from './useSearchModal';
interface swagePplCqlFiltersParams {
var1: string;
var2: string;
ctpvCd: string;
sggCd: string;
yearFilter: { start: string; end: string };
start1: string;
start2: string;
end1: string;
end2: string;
todayYear: string;
todayMonth: string;
todayDate: string;
getDateString: (year: string, month: string, day: string, defaultDate: string) => string;
}
// 하수관로용 CQL 필터 생성 // 하수관로용 CQL 필터 생성
export const swagePplCqlFilters = (params: swagePplCqlFiltersParams) => { export const swagePplCqlFilters = () => {
const { const {
var1, var2, var1, var2,
ctpvCd, sggCd, ctpvCd, sggCd,
@@ -34,7 +19,7 @@ export const swagePplCqlFilters = (params: swagePplCqlFiltersParams) => {
start1, start2, end1, end2, start1, start2, end1, end2,
todayYear, todayMonth, todayDate, todayYear, todayMonth, todayDate,
getDateString getDateString
} = params; } = useSearchModalStore.getState();
const cqlFilters: string[] = []; const cqlFilters: string[] = [];
if (var1) cqlFilters.push(`usg_cd='${var1}'`); if (var1) cqlFilters.push(`usg_cd='${var1}'`);
@@ -69,14 +54,14 @@ export const swagePplCqlFilters = (params: swagePplCqlFiltersParams) => {
return cqlFilters; return cqlFilters;
}; };
const SwagePpl: React.FC<SearchModalProps> = (props) => { const SwagePpl: React.FC = () => {
const { const {
var1, setVar1, var1, setVar1,
start1, setStart1, end1, setEnd1, start1, setStart1, end1, setEnd1,
ctpvCd, setCtpvCd, sggCd, setSggCd, ctpvCd, setCtpvCd, sggCd, setSggCd,
yearFilter, setYearFilter, yearFilter, setYearFilter,
} = useSearchModalStore(); } = useSearchModalStore();
const { handleSearch } = useSearchModal(props); const { handleSearch } = useSearchModal();
return ( return (
@@ -5,29 +5,18 @@ import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox';
import { Button, Input, Label } from '@common/components'; import { Button, Input, Label } from '@common/components';
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox'; import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
import useSearchModalStore from '../../../store/useSearchModalStore'; import useSearchModalStore from '../../../store/useSearchModalStore';
import { useSearchModal, type SearchModalProps } from './useSearchModal'; import useSearchModal from './useSearchModal';
interface swagePrcsFcltCqlFiltersParams {
ctpvName: string;
sggName: string;
yearFilter: { start: string; end: string };
start1: string;
end1: string;
todayYear: string;
todayMonth: string;
todayDate: string;
getDateString: (year: string, month: string, day: string, defaultDate: string) => string;
}
// 하수처리시설용 CQL 필터 생성 // 하수처리시설용 CQL 필터 생성
export const swagePrcsFcltCqlFilters = (params: swagePrcsFcltCqlFiltersParams) => { export const swagePrcsFcltCqlFilters = () => {
const { const {
ctpvName, sggName, ctpvName, sggName,
yearFilter, yearFilter,
start1, end1, start1, end1,
todayYear, todayMonth, todayDate, todayYear, todayMonth, todayDate,
getDateString getDateString
} = params; } = useSearchModalStore.getState();
const cqlFilters: string[] = []; const cqlFilters: string[] = [];
// 가동개시일자 범위 // 가동개시일자 범위
@@ -65,14 +54,14 @@ export const swagePrcsFcltCqlFilters = (params: swagePrcsFcltCqlFiltersParams) =
return cqlFilters; return cqlFilters;
}; };
const SwagePrcsFclt: React.FC<SearchModalProps> = (props) => { const SwagePrcsFclt: React.FC = () => {
const { const {
start1, setStart1, end1, setEnd1, start1, setStart1, end1, setEnd1,
ctpvCd, setCtpvCd, setCtpvName, ctpvCd, setCtpvCd, setCtpvName,
sggCd, setSggCd, setSggName, sggCd, setSggCd, setSggName,
yearFilter, setYearFilter, yearFilter, setYearFilter,
} = useSearchModalStore(); } = useSearchModalStore();
const { handleSearch } = useSearchModal(props); const { handleSearch } = useSearchModal();
return ( return (
<div className="mb-3"> <div className="mb-3">
@@ -4,21 +4,15 @@ import React from 'react';
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox'; import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
import { Button, Input, Label } from '@common/components'; import { Button, Input, Label } from '@common/components';
import useSearchModalStore from '../../../store/useSearchModalStore'; import useSearchModalStore from '../../../store/useSearchModalStore';
import { useSearchModal, type SearchModalProps } from './useSearchModal'; import useSearchModal from './useSearchModal';
interface swagePrcsZoneCqlFiltersParams {
ctpvCd: string;
sggCd: string;
var1: string;
var2: string;
}
// 하수처리구역용 CQL 필터 생성 // 하수처리구역용 CQL 필터 생성
export const swagePrcsZoneCqlFilters = (params: swagePrcsZoneCqlFiltersParams) => { export const swagePrcsZoneCqlFilters = () => {
const { const {
ctpvCd, sggCd, ctpvCd, sggCd,
var1, var2 var1, var2
} = params; } = useSearchModalStore.getState();
const cqlFilters: string[] = []; const cqlFilters: string[] = [];
if (ctpvCd !== '' && ctpvCd !== '0') cqlFilters.push(`ctpv_cd='${ctpvCd?.trim()}'`); if (ctpvCd !== '' && ctpvCd !== '0') cqlFilters.push(`ctpv_cd='${ctpvCd?.trim()}'`);
if (sggCd !== '' && sggCd !== '0') cqlFilters.push(`sgg_cd='${sggCd}'`); if (sggCd !== '' && sggCd !== '0') cqlFilters.push(`sgg_cd='${sggCd}'`);
@@ -27,13 +21,13 @@ export const swagePrcsZoneCqlFilters = (params: swagePrcsZoneCqlFiltersParams) =
return cqlFilters; return cqlFilters;
}; };
const swagePrcsZone: React.FC<SearchModalProps> = (props) => { const swagePrcsZone: React.FC = () => {
const { const {
var1, setVar1, var2, setVar2, var1, setVar1, var2, setVar2,
ctpvCd, setCtpvCd, ctpvCd, setCtpvCd,
sggCd, setSggCd, sggCd, setSggCd,
} = useSearchModalStore(); } = useSearchModalStore();
const { handleSearch } = useSearchModal(props); const { handleSearch } = useSearchModal();
return ( return (
<div className="mb-3"> <div className="mb-3">
@@ -0,0 +1,329 @@
import { useEffect, useState } from 'react';
import { downloadExcelWithKoreanHeaders } from '@common/business_components/ui';
import axios from 'axios';
import {
MS_SWAGE_PPL_USE_CODES, MS_SWAGE_MHL_CODES, MS_SWAGE_MHL_USE_CODES,
MsSwagePplExcelCol, SwtrmtFcltExcelCol, SwtrmtPrcsZoneExcelCol, MsSwageMhlExcelCol, ExclnSewagePumpExcelCol, stofcbrExcelCol
} from '../components/common/table';
import { getData } from "@/components/lib/api";
import { BASIC_TYPES } from './basicTab';
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
import useStore from '../../../store/useStore';
import GisApp from "@common/business_components/ui/map/lib/gis.core";
import { confirmToast } from '@/components/ui/toast'
import { toast } from "react-hot-toast";
import useSearchModalStore from '../../../store/useSearchModalStore';
import { swagePplCqlFilters } from './swagePpl';
import { swagePrcsFcltCqlFilters } from './swagePrcsFclt';
import { swagePrcsZoneCqlFilters } from './swagePrcsZone';
import { swageMhlCqlFilters } from './swageMhl';
import { swagePmstnCqlFilters } from './swagePmstn';
import { stofcbrCqlFilters } from './stofcbr';
export default function useBasicTab() {
const {
basicType,
tableData,
curPage,
todayYear, todayMonth, todayDate,
setIsSearchModalOpen,
setTableData,
setTableCount,
setIsLoading,
} = useSearchModalStore();
const { ctpv, sgg } = useStore();
const fetchBasicFilteredData = async () => {
let cqlFilters: string[] = [];
const queryParams = new URLSearchParams({
lyrTypeNm: basicType,
page: `${curPage - 1}`,
size: '10',
sort: 'string',
});
if (basicType === 'tb_ms_swage_ppl') cqlFilters = swagePplCqlFilters(); // 하수관로
else if (basicType === 'tb_il_swage_prcs_fclt') cqlFilters = swagePrcsFcltCqlFilters(); // 하수처리시설
else if (basicType === 'tb_ms_swage_prcs_zone') cqlFilters = swagePrcsZoneCqlFilters(); // 하수처리구역
else if (basicType === 'tb_ms_swage_mhl') cqlFilters = swageMhlCqlFilters(); // 하수맨홀
else if (basicType === 'tb_ms_swage_pmstn') cqlFilters = swagePmstnCqlFilters(); // 하수펌프장
else if (basicType === 'tb_ms_stofcbr') cqlFilters = stofcbrCqlFilters(); // 우수토실
if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and '));
const res = await getData(`v1/fclts?${queryParams.toString()}`);
// const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`);
console.log('fetch res', res);
console.log('fetch res.data', res.data);
console.log('fetch res.count', res.count);
console.log('fetch res.length', res.length);
// 🎯 각 아이템을 개별적으로 포맷팅하면서 일련번호 추가
const dataWithRowNumber = (res.data || []).map((item: any, index: number) => {
// 🎯 먼저 각 아이템을 포맷팅
const formattedItem = formatDisplayData(item);
let ctpvCd = item?.ctpvNm ?? formattedItem?.ctpvNm ?? '';
let sggCd = item?.sggNm ?? formattedItem?.sggNm ?? '';
if (!ctpvCd || !sggCd) {
const code = String(item?.pbadmsEmdCd ?? formattedItem?.pbadmsEmdCd ?? '')?.trim();
if (!ctpvCd && code) ctpvCd = code.slice(0, 2);
if (!sggCd && code) sggCd = code.slice(0, 5);
}
const matchedMhlCode = MS_SWAGE_MHL_CODES.find(code => code.val === item?.mqltCd?.trim());
const matchedMhlUsgCode = MS_SWAGE_MHL_USE_CODES.find(code => code.val === item?.usgCd);
const ctpvNm = ctpv.find(code => code.val.trim() === ctpvCd)?.text || item?.ctpvNm;
const sggNm = sgg.find(code => code.val.trim() === sggCd)?.text || item?.sggNm;
const usgCdObj = MS_SWAGE_PPL_USE_CODES.find(code => code.val === item?.usgCd?.trim());
// 추가 처리 및 일련번호 추가
return {
...formattedItem,
rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가
scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item?.scsYn, // scsYn이 null이면 'N'으로 변경
mqltCd: matchedMhlCode ? matchedMhlCode.text : item?.mqltCd,
usgCd: matchedMhlUsgCode ? matchedMhlUsgCode.text : (usgCdObj ? usgCdObj.text : item?.usgCd),
ctpvNm: ctpvNm ?? '-',
sggNm: sggNm ?? '-',
swtrmt_zone_nm: item.swtrmtZoneNm || item.swtrmt_zone_nm || '-',
};
});
setTableData(dataWithRowNumber);
setTableCount(res.count || []);
};
// 기본정보 테이블 cellClick 핸들러
const handleBasicCellClick = async (index?: number | undefined, headId?: String) => {
if (typeof index === "number") {
setIsLoading(true);
const row = tableData[index];
const layerNm = row.lyrTypeNm;
const fid = row.fid;
const core = GisApp.Module.layer as any;
await core.showSearchResultsFromGeoJSON(layerNm, fid);
setIsLoading(false);
setIsSearchModalOpen(false); // SearchModal 닫기
}
};
// 기본정보 Excel 다운로드 핸들러
const basicExcelDownload = async () => {
if (tableData.length === 0) {
alert("다운로드 할 Excel 파일이 없습니다.\n조회 후 다시 시도해주세요.");
return;
}
toast.dismiss("global-confirm-toast");
confirmToast(
"파일 다운로드에는 2분 내외의 시간이 소요될 수 있습니다.\n파일 다운로드를 진행하시겠습니까?",
onDownload ?? (() => { }),
"text-xl font-medium text-gray-800"
);
async function onDownload() {
setIsLoading(true);
try {
const queryParams = new URLSearchParams({
lyrTypeNm: basicType,
size: '10000',
});
let cqlFilters: string[] = [];
let headerMapping: { [key: string]: string } = {};
let headerRange: string = "A1:F1";
switch (basicType) {
case 'tb_ms_swage_ppl': {
// 하수관로
cqlFilters = swagePplCqlFilters()
headerMapping = MsSwagePplExcelCol;
headerRange = "A1:M1";
break;
}
case 'tb_il_swage_prcs_fclt': {
// 하수처리시설
cqlFilters = swagePrcsFcltCqlFilters();
headerMapping = SwtrmtFcltExcelCol;
headerRange = "A1:J1";
break;
}
case 'tb_ms_swage_prcs_zone': {
// 하수처리구역
cqlFilters = swagePrcsZoneCqlFilters();
headerMapping = SwtrmtPrcsZoneExcelCol;
headerRange = "A1:D1";
break;
}
case 'tb_ms_swage_mhl': {
// 하수맨홀
cqlFilters = swageMhlCqlFilters();
headerMapping = MsSwageMhlExcelCol;
headerRange = "A1:J1";
break;
}
case 'tb_ms_swage_pmstn': {
// 하수펌프장
cqlFilters = swagePmstnCqlFilters();
headerMapping = ExclnSewagePumpExcelCol;
headerRange = "A1:J1";
break;
}
case 'tb_ms_stofcbr': {
// 우수토실
cqlFilters = stofcbrCqlFilters();
headerMapping = stofcbrExcelCol;
headerRange = "A1:H1";
break;
}
}
if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and '));
const res = await getData(`v1/fclts?${queryParams.toString()}`);
// const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`);
console.log('onDownload res', res);
const mhlUsgCdMap = new Map(MS_SWAGE_MHL_USE_CODES.map(code => [code.val?.trim(), code.text]));
const pplUsgCdMap = new Map(MS_SWAGE_PPL_USE_CODES.map(code => [code.val?.trim(), code.text]));
const ctpvMap = new Map(ctpv.map(code => [code.val?.trim(), code.text]));
const sggMap = new Map(sgg.map(code => [code.val?.trim(), code.text]));
const mappedContent = (res.data.data || []).map((item: any) => {
const formattedItem = formatDisplayData(item);
let ctpvCd = item?.ctpvCd ?? formattedItem?.ctpvCd ?? '';
let sggCd = item?.sggCd ?? formattedItem?.sggCd ?? '';
if (!ctpvCd || !sggCd) {
const code = String(item?.pbadmsEmdCd ?? formattedItem?.pbadmsEmdCd ?? '')?.trim();
if (!ctpvCd && code) ctpvCd = code.slice(0, 2);
if (!sggCd && code) sggCd = code.slice(0, 5);
}
// trim 한 번만 수행
const trimmedUsgCd = item?.usgCd?.trim();
// const trimmedMqltCd = item?.mqltCd?.trim();
switch (basicType) {
case 'tb_ms_swage_ppl': {
// 하수관로
return {
fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '-',
instMngNo: formattedItem.instMngNo ?? item.instMngNo ?? '-',
ctpvNm: ctpvMap.get(ctpvCd) ?? item.ctpvNm ?? '-',
sggNm: sggMap.get(sggCd) ?? item.sggNm ?? '-',
swtrmt_zone_nm: formattedItem.swtrmt_zone_nm ?? item.swtrmt_zone_nm ?? '-',
instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '-',
usgCd: pplUsgCdMap.get(trimmedUsgCd) || item.usgCd || formattedItem.usgCd || '-',
pdmtVl: formattedItem.pdmtVl ?? item.pdmtVl ?? '-',
prlgVl: formattedItem.prlgVl ?? item.prlgVl ?? '-',
tmptDpth: formattedItem.tmptDpth ?? item.tmptDpth ?? '-',
tmptMhlMngNo: formattedItem.tmptMhlMngNo ?? item.tmptMhlMngNo ?? '-',
scendDpth: formattedItem.scendDpth ?? item.scendDpth ?? '-',
scendMhlMngNo: formattedItem.scendMhlMngNo ?? item.scendMhlMngNo ?? '-',
};
}
case 'tb_il_swage_prcs_fclt': {
// 하수처리시설
return {
swtrmtFcltNm: formattedItem.swtrmtFcltNm ?? item.swtrmtFcltNm ?? '-',
fcltCpct: formattedItem.fcltCpct ?? item.fcltCpct ?? '-',
rgnExpln: formattedItem.rgnExpln ?? item.rgnExpln ?? '-',
oprtngStrtYmd: formattedItem.oprtngStrtYmd ?? item.oprtngStrtYmd ?? '-',
laeNm: formattedItem.laeNm ?? item.laeNm ?? '-',
sareaNm: formattedItem.sareaNm ?? item.sareaNm ?? '-',
rvrNm: formattedItem.rvrNm ?? item.rvrNm ?? '-',
dschgWtstNm: formattedItem.dschgWtstNm ?? item.dschgWtstNm ?? '-',
swtrmtPpltnCnt: formattedItem.swtrmtPpltnCnt ?? item.swtrmtPpltnCnt ?? '-',
rprsMsrcCn: formattedItem.rprsMsrcCn ?? item.rprsMsrcCn ?? '-'
};
}
case 'tb_ms_swage_prcs_zone': {
// 하수처리구역
return {
swtrmtZoneNm: formattedItem.swtrmtZoneNm ?? item.swtrmtZoneNm ?? '-',
ctpvNm: ctpvMap.get(ctpvCd) ?? formattedItem.ctpvNm ?? '-',
sggNm: sggMap.get(sggCd) ?? formattedItem.sggNm ?? '-',
swtrmtFcltNm: formattedItem.swtrmtFcltNm ?? item.swtrmtFcltNm ?? '-'
};
}
case 'tb_ms_swage_mhl': {
// 하수맨홀
return {
fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '-',
ctpvNm: ctpvMap.get(ctpvCd) ?? formattedItem.ctpvNm ?? '-',
sggNm: sggMap.get(sggCd) ?? formattedItem.sggNm ?? '-',
instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '-',
usgCd: mhlUsgCdMap.get(trimmedUsgCd) || item.usgCd || formattedItem.usgCd || '-',
swageMhlDiamVl: formattedItem.swageMhlDiamVl ?? item.swageMhlDiamVl ?? '-',
wdthLen: formattedItem.wdthLen ?? item.wdthLen ?? '-',
vrtcLen: formattedItem.vrtcLen ?? item.vrtcLen ?? '-',
altdVl: formattedItem.altdVl ?? item.altdVl ?? '-',
dpthlVl: formattedItem.dpthlVl ?? item.dpthlVl ?? '-'
};
}
case 'tb_ms_swage_pmstn': {
// 하수펌프장
return {
fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '-',
ctpvNm: ctpvMap.get(ctpvCd) ?? formattedItem.ctpvNm ?? '-',
sggNm: sggMap.get(sggCd) ?? formattedItem.sggNm ?? '-',
swagePmstnNm: formattedItem.swagePmstnNm ?? item.swagePmstnNm ?? '-',
instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '-',
dailyPrcsCpct: formattedItem.dailyPrcsCpct ?? item.dailyPrcsCpct ?? '-',
maxImpndCpct: formattedItem.maxImpndCpct ?? item.maxImpndCpct ?? '-',
altdVl: formattedItem.altdVl ?? item.altdVl ?? '-',
wtlvVl: formattedItem.wtlvVl ?? item.wtlvVl ?? '-',
exclnWpmpAbltVl: formattedItem.exclnWpmpAbltVl ?? item.exclnWpmpAbltVl ?? '-'
};
}
case 'tb_ms_stofcbr': {
// 우수토실
return {
fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '-',
mapshtNo: formattedItem.mapshtNo ?? item.mapshtNo ?? '-',
ctpvNm: ctpvMap.get(ctpvCd) ?? formattedItem.ctpvNm ?? '-',
sggNm: sggMap.get(sggCd) ?? formattedItem.sggNm ?? '-',
instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '-',
worflwWallHgt: formattedItem.worflwWallHgt ?? item.worflwWallHgt ?? '-',
swageMhlMngNo: formattedItem.swageMhlMngNo ?? item.swageMhlMngNo ?? '-',
drtnAngl: formattedItem.drtnAngl ?? item.drtnAngl ?? '-'
};
}
}
});
// 선택된 행 데이터를 Excel 다운로드 형식으로 변환
const excelData = mappedContent; // 배열로 감싸서 전달
// 파일명 생성 (분석번호 기반)
const label = BASIC_TYPES.find((type) => type.val === basicType)?.text
const fileName = `${label} 조회 결과_${todayYear}${todayMonth}${todayDate}`;
console.log(`excelData: ${excelData}, label: ${label}, fileName: ${fileName}`);
await downloadExcelWithKoreanHeaders({
data: excelData as JSON[],
fileName: fileName,
sheetNameArray: [fileName],
headerRange: headerRange,
headerMapping: headerMapping
});
setIsLoading(false);
} catch (error) {
setIsLoading(false);
console.error("Excel 다운로드 중 오류 발생:", error);
alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요.");
}
}
};
return { fetchBasicFilteredData, handleBasicCellClick, basicExcelDownload };
};
@@ -0,0 +1,439 @@
import { downloadExcelWithKoreanHeaders } from '@common/business_components/ui';
import { MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES } from '../components/common/table';
import { getData } from "@/components/lib/api";
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
import GisApp from "@common/business_components/ui/map/lib/gis.core";
import { confirmToast } from '@/components/ui/toast'
import { toast } from "react-hot-toast";
import useSearchModalStore from '@/store/useSearchModalStore';
import useStore from '@/store/useStore';
export default function useOperationTab() {
const OPERATION_TABLE_SIZE = 5;
const {
workType,
tableData, detailTableData,
curPage, detailCurPage,
detailBizId,
var1, var2,
yearFilter,
todayYear, todayMonth, todayDate,
setIsSearchModalOpen,
setTableData, setDetailTableData,
setTableCount, setDetailTableCount,
setDetailCurPage,
setIsLoading, setIsDetailLoading,
setIsResetDetailPage,
setIsResetDetailSelection,
setDetailBizId,
getDateString
} = useSearchModalStore();
const { BIZ_TY_CD } = useStore();
const WORK_TYPES = [ // 운영정보 '구분' 선택 목록
{ val: "['200', '201', '204']", text: "보수 및 갱신" },
{ val: "['100']", text: "점검 및 진단" },
{ val: "['203']", text: "청소 및 준설" },
];
const OPERATION_TABLE_HEADLIST = [ // 운영정보 - 사업 현황
{ id: 'rowNumber', title: '일련번호' },
{ id: "bizInfoNm", title: "사업명" },
{ id: "flfmtBgngYmd", title: "수행시작일자" },
{ id: "flfmtEndYmd", title: "수행종료일자" },
{ id: "flfmtYr", title: "수행년도" },
{ id: "bizTypeCd", title: "사업유형" },
{ id: "bizAmt", title: "사업금액" },
];
const MAINTENANACE_TABLE_HEADLIST = [ // 운영정보 - 보수 및 갱신 결과표
{ id: 'rowNumber', title: '일련번호' },
{ id: "fcltMngNo", title: "관리번호" },
{ id: "swtrmt_zone_nm", title: "처리구역" },
{ id: "usgCd", title: "용도" },
// { id: "lgalDrblyr", title: "내용연수" },
// { id: "acqsPrmpc", title: "취득금액" },
{ id: "mqltCd", title: "재질" },
{ id: "pdmtVl", title: "관경(mm)" },
{ id: "prlgVl", title: "연장(m)" },
{ id: "instlYmd", title: "설치일자" },
// { id: "instlYmd", title: "사업구분" },
];
const CLEANING_TABLE_HEADLIST = [ // 운영정보 - 청소 및 준설 결과표
{ id: 'rowNumber', title: '일련번호' },
{ id: "fcltMngNo", title: "관리번호" },
{ id: "swtrmt_zone_nm", title: "처리구역" },
{ id: "usgCd", title: "용도" },
// { id: "lgalDrblyr", title: "내용연수" },
// { id: "acqsPrmpc", title: "취득금액" },
{ id: "mqltCd", title: "재질" },
{ id: "pdmtVl", title: "관경(mm)" },
{ id: "prlgVl", title: "연장(m)" },
{ id: "instlYmd", title: "설치일자" },
];
const INSPECTION_TABLE_HEADLIST = [ // 운영정보 - 점검 및 진단 결과표
{ id: 'rowNumber', title: '일련번호' },
{ id: "fcltMngNo", title: "관리번호" },
{ id: "swtrmt_zone_nm", title: "처리구역" },
{ id: "usgCd", title: "용도" },
// { id: "riskEval", title: "위험평가" },
// { id: "acqsPrmpc", title: "취득금액" },
// { id: "lgalDrblyr", title: "내용연수" },
// { id: "remLife", title: "잔존수명" },
{ id: "mqltCd", title: "재질" },
{ id: "pdmtVl", title: "관경(mm)" },
{ id: "prlgVl", title: "연장(m)" },
{ id: "instlYmd", title: "설치일자" },
{ id: "strc_stts_grd", title: "구조적 상태등급" },
{ id: "oper_stts_grd", title: "운영적 상태등급" },
];
const fetchOperationFilteredData = async () => {
const RequestDTO: Record<string, string> = {
page: `${curPage - 1}`,
size: `${OPERATION_TABLE_SIZE}`,
};
if (var1) RequestDTO.bizInfoNm = var1;
if (var2 != '0' && var2) RequestDTO.bizTypeCd = var2;
// 수행기간 범위
const startDate = getDateString(
yearFilter.start,
'',
'',
"19000101",
);
const endDate = getDateString(
yearFilter.end,
'',
'',
`${todayYear}${todayMonth}${todayDate}`,
);
RequestDTO.fromflfmtEndYmd = startDate;
RequestDTO.toflfmtEndYmd = endDate;
// JSON.parse 사용으로 문자열 -> 배열 변환
RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"'));
const params = new URLSearchParams(RequestDTO).toString();
const res = await getData(`v1/bizInfo?${params}`);
const mappedContent = (res.content || []).map((item: any, index: number) => {
const formattedItem = formatDisplayData(item);
const matched = BIZ_TY_CD.find(code => code.lwpoCd === item.bizTypeCd);
return {
...formattedItem,
rowNumber: (curPage - 1) * OPERATION_TABLE_SIZE + index + 1, // 1부터 시작하는 일련번호 추가
bizTypeCd: matched ? matched.lwpoCdNm : item.bizTypeCd,
};
});
setTableData(mappedContent || []);
setTableCount(res.totalElements || 0);
setDetailTableData([]);
setDetailTableCount(0);
};
// 운영정보 테이블 cellClick 핸들러
const handleOperationCellClick = async (index?: number | undefined, headId?: String) => {
setIsResetDetailPage(true); // 페이지를 1로 초기화
setIsResetDetailSelection(true); // 선택 초기화
setIsResetDetailPage(true); // 디테일 페이지를 초기화
if (typeof index === "number") {
setIsDetailLoading(true);
setDetailBizId(tableData[index].bizId);
setDetailCurPage(1); // 상세 페이지 초기화
try {
const RequestDTO: Record<string, string> = {
page: `${detailCurPage - 1}`,
size: `${OPERATION_TABLE_SIZE}`,
bizId: tableData[index % OPERATION_TABLE_SIZE].bizId,
};
RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"'));
const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`);
const mappedData = (res.data || []).map((item: any, detailIndex: number) => {
const formattedItem = formatDisplayData(item);
const usgCdObj = MS_SWAGE_PPL_USE_CODES.find(code => code.val === item?.usgCd?.trim());
const mqltCdObj = MS_SWAGE_PPL_CODES.find(code => code.val === item?.mqltCd?.trim());
return {
...formattedItem,
rowNumber: (detailCurPage - 1) * OPERATION_TABLE_SIZE + detailIndex + 1,
usgCd: usgCdObj ? usgCdObj.text : item.usgCd,
mqltCd: mqltCdObj ? mqltCdObj.text : item.mqltCd,
swtrmt_zone_nm: item.swtrmt_zone_nm ? item.swtrmt_zone_nm : '-',
};
});
setDetailTableData(mappedData);
setDetailTableCount(res.count || 0);
} catch (error) {
console.error('운영정보 cellClick 에러:', error);
} finally {
setIsDetailLoading(false);
setIsResetDetailPage(false);
setIsResetDetailSelection(false);
}
}
};
// 운영정보 디테일 테이블 cellClick 핸들러
const handleOperationResultCellClick = async (index?: number | undefined, headId?: String) => {
if (typeof index === "number") {
setIsLoading(true);
const row = detailTableData[index];
const layerNm = row.lyrTypeNm;
const fid = row.fid;
const core = GisApp.Module.layer as any;
await core.showSearchResultsFromGeoJSON(layerNm, fid);
setIsLoading(false);
setIsSearchModalOpen(false); // SearchModal 닫기
}
};
// 운영정보 디테일 테이블 페이지네이션 핸들러
const handleOperationPageNation = async (index?: number | undefined) => {
if (typeof index === "number") {
setIsDetailLoading(true);
try {
const RequestDTO: Record<string, string> = {
page: `${detailCurPage - 1}`,
size: `${OPERATION_TABLE_SIZE}`,
bizId: String(detailBizId),
};
const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`);
const mappedData = (res.data || []).map((item: any, detailIndex: number) => {
const formattedItem = formatDisplayData(item);
const usgCdObj = MS_SWAGE_PPL_USE_CODES.find(code => code.val === item?.usgCd?.trim());
const mqltCdObj = MS_SWAGE_PPL_CODES.find(code => code.val === item?.mqltCd?.trim());
return {
...formattedItem,
rowNumber: (detailCurPage - 1) * OPERATION_TABLE_SIZE + detailIndex + 1,
usgCd: usgCdObj ? usgCdObj.text : item.usgCd,
mqltCd: mqltCdObj ? mqltCdObj.text : item.mqltCd,
swtrmt_zone_nm: item.swtrmt_zone_nm ? item.swtrmt_zone_nm : '-',
};
});
setDetailTableData(mappedData);
setDetailTableCount(res.count || 0);
} catch (error) {
console.error('운영정보 페이지네이션 에러:', error);
} finally {
setIsDetailLoading(false);
}
}
};
// 운영정보 Excel 다운로드 핸들러 추가
const operationExcelDownload = async () => {
if (tableData.length === 0) {
alert("다운로드 할 Excel 파일이 없습니다.\n조회 후 다시 시도해주세요.");
return;
}
toast.dismiss("global-confirm-toast");
confirmToast(
"파일 다운로드에는 2분 내외의 시간이 소요될 수 있습니다.\n파일 다운로드를 진행하시겠습니까?",
onAnalysis ?? (() => { }),
"text-xl font-medium text-gray-800"
);
async function onAnalysis() {
setIsLoading(true);
try {
const RequestDTO: Record<string, string> = {
size: '10000',
};
if (var1) RequestDTO.bizInfoNm = var1;
if (var2 != '0' && var2) RequestDTO.bizTypeCd = var2;
// 수행기간 범위
const startDate = getDateString(
yearFilter.start,
'',
'',
"19000101",
);
const endDate = getDateString(
yearFilter.end,
'',
'',
`${todayYear}${todayMonth}${todayDate}`,
);
RequestDTO.fromflfmtEndYmd = startDate;
RequestDTO.toflfmtEndYmd = endDate;
// JSON.parse 사용으로 문자열 -> 배열 변환
RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"'));
const params = new URLSearchParams(RequestDTO).toString();
const res = await getData(`v1/bizInfo?${params}`);
// 헤더 매핑 정의
const headerMapping: { [key: string]: string } = {
'bizInfoNm': '사업명',
'flfmtBgngYmd': '수행시작일자',
'flfmtEndYmd': '수행종료일자',
'flfmtYr': '수행년도',
'bizTypeCd': '사업유형',
'bizAmt': '사업금액(원)',
};
const bizTyCdMap = new Map(BIZ_TY_CD.map(code => [code.lwpoCd, code.lwpoCdNm]));
const mappedContent = (res.content || []).map((item: any) => {
const formattedItem = formatDisplayData(item);
return {
bizInfoNm: formattedItem.bizInfoNm ?? item.bizInfoNm ?? '',
flfmtBgngYmd: formattedItem.flfmtBgngYmd ?? item.flfmtBgngYmd ?? '',
flfmtEndYmd: formattedItem.flfmtEndYmd ?? item.flfmtEndYmd ?? '',
flfmtYr: formattedItem.flfmtYr ?? item.flfmtYr ?? '',
bizTypeCd: bizTyCdMap.get(item.bizTypeCd) || item.bizTypeCd || formattedItem.bizTypeCd || '',
bizAmt: ((v) => (v === 0 || v === '0') ? '0' : (v ?? ''))(formattedItem.bizAmt ?? item.bizAmt),
};
});
// 선택된 행 데이터를 Excel 다운로드 형식으로 변환
const excelData = mappedContent; // 배열로 감싸서 전달
// 파일명 생성 (분석번호 기반)
const label = WORK_TYPES.find((type) => type.val === workType)?.text
const fileName = `${label} 사업 현황_${todayYear}${todayMonth}${todayDate}`;
await downloadExcelWithKoreanHeaders({
data: excelData as JSON[],
fileName: fileName,
sheetNameArray: [fileName],
headerRange: "A1:F1",
headerMapping: headerMapping
});
setIsLoading(false);
} catch (error) {
setIsLoading(false);
console.error("Excel 다운로드 중 오류 발생:", error);
alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요.");
}
}
};
// 운영정보 결과표 Excel 다운로드 핸들러 추가
const operationResultExcelDownload = async () => {
if (detailTableData.length === 0) {
alert("다운로드 할 Excel 파일이 없습니다.\n조회 후 다시 시도해주세요.");
return;
}
toast.dismiss("global-confirm-toast");
confirmToast(
"파일 다운로드에는 2분 내외의 시간이 소요될 수 있습니다.\n파일 다운로드를 진행하시겠습니까?",
onAnalysis ?? (() => { }),
"text-xl font-medium text-gray-800"
);
async function onAnalysis() {
setIsLoading(true);
try {
const RequestDTO: Record<string, string> = {
size: '10000',
bizId: String(detailBizId),
};
const apiStartTime = performance.now();
const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`);
const apiEndTime = performance.now();
const mappingStartTime = performance.now();
// 헤더 매핑 정의
const headerMapping: { [key: string]: string } = {
'fcltMngNo': '관리번호',
'swtrmt_zone_nm': '처리구역',
'usgCd': '용도',
'mqltCd': '재질',
'pdmtVl': '관경(mm)',
'prlgVl': '연장(m)',
'instlYmd': '설치일자',
'strc_stts_grd': '구조적 상태등급',
'oper_stts_grd': '운영적 상태등급',
};
const usgCdMap = new Map(MS_SWAGE_PPL_USE_CODES.map(code => [code.val, code.text]));
const mqltCdMap = new Map(MS_SWAGE_PPL_CODES.map(code => [code.val, code.text]));
// 🎯 단일 반복문으로 최적화 - 각 아이템마다 필요한 필드만 직접 매핑
const mappedData = (res.data || []).map((item: any) => {
const formattedItem = formatDisplayData(item);
// trim 한 번만 수행
const trimmedUsgCd = item?.usgCd?.trim();
const trimmedMqltCd = item?.mqltCd?.trim();
// zone 값 한 번만 계산
const zone = item.swtrmtZoneNm ?? formattedItem.swtrmt_zone_nm ?? item.swtrmt_zone_nm ?? '';
// 직접 매핑 - 반복문 없이 한 번에 객체 생성
return {
fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '',
swtrmt_zone_nm: (zone === null || zone === '') ? '-' : zone,
usgCd: usgCdMap.get(trimmedUsgCd) || item.usgCd || formattedItem.usgCd || '',
mqltCd: mqltCdMap.get(trimmedMqltCd) || item.mqltCd || formattedItem.mqltCd || '',
pdmtVl: formattedItem.pdmtVl ?? item.pdmtVl ?? '',
prlgVl: formattedItem.prlgVl ?? item.prlgVl ?? '',
instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '',
strc_stts_grd: ((v) => (v === 0 || v === '0') ? '0' : (v ?? ''))(formattedItem.strc_stts_grd ?? item.strc_stts_grd),
oper_stts_grd: ((v) => (v === 0 || v === '0') ? '0' : (v ?? ''))(formattedItem.oper_stts_grd ?? item.oper_stts_grd),
};
});
// 선택된 행 데이터를 Excel 다운로드 형식으로 변환
const excelData = mappedData; // 배열로 감싸서 전달
// 파일명 생성 (분석번호 기반)
const label = WORK_TYPES.find((type) => type.val === workType)?.text
const fileName = `${label} 결과표_${todayYear}${todayMonth}${todayDate}`;
await downloadExcelWithKoreanHeaders({
data: excelData as JSON[],
fileName: fileName,
sheetNameArray: [fileName],
headerRange: "A1:I1",
headerMapping: headerMapping
});
setIsLoading(false);
} catch (error) {
setIsLoading(false);
console.error("Excel 다운로드 중 오류 발생:", error);
alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요.");
}
}
};
return {
WORK_TYPES,
OPERATION_TABLE_HEADLIST,
MAINTENANACE_TABLE_HEADLIST,
CLEANING_TABLE_HEADLIST,
INSPECTION_TABLE_HEADLIST,
OPERATION_TABLE_SIZE,
fetchOperationFilteredData,
handleOperationCellClick,
handleOperationResultCellClick,
handleOperationPageNation,
operationExcelDownload,
operationResultExcelDownload
};
};
@@ -0,0 +1,173 @@
import { getDcData } from '@/components/lib/api';
import { fileDownload } from '@/utils';
import { useReportTabStore } from '@/store/useReportTabStore';
import useSearchModalStore from '@/app/store/useSearchModalStore';
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;
}
export default function useReportTab() {
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) {
if (exceptNodeNm.includes(node.clsfSystmNm)) {
level--; // 제외할 노드 이름인 경우, 현재의 레벨에 하위값이 써지도록 함.
}
}
node.children.forEach((child) => {
traverse(child, newPath, level + 1);
});
}
}
if (data && data.length > 0 && data[0]?.clsfSystmNm == "기본계획") { // data가 비어있지 않고 children이 있는 경우에만 처리
data.forEach((root) => traverse(root, {}, 0));
} else {
data.forEach((root) => traverse(root, {}, 1));
}
return result;
}
const getDcFiles = async (clsfSystmSn: string): Promise<ReportDataFile[]> => {
if (!clsfSystmSn) {
console.error("clsfSystmSn is required to get files.");
return [];
}
const queryString = 'clsfSystmSn=' + clsfSystmSn;
const result = await getDcData(`/api/v1/dc/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));
let result: FlattenedFolder;
if (fileArr.length > 0) {
const f = fileArr[0];
const fCount = fileArr.length;
result = { ...item, fileName: f?.fileTitle + " 외 " + fCount + "건", regDate: f?.regDt, fileSn: f?.fileSn, filePath: f?.filePath };
} else {
result = { ...item, fileName: "", regDate: "", fileSn: "", filePath: "" };
}
return result;
})
);
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/dc/file/download${queryString ? `?${queryString}` : ''}`, "blob");
fileDownload(responseBlob, fileName);
}
// 파일 목록 조회
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,
getFiles,
handleCellClick
};
}
@@ -0,0 +1,171 @@
import { useEffect } from 'react';
import { getData } from "@/components/lib/api";
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
import useStore from '../../../store/useStore';
import GisApp from "@common/business_components/ui/map/lib/gis.core";
import useSearchModalStore from '../../../store/useSearchModalStore';
import useBasicTab from './useBasicTab';
import useOperationTab from './useOperationTab';
export default function useSearchModal() {
const {
activeTab, basicType, workType,
showTable,
tableData, detailTableData,
curPage, detailCurPage,
detailBizId,
var1, var2,
yearFilter,
todayYear, todayMonth, todayDate,
setIsSearchModalOpen,
setShowTable,
setTableData, setDetailTableData,
setTableCount, setDetailTableCount,
setCurPage,
setIsLoading,
setIsResetPage, setIsResetDetailPage,
setIsResetSelection, setIsResetDetailSelection,
setDetailBizId,
resetFilters,
getDateString
} = useSearchModalStore();
const { fetchBasicFilteredData } = useBasicTab();
const { OPERATION_TABLE_SIZE, handleOperationPageNation } = useOperationTab();
const { BIZ_TY_CD } = useStore();
const TABS = [
{ val: "basic", text: "기본정보" },
{ val: "operation", text: "운영정보" },
{ val: "report", text: "하수도정비기본계획보고서" },
];
useEffect(() => {
resetFilters();
}, [activeTab, basicType, workType]);
// 페이지 변경 시마다 데이터 fetch
useEffect(() => {
setIsResetPage(false);
setIsResetDetailPage(true);
setDetailBizId(0);
console.log(`curPage: ${curPage}, showTable: ${showTable}`);
if (showTable && curPage > 1) fetchFilteredData();
}, [curPage]);
// 페이지 변경 시마다 데이터 fetch
useEffect(() => {
setIsResetDetailSelection(false);
if (showTable) handleOperationPageNation(Number(detailBizId));
}, [detailCurPage]);
// 검색 API 호출 함수
const fetchFilteredData = async () => {
setIsLoading(true); // 로딩 시작
setIsResetSelection(true); // 선택 초기화
setIsResetDetailSelection(true); // 선택 초기화
try {
if (activeTab === 'basic') {
fetchBasicFilteredData();
} else if (activeTab === 'operation') {
const RequestDTO: Record<string, string> = {
page: `${curPage - 1}`,
size: `${OPERATION_TABLE_SIZE}`,
};
if (var1) RequestDTO.bizInfoNm = var1;
if (var2 != '0' && var2) RequestDTO.bizTypeCd = var2;
// 수행기간 범위
const startDate = getDateString(
yearFilter.start,
'',
'',
"19000101",
);
const endDate = getDateString(
yearFilter.end,
'',
'',
`${todayYear}${todayMonth}${todayDate}`,
);
RequestDTO.fromflfmtEndYmd = startDate;
RequestDTO.toflfmtEndYmd = endDate;
// JSON.parse 사용으로 문자열 -> 배열 변환
RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"'));
const params = new URLSearchParams(RequestDTO).toString();
const res = await getData(`v1/bizInfo?${params}`);
const mappedContent = (res.content || []).map((item: any, index: number) => {
const formattedItem = formatDisplayData(item);
const matched = BIZ_TY_CD.find(code => code.lwpoCd === item.bizTypeCd);
return {
...formattedItem,
rowNumber: (curPage - 1) * OPERATION_TABLE_SIZE + index + 1, // 1부터 시작하는 일련번호 추가
bizTypeCd: matched ? matched.lwpoCdNm : item.bizTypeCd,
};
});
setTableData(mappedContent || []);
setTableCount(res.totalElements || 0);
setDetailTableData([]);
setDetailTableCount(0);
}
setShowTable(true);
setIsResetSelection(false);
} catch (err) {
console.error('API ERROR:', err);
setTableData([]);
setIsResetSelection(false);
} finally {
setIsLoading(false); // 로딩 종료
setIsResetSelection(false);
// setIsResetDetailSelection(false);
}
};
// 조회 버튼 클릭 시 1페이지로 이동 및 데이터 초기화
const handleSearch = () => {
setCurPage(1); // 페이지를 1로 초기화
fetchFilteredData();
setIsResetPage(true); // 페이지를 초기화
setIsResetDetailPage(true); // 디테일 페이지를 초기화
setIsResetSelection(false); // 선택 초기화
setIsResetDetailSelection(false); // 선택 초기화
setShowTable(true);
};
// 여러 시설 보기
const viewMultipleFeature = async () => {
if (activeTab === 'basic') {
if (!(tableData.length === 0)) {
setIsLoading(true);
const row = tableData[0];
const layerNm = row.lyrTypeNm;
const fids = tableData.map((item) => item.fid);
const core = GisApp.Module.layer as any;
await core.showMultipleSearchResultsFromGeoJSON(layerNm, fids);
setIsLoading(false);
setIsSearchModalOpen(false);
}
} else if (activeTab === 'operation') {
if (!(detailTableData.length === 0)) {
setIsLoading(true);
const row = detailTableData[0];
const layerNm = row.lyrTypeNm;
const fids = detailTableData.map((item) => item.fid);
const core = GisApp.Module.layer as any;
await core.showMultipleSearchResultsFromGeoJSON(layerNm, fids);
setIsLoading(false);
setIsSearchModalOpen(false);
}
}
}
return { TABS, getDateString, handleSearch, viewMultipleFeature };
};
@@ -1,841 +0,0 @@
import { useEffect, useState } from 'react';
import { downloadExcelWithKoreanHeaders } from '@common/business_components/ui';
import axios from 'axios';
import {
MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES, MS_SWAGE_MHL_CODES, MS_SWAGE_MHL_USE_CODES,
MsSwagePplExcelCol, SwtrmtFcltExcelCol, SwtrmtPrcsZoneExcelCol, MsSwageMhlExcelCol, ExclnSewagePumpExcelCol, stofcbrExcelCol
} from '../components/common/table';
import { getData } from "@/components/lib/api";
import { BASIC_TYPES } from './basicTab';
import { WORK_TYPES } from './operationTab';
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
import useStore from '../../../store/useStore';
import GisApp from "@common/business_components/ui/map/lib/gis.core";
import { confirmToast } from '@/components/ui/toast'
import { toast } from "react-hot-toast";
import useSearchModalStore from '../../../store/useSearchModalStore';
import { swagePplCqlFilters } from './swagePpl';
import { swagePrcsFcltCqlFilters } from './swagePrcsFclt';
import { swagePrcsZoneCqlFilters } from './swagePrcsZone';
import { swageMhlCqlFilters } from './swageMhl';
import { swagePmstnCqlFilters } from './swagePmstn';
import { stofcbrCqlFilters } from './stofcbr';
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`;
export const OPERATION_TABLE_SIZE = 5;
export interface SearchModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onFeatureSelect: (row: any) => void;
}
export function useSearchModal({ open, onOpenChange, onFeatureSelect }: SearchModalProps) {
const {
activeTab, basicType, workType,
showTable,
tableData, detailTableData,
curPage, detailCurPage,
detailBizId,
ctpvCd, ctpvName, sggCd, sggName,
start1, start2, end1, end2,
var1, var2,
yearFilter,
todayYear, todayMonth, todayDate,
setShowTable,
setTableData, setDetailTableData,
setTableCount, setDetailTableCount,
setCurPage, setDetailCurPage,
setIsLoading, setIsDetailLoading,
setIsResetPage, setIsResetDetailPage,
setIsResetSelection, setIsResetDetailSelection,
setDetailBizId,
resetFilters,
getDateString
} = useSearchModalStore();
const { ctpv, sgg, BIZ_TY_CD } = useStore();
const TABS = [
{ val: "basic", text: "기본정보" },
{ val: "operation", text: "운영정보" },
{ val: "report", text: "하수도정비기본계획보고서" },
];
useEffect(() => {
resetFilters();
}, [activeTab, basicType, workType]);
// 페이지 변경 시마다 데이터 fetch
useEffect(() => {
setIsResetPage(false);
setIsResetDetailPage(true);
setDetailBizId(0);
if (showTable) fetchFilteredData();
}, [curPage]);
// 페이지 변경 시마다 데이터 fetch
useEffect(() => {
setIsResetDetailSelection(false);
if (showTable) handleOperationPageNation(Number(detailBizId));
}, [detailCurPage]);
// 검색 API 호출 함수
const fetchFilteredData = async () => {
setIsLoading(true); // 로딩 시작
setIsResetSelection(true); // 선택 초기화
setIsResetDetailSelection(true); // 선택 초기화
try {
let cqlFilters: string[] = [];
if (activeTab === 'basic') {
const queryParams = new URLSearchParams({
lyrTypeNm: basicType,
page: `${curPage - 1}`,
size: '10',
sort: 'string',
});
if (basicType === 'tb_ms_swage_prcs_zone') cqlFilters = swagePrcsZoneCqlFilters({
ctpvCd, sggCd,
var1, var2
}); // 하수처리구역
else if (basicType === 'tb_il_swage_prcs_fclt') cqlFilters = swagePrcsFcltCqlFilters({
ctpvName, sggName,
yearFilter,
start1, end1,
todayYear, todayMonth, todayDate,
getDateString
}); // 하수처리시설
else if (basicType === 'tb_ms_swage_ppl') cqlFilters = swagePplCqlFilters({
var1, var2,
ctpvCd, sggCd,
yearFilter,
start1, start2, end1, end2,
todayYear, todayMonth, todayDate,
getDateString
}); // 하수관로
else if (basicType === 'tb_ms_swage_mhl') cqlFilters = swageMhlCqlFilters({
ctpvCd, sggCd,
yearFilter,
start1, end1,
var1,
todayYear, todayMonth, todayDate,
getDateString
}); // 하수맨홀
else if (basicType === 'tb_ms_swage_pmstn') cqlFilters = swagePmstnCqlFilters({
ctpvCd, sggCd,
yearFilter,
start1, start2, end1, end2,
todayYear, todayMonth, todayDate,
getDateString
}); // 하수펌프장
else if (basicType === 'tb_ms_stofcbr') cqlFilters = stofcbrCqlFilters({
ctpvCd, sggCd,
yearFilter,
todayYear, todayMonth, todayDate,
getDateString
}); // 우수토실
if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and '));
const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`);
// 🎯 각 아이템을 개별적으로 포맷팅하면서 일련번호 추가
const dataWithRowNumber = (res.data.data || []).map((item: any, index: number) => {
// 🎯 먼저 각 아이템을 포맷팅
const formattedItem = formatDisplayData(item);
let ctpvCd = item?.ctpvNm ?? formattedItem?.ctpvNm ?? '';
let sggCd = item?.sggNm ?? formattedItem?.sggNm ?? '';
if (!ctpvCd || !sggCd) {
const code = String(item?.pbadmsEmdCd ?? formattedItem?.pbadmsEmdCd ?? '')?.trim();
if (!ctpvCd && code) ctpvCd = code.slice(0, 2);
if (!sggCd && code) sggCd = code.slice(0, 5);
}
const matchedMhlCode = MS_SWAGE_MHL_CODES.find(code => code.val === item?.mqltCd?.trim());
const matchedMhlUsgCode = MS_SWAGE_MHL_USE_CODES.find(code => code.val === item?.usgCd);
const ctpvNm = ctpv.find(code => code.val.trim() === ctpvCd)?.text || item?.ctpvNm;
const sggNm = sgg.find(code => code.val.trim() === sggCd)?.text || item?.sggNm;
const usgCdObj = MS_SWAGE_PPL_USE_CODES.find(code => code.val === item?.usgCd?.trim());
// 추가 처리 및 일련번호 추가
return {
...formattedItem,
rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가
scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item?.scsYn, // scsYn이 null이면 'N'으로 변경
mqltCd: matchedMhlCode ? matchedMhlCode.text : item?.mqltCd,
usgCd: matchedMhlUsgCode ? matchedMhlUsgCode.text : (usgCdObj ? usgCdObj.text : item?.usgCd),
ctpvNm: ctpvNm ?? '-',
sggNm : sggNm ?? '-',
swtrmt_zone_nm: item.swtrmtZoneNm || item.swtrmt_zone_nm || '-',
};
});
setTableData(dataWithRowNumber);
setTableCount(res.data.count || []);
} else if (activeTab === 'operation') {
const RequestDTO: Record<string, string> = {
page: `${curPage - 1}`,
size: `${OPERATION_TABLE_SIZE}`,
};
if (var1) RequestDTO.bizInfoNm = var1;
if (var2 != '0' && var2) RequestDTO.bizTypeCd = var2;
// 수행기간 범위
const startDate = getDateString(
yearFilter.start,
'',
'',
"19000101",
);
const endDate = getDateString(
yearFilter.end,
'',
'',
`${todayYear}${todayMonth}${todayDate}`,
);
RequestDTO.fromflfmtEndYmd = startDate;
RequestDTO.toflfmtEndYmd = endDate;
// JSON.parse 사용으로 문자열 -> 배열 변환
RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"'));
const params = new URLSearchParams(RequestDTO).toString();
const res = await getData(`v1/bizInfo?${params}`);
const mappedContent = (res.content || []).map((item: any, index: number) => {
const formattedItem = formatDisplayData(item);
const matched = BIZ_TY_CD.find(code => code.lwpoCd === item.bizTypeCd);
return {
...formattedItem,
rowNumber: (curPage - 1) * OPERATION_TABLE_SIZE + index + 1, // 1부터 시작하는 일련번호 추가
bizTypeCd: matched ? matched.lwpoCdNm : item.bizTypeCd,
};
});
setTableData(mappedContent || []);
setTableCount(res.totalElements || 0);
setDetailTableData([]);
setDetailTableCount(0);
}
setShowTable(true);
setIsResetSelection(false);
} catch (err) {
console.error('API ERROR:', err);
setTableData([]);
setIsResetSelection(false);
} finally {
setIsLoading(false); // 로딩 종료
setIsResetSelection(false);
// setIsResetDetailSelection(false);
}
};
// 조회 버튼 클릭 시 1페이지로 이동 및 데이터 초기화
const handleSearch = () => {
setCurPage(1); // 페이지를 1로 초기화
fetchFilteredData();
setIsResetPage(true); // 페이지를 초기화
setIsResetDetailPage(true); // 디테일 페이지를 초기화
setIsResetSelection(false); // 선택 초기화
setIsResetDetailSelection(false); // 선택 초기화
setShowTable(true);
};
// 기본정보 테이블 cellClick 핸들러
const handleBasicCellClick = async (index?: number | undefined, headId?: String) => {
if (typeof index === "number") {
setIsLoading(true);
const row = tableData[index];
const layerNm = row.lyrTypeNm;
const fid = row.fid;
const core = GisApp.Module.layer as any;
await core.showSearchResultsFromGeoJSON(layerNm, fid);
setIsLoading(false);
onOpenChange(false); // SearchModal 닫기
}
};
// 운영정보 테이블 cellClick 핸들러
const handleOperationCellClick = async (index?: number | undefined, headId?: String) => {
setIsResetDetailPage(true); // 페이지를 1로 초기화
setIsResetDetailSelection(true); // 선택 초기화
setIsResetDetailPage(true); // 디테일 페이지를 초기화
if (typeof index === "number") {
setIsDetailLoading(true);
setDetailBizId(tableData[index].bizId);
setDetailCurPage(1); // 상세 페이지 초기화
try {
const RequestDTO: Record<string, string> = {
page: `${detailCurPage - 1}`,
size: `${OPERATION_TABLE_SIZE}`,
bizId: tableData[index % OPERATION_TABLE_SIZE].bizId,
};
RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"'));
const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`);
const mappedData = (res.data || []).map((item: any, detailIndex: number) => {
const formattedItem = formatDisplayData(item);
const usgCdObj = MS_SWAGE_PPL_USE_CODES.find(code => code.val === item?.usgCd?.trim());
const mqltCdObj = MS_SWAGE_PPL_CODES.find(code => code.val === item?.mqltCd?.trim());
return {
...formattedItem,
rowNumber: (detailCurPage - 1) * OPERATION_TABLE_SIZE + detailIndex + 1,
usgCd: usgCdObj ? usgCdObj.text : item.usgCd,
mqltCd: mqltCdObj ? mqltCdObj.text : item.mqltCd,
swtrmt_zone_nm: item.swtrmt_zone_nm ? item.swtrmt_zone_nm : '-',
};
});
setDetailTableData(mappedData);
setDetailTableCount(res.count || 0);
} catch (error) {
console.error('운영정보 cellClick 에러:', error);
} finally {
setIsDetailLoading(false);
setIsResetDetailPage(false);
setIsResetDetailSelection(false);
}
}
};
// 운영정보 디테일 테이블 페이지네이션 핸들러
const handleOperationPageNation = async (index?: number | undefined) => {
if (typeof index === "number") {
setIsDetailLoading(true);
try {
const RequestDTO: Record<string, string> = {
page: `${detailCurPage - 1}`,
size: `${OPERATION_TABLE_SIZE}`,
bizId: String(detailBizId),
};
const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`);
const mappedData = (res.data || []).map((item: any, detailIndex: number) => {
const formattedItem = formatDisplayData(item);
const usgCdObj = MS_SWAGE_PPL_USE_CODES.find(code => code.val === item?.usgCd?.trim());
const mqltCdObj = MS_SWAGE_PPL_CODES.find(code => code.val === item?.mqltCd?.trim());
return {
...formattedItem,
rowNumber: (detailCurPage - 1) * OPERATION_TABLE_SIZE + detailIndex + 1,
usgCd: usgCdObj ? usgCdObj.text : item.usgCd,
mqltCd: mqltCdObj ? mqltCdObj.text : item.mqltCd,
swtrmt_zone_nm: item.swtrmt_zone_nm ? item.swtrmt_zone_nm : '-',
};
});
setDetailTableData(mappedData);
setDetailTableCount(res.count || 0);
} catch (error) {
console.error('운영정보 페이지네이션 에러:', error);
} finally {
setIsDetailLoading(false);
}
}
};
const handleOperationResultCellClick = async (index?: number | undefined, headId?: String) => {
if (typeof index === "number") {
setIsLoading(true);
const row = detailTableData[index];
const layerNm = row.lyrTypeNm;
const fid = row.fid;
const core = GisApp.Module.layer as any;
await core.showSearchResultsFromGeoJSON(layerNm, fid);
setIsLoading(false);
onOpenChange(false); // SearchModal 닫기
}
};
// 기본정보 Excel 다운로드 핸들러
const basicExcelDownload = async () => {
if (tableData.length === 0) {
alert("다운로드 할 Excel 파일이 없습니다.\n조회 후 다시 시도해주세요.");
return;
}
toast.dismiss("global-confirm-toast");
confirmToast(
"파일 다운로드에는 2분 내외의 시간이 소요될 수 있습니다.\n파일 다운로드를 진행하시겠습니까?",
onAnalysis ?? (() => { }),
"text-xl font-medium text-gray-800"
);
async function onAnalysis() {
setIsLoading(true);
try {
const queryParams = new URLSearchParams({
lyrTypeNm: basicType,
size: '10000',
});
let cqlFilters: string[] = [];
let headerMapping: { [key: string]: string } = {};
let headerRange: string = "A1:F1";
switch (basicType) {
case 'tb_ms_swage_ppl': {
// 하수관로
cqlFilters = swagePplCqlFilters({
var1, var2,
ctpvCd, sggCd,
yearFilter,
start1, start2, end1, end2,
todayYear, todayMonth, todayDate,
getDateString
})
headerMapping = MsSwagePplExcelCol;
headerRange = "A1:M1";
break;
}
case 'tb_il_swage_prcs_fclt': {
// 하수처리시설
cqlFilters = swagePrcsFcltCqlFilters({
ctpvName, sggName,
yearFilter,
start1, end1,
todayYear, todayMonth, todayDate,
getDateString
});
headerMapping = SwtrmtFcltExcelCol;
headerRange = "A1:J1";
break;
}
case 'tb_ms_swage_prcs_zone': {
// 하수처리구역
cqlFilters = swagePrcsZoneCqlFilters({
ctpvCd, sggCd,
var1, var2
});
headerMapping = SwtrmtPrcsZoneExcelCol;
headerRange = "A1:D1";
break;
}
case 'tb_ms_swage_mhl': {
// 하수맨홀
cqlFilters = swageMhlCqlFilters({
ctpvCd, sggCd,
yearFilter,
start1, end1,
var1,
todayYear, todayMonth, todayDate,
getDateString
});
headerMapping = MsSwageMhlExcelCol;
headerRange = "A1:J1";
break;
}
case 'tb_ms_swage_pmstn': {
// 하수펌프장
cqlFilters = swagePmstnCqlFilters({
ctpvCd, sggCd,
yearFilter,
start1, start2, end1, end2,
todayYear, todayMonth, todayDate,
getDateString
});
headerMapping = ExclnSewagePumpExcelCol;
headerRange = "A1:J1";
break;
}
case 'tb_ms_stofcbr': {
// 우수토실
cqlFilters = stofcbrCqlFilters({
ctpvCd, sggCd,
yearFilter,
todayYear, todayMonth, todayDate,
getDateString
});
headerMapping = stofcbrExcelCol;
headerRange = "A1:H1";
break;
}
}
if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and '));
const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`);
// const mhlCdMap = new Map(MS_SWAGE_MHL_CODES.map(code => [code.val?.trim(), code.text]));
const mhlUsgCdMap = new Map(MS_SWAGE_MHL_USE_CODES.map(code => [code.val?.trim(), code.text]));
const pplUsgCdMap = new Map(MS_SWAGE_PPL_USE_CODES.map(code => [code.val?.trim(), code.text]));
const ctpvMap = new Map(ctpv.map(code => [code.val?.trim(), code.text]));
const sggMap = new Map(sgg.map(code => [code.val?.trim(), code.text]));
const mappedContent = (res.data.data || []).map((item: any) => {
const formattedItem = formatDisplayData(item);
let ctpvCd = item?.ctpvCd ?? formattedItem?.ctpvCd ?? '';
let sggCd = item?.sggCd ?? formattedItem?.sggCd ?? '';
if (!ctpvCd || !sggCd) {
const code = String(item?.pbadmsEmdCd ?? formattedItem?.pbadmsEmdCd ?? '')?.trim();
if (!ctpvCd && code) ctpvCd = code.slice(0, 2);
if (!sggCd && code) sggCd = code.slice(0, 5);
}
// trim 한 번만 수행
const trimmedUsgCd = item?.usgCd?.trim();
// const trimmedMqltCd = item?.mqltCd?.trim();
switch (basicType) {
case 'tb_ms_swage_ppl': {
// 하수관로
return {
fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '-',
instMngNo: formattedItem.instMngNo ?? item.instMngNo ?? '-',
ctpvNm: ctpvMap.get(ctpvCd) ?? item.ctpvNm ?? '-',
sggNm: sggMap.get(sggCd) ?? item.sggNm ?? '-',
swtrmt_zone_nm: formattedItem.swtrmt_zone_nm ?? item.swtrmt_zone_nm ?? '-',
instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '-',
usgCd: pplUsgCdMap.get(trimmedUsgCd) || item.usgCd || formattedItem.usgCd || '-',
pdmtVl: formattedItem.pdmtVl ?? item.pdmtVl ?? '-',
prlgVl: formattedItem.prlgVl ?? item.prlgVl ?? '-',
tmptDpth: formattedItem.tmptDpth ?? item.tmptDpth ?? '-',
tmptMhlMngNo: formattedItem.tmptMhlMngNo ?? item.tmptMhlMngNo ?? '-',
scendDpth: formattedItem.scendDpth ?? item.scendDpth ?? '-',
scendMhlMngNo: formattedItem.scendMhlMngNo ?? item.scendMhlMngNo ?? '-',
};
}
case 'tb_il_swage_prcs_fclt': {
// 하수처리시설
return {
swtrmtFcltNm: formattedItem.swtrmtFcltNm ?? item.swtrmtFcltNm ?? '-',
fcltCpct: formattedItem.fcltCpct ?? item.fcltCpct ?? '-',
rgnExpln: formattedItem.rgnExpln ?? item.rgnExpln ?? '-',
oprtngStrtYmd: formattedItem.oprtngStrtYmd ?? item.oprtngStrtYmd ?? '-',
laeNm: formattedItem.laeNm ?? item.laeNm ?? '-',
sareaNm: formattedItem.sareaNm ?? item.sareaNm ?? '-',
rvrNm: formattedItem.rvrNm ?? item.rvrNm ?? '-',
dschgWtstNm: formattedItem.dschgWtstNm ?? item.dschgWtstNm ?? '-',
swtrmtPpltnCnt: formattedItem.swtrmtPpltnCnt ?? item.swtrmtPpltnCnt ?? '-',
rprsMsrcCn: formattedItem.rprsMsrcCn ?? item.rprsMsrcCn ?? '-'
};
}
case 'tb_ms_swage_prcs_zone': {
// 하수처리구역
return {
swtrmtZoneNm: formattedItem.swtrmtZoneNm ?? item.swtrmtZoneNm ?? '-',
ctpvNm: ctpvMap.get(ctpvCd) ?? formattedItem.ctpvNm ?? '-',
sggNm: sggMap.get(sggCd) ?? formattedItem.sggNm ?? '-',
swtrmtFcltNm: formattedItem.swtrmtFcltNm ?? item.swtrmtFcltNm ?? '-'
};
}
case 'tb_ms_swage_mhl': {
// 하수맨홀
return {
fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '-',
ctpvNm: ctpvMap.get(ctpvCd) ?? formattedItem.ctpvNm ?? '-',
sggNm: sggMap.get(sggCd) ?? formattedItem.sggNm ?? '-',
instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '-',
usgCd: mhlUsgCdMap.get(trimmedUsgCd) || item.usgCd || formattedItem.usgCd || '-',
swageMhlDiamVl: formattedItem.swageMhlDiamVl ?? item.swageMhlDiamVl ?? '-',
wdthLen: formattedItem.wdthLen ?? item.wdthLen ?? '-',
vrtcLen: formattedItem.vrtcLen ?? item.vrtcLen ?? '-',
altdVl: formattedItem.altdVl ?? item.altdVl ?? '-',
dpthlVl: formattedItem.dpthlVl ?? item.dpthlVl ?? '-'
};
}
case 'tb_ms_swage_pmstn': {
// 하수펌프장
return {
fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '-',
ctpvNm: ctpvMap.get(ctpvCd) ?? formattedItem.ctpvNm ?? '-',
sggNm: sggMap.get(sggCd) ?? formattedItem.sggNm ?? '-',
swagePmstnNm: formattedItem.swagePmstnNm ?? item.swagePmstnNm ?? '-',
instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '-',
dailyPrcsCpct: formattedItem.dailyPrcsCpct ?? item.dailyPrcsCpct ?? '-',
maxImpndCpct: formattedItem.maxImpndCpct ?? item.maxImpndCpct ?? '-',
altdVl: formattedItem.altdVl ?? item.altdVl ?? '-',
wtlvVl: formattedItem.wtlvVl ?? item.wtlvVl ?? '-',
exclnWpmpAbltVl: formattedItem.exclnWpmpAbltVl ?? item.exclnWpmpAbltVl ?? '-'
};
}
case 'tb_ms_stofcbr': {
// 우수토실
return {
fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '-',
mapshtNo: formattedItem.mapshtNo ?? item.mapshtNo ?? '-',
ctpvNm: ctpvMap.get(ctpvCd) ?? formattedItem.ctpvNm ?? '-',
sggNm: sggMap.get(sggCd) ?? formattedItem.sggNm ?? '-',
instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '-',
worflwWallHgt: formattedItem.worflwWallHgt ?? item.worflwWallHgt ?? '-',
swageMhlMngNo: formattedItem.swageMhlMngNo ?? item.swageMhlMngNo ?? '-',
drtnAngl: formattedItem.drtnAngl ?? item.drtnAngl ?? '-'
};
}
}
});
// 선택된 행 데이터를 Excel 다운로드 형식으로 변환
const excelData = mappedContent; // 배열로 감싸서 전달
// 파일명 생성 (분석번호 기반)
const label = BASIC_TYPES.find((type) => type.val === basicType)?.text
const fileName = `${label} 조회 결과_${todayYear}${todayMonth}${todayDate}`;
await downloadExcelWithKoreanHeaders({
data: excelData as JSON[],
fileName: fileName,
sheetNameArray: [fileName],
headerRange: headerRange,
headerMapping: headerMapping
});
setIsLoading(false);
} catch (error) {
setIsLoading(false);
console.error("Excel 다운로드 중 오류 발생:", error);
alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요.");
}
}
};
// 운영정보 Excel 다운로드 핸들러 추가
const operationExcelDownload = async () => {
if (tableData.length === 0) {
alert("다운로드 할 Excel 파일이 없습니다.\n조회 후 다시 시도해주세요.");
return;
}
toast.dismiss("global-confirm-toast");
confirmToast(
"파일 다운로드에는 2분 내외의 시간이 소요될 수 있습니다.\n파일 다운로드를 진행하시겠습니까?",
onAnalysis ?? (() => { }),
"text-xl font-medium text-gray-800"
);
async function onAnalysis() {
setIsLoading(true);
try {
const RequestDTO: Record<string, string> = {
size: '10000',
};
if (var1) RequestDTO.bizInfoNm = var1;
if (var2 != '0' && var2) RequestDTO.bizTypeCd = var2;
// 수행기간 범위
const startDate = getDateString(
yearFilter.start,
'',
'',
"19000101",
);
const endDate = getDateString(
yearFilter.end,
'',
'',
`${todayYear}${todayMonth}${todayDate}`,
);
RequestDTO.fromflfmtEndYmd = startDate;
RequestDTO.toflfmtEndYmd = endDate;
// JSON.parse 사용으로 문자열 -> 배열 변환
RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"'));
const params = new URLSearchParams(RequestDTO).toString();
const res = await getData(`v1/bizInfo?${params}`);
// 헤더 매핑 정의
const headerMapping: { [key: string]: string } = {
'bizInfoNm': '사업명',
'flfmtBgngYmd': '수행시작일자',
'flfmtEndYmd': '수행종료일자',
'flfmtYr': '수행년도',
'bizTypeCd': '사업유형',
'bizAmt': '사업금액(원)',
};
const bizTyCdMap = new Map(BIZ_TY_CD.map(code => [code.lwpoCd, code.lwpoCdNm]));
const mappedContent = (res.content || []).map((item: any) => {
const formattedItem = formatDisplayData(item);
return {
bizInfoNm: formattedItem.bizInfoNm ?? item.bizInfoNm ?? '',
flfmtBgngYmd: formattedItem.flfmtBgngYmd ?? item.flfmtBgngYmd ?? '',
flfmtEndYmd: formattedItem.flfmtEndYmd ?? item.flfmtEndYmd ?? '',
flfmtYr: formattedItem.flfmtYr ?? item.flfmtYr ?? '',
bizTypeCd: bizTyCdMap.get(item.bizTypeCd) || item.bizTypeCd || formattedItem.bizTypeCd || '',
bizAmt: ((v) => (v === 0 || v === '0') ? '0' : (v ?? ''))(formattedItem.bizAmt ?? item.bizAmt),
};
});
// 선택된 행 데이터를 Excel 다운로드 형식으로 변환
const excelData = mappedContent; // 배열로 감싸서 전달
// 파일명 생성 (분석번호 기반)
const label = WORK_TYPES.find((type) => type.val === workType)?.text
const fileName = `${label} 사업 현황_${todayYear}${todayMonth}${todayDate}`;
await downloadExcelWithKoreanHeaders({
data: excelData as JSON[],
fileName: fileName,
sheetNameArray: [fileName],
headerRange: "A1:F1",
headerMapping: headerMapping
});
setIsLoading(false);
} catch (error) {
setIsLoading(false);
console.error("Excel 다운로드 중 오류 발생:", error);
alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요.");
}
}
};
// 운영정보 Excel 다운로드 핸들러 추가
const operationResultExcelDownload = async () => {
if (detailTableData.length === 0) {
alert("다운로드 할 Excel 파일이 없습니다.\n조회 후 다시 시도해주세요.");
return;
}
toast.dismiss("global-confirm-toast");
confirmToast(
"파일 다운로드에는 2분 내외의 시간이 소요될 수 있습니다.\n파일 다운로드를 진행하시겠습니까?",
onAnalysis ?? (() => { }),
"text-xl font-medium text-gray-800"
);
async function onAnalysis() {
setIsLoading(true);
try {
const RequestDTO: Record<string, string> = {
size: '10000',
bizId: String(detailBizId),
};
const apiStartTime = performance.now();
const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`);
const apiEndTime = performance.now();
const mappingStartTime = performance.now();
// 헤더 매핑 정의
const headerMapping: { [key: string]: string } = {
'fcltMngNo': '관리번호',
'swtrmt_zone_nm': '처리구역',
'usgCd': '용도',
'mqltCd': '재질',
'pdmtVl': '관경(mm)',
'prlgVl': '연장(m)',
'instlYmd': '설치일자',
'strc_stts_grd': '구조적 상태등급',
'oper_stts_grd': '운영적 상태등급',
};
const usgCdMap = new Map(MS_SWAGE_PPL_USE_CODES.map(code => [code.val, code.text]));
const mqltCdMap = new Map(MS_SWAGE_PPL_CODES.map(code => [code.val, code.text]));
// 🎯 단일 반복문으로 최적화 - 각 아이템마다 필요한 필드만 직접 매핑
const mappedData = (res.data || []).map((item: any) => {
const formattedItem = formatDisplayData(item);
// trim 한 번만 수행
const trimmedUsgCd = item?.usgCd?.trim();
const trimmedMqltCd = item?.mqltCd?.trim();
// zone 값 한 번만 계산
const zone = item.swtrmtZoneNm ?? formattedItem.swtrmt_zone_nm ?? item.swtrmt_zone_nm ?? '';
// 직접 매핑 - 반복문 없이 한 번에 객체 생성
return {
fcltMngNo: formattedItem.fcltMngNo ?? item.fcltMngNo ?? '',
swtrmt_zone_nm: (zone === null || zone === '') ? '-' : zone,
usgCd: usgCdMap.get(trimmedUsgCd) || item.usgCd || formattedItem.usgCd || '',
mqltCd: mqltCdMap.get(trimmedMqltCd) || item.mqltCd || formattedItem.mqltCd || '',
pdmtVl: formattedItem.pdmtVl ?? item.pdmtVl ?? '',
prlgVl: formattedItem.prlgVl ?? item.prlgVl ?? '',
instlYmd: formattedItem.instlYmd ?? item.instlYmd ?? '',
strc_stts_grd: ((v) => (v === 0 || v === '0') ? '0' : (v ?? ''))(formattedItem.strc_stts_grd ?? item.strc_stts_grd),
oper_stts_grd: ((v) => (v === 0 || v === '0') ? '0' : (v ?? ''))(formattedItem.oper_stts_grd ?? item.oper_stts_grd),
};
});
// 선택된 행 데이터를 Excel 다운로드 형식으로 변환
const excelData = mappedData; // 배열로 감싸서 전달
// 파일명 생성 (분석번호 기반)
const label = WORK_TYPES.find((type) => type.val === workType)?.text
const fileName = `${label} 결과표_${todayYear}${todayMonth}${todayDate}`;
await downloadExcelWithKoreanHeaders({
data: excelData as JSON[],
fileName: fileName,
sheetNameArray: [fileName],
headerRange: "A1:I1",
headerMapping: headerMapping
});
setIsLoading(false);
} catch (error) {
setIsLoading(false);
console.error("Excel 다운로드 중 오류 발생:", error);
alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요.");
}
}
};
// 여러 시설 보기
const viewMultipleFeature = async () => {
if (activeTab === 'basic') {
if (!(tableData.length === 0)) {
setIsLoading(true);
const row = tableData[0];
const layerNm = row.lyrTypeNm;
const fids = tableData.map((item) => item.fid);
const core = GisApp.Module.layer as any;
await core.showMultipleSearchResultsFromGeoJSON(layerNm, fids);
setIsLoading(false);
onOpenChange(false);
}
} else if (activeTab === 'operation') {
if (!(detailTableData.length === 0)) {
setIsLoading(true);
const row = detailTableData[0];
const layerNm = row.lyrTypeNm;
const fids = detailTableData.map((item) => item.fid);
const core = GisApp.Module.layer as any;
await core.showMultipleSearchResultsFromGeoJSON(layerNm, fids);
setIsLoading(false);
onOpenChange(false);
}
}
}
return {
open, onOpenChange, onFeatureSelect,
TABS,
getDateString,
handleSearch,
handleBasicCellClick,
handleOperationCellClick,
handleOperationPageNation,
handleOperationResultCellClick,
basicExcelDownload,
operationExcelDownload,
operationResultExcelDownload,
viewMultipleFeature,
};
};
@@ -1,7 +1,7 @@
"use client"; "use client";
import React, { useRef, useEffect, useState } from "react";
import { Label } from "@common/components/ui/label"; import { Label } from "@common/components/ui/label";
import { useEffect } from "react";
export const WATER_DEPTH_LAYER_TYPES: string[] = [ export const WATER_DEPTH_LAYER_TYPES: string[] = [
'tb_ms_flood_dngr_map', 'tb_ms_flood_dngr_map',
@@ -9,11 +9,11 @@ function FileUpload() {
const [file, setFile] = useState(null); const [file, setFile] = useState(null);
const [text, setText] = useState(''); const [text, setText] = useState('');
const handleFileChange = (e) => { const handleFileChange = (e: any) => {
setFile(e.target.files[0]); setFile(e.target.files[0]);
}; };
const handleTextChange = (e) => { const handleTextChange = (e: any) => {
setText(e.target.value); setText(e.target.value);
}; };
@@ -24,7 +24,7 @@ function FileUpload() {
} }
return null; return null;
} }
const handleFindUser = (e) => { const handleFindUser = () => {
const auth = getCookie("Authorization"); const auth = getCookie("Authorization");
if (auth) console.log("쿠키 auth : " + auth); if (auth) console.log("쿠키 auth : " + auth);
fetch(SEWERAGE_URL+"/v1/inftwtAnls/users/65a00f65-8460-49af-98ec-042977e56f4b", { fetch(SEWERAGE_URL+"/v1/inftwtAnls/users/65a00f65-8460-49af-98ec-042977e56f4b", {
@@ -37,7 +37,7 @@ function FileUpload() {
console.log(data); console.log(data);
}); });
}; };
const handleFindUserOna = (e) => { const handleFindUserOna = () => {
const auth = getCookie("Authorization"); const auth = getCookie("Authorization");
if (auth) console.log("쿠키 auth : " + auth); if (auth) console.log("쿠키 auth : " + auth);
fetch("/server/user-service/api/v1/users/65a00f65-8460-49af-98ec-042977e56f4b").then((data) => { fetch("/server/user-service/api/v1/users/65a00f65-8460-49af-98ec-042977e56f4b").then((data) => {
@@ -45,7 +45,7 @@ function FileUpload() {
}); });
}; };
const handleSubmit = async (e) => { const handleSubmit = async (e: any) => {
e.preventDefault(); e.preventDefault();
if (!file) { if (!file) {
@@ -100,7 +100,7 @@ const Page = () => {
alert('파일을 선택해주세요') alert('파일을 선택해주세요')
return false return false
} }
handleDownload(`/attach-service/api/v1/download/${atflId}`, fileNm) handleDownload(`/attach-service/api/v1/download/${atflId}`, fileNm);
} }
// 선택 파일 삭제 // 선택 파일 삭제
+5 -2
View File
@@ -80,12 +80,15 @@ export const deleteData = async (url: string, data?: object): Promise<any> => {
}; };
// DC API 호출 // DC API 호출
export const getDcData = async (url: string, data?: object): Promise<any> => { export const getDcData = async (url: string, resType: string = "json"): Promise<any> => {
const res = await fetch('/dc-service' + url, { const res = await fetch('/dc-service' + url, {
method: "GET", method: "GET",
}); });
if (res.ok) { if (res.ok) {
const data = await res.json(); let data;
if (resType === "json") data = await res.json();
else if (resType === "blob") data = await res.blob();
return data; return data;
} }
throw new Error('Failed to fetch data'); throw new Error('Failed to fetch data');
@@ -1,28 +1,27 @@
const BASE_URL = process.env.NEXT_PUBLIC_SERVER_API_URL || ''; const BASE_URL = process.env.NEXT_PUBLIC_SERVER_API_URL || '';
export const handleDownload = async (api:string, fileNm:string) => { export const handleDownload = async (api: string, fileNm: string) => {
try { try {
const response = await fetch(api, { const response = await fetch(api, {
method: 'GET', method: 'GET',
}); });
if (!response.ok) { if (!response.ok) throw new Error('파일 다운로드 실패');
throw new Error('파일 다운로드 실패');
}
const blob = await response.blob(); const blob = await response.blob();
const url = window.URL.createObjectURL(blob); fileDownload(blob, fileNm);
} catch (error) {
console.error('다운로드 중 오류 발생:', error);
}
};
// 다운로드를 트리거할 가짜 <a> 태그 생성 export const fileDownload = (data: Blob, fileName: string) => { // Blob데이터 임시link로 연결
const link = document.createElement('a'); const url = window.URL.createObjectURL(data);
link.href = url; const link = document.createElement('a');
link.download = fileNm.normalize('NFC'); // 원하는 파일명 지정 link.href = url;
document.body.appendChild(link); link.download = fileName; // 원하는 파일명 지정
link.click(); document.body.appendChild(link);
link.click();
// 메모리 해제 // 메모리 해제
link.remove(); link.remove();
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
} catch (error) { }
console.error('다운로드 중 오류 발생:', error);
}
};
+43
View File
@@ -0,0 +1,43 @@
import useReportTab, { type Folder, type ReportDataFile } from '../(map-layout)/map/searchModal/useReportTab';
import { create } from 'zustand';
import { getDcData } from '@/components/lib/api';
interface useReportTabStoreType {
isOpen: boolean;
files: ReportDataFile[];
dcTree: Folder[];
dcFiles: ReportDataFile[];
setIsOpen: (open: boolean) => void;
setFiles: (files: ReportDataFile[]) => void;
fetchDcTree: (clsfSystmNm: string) => void;
fetchDcFiles: (clsfSystmNm: string) => void;
}
export const useReportTabStore = create<useReportTabStoreType>((set, get) => ({
isOpen: false,
files: [],
dcTree: [],
dcFiles: [],
setIsOpen: (open: boolean) => set({ isOpen: open }),
setFiles: (files: ReportDataFile[]) => set({ files }),
fetchDcTree: async (clsfSystmNm: string) => {
const { dcTree } = get();
try {
const result = await getDcData("/api/v1/dc/clsf?clsfSystmNm=" + clsfSystmNm);
if (result && JSON.stringify(result) !== JSON.stringify(dcTree))
set({ dcTree: result });
} catch (error) {
console.error('Failed to fetch DC tree:', error);
}
},
fetchDcFiles: async (clsfSystmNm: string) => {
const { getDcFiles } = useReportTab();
const { dcFiles } = get();
try {
const result = await getDcFiles(clsfSystmNm);
if (result && JSON.stringify(result) !== JSON.stringify(dcFiles))
set({ dcFiles: result });
} catch (error) {
console.error('Failed to fetch DC files:', error);
}
}
}));
+10 -5
View File
@@ -23,6 +23,8 @@ const todayMonth = (today.getMonth() + 1).toString().padStart(2, "0");
const todayDate = today.getDate().toString().padStart(2, "0"); const todayDate = today.getDate().toString().padStart(2, "0");
export interface SearchModalState { export interface SearchModalState {
// 모달 열림 상태
isSearchModalOpen: boolean;
// 탭 관련 // 탭 관련
activeTab: string; activeTab: string;
basicType: string; basicType: string;
@@ -67,9 +69,9 @@ export interface SearchModalState {
todayYear: string; todayYear: string;
todayMonth: string; todayMonth: string;
todayDate: string; todayDate: string;
getDateString: (year: string, month: string, day: string, defaultDate: string) => string;
// setters // setters
setIsSearchModalOpen: (open: boolean) => void;
setActiveTab: (tab: string) => void; setActiveTab: (tab: string) => void;
setBasicType: (type: string) => void; setBasicType: (type: string) => void;
setWorkType: (type: string) => void; setWorkType: (type: string) => void;
@@ -98,11 +100,13 @@ export interface SearchModalState {
setVar1: (val: string) => void; setVar1: (val: string) => void;
setVar2: (val: string) => void; setVar2: (val: string) => void;
setYearFilter: (filter: { start: string; end: string }) => void; setYearFilter: (filter: { start: string; end: string }) => void;
// 초기화 액션
resetFilters: () => void; getDateString: (year: string, month: string, day: string, defaultDate: string) => string;
resetFilters: () => void; // 초기화 액션
} }
const useSearchModalStore = create<SearchModalState>((set) => ({ const useSearchModalStore = create<SearchModalState>((set) => ({
isSearchModalOpen: false,
activeTab: "basic", activeTab: "basic",
basicType: "tb_ms_swage_ppl", basicType: "tb_ms_swage_ppl",
workType: "['200', '201', '204']", workType: "['200', '201', '204']",
@@ -135,7 +139,7 @@ const useSearchModalStore = create<SearchModalState>((set) => ({
todayYear, todayYear,
todayMonth, todayMonth,
todayDate, todayDate,
getDateString, setIsSearchModalOpen: (isSearchModalOpen) => set({ isSearchModalOpen }),
setActiveTab: (tab) => set({ activeTab: tab }), setActiveTab: (tab) => set({ activeTab: tab }),
setBasicType: (type) => set({ basicType: type }), setBasicType: (type) => set({ basicType: type }),
setWorkType: (type) => set({ workType: type }), setWorkType: (type) => set({ workType: type }),
@@ -164,7 +168,8 @@ const useSearchModalStore = create<SearchModalState>((set) => ({
setVar1: (val) => set({ var1: val }), setVar1: (val) => set({ var1: val }),
setVar2: (val) => set({ var2: val }), setVar2: (val) => set({ var2: val }),
setYearFilter: (filter) => set({ yearFilter: filter }), setYearFilter: (filter) => set({ yearFilter: filter }),
getDateString: getDateString,
// 초기화 액션 // 초기화 액션
resetFilters: () => resetFilters: () =>
set({ set({
+4 -2
View File
@@ -31,8 +31,10 @@
"@common/business_components/*": ["../../../packages/common/business_components/src/*"], "@common/business_components/*": ["../../../packages/common/business_components/src/*"],
"@common/network": ["../../../packages/common/network/"], "@common/network": ["../../../packages/common/network/"],
"@common/config": ["../../../packages/common/config/dist/"], "@common/config": ["../../../packages/common/config/dist/"],
"@utils": ["../app/components/lib/utils"], "@/app/*": ["../app/*"],
"@/components/*": ["../app/components/*"] "@/components/*": ["../app/components/*"],
"@/utils": ["../app/components/lib/utils"],
"@/store/*": ["../app/store/*"],
} }
}, },
"include": [ "include": [
@@ -118,15 +118,21 @@ export default function LineBarchart({
if (!isFinite(maxValue) || maxValue <= 0) { if (!isFinite(maxValue) || maxValue <= 0) {
return { max: 0, ticks: [0] }; return { max: 0, ticks: [0] };
} }
const exponent = Math.floor(Math.log10(maxValue)); const exponent = Math.floor(Math.log10(maxValue));
const base = Math.pow(10, exponent); const base = Math.pow(10, exponent);
const fraction = maxValue / base; const fraction = maxValue / base;
let niceFraction: number; let niceFraction: number;
if (fraction <= 1) niceFraction = 1; if (fraction <= 1) niceFraction = 1;
else if (fraction <= 2) niceFraction = 3; else if (fraction <= 2) niceFraction = 2;
else if (fraction <= 3) niceFraction = 3;
else if (fraction <= 4) niceFraction = 4;
else if (fraction <= 5) niceFraction = 5; else if (fraction <= 5) niceFraction = 5;
else if (fraction <= 6) niceFraction = 6;
else if (fraction <= 7) niceFraction = 7;
else if (fraction <= 8) niceFraction = 8;
else if (fraction <= 9) niceFraction = 9;
else niceFraction = 10; else niceFraction = 10;
const niceMax = niceFraction * base; // round up to 1/2/5 * 10^k const niceMax = niceFraction * base; // round up to 1/2/5 * 10^k
@@ -164,7 +170,7 @@ export default function LineBarchart({
return ( return (
<text <text
x={x - padding} // 맨 좌측 바깥 x={x - padding} // 맨 좌측 바깥
y={y + height / 2} // 수직 중앙 y={y + height / 2 - 12} // 수직 중앙
textAnchor="middle" textAnchor="middle"
transform={`rotate(-90, ${x - padding}, ${y + height / 2})`} transform={`rotate(-90, ${x - padding}, ${y + height / 2})`}
className="text-[12px] font-bold" className="text-[12px] font-bold"
@@ -195,12 +201,12 @@ export default function LineBarchart({
</div> </div>
) : null} ) : null}
<div className="h-[calc(100%-42px)] w-[calc(100%-40px)] "> <div className="h-[calc(100%-42px)] w-[calc(100%-40px)] ">
<ChartContainer config={chartConfig ?? {}} className="h-full w-full"> <ChartContainer config={chartConfig ?? {}} className="h-full w-[100%] ml-5">
<ComposedChart <ComposedChart
data={chartData} data={chartData}
height={graphSize * 7.5} height={graphSize * 7.5}
width={graphSize * 12.5} width={graphSize * 12.5}
margin={{ top: 5, right: 30, left: 20, bottom: 5 }} margin={{ top: 5, right: 20, left: 30, bottom: 5 }}
> >
<CartesianGrid strokeDasharray="3 3" /> <CartesianGrid strokeDasharray="3 3" />
{setXAxisYearTick? {setXAxisYearTick?
@@ -210,7 +216,7 @@ export default function LineBarchart({
{/* 왼쪽 Y축: lineVal */} {/* 왼쪽 Y축: lineVal */}
{yFieldText && <YAxis {yFieldText && <YAxis
yAxisId="left" yAxisId="left"
className="text-[12px] font-bold" className="text-[12px] font-bold"
tickFormatter={(value) => tickFormatter={(value) =>
typeof value === "number" ? value.toLocaleString() : value typeof value === "number" ? value.toLocaleString() : value
} }
@@ -218,7 +224,6 @@ export default function LineBarchart({
ticks={leftScale.ticks} ticks={leftScale.ticks}
orientation="left" orientation="left"
label={(props) => <CustomAxisText {...props} fieldText={yFieldText} position="left" />} label={(props) => <CustomAxisText {...props} fieldText={yFieldText} position="left" />}
/>} />}
{/* 오른쪽 Y축: barVal */} {/* 오른쪽 Y축: barVal */}
{yFieldText2 && <YAxis {yFieldText2 && <YAxis
@@ -316,11 +316,7 @@ function Gis({
)} )}
{/* SearchView가 존재할 때만 렌더링 */} {/* SearchView가 존재할 때만 렌더링 */}
{SearchView && ( {SearchView && (
<SearchView <SearchView />
open={isSearchModalOpen}
onOpenChange={setIsSearchModalOpen}
onFeatureSelect={handleFeatureSelect}
/>
)} )}
{/* 시도/시군구 조회 영역 */} {/* 시도/시군구 조회 영역 */}
{SearchBar && ( {SearchBar && (