Merge remote-tracking branch 'origin/main' into iipbs/sn/dev

This commit is contained in:
yschoi
2025-08-03 14:33:05 +09:00
52 changed files with 1210 additions and 614 deletions
+2 -2
View File
@@ -52,9 +52,9 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink,
if (await confirm(confirmMsg)) { if (await confirm(confirmMsg)) {
if (linkSn) { if (linkSn) {
updateLinkInfo?.(data); updateLinkInfo?.(data);
} else { } else {
handleInsertLink?.(data); handleInsertLink?.(data);
} }
} }
}); });
}; };
@@ -66,7 +66,7 @@ function LinkParamsPage() {
const userUniqId = getCookie(AUTH_USER_ID) ?? ""; const userUniqId = getCookie(AUTH_USER_ID) ?? "";
const { setMessage: alert } = useAlertStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state);
const dbIdList = ['link0001', 'link0002']; const dbIdList = ['link0001', 'link0002', 'link0007'];
// Link 목록 쿼리 // Link 목록 쿼리
const query = useQuery({ const query = useQuery({
@@ -233,6 +233,7 @@ const Map = () => {
<> <>
<div className="bg-container"> <div className="bg-container">
<Gis <Gis
height='h-screen'
treeData={treeData} treeData={treeData}
isSearch={false} isSearch={false}
gisMaps={gisMaps} gisMaps={gisMaps}
@@ -1,8 +1,6 @@
"use client"; "use client";
import { useDmStore } from "@/app/store"; import { useDmStore } from "@/app/store";
import { import {
SearchDiv,
SearchFormLeft,
SelectBox, SelectBox,
type SelectDataType, type SelectDataType,
} from "@common/business_components/ui"; } from "@common/business_components/ui";
@@ -48,6 +46,7 @@ export function SearchBar({
}, },
[Common.selectSigunData] [Common.selectSigunData]
); );
const onSelectValue = useCallback( const onSelectValue = useCallback(
(val: string, type: string) => { (val: string, type: string) => {
switch (type) { switch (type) {
@@ -62,78 +61,58 @@ export function SearchBar({
[initializeSigunData] [initializeSigunData]
); );
// useEffect(() => {
// console.log("map.ruseFcltList ", map.ruseFcltList);
// }, [map.ruseFcltList]);
return ( return (
<> <div className="flex justify-center w-full px-4 py-3">
<div className="grid grid-cols-4 gap-4 "> <div className="flex items-center w-full max-w-screen-lg rounded-xl bg-white shadow-md px-6 py-4 space-x-3">
<div className="col-span-4"> <SelectBox
<SearchDiv> labelClass="w-[40px]"
<SearchFormLeft> className="w-[200px]"
<div className="col-span-10 flex flex-row"> label="시도"
<SelectBox selectClass="rounded-lg bg-white"
className="col-span-2 w-1/6 h-full text-2xl" selectArray={Common.selectSidoData || []}
label="시도" onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")}
selectClass={""} value={sidoValue || Common.selectSidoData[0]?.val || ""}
selectArray={Common.selectSidoData || []} />
onSelectValue={(val) => <SelectBox
onSelectValue(val.toString(), "ctpvCd") labelClass="w-[65px]"
} className="w-[200px]"
value={sidoValue || Common.selectSidoData[0]?.val || ""} label="시군구"
/> selectClass="rounded-lg bg-white"
<SelectBox selectArray={filteredSigunData || []}
className="col-span-2 w-1/6 h-full text-2xl ml-10" onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")}
label="시군구" value={sigunValue}
selectClass={""} />
selectArray={filteredSigunData || []} <Label className="text-gray-600">하수재이용시설</Label>
onSelectValue={(val) => <SearchInputText
onSelectValue(val.toString(), "sggCd") label="시설명"
} initValue={undefined}
value={sigunValue} setValue={(searchNm?: string) => {
/> let sggOrCity = "";
<div className="col-span-6 flex flex-row items-center min-w-[27rem] ml-10"> if (searchNm) {
<Label className="text-xl justify-self-center mt-2 mx-3"> if (sigunValue === "0" || sigunValue === "") {
하수재이용시설 sggOrCity = sidoValue;
</Label> } else {
<SearchInputText sggOrCity = sigunValue;
label={"시설명"} }
initValue={undefined} findRuseFclt(sggOrCity, searchNm);
setValue={(searchNm?: string) => { }
let sggOrCity = ""; }}
if (searchNm) { setRtnData={(rtnData: any) => {
if (sigunValue === "0" || sigunValue === "") { setHasuPk(rtnData);
sggOrCity = sidoValue; }}
} else { searchList={map.ruseFcltList || []}
sggOrCity = sigunValue; />
} <Button
findRuseFclt(sggOrCity, searchNm); color="sky"
} className="ml-2 text-white"
}} size="sm"
setRtnData={(rtnData: any) => { onClick={() => {
setHasuPk(rtnData); onApply();
}} }}
searchList={map.ruseFcltList || []} >
/> 적용
</div> </Button>
<div className="flex flex-row space-x-2">
<Button
color="sky"
className="mt-2"
size={"sm"}
onClick={() => {
onApply();
}}
>
적용
</Button>
</div>
</div>
</SearchFormLeft>
</SearchDiv>
</div>
</div> </div>
</> </div>
); );
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1016 B

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 837 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 923 B

+1
View File
@@ -2,6 +2,7 @@ NEXT_PUBLIC_SITE_ID = FO
NEXT_PUBLIC_PORT = 11300 NEXT_PUBLIC_PORT = 11300
NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11300 NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11300
NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1 NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1
#NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1 NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1
NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
+2 -1
View File
@@ -1,7 +1,8 @@
NEXT_PUBLIC_SITE_ID = FO NEXT_PUBLIC_SITE_ID = FO
NEXT_PUBLIC_PORT = 3000 NEXT_PUBLIC_PORT = 3000
NEXT_PUBLIC_PROXY_HOST = http://localhost:3000 NEXT_PUBLIC_PROXY_HOST = http://localhost:3000
NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1 NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1
#NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1 NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1
NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
+1
View File
@@ -15,6 +15,7 @@ NEXT_PUBLIC_SITE_ID = FO
NEXT_PUBLIC_PORT = 11300 NEXT_PUBLIC_PORT = 11300
NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11300 NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11300
NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1 NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1
#NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1 NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1
NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
+6 -14
View File
@@ -3,6 +3,7 @@
import Gis from "@common/business_components/ui/map/Gis"; import Gis from "@common/business_components/ui/map/Gis";
import GisApp from "@common/business_components/ui/map/lib/gis.core"; import GisApp from "@common/business_components/ui/map/lib/gis.core";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { SearchBar } from "./searchBar/searchBar";
interface FeatureData { interface FeatureData {
fid?: string; fid?: string;
@@ -123,24 +124,15 @@ const Map = () => {
setIsSearchModalOpen(false); setIsSearchModalOpen(false);
}; };
// onCheckedLayersChange handler
const handleCheckedLayersChange = (
checkedIds: string[],
checkedNodesData: unknown[]
) => {
// Handle checked layers change if needed
console.log("Checked layers changed:", checkedIds, checkedNodesData);
};
return ( return (
<div className="bg-container"> <div className="bg-container">
<Gis <Gis
treeData={treeData} treeData={treeData}
isSearch={false} isSearch={false}
gisMaps={gisMaps} gisMaps={gisMaps}
isMiniMap={true} isMiniMap={false}
searchView={null} searchView={undefined}
detailView={null} detailView={undefined}
onFeatureSelect={onFeatureSelect} onFeatureSelect={onFeatureSelect}
setIsOpen={setIsOpen} setIsOpen={setIsOpen}
setFeatureData={setFeatureData} setFeatureData={setFeatureData}
@@ -148,9 +140,9 @@ const Map = () => {
setIsSearchModalOpen={setIsSearchModalOpen} setIsSearchModalOpen={setIsSearchModalOpen}
isOpen={isOpen} isOpen={isOpen}
isSearchModalOpen={isSearchModalOpen} isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={handleCheckedLayersChange} onCheckedLayersChange={undefined}
imgVisble={false} imgVisble={false}
searchBar={undefined} searchBar={(props:any) => <SearchBar onFeatureSelect={onFeatureSelect} gisMaps={gisMaps} />}
/> />
</div> </div>
); );
@@ -0,0 +1,159 @@
"use client";
import { SelectBox, type SelectDataType } from "@common/business_components/ui";
import { Button } from "@common/components/ui";
import { useCallback, useEffect, useState } from "react";
import { GEOSERVER_URL } from "@/src/shared/config/env";
interface SearchBarProps {
onFeatureSelect: (feature: any) => void;
gisMaps: React.RefObject<any>;
}
export function SearchBar({ onFeatureSelect, gisMaps }: SearchBarProps) {
const [sidoValue, setSidoValue] = useState("0");
const [sigunValue, setSigunValue] = useState("0");
const [sidoData, setSidoData] = useState<SelectDataType[]>([{ text: "선택", val: "0" }]);
const [sigunData, setSigunData] = useState<SelectDataType[]>([{ text: "선택", val: "0" }]);
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([{ text: "선택", val: "0" }]);
const [facilityData, setFacilityData] = useState<SelectDataType[]>([{ text: "선택", val: "0" }]);
const [facilityValue, setFacilityValue] = useState("0");
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);
const json = await res.json();
const list = json.features.map((f: any) => ({
text: f.properties.mctpv_korn_nm,
val: f.properties.mctpv_cd,
}));
setSidoData([{ text: "선택", val: "0" }, ...list]);
};
const fetchSigun = 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);
const json = await res.json();
const list = json.features.map((f: any) => ({
text: f.properties.sgg_korn_nm,
val: f.properties.sgg_cd,
}));
setSigunData(list);
};
const fetchFacilitiesBySggNm = async (sggNm: string) => {
const encodedSggNm = encodeURIComponent(sggNm);
const url = `/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_il_swage_prcs_fclt&outputFormat=application/json&cql_filter=sgg_nm='${encodedSggNm}'`;
const res = await fetch(url);
const json = await res.json();
const list = json.features.map((f: any) => ({
text: f.properties.swtrmt_fclt_nm ?? "(이름없음)",
val: f.id, // f.properties.swtrmt_fclt_cd 도 가능
}));
setFacilityData([{ text: "선택", val: "0" }, ...list]);
setFacilityValue("0");
};
useEffect(() => {
fetchSido();
fetchSigun();
}, []);
const initializeSigunData = useCallback(
(sidoCd: string) => {
if (sidoCd === "0") {
setFilteredSigunData([{ text: "선택", val: "0" }]);
setSigunValue("0");
return;
}
const prefix = sidoCd.substring(0, 2);
const filtered = sigunData.filter((item) => item.val.startsWith(prefix));
setFilteredSigunData([{ text: "선택", val: "0" }, ...filtered]);
setSigunValue("0");
},
[sigunData]
);
const onSelectValue = useCallback(
(val: string, type: string) => {
if (type === "ctpvCd") {
setSidoValue(val);
initializeSigunData(val);
setFacilityData([{ text: "선택", val: "0" }]);
} else if (type === "sggCd") {
setSigunValue(val);
const sggNm = sigunData.find((item) => item.val === val)?.text || "";
if (sggNm) fetchFacilitiesBySggNm(sggNm);
} else if (type === "fcltCd") {
setFacilityValue(val);
}
},
[initializeSigunData, sigunData]
);
const handleSearch = () => {
if (!gisMaps.current || facilityValue === "0") return;
const layers = gisMaps.current.getLayers().getArray();
for (const layer of layers) {
const source = layer.getSource?.();
const feature = source?.getFeatureById?.(facilityValue);
if (feature) {
onFeatureSelect(feature);
return;
}
}
alert("해당 처리시설을 지도에서 찾을 수 없습니다.");
};
return (
<div className="grid grid-cols-4 gap-4">
<div className="col-span-4">
<div className="card card-border flex">
<div className="col-span-10 flex flex-row">
<SelectBox
labelClass="w-[40px]"
className="mr-10 w-[200px]"
selectClass="rounded-lg bg-white"
label="시도"
selectArray={sidoData}
onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")}
value={sidoValue}
/>
<SelectBox
labelClass="w-[65px]"
className="mr-10 w-[200px]"
selectClass="rounded-lg bg-white"
label="시군구"
selectArray={filteredSigunData}
onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")}
value={sigunValue}
/>
<SelectBox
labelClass="w-[140px]"
className="mr-3 w-[250px]"
selectClass="rounded-lg bg-white"
label="하수처리시설"
selectArray={facilityData}
onSelectValue={(val) => onSelectValue(val.toString(), "fcltCd")}
value={facilityValue}
/>
</div>
<div className="flex flex-row space-x-2">
<Button
color="primary"
className="mx-2 mt-1 ml-10"
size={"sm"}
onClick={handleSearch}
>
조회
</Button>
</div>
</div>
</div>
</div>
);
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1016 B

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 837 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 923 B

+1 -2
View File
@@ -2,8 +2,7 @@ export const SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "FO";
export const PORT = process.env.NEXT_PUBLIC_PORT || "3000"; export const PORT = process.env.NEXT_PUBLIC_PORT || "3000";
export const PROXY_HOST = process.env.NEXT_PUBLIC_PROXY_HOST || `http://localhost:${PORT}`; export const PROXY_HOST = process.env.NEXT_PUBLIC_PROXY_HOST || `http://localhost:${PORT}`;
export const SERVER_API_WR_URL = process.env.NEXT_PUBLIC_WR_SERVER_API_URL ||'http://con.mind-one.co.kr:11080/api/v1'; export const SERVER_API_WR_URL = process.env.NEXT_PUBLIC_WR_SERVER_API_URL ||'http://con.mind-one.co.kr:11080/api/v1';
//export const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL||' http://con.mind-one.co.kr:11082/api/v1'; export const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL||' http://con.mind-one.co.kr:11082/api/v1';
export const SERVER_API_URL = process.env.NEXT_PUBLIC_CS_SERVER_API_URL||' http://con.mind-one.co.kr:11082/api/v1';
export const SERVER_API_CS_URL = process.env.NEXT_PUBLIC_CS_SERVER_API_URL||'http://dev.onasys.co.kr:8000'; export const SERVER_API_CS_URL = process.env.NEXT_PUBLIC_CS_SERVER_API_URL||'http://dev.onasys.co.kr:8000';
export const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization"; export const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization";
export const ACCESS_TOKEN = process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token"; export const ACCESS_TOKEN = process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token";
Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 837 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

+2
View File
@@ -10,6 +10,8 @@ NEXT_PUBLIC_CLAIM_NAME = Authorization
NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_AUTH_USER_ID = token-id
NEXT_PUBLIC_ACCESS_TOKEN = access-token NEXT_PUBLIC_ACCESS_TOKEN = access-token
NEXT_PUBLIC_REFRESH_TOKEN = refresh-token NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
+29 -18
View File
@@ -5,6 +5,7 @@ import { useEffect, useRef, useState, useCallback } from "react";
import { mapService } from "../service"; import { mapService } from "../service";
import NewWindowPopup from "../newWindowPopup"; import NewWindowPopup from "../newWindowPopup";
import GisApp from "@common/business_components/ui/map/lib/gis.core"; import GisApp from "@common/business_components/ui/map/lib/gis.core";
import { SearchBar } from "../searchBar/searchBar";
const Map = () => { const Map = () => {
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정 const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
@@ -61,24 +62,28 @@ const Map = () => {
// 2. data가 OpenLayers Feature 인스턴스인지 확인 // 2. data가 OpenLayers Feature 인스턴스인지 확인
let foundFeature: any = null; let foundFeature: any = null;
console.log(data) console.log(data);
//console.log(data.getId()) //console.log(data.getId())
let featureId = data.fid; let featureId = data.fid;
const layerName = data.layerNm; const layerName = data.layerNm;
if (layerName === "재이용시설") { if (layerName === "재이용시설") {
// 재이용시설 // 재이용시설
featureId = `${layerName}.${data.searchPk}`; featureId = `${layerName}.${data.searchPk}`;
} else if ( //빗물이용시설, 중수도시설 } else if (
//빗물이용시설, 중수도시설
layerName === "빗물이용시설" || layerName === "빗물이용시설" ||
layerName == "tb_wr_rswtr_fclt" layerName == "tb_wr_rswtr_fclt"
) { ) {
featureId = `${layerName}.${data.bdstInfoSn}`; featureId = `${layerName}.${data.bdstInfoSn}`;
} else if (layerName === "tb_il_swage_prcs_fclt") { //하수처리시설 } else if (layerName === "tb_il_swage_prcs_fclt") {
//하수처리시설
featureId = `${layerName}.${data.bscFctCd}`; featureId = `${layerName}.${data.bscFctCd}`;
} else if (layerName === "tb_wr_htdfl_fclt") { //온배수시설 } else if (layerName === "tb_wr_htdfl_fclt") {
//온배수시설
featureId = `${layerName}.${data.bdstRuseFcltSn}`; featureId = `${layerName}.${data.bdstRuseFcltSn}`;
} else if (layerName == "tb_wr_ruse_fclt_dmd_co") { //수요처 } else if (layerName == "tb_wr_ruse_fclt_dmd_co") {
//수요처
featureId = `${layerName}.${data.dmdInfoId.replaceAll("-", "")}`; featureId = `${layerName}.${data.dmdInfoId.replaceAll("-", "")}`;
} else { } else {
console.warn("해당 레이어에 대한 featureId 매핑 없음", layerName); console.warn("해당 레이어에 대한 featureId 매핑 없음", layerName);
@@ -120,17 +125,6 @@ const Map = () => {
setIsSearchModalOpen(false); setIsSearchModalOpen(false);
}; };
// MapSearchBar 래퍼 컴포넌트
// const CustomMapSearchBar = (props: any) => {
// return (
// <MapSearchBar
// {...props}
// searchType="공급처 후보지 조회"
// showApplyButton={false}
// />
// );
// };
return ( return (
<div className="bg-container"> <div className="bg-container">
<Gis <Gis
@@ -156,7 +150,24 @@ const Map = () => {
isSearchModalOpen={isSearchModalOpen} isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={handleCheckedLayersChange} onCheckedLayersChange={handleCheckedLayersChange}
imgVisble={true} imgVisble={true}
searchBar={undefined} searchBar={(props: any) => {
const facilityTypes = [
{ text: "하수처리시설", val: "tb_il_swage_prcs_fclt" },
{ text: "하수재이용시설", val: "tb_wr_bdst_ruse_fclt" },
{ text: "빗물이용시설", val: "tb_wr_rnwtr_utztn_fclt" },
{ text: "중수도시설", val: "tb_wr_rswtr_fclt" },
{ text: "온배수시설", val: "tb_wr_htdfl_fclt" },
{ text: "수요처", val: "tb_wr_ruse_fclt_dmd_co" },
{ text: "잠재수요처", val: "tb_wr_tmpr_dmd_co" },
];
return (
<SearchBar
onFeatureSelect={onFeatureSelect}
gisMaps={gisMaps}
facilityTypes={facilityTypes}
/>
);
}}
/> />
</div> </div>
); );
+299
View File
@@ -0,0 +1,299 @@
"use client";
import { SelectBox, type SelectDataType } from "@common/business_components/ui";
import { Button } from "@common/components/ui";
import { useCallback, useEffect, useState } from "react";
import { SERVER_API_WR_URL } from "@/src/shared/config";
const geoserverUrl =
"/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&outputFormat=application/json";
const FACILITY_TYPE_URL_MAP: Record<string, string> = {
tb_il_swage_prcs_fclt: geoserverUrl, //하수처리시설
tb_wr_bdst_ruse_fclt:
"/reuse-service/reuseFc/pubDetailList/{year}/{sggCd}/1/1000", //재이용시설 post
tb_wr_rnwtr_utztn_fclt: "/reuse-service/reuseFc/rainyInfoList/1/1000/{sggCd}", //빗물이용시설 get
tb_wr_rswtr_fclt: "/reuse-service/reuseFc/waterreuseFc/1/1000/{sggCd}", //중수도시설 get
tb_wr_htdfl_fclt: "/reuse-service/drain/facilityList/1/1000/{sggCd}", //온배수시설 get
tb_wr_ruse_fclt_dmd_co: "/reuse-service/reuseFc/request/1/1000/{sggCd}", //수요처 get
tb_wr_tmpr_dmd_co:
"/reuse-service/reuseFc/request/temp/1/1000/{sidoNm} {sggNm}", //잠재수요처 get
};
interface SearchBarProps {
onFeatureSelect: (feature: any) => void;
gisMaps: React.RefObject<any>;
facilityTypes: { text: string; val: string }[];
}
export function SearchBar({
onFeatureSelect,
gisMaps,
facilityTypes,
}: SearchBarProps) {
const [sidoValue, setSidoValue] = useState("0");
const [sigunValue, setSigunValue] = useState("0");
const [sidoData, setSidoData] = useState<SelectDataType[]>([
{ text: "선택", val: "0" },
]);
const [sigunData, setSigunData] = useState<SelectDataType[]>([
{ text: "선택", val: "0" },
]);
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([
{ text: "선택", val: "0" },
]);
const [facilityData, setFacilityData] = useState<SelectDataType[]>([
{ text: "선택", val: "0" },
]);
const [facilityValue, setFacilityValue] = useState("0");
const [facilityType, setFacilityType] = useState(
facilityTypes?.length === 1 && facilityTypes[0] ? facilityTypes[0].val : "0"
);
//시도, 시군구 조회
const fetchSido = async () => {
const url = `/cms-service/api/v1/code/cache/ADSTDSTRCD/Y`;
const res = await fetch(url);
const json = await res.json();
const sidoList = json
.filter(
(item: any) =>
typeof item.lwpoCd === "string" && item.lwpoCd.length === 2
)
.map((feature: any) => ({
val: feature.lwpoCd,
text: feature.lwpoCdNm,
}));
setSidoData([{ text: "선택", val: "0" }, ...sidoList]);
const sigunList = json
.filter(
(item: any) =>
typeof item.lwpoCd === "string" && item.lwpoCd.length !== 2
)
.map((feature: any) => ({
val: feature.lwpoCd,
text: feature.lwpoCdNm,
}));
setSigunData(sigunList);
};
//시설명 콤보박스 조회
const fetchFacilitiesBySggNm = async (
sggCd: string,
sggNm: string,
sidoNm: string,
currentFacilityType?: string
) => {
const type = currentFacilityType ?? facilityType;
if (!type || type === "0") return;
const baseUrlTemplate = FACILITY_TYPE_URL_MAP[type];
if (!baseUrlTemplate) return;
const year = new Date().getFullYear().toString();
let url = baseUrlTemplate
.replace("{year}", year)
.replace("{sggCd}", sggCd)
.replace("{sidoNm}", encodeURIComponent(sidoNm))
.replace("{sggNm}", encodeURIComponent(sggNm));
let list: SelectDataType[] = [];
if (
type === "tb_il_swage_prcs_fclt" //하수처리시설
) {
const cqlFilter = `ctpv_nm='${encodeURIComponent(sidoNm)}' AND sgg_nm='${encodeURIComponent(sggNm)}'`;
url = `${url}&typeName=hasudo:${type}&cql_filter=${cqlFilter}`;
const res = await fetch(url);
const json = await res.json();
list = json.features.map((f: any) => ({
text: f.properties.swtrmt_fclt_nm ?? "(이름없음)",
val: f.id,
}));
} else if (
type === "tb_wr_bdst_ruse_fclt" // 재이용시설
) {
//재용에서 참조하는 하수처리시설 목록
const paramsRes = await fetch("/reuse-service/reuseFc/pubFc");
const paramsJson = await paramsRes.json();
const params = paramsJson.data;
const res = await fetch(url, {
method: "POST",
headers: {
accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(params),
});
const json = await res.json();
list = (json.data || []).map((item: any) => ({
text: item.ruseUtztnFcltNm ?? "(이름없음)",
val: item.pbnstRuseFcltId,
}));
} else if (
type === "tb_wr_ruse_fclt_dmd_co" //수요처
) {
const res = await fetch(url);
const json = await res.json();
list = (json.data || []).map((item: any) => ({
text: item.dmdCoNm ?? "(이름없음)",
val: item.dmdInfoId,
}));
} else if (
type === "tb_wr_tmpr_dmd_co" //잠재수요처
) {
// GET 방식
const res = await fetch(url);
const json = await res.json();
list = (json.data || []).map((item: any) => ({
text: item.dmdCoNm ?? "(이름없음)",
val: item.fctryMngSn,
}));
} else {
// GET 방식
const res = await fetch(url);
const json = await res.json();
list = (json.data || []).map((item: any) => ({
text: item.bdstNm ?? "(이름없음)",
val: item.bdstInfoSn,
}));
}
setFacilityData([{ text: "선택", val: "0" }, ...list]);
setFacilityValue("0");
};
//시도, 시군구 초기 데이터 조회
useEffect(() => {
fetchSido();
}, []);
//시군구 데이터 초기화
const initializeSigunData = useCallback(
(sidoCd: string) => {
if (sidoCd === "0") {
setFilteredSigunData([{ text: "선택", val: "0" }]);
setSigunValue("0");
return;
}
const prefix = sidoCd.substring(0, 2);
const filtered = sigunData.filter((item) => item.val.startsWith(prefix));
setFilteredSigunData([{ text: "선택", val: "0" }, ...filtered]);
setSigunValue("0");
},
[sigunData]
);
//콤보박스 선택시 연관 콤보박스 처리
const onSelectValue = useCallback(
(val: string, type: string) => {
if (type === "ctpvCd") {
setSidoValue(val);
initializeSigunData(val);
setFacilityData([{ text: "선택", val: "0" }]);
setFacilityValue("0");
} else if (type === "sggCd") {
setSigunValue(val);
const sidoNm =
sidoData.find((item) => item.val === sidoValue)?.text || "";
const sggNm = sigunData.find((item) => item.val === val)?.text || "";
const sggCd = sigunData.find((item) => item.val === val)?.val || "";
if (sggNm && sidoNm)
fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, facilityType);
} else if (type === "facilityType") {
setFacilityType(val);
setFacilityValue("0");
setFacilityData([{ text: "선택", val: "0" }]);
const sidoNm =
sidoData.find((item) => item.val === sidoValue)?.text || "";
const sggNm =
sigunData.find((item) => item.val === sigunValue)?.text || "";
const sggCd =
sigunData.find((item) => item.val === sigunValue)?.val || "";
if (sggNm && sidoNm) fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, val);
} else if (type === "fcltCd") {
setFacilityValue(val);
}
},
[initializeSigunData, sigunData, sigunValue, sidoData, sidoValue]
);
//조회 버튼 클릭
const handleSearch = () => {
if (!gisMaps.current || facilityValue === "0") return;
const layers = gisMaps.current.getLayers().getArray();
for (const layer of layers) {
const source = layer.getSource?.();
const feature = source?.getFeatureById?.(facilityValue);
if (feature) {
onFeatureSelect(feature);
return;
}
}
alert("해당 처리시설을 지도에서 찾을 수 없습니다.");
};
return (
<div className="grid grid-cols-4 gap-4">
<div className="col-span-4">
<div className="card card-border flex">
<div className="col-span-10 flex flex-row">
{facilityTypes.length > 1 && (
<SelectBox
labelClass="w-[120px]"
className="mr-10 w-[190px]"
selectClass="rounded-lg bg-white"
label="시설물유형"
selectArray={[{ text: "선택", val: "0" }, ...facilityTypes]}
onSelectValue={(val) =>
onSelectValue(val.toString(), "facilityType")
}
value={facilityType}
/>
)}
<SelectBox
labelClass="w-[40px]"
className="mr-10 w-[150px]"
selectClass="rounded-lg bg-white"
label="시도"
selectArray={sidoData}
onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")}
value={sidoValue}
/>
<SelectBox
labelClass="w-[65px]"
className="mr-10 w-[150px]"
selectClass="rounded-lg bg-white"
label="시군구"
selectArray={filteredSigunData}
onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")}
value={sigunValue}
/>
<SelectBox
labelClass="w-[60px]"
className="mr-3 w-[180px]"
selectClass="rounded-lg bg-white"
label="시설명"
selectArray={facilityData}
onSelectValue={(val) => onSelectValue(val.toString(), "fcltCd")}
value={facilityValue}
/>
</div>
<div className="flex flex-row space-x-2">
<Button
color="primary"
className="mx-2 mt-1 ml-5"
size={"sm"}
onClick={handleSearch}
>
조회
</Button>
</div>
</div>
</div>
</div>
);
}
+138 -100
View File
@@ -17,35 +17,45 @@ import {
UiModal, UiModal,
UiTable, UiTable,
type SelectDataType, type SelectDataType,
type SheetParameter,
} from "@common/business_components/ui"; } from "@common/business_components/ui";
import ListDiv from "@common/business_components/ui/list_div"; import ListDiv from "@common/business_components/ui/list_div";
import { Button } from "@common/components/ui"; import { Button, Label } from "@common/components/ui";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Toaster } from "react-hot-toast"; import toast, { Toaster } from "react-hot-toast";
import { useTranslation } from "react-i18next";
export default function RuseCaseMng() { export default function RuseCaseMng() {
const { code, ruseCaseMng, selectRuseCaseMng } = useWrStore((state) => state); const { code, ruseCaseMng, selectRuseCaseMng, ruseCaseMngInit } = useWrStore((state) => state);
const { ruseCaseMngList, status, count } = ruseCaseMng; const { ruseCaseMngList, status, count, excelList } = ruseCaseMng;
const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>([]); const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>([]);
const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" }); const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" });
const [sidoCd, setSidoCd] = useState<string>(""); const [sidoCd, setSidoCd] = useState<string>("");
const [sigunguCd, setSigunguCd] = useState<string>(""); const [sigunguCd, setSigunguCd] = useState<string>("");
const [selectUtz, setSelectUtz] = useState<string[] | []>(["조경용수"]); // const [selectUtz, setSelectUtz] = useState<string[] | []>(["조경용수"]);
const [selectUtz, setSelectUtz] = useState<string>("");
const [name, setName] = useState<string>(""); const [name, setName] = useState<string>("");
const [resetPage, isResetPage] = useState(false);
const { t } = useTranslation('base');
// 물재이용사례 조회 // 물재이용사례 조회
const searchRuseCaseMng = async (pageNo: number, pageSize: number) => { const searchRuseCaseMng = async (isExcel: boolean, pageNo: number, pageSize: number) => {
await selectRuseCaseMng( await selectRuseCaseMng(
isExcel,
pageNo, pageNo,
pageSize, pageSize,
sidoCd ?? "", sidoCd ?? "",
sigunguCd ?? "", sigunguCd ?? "",
selectUtz[0] ?? "", selectUtz ?? "",
name ?? "" name ?? ""
); );
} }
useEffect(() => {
ruseCaseMngInit();
}, [])
// 시도 선택 // 시도 선택
const getSelCity = (val: string | number) => { const getSelCity = (val: string | number) => {
const cityValue = String(val); const cityValue = String(val);
@@ -69,91 +79,97 @@ export default function RuseCaseMng() {
isModal: boolean; isModal: boolean;
}>({ isModal: false }); }>({ isModal: false });
// 다운로드
const download = () => { useEffect(() => {
searchRuseCaseMng(1, 10000);;
if (excelList !== undefined && excelList.length === 0) {
toast.error(t('excelDownErrMsg'));
return;
}
fetch(`/sample_ruseCaseMng.xlsx`, {}).then(async (response) => { fetch(`/sample_ruseCaseMng.xlsx`, {}).then(async (response) => {
if (!response.ok) { if (!response.ok) {
throw Error(response.statusText); throw Error(response.statusText);
} }
// 받은 파일 바인너리 처리 // 받은 파일 바인너리 처리
const blobRet = await response.arrayBuffer(); const blobRet = await response.arrayBuffer();
if(excelList){
const excelData1 = ruseCaseMngList.map((item) => { const excelData1 = excelList.map((item) => {
return [ return [
item.ruseBizNm, item.ruseBizNm,
item.bizBgngDate, item.bizBgngDate,
item.bizEndDate, item.bizEndDate,
item.splyFcltKnd, item.splyFcltKnd,
item.splyFcltNm, item.splyFcltNm,
item.splyFcltCpct, item.splyFcltCpct,
item.dmdFcltNm, item.dmdFcltNm,
item.dmdFcltAddr, item.dmdFcltAddr,
item.ruseFcltCpct, item.ruseFcltCpct,
item.ruseFcltPplLen, item.ruseFcltPplLen,
item.ruseFcltUsg, item.ruseFcltUsg,
item.ruseFcltSplyCpct, item.ruseFcltSplyCpct,
item.ruseFcltPrcsMsrc, item.ruseFcltPrcsMsrc,
item.grnteWqUsg, item.grnteWqUsg,
item.grnteWqGrssClfg, item.grnteWqGrssClfg,
item.grnteWqCombResdch, item.grnteWqCombResdch,
item.grnteWqTrbt, item.grnteWqTrbt,
item.grnteWqBod, item.grnteWqBod,
item.grnteWqSmll, item.grnteWqSmll,
item.grnteWqCrmty, item.grnteWqCrmty,
item.grnteWqTn, item.grnteWqTn,
item.grnteWqTp, item.grnteWqTp,
item.grnteWqPh, item.grnteWqPh,
item.grnteWqClIonQnt, item.grnteWqClIonQnt,
item.grnteWqCndctStat, item.grnteWqCndctStat,
item.grnteWqRmrk, item.grnteWqRmrk,
item.reqBizGrssCst, item.reqBizGrssCst,
item.reqBizReuseFcltCst, item.reqBizReuseFcltCst,
item.reqBizPplCst, item.reqBizPplCst,
item.reqBizArchCst, item.reqBizArchCst,
item.reqBizMchnCst, item.reqBizMchnCst,
item.reqBizMtrngCst, item.reqBizMtrngCst,
item.reqBizGisCst, item.reqBizGisCst,
item.reqBizCctvCst, item.reqBizCctvCst,
item.reqBizCmcnCst, item.reqBizCmcnCst,
item.reqBizWstCst, item.reqBizWstCst,
item.reqBizDesignCst, item.reqBizDesignCst,
item.reqBizSpvsCst, item.reqBizSpvsCst,
item.reqBizSbsdCst, item.reqBizSbsdCst,
item.reqBizEtc, item.reqBizEtc,
item.prcmPlanGrssCst, item.prcmPlanGrssCst,
item.prcmPlanNlep, item.prcmPlanNlep,
item.prcmPlanLcep, item.prcmPlanLcep,
item.prcmPlanBrdnCst, item.prcmPlanBrdnCst,
item.prcmPlanEcqrRt, item.prcmPlanEcqrRt,
item.clclnGrssCst, item.clclnGrssCst,
item.clclnNlep, item.clclnNlep,
item.clclnLcep, item.clclnLcep,
item.clclnBrdnCst, item.clclnBrdnCst,
item.clclnEcqrRt, item.clclnEcqrRt,
]; ];
}); });
const sheetParameter = [ const sheetParameter: SheetParameter[] = [
{ {
data: excelData1, data: excelData1,
headerNum: 2, headerNum: 2,
headerRange: 'A1:AX1', headerRange: 'A1:AX1',
sheetName: '물재이용사례', sheetName: '물재이용사례',
startCol: 'A' startCol: 'A'
}
];
// 버퍼에 있는 엑셀데이터 전달 후 json으로 변환
await filePickedBlobArray(blobRet, '물재이용사례현황', sheetParameter);
} }
];
// 버퍼에 있는 엑셀데이터 전달 후 json으로 변환
await filePickedBlobArray(blobRet, '물재이용사례현황', sheetParameter);
}); });
}
}, [excelList])
// 양식받기 // 양식받기
const sampleDownLoad = () => { const sampleDownLoad = () => {
excelDownload( excelDownload(
[[]], [[]],
"물재이용사례 관리", "물재이용사례 관리 등록양식",
"sample_ruseCaseMng.xlsx", "sample_ruseCaseMng.xlsx",
"A1:AX1", "A1:AX1",
2 2
@@ -161,7 +177,7 @@ export default function RuseCaseMng() {
} }
useEffect(() => { useEffect(() => {
if(status === 200){ if (status === 200) {
setIsExcelOpen({ isModal: false }); setIsExcelOpen({ isModal: false });
} }
}, [status]); }, [status]);
@@ -174,7 +190,7 @@ export default function RuseCaseMng() {
<Title title={"물재이용사례 관리"} /> <Title title={"물재이용사례 관리"} />
<SearchDiv> <SearchDiv>
<SearchFormLeft> <SearchFormLeft>
<div className="col-span-8 flex flex-row"> <div className="col-span-8 flex flex-row items-center">
<SelectBox <SelectBox
className="col-span-2 w-1/3 h-full text-2xl" className="col-span-2 w-1/3 h-full text-2xl"
label="시도" label="시도"
@@ -183,24 +199,28 @@ export default function RuseCaseMng() {
onSelectValue={getSelCity} onSelectValue={getSelCity}
/> />
<SelectBox <SelectBox
className="col-span-2 w-1/3 h-full text-2xl" className="col-span-2 w-1/3 h-full text-2xl ml-4"
label="시군구" label="시군구"
selectClass={""} selectClass={""}
selectArray={selectSgg || []} selectArray={selectSgg || []}
onSelectValue={getSgg} onSelectValue={getSgg}
/> />
<SelectBox <SelectBox
className="col-span-2 w-1/3 h-full text-2xl" className="col-span-2 w-1/3 h-full text-2xl ml-4"
label="용도" label="용도"
selectClass={""} selectClass={""}
selectArray={ruseUtzType} selectArray={ruseUtzType}
onSelectValue={(val) => { onSelectValue={(val) => {
if (val) setSelectUtz([String(val)]); if (val) setSelectUtz(String(val));
}} }}
value={selectUtz || ruseUtzType[0]?.val}
/> />
<Label className="w-28 text-xl ml-4 mr-6">
{"수요처명"}
</Label>
<SearchInput <SearchInput
className={"text-2xl"} className={"col-span-2 w-1/3 text-2xl"}
label="수요처명" label=""
textValue={name} textValue={name}
setText={(e) => { setText={(e) => {
setName(e); setName(e);
@@ -211,13 +231,29 @@ export default function RuseCaseMng() {
<SearchFormRight className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2"> <SearchFormRight className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2">
<div className="space-y"> <div className="space-y">
<div className="flex flex-row space-x-2"> <div className="flex flex-row space-x-2">
<Button size={"sm"} onClick={() => { searchRuseCaseMng(1, 20) }}> <Button size={"sm"} onClick={() => {
searchRuseCaseMng(false, 1, 10);
isResetPage(true);
}}>
조회 조회
</Button> </Button>
<Button size={"sm"} onClick={() => { setIsExcelOpen({ isModal: true }); }}> <Button size={"sm"} onClick={() => { setIsExcelOpen({ isModal: true }); }}>
엑셀등록 엑셀등록
</Button> </Button>
<Button size={"sm"} onClick={() => { download() }}> <Button size={"sm"} onClick={() => {
selectRuseCaseMng(
true,
1,
count,
sidoCd ?? "",
sigunguCd ?? "",
selectUtz ?? "",
name ?? ""
);
// download()
}} className={"green"}>
다운로드 다운로드
</Button> </Button>
<Button size={"sm"} onClick={() => { sampleDownLoad() }}> <Button size={"sm"} onClick={() => { sampleDownLoad() }}>
@@ -239,10 +275,12 @@ export default function RuseCaseMng() {
headlist={ruseCaseMng.ruseHeadList} headlist={ruseCaseMng.ruseHeadList}
pageSize={20} pageSize={20}
total={count} total={count}
cellClick={() => {}} cellClick={undefined}
onPageChange={(pageNo: number) => { onPageChange={(pageNo: number) => {
searchRuseCaseMng(pageNo, 10); searchRuseCaseMng(false, pageNo, 10);
isResetPage(false)
}} }}
resetPageNo={resetPage}
/> />
</div> </div>
</ListDiv> </ListDiv>
@@ -254,9 +292,9 @@ export default function RuseCaseMng() {
<UiModal <UiModal
open={isExcelOpen.isModal} open={isExcelOpen.isModal}
onOpenChange={() => { onOpenChange={() => {
setIsExcelOpen({ isModal: !isExcelOpen.isModal }) setIsExcelOpen({ isModal: !isExcelOpen.isModal })
}} }}
bodyClass={"w-[40vw] max-w-none"} bodyClass={"w-[35vw] max-w-none"}
body={<RuseCaseMngExcel />} body={<RuseCaseMngExcel />}
/> />
</> </>
@@ -1,7 +1,6 @@
import type { SelectDataType } from "@common/business_components/ui"; import type { SelectDataType } from "@common/business_components/ui";
import type { TableUpperProps } from "../../common/type/header"; import type { TableUpperProps } from "../../common/type/header";
//★☆★☆ 여기서부터 수정하기 ★☆★☆
export const ruseCaseHeadListExport: TableUpperProps[] = [ export const ruseCaseHeadListExport: TableUpperProps[] = [
{ {
id: "ruseBizNm", id: "ruseBizNm",
@@ -358,6 +357,7 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [
]; ];
export const ruseUtzType: SelectDataType[] = [ export const ruseUtzType: SelectDataType[] = [
{ text: "전체", val: "0" },
{ text: "조경용수", val: "조경용수" }, { text: "조경용수", val: "조경용수" },
{ text: "청소화장실용수", val: "청소화장실용수" }, { text: "청소화장실용수", val: "청소화장실용수" },
{ text: "세척살수용수", val: "세척살수용수" }, { text: "세척살수용수", val: "세척살수용수" },
+90 -64
View File
@@ -4,90 +4,93 @@ import type { TableUpperProps } from "../../common/type/header";
import { ruseCaseHeadListExport } from "./headList"; import { ruseCaseHeadListExport } from "./headList";
export type ruseCaseState = { export type ruseCaseState = {
ruseCaseMng: { ruseCaseMng: {
ruseHeadList:TableUpperProps[]; ruseHeadList: TableUpperProps[];
ruseCaseMngList:ruseCaseMngExcelProps[]; ruseCaseMngList: ruseCaseMngExcelProps[];
status: number; status: number;
count: number; count: number;
excelList?: ruseCaseMngExcelProps[];
}; };
}; };
export interface ruseCaseMngExcelProps { export interface ruseCaseMngExcelProps {
ruseBizNm: string, ruseBizNm: string,
bizBgngDate: string, bizBgngDate: string,
bizEndDate: string, bizEndDate: string,
splyFcltKnd: string, splyFcltKnd: string,
splyFcltNm: string, splyFcltNm: string,
splyFcltCpct: number | 0 |'', splyFcltCpct: number | 0 | '',
dmdFcltNm: string, dmdFcltNm: string,
dmdFcltAddr: string, dmdFcltAddr: string,
ruseFcltCpct: number | 0 |'', ruseFcltCpct: number | 0 | '',
ruseFcltPplLen: string, ruseFcltPplLen: string,
ruseFcltUsg: string, ruseFcltUsg: string,
ruseFcltSplyCpct: number | 0 |'', ruseFcltSplyCpct: number | 0 | '',
ruseFcltPrcsMsrc: string, ruseFcltPrcsMsrc: string,
grnteWqUsg: string, grnteWqUsg: string,
grnteWqGrssClfg: number | 0 |'', grnteWqGrssClfg: number | 0 | '',
grnteWqCombResdch: number | 0 |'', grnteWqCombResdch: number | 0 | '',
grnteWqTrbt: string, grnteWqTrbt: string,
grnteWqBod: string, grnteWqBod: string,
grnteWqSmll: string, grnteWqSmll: string,
grnteWqCrmty: string, grnteWqCrmty: string,
grnteWqTn: string, grnteWqTn: string,
grnteWqTp: string, grnteWqTp: string,
grnteWqPh: string, grnteWqPh: string,
grnteWqClIonQnt: string, grnteWqClIonQnt: string,
grnteWqCndctStat: string, grnteWqCndctStat: string,
grnteWqRmrk: string, grnteWqRmrk: string,
reqBizReuseFcltCst: number | 0 |'', reqBizReuseFcltCst: number | 0 | '',
reqBizPplCst: number | 0 |'', reqBizPplCst: number | 0 | '',
reqBizArchCst: number | 0 |'', reqBizArchCst: number | 0 | '',
reqBizMchnCst: number | 0 |'', reqBizMchnCst: number | 0 | '',
reqBizMtrngCst: number | 0 |'', reqBizMtrngCst: number | 0 | '',
reqBizGisCst: number | 0 |'', reqBizGisCst: number | 0 | '',
reqBizCctvCst: number | 0 |'', reqBizCctvCst: number | 0 | '',
reqBizCmcnCst: number | 0 |'', reqBizCmcnCst: number | 0 | '',
reqBizWstCst: number | 0 |'', reqBizWstCst: number | 0 | '',
reqBizDesignCst: number | 0 |'', reqBizDesignCst: number | 0 | '',
reqBizSpvsCst: number | 0 |'', reqBizSpvsCst: number | 0 | '',
reqBizSbsdCst: number | 0 |'', reqBizSbsdCst: number | 0 | '',
reqBizEtc: string, reqBizEtc: string,
reqBizGrssCst: number | 0 |'', reqBizGrssCst: number | 0 | '',
prcmPlanNlep: number | 0 |'', prcmPlanNlep: number | 0 | '',
prcmPlanLcep: number | 0 |'', prcmPlanLcep: number | 0 | '',
prcmPlanBrdnCst: number | 0 |'', prcmPlanBrdnCst: number | 0 | '',
prcmPlanEcqrRt: number | 0 |'', prcmPlanEcqrRt: number | 0 | '',
prcmPlanGrssCst: number | 0 |'', prcmPlanGrssCst: number | 0 | '',
clclnNlep: number | 0 |'', clclnNlep: number | 0 | '',
clclnLcep: number | 0 |'', clclnLcep: number | 0 | '',
clclnBrdnCst: number | 0 |'', clclnBrdnCst: number | 0 | '',
clclnEcqrRt: number | 0 |'', clclnEcqrRt: number | 0 | '',
clclnGrssCst: number | 0 |'', clclnGrssCst: number | 0 | '',
} }
type ruseCaseActions = { type ruseCaseActions = {
selectRuseCaseMng: (pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd : string, name: string) => Promise<void>; ruseCaseMngInit: () => void;
selectRuseCaseMng: (isExcel: boolean, pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd: string, name: string) => Promise<void>;
insertRuseCaseMng: (data: any) => Promise<void>; insertRuseCaseMng: (data: any) => Promise<void>;
}; };
export const fetchSearchRuseCaseMng = async (pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd : string, name: string) => { export const fetchSearchRuseCaseMng = async (pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd: string, name: string) => {
const tailUrl = '/'+pageNo+'/'+pageSize+ ((sigunguCd !== '전체' && sigunguCd !=='') ? `/${sigunguCd}` : (sidoCd != '전체' && sidoCd != '') ? `/${sidoCd}` : `/0`) + (usageCd? `/${usageCd}` : '/0') + (name? `/${name}` : ''); const tailUrl = '/' + pageNo + '/' + pageSize + ((sigunguCd !== '전체' && sigunguCd !== '') ? `/${sigunguCd}` : (sidoCd != '전체' && sidoCd != '') ? `/${sidoCd}` : `/0`) + (usageCd ? `/${usageCd}` : '/0') + (name ? `/${name}` : '');
const resp = await getFetchWithCount(fetchType.WR,`/reuseFc/pubFcRuseCase${tailUrl}`); const resp = await getFetchWithCount(fetchType.WR, `/reuseFc/pubFcRuseCase${tailUrl}`);
return {data:resp.data, count:resp.count}; return { data: resp.data, count: resp.count };
}; };
export const fetchInsertRuseCaseMng = async (data: any) => { export const fetchInsertRuseCaseMng = async (data: any) => {
const response = await postFectch('/reuseFc/pubFcRuseCase', data, fetchType.WR); const response = await postFectch('/reuseFc/pubFcRuseCase', data, fetchType.WR);
return {data:response.data, status:response.status}; return { data: response.data, status: response.status };
}; };
export type ruseCaseType = ruseCaseState & ruseCaseActions; export type ruseCaseType = ruseCaseState & ruseCaseActions;
export const ruseCaseStateExport: ruseCaseState = { export const ruseCaseStateExport: ruseCaseState = {
ruseCaseMng: { ruseCaseMng: {
ruseHeadList:ruseCaseHeadListExport, ruseHeadList: ruseCaseHeadListExport,
ruseCaseMngList:[], ruseCaseMngList: [],
status: 0, status: 0,
count: 0, count: 0,
excelList: undefined,
}, },
}; };
@@ -95,8 +98,8 @@ export const ruseCaseActionsExport: (set: any) => ruseCaseActions = (
set: any set: any
) => { ) => {
return { return {
selectRuseCaseMng: async (pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd : string, name: string) => { ruseCaseMngInit: async () => {
const res = await fetchSearchRuseCaseMng(pageNo, pageSize, sidoCd, sigunguCd, usageCd, name); const res = await getFetchWithCount(fetchType.WR, `/reuseFc/pubFcRuseCase/1/10/0/0`);
set((state: ruseCaseState) => ({ set((state: ruseCaseState) => ({
ruseCaseMng: { ruseCaseMng: {
...state.ruseCaseMng, ...state.ruseCaseMng,
@@ -105,12 +108,35 @@ export const ruseCaseActionsExport: (set: any) => ruseCaseActions = (
}, },
})); }));
}, },
selectRuseCaseMng: async (isExcel: boolean, pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd: string, name: string) => {
const res = await fetchSearchRuseCaseMng(pageNo, pageSize, sidoCd, sigunguCd, usageCd, name);
if (isExcel) {
set((state: ruseCaseState) => {
return {
...state,
ruseCaseMng: {
...state.ruseCaseMng,
count: res.count,
excelList: res.data
},
};
});
} else {
set((state: ruseCaseState) => ({
ruseCaseMng: {
...state.ruseCaseMng,
ruseCaseMngList: res.data,
count: res.count,
},
}));
}
},
insertRuseCaseMng: async (data: any) => { insertRuseCaseMng: async (data: any) => {
const response = await fetchInsertRuseCaseMng(data); const response = await fetchInsertRuseCaseMng(data);
if(response.status === 200){ if (response.status === 200) {
toast.success("물재이용사례 등록이 완료되었습니다. "); toast.success("물재이용사례 등록이 완료되었습니다. ");
}else{ } else {
toast.error("물재이용사례 등록에 실패하였습니다. "); toast.error("물재이용사례 등록에 실패하였습니다. ");
} }
set((state: ruseCaseState) => ({ set((state: ruseCaseState) => ({
@@ -6,6 +6,7 @@ import {
isInvalidNumber, isInvalidNumber,
parseRawValue, parseRawValue,
} from "@/components/lib/func/excelFunc"; } from "@/components/lib/func/excelFunc";
import toast from "react-hot-toast";
export const useTypes = [ export const useTypes = [
"조경용수", "조경용수",
"청소화장실용수", "청소화장실용수",
@@ -16,6 +17,18 @@ export const useTypes = [
"기타", "기타",
]; ];
export const chkMngType =[
'위탁',
'자체'
]
export const chkSpwtPositnType = [
'유량계',
'수위계',
'펌프적산전력계',
'기타'
]
export type Buildingtype = { export type Buildingtype = {
bdstInfoSn: number; bdstInfoSn: number;
ctpvCd: 1; ctpvCd: 1;
@@ -143,43 +156,56 @@ export const excelToRainChkData = (excelData: any, year: string) => {
}) })
.filter((el: any) => el !== undefined); .filter((el: any) => el !== undefined);
console.log("rainChkgdData", rainChkgdData);
if (rainChkgdData.length > 0) { if (rainChkgdData.length > 0) {
rainChkgdData.forEach((rain: any) => { rainChkgdData.forEach((rain: any) => {
const defaultMessage = `[시설명: ${rain.bdstNm}, 점검일: ${rain.chckYmd}, 이용용도: ${rain.guideDetails[0]?.utztnUsgCd}] 항목에서 `; const defaultMessage = `[시설명: ${rain.bdstNm}, 점검일: ${rain.chckYmd}, 이용용도: ${rain.guideDetails[0]?.utztnUsgCd}] 항목에서 `;
if (rain.chckYmd && isInvalidDate(rain.chckYmd)) { if (rain.chckYmd && isInvalidDate(rain.chckYmd)) {
issues.push( issues.push(
defaultMessage + `점검일: 날짜 형식 아님 (${rain.chckYmd})` defaultMessage + `점검일: 날짜 형식 아님 입력값 : (${rain.chckYmd})`
); );
} }
if (rain.anlOperCst.length > 2 && isInvalidNumber(rain.anlOperCst)) { if (rain.anlOperCst.length > 2 && isInvalidNumber(rain.anlOperCst)) {
issues.push( issues.push(
defaultMessage + defaultMessage +
` 연간운영비(원/년): 숫자 형식 아님 (${rain.anlOperCst})` ` 연간운영비(원/년): 숫자 형식 아님 /입력값:${rain.anlOperCst}`
); );
} }
if (rain.oilqMeasMthd.length > 1 && !chkMngType.includes(rain.oilqMeasMthd)) {
issues.push(
defaultMessage + `유량측정방법: 위탁 혹은 자체 만 허용) /입력값:${rain.oilqMeasMthd}`
);
}
if (rain.spwtPositn.length > 1 && !chkSpwtPositnType.includes(rain.spwtPositn)) {
issues.push(
defaultMessage + `측정위치: 유량계, 수위계, 펌프적산전력계, 기타 중 하나만 허용) /입력값:${rain.spwtPositn}`
);
}
rain.rainyDetails?.forEach((key) => { rain.rainyDetails?.forEach((key) => {
if (key.ph?.length > 1 && isInvalidNumber(key.ph)) { if (key.ph?.length > 1 && isInvalidNumber(key.ph)) {
issues.push(defaultMessage + `PH: 숫자 형식 아님 (${key.ph})`); issues.push(defaultMessage + `PH: 숫자 형식 아님 /입력값:${key.ph}`);
} }
if (key.trbt?.length > 1 && isInvalidNumber(key.trbt)) { if (key.trbt?.length > 1 && isInvalidNumber(key.trbt)) {
issues.push(defaultMessage + `탁도: 숫자 형식 아님 (${key.trbt})`); issues.push(defaultMessage + `탁도: 숫자 형식 아님 /입력값:${key.trbt}`);
} }
if (key.grssClfg?.length > 1 && isInvalidNumber(key.grssClfg)) { if (key.grssClfg?.length > 1 && isInvalidNumber(key.grssClfg)) {
issues.push( issues.push(
defaultMessage + `총대장균군수: 숫자 형식 아님 (${key.grssClfg})` defaultMessage + `총대장균군수: 숫자 형식 아님 /입력값:${key.grssClfg}`
); );
} }
if (key.levyAmt?.length > 1 && isInvalidNumber(key.levyAmt)) { if (key.levyAmt?.length > 1 && isInvalidNumber(key.levyAmt)) {
issues.push( issues.push(
defaultMessage + defaultMessage +
`과태료부과금액(천원): 숫자 형식 아님 (${key.levyAmt})` `과태료부과금액(천원): 숫자 형식 아님 /입력값:${key.levyAmt}`
); );
} }
if (key.vltnDt?.length > 2 && isInvalidDate(key.vltnDt)) { if (key.vltnDt?.length > 2 && isInvalidDate(key.vltnDt)) {
issues.push( issues.push(
defaultMessage + defaultMessage +
`과태료 부과일(위반일): 날짜 형식 아님 (${key.vltnDt})` `과태료 부과일(위반일): 날짜 형식 아님 /입력값:${key.vltnDt}`
); );
} }
}); });
@@ -188,17 +214,19 @@ export const excelToRainChkData = (excelData: any, year: string) => {
if (isInvalidNumber(guide.splyUswtrUtztnQnt)) { if (isInvalidNumber(guide.splyUswtrUtztnQnt)) {
issues.push( issues.push(
defaultMessage + defaultMessage +
`대체_${mm}: 숫자 형식 아님 (${guide.splyUswtrUtztnQnt})` `대체_${mm}: 숫자 형식 아님 /입력값:${guide.splyUswtrUtztnQnt}`
); );
} }
if (isInvalidNumber(guide.ruseFcltUtztnQnt)) { if (isInvalidNumber(guide.ruseFcltUtztnQnt)) {
issues.push( issues.push(
defaultMessage + defaultMessage +
`빗물_${mm}: 숫자 형식 아님 (${guide.ruseFcltUtztnQnt})` `빗물_${mm}: 숫자 형식 아님 /입력값:${guide.ruseFcltUtztnQnt}`
); );
} }
}); });
}); });
}else{
toast.error("데이터가 없습니다. 점검 데이터를 확인해주세요.");
} }
if (issues.length > 0) { if (issues.length > 0) {
@@ -1,5 +1,10 @@
import type { SelectDataType } from "@common/business_components/ui"; import type { SelectDataType } from "@common/business_components/ui";
import { useTypes, type Buildingtype } from "../rainChkgdn/excel"; import {
chkMngType,
chkSpwtPositnType,
useTypes,
type Buildingtype,
} from "../rainChkgdn/excel";
import { TEST_UUID } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; import { TEST_UUID } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import type { rswtChkZodType } from "."; import type { rswtChkZodType } from ".";
import { import {
@@ -171,14 +176,14 @@ export const excelToRswtGuideData = (
.filter((el: any) => el !== undefined); .filter((el: any) => el !== undefined);
if (rswtChkgdData.length > 0) { if (rswtChkgdData.length > 0) {
rswtExcelDataCheck(rswtChkgdData,issues) rswtExcelDataCheck(rswtChkgdData, issues);
} }
if (issues.length > 0) { if (issues.length > 0) {
return { data: [], issues }; return { data: [], issues };
} }
return { data:rswtChkgdData, issues: [] }; return { data: rswtChkgdData, issues: [] };
}; };
export const excelToRswtPolution = (excelData: any, issues: string[]) => { export const excelToRswtPolution = (excelData: any, issues: string[]) => {
@@ -248,25 +253,25 @@ export const excelToRswtPolution = (excelData: any, issues: string[]) => {
}) })
.filter((el: any) => el !== undefined); .filter((el: any) => el !== undefined);
if (rswtChkgdData.length > 0) { if (rswtChkgdData.length > 0) {
rswtExcelDataCheck(rswtChkgdData,issues) rswtExcelDataCheck(rswtChkgdData, issues);
} }
if (issues.length > 0) { if (issues.length > 0) {
return { data: [], issues }; return { data: [], issues };
} }
return { data:rswtChkgdData, issues: [] }; return { data: rswtChkgdData, issues: [] };
}; };
const rswtExcelDataCheck = (
const rswtExcelDataCheck=(rswtChkgdData:rswtChkZodType[], issues:string[]) =>{ rswtChkgdData: rswtChkZodType[],
issues: string[]
) => {
rswtChkgdData.forEach((rswt: any) => { rswtChkgdData.forEach((rswt: any) => {
const defaultMessage = `*중수도 수질 [NO: ${rswt.bdstRuseFcltSn}, 점검일: ${rswt.chckYmd}, 이용용도: ${rswt.utztnUsgCd}] 항목에서 `; const defaultMessage = `*중수도 수질 [NO: ${rswt.bdstRuseFcltSn}, 점검일: ${rswt.chckYmd}, 이용용도: ${rswt.utztnUsgCd}] 항목에서 `;
if (rswt.chckYmd && isInvalidDate(rswt.chckYmd)) { if (rswt.chckYmd && isInvalidDate(rswt.chckYmd)) {
issues.push( issues.push(defaultMessage + `점검일: 날짜 형식 아님 (${rswt.chckYmd})`);
defaultMessage + `점검일: 날짜 형식 아님 (${rswt.chckYmd})`
);
} }
if (rswt.anlOperCst.length > 2 && isInvalidNumber(rswt.anlOperCst)) { if (rswt.anlOperCst.length > 2 && isInvalidNumber(rswt.anlOperCst)) {
issues.push( issues.push(
@@ -279,6 +284,25 @@ const rswtExcelDataCheck=(rswtChkgdData:rswtChkZodType[], issues:string[]) =>{
defaultMessage + ` 처리용량(m³): 숫자 형식 아님 (${rswt.prcsCpct})` defaultMessage + ` 처리용량(m³): 숫자 형식 아님 (${rswt.prcsCpct})`
); );
} }
if (
rswt.oilqMeasMthd.length > 1 &&
!chkMngType.includes(rswt.oilqMeasMthd)
) {
issues.push(
defaultMessage +
`유량측정방법: 위탁 혹은 자체 만 허용) /입력값:${rswt.oilqMeasMthd}`
);
}
if (
rswt.spwtPositn.length > 1 &&
!chkSpwtPositnType.includes(rswt.spwtPositn)
) {
issues.push(
defaultMessage +
`측정위치: 유량계, 수위계, 펌프적산전력계, 기타 중 하나만 허용) /입력값:${rswt.spwtPositn}`
);
}
if (rswt?.rswtrPollutions && rswt?.rswtrPollutions.length > 0) { if (rswt?.rswtrPollutions && rswt?.rswtrPollutions.length > 0) {
rswt.rswtrPollutions.forEach((pol) => { rswt.rswtrPollutions.forEach((pol) => {
if (isInvalidNumber(pol.grssClfgUswtr)) { if (isInvalidNumber(pol.grssClfgUswtr)) {
@@ -301,8 +325,7 @@ const rswtExcelDataCheck=(rswtChkgdData:rswtChkZodType[], issues:string[]) =>{
} }
if (isInvalidNumber(pol.bod)) { if (isInvalidNumber(pol.bod)) {
issues.push( issues.push(
defaultMessage + defaultMessage + `${pol.node}분기_BOD: 숫자 형식 아님 (${pol.bod})`
`${pol.node}분기_BOD: 숫자 형식 아님 (${pol.bod})`
); );
} }
if (isInvalidNumber(pol.smll)) { if (isInvalidNumber(pol.smll)) {
@@ -319,8 +342,7 @@ const rswtExcelDataCheck=(rswtChkgdData:rswtChkZodType[], issues:string[]) =>{
} }
if (isInvalidNumber(pol.tph)) { if (isInvalidNumber(pol.tph)) {
issues.push( issues.push(
defaultMessage + defaultMessage + `${pol.node}분기_T-P: 숫자 형식 아님 (${pol.tph})`
`${pol.node}분기_T-P: 숫자 형식 아님 (${pol.tph})`
); );
} }
if (isInvalidNumber(pol.tn)) { if (isInvalidNumber(pol.tn)) {
@@ -358,7 +380,5 @@ const rswtExcelDataCheck=(rswtChkgdData:rswtChkZodType[], issues:string[]) =>{
} }
}); });
} }
}) });
};
}
@@ -1,14 +1,14 @@
import { useWrStore } from "@/app/store";
import { coRegExcel } from "@/app/store/matDmd/coSttn/headList"; import { coRegExcel } from "@/app/store/matDmd/coSttn/headList";
import type { ruseCaseMngExcelProps } from "@/app/store/matDmd/ruseCaseMng/index";
import { reUseCaseNames } from "@/app/store/matDmd/ruseCaseMng/excel"; import { reUseCaseNames } from "@/app/store/matDmd/ruseCaseMng/excel";
import { filePicked, FileUpload, FormUi, Title } from "@common/business_components/ui"; import type { ruseCaseMngExcelProps } from "@/app/store/matDmd/ruseCaseMng/index";
import { Form, FormField } from "@common/business_components/ui/form"; import { filePicked, Title } from "@common/business_components/ui";
import { Button } from "@common/components/ui"; import { Form } from "@common/business_components/ui/form";
import { Button, Input } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useState } from "react"; import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import type { z } from "zod"; import type { z } from "zod";
import { useWrStore } from "@/app/store";
export default function RuseCaseMngExcel() { export default function RuseCaseMngExcel() {
const { insertRuseCaseMng } = useWrStore((state) => state); const { insertRuseCaseMng } = useWrStore((state) => state);
@@ -79,6 +79,7 @@ export default function RuseCaseMngExcel() {
}; };
Object.keys(reUseCaseNames).forEach((key: string) => { Object.keys(reUseCaseNames).forEach((key: string) => {
console.log("reUseCaseNames", reUseCaseNames)
if (item && reUseCaseNames[key] !== undefined) { if (item && reUseCaseNames[key] !== undefined) {
const mappedKey = reUseCaseNames[key]; const mappedKey = reUseCaseNames[key];
if (mappedKey && object.hasOwnProperty(mappedKey)) { if (mappedKey && object.hasOwnProperty(mappedKey)) {
@@ -146,23 +147,16 @@ export default function RuseCaseMngExcel() {
</div> </div>
<div> <div>
<FormField <div className="space-y-4 p-4">
control={form.control} <Input
name="file" type="file"
render={() => ( onChange={(e) => {
<FormUi label=""> const selectedFile = e.target.files?.[0];
<FileUpload if (selectedFile) setFiles(selectedFile);
files={file ? [file] : []} }}
setFiles={(files: File[] | null) => { accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
console.log("files", files) />
if (files && files?.length === 1) setFiles(files[0]); </div>
}}
fileTypes=""
maxFiles={1}
></FileUpload>
</FormUi>
)}
/>
</div> </div>
</form> </form>
</Form> </Form>
+11
View File
@@ -8,6 +8,7 @@ const serverApiUrl = CS_URL;
const GEOSERVER_URL = process.env.NEXT_PUBLIC_GEOSERVER_URL || ""; const GEOSERVER_URL = process.env.NEXT_PUBLIC_GEOSERVER_URL || "";
const LX_GEOSERVER_URL = process.env.NEXT_PUBLIC_LX_GEOSERVER_URL || ""; const LX_GEOSERVER_URL = process.env.NEXT_PUBLIC_LX_GEOSERVER_URL || "";
const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ""; const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || "";
const REUSE_API_URL = process.env.NEXT_PUBLIC_WR_SERVER_API_URL || "";
export function middleware(req: NextRequest) { export function middleware(req: NextRequest) {
const pathname = req.nextUrl.pathname; const pathname = req.nextUrl.pathname;
@@ -135,6 +136,15 @@ export function middleware(req: NextRequest) {
return NextResponse.rewrite(new URL(newUrl)); return NextResponse.rewrite(new URL(newUrl));
} }
if (pathname.startsWith("/reuse-service")) {
const queryParams = searchParams.toString();
const newUrl = `${REUSE_API_URL}${pathname.replace("/reuse-service", "")}${
queryParams ? `?${queryParams}` : ""
}`;
console.log(`[Middleware] Rewriting from '/reuse-service' path to: ${newUrl}`);
return NextResponse.rewrite(new URL(newUrl));
}
return NextResponse.next(); return NextResponse.next();
} }
@@ -146,6 +156,7 @@ export const config = {
"/attach-service/:path*", "/attach-service/:path*",
"/geoserver/:path*", "/geoserver/:path*",
"/sewerage-service/:path*", "/sewerage-service/:path*",
"/reuse-service/:path*",
"/" "/"
], ],
}; };
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1016 B

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 837 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 923 B

Binary file not shown.
@@ -1,323 +1,357 @@
import { Search } from 'lucide-react'; import { Search } from "lucide-react";
import OlMap from 'ol/Map'; import OlMap from "ol/Map";
import View from 'ol/View'; import View from "ol/View";
import { defaults as defaultControls, OverviewMap } from 'ol/control'; import { defaults as defaultControls, OverviewMap } from "ol/control";
import { defaults } from 'ol/interaction'; import { defaults } from "ol/interaction";
import * as olProj from 'ol/proj'; import * as olProj from "ol/proj";
import { register } from 'ol/proj/proj4'; import { register } from "ol/proj/proj4";
import proj4 from 'proj4'; import proj4 from "proj4";
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import React, {
import GisMapToggle from './GisMapToggle'; createContext,
import GisSelect from './GisSelect'; useCallback,
import GisToolBar from './GisToolBar'; useContext,
import GisTree from './GisTree'; useEffect,
import './lib/css/gis.common.css'; useMemo,
import GisApp from './lib/gis.core'; useRef,
useState,
} from "react";
import GisMapToggle from "./GisMapToggle";
import GisSelect from "./GisSelect";
import GisToolBar from "./GisToolBar";
import GisTree from "./GisTree";
import "./lib/css/gis.common.css";
import GisApp from "./lib/gis.core";
export const GisContext = createContext({ gisCore: null, gisMap: null, treeData: null, checkedLayerIds: null }); export const GisContext = createContext({
gisCore: null,
gisMap: null,
treeData: null,
checkedLayerIds: null,
});
export const useGisContext = () => useContext(GisContext); export const useGisContext = () => useContext(GisContext);
export const searchUpperPpls = async (fcltMngNo) => { export const searchUpperPpls = async (fcltMngNo) => {
const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ''; const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || "";
const res = await fetch(`${BASE_URL}/v1/fcltmhl/upperPpls/${fcltMngNo}`, {
method: "GET",
});
const res = await fetch(`${BASE_URL}/v1/fcltmhl/upperPpls/${fcltMngNo}`, { let data = null;
method: "GET", if (res.ok) {
}); try {
// 응답이 비어있는지 확인
const contentLength = res.headers.get("content-length");
if (contentLength === "0")
alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
let data = null; // 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인
if (res.ok) { const text = await res.text();
try { if (!text || text.trim() === "")
// 응답이 비어있는지 확인 alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
const contentLength = res.headers.get('content-length');
if (contentLength === '0') alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
// 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인 // JSON 파싱 시도
const text = await res.text(); data = JSON.parse(text);
if (!text || text.trim() === '') alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); } catch (error) {
console.error("JSON parsing error:", error);
alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
}
}
// JSON 파싱 시도 const select = GisApp.Module.select;
data = JSON.parse(text); const gisMap = GisApp.Module.core?.map;
} catch (error) { let foundFeature = null;
console.error('JSON parsing error:', error);
alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
}
}
const select = GisApp.Module.select; data.forEach((item) => {
const pplMngNo = item.ppl_mng_no.trim();
const gisMap = GisApp.Module.core?.map; gisMap
.getLayers()
.getArray()
.filter((e) => e.get("lyrTyNm") == "tb_ms_swage_ppl")
.forEach((layer) => {
const source = layer.getSource?.();
if (source?.getFeatures) {
const features = source.getFeatures();
for (const f of features) {
if (f.values_.fclt_mng_no.trim() === pplMngNo) {
foundFeature = f;
break;
}
}
}
});
let foundFeature = null; if (foundFeature) select.selectMultiFeature(foundFeature, true);
});
data.forEach(item => { if (!foundFeature) alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
const pplMngNo = item.ppl_mng_no.trim();
gisMap.getLayers().getArray().filter(e => e.get("lyrTyNm") == 'tb_ms_swage_ppl').forEach(layer => { let geometry = foundFeature?.getGeometry();
const source = layer.getSource?.(); if (geometry) {
if (source?.getFeatures) { let extent = geometry.getExtent();
const features = source.getFeatures();
for (const f of features) {
if (f.values_.fclt_mng_no.trim() === pplMngNo) {
foundFeature = f;
break;
}
}
}
});
if (foundFeature) select.selectMultiFeature(foundFeature, true); gisMap.getView().fit(extent, {
}); duration: 300,
padding: [50, 50, 50, 50],
if (!foundFeature) alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); maxZoom: 14,
constrainResolution: true,
let geometry = foundFeature?.getGeometry(); nearest: true,
if (geometry) { });
let extent = geometry.getExtent(); }
gisMap.getView().fit(extent, {
duration: 300,
padding: [50, 50, 50, 50],
maxZoom: 14,
constrainResolution: true,
nearest: true,
});
}
}; };
function Gis({ function Gis({
isOpen, //우측 상세 레이어 팝업 isOpen, //우측 상세 레이어 팝업
gisMaps, gisMaps,
isSearchModalOpen, isSearchModalOpen,
treeData = [], treeData = [],
isSearch = false, isSearch = false,
isMiniMap = false, isMiniMap = false,
setIsOpen, setIsOpen,
setFeatureData, setFeatureData,
featureData, featureData,
imgVisble = false, imgVisble = false,
searchView: SearchView, searchView: SearchView,
detailView: DetailView, detailView: DetailView,
onFeatureSelect, onFeatureSelect,
setIsSearchModalOpen, setIsSearchModalOpen,
onCheckedLayersChange, onCheckedLayersChange,
searchBar: SearchBar, searchBar: SearchBar,
waterDepth: WaterDepth, waterDepth: WaterDepth,,
height:height="h-[calc(100vh-10rem)]",
}) { }) {
const mapRef = useRef(); const mapRef = useRef();
const [mapObject, setMapObject] = useState({}); const [mapObject, setMapObject] = useState({});
const [tabVisible, setTabVisible] = useState(true); const [tabVisible, setTabVisible] = useState(true);
const [layerNm, setLayerNm] = useState('') const [layerNm, setLayerNm] = useState("");
// const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 // const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
// const [featureData, setFeatureData] = useState(null) // 선택된 피쳐 데이터 // const [featureData, setFeatureData] = useState(null) // 선택된 피쳐 데이터
// const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태 // const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태
const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []); const gisCore = useMemo(
const mapInstanceRef = useRef(null); () => new GisApp.core({ targetId: mapRef.current }),
const checkedLayerIds = useRef([]); [],
const layerNmRef = useRef('') );
const mapInstanceRef = useRef(null);
const checkedLayerIds = useRef([]);
const layerNmRef = useRef("");
// 팝업 데이터를 저장할 상태와 오버레이 참조를 저장할 ref 추가 // 팝업 데이터를 저장할 상태와 오버레이 참조를 저장할 ref 추가
// const [facilityInfoPopupData, setFacilityInfoPopupData] = useState([]); // const [facilityInfoPopupData, setFacilityInfoPopupData] = useState([]);
// const facilityOverlaysRef = useRef([]); // const facilityOverlaysRef = useRef([]);
// 체크된 레이어 상태 관리 // 체크된 레이어 상태 관리
const [checkedLayers, setCheckedLayers] = useState([]); const [checkedLayers, setCheckedLayers] = useState([]);
const [checkedLayersData, setCheckedLayersData] = useState([]); const [checkedLayersData, setCheckedLayersData] = useState([]);
let gisMap = null; let gisMap = null;
const closePopup = () => { const closePopup = () => {
setIsOpen(false); // 팝업 닫기 setIsOpen(false); // 팝업 닫기
setFeatureData(null) setFeatureData(null);
}; };
// Feature Click Event // Feature Click Event
const handleFeatureSelect = useCallback((data) => { const handleFeatureSelect = useCallback(
onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출 (data) => {
}, [mapInstanceRef]); onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출
},
[mapInstanceRef],
);
// 지도 생성 및 초기화 // 지도 생성 및 초기화
useEffect(() => { useEffect(() => {
if (!mapRef.current) return; if (!mapRef.current) return;
if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단 if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단
// EPSG:5179 등록 // EPSG:5179 등록
proj4.defs([ proj4.defs([
[ [
'EPSG:5179', "EPSG:5179",
'+proj=tmerc +lat_0=38 +lon_0=127.5 +k=0.9996 +x_0=1000000 +y_0=2000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs', "+proj=tmerc +lat_0=38 +lon_0=127.5 +k=0.9996 +x_0=1000000 +y_0=2000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs",
], ],
]); ]);
register(proj4); register(proj4);
// OpenLayers 지도 객체 생성 // OpenLayers 지도 객체 생성
const map = new OlMap({ const map = new OlMap({
layers: [gisCore.baseLayers['base'], gisCore.baseLayers['satellite']], layers: [gisCore.baseLayers["base"], gisCore.baseLayers["satellite"]],
renderer: 'webgl', renderer: "webgl",
target: mapRef.current, target: mapRef.current,
interactions: defaults({ doubleClickZoom: false }), interactions: defaults({ doubleClickZoom: false }),
controls: defaultControls({ controls: defaultControls({
zoom: false, zoom: false,
rotate: false, rotate: false,
attributionOptions: { attributionOptions: {
collapsible: false, collapsible: false,
}, },
}).extend([]), }).extend([]),
view: new View({ view: new View({
projection: GisApp.TO_PROJ, projection: GisApp.TO_PROJ,
zoom: gisCore.defaults.zoom, zoom: gisCore.defaults.zoom,
center: olProj.transform( center: olProj.transform(
[127.4875, 37.4916], [127.4875, 37.4916],
GisApp.FROM_PROJ, GisApp.FROM_PROJ,
GisApp.TO_PROJ GisApp.TO_PROJ,
), ),
minZoom: gisCore.defaults.minZoom, minZoom: gisCore.defaults.minZoom,
maxZoom: gisCore.defaults.maxZoom, maxZoom: gisCore.defaults.maxZoom,
}), }),
}); });
gisMaps.current = map gisMaps.current = map;
// 미니맵 추가 // 미니맵 추가
if (isMiniMap) { if (isMiniMap) {
const overviewMap = new OverviewMap({ const overviewMap = new OverviewMap({
layers: [GisApp.Module.core.baseMiniMapLayers['base']], layers: [GisApp.Module.core.baseMiniMapLayers["base"]],
view: new View({ view: new View({
projection: GisApp.TO_PROJ, projection: GisApp.TO_PROJ,
minZoom: 9, minZoom: 9,
}), }),
collapsed: false, collapsed: false,
collapseLabel: '\u00BB', collapseLabel: "\u00BB",
label: '\u00AB', label: "\u00AB",
}); });
map.addControl(overviewMap); map.addControl(overviewMap);
} }
// 스케일 컨트롤 및 베이스 레이어 설정 // 스케일 컨트롤 및 베이스 레이어 설정
gisCore.__proto__.map = map; gisCore.__proto__.map = map;
gisCore.scaleControl(); gisCore.scaleControl();
gisCore.changeBaseLayer('base'); gisCore.changeBaseLayer("base");
setMapObject({ map: map }); setMapObject({ map: map });
mapInstanceRef.current = map; // 생성 완료 플래그 설정 mapInstanceRef.current = map; // 생성 완료 플래그 설정
}, []);
}, []); // treeData 변경 시 레이어 코드 생성
useEffect(() => {
if (!treeData || treeData.length === 0) return;
// treeData 변경 시 레이어 코드 생성 // 기존 오버레이 및 레이어 모두 삭제
useEffect(() => { gisCore.deleteAllOverlay();
if (!treeData || treeData.length === 0) return; gisCore.deleteAllLayer();
// 기존 오버레이 및 레이어 모두 삭제 // 트리 데이터를 레이어 코드 객체로 변환
gisCore.deleteAllOverlay(); const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, "lyrSn");
gisCore.deleteAllLayer(); GisApp.layerCode = layerCode; // 전역 레이어 코드 설정
// 트리 데이터를 레이어 코드 객체로 변환 //setTreeData(data);
const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, 'lyrSn'); // 편집 가능한 레이어 목록 생성
GisApp.layerCode = layerCode; // 전역 레이어 코드 설정 //const layerItems = getLayerItems(layerCode);
//setEditLayerItems(prev => [...layerItems])
}, [treeData]);
//setTreeData(data); const getLayerItems = (layerCode) => {
// 편집 가능한 레이어 목록 생성 const layerItems = Object.entries(layerCode)
//const layerItems = getLayerItems(layerCode); .filter(([key, obj], idx) => obj?.updtPsblYn === "Y")
//setEditLayerItems(prev => [...layerItems]) .map(([key, obj]) => ({
}, [treeData]); ...obj,
name: obj.lyrNm,
label: obj.lyrNm,
value: obj.lyrSn + "",
}))
.sort((a, b) => {
if (a.sortOrdr > b.sortOrdr) return 1;
if (a.sortOrdr < b.sortOrdr) return -1;
return 0;
});
return layerItems;
};
const getLayerItems = (layerCode) => { if (mapObject.map) {
const layerItems = Object.entries(layerCode) try {
.filter(([key, obj], idx) => obj?.updtPsblYn === 'Y') gisMap = mapObject.map;
.map(([key, obj]) => ({ } catch (e) {
...obj, console.error("오류 발생:", e); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력
name: obj.lyrNm, }
label: obj.lyrNm, }
value: obj.lyrSn + '',
}))
.sort((a, b) => {
if (a.sortOrdr > b.sortOrdr) return 1;
if (a.sortOrdr < b.sortOrdr) return -1;
return 0;
});
return layerItems;
};
if (mapObject.map) { // 체크된 레이어 정보를 처리하는 함수
try { const handleCheckedLayersChange = useCallback(
gisMap = mapObject.map; (checkedIds, checkedNodesData) => {
} catch (e) { setTimeout(() => {
console.error('오류 발생:', e); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력 setCheckedLayers(checkedIds);
} setCheckedLayersData(checkedNodesData);
} onCheckedLayersChange?.(checkedIds, checkedNodesData);
}, 1000);
},
[onCheckedLayersChange],
);
// 체크된 레이어 정보를 처리하는 함수 return (
const handleCheckedLayersChange = useCallback((checkedIds, checkedNodesData) => { <>
setTimeout(() => { <GisContext.Provider
setCheckedLayers(checkedIds); value={{
setCheckedLayersData(checkedNodesData); gisCore,
onCheckedLayersChange?.(checkedIds, checkedNodesData); gisMap: mapInstanceRef.current,
}, 1000); treeData,
}, [onCheckedLayersChange]); checkedLayerIds,
}}
>
<div className={`relative flex ${height}`}>
<div
ref={mapRef}
className="gis-map relative flex-1"
style={{ height: "100%", padding: 0 }}
>
<GisTree
visible={tabVisible}
setVisible={setTabVisible}
onCheckedChange={handleCheckedLayersChange}
onLayerIconClick={(layerNm) => {
layerNmRef.current = layerNm;
setLayerNm(layerNm);
setIsOpen(true);
const dummyFeature = {
getProperties: () => ({}), // 최소 구조
};
onFeatureSelect(dummyFeature);
}}
imageVisible={imgVisble}
/>
return ( {/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */}
<> {isOpen && !isSearchModalOpen && (
<GisContext.Provider value={{ gisCore, gisMap: mapInstanceRef.current, treeData, checkedLayerIds }}> <DetailView
<div className="h-[calc(100vh-10rem)] flex relative"> featureData={featureData || { getProperties: () => ({}) }}
<div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} > seletectedLayerNm={layerNm}
<GisTree onClose={closePopup} // 닫기 콜백 전달
visible={tabVisible} onFeatureSelect={handleFeatureSelect}
setVisible={setTabVisible} />
onCheckedChange={handleCheckedLayersChange} )}
onLayerIconClick={(layerNm) => { {/* SearchView가 존재할 때만 렌더링 */}
layerNmRef.current = layerNm {SearchView && (
setLayerNm(layerNm); setIsOpen(true); const dummyFeature = { <SearchView
getProperties: () => ({}), // 최소 구조 open={isSearchModalOpen}
}; onOpenChange={setIsSearchModalOpen}
onFeatureSelect(dummyFeature); onFeatureSelect={handleFeatureSelect}
}} />
imageVisible={imgVisble} )}
/> {/* 시도/시군구 조회 영역 */}
{SearchBar && (
{/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} <div
{isOpen && !isSearchModalOpen && ( className="absolute left-1/2 top-4 z-30 -translate-x-1/2 rounded w-max"
<DetailView >
featureData={featureData || { getProperties: () => ({}) }} <SearchBar />
seletectedLayerNm={layerNm} </div>
onClose={closePopup} // 닫기 콜백 전달 )}
onFeatureSelect={handleFeatureSelect} <GisToolBar />
/> <GisMapToggle />
)} <GisSelect onFeatureSelect={handleFeatureSelect} />
{/* SearchView가 존재할 때만 렌더링 */}
{SearchView && (
<SearchView
open={isSearchModalOpen}
onOpenChange={setIsSearchModalOpen}
onFeatureSelect={handleFeatureSelect}
/>
)}
{/* 시도/시군구 조회 영역 */}
{SearchBar && (
<div className="absolute top-4 left-1/2 -translate-x-1/2 z-30 bg-gray-300 rounded " style={{ maxWidth: 'calc(100% - 2rem)' }}>
<SearchBar/>
</div>
)}
<GisToolBar />
<GisMapToggle />
<GisSelect onFeatureSelect={handleFeatureSelect} />
{typeof WaterDepth === "function" && <WaterDepth onClose={() => setIsOpen(false)} />} {typeof WaterDepth === "function" && <WaterDepth onClose={() => setIsOpen(false)} />}
</div> </div>
</div> </div>
{isSearch && ( {isSearch && (
<div className="absolute right-8 top-[305px] z-20"> <div className="absolute right-8 top-[305px] z-20">
<button <button
onClick={() => setIsSearchModalOpen(true)} onClick={() => setIsSearchModalOpen(true)}
className="overflow-hidden w-[40px] h-[40px] bg-white className="flex h-[40px] w-[40px] items-center justify-center overflow-hidden rounded border border-[#9aa7b8] bg-white p-0 shadow-[0_0_5px_#999]"
border border-[#9aa7b8] >
rounded <Search size={24} className="text-gray-700" />
shadow-[0_0_5px_#999] </button>
flex items-center justify-center </div>
p-0 )}
" </GisContext.Provider>
> </>
<Search size={24} className="text-gray-700" /> );
</button>
</div>
)}
</GisContext.Provider>
</>
);
} }
export default React.memo(Gis); export default React.memo(Gis);
@@ -196,7 +196,7 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
return ( return (
<> <>
<div <div
className={`absolute top-0 left-0 w-[250px] h-[calc(100vh-10rem)] bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-40 className={`absolute top-0 left-0 w-[250px] h-full bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-40
${visible ? 'translate-x-0 opacity-100' : '-translate-x-full opacity-0 pointer-events-none'}`} ${visible ? 'translate-x-0 opacity-100' : '-translate-x-full opacity-0 pointer-events-none'}`}
> >
<div <div
@@ -143,11 +143,11 @@ GisOlLayer.prototype = {
let source; let source;
let lyrTyNm = option.lyrTyNm; let lyrTyNm = option.lyrTyNm;
if(lyrTyNm == 'tb_ms_flood_dngr_map' if(lyrTyNm == 'tb_ms_flood_dngr_map' //침수위험지도
|| lyrTyNm == 'tb_ms_sbmrg_dngr_map' || lyrTyNm == 'tb_ms_sbmrg_dngr_map' //도시침수지도
|| lyrTyNm == 'tb_ms_swage_mhl' || lyrTyNm == 'tb_ms_swage_mhl' //맨홀
|| lyrTyNm == 'tb_ms_swage_ppl' || lyrTyNm == 'tb_ms_swage_ppl' //관로
|| lyrTyNm == 'tb_ms_swage_pmstn' || lyrTyNm == 'tb_ms_swage_pmstn' //펌프장
) { ) {
source = this.getVectorSourceBbox(option); source = this.getVectorSourceBbox(option);
}else { }else {