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/coMng/drgtSttn/page.tsx b/apps/wr/app/matMng/coMng/drgtSttn/page.tsx index d7c5170b2..55dbeb454 100644 --- a/apps/wr/app/matMng/coMng/drgtSttn/page.tsx +++ b/apps/wr/app/matMng/coMng/drgtSttn/page.tsx @@ -3,18 +3,17 @@ import { useWrStore } from "@/app/store"; import { btnClass } from "@/app/store/common/code"; import type { customDrgt } from "@/app/store/matDmd/drgtSttn"; import { drgtHeadListExport } from "@/app/store/matDmd/drgtSttn/headList"; -import { excelDownload, getCity } from "@/components/lib/func/commom"; +import { getCity } from "@/components/lib/func/commom"; import DrgtDetailModal from "@/components/ui/modal/coMat/drgtDetailModal"; import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal"; -import { ContentsDiv, DateMonthPicker, SearchDiv, SearchFormLeft, SearchFormRight, SelectBox, Title, UiModal, UiTable, type SelectDataType } from "@common/business_components/ui"; -import { MonthPicker } from "@common/business_components/ui/date_month_picker/mothPicker"; +import { ContentsDiv, DateMonthPicker, downloadExcel, SearchDiv, SearchFormLeft, SearchFormRight, SelectBox, Title, UiModal, UiTable, type SelectDataType } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; -import { Button, DatePicker, Label } from "@common/components/ui"; -import { useCallback, useEffect, useState } from "react"; +import { Button, Label, ScrollArea } from "@common/components/ui"; +import { useEffect, useState } from "react"; import { toast, Toaster } from "react-hot-toast"; export default function DrgtSttn() { - const { code, drgt, drgtSearch, drgtModalSearch } = useWrStore((state) => state); + const { code, drgt, drgtSearch, drgtModalSearch, drgtInit } = useWrStore((state) => state); const { drgtRes, drgtMoRes } = drgt; const [fromDate, setFromDate] = useState<string>(); const [toDate, setToDate] = useState<string>(); @@ -22,6 +21,7 @@ export default function DrgtSttn() { const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" }); const [tableData, setTableData] = useState<customDrgt[]>(); const [selectedSgg, setSelectedSgg] = useState<string>(''); + const [excelData, setExcelData] = useState<any[]>(); const [drgtModal, setDrgtModal] = useState<{ isOpen: boolean; data: customDrgt | undefined; @@ -35,45 +35,82 @@ export default function DrgtSttn() { } } + const excelClick = () => { + downloadExcel({ + data: excelData ?? [], + fileName: "우리동네가뭄 현황", + sheetNameArray: ["Sheet1"], + headerRange: "A1:G1" + }) + } + + useEffect(() => { + if (!drgt.isInit) { + drgtInit(); + } + }, [drgt.isInit, drgtInit]); + + useEffect(() => { + let resData = Array.isArray(drgtRes) ? drgtRes : [drgtRes]; + resData = resData.map((item) => { + if (item.sggNm === '세종시') { + return { + ...item, + ctpvCd: '세종특별자치시', + sggNm: '' + } + } + + return { + ...item, + ctpvCd: code.cityCd.find(ct => (ct.text == item.sggNm || (code.sggCd.find(sgg => sgg.text == item.sggNm)?.val.substring(0, 2) == ct.val)))?.text, + sggNm: code.sggCd.find(sgg => sgg.text == item.sggNm)?.text + } + }) + const data = Array.isArray(resData) + ? resData.map((item: customDrgt) => { + return { + "시도": item.ctpvCd, + "시군구": item.sggNm, + "정상(횟수)": item.normal, + "관심(횟수)": item.attention, + "주의(횟수)": item.caution, + "관심+주의(횟수)": item.warning, + "가뭄순위": item.ranking, + } + }) : []; + setExcelData(data) + setTableData(resData) + }, [drgtRes]) + useEffect(() => { if (drgtMoRes && drgtMoRes.length > 0) { setDrgtModal({ isOpen: true, data: drgtMoRes }); } }, [drgtMoRes]); - const getCityVal = (val: string, sggCd: SelectDataType[]) => { - const data = sggCd - .filter((item) => String(item.val).substring(0, 2) === val) - .map((item) => ({ - val: item.val, - text: item.text, // 한국어 텍스트 - })); - return data; - }; - const getSelCity = (val: string | number) => { const cityValue = String(val); - const data = getCityVal(cityValue, code.sggCd); - setSelectSgg(data); - const sggVal = data.length > 0 ? data[0].val : ''; - setSelectedSgg(sggVal); // 시군구 value 세팅 - setObjSearch({ city: cityValue, sgg: sggVal }); + if (cityValue === "0") { + setSelectSgg([]); + setSelectedSgg(''); + setObjSearch({ city: cityValue, sgg: '' }); + } else { + const data = getCity(cityValue, code.sggCd); + setSelectSgg(data); + + const firstSggVal = data.find(item => item.val === "0")?.val || (data[0]?.val ?? ""); + setSelectedSgg(firstSggVal); + setObjSearch({ city: cityValue, sgg: firstSggVal }); + } }; + const getSgg = (val: string | number) => { setSelectedSgg(String(val)); setObjSearch({ ...objSearch, sgg: String(val) }); }; - const excelClick = useCallback(() => { - excelDownload( - [[]], - "우리동네가뭄", - "sample_drgtSttn.xlsx", - "A1:H1" - ); - }, []); - useEffect(() => { if (fromDate === undefined && toDate === undefined) { const today = new Date(); @@ -101,7 +138,7 @@ export default function DrgtSttn() { onSelectValue={getSelCity} /> <SelectBox - className="col-span-2 w-1/6 h-full text-2xl" + className="col-span-2 w-1/6 h-full text-2xl ml-8" label="시군구" selectClass={""} selectArray={selectSgg || []} @@ -110,7 +147,7 @@ export default function DrgtSttn() { /> <div className="col-span-5 w-auto"> <div className="flex flex-row items-center"> - <Label className="text-xl text-gray-600">조회기간</Label> + <Label className="text-xl text-gray-600 ml-8">조회기간</Label> <div className="flex flex-row w-1/4"> <DateMonthPicker field={{ @@ -125,8 +162,9 @@ export default function DrgtSttn() { setFromDate(formatted); } }} + selectClass="ml-4 text-2xl" /> - <span>~</span> + <span className="ml-2 text-center">~</span> <DateMonthPicker field={{ value: toDate, @@ -140,6 +178,7 @@ export default function DrgtSttn() { setToDate(formatted); } }} + selectClass="ml-4" /> </div> </div> @@ -148,35 +187,42 @@ export default function DrgtSttn() { </SearchFormLeft> <SearchFormRight className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 "> - <div className="flex flex-row space-x-2"> + <div className="flex flex-row space-x-2 items-center"> <Button size={"sm"} onClick={(e) => { let sidoText = ''; - let sggText = ''; + let sggText = []; if (objSearch?.city.toString().length > 0) { sidoText = code.cityCd.find((item) => item.val === objSearch?.city)?.text ?? ''; } if (objSearch?.sgg.toString().length > 1) { sggText = code.sggCd.find((item) => item.val === objSearch?.sgg)?.text ?? ''; + } else { + sggText = code.sggCd.filter((item) => item.val.substring(0, 2) === objSearch?.city).map((item) => item.text); + } + if (sidoText === '전체') { sidoText = '' } + if (sidoText === '세종특별자치시') { sidoText = '세종시'; sggText = sidoText; } - drgtSearch(fromDate ?? "", toDate ?? "", sidoText, sggText) + + const searchArr = []; + if (sidoText) searchArr.push(sidoText); + if (Array.isArray(sggText)) { + searchArr.push(...sggText); + } else if (sggText) { + searchArr.push(sggText); + } + + drgtSearch(fromDate ?? "", toDate ?? "", searchArr) }} > 조회 </Button> - <Button - size={"sm"} - className={btnClass} - onClick={() => { }} - > - 상세보기 - </Button> <Button size={"sm"} className={`${btnClass} green`} @@ -199,17 +245,19 @@ export default function DrgtSttn() { <div className="grid grid-cols-6 gap-4 "> <div className="col-span-6"> <ListDiv> - <UiTable - headName={[]} - tableData={drgtRes || []} - headlist={drgtHeadListExport} - pageSize={20} - total={10000} - cellClick={(index: number | undefined, headId: string) => { - cellClick(index ?? undefined, headId); - }} - onPageChange={() => { }} - ></UiTable> + <ScrollArea className="h-[66vh]"> + <UiTable + headName={undefined} + tableData={tableData || []} + headlist={drgtHeadListExport} + pageSize={14} + total={0} + cellClick={(index: number | undefined, headId: string) => { + cellClick(index ?? undefined, headId); + }} + onPageChange={() => { }} + ></UiTable> + </ScrollArea> </ListDiv> </div> </div> @@ -219,7 +267,7 @@ export default function DrgtSttn() { setDrgtModal({ ...drgtModal, isOpen: !drgtModal.isOpen }); }} bodyClass={"w-[50vw] max-w-none h-[80vh]"} - body={<DrgtDetailModal data={drgtModal.data} /*formObj={drgtModal.data}*/ />} + body={<DrgtDetailModal data={drgtModal.data} />} /> <Toaster position="top-center" /> </ContentsDiv> diff --git a/apps/wr/app/matMng/coMng/ldrmntCoSttn/page.tsx b/apps/wr/app/matMng/coMng/ldrmntCoSttn/page.tsx index c5daee29c..eb43b29bf 100644 --- a/apps/wr/app/matMng/coMng/ldrmntCoSttn/page.tsx +++ b/apps/wr/app/matMng/coMng/ldrmntCoSttn/page.tsx @@ -44,7 +44,7 @@ export default function LdrmntCoSttn() { const [searchTxt, setSearchTxt] = useState<string>(); const ref = useRef<ReturnType>({} as ReturnType); const [excelData, setExcelData] = useState<any[]>(); - + const [isExcelDownload, setIsExcelDownload] = useState(false); const excelClick = useCallback(() => { excelDownload( @@ -57,14 +57,22 @@ export default function LdrmntCoSttn() { }, []); const excelDownloadClick = () => { - downloadExcel({ - data: excelData ?? [], - fileName: "잠재수요처 관리", - sheetNameArray: ["Sheet1"], - headerRange: "A1:F1" - }) + ldrmntSearch(1, "10000", "0", ""); + setIsExcelDownload(true); } + useEffect(() => { + if (isExcelDownload && excelData && excelData.length > 0) { + downloadExcel({ + data: excelData, + fileName: "잠재수요처 관리", + sheetNameArray: ["Sheet1"], + headerRange: "A1:F1" + }); + setIsExcelDownload(false); + } + }, [excelData, isExcelDownload]); + const [isExcelOpen, setIsExcelOpen] = useState<{ isModal: boolean; }>({ isModal: false }); @@ -73,14 +81,21 @@ export default function LdrmntCoSttn() { if (index !== undefined) { setDetailModal({ isOpen: true, data: ldrmntRes[index] }); } - } const getSelCity = (val: string | number) => { - const cityValue = String(val); // Ensure val is treated as a string - const data = getCity(cityValue, code.sggCd); - setSelectSgg(data); - setObjSearch({ city: cityValue, sgg: '' }); + const cityValue = String(val); + + if (cityValue === "0") { + setSelectSgg([]); + setObjSearch({ city: cityValue, sgg: "" }); + } else { + const data = getCity(cityValue, code.sggCd); + setSelectSgg(data); + + const firstSggVal = data.find(item => item.val === "0")?.val || (data[0]?.val ?? ""); + setObjSearch({ city: cityValue, sgg: firstSggVal }); + } }; const getSgg = (val: string | number) => { @@ -125,10 +140,9 @@ export default function LdrmntCoSttn() { setExcelData(data) }, [excelList]) - useEffect(() => { - let cityTxt = ''; - let sggTxt = ''; + let cityTxt = '' + let sggTxt = '' if (objSearch?.city.toString().length > 0) { cityTxt = code.cityCd.find((item) => item.val === objSearch?.city)?.text ?? ''; @@ -138,21 +152,23 @@ export default function LdrmntCoSttn() { sggTxt = code.sggCd.find((item) => item.val === objSearch?.sgg)?.text ?? ''; } - let cityNm = ""; + if (cityTxt === '전체' || cityTxt === '') { + cityTxt = '0' + } + + let cityNm = "" if (sggTxt !== "0") { - cityNm = cityTxt + " " + sggTxt; + cityNm = cityTxt + " " + sggTxt } else { - cityNm = cityTxt; + cityNm = cityTxt } - if (cityNm) { - if (cityTxt === '') cityNm = "0"; - ldrmntSearch(pageNation, cityNm, searchTxt || ""); + ldrmntSearch(pageNation, '20', cityNm, searchTxt || ""); } else { - ldrmntSearch(pageNation, "", searchTxt || ""); + ldrmntSearch(pageNation, '20', '0', searchTxt || ""); } - }, [pageNation, objSearch, code, searchTxt, ldrmntSearch]) + }, [pageNation]); return ( <> @@ -175,6 +191,7 @@ export default function LdrmntCoSttn() { label="시군구" selectClass={""} selectArray={selectSgg || []} + value={objSearch.sgg} // value props 추가 onSelectValue={getSgg} /> <SearchInput @@ -189,7 +206,7 @@ export default function LdrmntCoSttn() { </SearchFormLeft> <SearchFormRight className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 "> - <div className="flex flex-row space-x-2"> + <div className="flex flex-row space-x-2 items-center"> <Button size={"sm"} onClick={(e) => { @@ -205,6 +222,10 @@ export default function LdrmntCoSttn() { sggTxt = code.sggCd.find((item) => item.val === objSearch?.sgg)?.text ?? ''; } + if (cityTxt === '전체' || cityTxt === '') { + cityTxt = '0' + } + let cityNm = "" if (sggTxt !== "0") { @@ -212,10 +233,8 @@ export default function LdrmntCoSttn() { } else { cityNm = cityTxt } - if (cityNm) { - if (cityTxt === '') cityNm = "0"; - ldrmntSearch(pageNation, cityNm, searchTxt || ""); + ldrmntSearch(pageNation, '20', cityNm, searchTxt || ""); } }} > 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/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/prfmncReg/chkgdnReg/page.tsx b/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx index fd8e2a6d6..53a7a31f3 100644 --- a/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx @@ -31,7 +31,6 @@ import { useCallback, useEffect, useState } from "react"; import { Toaster } from "react-hot-toast"; import { shallow } from "zustand/shallow"; -//----------------------- export default function Page() { const { code, @@ -43,7 +42,6 @@ export default function Page() { rainChkExcelDownload, rainChkHeadChange, isRainSttn, - rswtChkgdnInit, rswtChkPolInit, rswtChkDetilsUpdate, @@ -61,7 +59,7 @@ export default function Page() { rswtrChkDetailsZodType[] >([]); const [deleteIdx, setDeleteIdx] = useState<number[]>([]); - + const [pageNation, setPageNation] = useState(1); const [selFcltDiv, setSelFcltDiv] = useState("RNB"); const [isModal, setIsModal] = useState({ open: false, @@ -70,9 +68,9 @@ export default function Page() { const cellClick = () => { if (searchYear) if (selFcltDiv === "RNB") { - rainChkInit(searchYear, "0"); + rainChkInit("1", "20", searchYear, "0"); } else { - rswtChkgdnInit(searchYear, "0"); + rswtChkgdnInit("1", "20", searchYear, "0"); } }; @@ -82,14 +80,14 @@ export default function Page() { useEffect(() => { if (!rainChkgdn.isInit) { - rainChkInit(new Date().getFullYear().toString(), "0"); + rainChkInit("1", "20", new Date().getFullYear().toString(), "0"); isRainSttn(false); setIsModal({ data: {}, open: false }); setIsRegExcel(false); } if (!rswtChkgdn.isInit) { - rswtChkgdnInit(new Date().getFullYear().toString(), "0"); + rswtChkgdnInit("1", "20", new Date().getFullYear().toString(), "0"); setIsModal({ data: {}, open: false }); setIsRegExcel(false); } @@ -179,9 +177,11 @@ export default function Page() { selectClass={""} selectArray={yearOptions} value={searchYear} - onSelectValue={(val: string | number) => + onSelectValue={(val: string | number) => { setSearchYear(String(val)) } + + } /> </div> <div className="col-span-2 flex flex-row space-x-2"> @@ -255,6 +255,8 @@ export default function Page() { detailUp={setUpdateRainDetail} handleClick={handleClick} rainChkData={rainChkgdn.rainChk} + pageNation={pageNation} + onPageChange={setPageNation} /> ) : ( <RswtChkgdTable @@ -262,6 +264,8 @@ export default function Page() { deleteRswtData={setDeleteIdx} handleRswtClick={handleClick} rswtChkData={rswtChkgdn.rswtChkRes} + pageNation={pageNation} + onPageChange={setPageNation} /> )} </ListDiv> diff --git a/apps/wr/app/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx b/apps/wr/app/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx index 6be50a8c5..10dd7ccb6 100644 --- a/apps/wr/app/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx @@ -25,7 +25,7 @@ import { } from "@common/components/ui"; import { useState, useEffect, useCallback } from "react"; import { set } from "react-hook-form"; -import { Toaster } from "react-hot-toast"; +import toast, { Toaster } from "react-hot-toast"; import { headMakeprfmnc } from "../chkgdnReg/TableHead"; import { InputTableBody } from "@/components/lib/func/TableData"; import { @@ -51,8 +51,8 @@ export default function prfmncPrfmncReg() { }>({ isModal: false, data: {} as FcPrfType }); const [inputData, setInputData] = useState<any[]>(); const [pbnstRuseFcltPk, setPbnstRuseFcltPk] = useState<string>(); - const [pageNation , setPageNation] = useState<number>(1); - + const [pageNation, setPageNation] = useState<number>(1); + const [cellNum, setCellNum] = useState<number>(); const [searchYear, setSearchYear] = useState<string>( String(new Date().getFullYear()) ); @@ -76,7 +76,7 @@ export default function prfmncPrfmncReg() { ); prfmncHasuInit(String(pageNation), "41"); } - }, [inputData, searchYear, pbnstRuseFcltPk,prfmncHasuInit,pageNation]); + }, [inputData, searchYear, pbnstRuseFcltPk, prfmncHasuInit, pageNation]); @@ -88,10 +88,16 @@ export default function prfmncPrfmncReg() { isModal: false, data: prfmncRes[index] || ({} as FcPrfType), }); - const inUsageData = structuredClone(initValInUsage); - const outUsageData = structuredClone(initValOutUsage); - const inSubtotal = structuredClone(subTotalObj); - const outSubtotal = structuredClone(subTotalObj); + const inUsageData = structuredClone(initValInUsage).map(row => ({ + ...row, + 구분: "장내용수", + })); + const outUsageData = structuredClone(initValOutUsage).map(row => ({ + ...row, + 구분: "장외용수", + })); + const inSubtotal = { ...structuredClone(subTotalObj), 구분: "장내용수" }; + const outSubtotal = { ...structuredClone(subTotalObj), 구분: "장외용수" }; if (prfmncRes[index]?.prfList) { prfmncRes[index].prfList?.forEach((prfItem) => { const month = Number(prfItem?.ym?.slice(4)); // "MM" @@ -127,7 +133,7 @@ export default function prfmncPrfmncReg() { }); inUsageData.push(inSubtotal); outUsageData.push(outSubtotal); - const usageTotalObj: { [key: string]: any } = structuredClone(totalObj); + const usageTotalObj: { [key: string]: any } = { ...structuredClone(totalObj), 구분: "총합계", 용수명: "" }; for (const key in usageTotalObj) { if (["usage", "gubun", "edit"].includes(key)) continue; const val = @@ -162,7 +168,7 @@ export default function prfmncPrfmncReg() { <ContentsDiv> <div className="grid grid-cols-4 gap-4"> <div className="col-span-4"> - <Title title={"하수재이용 시설 등록"} /> + <Title title={"하수재이용 실적 등록"} /> <SearchDiv> <SearchFormLeft> <div className="col-span-2 flex flex-row space-x-2"> @@ -197,7 +203,7 @@ export default function prfmncPrfmncReg() { <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={(e) => { }}> 조회 </Button> <Button @@ -211,7 +217,12 @@ export default function prfmncPrfmncReg() { <Button size={"sm"} onClick={() => { - setIsExcelOPen({ ...isExcelOpen, isModal: true }); + if (cellNum === undefined) { + toast.error("하수처리재이용시설을 선택해주세요.") + return; + } else { + setIsExcelOPen({ ...isExcelOpen, isModal: true }); + } }} > 파일등록 @@ -238,6 +249,7 @@ export default function prfmncPrfmncReg() { headName={"하수재이용시설 목록"} cellClick={(index: number | undefined, headId: string) => { cellClick(index, headId); + setCellNum(index ?? 0); }} onPageChange={setPageNation} children={undefined} @@ -266,7 +278,7 @@ export default function prfmncPrfmncReg() { </div> </ContentsDiv> <UiModal - bodyClass={""} + bodyClass={"w-[30vw] max-w-none"} body={<PbnPrfExcelModal data={isExcelOpen.data}></PbnPrfExcelModal>} open={isExcelOpen.isModal} onOpenChange={() => { diff --git a/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx b/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx index a50c08071..f689eb745 100644 --- a/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx @@ -8,9 +8,11 @@ import { excelDownload, yearOptions } from "@/components/lib/func/commom"; import SearchInputText from "@/components/ui/searchInput/searchInput"; import { CHART_FORM, ContentsDiv, MainChart, ScrollArea, SearchBaseForm, SearchInput, SelectBox, Title, UiTable, type ChartConfig, type ReturnType } from "@common/business_components"; import ListDiv from "@common/business_components/ui/list_div"; -import { Button, Label } from "@common/components/ui"; +import { Button, Label, Table, TableBody, TableHeader } from "@common/components/ui"; import { useCallback, useEffect, useRef, useState } from "react"; import toast, { Toaster } from "react-hot-toast"; +import { headMakeprfmnc } from "../../prfmncReg/chkgdnReg/TableHead"; +import { InputTableBody } from "@/components/lib/func/TableData"; export default function PbnstPrfmncSttn() { const { @@ -34,9 +36,9 @@ export default function PbnstPrfmncSttn() { const [swtrmtFcltNm, setSwtrmtFcltNm] = useState<string>(); const [pbnRuseNm, setPbnRuseNm] = useState<string>(""); - const [searchYear, setSearchYear] = useState<string>( - String(new Date().getFullYear()) - ); + const [searchYear, setSearchYear] = useState<string>( + String(new Date().getFullYear()) + ); const [excelData, setExcelData] = useState<any[]>([]); const excelClick = useCallback(() => { excelDownload( @@ -83,15 +85,20 @@ export default function PbnstPrfmncSttn() { type lineChartDataProps = { label: string, val: number, color: string }; const prfTableData = (prfList: PrfType[], year: string) => { - const inUsageData = structuredClone(initValInUsage); - const outUsageData = structuredClone(initValOutUsage); - - const inSubtotal = structuredClone(subTotalObj); - const outSubtotal = structuredClone(subTotalObj); + const inUsageData = structuredClone(initValInUsage).map(row => ({ + ...row, + 구분: "장내용수", + })); + const outUsageData = structuredClone(initValOutUsage).map(row => ({ + ...row, + 구분: "장외용수", + })); + const inSubtotal = { ...structuredClone(subTotalObj), 구분: "장내용수" }; + const outSubtotal = { ...structuredClone(subTotalObj), 구분: "장외용수" }; 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}월`; @@ -142,7 +149,7 @@ export default function PbnstPrfmncSttn() { setPrfData([]); } } - + const [chartId, setChartId] = useState<string>(); useEffect(() => { // 누적 변수 초기화 @@ -198,8 +205,8 @@ export default function PbnstPrfmncSttn() { prfTableData([], year) rawData.forEach((item) => { // 실적 테이블 데이터 - if (chartId){ - if(item.prfList?.[0]?.ym.slice(0, 4) === year && item.prfList?.[0]?.pbnstRuseFcltFk === chartId){ + if (chartId) { + if (item.prfList?.[0]?.ym.slice(0, 4) === year && item.prfList?.[0]?.pbnstRuseFcltFk === chartId) { prfTableData(item.prfList, year); } item.prfList.filter((e) => e.ym.slice(0, 4) === year && e.pbnstRuseFcltFk === chartId).forEach((element) => { @@ -265,40 +272,40 @@ export default function PbnstPrfmncSttn() { }); // 도넛차트 데이터 - dnData.push({ label: "세척수", val: wshwtr, fill: "red" }); - dnData.push({ label: "냉각수", val: coolUswtr, fill: "orange" }); - dnData.push({ label: "청소수", val: cleanUswtr, fill: "yellow" }); - dnData.push({ label: "식수대", val: drwtUswtr, fill: "green" }); - dnData.push({ label: "희석용수", val: dltnUswtr, fill: "skyblue" }); - dnData.push({ label: "중수도", val: ithlRswtr, fill: "blue" }); - dnData.push({ label: "기타", val: ithlEtcUswtr, fill: "purple" }); - dnData.push({ label: "청소화장실용수", val: cleanToilUswtr, fill: "red" }); - dnData.push({ label: "세척살수용수", val: resn, fill: "orange" }); - dnData.push({ label: "조경용수", val: lndwtr, fill: "yellow" }); - dnData.push({ label: "친수용수", val: hydUswtr, fill: "green" }); - dnData.push({ label: "지하수충전", val: ugwtChr, fill: "skyblue" }); - dnData.push({ label: "농업용수", val: farmUswtr, fill: "blue" }); - dnData.push({ label: "하천등유지용수", val: rvrKeepUswtr, fill: "purple" }); - dnData.push({ label: "공업용수", val: indstUswtr, fill: "black" }); + dnData.push({ label: "세척수", val: wshwtr, fill: "#F7CFD8" }); + dnData.push({ label: "냉각수", val: coolUswtr, fill: "#F4F8D3" }); + dnData.push({ label: "청소수", val: cleanUswtr, fill: "#A6D6D6" }); + dnData.push({ label: "식수대", val: drwtUswtr, fill: "#8E7DBE" }); + dnData.push({ label: "희석용수", val: dltnUswtr, fill: "#FF8383" }); + dnData.push({ label: "중수도", val: ithlRswtr, fill: "#FFF574" }); + dnData.push({ label: "기타", val: ithlEtcUswtr, fill: "#A1D6CB" }); + dnData.push({ label: "청소화장실용수", val: cleanToilUswtr, fill: "#A19AD3" }); + dnData.push({ label: "세척살수용수", val: resn, fill: "#B1F0F7" }); + dnData.push({ label: "조경용수", val: lndwtr, fill: "#81BFDA" }); + dnData.push({ label: "친수용수", val: hydUswtr, fill: "#F5F0CD" }); + dnData.push({ label: "지하수충전", val: ugwtChr, fill: "#FADA7A" }); + dnData.push({ label: "농업용수", val: farmUswtr, fill: "#A6AEBF" }); + dnData.push({ label: "하천등유지용수", val: rvrKeepUswtr, fill: "#C5D3E8" }); + dnData.push({ label: "공업용수", val: indstUswtr, fill: "#D0E8C5" }); // 장내용수 도넛 - dnInData.push({ label: "세척수", val: wshwtr, fill: "yellow" }); - dnInData.push({ label: "냉각수", val: coolUswtr, fill: "green" }); - dnInData.push({ label: "청소수", val: cleanUswtr, fill: "yellow" }); - dnInData.push({ label: "식수대", val: drwtUswtr, fill: "green" }); - dnInData.push({ label: "희석용수", val: dltnUswtr, fill: "yellow" }); - dnInData.push({ label: "중수도", val: ithlRswtr, fill: "green" }); - dnInData.push({ label: "기타", val: ithlEtcUswtr, fill: "yellow" }); - dnInData.push({ label: "청소화장실용수", val: cleanToilUswtr, fill: "green" }); - dnInData.push({ label: "세척살수용수", val: resn, fill: "yellow" }); + dnInData.push({ label: "세척수", val: wshwtr, fill: "#F7CFD8" }); + dnInData.push({ label: "냉각수", val: coolUswtr, fill: "#F4F8D3" }); + dnInData.push({ label: "청소수", val: cleanUswtr, fill: "#A6D6D6" }); + dnInData.push({ label: "식수대", val: drwtUswtr, fill: "#8E7DBE" }); + dnInData.push({ label: "희석용수", val: dltnUswtr, fill: "#FF8383" }); + dnInData.push({ label: "중수도", val: ithlRswtr, fill: "#FFF574" }); + dnInData.push({ label: "기타", val: ithlEtcUswtr, fill: "#A1D6CB" }); + dnInData.push({ label: "청소화장실용수", val: cleanToilUswtr, fill: "#A19AD3" }); + dnInData.push({ label: "세척살수용수", val: resn, fill: "#B1F0F7" }); // 장외용수 도넛 - dnOutData.push({ label: "조경용수", val: lndwtr, fill: "yellow" }); - dnOutData.push({ label: "친수용수", val: hydUswtr, fill: "black" }); - dnOutData.push({ label: "지하수충전", val: ugwtChr, fill: "yellow" }); - dnOutData.push({ label: "농업용수", val: farmUswtr, fill: "black" }); - dnOutData.push({ label: "하천등유지용수", val: rvrKeepUswtr, fill: "yellow" }); - dnOutData.push({ label: "공업용수", val: indstUswtr, fill: "black" }); + dnOutData.push({ label: "조경용수", val: lndwtr, fill: "#81BFDA" }); + dnOutData.push({ label: "친수용수", val: hydUswtr, fill: "#F5F0CD" }); + dnOutData.push({ label: "지하수충전", val: ugwtChr, fill: "#FADA7A" }); + dnOutData.push({ label: "농업용수", val: farmUswtr, fill: "#A6AEBF" }); + dnOutData.push({ label: "하천등유지용수", val: rvrKeepUswtr, fill: "#C5D3E8" }); + dnOutData.push({ label: "공업용수", val: indstUswtr, fill: "#D0E8C5" }); // 라인차트 데이터 setInLineData({ @@ -466,7 +473,7 @@ export default function PbnstPrfmncSttn() { useEffect(() => { setbdstTable(prfmncRes); - if(prfmncRes?.[0]?.pbnstRuseFcltPk) setChartId(prfmncRes[0].pbnstRuseFcltPk) + if (prfmncRes?.[0]?.pbnstRuseFcltPk) setChartId(prfmncRes[0].pbnstRuseFcltPk) }, [prfmncRes]); @@ -619,12 +626,12 @@ export default function PbnstPrfmncSttn() { </div> <h1 className="">하수재이용실적 현황</h1> <ListDiv className="col-span-8 pr-6 "> - <ScrollArea className="h-[60vh]"> - <div className="card-header"> + {/* <ScrollArea className="h-[60vh]"> */} + {/* <div className="card-header"> <h3 className="text-2xl font-medium text-label"> {"하수처리수재이용 실적(m3) 등록결과"} </h3> - </div> + </div> */} <UiTable headName={undefined} @@ -637,7 +644,26 @@ export default function PbnstPrfmncSttn() { throw new Error("Function not implemented."); }}> </UiTable> - </ScrollArea> + + {/* <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> + + <Table variant="secondary"> + <TableHeader className="font-bold"> + {headMakeprfmnc(monthHeadList, [])} + </TableHeader> + <TableBody> + {InputTableBody(prfData ?? [], monthHeadList, setPrfData)} + </TableBody> + </Table> + </ScrollArea> + </ListDiv> */} + {/* </ScrollArea> */} </ListDiv> </ListDiv> 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/drgtSttn/headList.tsx b/apps/wr/app/store/matDmd/drgtSttn/headList.tsx index 1741b7456..dcdc655b5 100644 --- a/apps/wr/app/store/matDmd/drgtSttn/headList.tsx +++ b/apps/wr/app/store/matDmd/drgtSttn/headList.tsx @@ -2,8 +2,8 @@ import { z } from "zod"; import type { CommonTableProps } from "../../common/type/header"; export const drgtZod = z.object({ - ctpvCd: z.string().default(''), - sggNm: z.string().default(''), + ctpvCd: z.string().default('').nullish(), + sggNm: z.string().default('').nullish(), normal: z.string().default(''), attention: z.string().default(''), caution: z.string().default(''), diff --git a/apps/wr/app/store/matDmd/drgtSttn/index.tsx b/apps/wr/app/store/matDmd/drgtSttn/index.tsx index 698ba5984..4211d2d08 100644 --- a/apps/wr/app/store/matDmd/drgtSttn/index.tsx +++ b/apps/wr/app/store/matDmd/drgtSttn/index.tsx @@ -1,4 +1,4 @@ -import { fetchType, getFetch } from "@/components/lib/api"; +import { fetchType, getFetch, postFectch } from "@/components/lib/api"; import type { CommonTableProps } from "../../common/type/header"; import { drgtHeadListExport, drgtZod } from "./headList"; import type { z } from "zod"; @@ -14,11 +14,13 @@ export type drgtState = { drgtHeadList: CommonTableProps[]; drgtRes: customDrgt[]; drgtMoRes: customDrgt[]; + isInit: boolean; } } type drgtActions = { - drgtSearch: (fromDate: string, toDate: string, sido: string, sgg: string) => void + drgtInit: () => void + drgtSearch: (fromDate: string, toDate: string, sido: string[]) => void drgtModalSearch: (fromDate: string, toDate: string, sgg: string) => void }; @@ -29,72 +31,97 @@ export const drgtStateExport: drgtState = { drgtHeadList: drgtHeadList, drgtRes: drgtRes, drgtMoRes: drgtRes, + isInit: false, } } +const initFetch = async (fromDate: string, toDate: string, sido:string[]) => { +console.log("sido",sido) + const drgtData = await postFectch( + `/il/getDataCount/${fromDate}/${toDate}`, + sido, + fetchType.WR + ); + + //주의, 관심, 정상 추출 + const statusData = Array.from( + new Set(drgtData.data.map((e) => e.lvngMfgUswtrDrgtInfo)) + ); + + const statusMap = drgtData.data.reduce((acc: any, { sggNm, lvngMfgUswtrDrgtInfo: status, countDrgt }: { sggNm: string; lvngMfgUswtrDrgtInfo: string; countDrgt: number }) => { + if (!acc[sggNm]) { + acc[sggNm] = { sggNm, ...Object.fromEntries(statusData.map((e) => [e, 0])) }; + } + acc[sggNm][status] = Number(countDrgt); + return acc; + }, {}); + const data = Object.values(statusMap); + + const formatStatus = data.map((item) => { + const attention = item['관심'] || 0; + const caution = item['주의'] || 0; + return { + sggNm: item.sggNm, + normal: String(item['정상'] || 0), + caution: String(caution), + attention: String(attention), + warning: String(attention + caution), + }; + }); + + const ranking = Array.from( + new Set(formatStatus.map(e => Number(e.warning))) + ).sort((a, b) => b - a); + + const rankData = ranking.reduce((acc, val, idx) => { + acc[val] = idx + 1; + return acc; + }, {}); + + const drgtRes = formatStatus.map(item => ({ + ...item, + warning: String(item.warning), + ranking: rankData[Number(item.warning)], + })); + + return drgtRes; + +} + + export const drgtActionsExport: (set: any) => drgtActions = ( set: any ) => { return { - drgtSearch: async (fromDate: string, toDate: string, sido: string, sgg: string) => { + drgtInit: async () => { try { - let searchUrl = ""; + const today = new Date(); + const year = today.getFullYear(); + const month = String(today.getMonth() + 1).padStart(2, '0'); + const formatted = `${year}${month}`; + const fromDate = formatted; + const toDate = formatted; - if (sido === "" || sido === "전체") { - searchUrl = `/il/getDataCount/${fromDate}/${toDate}`; - } else { - searchUrl = `/il/getDataCount/${fromDate}/${toDate}/${sido}/${sgg}`; - } - - console.log("searchUrl", searchUrl); - - const drgtData = await getFetch( - fetchType.WR, - searchUrl - ); - - console.log("drgtRes", drgtData) - - //주의, 관심, 정상 추출 - const statusData = Array.from( - new Set(drgtData.map((e) => e.lvngMfgUswtrDrgtInfo)) - ); - - const statusMap = drgtData.reduce((acc: any, { sggNm, lvngMfgUswtrDrgtInfo: status, countDrgt }: { sggNm: string; lvngMfgUswtrDrgtInfo: string; countDrgt: number }) => { - if (!acc[sggNm]) { - acc[sggNm] = { sggNm, ...Object.fromEntries(statusData.map((e) => [e, 0])) }; - } - acc[sggNm][status] = Number(countDrgt); - return acc; - }, {}); - const data = Object.values(statusMap); - - const formatStatus = data.map((item) => { - const attention = item['관심'] || 0; - const caution = item['주의'] || 0; + //FIXME: 페이지 처음 로딩될때 데이터 보려고 6월로 박아놓음 이거 fromDate로 고치기 + const drgtRes = await initFetch("202506", toDate, []); + set((state: drgtState) => { return { - sggNm: item.sggNm, - normal: String(item['정상'] || 0), - caution: String(caution), - attention: String(attention), - warning: String(attention + caution), - }; + ...state, + drgt: { + ...state.drgt, + drgtRes: drgtRes, + isInit: false, + }, + } }); + } catch (e) { + console.log(e) + } + }, + drgtSearch: async (fromDate: string, toDate: string, sido: string[]) => { + try { + const drgtRes = await initFetch(fromDate, toDate, sido); - const ranking = Array.from( - new Set(formatStatus.map(e => Number(e.warning))) - ).sort((a, b) => b - a); - - const rankData = ranking.reduce((acc, val, idx) => { - acc[val] = idx + 1; - return acc; - }, {}); - - const drgtRes = formatStatus.map(item => ({ - ...item, - warning: String(item.warning), - ranking: rankData[Number(item.warning)], - })); set((state: drgtState) => { return { ...state, @@ -115,10 +142,7 @@ export const drgtActionsExport: (set: any) => drgtActions = ( `/il/getData/${fromDate}/${toDate}/${sgg}/${sgg}` ); - console.log("drgtMoData", drgtMoData) - const drgtMoRes = drgtMoData.map((item) => { - console.log("item", item) const year = item.anlsYmd.slice(0, 4); const month = item.anlsYmd.slice(4, 6); const drgtState = item.lvngMfgUswtrDrgtInfo; diff --git a/apps/wr/app/store/matDmd/ldrmntCoSttn/excel.tsx b/apps/wr/app/store/matDmd/ldrmntCoSttn/excel.tsx index dc0601127..ad7990cfc 100644 --- a/apps/wr/app/store/matDmd/ldrmntCoSttn/excel.tsx +++ b/apps/wr/app/store/matDmd/ldrmntCoSttn/excel.tsx @@ -46,29 +46,17 @@ export const names: nameMap = { } -export const excelPbnstSttnParsing = (data: any[]): string[][] => { +export const excelLdrmntParsing = (data: any[]): 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 ?? ""), + item.fctryAddr?.split(" ")[0] || "", + item.fctryAddr?.split(" ")[1] || "", + item.dmdCoNm, + item.fctryAddr, + item.fctryMngNo, + item.telno, ]) }) return excelExportData; diff --git a/apps/wr/app/store/matDmd/ldrmntCoSttn/index.tsx b/apps/wr/app/store/matDmd/ldrmntCoSttn/index.tsx index d1176528a..6a5f18247 100644 --- a/apps/wr/app/store/matDmd/ldrmntCoSttn/index.tsx +++ b/apps/wr/app/store/matDmd/ldrmntCoSttn/index.tsx @@ -2,10 +2,8 @@ import type { CommonTableProps } from "@/app/store/common/type/header"; import { deleteFectch, fetchType, getFetch, postFectch, putFectch } from "@/components/lib/api"; import { ToastFormUi } from "@/components/ui/toast/toastFromUi"; import { z } from "zod"; -import { ldrmntHeadListExport, ldrmntNodeListExport, ldrmntZod } from "./headList"; import { TEST_UUID } from "../../ruseFcltMng/fcltReg/rainReg"; - - +import { ldrmntHeadListExport, ldrmntNodeListExport, ldrmntZod } from "./headList"; // 테이블 헤드 리스트 const ldrmntHeadList = ldrmntHeadListExport; @@ -34,7 +32,7 @@ const excelDownList: ExcelType[] = []; type ldrmntActions = { ldrmntRegData: (data: any[]) => void, ldrmntInit: (con: string) => void - ldrmntSearch: (page: number, sggCd: string, coNm: string) => void; + ldrmntSearch: (page: number, sggCd: string, total: string, coNm: string) => void; ldrmntUpdate: (ldrmntRes: customLdrmnt) => void; ldrmntDelete: () => void; }; @@ -61,6 +59,7 @@ export const ldrmntActionsExport: (set: any) => ldrmntActions = ( fetchType.WR, `/reuseFc/request/temp/1/20/${con}` ); + set((state: ldrmntState) => { return { ...state, @@ -68,6 +67,7 @@ export const ldrmntActionsExport: (set: any) => ldrmntActions = ( ...state.ldrmnt, ldrmntRes: ldrmntRes, isInit: false, + excelList: excelDownList, }, } }); @@ -101,44 +101,57 @@ export const ldrmntActionsExport: (set: any) => ldrmntActions = ( } }, - ldrmntSearch: async (page: number, sggCd: string, coNm: string) => { + ldrmntSearch: async (page: number, total: string, sggCd: string, coNm: string) => { let searchUrl = ""; if (sggCd !== "") { if (coNm !== "") { - searchUrl = `/reuseFc/request/temp/${page}/20/${sggCd}/${coNm}`; + searchUrl = `/reuseFc/request/temp/${page}/${total}/${sggCd}/${coNm}`; } else { - searchUrl = `/reuseFc/request/temp/${page}/20/${sggCd}`; + searchUrl = `/reuseFc/request/temp/${page}/${total}/${sggCd}`; } } - try { const ldrmntRes = await getFetch( fetchType.WR, searchUrl ); - ldrmntRes.forEach((item: customLdrmnt) => { - excelDownList.push({ - ctpvCd: item.ctpvCd, - sggCd: item.sggCd, - dmdCoNm: item.dmdCoNm, - fctryAddr: item.fctryAddr, - fctryMngNo: item.fctryMngNo, - telno: item.telno, + if (Number(total) > 5000) { + let excelDownList: ExcelType[] = []; + ldrmntRes.forEach((item: customLdrmnt) => { + excelDownList.push({ + ctpvCd: item.ctpvCd, + sggCd: item.sggCd, + dmdCoNm: item.dmdCoNm, + fctryAddr: item.fctryAddr, + fctryMngNo: item.fctryMngNo, + telno: item.telno, + }) }) - }) + set((state: ldrmntState) => { + return { + ...state, + ldrmnt: { + ...state.ldrmnt, + excelList: excelDownList, - set((state: ldrmntState) => { - return { - ...state, - ldrmnt: { - ...state.ldrmnt, - ldrmntRes: ldrmntRes, - excelList: excelDownList, - }, - } - }) + }, + }; + }); + return excelDownList; + } else + set((state: ldrmntState) => { + return { + ...state, + ldrmnt: { + ...state.ldrmnt, + ldrmntRes: ldrmntRes, + isInit: false, + }, + }; + }); + return ldrmntRes; } catch (e) { console.log(e) } diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList.tsx index d8057cad6..d7335e129 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList.tsx @@ -134,7 +134,7 @@ export const rainChkgdnHeadListExport: TableUpperProps[] = [ { id: "dtfcltCpct", title: "빗물저류조용량(m³)", mergeCell: true }, { id: "chckYmd", title: "점검일", mergeCell: true }, { id: "utztnUsgCd", title: "이용용도" }, - { id: "totalSplyUswtrUtztnQnt", title: "연간보급수이용량(m³)" }, + { id: "totalSplyUswtrUtztnQnt", title: "연간대체수이용량(m³)" }, { id: "totalRuseFcltUtztnQnt", title: "연간빗물이용량(m³)" }, { id: "ph", diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/index.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/index.tsx index 3bd10169f..4af27a51c 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/index.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/index.tsx @@ -52,7 +52,7 @@ export type rainChkgdnState = { }; type rainChkgdnActions = { - rainChkInit: (sggCd: string, searchTxt: string) => void; + rainChkInit: (pageNo: string, cnt: string, sggCd: string, searchTxt: string) => void; rainChkDetilUpdate(rainChk: rainChkZodType[]): Promise<void>; rainChkGuidUpdate(rainChk: rainChkZodType): Promise<void>; rainChkExcelDownload( @@ -98,11 +98,11 @@ const makeMdfrChk = (data: rainChkZodType[], type: string, toDo: string) => { return chData; }; -const initFetch = async (txt: string | undefined, sgg: string | undefined) => { +const initFetch = async (pageNo: string, cnt: string, txt: string | undefined, sgg: string | undefined) => { const currentYear = new Date().getFullYear().toString(); const rainRes = await getFetch( "wr", - `/reuseChk/rainy` + (txt ? `/${txt}` : currentYear) + (sgg ? `/${sgg}` : "/0") + `/reuseChk/rainy` + (pageNo ? `/${pageNo}` : "/1") + (cnt ? `/${cnt}` : "/20") + (txt ? `/${txt}` : `/${currentYear}`) + (sgg ? `/${sgg}` : "/0") ); return rainRes; }; @@ -140,7 +140,7 @@ export const rainChkgdnActionsExport: (set: any) => rainChkgdnActions = ( }); } const currentYear = new Date().getFullYear().toString(); - const rainChkRes = await initFetch(currentYear, "0"); + const rainChkRes = await initFetch("1", "20", currentYear, "0"); return set((state: rainChkgdnState) => ({ ...state, rainChkgdn: { @@ -156,9 +156,9 @@ export const rainChkgdnActionsExport: (set: any) => rainChkgdnActions = ( }); } }, - rainChkInit: async (searchTxt: string, sggCd: string) => { + rainChkInit: async (pageNo: string, cnt: string, searchTxt: string, sggCd: string) => { try { - const rainChkRes = await initFetch(searchTxt, sggCd); + const rainChkRes = await initFetch(pageNo, cnt, searchTxt, sggCd); return set((state: rainChkgdnState) => ({ ...state, diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx index 1cc7bd766..acc6ccaba 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx @@ -3,7 +3,7 @@ import { } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; import type { SelectDataType, - SheetParameter, + SheetParameter, } from "@common/business_components"; import { deleteFectch, @@ -41,12 +41,12 @@ export type rswtChkgdnState = { rswtChkRes: rswtChkZodType[]; rswtChkPolGuide: rswtChkZodType[]; isInit: boolean; - isSttn:boolean + isSttn: boolean }; }; type rswtChkgdnActions = { - rswtChkgdnInit: (year: string, sggCd: string) => void; + rswtChkgdnInit: (pageNo: string, cnt: string, year: string, sggCd: string) => void; rswtrChkExcelDownload( sidoCode: SelectDataType[], sggCode: SelectDataType[] @@ -57,7 +57,7 @@ type rswtChkgdnActions = { rswtChkPolAndGuide: (chkGdnPk: number) => void; rswtChkPolInit: () => void; rwstPolAndGuideUpdate: (rswtChk: rswtChkZodType[]) => void; - rswtChkHeadChange: (rswtChkgdn: rswtChkgdnState, isEdit: boolean) => void; + rswtChkHeadChange: (rswtChkgdn: rswtChkgdnState, isEdit: boolean) => void; }; export type rswtChkgdnType = rswtChkgdnState & rswtChkgdnActions; @@ -75,17 +75,19 @@ export const rswtChkgdnStateExport: rswtChkgdnState = { rswtChkRes: [], rswtChkPolGuide: [], isInit: false, - isSttn:false, + isSttn: false, }, }; -const initFetch = async (year: string | undefined, sgg: string | undefined) => { +const initFetch = async (pageNo: string, cnt: string, year: string | undefined, sgg: string | undefined) => { const currentYear = new Date().getFullYear().toString(); const rswtChkRes = await getFetch( "wr", - `/reuseChk/reuseWater/` + - (year ? `${year}` : currentYear) + - (sgg ? `/${sgg}` : "/0") + `/reuseChk/reuseWater` + + (pageNo ? `/${pageNo}` : "/1") + + (cnt ? `/${cnt}` : "/20") + + (year ? `/${year}` : `/${currentYear}`) + + (sgg ? `/${sgg}` : "/0") ); return rswtChkRes; }; @@ -137,9 +139,9 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( }, })); }, - rswtChkgdnInit: async (year: string, sggCd: string) => { + rswtChkgdnInit: async (pageNo: string, cnt: string, year: string, sggCd: string) => { try { - const rswtChkRes = await initFetch(year, sggCd); + const rswtChkRes = await initFetch(pageNo, cnt, year, sggCd); return set((state: rswtChkgdnState) => ({ ...state, @@ -163,7 +165,7 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( }); } const currentYear = new Date().getFullYear().toString(); - const rswtChkRes = await initFetch(currentYear, "0"); + const rswtChkRes = await initFetch("1", "20", currentYear, "0"); return set((state: rswtChkgdnState) => ({ ...state, rswtChkgdn: { @@ -188,7 +190,7 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( }); } const currentYear = new Date().getFullYear().toString(); - const rswtChkRes = await initFetch(currentYear, "0"); + const rswtChkRes = await initFetch("1", "20", currentYear, "0"); return set((state: rswtChkgdnState) => ({ ...state, rswtChkgdn: { @@ -212,7 +214,7 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( }); } const currentYear = new Date().getFullYear().toString(); - const rswtChkRes = await initFetch(currentYear, "0"); + const rswtChkRes = await initFetch("1", "20", currentYear, "0"); return set((state: rswtChkgdnState) => ({ ...state, rswtChkgdn: { @@ -238,7 +240,7 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( rswtChkPolGuide: rswtChkPolGuide, }, })); - } catch (error) {} + } catch (error) { } }, rswtChkPolInit: () => { try { @@ -249,7 +251,7 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( rswtChkPolGuide: undefined, }, })); - } catch (error) {} + } catch (error) { } }, rwstPolAndGuideUpdate: async (rswtChk: rswtChkZodType[]) => { try { @@ -265,7 +267,7 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( }); } const currentYear = new Date().getFullYear().toString(); - const rswtChkRes = await initFetch(currentYear, "0"); + const rswtChkRes = await initFetch("1", "20", currentYear, "0"); return set((state: rswtChkgdnState) => ({ ...state, rswtChkgdn: { @@ -299,7 +301,7 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( rswtDetailHeadList: rswtDetailHeadList, }, })); - } catch (e) {} + } catch (e) { } }, }; }; diff --git a/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/headList.tsx b/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/headList.tsx index 544f07516..abde1778f 100644 --- a/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/headList.tsx +++ b/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/headList.tsx @@ -4,7 +4,7 @@ import type { TableUpperProps } from "../../common/type/header"; export const pbnPrfRegExcelZod = z.object({ ym: z.string().default(""), bscFctNm: z.string().default(""), - pbnstRuseFcltFk:z.string(), + pbnstRuseFcltFk: z.string(), pbnstRuseFcltNm: z.string().default(""), file: z.instanceof(File).optional(), }); @@ -18,20 +18,21 @@ export const pbnstPrfmncHeadListExport: TableUpperProps[] = [ ]; export const monthHeadListExport: TableUpperProps[] = [ + { id: "구분", title: "구분", mergeCell: true }, { id: "용수명", title: "용수명" }, { id: "total", title: "계" }, - { id: "1월", title: "1월" ,edit: true }, - { id: "2월", title: "2월" ,edit: true }, - { id: "3월", title: "3월" ,edit: true }, - { id: "4월", title: "4월" ,edit: true }, - { id: "5월", title: "5월" ,edit: true }, - { id: "6월", title: "6월" ,edit: true }, - { id: "7월", title: "7월" ,edit: true }, - { id: "8월", title: "8월" ,edit: true }, - { id: "9월", title: "9월" ,edit: true }, - { id: "10월", title: "10월" ,edit: true }, - { id: "11월", title: "11월" ,edit: true }, - { id: "12월", title: "12월" ,edit: true }, + { id: "1월", title: "1월", edit: true }, + { id: "2월", title: "2월", edit: true }, + { id: "3월", title: "3월", edit: true }, + { id: "4월", title: "4월", edit: true }, + { id: "5월", title: "5월", edit: true }, + { id: "6월", title: "6월", edit: true }, + { id: "7월", title: "7월", edit: true }, + { id: "8월", title: "8월", edit: true }, + { id: "9월", title: "9월", edit: true }, + { id: "10월", title: "10월", edit: true }, + { id: "11월", title: "11월", edit: true }, + { id: "12월", title: "12월", edit: true }, ]; diff --git a/apps/wr/components/lib/func/TableData.tsx b/apps/wr/components/lib/func/TableData.tsx index dbb3aa7a6..9ac26827d 100644 --- a/apps/wr/components/lib/func/TableData.tsx +++ b/apps/wr/components/lib/func/TableData.tsx @@ -54,7 +54,7 @@ export const rainChkTableData = (chkData: rainChkZodType[]) => { export const rswtChkTableData = (chkData: rswtChkZodType[]) => { // 1. chkgdnFk + utztnUsgCd 기준 누계 정보 만들기 const makeRswtData: any[] = []; - chkData.forEach((root) => { + chkData?.forEach((root) => { root.rswtrDetails.forEach((detail) => { let flatRswtChk = {}; const findGuide = root.guideDetails.find( @@ -174,8 +174,6 @@ export const chRowspanTable = ( ); }; - - export const headVisibleChange = (headList: TableUpperProps[], isEdit: boolean) => { return headList.forEach((head) => { if (head.edit !== undefined) @@ -187,63 +185,124 @@ export const headVisibleChange = (headList: TableUpperProps[], isEdit: boolean) }); } - const handleChange = ( rowIndex: number, field: any, value: string | number, - setInputData: (data:any) => void + setInputData: (data: any) => void ) => { - if(Number(value) < 0 ||! Number(value)){ - value = 0; - }else{ + if (Number(value) < 0 || !Number(value)) { + value = 0; + } else { value = Number(value); } - setInputData((prevData:any) => - prevData?.map((row:any, index: number) => { - if (index === rowIndex) { - return { - ...row, - [field ]: value, - }; - } - return row; - }) - ); + setInputData((prevData: any) => + prevData?.map((row: any, index: number) => { + if (index === rowIndex) { + return { + ...row, + [field]: value, + }; + } + return row; + }) + ); }; ; +export const InputTableBody = ( + chgRes: any[] | [], + headList: TableUpperProps[], + setInputData: (data: any[]) => void +) => { + // mergeCell 처리용 + const getRowSpan = (data: any[], key: string, rowIndex: number) => { + if (rowIndex > 0 && data[rowIndex][key] === data[rowIndex - 1][key]) { + return 0; // 이전과 같으면 셀을 그리지 않음 + } + let span = 1; + for (let i = rowIndex + 1; i < data.length; i++) { + if (data[i][key] === data[rowIndex][key]) span++; + else break; + } + return span; + }; - -export const InputTableBody = (chgRes: any[] | [] ,headList:TableUpperProps[], setInputData: (data: any[]) => void) => { - return ( + return ( <> - {chgRes.map((data, dataIndex) => ( - <TableRow key={dataIndex + "row"}> - {headList.map((head, headIndex) => ( - <TableCell key={head.id + headIndex} className="w-36" > - {head.edit ===true && data.edit !==false? - <Input - className={`${head.id !== "utztnUsgCd" ? "text-right" : "text-center"} !text-xl h-10 w-full`} - onChange={(e) => - handleChange(dataIndex, head.id, e.target.value,setInputData) - } - value={data[head.id as keyof any] ?? ""} - /> : ( - <div - className={` - ${head.id !== "utztnUsgCd" ? "text-right" : "text-center"} - !text-xl w-full h-10 - `} + {chgRes.map((data, dataIndex) => { + // 소계/총합계 행 판별 + const isSubTotal = data["용수명"] === "소계"; + const isTotal = data["구분"] === 0; + + const bgClass = isTotal + ? "bg-[#d5e1eb] font-bold" + : isSubTotal + ? "bg-[#e6eef5] font-semibold" + : ""; + + return ( + <TableRow key={dataIndex + "row"}> + {headList.map((head, headIndex) => { + // 총합계 행이면 "구분"에서 colSpan=2로 병합 + if ( + data["구분"] === 0 && + head.id === "구분" + ) { + return ( + <TableCell + key={head.id + headIndex} + colSpan={2} + className="font-bold text-center bg-[#d5e1eb]" > - {data[head.id as keyof any] ?? ""} - </div> - ) + 총합계 + </TableCell> + ); + } + // 총합계(합계) 행이면 "용수명" 컬럼은 렌더링하지 않음 + if ( + data["구분"] === 0 && + head.id === "용수명" + ) { + return null; + } + // mergeCell 처리 + if (head.mergeCell) { + const rowSpan = getRowSpan(chgRes, head.id, dataIndex); + if (rowSpan === 0) return null; + return ( + <TableCell key={head.id + headIndex} rowSpan={rowSpan} className={`w-36 align-middle text-center whitespace-nowrap ${bgClass}`}> + {data[head.id as keyof any] ?? ""} + </TableCell> + ); } - </TableCell> - ))} - </TableRow> - ))} + // 기본 셀 + return head.edit === true && data.edit !== false ? ( + <TableCell key={head.id + headIndex} className={`w-36 ${bgClass}`}> + <Input + className={`${head.id !== "utztnUsgCd" ? "text-right" : "text-center"} !text-xl h-10 w-full`} + onChange={(e) => + handleChange(dataIndex, head.id, e.target.value, setInputData) + } + value={data[head.id as keyof any] ?? ""} + /> + </TableCell> + ) : ( + <TableCell key={head.id + headIndex} className={`w-36 ${bgClass}`}> + <div + className={` + ${head.id !== "utztnUsgCd" ? "text-right" : "text-center"} + !text-xl w-full h-10 + `} + > + {data[head.id as keyof any] ?? ""} + </div> + </TableCell> + ); + })} + </TableRow> + ); + })} </> ); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/apps/wr/components/ui/modal/chkgd/pbnPrfExcelModal/pbnPrfExcelModal.tsx b/apps/wr/components/ui/modal/chkgd/pbnPrfExcelModal/pbnPrfExcelModal.tsx index 28dc882c3..fb6ba9b22 100644 --- a/apps/wr/components/ui/modal/chkgd/pbnPrfExcelModal/pbnPrfExcelModal.tsx +++ b/apps/wr/components/ui/modal/chkgd/pbnPrfExcelModal/pbnPrfExcelModal.tsx @@ -52,6 +52,7 @@ export default function PbnPrfExcelModal({ data }: { data: FcPrfType }) { useEffect(() => { if (files) { + console.log("이런") const file = files[0]; if (form.getValues("ym").length < 1) { ToastFormUi({ @@ -62,11 +63,12 @@ export default function PbnPrfExcelModal({ data }: { data: FcPrfType }) { return; } - if (file) + if (file) { filePicked(file, 0).then((res) => { if (res !== undefined) - setExcelData(excelToPbnPrfData(res as unknown as JSON[], form.getValues("ym"),form.getValues('pbnstRuseFcltFk') ) as PrfType[]) + setExcelData(excelToPbnPrfData(res as unknown as JSON[], form.getValues("ym"), form.getValues('pbnstRuseFcltFk')) as PrfType[]) }); + } } }, [files]); @@ -75,82 +77,85 @@ export default function PbnPrfExcelModal({ data }: { data: FcPrfType }) { <div> <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)}> - <div> + <div className="flex justify-between items-center"> <Title title={"하수재이용 실적등록"}> -
-
-

- {"점검정보 입력"} -

- -
+

+ {"점검정보 입력"} +

-
+
( field.onChange(val)} /> )} /> - - ( - - - - )} - /> - ( - - - - )} - /> - - ( - - { - if (files) setFiles(files); - }} - fileTypes="xlsx" - maxFiles={1} - > - - )} - /> +
+
+ ( + + + + )} + /> +
+
+ ( + + + + )} + /> +
+
+
+ ( + + { + if (files) setFiles(files); + }} + fileTypes="xlsx" + maxFiles={1} + > + + )} + /> +
diff --git a/apps/wr/components/ui/modal/coMat/drgtDetailModal.tsx b/apps/wr/components/ui/modal/coMat/drgtDetailModal.tsx index 1be95fae6..880c789c3 100644 --- a/apps/wr/components/ui/modal/coMat/drgtDetailModal.tsx +++ b/apps/wr/components/ui/modal/coMat/drgtDetailModal.tsx @@ -3,25 +3,38 @@ import { btnClass } from "@/app/store/common/code" import type { customDrgt } from "@/app/store/matDmd/drgtSttn" import { drgtDetailHeadListExport } from "@/app/store/matDmd/drgtSttn/headList" -import { excelDownload } from "@/components/lib/func/commom" -import { Title, UiTable } from "@common/business_components/ui" +import { downloadExcel, Title, UiTable } from "@common/business_components/ui" import ListDiv from "@common/business_components/ui/list_div" -import { Button } from "@common/components/ui" -import { useCallback, useEffect } from "react" +import { Button, ScrollArea } from "@common/components/ui" +import { useEffect, useState } from "react" export default function DrgtDetailModal({ data, }: { data: customDrgt; }) { - const excelClick = useCallback(() => { - excelDownload( - [[]], - "우리동네가뭄", - "sample_drgtSttn.xlsx", - "A1:H1" - ); - }, []); + const [excelData, setExcelData] = useState(); + const excelClick = () => { + downloadExcel({ + data: excelData ?? [], + fileName: "우리동네가뭄 상세현황", + sheetNameArray: ["Sheet1"], + headerRange: "A1:C1" + }) + } + + useEffect(() => { + const resData = Array.isArray(data) ? data : [data]; + const exData = Array.isArray(resData) + ? resData.map((item: customDrgt) => { + return { + "년": item.year, + "월": item.month, + "가뭄상태": item.drgtState, + } + }) : []; + setExcelData(exData) + }, [data]) return ( <> @@ -43,15 +56,17 @@ export default function DrgtDetailModal({
- { }} - onPageChange={() => { }} - > + + { }} + onPageChange={() => { }} + > +
diff --git a/apps/wr/components/ui/modal/coReg/coRegExcel/coRegExcelModal.tsx b/apps/wr/components/ui/modal/coReg/coRegExcel/coRegExcelModal.tsx index 34d46043f..89f15a3cc 100644 --- a/apps/wr/components/ui/modal/coReg/coRegExcel/coRegExcelModal.tsx +++ b/apps/wr/components/ui/modal/coReg/coRegExcel/coRegExcelModal.tsx @@ -4,9 +4,9 @@ import type { customLdrmnt } from "@/app/store/matDmd/ldrmntCoSttn"; import { names } from "@/app/store/matDmd/ldrmntCoSttn/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 { Button, Progress } from "@common/components/ui"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import type { z } from "zod"; @@ -30,20 +30,20 @@ export default function CoRegExcel() { try { let array: { [key: string]: string | number }[] = [] let isError = false; - if (Array.isArray(res)){ + if (Array.isArray(res)) { res.some((item, index) => { let object: { [key: string]: string | number } = {} Object.keys(names).some((key) => { if (item && names[key] !== undefined) { - if (key === "공장관리번호") { - item[key] = String(item[key]) - } else { - object[names[key] as string] = item[key]; - } + // if (key === "공장관리번호") { + // item[key] = String(item[key]) + // } else { + object[names[key] as string] = item[key]; + // } } return false; }) - if(isError === true) + if (isError === true) return true; array.push(object) @@ -55,8 +55,8 @@ export default function CoRegExcel() { return false; }) } - if(isError === false) - ldrmntRegData(array); + if (isError === false) + ldrmntRegData(array); } catch (e) { console.log(e) alert(e) @@ -67,7 +67,6 @@ export default function CoRegExcel() { }; } - return ( <>
@@ -87,7 +86,6 @@ export default function CoRegExcel() {
- state); const [fcltTab, setFcltTab] = useState("fclt"); const [ruseTab, setRuseTab] = useState("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 ( <>
- + {titleVisible && ( + <> + <Title title={"하수재이용 시설 상세정보"} /> + </> + )} <ScrollArea className={"h-[70vh]"}> <PbnstMngForm form={form} diff --git a/apps/wr/components/ui/table/chkgdTable/rainChkgd/rainChkgdTable.tsx b/apps/wr/components/ui/table/chkgdTable/rainChkgd/rainChkgdTable.tsx index d0ad437b3..5166b461f 100644 --- a/apps/wr/components/ui/table/chkgdTable/rainChkgd/rainChkgdTable.tsx +++ b/apps/wr/components/ui/table/chkgdTable/rainChkgd/rainChkgdTable.tsx @@ -16,22 +16,27 @@ import { TableHeader, } from "@common/components/ui"; import { useCallback, useEffect, useState } from "react"; +import UiPageNations from "@common/business_components/ui/table/uiPageNations"; export default function RainChkgdTable({ - detailUp, - handleClick=() => {}, + detailUp, + handleClick = () => { }, deleteData, rainChkData, + pageNation, + onPageChange, }: { detailUp: (data: rainChkZodType[]) => void; - handleClick?: (data:any) => void; - deleteData:(selectedIds:number[])=>void + handleClick?: (data: any) => void; + deleteData: (selectedIds: number[]) => void rainChkData?: rainChkZodType[]; + pageNation: number; + onPageChange: (page: number) => void; }) { - const { code, rainChkgdn ,rainChkSttn,getRainSttnExcel } = useWrStore((state) => state); + const { code, rainChkgdn, rainChkSttn, getRainSttnExcel } = useWrStore((state) => state); const [makeTableData, setMakeTableData] = useState<any[]>([]); const [selectedIds, setSelectedIds] = useState<number[]>([]); const [upRaintDetail, setUpRaintDetail] = useState<rainChkZodType[]>([]); - const [headList, setHeadList] = useState< {headList:TableUpperProps[],upHeadList:chHeadType[]}>({headList:[], upHeadList:[]}); + const [headList, setHeadList] = useState<{ headList: TableUpperProps[], upHeadList: chHeadType[] }>({ headList: [], upHeadList: [] }); const toggleSelection = (pk: number, checked: boolean) => { @@ -46,29 +51,29 @@ export default function RainChkgdTable({ ) => { setMakeTableData((prev) => - prev.map((row, idx) => { - return idx === rowIndex ? { ...row, [field]: value } : row + prev.map((row, idx) => { + return idx === rowIndex ? { ...row, [field]: value } : row } ) ); }; - const handleCellClick= useCallback((index: number, headId: string) => { + const handleCellClick = useCallback((index: number, headId: string) => { if ( headId === "totalSplyUswtrUtztnQnt" || headId === "totalRuseFcltUtztnQnt" ) { - + handleClick(makeTableData[index]) } - },[makeTableData, handleClick]); + }, [makeTableData, handleClick]); useEffect(() => { setSelectedIds([])// 셀렉트 초기화를 위한 작업 - if(rainChkData){ + if (rainChkData) { const rainChg = rainChkData.map((item: any) => { const bud = tableData([item.building], { code }); return { @@ -83,10 +88,10 @@ export default function RainChkgdTable({ useEffect(() => { //데이터를 평탄화 한것중에 체크박스 선택한 테이블만 선택 - + //현황 페이지에서 필요한 엑셀 데이터를 바뀔때마다 넣기 위해서 getRainSttnExcel(makeTableData); - const selectedData = makeTableData.filter((item) =>selectedIds.includes(item.chkgdnPk)); + const selectedData = makeTableData.filter((item) => selectedIds.includes(item.chkgdnPk)); if (selectedData.length > 0) { const updateTableData = rainChkgdn.rainChk @@ -101,7 +106,7 @@ export default function RainChkgdTable({ ); if (findRain != undefined) { - console.log(findRain) + console.log(findRain) return { ...rd, ph: Number(findRain.ph), @@ -127,34 +132,34 @@ export default function RainChkgdTable({ } }) .filter(Boolean); - + setUpRaintDetail(updateTableData as rainChkZodType[]); } - }, [makeTableData,selectedIds]); + }, [makeTableData, selectedIds]); useEffect(() => { detailUp(upRaintDetail); }, [upRaintDetail]); - useEffect(() =>{ - if(selectedIds.length >0){ + useEffect(() => { + if (selectedIds.length > 0) { // 삭제를 위한 데이터 삽입 - deleteData(selectedIds ) + deleteData(selectedIds) } - },[selectedIds]) + }, [selectedIds]) ///현황일 경우 input 비활성화 한다. - useEffect(() => { - if(rainChkSttn.isSttn){ - + useEffect(() => { + if (rainChkSttn.isSttn) { + setHeadList({ headList: rainChkSttn.rainChkgdnHeadList, upHeadList: rainChkSttn.rainHeadNm }) - }else{ + } else { setHeadList({ headList: rainChkgdn.rainChkgdnHeadList, upHeadList: rainChkgdn.rainHeadNm @@ -162,28 +167,36 @@ export default function RainChkgdTable({ } - }, [rainChkSttn.isSttn, rainChkgdn.rainChkgdnHeadList , rainChkSttn.rainChkgdnHeadList]); + }, [rainChkSttn.isSttn, rainChkgdn.rainChkgdnHeadList, rainChkSttn.rainChkgdnHeadList]); return ( - <Table variant="secondary"> - <TableHeader className="font-bold"> - {headMakeprfmnc( - headList?.headList, - headList?.upHeadList - )} - </TableHeader> + <> + <Table variant="secondary"> + <TableHeader className="font-bold"> + {headMakeprfmnc( + headList?.headList, + headList?.upHeadList + )} + </TableHeader> - <TableBody> - {chRowspanTable( - makeTableData, - headList.headList, - handleChange, - toggleSelection, - selectedIds, - handleCellClick - )} - </TableBody> - </Table> + <TableBody> + {chRowspanTable( + makeTableData, + headList.headList, + handleChange, + toggleSelection, + selectedIds, + handleCellClick + )} + </TableBody> + </Table> + <UiPageNations + total={rainChkData?.length ?? 0} + cur={pageNation} + pageSize={20} + onPageChange={onPageChange} + /> + </> ); } diff --git a/apps/wr/components/ui/table/chkgdTable/rswtChkgd/rswtChkgdTable.tsx b/apps/wr/components/ui/table/chkgdTable/rswtChkgd/rswtChkgdTable.tsx index da6cacc9a..31891a595 100644 --- a/apps/wr/components/ui/table/chkgdTable/rswtChkgd/rswtChkgdTable.tsx +++ b/apps/wr/components/ui/table/chkgdTable/rswtChkgd/rswtChkgdTable.tsx @@ -14,22 +14,27 @@ import { } from "@/components/lib/func/TableData"; import { Table, TableBody, TableHeader } from "@common/components/ui"; import { useCallback, useEffect, useState } from "react"; +import UiPageNations from "@common/business_components/ui/table/uiPageNations"; export default function RswtChkgdTable({ detaiRswtlUpdate, - handleRswtClick = () => {}, + handleRswtClick = () => { }, deleteRswtData, rswtChkData, + pageNation, + onPageChange, }: { detaiRswtlUpdate: (data: rswtrChkDetailsZodType[]) => void; handleRswtClick?: (data: any) => void; deleteRswtData: (selectedIds: number[]) => void; rswtChkData: rswtChkZodType[]; + pageNation: number; + onPageChange: (page: number) => void; }) { - const { code, rswtChkgdn,rswtChkSttn , rswtChkgdnInit, rswtChkPolAndGuide,getRswtSttnExcel } = useWrStore( + const { code, rswtChkgdn, rswtChkSttn, rswtChkgdnInit, rswtChkPolAndGuide, getRswtSttnExcel } = useWrStore( (state) => state ); - const [headList, setHeadList] = useState< {headList:TableUpperProps[],upHeadList:chHeadType[]}>({headList:[], upHeadList:[]}); + const [headList, setHeadList] = useState<{ headList: TableUpperProps[], upHeadList: chHeadType[] }>({ headList: [], upHeadList: [] }); const [makeTableData, setMakeTableData] = useState<any[]>([]); const [selectedIds, setSelectedIds] = useState<number[]>([]); @@ -111,22 +116,20 @@ export default function RswtChkgdTable({ }, [selectedIds]); ///현황일 경우 input 비활성화 한다. - useEffect(() => { - if(rswtChkSttn.isSttn){ + useEffect(() => { + if (rswtChkSttn.isSttn) { setHeadList({ headList: rswtChkSttn.rswtChkSttnHeadList, upHeadList: rswtChkSttn.rswtSttnHeadNm }) - }else{ + } else { setHeadList({ headList: rswtChkgdn.rswtChkHeadList, upHeadList: rswtChkgdn.rswtHeadNm }) } - - }, [rswtChkSttn.isSttn, rswtChkgdn.rswtChkHeadList , rswtChkSttn.rswtChkSttnHeadList]); - + }, [rswtChkSttn.isSttn, rswtChkgdn.rswtChkHeadList, rswtChkSttn.rswtChkSttnHeadList]); return ( <> @@ -146,6 +149,12 @@ export default function RswtChkgdTable({ )} </TableBody> </Table> + <UiPageNations + total={rswtChkData?.length ?? 0} + cur={pageNation} + pageSize={20} + onPageChange={onPageChange} + /> </> ); } diff --git a/apps/wr/middleware.ts b/apps/wr/middleware.ts index 330252074..5acc41863 100644 --- a/apps/wr/middleware.ts +++ b/apps/wr/middleware.ts @@ -5,6 +5,9 @@ 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 || ""; +const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ""; + export function middleware(req: NextRequest) { const pathname = req.nextUrl.pathname; console.log(`[Middleware] Request URL: ${req.url}`); @@ -44,9 +47,26 @@ 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)); + } + + if (pathname.startsWith("/sewerage-service")) { + const queryParams = searchParams.toString(); + const newUrl = `${SEWERAGE_API_URL}${pathname.replace("/sewerage-service/api", "")}${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*", "/sewerage-service/: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_pbnstPrfmncReg.xlsx b/apps/wr/public/sample_pbnstPrfmncReg.xlsx index 67f0d559e..1f2ff81aa 100644 Binary files a/apps/wr/public/sample_pbnstPrfmncReg.xlsx and b/apps/wr/public/sample_pbnstPrfmncReg.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..6d46e77aa 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -18,7 +18,7 @@ import GisApp from './lib/gis.core'; 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 }) { const mapRef = useRef(); const [mapObject, setMapObject] = useState({}); const [tabVisible, setTabVisible] = useState(true); @@ -121,7 +121,7 @@ function Gis({ isOpen , gisMaps, isSearchModalOpen, treeData = [], isSearch = fa projection: GisApp.TO_PROJ, zoom: gisCore.defaults.zoom, center: olProj.transform( - [126.6757, 37.5585], + [127.4875, 37.4916], GisApp.FROM_PROJ, GisApp.TO_PROJ ), @@ -133,7 +133,7 @@ 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({ @@ -199,9 +199,9 @@ function Gis({ isOpen , gisMaps, isSearchModalOpen, treeData = [], isSearch = fa <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} /> - + {/* <GisContent onFeatureSelect={handleFeatureSelect} isSearch={isSearch} isSearchModalOpen={isSearchModalOpen} setIsSearchModalOpen={setIsSearchModalOpen} /> */} - + {/* isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} {isOpen && featureData && !isSearchModalOpen && ( <DetailView @@ -214,9 +214,9 @@ function Gis({ isOpen , gisMaps, isSearchModalOpen, treeData = [], isSearch = fa onOpenChange={setIsSearchModalOpen} onFeatureSelect={onFeatureSelect} /> - <GisLonLat/> - <GisToolBar/> - <GisMapToggle/> + <GisLonLat /> + <GisToolBar /> + <GisMapToggle /> <GisSelect onFeatureSelect={onFeatureSelect} /> </div> 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..ddfcec3a1 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 @@ -94,7 +94,7 @@ GisApp.core.prototype = { target: '', centerX: 37.2806094, centerY: 127.0032502, - zoom: 12, + zoom: 15, minZoom: 6, maxZoom: 20, }, diff --git a/packages/common/business_components/src/ui/map/lib/gis.layer.js b/packages/common/business_components/src/ui/map/lib/gis.layer.js index d9657c313..097c90ba5 100644 --- a/packages/common/business_components/src/ui/map/lib/gis.layer.js +++ b/packages/common/business_components/src/ui/map/lib/gis.layer.js @@ -77,7 +77,15 @@ GisOlLayer.prototype = { let lyrTyNm = options.lyrTyNm; let typename = this.storageNm + lyrTyNm; - if(options.lyrTyNm == "om_dt_untrt_swg_rain"){ //중점우수토실실 + if(options.lyrTyNm == "om_dt_untrt_swg_rain" //중점우수토실 + || options.lyrTyNm == "om_wqlt_station" //관측소 + || options.lyrTyNm == "tb_wr_bdst_ruse_fclt" //재이용시설 + || options.lyrTyNm == "tb_wr_rnwtr_utztn_fclt" //빗물이용시설 + || options.lyrTyNm == "tb_wr_rswtr_fclt" //중수도시설 + || options.lyrTyNm == "tb_wr_htdfl_fclt" //온배수시설 + || options.lyrTyNm == "tb_wr_tmpr_dmd_co" //잠재수요처 + ) + { url = GisApp.SN_GEOSERVER_URL; typename = lyrTyNm; }