239 lines
9.0 KiB
TypeScript
239 lines
9.0 KiB
TypeScript
/**
|
|
* ➡️ 경로 : 하수관망도 메인 페이지 (오른쪽 돋보기 모양 클릭) - 검색 모달 창
|
|
*
|
|
* 📄 Search Modal
|
|
* Next.js App Router (app/) 기반 검색 모달 컴포넌트
|
|
*
|
|
* - 탭 기반 검색 인터페이스 (기본정보, 유지관리 정보, 보고서)
|
|
* - 테이블 데이터 표시 및 페이지네이션 기능
|
|
* - 시설물 조회 결과 및 유지관리 사업 현황 표시
|
|
* - 엑셀 다운로드 및 지도에서 보기 기능
|
|
* - Zustand를 통한 검색 모달 상태 관리
|
|
* - 로딩 오버레이 및 선택 초기화 지원
|
|
*/
|
|
import LoadingOverlay from '@/components/ui/LoadingBar';
|
|
import { UiModal, UITab, UiTable } from '@common/business_components/ui';
|
|
import { Button } from '@common/components/ui';
|
|
import React, { useEffect, useMemo } from 'react';
|
|
import { Toaster } from "react-hot-toast";
|
|
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 = () => {
|
|
const {
|
|
showTable,
|
|
isSearchModalOpen, setIsSearchModalOpen,
|
|
activeTab, basicType, workType,
|
|
tableData, detailTableData,
|
|
tableCount, detailTableCount,
|
|
curPage, detailCurPage,
|
|
detailBizId,
|
|
isLoading, isDetailLoading,
|
|
isResetPage, isResetDetailPage,
|
|
isResetSelection, isResetDetailSelection,
|
|
setActiveTab,
|
|
setCurPage, setDetailCurPage,
|
|
setIsResetPage, setIsResetDetailPage,
|
|
setDetailBizId,
|
|
setIsResetDetailSelection,
|
|
resetFilters
|
|
} = useSearchModalStore();
|
|
const { TABS, viewMultipleFeature, fetchFilteredData } = 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,
|
|
handleOperationPageNation
|
|
} = useOperationTab();
|
|
|
|
|
|
const dynamicHeadlist: { id: string, title: string }[] = useMemo(() => {
|
|
if (activeTab === 'basic') { // 기본정보
|
|
if (basicType === 'tb_ms_swage_ppl') return MsSwagePplCol2;// 하수관로
|
|
if (basicType === 'tb_il_swage_prcs_fclt') return SwtrmtFcltCol2;// 하수처리시설
|
|
if (basicType === 'tb_ms_swage_prcs_zone') return SwtrmtPrcsZoneCol2; // 하수처리구역
|
|
if (basicType === 'tb_ms_swage_mhl') return MsSwageMhlCol2; // 하수맨홀
|
|
if (basicType === 'tb_ms_swage_pmstn') return ExclnSewagePumpCol2; // 하수펌프장
|
|
if (basicType === 'tb_ms_stofcbr') return stofcbrCol2; // 우수토실
|
|
|
|
return [];
|
|
}
|
|
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 MAINTENANACE_TABLE_HEADLIST; // 보수 및 갱신
|
|
if (workType === WORK_TYPES[1]?.val) return CLEANING_TABLE_HEADLIST; // 점검 및 진단
|
|
if (workType === WORK_TYPES[2]?.val) return INSPECTION_TABLE_HEADLIST; // 청소 및 준설
|
|
}
|
|
|
|
return [];
|
|
}, [activeTab, workType]);
|
|
|
|
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]);
|
|
|
|
|
|
const renderBody = () => (
|
|
<>
|
|
{/* 탭 영역 */}
|
|
<UITab
|
|
defaultValue={activeTab}
|
|
tabList={TABS.map((tab: any) => ({
|
|
value: tab.val,
|
|
label: tab.text
|
|
}))}
|
|
onValueChange={(value: string) => setActiveTab(value)}
|
|
/>
|
|
{activeTab === 'basic' && (<BasicTab />)} {/* 기본정보 탭 */}
|
|
{activeTab === 'operation' && (<OperationTab />)} {/* 유지관리 정보 탭 */}
|
|
{activeTab === 'report' && (<ReportTab open={isSearchModalOpen} />)} {/* 하수도정비기본계획보고서 탭 */}
|
|
|
|
|
|
{/* 📋 결과 리스트 */}
|
|
{/* 기본정보 탭 */}
|
|
{activeTab === 'basic' && (
|
|
<div className="h-[79dvh] w-full rounded-lg border mt-4 overflow-x-scroll">
|
|
<LoadingOverlay isLoading={isLoading} />
|
|
<UiTable className='w-[120%]'
|
|
headName={(() => {
|
|
const label = BASIC_TYPES.find((type) => type.val === basicType)?.text
|
|
return (<>{label} 조회 결과</>);
|
|
})()
|
|
}
|
|
tableData={tableData}
|
|
headlist={dynamicHeadlist}
|
|
pageSize={10}
|
|
total={tableCount}
|
|
cellClick={handleBasicCellClick}
|
|
onPageChange={setCurPage}
|
|
resetPageNo={isResetPage}
|
|
resetSelection={isResetSelection}
|
|
curPage={curPage}
|
|
headButton={
|
|
<div className='flex'>
|
|
<Button className="px-4 py-2 !mr-[1.2rem] text-white rounded-sm text-lg font-medium primary" size="sm" onClick={viewMultipleFeature}>지도에서 보기</Button>
|
|
<Button className="px-4 py-2 !mr-[1.2rem] text-white rounded-sm text-lg font-medium green" size="sm" onClick={basicExcelDownload}>파일 다운로드</Button>
|
|
</div>
|
|
}
|
|
/>
|
|
</div>
|
|
)}
|
|
{/* 유지관리 정보 탭 */}
|
|
{activeTab === 'operation' && (
|
|
<>
|
|
<div className="h-[39dvh] w-full overflow-x-hidden overflow-y-auto rounded-lg border mt-4">
|
|
{/* <div className="flex-1 px-2 pb-2"> */}
|
|
<LoadingOverlay isLoading={isLoading} />
|
|
<UiTable
|
|
headName={
|
|
(() => {
|
|
const label = WORK_TYPES.find((type) => type.val === workType)?.text
|
|
return (<>{label} 사업 현황</>);
|
|
})()
|
|
}
|
|
tableData={tableData}
|
|
headlist={dynamicHeadlist}
|
|
pageSize={OPERATION_TABLE_SIZE}
|
|
total={tableCount}
|
|
cellClick={handleOperationCellClick}
|
|
onPageChange={setCurPage}
|
|
resetPageNo={isResetPage}
|
|
resetSelection={isResetSelection}
|
|
curPage={curPage}
|
|
headButton={
|
|
<Button className="px-4 py-2 !mr-[1.2rem] text-white rounded-sm text-sm font-medium green" size="sm" onClick={operationExcelDownload}>파일 다운로드</Button>
|
|
}
|
|
/>
|
|
{/* </div> */}
|
|
</div>
|
|
|
|
<div className="h-[39dvh] w-full overflow-x-hidden overflow-y-auto rounded-lg border">
|
|
{/* 테이블2 영역 */}
|
|
{/* <div className="flex-1 px-2 pb-2"> */}
|
|
<LoadingOverlay isLoading={isDetailLoading} />
|
|
<UiTable
|
|
headName={
|
|
(() => {
|
|
const label = WORK_TYPES.find((type) => type.val === workType)?.text
|
|
return (<>{label} 결과표</>);
|
|
})()
|
|
}
|
|
tableData={detailTableData}
|
|
headlist={dynamicHeadlist2}
|
|
pageSize={OPERATION_TABLE_SIZE}
|
|
total={detailTableCount}
|
|
cellClick={handleOperationResultCellClick}
|
|
onPageChange={setDetailCurPage}
|
|
resetPageNo={isResetDetailPage}
|
|
resetSelection={isResetDetailSelection}
|
|
curPage={detailCurPage}
|
|
headButton={
|
|
<div className='flex'>
|
|
<Button className="px-4 py-2 !mr-[1.2rem] text-white rounded-sm text-lg font-medium primary" size="sm" onClick={viewMultipleFeature}>지도에서 보기</Button>
|
|
<Button className="px-4 py-2 !mr-[1.2rem] text-white rounded-sm text-lg font-medium green" size="sm" onClick={operationResultExcelDownload}>파일 다운로드</Button>
|
|
</div>
|
|
}
|
|
/>
|
|
{/* </div> */}
|
|
</div>
|
|
</>
|
|
)}
|
|
</>
|
|
);
|
|
|
|
return (
|
|
<div className='overflow-x-auto overflow-y-hidden'>
|
|
<UiModal
|
|
open={isSearchModalOpen}
|
|
onOpenChange={setIsSearchModalOpen}
|
|
bodyClass={`
|
|
h-[100dvh]
|
|
w-[90dvw]
|
|
max-w-[1220px]
|
|
max-h-[970px]
|
|
overflow-y-hidden
|
|
z-[101]
|
|
`}
|
|
body={renderBody()}
|
|
useDirectRender={true}
|
|
/>
|
|
<Toaster position="top-center"></Toaster>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default React.memo(SearchModal);
|