feat: DO-gated manual batch mode (auto default, office-only switches)
ci / smoke (push) Canceled after 0s

This commit is contained in:
andrew committed 2026-09-24 15:45:12 +07:00
1 parent 735e0c7594
commit 0efcfa45b8
20 files changed
+3128 -209

No files matched your search

+100 -24
View File
@@ -305,14 +305,17 @@
<div class="active-batch-card">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;">
<h3 class="card-label" style="margin-bottom: 0;">Active Batch</h3>
<!-- Mode Switcher -->
<!-- Mode Switcher: auto | do_manual | manual -->
<div style="display: inline-flex; background: var(--bg-base); border: 1px solid var(--border-color); border-radius: 20px; padding: 2px;">
<button id="btnModeManual" class="btn btn-sm" style="padding: 4px 10px; font-size: 11px; border-radius: 18px; border: none; background: var(--accent-blue); color: #fff; font-weight: 600;" onclick="switchBatchMode('manual')">
<i class="fa-solid fa-hand"></i> Manual
</button>
<button id="btnModeAuto" class="btn btn-sm" style="padding: 4px 10px; font-size: 11px; border-radius: 18px; border: none; background: transparent; color: var(--text-secondary);" onclick="switchBatchMode('auto')">
<i class="fa-solid fa-robot"></i> Otomatis
</button>
<button id="btnModeDoManual" class="btn btn-sm" style="padding: 4px 10px; font-size: 11px; border-radius: 18px; border: none; background: transparent; color: var(--text-secondary);" onclick="switchBatchMode('do_manual')">
<i class="fa-solid fa-file-lines"></i> DO Manual
</button>
<button id="btnModeManual" class="btn btn-sm" style="padding: 4px 10px; font-size: 11px; border-radius: 18px; border: none; background: transparent; color: var(--text-secondary);" onclick="switchBatchMode('manual')">
<i class="fa-solid fa-hand"></i> Manual
</button>
</div>
</div>
@@ -347,6 +350,24 @@
<span class="meta-key">Pipeline Speed</span>
<span class="meta-value"><span id="liveFps">--</span> FPS</span>
</div>
<div class="meta-item">
<span class="meta-key">OCR Engine</span>
<span class="meta-value">
<select id="monOcrEngine" style="padding:4px 8px;border-radius:8px;border:1px solid var(--border-color);background:var(--bg-base);color:var(--text-primary);font-size:12px;" onchange="setOcrEngine(this.value)">
<option value="tesseract">tesseract</option>
<option value="paddle">paddle</option>
<option value="none">none</option>
</select>
</span>
</div>
<div class="meta-item">
<span class="meta-key">Require plat</span>
<span class="meta-value">
<label style="font-weight:400;">
<input type="checkbox" id="monRequirePlate" onchange="setRequirePlate(this.checked)"> wajib
</label>
</span>
</div>
</div>
<!-- Manual Batch Control Buttons for Office Dashboard (Hidden when in Auto Mode) -->
@@ -613,7 +634,7 @@
}
}
let currentBatchMode = 'manual';
let currentBatchMode = 'auto';
async function switchBatchMode(mode) {
try {
@@ -625,6 +646,12 @@
const data = await res.json();
if (data.success) {
updateModeUI(data.mode);
} else if (res.status === 409) {
alert(data.error || 'Batch sedang berjalan — hentikan dulu.');
} else if (res.status === 403) {
alert(data.error || 'Hanya dari port kantor.');
} else {
alert(data.error || 'Gagal ganti mode');
}
} catch (e) {
console.error('Failed to switch batch mode:', e);
@@ -647,46 +674,95 @@
currentBatchMode = mode;
const btnManual = document.getElementById('btnModeManual');
const btnAuto = document.getElementById('btnModeAuto');
const btnDo = document.getElementById('btnModeDoManual');
const modeText = document.getElementById('liveBatchModeText');
const manualSection = document.getElementById('manualControlSection');
const autoHint = document.getElementById('autoModeHint');
if (mode === 'auto') {
btnAuto.style.background = 'var(--accent-blue)';
btnAuto.style.color = '#fff';
btnAuto.style.fontWeight = '600';
btnManual.style.background = 'transparent';
btnManual.style.color = 'var(--text-secondary)';
btnManual.style.fontWeight = 'normal';
if (modeText) {
const setActive = (el, on) => {
if (!el) return;
if (on) {
el.style.background = 'var(--accent-blue)';
el.style.color = '#fff';
el.style.fontWeight = '600';
} else {
el.style.background = 'transparent';
el.style.color = 'var(--text-secondary)';
el.style.fontWeight = 'normal';
}
};
setActive(btnAuto, mode === 'auto');
setActive(btnDo, mode === 'do_manual');
setActive(btnManual, mode === 'manual');
if (modeText) {
if (mode === 'auto') {
modeText.textContent = 'Otomatis (AI Truk)';
modeText.style.color = 'var(--accent-orange)';
}
if (manualSection) manualSection.style.display = 'none';
if (autoHint) autoHint.style.display = 'block';
} else {
btnManual.style.background = 'var(--accent-blue)';
btnManual.style.color = '#fff';
btnManual.style.fontWeight = '600';
btnAuto.style.background = 'transparent';
btnAuto.style.color = 'var(--text-secondary)';
btnAuto.style.fontWeight = 'normal';
if (modeText) {
} else if (mode === 'do_manual') {
modeText.textContent = 'DO Manual (Scan Surat)';
modeText.style.color = 'var(--accent-blue)';
} else {
modeText.textContent = 'Manual (Tombol)';
modeText.style.color = 'var(--accent-blue)';
}
}
if (mode === 'auto') {
if (manualSection) manualSection.style.display = 'none';
if (autoHint) autoHint.style.display = 'block';
} else {
if (manualSection) manualSection.style.display = 'flex';
if (autoHint) autoHint.style.display = 'none';
}
}
async function loadDoSettings() {
try {
const res = await fetch('/api/do/settings');
const data = await res.json();
if (!data.success) return;
const sel = document.getElementById('monOcrEngine');
if (sel && data.ocr_engine) sel.value = data.ocr_engine;
const chk = document.getElementById('monRequirePlate');
if (chk) chk.checked = !!data.require_plate;
} catch (e) { console.error(e); }
}
async function setOcrEngine(eng) {
try {
const res = await fetch('/api/do/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ocr_engine: eng })
});
const data = await res.json();
if (!data.success) alert(data.error || 'Gagal set OCR');
loadDoSettings();
} catch (e) { alert('Gagal terhubung'); loadDoSettings(); }
}
async function setRequirePlate(val) {
try {
const res = await fetch('/api/do/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ require_plate: val })
});
const data = await res.json();
if (!data.success) alert(data.error || 'Gagal set require_plate');
loadDoSettings();
} catch (e) { alert('Gagal terhubung'); loadDoSettings(); }
}
// Start interval pollings
setInterval(pollCurrentBatch, 2000);
setInterval(pollFinalizedBatch, 3000);
setInterval(checkBatchMode, 5000);
setInterval(loadDoSettings, 8000);
// Initial first load
checkBatchMode();
loadDoSettings();
pollCurrentBatch();
pollFinalizedBatch();
</script>