262 lines
9.8 KiB
TypeScript
262 lines
9.8 KiB
TypeScript
/**
|
|
* ➡️ 경로 : 하수관망도 메인 페이지 (오른쪽 돋보기 모양 클릭) - 검색 모달 창
|
|
*
|
|
* 📄 Search Modal
|
|
* Next.js App Router (app/) 기반 검색 모달 컴포넌트
|
|
*
|
|
* - 탭 기반 검색 인터페이스 (기본정보, 유지관리 정보, 보고서)
|
|
* - 테이블 데이터 표시 및 페이지네이션 기능
|
|
* - 시설물 조회 결과 및 유지관리 사업 현황 표시
|
|
* - 엑셀 다운로드 및 지도에서 보기 기능
|
|
* - Zustand를 통한 검색 모달 상태 관리
|
|
* - 로딩 오버레이 및 선택 초기화 지원
|
|
*/
|
|
'use client';
|
|
import LoadingOverlay from '@/components/ui/LoadingBar';
|
|
import { UITab, UiTable } from '@common/business_components/ui';
|
|
import { Button } from '@common/components/ui';
|
|
import React, { useEffect, useMemo, useRef } 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';
|
|
import { openWindowPopup } from '@/features/windowPopup/lib/openWindowPopup';
|
|
|
|
type SearchModalProps = {
|
|
renderMode?: 'launcher' | 'popup';
|
|
autoSearchReady?: boolean;
|
|
};
|
|
|
|
const SearchModal: React.FC<SearchModalProps> = ({ renderMode = 'launcher', autoSearchReady = false }) => {
|
|
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 hasAutoSearchedRef = useRef(false);
|
|
const hasInitializedTabAutoSearchRef = useRef(false);
|
|
|
|
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, OPERATION_TABLE_HEADLIST]);
|
|
|
|
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, WORK_TYPES, MAINTENANACE_TABLE_HEADLIST, CLEANING_TABLE_HEADLIST, INSPECTION_TABLE_HEADLIST]);
|
|
|
|
useEffect(() => {
|
|
if (renderMode !== 'launcher' || !isSearchModalOpen) return;
|
|
|
|
const popupWindow = openWindowPopup('mapSearch', {
|
|
sourcePath: window.location.pathname,
|
|
});
|
|
|
|
if (!popupWindow) {
|
|
alert('팝업이 차단되었습니다. 브라우저 팝업 허용 설정을 확인해주세요.');
|
|
}
|
|
|
|
setIsSearchModalOpen(false);
|
|
}, [isSearchModalOpen, renderMode, setIsSearchModalOpen]);
|
|
|
|
useEffect(() => {
|
|
resetFilters();
|
|
}, [activeTab, basicType, workType, resetFilters]);
|
|
|
|
useEffect(() => {
|
|
if (renderMode !== 'popup' || !autoSearchReady || hasAutoSearchedRef.current) return;
|
|
|
|
hasAutoSearchedRef.current = true;
|
|
hasInitializedTabAutoSearchRef.current = true;
|
|
void fetchFilteredData();
|
|
}, [autoSearchReady, fetchFilteredData, renderMode]);
|
|
|
|
useEffect(() => {
|
|
if (renderMode !== 'popup' || !autoSearchReady) return;
|
|
if (!hasInitializedTabAutoSearchRef.current) return;
|
|
if (activeTab === 'report') return;
|
|
|
|
setCurPage(1);
|
|
setIsResetPage(true);
|
|
setIsResetDetailPage(true);
|
|
void fetchFilteredData();
|
|
}, [activeTab, basicType, workType]);
|
|
|
|
useEffect(() => {
|
|
setIsResetPage(false);
|
|
setIsResetDetailPage(true);
|
|
setDetailBizId(0);
|
|
if (showTable) {
|
|
void fetchFilteredData();
|
|
}
|
|
}, [curPage]);
|
|
|
|
useEffect(() => {
|
|
setIsResetDetailSelection(false);
|
|
if (showTable) {
|
|
void 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={activeTab === 'report'} />)}
|
|
|
|
{activeTab === 'basic' && (
|
|
<div className="mt-4 h-[79dvh] w-full overflow-x-hidden overflow-y-auto rounded-lg border">
|
|
<LoadingOverlay isLoading={isLoading} />
|
|
<UiTable
|
|
className='overflow-x-auto [&_table]:min-w-full [&_table]:w-max'
|
|
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="mt-4 h-[39dvh] w-full overflow-x-hidden overflow-y-auto rounded-lg border">
|
|
<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 className="h-[39dvh] w-full overflow-x-hidden overflow-y-auto rounded-lg border">
|
|
<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>
|
|
</>
|
|
)}
|
|
</>
|
|
);
|
|
|
|
if (renderMode === 'launcher') {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className='h-dvh overflow-x-auto overflow-y-hidden bg-container p-4 text-foreground'>
|
|
<h1 className="mb-3 text-2xl font-semibold text-foreground">관망도 검색</h1>
|
|
<div className="h-[calc(100dvh-5.6rem)] w-full overflow-y-hidden rounded-lg border border-border bg-background p-4">
|
|
{renderBody()}
|
|
</div>
|
|
<Toaster position="top-center" />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default React.memo(SearchModal);
|
|
|