Promote job powders to top of Log Material dropdown

Powders already assigned to this job's coats appear under a 'This Job'
section header, then a divider, then 'All Inventory' — so the most
relevant choices are always one click away.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 21:49:46 -04:00
parent 79c8c7e6a4
commit f380c152ca
3 changed files with 47 additions and 18 deletions
@@ -503,9 +503,17 @@ public class JobsController : Controller
.OrderBy(i => i.Name)
.Select(i => new { i.Id, i.Name, i.Manufacturer, i.UnitOfMeasure, i.QuantityOnHand })
.ToList();
ViewBag.InventoryItemsForModal = System.Text.Json.JsonSerializer.Serialize(
inventoryItemsForModal,
new System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase });
var jsonOpts = new System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase };
ViewBag.InventoryItemsForModal = System.Text.Json.JsonSerializer.Serialize(inventoryItemsForModal, jsonOpts);
// IDs of powders already assigned to this job's coats — shown at top of log-material dropdown
var jobPowderIds = (jobDto.Items ?? new List<PowderCoating.Application.DTOs.Job.JobItemDto>())
.SelectMany(i => i.Coats ?? new List<PowderCoating.Application.DTOs.Job.JobItemCoatDto>())
.Where(c => c.InventoryItemId.HasValue)
.Select(c => c.InventoryItemId!.Value)
.Distinct()
.ToList();
ViewBag.JobPowderIds = System.Text.Json.JsonSerializer.Serialize(jobPowderIds, jsonOpts);
// Pre-logged powder grouped by InventoryItemId (for Complete Job modal pre-fill)
ViewBag.PreLoggedPowder = allJobTransactions
@@ -3162,10 +3162,11 @@
<script>
(function () {
const inventoryItems = @Html.Raw(ViewBag.InventoryItemsForModal ?? "[]");
const jobPowderIds = @Html.Raw(ViewBag.JobPowderIds ?? "[]");
const jobId = @Model.Id;
const logUrl = '@Url.Action("LogMaterial", "Jobs")';
const token = document.querySelector('input[name="__RequestVerificationToken"]')?.value ?? '';
window.__logMaterial = { inventoryItems, jobId, logUrl, token };
window.__logMaterial = { inventoryItems, jobPowderIds, jobId, logUrl, token };
})();
</script>
@@ -4,6 +4,7 @@
*/
(function () {
let _items = [];
let _jobPowderIds = new Set();
let _modal = null;
// ── Combobox state ────────────────────────────────────────────────────────
@@ -35,6 +36,23 @@
}
}
function lmMakeRow(it) {
const label = it.name + (it.unitOfMeasure ? ' (' + it.unitOfMeasure + ')' : '');
const mfr = it.manufacturer
? `<span class="text-muted ms-1" style="font-size:.78rem;">${escLm(it.manufacturer)}</span>`
: '';
return `<div class="lm-item-opt" style="padding:.35rem .75rem;font-size:.875rem;cursor:pointer;"
data-id="${it.id}"
data-qty="${it.quantityOnHand}"
data-uom="${escLm(it.unitOfMeasure || '')}"
data-label="${escLm(label)}"
onmousedown="event.preventDefault(); lmComboSelect(this)"
onmouseenter="this.style.background='#f0f4ff'"
onmouseleave="this.classList.contains('lm-active') ? null : this.style.background=''">
${escLm(label)}${mfr}
</div>`;
}
function lmComboRender(query) {
const dd = document.getElementById('lmItemDropdown');
if (!dd) return;
@@ -47,20 +65,21 @@
dd.innerHTML = '<div class="px-3 py-2 text-muted small">No items match.</div>';
return;
}
dd.innerHTML = filtered.map(it => {
const label = it.name + (it.unitOfMeasure ? ' (' + it.unitOfMeasure + ')' : '');
const mfr = it.manufacturer ? `<span class="text-muted ms-1" style="font-size:.78rem;">${escLm(it.manufacturer)}</span>` : '';
return `<div class="lm-item-opt" style="padding:.35rem .75rem;font-size:.875rem;cursor:pointer;"
data-id="${it.id}"
data-qty="${it.quantityOnHand}"
data-uom="${escLm(it.unitOfMeasure || '')}"
data-label="${escLm(label)}"
onmousedown="event.preventDefault(); lmComboSelect(this)"
onmouseenter="this.style.background='#f0f4ff'"
onmouseleave="this.classList.contains('lm-active') ? null : this.style.background=''">
${escLm(label)}${mfr}
</div>`;
}).join('');
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 += '<div class="px-3 py-1 text-muted" style="font-size:.72rem;letter-spacing:.04em;text-transform:uppercase;background:#f8f9fa;border-bottom:1px solid #dee2e6;">This Job</div>';
html += jobItems.map(lmMakeRow).join('');
if (otherItems.length > 0) {
html += '<div style="height:1px;background:#dee2e6;margin:.25rem 0;"></div>';
html += '<div class="px-3 py-1 text-muted" style="font-size:.72rem;letter-spacing:.04em;text-transform:uppercase;background:#f8f9fa;border-bottom:1px solid #dee2e6;">All Inventory</div>';
}
}
html += otherItems.map(lmMakeRow).join('');
dd.innerHTML = html;
}
function lmComboShow() {
@@ -245,6 +264,7 @@
if (!cfg) return;
_items = cfg.inventoryItems || [];
_jobPowderIds = new Set(cfg.jobPowderIds || []);
_modal = new bootstrap.Modal(document.getElementById('logMaterialModal'));
document.getElementById('lmQuantity').addEventListener('input', lmOnQtyInput);