sn: 침수흔적위선 심선/위선 모두 나오도록

This commit is contained in:
2026-08-31 23:16:57 +09:00
parent 4a3945f1e4
commit 3cdcf6a6f7
3 changed files with 199 additions and 86 deletions
@@ -186,10 +186,17 @@ const FeatureClickEventPopup = ({
const [floodDepthRows, setFloodDepthRows] = useState<
Record<string, unknown>[]
>([]);
const [selectedFloodDepthYear, setSelectedFloodDepthYear] = useState<
const [floodWtlvRows, setFloodWtlvRows] = useState<Record<string, unknown>[]>(
[],
);
const [selectedFloodDepthFid, setSelectedFloodDepthFid] = useState<
string | null
>(null);
const [selectedFloodWtlvFid, setSelectedFloodWtlvFid] = useState<
string | null
>(null);
const floodDepthYearListRef = useRef<HTMLDivElement>(null);
const floodWtlvListRef = useRef<HTMLDivElement>(null);
const { ctpv, sgg, fetchCtpv, fetchSgg } = useStore();
// 🎯 관로관리번호 추출
@@ -850,7 +857,9 @@ const FeatureClickEventPopup = ({
if (selectedLayerName === "침수흔적위선" && featureId) {
const depthLookupKey = normalizeFldngWtlvMapApiFid(featureId);
setFloodDepthRows([]);
setSelectedFloodDepthYear(null);
setFloodWtlvRows([]);
setSelectedFloodDepthFid(null);
setSelectedFloodWtlvFid(null);
try {
const normalizedFloodMapFid =
normalizeFldngWtlvMapApiFid(featureId);
@@ -877,18 +886,25 @@ const FeatureClickEventPopup = ({
`v1/fldng/hstry/depth-map/${encodeURIComponent(depthLookupKey)}`,
);
const depthPayload = depthRows?.data ?? depthRows;
const normalizedDepthRows = Array.isArray(depthPayload)
? depthPayload.filter((row) => row && typeof row === "object")
const depthItems = Array.isArray(depthPayload)
? depthPayload
: depthPayload?.depthMaps;
const waterLevelItems = Array.isArray(
depthPayload?.waterLevelMaps,
)
? depthPayload.waterLevelMaps
: [];
const normalizedDepthRows = Array.isArray(depthItems)
? depthItems.filter((row) => row && typeof row === "object")
: [];
const normalizedWaterLevelRows = waterLevelItems.filter(
(row: unknown) => row && typeof row === "object",
);
setFloodDepthRows(normalizedDepthRows);
const years = normalizedDepthRows
.map((row) =>
String(row.fldng_yr ?? row.fldngYr ?? row.yr ?? "").trim(),
)
.filter(Boolean);
console.log("depth years:", years);
// 최초에는 연도를 선택하지 않고 목록만 표시한다.
setSelectedFloodDepthYear(null);
setFloodWtlvRows(normalizedWaterLevelRows);
// 최초에는 연계 항목을 선택하지 않고 버튼 목록만 표시한다.
setSelectedFloodDepthFid(null);
setSelectedFloodWtlvFid(null);
}
} catch (depthError) {
console.warn("침수깊이지도 조회 실패:", depthError);
@@ -1097,82 +1113,153 @@ const FeatureClickEventPopup = ({
>
<VerticalTable
headName={
layerNm === "침수흔적위선" && floodDepthRows.length > 0 ? (
<div className="flex flex-col items-start gap-2">
layerNm === "침수흔적위선" &&
(floodDepthRows.length > 0 || floodWtlvRows.length > 0) ? (
<div className="flex w-full min-w-0 flex-col items-start gap-2">
<span>기본정보</span>
<div className="flex w-full items-center gap-1">
<button
type="button"
aria-label="이전 연도"
className="shrink-0 px-1 text-lg leading-none text-foreground"
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={async () => {
setSelectedFloodDepthYear(year);
const selectedDepthRow = floodDepthRows.find(
(row) =>
String(
row.fldng_yr ?? row.fldngYr ?? row.yr ?? "",
).trim() === year,
{floodDepthRows.length > 0 && (
<div className="flex w-full items-center gap-1">
<span className="w-9 shrink-0 text-[1.1rem] font-semibold">
심선
</span>
<button
type="button"
aria-label="이전 연도"
className="shrink-0 px-1 text-lg leading-none text-foreground"
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 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
>
{[...floodDepthRows]
.map((row, index) => ({
row,
index,
year: String(
row.fldng_yr ?? row.fldngYr ?? row.yr ?? "",
).trim(),
}))
.filter(({ year }) => Boolean(year))
.sort((left, right) => {
const yearOrder = left.year.localeCompare(
right.year,
undefined,
{ numeric: true },
);
const depthFid = String(
selectedDepthRow?.fldng_dpth_map_sn ??
selectedDepthRow?.fid ??
"",
).trim();
if (depthFid) {
const core = GisApp.Module.layer as any;
await core.showSearchResultsFromGeoJSON?.(
"tb_ms_fldng_dpth_map",
`tb_ms_fldng_dpth_map.${depthFid.replace(/^tb_ms_fldng_dpth_map\./, "")}`,
);
}
}}
className={`rounded border px-3 py-1 text-[1.2rem] font-medium leading-5 ${selectedFloodDepthYear === year ? "border-blue-600 bg-white text-blue-700" : "border-border bg-muted text-muted-foreground"}`}
>
{year}년
</button>
))}
return yearOrder !== 0
? yearOrder
: left.index - right.index;
})
.map(({ row, index, year }) => (
<button
key={`${String(row.fldng_dpth_map_sn ?? row.fid ?? "row")}-${index}`}
type="button"
onClick={async () => {
const depthFid = String(
row.fldng_dpth_map_sn ?? row.fid ?? "",
).trim();
if (depthFid) {
setSelectedFloodDepthFid(depthFid);
setSelectedFloodWtlvFid(null);
const core = GisApp.Module.layer as any;
await core.showSearchResultsFromGeoJSON?.(
"tb_ms_fldng_dpth_map",
`tb_ms_fldng_dpth_map.${depthFid.replace(/^tb_ms_fldng_dpth_map\./, "")}`,
);
}
}}
className={`shrink-0 rounded border px-3 py-1 text-[1.2rem] font-medium leading-5 ${selectedFloodDepthFid === String(row.fldng_dpth_map_sn ?? row.fid ?? "").trim() ? "border-blue-600 bg-white text-blue-700" : "border-border bg-muted text-muted-foreground"}`}
>
{year}년
</button>
))}
</div>
<button
type="button"
aria-label="다음 연도"
className="shrink-0 px-1 text-lg leading-none text-foreground"
onClick={() =>
floodDepthYearListRef.current?.scrollBy({
left: 120,
behavior: "smooth",
})
}
>
›
</button>
</div>
<button
type="button"
aria-label="다음 연도"
className="shrink-0 px-1 text-lg leading-none text-foreground"
onClick={() =>
floodDepthYearListRef.current?.scrollBy({
left: 120,
behavior: "smooth",
})
}
>
›
</button>
</div>
)}
{floodWtlvRows.length > 0 && (
<div className="flex w-full items-center gap-1">
<span className="w-9 shrink-0 text-[1.1rem] font-semibold">
위선
</span>
<button
type="button"
aria-label="이전 위선"
className="shrink-0 px-1 text-lg leading-none text-foreground"
onClick={() =>
floodWtlvListRef.current?.scrollBy({
left: -120,
behavior: "smooth",
})
}
>
‹
</button>
<div
ref={floodWtlvListRef}
className="flex min-w-0 flex-1 gap-2 overflow-x-auto whitespace-nowrap [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
>
{floodWtlvRows.map((row, index) => {
const waterLevelFid = String(row.fid ?? "").trim();
const disasterName = String(
row.fldng_dstr_nm ?? row.fldngDstrNm ?? "",
).trim();
return (
<button
key={`${waterLevelFid || "wtlv"}-${index}`}
type="button"
onClick={async () => {
if (!waterLevelFid) return;
setSelectedFloodWtlvFid(waterLevelFid);
setSelectedFloodDepthFid(null);
const core = GisApp.Module.layer as any;
await core.showSearchResultsFromGeoJSON?.(
"tb_ms_fldng_wtlv_map",
`tb_ms_fldng_wtlv_map.${waterLevelFid.replace(/^tb_ms_fldng_wtlv_map\./, "")}`,
);
}}
className={`shrink-0 rounded border px-3 py-1 text-[1.2rem] font-medium leading-5 ${selectedFloodWtlvFid === waterLevelFid ? "border-blue-600 bg-white text-blue-700" : "border-border bg-muted text-muted-foreground"}`}
>
{disasterName || "재해명 없음"}
</button>
);
})}
</div>
<button
type="button"
aria-label="다음 위선"
className="shrink-0 px-1 text-lg leading-none text-foreground"
onClick={() =>
floodWtlvListRef.current?.scrollBy({
left: 120,
behavior: "smooth",
})
}
>
›
</button>
</div>
)}
</div>
) : layerNm === "하수관로" && tableData?.elapsedYears ? (
<>
+24
View File
@@ -5208,6 +5208,30 @@ const Map = () => {
return;
}
// 연계 심선/위선 조회는 공용 search-layer를 교체한다. 이때 탭에 보관된
// 기존 침수흔적 feature는 더 이상 지도 레이어에 속하지 않으므로, 단순히
// Select 컬렉션에 다시 넣어도 강조가 렌더링되지 않는다. 탭으로 돌아올
// 때는 FID로 search-layer를 복원한 뒤 로드된 feature를 선택하게 한다.
if (
layerTypeName === FLOOD_HISTORY_LAYER_TYPE ||
layerTypeName === "tb_ms_fldng_dpth_map"
) {
clearSinkholeFocusedPipeLayer();
const featureIdentifier = getRightPanelFeatureIdentifier(data).replace(
new RegExp(`^${layerTypeName}\\.`),
"",
);
if (featureIdentifier) {
const core = GisApp.Module.layer as any;
void core?.showSearchResultsFromGeoJSON?.(
layerTypeName,
`${layerTypeName}.${featureIdentifier}`,
);
}
return;
}
if (!data || typeof data.getGeometry !== "function") {
clearSinkholeFocusedPipeLayer();
+3 -1
View File
@@ -165,7 +165,9 @@ export function VerticalTable({
<>
{headName ? (
<div className="card-header flex items-center justify-between">
<h3 className="mx-3 text-2xl font-medium text-label">{headName}</h3>
<h3 className="mx-3 min-w-0 flex-1 text-2xl font-medium text-label">
{headName}
</h3>
{showSaveButton && onSaveClick && (
<Button type="button" color="primary" onClick={onSaveClick}>
저장