feat: WR(물재이용) 가뭄지도 팝업 구현 (잠재수요처, 수요처 클릭 이벤트 구현 필요)

This commit is contained in:
swcho
2025-08-09 22:26:51 +09:00
parent 114270d8e9
commit 958f6eea02
8 changed files with 620 additions and 247 deletions
@@ -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 (
<>
<div className="bg-white rounded-lg shadow-lg border border-gray-200 w-[160px] px-4 py-3">
<div className="flex items-center justify-between mx-2 my-4">
<Label className="text-3xl text-gray-900 font-bold">가뭄정보 범례</Label>
</div>
<div className="mb-3">
<div className="w-full bg-gray-200 rounded-full h-1">
<div className="bg-blue-900 h-1 rounded-full transition-all duration-300 ease-in-out" />
</div>
</div>
<div className="w-[140px] mx-auto flex flex-col items-center">
<div className="bg-blue-900 text-white text-2xl text-center py-1 mb-2 rounded w-full">가 뭄 정 보</div>
<div className="space-y-1 w-full">
<Label className="text-xl text-gray-900 font-bold">관심</Label>
<LegendItem color="rgba(222,235,247, 1)" label="1회 미만" /> {/* 관심 - 1회 미만 */}
<LegendItem color="rgba(46, 117, 182, 1)" label="1회 이상" /> {/* 관심 - 1회 이상 */}
<Label className="text-xl text-gray-900 font-bold">주의</Label>
<LegendItem color="rgba(255, 242, 204, 1)" label="1회 미만" /> {/* 주의 - 1회 미만 */}
<LegendItem color="rgba(255, 217, 102, 1)" label="1회 이상" /> {/* 주의 - 1회 이상 */}
<Label className="text-xl text-gray-900 font-bold">관심 + 주의</Label>
<LegendItem color="rgba(251, 229, 214, 1)" label="1회 미만" /> {/* 관심+주의 - 1회 미만 */}
<LegendItem color="rgba(237, 125, 49, 1)" label="1회 이상" /> {/* 관심+주의 - 1회 이상 */}
</div>
<div className="border-t-2 border-blue-900 mt-1 mx-auto w-full" />
</div>
</div>
</>
);
}
function LegendItem({ color, label }: { color: string; label: string }) {
return (
<div className="flex items-center gap-2 w-full">
<span
className="inline-block w-16 h-6"
style={{ backgroundColor: color, border: "1px solid #ccc" }}
/>
<span className="text-xl text-gray-900 font-bold">{label}</span>
</div>
);
}
@@ -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<Geometry>) {
// 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;
}
@@ -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<any | null>(null);
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
const [checkedLayers, setCheckedLayers] = useState<string[]>([]);
const [checkedLayersData, setCheckedLayersData] = useState<any[]>([]);
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 (
<div className="bg-container">
<Gis
treeData={treeData}
isSearch={false}
gisMaps={gisMaps}
isMiniMap={false}
searchView={undefined}
detailView={undefined}
onFeatureSelect={undefined}
setIsOpen={setIsOpen}
setFeatureData={undefined}
featureData={undefined}
setIsSearchModalOpen={undefined}
isOpen={isOpen}
isSearchModalOpen={undefined}
onCheckedLayersChange={handleCheckedLayersChange}
imgVisble={true}
searchBar={<SearchBar gisMaps={gisMaps} ctpvCode={ctpvCd} sggCode={sggCd} fromDate={fromDate} toDate={toDate} />}
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 }}
/>
</div>
);
}
@@ -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<any>;
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<string>(ctpvCode);
const [sggCd, setSggCd] = useState<string>(sggCode);
const [ctpv, setCtpv] = useState<SelectDataType[]>([]);
const [sgg, setSgg] = useState<SelectDataType[]>([]);
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<Geometry> = 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<Geometry> = 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 (
<div className="flex items-center gap-4 bg-white min-h-[48px] min-w-[430px] px-5 py-3 rounded-lg shadow">
<div className={`flex items-center justify-center mr-8`}>
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시도</Label>
<SelectBox
className="w-60 mr-8"
selectClass="rounded-lg bg-white"
label=""
selectArray={[DEFAULT_OPTION, ...ctpv]}
value={ctpvCd}
onSelectValue={val => {
setCtpvCd(String(val));
setSggCd(String(0));
}}
selectContentClass="z-[101]"
/>
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시군구</Label>
<SelectBox
className="w-48"
selectClass="w-48 rounded-lg bg-white truncate"
label=""
selectArray={
[DEFAULT_OPTION,
...sgg.filter(opt => String(opt.val).slice(0, 2) === String(ctpvCd).trim())
]
}
value={sggCd}
onSelectValue={val => { setSggCd(String(val)) }}
selectContentClass="z-[101]"
/>
</div>
<div className='flex flex-1 justify-end '>
<Button size="sm" onClick={() => onViewMapClick()}>조회</Button>
</div>
</div>
);
}
@@ -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);
+2 -2
View File
@@ -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',
@@ -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<string>('202506');
const [toDate, setToDate] = useState<string>('202506');
const [selectSgg, setSelectSgg] = useState<SelectDataType[]>([{ val: '0', text: '전체' }]);
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;
}>({ data: undefined, isOpen: false });
const { code, drgt, drgtSearch, drgtModalSearch, drgtInit } = useWrStore((state) => state);
const { drgtRes, drgtMoRes } = drgt;
const [fromDate, setFromDate] = useState<string>('202506');
const [toDate, setToDate] = useState<string>('202506');
const [selectSgg, setSelectSgg] = useState<SelectDataType[]>([{ val: '0', text: '전체' }]);
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;
}>({ data: undefined, isOpen: false });
const popupRef = useRef<Window | null>(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 (
<>
<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>
</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>
</>
)
}
+6 -1
View File
@@ -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}` : ""}`;
}