From ebafff03ecaffb5e61756f2729ff6079f15e964c Mon Sep 17 00:00:00 2001 From: swcho Date: Tue, 2 Sep 2025 16:02:22 +0900 Subject: [PATCH 1/4] merge --- .../src/ui/map/searchBar/procSearchBar.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/common/business_components/src/ui/map/searchBar/procSearchBar.tsx b/packages/common/business_components/src/ui/map/searchBar/procSearchBar.tsx index 016774fdd..79e195925 100644 --- a/packages/common/business_components/src/ui/map/searchBar/procSearchBar.tsx +++ b/packages/common/business_components/src/ui/map/searchBar/procSearchBar.tsx @@ -88,11 +88,7 @@ export const ProcSearchBar = ({ gisMaps, facilityTypes }: SearchBarProps) => { text: f.properties.sgg_korn_nm, val: f.properties.sgg_cd, })) -<<<<<<< HEAD - .sort((a, b) => a.text.localeCompare(b.text, 'ko')); -======= .sort((a: any, b: any) => a.text.localeCompare(b.text, 'ko')); ->>>>>>> iipbs/common/main setSigunData(list); }; @@ -130,11 +126,7 @@ export const ProcSearchBar = ({ gisMaps, facilityTypes }: SearchBarProps) => { text: f.properties.swtrmt_fclt_nm ?? "(이름없음)", val: f.id, })) -<<<<<<< HEAD - .sort((a, b) => a.text.localeCompare(b.text, 'ko')); -======= .sort((a: any, b: any) => a.text.localeCompare(b.text, 'ko')); ->>>>>>> iipbs/common/main } else if ( type === "tb_wr_bdst_ruse_fclt" // 재이용시설 ) { From 0506464a979821a796f135bd3e5cb9d045370af5 Mon Sep 17 00:00:00 2001 From: swcho Date: Tue, 2 Sep 2025 17:49:22 +0900 Subject: [PATCH 2/4] =?UTF-8?q?refactor:=20searchModal=20UI,=20=EB=B9=84?= =?UTF-8?q?=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../map/searchModal/searchModal.tsx | 1013 +---------------- .../map/searchModal/useSearchModal.tsx | 1000 ++++++++++++++++ 2 files changed, 1035 insertions(+), 978 deletions(-) create mode 100644 apps/sn/app/(map-layout)/map/searchModal/useSearchModal.tsx diff --git a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx index fdb52a1c2..b9eb29a10 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx @@ -1,95 +1,49 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import LoadingOverlay from '@/components/ui/LoadingBar'; -import { UiModal, UITab, UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui'; +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_CODES, MS_SWAGE_PPL_USE_CODES, MS_SWAGE_MHL_CODES, MS_SWAGE_MHL_USE_CODES, - MsSwagePplExcelCol, SwtrmtFcltExcelCol, SwtrmtPrcsZoneExcelCol, MsSwageMhlExcelCol, ExclnSewagePumpExcelCol, stofcbrExcelCol + 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 useStore from '../../../store/useStore'; -import GisApp from "@common/business_components/ui/map/lib/gis.core"; -import { confirmToast } from '@/components/ui/toast' -import { toast, Toaster } from "react-hot-toast"; +import { Toaster } from "react-hot-toast"; +import { useSearchModal, OPERATION_TABLE_SIZE, type SearchModalProps } from './useSearchModal'; -const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`; - -const TABS = [ - { val: "basic", text: "기본정보" }, - { val: "operation", text: "운영정보" }, - { val: "report", text: "하수도정비기본계획보고서" }, -]; - -const OPERATION_TABLE_SIZE = 5; - -// 날짜 조합 함수 -const getDateString = ( - year: string, - month: string, - day: string, - defaultDate: string, -) => { - if (!year && !month && !day) return defaultDate; - const y = year || defaultDate.slice(0, 4); - const m = month || defaultDate.slice(4, 6); - const d = day || defaultDate.slice(6, 8); - return `${y}${m}${d}`; -}; - -interface SearchModalProps { - open: boolean; - onOpenChange: (open: boolean) => void; - onFeatureSelect: (row: any) => void; -} - -const SearchModal: React.FC = ({ - open, - onOpenChange, - onFeatureSelect, -}) => { - const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? "basic"); - const [basicType, setBasicType] = useState(BASIC_TYPES[0]?.val ?? ""); - const [workType, setWorkType] = useState(WORK_TYPES[0]?.val ?? ""); - const [showTable, setShowTable] = useState(false); - const [tableData, setTableData] = useState([]); - const [detailTableData, setDetailTableData] = useState([]); - const [tableCount, setTableCount] = useState(0); - const [detailTableCount, setDetailTableCount] = useState(0); - const [curPage, setCurPage] = useState(1); - const [detailCurPage, setDetailCurPage] = useState(1); - const [isLoading, setIsLoading] = useState(false); - const [isDetailLoading, setIsDetailLoading] = useState(false); - const [isResetPage, setIsResetPage] = useState(false); - const [isResetDetailPage, setIsResetDetailPage] = useState(false); - const [isResetSelection, setIsResetSelection] = useState(false); - const [isResetDetailSelection, setIsResetDetailSelection] = useState(false); - const [detailBizId, setDetailBizId] = useState(0); - const [ctpvCd, setCtpvCd] = useState("0"); - const [ctpvName, setCtpvName] = useState(''); - const [sggCd, setSggCd] = useState("0"); - const [sggName, setSggName] = useState(''); - const [start1, setStart1] = useState(""); - const [start2, setStart2] = useState(""); - const [end1, setEnd1] = useState(""); - const [end2, setEnd2] = useState(""); - const [var1, setVar1] = useState(""); - const [var2, setVar2] = useState(""); - const [yearFilter, setYearFilter] = useState({ start: "", end: "" }); - - const { ctpv, sgg, BIZ_TY_CD } = useStore(); - - - // 오늘 날짜 구하기 - const today = new Date(); - const todayYear = today.getFullYear().toString(); - const todayMonth = (today.getMonth() + 1).toString().padStart(2, "0"); - const todayDate = today.getDate().toString().padStart(2, "0"); +const SearchModal: React.FC = (props) => { + const { + open, onOpenChange, + TABS, + activeTab, setActiveTab, + basicType, setBasicType, + workType, setWorkType, + tableData, detailTableData, + tableCount, detailTableCount, + curPage, setCurPage, + detailCurPage, setDetailCurPage, + isLoading, isDetailLoading, + isResetPage, isResetDetailPage, + isResetSelection, isResetDetailSelection, + ctpvCd, setCtpvCd, setCtpvName, + sggCd, setSggCd, setSggName, + start1, setStart1, start2, setStart2, + end1, setEnd1, end2, setEnd2, + var1, setVar1, var2, setVar2, + yearFilter, setYearFilter, + handleSearch, + handleBasicCellClick, + handleOperationCellClick, + handleOperationResultCellClick, + basicExcelDownload, + operationExcelDownload, + operationResultExcelDownload, + viewMultipleFeature, + } = useSearchModal(props); const dynamicHeadlist: { id: string, title: string }[] = useMemo(() => { if (activeTab === 'basic') { @@ -117,910 +71,13 @@ const SearchModal: React.FC = ({ return []; }, [activeTab, workType]); - useEffect(() => { - // filterParams 초기화 - setStart1(""); - setStart2(""); - setEnd1(""); - setEnd2(""); - setVar1(""); - setVar2(""); - setCtpvCd("0"); - setSggCd("0"); - setYearFilter({ - start: "", - end: "", - }); - setShowTable(false); - setTableData([]); - setDetailTableData([]); - setTableCount(0); - setDetailTableCount(0); - setCurPage(1); // 페이지를 1로 초기화 - setDetailCurPage(1); // 페이지를 1로 초기화 - setIsResetPage(true); // 페이지를 초기화 - setIsResetDetailPage(true); // 디테일 페이지를 초기화 - setIsResetSelection(true); // 선택 초기화 - setIsResetDetailSelection(true); // 선택 초기화 - setDetailBizId(0); - // queryParams는 fetchFilteredData 내에서 매번 새로 생성되므로 별도 상태 초기화는 필요 없음 - }, [activeTab, basicType, workType]); - - // 페이지 변경 시마다 데이터 fetch - useEffect(() => { - // showTable이 true일 때만 fetch (최초 렌더 방지) - setIsResetPage(false); - setIsResetDetailPage(true); - setDetailBizId(0); - if (showTable) fetchFilteredData(); - }, [curPage]); - - // 페이지 변경 시마다 데이터 fetch - useEffect(() => { - setIsResetDetailSelection(false); - // showTable이 true일 때만 fetch (최초 렌더 방지) - 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 = zoneCqlFilters(); // 하수처리구역 - else if (basicType === 'tb_il_swage_prcs_fclt') cqlFilters = facilityCqlFilters(); // 하수처리시설 - else if (basicType === 'tb_ms_swage_ppl') cqlFilters = pplCqlFilters(); // 하수관로 - else if (basicType === 'tb_ms_swage_mhl') cqlFilters = mhCqlFilters(); // 하수맨홀 - else if (basicType === 'tb_ms_swage_pmstn') cqlFilters = psmtnCqlFilters(); // 하수펌프장 - else if (basicType === 'tb_ms_stofcbr') cqlFilters = otflCqlFilters(); // 우수토실 - - 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 || ctpvCd; - const sggNm = sgg.find(code => code.val.trim() === sggCd)?.text || '-'; - 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, - sggNm, - swtrmt_zone_nm: item.swtrmtZoneNm || item.swtrmt_zone_nm || '-', - }; - }); - - setTableData(dataWithRowNumber); - setTableCount(res.data.count || []); - - } else if (activeTab === 'operation') { - const RequestDTO: Record = { - 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); - } - }; - - // 하수관로용 CQL 필터 생성 - const pplCqlFilters = () => { - const cqlFilters: string[] = []; - // 추후 작성 예정 - if (var1) cqlFilters.push(`usg_cd='${var1}'`); - if (var2) cqlFilters.push(`mqlt_cd='${var2}'`); - if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); - else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); - // 설치년도 범위 - const startDate = getDateString( - yearFilter.start, - '', - '', - "19000101", - ); - const endDate = getDateString( - yearFilter.end, - '', - '', - `${todayYear}${todayMonth}${todayDate}`, - ); - cqlFilters.push( - `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, - ); - // 관경(mm) 범위 - if (start1 && end1) cqlFilters.push(`pdmt_vl BETWEEN ${start1} AND ${end1}`); - else if (start1) cqlFilters.push(`pdmt_vl >= ${start1}`); - else if (end1) cqlFilters.push(`pdmt_vl <= ${end1}`); - // 연장(m) 범위 - if (start2 && end2) cqlFilters.push(`prlg_vl BETWEEN ${start2} AND ${end2}`); - else if (start2) cqlFilters.push(`prlg_vl >= ${start2}`); - else if (end2) cqlFilters.push(`prlg_vl <= ${end2}`); - - return cqlFilters; - }; - // 하수처리시설용 CQL 필터 생성 - const facilityCqlFilters = () => { - const cqlFilters: string[] = []; - // 가동개시일자 범위 - const startDate = getDateString( - yearFilter.start, - '', - '', - "19000101", - ); - const endDate = getDateString( - yearFilter.end, - '', - '', - `${todayYear}${todayMonth}${todayDate}`, - ); - cqlFilters.push( - `oprtng_strt_ymd >= '${startDate}' and oprtng_strt_ymd <= '${endDate}'`, - ); - // 시설용량 범위 - if (start1 && end1) - cqlFilters.push(`fclt_cpct BETWEEN ${start1} AND ${end1}`); - else if (start1) cqlFilters.push(`fclt_cpct >= ${start1}`); - else if (end1) cqlFilters.push(`fclt_cpct <= ${end1}`); - - // 시도 - if (ctpvName) cqlFilters.push(`ctpv_nm ILIKE '%${ctpvName}%'`); - if (sggName) cqlFilters.push(`sgg_nm ILIKE '%${sggName}%'`); - // // 처리구역 면적 - // if (start2 && end2) - // cqlFilters.push(`prcsZoneAreaSum BETWEEN ${start2} AND ${end2}`); - // else if (start2) cqlFilters.push(`prcsZoneAreaSum >= ${start2}`); - // else if (end2) cqlFilters.push(`prcsZoneAreaSum <= ${end2}`); - // // 하수처리시설명 - // if (var1) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${var1}%'`); - return cqlFilters; - }; - // 하수처리구역용 CQL 필터 생성 - const zoneCqlFilters = () => { - const cqlFilters: string[] = []; - if (ctpvCd !== '' && ctpvCd !== '0') cqlFilters.push(`ctpv_cd='${ctpvCd?.trim()}'`); - if (sggCd !== '' && sggCd !== '0') cqlFilters.push(`sgg_cd='${sggCd}'`); - if (var1) cqlFilters.push(`swtrmt_zone_nm ILIKE '%${var1}%'`); - if (var2) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${var2}%'`); - return cqlFilters; - }; - // 하수맨홀용 CQL 필터 생성 - const mhCqlFilters = () => { - const cqlFilters: string[] = []; - // 시도, 시군구 - if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); - else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); - // // 설치년도 범위 - const startDate = getDateString( - yearFilter.start, - '', - '', - "19000101", - ); - const endDate = getDateString( - yearFilter.end, - '', - '', - `${todayYear}${todayMonth}${todayDate}`, - ); - cqlFilters.push( - `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, - ); - if (var1) cqlFilters.push(`usg_cd='${var1}'`); - // 구경(mm) 범위 - if (start1 && end1) cqlFilters.push(`swage_mhL_diam_vl BETWEEN ${start1} AND ${end1}`); - else if (start1) cqlFilters.push(`swage_mhL_diam_vl >= ${start1}`); - else if (end1) cqlFilters.push(`swage_mhL_diam_vl <= ${end1}`); - - return cqlFilters; - }; - // 하수펌프장용 CQL 필터 생성 - const psmtnCqlFilters = () => { - const cqlFilters: string[] = []; - // 시도, 시군구 - if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); - else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); - // 설치년도 범위 - const startDate = getDateString( - yearFilter.start, - '', - '', - "19000101", - ); - const endDate = getDateString( - yearFilter.end, - '', - '', - `${todayYear}${todayMonth}${todayDate}`, - ); - cqlFilters.push( - `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, - ); - // 일일처리용량 범위 - if (start1 && end1) - cqlFilters.push(`daily_prcs_cpct BETWEEN ${start1} AND ${end1}`); - else if (start1) cqlFilters.push(`daily_prcs_cpct >= ${start1}`); - else if (end1) cqlFilters.push(`daily_prcs_cpct <= ${end1}`); - // 양수능력 범위 - if (start2 && end2) - cqlFilters.push(`excln_wpmp_ablt_vl BETWEEN ${start2} AND ${end2}`); - else if (start2) cqlFilters.push(`excln_wpmp_ablt_vl >= ${start2}`); - else if (end2) cqlFilters.push(`excln_wpmp_ablt_vl <= ${end2}`); - - return cqlFilters; - }; - // 우수토실용 CQL 필터 생성 - const otflCqlFilters = () => { - const cqlFilters: string[] = []; - // 시도, 시군구 - if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); - else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); - // 설치년도 범위 - const startDate = getDateString( - yearFilter.start, - '', - '', - "19000101", - ); - const endDate = getDateString( - yearFilter.end, - '', - '', - `${todayYear}${todayMonth}${todayDate}`, - ); - cqlFilters.push( - `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, - ); - - return cqlFilters; - }; - - // 조회 버튼 클릭 시 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 = { - 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 = { - 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 = pplCqlFilters() - headerMapping = MsSwagePplExcelCol; - headerRange = "A1:M1"; - break; - } - case 'tb_il_swage_prcs_fclt': { - // 하수처리시설 - cqlFilters = facilityCqlFilters(); - headerMapping = SwtrmtFcltExcelCol; - headerRange = "A1:J1"; - break; - } - case 'tb_ms_swage_prcs_zone': { - // 하수처리구역 - cqlFilters = zoneCqlFilters(); - headerMapping = SwtrmtPrcsZoneExcelCol; - headerRange = "A1:D1"; - break; - } - case 'tb_ms_swage_mhl': { - // 하수맨홀 - cqlFilters = mhCqlFilters(); - headerMapping = MsSwageMhlExcelCol; - headerRange = "A1:J1"; - break; - } - case 'tb_ms_swage_pmstn': { - // 하수펌프장 - cqlFilters = psmtnCqlFilters(); - headerMapping = ExclnSewagePumpExcelCol; - headerRange = "A1:J1"; - break; - } - case 'tb_ms_stofcbr': { - // 우수토실 - cqlFilters = otflCqlFilters(); - 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 = { - 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 = { - 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), - }; - }); - const mappingEndTime = performance.now(); - - // 선택된 행 데이터를 Excel 다운로드 형식으로 변환 - const excelData = mappedData; // 배열로 감싸서 전달 - - const excelStartTime = performance.now(); - // 파일명 생성 (분석번호 기반) - 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 - }); - const excelEndTime = performance.now(); - - 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); - } - } - } - const renderBody = () => ( <> {/* 탭 영역 */} ({ + tabList={TABS.map((tab: any) => ({ value: tab.val, label: tab.text }))} diff --git a/apps/sn/app/(map-layout)/map/searchModal/useSearchModal.tsx b/apps/sn/app/(map-layout)/map/searchModal/useSearchModal.tsx new file mode 100644 index 000000000..35ab4d09c --- /dev/null +++ b/apps/sn/app/(map-layout)/map/searchModal/useSearchModal.tsx @@ -0,0 +1,1000 @@ +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"; + +const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`; +export const OPERATION_TABLE_SIZE = 5; + +// 날짜 조합 함수 +const getDateString = ( + year: string, + month: string, + day: string, + defaultDate: string, +) => { + if (!year && !month && !day) return defaultDate; + const y = year || defaultDate.slice(0, 4); + const m = month || defaultDate.slice(4, 6); + const d = day || defaultDate.slice(6, 8); + return `${y}${m}${d}`; +}; + +export interface SearchModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onFeatureSelect: (row: any) => void; +} + +export function useSearchModal({ open, onOpenChange, onFeatureSelect }: SearchModalProps) { + const TABS = [ + { val: "basic", text: "기본정보" }, + { val: "operation", text: "운영정보" }, + { val: "report", text: "하수도정비기본계획보고서" }, + ]; + const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? "basic"); + const [basicType, setBasicType] = useState(BASIC_TYPES[0]?.val ?? ""); + const [workType, setWorkType] = useState(WORK_TYPES[0]?.val ?? ""); + const [showTable, setShowTable] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [isDetailLoading, setIsDetailLoading] = useState(false); + const [tableData, setTableData] = useState([]); + const [detailTableData, setDetailTableData] = useState([]); + const [tableCount, setTableCount] = useState(0); + const [detailTableCount, setDetailTableCount] = useState(0); + const [curPage, setCurPage] = useState(1); + const [detailCurPage, setDetailCurPage] = useState(1); + const [isResetPage, setIsResetPage] = useState(false); + const [isResetDetailPage, setIsResetDetailPage] = useState(false); + const [isResetSelection, setIsResetSelection] = useState(false); + const [isResetDetailSelection, setIsResetDetailSelection] = useState(false); + const [detailBizId, setDetailBizId] = useState(0); + const [ctpvCd, setCtpvCd] = useState("0"); + const [ctpvName, setCtpvName] = useState(''); + const [sggCd, setSggCd] = useState("0"); + const [sggName, setSggName] = useState(''); + const [start1, setStart1] = useState(""); + const [start2, setStart2] = useState(""); + const [end1, setEnd1] = useState(""); + const [end2, setEnd2] = useState(""); + const [var1, setVar1] = useState(""); + const [var2, setVar2] = useState(""); + const [yearFilter, setYearFilter] = useState({ start: "", end: "" }); + + const { ctpv, sgg, BIZ_TY_CD } = useStore(); + + // 오늘 날짜 구하기 + const today = new Date(); + const todayYear = today.getFullYear().toString(); + const todayMonth = (today.getMonth() + 1).toString().padStart(2, "0"); + const todayDate = today.getDate().toString().padStart(2, "0"); + + + useEffect(() => { + // filterParams 초기화 + setStart1(""); + setStart2(""); + setEnd1(""); + setEnd2(""); + setVar1(""); + setVar2(""); + setCtpvCd("0"); + setSggCd("0"); + setYearFilter({ start: "", end: "" }); + setShowTable(false); + setTableData([]); + setDetailTableData([]); + setTableCount(0); + setDetailTableCount(0); + setCurPage(1); + setDetailCurPage(1); + setIsResetPage(true); + setIsResetDetailPage(true); + setIsResetSelection(true); + setIsResetDetailSelection(true); + setDetailBizId(0); + }, [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 = zoneCqlFilters(); // 하수처리구역 + else if (basicType === 'tb_il_swage_prcs_fclt') cqlFilters = facilityCqlFilters(); // 하수처리시설 + else if (basicType === 'tb_ms_swage_ppl') cqlFilters = pplCqlFilters(); // 하수관로 + else if (basicType === 'tb_ms_swage_mhl') cqlFilters = mhCqlFilters(); // 하수맨홀 + else if (basicType === 'tb_ms_swage_pmstn') cqlFilters = psmtnCqlFilters(); // 하수펌프장 + else if (basicType === 'tb_ms_stofcbr') cqlFilters = otflCqlFilters(); // 우수토실 + + 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 || ctpvCd; + const sggNm = sgg.find(code => code.val.trim() === sggCd)?.text || '-'; + 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, + sggNm, + swtrmt_zone_nm: item.swtrmtZoneNm || item.swtrmt_zone_nm || '-', + }; + }); + + setTableData(dataWithRowNumber); + setTableCount(res.data.count || []); + + } else if (activeTab === 'operation') { + const RequestDTO: Record = { + 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); + } + }; + + // 하수관로용 CQL 필터 생성 + const pplCqlFilters = () => { + const cqlFilters: string[] = []; + // 추후 작성 예정 + if (var1) cqlFilters.push(`usg_cd='${var1}'`); + if (var2) cqlFilters.push(`mqlt_cd='${var2}'`); + if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); + else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); + // 설치년도 범위 + const startDate = getDateString( + yearFilter.start, + '', + '', + "19000101", + ); + const endDate = getDateString( + yearFilter.end, + '', + '', + `${todayYear}${todayMonth}${todayDate}`, + ); + cqlFilters.push( + `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, + ); + // 관경(mm) 범위 + if (start1 && end1) cqlFilters.push(`pdmt_vl BETWEEN ${start1} AND ${end1}`); + else if (start1) cqlFilters.push(`pdmt_vl >= ${start1}`); + else if (end1) cqlFilters.push(`pdmt_vl <= ${end1}`); + // 연장(m) 범위 + if (start2 && end2) cqlFilters.push(`prlg_vl BETWEEN ${start2} AND ${end2}`); + else if (start2) cqlFilters.push(`prlg_vl >= ${start2}`); + else if (end2) cqlFilters.push(`prlg_vl <= ${end2}`); + + return cqlFilters; + }; + // 하수처리시설용 CQL 필터 생성 + const facilityCqlFilters = () => { + const cqlFilters: string[] = []; + // 가동개시일자 범위 + const startDate = getDateString( + yearFilter.start, + '', + '', + "19000101", + ); + const endDate = getDateString( + yearFilter.end, + '', + '', + `${todayYear}${todayMonth}${todayDate}`, + ); + cqlFilters.push( + `oprtng_strt_ymd >= '${startDate}' and oprtng_strt_ymd <= '${endDate}'`, + ); + // 시설용량 범위 + if (start1 && end1) + cqlFilters.push(`fclt_cpct BETWEEN ${start1} AND ${end1}`); + else if (start1) cqlFilters.push(`fclt_cpct >= ${start1}`); + else if (end1) cqlFilters.push(`fclt_cpct <= ${end1}`); + + // 시도 + if (ctpvName) cqlFilters.push(`ctpv_nm ILIKE '%${ctpvName}%'`); + if (sggName) cqlFilters.push(`sgg_nm ILIKE '%${sggName}%'`); + // // 처리구역 면적 + // if (start2 && end2) + // cqlFilters.push(`prcsZoneAreaSum BETWEEN ${start2} AND ${end2}`); + // else if (start2) cqlFilters.push(`prcsZoneAreaSum >= ${start2}`); + // else if (end2) cqlFilters.push(`prcsZoneAreaSum <= ${end2}`); + // // 하수처리시설명 + // if (var1) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${var1}%'`); + return cqlFilters; + }; + // 하수처리구역용 CQL 필터 생성 + const zoneCqlFilters = () => { + const cqlFilters: string[] = []; + if (ctpvCd !== '' && ctpvCd !== '0') cqlFilters.push(`ctpv_cd='${ctpvCd?.trim()}'`); + if (sggCd !== '' && sggCd !== '0') cqlFilters.push(`sgg_cd='${sggCd}'`); + if (var1) cqlFilters.push(`swtrmt_zone_nm ILIKE '%${var1}%'`); + if (var2) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${var2}%'`); + return cqlFilters; + }; + // 하수맨홀용 CQL 필터 생성 + const mhCqlFilters = () => { + const cqlFilters: string[] = []; + // 시도, 시군구 + if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); + else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); + // // 설치년도 범위 + const startDate = getDateString( + yearFilter.start, + '', + '', + "19000101", + ); + const endDate = getDateString( + yearFilter.end, + '', + '', + `${todayYear}${todayMonth}${todayDate}`, + ); + cqlFilters.push( + `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, + ); + if (var1) cqlFilters.push(`usg_cd='${var1}'`); + // 구경(mm) 범위 + if (start1 && end1) cqlFilters.push(`swage_mhL_diam_vl BETWEEN ${start1} AND ${end1}`); + else if (start1) cqlFilters.push(`swage_mhL_diam_vl >= ${start1}`); + else if (end1) cqlFilters.push(`swage_mhL_diam_vl <= ${end1}`); + + return cqlFilters; + }; + // 하수펌프장용 CQL 필터 생성 + const psmtnCqlFilters = () => { + const cqlFilters: string[] = []; + // 시도, 시군구 + if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); + else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); + // 설치년도 범위 + const startDate = getDateString( + yearFilter.start, + '', + '', + "19000101", + ); + const endDate = getDateString( + yearFilter.end, + '', + '', + `${todayYear}${todayMonth}${todayDate}`, + ); + cqlFilters.push( + `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, + ); + // 일일처리용량 범위 + if (start1 && end1) + cqlFilters.push(`daily_prcs_cpct BETWEEN ${start1} AND ${end1}`); + else if (start1) cqlFilters.push(`daily_prcs_cpct >= ${start1}`); + else if (end1) cqlFilters.push(`daily_prcs_cpct <= ${end1}`); + // 양수능력 범위 + if (start2 && end2) + cqlFilters.push(`excln_wpmp_ablt_vl BETWEEN ${start2} AND ${end2}`); + else if (start2) cqlFilters.push(`excln_wpmp_ablt_vl >= ${start2}`); + else if (end2) cqlFilters.push(`excln_wpmp_ablt_vl <= ${end2}`); + + return cqlFilters; + }; + // 우수토실용 CQL 필터 생성 + const otflCqlFilters = () => { + const cqlFilters: string[] = []; + // 시도, 시군구 + if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); + else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); + // 설치년도 범위 + const startDate = getDateString( + yearFilter.start, + '', + '', + "19000101", + ); + const endDate = getDateString( + yearFilter.end, + '', + '', + `${todayYear}${todayMonth}${todayDate}`, + ); + cqlFilters.push( + `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, + ); + + return cqlFilters; + }; + + // 조회 버튼 클릭 시 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 = { + 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 = { + 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 = pplCqlFilters() + headerMapping = MsSwagePplExcelCol; + headerRange = "A1:M1"; + break; + } + case 'tb_il_swage_prcs_fclt': { + // 하수처리시설 + cqlFilters = facilityCqlFilters(); + headerMapping = SwtrmtFcltExcelCol; + headerRange = "A1:J1"; + break; + } + case 'tb_ms_swage_prcs_zone': { + // 하수처리구역 + cqlFilters = zoneCqlFilters(); + headerMapping = SwtrmtPrcsZoneExcelCol; + headerRange = "A1:D1"; + break; + } + case 'tb_ms_swage_mhl': { + // 하수맨홀 + cqlFilters = mhCqlFilters(); + headerMapping = MsSwageMhlExcelCol; + headerRange = "A1:J1"; + break; + } + case 'tb_ms_swage_pmstn': { + // 하수펌프장 + cqlFilters = psmtnCqlFilters(); + headerMapping = ExclnSewagePumpExcelCol; + headerRange = "A1:J1"; + break; + } + case 'tb_ms_stofcbr': { + // 우수토실 + cqlFilters = otflCqlFilters(); + 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 = { + 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 = { + 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, + activeTab, setActiveTab, + basicType, setBasicType, + workType, setWorkType, + tableData, detailTableData, + tableCount, detailTableCount, + curPage, setCurPage, + detailCurPage, setDetailCurPage, + isLoading, isDetailLoading, + isResetPage, isResetDetailPage, + isResetSelection, isResetDetailSelection, + detailBizId, setDetailBizId, + ctpvCd, setCtpvCd, ctpvName, setCtpvName, + sggCd, setSggCd, sggName, setSggName, + start1, setStart1, start2, setStart2, + end1, setEnd1, end2, setEnd2, + var1, setVar1, var2, setVar2, + yearFilter, setYearFilter, + handleSearch, + handleBasicCellClick, + handleOperationCellClick, + handleOperationPageNation, + handleOperationResultCellClick, + basicExcelDownload, + operationExcelDownload, + operationResultExcelDownload, + viewMultipleFeature, + }; +}; From 18c46ce8296ab73c1e39dce6ef3caabcbce0dcf9 Mon Sep 17 00:00:00 2001 From: swcho Date: Wed, 3 Sep 2025 10:57:28 +0900 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20useSearchModal.tsx=20store=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=98=EC=97=AC=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(map-layout)/map/searchModal/basicTab.tsx | 174 ++-------------- .../map/searchModal/operationTab.tsx | 33 +-- .../map/searchModal/searchModal.tsx | 86 +++----- .../(map-layout)/map/searchModal/stofcbr.tsx | 38 ++-- .../(map-layout)/map/searchModal/swageMhl.tsx | 55 ++--- .../map/searchModal/swagePmstn.tsx | 55 ++--- .../(map-layout)/map/searchModal/swagePpl.tsx | 95 +++++---- .../map/searchModal/swagePrcsFclt.tsx | 57 ++--- .../map/searchModal/swagePrcsZone.tsx | 43 ++-- .../map/searchModal/useSearchModal.tsx | 119 +++-------- apps/sn/app/store/useSearchModalStore.ts | 195 ++++++++++++++++++ 11 files changed, 395 insertions(+), 555 deletions(-) create mode 100644 apps/sn/app/store/useSearchModalStore.ts diff --git a/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx index 70cd18884..5443bb65d 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx @@ -8,6 +8,9 @@ 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'; + // 기본정보 '구분' 선택 목록 export const BASIC_TYPES = [ @@ -19,57 +22,9 @@ export const BASIC_TYPES = [ { val: "tb_ms_stofcbr", text: "우수토실" }, ]; -interface BasicTabProps { - basicType: string; - city: string; - sgg: string; - start1: string; - start2: string; - end1: string; - end2: string; - var1: string; - var2: string; - yearFilter: { start: string; end: string; }; - handleSearch: () => void; - setBasicType: (type: string) => void; - setCity: (city: string) => void; - setCtpvName: (name: string) => void; - setSgg: (sgg: string) => void; - setSggName: (name: string) => void; - setYearFilter: (filter: { start: string; end: string }) => void; - setStart1: (value: string) => void; - setStart2: (value: string) => void; - setEnd1: (value: string) => void; - setEnd2: (value: string) => void; - setVar1: (value: string) => void; - setVar2: (value: string) => void; -} +const BasicTab: React.FC = (props) => { + const { basicType, setBasicType } = useSearchModalStore(); -const BasicTab: React.FC = ({ - basicType, - city, - sgg, - start1, - start2, - end1, - end2, - var1, - var2, - yearFilter, - handleSearch, - setBasicType, - setCity, - setCtpvName, - setSgg, - setSggName, - setYearFilter, - setStart1, - setStart2, - setEnd1, - setEnd2, - setVar1, - setVar2, -}) => { return (
{/* 구분 */} @@ -89,118 +44,13 @@ const BasicTab: React.FC = ({ />
- {/* 분기처리: 하수관로 */} - {basicType === "tb_ms_swage_ppl" && ( - - )} - {/* 분기처리: 하수처리시설 */} - {basicType === "tb_il_swage_prcs_fclt" && ( - - )} - {/* 분기처리: 하수처리구역 */} - {basicType === "tb_ms_swage_prcs_zone" && ( - - )} - {/* 분기처리: 하수맨홀 */} - {basicType === "tb_ms_swage_mhl" && ( - - )} - {/* 분기처리: 하수펌프장 */} - {basicType === "tb_ms_swage_pmstn" && ( - - )} - {/* 분기처리: 우수토실 */} - {basicType === "tb_ms_stofcbr" && ( - - )} + {/* 조회 조건 */} + {basicType === "tb_ms_swage_ppl" && ()} {/* 하수관로 */} + {basicType === "tb_il_swage_prcs_fclt" && ()} {/* 하수처리시설 */} + {basicType === "tb_ms_swage_prcs_zone" && ()} {/* 하수처리구역 */} + {basicType === "tb_ms_swage_mhl" && ()} {/* 하수맨홀 */} + {basicType === "tb_ms_swage_pmstn" && ()} {/* 하수펌프장 */} + {basicType === "tb_ms_stofcbr" && ()} {/* 우수토실 */} ); }; diff --git a/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx index 1068bb2e5..6f9e532a1 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx @@ -3,6 +3,8 @@ 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 = [ @@ -65,30 +67,15 @@ export const operationInspectionTableHeadlist = [ { id: "oper_stts_grd", title: "운영적 상태등급" }, ]; -interface OperationTabProps { - workType: string; - var1: string; - var2: string; - yearFilter: { start: string; end: string; }; - handleSearch: () => void; - setWorkType: (type: string) => void; - setYearFilter: (filter: { start: string; end: string }) => void; - setVar1: (value: string) => void; - setVar2: (value: string) => void; -} - -const OperationTab: React.FC = ({ - workType, - var1, - var2, - yearFilter, - handleSearch, - setWorkType, - setVar1, - setVar2, - setYearFilter, -}) => { +const OperationTab: React.FC = (props) => { + const { + workType, setWorkType, + var1, setVar1, var2, setVar2, + yearFilter, setYearFilter, + } = useSearchModalStore(); + const { handleSearch } = useSearchModal(props); + const { BIZ_TY_CD } = useStore(); const BIZ_TY_CD_SELECT_ARRAY = BIZ_TY_CD.map(item => ({ diff --git a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx index b9eb29a10..078f3d473 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +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'; @@ -14,27 +14,12 @@ import ReportTab from './reportTab'; import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; import { Toaster } from "react-hot-toast"; import { useSearchModal, OPERATION_TABLE_SIZE, type SearchModalProps } from './useSearchModal'; +import useSearchModalStore from '../../../store/useSearchModalStore'; const SearchModal: React.FC = (props) => { const { open, onOpenChange, TABS, - activeTab, setActiveTab, - basicType, setBasicType, - workType, setWorkType, - tableData, detailTableData, - tableCount, detailTableCount, - curPage, setCurPage, - detailCurPage, setDetailCurPage, - isLoading, isDetailLoading, - isResetPage, isResetDetailPage, - isResetSelection, isResetDetailSelection, - ctpvCd, setCtpvCd, setCtpvName, - sggCd, setSggCd, setSggName, - start1, setStart1, start2, setStart2, - end1, setEnd1, end2, setEnd2, - var1, setVar1, var2, setVar2, - yearFilter, setYearFilter, handleSearch, handleBasicCellClick, handleOperationCellClick, @@ -44,28 +29,39 @@ const SearchModal: React.FC = (props) => { operationResultExcelDownload, viewMultipleFeature, } = useSearchModal(props); + const { + activeTab, basicType, workType, + tableData, detailTableData, + tableCount, detailTableCount, + curPage, detailCurPage, + isLoading, isDetailLoading, + isResetPage, isResetDetailPage, + isResetSelection, isResetDetailSelection, + setActiveTab, + setCurPage, setDetailCurPage, + } = useSearchModalStore(); 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 (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; // 우수토실 + if (basicType === 'tb_ms_swage_pmstn') return ExclnSewagePumpCol2; // 하수펌프장 + if (basicType === 'tb_ms_stofcbr') return stofcbrCol2; // 우수토실 return []; } - if (activeTab === 'operation') return operationTableHeadlist; + if (activeTab === 'operation') return operationTableHeadlist; // 운영정보 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 operationMaintenanceTableHeadlist; // 보수 및 갱신 + if (workType === WORK_TYPES[1]?.val) return operationInspectionTableHeadlist; // 점검 및 진단 + if (workType === WORK_TYPES[2]?.val) return operationCleaningTableHeadlist; // 청소 및 준설 } return []; @@ -85,45 +81,11 @@ const SearchModal: React.FC = (props) => { /> {/* 기본정보 탭 */} {activeTab === 'basic' && ( - + )} {/* 운영정보 탭 */} {activeTab === 'operation' && ( - + )} {/* 하수도정비기본계획보고서 탭 */} {activeTab === 'report' && ( diff --git a/apps/sn/app/(map-layout)/map/searchModal/stofcbr.tsx b/apps/sn/app/(map-layout)/map/searchModal/stofcbr.tsx index 2e4fd26f0..455a17a60 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/stofcbr.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/stofcbr.tsx @@ -4,38 +4,30 @@ import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox'; 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'; + +const Stofcbr: React.FC = (props) => { + const { + ctpvCd, setCtpvCd, + sggCd, setSggCd, + yearFilter, setYearFilter, + } = useSearchModalStore(); + const { handleSearch } = useSearchModal(props); -interface StofcbrProps { - city: string; - sgg: string; - yearFilter: { start: string; end: string }; - setCity: (value: string) => void; - setSgg: (value: string) => void; - setYearFilter: (filter: { start: string; end: string }) => void; - handleSearch: () => void; -} -const Stofcbr: React.FC = ({ - city, - sgg, - yearFilter, - setCity, - setSgg, - setYearFilter, - handleSearch, -}) => { return (
{/* 시도, 시군구 */} { - setCity(sidoId); - setSgg("0"); + setCtpvCd(sidoId); + setSggCd("0"); }} - onSigunguChange={setSgg} + onSigunguChange={setSggCd} /> {/* 설치년도 */}
diff --git a/apps/sn/app/(map-layout)/map/searchModal/swageMhl.tsx b/apps/sn/app/(map-layout)/map/searchModal/swageMhl.tsx index 1c578a3e6..f16fae331 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/swageMhl.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/swageMhl.tsx @@ -1,56 +1,35 @@ // 하수맨홀 +import React from 'react'; import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox'; import { Button, Input, Label } from '@common/components'; -import React from 'react'; 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'; - -interface SwageMhlProps { - var1: string; - start1: string; - end1: string; - city: string; - sgg: string; - yearFilter: { start: string; end: string }; - setVar1: (value: string) => void; - setStart1: (value: string) => void; - setEnd1: (value: string) => void; - setCity: (value: string) => void; - setSgg: (value: string) => void; - setYearFilter: (filter: { start: string; end: string }) => void; - handleSearch: () => void; -} - -const SwageMhl: React.FC = ({ - var1, - start1, - end1, - city, - sgg, - yearFilter, - setVar1, - setStart1, - setEnd1, - setCity, - setSgg, - setYearFilter, - handleSearch, -}) => { +const SwageMhl: React.FC = (props) => { + const { + var1, setVar1, + start1, setStart1, end1, setEnd1, + ctpvCd, setCtpvCd, + sggCd, setSggCd, + yearFilter, setYearFilter, + } = useSearchModalStore(); + const { handleSearch } = useSearchModal(props); return (
{ - setCity(sidoId); - setSgg("0"); + setCtpvCd(sidoId); + setSggCd("0"); }} - onSigunguChange={setSgg} + onSigunguChange={setSggCd} /> {/* 설치년도 */} void; - setStart2: (value: string) => void; - setEnd1: (value: string) => void; - setEnd2: (value: string) => void; - setCity: (value: string) => void; - setSgg: (value: string) => void; - setYearFilter: (filter: { start: string; end: string }) => void; - handleSearch: () => void; -} -const SwagePmstn: React.FC = ({ - start1, - start2, - end1, - end2, - city, - sgg, - yearFilter, - setStart1, - setStart2, - setEnd1, - setEnd2, - setCity, - setSgg, - setYearFilter, - handleSearch, -}) => { +const SwagePmstn: React.FC = (props) => { + const { + start1, setStart1, + end1, setEnd1, + ctpvCd, setCtpvCd, + sggCd, setSggCd, + yearFilter, setYearFilter, + } = useSearchModalStore(); + const { handleSearch } = useSearchModal(props); return (
{/* 시도, 시군구 */} { - setCity(sidoId); - setSgg("0"); + setCtpvCd(sidoId); + setSggCd("0"); }} - onSigunguChange={setSgg} + onSigunguChange={setSggCd} /> {/* 설치년도 */} void; - setVar2: (value: string) => void; - setStart1: (value: string) => void; - setStart2: (value: string) => void; - setEnd1: (value: string) => void; - setEnd2: (value: string) => void; - setCity: (value: string) => void; - setSgg: (value: string) => void; - setYearFilter: (filter: { start: string; end: string }) => void; - handleSearch: () => void; -} +// 하수관로용 CQL 필터 생성 +export const pplCqlFilters = () => { + const store = useSearchModalStore(); + const { + todayYear, todayMonth, todayDate, + ctpvCd, sggCd, + start1, start2, end1, end2, + var1, var2, + yearFilter, getDateString + } = store; -const SwagePpl: React.FC = ({ - var1, - var2, - start1, - start2, - end1, - end2, - city, - sgg, - yearFilter, - setVar1, - setVar2, - setStart1, - setStart2, - setEnd1, - setEnd2, - setCity, - setSgg, - setYearFilter, - handleSearch, -}) => { + const cqlFilters: string[] = []; + if (var1) cqlFilters.push(`usg_cd='${var1}'`); + if (var2) cqlFilters.push(`mqlt_cd='${var2}'`); + if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); + else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd?.trim()}%'`); + // 설치년도 범위 + const startDate = getDateString(yearFilter.start, '', '', "19000101"); + const endDate = getDateString(yearFilter.end, '', '', `${todayYear}${todayMonth}${todayDate}`); + cqlFilters.push(`instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`); + // 관경(mm) 범위 + if (start1 && end1) cqlFilters.push(`pdmt_vl BETWEEN ${start1} AND ${end1}`); + else if (start1) cqlFilters.push(`pdmt_vl >= ${start1}`); + else if (end1) cqlFilters.push(`pdmt_vl <= ${end1}`); + // 연장(m) 범위 + if (start2 && end2) cqlFilters.push(`prlg_vl BETWEEN ${start2} AND ${end2}`); + else if (start2) cqlFilters.push(`prlg_vl >= ${start2}`); + else if (end2) cqlFilters.push(`prlg_vl <= ${end2}`); + + return cqlFilters; +}; + +const SwagePpl: React.FC = (props) => { + const { + var1, setVar1, + start1, setStart1, end1, setEnd1, + ctpvCd, setCtpvCd, sggCd, setSggCd, + yearFilter, setYearFilter, + } = useSearchModalStore(); + const { handleSearch } = useSearchModal(props); return (
{ - setCity(sidoId); - setSgg("0"); + setCtpvCd(sidoId); + setSggCd("0"); }} - onSigunguChange={setSgg} + onSigunguChange={setSggCd} /> {/* 설치년도 */} = ({ className="w-32 mr-8" selectClass="rounded-lg bg-white text-xl" label="" - selectArray={[{val: "0", text: "전체"}, ...MS_SWAGE_PPL_USE_CODES]} + selectArray={[{ val: "0", text: "전체" }, ...MS_SWAGE_PPL_USE_CODES]} value={var1} onSelectValue={(val) => { setVar1(String(val)); diff --git a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx index a3db595a5..934499a6c 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsFclt.tsx @@ -4,57 +4,30 @@ import React from 'react'; 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'; -interface SwagePrcsFcltProps { - var1: string; - start1: string; - start2: string; - end1: string; - end2: string; - city: string; - sgg: string; - yearFilter: { start: string; end: string }; - setVar1: (value: string) => void; - setStart1: (value: string) => void; - setStart2: (value: string) => void; - setEnd1: (value: string) => void; - setEnd2: (value: string) => void; - setCity: (value: string) => void; - setCtpvName: (name: string) => void; - setSgg: (value: string) => void; - setSggName: (name: string) => void; - setYearFilter: (filter: { start: string; end: string }) => void; - handleSearch: () => void; -} - -const SwagePrcsFclt: React.FC = ({ - start1, - end1, - city, - sgg, - yearFilter, - setStart1, - setEnd1, - setCity, - setCtpvName, - setSgg, - setSggName, - setYearFilter, - handleSearch, -}) => { +const SwagePrcsFclt: React.FC = (props) => { + const { + start1, setStart1, end1, setEnd1, + ctpvCd, setCtpvCd, setCtpvName, + sggCd, setSggCd, setSggName, + yearFilter, setYearFilter, + } = useSearchModalStore(); + const { handleSearch } = useSearchModal(props); return (
{ - setCity(sidoId); - setSgg("0"); + setCtpvCd(sidoId); + setSggCd("0"); }} - onSigunguChange={setSgg} + onSigunguChange={setSggCd} setCtpvName={setCtpvName} setSggName={setSggName} /> diff --git a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsZone.tsx b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsZone.tsx index 9f8e8a181..c7d3df3ce 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/swagePrcsZone.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/swagePrcsZone.tsx @@ -1,44 +1,31 @@ // 하수처리구역 +import React from 'react'; import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox'; import { Button, Input, Label } from '@common/components'; -import React from 'react'; +import useSearchModalStore from '../../../store/useSearchModalStore'; +import { useSearchModal, type SearchModalProps } from './useSearchModal'; -interface swagePrcsZoneProps { - var1: string; - var2: string; - city: string; - sgg: string; - setVar1: (value: string) => void; - setVar2: (value: string) => void; - setCity: (value: string) => void; - setSgg: (value: string) => void; - handleSearch: () => void; -} -const swagePrcsZone: React.FC = ({ - var1, - var2, - city, - sgg, - setVar1, - setVar2, - setCity, - setSgg, - handleSearch, -}) => { +const swagePrcsZone: React.FC = (props) => { + const { + var1, setVar1, var2, setVar2, + ctpvCd, setCtpvCd, + sggCd, setSggCd, + } = useSearchModalStore(); + const { handleSearch } = useSearchModal(props); return (
{ - setCity(sidoId); - setSgg("0"); + setCtpvCd(sidoId); + setSggCd("0"); }} - onSigunguChange={setSgg} + onSigunguChange={setSggCd} /> { - if (!year && !month && !day) return defaultDate; - const y = year || defaultDate.slice(0, 4); - const m = month || defaultDate.slice(4, 6); - const d = day || defaultDate.slice(6, 8); - return `${y}${m}${d}`; -}; - export interface SearchModalProps { open: boolean; onOpenChange: (open: boolean) => void; @@ -38,72 +25,39 @@ export interface SearchModalProps { } 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: "하수도정비기본계획보고서" }, ]; - const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? "basic"); - const [basicType, setBasicType] = useState(BASIC_TYPES[0]?.val ?? ""); - const [workType, setWorkType] = useState(WORK_TYPES[0]?.val ?? ""); - const [showTable, setShowTable] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [isDetailLoading, setIsDetailLoading] = useState(false); - const [tableData, setTableData] = useState([]); - const [detailTableData, setDetailTableData] = useState([]); - const [tableCount, setTableCount] = useState(0); - const [detailTableCount, setDetailTableCount] = useState(0); - const [curPage, setCurPage] = useState(1); - const [detailCurPage, setDetailCurPage] = useState(1); - const [isResetPage, setIsResetPage] = useState(false); - const [isResetDetailPage, setIsResetDetailPage] = useState(false); - const [isResetSelection, setIsResetSelection] = useState(false); - const [isResetDetailSelection, setIsResetDetailSelection] = useState(false); - const [detailBizId, setDetailBizId] = useState(0); - const [ctpvCd, setCtpvCd] = useState("0"); - const [ctpvName, setCtpvName] = useState(''); - const [sggCd, setSggCd] = useState("0"); - const [sggName, setSggName] = useState(''); - const [start1, setStart1] = useState(""); - const [start2, setStart2] = useState(""); - const [end1, setEnd1] = useState(""); - const [end2, setEnd2] = useState(""); - const [var1, setVar1] = useState(""); - const [var2, setVar2] = useState(""); - const [yearFilter, setYearFilter] = useState({ start: "", end: "" }); - - const { ctpv, sgg, BIZ_TY_CD } = useStore(); - - // 오늘 날짜 구하기 - const today = new Date(); - const todayYear = today.getFullYear().toString(); - const todayMonth = (today.getMonth() + 1).toString().padStart(2, "0"); - const todayDate = today.getDate().toString().padStart(2, "0"); useEffect(() => { - // filterParams 초기화 - setStart1(""); - setStart2(""); - setEnd1(""); - setEnd2(""); - setVar1(""); - setVar2(""); - setCtpvCd("0"); - setSggCd("0"); - setYearFilter({ start: "", end: "" }); - setShowTable(false); - setTableData([]); - setDetailTableData([]); - setTableCount(0); - setDetailTableCount(0); - setCurPage(1); - setDetailCurPage(1); - setIsResetPage(true); - setIsResetDetailPage(true); - setIsResetSelection(true); - setIsResetDetailSelection(true); - setDetailBizId(0); + resetFilters(); }, [activeTab, basicType, workType]); // 페이지 변경 시마다 데이터 fetch @@ -939,6 +893,7 @@ export function useSearchModal({ open, onOpenChange, onFeatureSelect }: SearchMo } }; + // 여러 시설 보기 const viewMultipleFeature = async () => { if (activeTab === 'basic') { if (!(tableData.length === 0)) { @@ -970,23 +925,7 @@ export function useSearchModal({ open, onOpenChange, onFeatureSelect }: SearchMo return { open, onOpenChange, onFeatureSelect, TABS, - activeTab, setActiveTab, - basicType, setBasicType, - workType, setWorkType, - tableData, detailTableData, - tableCount, detailTableCount, - curPage, setCurPage, - detailCurPage, setDetailCurPage, - isLoading, isDetailLoading, - isResetPage, isResetDetailPage, - isResetSelection, isResetDetailSelection, - detailBizId, setDetailBizId, - ctpvCd, setCtpvCd, ctpvName, setCtpvName, - sggCd, setSggCd, sggName, setSggName, - start1, setStart1, start2, setStart2, - end1, setEnd1, end2, setEnd2, - var1, setVar1, var2, setVar2, - yearFilter, setYearFilter, + getDateString, handleSearch, handleBasicCellClick, handleOperationCellClick, diff --git a/apps/sn/app/store/useSearchModalStore.ts b/apps/sn/app/store/useSearchModalStore.ts new file mode 100644 index 000000000..8ad1c356c --- /dev/null +++ b/apps/sn/app/store/useSearchModalStore.ts @@ -0,0 +1,195 @@ +import { create } from "zustand"; +import { type SelectDataType } from "@common/business_components"; + + +// 날짜 조합 함수 +const getDateString = ( + year: string, + month: string, + day: string, + defaultDate: string, +) => { + if (!year && !month && !day) return defaultDate; + const y = year || defaultDate.slice(0, 4); + const m = month || defaultDate.slice(4, 6); + const d = day || defaultDate.slice(6, 8); + return `${y}${m}${d}`; +}; + +// 오늘 날짜 계산 +const today = new Date(); +const todayYear = today.getFullYear().toString(); +const todayMonth = (today.getMonth() + 1).toString().padStart(2, "0"); +const todayDate = today.getDate().toString().padStart(2, "0"); + +export interface SearchModalState { + // 탭 관련 + activeTab: string; + basicType: string; + workType: string; + // 최초 렌더 방지 플래그 + showTable: boolean; + // 테이블 데이터 + tableData: any[]; + detailTableData: any[]; + tableCount: number; + detailTableCount: number; + // 페이지네이션 + curPage: number; + detailCurPage: number; + // 로딩 상태 + isLoading: boolean; + isDetailLoading: boolean; + // 리셋 플래그 + isResetPage: boolean; + isResetDetailPage: boolean; + isResetSelection: boolean; + isResetDetailSelection: boolean; + // 사업 ID + detailBizId: number; + // 지역 필터 + ctpvCd: string; + ctpvName: string; + sggCd: string; + sggName: string; + // 범위 필터 + start1: string; + start2: string; + end1: string; + end2: string; + // 변수 필터 + var1: string; + var2: string; + // 연도 필터 + yearFilter: { start: string; end: string }; + // 날짜 관련 + today: Date; + todayYear: string; + todayMonth: string; + todayDate: string; + getDateString: (year: string, month: string, day: string, defaultDate: string) => string; + + // setters + setActiveTab: (tab: string) => void; + setBasicType: (type: string) => void; + setWorkType: (type: string) => void; + setShowTable: (show: boolean) => void; + setTableData: (data: any[]) => void; + setDetailTableData: (data: any[]) => void; + setTableCount: (count: number) => void; + setDetailTableCount: (count: number) => void; + setCurPage: (page: number) => void; + setDetailCurPage: (page: number) => void; + setIsLoading: (loading: boolean) => void; + setIsDetailLoading: (loading: boolean) => void; + setIsResetPage: (reset: boolean) => void; + setIsResetDetailPage: (reset: boolean) => void; + setIsResetSelection: (reset: boolean) => void; + setIsResetDetailSelection: (reset: boolean) => void; + setDetailBizId: (id: number) => void; + setCtpvCd: (cd: string) => void; + setCtpvName: (name: string) => void; + setSggCd: (cd: string) => void; + setSggName: (name: string) => void; + setStart1: (val: string) => void; + setStart2: (val: string) => void; + setEnd1: (val: string) => void; + setEnd2: (val: string) => void; + setVar1: (val: string) => void; + setVar2: (val: string) => void; + setYearFilter: (filter: { start: string; end: string }) => void; + // 초기화 액션 + resetFilters: () => void; +} + +const useSearchModalStore = create((set) => ({ + activeTab: "basic", + basicType: "tb_ms_swage_ppl", + workType: "['200', '201', '204']", + showTable: false, + tableData: [], + detailTableData: [], + tableCount: 0, + detailTableCount: 0, + curPage: 1, + detailCurPage: 1, + isLoading: false, + isDetailLoading: false, + isResetPage: false, + isResetDetailPage: false, + isResetSelection: false, + isResetDetailSelection: false, + detailBizId: 0, + ctpvCd: "0", + ctpvName: "", + sggCd: "0", + sggName: "", + start1: "", + start2: "", + end1: "", + end2: "", + var1: "", + var2: "", + yearFilter: { start: "", end: "" }, + today, + todayYear, + todayMonth, + todayDate, + getDateString, + setActiveTab: (tab) => set({ activeTab: tab }), + setBasicType: (type) => set({ basicType: type }), + setWorkType: (type) => set({ workType: type }), + setShowTable: (show) => set({ showTable: show }), + setTableData: (data) => set({ tableData: data }), + setDetailTableData: (data) => set({ detailTableData: data }), + setTableCount: (count) => set({ tableCount: count }), + setDetailTableCount: (count) => set({ detailTableCount: count }), + setCurPage: (page) => set({ curPage: page }), + setDetailCurPage: (page) => set({ detailCurPage: page }), + setIsLoading: (loading) => set({ isLoading: loading }), + setIsDetailLoading: (loading) => set({ isDetailLoading: loading }), + setIsResetPage: (reset) => set({ isResetPage: reset }), + setIsResetDetailPage: (reset) => set({ isResetDetailPage: reset }), + setIsResetSelection: (reset) => set({ isResetSelection: reset }), + setIsResetDetailSelection: (reset) => set({ isResetDetailSelection: reset }), + setDetailBizId: (id) => set({ detailBizId: id }), + setCtpvCd: (cd) => set({ ctpvCd: cd }), + setCtpvName: (name) => set({ ctpvName: name }), + setSggCd: (cd) => set({ sggCd: cd }), + setSggName: (name) => set({ sggName: name }), + setStart1: (val) => set({ start1: val }), + setStart2: (val) => set({ start2: val }), + setEnd1: (val) => set({ end1: val }), + setEnd2: (val) => set({ end2: val }), + setVar1: (val) => set({ var1: val }), + setVar2: (val) => set({ var2: val }), + setYearFilter: (filter) => set({ yearFilter: filter }), + + // 초기화 액션 + resetFilters: () => + set({ + showTable: false, + start1: "", + start2: "", + end1: "", + end2: "", + var1: "", + var2: "", + ctpvCd: "0", + sggCd: "0", + yearFilter: { start: "", end: "" }, + tableData: [], + detailTableData: [], + tableCount: 0, + detailTableCount: 0, + curPage: 1, + detailCurPage: 1, + isResetPage: true, + isResetDetailPage: true, + isResetSelection: true, + isResetDetailSelection: true, + detailBizId: 0, + }), +})); + +export default useSearchModalStore; \ No newline at end of file From 99fec24764479a8e269c32c87252dcca32094e47 Mon Sep 17 00:00:00 2001 From: swcho Date: Wed, 3 Sep 2025 11:02:56 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=EC=9A=B0=EC=88=98=ED=86=A0=EC=8B=A4?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EB=B2=84=ED=8A=BC=20=EC=98=A4=EB=A5=B8?= =?UTF-8?q?=EC=AA=BD=20=EB=B0=B0=EC=B9=98=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(map-layout)/map/searchModal/stofcbr.tsx | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/apps/sn/app/(map-layout)/map/searchModal/stofcbr.tsx b/apps/sn/app/(map-layout)/map/searchModal/stofcbr.tsx index 455a17a60..7571871d5 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/stofcbr.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/stofcbr.tsx @@ -30,18 +30,16 @@ const Stofcbr: React.FC = (props) => { onSigunguChange={setSggCd} /> {/* 설치년도 */} -
- setYearFilter({ ...yearFilter, start: range.start, end: range.end })} - startLabel="설치년도" - endLabel="" - /> - {/* 조회 버튼 */} -
- -
+ setYearFilter({ ...yearFilter, start: range.start, end: range.end })} + startLabel="설치년도" + endLabel="" + /> + {/* 조회 버튼 */} +
+