diff --git a/apps/sn/app/components/lib/api/index.ts b/apps/sn/app/components/lib/api/index.ts index d7db93f77..fa2727ea0 100644 --- a/apps/sn/app/components/lib/api/index.ts +++ b/apps/sn/app/components/lib/api/index.ts @@ -1,4 +1,4 @@ -const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ''; +const BASE_URL = 'http://con.mind-one.co.kr:11083/sewerage-service/api'; export const postData = async (url: string, data: object): Promise => { const options: RequestInit = { diff --git a/apps/sn/app/map/components/popupComponent/MsSwagePpl.tsx b/apps/sn/app/map/components/popupComponent/MsSwagePpl.tsx index 1db284c8e..1fc0cef80 100644 --- a/apps/sn/app/map/components/popupComponent/MsSwagePpl.tsx +++ b/apps/sn/app/map/components/popupComponent/MsSwagePpl.tsx @@ -1,4 +1,4 @@ -import { VerticalTable } from '@/components/ui/verticalTable'; +import { VerticalTable } from '../../../components/ui/verticalTable'; import React, { useState } from 'react' import InputSection from '../InputSection'; import { handleInputChange } from '../common'; diff --git a/apps/sn/app/map/components/popupComponent/SwtrmtFclt.tsx b/apps/sn/app/map/components/popupComponent/SwtrmtFclt.tsx index eef14ea5e..0d1c3ef2e 100644 --- a/apps/sn/app/map/components/popupComponent/SwtrmtFclt.tsx +++ b/apps/sn/app/map/components/popupComponent/SwtrmtFclt.tsx @@ -1,5 +1,5 @@ -import Modal from '@/components/ui/modal'; -import { VerticalTable } from '@/components/ui/verticalTable'; +import Modal from '../../../components/ui/modal'; +import { VerticalTable } from '../../../components/ui/verticalTable'; import { UiTable } from '@common/business_components/ui'; import { Button, TableCell } from '@common/components/ui'; import React, { useState } from 'react' diff --git a/apps/sn/app/map/components/popupComponent/chart/infilChart.tsx b/apps/sn/app/map/components/popupComponent/chart/infilChart.tsx index ba0b70455..06c5b5fec 100644 --- a/apps/sn/app/map/components/popupComponent/chart/infilChart.tsx +++ b/apps/sn/app/map/components/popupComponent/chart/infilChart.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react' import { type ChartConfig, MainChart, CHART_FORM } from "@common/business_components"; -import Modal from '@/components/ui/modal'; +import Modal from '../../../../components/ui/modal'; import { Button } from '@common/components/ui'; type ModalOpenProp = { isModalOpen: boolean, diff --git a/apps/sn/app/map/components/popupComponent/sewagePump.tsx b/apps/sn/app/map/components/popupComponent/sewagePump.tsx index 5776df5b2..79103a40e 100644 --- a/apps/sn/app/map/components/popupComponent/sewagePump.tsx +++ b/apps/sn/app/map/components/popupComponent/sewagePump.tsx @@ -1,4 +1,4 @@ -import { VerticalTable } from '@/components/ui/verticalTable'; +import { VerticalTable } from '../../../components/ui/verticalTable'; import React from 'react' const inputColumn = [ { id: "swtrmt_zone_cd", title: "처리구역", type: "select", options: ["구역1", "구역2"] }, diff --git a/apps/sn/app/map/components/popupComponent/smh.tsx b/apps/sn/app/map/components/popupComponent/smh.tsx index e693a28ce..39920232b 100644 --- a/apps/sn/app/map/components/popupComponent/smh.tsx +++ b/apps/sn/app/map/components/popupComponent/smh.tsx @@ -1,5 +1,5 @@ // 하수맨홀 -import { VerticalTable } from '@/components/ui/verticalTable'; +import { VerticalTable } from '../../../components/ui/verticalTable'; import React from 'react' const inputColumn = [ { id: "guyeok", title: "처리구역", type: "select", options: ["구역1", "구역2"] }, diff --git a/apps/sn/app/map/components/popupComponent/stormWaterPump.tsx b/apps/sn/app/map/components/popupComponent/stormWaterPump.tsx index eefef8b3b..172022f31 100644 --- a/apps/sn/app/map/components/popupComponent/stormWaterPump.tsx +++ b/apps/sn/app/map/components/popupComponent/stormWaterPump.tsx @@ -1,4 +1,4 @@ -import { VerticalTable } from '@/components/ui/verticalTable'; +import { VerticalTable } from '../../../components/ui/verticalTable'; import React from 'react' const inputColumn = [ { id: "swtrmt_zone_cd", title: "처리구역", type: "input" }, diff --git a/apps/sn/app/map/page.tsx b/apps/sn/app/map/page.tsx index e1a90d5f1..d8bab06bb 100644 --- a/apps/sn/app/map/page.tsx +++ b/apps/sn/app/map/page.tsx @@ -106,6 +106,7 @@ const Map = () => { setIsSearchModalOpen={setIsSearchModalOpen} isOpen={isOpen} isSearchModalOpen={isSearchModalOpen} + onCheckedLayersChange={undefined} /> ); diff --git a/apps/wr/app/map/featureClickEventPopup.tsx b/apps/wr/app/map/featureClickEventPopup.tsx new file mode 100644 index 000000000..8048d0997 --- /dev/null +++ b/apps/wr/app/map/featureClickEventPopup.tsx @@ -0,0 +1,201 @@ +'use client' +import { getData, putData } from "../../../sn/app/components/lib/api"; +import Modal from "../../../sn/app/components/ui/modal"; +import { VerticalTable } from "../../../sn/app/components/ui/verticalTable"; +import { useGisContext } from "@common/business_components/ui/map/Gis"; +import { Button } from "@common/components/ui"; +import { useEffect, useRef, useState } from "react"; +import { getLayerContentComponent } from "../../../sn/app/map/components/common"; +// TODO: +const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, featureData: any }) => { + const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; }; + // const featureId = featureData.getId(); // 피처의 id 가져오기 + const featureId = typeof featureData.getId === 'function' + ? featureData.getId() + : featureData.fid; + const [layerNm, setLayerNm] = useState(""); // 레이어 이름 상태 + const [tableData, setTableData] = useState(null); // 피처 데이터 + const [mntrngsData, setMntrngsData] = useState(false); // 수량/수질 측정 정보 + const [headlist, setHeadlist] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태 + const [headlist2, setHeadlist2] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태 + const [buttonChk, setButtonChk] = useState(true) + const [fetchData, setfetchData] = useState(); // api 호출여부 + const contentRef = useRef(null); + const [isModalOpen, setIsModalOpen] = useState(false); // 모달 열기 상태 + const [chkMainSsc, setChkMainSsc] = useState(false); // 중점우수토실 여부 + + // 피쳐데이터의 현재 레이어 이름 가져오는 함수 + const getSelectedLayers = () => { + let result = null; + const layers = gisMap.getLayers().getArray(); // gisMap에서 모든 레이어 가져오기 + let featureLayer: { get: (key: string) => any } = { get: (key: string) => null }; // 피처가 속한 레이어를 찾기 위한 변수 + layers.forEach((layer: any) => { + const source = layer.getSource(); + if (source && source.hasFeature && featureData + && typeof featureData.getId === 'function' && source.hasFeature(featureData)) { + featureLayer = layer; // 피처가 속한 레이어 찾기 + } + }); + // SearchModal 클릭 이벤트와 Gis 클릭 이벤트 구분하기 + if (featureLayer.get('lyrNm') === null) { + if (featureData.lyrTypeNm === 'tb_il_swage_prcs_fclt') result = '하수처리시설'; + else if (featureData.lyrTypeNm === 'tb_ms_swage_prcs_zone') result = '하수처리구역'; + else if (featureData.lyrTypeNm === 'om_dt_untrt_swg_rain') result = '중점우수토실'; + } + else result = featureLayer.get('lyrNm'); + + return result; + // return featureLayer.get('lyrNm'); // 레이어 이름 출력 + }; + + useEffect(() => { + const fetchData = async () => { + if (featureId) { + const selectedLayerName = getSelectedLayers(); // 레이어 이름 가져오기 + setLayerNm(selectedLayerName); // 상태 업데이트 + const layerId = featureId.split('.')[0] + let data = await getData(`v1/fclts/${featureId}?lyrTypeNm=${layerId}`); + setfetchData(data) + switch (selectedLayerName) { + case '하수처리시설': + setButtonChk(false) + break; + case '하수처리구역': + setButtonChk(false) + break; + case '중점우수토실': + setButtonChk(false) + setChkMainSsc(true) + + // 데이터 전처리: 합쳐진 필드 추가 + data = data ? { + ...data, + discharge_pipe_spec: `직경: ${data.pipe_diameter || '-'} / 높이: ${data.pipe_hg || '-'} / 길이: ${data.pipe_lt || '-'}` + } : {}; + + // 수량/수질측정 정보 데이터 호출 + const additionalResponse = await getData(`v1/onsemiro/swgRain/${data.adst_dstr_cd}/${data.manage_no}/mntrngs`); + setMntrngsData(additionalResponse.content); + break; + case '우수토실': + setButtonChk(true) + break; + case '하수관로': + setButtonChk(true) + break; + case '오수펌프': + setButtonChk(true) + break; + case '빗물펌프': + setButtonChk(true) + break; + case '하수맨홀': + setButtonChk(true) + break; + default: + break; + } + setTableData(data); + }; + } + fetchData(); + }, [featureId]); + + // 저장 시 모달 띄우기 + const handleSaveClick = () => { + setIsModalOpen(true); // 모달 열기 + }; + + const handleModalSave = () => { + setIsModalOpen(false); // 모달 닫기 + btnClickEvent(); // 저장 이벤트 실행 + }; + + const handleModalClose = () => { + setIsModalOpen(false); // 모달 닫기 + }; + + const btnClickEvent = async () => { + const bodyData: Record = {} + if (buttonChk && contentRef.current) { + const inputElements = contentRef.current.querySelectorAll('input'); + const selectElements = contentRef.current.querySelectorAll('select'); + // 카멜케이스 변환 함수 + const toCamelCase = (str: string) => + str.replace(/_([a-z])/g, (_, char) => char.toUpperCase()); + // select 태그의 값 가져오기 + selectElements.forEach((select) => { + const camelCaseId = toCamelCase(select.id); + bodyData[camelCaseId] = select.value; + }); + // input 태그의 값 가져오기 + inputElements.forEach((input) => { + const camelCaseId = toCamelCase(input.id); + bodyData[camelCaseId] = input.value; + }); + if (layerNm === '하수관로') bodyData['lyrTypeNm'] = 'tb_ms_swage_ppl' + else if (layerNm === '우수토실') bodyData['lyrTypeNm'] = 'tb_ms_stofcbr' + else if (layerNm === '오수펌프') bodyData['lyrTypeNm'] = 'tb_ms_swage_pmstn' + else if (layerNm === '빗물펌프') bodyData['lyrTypeNm'] = 'tb_ms_swage_pmstn' + else if (layerNm === '하수맨홀') bodyData['lyrTypeNm'] = 'tb_ms_swage_mnhl' + bodyData['fid'] = featureId + const data = await putData(`v1/fclts/${featureId}`, bodyData); + if (data.status == 200) { + alert('저장되었습니다') + setTableData(data) + setfetchData(data) + } + } + } + type ContentComponentProps = { + featureData: any; + chkMainSsc: boolean; + additionalData: any; + }; + const ContentComponent = layerNm ? getLayerContentComponent(layerNm) as React.ComponentType : null; + return ( + + ) +} +export default FeatureClickEventPopup; \ No newline at end of file diff --git a/apps/wr/app/map/htdflPrfmncSttnPopup.tsx b/apps/wr/app/map/htdflPrfmncSttnPopup.tsx new file mode 100644 index 000000000..8d1bec486 --- /dev/null +++ b/apps/wr/app/map/htdflPrfmncSttnPopup.tsx @@ -0,0 +1,621 @@ +"use client"; + +import { + Form, + FormField, + FormUi, + SelectBox, +} from "@common/business_components/ui"; +import { Button, Input } from "@common/components/ui"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { useWrStore } from "@/app/store"; +import { selectClass } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; +import { + hdtflZod, + type customHtdflBdst, +} from "@/app/store/ruseFcltMng/fcltReg/htdflReg"; +import { useEffect } from "react"; +import BdstReg from "@/components/ui/form/fcltReg/bdstReg/bdstReg"; +import { + formatCommaNumber, + handleCommaNumberChange, +} from "@/components/lib/func/commom"; +import { formH1Class } from "@/components/lib/class/comClass"; + +export default function HtdflPrfmncSttnPopup({ + htdflForm, + isModal, +}: { + htdflForm: customHtdflBdst; + isModal: boolean; +}) { + const { code, htdflBdstUpdate, htdflBdstReg } = useWrStore((state) => state); + + const defaultValues: z.infer = hdtflZod.parse({}); + + const form = useForm>({ + resolver: zodResolver(hdtflZod), + defaultValues: defaultValues, + }); + + const onSubmit = (values: z.infer) => { + if (isModal) { + htdflBdstReg(values, '41'); + } else { + htdflBdstUpdate(values, '41'); + } + }; + + + const cleanHtdflData = (raw: any): z.infer => { + return { + ...raw, + frstArchPrmsnDt: new Date(raw?.frstArchPrmsnDt || null), + archChgPrmsnDt: raw?.archChgPrmsnDt + ? new Date(raw?.archChgPrmsnDt) + : null, + chgBuilding: (raw?.chgBuilding || []).map((item: any) => ({ + ...item, + archChgPrmsnDt: + item?.archChgPrmsnDt === null + ? new Date() + : new Date(item?.archChgPrmsnDt), + bdstChgNm: item?.bdstChgNm ?? "", // null → "" + })), + reuseBuilding: (raw?.reuseBuilding || []).map((item: any) => ({ + ...item, + instlCmptnDt: new Date(item?.instlCmptnDt), + htdflObj: { + ...item?.htdflObj, + }, + })), + }; + }; + + useEffect(() => { + console.log("form errors:", form.formState.errors); + }, [form.formState.errors]); + + useEffect(() => { + if (hdtflZod) form.reset(cleanHtdflData(htdflForm)); + }, [htdflForm]); + + return ( +
+
+ + {/*
*/} +
+
+

온배수 시설 정보

+
+
+ + {isModal && htdflForm != undefined ? : <>} + {isModal ? ( +

+ 온배수 시설 정보{" "} +

+ ) : ( + <> + )} + +
+
+ ( + + + + )} + /> +
+ +
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ ( + + + + )} + /> +
+
+
+

+ 온배수 시설 운영현황 +

+
+ +
+
+ ( + + { + field.onChange(Number(e.target.value)); + }} + readOnly={true} + /> + + )} + /> +
+
+ ( + + + + )} + /> +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ ( + + + + )} + /> +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ ( + + + + )} + /> +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ ( + + + + )} + /> +
+
+ +
+

+ 요구수질 +

