Add remaining-weight input mode to inventory scan/usage page

Users can now toggle between 'Amount Used' and 'Remaining Weight' on the
QR scan page. In remaining-weight mode, usage is calculated as
(current stock - remaining) before submit — no controller changes needed.
Includes live hint showing calculated usage and new balance as they type,
with validation preventing negative usage or remaining > current stock.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 19:12:28 -04:00
parent f34ee749be
commit cefdf3e35c
@@ -168,6 +168,23 @@
} }
.reason-pill.selected { border-color: var(--purple); background: #f3effe; color: var(--purple); font-weight: 600; } .reason-pill.selected { border-color: var(--purple); background: #f3effe; color: var(--purple); font-weight: 600; }
/* ── Input mode toggle ───────────────────────── */
.mode-toggle { display: flex; border: 1.5px solid var(--border); border-radius: 8px; overflow: hidden; margin-bottom: 18px; }
.mode-btn {
flex: 1;
padding: 10px 8px;
background: #fff;
border: none;
font-size: 13px;
font-weight: 600;
color: var(--muted);
cursor: pointer;
text-align: center;
transition: background .15s, color .15s;
}
.mode-btn.active { background: var(--purple); color: #fff; }
.mode-btn:first-child { border-right: 1.5px solid var(--border); }
/* ── Submit / Cancel ─────────────────────────── */ /* ── Submit / Cancel ─────────────────────────── */
.btn-submit { .btn-submit {
width: 100%; width: 100%;
@@ -309,12 +326,28 @@
<div class="form-card"> <div class="form-card">
<h2>2. Enter Quantity</h2> <h2>2. Enter Quantity</h2>
<div class="field">
<div class="mode-toggle">
<button type="button" class="mode-btn active" id="modeUsed" onclick="setMode('used')">Amount Used</button>
<button type="button" class="mode-btn" id="modeRemaining" onclick="setMode('remaining')">Remaining Weight</button>
</div>
<!-- amount-used mode -->
<div id="usedField" class="field">
<label for="quantityInput">Amount Used (@item.UnitOfMeasure) <span class="req">*</span></label> <label for="quantityInput">Amount Used (@item.UnitOfMeasure) <span class="req">*</span></label>
<input type="number" id="quantityInput" name="quantity" <input type="number" id="quantityInput" name="quantity"
min="0" step="any" required placeholder="0" inputmode="decimal" /> min="0" step="any" placeholder="0" inputmode="decimal"
oninvalid="this.setCustomValidity('')" />
<div class="hint" id="balanceHint"></div> <div class="hint" id="balanceHint"></div>
</div> </div>
<!-- remaining-weight mode -->
<div id="remainingField" class="field" style="display:none">
<label for="remainingInput">Weight Remaining (@item.UnitOfMeasure) <span class="req">*</span></label>
<input type="number" id="remainingInput" min="0" step="any"
placeholder="0" inputmode="decimal" />
<div class="hint" id="remainingHint"></div>
</div>
</div> </div>
<div class="form-card"> <div class="form-card">
@@ -346,6 +379,21 @@
<script> <script>
var currentQty = @item.QuantityOnHand; var currentQty = @item.QuantityOnHand;
var uom = '@item.UnitOfMeasure'; var uom = '@item.UnitOfMeasure';
var inputMode = 'used'; // 'used' | 'remaining'
// ── Input mode toggle ────────────────────────────
function setMode(mode) {
inputMode = mode;
document.getElementById('modeUsed').classList.toggle('active', mode === 'used');
document.getElementById('modeRemaining').classList.toggle('active', mode === 'remaining');
document.getElementById('usedField').style.display = mode === 'used' ? '' : 'none';
document.getElementById('remainingField').style.display = mode === 'remaining' ? '' : 'none';
document.getElementById('balanceHint').textContent = '';
document.getElementById('remainingHint').textContent = '';
// clear both inputs when switching
document.getElementById('quantityInput').value = '';
document.getElementById('remainingInput').value = '';
}
// ── Job selection ──────────────────────────────── // ── Job selection ────────────────────────────────
function showTab(tab) { function showTab(tab) {
@@ -384,7 +432,7 @@
document.getElementById('transactionTypeInput').value = el.dataset.val; document.getElementById('transactionTypeInput').value = el.dataset.val;
} }
// ── Balance hint ───────────────────────────────── // ── Balance hint (amount-used mode) ─────────────
document.getElementById('quantityInput').addEventListener('input', function() { document.getElementById('quantityInput').addEventListener('input', function() {
var qty = parseFloat(this.value) || 0; var qty = parseFloat(this.value) || 0;
if (!this.value) { document.getElementById('balanceHint').textContent = ''; return; } if (!this.value) { document.getElementById('balanceHint').textContent = ''; return; }
@@ -394,6 +442,24 @@
'New balance: <strong style="color:' + col + '">' + newBal.toFixed(2) + ' ' + uom + '</strong>'; 'New balance: <strong style="color:' + col + '">' + newBal.toFixed(2) + ' ' + uom + '</strong>';
}); });
// ── Remaining-weight hint ────────────────────────
document.getElementById('remainingInput').addEventListener('input', function() {
var hint = document.getElementById('remainingHint');
if (!this.value) { hint.textContent = ''; return; }
var remaining = parseFloat(this.value);
if (isNaN(remaining) || remaining < 0) { hint.innerHTML = '<span style="color:var(--danger)">Enter a valid weight.</span>'; return; }
if (remaining > currentQty) {
hint.innerHTML = '<span style="color:var(--danger)">Remaining cannot exceed current stock (' + currentQty.toFixed(2) + ' ' + uom + ').</span>';
return;
}
var used = currentQty - remaining;
if (used <= 0) {
hint.innerHTML = '<span style="color:var(--danger)">No usage to log &mdash; remaining equals current stock.</span>';
return;
}
hint.innerHTML = 'Will log <strong>' + used.toFixed(2) + ' ' + uom + '</strong> as used &mdash; new balance: <strong style="color:' + (remaining === 0 ? '#343a40' : 'var(--success)') + '">' + remaining.toFixed(2) + ' ' + uom + '</strong>';
});
// ── Preselect job if coming from success page ──── // ── Preselect job if coming from success page ────
@if (preselectedJobId.HasValue) @if (preselectedJobId.HasValue)
{ {
@@ -406,8 +472,37 @@
</text> </text>
} }
// ── Submit spinner ─────────────────────────────── // ── Submit: resolve quantity from whichever mode is active ──
document.getElementById('usageForm').addEventListener('submit', function() { document.getElementById('usageForm').addEventListener('submit', function(e) {
if (inputMode === 'remaining') {
var remaining = parseFloat(document.getElementById('remainingInput').value);
if (isNaN(remaining) || remaining < 0 || remaining > currentQty) {
e.preventDefault();
document.getElementById('remainingHint').innerHTML =
'<span style="color:var(--danger)">Please enter a valid remaining weight.</span>';
return;
}
var used = currentQty - remaining;
if (used <= 0) {
e.preventDefault();
document.getElementById('remainingHint').innerHTML =
'<span style="color:var(--danger)">No usage to log &mdash; remaining equals current stock.</span>';
return;
}
document.getElementById('quantityInput').value = used.toFixed(4);
}
// validate amount-used mode
if (inputMode === 'used') {
var qty = parseFloat(document.getElementById('quantityInput').value);
if (isNaN(qty) || qty <= 0) {
e.preventDefault();
document.getElementById('balanceHint').innerHTML =
'<span style="color:var(--danger)">Please enter a quantity greater than zero.</span>';
return;
}
}
var btn = document.getElementById('submitBtn'); var btn = document.getElementById('submitBtn');
btn.disabled = true; btn.disabled = true;
btn.textContent = 'Saving…'; btn.textContent = 'Saving…';