feat: multi-model modes (A/B/C/D, default B) + box counting + manual-mode banner
ci / smoke (push) Canceled after 0s
ci / smoke (push) Canceled after 0s
This commit is contained in:
1 parent
52d3127302
commit
6e4af50646
14 files changed
+819
-128
No files matched your search
+175
-5
@@ -167,10 +167,73 @@
|
||||
font-weight: 600;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
/* Non-blocking mode banner (fixed top, status stays visible below) */
|
||||
.mode-banner {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 500;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
padding: 10px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
background-color: rgba(255, 204, 0, 0.15);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.mode-banner .btn {
|
||||
padding: 6px 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
border-radius: var(--radius-md);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mode-banner .btn-yes {
|
||||
background-color: var(--accent-green);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.mode-banner .btn-no {
|
||||
background-color: var(--bg-base);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.model-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.model-row select {
|
||||
padding: 6px 10px;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--bg-base);
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Non-blocking manual-mode prompt: status/counts stay visible below -->
|
||||
<div id="modeBanner" class="mode-banner">
|
||||
<span>🟡 Mode OTOMATIS aktif. Alihkan ke MANUAL untuk kontrol tombol batch?</span>
|
||||
<button class="btn btn-yes" onclick="switchToManual()">Ya, Manual</button>
|
||||
<button class="btn btn-no" onclick="dismissModeBanner()">Tidak, Tetap Otomatis</button>
|
||||
</div>
|
||||
<div class="operator-container">
|
||||
<div class="operator-card">
|
||||
<h1 class="operator-title"><i class="fa-solid fa-boxes-packing"></i> Kontrol Pemuatan Karung</h1>
|
||||
@@ -192,6 +255,14 @@
|
||||
<div class="info-label">Waktu Mulai</div>
|
||||
<div class="info-val" id="opStartTime">--:--:--</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Karung Masuk</div>
|
||||
<div class="info-val" id="opSackCount">0</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Box Masuk</div>
|
||||
<div class="info-val" id="opBoxCount">0</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Button -->
|
||||
@@ -203,6 +274,16 @@
|
||||
<i class="fa-solid fa-square"></i> SELESAI / AKHIRI BATCH
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Model mode -->
|
||||
<div class="model-row">
|
||||
<span>Model:</span>
|
||||
<strong id="opModelMode">--</strong>
|
||||
<select id="modelModeSelect" onchange="setModelMode(this.value)">
|
||||
<option value="">Ganti mode…</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="operator-subtitle" style="margin-top:6px;font-size:12px">Ganti mode model berlaku setelah service direstart.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -239,6 +320,86 @@
|
||||
document.getElementById(id).style.display = 'none';
|
||||
}
|
||||
|
||||
// --- Non-blocking manual-mode prompt (banner, status stays visible) ---
|
||||
async function loadModeBanner() {
|
||||
try {
|
||||
const res = await fetch('/api/batch/mode');
|
||||
const data = await res.json();
|
||||
if (data.mode === 'auto' && !sessionStorage.getItem('mode_banner_dismissed')) {
|
||||
document.getElementById('modeBanner').style.display = 'flex';
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Mode load error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function dismissModeBanner() {
|
||||
sessionStorage.setItem('mode_banner_dismissed', '1');
|
||||
document.getElementById('modeBanner').style.display = 'none';
|
||||
}
|
||||
|
||||
async function switchToManual() {
|
||||
try {
|
||||
const res = await fetch('/api/batch/mode', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ mode: 'manual' })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
dismissModeBanner();
|
||||
} else {
|
||||
alert('Gagal alih mode: ' + (data.error || 'Terjadi kesalahan'));
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Gagal terhubung ke server');
|
||||
}
|
||||
}
|
||||
|
||||
// --- Model mode selector ---
|
||||
async function loadModelModes() {
|
||||
try {
|
||||
const res = await fetch('/api/model-modes');
|
||||
const data = await res.json();
|
||||
if (!data.success) return;
|
||||
const sel = document.getElementById('modelModeSelect');
|
||||
data.modes.forEach(m => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = m.id;
|
||||
opt.textContent = m.id + ' — ' + m.description;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Model modes load error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function setModelMode(mode) {
|
||||
if (!mode) return;
|
||||
if (!confirm('Ganti mode model ke ' + mode + '? Berlaku setelah service karung-counter direstart.')) {
|
||||
document.getElementById('modelModeSelect').value = '';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch('/api/batch/mode', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ model_mode: mode })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
document.getElementById('opModelMode').textContent = data.model_mode;
|
||||
alert('Mode model tersimpan: ' + data.model_mode + '. Restart service untuk berlaku.');
|
||||
} else {
|
||||
alert('Gagal ganti mode: ' + (data.error || 'Terjadi kesalahan'));
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Gagal terhubung ke server');
|
||||
} finally {
|
||||
document.getElementById('modelModeSelect').value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function confirmStartBatch() {
|
||||
document.getElementById('modalStart').style.display = 'flex';
|
||||
}
|
||||
@@ -301,15 +462,22 @@
|
||||
const text = document.getElementById('statusText');
|
||||
const batchNum = document.getElementById('opBatchNum');
|
||||
const startTime = document.getElementById('opStartTime');
|
||||
const sackCount = document.getElementById('opSackCount');
|
||||
const boxCount = document.getElementById('opBoxCount');
|
||||
const modelMode = document.getElementById('opModelMode');
|
||||
const btnStart = document.getElementById('btnStartBatch');
|
||||
const btnStop = document.getElementById('btnStopBatch');
|
||||
|
||||
|
||||
if (data.model_mode) modelMode.textContent = data.model_mode;
|
||||
sackCount.textContent = data.count || 0;
|
||||
boxCount.textContent = data.box_count || 0;
|
||||
|
||||
if (data.success && data.batch_number) {
|
||||
isBatchActive = true;
|
||||
badge.className = 'status-badge-box status-active';
|
||||
icon.className = 'fa-solid fa-circle-play';
|
||||
text.textContent = 'Status: Sedang Memuat (Batch #' + data.batch_number + ')';
|
||||
|
||||
|
||||
batchNum.textContent = '#' + data.batch_number;
|
||||
if (data.start_time) {
|
||||
const st = new Date(data.start_time);
|
||||
@@ -317,7 +485,7 @@
|
||||
} else {
|
||||
startTime.textContent = '--:--:--';
|
||||
}
|
||||
|
||||
|
||||
btnStart.style.display = 'none';
|
||||
btnStop.style.display = 'flex';
|
||||
} else {
|
||||
@@ -325,10 +493,10 @@
|
||||
badge.className = 'status-badge-box status-idle';
|
||||
icon.className = 'fa-solid fa-circle-pause';
|
||||
text.textContent = 'Status: Standby (Siap Memuat)';
|
||||
|
||||
|
||||
batchNum.textContent = '--';
|
||||
startTime.textContent = '--:--:--';
|
||||
|
||||
|
||||
btnStart.style.display = 'flex';
|
||||
btnStop.style.display = 'none';
|
||||
}
|
||||
@@ -339,5 +507,7 @@
|
||||
|
||||
setInterval(pollStatus, 2000);
|
||||
pollStatus();
|
||||
loadModeBanner();
|
||||
loadModelModes();
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user