Files
Kent-logistics-Laravel/resources/views/admin/container_show.blade.php
2026-03-12 12:34:27 +05:30

905 lines
30 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', 'Container Details')
@section('content')
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap');
html, body {
margin: 0;
padding: 0;
width: 100%;
overflow-x: hidden;
}
.cm-header-card {
background: linear-gradient(100deg, #4c6fff 0%, #8e54e9 100%);
border-radius: 14px;
border: none;
box-shadow: 0 6px 24px rgba(76,111,255,0.22);
margin-bottom: 18px;
color: #fff;
}
.cm-header-card .card-body {
padding: 14px 20px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
}
.cm-header-title {
margin: 0;
font-size: 19px;
font-weight: 700;
letter-spacing: -0.3px;
}
.cm-header-sub {
font-size: 12px;
opacity: 0.88;
margin-top: 2px;
}
.cm-main-card {
border-radius: 14px;
border: none;
box-shadow: 0 4px 20px rgba(15,35,52,0.10);
overflow: hidden;
background: #fff;
position: relative;
}
.cm-main-card .card-header {
background: #fff;
border-bottom: 1px solid #edf0f5;
padding: 12px 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.cm-main-card .card-header h5 {
margin: 0;
font-size: 15px;
font-weight: 700;
color: #1a2340;
}
.cm-info-cards-row {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr)); /* 3 cards one row */
gap: 14px;
padding: 14px 18px 8px 18px;
}
.cm-info-card {
border-radius: 16px;
padding: 12px 14px;
display: flex;
align-items: center;
gap: 10px;
box-shadow: 0 4px 16px rgba(15,35,52,0.12);
border: 1px solid rgba(148,163,184,0.25);
background: #fff;
min-height: 70px;
}
.cm-info-card-icon {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #0f172a;
font-size: 16px;
box-shadow: 0 3px 10px rgba(15,23,42,0.10);
flex-shrink: 0;
}
.cm-info-card-body {
display: flex;
flex-direction: column;
gap: 3px;
}
.cm-info-card-label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.4px;
color: #64748b;
font-weight: 600;
}
.cm-info-card-value {
font-size: 14px;
font-weight: 700;
color: #0f172a;
white-space: nowrap;
}
.cm-card-container {
background: linear-gradient(135deg, #e0f2ff, #eef4ff);
}
.cm-card-container .cm-info-card-icon {
background: linear-gradient(135deg, #2563eb, #4f46e5);
color: #e5edff;
}
.cm-card-date {
background: linear-gradient(135deg, #ecfdf3, #e0fbea);
}
.cm-card-date .cm-info-card-icon {
background: linear-gradient(135deg, #16a34a, #22c55e);
color: #ecfdf3;
}
.cm-card-excel {
background: linear-gradient(135deg, #fff7ed, #fffbeb);
}
.cm-card-excel .cm-info-card-icon {
background: linear-gradient(135deg, #f97316, #fb923c);
color: #fff7ed;
}
/* TOTAL BOXES */
.cm-total-cards-row {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 14px;
padding: 4px 18px 14px 18px;
}
.cm-total-card {
border-radius: 18px;
padding: 12px 18px;
display: flex;
align-items: center;
gap: 10px;
box-shadow: 0 8px 30px rgba(15,23,42,0.08);
border: 1px solid rgba(148,163,184,0.25);
}
.cm-total-icon {
width: 34px;
height: 34px;
border-radius: 999px;
display: flex;
align-items: center;
justify-content: center;
font-size: 17px;
flex-shrink: 0;
color: #0f172a;
background: #fff;
box-shadow: 0 4px 14px rgba(15,23,42,0.15);
}
.cm-total-text {
display: flex;
flex-direction: column;
gap: 3px;
}
.cm-total-label {
font-size: 11px;
font-weight: 600;
color: #4b5563;
}
.cm-total-value {
font-size: 18px;
font-weight: 800;
color: #111827;
}
.cm-total-card-ctn {
background: linear-gradient(135deg, #e0f2ff, #eef2ff);
border-left: 4px solid #3b82f6;
}
.cm-total-card-qty {
background: linear-gradient(135deg, #dcfce7, #ecfdf5);
border-left: 4px solid #22c55e;
}
.cm-total-card-cbm {
background: linear-gradient(135deg, #fef9c3, #fffbeb);
border-left: 4px solid #f59e0b;
}
.cm-total-card-kg {
background: linear-gradient(135deg, #fee2e2, #fef2f2);
border-left: 4px solid #ef4444;
}
.cm-filter-bar {
padding: 4px 20px 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.cm-row-count {
font-size: 12px;
color: #8a93a6;
font-weight: 500;
}
.cm-filter-input {
max-width: 240px;
font-size: 12px;
border-radius: 20px;
border: 1px solid #dde2ee;
padding: 6px 14px;
outline: none;
transition: border 0.2s;
}
.cm-filter-input:focus {
border-color: #4c6fff;
box-shadow: 0 0 0 3px rgba(76,111,255,0.1);
}
.cm-table-scroll-outer {
margin: 10px 14px 0 14px;
border-radius: 14px;
border: 1.5px solid #c9a359;
box-shadow: 0 4px 14px rgba(76,111,255,0.18);
overflow-x: auto;
overflow-y: hidden;
position: relative;
}
.cm-table-wrapper {
position: relative;
min-width: 1200px;
}
.cm-table {
font-size: 12px;
width: 100%;
border-collapse: separate;
border-spacing: 0;
font-family: 'DM Sans', sans-serif;
}
.cm-table thead tr th {
position: sticky;
top: 0;
z-index: 3;
background: linear-gradient(100deg, #fde4b3 0%, #fbd48a 100%);
color: #0c0909;
font-weight: 700;
font-size: 12px;
padding: 11px 14px;
border-bottom: 2px solid rgba(255,255,255,0.15);
border-right: 1px solid rgba(255,255,255,0.18);
white-space: nowrap;
text-align: center;
letter-spacing: 0.2px;
text-shadow: 0 1px 2px rgba(0,0,0,0.35);
}
.cm-table thead tr th:first-child {
border-top-left-radius: 10px;
}
.cm-table thead tr th:last-child {
border-top-right-radius: 10px;
border-right: none;
}
.cm-table thead tr th:first-child,
.cm-table tbody tr td:first-child {
width: 46px;
min-width: 46px;
max-width: 46px;
text-align: center;
}
.cm-table tbody tr td {
padding: 8px 14px;
border-bottom: 1px solid #f0f3fb;
border-right: 1px solid #f0f3fb;
color: #2d3a55;
font-size: 12.5px;
text-align: center;
vertical-align: middle;
background: #fff;
transition: background 0.15s;
white-space: nowrap;
}
.cm-table tbody tr td:last-child {
border-right: none;
}
.cm-table tbody tr:nth-child(even) td {
background: #f8f9ff;
}
.cm-table tbody tr:hover td {
background: #edf3ff !important;
}
.cm-row-num {
color: #8a93a6;
font-size: 11px;
font-weight: 600;
}
.cm-cell-input {
width: 100%;
min-width: 90px;
background: transparent;
border: 1.5px solid transparent;
border-radius: 5px;
font-size: 12.5px;
font-family: 'DM Sans', sans-serif;
color: #2d3a55;
padding: 3px 6px;
text-align: center;
transition: border 0.15s, background 0.15s, box-shadow 0.15s;
outline: none;
}
.cm-cell-input:hover {
border-color: #c9d4f5;
background: #f5f8ff;
}
.cm-cell-input:focus {
border-color: #4c6fff;
background: #fff;
box-shadow: 0 0 0 3px rgba(76,111,255,0.12);
}
.cm-cell-readonly {
background: #f3f4ff;
cursor: not-allowed;
border-color: #e2e3ff;
}
.cm-save-btn-floating {
position: fixed;
right: 26px;
bottom: 22px;
z-index: 50;
font-size: 13px;
font-weight: 600;
border-radius: 22px;
padding: 8px 20px;
background: linear-gradient(90deg, #4c6fff, #8e54e9);
border: none;
color: #fff;
box-shadow: 0 4px 16px rgba(76,111,255,0.35);
transition: opacity 0.2s, transform 0.15s;
cursor: pointer;
}
.cm-save-btn-floating:hover {
opacity: 0.94;
transform: translateY(-1px);
}
.cm-save-btn-floating.cm-disabled {
opacity: 0.6;
cursor: default;
pointer-events: none;
}
.cm-toast {
position: fixed;
right: 26px;
bottom: 70px;
background: #0f172a;
color: #f9fafb;
font-size: 12.5px;
padding: 8px 14px;
border-radius: 10px;
box-shadow: 0 8px 24px rgba(15,23,42,0.25);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s, transform 0.2s;
transform: translateY(8px);
z-index: 60;
}
.cm-toast.cm-show {
opacity: 1;
pointer-events: auto;
transform: translateY(0);
}
.cm-empty {
padding: 30px 20px;
color: #8a93a6;
font-size: 13px;
}
.cm-table-scroll-outer::-webkit-scrollbar {
height: 6px;
}
.cm-table-scroll-outer::-webkit-scrollbar-track {
background: #f0f3fb;
border-radius: 4px;
}
.cm-table-scroll-outer::-webkit-scrollbar-thumb {
background: #c5cce8;
border-radius: 4px;
}
.cm-table-scroll-outer::-webkit-scrollbar-thumb:hover {
background: #8a99d0;
}
@media (max-width: 991px) {
.cm-total-cards-row {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 767px) {
.cm-header-card .card-body {
flex-direction: column;
align-items: flex-start;
}
.cm-info-cards-row {
grid-template-columns: 1fr;
}
.cm-table-scroll-outer {
overflow-x: auto;
}
.cm-table-wrapper {
min-width: 900px;
}
}
@media (max-width: 575px) {
.cm-total-cards-row {
grid-template-columns: 1fr;
}
}
</style>
<div class="container-fluid cm-wrapper">
<div class="card cm-header-card">
<div class="card-body">
<div>
<h4 class="cm-header-title">Container {{ $container->container_number }}</h4>
<div class="cm-header-sub">
Edit loading list directly like Excel. TT columns autocalculate from CTN, QTY, CBM, KG, PRICE.
</div>
</div>
<div class="d-flex gap-2">
<a href="{{ route('containers.index') }}" class="btn btn-light btn-sm">Back to list</a>
<a href="{{ route('containers.download.pdf', $container->id) }}"
class="btn btn-sm btn-outline-primary">
Download PDF
</a>
<a href="{{ route('containers.download.excel', $container->id) }}"
class="btn btn-sm btn-outline-success">
Download Excel
</a>
</div>
</div>
</div>
<div class="card cm-main-card">
<div class="card-header">
<h5>Container Information</h5>
</div>
{{-- 3 INFO CARDS IN SINGLE ROW --}}
<div class="cm-info-cards-row">
<div class="cm-info-card cm-card-container">
<div class="cm-info-card-icon">
<i class="bi bi-box-seam"></i>
</div>
<div class="cm-info-card-body">
<div class="cm-info-card-label">Container</div>
<div class="cm-info-card-value">
{{ $container->container_name ?? $container->container_number }}
</div>
</div>
</div>
<div class="cm-info-card cm-card-date">
<div class="cm-info-card-icon">
<i class="bi bi-calendar-event"></i>
</div>
<div class="cm-info-card-body">
<div class="cm-info-card-label">Date</div>
<div class="cm-info-card-value">
{{ $container->container_date ? $container->container_date->format('d-m-Y') : '' }}
</div>
</div>
</div>
<div class="cm-info-card cm-card-excel">
<div class="cm-info-card-icon">
<i class="bi bi-file-earmark-excel"></i>
</div>
<div class="cm-info-card-body">
<div class="cm-info-card-label">Excel File</div>
<div class="cm-info-card-value">
@if($container->excel_file)
<a href="{{ url($container->excel_file) }}" target="_blank" style="color:#0f172a;text-decoration:none;">
Download / View
</a>
@else
<span style="color:#b0b8cc;">Not uploaded</span>
@endif
</div>
</div>
</div>
</div>
@php
$totalCtn = 0;
$totalQty = 0;
$totalCbm = 0.0;
$totalKg = 0.0;
if(!$container->rows->isEmpty()){
foreach ($container->rows as $row) {
if (!is_array($row->data)) continue;
foreach ($row->data as $h => $v) {
$norm = strtoupper(str_replace([' ', '/', '-', '.'],'', $h));
$val = is_numeric(str_replace([','], '', $v)) ? floatval(str_replace([','], '', $v)) : 0;
if (str_contains($norm, 'TOTALCTN') || $norm === 'CTN' || str_contains($norm,'TOTALCNTR') || str_contains($norm,'TOTALCARTON')) {
$totalCtn += $val;
}
if (
str_contains($norm,'TOTALQTY') ||
str_contains($norm,'ITLQTY') ||
str_contains($norm,'TTLQTY')
) {
$totalQty += $val;
}
if (
str_contains($norm,'TOTALCBM') ||
str_contains($norm,'TTLCBM') ||
str_contains($norm,'ITLCBM')
) {
$totalCbm += $val;
}
if (
str_contains($norm,'TOTALKG') ||
str_contains($norm,'TTKG')
) {
$totalKg += $val;
}
}
}
}
@endphp
@if(!$container->rows->isEmpty())
<div class="cm-total-cards-row">
<div class="cm-total-card cm-total-card-ctn">
<div class="cm-total-icon">
<i class="bi bi-box"></i>
</div>
<div class="cm-total-text">
<div class="cm-total-label">Total CTN</div>
<div class="cm-total-value">
{{ number_format($totalCtn, 0) }}
</div>
</div>
</div>
<div class="cm-total-card cm-total-card-qty">
<div class="cm-total-icon">
<i class="bi bi-check-circle"></i>
</div>
<div class="cm-total-text">
<div class="cm-total-label">Total QTY</div>
<div class="cm-total-value">
{{ number_format($totalQty, 0) }}
</div>
</div>
</div>
<div class="cm-total-card cm-total-card-cbm">
<div class="cm-total-icon">
<i class="bi bi-border-width"></i>
</div>
<div class="cm-total-text">
<div class="cm-total-label">Total CBM</div>
<div class="cm-total-value">
{{ number_format($totalCbm, 3) }}
</div>
</div>
</div>
<div class="cm-total-card cm-total-card-kg">
<div class="cm-total-icon">
<i class="bi bi-exclamation-triangle"></i>
</div>
<div class="cm-total-text">
<div class="cm-total-label">Total KG</div>
<div class="cm-total-value">
{{ number_format($totalKg, 2) }}
</div>
</div>
</div>
</div>
@endif
@if($container->rows->isEmpty())
<div class="cm-empty">No entries found for this container.</div>
@else
@php
$allHeadings = [];
foreach ($container->rows as $row) {
if (is_array($row->data)) {
$allHeadings = array_unique(array_merge($allHeadings, array_keys($row->data)));
}
}
@endphp
<div class="cm-filter-bar">
<span class="cm-row-count">
Total rows: {{ $container->rows->count() }} &nbsp;&nbsp; Edit cells then click "Save Changes".
</span>
<input type="text" id="cmRowSearch" class="cm-filter-input"
placeholder="Quick search..." onkeyup="cmFilterRows()">
</div>
<form id="cm-edit-rows-form" action="{{ route('containers.rows.update', $container->id) }}" method="POST">
@csrf
<div class="cm-table-scroll-outer">
<div class="cm-table-wrapper">
<table class="cm-table" id="cmExcelTable">
<thead>
<tr>
<th>#</th>
@foreach($allHeadings as $heading)
<th>{{ $heading }}</th>
@endforeach
</tr>
</thead>
<tbody>
@foreach($container->rows as $index => $row)
<tr>
<td class="cm-row-num">{{ $index + 1 }}</td>
@foreach($allHeadings as $heading)
@php
$value = $row->data[$heading] ?? '';
$norm = strtoupper(str_replace([' ', '/', '-', '.'],'', $heading));
$isCtn = str_contains($norm, 'CTN');
$isTotalQty = (
str_contains($norm, 'TOTALQTY') ||
str_contains($norm, 'ITLQTY') ||
str_contains($norm, 'TTLQTY')
);
$isQty = !$isTotalQty && (
str_contains($norm, 'QTY') ||
str_contains($norm, 'PCS') ||
str_contains($norm, 'PIECES')
);
$isTotalCbm = (
str_contains($norm, 'TOTALCBM') ||
str_contains($norm, 'TTLCBM') ||
str_contains($norm, 'ITLCBM')
);
$isCbm = !$isTotalCbm && str_contains($norm, 'CBM');
$isTotalKg = (
str_contains($norm, 'TOTALKG') ||
str_contains($norm, 'TTKG')
);
$isKg = !$isTotalKg && (str_contains($norm, 'KG') || str_contains($norm, 'WEIGHT'));
$isPrice = (str_contains($norm, 'PRICE') || str_contains($norm, 'RATE'));
$isAmount = (
str_contains($norm, 'AMOUNT') ||
str_contains($norm, 'TTLAMOUNT') ||
str_contains($norm, 'TOTALAMOUNT')
);
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
// जर container pending नसेल तर सर्व fields readonly
$isReadOnly = $isTotalColumn || $container->status !== 'pending';
@endphp
<td>
<input
type="text"
class="cm-cell-input {{ $isReadOnly ? 'cm-cell-readonly' : '' }}"
name="rows[{{ $row->id }}][{{ $heading }}]"
value="{{ $value }}"
data-row-id="{{ $row->id }}"
data-col-key="{{ $heading }}"
data-ctn="{{ $isCtn ? '1' : '0' }}"
data-qty="{{ $isQty ? '1' : '0' }}"
data-ttlqty="{{ $isTotalQty ? '1' : '0' }}"
data-cbm="{{ $isCbm ? '1' : '0' }}"
data-ttlcbm="{{ $isTotalCbm ? '1' : '0' }}"
data-kg="{{ $isKg ? '1' : '0' }}"
data-ttlkg="{{ $isTotalKg ? '1' : '0' }}"
data-price="{{ $isPrice ? '1' : '0' }}"
data-amount="{{ $isAmount ? '1' : '0' }}"
{{ $isReadOnly ? 'readonly' : '' }}
>
</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</form>
@endif
</div>
</div>
@if(!$container->rows->isEmpty())
<button
id="cmSaveBtnFloating"
class="cm-save-btn-floating {{ $container->status !== 'pending' ? 'cm-disabled' : '' }}"
{{ $container->status !== 'pending' ? 'disabled' : '' }}
>
Save Changes
</button>
@endif
<script>
function cmFilterRows() {
const input = document.getElementById('cmRowSearch');
if (!input) return;
const filter = input.value.toLowerCase();
const table = document.getElementById('cmExcelTable');
if (!table) return;
const rows = table.getElementsByTagName('tr');
for (let i = 1; i < rows.length; i++) {
const cells = rows[i].getElementsByTagName('td');
let match = false;
for (let j = 0; j < cells.length; j++) {
const txt = cells[j].textContent || cells[j].innerText;
if (txt.toLowerCase().indexOf(filter) > -1) {
match = true;
break;
}
}
rows[i].style.display = match ? '' : 'none';
}
}
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('cm-edit-rows-form');
const btn = document.getElementById('cmSaveBtnFloating');
const toast = document.getElementById('cmToast');
const table = document.getElementById('cmExcelTable');
function showToast(message, isError = false) {
if (!toast) return;
toast.textContent = message;
toast.style.background = isError ? '#b91c1c' : '#0f172a';
toast.classList.add('cm-show');
setTimeout(() => toast.classList.remove('cm-show'), 2500);
}
function parseNumber(str) {
if (!str) return 0;
const cleaned = String(str).replace(/,/g, '').trim();
const val = parseFloat(cleaned);
return isNaN(val) ? 0 : val;
}
function formatNumber(val, decimals) {
if (isNaN(val)) val = 0;
return val.toFixed(decimals);
}
function recalcRow(row) {
const inputs = row.querySelectorAll('.cm-cell-input');
let ctn = 0, qty = 0, ttlQty = 0;
let cbm = 0, ttlCbm = 0;
let kg = 0, ttlKg = 0;
let price = 0, amount = 0;
let ctnInput = null,
qtyInput = null,
ttlQtyInput = null,
cbmInput = null,
ttlCbmInput = null,
kgInput = null,
ttlKgInput = null,
priceInput = null,
amountInput = null;
inputs.forEach(inp => {
const val = parseNumber(inp.value);
if (inp.dataset.ctn === '1') {
ctn = val;
ctnInput = inp;
} else if (inp.dataset.qty === '1') {
qty = val;
qtyInput = inp;
} else if (inp.dataset.ttlqty === '1') {
ttlQty = val;
ttlQtyInput = inp;
} else if (inp.dataset.cbm === '1') {
cbm = val;
cbmInput = inp;
} else if (inp.dataset.ttlcbm === '1') {
ttlCbm = val;
ttlCbmInput = inp;
} else if (inp.dataset.kg === '1') {
kg = val;
kgInput = inp;
} else if (inp.dataset.ttlkg === '1') {
ttlKg = val;
ttlKgInput = inp;
} else if (inp.dataset.price === '1') {
price = val;
priceInput = inp;
} else if (inp.dataset.amount === '1') {
amount = val;
amountInput = inp;
}
});
if (ttlQtyInput && ctnInput && qtyInput) {
const newTtlQty = ctn * qty;
ttlQtyInput.value = formatNumber(newTtlQty, 0);
ttlQty = newTtlQty;
}
if (ttlCbmInput && cbmInput && ctnInput) {
const newTtlCbm = cbm * ctn;
ttlCbmInput.value = formatNumber(newTtlCbm, 3);
ttlCbm = newTtlCbm;
}
if (ttlKgInput && kgInput && ctnInput) {
const newTtlKg = kg * ctn;
ttlKgInput.value = formatNumber(newTtlKg, 2);
ttlKg = newTtlKg;
}
if (amountInput && priceInput && ttlQtyInput) {
const newAmount = price * ttlQty;
amountInput.value = formatNumber(newAmount, 2);
amount = newAmount;
}
}
if (table) {
table.addEventListener('input', function (e) {
const target = e.target;
if (!target.classList.contains('cm-cell-input')) return;
// readonly / non-pending cells साठी block
if (target.classList.contains('cm-cell-readonly') || target.hasAttribute('readonly')) {
target.blur();
return;
}
const row = target.closest('tr');
if (row) {
recalcRow(row);
}
});
}
if (form && btn) {
btn.addEventListener('click', function () {
// जर बटण आधीच disabled असेल (non-pending status किंवा processing)
if (btn.classList.contains('cm-disabled') || btn.hasAttribute('disabled')) {
return;
}
btn.classList.add('cm-disabled');
const formData = new FormData(form);
fetch(form.action, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': document.querySelector('input[name="_token"]').value
},
body: formData
})
.then(async res => {
if (!res.ok) {
const text = await res.text();
throw new Error(text || 'Failed to save');
}
return res.json().catch(() => ({}));
})
.then(() => {
showToast('Changes saved successfully.');
})
.catch(() => {
showToast('Error while saving changes.', true);
})
.finally(() => {
btn.classList.remove('cm-disabled');
});
});
}
});
</script>
@endsection