434 lines
21 KiB
HTML
434 lines
21 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>Swap Station – Dashboard</title>
|
||
|
||
<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@400;600;700;800&display=swap" rel="stylesheet">
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<script>
|
||
tailwind.config = {
|
||
theme: {
|
||
extend: {
|
||
fontFamily: { sans: ["Inter","ui-sans-serif","system-ui"] },
|
||
keyframes: {
|
||
pulseDot: { "0%,100%": { transform:"scale(1)", opacity: 1 }, "50%": { transform:"scale(1.2)", opacity: .7 } }
|
||
},
|
||
animation: { pulseDot: "pulseDot 1.2s ease-in-out infinite" }
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
:root { color-scheme: dark; }
|
||
html, body { height: 100%; margin: 0; }
|
||
body { background:#0a0a0a; }
|
||
|
||
.page { max-width:1400px; }
|
||
|
||
.glass {
|
||
background: rgba(30,41,59,.45);
|
||
border: 1px solid rgba(255,255,255,.10);
|
||
border-radius: .9rem;
|
||
backdrop-filter: saturate(150%) blur(12px);
|
||
}
|
||
|
||
.badge { font-size:12px; font-weight:800; padding:.35rem .6rem; border-radius:.6rem; border:1px solid; display:inline-flex; align-items:center; gap:.4rem; }
|
||
.chip{display:inline-flex;align-items:center;gap:.4rem;padding:.1rem .5rem;border-radius:.4rem;
|
||
font-size:10px;font-weight:800;letter-spacing:.02em;text-transform:uppercase;border:1px solid}
|
||
.chip-emerald{background:rgba(16,185,129,.15);color:#a7f3d0;border-color:rgba(16,185,129,.35)}
|
||
.chip-rose{background:rgba(244,63,94,.16);color:#fecaca;border-color:rgba(244,63,94,.35)}
|
||
.chip-amber{background:rgba(245,158,11,.18);color:#fde68a;border-color:rgba(245,158,11,.40)}
|
||
.chip-sky{background:rgba(56,189,248,.15);color:#bae6fd;border-color:rgba(56,189,248,.35)}
|
||
.chip-slate{background:rgba(148,163,184,.12);color:#cbd5e1;border-color:rgba(148,163,184,.30)}
|
||
|
||
.cham_chip{display:inline-flex;align-items:center;gap:.4rem;padding:.4rem .5rem;border-radius:.4rem;
|
||
font-size:10px;font-weight:800;letter-spacing:.02em;text-transform:uppercase;border:1px solid}
|
||
.cham_chip-emerald{background:rgba(16,185,129,.15);color:#a7f3d0;border-color:rgba(16,185,129,.35)}
|
||
.cham_chip-rose{background:rgba(244,63,94,.16);color:#fecaca;border-color:rgba(244,63,94,.35)}
|
||
.cham_chip-amber{background:rgba(245,158,11,.18);color:#fde68a;border-color:rgba(245,158,11,.40)}
|
||
.cham_chip-sky{background:rgba(56,189,248,.15);color:#bae6fd;border-color:rgba(56,189,248,.35)}
|
||
.cham_chip-slate{background:rgba(148,163,184,.12);color:#cbd5e1;border-color:rgba(148,163,184,.30)}
|
||
|
||
.btn { font-weight:700; font-size:10px; padding: 0.15rem 0.5rem; border-radius:.5rem; border:1px solid rgba(255,255,255,.12); background:rgba(255,255,255,.06); transition:.18s; }
|
||
.btn:hover { border-color: rgba(16,185,129,.45); background: rgba(16,185,129,.10); }
|
||
.btn-primary{background-image:linear-gradient(to right,#10b981,#14b8a6,#06b6d4);color:#fff;border-color:transparent}
|
||
.btn-primary:hover{filter:brightness(1.05);transform:translateY(-1px)}
|
||
.btn-danger { border-color: rgba(244,63,94,.35); background: rgba(244,63,94,.14); color: #fecaca; }
|
||
.btn-danger:hover { background: rgba(244,63,94,.22); }
|
||
.btn-ghost { border-color: rgba(255,255,255,.10); background: rgba(255,255,255,.05); }
|
||
|
||
.field{font-size:10px;color:#9ca3af}
|
||
.value{font-size:12px;font-weight:600;color:#e5e7eb}
|
||
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}
|
||
|
||
.chamber-card.paired{border-color:#34d399!important;box-shadow:0 0 0 2px rgba(52,211,153,.45)}
|
||
.chamber-card.pending{border-color:#60a5fa!important;box-shadow:0 0 0 2px rgba(96,165,250,.45)}
|
||
|
||
.door-pill{color:#fff;font-size:10px;font-weight:700;padding:4px;border-radius:6px; width: 100%; text-align: center;}
|
||
.door-open{background:#22c55e}.door-close{background:#ef4444}
|
||
|
||
.bat-id-big{font-size:14px;font-weight:800;border-radius:.5rem;padding:.2rem .4rem;
|
||
background:rgba(0,0,0,.25);border:1px solid rgba(255,255,255,.10)}
|
||
</style>
|
||
</head>
|
||
<body class="min-h-screen text-gray-100 flex flex-col">
|
||
|
||
<div class="pointer-events-none fixed inset-0">
|
||
<div class="absolute -top-24 -left-24 w-[32rem] h-[32rem] rounded-full bg-emerald-500/10 blur-3xl"></div>
|
||
<div class="absolute -bottom-24 -right-24 w-[36rem] h-[36rem] rounded-full bg-sky-500/10 blur-3xl"></div>
|
||
</div>
|
||
|
||
<header class="relative z-10 border-b border-white/10 bg-black/20 backdrop-blur">
|
||
<div class="mx-auto max-w-7.5xl px-3 sm:px-4 py-2 grid grid-cols-[auto_1fr_auto] items-center gap-2">
|
||
<div class="flex items-center gap-2 sm:gap-3">
|
||
<a href="./station_selection.html"
|
||
class="h-9 w-9 flex items-center justify-center rounded-full bg-white/5 hover:bg-white/10 transition"
|
||
title="Back">
|
||
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/>
|
||
</svg>
|
||
</a>
|
||
<div class="flex flex-col leading-tight">
|
||
<div id="station-name" class="text-base sm:text-lg font-extrabold tracking-tight">
|
||
Loading...
|
||
</div>
|
||
<div id="station-location" class="text-xs sm:text-sm text-slate-100">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex items-center justify-center">
|
||
<img src="./assets/vec_logo.png" alt="VECMOCON"
|
||
class="h-7 w-auto opacity-90" onerror="this.style.display='none'"/>
|
||
</div>
|
||
|
||
<div class="flex items-center flex-wrap justify-end gap-1.5 sm:gap-2">
|
||
|
||
<span class="badge border-white/10 bg-white/5 text-slate-200">
|
||
<span>Device ID:</span>
|
||
<span id="device-id">—</span>
|
||
</span>
|
||
|
||
<span class="badge border-white/10 bg-white/5 text-slate-200">
|
||
<svg class="w-2.5 h-2.5 opacity-90" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||
<circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 3"></path>
|
||
</svg>
|
||
<span id="last-update-status">Last Recv —</span>
|
||
</span>
|
||
|
||
<span class="cham_chip cham_chip-emerald">
|
||
<span class="h-2 w-2 rounded-full bg-emerald-400 animate-pulseDot"></span> Online
|
||
</span>
|
||
|
||
<span class="cham_chip cham_chip-amber" title="Running on backup supply">On Backup</span>
|
||
|
||
<div class="hidden sm:block w-px h-5 bg-white/10"></div>
|
||
|
||
<button id="refreshBtn" class="btn btn-ghost !p-2">
|
||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||
<path d="M21 12a9 9 0 10-3.5 7.1M21 12h-4m4 0l-2.5-2.5"></path>
|
||
</svg>
|
||
</button>
|
||
|
||
<button id="downloadBtn" class="btn btn-ghost !p-2">
|
||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||
<path d="M12 3v12m0 0l4-4m-4 4l-4-4"></path><path d="M5 21h14"></path>
|
||
</svg>
|
||
</button>
|
||
|
||
<button id="logout-btn" class="btn btn-danger !p-2">
|
||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
|
||
<path d="M16 17l5-5-5-5"></path><path d="M21 12H9"></path>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="border-t border-white/10 bg-black/10">
|
||
<nav class="mx-auto max-w-7.5xl px-3 sm:px-4 flex">
|
||
<a href="./dashboard.html" class="px-4 py-2 text-sm font-semibold border-b-2 border-emerald-400/70 text-white">Main</a>
|
||
<a href="./logs.html" class="px-4 py-2 text-sm text-gray-400 hover:text-gray-200 hover:border-b-2 hover:border-white/30 font-semibold">Logs</a>
|
||
<a href="./analytics.html" class="px-4 py-2 text-sm text-gray-400 hover:text-gray-200 hover:border-b-2 hover:border-white/30 font-semibold">Analytics</a>
|
||
</nav>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="relative z-10 flex-1 w-full px-3 py-3 overflow-y-auto lg:overflow-hidden">
|
||
<div class="page mx-auto flex flex-col lg:h-full lg:flex-row gap-3">
|
||
<section id="chambersGrid" class="flex-1 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 lg:grid-rows-3 gap-3"></section>
|
||
|
||
<aside class="w-full lg:w-96 lg:shrink-0 flex flex-col gap-3 overflow-y-auto">
|
||
<section class="glass p-4">
|
||
<div class="flex items-center justify-between mb-3">
|
||
<span class="text-xm font-bold mb-2">System Diagnostics Code</span>
|
||
<span class="text-sm font-bold text-emerald-300">148</span>
|
||
</div>
|
||
<div class="grid grid-cols-2 gap-x-8 gap-y-1 text-sm">
|
||
<div class="text-rose-300 text-center">Lock Power Cut</div><div class="text-rose-300 text-center">Main Power Cut</div>
|
||
<div class="text-rose-300 text-center">Relayboard CAN</div><div class="text-rose-300 text-center">DB CAN Recv</div>
|
||
<div class="text-rose-300 text-center">Smoke Alarm</div><div class="text-rose-300 text-center">Water Alarm</div>
|
||
<div class="text-rose-300 text-center">Phase Failure</div><div class="text-rose-300 text-center">Earth Leakage</div>
|
||
</div>
|
||
<button id="station-reset-btn" class="btn btn-danger w-full mt-3 !py-2">Station Reset</button>
|
||
</section>
|
||
|
||
<section id="swap-panel" class="glass p-4 flex flex-col min-h-[220px]">
|
||
<h3 class="text-sm font-bold mb-2">Swap Process</h3>
|
||
<div id="swap-pairs-list" class="flex-1 flex flex-wrap gap-2 content-center justify-center">
|
||
<p id="swap-idle-text" class="w-full text-sm text-center text-gray-400">
|
||
Click a <span class="text-sky-300 font-semibold">empty</span> slot, then an <span class="text-emerald-300 font-semibold">full</span> slot.
|
||
</p>
|
||
</div>
|
||
<div class="mt-3">
|
||
<div class="grid grid-cols-2 gap-2">
|
||
<button id="start-swap-btn" class="btn btn-primary !py-2" disabled>Start Swaps</button>
|
||
<button id="abort-swap-btn" class="btn btn-danger !py-2">Abort Swap</button>
|
||
</div>
|
||
<button id="clear-swap-btn" class="btn w-full mt-2 !py-2">Clear Selection</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="glass p-4">
|
||
<h3 class="text-sm font-bold mb-2">Audio Command</h3>
|
||
<select class="w-full rounded-md bg-white/5 border border-white/10 px-2 py-2 text-sm outline-none focus:ring-2 focus:ring-emerald-500/60">
|
||
<option>English</option><option>Hindi</option><option>Tamil</option>
|
||
</select>
|
||
</section>
|
||
|
||
<section class="glass p-4 flex-1 flex flex-col min-h-[610px]">
|
||
<h3 class="text-sm font-bold mb-2">Instance Log</h3>
|
||
<textarea class="flex-1 bg-black/20 border border-white/10 rounded-md p-2 text-xs font-mono resize-none" readonly>[--:--:--] Waiting for data…</textarea>
|
||
</section>
|
||
</aside>
|
||
</div>
|
||
</main>
|
||
|
||
<template id="chamberTemplate">
|
||
<div class="chamber-card relative glass rounded-xl p-2 flex flex-col transition border border-white/20">
|
||
|
||
<div class="text-center absolute left-0 right-0 top-0 -translate-y-1/2">
|
||
<span class="bg-slate-800 px-2 text-xs font-extrabold text-gray-200 tracking-wide rounded">
|
||
CHAMBER <span class="slotNo">#</span>
|
||
</span>
|
||
</div>
|
||
|
||
<div class="mt-2 mb-1.5">
|
||
<div class="flex items-center gap-2">
|
||
<h4 class="field font-bold text-gray-300 shrink-0">BAT_ID</h4>
|
||
<div class="bat-id-big mono truncate flex-1 text-left" title="—">—</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex-1 flex gap-2">
|
||
<div class="flex-1 space-y-0.5">
|
||
<div class="flex items-center justify-between border-b border-white/10 pb-0.5 mb-1">
|
||
<h4 class="field font-bold text-gray-300">BATTERY</h4>
|
||
<span class="battery-status-pill chip chip-slate"></span>
|
||
</div>
|
||
<div class="flex justify-between items-baseline"><span class="field">SOC</span><span class="value soc">—</span></div>
|
||
<div class="flex justify-between items-baseline"><span class="field">Voltage</span><span class="value voltage">—</span></div>
|
||
<div class="flex justify-between items-baseline"><span class="field">Bat Temp</span><span class="value bat-temp">—</span></div>
|
||
<div class="flex justify-between items-baseline"><span class="field">Bat Fault</span><span class="value bat-fault text-rose-300">—</span></div>
|
||
</div>
|
||
|
||
<div class="flex-1 space-y-0.5 border-l border-white/10 pl-2">
|
||
<div class="flex items-center justify-between border-b border-white/10 pb-0.5 mb-1">
|
||
<h4 class="field font-bold text-gray-300">CHARGER</h4>
|
||
<span class="charger-status-pill chip chip-slate"></span>
|
||
</div>
|
||
<div class="flex justify-between items-baseline"><span class="field">Current</span><span class="value current">—</span></div>
|
||
<div class="flex justify-between items-baseline"><span class="field">Slot Temp</span><span class="value slot-temp">—</span></div>
|
||
<div class="flex justify-between items-baseline"><span class="field">Chg Temp</span><span class="value chg-temp">—</span></div>
|
||
<div class="flex justify-between items-baseline"><span class="field">Chg Fault</span><span class="value chg-fault text-rose-300">—</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-4 gap-1.5 mt-2 shrink-0">
|
||
<div class="door-pill door-close">CLOSED</div>
|
||
<button class="btn">OPEN</button>
|
||
<button class="btn">CHG ON</button>
|
||
<button class="btn">CHG OFF</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
// SCRIPT IS UNCHANGED
|
||
const qs = (s) => document.querySelector(s);
|
||
|
||
(function setDevice() {
|
||
const el = document.querySelector('#device-id');
|
||
if (!el) return;
|
||
try {
|
||
const sel = JSON.parse(localStorage.getItem('selected_station') || '{}');
|
||
el.textContent = sel?.id || sel?.station_id || '—';
|
||
} catch {
|
||
el.textContent = '—';
|
||
}
|
||
})();
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const stationId = 'VEC-STN-0128';
|
||
loadStationInfo(stationId);
|
||
});
|
||
|
||
async function loadStationInfo(stationId) {
|
||
const nameElement = document.getElementById('station-name');
|
||
const locationElement = document.getElementById('station-location');
|
||
try {
|
||
const mockStationData = { name: "VEC-STN-0128", location: "Sector 62, Noida" };
|
||
nameElement.textContent = mockStationData.name;
|
||
locationElement.textContent = mockStationData.location;
|
||
} catch (error) {
|
||
nameElement.textContent = 'Error Loading Station';
|
||
locationElement.textContent = 'Could not fetch data.';
|
||
console.error('Error fetching station data:', error);
|
||
}
|
||
}
|
||
|
||
(function setLastRecv() {
|
||
const el = qs('#last-update-status');
|
||
if (el) {
|
||
// Handle both badge and text styles
|
||
const textContent = 'Last Recv ' + new Date().toLocaleTimeString();
|
||
if (el.tagName === 'SPAN') {
|
||
el.textContent = textContent;
|
||
} else {
|
||
// Fallback for different structures if needed
|
||
el.innerHTML = textContent;
|
||
}
|
||
}
|
||
})();
|
||
|
||
qs('#refreshBtn')?.addEventListener('click', () => location.reload());
|
||
qs('#downloadBtn')?.addEventListener('click', () => {
|
||
alert('Hook this to your /api/logs/export endpoint.');
|
||
});
|
||
qs('#logout-btn')?.addEventListener('click', () => {
|
||
window.location.href = './index.html';
|
||
});
|
||
|
||
const grid = document.getElementById('chambersGrid');
|
||
const tmpl = document.getElementById('chamberTemplate');
|
||
|
||
grid.innerHTML = '';
|
||
for (let i = 1; i <= 9; i++) {
|
||
const node = tmpl.content.cloneNode(true);
|
||
const card = node.querySelector('.chamber-card');
|
||
card.dataset.chamberId = i;
|
||
card.querySelector('.slotNo').textContent = i;
|
||
|
||
const batIdBig = node.querySelector('.bat-id-big');
|
||
const batteryStatus = node.querySelector('.battery-status-pill');
|
||
const chargerStatus = node.querySelector('.charger-status-pill');
|
||
const socEl = node.querySelector('.soc');
|
||
const voltageEl = node.querySelector('.voltage');
|
||
const batFaultEl = node.querySelector('.bat-fault');
|
||
const chgFaultEl = node.querySelector('.chg-fault');
|
||
const batTempEl = node.querySelector('.bat-temp');
|
||
const currentEl = node.querySelector('.current');
|
||
const slotTempEl = node.querySelector('.slot-temp');
|
||
const chgTempEl = node.querySelector('.chg-temp');
|
||
const doorPill = node.querySelector('.door-pill');
|
||
|
||
const present = i % 3 !== 0;
|
||
batIdBig.textContent = present ? `TK${510000 + i}X00${200 + i}` : '—';
|
||
|
||
if (present) {
|
||
batteryStatus.textContent = 'Present';
|
||
batteryStatus.className = 'battery-status-pill chip chip-emerald';
|
||
chargerStatus.textContent = 'Charging';
|
||
chargerStatus.className = 'charger-status-pill chip chip-sky';
|
||
socEl.textContent = `${Math.max(20, 96 - i*3)}%`;
|
||
voltageEl.textContent = `5${i}.2 V`;
|
||
batTempEl.textContent = `${25 + i}.0 °C`;
|
||
currentEl.textContent = '10.5 A';
|
||
slotTempEl.textContent = `${28 + i}.0 °C`;
|
||
chgTempEl.textContent = `${35 + i}.0 °C`;
|
||
if (i === 5) batFaultEl.textContent = 'OVERHEAT';
|
||
} else {
|
||
batteryStatus.textContent = 'Absent';
|
||
batteryStatus.className = 'battery-status-pill chip chip-rose';
|
||
chargerStatus.textContent = 'Idle';
|
||
chargerStatus.className = 'charger-status-pill chip chip-slate';
|
||
chgFaultEl.textContent = (i === 6) ? 'COMM_FAIL' : '—';
|
||
}
|
||
|
||
card.querySelectorAll('.btn').forEach(b => b.addEventListener('click', e => e.stopPropagation()));
|
||
grid.appendChild(node);
|
||
}
|
||
|
||
const swapIdleText = document.getElementById('swap-idle-text');
|
||
const swapPairsList = document.getElementById('swap-pairs-list');
|
||
const startSwapBtn = document.getElementById('start-swap-btn');
|
||
const abortSwapBtn = document.getElementById('abort-swap-btn');
|
||
const clearSwapBtn = document.getElementById('clear-swap-btn');
|
||
const resetBtn = document.getElementById('station-reset-btn');
|
||
|
||
let currentPair = [], swapPairs = [];
|
||
|
||
function updateSwapUI(){
|
||
const isBuilding = currentPair.length || swapPairs.length;
|
||
if (!swapIdleText) return;
|
||
swapIdleText.classList.toggle('hidden', isBuilding);
|
||
startSwapBtn.disabled = swapPairs.length === 0;
|
||
|
||
const pairedOut = swapPairs.map(p=>p[0]), pairedIn = swapPairs.map(p=>p[1]);
|
||
document.querySelectorAll('.chamber-card').forEach(card=>{
|
||
const n = +card.dataset.chamberId;
|
||
card.classList.remove('paired','pending');
|
||
if (pairedOut.includes(n) || pairedIn.includes(n)) card.classList.add('paired');
|
||
else if (currentPair.includes(n)) card.classList.add('pending');
|
||
});
|
||
|
||
swapPairsList.innerHTML = isBuilding ? '' : swapIdleText.outerHTML;
|
||
if (isBuilding){
|
||
swapPairs.forEach(p=>{
|
||
const e = document.createElement('div');
|
||
e.className = 'text-sm font-semibold flex items-center justify-center gap-2 bg-black/20 rounded p-1.5';
|
||
e.innerHTML = `<span class="text-emerald-300">${p[0]}</span><span>→</span><span class="text-sky-300">${p[1]}</span>`;
|
||
swapPairsList.appendChild(e);
|
||
});
|
||
if (currentPair.length){
|
||
const e = document.createElement('div');
|
||
e.className = 'text-sm font-semibold flex items-center justify-center gap-2 bg-black/20 rounded p-1.5 ring-2 ring-sky-500';
|
||
e.innerHTML = `<span class="text-emerald-300 font-bold">${currentPair[0]}</span><span>→</span><span class="text-gray-400">?</span>`;
|
||
swapPairsList.appendChild(e);
|
||
}
|
||
}
|
||
}
|
||
|
||
function handleChamberClick(num){
|
||
if (swapPairs.length >= 4 && currentPair.length === 0) return alert('Maximum of 4 swap pairs reached.');
|
||
const usedOut = swapPairs.map(p=>p[0]), usedIn = swapPairs.map(p=>p[1]);
|
||
if ((currentPair.length === 0 && usedOut.includes(num)) ||
|
||
(currentPair.length === 1 && usedIn.includes(num)) ||
|
||
(currentPair.length === 1 && currentPair[0] === num)) return;
|
||
|
||
currentPair.push(num);
|
||
if (currentPair.length === 2){ swapPairs.push([...currentPair]); currentPair = []; }
|
||
updateSwapUI();
|
||
}
|
||
function clearSelection(){ currentPair=[]; swapPairs=[]; updateSwapUI(); }
|
||
|
||
document.querySelectorAll('.chamber-card').forEach(c =>
|
||
c.addEventListener('click', () => handleChamberClick(+c.dataset.chamberId))
|
||
);
|
||
clearSwapBtn?.addEventListener('click', clearSelection);
|
||
startSwapBtn?.addEventListener('click', () => {
|
||
if (swapPairs.length){ alert('Executing swaps:\n'+swapPairs.map(p=>`${p[0]} → ${p[1]}`).join('\n')); clearSelection(); }
|
||
});
|
||
abortSwapBtn?.addEventListener('click', () => alert('Sending Swap Abort command!'));
|
||
resetBtn?.addEventListener('click', () => { if (confirm('Reset station?')) alert('Sending Station Reset…'); });
|
||
|
||
updateSwapUI();
|
||
</script>
|
||
</body>
|
||
</html> |