+
+ +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+ +
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+ +
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+ +
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+ +
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+ +
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+ +
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ + +
+ ); +} diff --git a/apps/wr/app/map/htdflSttn/page.tsx b/apps/wr/app/map/htdflSttn/page.tsx new file mode 100644 index 000000000..e74d055d8 --- /dev/null +++ b/apps/wr/app/map/htdflSttn/page.tsx @@ -0,0 +1,160 @@ +"use client"; + +import Gis from "@common/business_components/ui/map/Gis"; +import { useEffect, useRef, useState, useCallback } from "react"; +import FeatureClickEventPopup from "../featureClickEventPopup"; +import { mapService } from "../service"; +import { MapSearchBar } from "../mapSearchBar"; +import NewWindowPopup from "../newWindowPopup"; +import GisApp from "@common/business_components/ui/map/lib/gis.core"; + +const Map = () => { + const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정 + const [selectedFeature, setSelectedFeature] = useState(null); // 선택된 피처 상태 + const gisMaps = useRef(null); + const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 + const [isSearchModalOpen, setIsSearchModalOpen] = useState(true); // SearchModal 열림 상태 + const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터 + const [checkedLayers, setCheckedLayers] = useState([]); + const [checkedLayersData, setCheckedLayersData] = useState([]); + + const fetchData = async () => { + try { + const response = await mapService.getLyrStngs(); + setTreeData(response); // API 응답 저장 + } catch (error) { + console.error("데이터 불러오기 실패:", error); + } + }; + + // 팝업 닫기 핸들러를 useCallback으로 메모이제이션 + const handlePopupClose = useCallback(() => { + setIsOpen(false); + setFeatureData(null); + }, []); + + // 컴포넌트 마운트 시 1회 실행 + useEffect(() => { + fetchData(); + }, []); + + // 체크된 레이어 정보를 받는 핸들러 + const handleCheckedLayersChange = (checkedIds: string[], checkedNodesData: any[]) => { + setCheckedLayers(checkedIds); + setCheckedLayersData(checkedNodesData); + }; + + // SearchModal, GisContent 등에서 호출 + const onFeatureSelect = (data: any) => { + let map = gisMaps.current; + if (!map) { + console.warn("지도가 초기화되지 않았습니다."); + return; + } + + // 항상 같은 select 객체 사용 + const select = GisApp.Module.select as any; + if (!select) return; + + // 1. 기존 선택 초기화 + select.resetSelectedFeature(); + + // 2. data가 OpenLayers Feature 인스턴스인지 확인 + let foundFeature = null; + if ( + data && + typeof data.getGeometry === "function" && + typeof data.setStyle === "function" + ) { + // 이미 Feature 인스턴스라면 그대로 사용 + foundFeature = data; + } else { + // 아니면 featureId로 맵에서 Feature 인스턴스를 찾아서 사용 + const featureId = data.fid || data.ftrIdn || data.id; + map + .getLayers() + .getArray() + .forEach((layer: any) => { + const source = layer.getSource?.(); + if (source?.getFeatureById) { + const feature = source.getFeatureById(String(featureId).trim()); + if (feature) foundFeature = feature; + } + }); + } + + // 3. 피처 선택 + if (foundFeature) { + let geometry = foundFeature.getGeometry(); + if (geometry) { + let extent = geometry.getExtent(); + + map.getView().fit(extent, { + duration: 300, + padding: [50, 50, 50, 50], + maxZoom: 16, + constrainResolution: true, + nearest: true, + }); + } + select.selectFeature(foundFeature, true); + } else { + console.warn("OpenLayers Feature를 찾을 수 없습니다:", data); + } + + setFeatureData(data); + setIsOpen(true); + setIsSearchModalOpen(false); + }; + + // MapSearchBar 래퍼 컴포넌트 + // const CustomMapSearchBar = (props: any) => { + // return ( + // + // ); + // }; + + return ( +
+ ( + // + // ) + // } + detailView={(props: any) => ( + + )} + onFeatureSelect={undefined} + setIsOpen={setIsOpen} + setFeatureData={setFeatureData} + featureData={featureData} + setIsSearchModalOpen={setIsSearchModalOpen} + isOpen={isOpen} + isSearchModalOpen={isSearchModalOpen} + onCheckedLayersChange={handleCheckedLayersChange} + /> +
+ ); +}; + +export default Map; diff --git a/apps/wr/app/map/mapSearchBar.tsx b/apps/wr/app/map/mapSearchBar.tsx new file mode 100644 index 000000000..b24b5d83a --- /dev/null +++ b/apps/wr/app/map/mapSearchBar.tsx @@ -0,0 +1,414 @@ +// "use client" + +// import { useWrStore } from "@/app/store"; +// import SearchInputText from "@/components/ui/searchInput/searchInput"; +// import type { EnvPartSelectDataType, ReturnType, SelectDataType } from "@common/business_components/ui"; +// import { SelectBox } from "@common/business_components/ui"; +// import { Button, Input, Label } from "@common/components/ui"; +// import React, { useEffect, useMemo, useRef, useState } from "react"; +// import { MapSearchFormLeft } from "./mapSearchForm"; + + +// // 건물 검색 타입 정의 +// interface BuildingInfo { +// bdstInfoPk: number; +// ctpvCd: number; +// sggCd: number; +// daddr: string; +// bdstNm: string; +// frstArchPrmsnDt: string; +// archArea: number; +// ctpvNm: string | null; +// sggNm: string | null; +// lat: number | null; +// lot: number | null; +// regDt: string; +// rgtr: string | null; +// mdfcnDt: string; +// mdfr: string | null; +// } + +// type SearchBaseType = { +// searchType: string; +// selectSidoData?: SelectDataType[] | []; +// selectSigunData?: SelectDataType[] | []; +// selectRegionData?: SelectDataType[] | []; +// selectUnitWaterShedData?: SelectDataType[] | []; +// selectMiddleRegionData?: SelectDataType[] | []; +// selectEnvPartData?: EnvPartSelectDataType[] | []; +// onFeatureSelect: () => void; +// btnClass?: string; +// ref?: React.RefObject; +// isAdmin?: boolean; +// leftClassName?: string; +// rightClassName?: string; +// showQueryButton?: boolean; +// showInitButton?: boolean; +// showApplyButton?: boolean; +// checkedLayers?: string[]; +// checkedLayersData?: any[]; +// } + +// const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`; + +// const YEARS = Array.from({ length: 100 }, (_, i) => `${2025 - i}`); + +// interface SearchModalProps { +// open: boolean; +// onOpenChange: (open: boolean) => void; +// onFeatureSelect: (row: any) => void; +// } + +// interface SelectedDataTypeWithSggNm { +// text: string; +// val: string; +// sggNm: string; +// } + +// export function MapSearchBar({ +// searchType, +// selectSidoData, +// selectSigunData, +// selectRegionData, +// selectUnitWaterShedData, +// selectMiddleRegionData, +// selectEnvPartData, +// btnClass, +// onFeatureSelect, +// isAdmin = false, +// leftClassName, +// rightClassName, +// showQueryButton = true, +// showInitButton = true, +// showApplyButton = true, +// checkedLayers = [], +// checkedLayersData = [], +// }: SearchBaseType) { + +// // 로딩바 +// const [isLoading, setIsLoading] = useState(false); +// // 상세 정보 조회를 위한 상태 추가 +// const [popupFeature, setPopupFeature] = useState(null); +// const [isChHead, setIsChHead] = useState(false); +// const ref = useRef({} as ReturnType); + +// // zustand store에서 건물 검색 함수와 데이터 가져오기 +// const { +// code, +// bdst, +// findBdst, +// wrMap, +// buildingSearchRes, +// buildingNmSearch, +// buildingNmSearchMultiple +// } = useWrStore((state) => state); + +// const [selectedBuilding, setSelectedBuilding] = useState(null); +// const [buildingSearchText, setBuildingSearchText] = useState(""); +// const [buildingList, setBuildingList] = useState([]); + +// // 전체 목록 +// const [partValue, setPartValue] = useState("A"); +// const [sidoValue, setSidoValue] = useState("0"); +// const [sigunValue, setSigunValue] = useState("0"); +// const [regionValue, setRegionValue] = useState("0"); +// const [untitWaterShedValue, setUntitWaterShedValue] = useState("0"); +// const [middleRegionValue, setMiddleRegionValue] = useState("0"); +// const [envPartValue, setEnvPartValue] = useState("0"); +// const [resetTrigger, setResetTrigger] = useState(false); +// const [sidoText, setSidoText] = useState("0"); +// const [sigunText, setSigunText] = useState("0"); +// // 공급처 후보지 조회 필터 목록 +// const [selectedSido, setSelectedSido] = useState({ text: "전체", val: '0' }); +// const [selectedSigun, setSelectedSigun] = useState({ text: "전체", val: '0' }); +// const [selectedFclt, setSelectedFclt] = useState({ text: "전체", val: '0', sggNm: '전체' }); // 시설명 선택 상태 +// // 전체 시설 +// let allFclt: SelectedDataTypeWithSggNm[] = [{ text: "전체", val: '0', sggNm: '전체' }]; +// const [selectedDong, setSelectedDong] = useState({ text: "전체", val: '0', sggNm: '전체' }); // 시설명 선택 상태 // 행정동 + +// const filteredSigunData = useMemo(() => { +// if (!selectedSido || selectedSido.val === '0' || !selectedSido.val) return [{ text: "전체", val: '0' }]; +// return [ +// { text: "전체", val: '0' }, +// ...code.sggCd.filter((item: { val: string; }) => item.val.substring(0, 2) === selectedSido.val.substring(0, 2)) +// ]; +// }, [selectedSido]); + +// const filteredFcltData = useMemo(() => { +// if (!selectedSigun || selectedSigun.val === '0' || !selectedSigun.val) return [{ text: "전체", val: '0', sggNm: '전체' }]; +// return [ +// { text: "전체", val: '0', sggNm: '전체' }, +// ...allFclt.filter(item => item.sggNm === selectedSido.text) +// ]; +// }, [selectedSigun]); + +// const filteredDong = useMemo(() => { +// if (!selectedSigun || selectedSigun.val === '0' || !selectedSigun.val) return [{ text: "전체", val: '0', sggNm: '전체' }]; +// return [ +// { text: "전체", val: '0', sggNm: '전체' }, +// // ...code.dong.filter(item => item.sggNm === selectedSido.text) +// ]; +// }, [selectedSigun]); + +// // 조회 함수 +// const queryVal = () => { +// // 46 : 하수처리시설 +// // 47 : 하수재이용시설 +// // 48 : 빗물이용시설 +// // 49 : 중수도시설 +// // 50 : 온배수시설 +// console.log("조회버튼 클릭: ", checkedLayers); +// if (checkedLayers && checkedLayers.includes('48')) { +// // searchBuildings +// } +// } + +// // 초기화 함수 +// const initVal = () => { +// setPartValue("A"); +// setSidoValue("0"); +// setSigunValue("0"); +// setRegionValue("0"); +// setUntitWaterShedValue("0"); +// setMiddleRegionValue("0"); +// setEnvPartValue("0"); +// setSidoText("전체"); +// setSigunText("전체"); +// setSelectedSido({ text: "전체", val: '0' }); +// setSelectedSigun({ text: "전체", val: '0' }); +// setSelectedFclt({ text: "전체", val: '0', sggNm: '전체' }); +// setSelectedDong({ text: "전체", val: '0', sggNm: '전체' }); +// setResetTrigger(true); +// }; + +// // 적용 버튼 +// const applyVal = () => { +// onFeatureSelect(); +// }; + +// useEffect(() => { +// if (resetTrigger) { +// setResetTrigger(false); +// } +// }, [resetTrigger]); + +// // 시설 조회 +// useEffect(() => { +// const fetchData = async () => { +// setIsLoading(true); +// if (!selectedSigun || selectedSigun.val === '0' || !selectedSigun.val) { +// allFclt = ([{ text: "전체", val: '0', sggNm: '전체' }]); +// setIsLoading(false); +// return; +// } + +// try { +// const queryParamsFacility = new URLSearchParams({ +// lyrTypeNm: 'tb_il_swage_prcs_fclt', +// page: '0', +// size: '100000', +// sort: 'string', +// }); +// const cqlFiltersFacility: string[] = []; +// if (selectedSigun.text && selectedSigun.val !== '0') { +// cqlFiltersFacility.push(`sgg_nm ILIKE '%${selectedSigun.text}%'`); +// } +// if (cqlFiltersFacility.length > 0) { +// queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and ')); +// } +// console.log("BaseLayout API URL for Facilities:", `v1/fclts?${queryParamsFacility.toString()}`); + +// const facilityRes = await fetch(`${API_URL}/v1/fclts?${queryParamsFacility.toString()}`, { +// method: "GET", +// headers: { accept: "application/json" } +// }).then(res => res.json()); + +// const facilities = (facilityRes.data || []).map((item: any) => ({ +// text: item.swtrmtFcltNm, +// val: item.fid, +// sggNm: item.sggNm +// })); +// allFclt = (facilities); + +// } catch (error) { +// console.error("BaseLayout: 지역/시설 데이터 조회 중 오류 발생:", error); +// allFclt = ([{ text: "전체", val: '0', sggNm: '전체' }]); +// } finally { +// setIsLoading(false); +// } +// }; + +// fetchData(); +// }, [selectedSigun]); + +// useEffect(() => { +// if (ref && ref.current) { +// const customRef = ref.current; +// customRef.partValue = partValue; +// customRef.sidoValue = sidoValue; +// customRef.sigunValue = sigunValue; +// customRef.regionValue = regionValue; +// customRef.unitWaterShedValue = untitWaterShedValue; +// customRef.middleRegionValue = middleRegionValue; +// customRef.envPartValue = envPartValue; +// customRef.sidoText = selectedSido.text; +// customRef.sigunText = selectedSigun.text; +// } +// }, [partValue, sidoValue, sigunValue, regionValue, untitWaterShedValue, middleRegionValue, envPartValue, ref]); + +// // buildingSearchText가 변경될 때 API 호출 +// useEffect(() => { +// if (buildingSearchText && buildingSearchText.length > 1) { +// if (checkedLayers && checkedLayers.length > 0) { +// // 다중 레이어 검색 +// // buildingNmSearchMultiple(checkedLayers, selectedSigun.val || "", buildingSearchText); +// buildingNmSearch("RNB", selectedSigun.val, buildingSearchText); +// } else { +// // 단일 레이어 검색 +// // buildingNmSearch(checkedLayers[0], selectedSigun.val, buildingSearchText); +// } +// } else { +// // 검색 텍스트가 짧으면 리스트 초기화 +// if (checkedLayers && checkedLayers.length > 0) { +// // buildingNmSearchMultiple(checkedLayers, selectedSigun.val || "", ""); +// } else { +// buildingNmSearch("RNB", selectedSigun.val || "", ""); +// } +// } +// }, [buildingSearchText]); + +// return ( +//
+//
+//
+// {searchType == "공급처 후보지 조회" && ( +// <> +// { +// const selected = code.cityCd.find((item: { val: string | number; }) => (item.val === val)); +// setSelectedSido(selected || { text: "전체", val: '0' }); +// setSelectedSigun({ text: "전체", val: '0' }); +// }} +// value={selectedSido.val} +// selectClass={'w-32'} +// /> +// { +// const selected = filteredSigunData.find(item => (item.val === val)); +// setSelectedSigun(selected || { text: "전체", val: '0' }); +// setSelectedFclt({ text: "전체", val: '0', sggNm: '전체' }); +// }} +// value={selectedSigun.val} +// selectClass={'w-32'} +// /> +//
+// +//
+// { +// setBuildingSearchText(searchNm || ""); +// }} +// setRtnData={function (rtnData: any): void { +// // 선택된 건물 데이터 처리 +// // if (rtnData && rtnData.bdstInfoPk) { +// // setSelectedBuilding(rtnData); +// // setBuildingSearchText(rtnData.bdstNm || ""); +// // } +// }} +// searchList={buildingSearchRes || []} +// /> +//
+//
+// +// )} +// {searchType == "우리동네가뭄지도보기" && ( +// <> +// { +// const selected = code.cityCd.find((item: { val: string | number; }) => (item.val === val)); +// setSelectedSido(selected || { text: "전체", val: '0' }); +// setSelectedSigun({ text: "전체", val: '0' }); +// }} +// value={selectedSido.val} +// selectClass={'w-44'} +// /> +// { +// const selected = filteredSigunData.find(item => (item.val === val)); +// setSelectedSigun(selected || { text: "전체", val: '0' }); +// setSelectedDong({ text: "전체", val: '0', sggNm: '전체' }); +// }} +// value={selectedSigun.val} +// selectClass={'w-32'} +// /> +// { +// const selected = filteredDong.find(item => (item.val === val)); +// setSelectedDong(selected || { text: "전체", val: '0', sggNm: '전체' }); +// }} +// value={selectedDong.val} +// selectClass={'w-32'} +// /> +// +// )} +// {searchType == "지도보기" && ( +//
+//
{/* SearchFormLeft 래퍼 */} +// +//
+// +//
+// +//
+//
+// )} +//
+// {/* 버튼 부분 */} +//
+// {showQueryButton && } +// {showInitButton && } +// {showApplyButton && } +//
+//
+//
+// ) +// } diff --git a/apps/wr/app/map/mapSearchForm.tsx b/apps/wr/app/map/mapSearchForm.tsx new file mode 100644 index 000000000..70927996b --- /dev/null +++ b/apps/wr/app/map/mapSearchForm.tsx @@ -0,0 +1,340 @@ +"use client" + +import React, { useEffect, useState } from 'react'; +import type { SelectDataType } from '@common/business_components/ui/search_input_select'; +import { SelectBox } from '@common/business_components/ui/select'; + +/** + * 검색 폼 왼쪽 컴포넌트 타입 + * + * @param isShowPart 구분 조회 여부 + * @param children 자식 컴포넌트 + * @param selectSidoData 시도 데이터 + * @param selectSigunData 시군구 데이터 + * @param selectRegionData 유역 데이터 + * @param selectUnitWaterShedData 단위유역 데이터 + * @param selectMiddleRegionData 중권역 데이터 + * @param selectEnvPartData 환경청 데이터 + * @param selectEnvPartSidoData 환경청 시도 데이터 + * @param selectEnvPartSigunData 환경청 시군구 데이터 + * @param setRtnPartValue 부분 값 변경 핸들러 + */ +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: string) => void; + setRtnSidoText?: (value: string) => void; + setRtnSigunText?: (value: string) => void; + resetTrigger?: boolean; + isAdmin?: boolean; +} + +export type EnvPartSelectDataType = SelectDataType & { + adstDliStrCd: string, +} + +const selectPartData: SelectDataType[] = [ + { text: "행정별", val: 'A' }, + { text: "유역별", val: 'R' }, + { text: "환경청별", val: 'G' }, +]; + +function MapSearchFormLeft({ isShowPart = false, children, selectSidoData = [], selectSigunData = [], selectRegionData = [], selectUnitWaterShedData = [], selectMiddleRegionData = [], selectEnvPartData = [], setRtnPartValue, setRtnSidoValue, setRtnSidoText, setRtnSigunValue, setRtnSigunText, setRtnRegionValue, setRtnUnitWaterShedValue, setRtnMiddleRegionValue, setRtnEnvPartValue, setRtnEnvPartSidoValue, setRtnEnvPartSigunValue, resetTrigger = false, isAdmin = false }: SearchFormLeftProps) { + /******************선택된 데이터******************/ + const [partValue, setPartValue] = useState('A'); + //행정별 시도 시군구 + const [sidoValue, setSidoValue] = useState('0'); + const [sigunValue, setSigunValue] = useState('0'); + const [sidoText, setSidoText] = useState(''); + const [sigunText, setSigunText] = useState(''); + //유역별 + const [regionValue, setRegionValue] = useState('0'); + const [unitWaterShedValue, setUnitWaterShedValue] = useState('0'); + const [middleRegionValue, setMiddleRegionValue] = useState('0'); + //환경청별 시도 시군구 + const [envPartValue, setEnvPartValue] = useState('0'); + // 환경청 + const [filteredEnvPartSigunDataSelected, setFilteredEnvPartSigunDataSelected] = useState([]); + + /****************** 셀렉트 리스트 데이터. ******************/ + // 조회된 조건에 맞는 시도 시군 유역 + const [filteredSigunData, setFilteredSigunData] = useState([{ text: "전체", val: '0' }]); + const [filteredUnitWaterShedData, setFilteredUnitWaterShedData] = useState([{ text: "전체", val: '0' }]); + const [filteredMiddleRegionData, setFilteredMiddleRegionData] = useState([{ text: "전체", val: '0' }]); + // 조회된 조건에 맞는 환경청 시도 시군 + const [filteredEnvPartData, setFilteredEnvPartData] = useState([{ text: "전체", val: '0', adstDliStrCd: '' }]); + const [filteredEnvPartSidoData, setFilteredEnvPartSidoData] = useState([{ text: "전체", val: '0', adstDliStrCd: '' }]); + const [filteredEnvPartSigunData, setFilteredEnvPartSigunData] = useState([{ text: "전체", val: '0' }]); + + // 시군구 세팅 + const initializeSigunData = (sidoCd: string) => { + if (sidoCd === '0') { + setSigunValue('0'); + setFilteredSigunData([{ text: "전체", val: '0' }]); + } else { + const newFilteredData = [ + { text: "전체", val: '0' }, + ...selectSigunData.filter((item: SelectDataType) => item.val.substring(0, 2) === sidoCd) + ]; + setSigunValue('0'); + setFilteredSigunData(newFilteredData); + } + } + + // 단위유역 세팅 + const initializeUnitWaterShedData = (regionCd: string) => { + if (regionCd === '0') { + setUnitWaterShedValue('0'); + setFilteredUnitWaterShedData([{ text: "전체", val: '0' }]); + } else { + const newFilteredData = [ + { text: "전체", val: '0' }, + ...selectUnitWaterShedData.filter((item: SelectDataType) => item.val.substring(0, 1) === regionCd) + ]; + setUnitWaterShedValue('0'); + setFilteredUnitWaterShedData(newFilteredData); + } + } + + // 중권역 세팅 + const initializeMiddleRegionData = (unitWaterShedCd: string) => { + if (unitWaterShedCd === '0') { + setMiddleRegionValue('0'); + setFilteredMiddleRegionData([{ text: "전체", val: '0' }]); + } else { + const newFilteredData = [ + { text: "전체", val: '0' }, + ...selectMiddleRegionData.filter((item: SelectDataType) => item.val.substring(0, 2) === unitWaterShedCd) + ]; + setMiddleRegionValue('0'); + setFilteredMiddleRegionData(newFilteredData); + } + } + + // 환경청 세팅 - 시도 + const initializeCityDataByEnvPartSido = (envPartCd: string) => { + const foundEnvPart = selectEnvPartData.find((item: EnvPartSelectDataType) => item.val === envPartCd); + const cityArray = foundEnvPart?.adstDliStrCd.toString().split('|').join('').split(','); + + // 시군구 데이터 + const envSigunData = selectSigunData.filter((item: SelectDataType) => cityArray?.includes(item.val)); + + // 환경청 시도 데이터 + const sidoArray = [...new Set(envSigunData.map((item: SelectDataType) => item.val.substring(0, 2)))]; + const envSidoData = selectSidoData.filter((item: SelectDataType) => sidoArray?.includes(item.val)); + setFilteredEnvPartSigunDataSelected(envSigunData); + + setFilteredEnvPartSidoData([ + { text: "전체", val: '0', adstDliStrCd: '' }, + ...envSidoData.map((item: SelectDataType) => ({ text: item.text, val: item.val, adstDliStrCd: '' })) + ]); + } + + // 환경청 세팅 - 시군구 + const initializeCityDataByEnvPartSigun = (sidoCd: string) => { + const envSigunData = filteredEnvPartSigunDataSelected.filter((item: SelectDataType) => item.val.substring(0, 2) === sidoCd); + setFilteredEnvPartSigunData([ + { text: "전체", val: '0' }, + ...envSigunData.map((item: SelectDataType) => ({ text: item.text, val: item.val })) + ]); + } + + const onSelectValue = (val: string, type: string) => { + let selectedSido; + let selectedSigun; + let selectedEnvPartSido; + let selectedEnvPartSigun; + + switch (type) { + case 'part': + setPartValue(val); + if (setRtnPartValue) { + setRtnPartValue(val); + } + // 구분 + if (val == 'R') { + initializeMiddleRegionData('0'); + initializeUnitWaterShedData('0'); + } else if (val == 'G') { + setFilteredEnvPartData([ + { text: "전체", val: '0', adstDliStrCd: '' }, + ...selectEnvPartData.filter((item: EnvPartSelectDataType) => item.val !== null && item.adstDliStrCd !== '') + ]); + setEnvPartValue('0'); + initializeCityDataByEnvPartSido('0'); + } else { + initializeSigunData('0'); + onSelectValue(selectSigunData[0]?.val || '0', 'sigun'); + } + + // 다른 selectbox 전체로 초기화 + onSelectValue(selectSidoData[0]?.val || '0', 'sido'); + onSelectValue(selectSigunData[0]?.val || '0', 'sigun'); + break; + case 'sido': + setSidoValue(val); + selectedSido = selectSidoData.find(item => item.val === val); + setSidoText(selectedSido?.text || ''); + if (setRtnSidoValue) { + setRtnSidoValue(val); + } + if (setRtnSidoText) { + setRtnSidoText(selectedSido?.text || ''); + } + initializeSigunData(val); + onSelectValue(selectSigunData[0]?.val || '0', 'sigun'); + break; + case 'sigun': + if (setRtnSigunValue) { + setRtnSigunValue(val); + } + setSigunValue(val); + selectedSigun = filteredSigunData.find(item => item.val === val); + if (setRtnSigunText) { + setRtnSigunText(selectedSigun?.text || ''); + } + setSigunText(selectedSigun?.text || ''); + break; + + // 유역 세팅 + case 'region': + setRegionValue(val); + if (setRtnRegionValue) { + setRtnRegionValue(val); + } + initializeUnitWaterShedData(val); + onSelectValue(selectUnitWaterShedData[0]?.val || '0', 'unitWaterShed'); + break; + case 'unitWaterShed': + setUnitWaterShedValue(val); + if (setRtnUnitWaterShedValue) { + setRtnUnitWaterShedValue(val); + } + initializeMiddleRegionData(val); + onSelectValue(selectMiddleRegionData[0]?.val || '0', 'middleRegion'); + break; + case 'middleRegion': + setMiddleRegionValue(val); + if (setRtnMiddleRegionValue) { + setRtnMiddleRegionValue(val); + } + break; + + // 환경청 세팅 + case 'envPart': + setEnvPartValue(val); + if (setRtnEnvPartValue) { + setRtnEnvPartValue(val); + } + initializeCityDataByEnvPartSido(val); + onSelectValue(filteredEnvPartSidoData[0]?.val || '0', 'envPartSido'); + break; + case 'envPartSido': + setSidoValue(val); + selectedEnvPartSido = filteredEnvPartSidoData.find(item => item.val === val); + setSidoText(selectedEnvPartSido?.text || ''); + if (setRtnEnvPartSidoValue) { + setRtnEnvPartSidoValue(val); + } + initializeCityDataByEnvPartSigun(val); + onSelectValue(filteredEnvPartSigunData[0]?.val || '0', 'envPartSigun'); + break; + case 'envPartSigun': + setSigunValue(val); + selectedEnvPartSigun = filteredEnvPartSigunData.find(item => item.val === val); + setSigunText(selectedEnvPartSigun?.text || ''); + if (setRtnEnvPartSigunValue) { + setRtnEnvPartSigunValue(val); + } + break; + } + } + + // 리셋 함수 + const resetAllValues = () => { + setPartValue('A'); + setSidoValue('0'); + setSigunValue('0'); + setRegionValue('0'); + setUnitWaterShedValue('0'); + setMiddleRegionValue('0'); + setEnvPartValue('0'); + + setFilteredSigunData([{ text: "전체", val: '0' }]); + setFilteredUnitWaterShedData([{ text: "전체", val: '0' }]); + setFilteredMiddleRegionData([{ text: "전체", val: '0' }]); + setFilteredEnvPartData([{ text: "전체", val: '0', adstDliStrCd: '' }]); + setFilteredEnvPartSidoData([{ text: "전체", val: '0', adstDliStrCd: '' }]); + setFilteredEnvPartSigunData([{ text: "전체", val: '0' }]); + + if (setRtnPartValue) setRtnPartValue('A'); + if (setRtnSidoValue) setRtnSidoValue('0'); + if (setRtnSigunValue) setRtnSigunValue('0'); + if (setRtnRegionValue) setRtnRegionValue('0'); + if (setRtnUnitWaterShedValue) setRtnUnitWaterShedValue('0'); + if (setRtnMiddleRegionValue) setRtnMiddleRegionValue('0'); + if (setRtnEnvPartValue) setRtnEnvPartValue('0'); + if (setRtnEnvPartSidoValue) setRtnEnvPartSidoValue('0'); + if (setRtnEnvPartSigunValue) setRtnEnvPartSigunValue('0'); + }; + + // 리셋 트리거 변경 감지 + useEffect(() => { + if (resetTrigger) { + resetAllValues(); + // 리셋 후 트리거를 false로 설정 + if (setRtnPartValue) { + setRtnPartValue('0'); + } + } + }, [resetTrigger]); + + return ( + isShowPart ? ( +
+ onSelectValue(val.toString(), 'part')} value={partValue} selectClass={'w-32'} /> + {partValue === 'R' ? ( + <> + onSelectValue(val.toString(), 'region')} value={regionValue} selectClass={'w-44'} /> + onSelectValue(val.toString(), 'unitWaterShed')} value={unitWaterShedValue} selectClass={'w-44'} /> + onSelectValue(val.toString(), 'middleRegion')} value={middleRegionValue} selectClass={'w-44'} /> + + ) : partValue === 'G' ? ( + <> + onSelectValue(val.toString(), 'envPart')} value={envPartValue} selectClass={'w-44'} /> + onSelectValue(val.toString(), 'envPartSido')} value={sidoValue} selectClass={'w-44'} /> + onSelectValue(val.toString(), 'envPartSigun')} value={sigunValue} selectClass={'w-44'} /> + + ) : ( + <> + onSelectValue(val.toString(), 'sido')} value={sidoValue} selectClass={'w-44'} /> + onSelectValue(val.toString(), 'sigun')} value={sigunValue} selectClass={'w-44'} /> + + )} +
+ ) : ( +
+ {children} +
+ ) + ) +} + +export { MapSearchFormLeft }; diff --git a/apps/wr/app/map/newWindowPopup.tsx b/apps/wr/app/map/newWindowPopup.tsx new file mode 100644 index 000000000..ebfcac1ca --- /dev/null +++ b/apps/wr/app/map/newWindowPopup.tsx @@ -0,0 +1,382 @@ +import { useWrStore } from "@/app/store"; +import { btnClass } from "@/app/store/common/code"; +import type { customPbnst } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; +import { excelDownload } from "@/components/lib/func/commom"; +import { Title, UiTable, UITab, ContentsDiv } from '@common/business_components'; +import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; +import { Button } from "@common/components/ui"; +import React, { useCallback, useState, useEffect } from 'react'; +import NewWindowPortal from '@/app/map/newWindowPortal'; +import PbnstPrfmncSttnPopup from "@/app/map/pbnstPrfmncSttnPopup"; +import { PbnstSttnModal } from "@/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal"; +import type { WaterReuseFacility } from "@/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type"; +import type { chgBuilding, customRainBdst } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; +import RainPrfmncSttnPopup from "@/app/map/rainPrfmncSttnPopup"; +import RswtrPrfmncsSttnPopup from "@/app/map/rswtrPrfmncSttnPopup"; +import type { customRswtBdst } from "@/app/store/ruseFcltMng/fcltReg/rswtReg"; +import HtdflPrfmncSttnPopup from "@/app/map/htdflPrfmncSttnPopup"; +import type { customHtdflBdst } from "@/app/store/ruseFcltMng/fcltReg/htdflReg"; + +interface NewWindowPopupProps { + featureData: any; + checkedLayers?: string[]; + checkedLayersData?: any[]; + onClose: () => void; +} + +// 탭 +const TABS = [ + { val: "0", text: "재이용시설1" }, + { val: "1", text: "재이용시설2" }, + { val: "2", text: "재이용시설n" }, +]; + +// 데이터의 속성(key)을 사용자에게 보여줄 한글 이름으로 변환하기 위한 맵 +// 실제 데이터에 맞게 이 부분을 확장해야 합니다. +const propertyNameMap: { [key: string]: string } = { + fid: '고유 ID', + fcltNm: '시설명', + opertDe: '운영일자', + addr: '주소', + // ... 필요한 만큼 속성 매핑 추가 +}; + +const NewWindowPopup: React.FC = ({ + featureData, + checkedLayers = [], + checkedLayersData = [], + onClose +}) => { + const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? "0"); + // swcho : useState의 true, false 값 변경하여 목록, 상태 테스트 하였음 + const [showDetailView, setShowDetailView] = useState(true); // 상세정보 화면 표시 상태 추가 + + // onClose 함수를 useCallback으로 메모이제이션 + const memoizedOnClose = useCallback(() => { + onClose(); + }, [onClose]); + + // 하수재이용시설 변수 + const { + code, + pbnstSttn, + pbnstSttnSearch, + pbnSttnInit, + getPbnStnDetail, + pbnNmSearch, + } = useWrStore((state) => state); + const [bdstTable, setBdstTable] = useState(); + const [pageNation, setPageNation] = useState(1); + const [pbnDetaileData, setPbnDetaileData] = useState<{ + isModal: boolean; + data: customPbnst; + }>({ isModal: false, data: {} as customPbnst }); + + // 빗물재이용시설 변수 + const { rainSttn, rainSttnSearch, rainSttnInit } = useWrStore( + (state) => state + ); + const { rainSttnRes } = rainSttn; + const [rainTable, setRainTable] = useState(); + const [rainBdstChgProps, setRainBdstChgProps] = useState<{ + isOpen: boolean; + chBuilding: chgBuilding[]; + }>({ isOpen: false, chBuilding: [] }); + const [rainDetailModal, setRainDetailModal] = useState<{ isOpen: boolean, data: customRainBdst | undefined }>({ data: undefined, isOpen: false }); + + // 중수도시설 변수 + const { rswtSttn, rswtSttnSearch, rswtSttnInit } = useWrStore( + (state) => state + ); + const { rswtSttnRes } = rswtSttn; + const [rswtrTable, setRswtrTable] = useState(); + const [rswtrDetailModal, setRswtrDetailModal] = useState<{ isOpen: boolean, data: customRswtBdst | undefined }>({ data: undefined, isOpen: false }); + const [rswtrBdstChgProps, setRswtrBdstChgProps] = useState<{ + isOpen: boolean; + chBuilding: chgBuilding[]; + }>({ isOpen: false, chBuilding: [] }); + + // 온배수시설 변수 + const { htdflSttn, htdflSttnSearch, initHtdfl } = useWrStore((state) => state); + const { htdflSttnRes } = htdflSttn; + const [htdflTable, setHtdflTable] = useState(); + const [htdflDetailModal, setHtdflDetailModal] = useState<{ isOpen: boolean, data: customHtdflBdst | undefined }>({ data: undefined, isOpen: false }); + const [bdstChgProps, setBdstChgProps] = useState<{ isOpen: boolean, chBuilding: chgBuilding[] }>({ isOpen: false, chBuilding: [] }); + + if (!featureData) { + return null; + } + + // 2. RuseMngTable에 필요한 tableData를 생성합니다. (단일 행 데이터) + const tableData = [featureData]; + console.log("tableData", tableData); + + // 물재이용시설 cellClick 이벤트 (상세정보) + const pbnstCellClick = (index: number | undefined) => { + if ( + index !== undefined && + pbnstSttn.pbnstSttnRes && + pbnstSttn.pbnstSttnRes[index] + ) { + const pbnstRuseFcltPk = pbnstSttn.pbnstSttnRes[index].pbnstRuseFcltPk; + getPbnStnDetail(pbnstRuseFcltPk); + } + }; + + useEffect(() => { + setBdstTable(pbnstSttn.pbnstSttnRes); + }, [pbnstSttn.pbnstSttnRes]); + + useEffect(() => { + if (pbnstSttn.pbnstDetail.pbnstRuseFcltPk) setShowDetailView(true); + }, [pbnstSttn.pbnstDetail]); + + + // 빗물재이용시설 cellClick 이벤트 (상세정보) + const rainCellClick = (index: number | undefined, headId: string) => { + if ( + index !== undefined && + rainTable !== undefined && + headId !== 'chBuildingYn') { + setRainDetailModal({ isOpen: true, data: rainTable[index] }) + } else if (headId === 'chBuildingYn' && index !== undefined && rainTable !== undefined) { + if (rainTable[index] && rainTable[index]?.chgBuilding[0]?.bdstChgNm !== null) + setRainBdstChgProps({ isOpen: true, chBuilding: rainTable[index].chgBuilding }); + } + } + + // 중수도시설 cellClick 이벤트 (상세정보) + const rswtCellClick = (index: number | undefined, headId: string) => { + if (headId !== 'chBuildingYn' && index !== undefined && rswtrTable !== undefined) { + setRswtrDetailModal({ isOpen: true, data: rswtrTable[index] }) + } else if (headId === 'chBuildingYn' && index !== undefined && rswtrTable !== undefined) { + if (rswtrTable[index] && rswtrTable[index]?.chBuildingYn === 'Y') { + setRswtrBdstChgProps({ isOpen: true, chBuilding: rswtrTable[index].chgBuilding }); + } + } + } + + // 온배수시설 cellClick 이벤트 (상세정보) + const htdflCellClick = (index: number | undefined, headId: string) => { + if ( + headId !== "chBuildingYn" && + index !== undefined && + htdflTable !== undefined + ) { + setHtdflDetailModal({ isOpen: true, data: htdflTable[index] }); + } + }; + + const excelClick = useCallback(() => { + // if (codeData) pbnstSttnSearch(1, "10000", codeData); + excelDownload( + [[]], + "하수재이용시설 현황", + "sample_pbnstSttn.xlsx", + "A1:U2" + ); + }, [pbnstSttnSearch]); + + // 체크된 레이어에 따른 조건부 컴포넌트 렌더링 함수 + const renderConditionalContent = () => { + // 특정 레이어가 체크되었는지 확인 + const hasWaterTreatmentLayer = checkedLayersData.some(layer => + layer.data?.lyrNm?.includes('하수처리') || layer.data?.lyrNm?.includes('재이용시설') + ); + + // swcho : true, false 값 변경하여 시설 변경하여 테스트하였음 + // 재이용시설 + const isPbnst = true; + // 빗물이용시설 + const isRain = false; + // 중수도시설 + const isRswt = false; + // 온배수시설 + const isHtdfl = false; + + + return ( +
+ {/* 체크된 레이어가 없는 경우 (재이용시설 목록) */} + {isPbnst && ( + <> + {showDetailView ? ( + // 재이용시설 상세 + <> +
+ + </div> + <div className="flex justify-between items-center mb-8"> + <UITab + defaultValue={TABS[0]?.val} + tabList={TABS.map((tab) => ({ + value: tab.val, + label: tab.text, + onClick: () => setActiveTab(tab.val), + }))} + children={undefined} + /> + </div> + <PbnstSttnModal + data={pbnDetaileData.data || ({} as customPbnst)} + titleVisible={false} + /> + <PbnstPrfmncSttnPopup isVisible={false} /> + </> + ) : ( + // 재이용시설 목록 + <> + <div className="flex justify-between items-center"> + <Title title='하수재이용시설 현황' /> + <div className="flex gap-2 mt-8"> + <Button className={btnClass} color="primary" onClick={excelClick}>엑셀다운로드</Button> + </div> + </div> + <UiTable + headName={[]} + tableData={bdstTable || []} + children={ + <HeadMakeColSpan upHeadList={pbnstSttn.pbnstSttNodeList} /> + } + headlist={pbnstSttn.pbnstSttHeadList} + pageSize={20} + total={100000} + cellClick={(index: number | undefined, headId: string) => { + pbnstCellClick(index); + }} + onPageChange={setPageNation} + /> + </> + )} + </> + )} + {/* 빗물이용시설 */} + {isRain && ( + <> + {showDetailView ? ( + // 빗물이용시설 상세 + <> + <div className="flex justify-between items-center mb-4"> + <Title title='빗물이용시설 상세정보' /> + </div> + <RainPrfmncSttnPopup rainForm={rainDetailModal.data as customRainBdst} isModal={false} /> + {/* TODO: 지도 점검 결과 현황 추가 할 것 */} + </> + ) : ( + // 빗물이용시설 목록 + <> + <div className="flex justify-between items-center"> + <Title title='빗물이용시설 현황' /> + <div className="flex gap-2 mt-8"> + <Button className={btnClass} color="primary" onClick={excelClick}>엑셀다운로드</Button> + </div> + </div> + <UiTable + headName={[]} + tableData={rainTable || []} + children={ + <HeadMakeColSpan upHeadList={rainSttn.rainSttNodeList} /> + } + headlist={rainSttn.rainSttHeadList} + pageSize={20} + total={100000} + cellClick={(index: number | undefined, headId: string) => { + rainCellClick(index ?? undefined, headId) + }} + onPageChange={setPageNation} + /> + </> + )} + </> + )} + {/* 중수도시설 */} + {isRswt && ( + <> + {showDetailView ? ( + // 재이용시설 상세 + <> + <div className="flex justify-between items-center mb-4"> + <Title title='중수도시설 상세정보' /> + </div> + <RswtrPrfmncsSttnPopup rswtForm={rswtrDetailModal.data as customRswtBdst} isModal={false} /> + {/* TODO: 지도 점검 결과 현황 추가 할 것 */} + </> + ) : ( + // 재이용시설 목록 + <> + <div className="flex justify-between items-center"> + <Title title='중수도시설 현황' /> + <div className="flex gap-2 mt-8"> + <Button className={btnClass} color="primary" onClick={excelClick}>엑셀다운로드</Button> + </div> + </div> + <UiTable + headName={[]} + tableData={rswtrTable || []} + children={ + <HeadMakeColSpan upHeadList={rswtSttn.rswtSttNodeList} /> + } + headlist={rswtSttn.rswtSttHeadList} + pageSize={20} + total={100000} + cellClick={(index: number | undefined, headId: string) => { + rswtCellClick(index, headId); + }} + onPageChange={setPageNation} + /> + </> + )} + </> + )} + {/* 온배수시설 */} + {isHtdfl && ( + <> + {showDetailView ? ( + // 재이용시설 상세 + <> + <div className="flex justify-between items-center mb-4"> + <Title title='온배수시설 상세정보' /> + </div> + {/* <HtdflPrfmncSttnPopup htdflForm={htdflDetailModal.data as customHtdflBdst} isModal={false} /> */} + <HtdflPrfmncSttnPopup htdflForm={htdflDetailModal.data as customHtdflBdst} isModal={false} /> + </> + ) : ( + // 재이용시설 목록 + <> + <div className="flex justify-between items-center"> + <Title title='온배수시설 현황' /> + <div className="flex gap-2 mt-8"> + <Button className={btnClass} color="primary" onClick={excelClick}>엑셀다운로드</Button> + </div> + </div> + <UiTable + headName={[]} + tableData={bdstTable || []} + children={ + <HeadMakeColSpan upHeadList={htdflSttn.htdflSttNodeList} /> + } + headlist={htdflSttn.htdflSttHeadList} + pageSize={20} + total={100000} + cellClick={(index: number | undefined, headId: string) => { + htdflCellClick(index ?? undefined, headId); + }} + onPageChange={setPageNation} + /> + </> + )} + </> + )} + </div> + ); + }; + + return ( + <NewWindowPortal onClose={memoizedOnClose} title="시설 상세 정보"> + {/* 체크된 레이어에 따른 조건부 렌더링 */} + {renderConditionalContent()} + </NewWindowPortal> + ); +}; + +export default NewWindowPopup; \ No newline at end of file diff --git a/apps/wr/app/map/newWindowPortal.tsx b/apps/wr/app/map/newWindowPortal.tsx new file mode 100644 index 000000000..49949e0f4 --- /dev/null +++ b/apps/wr/app/map/newWindowPortal.tsx @@ -0,0 +1,90 @@ +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { createPortal } from 'react-dom'; + +// 현재 문서의 스타일(CSS)을 새 창으로 복사하는 함수 +const copyStyles = (sourceDoc: Document, targetDoc: Document) => { + Array.from(sourceDoc.styleSheets).forEach(styleSheet => { + try { + if (styleSheet.cssRules) { // <style> 태그로 주입된 CSS + const newStyleEl = sourceDoc.createElement('style'); + Array.from(styleSheet.cssRules).forEach(cssRule => { + newStyleEl.appendChild(sourceDoc.createTextNode(cssRule.cssText)); + }); + targetDoc.head.appendChild(newStyleEl); + } else if (styleSheet.href) { // <link> 태그로 연결된 CSS 파일 + const newLinkEl = sourceDoc.createElement('link'); + newLinkEl.rel = 'stylesheet'; + newLinkEl.href = styleSheet.href; + targetDoc.head.appendChild(newLinkEl); + } + } catch (e) { + console.warn('스타일시트를 복사할 수 없습니다:', e); + } + }); +}; + +interface NewWindowPortalProps { + children: React.ReactNode; + onClose: () => void; + title?: string; +} + +const NewWindowPortal: React.FC<NewWindowPortalProps> = ({ children, onClose, title = "Detail" }) => { + const [container, setContainer] = useState<HTMLElement | null>(null); + const newWindowRef = useRef<Window | null>(null); + const onCloseRef = useRef(onClose); + + // onClose 함수를 ref로 저장하여 의존성 문제 해결 + useEffect(() => { + onCloseRef.current = onClose; + }, [onClose]); + + const handleUnload = useCallback(() => { + onCloseRef.current(); + }, []); + + useEffect(() => { + // 이미 창이 열려있으면 새로 열지 않음 + if (newWindowRef.current && !newWindowRef.current.closed) return; + + // 1. 새 창을 열고 참조를 저장합니다. + newWindowRef.current = window.open('', title, 'width=1400,height=1024,resizable=yes,scrollbars=yes'); + + if (newWindowRef.current) { + newWindowRef.current.document.title = title; + + // 2. React 컴포넌트를 마운트할 컨테이너 div를 생성하여 새 창의 body에 추가합니다. + const div = newWindowRef.current.document.createElement('div'); + newWindowRef.current.document.body.appendChild(div); + setContainer(div); + + // 3. 메인 창의 스타일을 새 창으로 복사합니다. (매우 중요) + copyStyles(document, newWindowRef.current.document); + + // 4. 사용자가 창을 닫을 때 부모에게 알리기 위한 이벤트 리스너를 설정합니다. + // const handleUnload = () => { + // onClose(); + // }; + newWindowRef.current.addEventListener('unload', handleUnload); + + // 5. 컴포넌트가 사라질 때 창을 닫는 클린업 함수를 반환합니다. + return () => { + if (newWindowRef.current) { + newWindowRef.current.removeEventListener('unload', handleUnload); + if (!newWindowRef.current.closed) { + newWindowRef.current.close(); + } + } + }; + } else { + alert('팝업이 차단되었습니다. 팝업 차단을 해제하고 다시 시도해주세요.'); + onCloseRef.current(); + // onClose(); + } + }, [onClose, title]); + + // `createPortal`을 사용해 children을 새 창의 container div에 렌더링합니다. + return container ? createPortal(children, container) : null; +}; + +export default NewWindowPortal; \ No newline at end of file diff --git a/apps/wr/app/map/pbnstPrfmncSttnPopup.tsx b/apps/wr/app/map/pbnstPrfmncSttnPopup.tsx new file mode 100644 index 000000000..f6d870092 --- /dev/null +++ b/apps/wr/app/map/pbnstPrfmncSttnPopup.tsx @@ -0,0 +1,596 @@ +"use client"; + +import { useWrStore } from "@/app/store"; +import { initValInUsage, initValOutUsage, subTotalObj, totalObj, type FcPrfType, type PrfType } from "@/app/store/prfmncReg/pbnstPrfmncReg/type"; +import { CHART_FORM, ContentsDiv, MainChart, ScrollArea, SelectBox, UiTable, type ChartConfig, type ReturnType } from "@common/business_components"; +import ListDiv from "@common/business_components/ui/list_div"; +import { useEffect, useRef, useState } from "react"; +import { Toaster } from "react-hot-toast"; + +interface PbnstPrfmncSttnPopupProps { + isVisible?: boolean; +} + +export default function PbnstPrfmncSttnPopup({ isVisible = true }: PbnstPrfmncSttnPopupProps) { + const { code, tableHeadMaker, pbnstPrfmncSttn, pbnstPrfmncInit, pbnstPrfmncSearch, prfmnc } = useWrStore((state) => state); + const { monthHeadList } = prfmnc + const { pbnstPrfHeadList, pbnstPrfmncRes, prfmncRes, rawData, selectYearList } = pbnstPrfmncSttn; + const [isChHead, setIsChHead] = useState(false); + const [fromDate, setFromDate] = useState<string>(String(new Date().getFullYear())); + const [toDate, setToDate] = useState<string>(String(new Date().getFullYear())); + const [year, setYear] = useState<string>(String(new Date().getFullYear())); + const [bdstTable, setbdstTable] = useState<FcPrfType[]>(); + const ref = useRef<ReturnType>({} as ReturnType); + const [swtrmtFcltNm, setSwtrmtFcltNm] = useState<string>(); + const [chartConfig, setChartConfig] = useState<ChartConfig>({}); + const [chartData, setChartData] = useState<dnChartForm[]>([]); + const [dnInUsage, setDnInUsage] = useState<dnChartForm[]>([]); + const [dnInChartConfig, setDnInChartConfig] = useState<ChartConfig>({}); + const [dnOutUsage, setDnOutUsage] = useState<dnChartForm[]>([]); + const [dnOutChartConfig, setDnOutChartConfig] = useState<ChartConfig>({}); + const [barData, setBarData] = useState<barChartForm[]>([]); + const [barChartConfig, setBarChartConfig] = useState<ChartConfig>({}); + const [prfData, setPrfData] = useState<any[]>(); + const [lnInChartConfig, setLnInChartConfig] = useState<ChartConfig>({}); + const [lnOutChartConfig, setLnOutChartConfig] = useState<ChartConfig>({}); + const [inLineData, setInLineData] = useState<{ + chartData1: lineChartDataProps[]; + chartData2: lineChartDataProps[]; + chartData3: lineChartDataProps[]; + chartData4: lineChartDataProps[]; + chartData5: lineChartDataProps[]; + chartData6: lineChartDataProps[]; + chartData7: lineChartDataProps[]; + chartData8: lineChartDataProps[]; + chartData9: lineChartDataProps[]; + }>(); + // const [lnInChartConfig, setLnInChartConfig] = useState<ChartConfig>({}); + const [outLineData, setOutLineData] = useState<{ + chartData1: lineChartDataProps[]; + chartData2: lineChartDataProps[]; + chartData3: lineChartDataProps[]; + chartData4: lineChartDataProps[]; + chartData5: lineChartDataProps[]; + chartData6: lineChartDataProps[]; + }>(); + + + + type dnChartForm = { label: string, val: number, fill?: string }; + type barChartForm = { label: string, "장내용수": number, "장외용수": number }; + type lineChartDataProps = { label: string, val: number, color: string }; + + const prfTableData = (prfList: PrfType[], year: string) => { + const inUsageData = initValInUsage; + const outUsageData = initValOutUsage; + + const inSubtotal = subTotalObj; + const outSubtotal = subTotalObj; + + console.log(' prfList ', prfList) + const prfFilter = prfList?.filter((e) => e.ym.slice(0, 4) === year) + if (prfList && prfFilter.length > 0) { + prfList?.forEach((prfItem) => { + const month = Number(prfItem?.ym?.slice(4)); // "MM" + const monthKey = `${month}월`; + + inUsageData.forEach((inData: any) => { + const usageKey = inData.usage; + const value = (prfItem as any)[usageKey] ?? 0; + + inData[monthKey] = value; + inData.total += value; + + if (inSubtotal && usageKey !== "subtotal") { + (inSubtotal as any)[monthKey] = + ((inSubtotal as any)[monthKey] ?? 0) + value; + inSubtotal.total += value; + } + }); + + outUsageData.forEach((outData: any) => { + const usageKey = outData.usage; + const value = (prfItem as any)[usageKey] ?? 0; + + outData[monthKey] = value; + outData.total += value; + + if (usageKey !== "subtotal") { + (outSubtotal as any)[monthKey] = + ((outSubtotal as any)[monthKey] ?? 0) + value; + outSubtotal.total += value; + } + }); + }); + inUsageData.push(inSubtotal); + outUsageData.push(outSubtotal); + const usageTotalObj: { [key: string]: any } = totalObj; + for (const key in usageTotalObj) { + if (["usage", "gubun", "edit"].includes(key)) continue; + const val = + (Number((inSubtotal as any)[key]) || 0) + + (Number((outSubtotal as any)[key]) || 0); + + (usageTotalObj as any)[key] = val; + } + const tableData = [...inUsageData, ...outUsageData, usageTotalObj]; + setPrfData(tableData); + + } else { + setPrfData([]); + } + } + useEffect(() => { + let wshwtr = 0; + let coolUswtr = 0; + let cleanUswtr = 0; + let drwtUswtr = 0; + let dltnUswtr = 0; + let ithlRswtr = 0; + let ithlEtcUswtr = 0; + let cleanToilUswtr = 0; + let resn = 0; + let lndwtr = 0; + let hydUswtr = 0; + let ugwtChr = 0; + let farmUswtr = 0; + let rvrKeepUswtr = 0; + let indstUswtr = 0; + if (rawData) { + rawData.forEach((item) => { + prfTableData(item.prfList, year); + item.prfList.filter((e) => e.ym.slice(0, 4) === year).forEach((element) => { + + wshwtr += element.wshwtr; //세척수 + coolUswtr += element.coolUswtr; //냉각수 + cleanUswtr += element.cleanUswtr; //청소수 + drwtUswtr += element.drwtUswtr; //식수대 + dltnUswtr += element.dltnUswtr //희석용수 + ithlRswtr += element.ithlRswtr //중수도 + ithlEtcUswtr += element.ithlEtcUswtr //기타 + cleanToilUswtr += element.cleanToilUswtr //청소화장실용수 + resn += element.resn //세척살수용수 + + lndwtr += element.lndwtr; //조경용수 + hydUswtr += element.hydUswtr; //친수용수 + ugwtChr += element.ugwtChr; //지하수충전 + farmUswtr += element.farmUswtr //농업용수 + rvrKeepUswtr += element.rvrKeepUswtr //하천등유지용수 + indstUswtr += element.indstUswtr //공업용수 + + }); + }) + const data: dnChartForm[] = [] + data?.push({ label: "세척수", val: wshwtr, fill: "red" }) + data?.push({ label: "냉각수", val: coolUswtr, fill: "orange" }) + data?.push({ label: "청소수", val: cleanUswtr, fill: "yellow" }) + data?.push({ label: "식수대", val: drwtUswtr, fill: "green" }) + data?.push({ label: "희석용수", val: dltnUswtr, fill: "skyblue" }) + data?.push({ label: "중수도", val: ithlRswtr, fill: "blue" }) + data?.push({ label: "기타", val: ithlEtcUswtr, fill: "purple" }) + data?.push({ label: "청소화장실용수", val: cleanToilUswtr, fill: "red" }) + data?.push({ label: "세척살수용수", val: resn, fill: "orange" }) + data?.push({ label: "조경용수", val: lndwtr, fill: "yellow" }) + data?.push({ label: "친수용수", val: hydUswtr, fill: "green" }) + data?.push({ label: "지하수충전", val: ugwtChr, fill: "skyblue" }) + data?.push({ label: "농업용수", val: farmUswtr, fill: "blue" }) + data?.push({ label: "하천등유지용수", val: rvrKeepUswtr, fill: "purple" }) + data?.push({ label: "공업용수", val: indstUswtr, fill: "black" }) + setChartData(data); + } + }, [rawData, year]) + + + useEffect(() => { + const newChartConfig = chartData.reduce((config, element) => { + config[element.label] = { + label: element.label, + fill: element.fill + }; + return config; + }, {} as Record<string, { label: string; fill?: string }>) satisfies ChartConfig; + setChartConfig(newChartConfig); + }, [chartData]); + + // 장내용수 도넛차트 + useEffect(() => { + let wshwtr = 0; + let coolUswtr = 0; + let cleanUswtr = 0; + let drwtUswtr = 0; + let dltnUswtr = 0; + let ithlRswtr = 0; + let ithlEtcUswtr = 0; + let cleanToilUswtr = 0; + let resn = 0; + if (rawData) { + rawData.forEach((item) => { + item.prfList.filter((e) => e.ym.slice(0, 4) === year).forEach((element) => { + + wshwtr += element.wshwtr; //세척수 + coolUswtr += element.coolUswtr; //냉각수 + cleanUswtr += element.cleanUswtr; //청소수 + drwtUswtr += element.drwtUswtr; //식수대 + dltnUswtr += element.dltnUswtr //희석용수 + ithlRswtr += element.ithlRswtr //중수도 + ithlEtcUswtr += element.ithlEtcUswtr //기타 + cleanToilUswtr += element.cleanToilUswtr //청소화장실용수 + resn += element.resn //세척살수용수 + }); + }) + const data: dnChartForm[] = [] + data?.push({ label: "세척수", val: wshwtr, fill: "yellow" }) + data?.push({ label: "냉각수", val: coolUswtr, fill: "green" }) + data?.push({ label: "청소수", val: cleanUswtr, fill: "yellow" }) + data?.push({ label: "식수대", val: drwtUswtr, fill: "green" }) + data?.push({ label: "희석용수", val: dltnUswtr, fill: "yellow" }) + data?.push({ label: "중수도", val: ithlRswtr, fill: "green" }) + data?.push({ label: "기타", val: ithlEtcUswtr, fill: "yellow" }) + data?.push({ label: "청소화장실용수", val: cleanToilUswtr, fill: "green" }) + data?.push({ label: "세척살수용수", val: resn, fill: "yellow" }) + setDnInUsage(data); + } + }, [rawData, year]) + + useEffect(() => { + const newChartConfig = dnInUsage.reduce((config, element) => { + config[element.label] = { + label: element.label, + fill: element.fill + }; + return config; + }, {} as Record<string, { label: string; color: string; }>) satisfies ChartConfig; + setDnInChartConfig(newChartConfig); + }, [dnInUsage]); + + // 장외용수 도넛차트 + useEffect(() => { + let lndwtr = 0; + let hydUswtr = 0; + let ugwtChr = 0; + let farmUswtr = 0; + let rvrKeepUswtr = 0; + let indstUswtr = 0; + if (rawData) { + rawData.forEach((item) => { + item.prfList.filter((e) => e.ym.slice(0, 4) === year).forEach((element) => { + lndwtr += element.lndwtr; //조경용수 + hydUswtr += element.hydUswtr; //친수용수 + ugwtChr += element.ugwtChr; //지하수충전 + farmUswtr += element.farmUswtr //농업용수 + rvrKeepUswtr += element.rvrKeepUswtr //하천등유지용수 + indstUswtr += element.indstUswtr //공업용수 + + }); + }) + const data: dnChartForm[] = [] + data?.push({ label: "조경용수", val: lndwtr, fill: "yellow" }) + data?.push({ label: "친수용수", val: hydUswtr, fill: "black" }) + data?.push({ label: "지하수충전", val: ugwtChr, fill: "yellow" }) + data?.push({ label: "농업용수", val: farmUswtr, fill: "black" }) + data?.push({ label: "하천등유지용수", val: rvrKeepUswtr, fill: "yellow" }) + data?.push({ label: "공업용수", val: indstUswtr, fill: "black" }) + setDnOutUsage(data); + } + }, [rawData, year]) + + useEffect(() => { + const newChartConfig = dnOutUsage.reduce((config, element) => { + config[element.label] = { + label: element.label, + fill: element.fill + }; + return config; + }, {} as Record<string, { label: string; color: string; }>) satisfies ChartConfig; + setDnOutChartConfig(newChartConfig); + }, [dnOutUsage]); + + //월별 장내외 용수 총 실적 막대차트 + useEffect(() => { + let wshwtr = 0; + let coolUswtr = 0; + let cleanUswtr = 0; + let drwtUswtr = 0; + let dltnUswtr = 0; + let ithlRswtr = 0; + let ithlEtcUswtr = 0; + let cleanToilUswtr = 0; + let resn = 0; + let lndwtr = 0; + let hydUswtr = 0; + let ugwtChr = 0; + let farmUswtr = 0; + let rvrKeepUswtr = 0; + let indstUswtr = 0; + let inUsageSum = 0; + let outUsageSum = 0; + let inUsageArr = []; + let outUsageArr = []; + + + if (rawData) { + const data: barChartForm[] = [] + rawData.forEach((item) => { + item.prfList + .filter((e) => e.ym.slice(0, 4) === year).forEach((element) => { + + wshwtr = element.wshwtr; //세척수 + coolUswtr = element.coolUswtr; //냉각수 + cleanUswtr = element.cleanUswtr; //청소수 + drwtUswtr = element.drwtUswtr; //식수대 + dltnUswtr = element.dltnUswtr //희석용수 + ithlRswtr = element.ithlRswtr //중수도 + ithlEtcUswtr = element.ithlEtcUswtr //기타 + cleanToilUswtr = element.cleanToilUswtr //청소화장실용수 + resn = element.resn //세척살수용수 + + lndwtr = element.lndwtr; //조경용수 + hydUswtr = element.hydUswtr; //친수용수 + ugwtChr = element.ugwtChr; //지하수충전 + farmUswtr = element.farmUswtr //농업용수 + rvrKeepUswtr = element.rvrKeepUswtr //하천등유지용수 + indstUswtr = element.indstUswtr //공업용수 + + inUsageSum = wshwtr + coolUswtr + cleanUswtr + drwtUswtr + dltnUswtr + ithlRswtr + ithlEtcUswtr + cleanToilUswtr + resn; + inUsageArr.push(inUsageSum) + outUsageSum = lndwtr + hydUswtr + ugwtChr + farmUswtr + rvrKeepUswtr + indstUswtr; + outUsageArr.push(outUsageSum) + + const month = Number(element.ym.slice(4, 6)) + data.push({ label: `${month}월`, "장내용수": inUsageSum, "장외용수": outUsageSum }) + + }, + ); + + }) + + setBarData(data); + } + }, [rawData, year]) + + useEffect(() => { + const newChartConfig = barData.reduce((config, element) => { + config[element.label] = { + label: element.label, + }; + return config; + + }, {} as Record<string, { label: string }>) satisfies ChartConfig; + setBarChartConfig(newChartConfig); + }, [barData]); + + //월별 장내 용수 실적 라인차트 + useEffect(() => { + + if (rawData) { + const wshwtrData: lineChartDataProps[] = [] + const coolUswtrData: lineChartDataProps[] = [] + const cleanUswtrData: lineChartDataProps[] = [] + const drwtUswtrData: lineChartDataProps[] = [] + const dltnUswtrData: lineChartDataProps[] = [] + const ithlRswtrData: lineChartDataProps[] = [] + const ithlEtcUswtrData: lineChartDataProps[] = [] + const cleanToilUswtrData: lineChartDataProps[] = [] + const resnData: lineChartDataProps[] = [] + rawData.forEach((item) => { + item.prfList.filter((e) => e.ym.slice(0, 4) === year).forEach((element) => { + const month = Number(element.ym.slice(4, 6)) + wshwtrData.push({ label: `${month}월`, val: element.wshwtr, color: "red" }) + coolUswtrData.push({ label: `${month}월`, val: element.coolUswtr, color: "red" }) + cleanUswtrData.push({ label: `${month}월`, val: element.cleanUswtr, color: "red" }) + drwtUswtrData.push({ label: `${month}월`, val: element.drwtUswtr, color: "red" }) + dltnUswtrData.push({ label: `${month}월`, val: element.dltnUswtr, color: "red" }) + ithlRswtrData.push({ label: `${month}월`, val: element.ithlRswtr, color: "red" }) + ithlEtcUswtrData.push({ label: `${month}월`, val: element.ithlEtcUswtr, color: "red" }) + cleanToilUswtrData.push({ label: `${month}월`, val: element.cleanToilUswtr, color: "red" }) + resnData.push({ label: `${month}월`, val: element.resn, color: "red" }) + + }, + ); + }) + const rtndata = { + chartData1: wshwtrData, + chartData2: coolUswtrData, + chartData3: cleanUswtrData, + chartData4: drwtUswtrData, + chartData5: dltnUswtrData, + chartData6: ithlRswtrData, + chartData7: ithlEtcUswtrData, + chartData8: cleanToilUswtrData, + chartData9: resnData + } + + setInLineData(rtndata) + } + }, [rawData, year]) + + useEffect(() => { + if (inLineData?.chartData1) { + const newChartConfig = { + chartData1: { + label: '세척수', + color: "red", + }, + chartData2: { + label: '냉각수', + color: "orange", + }, + chartData3: { + label: '청소수', + color: "yellow", + }, + chartData4: { + label: '식수대', + color: "pink", + }, + chartData5: { + label: '희석용수', + color: "purple", + }, + chartData6: { + label: '중수도', + color: "green", + }, + chartData7: { + label: '기타', + color: "blue", + }, + chartData8: { + label: '청소화장실용수', + color: "magenta", + }, + chartData9: { + label: '세척살수용수', + color: "skyblue", + }, + } satisfies ChartConfig; + setLnInChartConfig(newChartConfig); + } + }, [inLineData]); + + //월별 장외 용수 실적 라인차트 + useEffect(() => { + if (rawData) { + const lndwtrData: lineChartDataProps[] = [] + const hydUswtrData: lineChartDataProps[] = [] + const ugwtChrData: lineChartDataProps[] = [] + const farmUswtrData: lineChartDataProps[] = [] + const rvrKeepUswtrData: lineChartDataProps[] = [] + const indstUswtrData: lineChartDataProps[] = [] + rawData.forEach((item) => { + item.prfList.filter((e) => e.ym.slice(0, 4) === year).forEach((element) => { + const month = Number(element.ym.slice(4, 6)) + lndwtrData.push({ label: `${month}월`, val: element.lndwtr, color: "red" }) + hydUswtrData.push({ label: `${month}월`, val: element.hydUswtr, color: "red" }) + ugwtChrData.push({ label: `${month}월`, val: element.ugwtChr, color: "red" }) + farmUswtrData.push({ label: `${month}월`, val: element.farmUswtr, color: "red" }) + rvrKeepUswtrData.push({ label: `${month}월`, val: element.rvrKeepUswtr, color: "red" }) + indstUswtrData.push({ label: `${month}월`, val: element.indstUswtr, color: "red" }) + }, + ); + }) + setOutLineData({ + chartData1: lndwtrData, + chartData2: hydUswtrData, + chartData3: ugwtChrData, + chartData4: farmUswtrData, + chartData5: rvrKeepUswtrData, + chartData6: indstUswtrData, + }) + } + }, [rawData, year]) + + useEffect(() => { + if (outLineData?.chartData1) { + const newChartConfig = { + chartData1: { + label: '조경용수', + color: "red", + }, + chartData2: { + label: '친수용수', + color: "orange", + }, + chartData3: { + label: '지하수충전', + color: "yellow", + }, + chartData4: { + label: '농업용수', + color: "green", + }, + chartData5: { + label: '하천등유지용수', + color: "blue", + }, + chartData6: { + label: '공업용수', + color: "purple", + }, + } satisfies ChartConfig; + setLnOutChartConfig(newChartConfig); + } + }, [outLineData]); + + useEffect(() => { + if (!pbnstPrfmncSttn.isInit) { + pbnstPrfmncInit("1", "11"); + } + }, [pbnstPrfmncSttn.isInit, pbnstPrfmncInit]); + + useEffect(() => { + setbdstTable(prfmncRes); + }, [prfmncRes]); + + return ( + <> + <ContentsDiv> + <ListDiv> + <hr /> + <h3 className="mt-4 ml-2 text-2xl font-medium text-label">하수재이용실적 상세현황</h3> + + <SelectBox + className="w-1/12 text-2xl mt-4 ml-2" + label="연도" + selectClass={""} + selectArray={selectYearList} + value={year} + onSelectValue={(val: string | number) => setYear(String(val))} /> + <h1 className="mt-4 ml-2">장내·외 용도별 하수재이용실적 현황(계)</h1> + + <div className="flex"> + <div className="w-1/3"> + <MainChart chartConfig={chartConfig} chartData={chartData} chartTitle={"장내·외 용수 총 실적"} chartForm={CHART_FORM.DN} /> + </div> + <div className="w-1/3"> + <MainChart chartConfig={dnInChartConfig} chartData={dnInUsage} chartTitle={"장내용수 실적"} chartForm={CHART_FORM.DN} /> + </div> + <div className="w-1/3"> + <MainChart chartConfig={dnOutChartConfig} chartData={dnOutUsage} chartTitle={"장외용수 실적"} chartForm={CHART_FORM.DN} /> + </div> + </div> + <h1 className="">장내·외 월별 하수재이용실적 현황</h1> + <div className="flex"> + <div className="w-1/3"> + <MainChart chartConfig={barChartConfig} chartData={barData} chartTitle={"월별 장내·외 용수 총 실적"} chartForm={CHART_FORM.DOUBLEBAR} /> + </div> + <div className="w-1/3"> + <MainChart chartConfig={lnInChartConfig} chartData={inLineData} chartTitle={"월별 장내 용수 실적"} chartForm={CHART_FORM.LINE} /> + </div> + <div className="w-1/3"> + <MainChart chartConfig={lnOutChartConfig} chartData={outLineData} chartTitle={"월별 장외 용수 총 실적"} chartForm={CHART_FORM.LINE} /> + </div> + </div> + {/* 하수재이용실적 현황 - 조건부 렌더링 */} + {isVisible && ( + <> + <h1 className="">하수재이용실적 현황</h1> + <ListDiv className="col-span-8 pr-6 "> + <ScrollArea className="h-[60vh]"> + <div className="card-header"> + <h3 className="text-2xl font-medium text-label"> + {"하수처리수재이용 실적(m3) 등록결과"} + </h3> + </div> + + <UiTable + headName={undefined} + tableData={prfData || []} + headlist={monthHeadList} + pageSize={0} + total={0} + cellClick={undefined} + onPageChange={function (page: number): void { + throw new Error("Function not implemented."); + }}> + </UiTable> + </ScrollArea> + </ListDiv> + </> + )} + </ListDiv> + </ContentsDiv> + <Toaster position="top-center" /> + </> + ) +} diff --git a/apps/wr/app/map/rainPrfmncSttnPopup.tsx b/apps/wr/app/map/rainPrfmncSttnPopup.tsx new file mode 100644 index 000000000..18cdac8e6 --- /dev/null +++ b/apps/wr/app/map/rainPrfmncSttnPopup.tsx @@ -0,0 +1,421 @@ +"use client"; + +import { useWrStore } from "@/app/store"; +import { + rainZod, + selectClass, + TEST_UUID, + type customRainBdst, +} from "@/app/store/ruseFcltMng/fcltReg/rainReg"; +import { DatePicker, FormUi } from "@common/business_components"; +import { Form, FormField, SelectBox } from "@common/business_components/ui"; +import { Button, Input } from "@common/components/ui"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useEffect, useState } from "react"; +import { useForm, useWatch, type UseFormReturn } from "react-hook-form"; +import { z } from "zod"; +import BdstReg from "@/components/ui/form/fcltReg/bdstReg/bdstReg"; +import { + formatCommaNumber, + handleCommaNumberChange, +} from "@/components/lib/func/commom"; +import { formH1Class, formH2Class } from "@/components/lib/class/comClass"; +import { Label } from "@common/business_components/ui/form/lib/label"; + +export default function RainPrfmncSttnPopup({ + rainForm, + isModal, +}: { + rainForm: customRainBdst; + isModal: boolean; +}) { + const { code, rainBdstReg, rainBdstUpdate, rain } = useWrStore( + (state) => state + ); + const { rainFcltCd } = code; + const defaultValues: z.infer<typeof rainZod> = rainZod.parse({}); + + const form = useForm<z.infer<typeof rainZod>>({ + resolver: zodResolver(rainZod), + defaultValues: defaultValues, + }); + + const [grntchBdstCertYn, instlCstSprtYn, filtrTrtmfctYn] = form.watch([ + "reuseBuilding.0.grntchBdstCertYn", + "reuseBuilding.0.instlCstSprtYn", + "reuseBuilding.0.rainyObj.filtrTrtmfctYn", + ]); + + const cleanRainData = (raw: any): z.infer<typeof rainZod> => { + return { + ...raw, + frstArchPrmsnDt: new Date(raw?.frstArchPrmsnDt || null), + archChgPrmsnDt: raw?.archChgPrmsnDt + ? new Date(raw?.archChgPrmsnDt) + : null, + reuseBuilding: (raw?.reuseBuilding || []).map((item: any) => ({ + ...item, + instlCmptnDt: new Date(item?.instlCmptnDt), + operMngCd: item?.operMngCd ?? "", // null → "" + grntchBdstCertYr: + typeof item?.grntchBdstCertYr === "string" + ? item?.grntchBdstCertYr + : "", // null → "" + rainObj: { + ...item?.rainObj, + }, + })), + }; + }; + + useEffect(() => { + if (grntchBdstCertYn === "N") { + form.setValue("reuseBuilding.0.grntchBdstCertYr", ""); + form.setValue("reuseBuilding.0.grntchBdstCertNo", ""); + form.setValue("reuseBuilding.0.grntchBdstCertGrd", ""); + } else if (instlCstSprtYn === "N") { + form.setValue("reuseBuilding.0.instlCstSprtAmt", 0); + } else if (filtrTrtmfctYn === "N") { + form.setValue("reuseBuilding.0.rainyObj.filtrTrtmfctCpct", 0); + form.setValue("reuseBuilding.0.rainyObj.filtrTrtmfctPrs", ""); + } + }, [grntchBdstCertYn, instlCstSprtYn, filtrTrtmfctYn]); + + // form 값이 변경될 때마다 로그 출력 + useEffect(() => { + const subscription = form.watch((value) => { + }); + + + return () => subscription.unsubscribe(); + }, [form]); + + useEffect(() => { + console.log("form errors:", form.formState.errors); + }, [form.formState.errors]); + + const onSubmit = (values: z.infer<typeof rainZod>) => { + if (isModal) { + rainBdstReg(values, "41"); + } else { + rainBdstUpdate(values, "41"); + } + }; + + useEffect(() => { + if (rainZod) form.reset(cleanRainData(rainForm)); + }, [rainForm]); + + + return ( + <div className="!mx-[1.6rem] !p-0r"> + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)}> + <div> + <div className="grid grid-cols-12 flex items-center card-header"> + <h1 className="text-2xl font-medium text-label col-span-10"> + {isModal ? "건축물 운영정보" : "빗물이용시설 정보"} + </h1> + </div> + </div> + {isModal && rainForm != undefined ? <BdstReg form={form} /> : <></>} + {isModal ? ( + <h1 className="pl-3 text-xl col-span-10 mt-4"> + 빗물이용시설 정보{" "} + </h1> + ) : ( + <></> + )} + <h1 className={formH1Class}> + 기본정보 + </h1> + + <div className="grid grid-cols-4 gap-2 mt-4"> + <div className="col-span-2"> + <FormField + control={form.control} + name="reuseBuilding.0.instlCmptnDt" + render={({ field }) => ( + <FormUi label="설치완료일"> + <DatePicker + mode="single" + field={{ + value: + field.value && !isNaN(new Date(field.value).getTime()) + ? new Date(field.value) + : new Date(), // Provide a default Date value + onChange: (date) => field.onChange(date), + }} + selectClass={"w-full"} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + + <div className="col-span-2"> + <FormField + control={form.control} + name="reuseBuilding.0.instlCst" + render={({ field }) => ( + <FormUi label="설치비"> + <Input + className={'w-full !text-xl'} + placeholder="1,000,000" + type="text" + onChange={(e) => { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + </div> + <div className=""> + <h1 className={formH1Class}> + 빗물이용시설 정보 + </h1> + </div> + <div> + <h2 className={formH2Class}>집수면</h2> + </div> + <div className="grid grid-cols-10 gap-4 mt-4 "> + <div className="col-span-5"> + <FormField + control={form.control} + name="reuseBuilding.0.rainyObj.pntfColKnd" + render={({ field }) => ( + <FormUi label="종류"> + <SelectBox + className={""} + selectClass={selectClass} + label={""} + selectArray={rainFcltCd} + onSelectValue={field.onChange} + value={field.value ?? rainFcltCd[0]?.val} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + <div className="col-span-5"> + <FormField + control={form.control} + name="reuseBuilding.0.rainyObj.pntfColArea" + render={({ field }) => ( + <FormUi label="면적(m³)"> + <Input + className={'w-full !text-xl'} + placeholder="1,000,000" + type="text" + onChange={(e) => { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + /> + </FormUi> + )} + /> + </div> + </div> + + <div> + <h2 className={formH2Class}>여과 등 처리시설</h2> + </div> + <div className="grid grid-cols-12 gap-2 mt-4"> + <div className="col-span-4"> + <FormField + control={form.control} + name="reuseBuilding.0.rainyObj.filtrTrtmfctYn" + render={({ field }) => ( + <FormUi label="유무"> + <SelectBox + className={""} + selectClass={selectClass} + label={""} + selectArray={code.yn} + onSelectValue={field.onChange} + value={field.value ?? code.yn[0]?.val} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + <div className="col-span-4"> + <FormField + control={form.control} + name="reuseBuilding.0.rainyObj.filtrTrtmfctCpct" + render={({ field }) => ( + <FormUi label="용량(m³/일)"> + <Input + className={'w-full !text-xl'} + placeholder="1,000,000" + type="text" + onChange={(e) => { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={filtrTrtmfctYn !== "Y"} + /> + </FormUi> + )} + /> + </div> + <div className="col-span-4"> + <FormField + control={form.control} + name="reuseBuilding.0.rainyObj.filtrTrtmfctPrs" + render={({ field }) => ( + <FormUi label="처리공정"> + <Input + className={'w-full !text-xl'} + placeholder="1,000,000" + type="" + value={field.value ?? ""} + onChange={field.onChange} + readOnly={filtrTrtmfctYn !== "Y"} + /> + </FormUi> + )} + /> + </div> + </div> + <div className=" my-2"> + <h1 className={formH1Class}> + 인센티브 정보 + </h1> + </div> + <div> + <h2 className={formH2Class}>설치비 지원</h2> + </div> + <div className="grid grid-cols-10 gap-2 mt-4"> + <div className="col-span-5"> + <FormField + control={form.control} + name="reuseBuilding.0.instlCstSprtYn" + render={({ field }) => ( + <FormUi label="지원 여부"> + <SelectBox + className={""} + selectClass={selectClass} + label={""} + selectArray={code.yn} + onSelectValue={field.onChange} + value={field.value ?? code.yn[0]?.val} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + <div className="col-span-5"> + <FormField + control={form.control} + name="reuseBuilding.0.instlCstSprtAmt" + render={({ field }) => ( + <FormUi label="지원금액(원)"> + <Input + className={'w-full !text-xl'} + placeholder="1,000,000" + type="text" + onChange={(e) => { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={instlCstSprtYn !== "Y"} + /> + </FormUi> + )} + /> + </div> + </div> + + <div> + <h2 className={formH2Class}>녹색건축물 인증</h2> + </div> + <div className="grid grid-cols-6 gap-2 mt-4"> + <div className="col-span-3"> + <FormField + control={form.control} + name="reuseBuilding.0.grntchBdstCertYn" + render={({ field }) => ( + <FormUi label="인증여부"> + <SelectBox + className={""} + selectClass={selectClass} + label={""} + selectArray={code.yn} + onSelectValue={field.onChange} + value={field.value ?? code.yn[0]?.val} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + <div className="col-span-3"> + <FormField + control={form.control} + name="reuseBuilding.0.grntchBdstCertYr" + render={({ field }) => ( + <FormUi label="인증년도"> + <Input + className={'w-full !text-xl'} + placeholder="2024" + type="" + value={field.value ?? ""} + onChange={field.onChange} + readOnly={grntchBdstCertYn !== "Y"} + /> + </FormUi> + )} + /> + </div> + <div className="col-span-3 "> + <FormField + control={form.control} + name="reuseBuilding.0.grntchBdstCertNo" + render={({ field }) => ( + <FormUi label="인증번호"> + <Input + className={'w-full !text-xl'} + placeholder="ab1234678" + type="" + value={field.value ?? ""} + onChange={field.onChange} + readOnly={grntchBdstCertYn !== "Y"} + /> + </FormUi> + )} + /> + </div> + <div className="col-span-3"> + <FormField + control={form.control} + name="reuseBuilding.0.grntchBdstCertGrd" + render={({ field }) => ( + <FormUi label="인증등급"> + <Input + className={'w-full !text-xl'} + placeholder="A" + type="" + value={field.value ?? ""} + onChange={field.onChange} + readOnly={grntchBdstCertYn !== "Y"} + /> + </FormUi> + )} + /> + </div> + </div> + </form> + </Form> + </div> + ); +} diff --git a/apps/wr/app/map/rswtrPrfmncSttnPopup.tsx b/apps/wr/app/map/rswtrPrfmncSttnPopup.tsx new file mode 100644 index 000000000..e7f94a3b5 --- /dev/null +++ b/apps/wr/app/map/rswtrPrfmncSttnPopup.tsx @@ -0,0 +1,404 @@ +"use client"; + +import { + DatePicker, + Form, + FormField, + FormUi, + SelectBox, +} from "@common/business_components/ui"; +import { Button, Input } from "@common/components/ui"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { useWrStore } from "@/app/store"; +import { selectClass } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; +import { + rswtZod, + type customRswtBdst, +} from "@/app/store/ruseFcltMng/fcltReg/rswtReg"; +import { useEffect } from "react"; +import BdstReg from "@/components/ui/form/fcltReg/bdstReg/bdstReg"; +import { + formatCommaNumber, + handleCommaNumberChange, +} from "@/components/lib/func/commom"; +import { formH1Class, formH2Class } from "@/components/lib/class/comClass"; +import { Label } from "@common/business_components/ui/form/lib/label"; + +export default function RswtrPrfmncsSttnPopup({ + rswtForm, + isModal, +}: { + rswtForm: customRswtBdst; + isModal: boolean; +}) { + const { rswt, code, rswtBdstUpdate, rswtBdstReg } = useWrStore( + (state) => state + ); + + const defaultValues: z.infer<typeof rswtZod> = rswtZod.parse({}); + + const form = useForm<z.infer<typeof rswtZod>>({ + resolver: zodResolver(rswtZod), + defaultValues: defaultValues, + }); + + const [grntchBdstCertYn, instlCstSprtYn, filtrTrtmfctProcs] = form.watch([ + "reuseBuilding.0.grntchBdstCertYn", + "reuseBuilding.0.instlCstSprtYn", + "reuseBuilding.0.rswtrObj.filtrTrtmfctProcs", + ]); + + const onSubmit = (values: z.infer<typeof rswtZod>) => { + if (isModal) { + rswtBdstReg(values, "41"); + } else { + rswtBdstUpdate(values, "41"); + } + }; + const cleanRswtData = (raw: any): z.infer<typeof rswtZod> => { + return { + ...raw, + frstArchPrmsnDt: new Date(raw?.frstArchPrmsnDt || null), + archChgPrmsnDt: raw?.archChgPrmsnDt + ? new Date(raw?.archChgPrmsnDt) + : null, + reuseBuilding: (raw?.reuseBuilding || []).map((item: any) => ({ + ...item, + instlCmptnDt: new Date(item?.instlCmptnDt), + operMngCd: item?.operMngCd ?? "", // null → "" + grntchBdstCertYr: + typeof item?.grntchBdstCertYr === "string" + ? item?.grntchBdstCertYr + : "", // null → "" + rswtrObj: { + ...item?.rswtrObj, + }, + })), + }; + }; + + useEffect(() => { + console.log("form errors:", form.formState.errors); + }, [form.formState.errors]); + + useEffect(() => { + console.log("filtrTrtmfctProcs ", filtrTrtmfctProcs); + if (grntchBdstCertYn === "N") { + form.setValue("reuseBuilding.0.grntchBdstCertYr", ""); + form.setValue("reuseBuilding.0.grntchBdstCertNo", ""); + form.setValue("reuseBuilding.0.grntchBdstCertGrd", ""); + } else if (instlCstSprtYn === "N") { + form.setValue("reuseBuilding.0.instlCstSprtAmt", 0); + } + }, [grntchBdstCertYn, instlCstSprtYn, filtrTrtmfctProcs]); + + useEffect(() => { + if (rswtZod) form.reset(cleanRswtData(rswtForm)); + }, [rswtForm]); + + return ( + <div className="!mx-[1.6rem] !p-0r"> + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)}> + {/* <div className="flex justify-between items-center mx-3"> */} + <div> + <div className="grid grid-cols-12 flex items-center card-header"> + <h1 className="text-2xl font-medium text-label col-span-10"> + 건축물 운영정보 + </h1> + </div> + </div> + + {isModal && rswtForm != undefined ? <BdstReg form={form} /> : <></>} + {isModal ? ( + <h1 className="pl-3 text-xl col-span-10 mt-4 "> + 중수도 시설 정보{" "} + </h1> + ) : ( + <></> + )} + <h1 className={formH1Class}>기본정보</h1> + <div className="grid grid-cols-4 gap-2 mt-4"> + <div className="col-span-2"> + <FormField + control={form.control} + name="reuseBuilding.0.instlCmptnDt" + render={({ field }) => ( + <FormUi label="설치완료일"> + <DatePicker + mode="single" + field={{ + value: + field.value && !isNaN(new Date(field.value).getTime()) + ? new Date(field.value) + : new Date(), // Provide a default Date value + onChange: (date) => field.onChange(date), + }} + selectClass={"w-full"} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + + <div className="col-span-2"> + <FormField + control={form.control} + name="reuseBuilding.0.instlCst" + render={({ field }) => ( + <FormUi label="설치비"> + <Input + className={'w-full !text-xl'} + placeholder="1,000,000" + type="text" + onChange={(e) => { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + </div> + <div className="grid grid-cols-4 gap-2 mt-4"> + <div className="col-span-2"> + <FormField + control={form.control} + name="reuseBuilding.0.rswtrObj.wtrUsqty" + render={({ field }) => ( + <FormUi label="물사용량(m³)"> + <Input + className={'w-full !text-xl'} + placeholder="1,000,000" + type="text" + onChange={(e) => { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + + <div className="col-span-2"> + <FormField + control={form.control} + name="reuseBuilding.0.rswtrObj.oblgTrgtWtrUsqty" + render={({ field }) => ( + <FormUi label="의무대상 물사용량(m³)"> + <Input + className={'w-full !text-xl'} + placeholder="1,000,000" + type="text" + onChange={(e) => { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + </div> + <div className=" my-4"> + <h1 className={formH1Class}>중수도 시설 정보</h1> + </div> + + <div className="grid grid-cols-12 gap-1 mt-4"> + <div className="col-span-4"> + <FormField + control={form.control} + name="reuseBuilding.0.rswtrObj.filtrTrtmfctCpct" + render={({ field }) => ( + <FormUi label="처리시설 용량"> + <Input + className={'w-full !text-xl'} + placeholder="0" + type="number" + value={field.value ?? ""} + onChange={(e)=>{field.onChange(Number(e.target.value))} } + readOnly={true} + /> + </FormUi> + )} + /> + </div> + <div className="col-span-4"> + <FormField + control={form.control} + name="reuseBuilding.0.rswtrObj.orgwtKndCd" + render={({ field }) => ( + <FormUi label="원수 종류"> + <SelectBox + className={""} + label={""} + selectClass={selectClass} + selectArray={code.orgwtKndCd} + onSelectValue={field.onChange} + value={field.value ?? code.orgwtKndCd[0]?.val} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + <div className="col-span-4"> + <FormField + control={form.control} + name="reuseBuilding.0.rswtrObj.filtrTrtmfctProcs" + render={({ field }) => ( + <FormUi label="주요처리공정"> + <Input + className={'w-full !text-xl'} + placeholder="주요처리공정" + type="" + value={field.value ?? ""} + onChange={field.onChange} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + </div> + + <div className=" my-2"> + <h1 className={formH1Class}>인센티브 정보</h1> + </div> + <div> + <h2 className={formH2Class}>설치비 지원</h2> + </div> + <div className="grid grid-cols-2 gap-2 mt-4"> + <div className="col-span-1"> + <FormField + control={form.control} + name="reuseBuilding.0.instlCstSprtYn" + render={({ field }) => ( + <FormUi label="설치비 지원여부"> + <SelectBox + className={""} + selectClass={selectClass} + label={""} + selectArray={code.yn} + onSelectValue={field.onChange} + value={field.value ?? code.yn[0]?.val} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + <div className="col-span-1"> + <FormField + control={form.control} + name="reuseBuilding.0.instlCstSprtAmt" + render={({ field }) => ( + <FormUi label="지원금액(원)"> + <Input + className={'w-full !text-xl'} + placeholder="1,000,000" + type="text" + onChange={(e) => { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={instlCstSprtYn !== "Y"} + /> + </FormUi> + )} + /> + </div> + </div> + + <div> + <h2 className={formH2Class}>녹색건축물 인증</h2> + </div> + <div className="grid grid-cols-6 gap-2 mt-4"> + <div className="col-span-3"> + <FormField + control={form.control} + name="reuseBuilding.0.grntchBdstCertYn" + render={({ field }) => ( + <FormUi label="인증여부"> + <SelectBox + className={""} + selectClass={selectClass} + label={""} + selectArray={code.yn} + onSelectValue={field.onChange} + value={field.value ?? code.yn[0]?.val} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + <div className="col-span-3"> + <FormField + control={form.control} + name="reuseBuilding.0.grntchBdstCertYr" + render={({ field }) => ( + <FormUi label="인증년도"> + <Input + className={'w-full !text-xl'} + placeholder="2024" + type="text" + {...field} + value={field.value ?? ""} + onChange={field.onChange} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + <div className="col-span-3"> + <FormField + control={form.control} + name="reuseBuilding.0.grntchBdstCertNo" + render={({ field }) => ( + <FormUi label="인증번호"> + <Input + className={'w-full !text-xl'} + placeholder="ab1234678" + type="" + value={field.value ?? ""} + onChange={field.onChange} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + <div className="col-span-3"> + <FormField + control={form.control} + name="reuseBuilding.0.grntchBdstCertGrd" + render={({ field }) => ( + <FormUi label="인증등급"> + <Input + className={'w-full !text-xl'} + placeholder="A" + type="" + value={field.value ?? ""} + onChange={field.onChange} + readOnly={true} + /> + </FormUi> + )} + /> + </div> + </div> + </form> + </Form> + </div> + ); +} diff --git a/apps/wr/app/map/service/index.ts b/apps/wr/app/map/service/index.ts new file mode 100644 index 000000000..c6bfcff72 --- /dev/null +++ b/apps/wr/app/map/service/index.ts @@ -0,0 +1 @@ +export * from './map' diff --git a/apps/wr/app/map/service/map.ts b/apps/wr/app/map/service/map.ts new file mode 100644 index 000000000..bf0c8a694 --- /dev/null +++ b/apps/wr/app/map/service/map.ts @@ -0,0 +1,47 @@ + +/** + * 관망도 API + */ +// const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map` +const API_URL = "http://con.mind-one.co.kr:11083/sewerage-service/api/v1/map"; + +export type LyrStngProps = { + lyr_sn: string + lyr_nm: string + upper_lyr_sn: string + drawOrdr: string + flterSql: string + gobjTy: string + lyrTpNm: string + useYn: string + minViewSize: string + iconNm: string + lblAtrbNm: string + fontNm: string + fontIntnColor: string + fontExtnColor: string + fontExtnWdth: string + lnWdth: string + lnColor: string + polgnColor: string + initEprssYn: string + clickPsblYn: string +} + +export const mapService = { + //레이어 설정 조회 + getLyrStngs: async () => { + const response = await fetch(`${API_URL}/lyrStngs/WR_NETWORK`, { + method: 'GET', + headers: { + 'Accept': 'application/json', + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return response.json(); + } +} diff --git a/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx b/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx index e8146b37a..436b77736 100644 --- a/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx +++ b/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx @@ -1,30 +1,162 @@ "use client"; + import { useWrStore } from "@/app/store"; import { ruseUtzType } from "@/app/store/matDmd/ruseCaseMng/headList"; -import { getCity } from "@/components/lib/func/commom"; +import { excelDownload, getCity } from "@/components/lib/func/commom"; +import RuseCaseMngExcel from "@/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal"; import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal"; -import { ContentsDiv, SearchDiv, SearchFormLeft, SearchFormRight, SearchInput, SelectBox, Title, UiTable, type SelectDataType } from "@common/business_components/ui"; +import { + ContentsDiv, + filePickedBlobArray, + SearchDiv, + SearchFormLeft, + SearchFormRight, + SearchInput, + SelectBox, + Title, + UiModal, + UiTable, + type SelectDataType, +} from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; import { Button } from "@common/components/ui"; import { useState } from "react"; export default function RuseCaseMng() { - const { code,ruseCase} = useWrStore((state) => state); - const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>() + const { code, ruseCaseMng, selectRuseCaseMng } = useWrStore((state) => state); + const { ruseCaseMngList } = ruseCaseMng; + const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>([]); const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" }); - const [selectUtz, setSelectUtz] = useState<string[] | []>() - const [searchTxt, setSearchTxt] = useState<string>() + + const [sidoCd, setSidoCd] = useState<string>(""); + const [sigunguCd, setSigunguCd] = useState<string>(""); + const [selectUtz, setSelectUtz] = useState<string[] | []>(["조경용수"]); + const [name, setName] = useState<string>(""); + // 물재이용사례 조회 + const searchRuseCaseMng = async () => { + const res = await selectRuseCaseMng( + sidoCd ?? "", + sigunguCd ?? "", + selectUtz[0] ?? "", + name ?? "" + ); + } + + // 시도 선택 const getSelCity = (val: string | number) => { const cityValue = String(val); + const cityText = code.cityCd.find((item: { val: string; text: string }) => item.val === cityValue)?.text; + setSidoCd(cityText || ""); const data = getCity(cityValue, code.sggCd); setSelectSgg(data); - setObjSearch({ city: cityValue, sgg: '' }); + setObjSearch({ city: cityValue, sgg: "" }); }; + // 시군구 선택 const getSgg = (val: string | number) => { - setObjSearch({ ...objSearch, sgg: String(val) }) + const sggValue = String(val); + const sggText = selectSgg?.find((item: { val: string; text: string }) => item.val === sggValue)?.text; + setSigunguCd(sggText || ""); + setObjSearch({ ...objSearch, sgg: String(val) }); }; + + // 엑셀 등록 모달 + const [isExcelOpen, setIsExcelOpen] = useState<{ + isModal: boolean; + }>({ isModal: false }); + + // 다운로드 + const download = () => { + searchRuseCaseMng(); + + fetch(`/sample_ruseCaseMng.xlsx`, {}).then(async (response) => { + if (!response.ok) { + throw Error(response.statusText); + } + // 받은 파일 바인너리 처리 + const blobRet = await response.arrayBuffer(); + + const excelData1 = ruseCaseMngList.map((item) => { + return [ + item.ruseBizNm, + item.bizBgngDate, + item.bizEndDate, + item.splyFcltKnd, + item.splyFcltNm, + item.splyFcltCpct, + item.dmdFcltNm, + item.dmdFcltAddr, + item.ruseFcltCpct, + item.ruseFcltPplLen, + item.ruseFcltUsg, + item.ruseFcltSplyCpct, + item.ruseFcltPrcsMsrc, + item.grnteWqUsg, + item.grnteWqGrssClfg, + item.grnteWqCombResdch, + item.grnteWqTrbt, + item.grnteWqBod, + item.grnteWqSmll, + item.grnteWqCrmty, + item.grnteWqTn, + item.grnteWqTp, + item.grnteWqPh, + item.grnteWqClIonQnt, + item.grnteWqCndctStat, + item.grnteWqRmrk, + item.reqBizGrssCst, + item.reqBizReuseFcltCst, + item.reqBizPplCst, + item.reqBizArchCst, + item.reqBizMchnCst, + item.reqBizMtrngCst, + item.reqBizGisCst, + item.reqBizCctvCst, + item.reqBizCmcnCst, + item.reqBizWstCst, + item.reqBizDesignCst, + item.reqBizSpvsCst, + item.reqBizSbsdCst, + item.reqBizEtc, + item.prcmPlanGrssCst, + item.prcmPlanNlep, + item.prcmPlanLcep, + item.prcmPlanBrdnCst, + item.prcmPlanEcqrRt, + item.clclnGrssCst, + item.clclnNlep, + item.clclnLcep, + item.clclnBrdnCst, + item.clclnEcqrRt, + ]; + }); + + const sheetParameter = [ + { + data: excelData1, + headerNum: 2, + headerRange: 'A1:AX1', + sheetName: '물재이용사례', + startCol: 'A' + } + ]; + // 버퍼에 있는 엑셀데이터 전달 후 json으로 변환 + await filePickedBlobArray(blobRet, '시설정보및운영현황황', sheetParameter); + }); + } + + // 양식받기 + const sampleDownLoad = () => { + excelDownload( + [[]], + "물재이용사례 관리", + "sample_ruseCaseMng.xlsx", + "A1:AX1", + 2 + ); + } + return ( <> <ContentsDiv> @@ -54,47 +186,32 @@ export default function RuseCaseMng() { selectClass={""} selectArray={ruseUtzType} onSelectValue={(val) => { - if(val) - setSelectUtz([String(val)])}} + if (val) setSelectUtz([String(val)]); + }} /> - <SearchInput className={"text-2xl"} label="수요처명" - textValue={""} + textValue={name} setText={(e) => { - setSearchTxt(e) + setName(e); }} /> </div> - </SearchFormLeft> - <SearchFormRight - className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 "> + <SearchFormRight className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2"> <div className="space-y"> <div className="flex flex-row space-x-2"> - <Button - size={"sm"} - onClick={(e) => { }} - > + <Button size={"sm"} onClick={() => { searchRuseCaseMng() }}> 조회 </Button> - <Button - size={"sm"} - onClick={(e) => { }} - > + <Button size={"sm"} onClick={() => { setIsExcelOpen({ isModal: true }); }}> 엑셀등록 </Button> - <Button - size={"sm"} - onClick={(e) => { }} - > + <Button size={"sm"} onClick={() => { download() }}> 다운로드 </Button> - <Button - size={"sm"} - onClick={(e) => { }} - > + <Button size={"sm"} onClick={() => { sampleDownLoad() }}> 양식받기 </Button> </div> @@ -103,24 +220,35 @@ export default function RuseCaseMng() { </SearchDiv> </div> </div> - <div className="grid grid-cols-6 gap-4 "> + <div className="grid grid-cols-6 gap-4"> <div className="col-span-6"> <ListDiv className="overflow-x-auto"> - <UiTable - headName={"물재이용사례 현황"} - tableData={[]} - children={undefined} - headlist={ruseCase.ruseHeadList} - pageSize={20} - total={100000} - cellClick={(index: number | undefined, headId: string) => { - }} - onPageChange={() => { }} - ></UiTable> + <div className="w-[350%]"> + <UiTable + headName={"물재이용사례 현황"} + tableData={ruseCaseMngList} + children={undefined} + headlist={ruseCaseMng.ruseHeadList} + pageSize={20} + total={100000} + cellClick={(index: number | undefined, headId: string) => {}} + onPageChange={() => {}} + /> + </div> </ListDiv> </div> </div> </ContentsDiv> + + {/* 엑셀 등록 모달 */} + <UiModal + open={isExcelOpen.isModal} + onOpenChange={() => { + setIsExcelOpen({ isModal: !isExcelOpen.isModal }) + }} + bodyClass={"w-[40vw] max-w-none"} + body={<RuseCaseMngExcel />} + /> </> - ) + ); } \ No newline at end of file diff --git a/apps/wr/app/page.tsx b/apps/wr/app/page.tsx index 4c6c58faf..c2be341cf 100644 --- a/apps/wr/app/page.tsx +++ b/apps/wr/app/page.tsx @@ -153,6 +153,18 @@ export default function Home() { </div> </div> </div> + + {/* 시설 현황(지도) */} + <div> + <h2 className="text-xl font-semibold mb-4">시설 현황(지도)</h2> + <div className="grid grid-cols-12 gap-4"> + <div className="col-span-2"> + <Button className="w-full" onClick={() => {router.push('/map/htdflSttn')}}> + 시설 현황(지도) + </Button> + </div> + </div> + </div> </div> </> ); diff --git a/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx b/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx index 441eabf25..174f268d0 100644 --- a/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx @@ -29,9 +29,9 @@ import { import { PbnstMngForm } from "@/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm"; import { PbnstBuseForm } from "@/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm"; import { PbnstRegModal } from "@/components/ui/modal/fcltReg/pbnstRegModal/pbnstRegModal"; -import toast, { Toaster } from "react-hot-toast"; +import toast, { Toaster } from "react-hot-toast"; // toast 이미 import 되어 있음 import { writeRgtrAndMdfr } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg"; -import { useFieldArray, useForm } from "react-hook-form"; +import { useFieldArray, useForm, type FieldErrors } from "react-hook-form"; // FieldErrors 타입 추가 import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; @@ -39,8 +39,8 @@ export default function PublicReuseFaciltyInsert() { const { code, pbnst, - searchFclts, pubHasuInit, + pbnstInsert, pbnstRegClick, pbnstRuseFcltUpdate, pubHasuSearch, @@ -50,17 +50,15 @@ export default function PublicReuseFaciltyInsert() { const [currentTab, setCurrentTab] = useState<string>("mng"); const [isRuseReg, setIsRuseReg] = useState<{ isModal: boolean; - data: pubHasuType; - }>({ isModal: false, data: {} as pubHasuType }); + }>({ isModal: false }); const [indexMng, setIndexMng] = useState<number>(0); const [indexBiz, setIndexBiz] = useState<number>(0); const [searchTxt, setSearchTxt] = useState<string>(); const [bdstTable, setbdstTable] = useState<pubHasuType[]>(); const [selectRow, setSelectRow] = useState<pubHasuType>(); + const [pageNation, setPageNation] = useState<number>(1); - - const form = useForm<{ items: z.infer<typeof pbnstRegZod>[]; }>({ @@ -76,6 +74,8 @@ export default function PublicReuseFaciltyInsert() { control: form.control, name: `items`, }); + + const formWatch = form.watch("items"); useEffect(() => { if (!pbnst.isInit) { pubHasuInit("1", "41"); @@ -88,32 +88,84 @@ export default function PublicReuseFaciltyInsert() { data.forEach((fclt) => { bizs = [...bizs, ...fclt.fcBizs]; }); - const pbnst =data.map((fclt) => { - return({ - pbnstRuseFcltPk: fclt.pbnstRuseFcltPk, - fcltCpct: fclt.fcltCpct, - ruseCpct: fclt.ruseCpct, - splyExtraCpct: fclt.splyExtraCpct, - rgtr: fclt.rgtr, - mdfr: fclt.mdfr, - fcFiles: fclt.fcFiles, - fcOpers: fclt.fcOpers, - }) - }) + const pbnst = data.map((fclt) => { + return { + pbnstRuseFcltPk: fclt.pbnstRuseFcltPk, + fcltCpct: fclt.fcltCpct, + ruseCpct: fclt.ruseCpct, + splyExtraCpct: fclt.splyExtraCpct, + rgtr: fclt.rgtr, + mdfr: fclt.mdfr, + fcFiles: fclt.fcFiles, + fcOpers: fclt.fcOpers, + }; + }); await pbnstRuseFcltUpdate(pbnst as customPbnst[]); await pbnstRuseBizUpdate(bizs); }; - useEffect(() => { - if (code.sggCd.length) { - if (searchFclts) { - setbdstTable(tableData(searchFclts, { code })); - } else if (code.sggCd.length > 0 && pbnstHasuRes.length > 0) { - setbdstTable(tableData(pbnstHasuRes, { code })); - } + // 폼 유효성 검사 실패 시 호출될 함수 + const onInvalid = ( + errors: FieldErrors<{ items: z.infer<typeof pbnstRegZod>[] }> + ) => { + // 재귀 함수를 사용해 중첩된 에러 객체에서 모든 에러 메시지와 경로를 찾습니다. + const findAllErrors = ( + errorObject: any, + path: (string | number)[] = [] + ): { message: string; path: (string | number)[] }[] => { + if (!errorObject) return []; + + return Object.keys(errorObject).flatMap((key) => { + const value = errorObject[key]; + const currentKey = !isNaN(parseInt(key)) ? parseInt(key) : key; + const newPath = [...path, currentKey]; + if (value && typeof value === "object") { + if (typeof value.message === "string") { + return [{ message: value.message, path: newPath }]; // 에러 메시지와 경로 반환 + } + return findAllErrors(value, newPath); + } + return []; + }); + }; + const errorList = findAllErrors(errors); + if (errorList.length > 0) { + errorList.forEach((errorInfo) => { + const { message, path } = errorInfo; + let prefix = ""; + const itemsIndex = path.indexOf("items"); + + if (itemsIndex > -1 && path.length > itemsIndex + 1) { + const itemIdx = path[itemsIndex + 1] as number; + const facilityName = form.getValues( + `items.${itemIdx}.ruseUtztnFcltNm` + ); + if (facilityName) { + prefix += `[${facilityName}] `; + } + + const fcBizsIndex = path.indexOf("fcBizs"); + if (fcBizsIndex > -1 && path.length > fcBizsIndex + 1) { + const bizIdx = path[fcBizsIndex + 1] as number; + const cycle = form.getValues( + `items.${itemIdx}.fcBizs.${bizIdx}.cycl` + ); + if (cycle) { + prefix += `[${cycle}차수] `; + } + } + } + toast.error(`${prefix}${message}`); + }); } - }, [code, pbnstHasuRes, searchFclts]); + }; + + useEffect(() => { + if (code.sggCd.length && pbnstHasuRes.length > 0) { + setbdstTable(tableData(pbnstHasuRes, { code })); + } + }, [code, pbnstHasuRes]); const cellClick = (val: number | undefined) => { if (val === undefined) return; @@ -121,6 +173,8 @@ export default function PublicReuseFaciltyInsert() { setSelectRow(pbnstHasuRes?.[val]); if (selectRow) { pbnstRegClick(selectRow); + } else { + console.error("선택된 행이 없습니다."); } }; @@ -129,12 +183,9 @@ export default function PublicReuseFaciltyInsert() { }; useEffect(() => { - if (pbnst.pbnstRes&& pbnst.pbnstRes?.length>0) { - console.log("pbnst.pbnstRes", pbnstRes); + if (pbnst.pbnstRes && pbnst.pbnstRes?.length > 0) { form.reset({ items: pbnstRegArrayZod.parse(pbnst.pbnstRes) }); - }else{ - - console.log("pbnst.pbnstRes is null", pbnstRes); + } else { form.reset({ items: [] }); } }, [pbnst.pbnstRes]); @@ -150,11 +201,9 @@ export default function PublicReuseFaciltyInsert() { const appendPbnstRuseFclt = useCallback(() => { const selecTableRow = selectRow; - if (pbnstRes && pbnstRes.length > 0) { const lastPbnst = pbnstRes[pbnstRes.length - 1]; if (lastPbnst && selecTableRow) { - append( pbnstRegZod.parse({ pbnstRuseFcltPk: @@ -170,32 +219,33 @@ export default function PublicReuseFaciltyInsert() { ] as fcBizType[], }) ); - setIsRuseReg({ ...isRuseReg, isModal: true }); + setIsRuseReg({ isModal: true }); } } else { if (selecTableRow) { append( - pbnstRegZod.parse({ - pbnstRuseFcltPk: String(selecTableRow.bscFctCd)+'1', - ruseUtztnFcltNm: "", - regDt: null, - mdfcnDt: null, - fcBizs: [fcBizSchema.parse({pbnstRuseFcltFk:String(selecTableRow.bscFctCd)+'1',cycl:1})] as fcBizType[], - }) - ) - setIsRuseReg({ ...isRuseReg, isModal: true }); + pbnstRegZod.parse({ + pbnstRuseFcltPk: String(selecTableRow.bscFctCd) + "1", + ruseUtztnFcltNm: "", + regDt: null, + mdfcnDt: null, + fcBizs: [ + fcBizSchema.parse({ + pbnstRuseFcltFk: String(selecTableRow.bscFctCd) + "1", + cycl: 1, + }), + ] as fcBizType[], + }) + ); + setIsRuseReg({ isModal: true }); } } }, [pbnstRegZod, pbnstRes, append, selectRow, form]); useEffect(() => { - // console.log(" page getgetvaslue ", form.getValues()); - }, [form.getValues()]); + pubHasuInit(String(pageNation), "41"); + }, [pageNation, pubHasuInit]); -useEffect(() =>{ - pubHasuInit(String(pageNation), "41"); -},[pageNation,pubHasuInit]) - return ( <> <ContentsDiv> @@ -206,7 +256,7 @@ useEffect(() =>{ <SearchFormLeft> <SearchInput className={"col-span-3 my-7 !text-3xl"} - label="건물명" + label="시설명" textValue={searchTxt || ""} setText={(e) => { setSearchTxt(e); @@ -228,7 +278,7 @@ useEffect(() =>{ size={"sm"} className="" onClick={() => { - // pbnstMngData([] as customPbnst[]); + // pbnstMngData([] as customPbnst[]); if (!selectRow) { toast.error( "하수리시시설을 테이블을 선택하고 등록버튼을 눌러주세요" @@ -251,6 +301,8 @@ useEffect(() =>{ <div className="col-span-4 h-full min-h-0"> <ListDiv className="min-h-0"> <ScrollArea className="h-[60vh]"> + {" "} + {/* 높이 조정 */} <UiTable tableData={bdstTable || []} headlist={pubFacHeadList} @@ -266,59 +318,95 @@ useEffect(() =>{ </div> <ListDiv className="col-span-2 pr-6 "> <ScrollArea className="h-[60vh]"> - <Tabs defaultValue="mng" onValueChange={handleTabChange}> - <TabsList className=""> - <TabsTrigger className="!text-xl" value="mng"> - 시설정보 - </TabsTrigger> - <TabsTrigger className="!text-xl" value="buse"> - 차수별정보 - </TabsTrigger> - <Button + {formWatch && formWatch[0]?.pbnstRuseFcltPk ? ( + <Tabs defaultValue="mng" onValueChange={handleTabChange}> + <TabsList className=""> + <TabsTrigger className="!text-xl" value="mng"> + 시설정보 + </TabsTrigger> + <TabsTrigger className="!text-xl" value="buse"> + 차수별정보 + </TabsTrigger> + <Button + size={"sm"} + onClick={form.handleSubmit(onSubmit, onInvalid)} + className="!justify-end" // 저장 버튼 위치 조정 + > + 저장 + </Button> + </TabsList> + <TabsContent className="max-h-full" value="mng"> + <PbnstMngForm + formType="update" + form={form} + setIndexMng={(index: number) => { + console.log("setIndexMng", index); + setIndexMng(index); + }} + indexMng={indexMng} + /> + </TabsContent> + <TabsContent className="max-h-full" value="buse"> + <PbnstBuseForm + formType="update" + form={form} + setIndexBiz={(index: number) => { + setIndexBiz(index); + }} + indexMng={indexMng} + indexBiz={indexBiz} + /> + </TabsContent> + </Tabs> + ) : ( + <> + <div className="card-header text-2xl"> + 등록된 재이용시설 정보가 없습니다. + </div> + <div className="!mx-[1.6rem] !p-0r flex flex-col items-center justify-center h-64 border rounded-md"> + <p className="text-3xl mb-4"> + 신규등록 버튼을 클릭하여 재이용시설을 등록해주세요. + </p> + <Button size={"sm"} - onClick={form.handleSubmit(onSubmit)} - className="!justify-end" + className="" + onClick={() => { + // pbnstMngData([] as customPbnst[]); + if (!selectRow) { + toast.error( + "하수리시시설을 테이블을 선택하고 등록버튼을 눌러주세요" + ); + return; + } else { + appendPbnstRuseFclt(); + } + }} > - 저장 + 신규등록 </Button> - </TabsList> - <TabsContent className="max-h-full" value="mng"> - <PbnstMngForm - formType="update" - form={form} - setIndexMng={(index: number) => { - console.log("setIndexMng", index); - setIndexMng(index); - }} - indexMng={indexMng} - /> - </TabsContent> - <TabsContent className="max-h-full" value="buse"> - <PbnstBuseForm - formType="update" - form={form} - setIndexBiz={(index: number) => { - setIndexBiz(index); - }} - indexMng={indexMng} - indexBiz={indexBiz} - /> - </TabsContent> - </Tabs> + </div> + </> + + )} </ScrollArea> </ListDiv> <UiModal body={ <PbnstRegModal - form={form} - data={isRuseReg.data || ({} as customPbnst)} + form={form} // form prop 전달 + setPbnstInsert={(data) => { + if (data) { + pbnstInsert(data); // pbnstInsert 함수 호출 + setIsRuseReg({ isModal: false }); + } + }} ></PbnstRegModal> } - bodyClass="w-[68vw] max-w-none h-[90vh]" + bodyClass="w-[68vw] max-w-none h-[90vh]" open={isRuseReg.isModal} onOpenChange={(open) => { remove(form.getValues("items").length - 1); - setIsRuseReg({ data: {} as pubHasuType, isModal: open }); + setIsRuseReg({ isModal: open }); }} /> </div> diff --git a/apps/wr/app/ruseFcltMng/fcltReg/rainReg/page.tsx b/apps/wr/app/ruseFcltMng/fcltReg/rainReg/page.tsx index 31743aaa9..2fa8b12b8 100644 --- a/apps/wr/app/ruseFcltMng/fcltReg/rainReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltReg/rainReg/page.tsx @@ -38,8 +38,10 @@ export default function Home() { }>({ isOpen: false, chgBuilding: [] }); useEffect(() => { + setIsNewReg( false) if (!rain.isInit) { initRain('41'); + } }, [rain.isInit, initRain]); @@ -80,7 +82,6 @@ export default function Home() { }; useEffect(() => { - console.log("getPageNation", pageNation); getRainData(pageNation, "41"); }, [pageNation]); diff --git a/apps/wr/app/ruseFcltMng/fcltReg/rswtReg/page.tsx b/apps/wr/app/ruseFcltMng/fcltReg/rswtReg/page.tsx index 2931690b9..d57c730ce 100644 --- a/apps/wr/app/ruseFcltMng/fcltReg/rswtReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltReg/rswtReg/page.tsx @@ -42,8 +42,8 @@ export default function Home() { } }, [rswt.isInit, initRswt]); useEffect(() => { - if (code.sggCd.length > 0) { - const rswtData = tableData(rswtRes, { code }).map( + if (code.sggCd.length > 0 && rswtRes?.length > 0) { + const rswtData = tableData(rswtRes, { code })?.map( (item: customRswtBdst) => { return { ...item, @@ -151,7 +151,7 @@ export default function Home() { </div> <UiModal - body={<BdstReg moType="RST"></BdstReg>} + body={<BdstReg moType="RST" setIsModal={setIsNewReg}></BdstReg>} bodyClass="w-[68vw] max-w-none h-[90vh]" open={issNewReg} onOpenChange={(open) => { @@ -162,7 +162,7 @@ export default function Home() { bodyClass={""} open={bdstChgProps.isOpen} body={ - <BdstChgModal chgBuilding={bdstChgProps.chgBuilding}></BdstChgModal> + <BdstChgModal chgBuilding={bdstChgProps.chgBuilding} ></BdstChgModal> } onOpenChange={(open) => { setBdstChgProps({ ...bdstChgProps, isOpen: open }); diff --git a/apps/wr/app/ruseFcltMng/fcltSttn/rainSttn/page.tsx b/apps/wr/app/ruseFcltMng/fcltSttn/rainSttn/page.tsx index 631e24350..f101be91b 100644 --- a/apps/wr/app/ruseFcltMng/fcltSttn/rainSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltSttn/rainSttn/page.tsx @@ -37,10 +37,7 @@ export default function RainSttn() { const [pageNation, setPageNation] = useState(1); const [isChHead, setIsChHead] = useState(false); const [bdstTable, setBdstTable] = useState<customRainBdst[]>(); - const [detailModal, setDetailModal] = useState<{ - isOpen: boolean; - data: customRainBdst | undefined; - }>({ data: undefined, isOpen: false }); + const [detailModal, setDetailModal] = useState<{ isOpen: boolean, data: customRainBdst | undefined }>({ data: undefined, isOpen: false }) const [bdstChgProps, setBdstChgProps] = useState<{ isOpen: boolean; chBuilding: chgBuilding[]; @@ -49,25 +46,12 @@ export default function RainSttn() { const codeData = getCityOrSgg(ref); const cellClick = (index: number | undefined, headId: string) => { - if ( - headId !== "chBuildingYn" && - index !== undefined && - bdstTable !== undefined - ) { - setDetailModal({ isOpen: true, data: bdstTable[index] }); - } else if ( - headId === "chBuildingYn" && - index !== undefined && - bdstTable !== undefined - ) { - if ( - bdstTable[index] && - bdstTable[index]?.chgBuilding[0]?.bdstChgNm !== null - ) { - setBdstChgProps({ - isOpen: true, - chBuilding: bdstTable[index].chgBuilding, - }); + + if (headId !== 'chBuildingYn' && index !== undefined && bdstTable !== undefined) { + setDetailModal({ isOpen: true, data: bdstTable[index] }) + } else if (headId === 'chBuildingYn' && index !== undefined && bdstTable !== undefined) { + if (bdstTable[index] && bdstTable[index]?.chgBuilding[0]?.bdstChgNm !== null) { + setBdstChgProps({ isOpen: true, chBuilding: bdstTable[index].chgBuilding }); } } }; @@ -168,9 +152,7 @@ export default function RainSttn() { headlist={rainSttn.rainSttHeadList} pageSize={20} total={100000} - cellClick={(index: number | undefined, headId: string) => { - cellClick(index ?? undefined, headId); - }} + cellClick={(index: number | undefined, headId: string) => { cellClick(index ?? undefined, headId) }} onPageChange={setPageNation} ></UiTable> </ListDiv> diff --git a/apps/wr/app/ruseFcltMng/fcltSttn/rswtSttn/page.tsx b/apps/wr/app/ruseFcltMng/fcltSttn/rswtSttn/page.tsx index 7a3444a9b..f9b3c6187 100644 --- a/apps/wr/app/ruseFcltMng/fcltSttn/rswtSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltSttn/rswtSttn/page.tsx @@ -65,7 +65,7 @@ export default function RswtSttn() { }, [rswtSttn.isInit, rswtSttnInit]); useEffect(() => { - if (rswtSttnRes.length > 0 && code.cityCd.length > 0) { + if (rswtSttnRes?.length > 0 && code.cityCd.length > 0) { const data = tableData(rswtSttnRes, { code }); const chData = data .map((item: customRswtBdst) => ({ diff --git a/apps/wr/app/store/common/code/index.ts b/apps/wr/app/store/common/code/index.ts index e7fca8180..83d87d738 100644 --- a/apps/wr/app/store/common/code/index.ts +++ b/apps/wr/app/store/common/code/index.ts @@ -10,6 +10,32 @@ const yn: SelectDataType[] = [ { val: 'N', text: 'N' }, ] +//처리장연계/자체+연계/자체처리 +const erctUswtrPrcsMeth : SelectDataType[]= [ + { val: '처리장연계', text: '처리장연계' }, + { val: '자체+연계', text: '자체+연계' }, + { val: '자체처리', text: '자체처리' }, +] + +const rusePrcsMthdCd : SelectDataType[] = [ + { val: '물리학적처리', text: '물리학적처리' }, + { val: '생물학적처리', text: '생물학적처리' }, +] + +const ruseSeCd : SelectDataType[] = [ + { val: '신규', text: '신규' }, + { val: '증설', text: '증설' }, + { val: '관로', text: '관로' }, + { val: '감량', text: '감량' }, + { val: '개량', text: '개량' }, +] + +const ruseBizMthdCd: SelectDataType[] = [ + { val: '재정', text: '재정' }, + { val: '민간투자', text: '민간투자' }, + { val: '자체', text: '자체' }, +] + //공통 건축물 용도 const bdstUsgCd: SelectDataType[] = [ { val: '-1', text: "확인불가" }, @@ -174,6 +200,10 @@ export type comCodeState = { HTB:string } ruseMngMtd:SelectDataType[] + ruseSeCd: SelectDataType[]; + ruseBizMthdCd: SelectDataType[]; + rusePrcsMthdCd: SelectDataType[]; + erctUswtrPrcsMeth: SelectDataType[]; }; }; @@ -323,6 +353,10 @@ export const comCodeStateExport: comCodeState = { dmdUszCd:dmdUszCd, buildingType:buildingType, ruseMngMtd:ruseMngMtd, + ruseSeCd:ruseSeCd, + ruseBizMthdCd: ruseBizMthdCd, + rusePrcsMthdCd:rusePrcsMthdCd, + erctUswtrPrcsMeth:erctUswtrPrcsMeth }, }; diff --git a/apps/wr/app/store/common/searchCode/index.ts b/apps/wr/app/store/common/searchCode/index.ts index 8f05eca09..51ebda8a5 100644 --- a/apps/wr/app/store/common/searchCode/index.ts +++ b/apps/wr/app/store/common/searchCode/index.ts @@ -7,8 +7,50 @@ import { import type { pubHasuType } from "../../ruseFcltMng/fcltReg/pbnstReg/type"; import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; import type { conCostFeeType, rateFeeType } from "./type"; + + +export const comFcltStateExport: comFcltState = { + searchFclts: [] as pubHasuType[], + demandCompList: [], + dmdIsSucees: '', + checkDemand: { + index: 0, + dmdInfoPk: "", + dmdInfoFk: "", + bzmnNo: "", + dmdCoNm: "", + daddr: "", + telno: "", + rgtr: "", + useUsgCd: "", + ctpvCd: "", + sggCd: "", + dmdAplyPswd: "", + wntUntprc: 0, + dmndTmng: "", + dmndQty: 0, + grssClfg: 0, + tn: 0, + combResdch: 0, + tph: 0, + trbt: 0, + ph: 0, + blgcOxygDmndQnt: 0, + clIonQnt: 0, + smll: 0, + cdtnv: 0, + crmty: 0, + rmrk: "", + reqDt: "" + }, + constCostRes:[], + rateFeeRes:[], +}; + +export type comFcltType = comFcltState & comFcltActions; + export type comFcltState = { - searchFclts: pubHasuType; + searchFclts: pubHasuType[]; demandCompList: DemandCompType[]; dmdIsSucees: string; checkDemand: DemandCompType; @@ -16,7 +58,7 @@ export type comFcltState = { rateFeeRes:rateFeeType[], }; -export type comFcltActions = { + type comFcltActions = { getSwtrmtFcltNm: (searchNm: string) => void; getDemandCompList: (searchNm: string, cnt: number) => void; regDemandCompany: (data: DemandCompType) => void; @@ -25,7 +67,7 @@ export type comFcltActions = { getRateFee: (year: string) => void; }; -export const searchActions: (set: any) => comFcltActions = (set: any) => { +export const comFcltSearchAction: (set: any) => comFcltActions = (set: any) => { return { getSwtrmtFcltNm: async (searchNm: string) => { const fcltNm = await getFetch( @@ -40,6 +82,7 @@ export const searchActions: (set: any) => comFcltActions = (set: any) => { addr: item.addr, fctCpc: item.fctCpc, })); + set((state: comFcltState) => ({ ...state, searchFclts: searchFclts, diff --git a/apps/wr/app/store/map/index.tsx b/apps/wr/app/store/map/index.tsx new file mode 100644 index 000000000..6a1e76c9b --- /dev/null +++ b/apps/wr/app/store/map/index.tsx @@ -0,0 +1,185 @@ +import { getFetch } from "@/components/lib/api"; + +export type wrMapType = wrMapState & wrMapActions; + +export type wrMapState = { + wrMap: { + buildingSearchRes: any[] + } // 건물 검색 결과 데이터 +}; + +type wrMapActions = { + buildingNmSearch: ( + flg: string, + sigunCode: string, + searchNm: string | undefined + ) => void; + buildingNmSearchMultiple: ( + checkedLayers: string[], + sigunCode: string, + searchNm: string | undefined + ) => void; // 여러 레이어 동시 검색 함수 추가 +}; + +export const wrMapStateExport: wrMapState = { + wrMap: { + buildingSearchRes: [] + } +}; + +// 레이어 타입과 플래그 매핑 +const LAYER_FLAG_MAP: { [key: string]: string } = { + // '46': '???', // 하수처리시설 + // '47': '???', // 하수재이용시설 + '48': 'RNB', // 빗물이용시설 + '49': 'RST', // 중수도시설 + '50': 'HTB', // 온배수시설 +}; + +const findBdst = async (flg: string, sggCd: string, searchTxt: string | undefined) => { + return await getFetch('wr', `/reuseFc/building/${flg}/${sggCd}` + (searchTxt ? `/${searchTxt}` : '')) +} + +export const wrMapActionsExport: (set: any) => wrMapActions = ( + set: any +) => { + return { + buildingNmSearch: async ( + flg: string, + sigunCode: string, + searchNm: string | undefined + ) => { + try { + // if (!searchNm || searchNm.length < 2) { + // set((state: wrMapState) => ({ + // ...state, + // buildingSearchData: [] + // })); + // return; + // } + console.log("건축물 코드, 시군구 코드, 검색어: ", flg, sigunCode, searchNm); + + const response = await findBdst(flg, sigunCode, searchNm); + console.log("response: ", response); + + // API 응답을 SearchInputText 형식으로 변환 + const searchListFormatted = (response || []).map((building: any) => ({ + searchNm: building.bdstNm, + searchPk: String(building.bdstInfoPk), + dmdCoNm: building.bdstNm, + layerType: flg, + ...building + })); + + // findBdst와 유사한 API 호출 + // findBdst(flg, sigunCode || "", searchNm).then(() => { + // console.log("건물 검색 API 호출: ", sigunCode, searchNm, flg); + // // bdst.bdstBase를 SearchInputText 형식으로 변환 + // const searchListFormatted = (state.bdst.bdstBase || []).map((building: any) => ({ + // searchNm: building.bdstNm, + // searchPk: String(building.bdstInfoPk), + // dmdCoNm: building.bdstNm, + // layerType: flg, + // ...building + // })); + + // set((state: any) => ({ + // ...state, + // wrMap: { + // ...state.wrMap, + // buildingSearchData: searchListFormatted + // } + // })); + // }); + set((state: wrMapState) => ({ + ...state, + wrMap: { + ...state.wrMap, + buildingSearchRes: searchListFormatted + } + })); + } catch (error) { + console.error("건물 검색 중 오류:", error); + } + }, + + buildingNmSearchMultiple: async ( + checkedLayers: string[], + sigunCode: string, + searchNm: string | undefined + ) => { + try { + if (!searchNm || searchNm.length < 2) { + set((state: any) => ({ + ...state.wrMap, + buildingSearchData: [] + })); + return; + } + + // 체크된 레이어들에 대해 병렬로 API 호출 + const searchPromises = checkedLayers + .filter(layer => LAYER_FLAG_MAP[layer]) // 매핑된 레이어만 필터링 + .map(layer => { + const flg = LAYER_FLAG_MAP[layer]; + + return findBdst(flg, sigunCode, searchNm) + .then(response => { + console.log(`레이어 ${layer}(${flg}) API 응답:`, response); + + const searchListFormatted = (state.bdst.bdstBase || []).map((building: any) => ({ + searchNm: building.bdstNm, + searchPk: String(building.bdstInfoPk), + dmdCoNm: building.bdstNm, + layerType: flg, + ...building + })); + + return { + layerType: layer, + flg: flg, + data: bdstNames + }; + }) + .catch(error => { + console.error(`레이어 ${layer}(${flg}) 검색 중 오류:`, error); + return { layerType: layer, flg: flg, data: [] }; + }); + }); + + const results = await Promise.all(searchPromises); + console.log("다중 레이어 검색 결과:", results); + + // 모든 결과를 하나의 배열로 합치기 + const allSearchResults = results.reduce((acc, result) => { + const formatted = result.data.map((building: any) => ({ + searchNm: building.bdstNm, + searchPk: String(building.bdstInfoPk), + dmdCoNm: building.bdstNm, + layerType: result.layerType, + flg: result.flg, + ...building.originalData + })); + return [...acc, ...formatted]; + }, [] as any[]); + + console.log("최종 합쳐진 검색 결과:", { + totalCount: allSearchResults.length, + sampleData: allSearchResults.slice(0, 3) + }); + + set((state: any) => ({ + ...state.wrMap, + buildingSearchData: allSearchResults + })); + + } catch (error) { + console.error("다중 레이어 검색 중 오류:", error); + set((state: any) => ({ + ...state, + buildingSearchData: [] + })); + } + } + }; +}; \ No newline at end of file diff --git a/apps/wr/app/store/matDmd/ruseCaseMng/excel.tsx b/apps/wr/app/store/matDmd/ruseCaseMng/excel.tsx new file mode 100644 index 000000000..ebbaa8b7a --- /dev/null +++ b/apps/wr/app/store/matDmd/ruseCaseMng/excel.tsx @@ -0,0 +1,109 @@ +import { z } from "zod"; + +export const reUseCaseExcel = z.object({ + file: z.any() +}); + +type nameMap = { + [key: string]: string; +} + +export const reUseCaseNames: nameMap = { + "사업명": "ruseBizNm", + "사업시작일": "bizBgngDate", + "사업종료일": "bizEndtDate", + "공급처_시설종류": "splyFcltKnd", + "공급처명": "splyFcltNm", + "공급처_시설용량(㎥/일)": "splyFcltCpct", + "수요처명": "dmdFcltNm", + "수요처_주소": "dmdFcltAddr", + "재이용시설_시설용량(㎥/일)": "ruseFcltCpct", + "재이용시설_관로(km)": "ruseFcltPplLen", + "재이용시설_용도": "ruseFcltUsg", + "재이용시설_공급량(㎥/일)": "ruseFcltSplyCpct", + "재이용시설_처리공법": "ruseFcltPrcsMsrc", + "보증수질_용도별": "grnteWqUsg", + "보증수질_총대장균군수": "grnteWqGrssClfg", + "보증수질_결합잔류염소": "grnteWqCombResdch", + "보증수질_탁도": "grnteWqTrbt", + "보증수질_생물학적산소요구량(BOD)(mg/L)": "grnteWqBod", + "보증수질_냄새": "grnteWqSmll", + "보증수질_색도(도)": "grnteWqCrmty", + "보증수질_총질소(T-N)(mg/L)": "grnteWqTn", + "보증수질_총인(T-P)(mg/L)": "grnteWqTp", + "보증수질_수소이온농도(pH)": "grnteWqPh", + "보증수질_염화물": "grnteWqClIonQnt", + "보증수질_전기전도도": "grnteWqCndctStat", + "보증수질_비고": "grnteWqRmrk", + "소요사업비_총(백만원)": "reqBizGrssCst", + "소요사업비_토목_재이용시설(백만원)": "reqBizReuseFcltCst", + "소요사업비_토목_관로(백만원)": "reqBizPplCst", + "소요사업비_건축(백만원)": "reqBizArchCst", + "소요사업비_기계(백만원)": "reqBizMchnCst", + "소요사업비_전기.계측(백만원)": "reqBizMtrngCst", + "소요사업비_GIS DB(백만원)": "reqBizGisCst", + "소요사업비_CCTV(백만원)": "reqBizCctvCst", + "소요사업비_시공 및 준공(백만원)": "reqBizCmcnCst", + "소요사업비_폐기물(백만원)": "reqBizWstCst", + "소요사업비_설계비(백만원)": "reqBizDesignCst", + "소요사업비_감리비(백만원)": "reqBizSpvsCst", + "소요사업비_시설부대비(백만원)": "reqBizSbsdCst", + "소요사업비_기타(백만원)": "reqBizEtc", + "재원조달계획_총(백만원)": "prcmPlanGrssCst", + "재원조달계획_국비(백만원)": "prcmPlanNlep", + "재원조달계획_지방비(백만원)": "prcmPlanLcep", + "재원조달계획_원인자부담(백만원)": "prcmPlanBrdnCst", + "재원조달계획_국고비율(백만원)": "prcmPlanEcqrRt", + "사업비 정산_총(백만원)": "clclnGrssCst", + "사업비 정산_국비(백만원)": "clclnNlep", + "사업비 정산_지방비(백만원)": "clclnLcep", + "사업비 정산_원인자부담(백만원)": "clclnBrdnCst", + "사업비 정산_국고비율(백만원)": "clclnEcqrRt" +} + +interface PbnstSttnItem { + rvbsn: string; + lgbsn: number; + mdmbsn?: string; + rgnNm?: string; + sido?: number; + gugun?: number; + bscFctNm?: string; + efflWqltStndrdNm?: string; + fctCpc?: number; + tel?: string; + ruseUtztnFcltNm?: string; + fcltCpct?: number; + splyExtraCpct?: number; + ruseCpct?: number; + usageTotal?: number; + inUsage?: number; + outUsage?: number; +} + +export const excelPbnstSttnParsing = (data: PbnstSttnItem[]): string[][] => { + const excelExportData: string[][] = []; + + data.forEach((item) => { + excelExportData.push([ + item.rvbsn, + String(item.lgbsn), + item.mdmbsn ?? "", + item.rgnNm ?? "", + String(item.sido ?? ""), + String(item.gugun ?? ""), + String(item.bscFctNm ?? ""), + String(item.efflWqltStndrdNm ?? ""), + String(item.fctCpc ?? ""), + String(item.tel ?? ""), + String(item.ruseUtztnFcltNm ?? ""), + String(item.fcltCpct ?? ""), + String(item.splyExtraCpct ?? ""), + String(item.ruseCpct ?? ""), + String(item.usageTotal ?? ""), + String(item.inUsage ?? ""), + String(item.outUsage ?? ""), + ]) + }) + return excelExportData; +}; \ No newline at end of file diff --git a/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx b/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx index 125866601..8c3a232e7 100644 --- a/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx +++ b/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx @@ -4,15 +4,22 @@ import type { TableUpperProps } from "../../common/type/header"; //★☆★☆ 여기서부터 수정하기 ★☆★☆ export const ruseCaseHeadListExport: TableUpperProps[] = [ { - id: "a", + id: "ruseBizNm", title: "사업명", visible: true, row: 0, rowSpan: 2, }, { - id: "b", - title: "사업기간", + id: "bizBgngDate", + title: "사업시작일", + visible: true, + row: 0, + rowSpan: 2, + }, + { + id: "bizEndDate", + title: "사업종료일", visible: true, row: 0, rowSpan: 2, @@ -25,19 +32,19 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [ colSpan: 3, }, { - id: "d", + id: "splyFcltKnd", title: "시설종류", visible: true, row: 1, }, { - id: "e", + id: "splyFcltNm", title: "공급처명", visible: true, row: 1, }, { - id: "f", + id: "splyFcltCpct", title: "시설용량(m³/일)", visible: true, row: 1, @@ -50,13 +57,13 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [ colSpan: 2, }, { - id: "h", + id: "dmdFcltNm", title: "수요처명", visible: true, row: 1, }, { - id: "i", + id: "dmdFcltAddr", title: "주소", visible: true, row: 1, @@ -69,31 +76,31 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [ colSpan: 5, }, { - id: "q2", + id: "ruseFcltCpct", title: "시설용량(m³/일)", visible: true, row: 1, }, { - id: "q3", + id: "ruseFcltPplLen", title: "관로(km)", visible: true, row: 1, }, { - id: "q4", + id: "ruseFcltUsg", title: "용도", visible: true, row: 1, }, { - id: "q5", + id: "ruseFcltSplyCpct", title: "공급량(m³/일)", visible: true, row: 1, }, { - id: "q6", + id: "ruseFcltPrcsMsrc", title: "처리공법", visible: true, row: 1, @@ -106,79 +113,79 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [ colSpan: 13, }, { - id: "k1", + id: "grnteWqUsg", title: "용도별", visible: true, row: 1, }, { - id: "k2", + id: "grnteWqGrssClfg", title: "총대장균군수​(개/100ml)", visible: true, row: 1, }, { - id: "k3", + id: "grnteWqCombResdch", title: "결합잔류염소(mg/L)", visible: true, row: 1, }, { - id: "k4", + id: "grnteWqTrbt", title: "탁도(NTU)", visible: true, row: 1, }, { - id: "k5", + id: "grnteWqBod", title: "생물학적​산소요구량(BOD)(mg/L)​", visible: true, row: 1, }, { - id: "k6", + id: "grnteWqSmll", title: "냄새​", visible: true, row: 1, }, { - id: "k7", + id: "grnteWqCrmty", title: "색도​", visible: true, row: 1, }, { - id: "k8", + id: "grnteWqTn", title: "총질소(T-N)(mg/L)​", visible: true, row: 1, }, { - id: "k9", + id: "grnteWqTp", title: "총인(T-P)(mg/L)", visible: true, row: 1, }, { - id: "k10", + id: "grnteWqPh", title: "수소이온농도(PH)​", visible: true, row: 1, }, { - id: "k11", - title: "수소이온농도(PH)​", + id: "grnteWqClIonQnt", + title: "염화물", visible: true, row: 1, }, { - id: "k12", - title: "전기전도도​(㎲/cm", + id: "grnteWqCndctStat", + title: "전기전도도​(㎲/cm)", visible: true, row: 1, }, { - id: "k13", + id: "grnteWqRmrk", title: "비고​", visible: true, row: 1, @@ -191,85 +198,85 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [ colSpan: 14, }, { - id: "h1", + id: "reqBizGrssCst", title: "총", visible: true, row: 1, }, { - id: "h2", - title: "토목재이용시설", + id: "reqBizReuseFcltCst", + title: "토목재이용시설", visible: true, row: 1, }, { - id: "h3", + id: "reqBizPplCst", title: "토목관로", visible: true, row: 1, }, { - id: "h4", + id: "reqBizArchCst", title: "건축", visible: true, row: 1, }, { - id: "h5", + id: "reqBizMchnCst", title: "기계", visible: true, row: 1, }, { - id: "h6", + id: "reqBizMtrngCst", title: "전기・계측", visible: true, row: 1, }, { - id: "h7", + id: "reqBizGisCst", title: "GIS・DB", visible: true, row: 1, }, { - id: "h8", + id: "reqBizCctvCst", title: "CCTV", visible: true, row: 1, }, { - id: "h9", + id: "reqBizCmcnCst", title: "시공및준공", visible: true, row: 1, }, { - id: "h10", + id: "reqBizWstCst", title: "페기물", visible: true, row: 1, }, { - id: "h11", - title: "설계비", + id: "reqBizDesignCst", + title: "설계비", visible: true, row: 1, }, { - id: "h12", + id: "reqBizSpvsCst", title: "감리비", visible: true, row: 1, }, { - id: "h13", + id: "reqBizSbsdCst", title: "시설부대비", visible: true, row: 1, }, { - id: "h14", + id: "reqBizEtc", title: "기타", visible: true, row: 1, @@ -279,32 +286,38 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [ title: "자원조달계획(백만원)", visible: true, row: 0, - colSpan: 4, + colSpan: 5, }, { - id: "i1", + id: "prcmPlanGrssCst", title: "총", visible: true, row: 1, }, { - id: "i2", + id: "prcmPlanNlep", title: "국비", visible: true, row: 1, }, { - id: "i3", + id: "prcmPlanLcep", title: "지방비", visible: true, row: 1, }, { - id: "i4", + id: "prcmPlanBrdnCst", title: "원인자부담", visible: true, row: 1, }, + { + id: "prcmPlanEcqrRt", + title: "국고비율", + visible: true, + row: 1, + }, { id: "j", title: "사업비정산(백만원)", @@ -313,31 +326,31 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [ colSpan: 5, }, { - id: "j1", + id: "clclnGrssCst", title: "총", visible: true, row: 1, }, { - id: "j2", + id: "clclnNlep", title: "국비", visible: true, row: 1, }, { - id: "j3", + id: "clclnLcep", title: "지방비", visible: true, row: 1, }, { - id: "j4", + id: "clclnBrdnCst", title: "원인자부담", visible: true, row: 1, }, { - id: "j5", + id: "clclnEcqrRt", title: "국고비율", visible: true, row: 1, diff --git a/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx b/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx index c54b0db1a..915740f79 100644 --- a/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx +++ b/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx @@ -1,24 +1,117 @@ +import { fetchType, getFetch, postFectch } from "@/components/lib/api"; import type { TableUpperProps } from "../../common/type/header"; import { ruseCaseHeadListExport } from "./headList"; +import { ToastFormUi } from "@/components/ui/toast/toastFromUi"; export type ruseCaseState = { - ruseCase: { - ruseHeadList:TableUpperProps[]; - }; + ruseCaseMng: { + ruseHeadList:TableUpperProps[]; + ruseCaseMngList:ruseCaseMngExcelProps[]; }; -type ruseCaseActions = { }; + +export interface ruseCaseMngExcelProps { + ruseBizNm: string, + bizBgngDate: string, + bizEndDate: string, + splyFcltKnd: string, + splyFcltNm: string, + splyFcltCpct: number | 0 |'', + dmdFcltNm: string, + dmdFcltAddr: string, + ruseFcltCpct: number | 0 |'', + ruseFcltPplLen: string, + ruseFcltUsg: string, + ruseFcltSplyCpct: number | 0 |'', + ruseFcltPrcsMsrc: string, + grnteWqUsg: string, + grnteWqGrssClfg: number | 0 |'', + grnteWqCombResdch: number | 0 |'', + grnteWqTrbt: string, + grnteWqBod: string, + grnteWqSmll: string, + grnteWqCrmty: string, + grnteWqTn: string, + grnteWqTp: string, + grnteWqPh: string, + grnteWqClIonQnt: string, + grnteWqCndctStat: string, + grnteWqRmrk: string, + reqBizReuseFcltCst: number | 0 |'', + reqBizPplCst: number | 0 |'', + reqBizArchCst: number | 0 |'', + reqBizMchnCst: number | 0 |'', + reqBizMtrngCst: number | 0 |'', + reqBizGisCst: number | 0 |'', + reqBizCctvCst: number | 0 |'', + reqBizCmcnCst: number | 0 |'', + reqBizWstCst: number | 0 |'', + reqBizDesignCst: number | 0 |'', + reqBizSpvsCst: number | 0 |'', + reqBizSbsdCst: number | 0 |'', + reqBizEtc: string, + reqBizGrssCst: number | 0 |'', + prcmPlanNlep: number | 0 |'', + prcmPlanLcep: number | 0 |'', + prcmPlanBrdnCst: number | 0 |'', + prcmPlanEcqrRt: number | 0 |'', + prcmPlanGrssCst: number | 0 |'', + clclnNlep: number | 0 |'', + clclnLcep: number | 0 |'', + clclnBrdnCst: number | 0 |'', + clclnEcqrRt: number | 0 |'', + clclnGrssCst: number | 0 |'', +} + +type ruseCaseActions = { + selectRuseCaseMng: (sidoCd: string, sigunguCd: string, usageCd : string, name: string) => Promise<void>; + insertRuseCaseMng: (data: any) => Promise<void>; +}; + +export const fetchSearchRuseCaseMng = async (sidoCd: string, sigunguCd: string, usageCd : string, name: string) => { + + const tailUrl = ((sigunguCd !== '전체' && sigunguCd !=='') ? `/${sigunguCd}` : (sidoCd != '전체' && sidoCd != '') ? `/${sidoCd}` : `/0`) + (usageCd? `/${usageCd}` : '/0') + (name? `/${name}` : ''); + const resp = await getFetch(fetchType.WR,`/reuseFc/pubFcRuseCase${tailUrl}`); + return resp; +}; + +export const fetchInsertRuseCaseMng = async (data: any) => { + const response = await postFectch('/reuseFc/pubFcRuseCase', data, fetchType.WR); + return response.data; +}; + export type ruseCaseType = ruseCaseState & ruseCaseActions; export const ruseCaseStateExport: ruseCaseState = { - ruseCase: { - ruseHeadList:ruseCaseHeadListExport, + ruseCaseMng: { + ruseHeadList:ruseCaseHeadListExport, + ruseCaseMngList:[], }, }; -export const ruseCaseActionsExport: (set: any) => ruseCaseActions = ( + +export const ruseCaseActionsExport: (set: any) => ruseCaseActions = ( set: any ) => { return { - + selectRuseCaseMng: async (sidoCd: string, sigunguCd: string, usageCd : string, name: string) => { + const res = await fetchSearchRuseCaseMng(sidoCd, sigunguCd, usageCd, name); + console.log('res : ', res); + set((state: ruseCaseState) => ({ + ruseCaseMng: { + ...state.ruseCaseMng, + ruseCaseMngList: res, + }, + })); + }, + + insertRuseCaseMng: async (data: any) => { + const response = await fetchInsertRuseCaseMng(data); + if(response.status === 200){ + ToastFormUi({ + body: "잠재수요처 등록이 완료되었습니다. ", + title: "잠재수요처 등록", + }); + } + }, }; }; diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList.tsx index 3cc75d70c..d8057cad6 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList.tsx @@ -17,7 +17,7 @@ export const chkgdRegExcel = z.object({ }, { message: "현재 년도보다 높은 수를 입력할 수 없습니다." } ), - file: z.instanceof(File).optional(), + file: z.instanceof(File).nullish(), }); export const guideDetail = z.object({ diff --git a/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx b/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx index 87e827f60..82f2ed59c 100644 --- a/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx +++ b/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx @@ -77,7 +77,6 @@ export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = ( prfmncHasuInit: async (page: string, sggOrCity: string) => { try { const prfmncRes = await hasuListFetch(page, sggOrCity); - console.log("prfmncRes", prfmncRes); return set((state: pbnstPrfmncState) => ({ ...state, prfmnc: { diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/htdflReg/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/htdflReg/index.tsx index 0a51ebb5d..314d5762b 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/htdflReg/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/htdflReg/index.tsx @@ -1,10 +1,63 @@ import { getFetch, postFectch, putFectch } from "@/components/lib/api"; -import { upHeadList, type chgBuilding} from "../rainReg"; +import { upHeadList, type chgBuilding } from "../rainReg"; import { z } from "zod"; import { makeMdfr, makeRstr } from "../bdstReg"; +import { + baseReuseBuildingSchema, + chgBuildingSchema, + validationMessages, +} from "../rainReg/type"; // Import baseReuseBuildingSchema, chgBuildingSchema, validationMessages import type { TableUpperProps } from "@/app/store/common/type/header"; +const hdtflObjSchema = z.object({ + htdflFcltPk: z.number().nullish(), + bdstRuseFcltFk: z.number().nullish(), + etgtType: z + .string() + .min(1, { message: validationMessages.required("에너지원 종류") }), + fclCpct: z + .number() + .positive({ message: validationMessages.required("시설 용량") }), + plwtYn: z.enum(["Y", "N"]).default("Y"), + orgwtWtritSpt: z + .number() + .positive({ message: validationMessages.required("원수 취수량") }), + orgwtOcrnProcs: z + .string() + .min(1, { message: validationMessages.required("원수 발생 공정") }), + wtritEntrcTp: z + .number() + .max(100, { message: "온도는 100이상이 될 수 없습니다." }) + .default(0), // Changed max to 100 based on message + amtgnr: z.number().default(0), + prcsProcsYn: z.enum(["Y", "N"]).default("Y"), + prcsFcltCpct: z + .number() + .positive({ message: validationMessages.required("처리시설 용량") }), + fmYn: z.string().default(""), + utztnCpct: z + .number() + .positive({ message: validationMessages.required("이용 용량") }), + utztnUsg: z + .string() + .min(1, { message: validationMessages.required("이용 용도") }), + grssClfg: z.number().default(0), // Measurements, 0 is fine as default + combResdch: z.number().default(0), // Measurements, 0 is fine as default + trbt: z.number().default(0), // Measurements, 0 is fine as default + bod: z.number().default(0), // Measurements, 0 is fine as default + smll: z.number().default(0), // Measurements, 0 is fine as default + crmty: z.number().default(0), // Measurements, 0 is fine as default + tn: z.number().default(0), // Measurements, 0 is fine as default + tph: z.number().default(0), // Measurements, 0 is fine as default + ph: z.number().default(0), // Measurements, 0 is fine as default + clIonQnt: z.number().default(0), // Measurements, 0 is fine as default + cdtnv: z.number().default(0), // Measurements, 0 is fine as default + emsnExitTp: z.number().default(0), // Measurements, 0 is fine as default + rgtr: z.string().nullish().default(null), + mdfr: z.string().nullish().default(null), +}); + export const hdtflZod = z.object({ bdstInfoPk: z.number().nullish(), ctpvCd: z.number().default(0), @@ -14,85 +67,25 @@ export const hdtflZod = z.object({ daddr: z.string().default(" "), bdstNm: z.string().default(" "), frstArchPrmsnDt: z.date().default(() => new Date()), - archChgPrmsnDt: z.date().default(() => new Date()).nullish() , + archChgPrmsnDt: z + .date() + .default(() => new Date()) + .nullish(), archArea: z.number().default(0), archChgArea: z.number().nullish().default(0), - rgtr:z.string().nullish().default(null), - mdfr:z.string().nullish().default(null), + rgtr: z.string().nullish().default(null), + mdfr: z.string().nullish().default(null), reuseBuilding: z .array( - z.object({ - bdstRuseFcltPk: z.number().nullish(), - bdstInfoFk: z.number().nullish(), - instlCmptnDt: z.coerce.date().default(() => new Date()), - instlCst: z.number().nullish().default(0), - operMngCd: z.string().nullish().default(""), - anlOperCst: z.number().default(0), - instlCstSprtYn: z.enum(["Y", "N"]).nullish(), - instlCstSprtAmt: z.number().nullish().default(0), - grntchBdstCertNo: z.string().nullish().default(""), - grntchBdstCertGrd: z.string().nullish().default(""), - ruseFcltFlg: z.string().nullish().default(""), - bdstUsgCd: z.number().default(0), - lawTrgtYn: z.enum(["Y", "N"]).nullish(), - rgtr:z.string().nullish().default(null), - mdfr:z.string().nullish().default(null), - // htdflObj 매핑 - htdflObj: z - .object({ - htdflFcltPk: z.number().nullish(), - bdstRuseFcltFk: z.number().nullish(), - etgtType: z.string().default(""), - fclCpct: z.number().default(0), - plwtYn: z.enum(["Y", "N"]), - orgwtWtritSpt: z.number().default(0), - orgwtOcrnProcs: z.string().default(""), - wtritEntrcTp: z.number().max(4, "온도는 100이상이 될 수 없습니다.") - .default(0), - amtgnr: z.number().default(0), - prcsProcsYn: z.enum(["Y", "N"]), - prcsFcltCpct: z.number().default(0), - fmYn: z.string().default(""), - utztnCpct: z.number().default(0), - utztnUsg: z.string().default(""), - grssClfg: z.number().default(0), - combResdch: z.number().default(0), - trbt: z.number().default(0), - bod: z.number().default(0), - smll: z.number().default(0), - crmty: z.number().default(0), - tn: z.number().default(0), - tph: z.number().default(0), - ph: z.number().default(0), - clIonQnt: z.number().default(0), - cdtnv: z.number().default(0), - emsnExitTp: z.number().default(0), - rgtr:z.string().nullish().default(null), - mdfr:z.string().nullish().default(null), - }) - .default({ - plwtYn: "N", - prcsProcsYn: "N", - }), - }) - ) - .default([]), - chgBuilding: z - .array( - z.object({ - bdstInfoFk: z.number().nullish(), - archChgPrmsnDt: z.coerce.date() , - archChgArea: z.string().default("-").nullish(), - bdstChgNm: z.string().default(""), - rgtr:z.string().nullish().default(null), - mdfr:z.string().nullish().default(null), + baseReuseBuildingSchema.extend({ + htdflObj: hdtflObjSchema, }) ) .default([]), + chgBuilding: z.array(chgBuildingSchema).default([]), // Use imported chgBuildingSchema }); - export type customHtdflBdst = z.infer<typeof hdtflZod> - +export type customHtdflBdst = z.infer<typeof hdtflZod>; export type htdflFacState = { htdfl: { @@ -102,11 +95,10 @@ export type htdflFacState = { }; }; type htdflFacSActions = { - initHtdfl: (code: string ) => void; - htdflBdstUpdate:(data: customHtdflBdst, code:string|number) => void; - htdflBdstReg:(data: customHtdflBdst, code:string|number) => void; - getHtdflBdstData:(pageNation:number, code:string|number) =>void; - + initHtdfl: (code: string) => void; + htdflBdstUpdate: (data: customHtdflBdst, code: string | number) => void; + htdflBdstReg: (data: customHtdflBdst, code: string | number) => void; + getHtdflBdstData: (pageNation: number, code: string | number) => void; }; const htdflRes: customHtdflBdst[] = []; @@ -121,25 +113,22 @@ export const htdflFacStateExport: htdflFacState = { }, }; +const initFetch = async (code: string | number) => { + const rswtRes = await getFetch("wr", `/facilityList/1/20/${code}`); -const initFetch = async (code:string|number) => { - const rswtRes = await getFetch( - "wr", - `/facilityList/1/20/${code}`) - return rswtRes; }; export const htdflFacActionsExport: (set: any) => htdflFacSActions = ( set: any ) => { return { - getHtdflBdstData: async (pageNation: number, code: string | number) => { + getHtdflBdstData: async (pageNation: number, code: string | number) => { try { const rswtRes = await getFetch( "wr", `/drain/facilityList/${pageNation}/20/${code}` ); - console.log(pageNation) + console.log(pageNation); // 여기서 상태 업데이트 return set((state: htdflFacState) => ({ ...state, @@ -154,8 +143,7 @@ export const htdflFacActionsExport: (set: any) => htdflFacSActions = ( } }, - - initHtdfl: async (code: string ) => { + initHtdfl: async (code: string) => { try { const htdflRes = await initFetch(code); set((state: htdflFacState) => { @@ -172,9 +160,9 @@ export const htdflFacActionsExport: (set: any) => htdflFacSActions = ( console.log(error); } }, - htdflBdstUpdate: async (data: customHtdflBdst, code:string|number) => { + htdflBdstUpdate: async (data: customHtdflBdst, code: string | number) => { try { - const chData=await makeMdfr(data) + const chData = await makeMdfr(data); const res = await putFectch("/drain/facility", data); if (res.ok) { alert(" 수정이 완료되었습니다."); @@ -196,18 +184,17 @@ export const htdflFacActionsExport: (set: any) => htdflFacSActions = ( ); } }, - htdflBdstReg: async (data: customHtdflBdst , code:string|number) => { + htdflBdstReg: async (data: customHtdflBdst, code: string | number) => { try { - const chData=await makeRstr(data) + const chData = await makeRstr(data); const res = await postFectch("/drain/facility", data); if (res.ok) { - - if(data.chgBuilding.length > 0){ + if (data.chgBuilding.length > 0) { await res .json() .then((resData: any) => { const chPK = resData?.data?.bdstInfoPk; - + const makeCh = resData.data.chgBuilding.map( (item: chgBuilding) => ({ ...item, @@ -222,9 +209,8 @@ export const htdflFacActionsExport: (set: any) => htdflFacSActions = ( alert("등록이 완료되었습니다."); } }); - }else{ + } else { alert("등록이 완료되었습니다."); - } } const htdflRes = await initFetch(code); @@ -238,7 +224,7 @@ export const htdflFacActionsExport: (set: any) => htdflFacSActions = ( })); } catch (errorReg) { alert("등록 오류입니다."); - } + } }, }; }; diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/headList.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/headList.tsx index 536114f3f..d77f4d7f0 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/headList.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/headList.tsx @@ -31,8 +31,8 @@ export const pbnstMngUpHeadListExport: TableUpperProps[] = [ export const pplInstlsHeadListExport: TableUpperProps[] = [ { id: "choose", title: "선택", type: "checkbox" }, { id: "ppMqlt", title: "관재질", type: "select", selectData: "pplQulity" }, - { id: "pdmt", title: "관경", type: "select", selectData: "pplLenght" }, - { id: "cpct", title: "연장", edit: true, type: "number" }, + { id: "pdmt", title: "관경(mm)", type: "select", selectData: "pplLenght" }, + { id: "cpct", title: "연장(m)", edit: true, type: "number" }, ]; export const pbnstDemandHeadListExport: TableUpperProps[] = [ @@ -69,20 +69,21 @@ export type formDetailType = { id: string; lable: string; type?: string; + selectData?: string; // select box일 경우 option?:string }; export const wasteWaterFcltINfo: formDetailType[] = [ - { id: "wwtEmsnFcltPrmsnYn", lable: "폐수배출물신고 허가여부", type: "selectYn" }, - { id: "tmsInstlYn", lable: "Tms설치여부", type: "selectYn" }, - { id: "pbnstDrngInflwExclYn", lable: "공공하수도유입제외 허가여부", type: "selectYn" }, + { id: "wwtEmsnFcltPrmsnYn", lable: "폐수배출물신고 허가여부", type: "select" , selectData: "yn"}, + { id: "tmsInstlYn", lable: "Tms설치여부", type: "select" , selectData: "yn" }, + { id: "pbnstDrngInflwExclYn", lable: "공공하수도유입제외 허가여부", type: "select" , selectData: "yn" }, ]; export const concentratedWaterInfo: formDetailType[] = [ { id: "erctWtrOcrnCpct", lable: "눙축수 발생량(m³/일)", type: "number" }, { id: "erctUswtrLinkSpt", lable: "눙축수연계지점" }, { id: "erctUswtrFcltCpct", lable: "시설용량(m³/일)", type: "number" }, - { id: "erctUswtrPrcsMeth", lable: "눙축수처리방식" }, + { id: "erctUswtrPrcsMeth", lable: "눙축수처리방식", type : "select", selectData: "erctUswtrPrcsMeth" }, { id: "erctUswtrPrcsMsrcCd", lable: "눙축수처리공법" }, ]; @@ -90,8 +91,8 @@ export const ruseFcltinfo: formDetailType[] = [ { id: "rprcsFcltCpct", lable: "시설용량(m³/일)", type: "number" }, { id: "majorPrcsMsrcCd", lable: "주처리공법" }, { id: "addr", lable: "위치(주소)" }, - { id: "rprcsMthdCd", lable: "재처리방식" }, - { id: "inoutYn", lable: "위치(처리장 내외)" ,type: "selectYn" }, // 머가 내? 외?, + { id: "rprcsMthdCd", lable: "재처리방식", type: "select", selectData: "rusePrcsMthdCd" }, + { id: "inoutYn", lable: "위치(처리장 내외)" ,type: "select" , selectData: "yn"}, // 머가 내? 외?, { id: "mainSplyPumpCpct", lable: "메인공급펌프 용량", type: "number" }, ]; @@ -100,12 +101,12 @@ export const businessMoneyInfo: formDetailType[] = [ { id: "lcep", lable: "지방비", type: "number" }, { id: "cusrAlotm", lable: "민간투자비", type: "number" }, { id: "etc", lable: "기타" }, - { id: "totalMoney", lable: "합계", type: "number" ,option:"readOnly" }, + { id: "totalMoney", lable: "합계", type: "number" ,option:"disabled" }, ]; export const businessSttnInfo: formDetailType[] = [ - { id: "seCd", lable: "구분" }, - { id: "bizMthdCd", lable: "사업방식" }, + { id: "seCd", lable: "구분", type: "select", selectData: "ruseSeCd" }, + { id: "bizMthdCd", lable: "사업방식", type: "select", selectData: "ruseBizMthdCd" }, { id: "bizCmcnDay", lable: "사업준공일", type: "date" }, { id: "instlAprvSncnDt", lable: "설치승인인가일", type: "date" }, ]; @@ -118,15 +119,15 @@ export const ruseFcltIntakeList: formDetailType[] = [ ]; export const ruseWaterQulityList: formDetailType[] = [ - { id: "bod", lable: "BOD(mg/L)", type: "number" }, - { id: "grssClfg", lable: "총대장균군수(개/mg)", type: "number" }, - { id: "combResdch", lable: "결합잔류염소(mh/L)", type: "number" }, - { id: "trbt", lable: "탁도(NTU)", type: "number" }, - { id: "smll", lable: "냄새", type: "number" }, - { id: "crmty", lable: "색도", type: "number" }, - { id: "tn", lable: "총질소(mg/L)", type: "number" }, - { id: "tph", lable: "총인(mg/L)", type: "number" }, - { id: "ph", lable: "수소이온농도", type: "number" }, - { id: "clIonQnt", lable: "염화물(mg/L)", type: "number" }, - { id: "cdtnv", lable: "전기전도도(μs/cm)​", type: "number" }, + { id: "bod", lable: "BOD(mg/L)", type: "text" }, + { id: "grssClfg", lable: "총대장균군수(개/mg)", type: "text" }, + { id: "combResdch", lable: "결합잔류염소(mh/L)", type: "text" }, + { id: "trbt", lable: "탁도(NTU)", type: "text" }, + { id: "smll", lable: "냄새", type: "text" }, + { id: "crmty", lable: "색도", type: "text" }, + { id: "tn", lable: "총질소(mg/L)", type: "text" }, + { id: "tph", lable: "총인(mg/L)", type: "text" }, + { id: "ph", lable: "수소이온농도", type: "text" }, + { id: "clIonQnt", lable: "염화물(mg/L)", type: "text" }, + { id: "cdtnv", lable: "전기전도도(μs/cm)​", type: "text" }, ]; diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx index fb799c26c..ea7eee5c8 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx @@ -52,6 +52,7 @@ export type pubFacState = { fcOrgwtsHeadList: TableUpperProps[]; pbnstMngRegData: customPbnst[]; pbnsUpdateIndex: pbnstInsertIndex; + fcOrgwtsNmList:pubHasuType[]; dmdComList: any[]; }; }; @@ -70,6 +71,7 @@ type pubFacActions = { pbnstRuseFcltUpdate: (ruseRes: customPbnst[]) => void; pbnstRuseBizUpdate: (ruseRes: fcBizType[]) => void; pubHasuSearch :(page: string, sggOrCity: string , searchTxt:string) => void + getFcOrgwts: (fcltPkList: string[], size:number) => void; }; export type pubFacType = pubFacState & pubFacActions; @@ -90,6 +92,7 @@ export const pubFacStateExport: pubFacState = { pbnstBizCycle: [{ text: "없음", val: "1" }], pbnstMngRegData: [], pbnsUpdateIndex: {} as pbnstInsertIndex, + fcOrgwtsNmList:[], dmdComList: [], }, }; @@ -102,12 +105,10 @@ const checkWrRegList = async () => { const hasuListFetch = async (page: string, sggOrCity: string, searchTxt?:string) => { const checkWrRegListRes = await checkWrRegList(); - console.log(' checkWrRegListRes ', checkWrRegListRes) let url = `/prcsFclt/ruseInfo/${page ? page : 1}/10/A/${sggOrCity? sggOrCity : "11"}/0/1/SW`; const data = checkWrRegListRes.filter((item:string) => item !== "") - console.log(' checkWrRegListRes ' ) if(searchTxt){ url= url +`/${searchTxt}` @@ -177,6 +178,7 @@ export const pubFacActionsExport: (set: any) => pubFacActions = (set: any) => { try { const pbnstHasuRes = await hasuListFetch(page, sggOrCity); + return set((state: pubFacState) => ({ ...state, pbnst: { @@ -399,6 +401,25 @@ export const pubFacActionsExport: (set: any) => pubFacActions = (set: any) => { } catch (e) { console.log(e); } + },getFcOrgwts : async (fcltPkList: string[] ,size:number) => { + let url = `/prcsFclt/ruseInfo/1/${size}/A/0/0/1/SW`; + + const fcOrgwtsNmList = await postFectch( + url, + fcltPkList, + fetchType.COMMON + ); + + return set((state: pubFacState) => ({ + ...state, + pbnst: { + ...state.pbnst, + fcOrgwtsNmList:fcOrgwtsNmList.data??[] + }, + })); + + + } }; }; diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx index a34985810..b908750fd 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx @@ -14,10 +14,10 @@ export type selectLabel = { val: string; }; -export type pbnstInsertIndex ={ - mng:number, - biz:number -} +export type pbnstInsertIndex = { + mng: number; + biz: number; +}; export type publicReuseFacilityType = { sido: string; @@ -45,20 +45,20 @@ export type ruseFcltType = { }; export type PbnstDemandComType = { - dmdInfoPk : string | ''; - bzmnNo : string | ''; - dmdCoNm : string | ''; - daddr : string | ''; - telno : string | ''; - rgtr : string | ''; - useUsgCd : string | ''; - ctpvCd : string | ''; - sggCd : string | ''; - dmdAplyPswd: string | ''; + dmdInfoPk: string | ""; + bzmnNo: string | ""; + dmdCoNm: string | ""; + daddr: string | ""; + telno: string | ""; + rgtr: string | ""; + useUsgCd: string | ""; + ctpvCd: string | ""; + sggCd: string | ""; + dmdAplyPswd: string | ""; splyCpct: number | 0; - splyFmYn:string|'Y' -// dmdInfoFk:string|'' -} + splyFmYn: string | "Y"; + // dmdInfoFk:string|'' +}; const pubHasuZod = z.object({ choose: z.boolean().default(false), @@ -68,23 +68,27 @@ const pubHasuZod = z.object({ bscFctNm: z.string().default(""), efflWqltStndrdNm: z.string().default(""), fctCpc: z.number().default(0), - addr:z.string().nullish() + addr: z.string().nullish(), }); - export type pubHasuType = z.infer<typeof pubHasuZod>; -const zDate = z.preprocess((v) => (v ? new Date(v as string) : undefined), z.date()).nullish() ; +const zDate = z + .preprocess((v) => (v ? new Date(v as string) : undefined), z.date()) + .nullish(); const zDateNullish = zDate.nullish(); const zDateDefaultNow = zDate.default(new Date()); -const zNum = z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number()); +const zNum = z.preprocess( + (v) => (v !== null && v !== undefined ? Number(v) : v), + z.number() +); const zNumNullish = zNum.nullish(); const zNumDefaultZero = zNum.default(0).nullish(); const pplInstlSchema = z.object({ ruseFcltBizFk: zNumNullish, choose: z.boolean().default(false), - pdmt: z.string().default(''), + pdmt: z.string().default(""), ppMqlt: z.string().default(""), cpct: zNumDefaultZero, }); @@ -115,22 +119,23 @@ export const fcOperSchema = z.object({ }); const fcOrgwtSchema = z.object({ - pbnstRuseFcltBizFk:zNumNullish, + pbnstRuseFcltBizFk: zNumNullish, swtrmtFcltFk: z.string().nullish(), orgwtWtritQnt: zNum, tmsYn: z.enum(["Y", "N"]).default("N"), dschgFmYn: z.enum(["Y", "N"]).default("N"), - dtlPstnExpln: z.string().default(''), + dtlPstnExpln: z.string().default(""), regDt: zDateNullish, - rgtr: z.string().default(''), + rgtr: z.string().default(""), mdfcnDt: zDateNullish, - mdfr: z.string().default(''), + mdfr: z.string().default(""), choose: z.boolean().default(false), + ruseUtztnFcltNm:z.string().nullish() }); const dmdColist = z.object({ - dmdInfoPk: z.string().default(''), - dmdCoNm: z.string().default(''), + dmdInfoPk: z.string().default(""), + dmdCoNm: z.string().default(""), daddr: z.string(), useUsgCd: z.string().nullish(), splyBgngDay: zDateDefaultNow, @@ -148,18 +153,18 @@ export const fcBizSchema = z.object({ pbnstRuseFcltFk: z.string().nullish(), superkey: z.string().default("").nullish(), cycl: zNum.default(1), - seCd: z.string().default(""), + seCd: z.string().max(10, "구분은 10자를 초과할 수 없습니다.").default(""), bizMthdCd: z.string().default(""), instlAprvSncnDt: zDateDefaultNow, bizCmcnDay: zDateDefaultNow, gvsbs: zNumDefaultZero, lcep: zNumDefaultZero, cusrAlotm: zNumDefaultZero, - etc: z.string().default(''), + etc: z.string().default(""), totalMoney: zNumNullish.default(0), rprcsFcltCpct: zNumDefaultZero, majorPrcsMsrcCd: z.string().default(""), - addr: z.string().default(''), + addr: z.string().default(""), rprcsMthdCd: z.string().default(""), inoutYn: z.enum(["Y", "N"]).default("Y"), mainSplyPumpCpct: zNumNullish, @@ -171,17 +176,29 @@ export const fcBizSchema = z.object({ wwtEmsnFcltPrmsnYn: z.string().default("Y"), tmsInstlYn: z.string().default("Y"), pbnstDrngInflwExclYn: z.string().default("Y"), - bod: zNumDefaultZero, - grssClfg: zNumDefaultZero, - combResdch: zNumDefaultZero, - trbt: zNumDefaultZero, - clIonQnt: zNumDefaultZero, - smll: zNumDefaultZero, - crmty: zNumDefaultZero, - tph: zNumDefaultZero, - tn: zNumDefaultZero, - ph: zNumDefaultZero, - cdtnv: zNumDefaultZero, + // bod: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "BOD 값은 5자리 미만이어야 합니다.")).nullish(), + // grssClfg: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "총대장균군 값은 5자리 미만이어야 합니다.")).nullish(), + // combResdch: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "결합잔류염소 값은 5자리 미만이어야 합니다.")).nullish(), + // trbt: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "탁도 값은 5자리 미만이어야 합니다.")).nullish(), + // clIonQnt: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "염소이온량 값은 5자리 미만이어야 합니다.")).nullish(), + // smll: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "냄새 값은 5자리 미만이어야 합니다.")).nullish(), + // crmty: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "색도 값은 5자리 미만이어야 합니다.")).nullish(), + // tph: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "총인 값은 5자리 미만이어야 합니다.")).nullish(), + // tn: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "총질소 값은 5자리 미만이어야 합니다.")).nullish(), + // ph: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "수소이온농도 값은 5자리 미만이어야 합니다.")).nullish(), + // cdtnv: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "전기전도도 값은 5자리 미만이어야 합니다.")).nullish(), + bod: z.string().nullish(), + grssClfg: z.string().nullish(), + combResdch: z.string().nullish(), + trbt: z.string().nullish(), + clIonQnt: z.string().nullish(), + smll: z.string().nullish(), + crmty: z.string().nullish(), + tph: z.string().nullish(), + tn: z.string().nullish(), + ph: z.string().nullish(), + cdtnv: z.string().nullish(), + designCst: zNumNullish, cstrnSpvsCst: zNumNullish, fcltSbsdCst: zNumNullish, @@ -217,7 +234,7 @@ export const fcBizSchema = z.object({ }); export const pbnstRegZod = z.object({ - pbnstRuseFcltPk: z.string().default(''), + pbnstRuseFcltPk: z.string().default(""), swtrmtFcltFk: z.string().default(""), ruseUtztnFcltNm: z.string().default(""), fcltCpct: zNumDefaultZero, @@ -232,13 +249,12 @@ export const pbnstRegZod = z.object({ fcOpers: z.array(fcOperSchema).default([]).nullish(), }); -export const pbnstRegArrayZod = z.array(pbnstRegZod) +export const pbnstRegArrayZod = z.array(pbnstRegZod); -export type PbnstRegArrrayType = z.infer<typeof pbnstRegArrayZod> +export type PbnstRegArrrayType = z.infer<typeof pbnstRegArrayZod>; export type fcBizType = z.infer<typeof fcBizSchema>; export type fcOrgwtType = z.infer<typeof fcOrgwtSchema>; export type ppInstlType = z.infer<typeof pplInstlSchema>; export type operType = z.infer<typeof fcOperSchema>; export type fcFileType = z.infer<typeof fcFileSchema>; - diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/index.tsx index 5b3ad6813..372a4bd9a 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/index.tsx @@ -2,76 +2,29 @@ import type { TableUpperProps } from "@/app/store/common/type/header"; import { getFetch, postFectch, putFectch } from "@/components/lib/api"; import { z } from "zod"; import { makeMdfr, makeRstr } from "../bdstReg"; - -const validationMessages = { - required: (field: string) => `${field}을(를) 입력해주세요.`, - requiredSelect: (field: string) => `${field}을(를) 선택해주세요.`, - yearFormat: '4자리 연도만 입력하세요', -}; +import toast from "react-hot-toast"; +import { baseReuseBuildingSchema, chgBuildingSchema, validationMessages } from "./type"; export const TEST_UUID= '2d4b151d-1828-41e0-91d9-4cd28ea1cc7e' const rainyObjSchema = z.object({ bdstReuseFcltFk: z.number().nullish(), - pntfColKnd: z.string().default(""), - pntfColArea: z.number().default(0), + pntfColKnd: z.string().min(1, { message: validationMessages.requiredSelect("종류") }), + pntfColArea: z.number().positive({ message: validationMessages.required("면적(m³)") }), dtfcltCpct: z.number().default(0), - filtrTrtmfctYn: z.string().default("Y").nullish(), + filtrTrtmfctYn: z.string().default("Y"), filtrTrtmfctCpct: z.number().nullish().default(0), filtrTrtmfctPrs: z.string().default(""), annRnwtQty: z.number().default(0), oilqMeasMthd: z.string().default("").nullish(), rgtr:z.string().nullish(), mdfr:z.string().nullish(), -}).refine((data) => data.pntfColKnd.length > 0, { - message: validationMessages.requiredSelect("종류"), - path: ["pntfColKnd"], -}).refine((data) => data.pntfColArea > 0, { - message: validationMessages.required("면적(m³)"), - path: ["pntfColArea"], }); -const reuseBuildingSchema = z.object({ - bdstRuseFcltPk: z.number().nullish(), - bdstInfoFk:z.number().nullish(), - instlCmptnDt: z.coerce.date().default(() => new Date()), - instlCst: z.number().nullish(), - operMngCd: z.string().optional().default(""), - anlOperCst: z.number().default(0), - instlCstSprtYn: z.string().default("Y"), - instlCstSprtAmt: z.number().nullish(), - grntchBdstCertYn: z.string().default("Y"), - grntchBdstCertYr: z.string().default("") - .refine((year)=> year.length===0 || /^\d{4}$/.test(year),{ - message: validationMessages.yearFormat - }), - grntchBdstCertNo: z.string().default(""), - grntchBdstCertGrd: z.string().default(""), - crgRdctYn: z.string().default("N").nullish(), /// 안쓰는 값 - crgRdctBss: z.string().default("").nullish(), /// 안쓰는 값 - crgRdctAmt: z.number().default(0), // 안쓰는 값 - ruseFcltFlg: z.string().default(""), - bdstUsgCd: z.number().default(0), - lawTrgtYn: z.string().default("Y"), - rgtr:z.string().nullish(), - mdfr:z.string().nullish(), - rainyObj: rainyObjSchema.default({}) -}).refine((data) => data.bdstUsgCd > 0, { - message: validationMessages.requiredSelect("건축물용도"), - path: ["bdstUsgCd"], -}); -const chgBuildingSchema = z.object({ - bdstInfoFk: z.number().nullish(), - archChgPrmsnDt: z.coerce.date({required_error: validationMessages.required("허가일")}), - archChgArea: z.string().default("-"), - bdstChgNm: z.string().default(""), - rgtr:z.string().nullish(), - mdfr:z.string().nullish(), -}).refine(data => data.bdstChgNm.length > 0, { - message: validationMessages.requiredSelect("구분"), - path: ["bdstChgNm"] -}); + const rainBuildingSchema = baseReuseBuildingSchema.extend({ + rainyObj: rainyObjSchema, +}) export const rainZod = z.object({ bdstInfoPk: z.number().optional(), @@ -85,7 +38,7 @@ export const rainZod = z.object({ archChgArea: z.number().default(0), rgtr:z.string().nullish(), mdfr:z.string().nullish(), - reuseBuilding: z.array(reuseBuildingSchema).default([]), + reuseBuilding: z.array(rainBuildingSchema).default([]), chgBuilding: z.array(chgBuildingSchema).default([]) }) @@ -219,15 +172,18 @@ export const rainFacActionsExport: (set: any) => rainFacSActions = ( bdstInfoFk: chPK, }) ); - const chch= postFectch("/reuseFc/chgBuilding", makeCh); + const chch= postFectch("/reuseFc/chgBuilding", makeCh); return chch; }).then((chch: any) => { - if(chch.ok) { - alert("등록이 완료되었습니다."); + + console.log('chch ', chch) + if(chch.status ===200) { + toast.success("등록이 완료되었습니다."); + } }) }else{ - alert("등록이 완료되었습니다."); + toast.success("등록이 완료되었습니다."); } } const rainRes = await initFetch(code); diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/type.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/type.tsx new file mode 100644 index 000000000..6e28c3e76 --- /dev/null +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/type.tsx @@ -0,0 +1,45 @@ +import { z } from "zod"; + + +export const validationMessages = { + required: (field: string) => `${field}을(를) 입력해주세요.`, + requiredSelect: (field: string) => `${field}을(를) 선택해주세요.`, + yearFormat: '4자리 연도만 입력하세요', + }; + +export const baseReuseBuildingSchema = z.object({ + bdstRuseFcltPk: z.number().nullish(), + bdstInfoFk: z.number().nullish(), + instlCmptnDt: z.coerce.date().default(() => new Date()), + instlCst: z.number().nullish(), + operMngCd: z.string().optional().default(""), + anlOperCst: z.number().default(0), + instlCstSprtYn: z.string().default("Y"), + instlCstSprtAmt: z.number().nullish(), + grntchBdstCertYn: z.string().default("Y"), + grntchBdstCertYr: z.string().default("").refine( + (year) => year.length === 0 || /^\d{4}$/.test(year), + { + message: validationMessages.yearFormat, + } + ), + grntchBdstCertNo: z.string().default(""), + grntchBdstCertGrd: z.string().default(""), + crgRdctYn: z.string().default("N").nullish(), /// 안쓰는 값 + crgRdctBss: z.string().default("").nullish(), /// 안쓰는 값 + crgRdctAmt: z.number().default(0), // 안쓰는 값 + ruseFcltFlg: z.string().default(""), + bdstUsgCd: z.number().min(1, { message: validationMessages.requiredSelect("건축물용도") }).default(0), + lawTrgtYn: z.string().default("Y"), + rgtr: z.string().nullish(), + mdfr: z.string().nullish(), +}); + +export const chgBuildingSchema = z.object({ + bdstInfoFk: z.number().nullish(), + archChgPrmsnDt: z.coerce.date({required_error: validationMessages.required("허가일")}), + archChgArea: z.string().default("-"), + bdstChgNm: z.string().min(1, { message: validationMessages.requiredSelect("구분") }), + rgtr:z.string().nullish(), + mdfr:z.string().nullish(), +}); diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/rswtReg/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/rswtReg/index.tsx index 3c2504b9a..178badd3b 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/rswtReg/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/rswtReg/index.tsx @@ -1,10 +1,32 @@ import { getFetch, postFectch, putFectch } from "@/components/lib/api"; -import { upHeadList, type chgBuilding } from "../rainReg"; +import { upHeadList, type chgBuilding } from "../rainReg"; import type { TableUpperProps } from "@/app/store/common/type/header"; import { z } from "zod"; +import { baseReuseBuildingSchema, chgBuildingSchema, validationMessages } from "../rainReg/type"; import { makeMdfr, makeRstr } from "../bdstReg"; + const rswtrObjSchema = z.object({ + bdstRuseFcltFk: z.number().nullish(), + wtrUsqty: z.number({ required_error: validationMessages.required("물사용량(m³)") }).positive({ message: validationMessages.required("물사용량(m³)") }), + oblgTrgtWtrUsqty: z.number().nullish().default(0), + orgwtKndCd: z.string({ required_error: validationMessages.requiredSelect("원수 종류") }).min(1, { message: validationMessages.requiredSelect("원수 종류") }), + filtrTrtmfctCpct: z.number({ required_error: validationMessages.required("처리시설 용량") }).positive({ message: validationMessages.required("처리시설 용량") }), + filtrTrtmfctProcs: z.string({ required_error: validationMessages.required("주요처리공정") }).min(1, { message: validationMessages.required("주요처리공정") }), + filtrTrtmfctUsgCd: z.string().default("").nullish(), + rswtrUsqty: z.number().default(0), + altUswtrUtztnYn: z.enum(["Y", "N"]).nullish(), //안쓰는값 + altUswtrKndCd: z.string().default("").nullish(), //안쓰는값 + altUswtrUtztnQnt: z.number().default(0), //안쓰는값 + altUswtrIdntyMthdCd: z.string().default("").nullish(), //안쓰는값 + rgtr: z.string().nullish().default(null), + mdfr: z.string().nullish().default(null), +}); + + const rswtBuildingSchema = baseReuseBuildingSchema.extend({ + rswtrObj: rswtrObjSchema, +}) + export const rswtZod = z.object({ bdstInfoPk: z.number().nullish(), ctpvCd: z.number().default(0), @@ -21,73 +43,8 @@ export const rswtZod = z.object({ bdstUsgText: z.string().default(""), cityText: z.string().default(""), sggText: z.string().default(""), - - reuseBuilding: z - .array( - z.object({ - bdstRuseFcltPk: z.number().nullish(), - bdstInfoFk: z.number().nullish(), - instlCmptnDt: z.coerce.date().default(() => new Date()), - instlCst: z.number().default(0), - operMngCd: z.string().nullish().default(""), - - anlOperCst: z.number().default(0), - instlCstSprtYn: z.enum(["Y", "N"]), - instlCstSprtAmt: z.number().default(0), - - grntchBdstCertYn: z.enum(["Y", "N"]), - grntchBdstCertYr: z - .string() - .default("") - .refine((year) => year.length === 0 || /^\d{4}$/.test(year), { - message: "4자리 연도만 입력하세요", - }), - grntchBdstCertNo: z.string().default(""), - grntchBdstCertGrd: z.string().default(""), - - crgRdctYn: z.string().default("N").nullish(), // 안쓰는 값 - crgRdctBss: z.string().default("").nullish(), /// 안쓰는 값 - crgRdctAmt: z.number().default(0), // 안쓰는 값 - ruseFcltFlg: z.string().default(""), - bdstUsgCd: z.number().default(0), - lawTrgtYn: z.string().default(""), - rgtr: z.string().nullish().default(null), - mdfr: z.string().nullish().default(null), - - rswtrObj: z - .object({ - bdstRuseFcltFk: z.number().nullish(), - wtrUsqty: z.number().default(0), - oblgTrgtWtrUsqty: z.number().nullish().default(0), - orgwtKndCd: z.string().default(""), - filtrTrtmfctCpct: z.number().default(0), - filtrTrtmfctProcs: z.string().default(""), - filtrTrtmfctUsgCd: z.string().default("").nullish(), - rswtrUsqty: z.number().default(0), - altUswtrUtztnYn: z.enum(["Y", "N"]).nullish(), //안쓰는값 - altUswtrKndCd: z.string().default("").nullish(), //안쓰는값 - altUswtrUtztnQnt: z.number().default(0), //안쓰는값 - altUswtrIdntyMthdCd: z.string().default("").nullish(), //안쓰는값 - rgtr: z.string().nullish().default(null), - mdfr: z.string().nullish().default(null), - }) - .default({}), - }) - ) - .default([]), - - chgBuilding: z - .array( - z.object({ - bdstInfoFk: z.number().nullish(), - archChgPrmsnDt: z.coerce.date(), - archChgArea: z.string().default("-"), - bdstChgNm: z.string().default(""), - rgtr: z.string().nullish().default(null), - mdfr: z.string().nullish().default(null), - }) - ) - .default([]), + reuseBuilding: z.array(rswtBuildingSchema).default([]), + chgBuilding: z.array(chgBuildingSchema).default([]), }); export type customRswtBdst = z.infer<typeof rswtZod>; diff --git a/apps/wr/app/store/store.tsx b/apps/wr/app/store/store.tsx index 4557662f8..7520f7998 100644 --- a/apps/wr/app/store/store.tsx +++ b/apps/wr/app/store/store.tsx @@ -86,7 +86,7 @@ import { import { rswtChkStateExport, rswtChkSttnActionsExport, type rswtChkSttnState, type rswtChkSttnType } from "./prfmncSttn/chkgdnSttn/rswtChkgdnSttn"; import { pbnstSttnActionsExport, pbnstSttnStateExport, type pbnstSttnState, type pbnstSttnType } from "./ruseFcltMng/fcltSttn/pbnstSttn"; import { coSttnActionsExport, coSttnStateExport, type coSttnState, type coSttnType } from "./matDmd/coSttn"; // 옮겨야함 -import { searchActions, type comFcltActions, type comFcltState } from "./common/searchCode"; +import { comFcltStateExport, comFcltSearchAction, type comFcltState, type comFcltType } from "./common/searchCode"; import { pbnstPrfmncActionsExport, pbnstPrfmncStateExport, type pbnstPrfmncState, type pbnstPrfmncType } from "./prfmncReg/pbnstPrfmncReg"; import { coRvwActionsExport, coRvwStateExport, type coRvwState, type coRvwType } from "./matDmd/coMatRvw"; import { ruseHstryActionsExport, ruseHstryStateExport, type ruseHstryState, type ruseHstryType } from "./matDmd/ruseRvwHstry"; @@ -113,7 +113,6 @@ export type WrStore = comCodeType & rainChkSttnType& rswtChkSttnType& pbnstSttnType& - comFcltActions&comFcltState& coSttnType& // 옮겨야함 ldrmntType& // 옮겨야함 pbnstPrfmncType& @@ -124,7 +123,8 @@ export type WrStore = comCodeType & ruseCaseType& rainStatType& rswtStatType& - matStatType + matStatType& + comFcltType ; export const StoreZus = ( @@ -141,6 +141,7 @@ export const StoreZus = ( rswtSttnState & htdflSttnState & rainChkSttnState& + comFcltState& rswtChkSttnState & pbnstSttnState& coSttnState& // 옮겨야함 @@ -180,7 +181,8 @@ export const StoreZus = ( ...ruseCaseStateExport, ...rainStatExport, ...rswtStatExport, - ...matStatExport + ...matStatExport, + ...comFcltStateExport } ) => { return createStore<WrStore>()((set) => { @@ -192,7 +194,7 @@ export const StoreZus = ( getEnvPartCd(set); return { ...initState, - ...searchActions(set), + ...comFcltSearchAction(set), ...pubFacActionsExport(set), ...rainFacActionsExport(set), ...rswtFacActionsExport(set), diff --git a/apps/wr/components/ui/form/fcltReg/bdstReg/bdstReg.tsx b/apps/wr/components/ui/form/fcltReg/bdstReg/bdstReg.tsx index 5ab5edf98..0d00454e2 100644 --- a/apps/wr/components/ui/form/fcltReg/bdstReg/bdstReg.tsx +++ b/apps/wr/components/ui/form/fcltReg/bdstReg/bdstReg.tsx @@ -171,7 +171,7 @@ export default function BdstReg({ form }: { form: any }) { <FormUi label="위치"> <Input className={selectClass} - placeholder="1,000,000" + placeholder="" type="search" {...field} /> @@ -193,7 +193,7 @@ export default function BdstReg({ form }: { form: any }) { onSelectValue={(val) => { field.onChange(Number(val)); }} - value={field.value} + value={field.value??code.bdstUsgCd[0]?.val} /> </FormUi> )} @@ -208,7 +208,7 @@ export default function BdstReg({ form }: { form: any }) { <DatePicker mode={"single"} field={{ - value: field.value || new Date(), + value: field.value?? new Date(), onChange: (date: Date | undefined) => field.onChange(date), }} selectClass={'w-full'} @@ -226,7 +226,7 @@ export default function BdstReg({ form }: { form: any }) { <Input className={selectClass} placeholder="1,000,000" - type="number" + type="text" onChange={(e) => { handleCommaNumberChange(e, field.onChange); }} @@ -250,7 +250,7 @@ export default function BdstReg({ form }: { form: any }) { onSelectValue={(val) => { field.onChange(val); }} - value={field.value} + value={field.value??code.yn[0]?.val } /> </FormUi> )} diff --git a/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx b/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx index df63b3520..152441c70 100644 --- a/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx @@ -55,18 +55,15 @@ export function PbnstBuseForm({ code, pbnst, demandCompList, - getpbnstInsertIndex, - appendRuseFclt, getDemandCompList, + getFcOrgwts, regDemandCompany, } = useWrStore((state) => state); - const { pbnstBizCycle, pbnstRes } = pbnst; + const { fcOrgwtsNmList } = pbnst; const { fields: fcBizs, append: appendBiz } = useFieldArray({ control: form.control, name: `items.${indexMng}.fcBizs`, }); - - const [bizCycle, setBizCycle] = useState("1"); const [ppiSelectList, setPpiSelectList] = useState<[{ name: string; val: SelectDataType[] }]>(); const [ppiYn, setPpiYn] = useState("Y"); @@ -79,25 +76,72 @@ export function PbnstBuseForm({ >([{ text: "없음", val: 0 }]); const [isFcOrgwts, setIsFcOrgwts] = useState<boolean>(false); const [sumCpct, setSumCpct] = useState<number>(0); - const [getDmdList, setGetFmdList] = useState<number>(0); const [isDmdModal, setIsDmdModal] = useState<{ isMoal: boolean; data: DemandCompType; }>({ isMoal: false, data: {} as DemandCompType }); useEffect(() => { - const total = + const etc= isNaN(Number(form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.etc`))) + let total = (form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.gvsbs`) ?? 0) + (form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.lcep`) ?? 0) + - (form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.cusrAlotm`) ?? 0); + (form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.cusrAlotm`) ?? 0) + + if( !etc ) { + total+=Number(form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.etc`)) + } form.setValue(`items.${indexMng}.fcBizs.${indexBiz}.totalMoney`, total); }, [ form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.gvsbs`), form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.lcep`), form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.cusrAlotm`), + form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.etc`) ]); + + const fcOrgwts = form.watch( `items.${indexMng}.fcBizs.${indexBiz}.fcOrgwts`) + + useEffect(() =>{ + + console.log('fcOrgwts ', fcOrgwts) + if(fcOrgwts&&fcOrgwts?.length>0){ + const swtrmtFcltFkList = fcOrgwts.map((item) => item.swtrmtFcltFk); + + console.log('swtrmtFcltFkList ', swtrmtFcltFkList) + + if(swtrmtFcltFkList.length > 0){ + getFcOrgwts(swtrmtFcltFkList.filter((item): item is string => item != null), fcOrgwts?.length) + } + } + },[fcOrgwts]) + + useEffect(() =>{ + + if (fcOrgwtsNmList && fcOrgwtsNmList.length > 0) { + + console.log('fcOrgwtsNmList ', fcOrgwtsNmList) + console.log('fcOrgwts ', fcOrgwts) + + fcOrgwts?.forEach((item, index) => { + const orgwtNm = fcOrgwtsNmList.find( + (orgwt) => orgwt.bscFctCd === item.swtrmtFcltFk + )?.bscFctNm; + + console.log('orgwtNm ', orgwtNm) + if (orgwtNm) { + form.setValue( + `items.${indexMng}.fcBizs.${indexBiz}.fcOrgwts.${index}.ruseUtztnFcltNm`, + orgwtNm + ); + } + }); + } + + },[fcOrgwtsNmList,fcOrgwts]) + + // fcBizs 데이터가 변경될 때마다 차수 선택 목록을 업데이트합니다. useEffect(() => { const cycleLength = fcBizs.length; @@ -250,23 +294,6 @@ export function PbnstBuseForm({ setSumCpct(sum); }, [form.formState, indexMng, indexBiz]); - // // 차수별 정보(fcBizs)가 하나도 없을 경우 플레이스홀더 UI를 보여줍니다. - // if (fcBizs.length === 0) { - // return ( - // <div className="!mx-[1.6rem] !p-0r flex flex-col items-center justify-center h-64 text-gray-500 border rounded-md"> - // <p className="text-lg">등록된 차수별 정보가 없습니다.</p> - // <p className="text-sm mb-4">아래 버튼을 눌러 신규 차수 정보를 등록해주세요.</p> - // <Button - // onClick={addRuseFcltObj} - // type="button" - // size={commonButonSize} - // className="mt-2" - // > - // 신규 차수 등록 - // </Button> - // </div> - // ); - // } const pbnstBiz = form.watch(`items.${indexMng}.fcBizs`); return ( @@ -499,7 +526,7 @@ export function PbnstBuseForm({ ) || [] ).map((field, index) => ( <div - key={`fcDemands-${indexMng}-${indexBiz}`} // 이게 핵심! + key={`fcDemands-${indexMng}-${indexBiz}=${index}`} // 이게 핵심! className="grid grid-cols-12 gap-4 mt-3" > <div key={index + "keySon1"} className="col-span-4"> @@ -664,6 +691,7 @@ export function PbnstBuseForm({ )} /> </div> + <br></br> </div> ))} </div> @@ -674,7 +702,7 @@ export function PbnstBuseForm({ <UiModal open={isFcOrgwts} onOpenChange={(open: boolean) => setIsFcOrgwts(open)} - bodyClass={"w-[50vw] max-w-none h-[90vh]"} + bodyClass={"w-[70vw] max-w-none max-h-[90vh] overflow-y-auto"} body={ <FcOrgwtsRegModal closeModal={() => { diff --git a/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm.tsx b/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm.tsx index d07d24a23..e1454342b 100644 --- a/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm.tsx @@ -29,6 +29,10 @@ import { TEST_UUID } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; import { Label } from "@common/business_components/ui/form/lib/label"; import React from "react"; import RuseMngTableForm from "@/components/ui/table/pbnstReg/ruseMngTableForm/ruseMngTableForm"; +import { + handleCommaNumberChange, + formatCommaNumber, +} from "@/components/lib/func/commom"; export type FilesType = { atflCd: string; file: File; @@ -46,13 +50,9 @@ export function PbnstMngForm({ indexMng: number; setIndexMng: (index: number) => void; }) { - const { - code, - pbnst, - pbnstInsert, - fileDelete, - fileDownLoad, - } = useWrStore((state) => state); + const { code, pbnst, pbnstInsert, fileDelete, fileDownLoad } = useWrStore( + (state) => state + ); const { pbnstRes } = pbnst; const [regFileList, setRegFileList] = useState<fcFileType[] | null>(null); @@ -215,16 +215,14 @@ export function PbnstMngForm({ useEffect(() => { if (pbnst.pbnstRes && pbnst.pbnstRes?.length >= 0) { - const fcltNmList = pbnst.pbnstRes?.map((item, index) => ({ text: item.ruseUtztnFcltNm, val: String(index), })); - - console.log(' fcltNmList ', fcltNmList) + console.log(" fcltNmList ", fcltNmList); if (fcltNmList) setFcltNm(fcltNmList); - }else{ + } else { setFcltNm(null); } }, [pbnst.pbnstRes]); @@ -253,16 +251,14 @@ export function PbnstMngForm({ } }, [code]); - - useEffect(() =>{ - // console.log(' fcltNm ', watchedItem?.ruseUtztnFcltNm) - },[watchedItem]) - + useEffect(() => { + // console.log(' fcltNm ', watchedItem?.ruseUtztnFcltNm) + }, [watchedItem]); useEffect(() => { console.log(" form indexMng ", indexMng); - }, [indexMng]); - + }, [indexMng]); + return ( <div className=" !mx-[1.6rem] !p-0r"> <div> @@ -297,8 +293,6 @@ export function PbnstMngForm({ render={({ field }) => ( <FormUi label=""> <Input - - className="!text-2xl" placeholder="" type="text" @@ -379,11 +373,12 @@ export function PbnstMngForm({ <Input key={`items.${indexMng}.fcltCpct`} placeholder="" - type="number" + type="text" onChange={(e) => { - field.onChange(Number(e.target.value)); + if (formType !== "sttn") + handleCommaNumberChange(e, field.onChange); }} - value={field.value ?? 0} + value={formatCommaNumber(field.value)} readOnly={formType === "sttn" ? true : false} /> </FormUi> @@ -401,11 +396,12 @@ export function PbnstMngForm({ <Input key={`items.${indexMng}.ruseCpct`} placeholder="" - type="number" + type="text" onChange={(e) => { - field.onChange(Number(e.target.value)); + if (formType !== "sttn") + handleCommaNumberChange(e, field.onChange); }} - value={field.value ?? 0} + value={formatCommaNumber(field.value)} readOnly={formType === "sttn" ? true : false} /> </FormUi> @@ -422,11 +418,12 @@ export function PbnstMngForm({ <Input key={`items.${indexMng}.splyExtraCpct`} placeholder="" - type="number" + type="text" onChange={(e) => { - field.onChange(Number(e.target.value)); + if (formType !== "sttn") + handleCommaNumberChange(e, field.onChange); }} - value={field.value ?? 0} + value={formatCommaNumber(field.value)} readOnly={formType === "sttn" ? true : false} /> </FormUi> diff --git a/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegFormDetail/mngRegFormDetail.tsx b/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegFormDetail/mngRegFormDetail.tsx index 971888e10..6e13058ab 100644 --- a/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegFormDetail/mngRegFormDetail.tsx +++ b/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegFormDetail/mngRegFormDetail.tsx @@ -2,17 +2,14 @@ import { useWrStore } from "@/app/store"; import type { formDetailType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/headList"; import { formH1Class } from "@/components/lib/class/comClass"; -import { safeDate } from "@/components/lib/func/commom"; +import { formatCommaNumber, handleCommaNumberChange, safeDate } from "@/components/lib/func/commom"; import { DatePicker, FormField, FormUi, - SearchInputSelect, SelectBox, - type SelectDataType, } from "@common/business_components/ui"; import { Input } from "@common/components/ui"; -import { useState } from "react"; export default function MngRegFormDetail({ formList, @@ -55,9 +52,7 @@ export default function MngRegFormDetail({ name={fieldId} render={({ field }) => ( <FormUi label={formHead.lable}> - {formHead.type === "select" ? ( - <Input placeholder="" type="text" {...field} /> - ) : formHead.type === "date" ? ( + {formHead.type === "date" ? ( <DatePicker mode={"single"} className={"rounded-lg !w-full"} @@ -67,35 +62,54 @@ export default function MngRegFormDetail({ field.onChange(date), }} selectClass={"w-full"} + disabled={formHead.option === "disabled" ? true : false} + /> ) : formHead.type === "number" ? ( <Input placeholder="" - type="number" - value={field.value ?? 0} + type="text" onChange={(e) => { - field.onChange(Number(e.target.value)); + handleCommaNumberChange( + e, + field.onChange + ); }} - readOnly={formHead.option === "readOnly"?true:false} - /> - ) : formHead.type === "selectYn" ? ( - <SelectBox - label={""} - selectClass={""} - className="w-full" - selectArray={code.yn} - onSelectValue={(val: string | number) => - field.onChange(val) - } - value={field.value ?? "Y"} + value={formatCommaNumber(field.value)} + readOnly={formHead.option === "readOnly" ? true : false} + disabled={formHead.option === "disabled" ? true : false} + /> + ) : formHead.type === "select" ? ( + (() => { + const potentialOptions = formHead.selectData + ? code[formHead.selectData as keyof typeof code] + : []; + + return ( + <SelectBox + label={""} + selectClass={""} + className="w-full" + selectArray={ + Array.isArray(potentialOptions) + ? potentialOptions + : [] + } + onSelectValue={field.onChange} + value={field.value.trim()} + readOnly={formHead.option === "readOnly"} + /> + ); + })() ) : ( <Input placeholder="" type="text" value={field.value ?? ""} onChange={field.onChange} - readOnly={formHead.option === "readOnly"?true:false} + readOnly={formHead.option === "readOnly" ? true : false} + disabled={formHead.option === "disabled" ? true : false} /> )} </FormUi> diff --git a/apps/wr/components/ui/form/fcltReg/rainReg/rainRegForm.tsx b/apps/wr/components/ui/form/fcltReg/rainReg/rainRegForm.tsx index 9ec4fc5c0..fc4ed566e 100644 --- a/apps/wr/components/ui/form/fcltReg/rainReg/rainRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/rainReg/rainRegForm.tsx @@ -46,30 +46,40 @@ export default function RainRegForm({ ]); const cleanRainData = (raw: any): z.infer<typeof rainZod> => { + const buildings = + raw?.reuseBuilding && raw.reuseBuilding.length > 0 + ? raw.reuseBuilding + : [{}]; return { ...raw, - frstArchPrmsnDt: new Date(raw?.frstArchPrmsnDt || null), + frstArchPrmsnDt: raw?.frstArchPrmsnDt + ? new Date(raw.frstArchPrmsnDt) + : new Date(), // raw?.frstArchPrmsnDt가 유효하지 않으면 현재 날짜로 초기화 archChgPrmsnDt: raw?.archChgPrmsnDt ? new Date(raw?.archChgPrmsnDt) : null, - reuseBuilding: (raw?.reuseBuilding || []).map((item: any) => ({ + reuseBuilding: buildings.map((item: any) => ({ ...item, - instlCmptnDt: new Date(item?.instlCmptnDt), + instlCmptnDt: item?.instlCmptnDt + ? new Date(item.instlCmptnDt) + : new Date(), // item?.instlCmptnDt가 유효하지 않으면 현재 날짜로 초기화 operMngCd: item?.operMngCd ?? "", // null → "" grntchBdstCertYr: typeof item?.grntchBdstCertYr === "string" ? item?.grntchBdstCertYr : "", // null → "" - // Ensure enum fields are null if empty string to trigger Zod default + instlCstSprtYn: item?.instlCstSprtYn ?? "Y", // null → "Y" + grntchBdstCertYn:item?.grntchBdstCertYn??"Y", rainyObj: { ...item?.rainyObj, - filtrTrtmfctYn: item?.rainyObj?.filtrTrtmfctYn === "" ? null : item?.rainyObj?.filtrTrtmfctYn, + filtrTrtmfctYn: item?.rainyObj?.filtrTrtmfctYn ?? "Y", }, })), }; }; useEffect(() => { + if (grntchBdstCertYn === "N") { form.setValue("reuseBuilding.0.grntchBdstCertYr", ""); form.setValue("reuseBuilding.0.grntchBdstCertNo", ""); @@ -82,6 +92,9 @@ export default function RainRegForm({ } }, [grntchBdstCertYn, instlCstSprtYn, filtrTrtmfctYn]); + + + // form 값이 변경될 때마다 로그 출력 useEffect(() => { const subscription = form.watch((value) => { @@ -146,9 +159,8 @@ export default function RainRegForm({ value: field.value && !isNaN(new Date(field.value).getTime()) ? new Date(field.value) - : new Date(), // Provide a default Date value - onChange: (date) => field.onChange(date), - + : new Date(), + onChange: (date: Date | undefined) => field.onChange(date), }} selectClass={"w-full"} readOnly={readonly} @@ -245,7 +257,7 @@ export default function RainRegForm({ label={""} selectArray={code.yn} onSelectValue={readonly ? () => { } : field.onChange} - value={field.value || code.yn[0]?.val} + value={field.value ?? code.yn[0]?.val} readOnly={readonly} /> </FormUi> @@ -266,7 +278,8 @@ export default function RainRegForm({ if (!readonly) handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} - readOnly={filtrTrtmfctYn !== "Y"} + readOnly={readonly } + disabled={filtrTrtmfctYn !== "Y"} /> </FormUi> )} @@ -284,7 +297,8 @@ export default function RainRegForm({ type="text" value={field.value ?? ""} onChange={field.onChange} - readOnly={filtrTrtmfctYn !== "Y"} + readOnly={readonly } + disabled={filtrTrtmfctYn !== "Y"} /> </FormUi> )} diff --git a/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx b/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx index a4f29477a..d820bddc1 100644 --- a/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx @@ -28,10 +28,12 @@ import BdstReg from "../bdstReg/bdstReg"; export default function RswtrRegForm({ rswtForm, isModal, + setIsModal, readonly = false }: { rswtForm: customRswtBdst; isModal: boolean; + setIsModal?: (isModal: boolean) => void; readonly?: boolean; }) { const { rswt, code, rswtBdstUpdate, rswtBdstReg } = useWrStore( @@ -54,6 +56,7 @@ export default function RswtrRegForm({ const onSubmit = (values: z.infer<typeof rswtZod>) => { if (isModal) { rswtBdstReg(values, "41"); + if(setIsModal) setIsModal(false) } else { rswtBdstUpdate(values, "41"); } @@ -61,28 +64,29 @@ export default function RswtrRegForm({ const cleanRswtData = (raw: any): z.infer<typeof rswtZod> => { return { ...raw, - frstArchPrmsnDt: new Date(raw?.frstArchPrmsnDt || null), + frstArchPrmsnDt: raw?.frstArchPrmsnDt + ? new Date(raw.frstArchPrmsnDt) + : new Date(), // raw?.frstArchPrmsnDt가 유효하지 않으면 현재 날짜로 초기화 archChgPrmsnDt: raw?.archChgPrmsnDt ? new Date(raw?.archChgPrmsnDt) : null, reuseBuilding: (raw?.reuseBuilding || []).map((item: any) => ({ ...item, - instlCmptnDt: new Date(item?.instlCmptnDt), - operMngCd: item?.operMngCd ?? "", // null → "" - grntchBdstCertYr: - typeof item?.grntchBdstCertYr === "string" - ? item?.grntchBdstCertYr - : "", // null → "" - rswtrObj: { + instlCmptnDt: item?.instlCmptnDt + ? new Date(item.instlCmptnDt) + : new Date(), // item?.instlCmptnDt가 유효하지 않으면 현재 날짜로 초기화 + operMngCd: item?.operMngCd ?? "", // null → "" + instlCstSprtYn: item?.instlCstSprtYn ?? "Y", // null → "Y" + grntchBdstCertYn:item?.grntchBdstCertYn??"Y", + rswtrObj: { ...item?.rswtrObj, + filtrTrtmfctYn: item?.rainyObj?.filtrTrtmfctYn ?? "Y", + }, })), }; }; - useEffect(()=>{ - console.log("rswtForm:", rswtForm) - }, [rswtForm]) useEffect(() => { console.log("form errors:", form.formState.errors); diff --git a/apps/wr/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal.tsx b/apps/wr/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal.tsx new file mode 100644 index 000000000..57424996e --- /dev/null +++ b/apps/wr/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal.tsx @@ -0,0 +1,172 @@ +import { coRegExcel } from "@/app/store/matDmd/coSttn/headList"; +import type { ruseCaseMngExcelProps } from "@/app/store/matDmd/ruseCaseMng/index"; +import { reUseCaseNames } from "@/app/store/matDmd/ruseCaseMng/excel"; +import { filePicked, FileUpload, FormUi, Title } from "@common/business_components/ui"; +import { Form, FormField } from "@common/business_components/ui/form"; +import { Button } from "@common/components/ui"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import type { z } from "zod"; +import { useWrStore } from "@/app/store"; + +export default function RuseCaseMngExcel() { + const { insertRuseCaseMng } = useWrStore((state) => state); + const form = useForm<z.infer<typeof coRegExcel>>({ + resolver: zodResolver(coRegExcel), + }); + + const [file, setFiles] = useState<File | undefined>(); + + const onSubmit = () => { + if (file) { + filePicked(file, 0).then((res: unknown) => { + try { + const newArray: ruseCaseMngExcelProps[] = [] + const dataArray = Array.isArray(res) ? res : []; + dataArray.forEach((item: Record<string, unknown>) => { + const object: ruseCaseMngExcelProps = { + ruseBizNm: '', + bizBgngDate: '', + bizEndDate: '', + splyFcltKnd: '', + splyFcltNm: '', + splyFcltCpct: 0, + dmdFcltNm: '', + dmdFcltAddr: '', + ruseFcltCpct: 0, + ruseFcltPplLen: '', + ruseFcltUsg: '', + ruseFcltSplyCpct: 0, + ruseFcltPrcsMsrc: '', + grnteWqUsg: '', + grnteWqGrssClfg: 0, + grnteWqCombResdch: 0, + grnteWqTrbt: '', + grnteWqBod: '', + grnteWqSmll: '', + grnteWqCrmty: '', + grnteWqTn: '', + grnteWqTp: '', + grnteWqPh: '', + grnteWqClIonQnt: '', + grnteWqCndctStat: '', + grnteWqRmrk: '', + reqBizReuseFcltCst: 0, + reqBizPplCst: 0, + reqBizArchCst: 0, + reqBizMchnCst: 0, + reqBizMtrngCst: 0, + reqBizGisCst: 0, + reqBizCctvCst: 0, + reqBizCmcnCst: 0, + reqBizWstCst: 0, + reqBizDesignCst: 0, + reqBizSpvsCst: 0, + reqBizSbsdCst: 0, + reqBizEtc: '', + reqBizGrssCst: 0, + prcmPlanNlep: 0, + prcmPlanLcep: 0, + prcmPlanBrdnCst: 0, + prcmPlanEcqrRt: 0, + prcmPlanGrssCst: 0, + clclnNlep: 0, + clclnLcep: 0, + clclnBrdnCst: 0, + clclnEcqrRt: 0, + clclnGrssCst: 0, + }; + + Object.keys(reUseCaseNames).forEach((key: string) => { + if (item && reUseCaseNames[key] !== undefined) { + const mappedKey = reUseCaseNames[key]; + if (mappedKey && object.hasOwnProperty(mappedKey)) { + let value; + + if(value === "-"){ + value = ''; + } + if(key === "사업시작일" || key === "사업종료일"){ + if(item[key]?.toString().split(".")[1]?.length === 1){ + value = `${item[key]?.toString().split(".")[0] || ""}${item[key]?.toString().split(".")[1]?.length === 1 ? "0" : ""}${item[key]?.toString().split(".")[1] || ""}` + }else{ + value = `${item[key]?.toString().split(".")[0] || ""}${item[key]?.toString().split(".")[1] || ""}` + } + }else if(key === "재이용시설_관로(km)" || key?.toString().includes("보증수질_")){ + value = item[key]?.toString().trim() || ''; + }else{ + if(item[key]?.toString().includes("%") && Number(item[key]?.toString().replaceAll("%", ""))){ + value = item[key]?.toString().replaceAll("%", ""); + }else{ + value = item[key]; + } + } + + if(value === null || value?.toString().trim() === "-" || value?.toString().trim() === ""){ + if (typeof object[mappedKey as keyof ruseCaseMngExcelProps] === 'number') { + (object as unknown as Record<string, unknown>)[mappedKey] = ''; + } else { + (object as unknown as Record<string, unknown>)[mappedKey] = ''; + } + }else if (typeof object[mappedKey as keyof ruseCaseMngExcelProps] === 'number' && Number(value?.toString().replaceAll("%", "").replaceAll(",", ""))) { + (object as unknown as Record<string, unknown>)[mappedKey] = Number(value?.toString().replaceAll("%", "").replaceAll(",", "")); + }else{ + (object as unknown as Record<string, unknown>)[mappedKey] = value?.toString().trim() || ''; + } + } + } + }); + newArray.push(object); + }); + console.log("newArray : ", newArray); + insertRuseCaseMng(newArray); + } catch (e) { + console.log(e) + } + }); + }; + } + + return ( + <> + <div> + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)}> + <div> + <Title title={"물재이용 사례 등록"}> +
+
+

+ {"물재이용 사례 파일"} +

+ +
+ +
+ ( + + { + console.log("files", files) + if (files && files?.length === 1) setFiles(files[0]); + }} + fileTypes="" + maxFiles={1} + > + + )} + /> +
+ + +
+ + ); +} \ No newline at end of file diff --git a/apps/wr/components/ui/modal/fcltReg/bdstChgModal/bdstChgModal.tsx b/apps/wr/components/ui/modal/fcltReg/bdstChgModal/bdstChgModal.tsx index 03c257c59..0b071aa91 100644 --- a/apps/wr/components/ui/modal/fcltReg/bdstChgModal/bdstChgModal.tsx +++ b/apps/wr/components/ui/modal/fcltReg/bdstChgModal/bdstChgModal.tsx @@ -14,7 +14,6 @@ const {bdst,rain} = useWrStore((state) => { return state; }); const { bdstChgHeadList } = bdst; - console.log("chgBuilding", chgBuilding); return ( diff --git a/apps/wr/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal.tsx b/apps/wr/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal.tsx index 315f9397f..205846bb4 100644 --- a/apps/wr/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal.tsx +++ b/apps/wr/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal.tsx @@ -29,9 +29,10 @@ export type objSearchType = { interface BdstRegProps { moType: T; + setIsModal?: (open: boolean) => void; } -export function BdstReg({ moType }: BdstRegProps) { +export function BdstReg({ moType,setIsModal }: BdstRegProps) { const { rain, code, bdst,findBdst } = useWrStore((state) => { return state; }); @@ -143,7 +144,7 @@ export function BdstReg({ moType }: BdstRegProps) { { moType === 'RNB'? :moType==='RST' - ? + ? {setIsModal}}> : } diff --git a/apps/wr/components/ui/modal/fcltReg/fcOrgwtsRegModal/fcOrgwtsRegModal.tsx b/apps/wr/components/ui/modal/fcltReg/fcOrgwtsRegModal/fcOrgwtsRegModal.tsx index 92b40e146..9c8f2cdd6 100644 --- a/apps/wr/components/ui/modal/fcltReg/fcOrgwtsRegModal/fcOrgwtsRegModal.tsx +++ b/apps/wr/components/ui/modal/fcltReg/fcOrgwtsRegModal/fcOrgwtsRegModal.tsx @@ -1,6 +1,10 @@ "use client"; import { useWrStore } from "@/app/store"; -import type { pubHasuType, fcBizType, fcOrgwtType,} from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; +import type { + pubHasuType, + fcBizType, + fcOrgwtType, +} from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; import type { TableUpperProps } from "@/app/store/common/type/header"; import { tableData } from "@/components/lib/func/commom"; import RuseMngTable from "@/components/ui/table/pbnstReg/ruseMngTable/ruseMngTable"; @@ -11,138 +15,161 @@ import { Input } from "@common/components/ui"; import { ArrowLeft, ArrowRight } from "lucide-react"; import { useEffect, useRef, useState } from "react"; -export default function FcOrgwtsRegModal({ onRightSet, closeModal }: { onRightSet?: (rightData:fcOrgwtType[]) => void,closeModal?:(val:boolean) =>void }) { - const { code, pbnst, getSwtrmtFcltNm, searchFclts } = useWrStore((state) => state); +export default function FcOrgwtsRegModal({ + onRightSet, + closeModal, +}: { + onRightSet?: (rightData: fcOrgwtType[]) => void; + closeModal?: (val: boolean) => void; +}) { + const { code, pbnst, getSwtrmtFcltNm, searchFclts } = useWrStore( + (state) => state + ); const { fcOrgwtsHeadList, pbnstHasuRes } = pbnst; const [bdstTable, setbdstTable] = useState(); const rightRef = useRef([] as pubHasuType[]); const leftRef = useRef([] as pubHasuType[]); const [rightData, setRightData] = useState(); const [inputVal, setInputVal] = useState(""); - - useEffect(()=>{ - if(Array.isArray(searchFclts)){ + + useEffect(() => { + if (Array.isArray(searchFclts)) { const filteredData = searchFclts.filter((item) => { - if(rightData){ - if(rightData.find(rightItem =>rightItem.bscFctCd===item.bscFctCd)) + if (rightData) { + if ( + rightData.find((rightItem) => rightItem.bscFctCd === item.bscFctCd) + ) return false; } - return true - }) - setbdstTable(Array.isArray(filteredData) ? [...filteredData] : []) + return true; + }); + setbdstTable(Array.isArray(filteredData) ? [...filteredData] : []); } - - }, [searchFclts]) + }, [searchFclts]); useEffect(() => { if (code.sggCd.length > 0 && pbnstHasuRes.length > 0) { setbdstTable(tableData(pbnstHasuRes, { code })); - console.log('bdstTable ', bdstTable) + console.log("bdstTable ", bdstTable); } }, [code, pbnstHasuRes]); useEffect(() => { - console.log('rightData ', rightData) - if(onRightSet) - onRightSet( - (rightData as pubHasuType[])?.map(item => ({ - swtrmtFcltFk: item.bscFctCd, // 임시값, 실제 데이터에 맞게 수정 - orgwtWtritQnt: Number(item.fctCpc), // 임시값, 실제 데이터에 맞게 수정 - tmsYn: "N", // 임시값, 실제 데이터에 맞게 수정 - dschgFmYn: "N", // 임시값, 실제 데이터에 맞게 수정 - dtlPstnExpln: item.addr, // 임시값, 실제 데이터에 맞게 수정 - choose: false, // 임시값, 실제 데이터에 맞게 수정 - // 필요한 경우 다른 속성들도 추가 - })) as unknown as fcOrgwtType[] - ); - }, [rightData]) + console.log("rightData ", rightData); + if (onRightSet) + onRightSet( + (rightData as pubHasuType[])?.map((item) => ({ + swtrmtFcltFk: item.bscFctCd, // 임시값, 실제 데이터에 맞게 수정 + orgwtWtritQnt: Number(item.fctCpc), // 임시값, 실제 데이터에 맞게 수정 + tmsYn: "N", // 임시값, 실제 데이터에 맞게 수정 + dschgFmYn: "N", // 임시값, 실제 데이터에 맞게 수정 + dtlPstnExpln: item.addr, // 임시값, 실제 데이터에 맞게 수정 + choose: false, // 임시값, 실제 데이터에 맞게 수정 + // 필요한 경우 다른 속성들도 추가 + })) as unknown as fcOrgwtType[] + ); + }, [rightData]); return ( - <> -
-
- - <Button className="m-2" size={"sm"} - onClick={() => {closeModal && closeModal(false) }}>저장</Button> - </div> + <div className="flex flex-col h-[80vh]"> + <div className="shrink-0 p-4 border-b z-10 bg-white"> + <div className="flex items-center"> + <Title title="취수하수처리장 추가" /> + {/* <Button size="sm" onClick={() => closeModal?.(false)}>저장</Button> */} + <div className="flex items-center gap-2 mt-8"> - <div className="flex flex-row flex-grow gap-4 mt-4"> - <div className="w-3/7"> - <div className="flex items-center justify-between"> - <Label className="w-full text-2xl text-gray-600 text-left">공공하수처리시설 목록</Label> - <div className="flex items-center"> - <Label className="w-full text-xl text-gray-600 text-right">시설명</Label> - <Input className="w-1/2 !text-xl ml-2" type="text" value={inputVal} onChange={(e) => {setInputVal(e.target.value)}} /> - <Button className="m-2" size={"sm"} onClick={() => getSwtrmtFcltNm(inputVal)}>조회</Button> - </div> - </div> - <div className="border-t border-gray-300 my-4"></div> - <RuseMngTable - tableData={bdstTable || []} - headList={fcOrgwtsHeadList} - upHeadList={[]} // TODO: Replace with actual upHeadList data if available - ref={leftRef} - /> - </div> - <div className="flex flex-col items-center justify-center w-1/7 gap-2"> - <ArrowRight - type="button" - onClick={() => { - if (leftRef && typeof leftRef === "object" && "current" in leftRef) { - const checked = leftRef.current?.filter((item) => ((item as unknown as pubHasuType)?.choose)) - const unchecked = leftRef.current?.filter((item) => !(item as unknown as pubHasuType)?.choose) - if (checked && checked.length > 0) { - checked.forEach(item=>(item as unknown as pubHasuType).choose = false) - setRightData([...(checked as unknown as pubHasuType[]), - ...((rightRef.current ?? []) as unknown as pubHasuType[]) - ]) - - } - if (unchecked && unchecked.length > 0) { - setbdstTable([...(unchecked as unknown as pubHasuType[])]) - }else{ - setbdstTable([]) - } - } - } - } - className="w-12 h-12 text-gray-500 cursor-pointer" - /> - <ArrowLeft - type="button" - onClick={() => { - if (rightRef && typeof rightRef === "object" && "current" in rightRef) { - const checked = rightRef.current?.filter((item) => ((item as unknown as pubHasuType)?.choose)) - const unchecked = rightRef.current?.filter((item) => !(item as unknown as pubHasuType)?.choose) - if (checked && checked.length > 0) { - checked.forEach(item=>(item as unknown as pubHasuType).choose = false) - setbdstTable([ - ...(checked as unknown as pubHasuType[]), - ...((leftRef.current ?? []) as unknown as pubHasuType[]) - ]) - } - if (unchecked && unchecked.length > 0) { - setRightData([...(unchecked as unknown as pubHasuType[])]) - }else{ - setRightData([]) - } - } - }} - className="w-12 h-12 text-gray-500 cursor-pointer mt-5" /> - </div> - {/* Right Table container: Takes 2/5 of the width */} - <div className="w-3/7"> - <Label className="w-full text-2xl text-gray-600 text-right">취수하수처리장 목록</Label> - <div className="border-t border-gray-300 my-4"></div> - <RuseMngTable - tableData={rightData || []} - headList={fcOrgwtsHeadList} - upHeadList={[]} // TODO: Replace with actual upHeadList data if available - ref={rightRef} - /> + <Label className="text-xl text-gray-600">시설명</Label> + <Input + className="w-48 text-xl" + type="text" + value={inputVal} + onChange={(e) => setInputVal(e.target.value)} + /> + <Button size="sm" onClick={() => getSwtrmtFcltNm(inputVal)}> + 조회 + </Button> </div> </div> </div> - </> + {/* <div className="flex items-center justify-between"> + <div className="flex items-center gap-2"> + <Label className="text-xl text-gray-600">시설명</Label> + <Input + className="w-48 text-xl" + type="text" + value={inputVal} + onChange={(e) => setInputVal(e.target.value)} + /> + <Button size="sm" onClick={() => getSwtrmtFcltNm(inputVal)}>조회</Button> + </div> + </div> + </div> */} + + {/* 테이블 영역 */} + <div className="flex flex-row gap-4 flex-grow p-4 overflow-x-auto"> + {/* 왼쪽 테이블 */} + + <div className="w-5/12 min-w-[400px]"> + <Label className="text-2xl text-gray-600 mb-2 block text-right"> + 공공하수처리시설 목록 + </Label> + <RuseMngTable + tableData={bdstTable || []} + headList={fcOrgwtsHeadList} + upHeadList={[]} + ref={leftRef} + /> + </div> + + {/* 중간 이동 버튼 */} + <div className="flex flex-col justify-center items-center gap-4 w-[200px] min-w-[120px]"> + <ArrowRight + onClick={() => { + const checked = leftRef.current?.filter((item) => item.choose); + const unchecked = leftRef.current?.filter((item) => !item.choose); + if (checked?.length) { + checked.forEach((item) => (item.choose = false)); + setRightData([ + ...(checked as pubHasuType[]), + ...(rightRef.current ?? []), + ]); + } + setbdstTable(unchecked ?? []); + }} + className="w-8 h-8 text-gray-500 cursor-pointer" + /> + <ArrowLeft + onClick={() => { + const checked = rightRef.current?.filter((item) => item.choose); + const unchecked = rightRef.current?.filter( + (item) => !item.choose + ); + if (checked?.length) { + checked.forEach((item) => (item.choose = false)); + setbdstTable([ + ...(checked as pubHasuType[]), + ...(leftRef.current ?? []), + ]); + } + setRightData(unchecked ?? []); + }} + className="w-8 h-8 text-gray-500 cursor-pointer" + /> + </div> + + {/* 오른쪽 테이블 */} + <div className="w-5/12 min-w-[400px]"> + <Label className="text-2xl text-gray-600 mb-2 block text-right"> + 취수하수처리장 목록 + </Label> + <RuseMngTable + tableData={rightData || []} + headList={fcOrgwtsHeadList} + upHeadList={[]} + ref={rightRef} + /> + </div> + </div> + </div> ); } diff --git a/apps/wr/components/ui/modal/fcltReg/pbnstRegModal/pbnstRegModal.tsx b/apps/wr/components/ui/modal/fcltReg/pbnstRegModal/pbnstRegModal.tsx index b930a36bb..cc0eb80bf 100644 --- a/apps/wr/components/ui/modal/fcltReg/pbnstRegModal/pbnstRegModal.tsx +++ b/apps/wr/components/ui/modal/fcltReg/pbnstRegModal/pbnstRegModal.tsx @@ -20,13 +20,13 @@ import { useFieldArray, type UseFormReturn } from "react-hook-form"; import { writeRgtrAndMdfr } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg"; export function PbnstRegModal({ - data, form, + setPbnstInsert }: { - data: pubHasuType; form: UseFormReturn<{ items: PbnstRegArrrayType }>; + setPbnstInsert: (data: customPbnst) => void }) { - const { pbnst, pbnstInsert } = useWrStore((state) => { + const { pbnstInsert } = useWrStore((state) => { return state; }); const [pbnstBdstForm, setPbnstBdstForm] = useState<customPbnst>( @@ -35,40 +35,30 @@ export function PbnstRegModal({ const [currentTab, setCurrentTab] = useState<string>("mng"); const [indexMng, setIndexMng] = useState<number>(0); const [indexBiz, setIndexBiz] = useState<number>(0); - const onSubmit = async (valuse: { items: PbnstRegArrrayType }) => { - - const item = valuse.items[indexMng]; - const data = item ? writeRgtrAndMdfr([item]) : []; + const onSubmit = async (valuse: { items: PbnstRegArrrayType }) => { + const item = valuse.items[indexMng]; + const data = item ? writeRgtrAndMdfr([item]) : []; - if(data && data[0]){ - pbnstInsert(data[0]) - } - }; + if (data && data[0]) { + // pbnstInsert(data[0]); + setPbnstInsert(data[0]) + } + }; const handleTabChange = (value: string) => { setCurrentTab(value); - }; - - useEffect(() => { - console.log("form errors:", form.formState.errors); - }, [form.formState.errors]); - - + }; useEffect(() => { - if (data) { - - const firstForm = form.getValues("items"); - setIndexMng(firstForm.length - 1) } - }, [data, form]); - - + console.log("form errors:", form.formState.errors); + }, [form.formState.errors]); useEffect(() => { - console.log(' form.getValues() ' ,form.getValues()) - - }, [form.getValues()]); - + const firstForm = form.getValues("items"); + if (firstForm) { + setIndexMng(firstForm.length - 1); + } + }, [form]); return ( <> <h1 className="text-2xl font-medium text-label pl-8"> @@ -86,7 +76,7 @@ export function PbnstRegModal({ 차수별정보 </TabsTrigger> <Button - onClick={ form.handleSubmit(onSubmit)} + onClick={form.handleSubmit(onSubmit)} size={"sm"} className="!justify-end" > @@ -104,7 +94,7 @@ export function PbnstRegModal({ </TabsContent> <TabsContent className="max-h-full" value="buse"> <PbnstBuseForm - form={form} + form={form} indexMng={indexMng} indexBiz={indexBiz} setIndexBiz={setIndexBiz} diff --git a/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx b/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx index d20d10591..f4a3735a5 100644 --- a/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx +++ b/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx @@ -28,7 +28,7 @@ import { useEffect, useState } from "react"; import { set, useForm } from "react-hook-form"; import { z } from "zod"; -export function PbnstSttnModal({ data }: { data: customPbnst }) { +export function PbnstSttnModal({ data, titleVisible }: { data: customPbnst; titleVisible?: boolean }) { const { code, pbnstSttn, prfmnc } = useWrStore((state) => state); const [fcltTab, setFcltTab] = useState<string>("fclt"); const [ruseTab, setRuseTab] = useState<string>("inoutTotal"); @@ -237,10 +237,10 @@ export function PbnstSttnModal({ data }: { data: customPbnst }) { }, [pbnstSttn.pbnstSttnRes, data]); - useEffect(()=>{ + useEffect(() => { - if(data){ - form.reset({items:[data]}) + if (data) { + form.reset({ items: [data] }) } },[data, form]) @@ -252,7 +252,11 @@ export function PbnstSttnModal({ data }: { data: customPbnst }) { return ( <> <div> - <Title title={"하수재이용 시설 상세정보"} /> + {titleVisible && ( + <> + <Title title={"하수재이용 시설 상세정보"} /> + </> + )} <ScrollArea className={"h-[70vh]"}> <PbnstMngForm form={form} diff --git a/apps/wr/middleware.ts b/apps/wr/middleware.ts index 330252074..3fabc529d 100644 --- a/apps/wr/middleware.ts +++ b/apps/wr/middleware.ts @@ -5,6 +5,8 @@ import { CS_URL } from "./components/lib/config"; const CLAIM_NAME = "Authorization"; const AUTH_USER_ID = "token-id"; const serverApiUrl = CS_URL; +const GEOSERVER_URL = process.env.NEXT_PUBLIC_GEOSERVER_URL || ""; + export function middleware(req: NextRequest) { const pathname = req.nextUrl.pathname; console.log(`[Middleware] Request URL: ${req.url}`); @@ -44,9 +46,18 @@ export function middleware(req: NextRequest) { }); } + if (pathname.startsWith("/geoserver")) { + const queryParams = searchParams.toString(); + const newUrl = `${GEOSERVER_URL}${pathname.replace("/geoserver", "")}${ + queryParams ? `?${queryParams}` : "" + }`; + console.log(`[Middleware] Rewriting from '/geoserver' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl)); + } + return NextResponse.next(); } export const config = { - matcher: ["/server/:path*", "/attach-service/:path*"], + matcher: ["/server/:path*", "/attach-service/:path*", "/geoserver/:path*"], }; diff --git a/apps/wr/public/images/map/layericon/BML_CBAS_PS.png b/apps/wr/public/images/map/layericon/BML_CBAS_PS.png new file mode 100644 index 000000000..5ff4add3e Binary files /dev/null and b/apps/wr/public/images/map/layericon/BML_CBAS_PS.png differ diff --git a/apps/wr/public/images/map/layericon/BML_ELEV_PS.png b/apps/wr/public/images/map/layericon/BML_ELEV_PS.png new file mode 100644 index 000000000..83970a331 Binary files /dev/null and b/apps/wr/public/images/map/layericon/BML_ELEV_PS.png differ diff --git a/apps/wr/public/images/map/layericon/BML_ETCP_PS.png b/apps/wr/public/images/map/layericon/BML_ETCP_PS.png new file mode 100644 index 000000000..df9e4957b Binary files /dev/null and b/apps/wr/public/images/map/layericon/BML_ETCP_PS.png differ diff --git a/apps/wr/public/images/map/layericon/BML_STMS_PS.png b/apps/wr/public/images/map/layericon/BML_STMS_PS.png new file mode 100644 index 000000000..423f2e8fc Binary files /dev/null and b/apps/wr/public/images/map/layericon/BML_STMS_PS.png differ diff --git a/apps/wr/public/images/map/layericon/BML_SYMB_PS.png b/apps/wr/public/images/map/layericon/BML_SYMB_PS.png new file mode 100644 index 000000000..497589bea Binary files /dev/null and b/apps/wr/public/images/map/layericon/BML_SYMB_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_BYST_PS.png b/apps/wr/public/images/map/layericon/RDL_BYST_PS.png new file mode 100644 index 000000000..1e78cba68 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_BYST_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_CROS_PS.png b/apps/wr/public/images/map/layericon/RDL_CROS_PS.png new file mode 100644 index 000000000..57b5f2ca5 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_CROS_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_ETCT_PS.png b/apps/wr/public/images/map/layericon/RDL_ETCT_PS.png new file mode 100644 index 000000000..29c0b77b4 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_ETCT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_OCUP_PS.png b/apps/wr/public/images/map/layericon/RDL_OCUP_PS.png new file mode 100644 index 000000000..8a1ac0074 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_OCUP_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_RDSN_PS.png b/apps/wr/public/images/map/layericon/RDL_RDSN_PS.png new file mode 100644 index 000000000..5629de9fb Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_RDSN_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_SCLT_PS.png b/apps/wr/public/images/map/layericon/RDL_SCLT_PS.png new file mode 100644 index 000000000..7281dc61d Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_SCLT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_SEPC_PS.png b/apps/wr/public/images/map/layericon/RDL_SEPC_PS.png new file mode 100644 index 000000000..087b4e941 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_SEPC_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_STAT_PS.png b/apps/wr/public/images/map/layericon/RDL_STAT_PS.png new file mode 100644 index 000000000..13d8ea689 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_STAT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_STLT_PS.png b/apps/wr/public/images/map/layericon/RDL_STLT_PS.png new file mode 100644 index 000000000..489524b5c Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_STLT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_TFSN_PS.png b/apps/wr/public/images/map/layericon/RDL_TFSN_PS.png new file mode 100644 index 000000000..6dfff6696 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_TFSN_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_TRCT_PS.png b/apps/wr/public/images/map/layericon/RDL_TRCT_PS.png new file mode 100644 index 000000000..ed1a72d8a Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_TRCT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_TREE_PS.png b/apps/wr/public/images/map/layericon/RDL_TREE_PS.png new file mode 100644 index 000000000..0ee84dedc Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_TREE_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_TRSN_PS.png b/apps/wr/public/images/map/layericon/RDL_TRSN_PS.png new file mode 100644 index 000000000..15675cb31 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_TRSN_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_TSCT_PS.png b/apps/wr/public/images/map/layericon/RDL_TSCT_PS.png new file mode 100644 index 000000000..ffc225159 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_TSCT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_CLAY_PS.png b/apps/wr/public/images/map/layericon/SWL_CLAY_PS.png new file mode 100644 index 000000000..e603ffa9c Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_CLAY_PS.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_CLAY_PS_RED.png b/apps/wr/public/images/map/layericon/SWL_CLAY_PS_RED.png new file mode 100644 index 000000000..0e5450688 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_CLAY_PS_RED.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_DEPT_PS.png b/apps/wr/public/images/map/layericon/SWL_DEPT_PS.png new file mode 100644 index 000000000..fede6c304 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_DEPT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_MANH_PS.png b/apps/wr/public/images/map/layericon/SWL_MANH_PS.png new file mode 100644 index 000000000..75217f494 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_MANH_PS.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_MANH_PS_BLACK.png b/apps/wr/public/images/map/layericon/SWL_MANH_PS_BLACK.png new file mode 100644 index 000000000..5d2e8a70a Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_MANH_PS_BLACK.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_MANH_PS_BLUE.png b/apps/wr/public/images/map/layericon/SWL_MANH_PS_BLUE.png new file mode 100644 index 000000000..e521cb84a Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_MANH_PS_BLUE.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_MANH_PS_GRAY.png b/apps/wr/public/images/map/layericon/SWL_MANH_PS_GRAY.png new file mode 100644 index 000000000..e19f3d8b3 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_MANH_PS_GRAY.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_MANH_PS_GREEN.png b/apps/wr/public/images/map/layericon/SWL_MANH_PS_GREEN.png new file mode 100644 index 000000000..4503c13c6 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_MANH_PS_GREEN.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_MANH_PS_RED.png b/apps/wr/public/images/map/layericon/SWL_MANH_PS_RED.png new file mode 100644 index 000000000..b8fa45f9c Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_MANH_PS_RED.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_MANH_PS_YELLOW.png b/apps/wr/public/images/map/layericon/SWL_MANH_PS_YELLOW.png new file mode 100644 index 000000000..b7de1fbfd Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_MANH_PS_YELLOW.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_SPEW_PS.png b/apps/wr/public/images/map/layericon/SWL_SPEW_PS.png new file mode 100644 index 000000000..d260ea3a6 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_SPEW_PS.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_SPOT_PS.png b/apps/wr/public/images/map/layericon/SWL_SPOT_PS.png new file mode 100644 index 000000000..84c7992e4 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_SPOT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_SPOT_PS_BLACK.png b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_BLACK.png new file mode 100644 index 000000000..fe67f8f98 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_BLACK.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_SPOT_PS_BLUE.png b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_BLUE.png new file mode 100644 index 000000000..691f2f45e Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_BLUE.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_SPOT_PS_GREEN.png b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_GREEN.png new file mode 100644 index 000000000..d09c4bd97 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_GREEN.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_SPOT_PS_GREY.png b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_GREY.png new file mode 100644 index 000000000..32d0bf519 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_GREY.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_SPOT_PS_RED.png b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_RED.png new file mode 100644 index 000000000..a2da36f9b Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_RED.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_VENT_PS.png b/apps/wr/public/images/map/layericon/SWL_VENT_PS.png new file mode 100644 index 000000000..834eeec21 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_VENT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/TL_SPBD_ENTRC.png b/apps/wr/public/images/map/layericon/TL_SPBD_ENTRC.png new file mode 100644 index 000000000..8e7de8593 Binary files /dev/null and b/apps/wr/public/images/map/layericon/TL_SPBD_ENTRC.png differ diff --git a/apps/wr/public/images/map/layericon/TL_SPGF_LOC.png b/apps/wr/public/images/map/layericon/TL_SPGF_LOC.png new file mode 100644 index 000000000..74b0adac3 Binary files /dev/null and b/apps/wr/public/images/map/layericon/TL_SPGF_LOC.png differ diff --git a/apps/wr/public/images/map/layericon/TL_SPOT_NOIN.png b/apps/wr/public/images/map/layericon/TL_SPOT_NOIN.png new file mode 100644 index 000000000..4800beaeb Binary files /dev/null and b/apps/wr/public/images/map/layericon/TL_SPOT_NOIN.png differ diff --git a/apps/wr/public/images/map/layericon/TL_SPRD_BANO.png b/apps/wr/public/images/map/layericon/TL_SPRD_BANO.png new file mode 100644 index 000000000..8d08dca29 Binary files /dev/null and b/apps/wr/public/images/map/layericon/TL_SPRD_BANO.png differ diff --git a/apps/wr/public/images/map/layericon/TL_SPRD_CRSRD.png b/apps/wr/public/images/map/layericon/TL_SPRD_CRSRD.png new file mode 100644 index 000000000..57b5f2ca5 Binary files /dev/null and b/apps/wr/public/images/map/layericon/TL_SPRD_CRSRD.png differ diff --git a/apps/wr/public/images/map/layericon/TL_SPRD_NONM.png b/apps/wr/public/images/map/layericon/TL_SPRD_NONM.png new file mode 100644 index 000000000..e68c05ab6 Binary files /dev/null and b/apps/wr/public/images/map/layericon/TL_SPRD_NONM.png differ diff --git a/apps/wr/public/images/map/layericon/TL_SPRD_RENM.png b/apps/wr/public/images/map/layericon/TL_SPRD_RENM.png new file mode 100644 index 000000000..4800beaeb Binary files /dev/null and b/apps/wr/public/images/map/layericon/TL_SPRD_RENM.png differ diff --git a/apps/wr/public/images/map/layericon/TL_SPRD_RWNM.png b/apps/wr/public/images/map/layericon/TL_SPRD_RWNM.png new file mode 100644 index 000000000..e68c05ab6 Binary files /dev/null and b/apps/wr/public/images/map/layericon/TL_SPRD_RWNM.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_BMAN_PS.png b/apps/wr/public/images/map/layericon/UFL_BMAN_PS.png new file mode 100644 index 000000000..f2f64a641 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_BMAN_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_BOPC_PS.png b/apps/wr/public/images/map/layericon/UFL_BOPC_PS.png new file mode 100644 index 000000000..0f8e99f45 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_BOPC_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_BPIP_PS.png b/apps/wr/public/images/map/layericon/UFL_BPIP_PS.png new file mode 100644 index 000000000..dcc3622a7 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_BPIP_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_BPOL_PS.png b/apps/wr/public/images/map/layericon/UFL_BPOL_PS.png new file mode 100644 index 000000000..4f1102989 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_BPOL_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_BTRS_PS.png b/apps/wr/public/images/map/layericon/UFL_BTRS_PS.png new file mode 100644 index 000000000..557c17089 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_BTRS_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_GMNT_PS.png b/apps/wr/public/images/map/layericon/UFL_GMNT_PS.png new file mode 100644 index 000000000..6081d8b00 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_GMNT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_GPIP_PS.png b/apps/wr/public/images/map/layericon/UFL_GPIP_PS.png new file mode 100644 index 000000000..5d7e86fd2 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_GPIP_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_GPRS_PS.png b/apps/wr/public/images/map/layericon/UFL_GPRS_PS.png new file mode 100644 index 000000000..79c120f20 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_GPRS_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_GVAL_PS.png b/apps/wr/public/images/map/layericon/UFL_GVAL_PS.png new file mode 100644 index 000000000..e8f7cde2e Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_GVAL_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_KMAN_PS.png b/apps/wr/public/images/map/layericon/UFL_KMAN_PS.png new file mode 100644 index 000000000..764ce8ba5 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_KMAN_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_KPOL_PS.png b/apps/wr/public/images/map/layericon/UFL_KPOL_PS.png new file mode 100644 index 000000000..d9fa712cf Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_KPOL_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_VALV_PS.png b/apps/wr/public/images/map/layericon/UFL_VALV_PS.png new file mode 100644 index 000000000..1b5d70178 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_VALV_PS.png differ diff --git a/apps/wr/public/images/map/layericon/WTL_FIRE_PS.png b/apps/wr/public/images/map/layericon/WTL_FIRE_PS.png new file mode 100644 index 000000000..4137a3f74 Binary files /dev/null and b/apps/wr/public/images/map/layericon/WTL_FIRE_PS.png differ diff --git a/apps/wr/public/images/map/layericon/WTL_FLOW_PS.png b/apps/wr/public/images/map/layericon/WTL_FLOW_PS.png new file mode 100644 index 000000000..285f7edfa Binary files /dev/null and b/apps/wr/public/images/map/layericon/WTL_FLOW_PS.png differ diff --git a/apps/wr/public/images/map/layericon/WTL_MANH_PS.png b/apps/wr/public/images/map/layericon/WTL_MANH_PS.png new file mode 100644 index 000000000..5f2a57cfe Binary files /dev/null and b/apps/wr/public/images/map/layericon/WTL_MANH_PS.png differ diff --git a/apps/wr/public/images/map/layericon/WTL_META_PS.png b/apps/wr/public/images/map/layericon/WTL_META_PS.png new file mode 100644 index 000000000..22c7af31b Binary files /dev/null and b/apps/wr/public/images/map/layericon/WTL_META_PS.png differ diff --git a/apps/wr/public/images/map/layericon/WTL_PIPE_PS.png b/apps/wr/public/images/map/layericon/WTL_PIPE_PS.png new file mode 100644 index 000000000..c9c6863f6 Binary files /dev/null and b/apps/wr/public/images/map/layericon/WTL_PIPE_PS.png differ diff --git a/apps/wr/public/images/map/layericon/WTL_RSRV_PS.png b/apps/wr/public/images/map/layericon/WTL_RSRV_PS.png new file mode 100644 index 000000000..d7b307a61 Binary files /dev/null and b/apps/wr/public/images/map/layericon/WTL_RSRV_PS.png differ diff --git a/apps/wr/public/images/map/layericon/WTL_RSRV_PS_GRAY.png b/apps/wr/public/images/map/layericon/WTL_RSRV_PS_GRAY.png new file mode 100644 index 000000000..33a667a1e Binary files /dev/null and b/apps/wr/public/images/map/layericon/WTL_RSRV_PS_GRAY.png differ diff --git a/apps/wr/public/images/map/layericon/WTL_VALV_PS.png b/apps/wr/public/images/map/layericon/WTL_VALV_PS.png new file mode 100644 index 000000000..ca2b4788b Binary files /dev/null and b/apps/wr/public/images/map/layericon/WTL_VALV_PS.png differ diff --git a/apps/wr/public/images/map/layericon/default.png b/apps/wr/public/images/map/layericon/default.png new file mode 100644 index 000000000..92bf1f587 Binary files /dev/null and b/apps/wr/public/images/map/layericon/default.png differ diff --git a/apps/wr/public/images/map/layericon/reset.png b/apps/wr/public/images/map/layericon/reset.png new file mode 100644 index 000000000..45f889206 Binary files /dev/null and b/apps/wr/public/images/map/layericon/reset.png differ diff --git a/apps/wr/public/sample_ruseCaseMng.xlsx b/apps/wr/public/sample_ruseCaseMng.xlsx new file mode 100644 index 000000000..3a88e0623 Binary files /dev/null and b/apps/wr/public/sample_ruseCaseMng.xlsx differ diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index f45fe4e12..5ac98f27f 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -1,5 +1,6 @@ import { Search } from 'lucide-react'; import OlMap from 'ol/Map'; +import Overlay from 'ol/Overlay'; import View from 'ol/View'; import { defaults as defaultControls, OverviewMap } from 'ol/control'; import { defaults } from 'ol/interaction'; @@ -7,6 +8,7 @@ import * as olProj from 'ol/proj'; import { register } from 'ol/proj/proj4'; import proj4 from 'proj4'; import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; +import { createRoot } from 'react-dom/client'; import GisLonLat from './GisLonLat'; import GisMapToggle from './GisMapToggle'; import GisSelect from './GisSelect'; @@ -14,11 +16,27 @@ import GisToolBar from './GisToolBar'; import GisTree from './GisTree'; import './lib/css/gis.common.css'; import GisApp from './lib/gis.core'; +// import FacilityInfoPopup from './FacilityInfoPopup'; export const GisContext = createContext({ gisCore: null, gisMap: null, treeData: null, checkedLayerIds: null }); export const useGisContext = () => useContext(GisContext); -function Gis({ isOpen , gisMaps, isSearchModalOpen, treeData = [], isSearch = false, isMiniMap = false, setIsOpen, setFeatureData, featureData, searchView : SearchView, detailView: DetailView, onFeatureSelect, setIsSearchModalOpen }) { +function Gis({ + isOpen, + gisMaps, + isSearchModalOpen, + treeData = [], + isSearch = false, + isMiniMap = false, + setIsOpen, + setFeatureData, + featureData, + searchView: SearchView, + detailView: DetailView, + onFeatureSelect, + setIsSearchModalOpen, + onCheckedLayersChange +}) { const mapRef = useRef(); const [mapObject, setMapObject] = useState({}); const [tabVisible, setTabVisible] = useState(true); @@ -28,13 +46,21 @@ function Gis({ isOpen , gisMaps, isSearchModalOpen, treeData = [], isSearch = fa const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []); const mapInstanceRef = useRef(null); const checkedLayerIds = useRef([]); + + // 팝업 데이터를 저장할 상태와 오버레이 참조를 저장할 ref 추가 + // const [facilityInfoPopupData, setFacilityInfoPopupData] = useState([]); + // const facilityOverlaysRef = useRef([]); + // 체크된 레이어 상태 관리 + const [checkedLayers, setCheckedLayers] = useState([]); + const [checkedLayersData, setCheckedLayersData] = useState([]); + let gisMap = null; const closePopup = () => { setIsOpen(false); // 팝업 닫기 setFeatureData(null) }; - // SearchModal, GisContent 등에서 호출 + // Feature Click Event const handleFeatureSelect = useCallback((data) => { gisMap = mapInstanceRef.current; @@ -89,8 +115,10 @@ function Gis({ isOpen , gisMaps, isSearchModalOpen, treeData = [], isSearch = fa setFeatureData(data); setIsOpen(true); setIsSearchModalOpen(false); + onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출 }, [mapInstanceRef]); + // 지도 생성 및 초기화 useEffect(() => { if (!mapRef.current) return; if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단 @@ -104,7 +132,7 @@ function Gis({ isOpen , gisMaps, isSearchModalOpen, treeData = [], isSearch = fa ]); register(proj4); - //맵 생성 + // OpenLayers 지도 객체 생성 const map = new OlMap({ layers: [gisCore.baseLayers['base'], gisCore.baseLayers['satellite']], renderer: 'webgl', @@ -132,8 +160,8 @@ function Gis({ isOpen , gisMaps, isSearchModalOpen, treeData = [], isSearch = fa gisMaps.current = map - //미니맵 - if(isMiniMap){ + // 미니맵 추가 + if (isMiniMap) { const overviewMap = new OverviewMap({ layers: [GisApp.Module.core.baseMiniMapLayers['base']], view: new View({ @@ -147,6 +175,7 @@ function Gis({ isOpen , gisMaps, isSearchModalOpen, treeData = [], isSearch = fa map.addControl(overviewMap); } + // 스케일 컨트롤 및 베이스 레이어 설정 gisCore.__proto__.map = map; gisCore.scaleControl(); gisCore.changeBaseLayer('base'); @@ -154,16 +183,20 @@ function Gis({ isOpen , gisMaps, isSearchModalOpen, treeData = [], isSearch = fa mapInstanceRef.current = map; // 생성 완료 플래그 설정 }, []); + // treeData 변경 시 레이어 코드 생성 useEffect(() => { if (!treeData || treeData.length === 0) return; + // 기존 오버레이 및 레이어 모두 삭제 gisCore.deleteAllOverlay(); gisCore.deleteAllLayer(); + // 트리 데이터를 레이어 코드 객체로 변환 const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, 'lyrSn'); - GisApp.layerCode = layerCode; + GisApp.layerCode = layerCode; // 전역 레이어 코드 설정 //setTreeData(data); + // 편집 가능한 레이어 목록 생성 const layerItems = getLayerItems(layerCode); //setEditLayerItems(prev => [...layerItems]) }, [treeData]); @@ -193,31 +226,159 @@ function Gis({ isOpen , gisMaps, isSearchModalOpen, treeData = [], isSearch = fa } } + // // 데이터 로드 및 팝업 생성 로직 + // useEffect(() => { + // if (!mapInstanceRef.current) return; // 맵이 생성되지 않았으면 실행 안 함 + + // // 1. 팝업 데이터 가져오기 (feature.js의 getData와 유사) + // const fetchPopupData = async () => { + // // 실제 API 호출 로직으로 대체해야 합니다. + // // 예시: const response = await yourApiService.getFcltyWtCnt(); + // // setFacilityInfoPopupData(response.data); + + // // 임시 목업 데이터 + // const mockData = [ + // { ftrIdn: 1, ftrCde: 'SA206', bplcNm: '처리장A', fcltyUseYn: 'Y', x: 127.005, y: 37.282, aCnt: 10, cCnt: 5, eCnt: 3, mCnt: 8, iCnt: 2 }, + // { ftrIdn: 2, ftrCde: 'SA112', bplcNm: '처리장B', fcltyUseYn: 'N', x: 127.010, y: 37.285, aCnt: 8, cCnt: 4, eCnt: 2, mCnt: 6, iCnt: 1 } + // ]; + // setFacilityInfoPopupData(mockData); + // }; + + // fetchPopupData(); + + // }, [mapInstanceRef.current]); // 맵 인스턴스가 생성된 후 한 번만 실행 + + // // 데이터가 변경되면 오버레이를 생성/업데이트하는 로직 + // useEffect(() => { + // const map = mapInstanceRef.current; + // if (!map || facilityInfoPopupData.length === 0) return; + + // // 기존 오버레이 정리 + // facilityOverlaysRef.current.forEach(overlay => map.removeOverlay(overlay)); + // facilityOverlaysRef.current = []; + + // facilityInfoPopupData.forEach(data => { + // const popupId = `${data.ftrIdn}_${data.ftrCde}`; + + // // 팝업을 담을 div 엘리먼트 생성 + // const popupElement = document.createElement('div'); + // popupElement.id = popupId; + + // // OpenLayers Overlay 생성 + // const position = olProj.transform([Number(data.x), Number(data.y)], GisApp.FROM_PROJ, GisApp.TO_PROJ); + // const overlay = new Overlay({ + // element: popupElement, + // position: position, + // offset: [-75, -210], // 팝업 너비의 절반, 높이만큼 오프셋 조정 + // positioning: 'bottom-center', + // className: 'facility-popup-overlay' // 필요시 공통 클래스 추가 + // }); + + // map.addOverlay(overlay); + // facilityOverlaysRef.current.push(overlay); + + // // React 컴포넌트를 popupElement에 렌더링 + // const root = createRoot(popupElement); + // root.render(<FacilityInfoPopup data={data} />); + // }); + + // // 초기 가시성 업데이트 + // updatePopupVisibility(); + + // }, [facilityInfoPopupData]); // 팝업 데이터가 변경될 때마다 실행 + + // // 레이어 가시성에 따라 팝업 가시성을 제어하는 함수 + // const updatePopupVisibility = useCallback(() => { + // const map = mapInstanceRef.current; + // if (!map) return; + + // const visibleLayers = new Set(); + // map.getLayers().forEach(layer => { + // if (layer.getVisible()) { + // // feature.js의 로직처럼 typeName이나 ftrCde를 사용해 필터링 + // const typeName = layer.get('typeName'); + // if (typeName === 'VW_WTL_FCLTY_AS') { // 예시 레이어 이름 + // visibleLayers.add(layer.get('ftrCde')); + // } + // } + // }); + + // facilityOverlaysRef.current.forEach(overlay => { + // const element = overlay.getElement(); + // const ftrCde = element.querySelector('table')?.classList[0]; // FacilityPopup의 클래스에서 ftrCde 가져오기 + + // if (visibleLayers.has(ftrCde)) { + // element.style.display = ''; + // } else { + // element.style.display = 'none'; + // } + // }); + // }, []); + + // GisTree에서 체크 상태가 변경될 때 이 함수를 호출하도록 연결해야 합니다. + // 예시: GisTree 컴포넌트에 onCheck 콜백을 추가하고, 그 콜백에서 이 함수를 호출 + // useEffect(() => { + // // 레이어 가시성이 변경될 때마다 팝업 가시성 업데이트 + // const map = mapInstanceRef.current; + // if (!map) return; + + // const handleLayerVisibilityChange = () => { + // updatePopupVisibility(); + // }; + + // map.getLayers().on('change:length', handleLayerVisibilityChange); + // map.getLayers().forEach(layer => { + // layer.on('change:visible', handleLayerVisibilityChange); + // }); + + // return () => { + // // 클린업 + // map.getLayers().un('change:length', handleLayerVisibilityChange); + // map.getLayers().forEach(layer => { + // layer.un('change:visible', handleLayerVisibilityChange); + // }); + // }; + // }, [updatePopupVisibility]); + + // 체크된 레이어 정보를 처리하는 함수 + const handleCheckedLayersChange = useCallback((checkedIds, checkedNodesData) => { + setCheckedLayers(checkedIds); + setCheckedLayersData(checkedNodesData); + onCheckedLayersChange?.(checkedIds, checkedNodesData); + }, [onCheckedLayersChange]); + return ( <> <GisContext.Provider value={{ gisCore, gisMap: mapInstanceRef.current, treeData, checkedLayerIds }}> <div className="h-[calc(100vh-10rem)] flex relative"> <div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} /> - <GisTree visible={tabVisible} setVisible={setTabVisible} /> - + <GisTree + visible={tabVisible} + setVisible={setTabVisible} + onCheckedChange={handleCheckedLayersChange} + /> + {/* <GisContent onFeatureSelect={handleFeatureSelect} isSearch={isSearch} isSearchModalOpen={isSearchModalOpen} setIsSearchModalOpen={setIsSearchModalOpen} /> */} - - {/* isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} + + {/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} {isOpen && featureData && !isSearchModalOpen && ( <DetailView featureData={featureData} onClose={closePopup} // 닫기 콜백 전달 /> )} - <SearchView - open={isSearchModalOpen} - onOpenChange={setIsSearchModalOpen} - onFeatureSelect={onFeatureSelect} - /> - <GisLonLat/> - <GisToolBar/> - <GisMapToggle/> - <GisSelect onFeatureSelect={onFeatureSelect} /> + {/* SearchView가 존재할 때만 렌더링 */} + {SearchView && ( + <SearchView + open={isSearchModalOpen} + onOpenChange={setIsSearchModalOpen} + onFeatureSelect={handleFeatureSelect} + /> + )} + <GisLonLat /> + <GisToolBar /> + <GisMapToggle /> + <GisSelect onFeatureSelect={handleFeatureSelect} /> </div> {isSearch && ( diff --git a/packages/common/business_components/src/ui/map/GisContent.js b/packages/common/business_components/src/ui/map/GisContent.js index a8595efb0..d2594ba95 100644 --- a/packages/common/business_components/src/ui/map/GisContent.js +++ b/packages/common/business_components/src/ui/map/GisContent.js @@ -1,12 +1,8 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import GisApp from './lib/gis.core'; -import { useGisContext } from './Gis'; -import GisLonLat from './GisLonLat'; -import GisSelect from './GisSelect'; -import GisToolBar from './GisToolBar'; -import './lib/css/gis.edit.css'; -import GisMapToggle from './GisMapToggle'; import { Search } from 'lucide-react'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useGisContext } from './Gis'; +import './lib/css/gis.edit.css'; +import GisApp from './lib/gis.core'; export default function GisContent({ edit = false, onFeatureSelect, isSearch, setIsSearchModalOpen }) { const { gisMap, gisCore } = useGisContext(); diff --git a/packages/common/business_components/src/ui/map/GisMapToggle.js b/packages/common/business_components/src/ui/map/GisMapToggle.js index b157dc141..c890f75f8 100644 --- a/packages/common/business_components/src/ui/map/GisMapToggle.js +++ b/packages/common/business_components/src/ui/map/GisMapToggle.js @@ -1,7 +1,5 @@ import React, { useContext, useState } from 'react'; -import { Button } from "@common/components"; import { GisContext } from './Gis'; -import GisApp from './lib/gis.core'; export default React.memo(function MapToggleBtn() { const { gisCore } = useContext(GisContext); diff --git a/packages/common/business_components/src/ui/map/GisSelect.js b/packages/common/business_components/src/ui/map/GisSelect.js index a9116e626..e9deb72f7 100644 --- a/packages/common/business_components/src/ui/map/GisSelect.js +++ b/packages/common/business_components/src/ui/map/GisSelect.js @@ -1,7 +1,7 @@ -import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import React, { useContext, useEffect } from 'react'; +import { GisContext } from './Gis'; import GisApp from './lib/gis.core'; import GisOlSelect from './lib/gis.select'; -import { GisContext } from './Gis'; export default React.memo(function GisSelect({ onFeatureSelect }) { diff --git a/packages/common/business_components/src/ui/map/GisTree.js b/packages/common/business_components/src/ui/map/GisTree.js index 6a2e8face..3c2cb2819 100644 --- a/packages/common/business_components/src/ui/map/GisTree.js +++ b/packages/common/business_components/src/ui/map/GisTree.js @@ -1,15 +1,15 @@ -import GisApp from './lib/gis.core'; -import React, { useEffect, useState } from 'react'; -import { useGisContext } from './Gis'; -import { DndProvider } from "react-dnd"; import { - Tree, MultiBackend, + Tree, getBackendOptions } from "@minoru/react-dnd-treeview"; import { ChevronLeft, ChevronRight } from 'lucide-react'; +import React, { useEffect, useState } from 'react'; +import { DndProvider } from "react-dnd"; +import { useGisContext } from './Gis'; +import GisApp from './lib/gis.core'; -export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes = () => { }, visible, setVisible }) { +export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes = () => { }, visible, setVisible, onCheckedChange }) { const { gisCore, treeData } = useGisContext(); const [checked, setChecked] = useState([]); const [expanded, setExpanded] = useState([]); @@ -24,19 +24,31 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes } }, [treeData]); + // 트리 노드 체크 시 레이어 표시/숨김 useEffect(() => { if (nodes.length > 0) { + // 기본으로 체크된 노드들 찾기 const defaultChecked = getDefaultCheckedNodes(nodes); setChecked(defaultChecked); + console.log('defaultChecked : ', defaultChecked); + + // 기본으로 확장된 노드들 설정 const defaultExpanded = Array.from(new Set([ ...defaultChecked, ...defaultChecked.flatMap((id) => getAncestors(id, nodes)) ])); setExpanded(defaultExpanded); - gisCore.setLayerOnMap(defaultChecked, true); - } - }, [nodes]); + // 기본 체크된 레이어들을 지도에 표시 + gisCore.setLayerOnMap(defaultChecked, true); + + // 초기 체크된 레이어 정보를 상위로 전달 + const defaultCheckedNodesData = nodes.filter(node => defaultChecked.includes(node.id)); + onCheckedChange?.(defaultChecked, defaultCheckedNodesData); + } + }, [nodes]); // onCheckedChange를 의존성에 추가 + + // 초기 표시 여부가 'Y'인 레이어들 찾기 const getDefaultCheckedNodes = (nodes, values = []) => { for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; @@ -94,6 +106,11 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes const unchecked = allIds.filter((nid) => !newChecked.includes(nid)); gisCore.setLayerOnMap(newChecked, true); gisCore.setLayerOnMap(unchecked, false); + + // 체크된 노드 정보와 함께 노드 데이터를 상위로 전달 + const checkedNodesData = nodes.filter(node => newChecked.includes(node.id)); + onCheckedChange?.(newChecked, checkedNodesData); + return newChecked; }); }; diff --git a/packages/common/business_components/src/ui/map/lib/gis.core.js b/packages/common/business_components/src/ui/map/lib/gis.core.js index db58c898f..72a584970 100644 --- a/packages/common/business_components/src/ui/map/lib/gis.core.js +++ b/packages/common/business_components/src/ui/map/lib/gis.core.js @@ -1,26 +1,24 @@ import MousePosition from 'ol/control/MousePosition'; import ScaleLine from 'ol/control/ScaleLine'; +import { getTopLeft } from 'ol/extent'; import Feature from 'ol/Feature'; +import GeoSJON from 'ol/format/GeoJSON'; import WKT from 'ol/format/WKT'; +import { Point } from 'ol/geom'; import LinearRing from 'ol/geom/LinearRing'; import Draw from 'ol/interaction/Draw'; import TileLayer from 'ol/layer/Tile'; +import VectorLayer from 'ol/layer/Vector'; import * as olProj from 'ol/proj'; import VectorSource from 'ol/source/Vector'; +import TileWMTS from 'ol/source/WMTS'; import XYZ from 'ol/source/XYZ'; -import GeoSJON from 'ol/format/GeoJSON'; import { Fill, Stroke, Style } from 'ol/style'; import CircleStyle from 'ol/style/Circle'; -import VectorLayer from 'ol/layer/Vector'; -import { Point } from 'ol/geom'; -import { getTopLeft } from 'ol/extent'; -import TileWMTS from 'ol/source/WMTS'; import WMTSTileGrid from 'ol/tilegrid/WMTS'; // import { get as getProjection } from 'ol/proj'; -import proj4 from 'proj4'; import { register } from 'ol/proj/proj4'; -import olSourceImageWMS from 'ol/source/ImageWMS'; -import { GeoJSON } from 'ol/format'; +import proj4 from 'proj4'; import GisOlEditLayer from './gis.edit'; import GisOlLayer from './gis.layer'; @@ -562,6 +560,7 @@ GisApp.core.prototype = { * * Returns: */ + // 레이어를 지도에 표시/숨김 setLayerOnMap: function (layerIds, visible, addQurryFlter) { for (let i in layerIds) { let id = layerIds[i]; @@ -572,10 +571,14 @@ GisApp.core.prototype = { const layerCode = GisApp.layerCode[id]; const queryFlter = layerCode?.flterSql || '' + ' ' + (addQurryFlter || ''); layerCode['flterSql'] = queryFlter; - //layer = this.addWmsLayer(layerCode); + //layer = this.addWmsLayer(layerCode); + + // WFS 레이어 생성 및 지도에 추가 layer = this.addWfsLayer(layerCode); } } + + // 선택 인터랙션 및 스냅 기능 설정 if (GisApp.Module.select) { this.setSelectInteraction(); }