sn: 선택 레이어 전체 디자인 수정
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { validateElapsedYears, type AgedPipeFilters } from "./agedPipeUtils";
|
||||
import LayerSettingCard from "../layerSetting/LayerSettingCard";
|
||||
|
||||
type MaybePromise<T = void> = T | Promise<T>;
|
||||
|
||||
@@ -11,6 +12,9 @@ type Props = {
|
||||
onFiltersChange: (filters: AgedPipeFilters) => void;
|
||||
onApply: (filters?: AgedPipeFilters) => MaybePromise;
|
||||
compact?: boolean;
|
||||
enabled?: boolean;
|
||||
onEnabledChange?: (enabled: boolean) => void;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
export default function AgedPipeLayerSettingPanel({
|
||||
@@ -18,6 +22,9 @@ export default function AgedPipeLayerSettingPanel({
|
||||
onFiltersChange,
|
||||
onApply,
|
||||
compact = false,
|
||||
enabled = true,
|
||||
onEnabledChange = () => {},
|
||||
onClose = () => {},
|
||||
}: Props) {
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
@@ -47,16 +54,16 @@ export default function AgedPipeLayerSettingPanel({
|
||||
};
|
||||
|
||||
const inputClass =
|
||||
"h-[22px] border border-input bg-background px-[2px] text-center text-[1rem] leading-none text-foreground";
|
||||
"h-[20px] !rounded border border-input bg-background px-[1px] text-center text-[1.1rem] leading-none text-foreground";
|
||||
const numberInputClass = inputClass + " w-[40px]";
|
||||
const applyButtonClass =
|
||||
"grid h-[20px] w-[20px] shrink-0 place-items-center rounded-full bg-primary text-[0.95rem] font-bold leading-none text-primary-foreground";
|
||||
"grid h-[16px] w-[16px] shrink-0 place-items-center rounded-full bg-primary text-[0.85rem] font-bold leading-none text-primary-foreground";
|
||||
const indicator = (
|
||||
<span className="h-2 w-2 shrink-0 rounded-full bg-primary" />
|
||||
);
|
||||
|
||||
const settingRow = (
|
||||
<div className="flex min-h-[25px] items-center gap-1 whitespace-nowrap text-[1.05rem] font-medium text-label">
|
||||
<div className="flex min-h-[26px] items-center gap-1 whitespace-nowrap text-[1.15rem] font-medium text-label">
|
||||
<div className="flex min-w-0 items-center gap-1">
|
||||
{indicator}
|
||||
<label htmlFor="aged-pipe-elapsed-years" className="shrink-0">
|
||||
@@ -88,7 +95,7 @@ export default function AgedPipeLayerSettingPanel({
|
||||
title="적용"
|
||||
aria-label="경과년도 적용"
|
||||
>
|
||||
✓
|
||||
<span className="relative left-[1px]">✓</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -96,9 +103,14 @@ export default function AgedPipeLayerSettingPanel({
|
||||
|
||||
if (compact) {
|
||||
return (
|
||||
<div className="w-full rounded-[6px] border border-border bg-background px-[6px] py-2 shadow-blur">
|
||||
<LayerSettingCard
|
||||
title="노후관로"
|
||||
enabled={enabled}
|
||||
onEnabledChange={onEnabledChange}
|
||||
onClose={onClose}
|
||||
>
|
||||
{settingRow}
|
||||
</div>
|
||||
</LayerSettingCard>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import type { ExmnMapFilters, ExmnMapJobType } from "./exmnMapTypes";
|
||||
import LayerSettingCard from "../layerSetting/LayerSettingCard";
|
||||
|
||||
type Props = {
|
||||
jobType: ExmnMapJobType;
|
||||
filters: ExmnMapFilters;
|
||||
onFiltersChange: (filters: ExmnMapFilters) => void;
|
||||
enabled?: boolean;
|
||||
onEnabledChange?: (enabled: boolean) => void;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
const GRADE_OPTIONS = ["", "1", "2", "3", "4", "5"];
|
||||
@@ -15,6 +19,9 @@ export default function ExmnLayerSettingPanel({
|
||||
jobType,
|
||||
filters,
|
||||
onFiltersChange,
|
||||
enabled = true,
|
||||
onEnabledChange = () => {},
|
||||
onClose = () => {},
|
||||
}: Props) {
|
||||
const updateFilter = (key: keyof ExmnMapFilters, value: string) => {
|
||||
onFiltersChange({
|
||||
@@ -23,23 +30,25 @@ export default function ExmnLayerSettingPanel({
|
||||
});
|
||||
};
|
||||
const selectClass =
|
||||
"h-[24px] w-[54px] rounded-[4px] border border-input bg-background px-2 text-[1rem] text-foreground outline-none focus:border-primary";
|
||||
const labelClass = "shrink-0 text-[1.05rem] font-medium text-label";
|
||||
"h-[20px] w-[62px] rounded border border-input bg-background px-[1px] text-center text-[1.1rem] leading-none text-foreground outline-none focus:border-primary";
|
||||
const labelClass = "shrink-0 text-[1.15rem] font-medium text-label";
|
||||
const title = jobType === "PPL" ? "관로조사" : "맨홀조사";
|
||||
|
||||
return (
|
||||
<div className="w-full rounded-[6px] border border-border bg-background px-2 py-[9px] shadow-blur">
|
||||
<div className="mb-2 flex items-center gap-1">
|
||||
{/* 관로/맨홀 조사유형을 구분하는 지도 범례 의미색이므로 고정색을 유지한다. */}
|
||||
<span
|
||||
className={`h-2 w-2 rounded-full ${jobType === "PPL" ? "bg-[#facc15]" : "bg-[#8b5cf6]"}`}
|
||||
/>
|
||||
<span className="text-[1.1rem] font-semibold text-label">
|
||||
{jobType === "PPL" ? "관로조사" : "맨홀조사"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<label className="flex min-h-[25px] items-center justify-between gap-2">
|
||||
<span className={labelClass}>사업차수</span>
|
||||
<LayerSettingCard
|
||||
title={title}
|
||||
enabled={enabled}
|
||||
onEnabledChange={onEnabledChange}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="space-y-[1px]">
|
||||
<label className="flex min-h-[24px] items-center justify-between ">
|
||||
<span className={`flex items-center gap-1 ${labelClass}`}>
|
||||
<span
|
||||
className={`h-2 w-2 rounded-full ${jobType === "PPL" ? "bg-[#facc15]" : "bg-[#8b5cf6]"}`}
|
||||
/>
|
||||
사업차수
|
||||
</span>
|
||||
<input
|
||||
className={selectClass}
|
||||
type="text"
|
||||
@@ -50,7 +59,7 @@ export default function ExmnLayerSettingPanel({
|
||||
onChange={(event) => updateFilter("bizCycl", event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<label className="flex min-h-[25px] items-center justify-between gap-2">
|
||||
<label className="flex min-h-[24px] items-center justify-between ">
|
||||
<span className={labelClass}>구조적 상태등급</span>
|
||||
<select
|
||||
className={selectClass}
|
||||
@@ -66,7 +75,7 @@ export default function ExmnLayerSettingPanel({
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="flex min-h-[25px] items-center justify-between gap-2">
|
||||
<label className="flex min-h-[24px] items-center justify-between ">
|
||||
<span className={labelClass}>운영적 상태등급</span>
|
||||
<select
|
||||
className={selectClass}
|
||||
@@ -83,6 +92,6 @@ export default function ExmnLayerSettingPanel({
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</LayerSettingCard>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function FldngHstryLayerSettingPanel({
|
||||
.filter((year) => Number(year) >= Number(normalizedRange.startYear))
|
||||
.map((year) => ({ val: year, text: year }));
|
||||
const yearSelectClass =
|
||||
"h-[22px] w-[62px] rounded-none border border-input bg-background px-[4px] text-center text-[1rem] leading-none text-foreground";
|
||||
"h-[20px] w-[62px] !rounded border border-input bg-background px-[1px] text-center text-[1.1rem] leading-none text-foreground";
|
||||
|
||||
const changeYear = (key: keyof FloodHistoryYearRange, value: string) => {
|
||||
onYearRangeChange(
|
||||
@@ -49,7 +49,7 @@ export default function FldngHstryLayerSettingPanel({
|
||||
|
||||
return (
|
||||
<div className="w-full overflow-visible rounded-[6px] border border-border bg-background shadow-blur">
|
||||
<div className="flex min-h-[32px] items-center justify-between border-b border-border px-2">
|
||||
<div className="flex min-h-[28px] items-center justify-between border-b border-border px-2">
|
||||
<span className="text-[1.15rem] font-semibold text-label">
|
||||
침수흔적도
|
||||
</span>
|
||||
@@ -58,18 +58,18 @@ export default function FldngHstryLayerSettingPanel({
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={enabled}
|
||||
aria-label="침수연도 필터 적용 여부"
|
||||
title={enabled ? "침수연도 필터 적용 중" : "침수연도 필터 미적용"}
|
||||
className={`relative h-[18px] w-[34px] rounded-full transition-colors ${enabled ? "bg-primary" : "bg-muted-foreground/40"}`}
|
||||
aria-label="침수년도 필터 적용 여부"
|
||||
title={enabled ? "침수년도 필터 적용 중" : "침수년도 필터 미적용"}
|
||||
className={`relative h-[16px] w-[28px] rounded-full border transition-colors ${enabled ? "border-primary bg-primary" : "border-[#94a3b8] bg-[#cbd5e1]"}`}
|
||||
onClick={() => onEnabledChange(!enabled)}
|
||||
>
|
||||
<span
|
||||
className={`absolute top-[2px] h-[14px] w-[14px] rounded-full bg-white shadow-sm transition-[left] ${enabled ? "left-[18px]" : "left-[2px]"}`}
|
||||
className={`absolute top-1/2 h-[12px] w-[12px] -translate-y-1/2 rounded-full bg-white shadow-sm transition-[left] ${enabled ? "left-[14px]" : "left-[2px]"}`}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="침수연도 설정 닫기"
|
||||
aria-label="침수년도 설정 닫기"
|
||||
title="닫기"
|
||||
className="grid h-[20px] w-[20px] place-items-center bg-transparent text-[1.3rem] leading-none text-muted-foreground hover:text-foreground"
|
||||
onClick={onClose}
|
||||
@@ -78,7 +78,7 @@ export default function FldngHstryLayerSettingPanel({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-h-[38px] items-center gap-1.5 px-2 py-2 text-[1.05rem] font-medium text-label">
|
||||
<div className="flex min-h-[30px] items-center gap-1.5 px-2 py-1 text-[1.15rem] font-medium text-label">
|
||||
<span className="h-2 w-2 shrink-0 rounded-full bg-primary" />
|
||||
<span className="shrink-0">침수년도</span>
|
||||
<div className="ml-auto flex shrink-0 items-center gap-1.5">
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
enabled: boolean;
|
||||
onEnabledChange: (enabled: boolean) => void;
|
||||
onClose: () => void;
|
||||
children?: ReactNode;
|
||||
showToggle?: boolean;
|
||||
};
|
||||
|
||||
/** 침수흔적도 설정과 같은 제목·토글·닫기·본문 여백을 제공한다. */
|
||||
export default function LayerSettingCard({
|
||||
title,
|
||||
enabled,
|
||||
onEnabledChange,
|
||||
onClose,
|
||||
children,
|
||||
showToggle = true,
|
||||
}: Props) {
|
||||
return (
|
||||
<div className="w-full overflow-visible rounded-[6px] border border-border bg-background shadow-blur">
|
||||
<div className="flex min-h-[28px] items-center justify-between border-b border-border px-2">
|
||||
<span className="text-[1.15rem] font-semibold text-label">{title}</span>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
{showToggle ? (
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={enabled}
|
||||
aria-label={`${title} 적용 여부`}
|
||||
title={enabled ? `${title} 적용 중` : `${title} 미적용`}
|
||||
className={`relative h-[16px] w-[28px] rounded-full border transition-colors ${enabled ? "border-primary bg-primary" : "border-[#94a3b8] bg-[#cbd5e1]"}`}
|
||||
onClick={() => onEnabledChange(!enabled)}
|
||||
>
|
||||
<span
|
||||
className={`absolute top-1/2 h-[12px] w-[12px] -translate-y-1/2 rounded-full bg-white shadow-sm transition-[left] ${enabled ? "left-[14px]" : "left-[2px]"}`}
|
||||
/>
|
||||
</button>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`${title} 설정 닫기`}
|
||||
title="닫기"
|
||||
className="grid h-[20px] w-[20px] place-items-center bg-transparent text-[1.3rem] leading-none text-muted-foreground hover:text-foreground"
|
||||
onClick={onClose}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{children ? (
|
||||
<div className="px-2 py-1 text-[1.15rem] text-label">{children}</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
type SinkholeSyncSummary,
|
||||
type SinkholeYearRange,
|
||||
} from "./sinkholeTypes";
|
||||
import LayerSettingCard from "../layerSetting/LayerSettingCard";
|
||||
|
||||
type MaybePromise<T = void> = T | Promise<T>;
|
||||
|
||||
@@ -21,6 +22,9 @@ type Props = {
|
||||
lastSyncSummary?: SinkholeSyncSummary | null;
|
||||
isAdmin?: boolean;
|
||||
compact?: boolean;
|
||||
enabled?: boolean;
|
||||
onEnabledChange?: (enabled: boolean) => void;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
export default function SinkholeLayerSettingPanel({
|
||||
@@ -31,6 +35,9 @@ export default function SinkholeLayerSettingPanel({
|
||||
lastSyncSummary,
|
||||
isAdmin = false,
|
||||
compact = false,
|
||||
enabled = true,
|
||||
onEnabledChange = () => {},
|
||||
onClose = () => {},
|
||||
}: Props) {
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
@@ -94,7 +101,7 @@ export default function SinkholeLayerSettingPanel({
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const yearSelectClass =
|
||||
"h-[22px] w-[62px] rounded-none border border-input bg-background px-[4px] text-center text-[1rem] leading-none text-foreground";
|
||||
"h-[20px] !w-[58px] !rounded border border-input bg-background px-[1px] text-center text-[1.1rem] leading-none text-foreground";
|
||||
const startYearOptions = Array.from(
|
||||
{ length: currentYear - SINKHOLE_MIN_SEARCH_YEAR + 1 },
|
||||
(_, index) => String(currentYear - index),
|
||||
@@ -117,17 +124,17 @@ export default function SinkholeLayerSettingPanel({
|
||||
.map((year) => ({ val: year, text: year }));
|
||||
|
||||
const inputClass =
|
||||
"h-[22px] border border-input bg-background px-[2px] text-center text-[1rem] leading-none text-foreground";
|
||||
"h-[20px] !rounded border border-input bg-background px-[1px] text-center text-[1.1rem] leading-none text-foreground";
|
||||
const numberInputClass = inputClass + " w-[40px]";
|
||||
const applyButtonClass =
|
||||
"grid h-[20px] w-[20px] shrink-0 place-items-center rounded-full bg-primary text-[0.95rem] font-bold leading-none text-primary-foreground";
|
||||
"grid h-[16px] w-[16px] shrink-0 place-items-center rounded-full bg-primary text-[0.85rem] font-bold leading-none text-primary-foreground";
|
||||
const indicator = (
|
||||
<span className="h-2 w-2 shrink-0 rounded-full bg-primary" />
|
||||
);
|
||||
|
||||
const settingRows = (
|
||||
<div className="space-y-1.5 text-[1.05rem] font-medium text-label">
|
||||
<div className="flex min-h-[25px] items-center gap-1.5 whitespace-nowrap">
|
||||
<div className="space-y-1.5 text-[1.15rem] font-medium text-label">
|
||||
<div className="flex min-h-[26px] items-center gap-1.5 whitespace-nowrap">
|
||||
<div className="flex min-w-0 items-center gap-1">
|
||||
{indicator}
|
||||
<label htmlFor="sinkhole-radius" className="shrink-0">
|
||||
@@ -158,15 +165,15 @@ export default function SinkholeLayerSettingPanel({
|
||||
title="적용"
|
||||
aria-label="반경 적용"
|
||||
>
|
||||
✓
|
||||
<span className="relative left-[1px]">✓</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-h-[25px] items-center gap-1.5 whitespace-nowrap">
|
||||
<div className="flex min-h-[26px] items-center gap-1.5 whitespace-nowrap">
|
||||
<div className="flex min-w-0 items-center gap-1">
|
||||
{indicator}
|
||||
<label htmlFor="sinkhole-start-year" className="shrink-0">
|
||||
년도설정
|
||||
발생년도
|
||||
</label>
|
||||
</div>
|
||||
<div className="ml-auto flex shrink-0 items-center gap-1.5">
|
||||
@@ -208,7 +215,7 @@ export default function SinkholeLayerSettingPanel({
|
||||
title="적용"
|
||||
aria-label="연도범위 적용"
|
||||
>
|
||||
✓
|
||||
<span className="relative left-[1px]">✓</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -217,9 +224,14 @@ export default function SinkholeLayerSettingPanel({
|
||||
|
||||
if (compact) {
|
||||
return (
|
||||
<div className="w-full rounded-[6px] border border-border bg-background px-2 py-[10px] shadow-blur">
|
||||
<LayerSettingCard
|
||||
title="지반침하정보"
|
||||
enabled={enabled}
|
||||
onEnabledChange={onEnabledChange}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="overflow-visible">{settingRows}</div>
|
||||
</div>
|
||||
</LayerSettingCard>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ import SinkholeLayerSettingPanel from "./components/sinkhole/SinkholeLayerSettin
|
||||
import FldngHstryDetailPanel from "./components/fldngHstry/FldngHstryDetailPanel";
|
||||
import FldngHstryLayerSettingPanel from "./components/fldngHstry/FldngHstryLayerSettingPanel";
|
||||
import ExmnLayerSettingPanel from "./components/exmn/ExmnLayerSettingPanel";
|
||||
import LayerSettingCard from "./components/layerSetting/LayerSettingCard";
|
||||
import { getExmnMapLayer } from "./components/exmn/exmnMapApi";
|
||||
import {
|
||||
EMPTY_EXMN_MAP_FILTERS,
|
||||
@@ -3013,6 +3014,24 @@ const Map = () => {
|
||||
const [checkedLayerLegendVersion, setCheckedLayerLegendVersion] = useState(0);
|
||||
const [isAgedPipeChecked, setIsAgedPipeChecked] = useState(false);
|
||||
const [isSinkholeChecked, setIsSinkholeChecked] = useState(false);
|
||||
const [isSinkholeSettingEnabled, setIsSinkholeSettingEnabled] =
|
||||
useState(true);
|
||||
const [isSinkholeSettingVisible, setIsSinkholeSettingVisible] =
|
||||
useState(true);
|
||||
const [isAgedPipeSettingEnabled, setIsAgedPipeSettingEnabled] =
|
||||
useState(true);
|
||||
const [isAgedPipeSettingVisible, setIsAgedPipeSettingVisible] =
|
||||
useState(true);
|
||||
const [isExmnPplSettingEnabled, setIsExmnPplSettingEnabled] = useState(true);
|
||||
const [isExmnPplSettingVisible, setIsExmnPplSettingVisible] = useState(true);
|
||||
const [isExmnMnhlSettingEnabled, setIsExmnMnhlSettingEnabled] =
|
||||
useState(true);
|
||||
const [isExmnMnhlSettingVisible, setIsExmnMnhlSettingVisible] =
|
||||
useState(true);
|
||||
const [isWaterDepthLegendEnabled, setIsWaterDepthLegendEnabled] =
|
||||
useState(true);
|
||||
const [isWaterDepthLegendVisible, setIsWaterDepthLegendVisible] =
|
||||
useState(true);
|
||||
const [isFldngHstryChecked, setIsFldngHstryChecked] = useState(false);
|
||||
// 침수흔적위선과 심선은 하나의 연도 범위를 공유하지만 체크 상태는 독립적으로 관리한다.
|
||||
const [isFloodWtlvChecked, setIsFloodWtlvChecked] = useState(false);
|
||||
@@ -3030,6 +3049,11 @@ const Map = () => {
|
||||
useState(true);
|
||||
// 닫힌 설정 카드는 레이어가 완전히 해제된 뒤 다시 체크될 때만 자동으로 연다.
|
||||
const floodLayerCheckedRef = useRef(false);
|
||||
const sinkholeLayerCheckedRef = useRef(false);
|
||||
const agedPipeLayerCheckedRef = useRef(false);
|
||||
const exmnPplLayerCheckedRef = useRef(false);
|
||||
const exmnMnhlLayerCheckedRef = useRef(false);
|
||||
const waterDepthLayerCheckedRef = useRef(false);
|
||||
// 체크 등급 변경과 연도 필터 변경을 구분해 연도 변경 때만 기존 선택 상세를 정리한다.
|
||||
const floodYearFilterSignatureRef = useRef("");
|
||||
const [isExmnPplChecked, setIsExmnPplChecked] = useState(false);
|
||||
@@ -3682,12 +3706,30 @@ const Map = () => {
|
||||
if (isSinkholePopupFilterActive) {
|
||||
return normalizeSinkholeFilters(currentFilters, sinkholeScope);
|
||||
}
|
||||
if (!isSinkholeSettingEnabled) {
|
||||
return normalizeSinkholeFilters(
|
||||
{
|
||||
...buildSinkholeFiltersFromYearRange(
|
||||
sinkholeYearRange,
|
||||
sinkholeScope,
|
||||
),
|
||||
startYmd: "",
|
||||
endYmd: "",
|
||||
},
|
||||
sinkholeScope,
|
||||
);
|
||||
}
|
||||
return normalizeSinkholeFilters(
|
||||
buildSinkholeFiltersFromYearRange(sinkholeYearRange, sinkholeScope),
|
||||
sinkholeScope,
|
||||
);
|
||||
});
|
||||
}, [isSinkholePopupFilterActive, sinkholeScope, sinkholeYearRange]);
|
||||
}, [
|
||||
isSinkholePopupFilterActive,
|
||||
isSinkholeSettingEnabled,
|
||||
sinkholeScope,
|
||||
sinkholeYearRange,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = window.setInterval(() => {
|
||||
@@ -6426,6 +6468,14 @@ const Map = () => {
|
||||
const waterDepthChecked = checkedNodesData.some((node) =>
|
||||
WATER_DEPTH_LAYER_TYPES.includes(String(node?.data?.lyrTyNm ?? "")),
|
||||
);
|
||||
if (agedPipeChecked && !agedPipeLayerCheckedRef.current) {
|
||||
setIsAgedPipeSettingVisible(true);
|
||||
}
|
||||
if (waterDepthChecked && !waterDepthLayerCheckedRef.current) {
|
||||
setIsWaterDepthLegendVisible(true);
|
||||
}
|
||||
agedPipeLayerCheckedRef.current = agedPipeChecked;
|
||||
waterDepthLayerCheckedRef.current = waterDepthChecked;
|
||||
setIsAgedPipeChecked(agedPipeChecked);
|
||||
const floodWtlvChecked = checkedNodesData.some((node) =>
|
||||
isFloodHistoryWaterLevelNode(node),
|
||||
@@ -6460,6 +6510,10 @@ const Map = () => {
|
||||
const sinkholeChecked = checkedNodesData.some((node) =>
|
||||
isSinkholeLayerNode(node),
|
||||
);
|
||||
if (sinkholeChecked && !sinkholeLayerCheckedRef.current) {
|
||||
setIsSinkholeSettingVisible(true);
|
||||
}
|
||||
sinkholeLayerCheckedRef.current = sinkholeChecked;
|
||||
setIsSinkholeChecked(sinkholeChecked);
|
||||
const exmnPplChecked = checkedNodesData.some((node) =>
|
||||
isExmnResultPplNode(node),
|
||||
@@ -6467,6 +6521,14 @@ const Map = () => {
|
||||
const exmnMnhlChecked = checkedNodesData.some((node) =>
|
||||
isExmnResultMnhlNode(node),
|
||||
);
|
||||
if (exmnPplChecked && !exmnPplLayerCheckedRef.current) {
|
||||
setIsExmnPplSettingVisible(true);
|
||||
}
|
||||
if (exmnMnhlChecked && !exmnMnhlLayerCheckedRef.current) {
|
||||
setIsExmnMnhlSettingVisible(true);
|
||||
}
|
||||
exmnPplLayerCheckedRef.current = exmnPplChecked;
|
||||
exmnMnhlLayerCheckedRef.current = exmnMnhlChecked;
|
||||
setIsExmnPplChecked(exmnPplChecked);
|
||||
setIsExmnMnhlChecked(exmnMnhlChecked);
|
||||
setActiveLayerSettingOrder((currentOrder) => {
|
||||
@@ -6522,15 +6584,9 @@ const Map = () => {
|
||||
);
|
||||
setSinkholeYearRange(normalizedRange);
|
||||
setIsSinkholePopupFilterActive(false);
|
||||
setAppliedSinkholeFilters(
|
||||
normalizeSinkholeFilters(
|
||||
buildSinkholeFiltersFromYearRange(normalizedRange, sinkholeScope),
|
||||
sinkholeScope,
|
||||
),
|
||||
);
|
||||
setSinkholeSelectionVersion((version) => version + 1);
|
||||
},
|
||||
[sinkholeScope, sinkholeYearRange],
|
||||
[sinkholeYearRange],
|
||||
);
|
||||
|
||||
/** 침수위험지도 루트의 목록보기 버튼에서 전용 조회 팝업을 연다. */
|
||||
@@ -6600,7 +6656,7 @@ const Map = () => {
|
||||
return index >= 0 ? index : fallback;
|
||||
};
|
||||
|
||||
if (isExmnPplChecked) {
|
||||
if (isExmnPplChecked && isExmnPplSettingVisible) {
|
||||
nextItems.push({
|
||||
key: "exmnPpl",
|
||||
label: "관로조사",
|
||||
@@ -6610,12 +6666,15 @@ const Map = () => {
|
||||
jobType="PPL"
|
||||
filters={exmnPplFilters}
|
||||
onFiltersChange={setExmnPplFilters}
|
||||
enabled={isExmnPplSettingEnabled}
|
||||
onEnabledChange={setIsExmnPplSettingEnabled}
|
||||
onClose={() => setIsExmnPplSettingVisible(false)}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (isExmnMnhlChecked) {
|
||||
if (isExmnMnhlChecked && isExmnMnhlSettingVisible) {
|
||||
nextItems.push({
|
||||
key: "exmnMnhl",
|
||||
label: "맨홀조사",
|
||||
@@ -6625,6 +6684,9 @@ const Map = () => {
|
||||
jobType="MNHL"
|
||||
filters={exmnMnhlFilters}
|
||||
onFiltersChange={setExmnMnhlFilters}
|
||||
enabled={isExmnMnhlSettingEnabled}
|
||||
onEnabledChange={setIsExmnMnhlSettingEnabled}
|
||||
onClose={() => setIsExmnMnhlSettingVisible(false)}
|
||||
/>
|
||||
),
|
||||
});
|
||||
@@ -6647,7 +6709,7 @@ const Map = () => {
|
||||
});
|
||||
}
|
||||
|
||||
if (isSinkholeChecked) {
|
||||
if (isSinkholeChecked && isSinkholeSettingVisible) {
|
||||
nextItems.push({
|
||||
key: "sinkhole",
|
||||
label: "지반침하정보",
|
||||
@@ -6660,13 +6722,16 @@ const Map = () => {
|
||||
onApplyYearRange={applySinkholeYearRange}
|
||||
lastSyncSummary={lastSinkholeSyncSummary}
|
||||
isAdmin={userInfo?.adminRoleYn === "Y"}
|
||||
enabled={isSinkholeSettingEnabled}
|
||||
onEnabledChange={setIsSinkholeSettingEnabled}
|
||||
onClose={() => setIsSinkholeSettingVisible(false)}
|
||||
compact
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (isAgedPipeChecked) {
|
||||
if (isAgedPipeChecked && isAgedPipeSettingVisible) {
|
||||
nextItems.push({
|
||||
key: "agedPipe",
|
||||
label: "노후관로",
|
||||
@@ -6676,43 +6741,47 @@ const Map = () => {
|
||||
filters={draftAgedPipeFilters}
|
||||
onFiltersChange={setDraftAgedPipeFilters}
|
||||
onApply={handleApplyAgedPipeFilters}
|
||||
enabled={isAgedPipeSettingEnabled}
|
||||
onEnabledChange={setIsAgedPipeSettingEnabled}
|
||||
onClose={() => setIsAgedPipeSettingVisible(false)}
|
||||
compact
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (isWaterDepthLayerActive) {
|
||||
if (isWaterDepthLayerActive && isWaterDepthLegendVisible) {
|
||||
nextItems.push({
|
||||
key: "waterDepthLegend",
|
||||
label: "침수심 범례",
|
||||
order: resolveSettingOrder("waterDepthLegend", 9997),
|
||||
content: (
|
||||
<div className="overflow-hidden rounded-sm border border-border bg-secondary">
|
||||
<div className="px-3 py-2">
|
||||
<div className="rounded-sm border border-border bg-background px-3 py-3">
|
||||
<div className="mb-2 bg-primary py-1 text-center text-[1.1rem] font-semibold text-primary-foreground">
|
||||
침수심 범례
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{waterDepthLegendItems.map((item) => (
|
||||
<div key={item.key} className="flex items-center gap-2">
|
||||
<span
|
||||
className="inline-block h-4 w-10 shrink-0"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
border: `1px solid ${item.borderColor}`,
|
||||
}}
|
||||
/>
|
||||
<span className="text-[1rem] font-medium text-label">
|
||||
{item.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<LayerSettingCard
|
||||
title="침수심 범례"
|
||||
enabled={isWaterDepthLegendEnabled}
|
||||
onEnabledChange={setIsWaterDepthLegendEnabled}
|
||||
onClose={() => setIsWaterDepthLegendVisible(false)}
|
||||
showToggle={false}
|
||||
>
|
||||
{isWaterDepthLegendEnabled ? (
|
||||
<div className="space-y-1">
|
||||
{waterDepthLegendItems.map((item) => (
|
||||
<div key={item.key} className="flex items-center gap-2">
|
||||
<span
|
||||
className="inline-block h-4 w-10 shrink-0"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
border: `1px solid ${item.borderColor}`,
|
||||
}}
|
||||
/>
|
||||
<span className="text-[1rem] font-medium text-label">
|
||||
{item.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</LayerSettingCard>
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -6730,13 +6799,23 @@ const Map = () => {
|
||||
handleApplyAgedPipeFilters,
|
||||
floodYearRange,
|
||||
isAgedPipeChecked,
|
||||
isAgedPipeSettingEnabled,
|
||||
isAgedPipeSettingVisible,
|
||||
isExmnMnhlChecked,
|
||||
isExmnMnhlSettingEnabled,
|
||||
isExmnMnhlSettingVisible,
|
||||
isExmnPplChecked,
|
||||
isExmnPplSettingEnabled,
|
||||
isExmnPplSettingVisible,
|
||||
isFldngHstryChecked,
|
||||
isFloodYearFilterEnabled,
|
||||
isFloodYearSettingVisible,
|
||||
isSinkholeChecked,
|
||||
isSinkholeSettingEnabled,
|
||||
isSinkholeSettingVisible,
|
||||
isWaterDepthLayerActive,
|
||||
isWaterDepthLegendEnabled,
|
||||
isWaterDepthLegendVisible,
|
||||
lastSinkholeSyncSummary,
|
||||
sinkholeRadiusMeters,
|
||||
sinkholeYearRange,
|
||||
@@ -6775,24 +6854,9 @@ const Map = () => {
|
||||
return (
|
||||
<div className="mt-3 flex h-[34vh] max-h-[32rem] min-h-[24rem] flex-col overflow-hidden rounded-sm border border-border bg-background shadow-[0_4px_14px_rgba(15,23,42,0.08)]">
|
||||
<div className="border-b border-border bg-secondary px-3 py-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-[1.2rem] font-semibold text-label">
|
||||
선택 레이어 설정
|
||||
</span>
|
||||
<span className="text-[1rem] text-muted-foreground">
|
||||
{activeLayerSettingItems.length}건
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1 flex flex-wrap gap-1">
|
||||
{activeLayerSettingItems.map((item) => (
|
||||
<span
|
||||
key={item.key}
|
||||
className="rounded-full border border-border bg-background px-2 py-[1px] text-[1rem] text-label"
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<span className="text-[1.2rem] font-semibold text-label">
|
||||
선택 레이어 설정
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
ref={restoreActiveLayerSettingScroll}
|
||||
@@ -6819,7 +6883,7 @@ const Map = () => {
|
||||
const core = GisApp.Module.core as any;
|
||||
core?.deleteLayer?.(AGED_PIPE_HIGHLIGHT_LAYER_ID);
|
||||
|
||||
if (!isAgedPipeChecked) return;
|
||||
if (!isAgedPipeChecked || !isAgedPipeSettingEnabled) return;
|
||||
|
||||
const baseLayerCode = Object.values(GisApp.layerCode ?? {}).find(
|
||||
(item: any) =>
|
||||
@@ -6855,7 +6919,12 @@ const Map = () => {
|
||||
return () => {
|
||||
core?.deleteLayer?.(AGED_PIPE_HIGHLIGHT_LAYER_ID);
|
||||
};
|
||||
}, [appliedAgedPipeFilters, isAgedPipeChecked, mapReady]);
|
||||
}, [
|
||||
appliedAgedPipeFilters,
|
||||
isAgedPipeChecked,
|
||||
isAgedPipeSettingEnabled,
|
||||
mapReady,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mapReady || !gisMaps.current) return;
|
||||
@@ -9038,10 +9107,14 @@ const Map = () => {
|
||||
removeExmnResultLayer("PPL");
|
||||
return;
|
||||
}
|
||||
void loadExmnResultLayer("PPL", exmnPplFilters);
|
||||
void loadExmnResultLayer(
|
||||
"PPL",
|
||||
isExmnPplSettingEnabled ? exmnPplFilters : EMPTY_EXMN_MAP_FILTERS,
|
||||
);
|
||||
}, [
|
||||
exmnPplFilters,
|
||||
isExmnPplChecked,
|
||||
isExmnPplSettingEnabled,
|
||||
loadExmnResultLayer,
|
||||
mapReady,
|
||||
removeExmnResultLayer,
|
||||
@@ -9052,10 +9125,14 @@ const Map = () => {
|
||||
removeExmnResultLayer("MNHL");
|
||||
return;
|
||||
}
|
||||
void loadExmnResultLayer("MNHL", exmnMnhlFilters);
|
||||
void loadExmnResultLayer(
|
||||
"MNHL",
|
||||
isExmnMnhlSettingEnabled ? exmnMnhlFilters : EMPTY_EXMN_MAP_FILTERS,
|
||||
);
|
||||
}, [
|
||||
exmnMnhlFilters,
|
||||
isExmnMnhlChecked,
|
||||
isExmnMnhlSettingEnabled,
|
||||
loadExmnResultLayer,
|
||||
mapReady,
|
||||
removeExmnResultLayer,
|
||||
|
||||
@@ -89,7 +89,9 @@ const hasLegendStyle = (layerInfo: any) =>
|
||||
|
||||
const normalizeLegendLabel = (label: string) => {
|
||||
const normalized = String(label).trim();
|
||||
return normalized.endsWith("미만") ? normalized : `${normalized} 미만`;
|
||||
return /(?:미만|이상)$/.test(normalized)
|
||||
? normalized
|
||||
: `${normalized} 미만`;
|
||||
};
|
||||
|
||||
export const extractWaterDepthLegendItems = (
|
||||
|
||||
@@ -346,3 +346,22 @@
|
||||
- 수정 진입은 `양식다운로드 → 저장 → 취소`만 표시하고 조사보고서 공통 양식 버튼은 숨긴다.
|
||||
- 수정 화면의 양식다운로드는 저장된 최신 처리분구 기준으로 입력양식을 다시 생성한다.
|
||||
- 등록된 실제 사업보고서 PDF는 기존 조사목록의 보고서 열에서 다운로드하는 계약을 유지한다.
|
||||
|
||||
## 27. 2026-09-01 지도 조사 설정 카드 UI 통일 계획
|
||||
|
||||
- `ExmnLayerSettingPanel`의 관로조사·맨홀조사 카드를 침수흔적도 설정과 동일한 header/body 구조로 변경한다.
|
||||
- 제목 오른쪽의 액션 `div`에는 `적용 여부 토글 → 닫기(×)`를 배치하고 토글 기본값은 ON으로 한다.
|
||||
- 토글 OFF에서는 해당 조사유형의 사업차수·구조·운영 필터 적용을 해제하되 입력값은 유지하며, 닫기는 카드만 숨기고 레이어 체크·필터 상태를 유지한다.
|
||||
- 두 조사유형의 적용 여부, 닫기, 입력값은 독립 상태로 관리한다.
|
||||
- 카드 제목 `1.15rem`, 본문 `1.05rem`, header `32px`, 본문 padding `8px`, 선택 입력 `22px`, font `1rem`, 좌우 padding `4px`를 적용한다.
|
||||
- 관로 노랑·맨홀 보라 의미색은 indicator로 유지하고 카드 외곽·제목은 중립 스타일로 통일한다.
|
||||
- 상태등급 select는 침수흔적도 설정의 SelectBox와 같은 font·padding·border/background 스타일을 적용한다.
|
||||
- 기존 자동반영 동작을 유지하며 원래 없는 `✓` 버튼을 새로 추가하지 않는다. 지반침하·노후관로 등 기존 `✓`가 있는 카드의 버튼은 공통 변경으로 제거하지 않는다.
|
||||
- `map/page.tsx`의 선택 레이어 설정 header에서는 활성 카드 개수와 카드명 pill/chip 목록을 제거하고 제목만 유지한다.
|
||||
- 검증은 관로·맨홀 카드 동시 표시, 독립 토글·닫기, 스타일 통일, 자동반영, 상단 개수·chip 미표시와 지도 필터 회귀를 포함한다.
|
||||
- API·DTO·DB 변경은 없다.
|
||||
|
||||
### 27.1 구현 결과
|
||||
|
||||
- `ExmnLayerSettingPanel`을 공통 카드 header·토글·닫기 구조로 변경하고 관로·맨홀 상태를 독립 관리한다.
|
||||
- 사업차수·상태등급의 기존 자동반영은 유지하며 토글 OFF에서는 빈 필터로 다시 조회한다.
|
||||
|
||||
@@ -273,3 +273,24 @@
|
||||
- 부모가 있는 관로 탭은 활성 여부와 무관하게 `←` 버튼을 항상 표시해 일반 관로와 구분하고 지반침하 상세로 복귀한다.
|
||||
- 공용 우측 탭은 최대 5개 FIFO를 적용하되, 제거 대상이 부모 지반침하이면 그 부모에서 열린 연계 관로도 함께 제거한다. 연계 관로만 제거될 때는 부모 지반침하를 유지한다.
|
||||
- 변경 범위는 `app/(map-layout)/map/page.tsx`이며 BE API·DTO·DB 계약은 변경하지 않는다.
|
||||
|
||||
## 16. 2026-09-01 선택 레이어 설정 지반침하 카드 UI 통일 계획
|
||||
|
||||
- 작업 모드: `maintenance`
|
||||
- 이번 단계는 작업계획만 수정하고 코드는 후속 승인 후 변경한다.
|
||||
- `SinkholeLayerSettingPanel`을 침수흔적도 설정 카드와 동일한 compact 카드 구조로 정리한다.
|
||||
- 헤더에 `지반침하정보` 제목을 표시하고 우측 액션 `div`에 `적용 여부 토글 → 닫기(×)` 순서로 배치한다.
|
||||
- 토글 기본값은 ON이며 OFF에서는 연도 조회조건을 제거하되 현재 연도·반경 입력값은 유지한다.
|
||||
- 닫기는 설정 카드만 숨기고 레이어와 현재 설정값은 유지한다.
|
||||
- 침수흔적도 설정과 동일하게 카드 header `32px`, 제목 `1.15rem`, 본문 `1.05rem`, 본문 padding `8px`, SelectBox `62×22px`, SelectBox font `1rem`·좌우 padding `4px`를 적용한다.
|
||||
- 주황 카드·제목 스타일은 제거하고 공통 border/background/label 색상으로 통일한다.
|
||||
- 반경과 년도설정의 `✓` 적용 버튼은 모두 유지하며 SelectBox 선택만으로 자동 적용하지 않는다.
|
||||
- `map/page.tsx`의 선택 레이어 설정 공통 header에서는 활성 카드 개수와 카드명 pill/chip 목록을 제거하고 `선택 레이어 설정` 제목만 유지한다.
|
||||
- 공통 상단 표시 제거는 지반침하·침수흔적도·조사시설·노후관로·침수심 범례 등 모든 설정 카드에 적용한다.
|
||||
- 검증은 토글/닫기 배치, 카드·SelectBox 스타일 일치, `✓` 적용 유지, 상단 개수·chip 미표시, 다중 카드 순서·스크롤 회귀를 포함한다.
|
||||
- API·DTO·DB 변경은 없다.
|
||||
|
||||
### 16.1 구현 결과
|
||||
|
||||
- `SinkholeLayerSettingPanel` compact 카드에 공통 header·토글·닫기와 침수흔적도 기준 입력 스타일을 적용했다.
|
||||
- 반경·년도 `✓` 적용 버튼은 유지했고 토글·닫기 상태는 `map/page.tsx`에서 관리한다.
|
||||
|
||||
Reference in New Issue
Block a user