feat: supplier modal wizard, pipeline delete, sidebar cleanup
- Replace two-tab supplier selector with two-step wizard (method select → suppliers → summary) - Add per-item channel picker (Email / WhatsApp / Both) in By Item mode - Add confirmation summary step before submitting By Item supplier assignments - Add type-to-confirm delete on pipeline list rows - Redirect purchase.requests.index to pipeline (same data, single entry point) - Remove Purchase Requests from sidebar nav - Add edit-request-modal, supplier-invite-list components - Add address coordinates migration for settings_locations Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
1 parent
dd924904c5
commit
d8cab94bcb
18 files changed
+1943
-840
No files matched your search
@@ -52,10 +52,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;">
|
||||
<a href="{{ route('purchase.requests.edit', $pr) }}"
|
||||
style="font-size:12px;color:#2563eb;text-decoration:none;border:1px solid #bfdbfe;padding:6px 14px;border-radius:7px;white-space:nowrap;background:#eff6ff;">
|
||||
✏️ Edit Request
|
||||
</a>
|
||||
<x-purchase.edit-request-modal :purchaseRequest="$pr" />
|
||||
<a href="{{ route('purchase.requests.show', $pr) }}"
|
||||
style="font-size:12px;color:#64748b;text-decoration:none;border:1px solid #e2e8f0;padding:6px 14px;border-radius:7px;white-space:nowrap;">
|
||||
View Full Request →
|
||||
@@ -402,225 +399,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ============================================================
|
||||
SELECT SUPPLIERS MODAL
|
||||
============================================================ --}}
|
||||
<div id="supplier-modal" class="pipe-modal" role="dialog" aria-modal="true" aria-labelledby="sup-modal-title">
|
||||
<div style="background:#fff;border-radius:20px;width:100%;max-width:680px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 30px 60px rgba(0,0,0,.3);overflow:hidden;">
|
||||
<x-purchase.supplier-select-modal :pr="$pr" :suppliers="$suppliers" :selectedIds="$selectedIds" />
|
||||
|
||||
{{-- Header --}}
|
||||
<div style="padding:20px 24px 0;border-bottom:1px solid #f1f5f9;flex-shrink:0;">
|
||||
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:16px;">
|
||||
<div>
|
||||
<div id="sup-modal-title" style="font-size:17px;font-weight:700;color:#0f172a;">Select Suppliers</div>
|
||||
<div style="font-size:12px;color:#64748b;margin-top:3px;">Choose who receives the quote request</div>
|
||||
</div>
|
||||
<button onclick="closeSupplierModal()" aria-label="Close"
|
||||
style="width:32px;height:32px;border-radius:8px;border:none;background:#f1f5f9;cursor:pointer;font-size:18px;color:#64748b;display:flex;align-items:center;justify-content:center;flex-shrink:0;">×</button>
|
||||
</div>
|
||||
|
||||
{{-- Tab bar --}}
|
||||
<div style="display:flex;gap:0;">
|
||||
<button id="stab-global" type="button" onclick="switchSupTab('global')"
|
||||
style="padding:10px 20px;font-size:13px;font-weight:700;border:none;background:none;cursor:pointer;color:#2563eb;border-bottom:2px solid #2563eb;margin-bottom:-1px;">
|
||||
Full Order
|
||||
</button>
|
||||
<button id="stab-item" type="button" onclick="switchSupTab('item')"
|
||||
style="padding:10px 20px;font-size:13px;font-weight:700;border:none;background:none;cursor:pointer;color:#94a3b8;border-bottom:2px solid transparent;margin-bottom:-1px;">
|
||||
By Item
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Single form wrapping both panes --}}
|
||||
<form id="sup-form" action="{{ route('purchase.requests.rfq.select', $pr) }}" method="POST"
|
||||
style="flex:1;overflow:hidden;display:flex;flex-direction:column;min-height:0;">
|
||||
@csrf
|
||||
<input type="hidden" name="mode" id="sup-mode" value="global">
|
||||
|
||||
{{-- ── GLOBAL PANE ── --}}
|
||||
<div id="sup-global-pane" style="flex:1;overflow-y:auto;overscroll-behavior:contain;display:flex;flex-direction:column;">
|
||||
{{-- Search --}}
|
||||
<div style="padding:14px 24px 10px;flex-shrink:0;">
|
||||
<div style="position:relative;">
|
||||
<svg style="position:absolute;left:11px;top:50%;transform:translateY(-50%);pointer-events:none;"
|
||||
width="14" height="14" fill="none" stroke="#94a3b8" stroke-width="2" viewBox="0 0 24 24">
|
||||
<circle cx="11" cy="11" r="8"/><path stroke-linecap="round" stroke-linejoin="round" d="m21 21-4.35-4.35"/>
|
||||
</svg>
|
||||
<input id="sup-search" type="text" placeholder="Search suppliers…" oninput="filterGlobalSups(this.value)"
|
||||
style="width:100%;box-sizing:border-box;padding:9px 12px 9px 34px;border:1px solid #e2e8f0;border-radius:9px;font-size:13px;outline:none;"
|
||||
onfocus="this.style.borderColor='#2563eb'" onblur="this.style.borderColor='#e2e8f0'">
|
||||
</div>
|
||||
</div>
|
||||
<div id="sup-list" style="flex:1;">
|
||||
@forelse($suppliers as $sup)
|
||||
@php $alreadyAdded = in_array($sup->id, $selectedIds); @endphp
|
||||
<div class="g-sup-item" data-name="{{ strtolower($sup->name) }}"
|
||||
style="padding:11px 24px;display:flex;align-items:center;gap:14px;border-bottom:1px solid #f8fafc;transition:background .1s;
|
||||
{{ $alreadyAdded ? 'opacity:.45;pointer-events:none;' : '' }}">
|
||||
<input type="checkbox" name="supplier_ids[]" value="{{ $sup->id }}" id="gsup-{{ $sup->id }}"
|
||||
{{ $alreadyAdded ? 'disabled checked' : '' }}
|
||||
onchange="toggleGlobalChan({{ $sup->id }}, this.checked); updateGlobalCount();"
|
||||
style="width:17px;height:17px;accent-color:#2563eb;cursor:pointer;flex-shrink:0;">
|
||||
<label for="gsup-{{ $sup->id }}" style="flex:1;cursor:{{ $alreadyAdded ? 'default' : 'pointer' }};">
|
||||
<div style="font-size:13px;font-weight:600;color:#0f172a;">
|
||||
{{ $sup->name }}
|
||||
@if($alreadyAdded)
|
||||
<span style="font-size:10px;background:#dcfce7;color:#15803d;padding:1px 6px;border-radius:10px;margin-left:6px;font-weight:700;">Added</span>
|
||||
@endif
|
||||
</div>
|
||||
@if($sup->email || $sup->phone)
|
||||
<div style="font-size:11px;color:#94a3b8;margin-top:1px;">
|
||||
{{ collect([$sup->email, $sup->phone])->filter()->implode(' · ') }}
|
||||
</div>
|
||||
@endif
|
||||
</label>
|
||||
@if(!$alreadyAdded)
|
||||
<div id="gchan-{{ $sup->id }}" style="display:none;flex-shrink:0;">
|
||||
<input type="hidden" name="channel_{{ $sup->id }}" id="gchan-val-{{ $sup->id }}" value="email">
|
||||
<div style="display:flex;border:1px solid #e2e8f0;border-radius:7px;overflow:hidden;">
|
||||
<span onclick="setGChan({{ $sup->id }},'email')" id="gce-{{ $sup->id }}"
|
||||
style="padding:5px 9px;font-size:10px;font-weight:700;cursor:pointer;background:#eff6ff;color:#2563eb;">Email</span>
|
||||
<span onclick="setGChan({{ $sup->id }},'whatsapp')" id="gcw-{{ $sup->id }}"
|
||||
style="padding:5px 9px;font-size:10px;font-weight:700;cursor:pointer;background:#fff;color:#94a3b8;border-left:1px solid #e2e8f0;">WA</span>
|
||||
<span onclick="setGChan({{ $sup->id }},'both')" id="gcb-{{ $sup->id }}"
|
||||
style="padding:5px 9px;font-size:10px;font-weight:700;cursor:pointer;background:#fff;color:#94a3b8;border-left:1px solid #e2e8f0;">Both</span>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@empty
|
||||
<div style="padding:40px;text-align:center;color:#94a3b8;font-size:13px;">No active suppliers found.</div>
|
||||
@endforelse
|
||||
<div id="no-sup-msg" style="display:none;padding:30px;text-align:center;color:#94a3b8;font-size:13px;">No suppliers match your search.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── BY ITEM PANE ── --}}
|
||||
<div id="sup-item-pane" style="flex:1;overflow-y:auto;overscroll-behavior:contain;display:none;flex-direction:column;">
|
||||
@if($pr->items->isEmpty())
|
||||
<div style="padding:40px;text-align:center;color:#94a3b8;font-size:13px;">This request has no items yet.</div>
|
||||
@else
|
||||
{{-- Column headers --}}
|
||||
<div style="display:grid;grid-template-columns:1fr 250px;gap:12px;padding:9px 24px;background:#f8fafc;border-bottom:1px solid #e2e8f0;flex-shrink:0;">
|
||||
<div style="font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.06em;">Item</div>
|
||||
<div style="font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.06em;">Assign Suppliers</div>
|
||||
</div>
|
||||
|
||||
{{-- Item rows --}}
|
||||
@foreach($pr->items as $item)
|
||||
<div style="display:grid;grid-template-columns:1fr 250px;gap:12px;align-items:center;padding:13px 24px;border-bottom:1px solid #f8fafc;">
|
||||
{{-- Left: item info --}}
|
||||
<div>
|
||||
<div style="font-size:13px;font-weight:600;color:#0f172a;line-height:1.3;">{{ $item->description }}</div>
|
||||
<div style="font-size:11px;color:#94a3b8;margin-top:2px;">
|
||||
Qty: {{ rtrim(rtrim(number_format($item->quantity_required,2),'0'),'.') }}{{ $item->unit ? ' '.$item->unit : '' }}
|
||||
</div>
|
||||
</div>
|
||||
{{-- Right: multi-select dropdown trigger --}}
|
||||
<div>
|
||||
<button type="button" id="idd-btn-{{ $item->id }}"
|
||||
onclick="toggleItemDd(event,{{ $item->id }})"
|
||||
style="width:100%;padding:8px 11px;border:1.5px solid #e2e8f0;border-radius:8px;background:#fff;cursor:pointer;
|
||||
display:flex;align-items:center;justify-content:space-between;gap:6px;text-align:left;transition:border .15s;">
|
||||
<span id="idd-label-{{ $item->id }}" style="font-size:12px;color:#94a3b8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;">
|
||||
Select suppliers…
|
||||
</span>
|
||||
<svg width="11" height="11" fill="none" stroke="#94a3b8" stroke-width="2.5" viewBox="0 0 24 24" style="flex-shrink:0;">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
{{-- Per-supplier channel settings --}}
|
||||
<div id="item-chan-section" style="padding:14px 24px;background:#fafafa;border-top:2px solid #f1f5f9;display:none;flex-shrink:0;">
|
||||
<div style="font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.06em;margin-bottom:10px;">Notify via</div>
|
||||
@foreach($suppliers as $sup)
|
||||
<div id="ic-{{ $sup->id }}" style="display:none;align-items:center;justify-content:space-between;padding:7px 0;border-bottom:1px solid #f1f5f9;">
|
||||
<span style="font-size:12px;font-weight:600;color:#0f172a;">{{ $sup->name }}</span>
|
||||
<div>
|
||||
<input type="hidden" name="channel_{{ $sup->id }}" id="ichan-val-{{ $sup->id }}" value="email">
|
||||
<div style="display:flex;border:1px solid #e2e8f0;border-radius:7px;overflow:hidden;">
|
||||
<span onclick="setIChan({{ $sup->id }},'email')" id="ice-{{ $sup->id }}"
|
||||
style="padding:5px 10px;font-size:10px;font-weight:700;cursor:pointer;background:#eff6ff;color:#2563eb;">Email</span>
|
||||
<span onclick="setIChan({{ $sup->id }},'whatsapp')" id="icw-{{ $sup->id }}"
|
||||
style="padding:5px 10px;font-size:10px;font-weight:700;cursor:pointer;background:#fff;color:#94a3b8;border-left:1px solid #e2e8f0;">WA</span>
|
||||
<span onclick="setIChan({{ $sup->id }},'both')" id="icb-{{ $sup->id }}"
|
||||
style="padding:5px 10px;font-size:10px;font-weight:700;cursor:pointer;background:#fff;color:#94a3b8;border-left:1px solid #e2e8f0;">Both</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Dropdown panels: fixed-positioned, one per item — must be inside form so checkboxes are submitted --}}
|
||||
@foreach($pr->items as $item)
|
||||
<div id="idd-{{ $item->id }}"
|
||||
style="display:none;position:fixed;z-index:99999;background:#fff;border:1.5px solid #e2e8f0;
|
||||
border-radius:12px;box-shadow:0 12px 32px rgba(0,0,0,.18);overflow:hidden;min-width:240px;">
|
||||
{{-- Search --}}
|
||||
<div style="padding:10px 10px 6px;">
|
||||
<input type="text" placeholder="Search suppliers…"
|
||||
oninput="filterItemDd({{ $item->id }}, this.value)"
|
||||
style="width:100%;box-sizing:border-box;padding:7px 10px;border:1px solid #e2e8f0;border-radius:7px;font-size:12px;outline:none;"
|
||||
onfocus="this.style.borderColor='#2563eb'" onblur="this.style.borderColor='#e2e8f0'">
|
||||
</div>
|
||||
{{-- Supplier list --}}
|
||||
<div style="max-height:210px;overflow-y:auto;padding-bottom:4px;">
|
||||
@forelse($suppliers as $sup)
|
||||
@php $alreadyAdded = in_array($sup->id, $selectedIds); @endphp
|
||||
<label class="idd-row-{{ $item->id }}" data-name="{{ strtolower($sup->name) }}"
|
||||
style="display:flex;align-items:center;gap:10px;padding:8px 12px;cursor:{{ $alreadyAdded ? 'default' : 'pointer' }};
|
||||
{{ $alreadyAdded ? 'opacity:.45;' : '' }}"
|
||||
onmouseover="{{ $alreadyAdded ? '' : "this.style.background='#f8fafc'" }}"
|
||||
onmouseout="this.style.background='transparent'">
|
||||
<input type="checkbox" name="item_suppliers[{{ $item->id }}][]" value="{{ $sup->id }}"
|
||||
data-supname="{{ $sup->name }}"
|
||||
id="isup-{{ $item->id }}-{{ $sup->id }}"
|
||||
{{ $alreadyAdded ? 'disabled checked' : '' }}
|
||||
onchange="onItemSupChange({{ $item->id }}, {{ $sup->id }})"
|
||||
style="width:15px;height:15px;accent-color:#2563eb;cursor:{{ $alreadyAdded ? 'default' : 'pointer' }};flex-shrink:0;">
|
||||
<div style="min-width:0;">
|
||||
<div style="font-size:12px;font-weight:600;color:#0f172a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">
|
||||
{{ $sup->name }}
|
||||
@if($alreadyAdded)
|
||||
<span style="font-size:9px;background:#dcfce7;color:#15803d;padding:1px 5px;border-radius:8px;margin-left:4px;">Added</span>
|
||||
@endif
|
||||
</div>
|
||||
@if($sup->email)
|
||||
<div style="font-size:10px;color:#94a3b8;">{{ $sup->email }}</div>
|
||||
@endif
|
||||
</div>
|
||||
</label>
|
||||
@empty
|
||||
<div style="padding:16px;text-align:center;color:#94a3b8;font-size:12px;">No suppliers found.</div>
|
||||
@endforelse
|
||||
<div id="idd-no-{{ $item->id }}" style="display:none;padding:14px;text-align:center;color:#94a3b8;font-size:12px;">No results</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</form>
|
||||
|
||||
{{-- Footer --}}
|
||||
<div style="padding:14px 24px;border-top:1px solid #f1f5f9;display:flex;align-items:center;justify-content:space-between;flex-shrink:0;background:#fafafa;">
|
||||
<div style="font-size:12px;color:#64748b;" id="sup-footer-msg">0 selected</div>
|
||||
<div style="display:flex;gap:10px;">
|
||||
<button type="button" onclick="closeSupplierModal()"
|
||||
style="padding:8px 18px;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;font-weight:600;color:#64748b;background:#fff;cursor:pointer;">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" onclick="submitSuppliers()"
|
||||
style="padding:8px 22px;background:#2563eb;color:#fff;border:none;border-radius:8px;font-size:13px;font-weight:700;cursor:pointer;">
|
||||
Save & Continue →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ---- Signature modal ----
|
||||
@@ -678,208 +458,5 @@
|
||||
});
|
||||
}());
|
||||
|
||||
// ---- Supplier modal ----
|
||||
var _supTab = 'global';
|
||||
|
||||
function openSupplierModal() {
|
||||
document.getElementById('supplier-modal').classList.add('open');
|
||||
switchSupTab('global');
|
||||
document.getElementById('sup-search').focus();
|
||||
}
|
||||
function closeSupplierModal() {
|
||||
closeAllItemDd();
|
||||
document.getElementById('supplier-modal').classList.remove('open');
|
||||
}
|
||||
document.getElementById('supplier-modal').addEventListener('click', function(e) {
|
||||
if (e.target === this) closeSupplierModal();
|
||||
});
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') { closeSignModal(); closeSupplierModal(); }
|
||||
});
|
||||
|
||||
// Tab switching
|
||||
function switchSupTab(tab) {
|
||||
_supTab = tab;
|
||||
document.getElementById('sup-mode').value = tab === 'item' ? 'by_item' : 'global';
|
||||
|
||||
document.getElementById('sup-global-pane').style.display = tab === 'global' ? 'flex' : 'none';
|
||||
document.getElementById('sup-item-pane').style.display = tab === 'item' ? 'flex' : 'none';
|
||||
|
||||
var gBtn = document.getElementById('stab-global');
|
||||
var iBtn = document.getElementById('stab-item');
|
||||
if (tab === 'global') {
|
||||
gBtn.style.color = '#2563eb'; gBtn.style.borderBottomColor = '#2563eb';
|
||||
iBtn.style.color = '#94a3b8'; iBtn.style.borderBottomColor = 'transparent';
|
||||
} else {
|
||||
iBtn.style.color = '#2563eb'; iBtn.style.borderBottomColor = '#2563eb';
|
||||
gBtn.style.color = '#94a3b8'; gBtn.style.borderBottomColor = 'transparent';
|
||||
}
|
||||
updateFooter();
|
||||
}
|
||||
|
||||
// ── Global tab helpers ──
|
||||
function filterGlobalSups(q) {
|
||||
q = q.toLowerCase().trim();
|
||||
var items = document.querySelectorAll('.g-sup-item');
|
||||
var visible = 0;
|
||||
items.forEach(function(item) {
|
||||
var match = !q || item.dataset.name.indexOf(q) !== -1;
|
||||
item.style.display = match ? 'flex' : 'none';
|
||||
if (match) visible++;
|
||||
});
|
||||
document.getElementById('no-sup-msg').style.display = (visible === 0 && q) ? 'block' : 'none';
|
||||
}
|
||||
|
||||
function toggleGlobalChan(id, checked) {
|
||||
var el = document.getElementById('gchan-' + id);
|
||||
if (el) el.style.display = checked ? 'block' : 'none';
|
||||
updateFooter();
|
||||
}
|
||||
|
||||
var chanStyles = {
|
||||
email: { bg:'#eff6ff', fg:'#2563eb' },
|
||||
whatsapp: { bg:'#f0fdf4', fg:'#15803d' },
|
||||
both: { bg:'#fef3c7', fg:'#92400e' },
|
||||
};
|
||||
function applyBtnStyles(prefix, id, val) {
|
||||
[['email','e'],['whatsapp','w'],['both','b']].forEach(function(p) {
|
||||
var el = document.getElementById(prefix + p[1] + '-' + id);
|
||||
if (!el) return;
|
||||
if (p[0] === val) { el.style.background = chanStyles[val].bg; el.style.color = chanStyles[val].fg; }
|
||||
else { el.style.background = '#fff'; el.style.color = '#94a3b8'; }
|
||||
});
|
||||
}
|
||||
function setGChan(id, val) {
|
||||
document.getElementById('gchan-val-' + id).value = val;
|
||||
applyBtnStyles('gc', id, val);
|
||||
}
|
||||
|
||||
function updateGlobalCount() { updateFooter(); }
|
||||
|
||||
// ── By-item tab helpers ──
|
||||
var _openItemDd = null;
|
||||
|
||||
function toggleItemDd(event, itemId) {
|
||||
event.stopPropagation();
|
||||
var dd = document.getElementById('idd-' + itemId);
|
||||
if (!dd) return;
|
||||
|
||||
// close if already open
|
||||
if (_openItemDd === itemId) {
|
||||
dd.style.display = 'none';
|
||||
_openItemDd = null;
|
||||
return;
|
||||
}
|
||||
// close any other open dropdown
|
||||
closeAllItemDd();
|
||||
|
||||
// position relative to the trigger button
|
||||
var btn = document.getElementById('idd-btn-' + itemId);
|
||||
var rect = btn.getBoundingClientRect();
|
||||
var ddWidth = 260;
|
||||
var left = Math.min(rect.left, window.innerWidth - ddWidth - 8);
|
||||
dd.style.left = left + 'px';
|
||||
dd.style.top = (rect.bottom + 4) + 'px';
|
||||
dd.style.width = ddWidth + 'px';
|
||||
dd.style.display = 'block';
|
||||
_openItemDd = itemId;
|
||||
|
||||
// focus search input
|
||||
var inp = dd.querySelector('input[type="text"]');
|
||||
if (inp) setTimeout(function(){ inp.focus(); }, 30);
|
||||
}
|
||||
|
||||
function closeAllItemDd() {
|
||||
if (_openItemDd !== null) {
|
||||
var dd = document.getElementById('idd-' + _openItemDd);
|
||||
if (dd) dd.style.display = 'none';
|
||||
_openItemDd = null;
|
||||
}
|
||||
}
|
||||
|
||||
function filterItemDd(itemId, q) {
|
||||
q = q.toLowerCase().trim();
|
||||
var rows = document.querySelectorAll('.idd-row-' + itemId);
|
||||
var visible = 0;
|
||||
rows.forEach(function(row) {
|
||||
var match = !q || (row.dataset.name || '').indexOf(q) !== -1;
|
||||
row.style.display = match ? 'flex' : 'none';
|
||||
if (match) visible++;
|
||||
});
|
||||
var noEl = document.getElementById('idd-no-' + itemId);
|
||||
if (noEl) noEl.style.display = (visible === 0 && q) ? 'block' : 'none';
|
||||
}
|
||||
|
||||
function updateItemDdLabel(itemId) {
|
||||
var checked = document.querySelectorAll('[id^="isup-' + itemId + '-"]:checked:not([disabled])');
|
||||
var label = document.getElementById('idd-label-' + itemId);
|
||||
if (!label) return;
|
||||
if (checked.length === 0) {
|
||||
label.textContent = 'Select suppliers…';
|
||||
label.style.color = '#94a3b8';
|
||||
} else {
|
||||
var names = Array.from(checked).map(function(c) { return c.dataset.supname || c.value; });
|
||||
label.textContent = names.join(', ');
|
||||
label.style.color = '#0f172a';
|
||||
}
|
||||
}
|
||||
|
||||
function onItemSupChange(itemId, supId) {
|
||||
// update dropdown trigger label for this item
|
||||
updateItemDdLabel(itemId);
|
||||
|
||||
// show/hide channel row for this supplier (appears if checked in ANY item)
|
||||
var anyChecked = document.querySelectorAll('input[name^="item_suppliers["][value="' + supId + '"]:checked:not([disabled])').length > 0;
|
||||
var row = document.getElementById('ic-' + supId);
|
||||
if (row) row.style.display = anyChecked ? 'flex' : 'none';
|
||||
|
||||
// show/hide entire channel section
|
||||
var anyItemChecked = document.querySelectorAll('#sup-item-pane input[type="checkbox"]:checked:not([disabled])').length > 0;
|
||||
document.getElementById('item-chan-section').style.display = anyItemChecked ? 'block' : 'none';
|
||||
|
||||
updateFooter();
|
||||
}
|
||||
|
||||
function setIChan(id, val) {
|
||||
document.getElementById('ichan-val-' + id).value = val;
|
||||
applyBtnStyles('ic', id, val);
|
||||
}
|
||||
|
||||
// close dropdown when clicking outside
|
||||
document.addEventListener('click', function(e) {
|
||||
if (_openItemDd === null) return;
|
||||
var dd = document.getElementById('idd-' + _openItemDd);
|
||||
var btn = document.getElementById('idd-btn-' + _openItemDd);
|
||||
if (dd && !dd.contains(e.target) && btn && !btn.contains(e.target)) {
|
||||
closeAllItemDd();
|
||||
}
|
||||
});
|
||||
|
||||
// ── Footer count ──
|
||||
function updateFooter() {
|
||||
var msg = document.getElementById('sup-footer-msg');
|
||||
if (_supTab === 'global') {
|
||||
var n = document.querySelectorAll('#sup-list input[type="checkbox"]:checked:not([disabled])').length;
|
||||
msg.textContent = n + ' supplier' + (n === 1 ? '' : 's') + ' selected';
|
||||
} else {
|
||||
var checked = document.querySelectorAll('#sup-item-pane input[type="checkbox"]:checked:not([disabled])');
|
||||
var supIds = new Set();
|
||||
checked.forEach(function(c) { supIds.add(c.value); });
|
||||
var n = supIds.size;
|
||||
msg.textContent = n + ' supplier' + (n === 1 ? '' : 's') + ' assigned';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Submit ──
|
||||
function submitSuppliers() {
|
||||
if (_supTab === 'global') {
|
||||
var checked = document.querySelectorAll('#sup-list input[type="checkbox"]:checked:not([disabled])');
|
||||
if (checked.length === 0) { showToast('Please select at least one supplier.', 'warn'); return; }
|
||||
} else {
|
||||
var checked = document.querySelectorAll('#sup-item-pane input[type="checkbox"]:checked:not([disabled])');
|
||||
if (checked.length === 0) { showToast('Please assign at least one supplier to an item.', 'warn'); return; }
|
||||
}
|
||||
document.getElementById('sup-form').submit();
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
Reference in new issue
Block a user