Merge branch 'iipbs/sn/dev'
This commit is contained in:
@@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react'
|
||||
import InputSection from '../InputSection';
|
||||
import { handleInputChange } from '../common';
|
||||
import { getData } from "@/components/lib/api";
|
||||
import useStore from '../../../../store/useStore';
|
||||
import useStore from '@/store/useStore';
|
||||
|
||||
// 하수관로
|
||||
const inputColumn = [
|
||||
|
||||
@@ -8,37 +8,15 @@ import SearchModal from "./searchModal/searchModal";
|
||||
import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup";
|
||||
import { WaterDepth, WATER_DEPTH_LAYER_TYPES } from "./waterDepth/waterDepth";
|
||||
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 { isSearchModalOpen, setIsSearchModalOpen } = useSearchModalStore();
|
||||
|
||||
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
|
||||
const [selectedFeature, setSelectedFeature] = useState(null); // 선택된 피처 상태
|
||||
const gisMaps = useRef<any | null>(null);
|
||||
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
|
||||
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태
|
||||
const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터
|
||||
const [showWaterDepth, setShowWaterDepth] = useState(false); // WaterDepth 표시 여부
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
@@ -148,12 +126,12 @@ const Map = () => {
|
||||
searchView={SearchModal}
|
||||
detailView={FeatureClickEventPopup}
|
||||
onFeatureSelect={onFeatureSelect}
|
||||
setIsOpen={setIsOpen}
|
||||
setFeatureData={setFeatureData}
|
||||
featureData={featureData}
|
||||
setIsSearchModalOpen={setIsSearchModalOpen}
|
||||
setFeatureData={setFeatureData}
|
||||
isOpen={isOpen}
|
||||
setIsOpen={setIsOpen}
|
||||
isSearchModalOpen={isSearchModalOpen}
|
||||
setIsSearchModalOpen={setIsSearchModalOpen}
|
||||
onCheckedLayersChange={handleCheckedLayersChange}
|
||||
searchBar={<ProcSearchBar gisMaps={gisMaps} facilityTypes={facilityTypes} />}
|
||||
waterDepth={showWaterDepth ? WaterDepth : undefined}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import React from 'react';
|
||||
import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox';
|
||||
import { SelectBox } from '@common/business_components/ui';
|
||||
import { Button, Label } from '@common/components';
|
||||
import { Label } from '@common/components';
|
||||
import SwagePpl from './swagePpl';
|
||||
import SwagePrcsFclt from './swagePrcsFclt';
|
||||
import SwagePrcsZone from './swagePrcsZone';
|
||||
import SwageMhl from './swageMhl';
|
||||
import SwagePmstn from './swagePmstn';
|
||||
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: "우수토실" },
|
||||
];
|
||||
|
||||
const BasicTab: React.FC<SearchModalProps> = (props) => {
|
||||
const BasicTab: React.FC = () => {
|
||||
const { basicType, setBasicType } = useSearchModalStore();
|
||||
|
||||
return (
|
||||
@@ -45,12 +43,12 @@ const BasicTab: React.FC<SearchModalProps> = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
{/* 조회 조건 */}
|
||||
{basicType === "tb_ms_swage_ppl" && (<SwagePpl {...props} />)} {/* 하수관로 */}
|
||||
{basicType === "tb_il_swage_prcs_fclt" && (<SwagePrcsFclt {...props} />)} {/* 하수처리시설 */}
|
||||
{basicType === "tb_ms_swage_prcs_zone" && (<SwagePrcsZone {...props} />)} {/* 하수처리구역 */}
|
||||
{basicType === "tb_ms_swage_mhl" && (<SwageMhl {...props} />)} {/* 하수맨홀 */}
|
||||
{basicType === "tb_ms_swage_pmstn" && (<SwagePmstn {...props} />)} {/* 하수펌프장 */}
|
||||
{basicType === "tb_ms_stofcbr" && (<Stofcbr {...props} />)} {/* 우수토실 */}
|
||||
{basicType === "tb_ms_swage_ppl" && (<SwagePpl />)} {/* 하수관로 */}
|
||||
{basicType === "tb_il_swage_prcs_fclt" && (<SwagePrcsFclt />)} {/* 하수처리시설 */}
|
||||
{basicType === "tb_ms_swage_prcs_zone" && (<SwagePrcsZone />)} {/* 하수처리구역 */}
|
||||
{basicType === "tb_ms_swage_mhl" && (<SwageMhl />)} {/* 하수맨홀 */}
|
||||
{basicType === "tb_ms_swage_pmstn" && (<SwagePmstn />)} {/* 하수펌프장 */}
|
||||
{basicType === "tb_ms_stofcbr" && (<Stofcbr />)} {/* 우수토실 */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,82 +1,22 @@
|
||||
import React from 'react';
|
||||
import React, { use } from 'react';
|
||||
import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox';
|
||||
import { SelectBox } from '@common/business_components/ui';
|
||||
import { Button, Input, Label } from '@common/components';
|
||||
import useStore from '../../../store/useStore';
|
||||
import useSearchModalStore from '../../../store/useSearchModalStore';
|
||||
import { useSearchModal, type SearchModalProps } from './useSearchModal';
|
||||
|
||||
// 운영정보 '구분' 선택 목록
|
||||
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: "운영적 상태등급" },
|
||||
];
|
||||
import useSearchModal from './useSearchModal';
|
||||
import useOperationTab from './useOperationTab';
|
||||
|
||||
|
||||
const OperationTab: React.FC<SearchModalProps> = (props) => {
|
||||
const OperationTab: React.FC = () => {
|
||||
const { BIZ_TY_CD } = useStore();
|
||||
const {
|
||||
workType, setWorkType,
|
||||
var1, setVar1, var2, setVar2,
|
||||
yearFilter, setYearFilter,
|
||||
} = useSearchModalStore();
|
||||
const { handleSearch } = useSearchModal(props);
|
||||
|
||||
const { BIZ_TY_CD } = useStore();
|
||||
const { handleSearch } = useSearchModal();
|
||||
const { WORK_TYPES } = useOperationTab();
|
||||
|
||||
const BIZ_TY_CD_SELECT_ARRAY = BIZ_TY_CD.map(item => ({
|
||||
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 { SelectBox, UiTable } from '@common/business_components/ui';
|
||||
import LoadingOverlay from '@/components/ui/LoadingBar';
|
||||
import { addFileName, flattenFolders, getFileDownload, useDcTree, reportGubuns } from './searchModalReportApi';
|
||||
import useStore from '../../../store/useStore';
|
||||
import useReportTab from './useReportTab';
|
||||
import useStore from '@/store/useStore';
|
||||
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
|
||||
import { useReportFileModalStore } from './searchModalReportApi'; // 모달 상태 관리 스토어
|
||||
import ReportFiles from './reportFiles'; // 파일목록모달
|
||||
import { useReportTabStore } from '@/store/useReportTabStore';
|
||||
import ReportFilesModal from './reportFilesModal'; // 파일목록모달
|
||||
|
||||
|
||||
// 하수도정비 기본계획 보고서
|
||||
@@ -42,13 +42,20 @@ const ReportTab: React.FC<ReportTabProps> = ({ open, isSearch = true, ctpvNm, sg
|
||||
const [ctpvCd, setCtpvCd] = useState('0');
|
||||
const [sggCd, setSggCd] = useState('0');
|
||||
const [reportGubun, setReportGubun] = useState("0");
|
||||
// const [gubun, setGubun] = useState("");
|
||||
const { isOpen: isModalOpen, setIsOpen: setIsModalOpen } = useReportFileModalStore(); // 모달 상태 관리 스토어
|
||||
const [clsfSystmSn, setClsfSystmSn] = useState(''); // 자료관 기본계획 일련번호
|
||||
const { ctpv, sgg } = useStore();
|
||||
|
||||
// 하수정비기본계획 report
|
||||
const reportOriginData = useDcTree(""); //:Folder[]
|
||||
const { ctpv, sgg } = useStore();
|
||||
const {
|
||||
isOpen: isReportFilesModalOpen,
|
||||
setIsOpen: setIsReportFilesModalOpen,
|
||||
dcTree: reportOriginData,
|
||||
fetchDcTree
|
||||
} = useReportTabStore(); // 모달 상태 관리 스토어
|
||||
const { REPORT_TYPES, flattenFolders, addFileName } = useReportTab();
|
||||
|
||||
useEffect(() => {
|
||||
fetchDcTree(""); // 하수정비기본계획 report
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// filterParams 초기화
|
||||
@@ -105,12 +112,9 @@ const ReportTab: React.FC<ReportTabProps> = ({ open, isSearch = true, ctpvNm, sg
|
||||
const handleReportCellClick = (index: number | undefined) => {
|
||||
if (typeof index === "number" && tableData[index]) {
|
||||
const reportData = tableData[index];
|
||||
const fileName = reportData.fileName;
|
||||
const filePath = reportData.filePath;
|
||||
if (reportData.fileSn !== "") {
|
||||
//getFileDownload(reportData.fileSn, fileName, filePath);
|
||||
setIsModalOpen(true); // 모달 열기
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
if (yearFilter.start != "" && yearFilter.end == "") //승인년도검색
|
||||
const { start, end } = yearFilter;
|
||||
if (start && !end) //승인년도검색
|
||||
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);
|
||||
if (yearFilter.start != "" && yearFilter.end != "") {
|
||||
else if (start && end) {
|
||||
filterReportData = filterReportData.filter(item => {
|
||||
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"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label=""
|
||||
selectArray={[...reportGubuns]}
|
||||
selectArray={[...REPORT_TYPES]}
|
||||
value={reportGubun} onSelectValue={val => setReportGubun(String(val))}
|
||||
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>
|
||||
{/* 모달 컴포넌트 */}
|
||||
{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 { UiModal, UITab, UiTable } from '@common/business_components/ui';
|
||||
import { Button } from '@common/components/ui';
|
||||
import axios from 'axios';
|
||||
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 React, { useMemo } from 'react';
|
||||
import { Toaster } from "react-hot-toast";
|
||||
import { useSearchModal, OPERATION_TABLE_SIZE, type SearchModalProps } from './useSearchModal';
|
||||
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 {
|
||||
open, onOpenChange,
|
||||
TABS,
|
||||
handleSearch,
|
||||
handleBasicCellClick,
|
||||
handleOperationCellClick,
|
||||
handleOperationResultCellClick,
|
||||
basicExcelDownload,
|
||||
operationExcelDownload,
|
||||
operationResultExcelDownload,
|
||||
viewMultipleFeature,
|
||||
} = useSearchModal(props);
|
||||
const SearchModal: React.FC = () => {
|
||||
const {
|
||||
isSearchModalOpen, setIsSearchModalOpen,
|
||||
activeTab, basicType, workType,
|
||||
tableData, detailTableData,
|
||||
tableData, detailTableData,
|
||||
tableCount, detailTableCount,
|
||||
curPage, detailCurPage,
|
||||
isLoading, isDetailLoading,
|
||||
@@ -40,6 +27,21 @@ const SearchModal: React.FC<SearchModalProps> = (props) => {
|
||||
setActiveTab,
|
||||
setCurPage, setDetailCurPage,
|
||||
} = 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(() => {
|
||||
if (activeTab === 'basic') { // 기본정보
|
||||
@@ -52,16 +54,16 @@ const SearchModal: React.FC<SearchModalProps> = (props) => {
|
||||
|
||||
return [];
|
||||
}
|
||||
if (activeTab === 'operation') return operationTableHeadlist; // 운영정보
|
||||
if (activeTab === 'operation') return OPERATION_TABLE_HEADLIST; // 운영정보
|
||||
|
||||
return [];
|
||||
}, [activeTab, basicType, workType]);
|
||||
|
||||
const dynamicHeadlist2: { id: string, title: string }[] = useMemo(() => {
|
||||
if (activeTab === 'operation') {
|
||||
if (workType === WORK_TYPES[0]?.val) return operationMaintenanceTableHeadlist; // 보수 및 갱신
|
||||
if (workType === WORK_TYPES[1]?.val) return operationInspectionTableHeadlist; // 점검 및 진단
|
||||
if (workType === WORK_TYPES[2]?.val) return operationCleaningTableHeadlist; // 청소 및 준설
|
||||
if (workType === WORK_TYPES[0]?.val) return MAINTENANACE_TABLE_HEADLIST; // 보수 및 갱신
|
||||
if (workType === WORK_TYPES[1]?.val) return CLEANING_TABLE_HEADLIST; // 점검 및 진단
|
||||
if (workType === WORK_TYPES[2]?.val) return INSPECTION_TABLE_HEADLIST; // 청소 및 준설
|
||||
}
|
||||
|
||||
return [];
|
||||
@@ -79,20 +81,9 @@ const SearchModal: React.FC<SearchModalProps> = (props) => {
|
||||
}))}
|
||||
onValueChange={(value: string) => setActiveTab(value)}
|
||||
/>
|
||||
{/* 기본정보 탭 */}
|
||||
{activeTab === 'basic' && (
|
||||
<BasicTab {...props} />
|
||||
)}
|
||||
{/* 운영정보 탭 */}
|
||||
{activeTab === 'operation' && (
|
||||
<OperationTab {...props} />
|
||||
)}
|
||||
{/* 하수도정비기본계획보고서 탭 */}
|
||||
{activeTab === 'report' && (
|
||||
<ReportTab
|
||||
open={open}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'basic' && (<BasicTab />)} {/* 기본정보 탭 */}
|
||||
{activeTab === 'operation' && (<OperationTab />)} {/* 운영정보 탭 */}
|
||||
{activeTab === 'report' && (<ReportTab open={isSearchModalOpen} />)} {/* 하수도정비기본계획보고서 탭 */}
|
||||
|
||||
|
||||
{/* 📋 결과 리스트 */}
|
||||
@@ -192,8 +183,8 @@ const SearchModal: React.FC<SearchModalProps> = (props) => {
|
||||
return (
|
||||
<>
|
||||
<UiModal
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
open={isSearchModalOpen}
|
||||
onOpenChange={setIsSearchModalOpen}
|
||||
bodyClass={`
|
||||
h-[100dvh]
|
||||
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 { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
|
||||
import useSearchModalStore from '../../../store/useSearchModalStore';
|
||||
import { useSearchModal, type SearchModalProps } from './useSearchModal';
|
||||
import useSearchModal from './useSearchModal';
|
||||
|
||||
interface stofcbrCqlFiltersParams {
|
||||
ctpvCd: string;
|
||||
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) => {
|
||||
|
||||
export const stofcbrCqlFilters = () => {
|
||||
const {
|
||||
ctpvCd, sggCd,
|
||||
yearFilter,
|
||||
todayYear, todayMonth, todayDate,
|
||||
getDateString
|
||||
} = params;
|
||||
} = useSearchModalStore.getState();
|
||||
|
||||
const cqlFilters: string[] = [];
|
||||
// 시도, 시군구
|
||||
@@ -48,13 +40,13 @@ export const stofcbrCqlFilters = (params: stofcbrCqlFiltersParams) => {
|
||||
return cqlFilters;
|
||||
};
|
||||
|
||||
const Stofcbr: React.FC<SearchModalProps> = (props) => {
|
||||
const Stofcbr: React.FC = () => {
|
||||
const {
|
||||
ctpvCd, setCtpvCd,
|
||||
sggCd, setSggCd,
|
||||
yearFilter, setYearFilter,
|
||||
} = useSearchModalStore();
|
||||
const { handleSearch } = useSearchModal(props);
|
||||
const { handleSearch } = useSearchModal();
|
||||
|
||||
|
||||
return (
|
||||
|
||||
@@ -7,21 +7,10 @@ import { MS_SWAGE_MHL_USE_CODES } from '../components/common/table';
|
||||
import { SelectBox } from '@common/business_components/ui';
|
||||
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
|
||||
import useSearchModalStore from '../../../store/useSearchModalStore';
|
||||
import { useSearchModal, type SearchModalProps } from './useSearchModal';
|
||||
import useSearchModal from './useSearchModal';
|
||||
|
||||
interface swageMhlCqlFiltersParams {
|
||||
ctpvCd: string;
|
||||
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) => {
|
||||
|
||||
export const swageMhlCqlFilters = () => {
|
||||
const {
|
||||
ctpvCd, sggCd,
|
||||
yearFilter,
|
||||
@@ -29,7 +18,7 @@ export const swageMhlCqlFilters = (params: swageMhlCqlFiltersParams) => {
|
||||
var1,
|
||||
todayYear, todayMonth, todayDate,
|
||||
getDateString
|
||||
} = params;
|
||||
} = useSearchModalStore.getState();
|
||||
|
||||
const cqlFilters: string[] = [];
|
||||
// 시도, 시군구
|
||||
@@ -60,7 +49,7 @@ export const swageMhlCqlFilters = (params: swageMhlCqlFiltersParams) => {
|
||||
return cqlFilters;
|
||||
};
|
||||
|
||||
const SwageMhl: React.FC<SearchModalProps> = (props) => {
|
||||
const SwageMhl: React.FC = () => {
|
||||
const {
|
||||
var1, setVar1,
|
||||
start1, setStart1, end1, setEnd1,
|
||||
@@ -68,7 +57,7 @@ const SwageMhl: React.FC<SearchModalProps> = (props) => {
|
||||
sggCd, setSggCd,
|
||||
yearFilter, setYearFilter,
|
||||
} = useSearchModalStore();
|
||||
const { handleSearch } = useSearchModal(props);
|
||||
const { handleSearch } = useSearchModal();
|
||||
|
||||
return (
|
||||
<div className="mb-3">
|
||||
|
||||
@@ -5,29 +5,17 @@ import { Button, Input, Label } from '@common/components';
|
||||
import React from 'react';
|
||||
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
|
||||
import useSearchModalStore from '../../../store/useSearchModalStore';
|
||||
import { useSearchModal, type SearchModalProps } from './useSearchModal';
|
||||
import useSearchModal from './useSearchModal';
|
||||
|
||||
interface swagePmstnCqlFiltersParams {
|
||||
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;
|
||||
}
|
||||
export const swagePmstnCqlFilters = (params: swagePmstnCqlFiltersParams) => {
|
||||
|
||||
export const swagePmstnCqlFilters = () => {
|
||||
const {
|
||||
ctpvCd, sggCd,
|
||||
yearFilter,
|
||||
start1, start2, end1, end2,
|
||||
todayYear, todayMonth, todayDate,
|
||||
getDateString
|
||||
} = params;
|
||||
} = useSearchModalStore.getState();
|
||||
|
||||
const cqlFilters: string[] = [];
|
||||
// 시도, 시군구
|
||||
@@ -63,7 +51,7 @@ export const swagePmstnCqlFilters = (params: swagePmstnCqlFiltersParams) => {
|
||||
return cqlFilters;
|
||||
};
|
||||
|
||||
const SwagePmstn: React.FC<SearchModalProps> = (props) => {
|
||||
const SwagePmstn: React.FC = () => {
|
||||
const {
|
||||
start1, setStart1,
|
||||
end1, setEnd1,
|
||||
@@ -71,7 +59,7 @@ const SwagePmstn: React.FC<SearchModalProps> = (props) => {
|
||||
sggCd, setSggCd,
|
||||
yearFilter, setYearFilter,
|
||||
} = useSearchModalStore();
|
||||
const { handleSearch } = useSearchModal(props);
|
||||
const { handleSearch } = useSearchModal();
|
||||
|
||||
return (
|
||||
<div className="mb-3">
|
||||
|
||||
@@ -4,29 +4,14 @@ import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox';
|
||||
import { SelectBox } from '@common/business_components/ui';
|
||||
import { Button, Input, Label } from '@common/components';
|
||||
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 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 필터 생성
|
||||
export const swagePplCqlFilters = (params: swagePplCqlFiltersParams) => {
|
||||
export const swagePplCqlFilters = () => {
|
||||
const {
|
||||
var1, var2,
|
||||
ctpvCd, sggCd,
|
||||
@@ -34,7 +19,7 @@ export const swagePplCqlFilters = (params: swagePplCqlFiltersParams) => {
|
||||
start1, start2, end1, end2,
|
||||
todayYear, todayMonth, todayDate,
|
||||
getDateString
|
||||
} = params;
|
||||
} = useSearchModalStore.getState();
|
||||
|
||||
const cqlFilters: string[] = [];
|
||||
if (var1) cqlFilters.push(`usg_cd='${var1}'`);
|
||||
@@ -69,14 +54,14 @@ export const swagePplCqlFilters = (params: swagePplCqlFiltersParams) => {
|
||||
return cqlFilters;
|
||||
};
|
||||
|
||||
const SwagePpl: React.FC<SearchModalProps> = (props) => {
|
||||
const SwagePpl: React.FC = () => {
|
||||
const {
|
||||
var1, setVar1,
|
||||
start1, setStart1, end1, setEnd1,
|
||||
ctpvCd, setCtpvCd, sggCd, setSggCd,
|
||||
yearFilter, setYearFilter,
|
||||
} = useSearchModalStore();
|
||||
const { handleSearch } = useSearchModal(props);
|
||||
const { handleSearch } = useSearchModal();
|
||||
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,29 +5,18 @@ import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox';
|
||||
import { Button, Input, Label } from '@common/components';
|
||||
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
|
||||
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 필터 생성
|
||||
export const swagePrcsFcltCqlFilters = (params: swagePrcsFcltCqlFiltersParams) => {
|
||||
export const swagePrcsFcltCqlFilters = () => {
|
||||
const {
|
||||
ctpvName, sggName,
|
||||
yearFilter,
|
||||
start1, end1,
|
||||
todayYear, todayMonth, todayDate,
|
||||
getDateString
|
||||
} = params;
|
||||
} = useSearchModalStore.getState();
|
||||
|
||||
const cqlFilters: string[] = [];
|
||||
// 가동개시일자 범위
|
||||
@@ -65,14 +54,14 @@ export const swagePrcsFcltCqlFilters = (params: swagePrcsFcltCqlFiltersParams) =
|
||||
return cqlFilters;
|
||||
};
|
||||
|
||||
const SwagePrcsFclt: React.FC<SearchModalProps> = (props) => {
|
||||
const SwagePrcsFclt: React.FC = () => {
|
||||
const {
|
||||
start1, setStart1, end1, setEnd1,
|
||||
ctpvCd, setCtpvCd, setCtpvName,
|
||||
sggCd, setSggCd, setSggName,
|
||||
yearFilter, setYearFilter,
|
||||
} = useSearchModalStore();
|
||||
const { handleSearch } = useSearchModal(props);
|
||||
const { handleSearch } = useSearchModal();
|
||||
|
||||
return (
|
||||
<div className="mb-3">
|
||||
|
||||
@@ -4,21 +4,15 @@ import React from 'react';
|
||||
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
|
||||
import { Button, Input, Label } from '@common/components';
|
||||
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 필터 생성
|
||||
export const swagePrcsZoneCqlFilters = (params: swagePrcsZoneCqlFiltersParams) => {
|
||||
export const swagePrcsZoneCqlFilters = () => {
|
||||
const {
|
||||
ctpvCd, sggCd,
|
||||
var1, var2
|
||||
} = params;
|
||||
} = useSearchModalStore.getState();
|
||||
const cqlFilters: string[] = [];
|
||||
if (ctpvCd !== '' && ctpvCd !== '0') cqlFilters.push(`ctpv_cd='${ctpvCd?.trim()}'`);
|
||||
if (sggCd !== '' && sggCd !== '0') cqlFilters.push(`sgg_cd='${sggCd}'`);
|
||||
@@ -27,13 +21,13 @@ export const swagePrcsZoneCqlFilters = (params: swagePrcsZoneCqlFiltersParams) =
|
||||
return cqlFilters;
|
||||
};
|
||||
|
||||
const swagePrcsZone: React.FC<SearchModalProps> = (props) => {
|
||||
const swagePrcsZone: React.FC = () => {
|
||||
const {
|
||||
var1, setVar1, var2, setVar2,
|
||||
ctpvCd, setCtpvCd,
|
||||
sggCd, setSggCd,
|
||||
} = useSearchModalStore();
|
||||
const { handleSearch } = useSearchModal(props);
|
||||
const { handleSearch } = useSearchModal();
|
||||
|
||||
return (
|
||||
<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";
|
||||
|
||||
import React, { useRef, useEffect, useState } from "react";
|
||||
import { Label } from "@common/components/ui/label";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export const WATER_DEPTH_LAYER_TYPES: string[] = [
|
||||
'tb_ms_flood_dngr_map',
|
||||
|
||||
@@ -9,11 +9,11 @@ function FileUpload() {
|
||||
const [file, setFile] = useState(null);
|
||||
const [text, setText] = useState('');
|
||||
|
||||
const handleFileChange = (e) => {
|
||||
const handleFileChange = (e: any) => {
|
||||
setFile(e.target.files[0]);
|
||||
};
|
||||
|
||||
const handleTextChange = (e) => {
|
||||
const handleTextChange = (e: any) => {
|
||||
setText(e.target.value);
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ function FileUpload() {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const handleFindUser = (e) => {
|
||||
const handleFindUser = () => {
|
||||
const auth = getCookie("Authorization");
|
||||
if (auth) console.log("쿠키 auth : " + auth);
|
||||
fetch(SEWERAGE_URL+"/v1/inftwtAnls/users/65a00f65-8460-49af-98ec-042977e56f4b", {
|
||||
@@ -37,7 +37,7 @@ function FileUpload() {
|
||||
console.log(data);
|
||||
});
|
||||
};
|
||||
const handleFindUserOna = (e) => {
|
||||
const handleFindUserOna = () => {
|
||||
const auth = getCookie("Authorization");
|
||||
if (auth) console.log("쿠키 auth : " + auth);
|
||||
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();
|
||||
|
||||
if (!file) {
|
||||
|
||||
@@ -100,7 +100,7 @@ const Page = () => {
|
||||
alert('파일을 선택해주세요')
|
||||
return false
|
||||
}
|
||||
handleDownload(`/attach-service/api/v1/download/${atflId}`, fileNm)
|
||||
handleDownload(`/attach-service/api/v1/download/${atflId}`, fileNm);
|
||||
}
|
||||
|
||||
// 선택 파일 삭제
|
||||
|
||||
@@ -80,12 +80,15 @@ export const deleteData = async (url: string, data?: object): Promise<any> => {
|
||||
};
|
||||
|
||||
// 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, {
|
||||
method: "GET",
|
||||
});
|
||||
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;
|
||||
}
|
||||
throw new Error('Failed to fetch data');
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_SERVER_API_URL || '';
|
||||
|
||||
export const handleDownload = async (api:string, fileNm:string) => {
|
||||
try {
|
||||
const response = await fetch(api, {
|
||||
method: 'GET',
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('파일 다운로드 실패');
|
||||
}
|
||||
export const handleDownload = async (api: string, fileNm: string) => {
|
||||
try {
|
||||
const response = await fetch(api, {
|
||||
method: 'GET',
|
||||
});
|
||||
if (!response.ok) throw new Error('파일 다운로드 실패');
|
||||
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const blob = await response.blob();
|
||||
fileDownload(blob, fileNm);
|
||||
} catch (error) {
|
||||
console.error('다운로드 중 오류 발생:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 다운로드를 트리거할 가짜 <a> 태그 생성
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileNm.normalize('NFC'); // 원하는 파일명 지정
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
// 메모리 해제
|
||||
link.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error('다운로드 중 오류 발생:', error);
|
||||
}
|
||||
};
|
||||
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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}));
|
||||
@@ -23,6 +23,8 @@ const todayMonth = (today.getMonth() + 1).toString().padStart(2, "0");
|
||||
const todayDate = today.getDate().toString().padStart(2, "0");
|
||||
|
||||
export interface SearchModalState {
|
||||
// 모달 열림 상태
|
||||
isSearchModalOpen: boolean;
|
||||
// 탭 관련
|
||||
activeTab: string;
|
||||
basicType: string;
|
||||
@@ -67,9 +69,9 @@ export interface SearchModalState {
|
||||
todayYear: string;
|
||||
todayMonth: string;
|
||||
todayDate: string;
|
||||
getDateString: (year: string, month: string, day: string, defaultDate: string) => string;
|
||||
|
||||
// setters
|
||||
setIsSearchModalOpen: (open: boolean) => void;
|
||||
setActiveTab: (tab: string) => void;
|
||||
setBasicType: (type: string) => void;
|
||||
setWorkType: (type: string) => void;
|
||||
@@ -98,11 +100,13 @@ export interface SearchModalState {
|
||||
setVar1: (val: string) => void;
|
||||
setVar2: (val: 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) => ({
|
||||
isSearchModalOpen: false,
|
||||
activeTab: "basic",
|
||||
basicType: "tb_ms_swage_ppl",
|
||||
workType: "['200', '201', '204']",
|
||||
@@ -135,7 +139,7 @@ const useSearchModalStore = create<SearchModalState>((set) => ({
|
||||
todayYear,
|
||||
todayMonth,
|
||||
todayDate,
|
||||
getDateString,
|
||||
setIsSearchModalOpen: (isSearchModalOpen) => set({ isSearchModalOpen }),
|
||||
setActiveTab: (tab) => set({ activeTab: tab }),
|
||||
setBasicType: (type) => set({ basicType: type }),
|
||||
setWorkType: (type) => set({ workType: type }),
|
||||
@@ -164,7 +168,8 @@ const useSearchModalStore = create<SearchModalState>((set) => ({
|
||||
setVar1: (val) => set({ var1: val }),
|
||||
setVar2: (val) => set({ var2: val }),
|
||||
setYearFilter: (filter) => set({ yearFilter: filter }),
|
||||
|
||||
|
||||
getDateString: getDateString,
|
||||
// 초기화 액션
|
||||
resetFilters: () =>
|
||||
set({
|
||||
|
||||
@@ -31,8 +31,10 @@
|
||||
"@common/business_components/*": ["../../../packages/common/business_components/src/*"],
|
||||
"@common/network": ["../../../packages/common/network/"],
|
||||
"@common/config": ["../../../packages/common/config/dist/"],
|
||||
"@utils": ["../app/components/lib/utils"],
|
||||
"@/components/*": ["../app/components/*"]
|
||||
"@/app/*": ["../app/*"],
|
||||
"@/components/*": ["../app/components/*"],
|
||||
"@/utils": ["../app/components/lib/utils"],
|
||||
"@/store/*": ["../app/store/*"],
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
|
||||
@@ -118,15 +118,21 @@ export default function LineBarchart({
|
||||
if (!isFinite(maxValue) || maxValue <= 0) {
|
||||
return { max: 0, ticks: [0] };
|
||||
}
|
||||
|
||||
|
||||
const exponent = Math.floor(Math.log10(maxValue));
|
||||
const base = Math.pow(10, exponent);
|
||||
const fraction = maxValue / base;
|
||||
|
||||
let niceFraction: number;
|
||||
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 <= 6) niceFraction = 6;
|
||||
else if (fraction <= 7) niceFraction = 7;
|
||||
else if (fraction <= 8) niceFraction = 8;
|
||||
else if (fraction <= 9) niceFraction = 9;
|
||||
else niceFraction = 10;
|
||||
|
||||
const niceMax = niceFraction * base; // round up to 1/2/5 * 10^k
|
||||
@@ -164,7 +170,7 @@ export default function LineBarchart({
|
||||
return (
|
||||
<text
|
||||
x={x - padding} // 맨 좌측 바깥
|
||||
y={y + height / 2} // 수직 중앙
|
||||
y={y + height / 2 - 12} // 수직 중앙
|
||||
textAnchor="middle"
|
||||
transform={`rotate(-90, ${x - padding}, ${y + height / 2})`}
|
||||
className="text-[12px] font-bold"
|
||||
@@ -195,12 +201,12 @@ export default function LineBarchart({
|
||||
</div>
|
||||
) : null}
|
||||
<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
|
||||
data={chartData}
|
||||
height={graphSize * 7.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" />
|
||||
{setXAxisYearTick?
|
||||
@@ -210,7 +216,7 @@ export default function LineBarchart({
|
||||
{/* 왼쪽 Y축: lineVal */}
|
||||
{yFieldText && <YAxis
|
||||
yAxisId="left"
|
||||
className="text-[12px] font-bold"
|
||||
className="text-[12px] font-bold"
|
||||
tickFormatter={(value) =>
|
||||
typeof value === "number" ? value.toLocaleString() : value
|
||||
}
|
||||
@@ -218,7 +224,6 @@ export default function LineBarchart({
|
||||
ticks={leftScale.ticks}
|
||||
orientation="left"
|
||||
label={(props) => <CustomAxisText {...props} fieldText={yFieldText} position="left" />}
|
||||
|
||||
/>}
|
||||
{/* 오른쪽 Y축: barVal */}
|
||||
{yFieldText2 && <YAxis
|
||||
|
||||
@@ -316,11 +316,7 @@ function Gis({
|
||||
)}
|
||||
{/* SearchView가 존재할 때만 렌더링 */}
|
||||
{SearchView && (
|
||||
<SearchView
|
||||
open={isSearchModalOpen}
|
||||
onOpenChange={setIsSearchModalOpen}
|
||||
onFeatureSelect={handleFeatureSelect}
|
||||
/>
|
||||
<SearchView />
|
||||
)}
|
||||
{/* 시도/시군구 조회 영역 */}
|
||||
{SearchBar && (
|
||||
|
||||
Reference in New Issue
Block a user