Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev
# Conflicts: # apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx
This commit is contained in:
@@ -12,6 +12,8 @@ import {
|
||||
TriangleAlert,
|
||||
Waves,
|
||||
} from "lucide-react";
|
||||
import { getRawJson } from "@/components/lib/api";
|
||||
import { createPredictionAnalysisTimeRange } from "@/features/fldngPredcPrst/model/analysisTimeRange";
|
||||
import {
|
||||
windowPopupDefinitions,
|
||||
type WindowPopupKey,
|
||||
@@ -120,6 +122,32 @@ export default function AnalysisPopupMenu({
|
||||
const [activeKey, setActiveKey] = useState<
|
||||
WindowPopupKey | "infoSearch" | null
|
||||
>(initialActiveKey);
|
||||
const [seriousPredictionCount, setSeriousPredictionCount] = useState(0);
|
||||
|
||||
// 침수예측현황 팝업의 기본 분석시각 범위와 동일한 범위로 메뉴 건수를 조회한다.
|
||||
useEffect(() => {
|
||||
const range = createPredictionAnalysisTimeRange();
|
||||
const params = new URLSearchParams({
|
||||
analysisFrom: range.analysisFrom,
|
||||
analysisTo: range.analysisTo,
|
||||
});
|
||||
let cancelled = false;
|
||||
void getRawJson(
|
||||
`/sewerage-service/api/v1/fldng/predc/prst/count?${params.toString()}`,
|
||||
)
|
||||
.then((response) => {
|
||||
if (cancelled) return;
|
||||
const payload = response?.data ?? response;
|
||||
const count = Number(payload);
|
||||
setSeriousPredictionCount(Number.isFinite(count) ? count : 0);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setSeriousPredictionCount(0);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isGisEditActive && activeKey === "gisEditPopup") {
|
||||
@@ -171,6 +199,10 @@ export default function AnalysisPopupMenu({
|
||||
item.key === "gisEditPopup"
|
||||
? isGisEditActive
|
||||
: activeKey === item.key;
|
||||
const label =
|
||||
item.key === "fldngPredcPrst"
|
||||
? `침수예측현황(${seriousPredictionCount}건)`
|
||||
: item.label;
|
||||
|
||||
return (
|
||||
<button
|
||||
@@ -218,7 +250,7 @@ export default function AnalysisPopupMenu({
|
||||
}`}
|
||||
/>
|
||||
) : null}
|
||||
<span className="whitespace-nowrap">{item.label}</span>
|
||||
<span className="whitespace-nowrap">{label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -519,6 +519,15 @@ export const FldngWtlvMapCol2 = [
|
||||
{ id: 'rainTot', title: '총강우량(mm)' },
|
||||
{ id: 'rainStr', title: '강우강도(mm/hr)' },
|
||||
];
|
||||
export const FldngDpthMapCol = [
|
||||
{ id: 'fldng_yr', title: '침수년도' },
|
||||
{ id: 'ctpv_nm', title: '시도명' },
|
||||
{ id: 'sgg_nm', title: '시군구명' },
|
||||
{ id: 'fldng_dpth', title: '침수깊이' },
|
||||
{ id: 'fldng_area', title: '침수면적' },
|
||||
{ id: 'shape_area', title: '형상면적' },
|
||||
{ id: 'shape_len', title: '형상길이' },
|
||||
];
|
||||
export const FldngWtlvMapExcelCol = {
|
||||
'fldngYr': '침수년도',
|
||||
'fldngDstrNm': '침수재해명',
|
||||
|
||||
@@ -83,8 +83,14 @@ const resolveRegionNames = (
|
||||
sggOptions: Array<{ text: string; val: string | number }>,
|
||||
): FldngHstryTableRow => ({
|
||||
...row,
|
||||
ctpvNm: resolveOptionText(row.ctpvCd ?? "", ctpvOptions, 2) || row.ctpvNm,
|
||||
sggNm: resolveOptionText(row.sggCd ?? "", sggOptions, 5) || row.sggNm,
|
||||
ctpvNm:
|
||||
resolveOptionText(row.ctpvCd ?? "", ctpvOptions, 2) ||
|
||||
row.ctpvNm ||
|
||||
String((row as any).ctpv_nm ?? row.ctpvCd ?? "").trim(),
|
||||
sggNm:
|
||||
resolveOptionText(row.sggCd ?? "", sggOptions, 5) ||
|
||||
row.sggNm ||
|
||||
String((row as any).sgg_nm ?? row.sggCd ?? "").trim(),
|
||||
});
|
||||
|
||||
export default function FldngHstryPopupPage() {
|
||||
@@ -148,8 +154,14 @@ export default function FldngHstryPopupPage() {
|
||||
const hydrated = attachFldngHstryGeometry(item);
|
||||
return {
|
||||
...hydrated,
|
||||
ctpvNm: resolveOptionText(hydrated.ctpvCd ?? "", ctpv as any, 2),
|
||||
sggNm: resolveOptionText(hydrated.sggCd ?? "", sgg as any, 5),
|
||||
ctpvNm:
|
||||
resolveOptionText(hydrated.ctpvCd ?? "", ctpv as any, 2) ||
|
||||
String((hydrated as any).ctpvNm ?? (hydrated as any).ctpv_nm ?? "").trim() ||
|
||||
String(hydrated.ctpvCd ?? "").trim(),
|
||||
sggNm:
|
||||
resolveOptionText(hydrated.sggCd ?? "", sgg as any, 5) ||
|
||||
String((hydrated as any).sggNm ?? (hydrated as any).sgg_nm ?? "").trim() ||
|
||||
String(hydrated.sggCd ?? "").trim(),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ export const FLOOD_HISTORY_POPUP_KEY = "fldngHstryPopup";
|
||||
export const FLDNG_HSTRY_ROW_SELECT_MESSAGE = "SN_FLOOD_HISTORY_ROW_SELECT";
|
||||
export const FLOOD_HISTORY_LAYER_TYPE = "tb_ms_fldng_wtlv_map";
|
||||
export const FLOOD_HISTORY_PREVIEW_LAYER_TYPE = "tb_sn_flood_trace_mock";
|
||||
export const FLOOD_HISTORY_LAYER_NAME = "침수흔적도";
|
||||
export const FLOOD_HISTORY_LAYER_NAME = "침수흔적수위";
|
||||
export const FLOOD_HISTORY_AREA_LAYER_ID = "SN_FLOOD_HISTORY_AREA";
|
||||
export const FLOOD_HISTORY_DEPTH_LAYER_ID = "SN_FLOOD_HISTORY_DEPTH";
|
||||
export const FLDNG_HSTRY_PAGE_SIZE = 10;
|
||||
@@ -119,13 +119,13 @@ export const FLDNG_HSTRY_DEPTH_LEGEND_ITEMS = [
|
||||
] as const;
|
||||
|
||||
export const FLDNG_HSTRY_LIST_HEADLIST: UiHeadProps[] = [
|
||||
{ id: "rowNo", title: "순번", widthPercent: 5 },
|
||||
{ id: "floodYearDisplay", title: "침수\n년도", widthPercent: 6 },
|
||||
{ id: "rowNo", title: "순번", widthPercent: 3 },
|
||||
{ id: "floodYearDisplay", title: "침수\n년도", widthPercent: 4 },
|
||||
{ id: "ctpvNm", title: "시도", widthPercent: 7 },
|
||||
{ id: "sggNm", title: "시군구", widthPercent: 8 },
|
||||
{ id: "disasterName", title: "침수재해명", widthPercent: 14 },
|
||||
{ id: "rainBgngTmDisplay", title: "강우\n시작일시", widthPercent: 10 },
|
||||
{ id: "rainEndTmDisplay", title: "강우\n종료일시", widthPercent: 10 },
|
||||
{ id: "disasterName", title: "침수재해명", widthPercent: 20 },
|
||||
{ id: "rainBgngTmDisplay", title: "강우\n시작일시", widthPercent: 9 },
|
||||
{ id: "rainEndTmDisplay", title: "강우\n종료일시", widthPercent: 9 },
|
||||
{ id: "rainTotDisplay", title: "총강우량(mm)", widthPercent: 8 },
|
||||
{ id: "rainStrDisplay", title: "강우강도(mm/hr)", widthPercent: 8 },
|
||||
{ id: "floodHourDisplay", title: "침수시간", widthPercent: 7 },
|
||||
|
||||
@@ -35,6 +35,7 @@ import { getLayerContentComponent } from "../common";
|
||||
import {
|
||||
FldngWtlvMapCol,
|
||||
FldngWtlvMapCol2,
|
||||
FldngDpthMapCol,
|
||||
SwtrmtFcltCol,
|
||||
SwtrmtFcltCol2,
|
||||
SwtrmtPrcsZoneCol,
|
||||
@@ -181,6 +182,13 @@ const FeatureClickEventPopup = ({
|
||||
new Map<string, Promise<any | null>>(),
|
||||
);
|
||||
const [mngInstOptions, setMngInstOptions] = useState<MngInstOption[]>([]);
|
||||
const [floodDepthRows, setFloodDepthRows] = useState<
|
||||
Record<string, unknown>[]
|
||||
>([]);
|
||||
const [selectedFloodDepthYear, setSelectedFloodDepthYear] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const floodDepthYearListRef = useRef<HTMLDivElement>(null);
|
||||
const { ctpv, sgg, fetchCtpv, fetchSgg } = useStore();
|
||||
|
||||
// 🎯 관로관리번호 추출
|
||||
@@ -469,7 +477,7 @@ const FeatureClickEventPopup = ({
|
||||
break;
|
||||
}
|
||||
case "tb_ms_fldng_wtlv_map": {
|
||||
result = "침수흔적도";
|
||||
result = "침수흔적수위";
|
||||
setHeadlist(FldngWtlvMapCol);
|
||||
setLayerSpecificNm(
|
||||
normalizeFldngWtlvMapDisplayId(
|
||||
@@ -478,6 +486,19 @@ const FeatureClickEventPopup = ({
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "tb_ms_fldng_dpth_map": {
|
||||
result = "침수흔적깊이";
|
||||
setHeadlist(FldngDpthMapCol);
|
||||
setLayerSpecificNm(
|
||||
String(
|
||||
featureData.values_?.data_sn ??
|
||||
featureData.data_sn ??
|
||||
featureId ??
|
||||
"",
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "tb_ms_stofcbr": {
|
||||
result = "우수토실";
|
||||
setHeadlist(stofcbrCol);
|
||||
@@ -542,11 +563,24 @@ const FeatureClickEventPopup = ({
|
||||
break;
|
||||
}
|
||||
case "tb_ms_fldng_wtlv_map": {
|
||||
result = "침수흔적도";
|
||||
result = "침수흔적수위";
|
||||
setHeadlist(FldngWtlvMapCol2);
|
||||
setLayerSpecificNm(normalizeFldngWtlvMapDisplayId(featureData.fid));
|
||||
break;
|
||||
}
|
||||
case "tb_ms_fldng_dpth_map": {
|
||||
result = "침수흔적깊이";
|
||||
setHeadlist(FldngDpthMapCol);
|
||||
setLayerSpecificNm(
|
||||
String(
|
||||
featureData.dataSn ??
|
||||
featureData.data_sn ??
|
||||
featureData.fid ??
|
||||
"",
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "tb_ms_stofcbr": {
|
||||
result = "우수토실";
|
||||
setHeadlist(stofcbrCol2);
|
||||
@@ -599,13 +633,14 @@ const FeatureClickEventPopup = ({
|
||||
|
||||
// 🎯 기본 데이터 포맷팅
|
||||
const normalizedRegionData =
|
||||
selectedLayerName === "침수흔적도"
|
||||
selectedLayerName === "침수흔적수위" ||
|
||||
selectedLayerName === "침수흔적깊이"
|
||||
? withResolvedRegionNames(data)
|
||||
: data;
|
||||
const formattedData = withMngInstName(
|
||||
formatDisplayData(normalizedRegionData),
|
||||
);
|
||||
if (selectedLayerName === "침수흔적도") {
|
||||
if (selectedLayerName === "침수흔적수위") {
|
||||
const fldngBgngDtm = formatFldngWtlvDateTime(
|
||||
formattedData.bgngYmd ??
|
||||
formattedData.bgng_ymd ??
|
||||
@@ -688,7 +723,7 @@ const FeatureClickEventPopup = ({
|
||||
case "하수처리구역":
|
||||
setButtonChk(false);
|
||||
break;
|
||||
case "침수흔적도":
|
||||
case "침수흔적수위":
|
||||
setButtonChk(false);
|
||||
break;
|
||||
case "우수토실":
|
||||
@@ -818,7 +853,7 @@ const FeatureClickEventPopup = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedLayerName === "침수흔적도" && featureId) {
|
||||
if (selectedLayerName === "침수흔적수위" && featureId) {
|
||||
try {
|
||||
const normalizedFloodMapFid =
|
||||
normalizeFldngWtlvMapApiFid(featureId);
|
||||
@@ -836,6 +871,30 @@ const FeatureClickEventPopup = ({
|
||||
setFetchData(mergedData);
|
||||
setTableData(mergedData);
|
||||
}
|
||||
const depthDataSn = Number(
|
||||
data?.data_sn ??
|
||||
data?.dataSn ??
|
||||
formattedData?.data_sn ??
|
||||
formattedData?.dataSn,
|
||||
);
|
||||
if (Number.isFinite(depthDataSn)) {
|
||||
const depthRows = await getData(
|
||||
`v1/fldng/hstry/depth-map/${encodeURIComponent(String(depthDataSn))}`,
|
||||
);
|
||||
const normalizedDepthRows = Array.isArray(depthRows)
|
||||
? depthRows.filter((row) => row && typeof row === "object")
|
||||
: [];
|
||||
setFloodDepthRows(normalizedDepthRows);
|
||||
const years = normalizedDepthRows
|
||||
.map((row) =>
|
||||
String(row.fldng_yr ?? row.fldngYr ?? row.yr ?? "").trim(),
|
||||
)
|
||||
.filter(Boolean);
|
||||
setSelectedFloodDepthYear(years[0] ?? null);
|
||||
} else {
|
||||
setFloodDepthRows([]);
|
||||
setSelectedFloodDepthYear(null);
|
||||
}
|
||||
} catch (rainError) {
|
||||
console.warn("침수강우분석 조회 실패:", rainError);
|
||||
}
|
||||
@@ -1074,6 +1133,88 @@ const FeatureClickEventPopup = ({
|
||||
total2={0}
|
||||
isEditable2={buttonChk}
|
||||
/>
|
||||
{layerNm === "침수흔적수위" && floodDepthRows.length > 0 && (
|
||||
<div className="mx-4 mb-4 rounded-md border border-border bg-background p-3">
|
||||
<div className="mb-2 flex items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="이전 연도"
|
||||
className="shrink-0 px-1 text-blue-600"
|
||||
onClick={() =>
|
||||
floodDepthYearListRef.current?.scrollBy({
|
||||
left: -120,
|
||||
behavior: "smooth",
|
||||
})
|
||||
}
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
<div
|
||||
ref={floodDepthYearListRef}
|
||||
className="flex min-w-0 flex-1 gap-2 overflow-x-auto whitespace-nowrap"
|
||||
>
|
||||
{Array.from(
|
||||
new Set(
|
||||
floodDepthRows
|
||||
.map((row) =>
|
||||
String(
|
||||
row.fldng_yr ?? row.fldngYr ?? row.yr ?? "",
|
||||
).trim(),
|
||||
)
|
||||
.filter(Boolean),
|
||||
),
|
||||
).map((year) => (
|
||||
<button
|
||||
key={year}
|
||||
type="button"
|
||||
onClick={() => setSelectedFloodDepthYear(year)}
|
||||
className={`rounded-full border px-3 py-1 text-sm ${selectedFloodDepthYear === year ? "border-blue-600 bg-blue-600 text-white" : "border-blue-400 bg-transparent text-blue-600"}`}
|
||||
>
|
||||
{year}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="다음 연도"
|
||||
className="shrink-0 px-1 text-blue-600"
|
||||
onClick={() =>
|
||||
floodDepthYearListRef.current?.scrollBy({
|
||||
left: 120,
|
||||
behavior: "smooth",
|
||||
})
|
||||
}
|
||||
>
|
||||
›
|
||||
</button>
|
||||
</div>
|
||||
<div className="max-h-64 overflow-auto">
|
||||
<table className="w-full border-collapse text-sm">
|
||||
<tbody>
|
||||
{Object.entries(
|
||||
floodDepthRows.find(
|
||||
(row) =>
|
||||
String(
|
||||
row.fldng_yr ?? row.fldngYr ?? row.yr ?? "",
|
||||
).trim() === selectedFloodDepthYear,
|
||||
) ?? {},
|
||||
)
|
||||
.filter(([key]) => !key.toLowerCase().includes("geom"))
|
||||
.map(([key, value]) => (
|
||||
<tr key={key}>
|
||||
<th className="border border-border bg-secondary px-2 py-1 text-left">
|
||||
{key}
|
||||
</th>
|
||||
<td className="border border-border px-2 py-1">
|
||||
{String(value ?? "-")}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{ContentComponent && (
|
||||
<ContentComponent
|
||||
featureData={fetchData}
|
||||
|
||||
@@ -33,6 +33,7 @@ import { usePathname, useSearchParams } from "next/navigation";
|
||||
import Collection from "ol/Collection.js";
|
||||
import Feature from "ol/Feature.js";
|
||||
import GeoJSON from "ol/format/GeoJSON.js";
|
||||
import Overlay from "ol/Overlay.js";
|
||||
import CircleGeom from "ol/geom/Circle.js";
|
||||
import Draw from "ol/interaction/Draw.js";
|
||||
import Snap from "ol/interaction/Snap.js";
|
||||
@@ -145,6 +146,7 @@ import {
|
||||
FLOOD_HISTORY_DEPTH_LAYER_ID,
|
||||
FLOOD_HISTORY_PREVIEW_LAYER_TYPE,
|
||||
FLOOD_HISTORY_LAYER_TYPE,
|
||||
FLOOD_HISTORY_LAYER_NAME,
|
||||
FLOOD_HISTORY_POPUP_KEY,
|
||||
FLDNG_HSTRY_ROW_SELECT_MESSAGE,
|
||||
isFldngHstryNode,
|
||||
@@ -203,6 +205,15 @@ const EXMN_GRADE_COLORS: Record<number, string> = {
|
||||
};
|
||||
const EXMN_RESULT_MANHOLE_ICON_SRC =
|
||||
"/images/map/layericon/SN_EXMN_MANHOLE_PURPLE.png?v=3";
|
||||
const FLOOD_PREDICTION_GRID_LAYER_ID = "SN_FLDNG_PREDICTION_GRID_LABEL";
|
||||
|
||||
/** 침수예측 오버레이용 12자리 분석시각을 화면 표시 형식으로 변환한다. */
|
||||
const formatFloodPredictionDateTime = (value: unknown) => {
|
||||
const text = String(value ?? "").trim();
|
||||
return /^\d{12}$/.test(text)
|
||||
? `${text.slice(0, 4)}-${text.slice(4, 6)}-${text.slice(6, 8)} ${text.slice(8, 10)}:${text.slice(10, 12)}`
|
||||
: text || "-";
|
||||
};
|
||||
|
||||
const getExmnGradeColor = (feature: any) => {
|
||||
const grade = Number(feature?.get?.("structuralGrade") ?? 0);
|
||||
@@ -228,6 +239,7 @@ const createExmnManholeStyle = () =>
|
||||
}),
|
||||
});
|
||||
|
||||
/** 선택한 도시침수지도 중앙에 침수예측 분석값을 표시하는 오버레이 스타일이다. */
|
||||
// 유하방향은 GeoServer WMS/WFS 레이어가 아니라 BE 보정 GeoJSON API로 그리는 가상 레이어이다.
|
||||
const FLOW_DIRECTION_LAYER_TYPE = "tb_sn_swage_ppl_flow_direction";
|
||||
const FLOW_DIRECTION_LAYER_NAME = "유하방향";
|
||||
@@ -1065,7 +1077,9 @@ const getRightPanelFeatureTitle = (data: any) => {
|
||||
case "tb_ms_swage_prcs_zone":
|
||||
return "처리구역";
|
||||
case "tb_ms_fldng_wtlv_map":
|
||||
return "침수흔적도";
|
||||
return FLOOD_HISTORY_LAYER_NAME;
|
||||
case "tb_ms_fldng_dpth_map":
|
||||
return "침수흔적깊이";
|
||||
case "om_dt_untrt_swg_rain":
|
||||
return "중점우수";
|
||||
default:
|
||||
@@ -3209,6 +3223,8 @@ const Map = () => {
|
||||
const waterRiskPipeSourceRef = useRef<VectorSource | null>(null);
|
||||
const waterRiskManholeLayerRef = useRef<VectorLayer<any> | null>(null);
|
||||
const waterRiskManholeSourceRef = useRef<VectorSource | null>(null);
|
||||
// 침수예측현황 선택행에 따라 초단기예보 격자 중앙 라벨을 교체하는 전용 overlay를 보관한다.
|
||||
const floodPredictionGridLayerRef = useRef<Overlay | null>(null);
|
||||
const gisEditDrawRef = useRef<any | null>(null);
|
||||
const gisEditDrawSnapRef = useRef<any | null>(null);
|
||||
const gisEditTranslateRef = useRef<any | null>(null);
|
||||
@@ -3483,6 +3499,63 @@ const Map = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
/** 기존 침수예측 중앙 오버레이를 제거한다. */
|
||||
const removeFloodPredictionGridLayer = useCallback(() => {
|
||||
const core = GisApp.Module.core as any;
|
||||
core?.deleteLayer?.(FLOOD_PREDICTION_GRID_LAYER_ID);
|
||||
const map = gisMaps.current;
|
||||
if (map && floodPredictionGridLayerRef.current) {
|
||||
map.removeOverlay(floodPredictionGridLayerRef.current);
|
||||
}
|
||||
floodPredictionGridLayerRef.current = null;
|
||||
}, []);
|
||||
|
||||
/** 선택행의 시군구·분석시각으로 도시침수지도 중앙 오버레이를 표시한다. */
|
||||
const loadFloodPredictionGridLayer = useCallback(
|
||||
async (sggCd: string, analysisDateTime: string, extent: number[]) => {
|
||||
const map = gisMaps.current;
|
||||
if (!map || !/^\d{5}$/.test(sggCd) || !/^\d{12}$/.test(analysisDateTime)) return;
|
||||
removeFloodPredictionGridLayer();
|
||||
try {
|
||||
const response = await getRawJson(
|
||||
`/sewerage-service/api/v1/fldng/predc/prst/grid-map?sggCd=${encodeURIComponent(sggCd)}&analysisDateTime=${encodeURIComponent(analysisDateTime)}`,
|
||||
);
|
||||
const payload = response?.data ?? response;
|
||||
const row = payload && !Array.isArray(payload) ? payload : null;
|
||||
if (!row || extent.length !== 4 || extent.some((value) => !Number.isFinite(value))) return;
|
||||
const center = [
|
||||
(extent[0]! + extent[2]!) / 2,
|
||||
(extent[1]! + extent[3]!) / 2,
|
||||
];
|
||||
const observed = Number(row.observedAccumulatedRainfall ?? 0).toFixed(1);
|
||||
const predicted = Number(row.predictedAccumulatedRainfall ?? 0).toFixed(1);
|
||||
const container = document.createElement("div");
|
||||
container.style.cssText = "position:relative; background:rgba(255,255,255,.55); color:#111827; border:1px solid rgba(17,24,39,.7); box-shadow:0 2px 8px rgba(0,0,0,.2); padding:8px 10px; font:400 11px/1.35 sans-serif; min-width:230px;";
|
||||
const close = document.createElement("button");
|
||||
close.type = "button";
|
||||
close.textContent = "×";
|
||||
close.title = "침수예측 오버레이 닫기";
|
||||
close.style.cssText = "position:absolute; right:3px; top:1px; border:0; background:transparent; color:#111827; font-size:16px; line-height:16px; cursor:pointer;";
|
||||
close.addEventListener("click", () => removeFloodPredictionGridLayer());
|
||||
const table = document.createElement("table");
|
||||
table.style.cssText = "border-collapse:collapse; width:100%;";
|
||||
const title = table.createTHead().insertRow();
|
||||
const titleCell = title.insertCell(); titleCell.colSpan = 2; titleCell.textContent = `${row.ctpvNm ?? row.ctpvCd ?? "-"} ${row.sggNm ?? row.sggCd ?? "-"} 침수예측`; titleCell.style.cssText = "font-weight:500; text-align:center; padding:2px 16px 6px 2px; border-bottom:1px solid #9ca3af;";
|
||||
const body = table.createTBody();
|
||||
[["분석시각", formatFloodPredictionDateTime(row.analysisDateTime ?? analysisDateTime)], ["강우시작시각", formatFloodPredictionDateTime(row.rainBeginningDateTime)], ["관측 누적강수량(mm)", observed], ["예측시각", formatFloodPredictionDateTime(row.predictionDateTime)], ["예측 누적강수량(mm)", predicted], ["초과 빈도", `${row.exceededFrequencyYear ?? "-"}년`], ["침수위험도", row.floodRiskGrade ?? "-"]].forEach(([label, value]) => { const tr = body.insertRow(); const key = tr.insertCell(); key.textContent = label; key.style.cssText = "font-weight:400; padding:2px 8px 2px 2px; white-space:nowrap; border-bottom:1px solid #e5e7eb;"; const val = tr.insertCell(); val.textContent = value; val.style.cssText = "font-weight:400; text-align:right; padding:2px 2px; white-space:nowrap; border-bottom:1px solid #e5e7eb;"; });
|
||||
container.append(close, table);
|
||||
const overlay = new Overlay({ element: container, positioning: "center-center", stopEvent: true });
|
||||
overlay.set("id", FLOOD_PREDICTION_GRID_LAYER_ID);
|
||||
overlay.setPosition(center);
|
||||
map.addOverlay(overlay);
|
||||
floodPredictionGridLayerRef.current = overlay;
|
||||
} catch (error) {
|
||||
console.error("침수예측지도 오버레이 표시 실패:", error);
|
||||
}
|
||||
},
|
||||
[removeFloodPredictionGridLayer],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
@@ -3602,6 +3675,7 @@ const Map = () => {
|
||||
: [];
|
||||
const sggCd = String(data.payload?.sggCd ?? "").trim();
|
||||
const frequency = String(data.payload?.sbmrgFrqExpln ?? "").trim();
|
||||
const analysisDateTime = String(data.payload?.analysisDateTime ?? "").trim();
|
||||
const map = gisMaps.current;
|
||||
const select = GisApp.Module.select as any;
|
||||
if (
|
||||
@@ -3654,7 +3728,7 @@ const Map = () => {
|
||||
);
|
||||
});
|
||||
if (!floodLayerVisible) {
|
||||
event.source?.postMessage?.(
|
||||
(event.source as Window | null)?.postMessage?.(
|
||||
{ type: WINDOW_POPUP_MESSAGE_TYPE.MAP_SEARCH_FLOOD_LAYER_REQUIRED },
|
||||
event.origin,
|
||||
);
|
||||
@@ -3665,6 +3739,7 @@ const Map = () => {
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 16,
|
||||
});
|
||||
void loadFloodPredictionGridLayer(sggCd, analysisDateTime, extent);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3703,7 +3778,7 @@ const Map = () => {
|
||||
window.clearInterval(timer);
|
||||
window.removeEventListener("message", handlePopupMessage);
|
||||
};
|
||||
}, []);
|
||||
}, [loadFloodPredictionGridLayer]);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldShowGisEditSession) return;
|
||||
@@ -5174,7 +5249,7 @@ const Map = () => {
|
||||
zIndex: 9993,
|
||||
});
|
||||
layer.set("id", FLOOD_HISTORY_AREA_LAYER_ID);
|
||||
layer.set("lyrNm", "침수흔적도");
|
||||
layer.set("lyrNm", FLOOD_HISTORY_LAYER_NAME);
|
||||
layer.set("lyrTyNm", FLOOD_HISTORY_PREVIEW_LAYER_TYPE);
|
||||
layer.set("clickPsblYn", "N");
|
||||
gisMaps.current.addLayer(layer);
|
||||
@@ -5194,7 +5269,7 @@ const Map = () => {
|
||||
zIndex: 9992,
|
||||
});
|
||||
layer.set("id", FLOOD_HISTORY_DEPTH_LAYER_ID);
|
||||
layer.set("lyrNm", "침수흔적도");
|
||||
layer.set("lyrNm", FLOOD_HISTORY_LAYER_NAME);
|
||||
layer.set("lyrTyNm", FLOOD_HISTORY_PREVIEW_LAYER_TYPE);
|
||||
layer.set("clickPsblYn", "N");
|
||||
gisMaps.current.addLayer(layer);
|
||||
@@ -6621,7 +6696,7 @@ const Map = () => {
|
||||
...baseLayerCode,
|
||||
id: FLDNG_HSTRY_WFS_LAYER_ID,
|
||||
lyrSn: FLDNG_HSTRY_WFS_LAYER_ID,
|
||||
lyrNm: "침수흔적도",
|
||||
lyrNm: FLOOD_HISTORY_LAYER_NAME,
|
||||
lyrTyNm: FLOOD_HISTORY_LAYER_TYPE,
|
||||
actlLyrYn: "Y",
|
||||
exprsYn: "Y",
|
||||
@@ -6632,7 +6707,7 @@ const Map = () => {
|
||||
|
||||
const layer = core?.addWfsLayer?.(layerCode);
|
||||
layer?.set?.("id", FLDNG_HSTRY_WFS_LAYER_ID);
|
||||
layer?.set?.("lyrNm", "침수흔적도");
|
||||
layer?.set?.("lyrNm", FLOOD_HISTORY_LAYER_NAME);
|
||||
layer?.set?.("lyrTyNm", FLOOD_HISTORY_LAYER_TYPE);
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -26,7 +26,8 @@ export const BASIC_TYPES = [
|
||||
{ val: "tb_ms_swage_ppl", text: "하수관로" },
|
||||
{ val: "tb_il_swage_prcs_fclt", text: "하수처리시설" },
|
||||
{ val: "tb_ms_swage_prcs_zone", text: "하수처리구역" },
|
||||
{ val: "tb_ms_fldng_wtlv_map", text: "침수흔적도" },
|
||||
{ val: "tb_ms_fldng_wtlv_map", text: "침수흔적수위" },
|
||||
{ val: "tb_ms_fldng_dpth_map", text: "침수흔적깊이" },
|
||||
{ val: "tb_ms_swage_mhl", text: "하수맨홀" },
|
||||
{ val: "tb_ms_swage_pmstn", text: "하수펌프장" },
|
||||
{ val: "tb_ms_stofcbr", text: "우수토실" },
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# SFR-011-008 침수예측지도 격자 표시 검증계획
|
||||
|
||||
- 침수예측현황 행 클릭 시 기존 지도 extent 이동 확인
|
||||
- `grid-map` API의 시군구·분석시각 파라미터와 WFS `latc_cd` 필터 확인
|
||||
- 각 격자 중앙 `latc_cd`·침수위험도·현재/예측 누적강우 표시 확인
|
||||
- 행 재선택 시 기존 overlay 제거 및 신규 격자 갱신 확인
|
||||
- BE 단위 테스트·컴파일 및 SN 브라우저 콘솔 오류 확인
|
||||
@@ -31,6 +31,9 @@
|
||||
- `ListDiv` 내부에서 최소 테이블 폭과 가로 스크롤을 제공한다.
|
||||
- Excel은 현재 적용 검색조건을 동일하게 전달한다.
|
||||
- 신규 파일과 함수에 화면/API 역할 한글 주석을 작성한다.
|
||||
- 결과표는 `시도, 시군구, 분석시각, 강우시작시각, 관측 누적강수량, 예측시간, 예측 누적강수량, 초과 빈도(년), 침수위험도` 순서로 표시한다.
|
||||
- 예측시간은 `N시간 후`, 관측·예측 누적강수량은 소수점 한 자리로 표시한다.
|
||||
- 지도화면 분석도구는 공용 분석시각 범위와 `위험` 조건의 count API를 조회하여 `침수예측현황(x건)`으로 표시한다.
|
||||
|
||||
## 지도 연계
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# SFR-011-008 침수예측지도 중앙 오버레이 표시 FE 작업계획
|
||||
|
||||
## 목적
|
||||
|
||||
침수예측현황 선택행으로 지도 이동 시 선택한 도시침수지도 중앙에 사각 HTML table 단일 오버레이를 표시한다. 제목은 `[시도] [시군구] 침수예측` 두 열 병합이며, 본문은 분석시각, 강우시작시각, 관측 누적강수량(mm), 예측시각, 예측 누적강수량(mm), 초과 빈도(년), 침수위험도 순서다. 격자별 라벨과 위치 마커는 표시하지 않는다.
|
||||
|
||||
## 구현 방법
|
||||
|
||||
- 현황 팝업 메시지에 `analysisDateTime`을 추가한다.
|
||||
- 지도 페이지에서 BE `grid-map` API를 호출한다.
|
||||
- 기존 도시침수지도 extent 중심 좌표에 위 9개 표시 항목을 table 행으로 렌더링한다.
|
||||
- 초단기예보격자망 WFS 폴리곤은 기존 레이어 표현을 유지한다.
|
||||
- 다음 선택 시 전용 중앙 overlay를 삭제하고 새 응답으로 갱신한다.
|
||||
|
||||
현재·예측 누적강우는 FE에서 격자별로 계산하지 않는다. BE가 반환하는 선택 시군구 분석행의 `obsrvn_acml_rn`, `predc_acml_rn`을 사용하며, 선택 도시침수지도 중앙의 단일 table overlay에 표시한다.
|
||||
|
||||
## 검증
|
||||
|
||||
- 슬라이드 4의 선택 시군구 도시침수지도 중앙 정보 배치
|
||||
- 다른 행 선택 시 중앙 overlay 교체
|
||||
- 닫기 버튼 클릭 시 overlay 제거
|
||||
- 기존 지도 extent 이동·침수위험 레이어 선택 유지
|
||||
- API 오류 시 기존 지도 이동 기능 유지
|
||||
@@ -0,0 +1,28 @@
|
||||
# SFR-011-009 FE 침수흔적도-깊이도 연계 작업계획
|
||||
|
||||
## 1. 상태
|
||||
|
||||
- 승인 대기
|
||||
- 구현 전 계획 문서
|
||||
- 범위: `apps/sn` 내부
|
||||
|
||||
## 2. 화면 계획
|
||||
|
||||
- 기존 침수흔적도 기본정보 상세 영역 우측에 침수깊이지도 연도 버튼 목록을 추가한다.
|
||||
- 버튼은 투명 배경, 테두리만 있는 라운드 형태로 기존 상세정보 UI와 조화되게 구현한다.
|
||||
- 선택 연도의 깊이도 항목은 기본정보 아래 별도 div와 table로 표시한다.
|
||||
- 상세 div 우측 상단에 `X` 닫기 버튼을 둔다.
|
||||
- 지도 시설물 클릭과 침수발생이력 선택에서 동일 상태 모델을 사용한다.
|
||||
- 현재 표시 중인 연도 버튼은 파란색으로 표시한다.
|
||||
- 연도가 많으면 좌우 화살표로 연도 버튼 목록을 가로 스크롤한다.
|
||||
- 연도 선택 시 해당 침수흔적도-깊이 feature를 지도에서 선택 상태로 표시한다.
|
||||
|
||||
## 3. 구현 후보
|
||||
|
||||
- `app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx`
|
||||
- `app/(map-layout)/map/components/fldngHstry/**`
|
||||
- 관련 침수흔적도 타입/API 호출부
|
||||
|
||||
## 4. 검증
|
||||
|
||||
- 연도 중복 제거·정렬, 선택 연도 파란색 상태, 좌우 스크롤, 연도 전환, 닫기, 빈 데이터, 지도 feature 선택, 양쪽 진입 경로 및 기존 상세정보 회귀를 브라우저에서 확인한다.
|
||||
@@ -82,5 +82,6 @@
|
||||
- 관로분석 메시지 처리 함수에 두었던 한정 보강을 제거하고 `FeatureClickEventPopup`의 하수관로 상세 공통 로딩 흐름으로 이동했다.
|
||||
- 지도 피처 클릭, 정보검색/관로현황 연계 등 진입 경로와 관계없이 시설관리번호·관리기관코드가 유효하면 `v1/fclts?includeFlowDirectionBasis=true` 한 건을 조회한다.
|
||||
- 기존 피처 정보와 자체입력정보를 먼저 병합한 뒤 유하방향 응답을 최종 병합해 기존 상세 필드를 잃지 않도록 했다.
|
||||
- 동일 관로의 상세 효과가 개발 모드에서 반복 실행돼도 시설관리번호·관리기관코드별 요청 Promise를 재사용해 중복 API 호출을 막는다. 실패한 요청은 캐시에서 제거해 재시도를 허용한다.
|
||||
- 대상 관로 `2015303665`의 화면 표출값은 `시종점 관저고`이며, 계산코드는 `DEPTH`이다.
|
||||
- 수정 파일은 `app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx`이며, `map/page.tsx`는 관로 선택 행을 그대로 상세 컴포넌트에 전달한다.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# SFR-011-008 침수예측지도 격자 표시 FE 구현기록
|
||||
|
||||
## 구현 내용
|
||||
|
||||
- 침수예측현황 선택행의 `analysisDateTime`을 지도 이동 메시지에 포함했다.
|
||||
- BE `grid-map` 응답의 `latcCd`를 GeoServer `tb_ms_shtm_frcst_latc_info` WFS CQL 필터로 사용한다.
|
||||
- 격자 폴리곤 내부점에 `latc_cd`, 침수위험도, 현재·예측 누적강우 라벨을 표시한다.
|
||||
- `SN_FLDNG_PREDICTION_GRID_LABEL` 전용 레이어로 재선택 시 오버레이를 교체한다.
|
||||
|
||||
## 설계 변경 대기
|
||||
|
||||
사용자 확인에 따라 최종 구현 대상은 격자별 라벨이 아니라 선택한 도시침수지도 중앙의 단일 오버레이로 변경되었다. 기존 격자별 구현은 중앙 오버레이 구현으로 교체한다.
|
||||
|
||||
## 기준
|
||||
|
||||
- `침수예측.pptx` 4페이지
|
||||
- 요구사항 `SFR-011-008`
|
||||
@@ -0,0 +1,9 @@
|
||||
/** 침수예측현황 목록과 분석도구 메뉴가 공유하는 기본 분석시각 범위다. */
|
||||
export const createPredictionAnalysisTimeRange = (now = new Date()) => {
|
||||
const from = new Date(now);
|
||||
from.setHours(0, 0, 0, 0);
|
||||
const to = new Date(now);
|
||||
const toApiDateTime = (value: Date) =>
|
||||
`${value.getFullYear()}${String(value.getMonth() + 1).padStart(2, "0")}${String(value.getDate()).padStart(2, "0")}${String(value.getHours()).padStart(2, "0")}${String(value.getMinutes()).padStart(2, "0")}`;
|
||||
return { analysisFrom: toApiDateTime(from), analysisTo: toApiDateTime(to) };
|
||||
};
|
||||
@@ -17,6 +17,7 @@ import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import useStore from "@/store/useStore";
|
||||
import { createPredictionAnalysisTimeRange } from "../model/analysisTimeRange";
|
||||
|
||||
type ApiRow = {
|
||||
sggCd: string;
|
||||
@@ -26,8 +27,8 @@ type ApiRow = {
|
||||
rainBeginningDateTime: string;
|
||||
analysisDateTime: string;
|
||||
observedAccumulatedRainfall: number | null;
|
||||
rainHour: number | null;
|
||||
rainfall: number | null;
|
||||
predictionHour: number | null;
|
||||
predictedAccumulatedRainfall: number | null;
|
||||
exceededFrequencyYear: number | null;
|
||||
floodRiskGrade: string;
|
||||
};
|
||||
@@ -35,11 +36,11 @@ type ApiRow = {
|
||||
type TableRow = ApiRow &
|
||||
Record<string, unknown> & {
|
||||
rowNo: number;
|
||||
rainBeginningDateTimeDisplay: string;
|
||||
analysisDateTimeDisplay: string;
|
||||
rainBeginningDateTimeDisplay: string;
|
||||
observedAccumulatedRainfallDisplay: string;
|
||||
rainHourDisplay: string;
|
||||
rainfallDisplay: string;
|
||||
predictionHourDisplay: string;
|
||||
predictedAccumulatedRainfallDisplay: string;
|
||||
exceededFrequencyYearDisplay: string;
|
||||
};
|
||||
|
||||
@@ -76,8 +77,8 @@ const HEADS: UiHeadProps[] = [
|
||||
},
|
||||
{
|
||||
id: "rainBeginningDateTimeDisplay",
|
||||
title: "강우시각",
|
||||
widthPercent: 13,
|
||||
title: "강우시작시각",
|
||||
widthPercent: 16,
|
||||
},
|
||||
{
|
||||
id: "observedAccumulatedRainfallDisplay",
|
||||
@@ -86,14 +87,14 @@ const HEADS: UiHeadProps[] = [
|
||||
align: "right",
|
||||
},
|
||||
{
|
||||
id: "rainHourDisplay",
|
||||
title: "강우시간",
|
||||
widthPercent: 8,
|
||||
id: "predictionHourDisplay",
|
||||
title: "예측시간",
|
||||
widthPercent: 9,
|
||||
},
|
||||
{
|
||||
id: "rainfallDisplay",
|
||||
title: "강수량(mm)",
|
||||
widthPercent: 10,
|
||||
id: "predictedAccumulatedRainfallDisplay",
|
||||
title: "예측 누적강수량(mm)",
|
||||
widthPercent: 15,
|
||||
align: "right",
|
||||
},
|
||||
{
|
||||
@@ -102,25 +103,21 @@ const HEADS: UiHeadProps[] = [
|
||||
widthPercent: 10,
|
||||
align: "right",
|
||||
},
|
||||
{ id: "floodRiskGrade", title: "위험등급", widthPercent: 10 },
|
||||
{ id: "floodRiskGrade", title: "침수위험도", widthPercent: 10 },
|
||||
];
|
||||
|
||||
/** 브라우저 로컬시각을 datetime-local 입력값으로 변환한다. */
|
||||
const localDateTime = (date: Date) =>
|
||||
`${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}T${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
||||
|
||||
/** 최초·초기화 조회조건은 금일 분석결과를 기준으로 생성한다. */
|
||||
const buildInitialFilters = (): Filters => {
|
||||
const now = new Date();
|
||||
const from = new Date(now);
|
||||
from.setHours(0, 0, 0, 0);
|
||||
const range = createPredictionAnalysisTimeRange();
|
||||
const toDateTimeLocal = (value: string) =>
|
||||
`${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}T${value.slice(8, 10)}:${value.slice(10, 12)}`;
|
||||
return {
|
||||
ctpvCd: "0",
|
||||
sggCd: "0",
|
||||
rainBgngFrom: "",
|
||||
rainBgngTo: "",
|
||||
analysisFrom: localDateTime(from),
|
||||
analysisTo: localDateTime(now),
|
||||
analysisFrom: toDateTimeLocal(range.analysisFrom),
|
||||
analysisTo: toDateTimeLocal(range.analysisTo),
|
||||
frequencyYear: "",
|
||||
};
|
||||
};
|
||||
@@ -132,9 +129,10 @@ const displayDate = (value: string) =>
|
||||
? `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)} ${value.slice(8, 10)}:${value.slice(10, 12)}`
|
||||
: value || "-";
|
||||
|
||||
const rainHourText = (value: number | null) => {
|
||||
/** 분석시각 기준 예측시간을 사용자 표시문구로 변환한다. */
|
||||
const predictionHourText = (value: number | null) => {
|
||||
const hour = Number(value ?? 0);
|
||||
return hour === 0 ? "현재" : `${hour > 0 ? "+" : "-"}${Math.abs(hour)}시간`;
|
||||
return hour > 0 ? `${hour}시간 후` : "-";
|
||||
};
|
||||
|
||||
const formatNumber = (value: number | null, digits: number) =>
|
||||
@@ -189,7 +187,7 @@ const validateFilters = (filters: Filters) => {
|
||||
};
|
||||
|
||||
const buildRowId = (row: ApiRow) =>
|
||||
`${row.ctpvCd}:${row.sggCd}:${row.rainBeginningDateTime}:${row.analysisDateTime}:${row.rainHour}`;
|
||||
`${row.ctpvCd}:${row.sggCd}:${row.rainBeginningDateTime}:${row.analysisDateTime}:${row.predictionHour}`;
|
||||
|
||||
const isDangerGrade = (value: string) => {
|
||||
const grade = String(value ?? "")
|
||||
@@ -262,16 +260,19 @@ export default function FldngPredcPrstPopupContent() {
|
||||
content.map((item) => ({
|
||||
...item,
|
||||
rowNo: 0,
|
||||
analysisDateTimeDisplay: displayDate(item.analysisDateTime),
|
||||
rainBeginningDateTimeDisplay: displayDate(
|
||||
item.rainBeginningDateTime,
|
||||
),
|
||||
analysisDateTimeDisplay: displayDate(item.analysisDateTime),
|
||||
observedAccumulatedRainfallDisplay: formatNumber(
|
||||
item.observedAccumulatedRainfall,
|
||||
4,
|
||||
1,
|
||||
),
|
||||
predictionHourDisplay: predictionHourText(item.predictionHour),
|
||||
predictedAccumulatedRainfallDisplay: formatNumber(
|
||||
item.predictedAccumulatedRainfall,
|
||||
1,
|
||||
),
|
||||
rainHourDisplay: rainHourText(item.rainHour),
|
||||
rainfallDisplay: formatNumber(item.rainfall, 1),
|
||||
exceededFrequencyYearDisplay:
|
||||
item.exceededFrequencyYear == null
|
||||
? "-"
|
||||
@@ -359,7 +360,12 @@ export default function FldngPredcPrstPopupContent() {
|
||||
window.opener.postMessage(
|
||||
{
|
||||
type: WINDOW_POPUP_MESSAGE_TYPE.MAP_SEARCH_SHOW_FLOOD_GROUP,
|
||||
payload: { extent, sggCd: row.sggCd, sbmrgFrqExpln },
|
||||
payload: {
|
||||
extent,
|
||||
sggCd: row.sggCd,
|
||||
sbmrgFrqExpln,
|
||||
analysisDateTime: row.analysisDateTime,
|
||||
},
|
||||
},
|
||||
window.location.origin,
|
||||
);
|
||||
|
||||
@@ -186,7 +186,7 @@ export const windowPopupDefinitions = {
|
||||
key: "fldngPredcPrst",
|
||||
title: "침수예측현황",
|
||||
width: 1700,
|
||||
height: 900,
|
||||
height: 820,
|
||||
},
|
||||
agedPipeStatusByCtpvPopup: {
|
||||
key: "agedPipeStatusByCtpvPopup",
|
||||
@@ -197,7 +197,7 @@ export const windowPopupDefinitions = {
|
||||
fldngHstryPopup: {
|
||||
key: "fldngHstryPopup",
|
||||
title: "침수발생이력",
|
||||
width: 1160,
|
||||
width: 1700,
|
||||
height: 820,
|
||||
},
|
||||
gisEditPopup: {
|
||||
|
||||
Reference in New Issue
Block a user