335 lines
10 KiB
TypeScript
335 lines
10 KiB
TypeScript
/**
|
|
* ➡️ 경로 : 하수관망도 메인 페이지 (오른쪽 돋보기 모양 클릭) - 검색 모달 창
|
|
*
|
|
* 📄 Search Modal
|
|
* Next.js App Router (app/) 기반 검색 모달 컴포넌트
|
|
*
|
|
* - 탭 기반 검색 인터페이스 (기본정보, 유지관리 정보, 보고서)
|
|
* - 테이블 데이터 표시 및 페이지네이션 기능
|
|
* - 시설물 조회 결과 및 유지관리 사업 현황 표시
|
|
* - 엑셀 다운로드 및 지도에서 보기 기능
|
|
* - Zustand를 통한 검색 모달 상태 관리
|
|
* - 로딩 오버레이 및 선택 초기화 지원
|
|
*/
|
|
"use client";
|
|
import LoadingOverlay from "@/components/ui/LoadingBar";
|
|
import { UITab } from "@common/business_components/ui/tab";
|
|
import { StandardPopupLayout } from "@common/business_components";
|
|
import { Button } from "@common/components/ui";
|
|
import React, { useEffect, useMemo, useRef } from "react";
|
|
import useSearchModalStore from "@/store/useSearchModalStore";
|
|
import { useAlertStore } from "@/shared/lib";
|
|
import { windowPopupDefinitions } from "@/features/windowPopup/model/popupDefinitions";
|
|
import {
|
|
ExclnSewagePumpCol2,
|
|
FldngWtlvMapCol2,
|
|
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";
|
|
import type { UiHeadProps } from "@common/business_components/ui/table";
|
|
import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
|
|
|
type SearchModalProps = {
|
|
renderMode?: "launcher" | "popup";
|
|
autoSearchReady?: boolean;
|
|
};
|
|
|
|
const SearchModal: React.FC<SearchModalProps> = ({
|
|
renderMode = "launcher",
|
|
autoSearchReady = false,
|
|
}) => {
|
|
const alert = useAlertStore((state) => state.setMessage);
|
|
const {
|
|
showTable,
|
|
isSearchModalOpen,
|
|
setIsSearchModalOpen,
|
|
activeTab,
|
|
basicType,
|
|
workType,
|
|
tableData,
|
|
detailTableData,
|
|
tableCount,
|
|
detailTableCount,
|
|
curPage,
|
|
detailCurPage,
|
|
detailBizId,
|
|
isLoading,
|
|
isDetailLoading,
|
|
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: UiHeadProps[] = useMemo(() => {
|
|
if (activeTab === "basic") {
|
|
const headlist =
|
|
basicType === "tb_ms_swage_ppl"
|
|
? MsSwagePplCol2
|
|
: basicType === "tb_il_swage_prcs_fclt"
|
|
? SwtrmtFcltCol2
|
|
: basicType === "tb_ms_swage_prcs_zone"
|
|
? SwtrmtPrcsZoneCol2
|
|
: basicType === "tb_ms_fldng_wtlv_map"
|
|
? FldngWtlvMapCol2
|
|
: basicType === "tb_ms_swage_mhl"
|
|
? MsSwageMhlCol2
|
|
: basicType === "tb_ms_swage_pmstn"
|
|
? ExclnSewagePumpCol2
|
|
: basicType === "tb_ms_stofcbr"
|
|
? stofcbrCol2
|
|
: [];
|
|
return headlist.map((head) => ({
|
|
...head,
|
|
title: head.id === "rowNumber" ? "순번" : head.title,
|
|
}));
|
|
}
|
|
if (activeTab === "operation") {
|
|
return OPERATION_TABLE_HEADLIST.map((head) => ({
|
|
...head,
|
|
title: head.id === "rowNumber" ? "순번" : head.title,
|
|
}));
|
|
}
|
|
|
|
return [];
|
|
}, [activeTab, basicType, workType, OPERATION_TABLE_HEADLIST]);
|
|
|
|
const dynamicHeadlist2: UiHeadProps[] = useMemo(() => {
|
|
if (activeTab === "operation") {
|
|
const headlist =
|
|
workType === WORK_TYPES[0]?.val
|
|
? MAINTENANACE_TABLE_HEADLIST
|
|
: workType === WORK_TYPES[1]?.val
|
|
? CLEANING_TABLE_HEADLIST
|
|
: workType === WORK_TYPES[2]?.val
|
|
? INSPECTION_TABLE_HEADLIST
|
|
: [];
|
|
return headlist.map((head) => ({
|
|
...head,
|
|
title: head.id === "rowNumber" ? "순번" : head.title,
|
|
}));
|
|
}
|
|
|
|
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);
|
|
}, [alert, 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}
|
|
variant="underline"
|
|
className="shrink-0"
|
|
tabList={TABS.map((tab: any) => ({
|
|
value: tab.val,
|
|
label: tab.text,
|
|
}))}
|
|
onValueChange={(value: string) => setActiveTab(value)}
|
|
/>
|
|
<LoadingOverlay isLoading={isLoading || isDetailLoading} />
|
|
{activeTab === "basic" && <BasicTab />}
|
|
{activeTab === "operation" && <OperationTab />}
|
|
{activeTab === "report" && <ReportTab open={activeTab === "report"} />}
|
|
|
|
{activeTab === "basic" && (
|
|
<div className="w-full">
|
|
<MapPopupDataTable
|
|
rows={tableData as Array<Record<string, unknown>>}
|
|
title={(() => {
|
|
const label = BASIC_TYPES.find(
|
|
(type) => type.val === basicType,
|
|
)?.text;
|
|
return <>{label} 목록</>;
|
|
})()}
|
|
headlist={dynamicHeadlist}
|
|
pageSize={10}
|
|
total={tableCount}
|
|
onCellClick={(_row, index, headId) =>
|
|
void handleBasicCellClick(index, headId)
|
|
}
|
|
onPageChange={setCurPage}
|
|
page={curPage}
|
|
scrollMode="horizontal"
|
|
minTableWidth="160rem"
|
|
actions={
|
|
<div className="flex gap-2">
|
|
<Button onClick={basicExcelDownload}>엑셀다운로드</Button>
|
|
<Button onClick={viewMultipleFeature}>지도에서 보기</Button>
|
|
</div>
|
|
}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === "operation" && (
|
|
<>
|
|
<div className="w-full">
|
|
<MapPopupDataTable
|
|
rows={tableData as Array<Record<string, unknown>>}
|
|
title={(() => {
|
|
const label = WORK_TYPES.find(
|
|
(type) => type.val === workType,
|
|
)?.text;
|
|
return <>{label} 사업 목록</>;
|
|
})()}
|
|
headlist={dynamicHeadlist}
|
|
pageSize={OPERATION_TABLE_SIZE}
|
|
total={tableCount}
|
|
onCellClick={(_row, index, headId) =>
|
|
void handleOperationCellClick(index, headId)
|
|
}
|
|
onPageChange={setCurPage}
|
|
page={curPage}
|
|
scrollMode="horizontal"
|
|
minTableWidth="140rem"
|
|
actions={
|
|
<Button onClick={operationExcelDownload}>엑셀다운로드</Button>
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
<div className="w-full">
|
|
<MapPopupDataTable
|
|
rows={detailTableData as Array<Record<string, unknown>>}
|
|
title={(() => {
|
|
const label = WORK_TYPES.find(
|
|
(type) => type.val === workType,
|
|
)?.text;
|
|
return <>{label} 결과 목록</>;
|
|
})()}
|
|
headlist={dynamicHeadlist2}
|
|
pageSize={OPERATION_TABLE_SIZE}
|
|
total={detailTableCount}
|
|
onCellClick={(_row, index, headId) =>
|
|
void handleOperationResultCellClick(index, headId)
|
|
}
|
|
onPageChange={setDetailCurPage}
|
|
page={detailCurPage}
|
|
scrollMode="horizontal"
|
|
minTableWidth="160rem"
|
|
actions={
|
|
<div className="flex gap-2">
|
|
<Button onClick={operationResultExcelDownload}>
|
|
엑셀다운로드
|
|
</Button>
|
|
<Button onClick={viewMultipleFeature}>지도에서 보기</Button>
|
|
</div>
|
|
}
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
</>
|
|
);
|
|
|
|
if (renderMode === "launcher") {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<StandardPopupLayout
|
|
title={windowPopupDefinitions.mapSearch.title}
|
|
bodyClassName="overflow-x-hidden"
|
|
>
|
|
<div className="flex min-w-0 flex-col gap-3">{renderBody()}</div>
|
|
</StandardPopupLayout>
|
|
);
|
|
};
|
|
|
|
export default React.memo(SearchModal);
|