Merge dev into master: QR scan URL fixes and http scheme failsafe
This commit is contained in:
@@ -946,7 +946,10 @@ public class InventoryController : Controller
|
||||
if (!string.IsNullOrWhiteSpace(urlMfr))
|
||||
{
|
||||
aiResult = await _aiLookupService.LookupAsync(urlMfr, urlColor, null, urlPart);
|
||||
if (aiResult.Success && aiResult.SpecPageUrl == null)
|
||||
// The scanned QR URL is always the authoritative product page link — it came
|
||||
// directly from the manufacturer's bag and is always fully-qualified. Overwrite
|
||||
// whatever LookupAsync returned (which may be a scheme-less path from the template).
|
||||
if (aiResult.Success)
|
||||
aiResult.SpecPageUrl = qrUrl;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
ViewData["PageIcon"] = "bi-box-seam";
|
||||
ViewData["PageHelpTitle"] = "Inventory Item";
|
||||
ViewData["PageHelpContent"] = "Full detail for this inventory item. Stock Information shows current quantity and reorder thresholds — a Low Stock banner appears when quantity is at or below the Reorder Point. Pricing shows Unit Cost (what you paid), Average Cost (weighted average across purchases), and Total Stock Value. Use the Actions panel to edit, view jobs using this powder, or delete the item.";
|
||||
|
||||
string SafeUrl(string? url) =>
|
||||
string.IsNullOrEmpty(url) ? "#"
|
||||
: (url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
? url : "http://" + url;
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
@@ -184,7 +189,7 @@
|
||||
<div class="col-12">
|
||||
<label class="text-muted small mb-1">Product URL</label>
|
||||
<p class="mb-0">
|
||||
<a href="@Model.SpecPageUrl" target="_blank" class="text-decoration-none">
|
||||
<a href="@SafeUrl(Model.SpecPageUrl)" target="_blank" class="text-decoration-none">
|
||||
<i class="bi bi-box-arrow-up-right me-1"></i>View on Manufacturer's Web Site
|
||||
</a>
|
||||
</p>
|
||||
@@ -197,13 +202,13 @@
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
@if (!string.IsNullOrEmpty(Model.SdsUrl))
|
||||
{
|
||||
<a href="@Model.SdsUrl" target="_blank" class="btn btn-sm btn-outline-secondary">
|
||||
<a href="@SafeUrl(Model.SdsUrl)" target="_blank" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-file-earmark-pdf me-1"></i>Safety Data Sheet
|
||||
</a>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.TdsUrl))
|
||||
{
|
||||
<a href="@Model.TdsUrl" target="_blank" class="btn btn-sm btn-outline-secondary">
|
||||
<a href="@SafeUrl(Model.TdsUrl)" target="_blank" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-file-earmark-text me-1"></i>Technical Data Sheet
|
||||
</a>
|
||||
}
|
||||
|
||||
@@ -413,10 +413,15 @@
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
function ensureAbsoluteUrl(url) {
|
||||
if (!url) return url;
|
||||
return /^https?:\/\//i.test(url) ? url : 'http://' + url;
|
||||
}
|
||||
|
||||
function syncLinkButton(inputId, linkId, url) {
|
||||
const link = document.getElementById(linkId);
|
||||
if (!link) return;
|
||||
if (url) { link.href = url; link.classList.remove('d-none'); }
|
||||
if (url) { link.href = ensureAbsoluteUrl(url); link.classList.remove('d-none'); }
|
||||
else { link.classList.add('d-none'); }
|
||||
}
|
||||
|
||||
|
||||
@@ -550,10 +550,15 @@
|
||||
});
|
||||
}
|
||||
|
||||
function ensureAbsoluteUrl(url) {
|
||||
if (!url) return url;
|
||||
return /^https?:\/\//i.test(url) ? url : 'http://' + url;
|
||||
}
|
||||
|
||||
function syncLink(inputId, linkId, url) {
|
||||
const link = document.getElementById(linkId);
|
||||
if (!link) return;
|
||||
if (url) { link.href = url; link.classList.remove('d-none'); }
|
||||
if (url) { link.href = ensureAbsoluteUrl(url); link.classList.remove('d-none'); }
|
||||
else { link.classList.add('d-none'); }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user