chore: initial commit of existing codebase

This commit is contained in:
ghassan committed 2026-05-19 12:40:08 +03:00
commit 11e94889b2
292 files changed
+38586

No files matched your search

@@ -0,0 +1,156 @@
@extends('layouts.app')
@section('title', 'New Goods Receipt Note')
@section('content')
<div class="mb-6">
<h1 class="page-title">New Goods Receipt Note</h1>
<p class="page-subtitle"><a href="{{ route('purchase.grns.index') }}" class="text-blue-600 hover:underline">GRNs</a> / New</p>
</div>
@if($errors->any())
<div class="mb-4 px-4 py-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
<ul class="list-disc list-inside space-y-1">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('purchase.grns.store') }}" method="POST">
@csrf
<div class="card card-body mb-4">
<h2 class="text-base font-semibold text-gray-700 mb-4">GRN Details</h2>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div>
<label class="form-label">Purchase Order <span class="text-red-500">*</span></label>
<select name="purchase_order_id" id="po-select" required onchange="loadPOItems(this)" class="form-select">
<option value="">-- Select PO --</option>
@foreach($purchaseOrders as $po)
<option value="{{ $po->id }}"
data-items="{{ json_encode($po->items) }}"
{{ old('purchase_order_id') == $po->id ? 'selected' : '' }}>
{{ $po->po_number ?? 'PO-' . str_pad($po->id, 5, '0', STR_PAD_LEFT) }} - {{ $po->supplier->name ?? '' }}
</option>
@endforeach
</select>
</div>
<div>
<label class="form-label">Warehouse <span class="text-red-500">*</span></label>
<select name="warehouse_id" required class="form-select">
<option value="">-- Select Warehouse --</option>
@foreach($warehouses as $warehouse)
<option value="{{ $warehouse->id }}" {{ old('warehouse_id') == $warehouse->id ? 'selected' : '' }}>
{{ $warehouse->name }}
</option>
@endforeach
</select>
</div>
<div>
<label class="form-label">Received Date <span class="text-red-500">*</span></label>
<input type="date" name="received_date" value="{{ old('received_date', date('Y-m-d')) }}" required class="form-input">
</div>
<div>
<label class="form-label">Notes</label>
<textarea name="notes" rows="2" class="form-textarea">{{ old('notes') }}</textarea>
</div>
</div>
</div>
<!-- Items from PO -->
<div class="card card-body mb-4">
<h2 class="text-base font-semibold text-gray-700 mb-4">Items Received</h2>
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead>
<tr class="border-b border-gray-200">
<th class="pb-2 text-left font-semibold text-gray-600">Item</th>
<th class="pb-2 text-left font-semibold text-gray-600 w-28">PO Qty</th>
<th class="pb-2 text-left font-semibold text-gray-600 w-28">Qty Received</th>
<th class="pb-2 text-left font-semibold text-gray-600 w-36">Type</th>
</tr>
</thead>
<tbody id="grn-items-body">
<tr id="no-items-row">
<td colspan="3" class="py-4 text-center text-gray-400">Select a Purchase Order to load items.</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="flex items-center gap-3">
<button type="submit" class="btn-primary">Save GRN</button>
<a href="{{ route('purchase.grns.index') }}" class="btn-secondary">Cancel</a>
</div>
</form>
<script>
function setGrnType(idx, val) {
document.getElementById('grn-type-' + idx).value = val;
var inv = document.getElementById('grn-t-inv-' + idx);
var con = document.getElementById('grn-t-con-' + idx);
if (val === 'inventory') {
inv.style.background = '#eff6ff'; inv.style.color = '#2563eb';
con.style.background = '#fff'; con.style.color = '#94a3b8';
} else {
con.style.background = '#fef3c7'; con.style.color = '#92400e';
inv.style.background = '#fff'; inv.style.color = '#94a3b8';
}
}
function loadPOItems(select) {
var tbody = document.getElementById('grn-items-body');
var option = select.options[select.selectedIndex];
tbody.innerHTML = '';
if (!option.value) {
tbody.innerHTML = '<tr id="no-items-row"><td colspan="4" class="py-4 text-center text-gray-400">Select a Purchase Order to load items.</td></tr>';
return;
}
var items = JSON.parse(option.dataset.items || '[]');
if (!items.length) {
tbody.innerHTML = '<tr><td colspan="4" class="py-4 text-center text-gray-400">No items on this PO.</td></tr>';
return;
}
items.forEach(function(item, idx) {
var row = document.createElement('tr');
row.className = 'border-b border-gray-100';
row.innerHTML = `
<td class="py-2 pr-2 text-gray-800">${item.item ? item.item.item_name : 'Item #' + item.item_id}
<input type="hidden" name="items[${idx}][item_id]" value="${item.item_id}">
</td>
<td class="py-2 pr-2 text-gray-500">${item.quantity}</td>
<td class="py-2 pr-2">
<input type="number" name="items[${idx}][quantity_received]"
value="${item.quantity}" min="0" step="0.01"
max="${item.quantity}"
class="w-full border border-gray-300 rounded px-2 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-blue-400">
</td>
<td class="py-2">
<div style="display:flex;gap:0;border:1px solid #e2e8f0;border-radius:6px;overflow:hidden;width:fit-content;">
<span id="grn-t-inv-${idx}" onclick="setGrnType(${idx},'inventory')"
style="padding:5px 10px;font-size:11px;font-weight:700;cursor:pointer;background:#eff6ff;color:#2563eb;white-space:nowrap;">
Inventory
</span>
<span id="grn-t-con-${idx}" onclick="setGrnType(${idx},'consumable')"
style="padding:5px 10px;font-size:11px;font-weight:700;cursor:pointer;background:#fff;color:#94a3b8;border-left:1px solid #e2e8f0;white-space:nowrap;">
Consumable
</span>
</div>
<input type="hidden" name="items[${idx}][type]" id="grn-type-${idx}" value="inventory">
</td>`;
tbody.appendChild(row);
});
}
</script>
@endsection
@@ -0,0 +1,64 @@
@extends('layouts.app')
@section('title', 'Goods Receipt Notes')
@section('content')
<div class="page-header">
<div>
<h1 class="page-title">Goods Receipt Notes</h1>
<p class="page-subtitle">Record goods received from suppliers</p>
</div>
<a href="{{ route('purchase.grns.create') }}" class="btn-primary">
+ New GRN
</a>
</div>
<div class="table-wrapper overflow-x-auto">
<table class="table-base">
<thead>
<tr>
<th>GRN #</th>
<th>PO #</th>
<th>Supplier</th>
<th>Warehouse</th>
<th>Date</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@forelse($grns as $grn)
<tr>
<td class="font-mono text-gray-700">{{ $grn->grn_number ?? 'GRN-' . str_pad($grn->id, 5, '0', STR_PAD_LEFT) }}</td>
<td class="font-mono text-gray-600">{{ $grn->purchaseOrder->po_number ?? 'PO-' . str_pad($grn->purchase_order_id, 5, '0', STR_PAD_LEFT) }}</td>
<td class="text-gray-800">{{ $grn->purchaseOrder->supplier->name ?? '' }}</td>
<td class="text-gray-700">{{ $grn->warehouse->name ?? '' }}</td>
<td>{{ $grn->received_date ? \Carbon\Carbon::parse($grn->received_date)->format('d M Y') : '' }}</td>
<td>
<span class="badge-green">{{ ucfirst($grn->status ?? 'received') }}</span>
</td>
<td>
<div class="flex items-center gap-2">
<a href="{{ route('purchase.grns.show', $grn) }}" class="btn-primary btn-sm">View</a>
<form action="{{ route('purchase.grns.destroy', $grn) }}" method="POST"
onsubmit="confirmDelete(this,'Delete this GRN?','This goods receipt note will be permanently removed.'); return false;">
@csrf
@method('DELETE')
<button type="submit" class="btn-danger btn-sm">Delete</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-4 py-8 text-center text-gray-400">No GRNs found.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($grns->hasPages())
<div class="mt-4">{{ $grns->links() }}</div>
@endif
@endsection
@@ -0,0 +1,84 @@
@extends('layouts.app')
@section('title', 'Goods Receipt Note')
@section('content')
<div class="page-header">
<div>
<h1 class="page-title">Goods Receipt Note</h1>
<p class="page-subtitle">
<a href="{{ route('purchase.grns.index') }}" class="text-blue-600 hover:underline">GRNs</a>
/ {{ $grn->grn_number ?? 'GRN-' . str_pad($grn->id, 5, '0', STR_PAD_LEFT) }}
</p>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
<div class="bg-white rounded-xl border border-gray-200 p-6 shadow-sm">
<h2 class="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-4">GRN Details</h2>
<dl class="space-y-3 text-sm">
<div class="flex justify-between">
<dt class="text-gray-500">GRN Number</dt>
<dd class="font-mono font-semibold text-gray-800">{{ $grn->grn_number ?? 'GRN-' . str_pad($grn->id, 5, '0', STR_PAD_LEFT) }}</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500">Purchase Order</dt>
<dd class="font-mono text-gray-800">
<a href="{{ route('purchase.orders.show', $grn->purchaseOrder) }}" class="text-blue-600 hover:underline">
{{ $grn->purchaseOrder->po_number ?? 'PO-' . str_pad($grn->purchase_order_id, 5, '0', STR_PAD_LEFT) }}
</a>
</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500">Supplier</dt>
<dd class="font-medium text-gray-800">{{ $grn->purchaseOrder->supplier->name ?? '-' }}</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500">Warehouse</dt>
<dd class="text-gray-800">{{ $grn->warehouse->name ?? '-' }}</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500">Received Date</dt>
<dd class="text-gray-800">{{ $grn->received_date ? \Carbon\Carbon::parse($grn->received_date)->format('d M Y') : '-' }}</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500">Status</dt>
<dd><span class="badge-green">{{ ucfirst($grn->status ?? 'received') }}</span></dd>
</div>
@if($grn->notes)
<div>
<dt class="text-gray-500">Notes</dt>
<dd class="text-gray-700 mt-1">{{ $grn->notes }}</dd>
</div>
@endif
</dl>
</div>
</div>
<!-- Items Received -->
<div class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200">
<h2 class="text-base font-semibold text-gray-700">Items Received</h2>
</div>
<table class="table-base">
<thead>
<tr>
<th>Item</th>
<th class="text-right">PO Qty</th>
<th class="text-right">Qty Received</th>
</tr>
</thead>
<tbody>
@forelse($grn->items as $item)
<tr>
<td class="text-gray-800">{{ $item->item->item_name ?? '' }}</td>
<td class="text-right text-gray-600">{{ number_format($item->quantity_ordered ?? 0, 2) }}</td>
<td class="text-right font-medium text-gray-800">{{ number_format($item->quantity_received, 2) }}</td>
</tr>
@empty
<tr><td colspan="3" class="px-4 py-6 text-center text-gray-400">No items recorded.</td></tr>
@endforelse
</tbody>
</table>
</div>
@endsection
@@ -0,0 +1,112 @@
@extends('layouts.app')
@section('title', 'New Supplier Invoice')
@section('content')
<div class="mb-6">
<h1 class="page-title">New Supplier Invoice</h1>
<p class="page-subtitle"><a href="{{ route('purchase.invoices.index') }}" class="text-blue-600 hover:underline">Supplier Invoices</a> / New</p>
</div>
@if($errors->any())
<div class="mb-4 px-4 py-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
<ul class="list-disc list-inside space-y-1">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card card-body max-w-2xl">
<form action="{{ route('purchase.invoices.store') }}" method="POST">
@csrf
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div>
<label class="form-label">Invoice Number <span class="text-red-500">*</span></label>
<input type="text" name="invoice_number" value="{{ old('invoice_number') }}" required class="form-input">
</div>
<div>
<label class="form-label">Supplier <span class="text-red-500">*</span></label>
<select name="supplier_id" required class="form-select">
<option value="">-- Select Supplier --</option>
@foreach($suppliers as $supplier)
<option value="{{ $supplier->id }}" {{ old('supplier_id') == $supplier->id ? 'selected' : '' }}>
{{ $supplier->name }}
</option>
@endforeach
</select>
</div>
<div>
<label class="form-label">Purchase Order</label>
<select name="purchase_order_id" class="form-select">
<option value="">-- None --</option>
@foreach($purchaseOrders as $po)
<option value="{{ $po->id }}" {{ old('purchase_order_id') == $po->id ? 'selected' : '' }}>
{{ $po->po_number ?? 'PO-' . str_pad($po->id, 5, '0', STR_PAD_LEFT) }}
</option>
@endforeach
</select>
</div>
<div>
<label class="form-label">GRN</label>
<select name="goods_receipt_note_id" class="form-select">
<option value="">-- None --</option>
@foreach($grns as $grn)
<option value="{{ $grn->id }}" {{ old('goods_receipt_note_id') == $grn->id ? 'selected' : '' }}>
{{ $grn->grn_number ?? 'GRN-' . str_pad($grn->id, 5, '0', STR_PAD_LEFT) }}
</option>
@endforeach
</select>
</div>
<div>
<label class="form-label">Invoice Date <span class="text-red-500">*</span></label>
<input type="date" name="invoice_date" value="{{ old('invoice_date', date('Y-m-d')) }}" required class="form-input">
</div>
<div>
<label class="form-label">Due Date</label>
<input type="date" name="due_date" value="{{ old('due_date') }}" class="form-input">
</div>
<div>
<label class="form-label">Subtotal <span class="text-red-500">*</span></label>
<input type="number" name="subtotal" id="subtotal" value="{{ old('subtotal') }}"
min="0" step="0.01" required onchange="calcInvoiceTotal()" class="form-input">
</div>
<div>
<label class="form-label">VAT Amount</label>
<input type="number" name="vat_amount" id="vat_amount" value="{{ old('vat_amount', 0) }}"
min="0" step="0.01" onchange="calcInvoiceTotal()" class="form-input">
</div>
<div class="sm:col-span-2">
<label class="form-label">Total Amount</label>
<input type="number" name="total_amount" id="total_amount" value="{{ old('total_amount') }}"
readonly
class="w-full border border-gray-200 bg-gray-50 rounded-lg px-3 py-2 text-sm font-semibold text-gray-800">
</div>
</div>
<div class="mt-6 flex items-center gap-3">
<button type="submit" class="btn-primary">Save Invoice</button>
<a href="{{ route('purchase.invoices.index') }}" class="btn-secondary">Cancel</a>
</div>
</form>
</div>
<script>
function calcInvoiceTotal() {
var subtotal = parseFloat(document.getElementById('subtotal').value) || 0;
var vat = parseFloat(document.getElementById('vat_amount').value) || 0;
document.getElementById('total_amount').value = (subtotal + vat).toFixed(2);
}
</script>
@endsection
@@ -0,0 +1,86 @@
@extends('layouts.app')
@section('title', 'Edit Supplier Invoice')
@section('content')
<div class="mb-6">
<h1 class="page-title">Edit Supplier Invoice</h1>
<p class="page-subtitle"><a href="{{ route('purchase.invoices.index') }}" class="text-blue-600 hover:underline">Supplier Invoices</a> / Edit</p>
</div>
@if($errors->any())
<div class="mb-4 px-4 py-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
<ul class="list-disc list-inside space-y-1">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card card-body max-w-2xl">
<form action="{{ route('purchase.invoices.update', $invoice) }}" method="POST">
@csrf
@method('PUT')
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div>
<label class="form-label">Invoice Number <span class="text-red-500">*</span></label>
<input type="text" name="invoice_number" value="{{ old('invoice_number', $invoice->invoice_number) }}" required class="form-input">
</div>
<div>
<label class="form-label">Invoice Date <span class="text-red-500">*</span></label>
<input type="date" name="invoice_date" value="{{ old('invoice_date', $invoice->invoice_date ? \Carbon\Carbon::parse($invoice->invoice_date)->format('Y-m-d') : '') }}" required class="form-input">
</div>
<div>
<label class="form-label">Due Date</label>
<input type="date" name="due_date" value="{{ old('due_date', $invoice->due_date ? \Carbon\Carbon::parse($invoice->due_date)->format('Y-m-d') : '') }}" class="form-input">
</div>
<div>
<label class="form-label">Subtotal</label>
<input type="number" name="subtotal" id="edit-subtotal" value="{{ old('subtotal', $invoice->subtotal) }}"
min="0" step="0.01" onchange="calcPurchaseEditTotal()" class="form-input">
</div>
<div>
<label class="form-label">VAT Amount</label>
<input type="number" name="vat_amount" id="edit-vat" value="{{ old('vat_amount', $invoice->vat_amount) }}"
min="0" step="0.01" onchange="calcPurchaseEditTotal()" class="form-input">
</div>
<div>
<label class="form-label">Total Amount</label>
<input type="number" name="total_amount" id="edit-total" value="{{ old('total_amount', $invoice->total_amount) }}"
readonly
class="w-full border border-gray-200 bg-gray-50 rounded-lg px-3 py-2 text-sm font-semibold text-gray-800">
</div>
<div>
<label class="form-label">Status</label>
<select name="status" class="form-select">
<option value="unpaid" {{ old('status', $invoice->status) === 'unpaid' ? 'selected' : '' }}>Unpaid</option>
<option value="partial" {{ old('status', $invoice->status) === 'partial' ? 'selected' : '' }}>Partial</option>
<option value="paid" {{ old('status', $invoice->status) === 'paid' ? 'selected' : '' }}>Paid</option>
</select>
</div>
</div>
<div class="mt-6 flex items-center gap-3">
<button type="submit" class="btn-primary">Update Invoice</button>
<a href="{{ route('purchase.invoices.index') }}" class="btn-secondary">Cancel</a>
</div>
</form>
</div>
<script>
function calcPurchaseEditTotal() {
var sub = parseFloat(document.getElementById('edit-subtotal').value) || 0;
var vat = parseFloat(document.getElementById('edit-vat').value) || 0;
document.getElementById('edit-total').value = (sub + vat).toFixed(2);
}
</script>
@endsection
@@ -0,0 +1,78 @@
@extends('layouts.app')
@section('title', 'Supplier Invoices')
@section('content')
<div class="page-header">
<div>
<h1 class="page-title">Supplier Invoices</h1>
<p class="page-subtitle">Track and manage supplier invoices</p>
</div>
<a href="{{ route('purchase.invoices.create') }}" class="btn-primary">
+ New Invoice
</a>
</div>
<div class="table-wrapper overflow-x-auto">
<table class="table-base">
<thead>
<tr>
<th>Invoice #</th>
<th>Supplier</th>
<th>PO #</th>
<th>Date</th>
<th class="text-right">Total</th>
<th class="text-right">Paid</th>
<th class="text-right">Outstanding</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@forelse($invoices as $invoice)
@php $outstanding = $invoice->total_amount - $invoice->paid_amount; @endphp
<tr>
<td class="font-mono text-gray-700">{{ $invoice->invoice_number }}</td>
<td class="text-gray-800">{{ $invoice->supplier->name ?? '' }}</td>
<td class="font-mono text-gray-600">{{ $invoice->purchaseOrder->po_number ?? '-' }}</td>
<td>{{ $invoice->invoice_date ? \Carbon\Carbon::parse($invoice->invoice_date)->format('d M Y') : '' }}</td>
<td class="text-right text-gray-800">{{ number_format($invoice->total_amount, 2) }}</td>
<td class="text-right text-green-700">{{ number_format($invoice->paid_amount, 2) }}</td>
<td class="text-right {{ $outstanding > 0 ? 'text-red-600 font-semibold' : 'text-gray-500' }}">{{ number_format($outstanding, 2) }}</td>
<td>
@php
$badgeClass = match($invoice->status ?? 'unpaid') {
'unpaid' => 'badge-red',
'partial' => 'badge-yellow',
'paid' => 'badge-green',
default => 'badge-gray',
};
@endphp
<span class="{{ $badgeClass }}">{{ ucfirst($invoice->status ?? 'unpaid') }}</span>
</td>
<td>
<div class="flex items-center gap-2">
<a href="{{ route('purchase.payments.create', ['invoice_id' => $invoice->id]) }}" class="btn-success btn-sm">Pay</a>
<a href="{{ route('purchase.invoices.edit', $invoice) }}" class="btn-secondary btn-sm">Edit</a>
<form action="{{ route('purchase.invoices.destroy', $invoice) }}" method="POST"
onsubmit="confirmDelete(this,'Delete this invoice?','This invoice will be permanently removed.'); return false;">
@csrf
@method('DELETE')
<button type="submit" class="btn-danger btn-sm">Delete</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="9" class="px-4 py-8 text-center text-gray-400">No invoices found.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($invoices->hasPages())
<div class="mt-4">{{ $invoices->links() }}</div>
@endif
@endsection
@@ -0,0 +1,196 @@
@extends('layouts.app')
@section('title', 'New Purchase Order')
@section('content')
<div class="mb-6">
<h1 class="page-title">New Purchase Order</h1>
<p class="page-subtitle"><a href="{{ route('purchase.orders.index') }}" class="text-blue-600 hover:underline">Purchase Orders</a> / New</p>
</div>
@if($errors->any())
<div class="mb-4 px-4 py-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
<ul class="list-disc list-inside space-y-1">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('purchase.orders.store') }}" method="POST">
@csrf
<!-- Header Section -->
<div class="card card-body mb-4">
<h2 class="text-base font-semibold text-gray-700 mb-4">Order Details</h2>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
<div>
<label class="form-label">Supplier <span class="text-red-500">*</span></label>
<select name="supplier_id" required class="form-select">
<option value="">-- Select Supplier --</option>
@foreach($suppliers as $supplier)
<option value="{{ $supplier->id }}" {{ old('supplier_id') == $supplier->id ? 'selected' : '' }}>
{{ $supplier->name }}
</option>
@endforeach
</select>
</div>
<div>
<label class="form-label">Purchase Request (optional)</label>
<select name="purchase_request_id" class="form-select">
<option value="">-- None --</option>
@foreach($purchaseRequests as $pr)
<option value="{{ $pr->id }}" {{ old('purchase_request_id') == $pr->id ? 'selected' : '' }}>
{{ $pr->request_number ?? '#' . $pr->id }} - {{ $pr->item->item_name ?? '' }}
</option>
@endforeach
</select>
</div>
<div>
<label class="form-label">PO Date <span class="text-red-500">*</span></label>
<input type="date" name="po_date" value="{{ old('po_date', date('Y-m-d')) }}" required class="form-input">
</div>
<div>
<label class="form-label">Expected Delivery Date</label>
<input type="date" name="expected_delivery_date" value="{{ old('expected_delivery_date') }}" class="form-input">
</div>
<div class="sm:col-span-2">
<label class="form-label">Notes</label>
<textarea name="notes" rows="2" class="form-textarea">{{ old('notes') }}</textarea>
</div>
</div>
</div>
<!-- Items Section -->
<div class="card card-body mb-4">
<div class="flex items-center justify-between mb-4">
<h2 class="text-base font-semibold text-gray-700">Order Items</h2>
<button type="button" onclick="addPORow()" class="btn-primary btn-sm">+ Add Row</button>
</div>
<div class="overflow-x-auto">
<table class="min-w-full text-sm" id="po-items-table">
<thead>
<tr class="border-b border-gray-200">
<th class="pb-2 text-left font-semibold text-gray-600">Item</th>
<th class="pb-2 text-left font-semibold text-gray-600 w-28">Quantity</th>
<th class="pb-2 text-left font-semibold text-gray-600 w-28">Rate</th>
<th class="pb-2 text-left font-semibold text-gray-600 w-28">Total</th>
<th class="pb-2 w-10"></th>
</tr>
</thead>
<tbody id="po-items-body">
<tr class="po-item-row">
<td class="py-1 pr-2">
<select name="items[0][item_id]"
class="w-full border border-gray-300 rounded px-2 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-blue-400">
<option value="">-- Select Item --</option>
@foreach($items as $item)
<option value="{{ $item->id }}">{{ $item->item_code }} - {{ $item->item_name }}</option>
@endforeach
</select>
</td>
<td class="py-1 pr-2">
<input type="number" name="items[0][quantity]" placeholder="0"
min="0" step="0.01"
onchange="calcRowTotal(this)"
class="w-full border border-gray-300 rounded px-2 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-blue-400 qty-input">
</td>
<td class="py-1 pr-2">
<input type="number" name="items[0][rate]" placeholder="0.00"
min="0" step="0.01"
onchange="calcRowTotal(this)"
class="w-full border border-gray-300 rounded px-2 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-blue-400 rate-input">
</td>
<td class="py-1 pr-2">
<input type="number" name="items[0][total]" placeholder="0.00"
readonly
class="w-full border border-gray-200 bg-gray-50 rounded px-2 py-1.5 text-sm row-total">
</td>
<td class="py-1">
<button type="button" onclick="removePORow(this)" class="text-red-400 hover:text-red-600 text-lg leading-none">&times;</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="mt-3 flex justify-end">
<div class="text-sm font-semibold text-gray-700">
Grand Total: <span id="grand-total" class="text-blue-700">0.00</span>
</div>
</div>
</div>
<div class="flex items-center gap-3">
<button type="submit" class="btn-primary">Create Purchase Order</button>
<a href="{{ route('purchase.orders.index') }}" class="btn-secondary">Cancel</a>
</div>
</form>
<script>
var poRowIndex = 1;
var itemsHtml = `@foreach($items as $item)<option value="{{ $item->id }}">{{ $item->item_code }} - {{ $item->item_name }}</option>@endforeach`;
function addPORow() {
var tbody = document.getElementById('po-items-body');
var row = document.createElement('tr');
row.className = 'po-item-row';
row.innerHTML = `
<td class="py-1 pr-2">
<select name="items[${poRowIndex}][item_id]" class="w-full border border-gray-300 rounded px-2 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-blue-400">
<option value="">-- Select Item --</option>
${itemsHtml}
</select>
</td>
<td class="py-1 pr-2">
<input type="number" name="items[${poRowIndex}][quantity]" placeholder="0" min="0" step="0.01"
onchange="calcRowTotal(this)" class="w-full border border-gray-300 rounded px-2 py-1.5 text-sm qty-input">
</td>
<td class="py-1 pr-2">
<input type="number" name="items[${poRowIndex}][rate]" placeholder="0.00" min="0" step="0.01"
onchange="calcRowTotal(this)" class="w-full border border-gray-300 rounded px-2 py-1.5 text-sm rate-input">
</td>
<td class="py-1 pr-2">
<input type="number" name="items[${poRowIndex}][total]" placeholder="0.00" readonly
class="w-full border border-gray-200 bg-gray-50 rounded px-2 py-1.5 text-sm row-total">
</td>
<td class="py-1">
<button type="button" onclick="removePORow(this)" class="text-red-400 hover:text-red-600 text-lg leading-none">&times;</button>
</td>`;
tbody.appendChild(row);
poRowIndex++;
}
function removePORow(btn) {
var rows = document.querySelectorAll('.po-item-row');
if (rows.length > 1) {
btn.closest('tr').remove();
updateGrandTotal();
}
}
function calcRowTotal(input) {
var row = input.closest('tr');
var qty = parseFloat(row.querySelector('.qty-input').value) || 0;
var rate = parseFloat(row.querySelector('.rate-input').value) || 0;
var total = qty * rate;
row.querySelector('.row-total').value = total.toFixed(2);
updateGrandTotal();
}
function updateGrandTotal() {
var totals = document.querySelectorAll('.row-total');
var grand = 0;
totals.forEach(function(t) { grand += parseFloat(t.value) || 0; });
document.getElementById('grand-total').textContent = grand.toFixed(2);
}
</script>
@endsection
@@ -0,0 +1,72 @@
@extends('layouts.app')
@section('title', 'Edit Purchase Order')
@section('content')
<div class="mb-6">
<h1 class="page-title">Edit Purchase Order</h1>
<p class="page-subtitle"><a href="{{ route('purchase.orders.index') }}" class="text-blue-600 hover:underline">Purchase Orders</a> / Edit</p>
</div>
@if($errors->any())
<div class="mb-4 px-4 py-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
<ul class="list-disc list-inside space-y-1">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card card-body max-w-2xl">
<form action="{{ route('purchase.orders.update', $order) }}" method="POST">
@csrf
@method('PUT')
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div>
<label class="form-label">Supplier <span class="text-red-500">*</span></label>
<select name="supplier_id" required class="form-select">
<option value="">-- Select Supplier --</option>
@foreach($suppliers as $supplier)
<option value="{{ $supplier->id }}" {{ old('supplier_id', $order->supplier_id) == $supplier->id ? 'selected' : '' }}>
{{ $supplier->name }}
</option>
@endforeach
</select>
</div>
<div>
<label class="form-label">PO Date <span class="text-red-500">*</span></label>
<input type="date" name="po_date" value="{{ old('po_date', $order->po_date ? \Carbon\Carbon::parse($order->po_date)->format('Y-m-d') : '') }}" required class="form-input">
</div>
<div>
<label class="form-label">Expected Delivery Date</label>
<input type="date" name="expected_delivery_date" value="{{ old('expected_delivery_date', $order->expected_delivery_date ? \Carbon\Carbon::parse($order->expected_delivery_date)->format('Y-m-d') : '') }}" class="form-input">
</div>
<div>
<label class="form-label">Status</label>
<select name="status" class="form-select">
<option value="draft" {{ old('status', $order->status) === 'draft' ? 'selected' : '' }}>Draft</option>
<option value="sent" {{ old('status', $order->status) === 'sent' ? 'selected' : '' }}>Sent</option>
<option value="received" {{ old('status', $order->status) === 'received' ? 'selected' : '' }}>Received</option>
<option value="cancelled" {{ old('status', $order->status) === 'cancelled' ? 'selected' : '' }}>Cancelled</option>
</select>
</div>
<div class="sm:col-span-2">
<label class="form-label">Notes</label>
<textarea name="notes" rows="3" class="form-textarea">{{ old('notes', $order->notes) }}</textarea>
</div>
</div>
<div class="mt-6 flex items-center gap-3">
<button type="submit" class="btn-primary">Update Order</button>
<a href="{{ route('purchase.orders.show', $order) }}" class="btn-secondary">Cancel</a>
</div>
</form>
</div>
@endsection
@@ -0,0 +1,74 @@
@extends('layouts.app')
@section('title', 'Purchase Orders')
@section('content')
<div class="page-header">
<div>
<h1 class="page-title">Purchase Orders</h1>
<p class="page-subtitle">Manage all purchase orders</p>
</div>
<a href="{{ route('purchase.orders.create') }}" class="btn-primary">
+ New PO
</a>
</div>
<div class="table-wrapper overflow-x-auto">
<table class="table-base">
<thead>
<tr>
<th>PO #</th>
<th>Supplier</th>
<th>Date</th>
<th>Expected Delivery</th>
<th class="text-right">Total Amount</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@forelse($orders as $order)
<tr>
<td class="font-mono text-gray-700">{{ $order->po_number ?? 'PO-' . str_pad($order->id, 5, '0', STR_PAD_LEFT) }}</td>
<td class="text-gray-800">{{ $order->supplier->name ?? '' }}</td>
<td>{{ $order->po_date ? \Carbon\Carbon::parse($order->po_date)->format('d M Y') : '' }}</td>
<td>{{ $order->expected_delivery_date ? \Carbon\Carbon::parse($order->expected_delivery_date)->format('d M Y') : '' }}</td>
<td class="text-right font-medium text-gray-800">{{ number_format($order->total_amount, 2) }}</td>
<td>
@php
$badgeClass = match($order->status ?? 'draft') {
'draft' => 'badge-gray',
'sent' => 'badge-blue',
'received' => 'badge-green',
'cancelled' => 'badge-red',
default => 'badge-gray',
};
@endphp
<span class="{{ $badgeClass }}">{{ ucfirst($order->status ?? 'draft') }}</span>
</td>
<td>
<div class="flex items-center gap-2">
<a href="{{ route('purchase.orders.show', $order) }}" class="btn-primary btn-sm">View</a>
<a href="{{ route('purchase.orders.edit', $order) }}" class="btn-secondary btn-sm">Edit</a>
<form action="{{ route('purchase.orders.destroy', $order) }}" method="POST"
onsubmit="confirmDelete(this,'Delete this purchase order?','This order will be permanently removed.'); return false;">
@csrf
@method('DELETE')
<button type="submit" class="btn-danger btn-sm">Delete</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-4 py-8 text-center text-gray-400">No purchase orders found.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($orders->hasPages())
<div class="mt-4">{{ $orders->links() }}</div>
@endif
@endsection
@@ -0,0 +1,152 @@
@extends('layouts.app')
@section('title', 'Purchase Order')
@section('content')
<div class="page-header">
<div>
<h1 class="page-title">Purchase Order</h1>
<p class="page-subtitle">
<a href="{{ route('purchase.orders.index') }}" class="text-blue-600 hover:underline">Purchase Orders</a>
/ {{ $order->po_number ?? 'PO-' . str_pad($order->id, 5, '0', STR_PAD_LEFT) }}
</p>
</div>
<div class="flex gap-2">
<a href="{{ route('purchase.orders.edit', $order) }}" class="btn-secondary">Edit</a>
<a href="{{ route('purchase.grns.create', ['purchase_order_id' => $order->id]) }}" class="btn-primary">Create GRN</a>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 mb-6">
<!-- PO Info -->
<div class="lg:col-span-2 bg-white rounded-xl border border-gray-200 p-6 shadow-sm">
<h2 class="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-4">Order Information</h2>
<dl class="grid grid-cols-2 gap-4 text-sm">
<div>
<dt class="text-gray-500">PO Number</dt>
<dd class="font-semibold text-gray-800 font-mono">{{ $order->po_number ?? 'PO-' . str_pad($order->id, 5, '0', STR_PAD_LEFT) }}</dd>
</div>
<div>
<dt class="text-gray-500">Status</dt>
<dd>
@php
$badgeClass = match($order->status ?? 'draft') {
'draft' => 'badge-gray',
'sent' => 'badge-blue',
'received' => 'badge-green',
'cancelled' => 'badge-red',
default => 'badge-gray',
};
@endphp
<span class="{{ $badgeClass }}">{{ ucfirst($order->status ?? 'draft') }}</span>
</dd>
</div>
<div>
<dt class="text-gray-500">PO Date</dt>
<dd class="font-medium text-gray-800">{{ $order->po_date ? \Carbon\Carbon::parse($order->po_date)->format('d M Y') : '-' }}</dd>
</div>
<div>
<dt class="text-gray-500">Expected Delivery</dt>
<dd class="font-medium text-gray-800">{{ $order->expected_delivery_date ? \Carbon\Carbon::parse($order->expected_delivery_date)->format('d M Y') : '-' }}</dd>
</div>
@if($order->notes)
<div class="col-span-2">
<dt class="text-gray-500">Notes</dt>
<dd class="text-gray-700">{{ $order->notes }}</dd>
</div>
@endif
</dl>
</div>
<!-- Supplier Info -->
<div class="bg-white rounded-xl border border-gray-200 p-6 shadow-sm">
<h2 class="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-4">Supplier</h2>
@if($order->supplier)
<dl class="space-y-2 text-sm">
<div>
<dt class="text-gray-500">Name</dt>
<dd class="font-semibold text-gray-800">{{ $order->supplier->name }}</dd>
</div>
<div>
<dt class="text-gray-500">Contact</dt>
<dd class="text-gray-700">{{ $order->supplier->contact_person }}</dd>
</div>
<div>
<dt class="text-gray-500">Email</dt>
<dd class="text-gray-700">{{ $order->supplier->email }}</dd>
</div>
<div>
<dt class="text-gray-500">Phone</dt>
<dd class="text-gray-700">{{ $order->supplier->phone }}</dd>
</div>
</dl>
@endif
</div>
</div>
<!-- Items Table -->
<div class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden mb-6">
<div class="px-6 py-4 border-b border-gray-200">
<h2 class="text-base font-semibold text-gray-700">Order Items</h2>
</div>
<table class="table-base">
<thead>
<tr>
<th>Item</th>
<th class="text-right">Quantity</th>
<th class="text-right">Rate</th>
<th class="text-right">Total</th>
</tr>
</thead>
<tbody>
@forelse($order->items as $item)
<tr>
<td class="text-gray-800">{{ $item->item->item_name ?? $item->item_name }}</td>
<td class="text-right text-gray-600">{{ number_format($item->quantity, 2) }}</td>
<td class="text-right text-gray-600">{{ number_format($item->rate, 2) }}</td>
<td class="text-right font-medium text-gray-800">{{ number_format($item->total, 2) }}</td>
</tr>
@empty
<tr><td colspan="4" class="px-4 py-6 text-center text-gray-400">No items.</td></tr>
@endforelse
</tbody>
<tfoot class="bg-gray-50">
<tr>
<td colspan="3" class="px-4 py-3 text-right font-semibold text-gray-700">Total Amount</td>
<td class="px-4 py-3 text-right font-bold text-gray-900">{{ number_format($order->total_amount, 2) }}</td>
</tr>
</tfoot>
</table>
</div>
<!-- GRNs Linked -->
@if($order->goodsReceiptNotes && $order->goodsReceiptNotes->count())
<div class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200">
<h2 class="text-base font-semibold text-gray-700">Goods Receipt Notes</h2>
</div>
<table class="table-base">
<thead>
<tr>
<th>GRN #</th>
<th>Warehouse</th>
<th>Received Date</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach($order->goodsReceiptNotes as $grn)
<tr>
<td class="font-mono text-gray-700">{{ $grn->grn_number ?? 'GRN-' . str_pad($grn->id, 5, '0', STR_PAD_LEFT) }}</td>
<td class="text-gray-700">{{ $grn->warehouse->name ?? '' }}</td>
<td>{{ $grn->received_date ? \Carbon\Carbon::parse($grn->received_date)->format('d M Y') : '' }}</td>
<td><span class="badge-green">{{ ucfirst($grn->status ?? 'received') }}</span></td>
<td><a href="{{ route('purchase.grns.show', $grn) }}" class="btn-primary btn-sm">View</a></td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
@endsection
@@ -0,0 +1,78 @@
@extends('layouts.app')
@section('title', 'Record Supplier Payment')
@section('content')
<div class="mb-6">
<h1 class="page-title">Record Supplier Payment</h1>
<p class="page-subtitle"><a href="{{ route('purchase.payments.index') }}" class="text-blue-600 hover:underline">Payments</a> / New</p>
</div>
@if($errors->any())
<div class="mb-4 px-4 py-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
<ul class="list-disc list-inside space-y-1">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card card-body max-w-2xl">
<form action="{{ route('purchase.payments.store') }}" method="POST">
@csrf
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div class="sm:col-span-2">
<label class="form-label">Invoice <span class="text-red-500">*</span></label>
<select name="supplier_invoice_id" required class="form-select">
<option value="">-- Select Unpaid Invoice --</option>
@foreach($unpaidInvoices as $invoice)
<option value="{{ $invoice->id }}" {{ (old('supplier_invoice_id', request('invoice_id'))) == $invoice->id ? 'selected' : '' }}>
{{ $invoice->invoice_number }} - {{ $invoice->supplier->name ?? '' }}
(Outstanding: {{ number_format($invoice->total_amount - $invoice->paid_amount, 2) }})
</option>
@endforeach
</select>
</div>
<div>
<label class="form-label">Payment Date <span class="text-red-500">*</span></label>
<input type="date" name="payment_date" value="{{ old('payment_date', date('Y-m-d')) }}" required class="form-input">
</div>
<div>
<label class="form-label">Amount <span class="text-red-500">*</span></label>
<input type="number" name="amount" value="{{ old('amount') }}" min="0.01" step="0.01" required class="form-input">
</div>
<div>
<label class="form-label">Payment Method <span class="text-red-500">*</span></label>
<select name="payment_method" required class="form-select">
<option value="">-- Select Method --</option>
<option value="cash" {{ old('payment_method') === 'cash' ? 'selected' : '' }}>Cash</option>
<option value="bank_transfer" {{ old('payment_method') === 'bank_transfer' ? 'selected' : '' }}>Bank Transfer</option>
<option value="cheque" {{ old('payment_method') === 'cheque' ? 'selected' : '' }}>Cheque</option>
<option value="other" {{ old('payment_method') === 'other' ? 'selected' : '' }}>Other</option>
</select>
</div>
<div>
<label class="form-label">Reference Number</label>
<input type="text" name="reference_number" value="{{ old('reference_number') }}" class="form-input">
</div>
<div class="sm:col-span-2">
<label class="form-label">Notes</label>
<textarea name="notes" rows="2" class="form-textarea">{{ old('notes') }}</textarea>
</div>
</div>
<div class="mt-6 flex items-center gap-3">
<button type="submit" class="btn-primary">Record Payment</button>
<a href="{{ route('purchase.payments.index') }}" class="btn-secondary">Cancel</a>
</div>
</form>
</div>
@endsection
@@ -0,0 +1,50 @@
@extends('layouts.app')
@section('title', 'Supplier Payments')
@section('content')
<div class="page-header">
<div>
<h1 class="page-title">Supplier Payments</h1>
<p class="page-subtitle">Payment history to suppliers</p>
</div>
<a href="{{ route('purchase.payments.create') }}" class="btn-primary">
+ Record Payment
</a>
</div>
<div class="table-wrapper overflow-x-auto">
<table class="table-base">
<thead>
<tr>
<th>Invoice #</th>
<th>Supplier</th>
<th>Payment Date</th>
<th class="text-right">Amount</th>
<th>Method</th>
<th>Reference</th>
</tr>
</thead>
<tbody>
@forelse($payments as $payment)
<tr>
<td class="font-mono text-gray-700">{{ $payment->supplierInvoice->invoice_number ?? '-' }}</td>
<td class="text-gray-800">{{ $payment->supplierInvoice->supplier->name ?? '' }}</td>
<td>{{ $payment->payment_date ? \Carbon\Carbon::parse($payment->payment_date)->format('d M Y') : '' }}</td>
<td class="text-right font-medium text-gray-800">{{ number_format($payment->amount, 2) }}</td>
<td class="capitalize">{{ str_replace('_', ' ', $payment->payment_method) }}</td>
<td>{{ $payment->reference_number ?? '-' }}</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-4 py-8 text-center text-gray-400">No payments recorded.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($payments->hasPages())
<div class="mt-4">{{ $payments->links() }}</div>
@endif
@endsection
@@ -0,0 +1,62 @@
<div style="background:#fff;border-radius:14px;box-shadow:0 2px 10px rgba(0,0,0,.05);overflow:hidden;">
<table style="width:100%;border-collapse:collapse;font-size:13px;">
<thead>
<tr style="border-bottom:1px solid #f1f5f9;">
<th style="padding:12px 18px;text-align:left;font-size:11px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.05em;">Request #</th>
<th style="padding:12px 18px;text-align:left;font-size:11px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.05em;">Project / Description</th>
<th style="padding:12px 18px;text-align:left;font-size:11px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.05em;">Department</th>
<th style="padding:12px 18px;text-align:left;font-size:11px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.05em;">Requested By</th>
<th style="padding:12px 18px;text-align:left;font-size:11px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.05em;">Stage</th>
<th style="padding:12px 18px;text-align:left;font-size:11px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.05em;">Progress</th>
<th style="padding:12px 18px;text-align:left;font-size:11px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.05em;">Date</th>
<th style="padding:12px 18px;"></th>
</tr>
</thead>
<tbody>
@foreach($rows as $pr)
@php
$stageIdx = $stages->stageIndex($pr->stage ?? 'draft');
$total = count(\App\Services\PurchaseStageService::STAGES);
$pct = $total > 1 ? round(($stageIdx / ($total - 1)) * 100) : 100;
$isDone = $pr->stage === 'complete';
@endphp
<tr class="pr-row" onclick="window.location='{{ route('purchase.pipeline.show', $pr) }}'"
style="border-bottom:1px solid #f8fafc;transition:background .12s;">
<td style="padding:14px 18px;font-weight:700;color:#0f172a;">{{ $pr->request_number }}</td>
<td style="padding:14px 18px;color:#374151;max-width:200px;">
<div style="font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">
{{ $pr->project_name ?: '—' }}
</div>
@if($pr->remarks)
<div style="font-size:11px;color:#94a3b8;margin-top:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">
{{ Str::limit($pr->remarks, 50) }}
</div>
@endif
</td>
<td style="padding:14px 18px;color:#64748b;">{{ $pr->department ?: '—' }}</td>
<td style="padding:14px 18px;color:#64748b;">{{ $pr->requested_by_name ?? $pr->requestedBy?->name ?? '—' }}</td>
<td style="padding:14px 18px;">
<span class="stage-pill"
style="background:{{ $isDone ? '#dcfce7' : '#fffbeb' }};color:{{ $isDone ? '#15803d' : '#92400e' }};">
{{ $stages->stageLabel($pr->stage) }}
</span>
</td>
<td style="padding:14px 18px;min-width:100px;">
<div style="height:5px;background:#f1f5f9;border-radius:3px;overflow:hidden;">
<div style="height:5px;background:{{ $isDone ? '#22c55e' : '#f59e0b' }};width:{{ $pct }}%;border-radius:3px;"></div>
</div>
<div style="font-size:10px;color:#94a3b8;margin-top:3px;">{{ $pct }}%</div>
</td>
<td style="padding:14px 18px;color:#94a3b8;white-space:nowrap;">
{{ $pr->date ? \Carbon\Carbon::parse($pr->date)->format('d M Y') : '—' }}
</td>
<td style="padding:14px 18px;">
<svg width="16" height="16" fill="none" stroke="#cbd5e1" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/>
</svg>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@@ -0,0 +1,79 @@
@extends('layouts.app')
@section('title', 'Purchase Pipeline')
@section('content')
<style>
.hidden-tab { display:none; }
.pr-row:hover { background:#f8fafc; cursor:pointer; }
.stage-pill { display:inline-block; font-size:11px; font-weight:700; padding:3px 10px; border-radius:20px; white-space:nowrap; }
</style>
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:24px;flex-wrap:wrap;gap:12px;">
<div>
<h1 style="font-size:22px;font-weight:700;color:#0f172a;">Purchase Pipeline</h1>
<p style="font-size:13px;color:#64748b;margin-top:3px;">Track every purchase from request to payment</p>
</div>
<a href="{{ route('purchase.requests.create') }}"
style="padding:10px 22px;background:#2563eb;color:#fff;border-radius:9px;font-size:13px;font-weight:700;text-decoration:none;">
+ New Request
</a>
</div>
{{-- Tabs --}}
<div style="display:flex;gap:0;border-bottom:2px solid #e2e8f0;margin-bottom:20px;">
<button id="tab-active" onclick="switchTab('active')"
style="padding:10px 22px;font-size:13px;font-weight:700;border:none;background:none;cursor:pointer;color:#2563eb;border-bottom:2px solid #2563eb;margin-bottom:-2px;">
Active
<span style="margin-left:6px;background:#dbeafe;color:#1d4ed8;font-size:11px;font-weight:700;padding:2px 8px;border-radius:20px;">{{ $active->count() }}</span>
</button>
<button id="tab-completed" onclick="switchTab('completed')"
style="padding:10px 22px;font-size:13px;font-weight:700;border:none;background:none;cursor:pointer;color:#64748b;border-bottom:2px solid transparent;margin-bottom:-2px;">
Completed
<span style="margin-left:6px;background:#f1f5f9;color:#64748b;font-size:11px;font-weight:700;padding:2px 8px;border-radius:20px;">{{ $completed->count() }}</span>
</button>
</div>
{{-- Active tab --}}
<div id="pane-active">
@if($active->isEmpty())
<div style="text-align:center;padding:60px 0;color:#94a3b8;">
<div style="font-size:44px;margin-bottom:12px;">✅</div>
<div style="font-size:15px;font-weight:600;">No active pipelines</div>
<div style="font-size:13px;margin-top:4px;">All purchases are complete, or create a new request.</div>
</div>
@else
@include('purchase.pipeline._table', ['rows' => $active, 'stages' => $stages])
@endif
</div>
{{-- Completed tab --}}
<div id="pane-completed" class="hidden-tab">
@if($completed->isEmpty())
<div style="text-align:center;padding:60px 0;color:#94a3b8;">
<div style="font-size:44px;margin-bottom:12px;">📋</div>
<div style="font-size:15px;font-weight:600;">No completed purchases yet</div>
</div>
@else
@include('purchase.pipeline._table', ['rows' => $completed, 'stages' => $stages])
@endif
</div>
<script>
function switchTab(tab) {
document.getElementById('pane-active').classList.toggle('hidden-tab', tab !== 'active');
document.getElementById('pane-completed').classList.toggle('hidden-tab', tab !== 'completed');
var btnActive = document.getElementById('tab-active');
var btnCompleted = document.getElementById('tab-completed');
if (tab === 'active') {
btnActive.style.color = '#2563eb'; btnActive.style.borderBottomColor = '#2563eb';
btnCompleted.style.color = '#64748b'; btnCompleted.style.borderBottomColor = 'transparent';
} else {
btnCompleted.style.color = '#2563eb'; btnCompleted.style.borderBottomColor = '#2563eb';
btnActive.style.color = '#64748b'; btnActive.style.borderBottomColor = 'transparent';
}
}
</script>
@endsection
@@ -0,0 +1,885 @@
@extends('layouts.app')
@section('title', 'Pipeline — ' . $pr->request_number)
@section('content')
<style>
.action-btn { display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:700;padding:6px 14px;border-radius:7px;text-decoration:none;white-space:nowrap;cursor:pointer;border:none; }
.sup-item:hover { background:#f8fafc; }
.pipe-modal { display:none;position:fixed;inset:0;background:rgba(15,23,42,.55);z-index:9999;align-items:center;justify-content:center;padding:16px; }
.pipe-modal.open { display:flex; }
</style>
{{-- Breadcrumb --}}
<div style="margin-bottom:20px;">
<a href="{{ route('purchase.pipeline.index') }}"
style="font-size:13px;color:#2563eb;text-decoration:none;display:inline-flex;align-items:center;gap:5px;">
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/>
</svg>
Purchase Pipeline
</a>
</div>
@php
$stageIdx = $stages->stageIndex($pr->stage);
$allStages = \App\Services\PurchaseStageService::STAGES;
$total = count($allStages);
$pct = $total > 1 ? round(($stageIdx / ($total - 1)) * 100) : 100;
$isDone = $pr->stage === 'complete';
$pendingInv = $pr->rfqInvitations->where('status', 'pending');
$sentInv = $pr->rfqInvitations->where('status', '!=', 'pending');
$selectedIds = $pr->rfqInvitations->pluck('supplier_id')->toArray();
@endphp
{{-- Header card --}}
<div style="background:#fff;border-radius:16px;box-shadow:0 2px 12px rgba(0,0,0,.06);overflow:hidden;margin-bottom:20px;">
<div style="padding:20px 24px;display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:12px;">
<div>
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
<h1 style="font-size:20px;font-weight:700;color:#0f172a;margin:0;">{{ $pr->request_number }}</h1>
<span style="font-size:11px;font-weight:700;padding:4px 12px;border-radius:20px;
background:{{ $isDone ? '#dcfce7' : '#fffbeb' }};
color:{{ $isDone ? '#15803d' : '#92400e' }};">
{{ $stages->stageLabel($pr->stage) }}
</span>
</div>
<div style="font-size:13px;color:#64748b;margin-top:6px;display:flex;flex-wrap:wrap;gap:14px;">
@if($pr->project_name) <span>📁 {{ $pr->project_name }}</span> @endif
@if($pr->department) <span>🏢 {{ $pr->department }}</span> @endif
@if($pr->requested_by_name ?? $pr->requestedBy) <span>👤 {{ $pr->requested_by_name ?? $pr->requestedBy->name }}</span> @endif
@if($pr->date) <span>📅 {{ \Carbon\Carbon::parse($pr->date)->format('d M Y') }}</span> @endif
</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>
<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 →
</a>
</div>
</div>
<div style="height:4px;background:#f1f5f9;">
<div style="height:4px;background:{{ $isDone ? '#22c55e' : '#f59e0b' }};width:{{ $pct }}%;transition:width .4s ease;"></div>
</div>
</div>
{{-- Two-column layout --}}
<div style="display:grid;grid-template-columns:1fr 320px;gap:20px;align-items:start;">
{{-- Timeline --}}
<div style="background:#fff;border-radius:16px;box-shadow:0 2px 12px rgba(0,0,0,.06);padding:24px;">
<h2 style="font-size:14px;font-weight:700;color:#0f172a;margin:0 0 20px;">Pipeline Stages</h2>
<div style="display:flex;flex-direction:column;gap:0;">
@foreach($allStages as $i => $stage)
@php
$done = $i < $stageIdx;
$current = $i === $stageIdx;
$isLast = $i === $total - 1;
@endphp
<div style="display:flex;align-items:stretch;gap:16px;">
{{-- Dot + line --}}
<div style="display:flex;flex-direction:column;align-items:center;width:20px;flex-shrink:0;padding-top:2px;">
<div style="width:18px;height:18px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;
{{ $done ? 'background:#2563eb;' : ($current ? 'background:#f59e0b;box-shadow:0 0 0 5px #fde68a;' : 'background:#e2e8f0;') }}">
@if($done)
<svg width="9" height="9" viewBox="0 0 8 8" fill="none">
<path d="M1.5 4L3 5.5L6.5 2" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@endif
</div>
@if(!$isLast)
<div style="width:2px;flex:1;min-height:12px;margin:4px 0;background:{{ $done ? '#2563eb' : '#e2e8f0' }};"></div>
@endif
</div>
{{-- Stage content --}}
<div style="flex:1;min-width:0;padding-bottom:{{ $isLast ? '0' : '16' }}px;">
<div style="display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:8px;">
<div>
<div style="font-size:14px;font-weight:{{ $current ? '700' : ($done ? '600' : '400') }};
color:{{ $done ? '#1d4ed8' : ($current ? '#d97706' : '#94a3b8') }};">
{{ $stages->stageLabel($stage) }}
</div>
@if($done || $current)
<div style="font-size:12px;color:#94a3b8;margin-top:2px;">
@if($stage === 'draft')
Created by {{ $pr->requested_by_name ?? $pr->requestedBy?->name ?? '—' }}
@if($pr->created_at) · {{ $pr->created_at->format('d M Y') }} @endif
@elseif($stage === 'gm_approval')
@if($pr->signature)
Signed by {{ $pr->signature->signedBy?->name ?? '—' }} · {{ $pr->signature->signed_at?->format('d M Y') }}
@elseif($current)
Awaiting GM signature
@endif
@elseif($stage === 'rfq')
@if($pr->rfqInvitations->count())
{{ $pr->rfqInvitations->count() }} supplier(s) selected
@if($pendingInv->count()) · {{ $pendingInv->count() }} unsent @endif
@elseif($current)
Select suppliers to receive quote requests
@endif
@elseif($stage === 'quoting')
{{ $pr->supplierQuotes->count() }} quote(s) received · {{ $sentInv->count() }} invited
@elseif($stage === 'comparison')
{{ $pr->supplierQuotes->count() }} quote(s) ready to compare
@elseif($stage === 'lpo' && $pr->awardedQuote)
Awarded to {{ $pr->awardedQuote->supplier->name }}
@endif
</div>
@endif
</div>
{{-- Action buttons per stage --}}
@if($current)
@if($stage === 'draft')
<button type="button" onclick="openSignModal()"
class="action-btn" style="background:#7c3aed;color:#fff;">
@if($pr->signature)
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
View Signature
@else
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/></svg>
Sign
@endif
</button>
@elseif($stage === 'gm_approval')
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<button type="button" onclick="openSignModal()"
class="action-btn" style="background:#7c3aed;color:#fff;">
@if($pr->signature)
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
View Signature
@else
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/></svg>
Sign
@endif
</button>
<button type="button" onclick="openSupplierModal()"
class="action-btn" style="background:#2563eb;color:#fff;">
🏭 Select Suppliers
</button>
</div>
@elseif($stage === 'rfq')
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<button type="button" onclick="openSupplierModal()"
class="action-btn" style="background:#2563eb;color:#fff;">
+ Add Suppliers
</button>
@if($pendingInv->count() > 0)
<form action="{{ route('purchase.requests.rfq.send-all', $pr) }}" method="POST" style="display:inline;">
@csrf
<button type="submit" class="action-btn" style="background:#16a34a;color:#fff;">
📨 Send ({{ $pendingInv->count() }})
</button>
</form>
@endif
</div>
@elseif($stage === 'quoting')
<a href="{{ route('purchase.requests.quotes', $pr) }}"
class="action-btn" style="background:#f59e0b;color:#fff;">
View Quotes ({{ $pr->supplierQuotes->count() }}) →
</a>
@elseif($stage === 'comparison')
<a href="{{ route('purchase.requests.compare', $pr) }}"
class="action-btn" style="background:#f59e0b;color:#fff;">
Compare & Award →
</a>
@elseif($stage === 'lpo')
<a href="{{ route('purchase.orders.create') }}"
class="action-btn" style="background:#16a34a;color:#fff;">
Issue LPO →
</a>
@elseif($stage === 'receiving')
<a href="{{ route('purchase.grns.create') }}"
class="action-btn" style="background:#16a34a;color:#fff;">
Record GRN →
</a>
@elseif($stage === 'payment')
<a href="{{ route('purchase.payments.create') }}"
class="action-btn" style="background:#0f172a;color:#fff;">
Issue Payment →
</a>
@endif
@endif
</div>
</div>
</div>
@endforeach
</div>
</div>
{{-- Info sidebar --}}
<div style="display:flex;flex-direction:column;gap:16px;">
{{-- Request details --}}
<div style="background:#fff;border-radius:14px;box-shadow:0 2px 10px rgba(0,0,0,.05);padding:20px;">
<h3 style="font-size:13px;font-weight:700;color:#0f172a;margin:0 0 14px;">Request Details</h3>
<dl style="display:flex;flex-direction:column;gap:8px;font-size:13px;">
@if($pr->location)
<div style="display:flex;justify-content:space-between;gap:8px;">
<dt style="color:#64748b;flex-shrink:0;">Location</dt>
<dd style="color:#0f172a;font-weight:600;text-align:right;margin:0;">{{ $pr->location }}</dd>
</div>
@endif
@if($pr->required_date_text)
<div style="display:flex;justify-content:space-between;gap:8px;">
<dt style="color:#64748b;flex-shrink:0;">Required By</dt>
<dd style="color:#0f172a;font-weight:600;text-align:right;margin:0;">{{ $pr->required_date_text }}</dd>
</div>
@endif
@if($pr->verified_by_name)
<div style="display:flex;justify-content:space-between;gap:8px;">
<dt style="color:#64748b;flex-shrink:0;">Verified By</dt>
<dd style="color:#0f172a;font-weight:600;text-align:right;margin:0;">{{ $pr->verified_by_name }}</dd>
</div>
@endif
<div style="display:flex;justify-content:space-between;gap:8px;">
<dt style="color:#64748b;flex-shrink:0;">Status</dt>
<dd style="color:#0f172a;font-weight:600;text-align:right;margin:0;">{{ ucfirst($pr->status ?? '—') }}</dd>
</div>
</dl>
</div>
{{-- Selected suppliers (rfq stage+) --}}
@if($pr->rfqInvitations->isNotEmpty())
<div style="background:#fff;border-radius:14px;box-shadow:0 2px 10px rgba(0,0,0,.05);padding:20px;">
<h3 style="font-size:13px;font-weight:700;color:#0f172a;margin:0 0 14px;">
Suppliers ({{ $pr->rfqInvitations->count() }})
</h3>
<div style="display:flex;flex-direction:column;gap:6px;">
@foreach($pr->rfqInvitations as $inv)
@php
$statusMap = [
'pending' => ['bg'=>'#f1f5f9','fg'=>'#64748b','label'=>'Pending'],
'sent' => ['bg'=>'#dbeafe','fg'=>'#1d4ed8','label'=>'Sent'],
'opened' => ['bg'=>'#e0e7ff','fg'=>'#3730a3','label'=>'Opened'],
'submitted' => ['bg'=>'#dcfce7','fg'=>'#15803d','label'=>'Submitted'],
'declined' => ['bg'=>'#fee2e2','fg'=>'#991b1b','label'=>'Declined'],
];
$sc = $statusMap[$inv->status] ?? $statusMap['pending'];
@endphp
<div style="display:flex;justify-content:space-between;align-items:center;gap:8px;font-size:12px;padding:7px 0;border-bottom:1px solid #f8fafc;">
<div>
<div style="font-weight:600;color:#0f172a;">{{ $inv->supplier->name }}</div>
@if($inv->channel !== 'email')
<div style="font-size:10px;color:#94a3b8;">{{ ucfirst($inv->channel) }}</div>
@endif
</div>
<div style="display:flex;align-items:center;gap:6px;">
@if($inv->status === 'pending' && in_array($pr->stage, ['rfq','quoting']))
{{-- WhatsApp link for pending --}}
@if($inv->supplier->phone)
<a href="{{ app(\App\Services\RfqInvitationService::class)->whatsappLink($inv) }}" target="_blank"
style="font-size:10px;background:#dcfce7;color:#15803d;padding:2px 7px;border-radius:10px;text-decoration:none;font-weight:700;">WA</a>
@endif
@endif
<span style="background:{{ $sc['bg'] }};color:{{ $sc['fg'] }};padding:2px 8px;border-radius:12px;font-weight:700;font-size:10px;">
{{ $sc['label'] }}
</span>
</div>
</div>
@endforeach
</div>
</div>
@endif
{{-- Quotes summary --}}
@if($pr->supplierQuotes->isNotEmpty())
<div style="background:#fff;border-radius:14px;box-shadow:0 2px 10px rgba(0,0,0,.05);padding:20px;">
<h3 style="font-size:13px;font-weight:700;color:#0f172a;margin:0 0 14px;">
Quotes ({{ $pr->supplierQuotes->count() }})
</h3>
<div style="display:flex;flex-direction:column;gap:8px;">
@foreach($pr->supplierQuotes->sortBy('total_amount') as $quote)
<div style="display:flex;justify-content:space-between;align-items:center;font-size:12px;padding:8px 10px;border-radius:8px;
background:{{ $quote->is_awarded ? '#f0fdf4' : '#f8fafc' }};
border:1px solid {{ $quote->is_awarded ? '#bbf7d0' : '#f1f5f9' }};">
<div style="font-weight:600;color:#0f172a;">{{ $quote->supplier->name }}</div>
<div style="color:{{ $quote->is_awarded ? '#15803d' : '#374151' }};font-weight:700;">
{{ number_format($quote->total_amount, 2) }}
@if($quote->is_awarded)
<span style="font-size:10px;background:#22c55e;color:#fff;padding:1px 6px;border-radius:10px;margin-left:4px;">Awarded</span>
@endif
</div>
</div>
@endforeach
</div>
<a href="{{ route('purchase.requests.quotes', $pr) }}"
style="display:block;margin-top:12px;text-align:center;font-size:12px;color:#2563eb;text-decoration:none;font-weight:600;">
View All Quotes →
</a>
</div>
@endif
</div>
</div>
{{-- ============================================================
GM SIGNATURE MODAL (always in DOM so the button always works)
============================================================ --}}
<div id="sign-modal" class="pipe-modal" role="dialog" aria-modal="true" aria-labelledby="sign-modal-title">
<div style="background:#fff;border-radius:20px;width:100%;max-width:560px;display:flex;flex-direction:column;box-shadow:0 30px 60px rgba(0,0,0,.3);overflow:hidden;">
{{-- Header --}}
<div style="background:linear-gradient(135deg,#7c3aed,#4f46e5);padding:22px 24px;display:flex;align-items:flex-start;justify-content:space-between;">
<div>
<div style="font-size:11px;font-weight:600;color:rgba(255,255,255,.7);text-transform:uppercase;letter-spacing:.06em;">GM Digital Signature</div>
<div id="sign-modal-title" style="font-size:18px;font-weight:700;color:#fff;margin-top:4px;">{{ $pr->request_number }}</div>
@if($pr->project_name)
<div style="font-size:12px;color:rgba(255,255,255,.75);margin-top:2px;">{{ $pr->project_name }}</div>
@endif
</div>
<button onclick="closeSignModal()" aria-label="Close"
style="width:32px;height:32px;border-radius:8px;border:none;background:rgba(255,255,255,.2);cursor:pointer;font-size:18px;color:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0;">
×
</button>
</div>
{{-- Body --}}
<div style="padding:24px;">
@if($pr->signature)
{{-- Already signed — show the captured signature --}}
<div style="text-align:center;">
<img src="{{ $pr->signature->signature_image }}"
style="max-width:100%;border:1px solid #e2e8f0;border-radius:10px;background:#f8fafc;">
<div style="font-size:12px;color:#64748b;margin-top:10px;">
Signed by <strong>{{ $pr->signature->signedBy?->name ?? '—' }}</strong>
on {{ $pr->signature->signed_at?->format('d M Y, H:i') }}
</div>
</div>
<button type="button" onclick="closeSignModal()"
style="width:100%;margin-top:18px;padding:11px;border:1.5px solid #e2e8f0;border-radius:8px;font-size:13px;font-weight:600;color:#64748b;background:#f8fafc;cursor:pointer;">
Close
</button>
@else
{{-- Signature pad --}}
<p style="font-size:13px;color:#475569;margin:0 0 14px;">
Draw your signature below to approve this purchase request. This is recorded with your name, timestamp, and IP.
</p>
<div style="position:relative;">
<canvas id="sig-canvas" width="510" height="180"
style="width:100%;border:2px dashed #cbd5e1;border-radius:10px;cursor:crosshair;touch-action:none;background:#fafafa;display:block;">
</canvas>
<div id="sig-hint" style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none;">
<span style="font-size:13px;color:#cbd5e1;font-style:italic;">Sign here</span>
</div>
</div>
<form id="sig-form" method="POST" action="{{ route('purchase.requests.sign.store', $pr) }}">
@csrf
<input type="hidden" name="signature_image" id="sig-data">
</form>
<div style="display:flex;gap:10px;margin-top:14px;">
<button type="button" onclick="clearSigCanvas()"
style="flex:1;padding:11px;border:1.5px solid #e2e8f0;border-radius:8px;font-size:13px;font-weight:600;color:#64748b;background:#f8fafc;cursor:pointer;">
Clear
</button>
<button type="button" onclick="submitSignature()"
style="flex:2;padding:11px;border:none;border-radius:8px;font-size:13px;font-weight:700;color:#fff;background:linear-gradient(135deg,#7c3aed,#4f46e5);cursor:pointer;">
Confirm Signature →
</button>
</div>
@endif
</div>
</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;">
{{-- 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 &amp; Continue →
</button>
</div>
</div>
</div>
</div>
<script>
// ---- Signature modal ----
(function () {
var canvas, ctx, hint, drawing = false, hasMark = false;
function initCanvas() {
canvas = document.getElementById('sig-canvas');
if (!canvas || canvas._bound) return;
canvas._bound = true;
ctx = canvas.getContext('2d');
hint = document.getElementById('sig-hint');
ctx.lineWidth = 2.5; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.strokeStyle = '#1e293b';
function pos(e) {
var r = canvas.getBoundingClientRect();
var src = e.touches ? e.touches[0] : e;
return { x: (src.clientX - r.left) * (canvas.width / r.width),
y: (src.clientY - r.top) * (canvas.height / r.height) };
}
function start(e) { e.preventDefault(); drawing = true; var p = pos(e); ctx.beginPath(); ctx.moveTo(p.x, p.y); if (!hasMark) { hasMark = true; if (hint) hint.style.display = 'none'; } }
function move(e) { e.preventDefault(); if (!drawing) return; var p = pos(e); ctx.lineTo(p.x, p.y); ctx.stroke(); ctx.beginPath(); ctx.moveTo(p.x, p.y); }
function end() { drawing = false; }
canvas.addEventListener('mousedown', start);
canvas.addEventListener('mousemove', move);
canvas.addEventListener('mouseup', end);
canvas.addEventListener('mouseleave', end);
canvas.addEventListener('touchstart', start, { passive: false });
canvas.addEventListener('touchmove', move, { passive: false });
canvas.addEventListener('touchend', end);
}
window.openSignModal = function () {
document.getElementById('sign-modal').classList.add('open');
initCanvas();
};
window.closeSignModal = function () {
document.getElementById('sign-modal').classList.remove('open');
};
window.clearSigCanvas = function () {
if (!canvas) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
hasMark = false;
if (hint) hint.style.display = 'flex';
};
window.submitSignature = function () {
if (!hasMark) { showToast('Please draw your signature first.', 'warn'); return; }
document.getElementById('sig-data').value = canvas.toDataURL('image/png');
document.getElementById('sig-form').submit();
};
document.getElementById('sign-modal').addEventListener('click', function (e) {
if (e.target === this) closeSignModal();
});
}());
// ---- 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
@@ -0,0 +1,179 @@
@extends('layouts.app')
@section('title', 'Compare Quotes — ' . $request->request_number)
@section('content')
<div style="max-width:1100px;margin:0 auto;">
<div style="background:#fff;border-radius:16px;box-shadow:0 4px 24px rgba(0,0,0,.08);overflow:hidden;">
<div style="background:linear-gradient(135deg,#f59e0b,#d97706);padding:24px 28px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;">
<div>
<div style="font-size:11px;font-weight:600;color:rgba(255,255,255,.7);text-transform:uppercase;letter-spacing:.06em;">Quote Comparison</div>
<div style="font-size:20px;font-weight:700;color:#fff;margin-top:4px;">{{ $request->request_number }}</div>
@if($request->project_name)
<div style="font-size:13px;color:rgba(255,255,255,.8);margin-top:2px;">{{ $request->project_name }}</div>
@endif
</div>
<a href="{{ route('purchase.requests.quotes', $request) }}"
style="padding:9px 18px;background:rgba(255,255,255,.15);color:#fff;border:1.5px solid rgba(255,255,255,.35);border-radius:8px;font-size:13px;font-weight:600;text-decoration:none;">
← Back to Quotes
</a>
</div>
<div style="padding:24px;overflow-x:auto;">
@if($quotes->isEmpty())
<p style="text-align:center;color:#94a3b8;padding:60px 0;">No quotes submitted yet.</p>
@else
@php
$awardedQuote = $request->awardedQuote;
@endphp
<table style="width:100%;border-collapse:collapse;font-size:13px;min-width:500px;">
<thead>
<tr>
<th style="padding:10px 14px;text-align:left;background:#f8fafc;font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;min-width:200px;border-radius:8px 0 0 0;">Item</th>
@foreach($quotes as $quote)
<th style="padding:10px 14px;text-align:center;background:#f8fafc;font-size:12px;font-weight:700;color:{{ $quote->is_awarded ? '#15803d' : '#0f172a' }};min-width:150px;
{{ $quote->is_awarded ? 'background:#f0fdf4;' : '' }}">
{{ $quote->supplier->name }}
@if($quote->is_awarded)
<div style="font-size:10px;font-weight:700;color:#15803d;margin-top:2px;">✓ AWARDED</div>
@endif
</th>
@endforeach
</tr>
</thead>
<tbody>
{{-- Per-item rows --}}
@foreach($items as $i => $reqItem)
@php
$rowPrices = $quotes->map(fn($q) => optional($q->items->get($i))->unit_price)->filter()->values();
$minPrice = $rowPrices->count() ? $rowPrices->min() : null;
@endphp
<tr>
<td style="padding:10px 14px;border-bottom:1px solid #f1f5f9;font-weight:500;color:#0f172a;">
{{ $reqItem->description }}
<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
<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>
@else
<span style="color:#e2e8f0;">—</span>
@endif
</td>
@endforeach
</tr>
@endforeach
{{-- Grand totals --}}
@php $minTotal = $quotes->min('total_amount'); @endphp
<tr style="background:#f8fafc;font-weight:700;">
<td style="padding:12px 14px;border-top:2px solid #e2e8f0;">Grand Total</td>
@foreach($quotes as $quote)
@php $isCheapest = (float)$quote->total_amount === (float)$minTotal && $quotes->count() > 1; @endphp
<td style="padding:12px 14px;text-align:center;border-top:2px solid #e2e8f0;font-size:15px;color:{{ $isCheapest ? '#15803d' : '#0f172a' }};background:{{ $isCheapest ? '#f0fdf4' : '' }};">
BD {{ number_format($quote->total_amount, 3) }}
</td>
@endforeach
</tr>
{{-- Lead time --}}
<tr>
<td style="padding:10px 14px;color:#64748b;font-size:12px;border-top:1px solid #f1f5f9;">Lead Time</td>
@foreach($quotes as $quote)
<td style="padding:10px 14px;text-align:center;font-size:12px;color:#64748b;border-top:1px solid #f1f5f9;">
{{ $quote->lead_time_days !== null ? $quote->lead_time_days.' days' : '—' }}
</td>
@endforeach
</tr>
{{-- Payment terms --}}
<tr>
<td style="padding:10px 14px;color:#64748b;font-size:12px;">Payment Terms</td>
@foreach($quotes as $quote)
<td style="padding:10px 14px;text-align:center;font-size:12px;color:#64748b;">
{{ $quote->payment_terms ?: '—' }}
</td>
@endforeach
</tr>
{{-- Award buttons --}}
@if(!$awardedQuote)
<tr style="background:#fffbeb;">
<td style="padding:12px 14px;font-size:12px;font-weight:700;color:#92400e;border-top:2px solid #fde68a;">Award to:</td>
@foreach($quotes as $quote)
<td style="padding:12px 14px;text-align:center;border-top:2px solid #fde68a;">
<button type="button"
onclick="openAwardModal({{ $quote->id }}, '{{ addslashes($quote->supplier->name) }}', 'BD {{ number_format($quote->total_amount, 3) }}')"
style="padding:8px 16px;background:#f59e0b;color:#fff;border:none;border-radius:7px;font-size:12px;font-weight:700;cursor:pointer;">
Award →
</button>
</td>
@endforeach
</tr>
@else
<tr style="background:#f0fdf4;">
<td colspan="{{ $quotes->count() + 1 }}" style="padding:14px;text-align:center;font-size:13px;font-weight:700;color:#15803d;border-top:2px solid #bbf7d0;">
✓ Awarded to {{ $awardedQuote->supplier->name }} on {{ $awardedQuote->awarded_at?->format('d M Y') }}
</td>
</tr>
@endif
</tbody>
</table>
@endif
</div>
</div>
</div>
{{-- Award modal --}}
<div id="award-modal" style="display:none;position:fixed;inset:0;background:rgba(15,23,42,.6);backdrop-filter:blur(4px);z-index:9999;align-items:center;justify-content:center;padding:20px;">
<div style="background:#fff;border-radius:18px;width:100%;max-width:440px;overflow:hidden;box-shadow:0 24px 60px rgba(0,0,0,.25);">
<div style="height:3px;background:linear-gradient(90deg,#f59e0b,#fbbf24);"></div>
<div style="padding:28px 28px 0;">
<div style="font-size:17px;font-weight:700;color:#0f172a;margin-bottom:4px;">Award Quote</div>
<div style="font-size:13px;color:#64748b;margin-bottom:4px;" id="award-supplier-line"></div>
<div style="font-size:13px;color:#64748b;margin-bottom:20px;" id="award-amount-line"></div>
<form id="award-form" method="POST">
@csrf
<label style="display:block;font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;margin-bottom:6px;">Reason for selection (required)</label>
<textarea name="award_reason" rows="3" required
style="width:100%;padding:9px 12px;border:1.5px solid #e2e8f0;border-radius:8px;font-size:13px;margin-bottom:16px;resize:vertical;"
placeholder="e.g. Lowest price, reliable supplier, suitable lead time…"></textarea>
<div style="display:flex;gap:10px;padding-bottom:24px;">
<button type="button" onclick="closeAwardModal()"
style="flex:1;padding:11px;border:1.5px solid #e2e8f0;border-radius:9px;font-size:13px;font-weight:600;background:#f8fafc;color:#475569;cursor:pointer;">
Cancel
</button>
<button type="submit"
style="flex:2;padding:11px;background:#f59e0b;color:#fff;border:none;border-radius:9px;font-size:13px;font-weight:700;cursor:pointer;">
Confirm Award
</button>
</div>
</form>
</div>
</div>
</div>
<script>
function openAwardModal(quoteId, supplierName, amount) {
document.getElementById('award-form').action = '/purchase/requests/{{ $request->id }}/quotes/' + quoteId + '/award';
document.getElementById('award-supplier-line').textContent = 'Supplier: ' + supplierName;
document.getElementById('award-amount-line').textContent = 'Total: ' + amount;
document.getElementById('award-modal').style.display = 'flex';
}
function closeAwardModal() {
document.getElementById('award-modal').style.display = 'none';
}
document.getElementById('award-modal').addEventListener('click', function(e) {
if (e.target === this) closeAwardModal();
});
</script>
@endsection
@@ -0,0 +1,104 @@
@extends('layouts.app')
@section('title', 'Quotes — ' . $request->request_number)
@section('content')
<div style="max-width:760px;margin:0 auto;">
<div style="background:#fff;border-radius:16px;box-shadow:0 4px 24px rgba(0,0,0,.08);overflow:hidden;">
<div style="background:linear-gradient(135deg,#f59e0b,#d97706);padding:24px 28px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;">
<div>
<div style="font-size:11px;font-weight:600;color:rgba(255,255,255,.7);text-transform:uppercase;letter-spacing:.06em;">Supplier Quotes</div>
<div style="font-size:20px;font-weight:700;color:#fff;margin-top:4px;">{{ $request->request_number }}</div>
</div>
@if($quotes->count() >= 1)
<a href="{{ route('purchase.requests.compare', $request) }}"
style="padding:10px 20px;background:rgba(255,255,255,.2);color:#fff;border:1.5px solid rgba(255,255,255,.4);border-radius:8px;font-size:13px;font-weight:700;text-decoration:none;">
Compare All →
</a>
@endif
</div>
<div style="padding:24px;">
@if($quotes->isEmpty())
<div style="text-align:center;padding:60px 0;color:#94a3b8;">
<div style="font-size:40px;margin-bottom:12px;">📬</div>
<div style="font-size:15px;font-weight:600;margin-bottom:4px;">No quotes yet</div>
<div style="font-size:13px;">Waiting for suppliers to submit their quotes via the private links.</div>
</div>
@else
<div style="display:flex;flex-direction:column;gap:12px;">
@foreach($quotes->sortBy('total_amount') as $quote)
@php $isLowest = $quote->total_amount == $quotes->min('total_amount') && $quotes->count() > 1; @endphp
<div style="border:1.5px solid {{ $quote->is_awarded ? '#bbf7d0' : ($isLowest ? '#bfdbfe' : '#e2e8f0') }};border-radius:12px;padding:18px 20px;background:{{ $quote->is_awarded ? '#f0fdf4' : ($isLowest ? '#eff6ff' : '#fff') }};">
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;">
<div>
<div style="font-size:14px;font-weight:700;color:#0f172a;">{{ $quote->supplier->name }}</div>
<div style="font-size:12px;color:#64748b;margin-top:3px;">
Lead time: {{ $quote->lead_time_days !== null ? $quote->lead_time_days.' days' : '—' }}
@if($quote->payment_terms) · {{ $quote->payment_terms }} @endif
</div>
<div style="font-size:11px;color:#94a3b8;margin-top:2px;">Submitted {{ $quote->submitted_at->format('d M Y, H:i') }}</div>
</div>
<div style="text-align:right;flex-shrink:0;">
<div style="font-size:18px;font-weight:700;color:{{ $isLowest ? '#2563eb' : '#0f172a' }};">BD {{ number_format($quote->total_amount, 3) }}</div>
@if($isLowest && $quotes->count() > 1)
<div style="font-size:10px;font-weight:700;color:#2563eb;margin-top:2px;">LOWEST PRICE</div>
@endif
@if($quote->is_awarded)
<div style="font-size:11px;font-weight:700;color:#15803d;margin-top:4px;">✓ Awarded</div>
@endif
</div>
</div>
@if($quote->notes)
<div style="margin-top:10px;padding-top:10px;border-top:1px dashed #e2e8f0;font-size:12px;color:#64748b;">
{{ $quote->notes }}
</div>
@endif
{{-- Item breakdown --}}
<details style="margin-top:10px;">
<summary style="font-size:12px;font-weight:600;color:#2563eb;cursor:pointer;">View item breakdown ({{ $quote->items->count() }} items)</summary>
<div style="margin-top:8px;overflow-x:auto;">
<table style="width:100%;border-collapse:collapse;font-size:12px;">
<thead>
<tr style="background:#f8fafc;">
<th style="padding:6px 10px;text-align:left;color:#64748b;">Item</th>
<th style="padding:6px 10px;text-align:right;color:#64748b;">Qty</th>
<th style="padding:6px 10px;text-align:right;color:#64748b;">Unit Price</th>
<th style="padding:6px 10px;text-align:right;color:#64748b;">Total</th>
</tr>
</thead>
<tbody>
@foreach($quote->items as $qi)
<tr style="border-top:1px solid #f1f5f9;">
<td style="padding:6px 10px;">{{ $qi->description }}</td>
<td style="padding:6px 10px;text-align:right;">{{ $qi->quantity }}</td>
<td style="padding:6px 10px;text-align:right;">BD {{ number_format($qi->unit_price, 3) }}</td>
<td style="padding:6px 10px;text-align:right;font-weight:600;">BD {{ number_format($qi->total_price, 3) }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</details>
</div>
@endforeach
</div>
@if(!$request->awardedQuote && $quotes->count() >= 1)
<div style="margin-top:20px;padding:14px;background:#fffbeb;border:1px solid #fde68a;border-radius:10px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;">
<div style="font-size:13px;color:#92400e;font-weight:500;">Ready to pick a winner?</div>
<a href="{{ route('purchase.requests.compare', $request) }}"
style="padding:9px 20px;background:#f59e0b;color:#fff;border-radius:8px;font-size:13px;font-weight:700;text-decoration:none;">
Open Comparison Table →
</a>
</div>
@endif
@endif
</div>
</div>
</div>
@endsection
@@ -0,0 +1,186 @@
@extends('layouts.app')
@section('title', 'New Material Purchase Request')
@section('content')
<div class="mb-6">
<h1 class="page-title">New Material Purchase Request</h1>
<p class="page-subtitle"><a href="{{ route('purchase.requests.index') }}" class="text-blue-600 hover:underline">Purchase Requests</a> / New</p>
</div>
@if($errors->any())
<div class="mb-4 px-4 py-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
<ul class="list-disc list-inside space-y-1">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('purchase.requests.store') }}" method="POST" id="mpr-form">
@csrf
{{-- Header Details --}}
<div class="card card-body mb-6">
<h2 class="text-base font-semibold text-gray-700 mb-4">Project / Department Details</h2>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
<div>
<label class="form-label">Date <span class="text-red-500">*</span></label>
<input type="date" name="date" value="{{ old('date', date('Y-m-d')) }}" required class="form-input">
</div>
<div>
<label class="form-label">Project / Site Name <span class="text-red-500">*</span></label>
<input type="text" name="project_name" value="{{ old('project_name') }}" required class="form-input" placeholder="e.g. Steel Tech Co WLL">
</div>
<div>
<label class="form-label">Requested By <span class="text-red-500">*</span></label>
<input type="text" name="requested_by_name" value="{{ old('requested_by_name') }}" required class="form-input" placeholder="Person's name">
</div>
<div>
<label class="form-label">Required Date / Urgency</label>
<input type="text" name="required_date_text" value="{{ old('required_date_text') }}" class="form-input" placeholder="e.g. Urgent, or 2026-06-01">
</div>
<div>
<label class="form-label">Location / Site</label>
<input type="text" name="location" value="{{ old('location') }}" class="form-input" placeholder="e.g. BaFa - Hidd">
</div>
<div>
<label class="form-label">Department</label>
<input type="text" name="department" value="{{ old('department') }}" class="form-input" placeholder="e.g. Operations, Production">
</div>
</div>
</div>
{{-- Material Items --}}
<div class="card card-body mb-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-base font-semibold text-gray-700">Material Details</h2>
<button type="button" id="add-row" class="btn-primary btn-sm">+ Add Item</button>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm border-collapse" id="items-table">
<thead>
<tr class="bg-gray-50 text-gray-600 uppercase text-xs">
<th class="border border-gray-300 px-3 py-2 text-left w-10">S.No</th>
<th class="border border-gray-300 px-3 py-2 text-left">Description of Material <span class="text-red-500">*</span></th>
<th class="border border-gray-300 px-3 py-2 text-left w-24">Unit</th>
<th class="border border-gray-300 px-3 py-2 text-left w-28">Qty Required <span class="text-red-500">*</span></th>
<th class="border border-gray-300 px-3 py-2 text-left w-40">Purpose / Use</th>
<th class="border border-gray-300 px-3 py-2 text-left w-36">Required Date</th>
<th class="border border-gray-300 px-2 py-2 w-10"></th>
</tr>
</thead>
<tbody id="items-body">
@php $oldItems = old('items', [[]]); @endphp
@foreach($oldItems as $idx => $oldItem)
<tr class="item-row">
<td class="border border-gray-300 px-3 py-2 text-center text-gray-500 row-num">{{ $idx + 1 }}</td>
<td class="border border-gray-300 px-2 py-1">
<input type="text" name="items[{{ $idx }}][description]" value="{{ $oldItem['description'] ?? '' }}"
class="w-full border-0 focus:ring-0 text-sm" placeholder="Material description" required>
</td>
<td class="border border-gray-300 px-2 py-1">
<input type="text" name="items[{{ $idx }}][unit]" value="{{ $oldItem['unit'] ?? '' }}"
class="w-full border-0 focus:ring-0 text-sm" placeholder="PCS, NOS, KG…">
</td>
<td class="border border-gray-300 px-2 py-1">
<input type="number" name="items[{{ $idx }}][quantity_required]" value="{{ $oldItem['quantity_required'] ?? '' }}"
min="0.01" step="0.01" class="w-full border-0 focus:ring-0 text-sm" placeholder="0" required>
</td>
<td class="border border-gray-300 px-2 py-1">
<input type="text" name="items[{{ $idx }}][purpose_use]" value="{{ $oldItem['purpose_use'] ?? '' }}"
class="w-full border-0 focus:ring-0 text-sm" placeholder="Purpose…">
</td>
<td class="border border-gray-300 px-2 py-1">
<input type="date" name="items[{{ $idx }}][required_date]" value="{{ $oldItem['required_date'] ?? '' }}"
class="w-full border-0 focus:ring-0 text-sm">
</td>
<td class="border border-gray-300 px-2 py-1 text-center">
<button type="button" class="remove-row text-red-500 hover:text-red-700 font-bold text-lg leading-none">×</button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
{{-- Remarks --}}
<div class="card card-body mb-6">
<label class="form-label">Remarks / Notes</label>
<textarea name="remarks" rows="2" class="form-textarea">{{ old('remarks') }}</textarea>
</div>
<div class="flex items-center gap-3">
<button type="submit" class="btn-primary">Submit Request</button>
<a href="{{ route('purchase.requests.index') }}" class="btn-secondary">Cancel</a>
</div>
</form>
<script>
(function () {
let rowIndex = {{ count($oldItems ?? [[]]) }};
function renumber() {
document.querySelectorAll('#items-body .row-num').forEach((el, i) => {
el.textContent = i + 1;
});
}
function newRow() {
const idx = rowIndex++;
const tr = document.createElement('tr');
tr.className = 'item-row';
tr.innerHTML = `
<td class="border border-gray-300 px-3 py-2 text-center text-gray-500 row-num"></td>
<td class="border border-gray-300 px-2 py-1">
<input type="text" name="items[${idx}][description]" class="w-full border-0 focus:ring-0 text-sm" placeholder="Material description" required>
</td>
<td class="border border-gray-300 px-2 py-1">
<input type="text" name="items[${idx}][unit]" class="w-full border-0 focus:ring-0 text-sm" placeholder="PCS, NOS, KG…">
</td>
<td class="border border-gray-300 px-2 py-1">
<input type="number" name="items[${idx}][quantity_required]" min="0.01" step="0.01" class="w-full border-0 focus:ring-0 text-sm" placeholder="0" required>
</td>
<td class="border border-gray-300 px-2 py-1">
<input type="text" name="items[${idx}][purpose_use]" class="w-full border-0 focus:ring-0 text-sm" placeholder="Purpose…">
</td>
<td class="border border-gray-300 px-2 py-1">
<input type="date" name="items[${idx}][required_date]" class="w-full border-0 focus:ring-0 text-sm">
</td>
<td class="border border-gray-300 px-2 py-1 text-center">
<button type="button" class="remove-row text-red-500 hover:text-red-700 font-bold text-lg leading-none">×</button>
</td>
`;
return tr;
}
document.getElementById('add-row').addEventListener('click', function () {
const tbody = document.getElementById('items-body');
tbody.appendChild(newRow());
renumber();
});
document.getElementById('items-body').addEventListener('click', function (e) {
if (e.target.classList.contains('remove-row')) {
const rows = document.querySelectorAll('#items-body .item-row');
if (rows.length > 1) {
e.target.closest('tr').remove();
renumber();
}
}
});
renumber();
})();
</script>
@endsection
@@ -0,0 +1,212 @@
@extends('layouts.app')
@section('title', 'Edit Purchase Request')
@section('content')
<div class="mb-6">
<h1 class="page-title">Edit Purchase Request</h1>
<p class="page-subtitle">
<a href="{{ route('purchase.requests.index') }}" class="text-blue-600 hover:underline">Purchase Requests</a> /
<a href="{{ route('purchase.pipeline.show', $purchaseRequest) }}" class="text-blue-600 hover:underline">{{ $purchaseRequest->request_number }}</a> /
Edit
</p>
</div>
@if($errors->any())
<div class="mb-4 px-4 py-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
<ul class="list-disc list-inside space-y-1">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('purchase.requests.update', $purchaseRequest) }}" method="POST" id="mpr-form">
@csrf
@method('PUT')
{{-- Header Details --}}
<div class="card card-body mb-6">
<h2 class="text-base font-semibold text-gray-700 mb-4">Project / Department Details</h2>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
<div>
<label class="form-label">Date <span class="text-red-500">*</span></label>
<input type="date" name="date"
value="{{ old('date', $purchaseRequest->date ? \Carbon\Carbon::parse($purchaseRequest->date)->format('Y-m-d') : '') }}"
required class="form-input">
</div>
<div>
<label class="form-label">Project / Site Name <span class="text-red-500">*</span></label>
<input type="text" name="project_name"
value="{{ old('project_name', $purchaseRequest->project_name) }}"
required class="form-input" placeholder="e.g. Steel Tech Co WLL">
</div>
<div>
<label class="form-label">Requested By <span class="text-red-500">*</span></label>
<input type="text" name="requested_by_name"
value="{{ old('requested_by_name', $purchaseRequest->requested_by_name) }}"
required class="form-input" placeholder="Person's name">
</div>
<div>
<label class="form-label">Required Date / Urgency</label>
<input type="text" name="required_date_text"
value="{{ old('required_date_text', $purchaseRequest->required_date_text) }}"
class="form-input" placeholder="e.g. Urgent, or 2026-06-01">
</div>
<div>
<label class="form-label">Location / Site</label>
<input type="text" name="location"
value="{{ old('location', $purchaseRequest->location) }}"
class="form-input" placeholder="e.g. BaFa - Hidd">
</div>
<div>
<label class="form-label">Department</label>
<input type="text" name="department"
value="{{ old('department', $purchaseRequest->department) }}"
class="form-input" placeholder="e.g. Operations, Production">
</div>
</div>
</div>
{{-- Material Items --}}
<div class="card card-body mb-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-base font-semibold text-gray-700">Material Details</h2>
<button type="button" id="add-row" class="btn-primary btn-sm">+ Add Item</button>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm border-collapse" id="items-table">
<thead>
<tr class="bg-gray-50 text-gray-600 uppercase text-xs">
<th class="border border-gray-300 px-3 py-2 text-left w-10">S.No</th>
<th class="border border-gray-300 px-3 py-2 text-left">Description of Material <span class="text-red-500">*</span></th>
<th class="border border-gray-300 px-3 py-2 text-left w-24">Unit</th>
<th class="border border-gray-300 px-3 py-2 text-left w-28">Qty Required <span class="text-red-500">*</span></th>
<th class="border border-gray-300 px-3 py-2 text-left w-40">Purpose / Use</th>
<th class="border border-gray-300 px-3 py-2 text-left w-36">Required Date</th>
<th class="border border-gray-300 px-2 py-2 w-10"></th>
</tr>
</thead>
<tbody id="items-body">
@php
$existingItems = old('items')
? collect(old('items'))->map(fn($i) => (object)$i)
: $purchaseRequest->items;
@endphp
@foreach($existingItems as $idx => $item)
<tr class="item-row">
<td class="border border-gray-300 px-3 py-2 text-center text-gray-500 row-num">{{ $idx + 1 }}</td>
<td class="border border-gray-300 px-2 py-1">
<input type="text" name="items[{{ $idx }}][description]"
value="{{ is_array($item) ? ($item['description'] ?? '') : $item->description }}"
class="w-full border-0 focus:ring-0 text-sm" placeholder="Material description" required>
</td>
<td class="border border-gray-300 px-2 py-1">
<input type="text" name="items[{{ $idx }}][unit]"
value="{{ is_array($item) ? ($item['unit'] ?? '') : $item->unit }}"
class="w-full border-0 focus:ring-0 text-sm" placeholder="PCS, NOS, KG…">
</td>
<td class="border border-gray-300 px-2 py-1">
<input type="number" name="items[{{ $idx }}][quantity_required]"
value="{{ is_array($item) ? ($item['quantity_required'] ?? '') : $item->quantity_required }}"
min="0.01" step="0.01" class="w-full border-0 focus:ring-0 text-sm" placeholder="0" required>
</td>
<td class="border border-gray-300 px-2 py-1">
<input type="text" name="items[{{ $idx }}][purpose_use]"
value="{{ is_array($item) ? ($item['purpose_use'] ?? '') : $item->purpose_use }}"
class="w-full border-0 focus:ring-0 text-sm" placeholder="Purpose…">
</td>
<td class="border border-gray-300 px-2 py-1">
<input type="date" name="items[{{ $idx }}][required_date]"
value="{{ is_array($item) ? ($item['required_date'] ?? '') : ($item->required_date ? \Carbon\Carbon::parse($item->required_date)->format('Y-m-d') : '') }}"
class="w-full border-0 focus:ring-0 text-sm">
</td>
<td class="border border-gray-300 px-2 py-1 text-center">
<button type="button" class="remove-row text-red-500 hover:text-red-700 font-bold text-lg leading-none">×</button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
{{-- Remarks --}}
<div class="card card-body mb-6">
<label class="form-label">Remarks / Notes</label>
<textarea name="remarks" rows="2" class="form-textarea">{{ old('remarks', $purchaseRequest->remarks) }}</textarea>
</div>
<div class="flex items-center gap-3">
<button type="submit" class="btn-primary">Save Changes</button>
<a href="{{ route('purchase.pipeline.show', $purchaseRequest) }}" class="btn-secondary">Cancel</a>
</div>
</form>
<script>
(function () {
let rowIndex = {{ $existingItems->count() }};
function renumber() {
document.querySelectorAll('#items-body .row-num').forEach((el, i) => {
el.textContent = i + 1;
});
}
function newRow() {
const idx = rowIndex++;
const tr = document.createElement('tr');
tr.className = 'item-row';
tr.innerHTML = `
<td class="border border-gray-300 px-3 py-2 text-center text-gray-500 row-num"></td>
<td class="border border-gray-300 px-2 py-1">
<input type="text" name="items[${idx}][description]" class="w-full border-0 focus:ring-0 text-sm" placeholder="Material description" required>
</td>
<td class="border border-gray-300 px-2 py-1">
<input type="text" name="items[${idx}][unit]" class="w-full border-0 focus:ring-0 text-sm" placeholder="PCS, NOS, KG…">
</td>
<td class="border border-gray-300 px-2 py-1">
<input type="number" name="items[${idx}][quantity_required]" min="0.01" step="0.01" class="w-full border-0 focus:ring-0 text-sm" placeholder="0" required>
</td>
<td class="border border-gray-300 px-2 py-1">
<input type="text" name="items[${idx}][purpose_use]" class="w-full border-0 focus:ring-0 text-sm" placeholder="Purpose…">
</td>
<td class="border border-gray-300 px-2 py-1">
<input type="date" name="items[${idx}][required_date]" class="w-full border-0 focus:ring-0 text-sm">
</td>
<td class="border border-gray-300 px-2 py-1 text-center">
<button type="button" class="remove-row text-red-500 hover:text-red-700 font-bold text-lg leading-none">×</button>
</td>
`;
return tr;
}
document.getElementById('add-row').addEventListener('click', function () {
const tbody = document.getElementById('items-body');
tbody.appendChild(newRow());
renumber();
});
document.getElementById('items-body').addEventListener('click', function (e) {
if (e.target.classList.contains('remove-row')) {
const rows = document.querySelectorAll('#items-body .item-row');
if (rows.length > 1) {
e.target.closest('tr').remove();
renumber();
}
}
});
renumber();
})();
</script>
@endsection
@@ -0,0 +1,87 @@
@extends('layouts.app')
@section('title', 'Purchase Requests')
@section('content')
<div class="page-header">
<div>
<h1 class="page-title">Purchase Requests</h1>
<p class="page-subtitle">Manage internal purchase requests</p>
</div>
<a href="{{ route('purchase.requests.create') }}" class="btn-primary">
+ New Request
</a>
</div>
<div class="table-wrapper overflow-x-auto">
<table class="table-base">
<thead>
<tr>
<th>Request #</th>
<th>Date</th>
<th>Department</th>
<th>Item</th>
<th>Quantity</th>
<th>Status</th>
<th>Requested By</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@forelse($requests as $request)
<tr>
<td class="font-mono text-gray-700">{{ $request->request_number ?? '#' . $request->id }}</td>
<td>{{ $request->date ? $request->date->format('d M Y') : '' }}</td>
<td>{{ $request->department }}</td>
<td class="text-gray-800">{{ $request->item->item_name ?? $request->item_name }}</td>
<td>{{ $request->quantity }} {{ $request->unit_of_measure }}</td>
<td>
@php
$badgeClass = match($request->status) {
'pending' => 'badge-yellow',
'approved' => 'badge-green',
'rejected' => 'badge-red',
'ordered' => 'badge-blue',
default => 'badge-gray',
};
@endphp
<span class="{{ $badgeClass }}">{{ ucfirst($request->status) }}</span>
</td>
<td>{{ $request->requestedBy->name ?? $request->requested_by }}</td>
<td>
<div class="flex items-center gap-2 flex-wrap">
@if($request->status === 'pending')
<form action="{{ route('purchase.requests.approve', $request) }}" method="POST">
@csrf
@method('PATCH')
<button type="submit" class="btn-success btn-sm">Approve</button>
</form>
<form action="{{ route('purchase.requests.reject', $request) }}" method="POST">
@csrf
@method('PATCH')
<button type="submit" class="btn-danger btn-sm">Reject</button>
</form>
@endif
<a href="{{ route('purchase.requests.edit', $request) }}" class="btn-secondary btn-sm">Edit</a>
<form action="{{ route('purchase.requests.destroy', $request) }}" method="POST"
onsubmit="confirmDelete(this,'Delete this purchase request?','This request will be permanently removed.'); return false;">
@csrf
@method('DELETE')
<button type="submit" class="btn-danger btn-sm">Delete</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="8" class="px-4 py-8 text-center text-gray-400">No purchase requests found.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($requests->hasPages())
<div class="mt-4">{{ $requests->links() }}</div>
@endif
@endsection
@@ -0,0 +1,133 @@
@extends('layouts.app')
@section('title', 'MPR ' . $purchaseRequest->request_number)
@section('content')
<div class="mb-6 flex items-start justify-between flex-wrap gap-3">
<div>
<h1 class="page-title">{{ $purchaseRequest->request_number }}</h1>
<p class="page-subtitle"><a href="{{ route('purchase.requests.index') }}" class="text-blue-600 hover:underline">Purchase Requests</a> / {{ $purchaseRequest->request_number }}</p>
</div>
<div class="flex items-center gap-2 flex-wrap">
<a href="{{ route('purchase.requests.print', $purchaseRequest) }}" target="_blank"
class="btn-primary">Print MPR Form</a>
@if($purchaseRequest->status === 'pending')
<a href="{{ route('purchase.requests.edit', $purchaseRequest) }}" class="btn-secondary">Edit</a>
<form action="{{ route('purchase.requests.approve', $purchaseRequest) }}" method="POST">
@csrf @method('PATCH')
<button type="submit" class="btn-success">Approve</button>
</form>
<form action="{{ route('purchase.requests.reject', $purchaseRequest) }}" method="POST">
@csrf @method('PATCH')
<button type="submit" class="btn-danger">Reject</button>
</form>
@endif
</div>
</div>
{{-- Status Badge --}}
<div class="mb-4">
@php
$badgeClass = match($purchaseRequest->status) {
'pending' => 'badge-yellow',
'approved' => 'badge-green',
'rejected' => 'badge-red',
'ordered' => 'badge-blue',
default => 'badge-gray',
};
@endphp
<span class="text-sm font-medium">Status:</span>
<span class="{{ $badgeClass }} ml-1">{{ ucfirst($purchaseRequest->status) }}</span>
</div>
{{-- Header Info --}}
<div class="card card-body mb-6">
<h2 class="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-4">Project / Department Details</h2>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-4 text-sm">
<div>
<p class="text-gray-500">MPR Number</p>
<p class="font-semibold text-gray-800">{{ $purchaseRequest->request_number }}</p>
</div>
<div>
<p class="text-gray-500">Date</p>
<p class="font-semibold text-gray-800">{{ $purchaseRequest->date->format('d-m-Y') }}</p>
</div>
<div>
<p class="text-gray-500">Project / Site Name</p>
<p class="font-semibold text-gray-800">{{ $purchaseRequest->project_name ?? '—' }}</p>
</div>
<div>
<p class="text-gray-500">Requested By</p>
<p class="font-semibold text-gray-800">{{ $purchaseRequest->requested_by_name ?? $purchaseRequest->requestedBy?->name ?? '—' }}</p>
</div>
<div>
<p class="text-gray-500">Required Date</p>
<p class="font-semibold text-gray-800">{{ $purchaseRequest->required_date_text ?? '—' }}</p>
</div>
<div>
<p class="text-gray-500">Location / Site</p>
<p class="font-semibold text-gray-800">{{ $purchaseRequest->location ?? '—' }}</p>
</div>
@if($purchaseRequest->department)
<div>
<p class="text-gray-500">Department</p>
<p class="font-semibold text-gray-800">{{ $purchaseRequest->department }}</p>
</div>
@endif
@if($purchaseRequest->remarks)
<div class="col-span-2 sm:col-span-3">
<p class="text-gray-500">Remarks</p>
<p class="font-semibold text-gray-800">{{ $purchaseRequest->remarks }}</p>
</div>
@endif
</div>
</div>
{{-- Material Items --}}
<div class="card card-body mb-6">
<h2 class="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-4">Material Details</h2>
<div class="overflow-x-auto">
<table class="table-base">
<thead>
<tr>
<th class="w-12">S.No</th>
<th>Description of Material</th>
<th>Unit</th>
<th>Qty Required</th>
<th>Purpose / Use</th>
<th>Required Date</th>
</tr>
</thead>
<tbody>
@foreach($purchaseRequest->items as $i => $item)
<tr>
<td class="text-center">{{ $i + 1 }}</td>
<td class="font-medium">{{ $item->description }}</td>
<td>{{ $item->unit ?? '—' }}</td>
<td>{{ number_format($item->quantity_required, 2) }}</td>
<td>{{ $item->purpose_use ?? '—' }}</td>
<td>{{ $item->required_date ? $item->required_date->format('d-m-Y') : '—' }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
{{-- Approval Info --}}
@if($purchaseRequest->approvedBy)
<div class="card card-body">
<h2 class="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-3">Approval Info</h2>
<div class="grid grid-cols-2 gap-4 text-sm">
<div>
<p class="text-gray-500">Approved By</p>
<p class="font-semibold text-gray-800">{{ $purchaseRequest->approvedBy->name }}</p>
</div>
<div>
<p class="text-gray-500">Approved At</p>
<p class="font-semibold text-gray-800">{{ $purchaseRequest->approved_at?->format('d M Y, H:i') ?? '—' }}</p>
</div>
</div>
</div>
@endif
@endsection
+156
View File
@@ -0,0 +1,156 @@
@extends('layouts.app')
@section('title', 'Send RFQ — ' . $request->request_number)
@section('content')
<div style="max-width:760px;margin:0 auto;">
<div style="background:#fff;border-radius:16px;box-shadow:0 4px 24px rgba(0,0,0,.08);overflow:hidden;">
<!-- Header -->
<div style="background:linear-gradient(135deg,#0ea5e9,#0284c7);padding:24px 28px;">
<div style="font-size:11px;font-weight:600;color:rgba(255,255,255,.7);text-transform:uppercase;letter-spacing:.06em;">Send RFQ Invitations</div>
<div style="font-size:20px;font-weight:700;color:#fff;margin-top:4px;">{{ $request->request_number }}</div>
@if($request->project_name)
<div style="font-size:13px;color:rgba(255,255,255,.8);margin-top:2px;">{{ $request->project_name }}</div>
@endif
</div>
<div style="padding:28px;">
{{-- Already invited --}}
@if($invitations->count())
<div style="margin-bottom:24px;">
<div style="font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px;">Already Invited</div>
<div style="display:flex;flex-direction:column;gap:8px;">
@foreach($invitations as $inv)
@php
$statusColors = [
'sent' => ['bg'=>'#eff6ff','border'=>'#bfdbfe','color'=>'#1d4ed8'],
'opened' => ['bg'=>'#fffbeb','border'=>'#fde68a','color'=>'#92400e'],
'submitted' => ['bg'=>'#f0fdf4','border'=>'#bbf7d0','color'=>'#15803d'],
'declined' => ['bg'=>'#fef2f2','border'=>'#fecaca','color'=>'#dc2626'],
'pending' => ['bg'=>'#f8fafc','border'=>'#e2e8f0','color'=>'#64748b'],
];
$sc = $statusColors[$inv->status] ?? $statusColors['pending'];
@endphp
<div style="display:flex;align-items:center;justify-content:space-between;background:{{ $sc['bg'] }};border:1px solid {{ $sc['border'] }};border-radius:10px;padding:12px 16px;">
<div>
<div style="font-size:13px;font-weight:600;color:#0f172a;">{{ $inv->supplier->name }}</div>
<div style="font-size:11px;color:#64748b;margin-top:2px;">
via {{ ucfirst($inv->channel) }}
@if($inv->sent_at) · sent {{ $inv->sent_at->format('d M Y') }} @endif
</div>
</div>
<div style="display:flex;align-items:center;gap:8px;">
@if(in_array($inv->channel, ['whatsapp', 'both']))
@php $wsLink = app(\App\Services\RfqInvitationService::class)->whatsappLink($inv); @endphp
<a href="{{ $wsLink }}" target="_blank"
style="font-size:11px;font-weight:700;padding:4px 10px;background:#25d366;color:#fff;border-radius:6px;text-decoration:none;">
WhatsApp
</a>
@endif
<span style="font-size:11px;font-weight:700;padding:4px 12px;border-radius:20px;background:{{ $sc['bg'] }};color:{{ $sc['color'] }};border:1px solid {{ $sc['border'] }};">
{{ ucfirst($inv->status) }}
</span>
</div>
</div>
@endforeach
</div>
</div>
@endif
{{-- Invite more suppliers --}}
<div style="font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px;">
{{ $invitations->count() ? 'Add More Suppliers' : 'Select Suppliers' }}
</div>
<input type="text" id="supplier-search" placeholder="Search suppliers…" oninput="filterSuppliers(this.value)"
style="width:100%;padding:9px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;margin-bottom:12px;box-sizing:border-box;">
<form method="POST" action="{{ route('purchase.requests.rfq.store', $request) }}" id="rfq-form">
@csrf
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<div id="supplier-list" style="display:flex;flex-direction:column;gap:6px;max-height:400px;overflow-y:auto;padding-right:2px;">
@php $alreadyIds = $invitations->pluck('supplier_id')->toArray(); @endphp
@forelse($suppliers as $supplier)
@php $already = in_array($supplier->id, $alreadyIds); @endphp
<div class="sup-row" data-name="{{ strtolower($supplier->name) }}"
style="border:1.5px solid #e2e8f0;border-radius:10px;padding:12px 14px;transition:border-color .15s;{{ $already ? 'opacity:.45;' : '' }}">
<label style="display:flex;align-items:center;gap:12px;cursor:{{ $already ? 'default' : 'pointer' }};">
<input type="checkbox" name="supplier_ids[]" value="{{ $supplier->id }}"
data-sid="{{ $supplier->id }}" onchange="toggleRow(this)"
{{ $already ? 'disabled' : '' }}
style="width:17px;height:17px;cursor:pointer;flex-shrink:0;">
<div style="flex:1;min-width:0;">
<div style="font-size:13px;font-weight:600;color:#0f172a;">{{ $supplier->name }}</div>
<div style="font-size:11px;color:#64748b;margin-top:1px;">
{{ $supplier->email ?: '—' }} · {{ $supplier->phone ?: '—' }}
</div>
</div>
@if($already)
<span style="font-size:11px;font-weight:600;color:#64748b;background:#f1f5f9;padding:3px 10px;border-radius:20px;">Already invited</span>
@endif
</label>
{{-- Channel selector (hidden until checked) --}}
<div id="ch-{{ $supplier->id }}" style="display:none;margin-top:10px;padding-top:10px;border-top:1px dashed #e2e8f0;">
<div style="font-size:11px;font-weight:700;color:#64748b;margin-bottom:8px;">Send via:</div>
<div style="display:flex;gap:8px;">
@foreach(['whatsapp' => 'WhatsApp', 'email' => 'Email', 'both' => 'Both'] as $val => $lbl)
<label style="flex:1;cursor:pointer;">
<input type="radio" name="channel_{{ $supplier->id }}" value="{{ $val }}" {{ $val === 'both' ? 'checked' : '' }}
style="display:none;" id="ch-{{ $supplier->id }}-{{ $val }}"
onchange="styleChannelBtns({{ $supplier->id }})">
<span id="chbtn-{{ $supplier->id }}-{{ $val }}"
onclick="document.getElementById('ch-{{ $supplier->id }}-{{ $val }}').checked=true;styleChannelBtns({{ $supplier->id }});"
style="display:block;text-align:center;padding:7px 6px;border-radius:7px;font-size:12px;font-weight:600;cursor:pointer;
border:1.5px solid {{ $val === 'both' ? '#0ea5e9' : '#e2e8f0' }};
background:{{ $val === 'both' ? '#eff6ff' : '#fff' }};
color:{{ $val === 'both' ? '#0284c7' : '#64748b' }};">
{{ $lbl }}
</span>
</label>
@endforeach
</div>
</div>
</div>
@empty
<p style="text-align:center;color:#94a3b8;padding:24px;">No active suppliers found. Add suppliers first.</p>
@endforelse
</div>
<button type="submit"
style="width:100%;margin-top:20px;padding:13px;background:linear-gradient(135deg,#0ea5e9,#0284c7);color:#fff;border:none;border-radius:9px;font-size:14px;font-weight:700;cursor:pointer;">
Send Invitations →
</button>
</form>
</div>
</div>
</div>
<script>
function filterSuppliers(q) {
document.querySelectorAll('.sup-row').forEach(function(row) {
row.style.display = row.dataset.name.includes(q.toLowerCase()) ? '' : 'none';
});
}
function toggleRow(cb) {
var ch = document.getElementById('ch-' + cb.dataset.sid);
if (ch) ch.style.display = cb.checked ? 'block' : 'none';
}
function styleChannelBtns(sid) {
['whatsapp','email','both'].forEach(function(v) {
var inp = document.getElementById('ch-' + sid + '-' + v);
var btn = document.getElementById('chbtn-' + sid + '-' + v);
if (!inp || !btn) return;
if (inp.checked) {
btn.style.borderColor = '#0ea5e9'; btn.style.background = '#eff6ff'; btn.style.color = '#0284c7';
} else {
btn.style.borderColor = '#e2e8f0'; btn.style.background = '#fff'; btn.style.color = '#64748b';
}
});
}
</script>
@endsection
@@ -0,0 +1,122 @@
@extends('layouts.app')
@section('title', 'GM Signature — ' . $request->request_number)
@section('content')
<div style="max-width:640px;margin:0 auto;">
<div style="background:#fff;border-radius:16px;box-shadow:0 4px 24px rgba(0,0,0,.08);overflow:hidden;">
<!-- Header -->
<div style="background:linear-gradient(135deg,#7c3aed,#4f46e5);padding:24px 28px;">
<div style="font-size:11px;font-weight:600;color:rgba(255,255,255,.7);text-transform:uppercase;letter-spacing:.06em;">GM Digital Signature</div>
<div style="font-size:20px;font-weight:700;color:#fff;margin-top:4px;">{{ $request->request_number }}</div>
@if($request->project_name)
<div style="font-size:13px;color:rgba(255,255,255,.8);margin-top:2px;">{{ $request->project_name }}</div>
@endif
</div>
<div style="padding:28px;">
@if($request->signature)
<!-- Already signed -->
<div style="text-align:center;margin-bottom:8px;">
<img src="{{ $request->signature->signature_image }}"
style="max-width:100%;border:1px solid #e2e8f0;border-radius:10px;background:#f8fafc;">
<div style="font-size:12px;color:#64748b;margin-top:10px;">
Signed by <strong>{{ $request->signature->signedBy->name }}</strong>
on {{ $request->signature->signed_at->format('d M Y, H:i') }}
</div>
</div>
<a href="{{ route('purchase.pipeline.index') }}"
style="display:block;text-align:center;margin-top:20px;padding:10px;background:#f1f5f9;color:#475569;border-radius:8px;font-size:13px;font-weight:600;text-decoration:none;">
← Back to Pipeline
</a>
@else
<!-- Signature pad -->
<p style="font-size:13px;color:#475569;margin-bottom:16px;">
Draw your signature below using your mouse or touch screen to approve this purchase request.
</p>
<div style="position:relative;">
<canvas id="sig-canvas" width="580" height="200"
style="width:100%;border:2px dashed #cbd5e1;border-radius:10px;cursor:crosshair;touch-action:none;background:#fafafa;display:block;">
</canvas>
<div id="sig-hint" style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none;">
<span style="font-size:13px;color:#cbd5e1;font-style:italic;">Sign here</span>
</div>
</div>
<div style="display:flex;gap:10px;margin-top:14px;">
<button type="button" onclick="clearCanvas()"
style="flex:1;padding:11px;border:1.5px solid #e2e8f0;border-radius:8px;font-size:13px;font-weight:600;color:#64748b;background:#f8fafc;cursor:pointer;">
Clear
</button>
<button type="button" onclick="submitSignature()"
style="flex:2;padding:11px;border:none;border-radius:8px;font-size:13px;font-weight:700;color:#fff;background:linear-gradient(135deg,#7c3aed,#4f46e5);cursor:pointer;">
Confirm Signature →
</button>
</div>
<form id="sig-form" method="POST" action="{{ route('purchase.requests.sign.store', $request) }}">
@csrf
<input type="hidden" name="signature_image" id="sig-data">
</form>
<p style="font-size:11px;color:#94a3b8;margin-top:14px;text-align:center;">
By signing, you approve this purchase request. This action is recorded with your name, timestamp, and IP address.
</p>
@endif
</div>
</div>
</div>
<script>
(function () {
const canvas = document.getElementById('sig-canvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
const hint = document.getElementById('sig-hint');
let drawing = false;
let hasMark = false;
ctx.lineWidth = 2.5;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = '#1e293b';
function pos(e) {
const r = canvas.getBoundingClientRect();
const sx = canvas.width / r.width;
const sy = canvas.height / r.height;
const src = e.touches ? e.touches[0] : e;
return { x: (src.clientX - r.left) * sx, y: (src.clientY - r.top) * sy };
}
function start(e) { e.preventDefault(); drawing = true; const p = pos(e); ctx.beginPath(); ctx.moveTo(p.x, p.y); if (!hasMark) { hasMark = true; if (hint) hint.style.display = 'none'; } }
function move(e) { e.preventDefault(); if (!drawing) return; const p = pos(e); ctx.lineTo(p.x, p.y); ctx.stroke(); ctx.beginPath(); ctx.moveTo(p.x, p.y); }
function end() { drawing = false; }
canvas.addEventListener('mousedown', start);
canvas.addEventListener('mousemove', move);
canvas.addEventListener('mouseup', end);
canvas.addEventListener('mouseleave', end);
canvas.addEventListener('touchstart', start, { passive: false });
canvas.addEventListener('touchmove', move, { passive: false });
canvas.addEventListener('touchend', end);
window.clearCanvas = function () {
ctx.clearRect(0, 0, canvas.width, canvas.height);
hasMark = false;
if (hint) hint.style.display = 'flex';
};
window.submitSignature = function () {
if (!hasMark) { showToast('Please draw your signature first.', 'warn'); return; }
document.getElementById('sig-data').value = canvas.toDataURL('image/png');
document.getElementById('sig-form').submit();
};
})();
</script>
@endsection
@@ -0,0 +1,71 @@
@extends('layouts.app')
@section('title', 'Add Supplier')
@section('content')
<div class="mb-6">
<h1 class="page-title">Add Supplier</h1>
<p class="page-subtitle"><a href="{{ route('purchase.suppliers.index') }}" class="text-blue-600 hover:underline">Suppliers</a> / New</p>
</div>
@if($errors->any())
<div class="mb-4 px-4 py-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
<ul class="list-disc list-inside space-y-1">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card card-body max-w-2xl">
<form action="{{ route('purchase.suppliers.store') }}" method="POST">
@csrf
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div class="sm:col-span-2">
<label class="form-label">Supplier Name <span class="text-red-500">*</span></label>
<input type="text" name="name" value="{{ old('name') }}" required class="form-input">
</div>
<div>
<label class="form-label">Contact Person</label>
<input type="text" name="contact_person" value="{{ old('contact_person') }}" class="form-input">
</div>
<div>
<label class="form-label">Email</label>
<input type="email" name="email" value="{{ old('email') }}" class="form-input">
</div>
<div>
<label class="form-label">Phone</label>
<input type="text" name="phone" value="{{ old('phone') }}" class="form-input">
</div>
<div>
<label class="form-label">Tax Number</label>
<input type="text" name="tax_number" value="{{ old('tax_number') }}" class="form-input">
</div>
<div class="sm:col-span-2">
<label class="form-label">Address</label>
<textarea name="address" rows="3" class="form-textarea">{{ old('address') }}</textarea>
</div>
<div class="sm:col-span-2 flex items-center gap-2">
<input type="checkbox" name="is_active" id="is_active" value="1"
{{ old('is_active', '1') ? 'checked' : '' }}
class="h-4 w-4 text-blue-600 border-gray-300 rounded">
<label for="is_active" class="form-label mb-0">Active</label>
</div>
</div>
<div class="mt-6 flex items-center gap-3">
<button type="submit" class="btn-primary">Save Supplier</button>
<a href="{{ route('purchase.suppliers.index') }}" class="btn-secondary">Cancel</a>
</div>
</form>
</div>
@endsection
@@ -0,0 +1,72 @@
@extends('layouts.app')
@section('title', 'Edit Supplier')
@section('content')
<div class="mb-6">
<h1 class="page-title">Edit Supplier</h1>
<p class="page-subtitle"><a href="{{ route('purchase.suppliers.index') }}" class="text-blue-600 hover:underline">Suppliers</a> / Edit</p>
</div>
@if($errors->any())
<div class="mb-4 px-4 py-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
<ul class="list-disc list-inside space-y-1">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card card-body max-w-2xl">
<form action="{{ route('purchase.suppliers.update', $supplier) }}" method="POST">
@csrf
@method('PUT')
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div class="sm:col-span-2">
<label class="form-label">Supplier Name <span class="text-red-500">*</span></label>
<input type="text" name="name" value="{{ old('name', $supplier->name) }}" required class="form-input">
</div>
<div>
<label class="form-label">Contact Person</label>
<input type="text" name="contact_person" value="{{ old('contact_person', $supplier->contact_person) }}" class="form-input">
</div>
<div>
<label class="form-label">Email</label>
<input type="email" name="email" value="{{ old('email', $supplier->email) }}" class="form-input">
</div>
<div>
<label class="form-label">Phone</label>
<input type="text" name="phone" value="{{ old('phone', $supplier->phone) }}" class="form-input">
</div>
<div>
<label class="form-label">Tax Number</label>
<input type="text" name="tax_number" value="{{ old('tax_number', $supplier->tax_number) }}" class="form-input">
</div>
<div class="sm:col-span-2">
<label class="form-label">Address</label>
<textarea name="address" rows="3" class="form-textarea">{{ old('address', $supplier->address) }}</textarea>
</div>
<div class="sm:col-span-2 flex items-center gap-2">
<input type="checkbox" name="is_active" id="is_active" value="1"
{{ old('is_active', $supplier->is_active) ? 'checked' : '' }}
class="h-4 w-4 text-blue-600 border-gray-300 rounded">
<label for="is_active" class="form-label mb-0">Active</label>
</div>
</div>
<div class="mt-6 flex items-center gap-3">
<button type="submit" class="btn-primary">Update Supplier</button>
<a href="{{ route('purchase.suppliers.index') }}" class="btn-secondary">Cancel</a>
</div>
</form>
</div>
@endsection
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,198 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Suppliers List</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'DejaVu Sans', 'Arial', sans-serif;
font-size: 11px;
color: #1e293b;
background: #fff;
}
/* ── Header ── */
.header {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 24px 28px 16px;
border-bottom: 2px solid #2563eb;
margin-bottom: 20px;
}
.brand { display: flex; align-items: center; gap: 10px; }
.brand-box {
width: 36px; height: 36px; border-radius: 8px;
background: #2563eb;
display: flex; align-items: center; justify-content: center;
}
.brand-name { font-size: 17px; font-weight: 700; color: #0f172a; }
.brand-sub { font-size: 9px; color: #64748b; margin-top: 1px; }
.report-info { text-align: right; }
.report-title { font-size: 15px; font-weight: 700; color: #1e293b; }
.report-meta { font-size: 9px; color: #64748b; margin-top: 3px; }
/* ── Summary strip ── */
.summary {
display: flex;
gap: 12px;
margin: 0 28px 16px;
}
.summary-card {
flex: 1;
padding: 10px 14px;
background: #f1f5f9;
border-radius: 8px;
border-left: 3px solid #2563eb;
}
.summary-card .val { font-size: 20px; font-weight: 700; color: #2563eb; }
.summary-card .lbl { font-size: 9px; color: #64748b; margin-top: 2px; }
.summary-card.green { border-color: #10b981; }
.summary-card.green .val { color: #10b981; }
.summary-card.gray { border-color: #94a3b8; }
.summary-card.gray .val { color: #94a3b8; }
/* ── Table ── */
.table-wrap { padding: 0 28px; }
table {
width: 100%;
border-collapse: collapse;
font-size: 10.5px;
}
thead th {
background: #1e293b;
color: #fff;
font-weight: 600;
font-size: 9px;
letter-spacing: .06em;
text-transform: uppercase;
padding: 8px 10px;
text-align: left;
}
tbody tr:nth-child(even) { background: #f8fafc; }
tbody tr:nth-child(odd) { background: #ffffff; }
tbody td {
padding: 7px 10px;
color: #334155;
border-bottom: 1px solid #e2e8f0;
vertical-align: middle;
}
tbody td:first-child { font-weight: 600; color: #0f172a; }
.badge {
display: inline-block;
padding: 2px 7px;
border-radius: 20px;
font-size: 8.5px;
font-weight: 700;
letter-spacing: .04em;
}
.badge-active { background: #d1fae5; color: #065f46; }
.badge-inactive { background: #f1f5f9; color: #64748b; }
/* ── Footer ── */
.footer {
margin-top: 24px;
padding: 12px 28px 0;
border-top: 1px solid #e2e8f0;
display: flex;
justify-content: space-between;
font-size: 9px;
color: #94a3b8;
}
</style>
</head>
<body>
<div class="header">
<div class="brand">
<div class="brand-box">
<svg width="18" height="18" fill="none" stroke="#fff" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
</svg>
</div>
<div>
<div class="brand-name">SteelERP</div>
<div class="brand-sub">Manufacturing &amp; Trading</div>
</div>
</div>
<div class="report-info">
<div class="report-title">Supplier Directory</div>
<div class="report-meta">Generated: {{ now()->format('d M Y, H:i') }}</div>
<div class="report-meta">Total Records: {{ $suppliers->count() }}</div>
</div>
</div>
@php
$active = $suppliers->where('is_active', true)->count();
$inactive = $suppliers->where('is_active', false)->count();
@endphp
<div class="summary">
<div class="summary-card">
<div class="val">{{ $suppliers->count() }}</div>
<div class="lbl">Total Suppliers</div>
</div>
<div class="summary-card green">
<div class="val">{{ $active }}</div>
<div class="lbl">Active</div>
</div>
<div class="summary-card gray">
<div class="val">{{ $inactive }}</div>
<div class="lbl">Inactive</div>
</div>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th style="width:4%">#</th>
<th style="width:22%">Supplier Name</th>
<th style="width:17%">Contact Person</th>
<th style="width:20%">Email</th>
<th style="width:14%">Phone</th>
<th style="width:15%">Tax Number</th>
<th style="width:8%">Status</th>
</tr>
</thead>
<tbody>
@forelse($suppliers as $i => $supplier)
<tr>
<td style="color:#94a3b8;">{{ $i + 1 }}</td>
<td>{{ $supplier->name }}</td>
<td>{{ $supplier->contact_person ?: '—' }}</td>
<td>{{ $supplier->email ?: '—' }}</td>
<td>{{ $supplier->phone ?: '—' }}</td>
<td>{{ $supplier->tax_number ?: '—' }}</td>
<td>
@if($supplier->is_active)
<span class="badge badge-active">Active</span>
@else
<span class="badge badge-inactive">Inactive</span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="7" style="text-align:center;padding:20px;color:#94a3b8;">
No suppliers found.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="footer">
<span>SteelERP — Confidential</span>
<span>Exported by: {{ Auth::user()->name ?? '—' }}</span>
</div>
</body>
</html>
@@ -0,0 +1,135 @@
@extends('layouts.app')
@section('title', $supplier->name)
@section('content')
<div class="page-header">
<div>
<h1 class="page-title">{{ $supplier->name }}</h1>
<p class="page-subtitle">
<a href="{{ route('purchase.suppliers.index') }}" style="color:#2563eb;text-decoration:none;">Suppliers</a>
&nbsp;/&nbsp; View Details
</p>
</div>
<div class="flex items-center gap-2">
<a href="{{ route('purchase.suppliers.edit', $supplier) }}" class="btn btn-secondary">Edit Supplier</a>
<a href="{{ route('purchase.suppliers.index') }}" class="btn btn-secondary">Back to List</a>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;max-width:900px;">
{{-- Identity --}}
<div style="background:#fff;border-radius:14px;border:1px solid #e2e8f0;padding:22px;grid-column:1/-1;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:18px;">
<h3 style="font-size:13px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#94a3b8;">Company</h3>
@if($supplier->is_active)
<span class="badge-green">Active</span>
@else
<span class="badge-gray">Inactive</span>
@endif
</div>
<div style="font-size:22px;font-weight:700;color:#0f172a;margin-bottom:6px;">{{ $supplier->name }}</div>
@if($supplier->supplier_code)
<div style="font-family:monospace;font-size:12px;color:#94a3b8;margin-bottom:4px;">{{ $supplier->supplier_code }}</div>
@endif
@if($supplier->category)
<span style="display:inline-block;padding:3px 12px;border-radius:20px;font-size:12px;font-weight:600;background:#f1f5f9;color:#475569;margin-top:4px;">
{{ $supplier->category }}
</span>
@endif
@if($supplier->website)
<div style="margin-top:10px;">
<a href="{{ $supplier->website }}" target="_blank"
style="font-size:13px;color:#2563eb;text-decoration:none;">
{{ $supplier->website }}
</a>
</div>
@endif
</div>
{{-- Contact --}}
<div style="background:#fff;border-radius:14px;border:1px solid #e2e8f0;padding:22px;">
<h3 style="font-size:13px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#94a3b8;margin-bottom:16px;">Contact</h3>
@if($supplier->contact_person)
<div style="margin-bottom:12px;">
<div style="font-size:11px;color:#94a3b8;margin-bottom:2px;">Contact Person</div>
<div style="font-size:14px;font-weight:600;color:#1e293b;">{{ $supplier->contact_person }}</div>
</div>
@endif
@if($supplier->email)
<div style="margin-bottom:10px;">
<div style="font-size:11px;color:#94a3b8;margin-bottom:2px;">Primary Email</div>
<a href="mailto:{{ $supplier->email }}" style="font-size:13px;color:#2563eb;text-decoration:none;">{{ $supplier->email }}</a>
</div>
@endif
@if($supplier->secondary_email)
<div style="margin-bottom:10px;">
<div style="font-size:11px;color:#94a3b8;margin-bottom:2px;">Secondary Email</div>
<a href="mailto:{{ $supplier->secondary_email }}" style="font-size:13px;color:#64748b;text-decoration:none;">{{ $supplier->secondary_email }}</a>
</div>
@endif
@if($supplier->phone)
<div style="margin-bottom:10px;">
<div style="font-size:11px;color:#94a3b8;margin-bottom:2px;">Phone 1</div>
<a href="tel:{{ $supplier->phone }}" style="font-size:13px;color:#334155;text-decoration:none;">{{ $supplier->phone }}</a>
</div>
@endif
@if($supplier->phone2)
<div style="margin-bottom:10px;">
<div style="font-size:11px;color:#94a3b8;margin-bottom:2px;">Phone 2</div>
<a href="tel:{{ $supplier->phone2 }}" style="font-size:13px;color:#334155;text-decoration:none;">{{ $supplier->phone2 }}</a>
</div>
@endif
@if($supplier->whatsapp)
<div>
<div style="font-size:11px;color:#94a3b8;margin-bottom:2px;">WhatsApp</div>
<a href="https://wa.me/{{ ltrim($supplier->whatsapp, '+') }}" target="_blank"
style="display:inline-flex;align-items:center;gap:5px;font-size:13px;color:#16a34a;text-decoration:none;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="#16a34a">
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z"/>
</svg>
{{ $supplier->whatsapp }}
</a>
</div>
@endif
</div>
{{-- Financial --}}
<div style="background:#fff;border-radius:14px;border:1px solid #e2e8f0;padding:22px;">
<h3 style="font-size:13px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#94a3b8;margin-bottom:16px;">Financial &amp; Address</h3>
@if($supplier->address)
<div style="margin-bottom:12px;">
<div style="font-size:11px;color:#94a3b8;margin-bottom:2px;">Address</div>
<div style="font-size:13px;color:#334155;line-height:1.5;">{{ $supplier->address }}</div>
</div>
@endif
@if($supplier->tax_number)
<div style="margin-bottom:12px;">
<div style="font-size:11px;color:#94a3b8;margin-bottom:2px;">Tax / VAT Number</div>
<div style="font-family:monospace;font-size:13px;color:#334155;">{{ $supplier->tax_number }}</div>
</div>
@endif
@if($supplier->credit_terms)
<div style="margin-bottom:12px;">
<div style="font-size:11px;color:#94a3b8;margin-bottom:2px;">Credit Terms</div>
<div style="font-size:13px;color:#16a34a;font-weight:600;">
{{ strtolower($supplier->credit_terms) === 'y' ? 'Yes' : $supplier->credit_terms }}
@if($supplier->credit_days)
&nbsp;·&nbsp; {{ $supplier->credit_days }} days
@endif
</div>
</div>
@endif
@if($supplier->remarks)
<div>
<div style="font-size:11px;color:#94a3b8;margin-bottom:2px;">Remarks</div>
<div style="font-size:13px;color:#475569;line-height:1.5;">{{ $supplier->remarks }}</div>
</div>
@endif
</div>
</div>
@endsection