Merge remote-tracking branch 'origin/iipbs/wr/dev'
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { getCenter } from "ol/extent";
|
||||
import { boundingExtent } from "ol/extent";
|
||||
import Feature from "ol/Feature";
|
||||
import Geometry from "ol/geom/Geometry";
|
||||
import { boundingExtent, getCenter } from 'ol/extent';
|
||||
import Feature from 'ol/Feature';
|
||||
import Geometry from 'ol/geom/Geometry';
|
||||
|
||||
export function medianCoordinate2D(feature: Feature<Geometry>) {
|
||||
// Feature를 입력 받아 중심 좌표를 계산하는 함수
|
||||
|
||||
@@ -45,17 +45,21 @@ export function SearchBar({ gisMaps, ctpvCode, sggCode }: CtpvSggPrcsFcltSelectB
|
||||
|
||||
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`)
|
||||
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&srsName=EPSG:5179`)
|
||||
.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 좌표계
|
||||
dataProjection: "EPSG:5179", // GeoJSON 좌표계
|
||||
featureProjection: GisApp.TO_PROJ // 지도 좌표계
|
||||
});
|
||||
|
||||
console.log('feature', feature);
|
||||
|
||||
const extent = medianCoordinate2D(feature);
|
||||
console.log('GisApp.TO_PROJ', GisApp.TO_PROJ);
|
||||
console.log('extent', extent)
|
||||
|
||||
handleFeatureFocus(extent);
|
||||
}
|
||||
@@ -66,14 +70,14 @@ export function SearchBar({ gisMaps, ctpvCode, sggCode }: CtpvSggPrcsFcltSelectB
|
||||
|
||||
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`)
|
||||
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&srsName=EPSG:5179`)
|
||||
.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 좌표계
|
||||
dataProjection: "EPSG:5179", // GeoJSON 좌표계
|
||||
featureProjection: GisApp.TO_PROJ // 지도 좌표계
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
type SearchType = {
|
||||
bscFctNm: string;
|
||||
bscFctCd: string;
|
||||
};
|
||||
|
||||
type thisProps = {
|
||||
label: string | undefined;
|
||||
initValue: string | undefined;
|
||||
setValue: (searchNm?: string) => void;
|
||||
setRtnData: (rtnData: any) => void;
|
||||
searchList?: SearchType[];
|
||||
};
|
||||
|
||||
export default function SearchInputText({
|
||||
label = undefined,
|
||||
initValue = undefined,
|
||||
setValue = () => {},
|
||||
setRtnData = () => {},
|
||||
searchList = [],
|
||||
}: thisProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [textValue, setTextValue] = useState(initValue || "");
|
||||
const [highlightIndex, setHighlightIndex] = useState<number>(-1);
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
setTextValue(value);
|
||||
setValue(value);
|
||||
setHighlightIndex(-1);
|
||||
setIsOpen(value !== "");
|
||||
},
|
||||
[setValue]
|
||||
);
|
||||
|
||||
const handleSuggestionClick = useCallback(
|
||||
(searchNm: string) => {
|
||||
setValue(searchNm);
|
||||
setTextValue(searchNm);
|
||||
setIsOpen(false);
|
||||
setRtnData(searchList?.find((item) => item.bscFctNm === searchNm) || {});
|
||||
},
|
||||
[searchList, setRtnData, setValue]
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (!isOpen || searchList.length === 0) return;
|
||||
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
setHighlightIndex((prev) => (prev + 1) % searchList.length);
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
setHighlightIndex((prev) =>
|
||||
prev <= 0 ? searchList.length - 1 : prev - 1
|
||||
);
|
||||
} else if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
if (searchList.length === 1 && searchList[0]) {
|
||||
handleSuggestionClick(searchList[0].bscFctNm);
|
||||
} else if (highlightIndex >= 0 && highlightIndex < searchList.length && searchList[highlightIndex]) {
|
||||
handleSuggestionClick(searchList[highlightIndex].bscFctNm);
|
||||
}
|
||||
}
|
||||
},
|
||||
[highlightIndex, isOpen, searchList, handleSuggestionClick]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
wrapperRef.current &&
|
||||
!wrapperRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef} className="relative">
|
||||
<input
|
||||
type="text"
|
||||
value={textValue || ""}
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="w-full h-[3.2rem] !text-xl pl-3 text-gray-600 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder={`${label}을(를) 입력하세요`}
|
||||
onClick={() => {
|
||||
setTextValue("");
|
||||
setValue("");
|
||||
setIsOpen(false);
|
||||
}}
|
||||
/>
|
||||
{isOpen && (
|
||||
<div className="absolute top-full left-0 z-50 mb-2 max-h-72 w-full overflow-auto rounded-md border border-gray-300 bg-white shadow-lg">
|
||||
<div className="max-h-72">
|
||||
{searchList.length > 0 ? (
|
||||
searchList.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`w-full cursor-pointer overflow-auto px-5 py-3 text-left !text-xl ${
|
||||
highlightIndex === index
|
||||
? "bg-blue-100"
|
||||
: "hover:bg-gray-100"
|
||||
}`}
|
||||
onClick={() => handleSuggestionClick(item.bscFctNm)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
handleSuggestionClick(item.bscFctNm);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="float-left">{item.bscFctNm}</span>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="!text-xl w-full px-5 py-3 hover:bg-gray-100 cursor-pointer overflow-auto">
|
||||
검색 결과가 없습니다.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
"use client";
|
||||
|
||||
import Gis from "@common/business_components/ui/map/Gis";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { mapService } from "./service";
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
import { SearchBar } from "./searchBar/searchBar";
|
||||
import SelectedFcltList from "./selectedFcltList";
|
||||
|
||||
export type SubsInfo = {
|
||||
ruseUtztnFcltNm: string; // 재이용시설명
|
||||
ruseUtztnFcltCd: string; // 재이용시설 코드
|
||||
}
|
||||
|
||||
const Map = () => {
|
||||
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
|
||||
const gisMaps = useRef<any | null>(null);
|
||||
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
|
||||
const [isSearchModalOpen, setIsSearchModalOpen] = useState(true); // SearchModal 열림 상태
|
||||
const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터
|
||||
const [selectDataPk, setSelectDataPk] = useState<Record<string, any>>({});
|
||||
const [selectedFclts, setSelectedFclts] = useState<any[]>([]);
|
||||
|
||||
//적용 버튼 클릭 이벤트
|
||||
const onApply = () => {
|
||||
if (selectedFclts.length > 1) {
|
||||
alert("재이용시설 공급처는 1곳만 선택할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const subsInfoList: SubsInfo[] = selectedFclts.map((fclt) => ({
|
||||
ruseUtztnFcltNm: fclt.ruseUtztnFcltNm,
|
||||
ruseUtztnFcltCd: fclt.pbnstRuseFcltId,
|
||||
}));
|
||||
|
||||
if (!window.opener || typeof window.opener.postMessage !== "function") {
|
||||
alert("부모 창을 찾을 수 없습니다. 팝업이 'noopener'로 열렸거나, 보안 정책으로 차단되었을 수 있어요.");
|
||||
return;
|
||||
}
|
||||
|
||||
window.opener.postMessage(
|
||||
{
|
||||
type: "SET_SUBS_INFO",
|
||||
payload: subsInfoList[0], // 또는 필요한 데이터
|
||||
},
|
||||
"*"
|
||||
);
|
||||
|
||||
window.close();
|
||||
};
|
||||
|
||||
//트리 레이어 데이터 조회
|
||||
const fetchTreeData = async () => {
|
||||
try {
|
||||
const response = await mapService.getLyrStngs();
|
||||
setTreeData(response); // API 응답 저장
|
||||
} catch (error) {
|
||||
console.error("데이터 불러오기 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
//하수처리시설코드로 재이용시설 목록 조회
|
||||
const reuseData = async (ruseFcltPk: any) => {
|
||||
try {
|
||||
const response = await mapService.getPbnstRuseFclts(ruseFcltPk);
|
||||
const data = response.data || [];
|
||||
setSelectedFclts((prev) => {
|
||||
const newItems = data.filter(
|
||||
(item: any) =>
|
||||
item &&
|
||||
Object.keys(item).length > 0 &&
|
||||
!prev.some((f) => f.pbnstRuseFcltId === item.pbnstRuseFcltId)
|
||||
);
|
||||
console.log(newItems);
|
||||
return [...prev, ...newItems];
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("데이터 불러오기 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 컴포넌트 마운트 시 1회 실행
|
||||
useEffect(() => {
|
||||
fetchTreeData();
|
||||
}, []);
|
||||
|
||||
// 팝업 Map.tsx useEffect 안
|
||||
useEffect(() => {
|
||||
const origin = window.location.origin;
|
||||
if (window.opener) {
|
||||
window.opener.postMessage({ type: "REQUEST_INIT_SUBS_INFO" }, origin);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const origin = window.location.origin;
|
||||
|
||||
const handler = (event: MessageEvent) => {
|
||||
// 동일 출처만 허용 (개발 중엔 임시로 주석처리하고 *로 테스트해도 됨)
|
||||
if (event.origin !== origin) return;
|
||||
|
||||
const data = event.data;
|
||||
if (!data || typeof data !== "object") return;
|
||||
|
||||
if (data.type === "INIT_SUBS_INFO") {
|
||||
// payload: [{ ruseUtztnFcltNm, ruseUtztnFcltCd }, ...]
|
||||
const list = Array.isArray(data.payload) ? data.payload : [];
|
||||
|
||||
// selectedFclts 형태에 맞게 변환해서 반영
|
||||
// (지금 onApply에서 pbnstRuseFcltId를 쓰므로 매핑 맞춰줍니다)
|
||||
setSelectedFclts(list.map((x) => ({
|
||||
ruseUtztnFcltNm: x.ruseUtztnFcltNm ?? "",
|
||||
pbnstRuseFcltId: x.ruseUtztnFcltCd ?? "", // <- 기존 코드가 기대하는 키
|
||||
})));
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", handler);
|
||||
return () => window.removeEventListener("message", handler);
|
||||
}, []);
|
||||
|
||||
// 레이어 생성 후 초기화 처리
|
||||
const onCheckedLayersChange = () => {
|
||||
//수요처 ID
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const dmdInfoId = urlParams.get("dmdInfoId");
|
||||
|
||||
//수요처 위치로 화면 이동
|
||||
const maps = gisMaps.current;
|
||||
const layer = GisApp.Module.layer as any;
|
||||
const layerNm = "tb_wr_ruse_fclt_dmd_co";
|
||||
const featureId = `${layerNm}.${dmdInfoId}`;
|
||||
let foundFeature: any = null;
|
||||
|
||||
maps
|
||||
.getLayers()
|
||||
.getArray()
|
||||
.forEach((layer: any) => {
|
||||
if (layer.get("lyrTyNm") == layerNm) {
|
||||
const source = layer.getSource?.();
|
||||
if (source?.getFeatureById) {
|
||||
const feature = source.getFeatureById(String(featureId).trim());
|
||||
if (feature) {
|
||||
foundFeature = feature;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (foundFeature) {
|
||||
const geometry = foundFeature.getGeometry();
|
||||
|
||||
if (geometry) {
|
||||
const extent = geometry.getExtent();
|
||||
|
||||
if (geometry.getType() === "Point") {
|
||||
maps.getView().setCenter(geometry.getCoordinates());
|
||||
maps.getView().setZoom(11);
|
||||
} else {
|
||||
maps.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [30, 30, 30, 30],
|
||||
maxZoom: 16,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
layer.highLightFeature(
|
||||
[foundFeature],
|
||||
{
|
||||
id: GisApp.hlLayerId,
|
||||
zIndex: 10,
|
||||
opacity: 1,
|
||||
selectable: true,
|
||||
},
|
||||
{ width: 30 }
|
||||
);
|
||||
}
|
||||
|
||||
const formatNumber = (val: any) => {
|
||||
const num = Number(val);
|
||||
return isNaN(num) ? "-" : num.toLocaleString();
|
||||
};
|
||||
|
||||
// 재이용시설 오버레이
|
||||
maps
|
||||
.getLayers()
|
||||
.getArray()
|
||||
.forEach((layer: any) => {
|
||||
if (layer.get("lyrTyNm") === "tb_wr_bdst_ruse_fclt") {
|
||||
const source = layer.getSource?.();
|
||||
if (source?.getFeatures) {
|
||||
const features = source.getFeatures();
|
||||
|
||||
features.forEach((feature: any) => {
|
||||
// 필요한 고유 ID 또는 속성 가져오기 (예: ftrIdn 또는 feature.getId())
|
||||
const featureId =
|
||||
feature.getId() || feature.get("ftrIdn") || Math.random();
|
||||
|
||||
// HTML 또는 React 오버레이 컴포넌트 구성
|
||||
const overlayContent = `
|
||||
<div class="rounded-md border border-gray-300 bg-white shadow-md text-sm w-[120px]">
|
||||
<!-- 헤더 -->
|
||||
<div class="bg-cyan-700 text-white px-3 py-2 text-[11px] font-semibold text-center rounded-t-md">
|
||||
${feature.get("swtrmt_fclt_nm") || "-"}
|
||||
</div>
|
||||
|
||||
<!-- 테이블 -->
|
||||
<table class="w-full text-sm text-gray-800 border-t border-gray-200">
|
||||
<tbody>
|
||||
<tr class="border-b border-gray-200">
|
||||
<th class="bg-gray-100 text-gray-600 font-semibold text-[10px] text-left">용량(m³/일)</th>
|
||||
<td class="text-right text-[10px] font-bold">${formatNumber(feature.get("fclt_cpct"))}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
feature.set("html", overlayContent);
|
||||
feature.set("overLayId", `overLay.${featureId}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 검색해서 재이용시설 선택하면 해당 시설로 화면 이동 처리
|
||||
useEffect(() => {
|
||||
if (!selectDataPk || Object.keys(selectDataPk).length === 0) return;
|
||||
const maps = gisMaps.current;
|
||||
let foundFeature: any = null;
|
||||
const layerNm = "tb_wr_bdst_ruse_fclt";
|
||||
const featureId = `${layerNm}.${selectDataPk.bscFctCd}`;
|
||||
maps
|
||||
.getLayers()
|
||||
.getArray()
|
||||
.forEach((layer: any) => {
|
||||
if (layer.get("lyrTyNm") == layerNm) {
|
||||
const source = layer.getSource?.();
|
||||
if (source?.getFeatureById) {
|
||||
const feature = source.getFeatureById(String(featureId).trim());
|
||||
if (feature) {
|
||||
foundFeature = feature;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (foundFeature) {
|
||||
const geometry = foundFeature.getGeometry();
|
||||
if (geometry) {
|
||||
const extent = geometry.getExtent();
|
||||
|
||||
maps.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 16,
|
||||
constrainResolution: true,
|
||||
nearest: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.warn("OpenLayers Feature를 찾을 수 없습니다:", selectDataPk);
|
||||
}
|
||||
}, [selectDataPk]);
|
||||
|
||||
// 재이용시설 클릭 시 선택 시설목록에 추가
|
||||
const onFeatureSelect = (data: any) => {
|
||||
// 처리시설코드
|
||||
const swtrmtFcltCd = data.get("swtrmt_fclt_cd");
|
||||
reuseData(swtrmtFcltCd);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="bg-container">
|
||||
<Gis
|
||||
height='h-screen'
|
||||
treeData={treeData}
|
||||
isSearch={false}
|
||||
gisMaps={gisMaps}
|
||||
isMiniMap={false}
|
||||
searchView={undefined}
|
||||
detailView={undefined}
|
||||
onFeatureSelect={onFeatureSelect}
|
||||
setIsOpen={setIsOpen}
|
||||
setFeatureData={setFeatureData}
|
||||
featureData={featureData}
|
||||
setIsSearchModalOpen={setIsSearchModalOpen}
|
||||
isOpen={isOpen}
|
||||
isSearchModalOpen={isSearchModalOpen}
|
||||
onCheckedLayersChange={onCheckedLayersChange}
|
||||
imgVisble={false}
|
||||
searchBar={
|
||||
<SearchBar
|
||||
setHasuPk={(hasuPk: any) => {
|
||||
if (hasuPk) setSelectDataPk(hasuPk);
|
||||
}}
|
||||
onApply={onApply}
|
||||
/>
|
||||
}
|
||||
waterDepth={undefined}
|
||||
/>
|
||||
</div>
|
||||
<SelectedFcltList
|
||||
data={selectedFclts}
|
||||
onRemove={(updatedList) => setSelectedFclts(updatedList)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Map;
|
||||
@@ -0,0 +1,118 @@
|
||||
"use client";
|
||||
import { useWrStore } from "@/app/store";
|
||||
import {
|
||||
SelectBox,
|
||||
type SelectDataType,
|
||||
} from "@common/business_components/ui";
|
||||
import { Button, Label } from "@common/components/ui";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import SearchInputText from "../SearchInputText/SearchInputText";
|
||||
|
||||
export function SearchBar({
|
||||
setHasuPk,
|
||||
onApply,
|
||||
}: {
|
||||
setHasuPk: (hasuPk: any) => void;
|
||||
onApply: () => void;
|
||||
}) {
|
||||
const { map, code, codeInit, findRuseFclt } = useWrStore(
|
||||
(state) => state
|
||||
);
|
||||
const [sidoValue, setSidoValue] = useState<string>("");
|
||||
const [sigunValue, setSigunValue] = useState<string>("");
|
||||
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([
|
||||
{ text: "전체", val: "0" },
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
//codeInit();
|
||||
}, [codeInit]);
|
||||
|
||||
const initializeSigunData = useCallback(
|
||||
(sidoCd: string) => {
|
||||
if (sidoCd === "0") {
|
||||
setSigunValue("0");
|
||||
setFilteredSigunData([{ text: "전체", val: "0" }]);
|
||||
} else {
|
||||
const newFilteredData = [
|
||||
{ text: "전체", val: "0" },
|
||||
...code.sggCd.filter(
|
||||
(item: SelectDataType) => item.val.substring(0, 2) === sidoCd
|
||||
),
|
||||
];
|
||||
setSigunValue("0");
|
||||
setFilteredSigunData(newFilteredData);
|
||||
}
|
||||
},
|
||||
[code.sggCd]
|
||||
);
|
||||
|
||||
const onSelectValue = useCallback(
|
||||
(val: string, type: string) => {
|
||||
switch (type) {
|
||||
case "ctpvCd":
|
||||
initializeSigunData(val);
|
||||
setSidoValue(val);
|
||||
break;
|
||||
case "sggCd":
|
||||
setSigunValue(val);
|
||||
}
|
||||
},
|
||||
[initializeSigunData]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex justify-center w-full px-4 py-3">
|
||||
<div className="flex items-center w-full max-w-screen-lg rounded-xl bg-white shadow-md px-6 py-4 space-x-3">
|
||||
<SelectBox
|
||||
labelClass="w-[40px]"
|
||||
className="w-[200px]"
|
||||
label="시도"
|
||||
selectClass="rounded-lg bg-white"
|
||||
selectArray={code.cityCd || []}
|
||||
onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")}
|
||||
value={sidoValue}
|
||||
/>
|
||||
<SelectBox
|
||||
labelClass="w-[65px]"
|
||||
className="w-[200px]"
|
||||
label="시군구"
|
||||
selectClass="rounded-lg bg-white"
|
||||
selectArray={filteredSigunData || []}
|
||||
onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")}
|
||||
value={sigunValue}
|
||||
/>
|
||||
<Label className="text-gray-600">하수재이용시설</Label>
|
||||
<SearchInputText
|
||||
label="시설명"
|
||||
initValue={undefined}
|
||||
setValue={(searchNm?: string) => {
|
||||
let sggOrCity = "";
|
||||
if (searchNm) {
|
||||
if (sigunValue === "0" || sigunValue === "") {
|
||||
sggOrCity = sidoValue;
|
||||
} else {
|
||||
sggOrCity = sigunValue;
|
||||
}
|
||||
findRuseFclt(sggOrCity, searchNm);
|
||||
}
|
||||
}}
|
||||
setRtnData={(rtnData: any) => {
|
||||
setHasuPk(rtnData);
|
||||
}}
|
||||
searchList={map.ruseFcltList || []}
|
||||
/>
|
||||
<Button
|
||||
color="sky"
|
||||
className="ml-2 text-white"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
onApply();
|
||||
}}
|
||||
>
|
||||
적용
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import React from "react";
|
||||
|
||||
type FcltItem = {
|
||||
pbnstRuseFcltId: string;
|
||||
ruseUtztnFcltNm: string;
|
||||
splyExtraCpct?: number;
|
||||
ruseCpct?: number;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export default function SelectedFcltList({
|
||||
data = [],
|
||||
onRemove,
|
||||
}: {
|
||||
data: FcltItem[];
|
||||
onRemove: (updatedList: FcltItem[]) => void;
|
||||
}) {
|
||||
const handleDelete = (code: string) => {
|
||||
const updatedList = data.filter((item) => item.pbnstRuseFcltId !== code);
|
||||
onRemove(updatedList);
|
||||
};
|
||||
|
||||
const TableContent = (
|
||||
<>
|
||||
<thead className="bg-gray-100 text-gray-700 text-[13px]">
|
||||
<tr>
|
||||
<th className="px-3 py-2 text-left border-b">시설명</th>
|
||||
<th className="px-3 py-2 text-right border-b">
|
||||
하수처리량
|
||||
<br />
|
||||
(㎥/일)
|
||||
</th>
|
||||
<th className="px-3 py-2 text-right border-b">
|
||||
재이용량
|
||||
<br />
|
||||
(㎥/일)
|
||||
</th>
|
||||
<th className="px-3 py-2 border-b">삭제</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((item) => (
|
||||
<tr key={item.pbnstRuseFcltId} className="border-b last:border-none">
|
||||
<td className="px-3 py-2 text-[12px]">{item.ruseUtztnFcltNm}</td>
|
||||
<td className="px-3 py-2 text-right text-[12px]">
|
||||
{Number(item.splyExtraCpct || 0).toLocaleString()}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-right text-[12px]">
|
||||
{Number(item.ruseCpct || 0).toLocaleString()}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-center">
|
||||
<button
|
||||
onClick={() => handleDelete(item.pbnstRuseFcltId)}
|
||||
className="text-red-500 hover:text-red-700 text-sm font-bold"
|
||||
aria-label="삭제"
|
||||
title="삭제"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</>
|
||||
);
|
||||
|
||||
if (data.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed right-40 top-[8rem] w-[40rem] max-h-[130rem] overflow-auto bg-white shadow-lg rounded p-4 border border-gray-300 z-50">
|
||||
<h3 className="bg-cyan-700 text-white text-xl font-bold mb-2 p-2">
|
||||
선택된 시설
|
||||
</h3>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm text-gray-800 border border-gray-200">
|
||||
{TableContent}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './map'
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
/**
|
||||
* 관망도 API
|
||||
*/
|
||||
import { DEV_URL_WR, DEV_URL_SN } from "@/components/lib/config";
|
||||
|
||||
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(`${DEV_URL_SN}/v1/map/lyrStngs/DM_NETWORK`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
},
|
||||
getPbnstRuseFclts: async (ruseFcltPk:any) => {
|
||||
const response = await fetch(`${DEV_URL_WR}/reuseFc/pubFc/${ruseFcltPk}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
}
|
||||
@@ -13,20 +13,20 @@ 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 popupRef = useRef<Window | null>(null);
|
||||
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]) {
|
||||
@@ -112,101 +112,102 @@ export default function DrgtSttn() {
|
||||
setObjSearch({ ...objSearch, sgg: String(val) });
|
||||
};
|
||||
|
||||
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);
|
||||
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])
|
||||
|
||||
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);
|
||||
}
|
||||
}, [fromDate, toDate])
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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[] = '';
|
||||
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[] = '';
|
||||
|
||||
if (objSearch?.city.toString().length > 0) {
|
||||
sidoText = code.cityCd.find((item) => item.val === objSearch?.city)?.text ?? '';
|
||||
@@ -224,67 +225,67 @@ export default function DrgtSttn() {
|
||||
sggText = sidoText;
|
||||
}
|
||||
|
||||
const searchArr: string[] = [];
|
||||
if (sidoText) searchArr.push(sidoText);
|
||||
if (Array.isArray(sggText)) {
|
||||
searchArr.push(...sggText);
|
||||
} else if (sggText) {
|
||||
searchArr.push(sggText);
|
||||
}
|
||||
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={onViewMapClick}
|
||||
>
|
||||
지도보기
|
||||
</Button>
|
||||
drgtSearch(fromDate ?? "", toDate ?? "", searchArr)
|
||||
}}
|
||||
>
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
size={"sm"}
|
||||
className={`${btnClass} green`}
|
||||
onClick={excelClick}
|
||||
>
|
||||
다운로드
|
||||
</Button>
|
||||
<Button
|
||||
size={"sm"}
|
||||
className={btnClass}
|
||||
onClick={onViewMapClick}
|
||||
>
|
||||
지도보기
|
||||
</Button>
|
||||
</div>
|
||||
</SearchFormRight>
|
||||
</SearchDiv>
|
||||
</div>
|
||||
</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>
|
||||
</>
|
||||
)
|
||||
<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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { fetchType, getFetch } from "@/components/lib/api";
|
||||
import type { FacilityInfo, SubsInfo } from "./type";
|
||||
import { DEV_URL_FO } from "@/components/lib/config";
|
||||
|
||||
const checkWrRegList = async () => {
|
||||
const wrRegList = await getFetch("wr", `/reuseFc/pubFc`);
|
||||
return wrRegList;
|
||||
};
|
||||
|
||||
const getRuseFclt = async (
|
||||
page: string,
|
||||
sggOrCity: string,
|
||||
searchTxt?: string
|
||||
) => {
|
||||
const checkWrRegListRes = await checkWrRegList();
|
||||
|
||||
let url = `/prcsFclt/ruseInfo/${page ? page : 1}/5/A/${sggOrCity ? sggOrCity : "0"}/0/1/SW`;
|
||||
|
||||
const ruseList = checkWrRegListRes.filter((item: string) => item !== "");
|
||||
|
||||
if (searchTxt) {
|
||||
url = url + `/${searchTxt}`;
|
||||
}
|
||||
|
||||
const response = await fetch(DEV_URL_FO + url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(ruseList),
|
||||
});
|
||||
|
||||
const hasuList = await response.json().then((res: any) => {
|
||||
const data = res.data
|
||||
.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
fctCpc: Number(item.fctCpc) || 0,
|
||||
searchNm: item.fctNm,
|
||||
searchPk: item.bscFctNm,
|
||||
};
|
||||
})
|
||||
.filter((hasu: any) => ruseList.find((ruse: any) => hasu.bscFctCd === ruse));
|
||||
return { data: data, count: res.count };
|
||||
});
|
||||
|
||||
return hasuList;
|
||||
};
|
||||
|
||||
export type DmMapActions = {
|
||||
findRuseFclt(sggOrCity: string, searchTxt: string): void;
|
||||
setSubsInfo(subsInfo: SubsInfo[]): void;
|
||||
};
|
||||
|
||||
export type DmMapType = {
|
||||
map: {
|
||||
ruseFcltList: FacilityInfo[];
|
||||
subsInfo: SubsInfo[];
|
||||
};
|
||||
};
|
||||
|
||||
export const dmMapState: DmMapType = {
|
||||
map: {
|
||||
ruseFcltList: [],
|
||||
subsInfo: []
|
||||
},
|
||||
};
|
||||
export type DmMapStore = DmMapType & DmMapActions;
|
||||
|
||||
export const DmMapReducer = (set: any): DmMapActions => {
|
||||
return {
|
||||
findRuseFclt: async (sggOrCity: string, searchTxt: string) => {
|
||||
const ruseFcltList = await getRuseFclt("1", sggOrCity, searchTxt);
|
||||
|
||||
set((state: DmMapType) => ({
|
||||
...state,
|
||||
map: {
|
||||
...state.map,
|
||||
ruseFcltList: ruseFcltList.data,
|
||||
},
|
||||
}));
|
||||
},
|
||||
setSubsInfo: async (subsInfo: SubsInfo[]) => {
|
||||
set((state: DmMapType) => ({
|
||||
...state,
|
||||
map: {
|
||||
...state.map,
|
||||
subsInfo: subsInfo,
|
||||
},
|
||||
}));
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
export type FacilityInfo= {
|
||||
bscFctCd: string; // 기본시설 코드
|
||||
bscFctNm: string; // 기본시설명
|
||||
rvbsnCd: string; // 유역 코드
|
||||
rvbsn: string; // 유역명
|
||||
lgbsn: string; // 대권역명
|
||||
mdmbsn: string; // 중권역명
|
||||
rgnCd: string; // 관할청 코드
|
||||
rgnNm: string; // 관할청 명칭
|
||||
adstDstrCd: string; // 행정구역 코드
|
||||
sido: string; // 시도
|
||||
gugun: string; // 구군
|
||||
addr: string; // 주소
|
||||
fctCpc: string; // 시설용량
|
||||
efflWqltStndrdCd: string; // 방류수 수질기준 코드
|
||||
efflWqltStndrdNm: string; // 방류수 수질기준명
|
||||
trtFrfNm: string; // 처리공법명
|
||||
trtDstr: string; // 처리구역
|
||||
rnDt: string; // 사용개시일 (예: YYYYMMDD)
|
||||
cmplDt: string; // 준공일자 (예: YYYYMMDD)
|
||||
trstOprEtpNm: string; // 위탁운영기관명
|
||||
bscFctClsNm: string; // 기본시설 구분명
|
||||
sitArea: string; // 부지면적
|
||||
trtDstrArea: string; // 처리구역 면적
|
||||
swrTrtPopl: string; // 하수처리 인구
|
||||
mnstr: string; // 총괄 유역
|
||||
efflWtrrgn: string; // 방류수역
|
||||
rvNm: string; // 하천명
|
||||
bscFctClsCd: string; // 기본시설 구분 코드
|
||||
rvbsnNm: string; // 유역명
|
||||
tel: string; // 전화번호
|
||||
rswtrYn: string; // 재이용수 여부 ("Y" 또는 "N")
|
||||
ruse: boolean; // 재이용 여부 (boolean)
|
||||
}
|
||||
|
||||
export type SubsInfo={
|
||||
ruseUtztnFcltNm: string; // 재이용시설명
|
||||
ruseUtztnFcltCd: string; // 재이용시설 코드
|
||||
}
|
||||
@@ -216,6 +216,12 @@ import {
|
||||
type ldrmntMapState,
|
||||
type ldrmntMapType,
|
||||
} from "./map/ldrmntCoMap";
|
||||
import {
|
||||
DmMapReducer,
|
||||
dmMapState,
|
||||
type DmMapType,
|
||||
type DmMapStore,
|
||||
} from "./map/splyPop";
|
||||
|
||||
export type WrStore = comCodeType &
|
||||
pubFacType &
|
||||
@@ -251,6 +257,7 @@ export type WrStore = comCodeType &
|
||||
ruseMapType &
|
||||
coSttnMapType &
|
||||
ldrmntMapType &
|
||||
DmMapStore &
|
||||
authType;
|
||||
|
||||
export const StoreZus = (
|
||||
@@ -288,6 +295,7 @@ export const StoreZus = (
|
||||
ruseMapState &
|
||||
coSttnMapState &
|
||||
ldrmntMapState &
|
||||
DmMapType &
|
||||
authState = {
|
||||
...comCodeStateExport,
|
||||
...pubFacStateExport,
|
||||
@@ -324,6 +332,7 @@ export const StoreZus = (
|
||||
...ruseMapStateExport,
|
||||
...coSttnMapStateExport,
|
||||
...ldrmntMapStateExport,
|
||||
...dmMapState,
|
||||
}
|
||||
) => {
|
||||
return createStore<WrStore>()((set) => {
|
||||
@@ -371,6 +380,7 @@ export const StoreZus = (
|
||||
...ruseMapActionsExport(set),
|
||||
...coSttnMapActionsExport(set),
|
||||
...ldrmntMapActionsExport(set),
|
||||
...DmMapReducer(set),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
SERVER_API_CS_URL,
|
||||
SERVER_API_FO_URL,
|
||||
SERVER_API_WR_URL,
|
||||
SERVER_SEWERAGE_API_URL,
|
||||
} from "@/src/shared/config";
|
||||
|
||||
//const DEV_PORT = "11080";
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
export const LOCAL_URL_WR = PROXY_HOST + `/api/v1`;
|
||||
export const DEV_URL_WR = SERVER_API_WR_URL;
|
||||
export const DEV_URL_FO = SERVER_API_FO_URL;
|
||||
export const DEV_URL_SN = SERVER_SEWERAGE_API_URL;
|
||||
export const LOCAL_URL = PROXY_HOST;
|
||||
export const CS_URL = SERVER_API_CS_URL;
|
||||
export const CS_TUS_UPLOAD = SERVER_API_CS_TUS_URL;
|
||||
|
||||
@@ -69,6 +69,18 @@ export default function ConCostForm({
|
||||
setCheckFinish(false);
|
||||
}, [selectionVersion]);
|
||||
|
||||
//공급처 팝업 값 셋팅
|
||||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
if (event.data?.type === "SET_SUBS_INFO") {
|
||||
setLastSub(event.data.payload);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", handleMessage);
|
||||
return () => window.removeEventListener("message", handleMessage);
|
||||
}, [setLastSub]);
|
||||
|
||||
const zipDowonLoad = () => {
|
||||
fetch(`/2024_constarctFeeProgram.zip`, {}).then(async (response) => {
|
||||
const blobRet = await response.blob();
|
||||
@@ -219,15 +231,62 @@ export default function ConCostForm({
|
||||
}, [form.formState]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("splyCndtList ", splyList);
|
||||
const origin = window.location.origin;
|
||||
|
||||
const handler = (event: MessageEvent) => {
|
||||
// 보안: 동일 출처만 수신
|
||||
if (event.origin !== origin) return;
|
||||
|
||||
// 구조 안전 체크
|
||||
const data = event.data;
|
||||
if (!data || typeof data !== "object") return;
|
||||
|
||||
if (data.type === "REQUEST_INIT_SUBS_INFO") {
|
||||
const initSubs =
|
||||
(splyList ?? [])
|
||||
.filter(Boolean)
|
||||
.map((x: any) => ({
|
||||
ruseUtztnFcltNm: x.ruseUtztnFcltNm ?? "",
|
||||
ruseUtztnFcltCd: x.ruseUtztnFcltCd ?? "",
|
||||
}));
|
||||
|
||||
// 응답: 옵션 객체 형태로 전송 (TS 에러 방지)
|
||||
const target = event.source as Window | null;
|
||||
target?.postMessage(
|
||||
{ type: "INIT_SUBS_INFO", payload: initSubs },
|
||||
{ targetOrigin: origin }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", handler);
|
||||
return () => window.removeEventListener("message", handler);
|
||||
}, [splyList]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
form.register("dmdInfoSn");
|
||||
form.register("dmdInfoId");
|
||||
form.register("ruseUtztnFcltNm");
|
||||
}, []);
|
||||
|
||||
|
||||
const searchCandidate = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const dmdInfoId = form.getValues("dmdInfoId");
|
||||
console.log(dmdInfoId)
|
||||
|
||||
const url = `/splyPopup?dmdInfoId=${encodeURIComponent(dmdInfoId)}`;
|
||||
const width = window.screen.availWidth;
|
||||
const height = window.screen.availHeight;
|
||||
const left = 0;
|
||||
const top = 0;
|
||||
const popupOptions = `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`;
|
||||
window.open(url, "candidatePopup", popupOptions);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<hr className="mt-8" />
|
||||
@@ -515,6 +574,8 @@ export default function ConCostForm({
|
||||
onClick={(e) => {
|
||||
console.log(e);
|
||||
setLastSub(null) // 빌드오류때문에 넣은거 삭제필요
|
||||
|
||||
searchCandidate(e);
|
||||
|
||||
//splyList => 해당객체가 대민에서 선택한 후보처 리스트
|
||||
//setLastSub => 해당 스테이트가 선택한 마지막 후보처를 넣어야하는 스테이트
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
"lucide-react": "^0.474.0",
|
||||
"moment": "^2.30.1",
|
||||
"next": "15.1.7",
|
||||
"ol": "^7.3.0",
|
||||
"ol-ext": "^4.0.11",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-hot-toast": "^2.5.2",
|
||||
"react-i18next": "^15.6.0",
|
||||
|
||||
Reference in New Issue
Block a user