From 872b3d08c06d3a44a761aba3e61d2352bcb6ad14 Mon Sep 17 00:00:00 2001 From: ljpark Date: Tue, 1 Sep 2026 18:10:31 +0900 Subject: [PATCH] =?UTF-8?q?sn:=20=EC=84=A0=ED=83=9D=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=20=EC=A0=84=EC=B2=B4=20=EB=94=94=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agedPipe/AgedPipeLayerSettingPanel.tsx | 24 ++- .../components/exmn/ExmnLayerSettingPanel.tsx | 45 ++-- .../FldngHstryLayerSettingPanel.tsx | 16 +- .../layerSetting/LayerSettingCard.tsx | 59 ++++++ .../sinkhole/SinkholeLayerSettingPanel.tsx | 34 ++- apps/sn/app/(map-layout)/map/page.tsx | 195 ++++++++++++------ .../map/waterDepth/waterDepth.tsx | 4 +- .../001-fe-SFR-010-004~007-조사사업-작업카드.md | 19 ++ .../001-fe-SFR-010-010-지반침하정보-작업카드.md | 21 ++ 9 files changed, 314 insertions(+), 103 deletions(-) create mode 100644 apps/sn/app/(map-layout)/map/components/layerSetting/LayerSettingCard.tsx diff --git a/apps/sn/app/(map-layout)/map/components/agedPipe/AgedPipeLayerSettingPanel.tsx b/apps/sn/app/(map-layout)/map/components/agedPipe/AgedPipeLayerSettingPanel.tsx index 9a6174db1..9dda96f8f 100644 --- a/apps/sn/app/(map-layout)/map/components/agedPipe/AgedPipeLayerSettingPanel.tsx +++ b/apps/sn/app/(map-layout)/map/components/agedPipe/AgedPipeLayerSettingPanel.tsx @@ -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 | Promise; @@ -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 = ( ); const settingRow = ( -
+
{indicator}
@@ -96,9 +103,14 @@ export default function AgedPipeLayerSettingPanel({ if (compact) { return ( -
+ {settingRow} -
+ ); } diff --git a/apps/sn/app/(map-layout)/map/components/exmn/ExmnLayerSettingPanel.tsx b/apps/sn/app/(map-layout)/map/components/exmn/ExmnLayerSettingPanel.tsx index 8d0907f5a..a8e0a020f 100644 --- a/apps/sn/app/(map-layout)/map/components/exmn/ExmnLayerSettingPanel.tsx +++ b/apps/sn/app/(map-layout)/map/components/exmn/ExmnLayerSettingPanel.tsx @@ -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 ( -
-
- {/* 관로/맨홀 조사유형을 구분하는 지도 범례 의미색이므로 고정색을 유지한다. */} - - - {jobType === "PPL" ? "관로조사" : "맨홀조사"} - -
-
-
+ ); } diff --git a/apps/sn/app/(map-layout)/map/components/fldngHstry/FldngHstryLayerSettingPanel.tsx b/apps/sn/app/(map-layout)/map/components/fldngHstry/FldngHstryLayerSettingPanel.tsx index ed579f904..0cf554a46 100644 --- a/apps/sn/app/(map-layout)/map/components/fldngHstry/FldngHstryLayerSettingPanel.tsx +++ b/apps/sn/app/(map-layout)/map/components/fldngHstry/FldngHstryLayerSettingPanel.tsx @@ -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 (
-
+
침수흔적도 @@ -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)} >
-
+
침수년도
diff --git a/apps/sn/app/(map-layout)/map/components/layerSetting/LayerSettingCard.tsx b/apps/sn/app/(map-layout)/map/components/layerSetting/LayerSettingCard.tsx new file mode 100644 index 000000000..04429c927 --- /dev/null +++ b/apps/sn/app/(map-layout)/map/components/layerSetting/LayerSettingCard.tsx @@ -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 ( +
+
+ {title} +
+ {showToggle ? ( + + ) : null} + +
+
+ {children ? ( +
{children}
+ ) : null} +
+ ); +} diff --git a/apps/sn/app/(map-layout)/map/components/sinkhole/SinkholeLayerSettingPanel.tsx b/apps/sn/app/(map-layout)/map/components/sinkhole/SinkholeLayerSettingPanel.tsx index d928a653e..3765cbba5 100644 --- a/apps/sn/app/(map-layout)/map/components/sinkhole/SinkholeLayerSettingPanel.tsx +++ b/apps/sn/app/(map-layout)/map/components/sinkhole/SinkholeLayerSettingPanel.tsx @@ -10,6 +10,7 @@ import { type SinkholeSyncSummary, type SinkholeYearRange, } from "./sinkholeTypes"; +import LayerSettingCard from "../layerSetting/LayerSettingCard"; type MaybePromise = T | Promise; @@ -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 = ( ); const settingRows = ( -
-
+
+
{indicator}
-
+
{indicator}
@@ -208,7 +215,7 @@ export default function SinkholeLayerSettingPanel({ title="적용" aria-label="연도범위 적용" > - ✓ + ✓
@@ -217,9 +224,14 @@ export default function SinkholeLayerSettingPanel({ if (compact) { return ( -
+
{settingRows}
-
+ ); } diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index 9b6037bdf..ff0c97513 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -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: ( -
-
-
-
- 침수심 범례 -
-
- {waterDepthLegendItems.map((item) => ( -
- - - {item.label} - -
- ))} -
+ setIsWaterDepthLegendVisible(false)} + showToggle={false} + > + {isWaterDepthLegendEnabled ? ( +
+ {waterDepthLegendItems.map((item) => ( +
+ + + {item.label} + +
+ ))}
-
-
+ ) : null} + ), }); } @@ -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 (
-
- - 선택 레이어 설정 - - - {activeLayerSettingItems.length}건 - -
-
- {activeLayerSettingItems.map((item) => ( - - {item.label} - - ))} -
+ + 선택 레이어 설정 +
{ 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, diff --git a/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx b/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx index 3c9cb48f5..266b4b59c 100644 --- a/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx +++ b/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx @@ -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 = ( diff --git a/apps/sn/docs/작업계획/SFR-010-004~SFR-010-007-조사사업/001-fe-SFR-010-004~007-조사사업-작업카드.md b/apps/sn/docs/작업계획/SFR-010-004~SFR-010-007-조사사업/001-fe-SFR-010-004~007-조사사업-작업카드.md index f50dfac15..1e06ae146 100644 --- a/apps/sn/docs/작업계획/SFR-010-004~SFR-010-007-조사사업/001-fe-SFR-010-004~007-조사사업-작업카드.md +++ b/apps/sn/docs/작업계획/SFR-010-004~SFR-010-007-조사사업/001-fe-SFR-010-004~007-조사사업-작업카드.md @@ -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에서는 빈 필터로 다시 조회한다. diff --git a/apps/sn/docs/작업계획/SFR-010-010-지반침하정보/001-fe-SFR-010-010-지반침하정보-작업카드.md b/apps/sn/docs/작업계획/SFR-010-010-지반침하정보/001-fe-SFR-010-010-지반침하정보-작업카드.md index 03072e28a..dc8b2f69e 100644 --- a/apps/sn/docs/작업계획/SFR-010-010-지반침하정보/001-fe-SFR-010-010-지반침하정보-작업카드.md +++ b/apps/sn/docs/작업계획/SFR-010-010-지반침하정보/001-fe-SFR-010-010-지반침하정보-작업카드.md @@ -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`에서 관리한다.