From da77de58ac4051c36c77df506472cfc496cf0716 Mon Sep 17 00:00:00 2001 From: gbpark Date: Wed, 6 May 2026 22:48:37 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20SCADA=20=EB=8D=B0=EB=AA=A8=20=EB=8B=A4?= =?UTF-8?q?=EC=A4=91=20=EA=B2=BD=EA=B3=A0=20=EB=AA=A8=EB=93=9C=EC=97=90?= =?UTF-8?q?=EC=84=9C=20dock=20=EC=9E=98=EB=A6=BC=20=ED=95=B4=EC=86=8C=20(e?= =?UTF-8?q?mergency-stack)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 메인 모달 + alarm-list-dock 을 .emergency-stack 으로 묶어 viewport 안에서 flex 자동 분할 (어떤 해상도/줌에서도 dock 이 잘리지 않게) - with-multi 진입 시 JS 가 stack 으로 동적 wrapping, 해제 시 원위치 복귀 - mini-modal 본문도 line-clamp 3 + min-height:0 로 길이 폭주 차단 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../public/scada-demo/css/invyone-stage2.css | 52 +++++++++++++++++-- frontend/public/scada-demo/js/ui.js | 25 +++++++-- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/frontend/public/scada-demo/css/invyone-stage2.css b/frontend/public/scada-demo/css/invyone-stage2.css index e907f96a..acf15e04 100644 --- a/frontend/public/scada-demo/css/invyone-stage2.css +++ b/frontend/public/scada-demo/css/invyone-stage2.css @@ -479,6 +479,8 @@ body { display: flex; flex-direction: column; } /* mini-modal — 메인 emergency-modal 의 컴팩트 버전 */ .alarm-mini-modal { flex: 1; + min-height: 0; + overflow: hidden; display: flex; flex-direction: column; background: linear-gradient(180deg, #081326 0%, #050a18 100%); @@ -548,8 +550,12 @@ body { display: flex; flex-direction: column; } /* body — 알람 상세 */ .alarm-mini-modal .mini-message { flex: 1; + min-height: 0; padding: 10px 12px; border-bottom: 1px solid #2a3f5a; + overflow: hidden; + display: flex; + flex-direction: column; } .alarm-mini-modal .mini-msg-label { font-size: 10px; @@ -557,11 +563,17 @@ body { display: flex; flex-direction: column; } font-weight: 700; margin-bottom: 4px; letter-spacing: 0.5px; + flex-shrink: 0; } .alarm-mini-modal .mini-msg-text { font-size: 12px; color: #fff; line-height: 1.5; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + min-height: 0; } .alarm-mini-modal.severity-medium .mini-msg-text { color: #cfd3d8; } @@ -953,17 +965,47 @@ body.critical-alarm.map-open::before { display: none; } max-height: 70vh; height: auto; } -/* 다중 경고 — with-map 과 같은 좌측 정렬 + dock 공간 확보 위해 max-height 압축 */ -.emergency-modal-backdrop.with-multi .emergency-modal, -.emergency-modal-backdrop.with-multi.with-map .emergency-modal { +/* 다중 경고 — 모달 + dock 을 .emergency-stack 으로 묶어서 viewport 안에서 자동 분할 + (어떤 해상도/줌에서도 dock 이 viewport 밖으로 잘리지 않도록 robust 하게) */ +.emergency-stack { position: fixed; top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 960px; + max-width: calc(100vw - 48px); + max-height: calc(100vh - 80px); + display: flex; + flex-direction: column; + gap: 14px; + z-index: 103; +} +.emergency-modal-backdrop.with-multi.with-map .emergency-stack { left: calc(50% - 732px); transform: translateY(-50%); - width: 960px; +} +.emergency-modal-backdrop.with-multi .emergency-modal, +.emergency-modal-backdrop.with-multi.with-map .emergency-modal { + position: static; + top: auto; + left: auto; + transform: none; + width: 100%; max-width: none; - max-height: 62vh; + max-height: none; height: auto; + flex: 1 1 auto; + min-height: 0; +} +.emergency-modal-backdrop.with-multi .alarm-list-dock { + position: static; + top: auto; + left: auto; + transform: none; + width: 100%; + max-width: none; + max-height: 32vh; + flex: 0 0 auto; } .emergency-modal { width: min(960px, 92vw); diff --git a/frontend/public/scada-demo/js/ui.js b/frontend/public/scada-demo/js/ui.js index 4bfb60f1..de92512f 100644 --- a/frontend/public/scada-demo/js/ui.js +++ b/frontend/public/scada-demo/js/ui.js @@ -639,9 +639,20 @@ const count = document.getElementById('alarm-list-count'); if (!dock || !rows || !count) return; - // 다중 모드일 때 메인 모달을 좌측 정렬 + 압축 크기로 고정 → dock 이 항상 그 아래 정렬 + // 다중 모드 — 메인 모달 + dock 을 .emergency-stack 으로 묶어 viewport 안에서 자동 분할 const backdrop = document.getElementById('emergency-modal'); - if (backdrop) backdrop.classList.add('with-multi'); + if (backdrop) { + backdrop.classList.add('with-multi'); + const modal = backdrop.querySelector('.emergency-modal'); + let stack = backdrop.querySelector('.emergency-stack'); + if (!stack) { + stack = document.createElement('div'); + stack.className = 'emergency-stack'; + } + if (modal) stack.appendChild(modal); + stack.appendChild(dock); + backdrop.appendChild(stack); + } // CRITICAL 은 메인 모달이 이미 표시 → dock 에는 HIGH/MEDIUM 만 mini-modal 로 const dockAlarms = MULTI_ALARMS.filter(a => a.severity !== 'critical'); @@ -716,7 +727,15 @@ if (rows) rows.innerHTML = ''; if (dock) dock.classList.remove('show'); const backdrop = document.getElementById('emergency-modal'); - if (backdrop) backdrop.classList.remove('with-multi'); + if (backdrop) { + // stack 안의 modal 과 dock 을 원위치로 복귀 후 stack 제거 + const stack = backdrop.querySelector('.emergency-stack'); + const modal = stack?.querySelector('.emergency-modal'); + if (modal) backdrop.appendChild(modal); + if (dock) document.body.appendChild(dock); + stack?.remove(); + backdrop.classList.remove('with-multi'); + } } let cctvClockTimer = null;