feat: RFQ portal, notifications, and project settings updates

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
ghassanandClaude Sonnet 4.6 committed 2026-06-01 11:52:21 +03:00
1 parent 72e6c3170e
commit dca9cd5d99
15 files changed
+1095 -140

No files matched your search

+122 -18
View File
@@ -6,31 +6,50 @@
<title>Quote Request — {{ $purchaseRequest->request_number }}</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;padding:20px 16px;}
.card{background:#fff;border-radius:16px;box-shadow:0 4px 24px rgba(0,0,0,.08);max-width:740px;margin:0 auto;overflow:hidden;}
.hd{background:linear-gradient(135deg,#2563eb,#1d4ed8);padding:28px 32px;}
.body{padding:32px;}
body{font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;}
label.fl{display:block;font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.05em;margin-bottom:5px;}
input[type=text],input[type=number],textarea{width:100%;padding:9px 12px;border:1.5px solid #e2e8f0;border-radius:8px;font-size:13px;outline:none;}
input[type=text],input[type=number],textarea{width:100%;padding:9px 12px;border:1.5px solid #e2e8f0;border-radius:8px;font-size:13px;outline:none;background:#fff;}
input:focus,textarea:focus{border-color:#2563eb;}
.btn{width:100%;padding:14px;background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;border:none;border-radius:10px;font-size:15px;font-weight:700;cursor:pointer;margin-top:24px;}
.btn{width:100%;padding:15px;background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;border:none;border-radius:10px;font-size:15px;font-weight:700;cursor:pointer;}
table{width:100%;border-collapse:collapse;font-size:13px;}
thead th{background:#f8fafc;padding:10px 12px;text-align:left;font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;}
tbody td{padding:10px 12px;border-bottom:1px solid #f1f5f9;vertical-align:middle;}
tfoot td{padding:12px;font-weight:700;}
input[type=number].price{width:130px;text-align:right;}
@media(max-width:600px){
.body{padding:20px;}
.two-col{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px;}
/* ── Desktop: centered card ── */
@media(min-width:641px){
body{padding:28px 16px;}
.card{background:#fff;border-radius:16px;box-shadow:0 4px 24px rgba(0,0,0,.08);max-width:740px;margin:0 auto;overflow:hidden;}
.hd{background:linear-gradient(135deg,#2563eb,#1d4ed8);padding:28px 32px;}
.body{padding:32px;}
}
/* ── Mobile: full page ── */
@media(max-width:640px){
body{background:#fff;padding:0;}
.card{background:#fff;border-radius:0;box-shadow:none;max-width:100%;}
.hd{background:linear-gradient(135deg,#2563eb,#1d4ed8);padding:22px 18px;}
.body{padding:18px 16px 32px;}
.two-col{grid-template-columns:1fr;}
/* Items table → stacked cards */
table,thead,tbody,tfoot,tr,th,td{display:block;}
thead{display:none;}
tbody td{border:none;padding:4px 0;}
tbody tr{border:1px solid #e2e8f0;border-radius:10px;padding:12px;margin-bottom:10px;}
tbody td{border:none;padding:3px 0;}
tbody tr{border:1px solid #e2e8f0;border-radius:10px;padding:12px 14px;margin-bottom:10px;background:#fff;}
tfoot tr{border-top:2px solid #e2e8f0;padding:12px 0;}
input[type=number].price{width:100%;}
/* Confirm code stacks */
.code-row{flex-direction:column !important;align-items:stretch !important;}
.code-display{display:block;text-align:center;}
}
</style>
</head>
<body>
<div class="card">
{{-- Header --}}
<div class="hd">
<div style="font-size:11px;font-weight:600;color:rgba(255,255,255,.7);text-transform:uppercase;letter-spacing:.06em;">Request for Quotation</div>
<div style="font-size:22px;font-weight:700;color:#fff;margin-top:4px;">{{ $purchaseRequest->request_number }}</div>
@@ -68,12 +87,12 @@
<tr>
<td style="color:#94a3b8;font-size:12px;">{{ $i + 1 }}</td>
<td style="font-weight:500;">{{ $item->description }}</td>
<td>{{ rtrim(rtrim(number_format((float)$item->quantity, 3), '0'), '.') }}</td>
<td>{{ rtrim(rtrim(number_format((float)$item->quantity_required, 3), '0'), '.') }}</td>
<td style="color:#64748b;">{{ $item->unit ?: '—' }}</td>
<td style="text-align:right;">
<input type="number" class="price" name="items[{{ $i }}][unit_price]"
min="0" step="0.001" required placeholder="0.000"
oninput="calcRow({{ $i }}, {{ (float)$item->quantity }})">
oninput="calcRow({{ $i }}, {{ (float)$item->quantity_required }})">
</td>
<td style="text-align:right;font-weight:600;" id="tot-{{ $i }}">—</td>
</tr>
@@ -88,10 +107,10 @@
</table>
</div>
{{-- Terms --}}
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px;">
{{-- Delivery / Payment --}}
<div class="two-col">
<div>
<label class="fl">Lead Time (days)</label>
<label class="fl">Delivery Time (days)</label>
<input type="number" name="lead_time_days" min="0" placeholder="e.g. 14">
</div>
<div>
@@ -99,15 +118,86 @@
<input type="text" name="payment_terms" placeholder="e.g. 30 days net">
</div>
</div>
<div>
<div style="margin-bottom:20px;">
<label class="fl">Notes / Remarks</label>
<textarea name="notes" rows="3" placeholder="Any additional notes, conditions, or remarks…"></textarea>
</div>
<button class="btn" type="submit">Submit My Quote →</button>
{{-- T&C --}}
<div style="background:#f8fafc;border:1.5px solid #e2e8f0;border-radius:10px;padding:16px 18px;margin-bottom:16px;">
<div style="font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.06em;margin-bottom:10px;">Terms &amp; Conditions</div>
<ul style="list-style:none;padding:0;margin:0 0 14px;display:flex;flex-direction:column;gap:7px;">
<li style="display:flex;align-items:flex-start;gap:9px;font-size:12px;color:#475569;line-height:1.5;">
<span style="flex-shrink:0;width:18px;height:18px;border-radius:50%;background:#dbeafe;display:flex;align-items:center;justify-content:center;margin-top:1px;">
<svg width="9" height="9" viewBox="0 0 10 10" fill="none"><path d="M2 5.5L4 7.5L8 3" stroke="#2563eb" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
Prices stated in this quote are valid for <strong>30 days</strong> from the submission date.
</li>
<li style="display:flex;align-items:flex-start;gap:9px;font-size:12px;color:#475569;line-height:1.5;">
<span style="flex-shrink:0;width:18px;height:18px;border-radius:50%;background:#dbeafe;display:flex;align-items:center;justify-content:center;margin-top:1px;">
<svg width="9" height="9" viewBox="0 0 10 10" fill="none"><path d="M2 5.5L4 7.5L8 3" stroke="#2563eb" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
Delivery will be made within the specified delivery time stated above.
</li>
<li style="display:flex;align-items:flex-start;gap:9px;font-size:12px;color:#475569;line-height:1.5;">
<span style="flex-shrink:0;width:18px;height:18px;border-radius:50%;background:#dbeafe;display:flex;align-items:center;justify-content:center;margin-top:1px;">
<svg width="9" height="9" viewBox="0 0 10 10" fill="none"><path d="M2 5.5L4 7.5L8 3" stroke="#2563eb" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
All items supplied will meet the required specifications and quality standards.
</li>
<li style="display:flex;align-items:flex-start;gap:9px;font-size:12px;color:#475569;line-height:1.5;">
<span style="flex-shrink:0;width:18px;height:18px;border-radius:50%;background:#dbeafe;display:flex;align-items:center;justify-content:center;margin-top:1px;">
<svg width="9" height="9" viewBox="0 0 10 10" fill="none"><path d="M2 5.5L4 7.5L8 3" stroke="#2563eb" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
This quote is <strong>binding upon acceptance</strong> and constitutes a formal offer.
</li>
</ul>
<label style="display:flex;align-items:flex-start;gap:10px;cursor:pointer;padding:10px 12px;background:#fff;border:1.5px solid #e2e8f0;border-radius:8px;">
<input type="checkbox" name="terms" id="terms-cb" value="1" required
onchange="checkReady()"
style="width:16px;height:16px;margin-top:1px;accent-color:#2563eb;flex-shrink:0;cursor:pointer;">
<span style="font-size:13px;font-weight:600;color:#0f172a;">I have read and agree to the terms and conditions above</span>
</label>
@error('terms')
<div style="font-size:12px;color:#dc2626;margin-top:6px;">{{ $message }}</div>
@enderror
</div>
{{-- Confirmation code --}}
<div style="background:#fffbeb;border:1.5px solid #fde68a;border-radius:10px;padding:16px 18px;margin-bottom:20px;">
<div style="font-size:11px;font-weight:700;color:#92400e;text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px;">Confirmation Required</div>
<div class="code-row" style="display:flex;align-items:center;gap:14px;">
<div style="flex-shrink:0;">
<div style="font-size:11px;color:#92400e;margin-bottom:6px;">Copy this code:</div>
<div class="code-display"
style="font-size:24px;font-weight:800;letter-spacing:.2em;color:#92400e;
background:#fef3c7;border:2px dashed #f59e0b;border-radius:8px;
padding:10px 20px;font-family:monospace;user-select:all;white-space:nowrap;">
{{ $confirmCode }}
</div>
</div>
<div style="flex:1;min-width:0;">
<label class="fl" style="color:#92400e;">Paste code here</label>
<input type="text" name="confirm_code" id="confirm-input"
autocomplete="off" autocorrect="off" autocapitalize="characters" spellcheck="false"
placeholder="Paste the code above"
oninput="checkReady()"
style="padding:11px 12px;border:1.5px solid #fde68a;
font-size:16px;font-weight:700;letter-spacing:.12em;font-family:monospace;
text-transform:uppercase;outline:none;background:#fff;">
@error('confirm_code')
<div style="font-size:12px;color:#dc2626;margin-top:4px;">{{ $message }}</div>
@enderror
</div>
</div>
</div>
<button class="btn" type="submit" id="submit-btn" disabled style="opacity:.4;cursor:not-allowed;">
Submit My Quote →
</button>
</form>
<p style="font-size:11px;color:#cbd5e1;margin-top:16px;text-align:center;">
<p style="font-size:11px;color:#cbd5e1;margin-top:18px;text-align:center;">
Link expires {{ $invitation->expires_at->format('d M Y') }} · One submission only
</p>
</div>
@@ -126,6 +216,20 @@ function calcRow(i, qty) {
var ge = document.getElementById('grand-total');
if (ge) ge.textContent = 'BD ' + grand.toFixed(3);
}
var _expected = '{{ $confirmCode }}';
function checkReady() {
var terms = document.getElementById('terms-cb');
var inp = document.getElementById('confirm-input');
var btn = document.getElementById('submit-btn');
if (!terms || !inp || !btn) return;
var codeOk = inp.value.trim().toUpperCase() === _expected;
var ok = terms.checked && codeOk;
btn.disabled = !ok;
btn.style.opacity = ok ? '1' : '.4';
btn.style.cursor = ok ? 'pointer' : 'not-allowed';
inp.style.borderColor = inp.value.length > 0 ? (codeOk ? '#16a34a' : '#ef4444') : '#fde68a';
}
</script>
</body>
</html>
+76 -12
View File
@@ -3,25 +3,89 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Quote Submitted</title>
<title>Quote Submitted — Thank You</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:system-ui,-apple-system,sans-serif;background:#f0fdf4;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;}
.card{background:#fff;border-radius:20px;box-shadow:0 8px 32px rgba(0,0,0,.10);padding:52px 44px;max-width:480px;width:100%;text-align:center;}
body{font-family:system-ui,-apple-system,sans-serif;min-height:100vh;}
@keyframes popIn{from{opacity:0;transform:scale(.88) translateY(18px);}to{opacity:1;transform:scale(1) translateY(0);}}
@keyframes checkDraw{from{stroke-dashoffset:60;}to{stroke-dashoffset:0;}}
@keyframes ringPulse{0%,100%{transform:scale(1);opacity:.3;}50%{transform:scale(1.2);opacity:.1;}}
.detail-row{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px;padding:8px 0;}
/* ── Desktop: centered card ── */
@media(min-width:641px){
body{background:linear-gradient(135deg,#f0fdf4 0%,#ecfdf5 50%,#d1fae5 100%);display:flex;align-items:center;justify-content:center;padding:28px 16px;}
.card{background:#fff;border-radius:24px;box-shadow:0 20px 60px rgba(0,0,0,.10),0 4px 16px rgba(0,0,0,.06);max-width:500px;width:100%;overflow:hidden;animation:popIn .5s cubic-bezier(.34,1.56,.64,1) forwards;}
.inner{padding:44px 40px 36px;text-align:center;}
.footer-bar{padding:14px 40px;background:#f8fafc;border-top:1px solid #f1f5f9;text-align:center;}
}
/* ── Mobile: full page ── */
@media(max-width:640px){
body{background:#fff;display:flex;flex-direction:column;}
.card{background:#fff;border-radius:0;box-shadow:none;width:100%;flex:1;display:flex;flex-direction:column;}
.inner{padding:36px 20px 28px;text-align:center;flex:1;}
.footer-bar{padding:14px 20px;background:#f8fafc;border-top:1px solid #f1f5f9;text-align:center;}
.icon-wrap{margin-bottom:20px !important;}
}
</style>
</head>
<body>
<div class="card">
<div style="font-size:60px;margin-bottom:20px;">✅</div>
<div style="font-size:24px;font-weight:700;color:#15803d;margin-bottom:10px;">Quote Submitted!</div>
<div style="font-size:14px;color:#475569;line-height:1.65;">
Thank you, <strong>{{ $invitation->supplier->name }}</strong>. Your quote for
<strong>{{ $invitation->purchaseRequest->request_number }}</strong> has been received
and is under review. You will be contacted if you are selected.
<div class="card">
<div style="height:6px;background:linear-gradient(90deg,#16a34a,#22c55e,#4ade80);"></div>
<div class="inner">
{{-- Animated check --}}
<div class="icon-wrap" style="position:relative;display:inline-block;margin-bottom:28px;">
<div style="position:absolute;inset:-12px;border-radius:50%;background:#dcfce7;animation:ringPulse 2.5s ease-in-out infinite;"></div>
<div style="width:80px;height:80px;border-radius:50%;background:linear-gradient(135deg,#16a34a,#22c55e);display:flex;align-items:center;justify-content:center;position:relative;box-shadow:0 8px 24px rgba(22,163,74,.3);">
<svg width="38" height="38" viewBox="0 0 38 38" fill="none">
<path d="M10 19.5L16 25.5L28 13" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"
style="stroke-dasharray:60;stroke-dashoffset:60;animation:checkDraw .6s .35s ease forwards;"/>
</svg>
</div>
</div>
<div style="margin-top:28px;padding:14px;background:#f0fdf4;border-radius:10px;font-size:12px;color:#64748b;">
Submitted on {{ now()->format('d M Y, H:i') }}
<div style="font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:#16a34a;margin-bottom:8px;">Quote Received</div>
<h1 style="font-size:26px;font-weight:800;color:#0f172a;margin-bottom:14px;line-height:1.25;">
Thank you,<br>{{ $invitation->supplier->name }}!
</h1>
<p style="font-size:15px;color:#475569;line-height:1.75;max-width:360px;margin:0 auto 24px;">
Your quote for <strong style="color:#0f172a;">{{ $invitation->purchaseRequest->request_number }}</strong>
has been successfully received. Our team will review all submitted quotes and get back to you shortly.
</p>
{{-- Details --}}
<div style="background:#f8fafc;border:1.5px solid #e2e8f0;border-radius:12px;padding:4px 20px;text-align:left;margin-bottom:24px;">
<div class="detail-row">
<span style="color:#64748b;">Reference</span>
<span style="font-weight:700;color:#0f172a;">{{ $invitation->purchaseRequest->request_number }}</span>
</div>
<div class="detail-row" style="border-top:1px solid #f1f5f9;">
<span style="color:#64748b;">Submitted</span>
<span style="font-weight:600;color:#0f172a;">{{ now()->format('d M Y, H:i') }}</span>
</div>
@if($invitation->purchaseRequest->project_name)
<div class="detail-row" style="border-top:1px solid #f1f5f9;">
<span style="color:#64748b;">Project</span>
<span style="font-weight:600;color:#0f172a;">{{ $invitation->purchaseRequest->project_name }}</span>
</div>
@endif
</div>
<p style="font-size:13px;color:#94a3b8;line-height:1.65;">
This link has now been closed. No further action is required.<br>
We appreciate your time and look forward to working with you.
</p>
</div>
<div class="footer-bar">
<span style="font-size:11px;color:#cbd5e1;font-weight:500;letter-spacing:.03em;">SteelERP · Procurement Portal</span>
</div>
</div>
</body>
</html>