/** * Log Material Usage modal — job details page. * Reads config from window.__logMaterial injected inline by the view. */ (function () { let _items = []; let _jobPowderIds = new Set(); let _modal = null; // ── Combobox state ──────────────────────────────────────────────────────── let _selectedItemId = 0; function lmComboInput() { const q = document.getElementById('lmItemSearch')?.value?.toLowerCase() || ''; lmComboRender(q); lmComboShow(); _selectedItemId = 0; document.getElementById('lmItemBalance').classList.add('d-none'); lmOnQtyInput(); } function lmComboOpen() { const q = document.getElementById('lmItemSearch')?.value?.toLowerCase() || ''; lmComboRender(q); lmComboShow(); } function lmComboToggle() { const dd = document.getElementById('lmItemDropdown'); if (!dd) return; if (dd.style.display === 'none' || !dd.style.display) { lmComboOpen(); document.getElementById('lmItemSearch')?.focus(); } else { lmComboClose(); } } function lmMakeRow(it) { const display = (it.manufacturer ? escLm(it.manufacturer) + ' – ' : '') + escLm(it.name) + (it.unitOfMeasure ? ' (' + escLm(it.unitOfMeasure) + ')' : ''); const label = (it.manufacturer ? it.manufacturer + ' - ' : '') + it.name + (it.unitOfMeasure ? ' (' + it.unitOfMeasure + ')' : ''); return `
${display}
`; } function lmComboRender(query) { const dd = document.getElementById('lmItemDropdown'); if (!dd) return; const filtered = query ? _items.filter(it => it.name.toLowerCase().includes(query) || (it.manufacturer && it.manufacturer.toLowerCase().includes(query)) || (it.unitOfMeasure && it.unitOfMeasure.toLowerCase().includes(query))) : _items; if (filtered.length === 0) { dd.innerHTML = '
No items match.
'; return; } const jobItems = filtered.filter(it => _jobPowderIds.has(it.id)); const otherItems = filtered.filter(it => !_jobPowderIds.has(it.id)); let html = ''; if (jobItems.length > 0) { html += '
This Job
'; html += jobItems.map(lmMakeRow).join(''); if (otherItems.length > 0) { html += '
'; html += '
All Inventory
'; } } html += otherItems.map(lmMakeRow).join(''); dd.innerHTML = html; } function lmComboShow() { const dd = document.getElementById('lmItemDropdown'); const anchor = document.getElementById('lmItemSearch'); if (!dd || !anchor) return; const rect = anchor.closest('.input-group').getBoundingClientRect(); dd.style.position = 'fixed'; dd.style.top = (rect.bottom + 2) + 'px'; dd.style.left = rect.left + 'px'; dd.style.width = rect.width + 'px'; dd.style.display = 'block'; } function lmComboClose() { const dd = document.getElementById('lmItemDropdown'); if (dd) dd.style.display = 'none'; } window.lmComboSelect = function (el) { _selectedItemId = parseInt(el.dataset.id) || 0; document.getElementById('lmItemSearch').value = el.dataset.label; lmComboClose(); const qty = parseFloat(el.dataset.qty) || 0; const uom = el.dataset.uom; const balDiv = document.getElementById('lmItemBalance'); balDiv.textContent = 'Current stock: ' + qty.toFixed(2) + (uom ? ' ' + uom : ''); balDiv.classList.remove('d-none'); lmOnQtyInput(); }; window.lmComboInput = lmComboInput; window.lmComboOpen = lmComboOpen; window.lmComboToggle = lmComboToggle; window.lmComboKey = function (event) { const dd = document.getElementById('lmItemDropdown'); if (!dd || dd.style.display === 'none') { if (event.key === 'ArrowDown' || event.key === 'Enter') { event.preventDefault(); lmComboOpen(); } return; } const opts = Array.from(dd.querySelectorAll('.lm-item-opt')); let idx = opts.findIndex(o => o.classList.contains('lm-active')); if (event.key === 'ArrowDown') { event.preventDefault(); idx = Math.min(idx + 1, opts.length - 1); opts.forEach(o => { o.classList.remove('lm-active'); o.style.background = ''; }); if (opts[idx]) { opts[idx].classList.add('lm-active'); opts[idx].style.background = '#e8eeff'; opts[idx].scrollIntoView({ block: 'nearest' }); } } else if (event.key === 'ArrowUp') { event.preventDefault(); idx = Math.max(idx - 1, 0); opts.forEach(o => { o.classList.remove('lm-active'); o.style.background = ''; }); if (opts[idx]) { opts[idx].classList.add('lm-active'); opts[idx].style.background = '#e8eeff'; opts[idx].scrollIntoView({ block: 'nearest' }); } } else if (event.key === 'Enter') { event.preventDefault(); const active = dd.querySelector('.lm-active') || opts[0]; if (active) active.dispatchEvent(new MouseEvent('mousedown')); } else if (event.key === 'Escape') { lmComboClose(); } }; function escLm(s) { return String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } // ── Quantity / label logic ──────────────────────────────────────────────── function lmOnQtyInput() { const method = document.querySelector('input[name="lmEntryMethod"]:checked')?.value; if (method !== 'remaining') { document.getElementById('lmComputedUsed').classList.add('d-none'); return; } if (!_selectedItemId) { document.getElementById('lmComputedUsed').classList.add('d-none'); return; } const item = _items.find(it => it.id === _selectedItemId); const onHand = item ? (parseFloat(item.quantityOnHand) || 0) : 0; const remaining = parseFloat(document.getElementById('lmQuantity').value) || 0; const used = onHand - remaining; const computedDiv = document.getElementById('lmComputedUsed'); computedDiv.textContent = 'Usage = ' + onHand.toFixed(2) + ' − ' + remaining.toFixed(2) + ' = ' + used.toFixed(2) + (item?.unitOfMeasure ? ' ' + item.unitOfMeasure : ''); computedDiv.classList.remove('d-none'); } window.lmUpdateQuantityLabel = function () { const method = document.querySelector('input[name="lmEntryMethod"]:checked')?.value; document.getElementById('lmQtyLabel').innerHTML = (method === 'remaining' ? 'Quantity Remaining' : 'Quantity Used') + ' *'; lmOnQtyInput(); }; // ── Modal open / save ───────────────────────────────────────────────────── window.openLogMaterialModal = function () { _selectedItemId = 0; document.getElementById('lmItemSearch').value = ''; document.getElementById('lmItemBalance').classList.add('d-none'); document.getElementById('lmQuantity').value = ''; document.getElementById('lmComputedUsed').classList.add('d-none'); document.getElementById('lmTransactionType').value = 'JobUsage'; document.getElementById('lmNotes').value = ''; document.getElementById('lmAlert').classList.add('d-none'); document.getElementById('lmSaveBtn').disabled = false; document.getElementById('lmMethodUsed').checked = true; window.lmUpdateQuantityLabel(); lmComboClose(); if (_modal) _modal.show(); }; window.lmSave = async function () { const cfg = window.__logMaterial; const alertEl = document.getElementById('lmAlert'); function showError(msg) { alertEl.className = 'alert alert-danger alert-permanent'; alertEl.textContent = msg; alertEl.classList.remove('d-none'); } if (!_selectedItemId) { showError('Please select an inventory item.'); return; } const qtyInput = parseFloat(document.getElementById('lmQuantity').value) || 0; if (qtyInput <= 0) { showError('Please enter a quantity greater than zero.'); return; } const method = document.querySelector('input[name="lmEntryMethod"]:checked')?.value; let quantityUsed = qtyInput; if (method === 'remaining') { const item = _items.find(it => it.id === _selectedItemId); const onHand = item ? (parseFloat(item.quantityOnHand) || 0) : 0; quantityUsed = onHand - qtyInput; if (quantityUsed <= 0) { showError('Remaining quantity cannot be equal to or greater than the current stock (' + onHand.toFixed(2) + ').'); return; } } const btn = document.getElementById('lmSaveBtn'); btn.disabled = true; alertEl.classList.add('d-none'); try { const resp = await fetch(cfg.logUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'RequestVerificationToken': cfg.token }, body: JSON.stringify({ jobId: cfg.jobId, inventoryItemId: _selectedItemId, quantityUsed: quantityUsed, transactionType: document.getElementById('lmTransactionType').value, notes: document.getElementById('lmNotes').value.trim() || null }) }); const data = await resp.json(); if (data.success) { if (_modal) _modal.hide(); window.location.reload(); } else { showError(data.message || 'An error occurred.'); btn.disabled = false; } } catch { showError('Network error. Please try again.'); btn.disabled = false; } }; // ── Init ────────────────────────────────────────────────────────────────── function init() { const cfg = window.__logMaterial; if (!cfg) return; _items = cfg.inventoryItems || []; _jobPowderIds = new Set(cfg.jobPowderIds || []); _modal = new bootstrap.Modal(document.getElementById('logMaterialModal')); document.getElementById('lmQuantity').addEventListener('input', lmOnQtyInput); // Close dropdown when clicking outside document.addEventListener('click', function (e) { if (!e.target.closest('#lmItemSearch') && !e.target.closest('#lmItemDropdown') && !e.target.closest('#lmItemDropdownToggle')) { lmComboClose(); } }); } document.addEventListener('DOMContentLoaded', init); })();