Files
Kent-logistics-Laravel/resources/views/admin/popup_invoice.blade.php
Abhishek Mali 43b1a64911 ajax update
2026-03-12 11:48:42 +05:30

1690 lines
65 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.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Professional Invoice</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--primary: #0f172a;
--secondary: #3b82f6;
--accent: #f59e0b;
--success: #10b981;
--danger: #ef4444;
--warning: #f59e0b;
--light: #f8fafc;
--surface: #ffffff;
--surface2: #f1f5f9;
--border: #e2e8f0;
--text-muted: #64748b;
--text-secondary: #475569;
--radius: 12px;
--radius-sm: 8px;
--shadow: 0 1px 3px rgba(0,0,0,0.08), 0 4px 16px rgba(0,0,0,0.06);
--shadow-lg: 0 8px 32px rgba(0,0,0,0.1);
}
* { box-sizing: border-box; }
body {
font-family: 'Plus Jakarta Sans', sans-serif;
background: linear-gradient(135deg, #e0f2fe 0%, #f0fdf4 50%, #fef9c3 100%);
min-height: 100vh;
color: var(--primary);
padding: 2rem 1rem;
}
/* ── CONTAINER ── */
.invoice-container {
max-width: 1400px;
margin: 0 auto;
background: var(--surface);
border-radius: 20px;
box-shadow: var(--shadow-lg);
overflow: hidden;
border: 1px solid var(--border);
}
/* ── TOP ACCENT BAR ── */
.invoice-accent-bar {
height: 5px;
background: linear-gradient(90deg, #3b82f6 0%, #8b5cf6 40%, #10b981 70%, #f59e0b 100%);
}
/* ── HEADER ── */
.invoice-header {
padding: 2rem 2.5rem 1.5rem;
background: var(--surface);
border-bottom: 1px solid var(--border);
position: relative;
}
.invoice-title {
font-size: 2rem;
font-weight: 800;
letter-spacing: -0.03em;
color: var(--primary);
line-height: 1;
margin-bottom: 0.25rem;
}
.invoice-number {
font-family: 'JetBrains Mono', monospace;
font-size: 0.9rem;
font-weight: 600;
color: var(--text-muted);
letter-spacing: 0.04em;
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-size: 0.8rem;
font-weight: 700;
padding: 0.5rem 1.1rem;
border-radius: 50px;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.status-paid { background: #dcfce7; color: #166534; }
.status-overdue { background: #fee2e2; color: #991b1b; }
.status-pending { background: #fef3c7; color: #92400e; }
.status-default { background: #f1f5f9; color: #475569; }
/* ── ID BOXES ── */
.id-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
padding: 1.5rem 2.5rem;
background: var(--surface2);
border-bottom: 1px solid var(--border);
}
.id-box {
background: var(--surface);
border-radius: var(--radius);
padding: 1.1rem 1.25rem;
border: 1px solid var(--border);
box-shadow: 0 1px 4px rgba(0,0,0,0.05);
display: flex;
align-items: center;
gap: 1rem;
transition: box-shadow 0.2s;
}
.id-box:hover { box-shadow: var(--shadow); }
.id-icon-wrap {
width: 42px;
height: 42px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
flex-shrink: 0;
}
.id-icon-blue { background: #eff6ff; color: #3b82f6; }
.id-icon-green { background: #f0fdf4; color: #10b981; }
.id-label {
font-size: 0.68rem;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.08em;
margin-bottom: 0.2rem;
}
.id-value {
font-family: 'JetBrains Mono', monospace;
font-size: 0.92rem;
font-weight: 600;
color: var(--primary);
}
/* ── DATES ── */
.date-strip {
padding: 1.5rem 2.5rem;
border-bottom: 1px solid var(--border);
}
.date-row {
display: flex;
align-items: center;
gap: 1rem;
}
.date-card {
flex: 1;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1rem 1.25rem;
display: flex;
align-items: center;
gap: 0.85rem;
box-shadow: 0 1px 4px rgba(0,0,0,0.04);
}
.date-icon-wrap {
width: 38px;
height: 38px;
border-radius: 9px;
background: #eff6ff;
color: #3b82f6;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.9rem;
flex-shrink: 0;
}
.date-label {
font-size: 0.68rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--text-muted);
margin-bottom: 0.2rem;
}
.date-value {
font-weight: 700;
font-size: 0.95rem;
color: var(--primary);
}
.date-value.overdue { color: #ef4444; }
.date-arrow {
width: 36px;
height: 36px;
border-radius: 50%;
background: var(--surface2);
border: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
font-size: 0.8rem;
flex-shrink: 0;
}
/* ── SECTION PANEL ── */
.panel {
margin: 1.5rem 2.5rem;
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
box-shadow: 0 1px 4px rgba(0,0,0,0.04);
}
.panel-header {
background: var(--surface2);
padding: 0.85rem 1.25rem;
border-bottom: 1px solid var(--border);
font-weight: 700;
font-size: 0.88rem;
color: var(--primary);
display: flex;
align-items: center;
gap: 0.5rem;
}
.panel-header i {
color: var(--secondary);
font-size: 0.85rem;
}
.panel-body {
padding: 1.25rem;
background: var(--surface);
}
/* ── CUSTOMER ── */
.customer-name {
font-size: 1.05rem;
font-weight: 700;
color: var(--secondary);
margin-bottom: 0.4rem;
}
.customer-detail {
font-size: 0.85rem;
color: var(--text-secondary);
margin-bottom: 0.3rem;
display: flex;
gap: 0.4rem;
}
.customer-detail strong { color: var(--primary); font-weight: 600; }
/* ── TABLE ── */
.invoice-table {
width: 100%;
min-width: 1100px;
border-collapse: collapse;
font-size: 0.83rem;
}
/* Items table specifically even wider */
.items-table {
min-width: 1300px;
}
.invoice-table thead tr {
background: var(--surface2);
}
.invoice-table thead th {
padding: 0.85rem 1rem;
font-size: 0.71rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-muted);
border-bottom: 2px solid var(--border);
white-space: nowrap;
}
.invoice-table tbody tr {
border-bottom: 1px solid var(--border);
transition: background 0.15s;
}
.invoice-table tbody tr:hover { background: #f0f7ff; }
.invoice-table tbody tr:last-child { border-bottom: none; }
.invoice-table tbody td {
padding: 0.85rem 1rem;
color: var(--text-secondary);
white-space: nowrap;
}
/* Description column can wrap */
.invoice-table tbody td.desc-col {
white-space: normal;
min-width: 180px;
}
.invoice-table tbody tr.grouped-item-row {
background: #f8f9ff;
}
.badge-shop {
display: inline-block;
padding: 0.2rem 0.55rem;
border-radius: 5px;
background: var(--surface2);
border: 1px solid var(--border);
font-family: 'JetBrains Mono', monospace;
font-size: 0.72rem;
font-weight: 600;
color: var(--text-secondary);
}
.price-green { color: #10b981; font-weight: 700; }
.price-blue { color: #3b82f6; font-weight: 700; }
/* ── ACTION BAR ── */
.action-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem 1.25rem;
border-top: 1px solid var(--border);
background: var(--surface2);
}
.action-bar small { color: var(--text-muted); font-size: 0.8rem; }
.action-bar span { font-weight: 700; color: var(--secondary); }
/* ── BUTTONS ── */
.btn-create-group {
display: inline-flex;
align-items: center;
gap: 0.4rem;
background: var(--secondary);
color: white;
border: none;
border-radius: 8px;
padding: 0.45rem 1rem;
font-size: 0.8rem;
font-weight: 600;
cursor: pointer;
transition: background 0.2s, opacity 0.2s;
}
.btn-create-group:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-create-group:not(:disabled):hover { background: #2563eb; }
/* ── CHARGE GROUP FORM PANEL ── */
.cg-panel {
margin: 0 2.5rem 1.5rem;
border: 2px solid #3b82f6;
border-radius: var(--radius);
overflow: hidden;
box-shadow: 0 0 0 4px rgba(59,130,246,0.08);
}
.cg-panel-header {
background: linear-gradient(135deg, #1d4ed8, #3b82f6);
padding: 0.9rem 1.25rem;
color: white;
font-weight: 700;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.cg-body { padding: 1.25rem; background: var(--surface); }
.form-label-custom {
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--text-muted);
margin-bottom: 0.35rem;
display: block;
}
.form-control-custom,
.form-select-custom {
width: 100%;
padding: 0.55rem 0.85rem;
border: 1.5px solid var(--border);
border-radius: 8px;
font-size: 0.85rem;
font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--primary);
background: var(--surface);
transition: border-color 0.15s, box-shadow 0.15s;
outline: none;
}
.form-control-custom:focus,
.form-select-custom:focus {
border-color: var(--secondary);
box-shadow: 0 0 0 3px rgba(59,130,246,0.12);
}
.basis-box {
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 9px;
padding: 1rem;
height: 100%;
}
.basis-box-label {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--text-muted);
margin-bottom: 0.5rem;
}
.basis-value-display {
font-family: 'JetBrains Mono', monospace;
font-size: 1.2rem;
font-weight: 700;
color: var(--secondary);
}
.basis-label-display {
font-size: 0.75rem;
color: var(--text-muted);
margin-left: 0.5rem;
}
.basis-hint { font-size: 0.75rem; color: var(--text-muted); margin-top: 0.35rem; }
.suggested-total {
font-family: 'JetBrains Mono', monospace;
font-weight: 700;
color: #10b981;
}
/* ── CHARGE GROUPS TABLE ── */
.cg-groups-panel {
margin: 0 2.5rem 1.5rem;
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
.cg-groups-header {
background: var(--primary);
color: white;
padding: 0.85rem 1.25rem;
font-weight: 700;
font-size: 0.88rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.summary-card-compact {
margin: 1.5rem 2.5rem 1.5rem;
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
box-shadow: var(--shadow-lg);
background: var(--surface);
}
/* Header full width, curved top corners */
.summary-header-compact {
background: var(--primary);
color: #fff;
padding: 0.8rem 1.25rem;
font-weight: 700;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.5rem;
width: 100%;
border-top-left-radius: var(--radius);
border-top-right-radius: var(--radius);
}
/* Icon थोडा highlight */
.summary-header-compact i {
font-size: 0.9rem;
opacity: 0.9;
}
.summary-body-compact {
padding: 1rem 1.25rem 0.9rem;
}
.summary-row-compact {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.45rem 0;
border-bottom: 1px solid var(--border);
font-size: 0.88rem;
}
.summary-row-compact:last-child {
border-bottom: none;
}
.summary-row-compact .label {
color: var(--text-secondary);
font-weight: 500;
}
.summary-row-compact .value {
font-weight: 700;
color: var(--primary);
}
.summary-row-compact.total .label {
font-size: 0.95rem;
font-weight: 700;
}
.summary-row-compact.total .value {
font-size: 1.05rem;
color: #10b981;
}
.summary-row-compact.muted .value {
color: var(--text-muted);
}
/* ── FOOTER ── */
.invoice-footer {
padding: 1.5rem 2.5rem;
border-top: 1px solid var(--border);
text-align: center;
background: var(--surface2);
}
.btn-download {
display: inline-flex;
align-items: center;
gap: 0.45rem;
background: var(--secondary);
color: white;
border: none;
border-radius: 9px;
padding: 0.65rem 1.4rem;
font-size: 0.88rem;
font-weight: 600;
text-decoration: none;
transition: background 0.2s;
margin-right: 0.5rem;
}
.btn-download:hover { background: #2563eb; color: white; }
.btn-share {
display: inline-flex;
align-items: center;
gap: 0.45rem;
background: #10b981;
color: white;
border: none;
border-radius: 9px;
padding: 0.65rem 1.4rem;
font-size: 0.88rem;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.btn-share:hover { background: #059669; }
.footer-note {
margin-top: 1.25rem;
color: var(--text-muted);
font-size: 0.82rem;
}
/* ── CHECKBOX STYLE ── */
input[type="checkbox"] {
width: 15px;
height: 15px;
accent-color: var(--secondary);
cursor: pointer;
}
/* ── RESPONSIVE ── */
@media (max-width: 768px) {
body { padding: 0.75rem 0.5rem; }
.invoice-header,
.date-strip,
.summary-wrap,
.invoice-footer { padding-left: 1.25rem; padding-right: 1.25rem; }
.id-grid { grid-template-columns: 1fr; padding: 1rem 1.25rem; }
.date-row { flex-direction: column; }
.panel, .cg-panel, .cg-groups-panel { margin-left: 1.25rem; margin-right: 1.25rem; }
.invoice-table { font-size: 0.75rem; }
}
/* ── GROUP ITEMS HIDDEN ROW ── */
.cg-items-row { background: #f8faff; }
.cg-toggle-btn {
display: inline-flex;
align-items: center;
gap: 0.3rem;
padding: 0.3rem 0.75rem;
border: 1.5px solid #3b82f6;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 600;
color: #3b82f6;
background: transparent;
cursor: pointer;
transition: all 0.15s;
}
.cg-toggle-btn:hover { background: #eff6ff; }
/* ── DIVIDER ── */
.section-divider { height: 1px; background: var(--border); margin: 0 2.5rem; }
</style>
</head>
<body>
<div class="invoice-container">
<!-- ACCENT BAR -->
<div class="invoice-accent-bar"></div>
@php
$showActions = $showActions ?? true;
@endphp
<!-- ═══════════════════════════ HEADER ═══════════════════════════ -->
<div class="invoice-header">
<div class="d-flex justify-content-between align-items-start flex-wrap gap-3">
<div>
<div class="invoice-title">
<i class="fas fa-file-invoice" style="color:#3b82f6;font-size:1.5rem;vertical-align:middle;margin-right:0.4rem;"></i>INVOICE
</div>
<div class="invoice-number mt-1">{{ $invoice->invoice_number }}</div>
</div>
<div>
@if($invoice->status == 'paid')
<span class="status-badge status-paid">
<i class="fas fa-check-circle"></i> Paid
</span>
@elseif($invoice->status == 'overdue')
<span class="status-badge status-overdue">
<i class="fas fa-exclamation-circle"></i> Overdue
</span>
@elseif($invoice->status == 'pending')
<span class="status-badge status-pending">
<i class="fas fa-clock"></i> Pending
</span>
@else
<span class="status-badge status-default">
<i class="fas fa-question-circle"></i> {{ ucfirst($invoice->status) }}
</span>
@endif
</div>
</div>
</div>
<!-- ═══════════════════════════ CONTAINER + INVOICE DATE + DUE DATE (ONE ROW) ═══════════════════════════ -->
<div class="date-strip">
<div class="date-row">
{{-- Container ID --}}
<div class="date-card" style="flex: 1.2;">
<div class="date-icon-wrap" style="background:#ecfeff;color:#0e7490;">
<i class="fas fa-box"></i>
</div>
<div>
<div class="date-label">Container ID</div>
<div class="date-value">
@if($invoice->container && $invoice->container->container_number)
{{ $invoice->container->container_number }}
@elseif($invoice->container_id)
{{ $invoice->container_id }}
@else
N/A
@endif
</div>
</div>
</div>
{{-- छोटा arrow --}}
<div class="date-arrow">
<i class="fas fa-arrow-right"></i>
</div>
{{-- Invoice Date --}}
<div class="date-card">
<div class="date-icon-wrap">
<i class="fas fa-calendar-alt"></i>
</div>
<div>
<div class="date-label">Invoice Date</div>
<div class="date-value">
{{ \Carbon\Carbon::parse($invoice->invoice_date)->format('M d, Y') }}
</div>
</div>
</div>
{{-- दुसरा arrow --}}
<div class="date-arrow">
<i class="fas fa-arrow-right"></i>
</div>
{{-- Due Date --}}
<div class="date-card">
<div class="date-icon-wrap" style="background:#fff7ed;color:#f59e0b;">
<i class="fas fa-clock"></i>
</div>
<div>
<div class="date-label">Due Date</div>
<div class="date-value {{ $invoice->status == 'overdue' ? 'overdue' : '' }}">
{{ \Carbon\Carbon::parse($invoice->due_date)->format('M d, Y') }}
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════ CUSTOMER ═══════════════════════════ -->
<div class="panel">
<div class="panel-header">
<i class="fas fa-user-circle"></i> Customer Details
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<div class="customer-name">{{ $invoice->customer_name }}</div>
@if($invoice->company_name)
<div class="customer-detail"><strong>Company:</strong> {{ $invoice->company_name }}</div>
@endif
<div class="customer-detail"><strong>Mobile:</strong> {{ $invoice->customer_mobile }}</div>
<div class="customer-detail"><strong>Email:</strong> {{ $invoice->customer_email }}</div>
</div>
<div class="col-md-6">
<div class="customer-detail"><strong>Address:</strong></div>
<div class="customer-detail">{{ $invoice->customer_address }}</div>
<div class="customer-detail"><strong>Pincode:</strong> {{ $invoice->pincode }}</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════ ITEMS TABLE ═══════════════════════════ -->
@php
$isEmbedded = isset($embedded) && $embedded;
@endphp
<div class="panel">
<div class="panel-header">
<i class="fas fa-list"></i> Invoice Items
</div>
<div class="table-responsive">
<table class="invoice-table items-table">
<thead>
<tr>
<th class="text-center" style="width:44px;">
<input type="checkbox" id="selectAllItems">
</th>
<th class="text-center" style="min-width:50px;">#</th>
<th style="min-width:200px;">Description</th>
<th class="text-center" style="min-width:75px;">CTN</th>
<th class="text-center" style="min-width:75px;">QTY</th>
<th class="text-center" style="min-width:95px;">TTL/QTY</th>
<th class="text-center" style="min-width:75px;">Unit</th>
<th class="text-center" style="min-width:110px;">Price</th>
<th class="text-center" style="min-width:125px;">TTL Amount</th>
<th class="text-center" style="min-width:85px;">CBM</th>
<th class="text-center" style="min-width:95px;">TTL CBM</th>
<th class="text-center" style="min-width:80px;">KG</th>
<th class="text-center" style="min-width:95px;">TTL KG</th>
<th class="text-center" style="min-width:95px;">Shop No</th>
</tr>
</thead>
<tbody>
@foreach($invoice->items as $i => $item)
@php
$alreadyGrouped = in_array($item->id, $groupedItemIds ?? []);
@endphp
<tr class="{{ $alreadyGrouped ? 'grouped-item-row' : '' }}">
<td class="text-center">
<input type="checkbox"
class="item-select-checkbox"
name="item_ids[]"
value="{{ $item->id }}"
{{ $alreadyGrouped ? 'disabled' : '' }}>
</td>
<td class="text-center" style="font-weight:600;color:var(--text-muted);">{{ $i + 1 }}</td>
<td class="desc-col" style="font-weight:600;color:var(--primary);">{{ $item->description }}</td>
<td class="text-center">{{ $item->ctn }}</td>
<td class="text-center">{{ $item->qty }}</td>
<td class="text-center" style="font-weight:700;">{{ $item->ttl_qty }}</td>
<td class="text-center">{{ $item->unit }}</td>
@if($isEmbedded)
<td class="text-center" style="min-width:120px;">
<input type="number" step="0.01" min="0"
name="items[{{ $item->id }}][price]"
value="{{ old('items.' . $item->id . '.price', $item->price) }}"
class="form-control form-control-sm text-end">
</td>
<td class="text-center" style="min-width:140px;">
<input type="number" step="0.01" min="0"
name="items[{{ $item->id }}][ttl_amount]"
value="{{ old('items.' . $item->id . '.ttl_amount', $item->ttl_amount) }}"
class="form-control form-control-sm text-end">
</td>
@else
<td class="text-center price-green">{{ number_format($item->price, 2) }}</td>
<td class="text-center price-blue">{{ number_format($item->ttl_amount, 2) }}</td>
@endif
<td class="text-center">{{ $item->cbm }}</td>
<td class="text-center">{{ $item->ttl_cbm }}</td>
<td class="text-center">{{ $item->kg }}</td>
<td class="text-center">{{ $item->ttl_kg }}</td>
<td class="text-center"><span class="badge-shop">{{ $item->shop_no }}</span></td>
</tr>
@endforeach
@if($invoice->items->isEmpty())
<tr>
<td colspan="15" class="text-center py-4" style="color:var(--text-muted);font-weight:600;">
<i class="fas fa-inbox me-2" style="font-size:1.3rem;opacity:.4;"></i><br>No invoice items found.
</td>
</tr>
@endif
</tbody>
</table>
</div>
<!-- ACTION BAR -->
<div class="action-bar">
<small>Selected items for charge group: <span id="selectedItemsCount">0</span></small>
<button type="button" id="btnCreateChargeGroup" class="btn-create-group" disabled>
<i class="fas fa-layer-group"></i> Create Charge Group
</button>
</div>
@if($isEmbedded)
<div class="text-end p-3" style="border-top:1px solid var(--border);">
<button type="submit" class="btn btn-primary btn-sm">
<i class="fas fa-save me-1"></i> Update Items & Recalculate
</button>
</div>
</form>
@endif
</div>
<!-- ═══════════════════════════ CHARGE GROUP FORM ═══════════════════════════ -->
<div id="chargeGroupBox" class="cg-panel d-none">
<div class="cg-panel-header">
<i class="fas fa-layer-group"></i> Create Charge Group for Selected Items
</div>
<div class="cg-body">
<form method="POST" id="chargeGroupForm"
action="{{ route('admin.invoices.charge-group.store', $invoice->id) }}">
@csrf
<div class="row g-3 mb-3">
<div class="col-md-4">
<label class="form-label-custom">Group Name</label>
<input type="text" class="form-control-custom" id="cgGroupName"
name="groupname" placeholder="e.g. Group #1">
</div>
<div class="col-md-4">
<label class="form-label-custom">Price based on</label>
<select class="form-select-custom" id="cgBasis">
<option value="" selected disabled>Select basis</option>
<option value="ttl_qty">TTL/QTY</option>
<option value="amount">AMOUNT</option>
<option value="ttl_cbm">TTL CBM</option>
<option value="ttl_kg">TTL KG</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label-custom">Rate per selected basis</label>
<input type="number" step="0.0001" class="form-control-custom"
id="cgRate" placeholder="Enter rate">
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-4">
<label class="form-label-custom">Tax Type</label>
<select class="form-select-custom" id="cgTaxType" name="tax_type">
<option value="" selected disabled>Select tax type</option>
<!-- <option value="none">No Tax</option> -->
<option value="gst">GST (CGST+SGST)</option>
<option value="igst">IGST</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label-custom">GST %</label>
<input type="number" step="0.01"
class="form-control-custom"
id="cgGstPercent"
name="gst_percent"
placeholder="e.g. 18">
</div>
<div class="col-md-4">
<label class="form-label-custom">Total charges with GST</label>
<input type="number" step="0.01"
class="form-control-custom"
id="cgTotalWithGst"
name="total_with_gst"
placeholder="Auto calculated"
readonly>
</div>
</div>
{{-- Hidden fields (controller validate साठी names match केलेत) --}}
<input type="hidden" name="basistype" id="cgBasisTypeInput">
<input type="hidden" name="basisvalue" id="cgBasisValueInput">
<input type="hidden" name="rate" id="cgRateHidden">
<input type="hidden" name="autototal" id="cgAutoTotal">
{{-- itemids[] हे JS मधून append होतात (checkbox selected items) --}}
<div class="row g-3 mb-3">
<!-- LEFT: Total basis value -->
<div class="col-md-6">
<div class="basis-box">
<div class="basis-box-label">Total basis value</div>
<div style="display:flex;align-items:baseline;gap:0.4rem;">
<span class="basis-value-display" id="cgBasisValue">0</span>
<span class="basis-label-display" id="cgBasisLabel"></span>
</div>
<div class="basis-hint" id="cgBasisHint">
Select basis to see total TTLQTY, CBM, KG or Amount.
</div>
</div>
</div>
<!-- RIGHT: Total charges (admin input) -->
<div class="col-md-6">
<div class="basis-box">
<div class="basis-box-label">Total charges (admin input)</div>
<input type="number"
step="0.01"
class="form-control-custom"
id="cgTotalChargeInput"
placeholder="Enter total charges"
readonly>
<div class="basis-hint" style="margin-top:0.5rem">
Suggested Rate × basis:
<span class="suggested-total" id="cgSuggestedTotal">0</span>
</div>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label-custom" style="margin-bottom:0.5rem;">Selected items in this group</label>
<div class="table-responsive" style="border:1px solid var(--border);border-radius:9px;overflow:hidden;">
<table class="invoice-table">
<thead>
<tr>
<th>#</th>
<th>Description</th>
<th class="text-center">QTY</th>
<th class="text-center">TTL QTY</th>
<th class="text-center">CBM</th>
<th class="text-center">TTL CBM</th>
<th class="text-center">KG</th>
<th class="text-center">TTL KG</th>
<th class="text-center">TTL Amount</th>
</tr>
</thead>
<tbody id="cgItemsTableBody"></tbody>
</table>
</div>
</div>
<div class="d-flex justify-content-end gap-2">
<button type="button" id="cgCancelBtn"
style="padding:0.5rem 1.1rem;border:1.5px solid var(--border);border-radius:8px;background:transparent;font-size:0.85rem;font-weight:600;cursor:pointer;color:var(--text-secondary);">
Cancel
</button>
<button type="submit" id="cgSaveBtn"
style="display:inline-flex;align-items:center;gap:0.4rem;padding:0.5rem 1.25rem;border:none;border-radius:8px;background:#10b981;color:white;font-size:0.85rem;font-weight:700;cursor:pointer;">
<i class="fas fa-save"></i> Save Charge Group
</button>
</div>
</form>
</div>
</div>
{{-- ═══════════════════════════ CHARGE GROUPS LIST ═══════════════════════════ --}}
@if($invoice->chargeGroups->isNotEmpty())
<div class="cg-groups-panel">
<div class="cg-groups-header">
<i class="fas fa-layer-group"></i> Charge Groups
</div>
<div class="table-responsive">
<table class="invoice-table">
<thead>
<tr>
<th>#</th>
<th>Group Name</th>
<th>Basis</th>
<th class="text-end">Basis Value</th>
<th class="text-end">Rate</th>
<th class="text-end">Total Charge</th>
<th class="text-end">GST %</th>
<th class="text-center">Tax Type</th>
<th class="text-end">Total With GST</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
@foreach($invoice->chargeGroups as $index => $group)
@php
// Base total आणि GST calculation
$groupBaseTotal = $group->total_charge ?? 0;
$groupTotalWithGst = $group->total_with_gst ?? $groupBaseTotal;
$groupGstAmount = max(0, $groupTotalWithGst - $groupBaseTotal);
$groupGstPercent = $group->gst_percent ?? 0;
@endphp
{{-- Main group row --}}
<tr>
<td>{{ $index + 1 }}</td>
<td style="font-weight:600;">
{{ $group->group_name ?? 'Group '.$group->id }}
</td>
<td>
<span style="text-transform:uppercase;
font-size:0.75rem;
font-weight:700;
color:var(--text-muted);">
{{ $group->basis_type }}
</span>
</td>
<td class="text-end"
style="font-family:'JetBrains Mono',monospace;font-size:0.82rem;">
{{ number_format($group->basis_value, 3) }}
</td>
<td class="text-end"
style="font-family:'JetBrains Mono',monospace;font-size:0.82rem;">
{{ number_format($group->rate, 2) }}
</td>
{{-- Base total without GST --}}
<td class="text-end price-blue">
{{ number_format($groupBaseTotal, 2) }}
</td>
{{-- GST % --}}
<td class="text-end"
style="font-family:'JetBrains Mono',monospace;font-size:0.82rem;">
{{ number_format($groupGstPercent, 2) }}%
</td>
{{-- Group tax type --}}
<td class="text-center">
{{ $group->tax_type ?? '-' }}
</td>
{{-- Group total with GST --}}
<td class="text-end" style="font-weight:700;color:#16a34a;">
{{ number_format($groupTotalWithGst, 2) }}
</td>
<td class="text-center">
<button type="button"
class="cg-toggle-btn cg-toggle-items"
data-group-id="{{ $group->id }}">
<i class="fas fa-eye"></i> View
</button>
</td>
</tr>
{{-- Hidden items row --}}
<tr class="cg-items-row d-none" data-group-id="{{ $group->id }}">
<td colspan="10">
<div style="padding:1rem;background:#f8faff;border-radius:8px;margin:0.25rem 0;">
<div style="font-weight:700;font-size:0.8rem;margin-bottom:0.6rem;color:var(--primary);">
Items in this group
<span style="font-weight:500;color:#64748b;margin-left:8px;">
(GST: {{ number_format($groupGstPercent ?? 0, 2) }}%)
</span>
</div>
@if($group->items->isEmpty())
<div style="color:var(--text-muted);font-size:0.82rem;">
No items linked.
</div>
@else
<div class="table-responsive">
<table class="invoice-table">
<thead>
<tr>
<th>#</th>
<th>Description</th>
<th class="text-center">QTY</th>
<th class="text-center">TTL QTY</th>
<th class="text-center">CBM</th>
<th class="text-center">TTL CBM</th>
<th class="text-center">KG</th>
<th class="text-center">TTL KG</th>
<th class="text-end">TTL Amount</th>
<th class="text-end">Rate</th>
<th class="text-end">Total Charge</th>
{{-- नवीन GST % कॉलम --}}
<th class="text-end">GST %</th>
<th class="text-end">Item GST</th>
<th class="text-end">Item Total (with GST)</th>
</tr>
</thead>
<tbody>
@foreach($group->items as $giIndex => $gi)
@php
$it = $gi->item;
$rate = $group->rate;
$itemBasis = 0;
switch ($group->basis_type) {
case 'ttl_qty':
$itemBasis = $it->ttl_qty ?? 0;
break;
case 'amount':
$itemBasis = $it->ttl_amount ?? 0;
break;
case 'ttl_cbm':
$itemBasis = $it->ttl_cbm ?? 0;
break;
case 'ttl_kg':
$itemBasis = $it->ttl_kg ?? 0;
break;
}
$itemTotal = $itemBasis * $rate;
// Peritem GST share (proportional)
$itemGst = 0;
$itemTotalWithGst = $itemTotal;
if ($groupBaseTotal > 0 && $groupGstAmount > 0) {
$share = $itemTotal / $groupBaseTotal;
$itemGst = $groupGstAmount * $share;
$itemTotalWithGst = $itemTotal + $itemGst;
}
$itemGstPercent = $groupGstPercent ?? 0; // same as group
@endphp
<tr>
<td>{{ $giIndex + 1 }}</td>
<td>{{ $it->description }}</td>
<td class="text-center">{{ $it->qty }}</td>
<td class="text-center">{{ $it->ttl_qty }}</td>
<td class="text-center">{{ $it->cbm }}</td>
<td class="text-center">{{ $it->ttl_cbm }}</td>
<td class="text-center">{{ $it->kg }}</td>
<td class="text-center">{{ $it->ttl_kg }}</td>
<td class="text-end">
{{ number_format($it->ttl_amount, 2) }}
</td>
<td class="text-end">
{{ number_format($rate, 2) }}
</td>
<td class="text-end" style="color:#06b6d4;font-weight:700;">
{{ number_format($itemTotal, 2) }}
</td>
{{-- GST % (per item = group GST %) --}}
<td class="text-end">
{{ number_format($itemGstPercent, 2) }}%
</td>
<td class="text-end" style="color:#ef4444;">
{{ $itemGst > 0 ? number_format($itemGst, 2) : '0.00' }}
</td>
<td class="text-end" style="font-weight:700;color:#16a34a;">
{{ number_format($itemTotalWithGst, 2) }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endif
{{-- ===== FINAL SUMMARY (POPUP) ===== --}}
<div class="summary-card-compact mt-3">
<div class="summary-header-compact">
<i class="fas fa-calculator"></i>
<span>Final Summary</span>
</div>
<div class="summary-body-compact">
@php
// Base amount (without GST) invoice level
$baseAmount = $invoice->final_amount; // items + groups base, जर तू तसे ठेवले असेल
$gstAmount = $invoice->gst_amount; // total GST
$groupsWithGst = $invoice->chargeGroups->sum('total_with_gst'); // सर्व charge groups with GST
@endphp
{{-- Total Charge (Before GST) --}}
<div class="summary-row-compact">
<span class="label">Total Charge (Before GST)</span>
<span class="value">
{{ number_format($baseAmount, 2) }}
</span>
</div>
{{-- GST Amount (total) --}}
<div class="summary-row-compact">
<span class="label">GST Amount</span>
<span class="value red">
{{ number_format($gstAmount, 2) }}
</span>
</div>
{{-- Charge Groups Total (with GST) --}}
<div class="summary-row-compact">
<span class="label">Charge Groups Total (with GST)</span>
<span class="value">
{{ number_format($groupsWithGst, 2) }}
</span>
</div>
{{--
<div class="summary-row-compact total">
<span class="label">Total Charge With GST</span>
<span class="value">
{{ number_format($invoice->final_amount_with_gst, 2) }}
</span>
</div>
--}}
</div>
</div>
<!-- ═══════════════════════════ FOOTER ═══════════════════════════ -->
<div class="invoice-footer">
@if($invoice->pdf_path && $showActions)
<a href="{{ asset($invoice->pdf_path) }}" class="btn-download" download>
<i class="fas fa-download"></i> Download PDF
</a>
<button class="btn-share" onclick="shareInvoice()">
<i class="fas fa-share"></i> Share
</button>
@endif
<div class="footer-note">
<p style="margin-bottom:0.25rem;">Thank you for your business!</p>
<p style="margin:0;">For any inquiries, contact us at support@Kent Logistic</p>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<script>
function shareInvoice() {
const shareData = {
title: "Invoice {{ $invoice->invoice_number }}",
text: "Sharing invoice {{ $invoice->invoice_number }}",
url: "{{ asset($invoice->pdf_path) }}"
};
if (navigator.share) {
navigator.share(shareData).catch(() => {});
} else {
navigator.clipboard.writeText(shareData.url);
alert("Link copied! Sharing not supported on this browser.");
}
}
function renumberChargeGroups() {
const groupsTbody = document.querySelector(
'.cg-groups-panel table.invoice-table tbody'
);
if (!groupsTbody) return;
let index = 1;
groupsTbody.querySelectorAll('tr').forEach(row => {
if (row.classList.contains('cg-items-row')) return;
const next = row.nextElementSibling;
const isMainGroupRow = next && next.classList.contains('cg-items-row');
if (!isMainGroupRow) return;
const firstCell = row.querySelector('td:first-child');
if (firstCell) {
firstCell.textContent = index++;
}
});
}
document.addEventListener('DOMContentLoaded', function () {
const selectAll = document.getElementById('selectAllItems');
const itemCheckboxes = document.querySelectorAll('.item-select-checkbox');
const countSpan = document.getElementById('selectedItemsCount');
const btnCreate = document.getElementById('btnCreateChargeGroup');
const chargeGroupBox = document.getElementById('chargeGroupBox');
const cgCancelBtn = document.getElementById('cgCancelBtn');
const cgItemsTableBody = document.getElementById('cgItemsTableBody');
const cgBasisSelect = document.getElementById('cgBasis');
const cgRateInput = document.getElementById('cgRate');
const cgBasisValueSpan = document.getElementById('cgBasisValue');
const cgBasisLabelSpan = document.getElementById('cgBasisLabel');
const cgSuggestedTotalSpan = document.getElementById('cgSuggestedTotal');
const cgTotalChargeInput = document.getElementById('cgTotalChargeInput');
const cgAutoTotalInput = document.getElementById('cgAutoTotal');
const cgBasisTypeInput = document.getElementById('cgBasisTypeInput');
const cgBasisValueInput = document.getElementById('cgBasisValueInput');
const cgRateHidden = document.getElementById('cgRateHidden');
const cgForm = document.getElementById('chargeGroupForm');
const cgGroupName = document.getElementById('cgGroupName');
// Tax related fields
const cgTaxType = document.getElementById('cgTaxType');
const cgGstPercent = document.getElementById('cgGstPercent');
const cgTotalWithGst = document.getElementById('cgTotalWithGst');
function updateSelectionState() {
let selectedCount = 0;
itemCheckboxes.forEach(cb => {
if (cb.checked && !cb.disabled) selectedCount++;
});
if (countSpan) countSpan.textContent = selectedCount;
if (btnCreate) btnCreate.disabled = (selectedCount === 0);
if (selectedCount === 0 && chargeGroupBox) {
chargeGroupBox.classList.add('d-none');
}
}
function getSelectedItemsDataBasic() {
const items = [];
itemCheckboxes.forEach(cb => {
if (cb.checked && !cb.disabled) {
const row = cb.closest('tr');
if (!row) return;
const cellText = (n) =>
row.querySelector(`td:nth-child(${n})`)?.textContent.trim() ?? '';
items.push({
description: cellText(3),
qty: cellText(5),
ttlqty: cellText(6),
cbm: cellText(10),
ttlcbm: cellText(11),
kg: cellText(12),
ttlkg: cellText(13),
amount: cellText(9),
});
}
});
return items;
}
function parseNumber(str) {
if (!str) return 0;
const cleaned = str.replace(/[,\s]/g, '');
const val = parseFloat(cleaned);
return isNaN(val) ? 0 : val;
}
// GST calculation based on total charge and selected tax type
function refreshChargeGroupGst() {
if (!cgTotalChargeInput || !cgTotalWithGst) return;
const base = parseNumber(cgTotalChargeInput.value);
let gstPercent = 0;
if (cgTaxType && cgGstPercent && cgTaxType.value && cgTaxType.value !== 'none') {
gstPercent = parseNumber(cgGstPercent.value);
}
const gstAmount = base * gstPercent / 100;
const totalWithGst = base + gstAmount;
cgTotalWithGst.value = totalWithGst ? totalWithGst.toFixed(2) : 0;
}
function fillChargeGroupItemsTable() {
if (!cgItemsTableBody) return;
const items = getSelectedItemsDataBasic();
cgItemsTableBody.innerHTML = '';
items.forEach((it, index) => {
cgItemsTableBody.insertAdjacentHTML('beforeend', `
<tr>
<td>${index + 1}</td>
<td>${it.description}</td>
<td class="text-center">${it.qty}</td>
<td class="text-center">${it.ttlqty}</td>
<td class="text-center">${it.cbm}</td>
<td class="text-center">${it.ttlcbm}</td>
<td class="text-center">${it.kg}</td>
<td class="text-center">${it.ttlkg}</td>
<td class="text-center">${it.amount}</td>
</tr>
`);
});
}
function refreshBasisSummaryAndSuggestion() {
if (!chargeGroupBox || chargeGroupBox.classList.contains('d-none')) return;
const items = getSelectedItemsDataBasic();
const basis = cgBasisSelect ? cgBasisSelect.value : '';
let totalBasis = 0;
let label = '';
if (basis === 'ttl_qty') {
totalBasis = items.reduce((sum, it) => sum + parseNumber(it.ttlqty), 0);
label = 'Total TTL/QTY';
} else if (basis === 'amount') {
totalBasis = items.reduce((sum, it) => sum + parseNumber(it.amount), 0);
label = 'Total Amount';
} else if (basis === 'ttl_cbm') {
totalBasis = items.reduce((sum, it) => sum + parseNumber(it.ttlcbm), 0);
label = 'Total TTL CBM';
} else if (basis === 'ttl_kg') {
totalBasis = items.reduce((sum, it) => sum + parseNumber(it.ttlkg), 0);
label = 'Total TTL KG';
}
if (cgBasisValueSpan) cgBasisValueSpan.textContent = totalBasis ? totalBasis.toFixed(3) : '0';
if (cgBasisLabelSpan) cgBasisLabelSpan.textContent = label || '';
const rate = parseNumber(cgRateInput ? cgRateInput.value : '0');
const suggested = rate * totalBasis;
if (cgSuggestedTotalSpan) cgSuggestedTotalSpan.textContent = suggested ? suggested.toFixed(2) : '0';
if (cgBasisTypeInput) cgBasisTypeInput.value = basis || '';
if (cgBasisValueInput) cgBasisValueInput.value = totalBasis || 0;
if (cgRateHidden && cgRateInput) cgRateHidden.value = cgRateInput.value || 0;
if (cgAutoTotalInput) cgAutoTotalInput.value = suggested || 0;
if (cgTotalChargeInput) {
cgTotalChargeInput.value = suggested ? suggested.toFixed(2) : '0';
}
// base बदलला की लगेच GST सह total पण refresh
refreshChargeGroupGst();
}
itemCheckboxes.forEach(cb => {
cb.addEventListener('change', function () {
updateSelectionState();
const total = itemCheckboxes.length;
const checked = Array.from(itemCheckboxes).filter(c => c.checked && !c.disabled).length;
if (selectAll) {
selectAll.checked = (checked > 0 && checked === total);
selectAll.indeterminate = (checked > 0 && checked < total);
}
if (chargeGroupBox && !chargeGroupBox.classList.contains('d-none')) {
fillChargeGroupItemsTable();
refreshBasisSummaryAndSuggestion();
}
});
});
if (selectAll) {
selectAll.addEventListener('change', function () {
itemCheckboxes.forEach(cb => { if (!cb.disabled) cb.checked = selectAll.checked; });
updateSelectionState();
if (chargeGroupBox && !chargeGroupBox.classList.contains('d-none')) {
fillChargeGroupItemsTable();
refreshBasisSummaryAndSuggestion();
}
});
}
if (cgBasisSelect) cgBasisSelect.addEventListener('change', refreshBasisSummaryAndSuggestion);
if (cgRateInput) cgRateInput.addEventListener('input', refreshBasisSummaryAndSuggestion);
// GST fields event listeners
if (cgGstPercent) {
cgGstPercent.addEventListener('input', refreshChargeGroupGst);
}
if (cgTaxType) {
cgTaxType.addEventListener('change', refreshChargeGroupGst);
}
if (cgTotalChargeInput) {
cgTotalChargeInput.addEventListener('input', refreshChargeGroupGst);
}
if (btnCreate && chargeGroupBox) {
btnCreate.addEventListener('click', function () {
const hasSelection = Array.from(itemCheckboxes).some(cb => cb.checked && !cb.disabled);
if (!hasSelection) return;
chargeGroupBox.classList.remove('d-none');
fillChargeGroupItemsTable();
refreshBasisSummaryAndSuggestion();
chargeGroupBox.scrollIntoView({ behavior: 'smooth', block: 'start' });
});
}
if (cgCancelBtn && chargeGroupBox) {
cgCancelBtn.addEventListener('click', function () {
chargeGroupBox.classList.add('d-none');
itemCheckboxes.forEach(cb => { if (!cb.disabled) cb.checked = false; });
if (selectAll) { selectAll.checked = false; selectAll.indeterminate = false; }
updateSelectionState();
});
}
// normal form submit, फक्त hidden item_ids तयार करतो
if (cgForm) {
cgForm.addEventListener('submit', function (e) {
e.preventDefault(); // 🚀 STOP PAGE REFRESH
const selectedIds = [];
itemCheckboxes.forEach(cb => {
if (cb.checked && !cb.disabled) {
selectedIds.push(cb.value);
}
});
if (selectedIds.length === 0) {
alert('Please select at least one item for this charge group.');
return;
}
if (!cgBasisSelect || !cgBasisSelect.value) {
alert('Please select a basis for this charge group.');
return;
}
if (!cgTotalChargeInput ||
!cgTotalChargeInput.value ||
parseFloat(cgTotalChargeInput.value) <= 0) {
alert('Please enter total charges for this group.');
return;
}
refreshChargeGroupGst();
const formData = new FormData(cgForm);
selectedIds.forEach(id => {
formData.append('itemids[]', id);
});
fetch(cgForm.action, {
method: "POST",
headers: {
"X-CSRF-TOKEN": document
.querySelector('meta[name="csrf-token"]')
.getAttribute("content")
},
body: formData
})
.then(async res => {
const data = await res.json();
if (!res.ok) {
// Laravel validation errors
if (data.errors) {
let msg = "";
Object.values(data.errors).forEach(errArr => {
msg += errArr.join("\n") + "\n";
});
throw new Error(msg);
}
throw new Error(data.message || "Something went wrong");
}
return data;
})
.then(data => {
if (data.success) {
alert("Charge group created successfully!");
location.reload();
}
})
.catch(err => {
alert("Check Group details again");
});
});
}
updateSelectionState();
});
// View/Hide group items
document.addEventListener('DOMContentLoaded', function () {
document.addEventListener('click', function (e) {
if (!e.target.classList.contains('cg-toggle-items') &&
!e.target.closest('.cg-toggle-items')) return;
const btn = e.target.closest('.cg-toggle-items') || e.target;
const groupId = btn.getAttribute('data-group-id');
const row = document.querySelector('.cg-items-row[data-group-id="' + groupId + '"]');
if (!row) return;
row.classList.toggle('d-none');
const isHidden = row.classList.contains('d-none');
btn.innerHTML = isHidden
? '<i class="fas fa-eye"></i> View'
: '<i class="fas fa-eye-slash"></i> Hide';
});
});
// simple select all (duplicate राहिला तरी harmless)
document.addEventListener('DOMContentLoaded', function () {
const selectAll = document.getElementById('selectAllItems');
const checkboxes = document.querySelectorAll('.item-select-checkbox');
if (selectAll) {
selectAll.addEventListener('change', function () {
checkboxes.forEach(cb => {
if (!cb.disabled) {
cb.checked = selectAll.checked;
}
});
});
}
});
</script>
</body>
</html>