Files
Kent-logistics-Laravel/resources/views/admin/invoice_edit.blade.php
2026-03-17 19:14:47 +05:30

851 lines
31 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@extends('admin.layouts.app')
@section('page-title', 'Edit Invoice')
@section('content')
<style>
:root {
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--success-gradient: linear-gradient(135deg, #10b981 0%, #059669 100%);
--warning-gradient: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
--danger-gradient: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
--glass-bg: rgba(255, 255, 255, 0.95);
--glass-border: rgba(255, 255, 255, 0.2);
--shadow-soft: 0 8px 32px rgba(0, 0, 0, 0.08);
--shadow-medium: 0 12px 48px rgba(0, 0, 0, 0.12);
--shadow-strong: 0 20px 60px rgba(0, 0, 0, 0.15);
--border-radius: 12px;
}
body {
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
font-family: "Inter", "Segoe UI", system-ui, sans-serif;
}
@keyframes fadeUp {
0% {
opacity: 0;
transform: translateY(20px) scale(0.98);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.glass-card {
background: var(--glass-bg);
backdrop-filter: blur(20px);
border-radius: var(--border-radius);
border: 1px solid var(--glass-border);
box-shadow: var(--shadow-strong);
position: relative;
overflow: hidden;
animation: fadeUp 0.6s ease;
margin-bottom: 1.5rem;
}
.glass-card::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
45deg,
rgba(102, 126, 234, 0.03),
rgba(118, 75, 162, 0.03) 50%,
rgba(16, 185, 129, 0.03)
);
pointer-events: none;
}
.card-header-compact {
background: var(--primary-gradient);
color: #fff;
padding: 1rem 1.5rem;
border: none;
position: relative;
overflow: hidden;
}
.card-header-compact h4 {
margin: 0;
font-weight: 700;
font-size: 1.3rem;
display: flex;
align-items: center;
gap: 10px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.card-body-compact {
padding: 1.5rem;
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
position: relative;
z-index: 1;
}
.form-grid-compact {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
margin-bottom: 1.5rem;
}
.form-group-compact {
position: relative;
}
.form-label-compact {
font-weight: 600;
color: #374151;
margin-bottom: 8px;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 8px;
}
.form-control-compact,
.form-select-compact {
border: 2px solid #e2e8f0;
border-radius: 8px;
padding: 10px 12px;
font-size: 0.9rem;
transition: all 0.3s ease;
background: #ffffff;
width: 100%;
}
.form-control-compact:focus,
.form-select-compact:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
outline: none;
}
.btn-compact {
padding: 10px 20px;
border-radius: 8px;
font-weight: 600;
font-size: 0.9rem;
transition: all 0.3s ease;
border: none;
display: inline-flex;
align-items: center;
gap: 6px;
text-decoration: none;
}
.btn-success-compact {
background: var(--success-gradient);
color: white;
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
}
.btn-success-compact:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
color: white;
}
.btn-primary-compact {
background: var(--primary-gradient);
color: white;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.btn-primary-compact:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
color: white;
}
.btn-danger-compact {
background: var(--danger-gradient);
color: white;
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
}
.btn-danger-compact:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(239, 68, 68, 0.4);
color: white;
}
.summary-grid-compact {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 1.5rem;
}
.summary-card-compact {
background: #ffffff;
padding: 1rem;
border-radius: 8px;
box-shadow: var(--shadow-soft);
border-left: 4px solid;
text-align: center;
}
.summary-card-compact.total {
border-left-color: #10b981;
}
.summary-card-compact.paid {
border-left-color: #3b82f6;
}
.summary-card-compact.remaining {
border-left-color: #f59e0b;
}
.summary-value-compact {
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 0.25rem;
}
.summary-label-compact {
font-size: 0.8rem;
color: #64748b;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.amount-breakdown-compact {
background: #ffffff;
padding: 1rem;
border-radius: 8px;
box-shadow: var(--shadow-soft);
margin-bottom: 1.5rem;
}
.breakdown-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0;
border-bottom: 1px solid #f1f5f9;
}
.breakdown-row:last-child {
border-bottom: none;
}
.breakdown-label {
font-weight: 600;
color: #374151;
font-size: 0.9rem;
}
.breakdown-value {
font-weight: 600;
font-size: 0.9rem;
}
.table-compact {
width: 100%;
border-collapse: collapse;
margin-bottom: 0;
font-size: 0.85rem;
}
.table-compact thead th {
background: var(--primary-gradient);
color: #ffffff;
padding: 0.75rem 1rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
font-size: 0.8rem;
border: none;
}
.table-compact tbody tr {
background: #ffffff;
transition: all 0.2s ease;
}
.table-compact tbody tr:hover {
background: #f8fafc;
}
.table-compact tbody td {
padding: 0.75rem 1rem;
border-bottom: 1px solid #e2e8f0;
vertical-align: middle;
}
.badge-compact {
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
}
@media (max-width: 768px) {
.glass-card {
margin: 0.5rem;
border-radius: 10px;
}
.card-body-compact {
padding: 1rem;
}
.form-grid-compact {
grid-template-columns: 1fr;
gap: 0.75rem;
}
.summary-grid-compact {
grid-template-columns: 1fr;
gap: 0.75rem;
}
.btn-compact {
width: 100%;
justify-content: center;
}
.table-compact {
font-size: 0.8rem;
}
}
</style>
<div class="container-fluid py-3">
{{-- Invoice Overview --}}
<div class="glass-card">
<div class="card-header-compact">
<h4>
<i class="fas fa-file-invoice me-2"></i>
Invoice Overview
</h4>
</div>
<div class="card-body-compact">
@include('admin.popup_invoice', ['invoice' => $invoice, 'shipment' => $shipment])
</div>
</div>
@if(false)
{{-- Edit Invoice Details card HIDDEN --}}
{{-- तुझा full header edit form इथे hidden ठेवलेला आहे --}}
@endif
@php
$totalPaid = $invoice->totalPaid();
$remaining = $invoice->remainingAmount();
$taxTypes = $invoice->chargeGroups
? $invoice->chargeGroups->pluck('tax_type')->filter()->unique()->values()
: collect([]);
$taxTypeLabel = 'None';
if ($taxTypes->count() === 1) {
if ($taxTypes[0] === 'gst') {
$taxTypeLabel = 'GST (CGST + SGST)';
} elseif ($taxTypes[0] === 'igst') {
$taxTypeLabel = 'IGST';
} else {
$taxTypeLabel = strtoupper($taxTypes[0]);
}
} elseif ($taxTypes->count() > 1) {
$parts = [];
if ($taxTypes->contains('gst')) {
$parts[] = 'GST (CGST + SGST)';
}
if ($taxTypes->contains('igst')) {
$parts[] = 'IGST';
}
$taxTypeLabel = implode(' + ', $parts);
}
@endphp
@if(false)
{{-- Amount Breakdown HIDDEN --}}
{{-- जुनं breakdown section इथे hidden आहे --}}
@endif
{{-- Summary cards --}}
<div class="summary-grid-compact">
<div class="summary-card-compact total">
<div class="summary-value-compact text-success" id="totalInvoiceWithGstCard">
{{ number_format($invoice->grand_total_with_charges, 2) }}
</div>
<div class="summary-label-compact">Grand Total (Charges + GST)</div>
</div>
<div class="summary-card-compact paid">
<div class="summary-value-compact text-primary" id="paidAmount">
{{ number_format($totalPaid, 2) }}
</div>
<div class="summary-label-compact">Total Paid</div>
</div>
<div class="summary-card-compact remaining">
<div class="summary-value-compact text-warning" id="remainingAmount">
{{ number_format(max(0, $remaining), 2) }}
</div>
<div class="summary-label-compact">Remaining</div>
</div>
</div>
{{-- Installment Management --}}
<div class="glass-card">
<div class="card-header-compact d-flex justify-content-between align-items-center">
<h4 class="mb-0">
<i class="fas fa-credit-card me-2"></i>Installment Payments
</h4>
@if($remaining > 0)
<button id="toggleInstallmentForm" class="btn-primary-compact btn-compact">
<i class="fas fa-plus-circle me-2"></i>Add Installment
</button>
@endif
</div>
<div class="card-body-compact">
{{-- Installment Form --}}
<div id="installmentForm" class="d-none mb-3">
<div class="glass-card" style="background: rgba(248, 250, 252, 0.8);">
<div class="card-header-compact" style="background: var(--success-gradient);">
<h4 class="mb-0">
<i class="fas fa-plus-circle me-2"></i>Add New Installment
</h4>
</div>
<div class="card-body-compact">
<form id="installmentSubmitForm">
@csrf
<div class="form-grid-compact">
<div class="form-group-compact">
<label class="form-label-compact">
<i class="fas fa-calendar-day"></i> Installment Date
</label>
<input type="date"
name="installment_date"
class="form-control-compact"
required>
</div>
<div class="form-group-compact">
<label class="form-label-compact">
<i class="fas fa-credit-card"></i> Payment Method
</label>
<select name="payment_method"
class="form-select-compact"
required>
<option value="cash">Cash</option>
<option value="bank">Bank Transfer</option>
<option value="upi">UPI</option>
<option value="cheque">Cheque</option>
</select>
</div>
<div class="form-group-compact">
<label class="form-label-compact">
<i class="fas fa-hashtag"></i> Reference No
</label>
<input type="text"
name="reference_no"
class="form-control-compact"
placeholder="Enter reference number">
</div>
<div class="form-group-compact">
<label class="form-label-compact">
<i class="fas fa-money-bill-wave"></i> Amount
</label>
<input type="number"
name="amount"
id="installmentAmount"
class="form-control-compact"
step="0.01"
min="1"
max="{{ max(0, $remaining) }}"
required
placeholder="Enter installment amount">
</div>
</div>
<div class="text-end mt-2">
<button type="submit"
class="btn-success-compact btn-compact"
id="installmentSubmitBtn">
<i class="fas fa-paper-plane me-2"></i>Submit Installment
</button>
</div>
</form>
</div>
</div>
</div>
{{-- Installment History --}}
<h6 class="fw-bold mb-2 text-dark">
<i class="fas fa-history me-2"></i>Installment History
</h6>
<div id="noInstallmentsMsg"
class="{{ $invoice->installments->count() ? 'd-none' : '' }} text-center text-muted fw-bold py-4">
No installments found. Click Add Installment to create one.
</div>
<div class="table-responsive">
<table class="table-compact">
<thead>
<tr>
<th>#</th>
<th>Date</th>
<th>Payment Method</th>
<th>Reference No</th>
<th>Amount</th>
<th>Action</th>
</tr>
</thead>
<tbody id="installmentTable">
@foreach($invoice->installments as $i)
<tr data-id="{{ $i->id }}">
<td class="fw-bold text-muted">{{ $loop->iteration }}</td>
<td>{{ $i->installment_date }}</td>
<td>
<span class="badge-compact bg-primary bg-opacity-10 text-primary">
{{ strtoupper($i->payment_method) }}
</span>
</td>
<td>
@if($i->reference_no)
<span class="text-muted">{{ $i->reference_no }}</span>
@else
<span class="text-muted">-</span>
@endif
</td>
<td class="fw-bold text-success">
{{ number_format($i->amount, 2) }}
</td>
<td>
<button class="btn-danger-compact btn-compact btn-sm deleteInstallment">
<i class="fas fa-trash me-1"></i>Delete
</button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
const toggleBtn = document.getElementById("toggleInstallmentForm");
const formBox = document.getElementById("installmentForm");
if (toggleBtn && formBox) {
toggleBtn.addEventListener("click", function (e) {
e.preventDefault();
formBox.classList.toggle("d-none");
});
}
const submitForm = document.getElementById("installmentSubmitForm");
const submitBtn = document.getElementById("installmentSubmitBtn");
const formatINR = (amt) => {
return Number(amt).toLocaleString("en-IN", {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
};
if (submitForm && submitBtn) {
submitForm.addEventListener("submit", function (e) {
e.preventDefault();
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Processing...';
submitBtn.disabled = true;
fetch("{{ route('admin.invoice.installment.store', $invoice->id) }}", {
method: "POST",
headers: {
"X-CSRF-TOKEN": submitForm.querySelector('input[name="_token"]').value,
"Accept": "application/json",
"X-Requested-With": "XMLHttpRequest",
},
body: new FormData(submitForm)
})
.then(async res => {
let data;
try {
data = await res.json();
} catch (e) {
throw new Error("Invalid server response.");
}
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
submitBtn.disabled = false;
if (!res.ok) {
const msg =
(data && data.message) ||
(data && data.errors && Object.values(data.errors)[0][0]) ||
"Something went wrong.";
alert(msg);
return;
}
if (data.status === "error") {
alert(data.message || "Something went wrong.");
return;
}
const table = document.getElementById("installmentTable");
const index = table.querySelectorAll("tr").length + 1;
document.getElementById("noInstallmentsMsg")?.classList.add("d-none");
const pmMethod = data.installment.payment_method
? data.installment.payment_method.toUpperCase()
: "-";
const refNo = data.installment.reference_no
? `<span class="text-muted">${data.installment.reference_no}</span>`
: '<span class="text-muted">-</span>';
table.insertAdjacentHTML("beforeend", `
<tr data-id="${data.installment.id}">
<td class="fw-bold text-muted">${index}</td>
<td>${data.installment.installment_date}</td>
<td>
<span class="badge-compact bg-primary bg-opacity-10 text-primary">
${pmMethod}
</span>
</td>
<td>${refNo}</td>
<td class="fw-bold text-success">
₹${formatINR(data.installment.amount)}
</td>
<td>
<button class="btn-danger-compact btn-compact btn-sm deleteInstallment">
<i class="fas fa-trash me-1"></i>Delete
</button>
</td>
</tr>
`);
if (document.getElementById("paidAmount")) {
document.getElementById("paidAmount").textContent = "" + formatINR(data.totalPaid);
}
if (document.getElementById("remainingAmount")) {
document.getElementById("remainingAmount").textContent = "" + formatINR(data.remaining);
}
if (document.getElementById("baseAmount")) {
document.getElementById("baseAmount").textContent = "" + formatINR(data.chargeGroupsTotal);
}
if (document.getElementById("gstAmount")) {
document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount);
}
if (document.getElementById("totalInvoiceWithGst")) {
document.getElementById("totalInvoiceWithGst").textContent =
"" + formatINR(data.grandTotal);
}
const totalCard = document.getElementById("totalInvoiceWithGstCard");
if (totalCard) {
totalCard.textContent = "" + formatINR(data.grandTotal);
}
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
if (paidCard) paidCard.textContent = "" + formatINR(data.totalPaid);
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
if (remainingCard) remainingCard.textContent = "" + formatINR(data.remaining);
const fsTotal = document.getElementById("finalSummaryTotalPaid");
if (fsTotal) fsTotal.textContent = "" + formatINR(data.totalPaid);
const fsRemaining = document.getElementById("finalSummaryRemaining");
if (fsRemaining) {
fsRemaining.textContent = "" + formatINR(data.remaining);
fsRemaining.style.color = data.remaining > 0 ? "#ef4444" : "#10b981";
}
const fsRemainingLabel = document.getElementById("finalSummaryRemainingLabel");
if (fsRemainingLabel) {
fsRemainingLabel.style.color = data.remaining > 0 ? "#ef4444" : "#10b981";
fsRemainingLabel.innerHTML = `<i class="fas fa-${data.remaining > 0 ? 'exclamation-circle' : 'check-circle'} me-1" style="font-size:0.8rem;"></i>Remaining`;
}
if (data.newStatus && typeof updateStatusBadge === "function") {
updateStatusBadge(data.newStatus);
}
submitForm.reset();
if (data.isCompleted && toggleBtn) {
toggleBtn.remove();
formBox.classList.add("d-none");
}
alert(data.message || "Installment added successfully.");
})
.catch((err) => {
console.error("Installment submit error:", err);
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
submitBtn.disabled = false;
alert(err.message || "Something went wrong. Please try again.");
});
});
}
document.addEventListener("click", function (e) {
if (!e.target.classList.contains("deleteInstallment") &&
!e.target.closest(".deleteInstallment")) {
return;
}
e.preventDefault();
if (!confirm("Are you sure you want to delete this installment?")) {
return;
}
const btn = e.target.closest(".deleteInstallment");
const row = btn.closest("tr");
const id = row.getAttribute("data-id");
fetch("{{ url('admin/installment') }}/" + id, {
method: "DELETE",
headers: {
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').getAttribute("content"),
"Accept": "application/json",
"X-Requested-With": "XMLHttpRequest",
},
})
.then(async res => {
let data;
try { data = await res.json(); } catch(e) { throw new Error("Invalid server response."); }
if (!res.ok || data.status !== "success") {
const msg = data && data.message ? data.message : "Something went wrong. Please try again.";
throw new Error(msg);
}
row.style.opacity = 0;
setTimeout(() => row.remove(), 300);
if (document.getElementById("paidAmount")) {
document.getElementById("paidAmount").textContent = "" + formatINR(data.totalPaid);
}
if (document.getElementById("remainingAmount")) {
document.getElementById("remainingAmount").textContent = "" + formatINR(data.remaining);
}
if (document.getElementById("baseAmount")) {
document.getElementById("baseAmount").textContent = "" + formatINR(data.chargeGroupsTotal);
}
if (document.getElementById("gstAmount")) {
document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount);
}
if (document.getElementById("totalInvoiceWithGst")) {
document.getElementById("totalInvoiceWithGst").textContent =
"" + formatINR(data.grandTotal);
}
const totalCard = document.getElementById("totalInvoiceWithGstCard");
if (totalCard) {
totalCard.textContent = "" + formatINR(data.grandTotal);
}
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
if (paidCard) paidCard.textContent = "" + formatINR(data.totalPaid);
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
if (remainingCard) remainingCard.textContent = "" + formatINR(data.remaining);
const fsTotal = document.getElementById("finalSummaryTotalPaid");
if (fsTotal) fsTotal.textContent = "" + formatINR(data.totalPaid);
const fsRemaining = document.getElementById("finalSummaryRemaining");
if (fsRemaining) {
fsRemaining.textContent = "" + formatINR(data.remaining);
fsRemaining.style.color = data.remaining > 0 ? "#ef4444" : "#10b981";
}
const fsRemainingLabel = document.getElementById("finalSummaryRemainingLabel");
if (fsRemainingLabel) {
fsRemainingLabel.style.color = data.remaining > 0 ? "#ef4444" : "#10b981";
fsRemainingLabel.innerHTML = `<i class="fas fa-${data.remaining > 0 ? 'exclamation-circle' : 'check-circle'} me-1" style="font-size:0.8rem;"></i>Remaining`;
}
if (data.newStatus && typeof updateStatusBadge === "function") {
updateStatusBadge(data.newStatus);
}
if (data.isZero) {
document.getElementById("noInstallmentsMsg")?.classList.remove("d-none");
}
alert(data.message || "Installment deleted.");
})
.catch(err => {
alert(err.message || "Something went wrong. Please try again.");
});
});
// Header AJAX (सध्या card hidden आहे, तरीही ठेवलेलं)
const headerForm = document.getElementById('invoiceHeaderForm');
const headerBtn = document.getElementById('btnHeaderSave');
const headerMsg = document.getElementById('headerUpdateMsg');
if (headerForm && headerBtn) {
headerForm.addEventListener('submit', function(e) {
e.preventDefault();
headerMsg.textContent = '';
headerBtn.disabled = true;
headerBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Saving...';
const formData = new FormData(headerForm);
fetch(headerForm.action, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
body: formData
})
.then(async res => {
let data;
try { data = await res.json(); } catch(e) {}
if (!res.ok) {
if (data && data.errors) {
const firstError = Object.values(data.errors)[0][0] ?? 'Validation error.';
throw new Error(firstError);
}
throw new Error(data && data.message ? data.message : 'Failed to update invoice.');
}
headerMsg.textContent = 'Invoice header updated.';
headerMsg.classList.remove('text-danger');
headerMsg.classList.add('text-light');
const status = document.getElementById('statusSelect')?.value;
const badge = document.querySelector('.status-badge');
if (badge && status) {
badge.classList.remove('status-paid','status-pending','status-overdue','status-default');
if (status === 'paid') badge.classList.add('status-paid');
else if (status === 'pending') badge.classList.add('status-pending');
else if (status === 'overdue') badge.classList.add('status-overdue');
else badge.classList.add('status-default');
badge.innerHTML = status.charAt(0).toUpperCase() + status.slice(1);
}
})
.catch(err => {
headerMsg.textContent = err.message || 'Error updating invoice.';
headerMsg.classList.remove('text-light');
headerMsg.classList.add('text-warning');
})
.finally(() => {
headerBtn.disabled = false;
headerBtn.innerHTML = '<i class="fas fa-save me-2"></i>Update Invoice';
});
});
}
});
</script>
@endsection