From 958f6eea021219f055b985fdcd78460be695f7d7 Mon Sep 17 00:00:00 2001 From: swcho Date: Sat, 9 Aug 2025 22:26:51 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20WR(=EB=AC=BC=EC=9E=AC=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?)=20=EA=B0=80=EB=AD=84=EC=A7=80=EB=8F=84=20=ED=8C=9D=EC=97=85?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20(=EC=9E=A0=EC=9E=AC=EC=88=98=EC=9A=94?= =?UTF-8?q?=EC=B2=98,=20=EC=88=98=EC=9A=94=EC=B2=98=20=ED=81=B4=EB=A6=AD?= =?UTF-8?q?=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84=20=ED=95=84?= =?UTF-8?q?=EC=9A=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapDrgtPopUp/drgtLegend.tsx | 70 +++ .../app/(popUp-layout)/mapDrgtPopUp/median.ts | 25 + .../app/(popUp-layout)/mapDrgtPopUp/page.tsx | 91 ++++ .../(popUp-layout)/mapDrgtPopUp/searchBar.tsx | 169 ++++++ .../wr/app/(with-layout)/map/mainMap/page.tsx | 2 +- apps/wr/app/(with-layout)/map/service/map.ts | 4 +- .../matMng/coMng/drgtSttn/page.tsx | 499 +++++++++--------- apps/wr/middleware.ts | 7 +- 8 files changed, 620 insertions(+), 247 deletions(-) create mode 100644 apps/wr/app/(popUp-layout)/mapDrgtPopUp/drgtLegend.tsx create mode 100644 apps/wr/app/(popUp-layout)/mapDrgtPopUp/median.ts create mode 100644 apps/wr/app/(popUp-layout)/mapDrgtPopUp/page.tsx create mode 100644 apps/wr/app/(popUp-layout)/mapDrgtPopUp/searchBar.tsx diff --git a/apps/wr/app/(popUp-layout)/mapDrgtPopUp/drgtLegend.tsx b/apps/wr/app/(popUp-layout)/mapDrgtPopUp/drgtLegend.tsx new file mode 100644 index 000000000..3bf97d677 --- /dev/null +++ b/apps/wr/app/(popUp-layout)/mapDrgtPopUp/drgtLegend.tsx @@ -0,0 +1,70 @@ +"use client"; + +import React, { useEffect} from "react"; +import { Label } from "@common/components/ui/label"; + +export const DRGT_LAYER_TYPES: string[] = [ + // 수정 필요 + 'drought1', + 'drought2', + 'drought3', + 'drought4', + 'drought5', + 'drought6', +]; +// 지정된 순서에 따른 lyr_sn 배열 +export const EXPRS_ORDERED_LAYERS: string[] = [ + // 수정 필요 + '106', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 30년 빈도 + '107', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 50년 빈도 + '108', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 80년 빈도 + '109', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 100년 빈도 + '110', // 도시침수지도(tb_ms_flood_dngr_map) - 30년 빈도 + '111', // 도시침수지도(tb_ms_flood_dngr_map) - 50년 빈도 +]; + +export function DrgtLegend() { + + + return ( + <> +
+
+ +
+
+
+
+
+
+
+
가 뭄 정 보
+
+ + {/* 관심 - 1회 미만 */} + {/* 관심 - 1회 이상 */} + + {/* 주의 - 1회 미만 */} + {/* 주의 - 1회 이상 */} + + {/* 관심+주의 - 1회 미만 */} + {/* 관심+주의 - 1회 이상 */} +
+
+
+
+ + ); +} + +function LegendItem({ color, label }: { color: string; label: string }) { + return ( +
+ + {label} +
+ ); +} \ No newline at end of file diff --git a/apps/wr/app/(popUp-layout)/mapDrgtPopUp/median.ts b/apps/wr/app/(popUp-layout)/mapDrgtPopUp/median.ts new file mode 100644 index 000000000..a19f83248 --- /dev/null +++ b/apps/wr/app/(popUp-layout)/mapDrgtPopUp/median.ts @@ -0,0 +1,25 @@ +import { getCenter } from "ol/extent"; +import { boundingExtent } from "ol/extent"; +import Feature from "ol/Feature"; +import Geometry from "ol/geom/Geometry"; + +export function medianCoordinate2D(feature: Feature) { + // Feature를 입력 받아 중심 좌표를 계산하는 함수 + if (!feature) { + console.warn("유효한 피처가 없습니다."); + return; + } + + console.log(feature) + + const geometry = feature.getGeometry(); + if (!geometry) { + console.warn("피처에 geometry가 없습니다."); + return; + } + + const extent = geometry.getExtent(); + const center = getCenter(extent); + const centerExtent = boundingExtent([center]); + return centerExtent; +} \ No newline at end of file diff --git a/apps/wr/app/(popUp-layout)/mapDrgtPopUp/page.tsx b/apps/wr/app/(popUp-layout)/mapDrgtPopUp/page.tsx new file mode 100644 index 000000000..f0ac6bcdd --- /dev/null +++ b/apps/wr/app/(popUp-layout)/mapDrgtPopUp/page.tsx @@ -0,0 +1,91 @@ +"use client"; + +import Gis from "@common/business_components/ui/map/Gis"; +import { useEffect, useRef, useState, useCallback } from "react"; +import { useSearchParams } from "next/navigation"; +import { mapService } from "../../(with-layout)/map/service"; +import { DrgtLegend, DRGT_LAYER_TYPES, EXPRS_ORDERED_LAYERS } from "./drgtLegend"; +import { SearchBar } from "./searchBar"; + + +export default function MapDrgtPopUp() { + const searchParams = useSearchParams(); + const ctpvCd = searchParams.get('ctpvCd') || ''; + const sggCd = searchParams.get('sggCd') || ''; + const fromDate = searchParams.get('fromDate') || ''; + const toDate = searchParams.get('toDate') || ''; + + const gisMaps = useRef(null); + const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정 + const [checkedLayers, setCheckedLayers] = useState([]); + const [checkedLayersData, setCheckedLayersData] = useState([]); + const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 + const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터 + const [showDrgtLegend, setShowDrgtLegend] = useState(false); // DrgtLegend 표시 여부 + const [isPlaying, setIsPlaying] = useState(false); + + // 컴포넌트 마운트 시 1회 실행 + useEffect(() => { + fetchData(); + }, []); + + function fetchData() { + (async () => { + try { + const response = await mapService.getLyrStngs('WR_DROUGHT'); + setTreeData(response); // API 응답 저장 + console.log("Drought layers fetched:", response); + } catch (error) { + console.error("데이터 불러오기 실패:", error); + } + })(); + } + + // WaterDepth 표시 여부를 결정하는 함수 + const handleDrgtLegndVisibility = useCallback((checkedIds: string[], checkedNodesData: any[]) => { + const drgtLegndLayers = checkedNodesData.filter(node => + DRGT_LAYER_TYPES.includes(node.data?.lyrTyNm) + ); + + const hasDrgtLegndLayer = drgtLegndLayers.length > 0; + setShowDrgtLegend(hasDrgtLegndLayer); + }, []); + + // 체크된 레이어 변경 처리 함수 + const handleCheckedLayersChange = useCallback((checkedIds: string[], checkedNodesData: any[]) => { + handleDrgtLegndVisibility(checkedIds, checkedNodesData); + }, [handleDrgtLegndVisibility]); + + + + return ( +
+ } + waterDepth={showDrgtLegend ? DrgtLegend : undefined} + isPlaying={isPlaying} + setIsPlaying={setIsPlaying as any} + targetLayerTypes={DRGT_LAYER_TYPES as any} + orderedLayers={EXPRS_ORDERED_LAYERS as any} + checkInterval={2500} + height={`h-[100vh]`} + droughtDates={{ startYmd: fromDate, endYmd: toDate }} + /> +
+ ); +} diff --git a/apps/wr/app/(popUp-layout)/mapDrgtPopUp/searchBar.tsx b/apps/wr/app/(popUp-layout)/mapDrgtPopUp/searchBar.tsx new file mode 100644 index 000000000..10de11308 --- /dev/null +++ b/apps/wr/app/(popUp-layout)/mapDrgtPopUp/searchBar.tsx @@ -0,0 +1,169 @@ +"use client"; + +import React, { useState, useMemo, useEffect } from "react"; +import { SelectBox } from '@common/business_components/ui' +import { Label, Button } from '@common/components'; +import { type SelectDataType } from "@common/business_components/ui"; +import GisApp from "@common/business_components/ui/map/lib/gis.core"; +import GeoJSON from "ol/format/GeoJSON"; +import Feature from "ol/Feature"; +import Geometry from "ol/geom/Geometry"; +import { medianCoordinate2D } from "./median"; + +interface CtpvSggPrcsFcltSelectBoxProps { + gisMaps: React.RefObject; + ctpvCode: string; + sggCode: string; + fromDate: string; + toDate: string; +} + +const DEFAULT_OPTION: SelectDataType = { val: '0', text: '전체' }; + +export function SearchBar({ gisMaps, ctpvCode, sggCode }: CtpvSggPrcsFcltSelectBoxProps) { + const [ctpvCd, setCtpvCd] = useState(ctpvCode); + const [sggCd, setSggCd] = useState(sggCode); + const [ctpv, setCtpv] = useState([]); + const [sgg, setSgg] = useState([]); + + useEffect(() => { + (async () => { + await fetchSido(); + await fetchSigungu(); + setCtpvCd(ctpvCode); + setSggCd(sggCode); + })(); + }, []); + + function onViewMapClick() { + if (sggCd && sggCd !== '0') { + fetchSggData(); + return; + } + if (ctpvCd && ctpvCd !== '0') fetchCtpvData(); + } + + async function fetchCtpvData() { + try { + const ctpvData = await fetch(`/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_ctpv&outputFormat=application%2Fjson&cql_filter=mctpv_cd=%27${ctpvCd.trim()}%27`) + .then(res => res.json()); + console.log('시도 데이터', ctpvData.features[0]); + + const format = new GeoJSON(); + const feature: Feature = format.readFeature(ctpvData.features[0], { + dataProjection: "EPSG:3857", // GeoJSON 좌표계 + featureProjection: GisApp.TO_PROJ // 지도 좌표계 + }); + + const extent = medianCoordinate2D(feature); + + handleFeatureFocus(extent); + } + catch (error) { + console.error("시도 data fetching Error:", error); + } + } + + async function fetchSggData() { + try { + const sggData = await fetch(`/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_sgg&outputFormat=application%2Fjson&cql_filter=sgg_cd=%27${sggCd}%27`) + .then(res => res.json()); + console.log('시군구 데이터', sggData.features[0]); + + + const format = new GeoJSON(); + const feature: Feature = format.readFeature(sggData.features[0], { + dataProjection: "EPSG:3857", // GeoJSON 좌표계 + featureProjection: GisApp.TO_PROJ // 지도 좌표계 + }); + + const extent = medianCoordinate2D(feature); + + handleFeatureFocus(extent); + } + catch (error) { + console.error("시군구 data fetching Error:", error); + } + } + + function handleFeatureFocus(extent: any) { + console.log('ctpv', ctpv); + console.log('sgg', sgg); + const map = gisMaps.current; + if (!map) { + console.warn("지도가 초기화되지 않았습니다."); + return; + } + + map.getView().fit(extent, { + duration: 300, + padding: [50, 50, 50, 50], + maxZoom: 11 + }); + }; + + const fetchSido = async () => { + const url = `/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_ctpv&outputFormat=application/json&propertyName=mctpv_korn_nm,mctpv_cd`; + const res = await fetch(url).then(res => res.json()); + + const list = res.features.map((f: any) => ({ + text: f.properties.mctpv_korn_nm, + val: f.properties.mctpv_cd, + })); + + console.log('시도 데이터', list); + setCtpv(list); + }; + + const fetchSigungu = async () => { + const url = `/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_sgg&outputFormat=application/json&propertyName=sgg_cd,sgg_korn_nm`; + const res = await fetch(url).then(res => res.json()); + + const list = res.features.map((f: any) => ({ + text: f.properties.sgg_korn_nm, + val: f.properties.sgg_cd, + })); + + console.log('시군구 데이터', list); + setSgg(list); + }; + + + + return ( +
+
+ + { + setCtpvCd(String(val)); + setSggCd(String(0)); + }} + selectContentClass="z-[101]" + /> + + String(opt.val).slice(0, 2) === String(ctpvCd).trim()) + ] + } + value={sggCd} + onSelectValue={val => { setSggCd(String(val)) }} + selectContentClass="z-[101]" + /> +
+
+ +
+
+ ); +} \ No newline at end of file diff --git a/apps/wr/app/(with-layout)/map/mainMap/page.tsx b/apps/wr/app/(with-layout)/map/mainMap/page.tsx index 664283c80..384c50dcd 100644 --- a/apps/wr/app/(with-layout)/map/mainMap/page.tsx +++ b/apps/wr/app/(with-layout)/map/mainMap/page.tsx @@ -23,7 +23,7 @@ const Map = () => { const fetchData = async () => { try { - const response = await mapService.getLyrStngs(); + const response = await mapService.getLyrStngs('WR_NETWORK'); setTreeData(response); // API 응답 저장 } catch (error) { console.error("데이터 불러오기 실패:", error); diff --git a/apps/wr/app/(with-layout)/map/service/map.ts b/apps/wr/app/(with-layout)/map/service/map.ts index 5002afef7..79ba7fe9e 100644 --- a/apps/wr/app/(with-layout)/map/service/map.ts +++ b/apps/wr/app/(with-layout)/map/service/map.ts @@ -30,8 +30,8 @@ export type LyrStngProps = { export const mapService = { //레이어 설정 조회 - getLyrStngs: async () => { - const response = await fetch(`${API_URL}/lyrStngs/WR_NETWORK`, { + getLyrStngs: async (lyrGrp : string) => { + const response = await fetch(`${API_URL}/lyrStngs/${lyrGrp}`, { method: 'GET', headers: { 'Accept': 'application/json', diff --git a/apps/wr/app/(with-layout)/matMng/coMng/drgtSttn/page.tsx b/apps/wr/app/(with-layout)/matMng/coMng/drgtSttn/page.tsx index be7210f3f..00c78b960 100644 --- a/apps/wr/app/(with-layout)/matMng/coMng/drgtSttn/page.tsx +++ b/apps/wr/app/(with-layout)/matMng/coMng/drgtSttn/page.tsx @@ -9,269 +9,282 @@ import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/b 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, Label, ScrollArea } from "@common/components/ui"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useRef } from "react"; import { toast, Toaster } from "react-hot-toast"; export default function DrgtSttn() { - const { code, drgt, drgtSearch, drgtModalSearch, drgtInit } = useWrStore((state) => state); - const { drgtRes, drgtMoRes } = drgt; - const [fromDate, setFromDate] = useState('202506'); - const [toDate, setToDate] = useState('202506'); - const [selectSgg, setSelectSgg] = useState([{ val: '0', text: '전체' }]); - const [objSearch, setObjSearch] = useState({ sgg: "", city: "" }); - const [tableData, setTableData] = useState(); - const [selectedSgg, setSelectedSgg] = useState(''); - const [excelData, setExcelData] = useState(); - const [drgtModal, setDrgtModal] = useState<{ - isOpen: boolean; - data: customDrgt[] | undefined; - }>({ data: undefined, isOpen: false }); + const { code, drgt, drgtSearch, drgtModalSearch, drgtInit } = useWrStore((state) => state); + const { drgtRes, drgtMoRes } = drgt; + const [fromDate, setFromDate] = useState('202506'); + const [toDate, setToDate] = useState('202506'); + const [selectSgg, setSelectSgg] = useState([{ val: '0', text: '전체' }]); + const [objSearch, setObjSearch] = useState({ sgg: "", city: "" }); + const [tableData, setTableData] = useState(); + const [selectedSgg, setSelectedSgg] = useState(''); + const [excelData, setExcelData] = useState(); + const [drgtModal, setDrgtModal] = useState<{ + isOpen: boolean; + data: customDrgt[] | undefined; + }>({ data: undefined, isOpen: false }); + const popupRef = useRef(null); - const cellClick = (index: number) => { - if (fromDate && toDate && drgtRes[index]) { - drgtModalSearch(fromDate, toDate, drgtRes[index].sggNm ?? ''); - } else { - toast.error("조회기간을 선택해주세요."); - } + const cellClick = (index: number) => { + if (fromDate && toDate && drgtRes[index]) { + drgtModalSearch(fromDate, toDate, drgtRes[index].sggNm ?? ''); + } else { + toast.error("조회기간을 선택해주세요."); } + } - const excelClick = () => { - downloadExcel({ - data: excelData ?? [], - fileName: "우리동네가뭄 현황", - sheetNameArray: ["Sheet1"], - headerRange: "A1:G1" - }) + const excelClick = () => { + downloadExcel({ + data: excelData ?? [], + fileName: "우리동네가뭄 현황", + sheetNameArray: ["Sheet1"], + headerRange: "A1:G1" + }) + } + + useEffect(() => { + if (!drgt.isInit) { + drgtInit(); } + }, [drgt.isInit, drgtInit]); - useEffect(() => { - if (!drgt.isInit) { - drgtInit(); + useEffect(() => { + let resData = Array.isArray(drgtRes) ? drgtRes : [drgtRes]; + resData = resData.map((item) => { + if (item.sggNm === '세종시') { + return { + ...item, + ctpvCd: '세종특별자치시', + sggNm: '' } - }, [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 }); + 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, } - }, [drgtMoRes]); + }) : []; + setExcelData(data) + setTableData(resData) + }, [drgtRes]) - const getSelCity = (val: string | number) => { - const cityValue = String(val); + useEffect(() => { + if (drgtMoRes && drgtMoRes.length > 0) { + setDrgtModal({ isOpen: true, data: drgtMoRes }); + } + }, [drgtMoRes]); - if (cityValue === "0") { - setSelectSgg([{ val: '0', text: '전체' }]); - setSelectedSgg('0'); - setObjSearch({ city: cityValue, sgg: '0' }); - } else { - const data = getCity(cityValue, code.sggCd); - setSelectSgg(data); + const getSelCity = (val: string | number) => { + const cityValue = String(val); - const firstSggVal = data.find(item => item.val === "0")?.val || (data[0]?.val ?? ""); - setSelectedSgg(firstSggVal); - setObjSearch({ city: cityValue, sgg: firstSggVal }); - } - }; + if (cityValue === "0") { + setSelectSgg([{ val: '0', text: '전체' }]); + setSelectedSgg('0'); + setObjSearch({ city: cityValue, sgg: '0' }); + } else { + const data = getCity(cityValue, code.sggCd); + setSelectSgg(data); - const getSgg = (val: string | number) => { - setSelectedSgg(String(val)); - setObjSearch({ ...objSearch, sgg: String(val) }); - }; + const firstSggVal = data.find(item => item.val === "0")?.val || (data[0]?.val ?? ""); + setSelectedSgg(firstSggVal); + setObjSearch({ city: cityValue, sgg: firstSggVal }); + } + }; - useEffect(() => { - if (fromDate === undefined && toDate === undefined) { - const today = new Date(); - const year = today.getFullYear(); - const month = String(today.getMonth() + 1).padStart(2, '0'); - const formatted = `${year}${month}`; - setFromDate(formatted); - setToDate(formatted); - } - }, [fromDate, toDate]) - return ( - <> - -
-
- - <SearchDiv> - <SearchFormLeft> - <div className="col-span-10 flex flex-row"> - <SelectBox - className="col-span-2 w-1/6 h-full text-2xl" - label="시도" - selectClass={""} - selectArray={code.cityCd || []} - onSelectValue={getSelCity} - value={objSearch?.city ||code.cityCd[0]?.val ||""} - /> - <SelectBox - className="col-span-2 w-1/6 h-full text-2xl ml-8" - label="시군구" - selectClass={""} - selectArray={selectSgg || [{ val: '0', text: '전체' }]} - onSelectValue={getSgg} - value={selectedSgg ||'0'} - /> - <div className="col-span-5 w-auto"> - <div className="flex flex-row items-center"> - <Label className="text-xl text-gray-600 ml-8">조회기간</Label> - <div className="flex flex-row w-1/4"> - <DateMonthPicker - field={{ - value: fromDate ?? "", - onChange: (date: string | undefined) => { - const year = date ? date.slice(0, 4) : ''; - let month = date?.slice(4); - if (month&&month?.length < 2) { - month = month?.padStart(2, "0"); - } - const formatted = `${year}${month}`; - setFromDate(formatted); - } - }} - selectClass="ml-4 text-2xl" - /> - <span className="ml-2 text-center">~</span> - <DateMonthPicker - field={{ - value: toDate ?? "", - onChange: (date: string | undefined) => { - const year = date ? date.slice(0, 4) : ''; - let month = date?.slice(4); - if (month&&month.length < 2) { - month = month?.padStart(2, "0"); - } - const formatted = `${year}${month}`; - setToDate(formatted); - } - }} - selectClass="ml-4" - /> - </div> - </div> - </div> - </div> - </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 items-center"> - <Button - size={"sm"} - onClick={() => { - let sidoText: string = ''; - let sggText: string | string[] = ''; + const getSgg = (val: string | number) => { + setSelectedSgg(String(val)); + setObjSearch({ ...objSearch, sgg: String(val) }); + }; - 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); + useEffect(() => { + if (fromDate === undefined && toDate === undefined) { + const today = new Date(); + const year = today.getFullYear(); + const month = String(today.getMonth() + 1).padStart(2, '0'); + const formatted = `${year}${month}`; + setFromDate(formatted); + setToDate(formatted); + } + }, [fromDate, toDate]) - } - if (sidoText === '전체') { sidoText = '' } + function onViewMapClick() { + const params = new URLSearchParams({ + ctpvCd: String(objSearch.city), + sggCd: String(objSearch.sgg), + fromDate: fromDate || '', + toDate: toDate || '' + }); + const popupOptions = `width=${window.screen.availWidth},height=${window.screen.availHeight},left=0,top=0,resizable=yes,scrollbars=yes`; + popupRef.current = window.open(`/mapDrgtPopUp?${params.toString()}`, "가뭄 지도 보기 팝업", popupOptions); + } - if (sidoText === '세종특별자치시') { - sidoText = '세종시'; - sggText = sidoText; - } - - const searchArr:string[] = []; - 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} green`} - onClick={excelClick} - > - 다운로드 - </Button> - <Button - size={"sm"} - className={btnClass} - onClick={() => { }} - > - 지도보기 - </Button> - </div> - </SearchFormRight> - </SearchDiv> + return ( + <> + <ContentsDiv> + <div className="grid grid-cols-4 gap-4 "> + <div className="col-span-4"> + <Title title={"우리동네가뭄"} /> + <SearchDiv> + <SearchFormLeft> + <div className="col-span-10 flex flex-row"> + <SelectBox + className="col-span-2 w-1/6 h-full text-2xl" + label="시도" + selectClass={""} + selectArray={code.cityCd || []} + onSelectValue={getSelCity} + value={objSearch?.city || code.cityCd[0]?.val || ""} + /> + <SelectBox + className="col-span-2 w-1/6 h-full text-2xl ml-8" + label="시군구" + selectClass={""} + selectArray={selectSgg || [{ val: '0', text: '전체' }]} + onSelectValue={getSgg} + value={selectedSgg || '0'} + /> + <div className="col-span-5 w-auto"> + <div className="flex flex-row items-center"> + <Label className="text-xl text-gray-600 ml-8">조회기간</Label> + <div className="flex flex-row w-1/4"> + <DateMonthPicker + field={{ + value: fromDate ?? "", + onChange: (date: string | undefined) => { + const year = date ? date.slice(0, 4) : ''; + let month = date?.slice(4); + if (month && month?.length < 2) { + month = month?.padStart(2, "0"); + } + const formatted = `${year}${month}`; + setFromDate(formatted); + } + }} + selectClass="ml-4 text-2xl" + /> + <span className="ml-2 text-center">~</span> + <DateMonthPicker + field={{ + value: toDate ?? "", + onChange: (date: string | undefined) => { + const year = date ? date.slice(0, 4) : ''; + let month = date?.slice(4); + if (month && month.length < 2) { + month = month?.padStart(2, "0"); + } + const formatted = `${year}${month}`; + setToDate(formatted); + } + }} + selectClass="ml-4" + /> + </div> </div> + </div> </div> - <div className="grid grid-cols-6 gap-4 "> - <div className="col-span-6"> - <ListDiv> - <ScrollArea className="h-[66vh]"> - <UiTable - headName={undefined} - tableData={tableData || []} - headlist={drgtHeadListExport} - pageSize={14} - total={0} - cellClick={(index: number | undefined) => { - cellClick(index ?? -1); - }} - onPageChange={() => { }} - ></UiTable> - </ScrollArea> - </ListDiv> - </div> - </div> - <UiModal - open={drgtModal.isOpen} - onOpenChange={() => { - setDrgtModal({ ...drgtModal, isOpen: !drgtModal.isOpen }); + </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 items-center"> + <Button + size={"sm"} + onClick={() => { + let sidoText: string = ''; + let sggText: string | string[] = ''; + + 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; + } + + const searchArr: string[] = []; + if (sidoText) searchArr.push(sidoText); + if (Array.isArray(sggText)) { + searchArr.push(...sggText); + } else if (sggText) { + searchArr.push(sggText); + } + + drgtSearch(fromDate ?? "", toDate ?? "", searchArr) }} - bodyClass={"w-[50vw] max-w-none h-[80vh]"} - body={<DrgtDetailModal data={drgtModal.data as customDrgt[]} />} - /> - <Toaster position="top-center" /> - </ContentsDiv> - </> - ) + > + 조회 + </Button> + <Button + size={"sm"} + className={`${btnClass} green`} + onClick={excelClick} + > + 다운로드 + </Button> + <Button + size={"sm"} + className={btnClass} + onClick={onViewMapClick} + > + 지도보기 + </Button> + </div> + </SearchFormRight> + </SearchDiv> + </div> + </div> + <div className="grid grid-cols-6 gap-4 "> + <div className="col-span-6"> + <ListDiv> + <ScrollArea className="h-[66vh]"> + <UiTable + headName={undefined} + tableData={tableData || []} + headlist={drgtHeadListExport} + pageSize={14} + total={0} + cellClick={(index: number | undefined) => { + cellClick(index ?? -1); + }} + onPageChange={() => { }} + ></UiTable> + </ScrollArea> + </ListDiv> + </div> + </div> + <UiModal + open={drgtModal.isOpen} + onOpenChange={() => { + setDrgtModal({ ...drgtModal, isOpen: !drgtModal.isOpen }); + }} + bodyClass={"w-[50vw] max-w-none h-[80vh]"} + body={<DrgtDetailModal data={drgtModal.data as customDrgt[]} />} + /> + <Toaster position="top-center" /> + </ContentsDiv> + </> + ) } \ No newline at end of file diff --git a/apps/wr/middleware.ts b/apps/wr/middleware.ts index d068f6aa5..c5c7f8c01 100644 --- a/apps/wr/middleware.ts +++ b/apps/wr/middleware.ts @@ -91,6 +91,8 @@ export function middleware(req: NextRequest) { let queryParams = searchParams.toString(); const typename = searchParams.get("TYPENAME"); console.log("typename ", typename); + const startYmd = searchParams.get("START_YMD") ?? null; + const endYmd = searchParams.get("END_YMD") ?? null; let newUrl = `${GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`; @@ -116,10 +118,13 @@ export function middleware(req: NextRequest) { typename == "hasudo:tb_wr_rswtr_fclt" || //중수도시설 typename == "hasudo:tb_wr_htdfl_fclt" || //온배수시설 typename == "hasudo:tb_wr_ruse_fclt_dmd_co" || //수요처 - typename == "hasudo:tb_wr_tmpr_dmd_co" //잠재수요처 + typename == "hasudo:tb_wr_tmpr_dmd_co" || //잠재수요처 + typename?.startsWith("hasudo:drought") //가뭄 지도 ) { const url = req.nextUrl.clone(); url.searchParams.set("TYPENAME", typename.replace("hasudo:", "")); + if (startYmd) url.searchParams.set("startYmd", startYmd); + if (endYmd) url.searchParams.set("endYmd", endYmd); queryParams = url.searchParams.toString(); newUrl = `${SEWERAGE_API_URL}/v1/map/geometry${queryParams ? `?${queryParams}` : ""}`; }