diff --git a/apps/cs/src/entities/link/model/link-interface.ts b/apps/cs/src/entities/link/model/link-interface.ts index c4493a6c0..57d1b4582 100644 --- a/apps/cs/src/entities/link/model/link-interface.ts +++ b/apps/cs/src/entities/link/model/link-interface.ts @@ -18,8 +18,8 @@ export interface Link { next_link_excn_dt: string; last_link_err_cd: string; mdfcn_dt: string; - mdfr: string; - link_batch_time: string; + mdfr_id_id: string; + link_cycle_mi: string; } export interface LinkLog { @@ -48,8 +48,8 @@ export interface LinkInfo extends LinkFormData { next_link_excn_dt: string; last_link_err_cd: string; mdfcn_dt: string; - mdfr: string; - link_batch_time: number; + mdfr_id: string; + link_cycle_mi: number; } export interface LinkStats { @@ -65,7 +65,7 @@ export interface LinkVrbl { link_vrbl_cd: string; link_vrbl_expln: string; mdfcn_dt: string; - mdfr: string; + mdfr_id: string; } export interface LinkTable { @@ -94,7 +94,7 @@ export interface LinkTableColumns { pk_yn: string; col_seq: number; mdfcn_dt: string; - mdfr: string; + mdfr_id: string; } export const linkFormSchema = z.object({ @@ -118,8 +118,8 @@ export const linkFormSchema = z.object({ next_link_excn_dt: z.string().max(40).nullable().optional(), last_link_err_cd: z.string().max(10).nullable().optional(), mdfcn_dt: z.string().nullable().optional(), - mdfr: z.string().nullable().optional(), - link_batch_time: z.coerce.number().nullable().optional().refine((val) => { + mdfr_id: z.string().nullable().optional(), + link_cycle_mi: z.coerce.number().nullable().optional().refine((val) => { if (val == null) return true; return val >= 5; }, { @@ -135,7 +135,7 @@ export const linkVrblFormSchema = z.object({ link_vrbl_cd: z.string().max(10, { message: '변수코드는 10자 이하로 입력해 주세요.' }).nullable().optional(), link_vrbl_vl: z.string().max(1000, { message: '변수값은 1000자 이하로 입력해 주세요.' }).nonempty({ message: '변수값은 필수입니다.' }), mdfcn_dt: z.string().nullable().optional(), - mdfr: z.string().nullable().optional(), + mdfr_id: z.string().nullable().optional(), }); export const LinkTableColumnsSchema = z.object({ @@ -168,7 +168,7 @@ export const LinkTableColumnsSchema = z.object({ message: "컬럼 매칭 순서는 0 이상이어야 합니다." }), mdfcn_dt: z.string().nullable().optional(), - mdfr: z.string().nullable().optional(), + mdfr_id: z.string().nullable().optional(), }); export const LinkTableSchema = z.object({ @@ -182,7 +182,7 @@ export const LinkTableSchema = z.object({ trsf_tbl_korn_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환테이블 한글명은 필수입니다.' }), trsf_tbl_expln: z.string().max(2000, { message: '설명은 2000자 이하로 입력해 주세요.' }).nullable().optional(), mdfcn_dt: z.string().nullable().optional(), - mdfr: z.string().nullable().optional(), + mdfr_id: z.string().nullable().optional(), }); interface LinkStatsListObject { diff --git a/apps/cs/src/pages/link/model/table-columns.tsx b/apps/cs/src/pages/link/model/table-columns.tsx index 5b1710fec..81b86b570 100644 --- a/apps/cs/src/pages/link/model/table-columns.tsx +++ b/apps/cs/src/pages/link/model/table-columns.tsx @@ -53,8 +53,8 @@ const columns = [ header: '연계서버 IP', cell: (info) => (info.getValue() ? info.getValue() : '-'), }), - columnHelper.accessor('link_batch_time', { - id: 'link_batch_time', + columnHelper.accessor('link_cycle_mi', { + id: 'link_cycle_mi', header: '연계 주기', cell: (info) => convertMinutesToPeriod(Number(info.getValue())), }), diff --git a/apps/cs/src/pages/link/ui/link-form.tsx b/apps/cs/src/pages/link/ui/link-form.tsx index 172215662..41f125335 100644 --- a/apps/cs/src/pages/link/ui/link-form.tsx +++ b/apps/cs/src/pages/link/ui/link-form.tsx @@ -336,7 +336,7 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, { const timeParts = convertMinutesToPeriod(Number(field.value)); return ( @@ -434,9 +434,9 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, - {form.formState.errors.link_batch_time && ( + {form.formState.errors.link_cycle_mi && (

- {form.formState.errors.link_batch_time.message} + {form.formState.errors.link_cycle_mi.message}

)} @@ -520,7 +520,7 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, ( diff --git a/apps/cs/src/pages/link/ui/modify-page.tsx b/apps/cs/src/pages/link/ui/modify-page.tsx index 594cb4c49..c690b8aa7 100644 --- a/apps/cs/src/pages/link/ui/modify-page.tsx +++ b/apps/cs/src/pages/link/ui/modify-page.tsx @@ -35,7 +35,7 @@ function ModifyLinkPage({ linkSn }: { linkSn: string }) { mutationFn: (data: LinkFormData) => LinkApi.updateLinkInfo({ ...data, link_clsf_cd: data.link_clsf_cd !== 'none' ? data.link_clsf_cd : null, - mdfr: userUniqId + mdfr_id: userUniqId }), onSuccess: () => { alert('수정되었습니다.'); @@ -73,11 +73,11 @@ function ModifyLinkPage({ linkSn }: { linkSn: string }) { if (linkInfo) { let newData = { ...linkInfo }; - if (linkInfo.mdfr) { + if (linkInfo.mdfr_id) { const userName = await fetchUserInfo(); newData = { ...linkInfo, - mdfr: userName, + mdfr_id: userName, }; } diff --git a/apps/cs/src/pages/link/ui/regist-page.tsx b/apps/cs/src/pages/link/ui/regist-page.tsx index 403aefa24..76912ab9f 100644 --- a/apps/cs/src/pages/link/ui/regist-page.tsx +++ b/apps/cs/src/pages/link/ui/regist-page.tsx @@ -27,7 +27,7 @@ function RegistLinkPage() { link_id: '', link_pswd: '', srvc_usg: '', - link_batch_time: 5, + link_cycle_mi: 5, link_clsf_cd: 'none', }, }); @@ -44,8 +44,8 @@ function RegistLinkPage() { const insertData = { ...data, link_clsf_cd: data.link_clsf_cd !== 'none' ? data.link_clsf_cd : null, - next_link_excn_dt: dayjs().add(Number(data.link_batch_time), 'minute').format('YYYY-MM-DD HH:mm:ss'), - mdfr: userUniqId, + next_link_excn_dt: dayjs().add(Number(data.link_cycle_mi), 'minute').format('YYYY-MM-DD HH:mm:ss'), + mdfr_id: userUniqId, }; insertLinkInfo(insertData); diff --git a/apps/cs/src/pages/linkParams/ui/link-params-dialog.tsx b/apps/cs/src/pages/linkParams/ui/link-params-dialog.tsx index cff72a030..9a3e2b87e 100644 --- a/apps/cs/src/pages/linkParams/ui/link-params-dialog.tsx +++ b/apps/cs/src/pages/linkParams/ui/link-params-dialog.tsx @@ -292,7 +292,7 @@ function LinkParamsDialog({ selectedLink, form, insertLinkVrbl, dialogOpen, onOp ( diff --git a/apps/cs/src/pages/linkParams/ui/link-params-page.tsx b/apps/cs/src/pages/linkParams/ui/link-params-page.tsx index 4c811ce13..319df80b2 100644 --- a/apps/cs/src/pages/linkParams/ui/link-params-page.tsx +++ b/apps/cs/src/pages/linkParams/ui/link-params-page.tsx @@ -112,7 +112,7 @@ function LinkParamsPage() { await LinkApi.insertLinkVrbl({ ...data, link_sn: selectedLink.linkSn, - mdfr: userUniqId, + mdfr_id: userUniqId, }), onSuccess: async () => { // 쿼리 무효화를 통한 최신 데이터 가져오기 @@ -128,7 +128,7 @@ function LinkParamsPage() { mutationFn: async (data: LinkVrblFormData) => await LinkApi.updateLinkVrblInfo({ ...data, link_sn: selectedLink.linkSn, - mdfr: userUniqId, + mdfr_id: userUniqId, }), onSuccess: async () => { await queryClient.invalidateQueries({ @@ -229,11 +229,11 @@ function LinkParamsPage() { if (linkVrblInfo) { let newData = { ...linkVrblInfo }; - if (linkVrblInfo.mdfr) { + if (linkVrblInfo.mdfr_id) { const userName = await fetchUserInfo(); newData = { ...linkVrblInfo, - mdfr: userName, + mdfr_id: userName, }; } diff --git a/apps/cs/src/pages/linkTable/ui/link-table-dialog.tsx b/apps/cs/src/pages/linkTable/ui/link-table-dialog.tsx index a566a7d95..be835872d 100644 --- a/apps/cs/src/pages/linkTable/ui/link-table-dialog.tsx +++ b/apps/cs/src/pages/linkTable/ui/link-table-dialog.tsx @@ -101,7 +101,7 @@ function LinkTableDialog({ dialogOpen, onOpenChange, form, tableInfo, insertLink col_seq: null, pk_yn: 'N', mdfcn_dt: '', - mdfr: '', + mdfr_id: '', }); } @@ -444,7 +444,7 @@ function LinkTableDialog({ dialogOpen, onOpenChange, form, tableInfo, insertLink ( diff --git a/apps/cs/src/pages/linkTable/ui/link-table-page.tsx b/apps/cs/src/pages/linkTable/ui/link-table-page.tsx index cbdf4627a..0394e7bf6 100644 --- a/apps/cs/src/pages/linkTable/ui/link-table-page.tsx +++ b/apps/cs/src/pages/linkTable/ui/link-table-page.tsx @@ -78,7 +78,7 @@ function LinkTablePage() { const { mutateAsync: insertLinkTableCol } = useMutation({ mutationFn: async (data: LinkTableColData) => await LinkApi.insertLinkTableCol({ ...data, - mdfr: userUniqId, + mdfr_id: userUniqId, }), onSuccess: async () => { if (selectedTableInfo) { @@ -104,7 +104,7 @@ function LinkTablePage() { const { mutateAsync: updateLinkTableCol } = useMutation({ mutationFn: async (data: LinkTableColData) => await LinkApi.updateLinkTableCol({ ...data, - mdfr: userUniqId, + mdfr_id: userUniqId, }), onSuccess: async () => { if (selectedTableInfo) { @@ -280,11 +280,11 @@ function LinkTablePage() { if (colInfo) { let newData = { ...colInfo }; - if (colInfo.mdfr) { + if (colInfo.mdfr_id) { const userName = await fetchUserInfo(); newData = { ...colInfo, - mdfr: userName, + mdfr_id: userName, }; } @@ -319,7 +319,7 @@ function LinkTablePage() { if (await confirm(confirmMsg)) { insertLinkTableInfo({ ...data, - mdfr: userUniqId, + mdfr_id: userUniqId, tbl_sn: (regTblInfo.count > 0) ? regTblInfo.tbl_sn : "", }); } @@ -330,7 +330,7 @@ function LinkTablePage() { updateLinkTableInfo({ ...data, tbl_sn: (regTblInfo.count > 0) ? regTblInfo.tbl_sn : "", - mdfr: userUniqId, + mdfr_id: userUniqId, }); } }); diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/SwtrmtFclt.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/SwtrmtFclt.tsx index 6d182072b..366218ec1 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/SwtrmtFclt.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/SwtrmtFclt.tsx @@ -1,88 +1,62 @@ 'use client' -import React, { useState,useEffect } from 'react' -import Modal from '@/components/ui/modal'; +import React, { useState } from 'react' import { VerticalTable } from '@/components/ui/verticalTable'; -import { UiTable } from '@common/business_components/ui'; +import { UiModal } from '@common/business_components/ui'; import { Button } from '@common/components/ui'; -import { useDcTree, flattenFolders, getFileDownload, addFileName, type FlattenedFolder} from '../../searchModal/searchModalReportApi'; +import ReportTab from '../../searchModal/reportTab'; // 하수처리시설 const headList = [ { id: "title", title: "하수도정비 기본계획보고서" }, ]; -const modalHeadList = [ - { id: "no", title: "번호" }, - { id: "year", title: "승인연도" }, - { id: "cad", title: '평면도' }, - { id: "fileNm", title: "파일명" }, - { id: "date", title: "입력시간" }, -] const SwtrmtFclt = ({ gisObject }: { gisObject: any }) => { const [isModalOpen, setIsModalOpen] = useState(false); - const [modalTitle, setModalTitle] = useState(''); - const [dcDataOne, setDcDataOne] = useState({}); - const openModal = (title: string) => { - setIsModalOpen(true); - setModalTitle(title) - }; - const closeModal = () => { - setIsModalOpen(false); - }; - const ctpvNm = gisObject?.ctpv_nm; - //const reportOriginData = useDcTree(ctpvNm); - const reportOriginData = useDcTree("강원특별자치도"); - useEffect(() => { - const fetch = async () => { - - if (!reportOriginData || reportOriginData.length === 0) { - return; - } - const reportData = flattenFolders(reportOriginData); - let filterReportData = [...reportData]; - if (gisObject && gisObject.sgg_nm !== undefined && gisObject.sgg_nm !== "") { // 시군구검색 - //const sggNm = gisObject?.sgg_nm; - const sggNm = "강릉시"; - filterReportData = filterReportData.filter(item => item.sigungu === sggNm); - } - addFileName(filterReportData).then(data => { - filterReportData = data.filter(item => item.filePath !== undefined && item.filePath !== ""); - if (filterReportData !== undefined && filterReportData.length > 0) { - setDcDataOne(filterReportData[0]); - } - }); - console.log("filterReportData", filterReportData); - }; - fetch(); - }, [reportOriginData]); - - // 하수도정비기본계획 filedownload 핸들러 - const handleFileDownloadClick = () => { - if (dcDataOne && dcDataOne !== undefined && dcDataOne.fileSn !== undefined && dcDataOne.fileSn !== "" - && dcDataOne.filePath !== undefined && dcDataOne.filePath !== "" && dcDataOne.fileName !== undefined && dcDataOne.fileName !== "" ) { - console.log("dcDataOne", dcDataOne); - getFileDownload(String(dcDataOne.fileSn), String(dcDataOne.fileName), String(dcDataOne.filePath)); - } - }; - //cad: , + const ctpvNm = gisObject?.ctpv_nm; + const sggNm = gisObject?.sgg_nm; + return (
- handleFileDownloadClick()}>다운로드 - }} headName='디지털 자료관 연계정보' tableData={[]} headlist={headList} pageSize={20} total={0} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void { - throw new Error("Function not implemented."); - }}> - {/* -
- -
-
*/} -
+ setIsModalOpen(true)}>조회 + }} + headName='디지털 자료관 연계정보' + tableData={[]} + headlist={headList} + pageSize={20} + total={0} + cellClick={undefined} + isEditable={true} + onPageChange={function (page: number): void { + throw new Error("Function not implemented."); + }} + /> + + } + useDirectRender={true} + /> + ) } diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx index f50595c10..85f614918 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx @@ -4,7 +4,7 @@ import Modal from "@/components/ui/modal"; import { VerticalTable } from "@/components/ui/verticalTable"; import { useGisContext } from "@common/business_components/ui/map/Gis"; import { Button } from "@common/components/ui"; -import { useEffect, useRef, useState, useCallback, useMemo } from "react"; +import { useEffect, useRef, useState, useMemo } from "react"; import { getLayerContentComponent } from "../common"; import { SwtrmtFcltCol, SwtrmtFcltCol2, SwtrmtPrcsZoneCol, SwtrmtPrcsZoneCol2, untrtSwgRainCol1, untrtSwgRainCol2, MsSwagePplCol, MsSwagePplCol2, ExclnSewagePumpCol, ExclnSewagePumpCol2, DrtwSewagePumpCol, DrtwSewagePumpCol2, MsSwageMhlCol, MsSwageMhlCol2, stofcbrCol, stofcbrCol2, MS_SWAGE_MHL_CODES, DRTW_SEWAGE_PUMP_USE_CODES } from "../common/table"; import InfilChart from './chart/infilChart'; @@ -61,9 +61,6 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, let featureLayer: { get: (key: string) => any }; // 피처가 속한 레이어를 찾기 위한 변수 layers.some((layer: any) => { const source = layer.getSource(); - // console.log('실행됨3 source', source); - // console.log('실행됨3 featureData', featureData); - // console.log('실행됨3 featureData.fid', featureData.fid); // if (source && featureData && featureData.fid) { // featureLayer = layer; // 피처가 속한 레이어 찾기 // } @@ -131,8 +128,6 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, setIsClicked(false); console.log('SearchModal data', featureData); const lyrTyNm = featureData.lyrTypeNm; - console.log('실행됨4 SearchModal 클릭 이벤트', lyrTyNm); - console.log('실행됨4 SearchModal 클릭 이벤트 featureData', featureData); if (featureData.lyrTypeNm === 'tb_ms_swage_ppl') { result = '하수관로'; setHeadlist(MsSwagePplCol2) @@ -187,7 +182,6 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, const selectedLayerName = getSelectedLayers(); // 레이어 이름 가져오기 setLayerNm(selectedLayerName); // 상태 업데이트 - // console.log('gis data', featureData.values_); let data; if (featureData['ol_uid']) data = featureData.values_; // Gis 레이어 클릭 이벤트일 때 else data = featureData; // SearchModal 클릭 이벤트일 때 @@ -271,11 +265,12 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, // 자체입력정보 API 엔드포인트 선택 let apiEndpoint = ''; - const getFeatureId = featureId.toString().split('.').pop() || null; - if (selectedLayerName === '하수관로') apiEndpoint = `v1/fcltppl/${getFeatureId}`; - else if (selectedLayerName === '우수토실') apiEndpoint = `v1/fcltstofcbr/${getFeatureId}`; - else if (selectedLayerName === '오수펌프' || selectedLayerName === '빗물펌프') apiEndpoint = `v1/fcltpump/${getFeatureId}`; - else if (selectedLayerName === '하수맨홀') apiEndpoint = `v1/fcltmhl/${getFeatureId}`; + const fcltMngNo = featureData?.values_?.fclt_mng_no || null; + const mngInstCd = featureData?.values_?.mng_inst_cd || null; + if (selectedLayerName === '하수관로') apiEndpoint = `v1/fcltppl/${fcltMngNo}/${mngInstCd}`; + else if (selectedLayerName === '우수토실') apiEndpoint = `v1/fcltstofcbr/${fcltMngNo}/${mngInstCd}`; + else if (selectedLayerName === '하수펌프장') apiEndpoint = `v1/fcltpump/${fcltMngNo}/${mngInstCd}`; + else if (selectedLayerName === '하수맨홀') apiEndpoint = `v1/fcltmhl/${fcltMngNo}/${mngInstCd}`; // API에서 상세 데이터 가져오기 if (apiEndpoint) { @@ -354,6 +349,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, else if (layerNm === '하수맨홀') apiEndpoint = 'v1/fcltmhl'; bodyData['fcltMngNo'] = featureData.values_.fclt_mng_no.toString() || null; + bodyData['mngInstCd'] = featureData.values_.mng_inst_cd.toString() || null; // 빈 키 제거 if (bodyData[""] !== undefined) delete bodyData[""]; diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/sewagePump.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/sewagePump.tsx index 2cde31b64..e19410d8d 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/sewagePump.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/sewagePump.tsx @@ -1,15 +1,15 @@ import { VerticalTable } from '@/components/ui/verticalTable'; import React, { useState, useEffect } from 'react' -// 빗물펌프 자체 입력 정보 +// 빗물펌프장 자체 입력 정보 const exclnInputColumn = [ { id: "swtrmtZoneCd", title: "배수구역", type: "input" }, { id: "mltvDtdvNm", title: "배수분구", type: "input" }, - { id: "dsgnFrqnc", title: "설계빈도(년)", type: "input" }, + { id: "designFrqYr", title: "설계빈도(년)", type: "input" }, ] -// 오수펌프 자체 입력 정보 +// 오수펌프장 자체 입력 정보 const drtwInputColumn = [ - { id: "swtrmt_zone_cd", title: "처리구역", type: "input" }, - { id: "mltv_dtdv_nm", title: "처리분구", type: "input" }, + { id: "swtrmtZoneCd", title: "처리구역", type: "input" }, + { id: "mltvDtdvNm", title: "처리분구", type: "input" }, ] // 하수펌프장 diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index b45b41936..3728332f0 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -7,7 +7,7 @@ import GisApp from "@common/business_components/ui/map/lib/gis.core"; import SearchModal from "./searchModal/searchModal"; import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup"; import { SearchBar } from "./searchBar/searchBar"; -import { WaterDepth } from "./waterDepth/waterDepth"; +import { WaterDepth, WATER_DEPTH_LAYER_TYPES, ORDERED_LAYERS } from "./waterDepth/waterDepth"; // 타입 정의 추가 export type LyrStngProps = { @@ -40,6 +40,8 @@ const Map = () => { const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태 const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터 + const [showWaterDepth, setShowWaterDepth] = useState(false); // WaterDepth 표시 여부 + const [isPlaying, setIsPlaying] = useState(false); useEffect(() => { @@ -118,6 +120,21 @@ const Map = () => { setIsSearchModalOpen(false); }; + // WaterDepth 표시 여부를 결정하는 함수 + const handleWaterDepthVisibility = useCallback((checkedIds: string[], checkedNodesData: any[]) => { + const waterDepthLayers = checkedNodesData.filter(node => + WATER_DEPTH_LAYER_TYPES.includes(node.data?.lyrTyNm) + ); + + const hasWaterDepthLayer = waterDepthLayers.length > 0; + setShowWaterDepth(hasWaterDepthLayer); + }, []); + + // 체크된 레이어 변경 처리 함수 + const handleCheckedLayersChange = useCallback((checkedIds: string[], checkedNodesData: any[]) => { + handleWaterDepthVisibility(checkedIds, checkedNodesData); + }, [handleWaterDepthVisibility]); + return (
@@ -135,10 +152,14 @@ const Map = () => { setIsSearchModalOpen={setIsSearchModalOpen} isOpen={isOpen} isSearchModalOpen={isSearchModalOpen} - onCheckedLayersChange={undefined} + onCheckedLayersChange={handleCheckedLayersChange} searchBar={SearchBar} - // waterDepth={WaterDepth} - waterDepth={undefined} + waterDepth={showWaterDepth ? WaterDepth : undefined} + isPlaying={isPlaying} + setIsPlaying={setIsPlaying as any} + targetLayerTypes={WATER_DEPTH_LAYER_TYPES as any} + orderedLayers={ORDERED_LAYERS as any} + checkInterval={2500} />
); diff --git a/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx index 5ca3f7059..a0627d4cf 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx @@ -4,9 +4,9 @@ import { Button, Label } from '@common/components'; import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox'; import { SelectBox, UiTable } from '@common/business_components/ui'; import LoadingOverlay from '@/components/ui/LoadingBar'; - -import { addFileName, flattenFolders, getFileDownload, useCityCd, useDcTree, useSggCd, reportGubuns } from './searchModalReportApi'; -import { type SelectDataType } from '../commonStore'; +import { addFileName, flattenFolders, getFileDownload, getDcTree, useDcTree, reportGubuns } from './searchModalReportApi'; +import useStore from '../../../store/useStore'; +import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox'; // 하수도정비 기본계획 보고서 @@ -15,18 +15,19 @@ export const reportTableHeadlist = [ { id: "sido", title: "시도" }, { id: "sigungu", title: "시군구" }, { id: "approvalYear", title: "승인년도" }, - { id: "plantZoneGugun", title: "구분" }, + { id: "plantZoneGubun", title: "구분" }, { id: "reportGubun", title: "보고서구분" }, { id: "fileName", title: "파일명" }, { id: "regDate", title: "등록일자" }, ]; - - -interface BasicTabProps { +interface ReportTabProps { open?: boolean; + isSearch?: boolean; + ctpvNm?: string; + sggNm?: string; } -const ReportTab: React.FC = ({ open }) => { +const ReportTab: React.FC = ({ open, isSearch = true, ctpvNm, sggNm }) => { const [isLoading, setIsLoading] = useState(false); const [isResetPage, setIsResetPage] = useState(false); @@ -36,24 +37,20 @@ const ReportTab: React.FC = ({ open }) => { const [tableCount, setTableCount] = useState(0); const [curPage, setCurPage] = useState(1); const [yearFilter, setYearFilter] = useState({ start: "", end: "" }); - const [ctpvCd, setCtpvCd] = useState('0'); // 하수정비기본계획 report - const [sigungu, setSigungu] = useState('0'); // 하수정비기본계획 report - const [filteredSiguns, setFilteredSiguns] = useState([{ val: '0', text: '전체', sido: '0' }]); - const [gubun, setGubun] = useState(""); // 하수정비기본계획 report - const [reportGubun, setReportGubun] = useState("0"); // 하수정비기본계획 report - + const [ctpvCd, setCtpvCd] = useState(0); + const [sggCd, setSggCd] = useState(0); + const [reportGubun, setReportGubun] = useState("0"); + // const [gubun, setGubun] = useState(""); + + const { ctpv, sgg } = useStore(); // 하수정비기본계획 report - const sidos = useCityCd(); - const siguns = useSggCd(); const reportOriginData = useDcTree(""); //:Folder[] - const dynamicHeadlist: { id: string, title: string }[] = reportTableHeadlist; - useEffect(() => { // filterParams 초기화 - setCtpvCd('0'); - setSigungu('0'); + setCtpvCd(0); + setSggCd(0); setReportGubun('0'); setYearFilter({ start: "", end: "" }); setShowTable(false); @@ -64,27 +61,36 @@ const ReportTab: React.FC = ({ open }) => { setIsResetSelection(true); // 선택 초기화 }, [open]); + useEffect(() => { + let newCtpvCd = ctpv.find((n) => n.name === ctpvNm)?.id || 0; + let newSggCd = sgg.find((n) => n.name === sggNm)?.id || 0; + + setCtpvCd(newCtpvCd); + setSggCd(newSggCd); + }, [ctpvNm, sggNm]); + + // ctpvCd, sggCd가 업데이트된 후에 fetchFilteredData 실행 + useEffect(() => { + if (!isSearch && ctpvCd && sggCd) { + fetchFilteredData(); + } + }, [reportOriginData, ctpvCd, sggCd, isSearch]); + // 페이지 변경 시마다 데이터 fetch useEffect(() => { // showTable이 true일 때만 fetch (최초 렌더 방지) setIsResetPage(false); - setIsResetSelection(true); // 선택 초기화 - if (showTable) fetchFilteredData(); - }, [curPage]); - - const handleCtpvCdChange = (val: string | number) => { // 하수도정비기본계획.시도 - const sidoOption = sidos.find((n) => n.val === val); - setCtpvCd(String(val)); - if (val !== '0') { - const filterSiguns = siguns.filter(item => item.sido === sidoOption?.text); - filterSiguns.unshift({ val: '0', text: '전체', sido: '0' }); - setFilteredSiguns(filterSiguns); - } - setSigungu("0"); - setGubun(""); - setYearFilter({ start: "", end: "", }); - } + setIsResetSelection(true); + if (showTable) fetchFilteredData(); + + setIsResetSelection(false); + }, [curPage]); + + const handleCtpvCdChange = (sidoId: number) => { + setCtpvCd(sidoId); + setSggCd(0); + } // 조회 버튼 클릭 시 1페이지로 이동 및 데이터 초기화 const handleSearch = () => { @@ -96,7 +102,7 @@ const ReportTab: React.FC = ({ open }) => { }; // 하수도정비기본계획 테이블 cellClick 핸들러 - const handleReportCellClick = (index: number | undefined ) => { + const handleReportCellClick = (index: number | undefined) => { console.log(index); if (typeof index === "number" && tableData[index]) { const reportData = tableData[index]; @@ -107,23 +113,30 @@ const ReportTab: React.FC = ({ open }) => { } } }; - + // 검색 API 호출 함수 const fetchFilteredData = async () => { setIsLoading(true); // 로딩 시작 setIsResetSelection(true); // 선택 초기화 - + try { - //const reportOriginData = await getDcTree('경기도'); const reportData = flattenFolders(reportOriginData); let filterReportData = reportData; - if (ctpvCd !== "0") { // 시도검색 - const sidoOption = sidos.find((n) => n.val === ctpvCd); - filterReportData = filterReportData.filter(item => item.sido === sidoOption?.text); + console.log("필터링 전 보고서 데이터:", filterReportData); + console.log('ctpv, sgg, ctpvCd, sggCd', ctpv, sgg, ctpvCd, sggCd); + + if (ctpvCd) { // 시도검색 + console.log('시도 실행되나'); + const sidoOption = ctpv.find((n) => String(n.id) === String(ctpvCd)); + console.log('sidoOption', sidoOption) + filterReportData = filterReportData.filter(item => item.sido === sidoOption?.name); + console.log('시도 필터링 후:', filterReportData); } - if (sigungu !== "0") { // 시군구검색 - const sigunguOption = filteredSiguns.find((n) => n.val === sigungu); - filterReportData = filterReportData.filter(item => item.sigungu === sigunguOption?.text); + if (sggCd) { // 시군구검색 + console.log('시군구 실행되나'); + const sigunguOption = sgg.find((n) => String(n.id) === String(sggCd)); + filterReportData = filterReportData.filter(item => item.sigungu === sigunguOption?.name); + console.log('시군구 필터링 후:', filterReportData); } if (reportGubun !== "0") { // 보고서구분검색 const reportGubunOption = reportGubuns.find((n) => n.val === reportGubun); @@ -139,14 +152,16 @@ const ReportTab: React.FC = ({ open }) => { return (item.approvalYear) ? item.approvalYear >= yearFilter.start + "년" && item.approvalYear <= yearFilter.end + "년" : null; }); } - if (gubun !== "") { // 구분검색 - filterReportData = filterReportData.filter(item => (item.plantZoneGugun) ? String(item.plantZoneGugun).includes(String(gubun)) : null); - } + // if (gubun !== "") { // 구분검색 (추후 사용할 수도 있을 것 같아 남겨둠) + // filterReportData = filterReportData.filter(item => (item.plantZoneGubun) ? String(item.plantZoneGubun).includes(String(gubun)) : null); + // } console.log('report count:', filterReportData.length); + setTableCount(filterReportData.length || 0); + filterReportData = filterReportData.slice((curPage - 1) * 10, curPage * 10); // paging filterReportData = filterReportData?.map((item, index) => ({ ...item, no: (curPage - 1) * 10 + index + 1 })); - //const fileList = await getDcFile("CLS20250530000041"); + addFileName(filterReportData).then(data => { filterReportData = data; setTableData(filterReportData || []); @@ -159,69 +174,70 @@ const ReportTab: React.FC = ({ open }) => { setTableData([]); } finally { setIsLoading(false); // 로딩 종료 + setIsResetSelection(false); } }; return ( <> -
-
- - - - setSigungu(String(val))} - selectContentClass="z-[101]" - /> -
-
- - setReportGubun(String(val))} - selectContentClass="z-[101]" labelClass='whitespace-nowrap text-xl text-gray-600 pr-2' - /> - {/* 승인년도 필터 */} - setYearFilter({ ...yearFilter, start: range.start, end: range.end })} - startLabel="승인년도" endLabel="" - /> - {/* 조회 버튼 */} -
- +
+ {isSearch && ( + <> +
+ { handleCtpvCdChange(sidoId) }} + onSigunguChange={setSggCd} + setIsLoading={setIsLoading} + /> +
+ + )} +
+ + setReportGubun(String(val))} + selectContentClass="z-[101]" labelClass='whitespace-nowrap text-xl text-gray-600 pr-2' + /> + {/* 승인년도 필터 */} + setYearFilter({ ...yearFilter, start: range.start, end: range.end })} + startLabel="승인년도" endLabel="" + /> + {/* 조회 버튼 */} +
+ +
-
-
- {/* 테이블2 영역 (가로/세로 스크롤 가능) */} -
- - { - return (<>조회 결과 ({tableCount.toLocaleString('ko-KR')})건); - })() - } - tableData={tableData} - headlist={dynamicHeadlist} - pageSize={10} - total={tableCount} - cellClick={handleReportCellClick} - onPageChange={setCurPage} - resetPageNo={isResetPage} - resetSelection={isResetSelection} - /> +
+ {/* 테이블2 영역 (가로/세로 스크롤 가능) */} +
+ + { + return (<>조회 결과 ({tableCount.toLocaleString('ko-KR')})건); + })() + } + tableData={tableData} + headlist={reportTableHeadlist} + pageSize={10} + total={tableCount} + cellClick={handleReportCellClick} + onPageChange={setCurPage} + resetPageNo={isResetPage} + resetSelection={isResetSelection} + /> +
-
); }; diff --git a/apps/sn/app/(map-layout)/map/searchModal/searchModalReportApi.ts b/apps/sn/app/(map-layout)/map/searchModal/searchModalReportApi.ts index 9610b4264..4a5df24a1 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/searchModalReportApi.ts +++ b/apps/sn/app/(map-layout)/map/searchModal/searchModalReportApi.ts @@ -1,39 +1,35 @@ -import {useState, useEffect} from 'react'; +import { useState, useEffect } from 'react'; import { getDcData } from '@/components/lib/api'; -const ADDR_URL = process.env.NEXT_PUBLIC_ADDR_URL; export type Folder = { - clsfSystmSn: string; - upClsfSystmSn: string; - clsfSystmNm: string; - children: Folder[]; - }; - + clsfSystmSn: string; + upClsfSystmSn: string; + clsfSystmNm: string; + children: Folder[]; +}; export type FlattenedFolder = { - [key: string]: string|number|undefined; -}; - -type SelectDataType = { - text: string; - val: string; - sido?: string; // 시도 코드 (선택 사항) + [key: string]: string | number | undefined; +}; +export type SelectDataType = { + text: string; + val: string; + sido?: string; // 시도 코드 (선택 사항) } - type ReportDataFile = { - no : number; - fileTitle : string; - regDt : string; - fileSn : string; - filePath : string; + no: number; + fileTitle: string; + regDt: string; + fileSn: string; + filePath: string; } const isYear = (value: string): boolean => { return /^[0-9]{4}년$/.test(value); }; +export const reportGubuns = [{ val: '0', text: '전체' }, { val: '1', text: '보고서' } + , { val: '2', text: '요약보고서' }, { val: '3', text: '부록' }, { val: '4', text: '기타' }]; -export const reportGubuns = [{val: '0', text: '전체'}, {val: '1', text: '보고서'} - , {val: '2', text: '요약보고서'} , {val: '3', text: '부록'}, {val: '4', text: '기타'}]; - + /** * Flatten a hierarchical folder structure into a flat array. * @param data - The hierarchical folder data to flatten. @@ -41,18 +37,20 @@ export const reportGubuns = [{val: '0', text: '전체'}, {val: '1', text: '보 */ export function flattenFolders(data: Folder[]): FlattenedFolder[] { const result: FlattenedFolder[] = []; - const exceptNodeNm = ["부분","통합","전체"]; - const addNodeNm = ["보고서","요약보고서","부록","기타"]; // level5 - const columnNms: { [key: number]: string } = { 0: "root", 1:"sido", 2:"sigungu", 3:"approvalYear" - ,4:"plantZoneGugun", 5:"reportGubun", 7:"fileName", 8:"regDate" }; + const exceptNodeNm = ["부분", "통합", "전체"]; + const addNodeNm = ["보고서", "요약보고서", "부록", "기타"]; // level5 + const columnNms: { [key: number]: string } = { + 0: "root", 1: "sido", 2: "sigungu", 3: "approvalYear" + , 4: "plantZoneGubun", 5: "reportGubun", 7: "fileName", 8: "regDate" + }; function traverse( node: Folder, - path: { [key: string]: string| number }, + path: { [key: string]: string | number }, level: number ) { if (level === 2 && isYear(node.clsfSystmNm)) { - level++; + level++; } if (level === 4 && addNodeNm.includes(node.clsfSystmNm)) { level++; // 레벨을 증가시켜 하위값에 추가되도록 함. @@ -79,7 +77,7 @@ export function flattenFolders(data: Folder[]): FlattenedFolder[] { } } - if (data && data.length > 0 && data[0].clsfSystmNm == "기본계획") { // data가 비어있지 않고 children이 있는 경우에만 처리 + if (data && data.length > 0 && data[0]?.clsfSystmNm == "기본계획") { // data가 비어있지 않고 children이 있는 경우에만 처리 data.forEach((root) => traverse(root, {}, 0)); } else { data.forEach((root) => traverse(root, {}, 1)); @@ -87,11 +85,11 @@ export function flattenFolders(data: Folder[]): FlattenedFolder[] { return result; } -export const getDcTree = async (clsfSystmNm:string) => { +export const getDcTree = async (clsfSystmNm: string) => { const result = await getDcData("/api/v1/dc/clsf?clsfSystmNm=" + clsfSystmNm); return result; }; -export const useDcTree = (clsfSystmNm:string) => { +export const useDcTree = (clsfSystmNm: string) => { const [data, setData] = useState([]); useEffect(() => { @@ -109,31 +107,31 @@ export const useDcTree = (clsfSystmNm:string) => { return data; }; -export const getDcFile = async (clsfSystmSn:string) => { - const queryString = 'clsfSystmSn='+clsfSystmSn; +export const getDcFile = async (clsfSystmSn: string) => { + const queryString = 'clsfSystmSn=' + clsfSystmSn; const result = await getDcData(`/api/v1/dc/file${queryString ? `?${queryString}` : ''}`); return result; }; -export const addFileName = async (data:FlattenedFolder[]) => { +export const addFileName = async (data: FlattenedFolder[]) => { const fileReportData = await Promise.all( data.map(async (item) => { - const filenameArr:ReportDataFile[] = await getDcFile(String(item.clsfSystmSn)); + const filenameArr: ReportDataFile[] = await getDcFile(String(item.clsfSystmSn)); const f = filenameArr[0]; - const result = {...item, fileName:f?.fileTitle, regDate:f?.regDt, fileSn:f?.fileSn, filePath:f?.filePath}; - + const result = { ...item, fileName: f?.fileTitle, regDate: f?.regDt, fileSn: f?.fileSn, filePath: f?.filePath }; + return result; }) ); return fileReportData; }; -export const getDcFileprcs = async (fileSn:string) => { - const queryString = 'fileSn='+fileSn; +export const getDcFileprcs = async (fileSn: string) => { + const queryString = 'fileSn=' + fileSn; const result = await getDcData(`/api/v1/dc/fileprcs${queryString ? `?${queryString}` : ''}`); return result; }; -export const getDcFileDownload = async (filePath:string) => { - const queryString = 'filePath='+ encodeURIComponent(filePath); +export const getDcFileDownload = async (filePath: string) => { + const queryString = 'filePath=' + encodeURIComponent(filePath); const response = await fetch(`/dc-service/api/v1/dc/file/download?${queryString}`, { method: 'GET', }); @@ -143,7 +141,7 @@ export const getDcFileDownload = async (filePath:string) => { const blob = await response.blob(); // PDF, image 등은 blob으로 받아야 함 return blob; }; -export const fileDownload = (data:Blob, fileName:string) => { // Blob데이터 임시link로 연결 +export const fileDownload = (data: Blob, fileName: string) => { // Blob데이터 임시link로 연결 const url = window.URL.createObjectURL(data); const link = document.createElement('a'); link.href = url; @@ -153,80 +151,9 @@ export const fileDownload = (data:Blob, fileName:string) => { // Blob데이터 link.remove(); window.URL.revokeObjectURL(url); } -export const getFileDownload = async (fileSn:string, fileName:string, filePath:string) => { - if (filePath === "") alert("filePath 정보 없으므로 다운로드 불가:filesn"+ fileSn); +export const getFileDownload = async (fileSn: string, fileName: string, filePath: string) => { + if (filePath === "") alert("filePath 정보 없으므로 다운로드 불가:filesn" + fileSn); const blobData = await getDcFileDownload(filePath); // 실제 다운로드 API fileDownload(blobData, fileName); } -// 시도 데이터 -export const getCityCd = async (): Promise => { - try { - const response = await fetch(`${ADDR_URL}/api/v1/addr/city`, { method: "GET", headers: { accept: "application/json" } }); - if (!response.ok) throw new Error(`시도 데이터 오류: ${response.status}`); - const res = await response.json(); - const city = res.data; - let citys:SelectDataType[] = city.map((item: {adstDstrCd:string, sido:string}) => ({ - val: item.adstDstrCd, - text: item.sido, - })); - citys = citys.sort((a:SelectDataType, b:SelectDataType) => a.text.localeCompare(b.text)); - citys.unshift({ val: '0', text: '전체' }); - return citys; - } catch (error) { - console.error('시도 데이터를 가져오는데 실패했습니다:', error); - return [{ val: '0', text: '전체', sido: '0' }]; - } - }; - -export const useSggCd = () => { - const [data, setData] = useState([]); - - useEffect(() => { - const fetch = async () => { - const result = await getSggCd(); - if (result) { - setData(result); - } - console.log("useSggCd호출"); - }; - fetch(); - }, []); - return data; -}; - -// 시군구 데이터 -export const getSggCd = async (): Promise => { - try { - const response = await fetch(`${ADDR_URL}/api/v1/addr/sgg`, { method: "GET", headers: { accept: "application/json" } }); - if (!response.ok) throw new Error(`시군구 데이터 오류: ${response.status}`); - const res = await response.json(); - const sgg = res.data; - const sggCd = sgg.map((item: {adstDstrCd:string, sido:string, gugun:string}) => ({ - val: item.adstDstrCd, - sido: item.sido, - text: item.gugun, - })); - sggCd.unshift({ val: '0', text: '전체' }); // "전체" 옵션 추가 - return sggCd; - } catch (error) { - console.error('시군구 데이터를 가져오는데 실패했습니다:', error); - return [{ val: '0', text: '전체', sido: '0' }]; - } -}; - -export const useCityCd = () => { - const [data, setData] = useState([]); - - useEffect(() => { - const fetch = async () => { - const result = await getCityCd(); - if (result) { - setData(result); - } - console.log("useCityCd호출"); - }; - fetch(); - }, []); - return data; -}; diff --git a/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx b/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx index 39e375a4b..78849bda2 100644 --- a/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx +++ b/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx @@ -3,46 +3,80 @@ import React, { useRef, useEffect, useState } from "react"; import { Label } from "@common/components/ui/label"; -export function WaterDepth({ onClose }: { onClose?: () => void }) { - const [isPlaying, setIsPlaying] = useState(false); +export const WATER_DEPTH_LAYER_TYPES: string[] = [ + 'tb_ms_flood_dngr_map', + 'tb_ms_sbmrg_dngr_map' +]; +// 지정된 순서에 따른 lyr_sn 배열 +export const ORDERED_LAYERS: string[] = [ + '27', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 30년 빈도 + '28', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 50년 빈도 + '3', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 80년 빈도 + '4', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 100년 빈도 + '59', // 도시침수지도(tb_ms_flood_dngr_map) - 30년 빈도 + '60', // 도시침수지도(tb_ms_flood_dngr_map) - 50년 빈도 + '58' // 도시침수지도(tb_ms_flood_dngr_map) - 80년 빈도 +]; + +interface WaterDepthProps { + onClose?: () => void; + isPlaying: boolean; + setIsPlaying: (isPlaying: boolean) => void; + totalSteps?: number; + currentStep?: number; +} + +export function WaterDepth({ + onClose, + isPlaying, + setIsPlaying, + totalSteps = 0, + currentStep = 0, +}: WaterDepthProps) { + + useEffect(() => { + if (setIsPlaying) setIsPlaying(isPlaying); + }, [isPlaying, setIsPlaying]); + + const handlePlayToggle = () => { + setIsPlaying(!isPlaying); + }; + + // 진행률 계산 (0-100%) + const progressPercentage = totalSteps > 0 ? (currentStep / totalSteps) * 100 : 100; + return ( <> -
-
+
+
-
-
+
+
+
+
+
침 수 심
- - - - - - + {/* 코드: N330 */} + {/* 코드: N331 */} + {/* 코드: N332 */} + {/* 코드: N333 */} + {/* 코드: N334 */}
-
+
diff --git a/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx b/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx index 66b8095db..43797b50a 100644 --- a/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx @@ -1,5 +1,5 @@ 'use client' -import { deleteData, getData } from '@/components/lib/api' +import { deleteData, getData, useCookieAuth } from '@/components/lib/api' import { UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui' import { zodResolver } from '@hookform/resolvers/zod' import { useState, useEffect } from 'react' @@ -48,6 +48,7 @@ const tableColumn = [ ] const Result = () => { + const AUTH = useCookieAuth(); const form = useForm({ resolver: zodResolver(formSchema), @@ -123,6 +124,7 @@ const Result = () => { "ctpvCd": ctpvCd === 0 ? null : ctpvCd, "sggCd": sggCd === 0 ? null : sggCd, "swageTmplntCd": processedSwageTmplntCd, + "auth": AUTH, }; console.log("Search DTO:", inftwtAnlsSearchDto); diff --git a/apps/sn/app/(with-base-layout)/ii/result/page.tsx b/apps/sn/app/(with-base-layout)/ii/result/page.tsx index 7c7e4721a..07acff774 100644 --- a/apps/sn/app/(with-base-layout)/ii/result/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/result/page.tsx @@ -1,5 +1,5 @@ 'use client' -import { deleteData, getData } from '@/components/lib/api' +import { deleteData, getData, useCookieAuth } from '@/components/lib/api' import { UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui' import { zodResolver } from '@hookform/resolvers/zod' import { useState, useEffect } from 'react' @@ -46,6 +46,8 @@ const tableColumn = [ ] const Result = () => { + const AUTH = useCookieAuth(); + const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { @@ -119,6 +121,7 @@ const Result = () => { "ctpvCd": ctpvCd === 0 ? null : ctpvCd, "sggCd": sggCd === 0 ? null : sggCd, "swageTmplntCd": processedSwageTmplntCd, + "auth": AUTH, }; console.log("Search DTO:", inftwtAnlsSearchDto); diff --git a/apps/sn/app/components/ui/govSidoSigunguSearchForm.tsx b/apps/sn/app/components/ui/govSidoSigunguSearchForm.tsx deleted file mode 100644 index 1fe3d7bae..000000000 --- a/apps/sn/app/components/ui/govSidoSigunguSearchForm.tsx +++ /dev/null @@ -1,360 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { SelectBox } from '@common/business_components/ui/select'; -import { Label } from '@radix-ui/react-label'; - -export type comCodeState = { - code: { - isInit: boolean; - chBuCd: SelectDataType[] - rainFcltCd: SelectDataType[]; - bdstUsgCd: SelectDataType[]; - cityCd: SelectDataType[]; - sggCd: SelectDataType[]; - rvbsnCd: SelectDataType[]; - mdmbsnCd: SelectDataType[]; - lgbsnCd: SelectDataType[]; - lclgvrnmntCd: EnvPartSelectDataType[]; - orgwtKndCd: SelectDataType[]; - yn: SelectDataType[]; - buildingType: { - RST: string, - RNB: string, - HTB: string - } - }; -}; - -type SelectDataType = { - text: string; - val: string; - sido?: string; // 시도 코드 (선택 사항) -} - -type EnvPartSelectDataType = SelectDataType & { - adstDliStrCd: string, -} - -type SearchFormLeftProps = { - isShowPart?: boolean | undefined; - children: React.ReactNode; - selectSidoData?: SelectDataType[] | []; - selectSigunData?: SelectDataType[] | []; - selectRegionData?: SelectDataType[] | []; - selectUnitWaterShedData?: SelectDataType[] | []; - selectMiddleRegionData?: SelectDataType[] | []; - selectEnvPartData?: EnvPartSelectDataType[] | []; - selectEnvPartSidoData?: SelectDataType[] | []; - selectEnvPartSigunData?: SelectDataType[] | []; - - setRtnPartValue?: (value: string) => void; - setRtnSidoValue?: (value: string) => void; - setRtnSigunValue?: (value: string) => void; - setRtnRegionValue?: (value: string) => void; - setRtnUnitWaterShedValue?: (value: string) => void; - setRtnMiddleRegionValue?: (value: string) => void; - setRtnEnvPartValue?: (value: string) => void; - setRtnEnvPartSidoValue?: (value: string) => void; - setRtnEnvPartSigunValue?: (value: { val: string, text: string }) => void; // setRtnEnvPartSigunValue prop 타입을 객체로 변경 - resetTrigger?: boolean; -} - - - -function GovSidoSigunguSearchForm({ isShowPart = false, children, selectSidoData = [], selectSigunData = [], selectRegionData = [], selectUnitWaterShedData = [], selectMiddleRegionData = [], selectEnvPartData = [], setRtnPartValue, setRtnSidoValue, setRtnSigunValue, setRtnRegionValue, setRtnUnitWaterShedValue, setRtnMiddleRegionValue, setRtnEnvPartValue, setRtnEnvPartSidoValue, setRtnEnvPartSigunValue, resetTrigger = false }: SearchFormLeftProps) { - //환경청별 시도 시군구 - const [envPartValue, setEnvPartValue] = useState('0'); - const [envPartSidoValue, setEnvPartSidoValue] = useState('0'); // 환경청 선택 시 시/도 값 - const [envPartSigunValue, setEnvPartSigunValue] = useState('0'); // 환경청 선택 시 시/군/구 값 - - const [filteredEnvPartSigunDataSelected, setFilteredEnvPartSigunDataSelected] = useState([]); - - // API로부터 가져온 전체 데이터 목록 (필터링 전) - const [allEnvPartOptions, setAllEnvPartOptions] = useState([]); - const [allSidoOptions, setAllSidoOptions] = useState([]); - const [allSigunOptions, setAllSigunOptions] = useState([]); - - // 조회된 조건에 맞는 환경청 시도 시군 (SelectBox에 실제 사용될 데이터) - const [filteredEnvPartData, setFilteredEnvPartData] = useState([{ text: "전체", val: '0', adstDliStrCd: '', sido: '0' }]); - const [filteredEnvPartSidoData, setFilteredEnvPartSidoData] = useState([{ text: "전체", val: '0', adstDliStrCd: '', sido: '0' }]); - const [filteredEnvPartSigunData, setFilteredEnvPartSigunData] = useState([{ text: "전체", val: '0', sido: '0' }]); - - // 시군구 데이터 - const getSggCd = async (): Promise => { - try { - const response = await fetch('http://con.mind-one.co.kr:11082/api/v1/addr/sgg', { method: "GET", headers: { accept: "application/json" } }); - if (!response.ok) throw new Error(`시군구 데이터 오류: ${response.status}`); - const res = await response.json(); - const sgg = res.data; - const sggCd = sgg.map((item: any) => ({ - val: item.adstDstrCd, - sido: item.sido, - text: item.gugun, - })); - sggCd.unshift({ val: '0', text: '전체' }); // "전체" 옵션 추가 - return sggCd; - } catch (error) { - console.error('시군구 데이터를 가져오는데 실패했습니다:', error); - return [{ val: '0', text: '전체', sido: '0' }]; - } - }; - - // 시도 데이터 - const getCityCd = async (): Promise => { - try { - const response = await fetch('http://con.mind-one.co.kr:11082/api/v1/addr/city', { method: "GET", headers: { accept: "application/json" } }); - if (!response.ok) throw new Error(`시도 데이터 오류: ${response.status}`); - const res = await response.json(); - const city = res.data; - const cityCd = city.map((item: any) => ({ - val: item.adstDstrCd, - text: item.sido, - })); - cityCd.unshift({ val: '0', text: '전체' }); - return cityCd; - } catch (error) { - console.error('시도 데이터를 가져오는데 실패했습니다:', error); - return [{ val: '0', text: '전체', sido: '0' }]; - } - }; - - // 환경청 데이터 - const getEnvPartCd = async (): Promise => { - try { - const response = await fetch('http://con.mind-one.co.kr:11082/api/v1/addr/govArea', { method: "GET", headers: { accept: "application/json" } }); - if (!response.ok) throw new Error(`환경청 데이터 오류: ${response.status}`); - const res = await response.json(); - const lclgvrnmnt = res.data.filter((item: { lclgvrnmntNm: string; }) => { - // item.lclgvrnmntNm이 null이나 undefined가 아닌지 확인 후 endsWith 사용 - return item.lclgvrnmntNm && item.lclgvrnmntNm.endsWith("환경청"); - }); - const lclgvrnmntCd = lclgvrnmnt.map((item: any) => ({ - val: item.lclgvrnmntCd, - text: item.lclgvrnmntNm, - adstDliStrCd: item.adstDliStrCd || '' - })); - lclgvrnmntCd.unshift({ val: '0', text: '전체', adstDliStrCd: '' }); - return lclgvrnmntCd; - } catch (error) { - console.error('환경청 데이터를 가져오는데 실패했습니다:', error); - return [{ val: '0', text: '전체', adstDliStrCd: '', sido: '0' }]; - } - }; - - useEffect(() => { - const loadInitialData = async () => { - const [envParts, sidos, siguns] = await Promise.all([ - getEnvPartCd(), - getCityCd(), - getSggCd() - ]); - - setAllEnvPartOptions(envParts.filter(item => item.val !== '0')); - setFilteredEnvPartData(envParts); - - setAllSidoOptions(sidos.filter(item => item.val !== '0')); - // 초기 filteredEnvPartSidoData는 모든 시/도를 포함 (환경청 "전체" 선택 시) - setFilteredEnvPartSidoData(sidos.map(s => ({ ...s, adstDliStrCd: '' }))); - - - setAllSigunOptions(siguns.filter(item => item.val !== '0')); - // 초기 filteredEnvPartSigunData는 모든 시/군/구를 포함 (환경청 "전체", 시/도 "전체" 선택 시) - setFilteredEnvPartSigunData(siguns); - - // 만약 isShowPart가 true이고, partValue가 'G'(환경청별)로 기본 설정되어 있다면, - // 초기 envPartValue ('0')에 대한 시도/시군구 목록을 설정합니다. - initializeCityDataByEnvPartSido(envPartValue); // envPartValue 기본값 '0' - }; - - if (isShowPart) { - loadInitialData(); - } - }, [isShowPart]); // isShowPart가 변경될 때 데이터를 다시 로드 - - // 환경청 세팅 - 시도 - const initializeCityDataByEnvPartSido = (envPartCd: string) => { - const foundEnvPart = allEnvPartOptions.find((item: EnvPartSelectDataType) => item.val === envPartCd); - console.log('foundEnvPart:', foundEnvPart); - - let relevantSidos: SelectDataType[] = []; // 기본값을 빈 배열로 설정 - let relevantSigunsForSidoDerivation: SelectDataType[] = []; // 기본값을 빈 배열로 설정 - - if (envPartCd === '0') { // "전체" 환경청 선택 시 - relevantSidos = [...allSidoOptions]; - relevantSigunsForSidoDerivation = [...allSigunOptions]; - } else if (foundEnvPart && foundEnvPart.adstDliStrCd) { // 특정 환경청 선택 & 해당 환경청에 adstDliStrCd 정보가 있는 경우 - // adstDliStrCd가 시도 코드를 포함한다고 가정 (주석 참고) - const cityCodesInEnvPart = foundEnvPart.adstDliStrCd.toString().split('|').join('').split(',').filter(c => c); - console.log('cityCodesInEnvPart:', cityCodesInEnvPart); - // 1. allSigunOptions에서 val이 cityCodesInEnvPart에 포함된 데이터의 sido만 추출 - const relevantSigunSidos = Array.from( - new Set( - allSigunOptions - .filter(sigungu => cityCodesInEnvPart.includes(sigungu.val)) - .map(sigungu => sigungu.sido) - ) - ); - console.log('relevantSigunTexts:', relevantSigunSidos); - - // 2. relevantSigunsForSidoDerivation은 해당 sido만을 가진 배열로 반환 - relevantSidos = allSidoOptions.filter(sido => relevantSigunSidos.includes(sido.text)); - console.log('relevantSidos:', relevantSidos); - - // 해당 시도들에 속하는 시군구들 (가나다순 오름차순 정렬) - relevantSigunsForSidoDerivation = allSigunOptions - .filter(sigun => { - const sigunSidoPrefix = sigun.val.substring(0, 2); - return relevantSidos.some(s => s.val === sigunSidoPrefix); - }) - .sort((a, b) => a.text.localeCompare(b.text, 'ko')); - } - // 특정 환경청이 선택되었으나, foundEnvPart가 없거나 adstDliStrCd 정보가 없는 경우, - // relevantSidos와 relevantSigunsForSidoDerivation은 초기 빈 배열 상태를 유지합니다. - - setFilteredEnvPartSidoData([ - { text: "전체", val: '0', adstDliStrCd: '', sido: '0' }, - ...relevantSidos.map((item: SelectDataType) => ({ text: item.text, val: item.val, adstDliStrCd: '', sido: '0' })) - ]); - - setFilteredEnvPartSigunDataSelected(relevantSigunsForSidoDerivation); - - // 시/도 선택이 "전체"('0')로 초기화되므로, 해당 시/군/구 목록도 업데이트 - initializeCityDataByEnvPartSigun('0'); - } - - // 환경청 세팅 - 시군구 - const initializeCityDataByEnvPartSigun = (selectedSidoInEnvPart: string) => { - let sigunsToList: SelectDataType[]; - if (selectedSidoInEnvPart === '0') { // 선택된 시도가 "전체"이면 - // filteredEnvPartSigunDataSelected는 이미 해당 환경청에 맞는 시군구들로 필터링 되어있음 - sigunsToList = [...filteredEnvPartSigunDataSelected]; - } else { - sigunsToList = filteredEnvPartSigunDataSelected.filter((item: SelectDataType) => item.val.substring(0, 2) === selectedSidoInEnvPart); - } - setFilteredEnvPartSigunData([ - { text: "전체", val: '0' }, - ...sigunsToList - ]); - } - - const onSelectValue = (val: string, type: string) => { - switch (type) { - // 환경청 세팅 - case 'envPart': - setEnvPartValue(val); - setRtnEnvPartValue?.(val); - initializeCityDataByEnvPartSido(val); - setEnvPartSidoValue('0'); // 시도 "전체"로 초기화 - setEnvPartSigunValue('0'); // 시군구 "전체"로 초기화 - // 부모에게도 초기화된 값 전달 (text 포함) - if (setRtnEnvPartSidoValue) { - const sidoDefault = filteredEnvPartSidoData.find(item => item.val === '0') || { val: '0', text: '전체' }; - // setRtnEnvPartSidoValue(sidoDefault.val); // 이전 방식: 값만 전달 - // TODO: 만약 시도 값도 객체로 전달해야 한다면 아래와 같이 수정 - // setRtnEnvPartSidoValue({ val: sidoDefault.val, text: sidoDefault.text }); - } - if (setRtnEnvPartSigunValue) { - const sigunDefault = filteredEnvPartSigunData.find(item => item.val === '0') || { val: '0', text: '전체' }; - setRtnEnvPartSigunValue({ val: sigunDefault.val, text: sigunDefault.text }); - } - break; - case 'envPartSido': // 환경청별 - 시도 선택 - setEnvPartSidoValue(val); - setRtnEnvPartSidoValue?.(val); - initializeCityDataByEnvPartSigun(val); - setEnvPartSigunValue('0'); // 시군구 "전체"로 초기화 - if (setRtnEnvPartSigunValue) { - const sigunDefault = filteredEnvPartSigunData.find(item => item.val === '0') || { val: '0', text: '전체' }; - setRtnEnvPartSigunValue({ val: sigunDefault.val, text: sigunDefault.text }); - } - break; - case 'envPartSigun': // 환경청별 - 시군구 선택 - setEnvPartSigunValue(val); - const selectedSigun = filteredEnvPartSigunData.find(item => item.val === val); - if (setRtnEnvPartSigunValue && selectedSigun) { - setRtnEnvPartSigunValue({ val: selectedSigun.val, text: selectedSigun.text }); - } else if (setRtnEnvPartSigunValue && val === '0') { // "전체"를 명시적으로 처리 (find 실패 대비) - setRtnEnvPartSigunValue({ val: '0', text: '전체' }); - } - break; - } - } - - // 리셋 함수 - const resetAllValues = () => { - - setEnvPartValue('0'); - setEnvPartSidoValue('0'); - setEnvPartSigunValue('0'); - - // 필터링된 목록들도 초기 "전체" 또는 전체 목록으로 리셋 - setFilteredEnvPartSigunData([{ text: "전체", val: '0', sido: '0' }, ...allSigunOptions]); - - setFilteredEnvPartData([{ text: "전체", val: '0', adstDliStrCd: '', sido: '0' }, ...allEnvPartOptions]); - setFilteredEnvPartSidoData([{ text: "전체", val: '0', adstDliStrCd: '', sido: '0' }, ...allSidoOptions.map(s => ({ ...s, adstDliStrCd: '' }))]); - setFilteredEnvPartSigunData([{ text: "전체", val: '0', sido: '0' }, ...allSigunOptions]); - setFilteredEnvPartSigunDataSelected(allSigunOptions); - - - setRtnPartValue?.('A'); - setRtnSidoValue?.('0'); - setRtnSigunValue?.('0'); - setRtnRegionValue?.('0'); - setRtnUnitWaterShedValue?.('0'); - setRtnMiddleRegionValue?.('0'); - setRtnEnvPartValue?.('0'); - setRtnEnvPartSidoValue?.('0'); - setRtnEnvPartSigunValue?.({ val: '0', text: '전체' }); - }; - - // 리셋 트리거 변경 감지 - useEffect(() => { - if (resetTrigger) { - resetAllValues(); - // 리셋 후 트리거를 false로 설정하는 로직은 부모 컴포넌트에서 처리해야 할 수 있습니다. - // 여기서는 resetTrigger props가 true일 때 리셋을 수행합니다. - } - }, [resetTrigger]); - - return ( - isShowPart ? ( -
- <> - - onSelectValue(val.toString(), 'envPart')} - value={envPartValue} - selectClass={'rounded-lg bg-white truncate'} - /> - - onSelectValue(val.toString(), 'envPartSido')} - value={envPartSidoValue} - selectClass={'rounded-lg bg-white truncate'} - /> - - onSelectValue(val.toString(), 'envPartSigun')} - value={envPartSigunValue} - selectClass={'rounded-lg bg-white truncate'} - /> - - {children} -
- ) : ( -
- {children} -
- ) - ) -} - -export { GovSidoSigunguSearchForm }; \ No newline at end of file diff --git a/packages/common/business_components/src/ui/dialog_ui/index.tsx b/packages/common/business_components/src/ui/dialog_ui/index.tsx index df7fffd3a..0858a66b8 100644 --- a/packages/common/business_components/src/ui/dialog_ui/index.tsx +++ b/packages/common/business_components/src/ui/dialog_ui/index.tsx @@ -9,6 +9,7 @@ type ModalType = { noHeaderYn?: 'N' | 'Y'; noFooterYn?: 'N' | 'Y'; headerTitle?: string; + headerTitleClass?: string; footerContent?: React.ReactNode; useDirectRender?: boolean; } @@ -21,6 +22,7 @@ export function UiModal({ noHeaderYn = 'N', noFooterYn = 'N', headerTitle, + headerTitleClass, footerContent, useDirectRender = false }: ModalType) { @@ -31,7 +33,7 @@ export function UiModal({ {noHeaderYn === 'N' && ( - + {headerTitle} diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index 41774fc59..7c92c9f59 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -116,13 +116,20 @@ function Gis({ setIsSearchModalOpen, onCheckedLayersChange, searchBar: SearchBar, - waterDepth: WaterDepth, - height:height="h-[calc(100vh-10rem)]", + waterDepth: WaterDepth, + isPlaying = false, + setIsPlaying = () => { }, + targetLayerTypes = [], + orderedLayers = [], + checkInterval = 3000, + height: height = "h-[calc(100vh-10rem)]", }) { const mapRef = useRef(); const [mapObject, setMapObject] = useState({}); const [tabVisible, setTabVisible] = useState(true); const [layerNm, setLayerNm] = useState(""); + const [totalSteps, setTotalSteps] = useState(0); + const [currentStep, setCurrentStep] = useState(0); // const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 // const [featureData, setFeatureData] = useState(null) // 선택된 피쳐 데이터 // const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태 @@ -275,6 +282,12 @@ function Gis({ [onCheckedLayersChange], ); + // 진행률 변경 핸들러 + const handleProgressChange = useCallback((total, current) => { + setTotalSteps(total); + setCurrentStep(current); + }, []); + return ( <> + {typeof WaterDepth === "function" && ( +
+ setIsOpen(false)} + isPlaying={isPlaying} + setIsPlaying={setIsPlaying} + totalSteps={totalSteps} + currentStep={currentStep} + /> +
+ )} + {/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} {isOpen && !isSearchModalOpen && ( - {typeof WaterDepth === "function" && setIsOpen(false)} />}
@@ -343,6 +373,7 @@ function Gis({