feat: supplier item name editing, N/A checkbox, and adjusted indicator on compare view
This commit is contained in:
1 parent
7b399d5167
commit
6061e8ca4f
6 files changed
+626
-41
No files matched your search
@@ -48,7 +48,7 @@
|
||||
{{-- Per-item rows --}}
|
||||
@foreach($items as $i => $reqItem)
|
||||
@php
|
||||
$rowPrices = $quotes->map(fn($q) => optional($q->items->get($i))->unit_price)->filter()->values();
|
||||
$rowPrices = $quotes->map(fn($q) => ($q->items->get($i) && !$q->items->get($i)->not_available) ? $q->items->get($i)->unit_price : null)->filter()->values();
|
||||
$minPrice = $rowPrices->count() ? $rowPrices->min() : null;
|
||||
@endphp
|
||||
<tr>
|
||||
@@ -57,14 +57,24 @@
|
||||
<div style="font-size:11px;color:#94a3b8;margin-top:2px;">Qty: {{ $reqItem->quantity }} {{ $reqItem->unit }}</div>
|
||||
</td>
|
||||
@foreach($quotes as $q)
|
||||
@php $qItem = $q->items->get($i); $isMin = $qItem && $minPrice !== null && (float)$qItem->unit_price === (float)$minPrice && $rowPrices->count() > 1; @endphp
|
||||
@php $qItem = $q->items->get($i); $isMin = $qItem && !$qItem->not_available && $minPrice !== null && (float)$qItem->unit_price === (float)$minPrice && $rowPrices->count() > 1; @endphp
|
||||
<td style="padding:10px 14px;border-bottom:1px solid #f1f5f9;text-align:center;background:{{ $isMin ? '#f0fdf4' : '' }};">
|
||||
@if($qItem)
|
||||
<div style="font-weight:600;color:{{ $isMin ? '#15803d' : '#0f172a' }};">
|
||||
@if($isMin)<span style="font-size:10px;font-weight:700;color:#15803d;display:block;">LOWEST</span>@endif
|
||||
BD {{ number_format($qItem->unit_price, 3) }}
|
||||
</div>
|
||||
<div style="font-size:11px;color:#64748b;margin-top:2px;">BD {{ number_format($qItem->total_price, 3) }}</div>
|
||||
@if($qItem->not_available)
|
||||
<span style="font-size:11px;font-weight:700;color:#dc2626;background:#fef2f2;padding:2px 8px;border-radius:4px;border:1px solid #fecaca;">Not available</span>
|
||||
@else
|
||||
@if($qItem->supplier_description)
|
||||
<div style="font-size:10px;color:#64748b;margin-bottom:2px;font-style:italic;">
|
||||
"{{ $qItem->supplier_description }}"
|
||||
<span style="background:#fef3c7;color:#92400e;font-size:9px;font-weight:700;padding:1px 5px;border-radius:3px;border:1px solid #fde68a;font-style:normal;margin-left:2px;">adjusted</span>
|
||||
</div>
|
||||
@endif
|
||||
<div style="font-weight:600;color:{{ $isMin ? '#15803d' : '#0f172a' }};">
|
||||
@if($isMin)<span style="font-size:10px;font-weight:700;color:#15803d;display:block;">LOWEST</span>@endif
|
||||
BD {{ number_format($qItem->unit_price, 3) }}
|
||||
</div>
|
||||
<div style="font-size:11px;color:#64748b;margin-top:2px;">BD {{ number_format($qItem->total_price, 3) }}</div>
|
||||
@endif
|
||||
@else
|
||||
<span style="color:#e2e8f0;">—</span>
|
||||
@endif
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
<th>Description</th>
|
||||
<th>Qty</th>
|
||||
<th>Unit</th>
|
||||
<th style="text-align:center;">N/A?</th>
|
||||
<th style="text-align:center;">VAT?</th>
|
||||
<th style="text-align:right;">Unit Price (BD)</th>
|
||||
<th style="text-align:right;">Total (BD)</th>
|
||||
@@ -85,14 +86,46 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($items as $i => $item)
|
||||
<tr>
|
||||
<tr id="row-{{ $i }}">
|
||||
<td style="color:#94a3b8;font-size:12px;">{{ $i + 1 }}</td>
|
||||
<td style="font-weight:500;">{{ $item->description }}</td>
|
||||
<td>
|
||||
{{-- Display mode --}}
|
||||
<div id="desc-display-{{ $i }}" style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;">
|
||||
<span id="desc-text-{{ $i }}" style="font-weight:500;">{{ $item->description }}</span>
|
||||
<button type="button" onclick="editDesc({{ $i }})"
|
||||
title="Edit item name"
|
||||
style="flex-shrink:0;background:none;border:1px solid #e2e8f0;border-radius:5px;padding:2px 6px;cursor:pointer;color:#64748b;font-size:11px;line-height:1.4;"
|
||||
onmouseover="this.style.borderColor='#2563eb';this.style.color='#2563eb'"
|
||||
onmouseout="this.style.borderColor='#e2e8f0';this.style.color='#64748b'">
|
||||
✎ Edit
|
||||
</button>
|
||||
<span id="desc-adj-{{ $i }}" style="display:none;font-size:10px;font-weight:700;background:#fef3c7;color:#92400e;padding:1px 6px;border-radius:4px;border:1px solid #fde68a;">adjusted</span>
|
||||
</div>
|
||||
{{-- Edit mode --}}
|
||||
<div id="desc-edit-{{ $i }}" style="display:none;align-items:center;gap:6px;">
|
||||
<input type="text" id="desc-input-{{ $i }}"
|
||||
value="{{ $item->description }}"
|
||||
style="flex:1;min-width:0;padding:5px 8px;border:1.5px solid #2563eb;border-radius:6px;font-size:13px;font-weight:500;outline:none;width:auto;">
|
||||
<button type="button" onclick="saveDesc({{ $i }}, {{ json_encode($item->description) }})"
|
||||
style="flex-shrink:0;background:#2563eb;color:#fff;border:none;border-radius:5px;padding:4px 8px;cursor:pointer;font-size:12px;font-weight:700;">✓</button>
|
||||
<button type="button" onclick="cancelDesc({{ $i }})"
|
||||
style="flex-shrink:0;background:#f1f5f9;color:#64748b;border:1px solid #e2e8f0;border-radius:5px;padding:4px 8px;cursor:pointer;font-size:12px;">✕</button>
|
||||
</div>
|
||||
{{-- Hidden field submitted with form --}}
|
||||
<input type="hidden" name="items[{{ $i }}][supplier_description]" id="desc-hidden-{{ $i }}" value="">
|
||||
</td>
|
||||
<td>{{ rtrim(rtrim(number_format((float)$item->quantity_required, 3), '0'), '.') }}</td>
|
||||
<td style="color:#64748b;">{{ $item->unit ?: '—' }}</td>
|
||||
{{-- N/A checkbox --}}
|
||||
<td style="text-align:center;">
|
||||
<input type="checkbox" name="items[{{ $i }}][not_available]" id="na-{{ $i }}" value="1"
|
||||
onchange="toggleNA({{ $i }}, {{ (float)$item->quantity_required }})"
|
||||
style="width:16px;height:16px;accent-color:#dc2626;cursor:pointer;">
|
||||
</td>
|
||||
{{-- VAT checkbox --}}
|
||||
<td style="text-align:center;">
|
||||
@if($vatRate > 0)
|
||||
<input type="checkbox" name="items[{{ $i }}][is_vatable]" value="1"
|
||||
<input type="checkbox" name="items[{{ $i }}][is_vatable]" id="vat-cb-{{ $i }}" value="1"
|
||||
onchange="calcRow({{ $i }}, {{ (float)$item->quantity_required }})"
|
||||
style="width:16px;height:16px;accent-color:#2563eb;cursor:pointer;">
|
||||
@else
|
||||
@@ -100,7 +133,7 @@
|
||||
@endif
|
||||
</td>
|
||||
<td style="text-align:right;">
|
||||
<input type="number" class="price" name="items[{{ $i }}][unit_price]"
|
||||
<input type="number" class="price" id="price-{{ $i }}" name="items[{{ $i }}][unit_price]"
|
||||
min="0" step="0.001" required placeholder="0.000"
|
||||
oninput="calcRow({{ $i }}, {{ (float)$item->quantity_required }})">
|
||||
</td>
|
||||
@@ -110,15 +143,15 @@
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr style="background:#f8fafc;">
|
||||
<td colspan="6" style="text-align:right;font-size:13px;color:#475569;">Subtotal:</td>
|
||||
<td colspan="7" style="text-align:right;font-size:13px;color:#475569;">Subtotal:</td>
|
||||
<td style="text-align:right;font-size:14px;color:#475569;" id="subtotal-row">BD 0.000</td>
|
||||
</tr>
|
||||
<tr id="vat-tr" style="background:#fffbeb;{{ $vatRate > 0 ? '' : 'display:none;' }}">
|
||||
<td colspan="6" style="text-align:right;font-size:13px;color:#92400e;">VAT ({{ $vatRate }}%):</td>
|
||||
<td colspan="7" style="text-align:right;font-size:13px;color:#92400e;">VAT ({{ $vatRate }}%):</td>
|
||||
<td style="text-align:right;font-size:14px;color:#92400e;" id="vat-row">BD 0.000</td>
|
||||
</tr>
|
||||
<tr style="background:#f8fafc;border-top:2px solid #e2e8f0;">
|
||||
<td colspan="6" style="text-align:right;font-size:13px;color:#475569;font-weight:700;">Grand Total:</td>
|
||||
<td colspan="7" style="text-align:right;font-size:13px;color:#475569;font-weight:700;">Grand Total:</td>
|
||||
<td style="text-align:right;font-size:15px;color:#2563eb;font-weight:700;" id="grand-total">BD 0.000</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
@@ -222,13 +255,77 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var _vatRate = {{ $vatRate }};
|
||||
var _totals = {};
|
||||
var _vatable = {};
|
||||
var _vatRate = {{ $vatRate }};
|
||||
var _totals = {};
|
||||
var _vatable = {};
|
||||
var _navail = {};
|
||||
|
||||
// ── Description inline edit ──────────────────────────────
|
||||
function editDesc(i) {
|
||||
document.getElementById('desc-display-' + i).style.display = 'none';
|
||||
var ed = document.getElementById('desc-edit-' + i);
|
||||
ed.style.display = 'flex';
|
||||
document.getElementById('desc-input-' + i).focus();
|
||||
}
|
||||
|
||||
function saveDesc(i, original) {
|
||||
var val = document.getElementById('desc-input-' + i).value.trim();
|
||||
if (!val) val = original;
|
||||
document.getElementById('desc-text-' + i).textContent = val;
|
||||
document.getElementById('desc-hidden-' + i).value = (val !== original) ? val : '';
|
||||
var adj = document.getElementById('desc-adj-' + i);
|
||||
adj.style.display = (val !== original) ? 'inline-block' : 'none';
|
||||
document.getElementById('desc-edit-' + i).style.display = 'none';
|
||||
document.getElementById('desc-display-' + i).style.display = 'flex';
|
||||
}
|
||||
|
||||
function cancelDesc(i) {
|
||||
var original = document.getElementById('desc-text-' + i).textContent;
|
||||
document.getElementById('desc-input-' + i).value = original;
|
||||
document.getElementById('desc-edit-' + i).style.display = 'none';
|
||||
document.getElementById('desc-display-' + i).style.display = 'flex';
|
||||
}
|
||||
|
||||
// ── N/A toggle ───────────────────────────────────────────
|
||||
function toggleNA(i, qty) {
|
||||
var naCb = document.getElementById('na-' + i);
|
||||
var priceIn = document.getElementById('price-' + i);
|
||||
var vatCb = document.getElementById('vat-cb-' + i);
|
||||
var totEl = document.getElementById('tot-' + i);
|
||||
var row = document.getElementById('row-' + i);
|
||||
var na = naCb.checked;
|
||||
_navail[i] = na;
|
||||
|
||||
if (na) {
|
||||
priceIn.value = '';
|
||||
priceIn.disabled = true;
|
||||
priceIn.removeAttribute('required');
|
||||
priceIn.style.opacity = '.35';
|
||||
if (vatCb) { vatCb.checked = false; vatCb.disabled = true; vatCb.style.opacity = '.35'; }
|
||||
totEl.innerHTML = '<span style="font-size:11px;font-weight:700;color:#dc2626;background:#fef2f2;padding:2px 7px;border-radius:4px;">Not available</span>';
|
||||
row.style.opacity = '.6';
|
||||
_totals[i] = 0;
|
||||
_vatable[i] = false;
|
||||
} else {
|
||||
priceIn.disabled = false;
|
||||
priceIn.setAttribute('required', '');
|
||||
priceIn.style.opacity = '1';
|
||||
if (vatCb) { vatCb.disabled = false; vatCb.style.opacity = '1'; }
|
||||
totEl.textContent = '—';
|
||||
row.style.opacity = '1';
|
||||
_totals[i] = 0;
|
||||
_vatable[i] = false;
|
||||
calcRow(i, qty);
|
||||
return;
|
||||
}
|
||||
recalcTotals();
|
||||
}
|
||||
|
||||
// ── Price / VAT calculation ──────────────────────────────
|
||||
function calcRow(i, qty) {
|
||||
var inp = document.querySelector('input[name="items[' + i + '][unit_price]"]');
|
||||
var cb = document.querySelector('input[name="items[' + i + '][is_vatable]"]');
|
||||
if (_navail[i]) return;
|
||||
var inp = document.getElementById('price-' + i);
|
||||
var cb = document.getElementById('vat-cb-' + i);
|
||||
var up = parseFloat(inp ? inp.value : 0) || 0;
|
||||
var tot = Math.round(up * qty * 1000) / 1000;
|
||||
_totals[i] = tot;
|
||||
@@ -242,6 +339,7 @@ function recalcTotals() {
|
||||
var subtotal = 0;
|
||||
var vatAmount = 0;
|
||||
Object.keys(_totals).forEach(function(i) {
|
||||
if (_navail[i]) return;
|
||||
subtotal += _totals[i];
|
||||
if (_vatable[i] && _vatRate > 0) {
|
||||
vatAmount += Math.round(_totals[i] * _vatRate / 100 * 1000) / 1000;
|
||||
|
||||
Reference in new issue
Block a user