초기화

This commit is contained in:
2026-07-15 18:37:19 +09:00
commit 94abc5461d
1268 changed files with 380198 additions and 0 deletions
+728
View File
@@ -0,0 +1,728 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>설비 관리 및 실시간 HMI - ChemiFactory</title>
<!-- Google Fonts Inter -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- 테마 적용 (base.css보다 먼저 로드해 FOWT 방지) -->
<script src="js/theme.js"></script>
<link rel="stylesheet" href="css/base.css">
<style>
/* 설비 카드 그리드 전용 스타일 */
.equip-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.equip-card {
cursor: pointer;
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 180px;
}
.equip-card.active-card {
border-color: var(--accent-blue);
box-shadow: 0 0 15px rgba(59, 130, 246, 0.15);
}
.equip-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.equip-title {
font-size: 18px;
font-weight: 700;
color: var(--text-primary);
}
.equip-type {
font-size: 11px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 2px;
}
.equip-status-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 24px;
padding-top: 14px;
border-top: 1px solid var(--border-color);
}
/* HMI 디테일 분할 레이아웃 */
.hmi-layout {
display: grid;
grid-template-columns: 3fr 2fr;
gap: 24px;
margin-top: 20px;
}
.hmi-section {
background-color: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 24px;
}
.hmi-panel-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border-color);
color: var(--text-primary);
display: flex;
justify-content: space-between;
align-items: center;
}
/* 실시간 램프 그리드 */
.lamp-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
gap: 12px;
}
.lamp-card {
background: var(--overlay-subtle);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 12px;
display: flex;
align-items: center;
justify-content: space-between;
}
.lamp-label {
font-size: 12px;
color: var(--text-secondary);
}
.lamp-indicator {
width: 14px;
height: 14px;
border-radius: 50%;
background-color: var(--toggle-off-bg); /* OFF 상태 */
transition: all 0.2s ease;
}
.lamp-indicator.active {
background-color: var(--accent-green);
box-shadow: 0 0 10px var(--accent-green-glow);
}
/* 제어 버튼 폼 */
.control-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
margin-bottom: 20px;
}
#hmi-detail-panel {
position: relative;
}
.initialization-overlay {
position: absolute;
inset: 0;
z-index: 2000;
display: none;
align-items: center;
justify-content: center;
background: var(--modal-backdrop);
backdrop-filter: blur(2px);
border-radius: 10px;
cursor: wait;
}
.initialization-overlay.active {
display: flex;
}
.initialization-overlay-message {
padding: 22px 30px;
border: 1px solid rgba(59, 130, 246, 0.65);
border-radius: 10px;
background: var(--modal-bg);
color: var(--text-primary);
text-align: center;
font-weight: 700;
line-height: 1.7;
box-shadow: 0 12px 30px var(--card-shadow);
}
.btn-control {
padding: 14px;
font-weight: 600;
border-radius: 8px;
border: 1px solid var(--border-color);
background: var(--overlay-subtle);
color: var(--text-primary);
cursor: pointer;
transition: all 0.2s;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.btn-control:hover {
background: var(--overlay-strong);
border-color: var(--text-secondary);
}
.btn-control:active {
transform: scale(0.98);
}
.btn-control.momentary-active {
background: rgba(59, 130, 246, 0.2) !important;
border-color: var(--accent-blue) !important;
box-shadow: 0 0 10px var(--accent-blue-glow);
}
/* D영역 수치 입출력 */
.register-grid {
display: grid;
grid-template-columns: 1fr;
gap: 14px;
}
.register-row {
display: flex;
align-items: center;
justify-content: space-between;
background: var(--overlay-subtle);
padding: 12px 16px;
border-radius: 8px;
border: 1px solid var(--border-color);
}
.register-value {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.lamp-label {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.input-inline {
background: var(--bg-dark);
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: 6px 10px;
border-radius: 6px;
width: 80px;
text-align: right;
font-weight: 600;
}
.input-inline:focus {
border-color: var(--accent-blue);
outline: none;
}
/* 모달 스타일 */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: var(--modal-backdrop);
align-items: center;
justify-content: center;
}
.modal-content {
background-color: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 24px;
width: 90%;
max-width: 450px;
box-shadow: 0 10px 30px var(--card-shadow);
}
.modal-header {
font-size: 18px;
font-weight: 700;
margin-bottom: 20px;
color: var(--text-primary);
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
font-size: 13px;
color: var(--text-secondary);
margin-bottom: 6px;
}
.form-control {
width: 100%;
background-color: var(--bg-dark);
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: 10px 12px;
border-radius: 8px;
}
.form-control:focus {
border-color: var(--accent-blue);
outline: none;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 24px;
}
/* HMI Functional Groups Styling */
.group-tab-container {
display: flex;
gap: 12px;
margin-bottom: 24px;
background: var(--overlay-subtle);
padding: 6px;
border-radius: 8px;
border: 1px solid var(--border-color);
width: fit-content;
}
.btn-tab {
padding: 8px 20px;
font-size: 14px;
font-weight: 600;
background: transparent;
color: var(--text-secondary);
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.btn-tab.active {
background: var(--accent-blue);
color: var(--text-on-accent);
box-shadow: 0 0 10px var(--accent-blue-glow);
}
.hmi-groups-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
gap: 20px;
}
.hmi-group-card {
background-color: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 16px;
}
.hmi-group-title {
font-size: 15px;
font-weight: 700;
color: var(--text-primary);
border-bottom: 1px solid var(--border-color);
padding-bottom: 8px;
margin-bottom: 4px;
display: flex;
justify-content: space-between;
align-items: center;
}
/* HMI Button with Integrated Status Glow */
.btn-hmi {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
background: var(--overlay-subtle);
border: 1px solid var(--border-color);
border-radius: 8px;
color: var(--text-primary);
font-weight: 600;
font-size: 13px;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-hmi:hover {
background: var(--overlay-strong);
}
.btn-hmi:active {
transform: scale(0.98);
}
/* HMI Lamp Base (Global) */
.hmi-lamp {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: var(--toggle-off-bg);
box-shadow: none;
transition: all 0.2s ease;
}
.hmi-lamp.active {
background-color: var(--accent-green);
box-shadow: 0 0 8px var(--accent-green-glow);
}
.hmi-lamp.active-red {
background-color: var(--accent-red);
box-shadow: 0 0 8px var(--accent-red-glow);
}
/* HMI Button with Integrated Status Glow */
.btn-hmi.active {
border-color: var(--accent-green);
}
.btn-hmi.active .hmi-lamp {
background-color: var(--accent-green);
box-shadow: 0 0 8px var(--accent-green-glow);
}
.btn-hmi.active-red {
border-color: var(--accent-red);
}
.btn-hmi.active-red .hmi-lamp {
background-color: var(--accent-red);
box-shadow: 0 0 8px var(--accent-red-glow);
}
/* Compact Port Hanger Grid */
.port-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 8px;
}
.port-cell {
padding: 12px 6px;
font-size: 13px;
font-weight: 600;
text-align: center;
background: var(--overlay-subtle);
border: 1px solid var(--border-color);
border-radius: 6px;
color: var(--text-primary);
cursor: pointer;
transition: all 0.2s ease;
position: relative;
}
.port-cell:hover {
background: var(--overlay-strong);
border-color: var(--text-secondary);
}
.port-cell.port-active-auto-down {
border-color: var(--accent-blue);
color: var(--accent-blue);
box-shadow: inset 0 0 8px rgba(59, 130, 246, 0.15);
}
.port-cell.port-active-manual-down {
border-color: var(--accent-orange);
color: var(--accent-orange);
box-shadow: inset 0 0 8px rgba(245, 158, 11, 0.15);
}
.port-cell.port-active-manual-up {
border-color: var(--accent-green);
color: var(--accent-green);
box-shadow: inset 0 0 8px rgba(16, 185, 129, 0.15);
}
/* Popover Overlay */
.hmi-popover {
position: absolute;
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 10px;
box-shadow: 0 10px 25px var(--card-shadow);
display: none;
flex-direction: column;
gap: 6px;
z-index: 500;
width: 130px;
}
.hmi-popover button {
width: 100%;
padding: 6px 10px;
font-size: 11px;
font-weight: 600;
text-align: center;
}
@media (max-width: 992px) {
.hmi-groups-container {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="app-container">
<!-- 공통 사이드바 -->
<aside class="sidebar" id="sidebar"></aside>
<!-- 메인 콘텐츠 -->
<main class="main-content">
<header class="content-header">
<div class="page-title">
<h1 id="main-page-title">설비 및 실시간 HMI 제어</h1>
<p id="main-page-desc">등록된 설비 리스트 확인 및 개별 HMI 원격 제어 패널</p>
</div>
</header>
<!-- 설비 목록 그리드 -->
<div class="equip-grid" id="equipment-cards-container">
<!-- 동적 로드 -->
</div>
<!-- HMI 상세 원격 제어 판넬 (설비 선택 시 노출) -->
<div id="hmi-detail-panel" style="display: none; margin-top: 15px;">
<div id="initialization-overlay" class="initialization-overlay" aria-hidden="true">
<div class="initialization-overlay-message">설비 초기화 중<br><span style="font-size:13px; font-weight:400; color:var(--text-secondary);">PLC 상태 동기화가 완료될 때까지 조작할 수 없습니다.</span></div>
</div>
<!-- A/B 그룹 제어 토글 탭, 목록 복귀 & 수동 초기화 버튼 -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; flex-wrap: wrap; gap: 12px;">
<div style="display: flex; gap: 12px; align-items: center;">
<button class="btn" style="background: var(--overlay-hover); border: 1px solid var(--border-color); color: var(--text-primary); padding: 8px 16px; font-weight: 600; font-size: 14px;" onclick="goBackToEquipmentList()">📋 목록</button>
<div class="group-tab-container" style="margin-bottom: 0;">
<button class="btn-tab active" id="btn-tab-a" onclick="setHmiActiveGroup('A')">A그룹</button>
<button class="btn-tab" id="btn-tab-b" onclick="setHmiActiveGroup('B')">B그룹</button>
</div>
</div>
<button class="btn" style="background: var(--nav-active-bg); border: 1px solid var(--accent-blue); color: var(--accent-blue); padding: 8px 16px; font-weight: 600; font-size: 14px;" onclick="triggerManualSync()">🔄 Sync 초기화</button>
</div>
<!-- 6대 기능 그룹 그리드 -->
<div class="hmi-groups-container">
<!-- 1그룹: 그룹 활성화 및 리셋 -->
<div class="hmi-group-card">
<div class="hmi-group-title">
<span>메인 제어</span>
<span class="badge badge-online" id="hmi-websocket-status">실시간 연결</span>
</div>
<!-- 안전 인터락 상태 모니터링 영역 (A-1) -->
<div class="interlock-status-container" style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 4px; padding: 6px; background: var(--overlay-subtle); border-radius: 6px; margin-bottom: 12px; border: 1px solid var(--border-color); text-align: center;">
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; font-size: 10px; color: var(--text-secondary);">
<span>비상정지</span>
<span class="hmi-lamp" id="lamp-estop"></span>
</div>
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; font-size: 10px; color: var(--text-secondary);">
<span>도어(좌)</span>
<span class="hmi-lamp" id="lamp-door-left"></span>
</div>
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; font-size: 10px; color: var(--text-secondary);">
<span>도어(우)</span>
<span class="hmi-lamp" id="lamp-door-right"></span>
</div>
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; font-size: 10px; color: var(--text-secondary);">
<span>수위장치</span>
<span class="hmi-lamp" id="lamp-water-level"></span>
</div>
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; font-size: 10px; color: var(--text-secondary);">
<span>히터장치</span>
<span class="hmi-lamp" id="lamp-heater-interlock"></span>
</div>
</div>
<div class="control-grid" style="grid-template-columns: 1fr 1fr; margin-bottom: 0;">
<button class="btn-hmi" id="btn-group-active-on" onclick="sendHmiBit(1, 'group_active_on')">전체 활성화 ON <span class="hmi-lamp" id="lamp-group-active-on"></span></button>
<button class="btn-hmi" id="btn-group-active-off" onclick="sendHmiBit(2, 'group_active_off')">전체 활성화 OFF <span class="hmi-lamp" id="lamp-group-active-off"></span></button>
</div>
<div style="display:grid; grid-template-columns: repeat(3, 1fr); gap:10px; margin-top:8px;">
<button class="btn-hmi btn-danger" style="background: rgba(239,68,68,0.15);" id="btn-group-stop" onclick="sendHmiBit(3, 'group_stop')">전체정지 <span class="hmi-lamp" id="lamp-group-stop"></span></button>
<button class="btn-hmi" style="justify-content:center; cursor:default;" id="btn-group-pause">일시정지 <span class="hmi-lamp" style="margin-left:8px;" id="lamp-group-pause"></span></button>
<button class="btn-hmi" style="justify-content:center; cursor:default;" id="btn-group-restart">재시작 <span class="hmi-lamp" style="margin-left:8px;" id="lamp-group-restart"></span></button>
</div>
<button class="btn btn-secondary btn-hmi" style="justify-content:center; gap:8px;" id="btn-interlock-reset" onclick="sendHmiBit(0, 'interlock_reset')">⚡ 인터락 리셋 <span class="hmi-lamp" id="lamp-interlock-reset"></span></button>
</div>
<!-- 2그룹: 함침부 작동 -->
<div class="hmi-group-card">
<div class="hmi-group-title">
<span>🧪 함침부</span>
<div style="display: flex; gap: 6px;">
<button class="btn-hmi" id="btn-impreg-hanger-on-g2" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(4, 'impreg_hanger_on')">ON <span class="hmi-lamp" id="lamp-impreg-hanger-on-g2"></span></button>
<button class="btn-hmi" id="btn-impreg-hanger-off-g2" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(5, 'impreg_hanger_off')">OFF <span class="hmi-lamp" id="lamp-impreg-hanger-off-g2"></span></button>
</div>
</div>
<!-- 기존 조작 버튼들 -->
<div style="display:flex; justify-content:space-between; gap:10px;">
<button class="btn-hmi" style="flex:1;" id="btn-impreg-auto" onclick="sendHmiBit(24, 'impreg_auto')">자동 모드 <span class="hmi-lamp" id="lamp-impreg-auto"></span></button>
<button class="btn-hmi" style="flex:1;" id="btn-impreg-manual" onclick="sendHmiBit(25, 'impreg_manual')">수동 모드 <span class="hmi-lamp" id="lamp-impreg-manual"></span></button>
</div>
<div style="display:flex; justify-content:space-between; gap:10px;">
<button class="btn-hmi" style="flex:1;" id="btn-pump-circ" onclick="sendHmiBit(26, 'pump_circ')">순환 펌프 <span class="hmi-lamp" id="lamp-pump-circ"></span></button>
<button class="btn-hmi" style="flex:1;" id="btn-pump-drain" onclick="sendHmiBit(27, 'pump_drain')">배출 펌프 <span class="hmi-lamp" id="lamp-pump-drain"></span></button>
</div>
<div class="register-row">
<span class="lamp-label">순환펌프 재작동 시간</span>
<span class="register-value" id="val-pump-circ-time">0 <span style="font-size:11px; font-weight:normal; color:var(--text-secondary);"></span></span>
</div>
</div>
<!-- 3그룹: 건조기 작동 -->
<div class="hmi-group-card">
<div class="hmi-group-title">
<span>♨️ 건조부</span>
<div style="display: flex; gap: 6px;">
<button class="btn-hmi" id="btn-dryer-group-on" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(6, 'dryer_on')">ON <span class="hmi-lamp" id="lamp-dryer-on"></span></button>
<button class="btn-hmi" id="btn-dryer-group-off" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(7, 'dryer_off')">OFF <span class="hmi-lamp" id="lamp-dryer-off"></span></button>
</div>
</div>
<!-- 기존 조작 버튼들 -->
<div style="display:flex; justify-content:space-between; gap:10px;">
<button class="btn-hmi" style="flex:1;" id="btn-dryer-auto" onclick="sendHmiBit(32, 'dryer_auto')">자동 모드 <span class="hmi-lamp" id="lamp-dryer-auto"></span></button>
<button class="btn-hmi" style="flex:1;" id="btn-dryer-manual" onclick="sendHmiBit(33, 'dryer_manual')">수동 모드 <span class="hmi-lamp" id="lamp-dryer-manual"></span></button>
</div>
<button class="btn-hmi" id="btn-dryer-fan" onclick="sendHmiBit(34, 'dryer_fan')">송풍기 작동 <span class="hmi-lamp" id="lamp-dryer-fan"></span></button>
<div style="display:grid; grid-template-columns: repeat(5, 1fr); gap:6px;">
<button class="btn-hmi" style="flex-direction:column; padding:8px 4px; gap:4px; font-size:11px;" id="btn-dryer-h1" onclick="sendHmiBit(35, 'dryer_h1')">H1 <span class="hmi-lamp" id="lamp-dryer-h1"></span></button>
<button class="btn-hmi" style="flex-direction:column; padding:8px 4px; gap:4px; font-size:11px;" id="btn-dryer-h2" onclick="sendHmiBit(36, 'dryer_h2')">H2 <span class="hmi-lamp" id="lamp-dryer-h2"></span></button>
<button class="btn-hmi" style="flex-direction:column; padding:8px 4px; gap:4px; font-size:11px;" id="btn-dryer-h3" onclick="sendHmiBit(37, 'dryer_h3')">H3 <span class="hmi-lamp" id="lamp-dryer-h3"></span></button>
<button class="btn-hmi" style="flex-direction:column; padding:8px 4px; gap:4px; font-size:11px;" id="btn-dryer-h4" onclick="sendHmiBit(38, 'dryer_h4')">H4 <span class="hmi-lamp" id="lamp-dryer-h4"></span></button>
<button class="btn-hmi" style="flex-direction:column; padding:8px 4px; gap:4px; font-size:11px;" id="btn-dryer-h5" onclick="sendHmiBit(39, 'dryer_h5')">H5 <span class="hmi-lamp" id="lamp-dryer-h5"></span></button>
</div>
<div style="display:flex; justify-content:space-between; gap:10px;">
<div class="btn-hmi" style="flex:1; cursor:default; background:var(--overlay-subtle);" id="lamp-card-fan-trip">송풍기 트립 <span class="hmi-lamp" id="lamp-fan-trip"></span></div>
<div class="btn-hmi" style="flex:1; cursor:default; background:var(--overlay-subtle);" id="lamp-card-heater-req">히터 제어 <span class="hmi-lamp" id="lamp-heater-req"></span></div>
</div>
</div>
<!-- 4그룹: 커팅부 작동 -->
<div class="hmi-group-card">
<div class="hmi-group-title">
<span>✂️ 커팅부</span>
<div style="display: flex; gap: 6px;">
<button class="btn-hmi" id="btn-cutter-group-on" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(8, 'cutter_on')">ON <span class="hmi-lamp" id="lamp-cutter-on"></span></button>
<button class="btn-hmi" id="btn-cutter-group-off" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(9, 'cutter_off')">OFF <span class="hmi-lamp" id="lamp-cutter-off"></span></button>
</div>
</div>
<!-- 기존 조작 버튼들 -->
<button class="btn-hmi" id="btn-cutter-motor" onclick="sendHmiBit(48, 'cutter_motor')">커팅모터 작동 기동 <span class="hmi-lamp" id="lamp-cutter-motor-start"></span></button>
<div style="display:flex; justify-content:space-between; gap:10px;">
<div class="btn-hmi" style="flex:1; cursor:default; background:var(--overlay-subtle);">모터 활성 피드백 <span class="hmi-lamp" id="lamp-cutter-motor-active"></span></div>
</div>
<div class="register-row">
<span class="lamp-label">실시간 커팅모터 속도</span>
<span class="register-value" id="val-cutter-speed">0.0 <span style="font-size:11px; font-weight:normal; color:var(--text-secondary);">Hz</span></span>
</div>
</div>
<!-- 5그룹: 이송 작동 -->
<div class="hmi-group-card">
<div class="hmi-group-title">
<span>🔄 이송부</span>
<div style="display: flex; gap: 6px;">
<button class="btn-hmi" id="btn-transfer-group-on" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(10, 'transfer_on')">ON <span class="hmi-lamp" id="lamp-transfer-on"></span></button>
<button class="btn-hmi" id="btn-transfer-group-off" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(11, 'transfer_off')">OFF <span class="hmi-lamp" id="lamp-transfer-off"></span></button>
</div>
</div>
<!-- 기존 조작 버튼들 -->
<div style="display:flex; justify-content:space-between; gap:10px;">
<button class="btn-hmi" style="flex:1;" id="btn-transfer-auto" onclick="sendHmiBit(50, 'transfer_auto')">자동 모드 <span class="hmi-lamp" id="lamp-transfer-auto"></span></button>
<button class="btn-hmi" style="flex:1;" id="btn-transfer-manual" onclick="sendHmiBit(51, 'transfer_manual')">수동 모드 <span class="hmi-lamp" id="lamp-transfer-manual"></span></button>
</div>
<div style="display:flex; justify-content:space-between; gap:10px;">
<button class="btn-hmi" style="flex:1;" id="btn-transfer-auto-start" onclick="sendHmiBit(52, 'transfer_auto_start')">자동 시작 <span class="hmi-lamp" id="lamp-transfer-auto-start"></span></button>
<button class="btn-hmi" style="flex:1;" id="btn-transfer-auto-stop" onclick="sendHmiBit(53, 'transfer_auto_stop')">자동 정지 <span class="hmi-lamp" id="lamp-transfer-auto-stop"></span></button>
</div>
<div style="display:grid; grid-template-columns: repeat(3, 1fr); gap:8px;">
<button class="btn-hmi" style="padding:10px 4px; font-size:12px;" id="btn-transfer-fwd" onclick="sendHmiBit(54, 'transfer_fwd')">수동(정) <span class="hmi-lamp" id="lamp-transfer-fwd"></span></button>
<button class="btn-hmi" style="padding:10px 4px; font-size:12px;" id="btn-transfer-rev" onclick="sendHmiBit(55, 'transfer_rev')">수동(역) <span class="hmi-lamp" id="lamp-transfer-rev"></span></button>
<button class="btn-hmi btn-danger" style="padding:10px 4px; font-size:12px; background: rgba(239,68,68,0.15);" id="btn-transfer-stop" onclick="sendHmiBit(56, 'transfer_stop')">수동 정지 <span class="hmi-lamp" id="lamp-transfer-stop"></span></button>
</div>
<div style="display:flex; justify-content:space-between; gap:10px;">
<button class="btn-hmi" style="flex:1;" id="btn-transfer-zero" onclick="sendHmiBit(57, 'transfer_zero')">제로셋 <span class="hmi-lamp" id="lamp-transfer-zero"></span></button>
<button class="btn-hmi" style="flex:1;" id="btn-transfer-err-reset" onclick="sendHmiBit(58, 'transfer_err_reset')">에러리셋 <span class="hmi-lamp" id="lamp-transfer-err-reset"></span></button>
</div>
<div class="register-row">
<span class="lamp-label">현재 이동량 (1초 기준)</span>
<span class="register-value" id="val-transfer-cur-pos">0.0 <span style="font-size:11px; font-weight:normal; color:var(--text-secondary);">mm/s</span></span>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 10px;">
<!-- 자동 목표 이동량 설정 -->
<div style="display: flex; flex-direction: column; gap: 6px; background: var(--overlay-subtle); border: 1px solid var(--border-color); padding: 8px 12px; border-radius: 8px;">
<span class="lamp-label" style="font-size: 13px; color: var(--text-primary); font-weight: 600;">자동 이동량 (mm/rev)</span>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 6px; align-items: center; width: 100%;">
<span style="font-size: 13px; color: var(--text-primary); white-space: nowrap; text-align: left;">현: <span id="val-transfer-target-pos" style="color: var(--text-primary); font-weight: 600;">0</span></span>
<input type="number" class="input-inline" style="width: 100%; box-sizing: border-box; padding: 4px 6px; font-size: 12px; text-align: center;" id="input-transfer-target-pos" value="0">
</div>
<button class="btn btn-primary" style="width: 100%; padding: 6px; font-size: 11px; white-space: nowrap; margin-top: 2px;" onclick="sendHmiRegister(0, 'input-transfer-target-pos')">전송</button>
</div>
<!-- 수동 목표 속도 설정 -->
<div style="display: flex; flex-direction: column; gap: 6px; background: var(--overlay-subtle); border: 1px solid var(--border-color); padding: 8px 12px; border-radius: 8px;">
<span class="lamp-label" style="font-size: 13px; color: var(--text-primary); font-weight: 600;">수동 속도 (mm/s)</span>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 6px; align-items: center; width: 100%;">
<span style="font-size: 13px; color: var(--text-primary); white-space: nowrap; text-align: left;">현: <span id="val-transfer-target-speed" style="color: var(--text-primary); font-weight: 600;">0</span></span>
<input type="number" class="input-inline" style="width: 100%; box-sizing: border-box; padding: 4px 6px; font-size: 12px; text-align: center;" id="input-transfer-target-speed" value="0">
</div>
<button class="btn btn-primary" style="width: 100%; padding: 6px; font-size: 11px; white-space: nowrap; margin-top: 2px;" onclick="sendHmiRegister(1, 'input-transfer-target-speed')">전송</button>
</div>
</div>
</div>
<!-- 6그룹: 행거부 작동 -->
<div class="hmi-group-card" style="position:relative;">
<div class="hmi-group-title">
<span>🪝 행거부</span>
<div style="display: flex; gap: 6px;">
<button class="btn-hmi" id="btn-impreg-hanger-on-g6" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(4, 'impreg_hanger_on')">ON <span class="hmi-lamp" id="lamp-impreg-hanger-on-g6"></span></button>
<button class="btn-hmi" id="btn-impreg-hanger-off-g6" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(5, 'impreg_hanger_off')">OFF <span class="hmi-lamp" id="lamp-impreg-hanger-off-g6"></span></button>
</div>
</div>
<!-- 기존 조작 영역 -->
<div class="port-grid" id="hanger-ports-container">
<!-- 20개 컴팩트 셀 JS로 동적 렌더링 -->
</div>
<!-- 공용 팝오버 오버레이 -->
<div class="hmi-popover" id="hanger-popover">
<h4 style="margin:0 0 6px 0; font-size:11px; color:var(--text-primary); text-align:center;" id="popover-title">P01 제어</h4>
<button class="btn btn-primary" id="btn-popover-auto-down">자동하강</button>
<button class="btn" style="background:rgba(245,158,11,0.2); border-color:var(--accent-orange); color:var(--accent-orange);" id="btn-popover-manual-down">수동하강</button>
<button class="btn" style="background:rgba(16,185,129,0.2); border-color:var(--accent-green); color:var(--accent-green);" id="btn-popover-manual-up">수동상승</button>
</div>
</div>
</div>
</main>
</div>
<!-- 신규 설비 등록 모달 -->
<div class="modal" id="add-equipment-modal">
<div class="modal-content">
<div class="modal-header">신규 설비 등록</div>
<form id="add-equipment-form" onsubmit="saveEquipment(event)">
<div class="form-group">
<label for="eq-id">설비 코드 (Unique ID)</label>
<input type="text" id="eq-id" class="form-control" placeholder="예: CC_MC_TYPE_A" required>
</div>
<div class="form-group">
<label for="eq-name">설비명</label>
<input type="text" id="eq-name" class="form-control" placeholder="예: 카본절단기 1호기" required>
</div>
<div class="form-group">
<label for="eq-type">설비 타입</label>
<input type="text" id="eq-type" class="form-control" placeholder="예: cutter" required>
</div>
<div class="form-group">
<label for="eq-ip">IP 주소</label>
<input type="text" id="eq-ip" class="form-control" placeholder="예: 192.168.1.100" required>
</div>
<div class="modal-footer">
<button type="button" class="btn" onclick="closeAddModal()" style="background:transparent; color:var(--text-secondary);">취소</button>
<button type="submit" class="btn btn-primary">등록하기</button>
</div>
</form>
</div>
</div>
<!-- 스크립트 파일 연결 -->
<script src="js/common.js"></script>
<script src="js/equipment.js"></script>
</body>
</html>