Pdf Changes Done
This commit is contained in:
@@ -6,7 +6,13 @@
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap');
|
||||
|
||||
/* ── TOP HEADER CARD ── */
|
||||
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;
|
||||
@@ -34,13 +40,13 @@
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* ── MAIN CARD ── */
|
||||
.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;
|
||||
@@ -57,30 +63,138 @@
|
||||
color: #1a2340;
|
||||
}
|
||||
|
||||
/* ── INFO STRIP ── */
|
||||
.cm-info-strip {
|
||||
.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;
|
||||
gap: 28px;
|
||||
padding: 12px 20px 0 20px;
|
||||
flex-wrap: wrap;
|
||||
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-label {
|
||||
.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;
|
||||
color: #8a93a6;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.cm-info-value {
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.4px;
|
||||
color: #64748b;
|
||||
font-weight: 600;
|
||||
color: #1a2340;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
||||
/* ── FILTER BAR ── */
|
||||
.cm-filter-bar {
|
||||
padding: 12px 20px 0 20px;
|
||||
padding: 4px 20px 0 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@@ -106,59 +220,50 @@
|
||||
box-shadow: 0 0 0 3px rgba(76,111,255,0.1);
|
||||
}
|
||||
|
||||
/* ── TABLE SCROLL OUTER ── */
|
||||
.cm-table-scroll-outer {
|
||||
margin: 10px 14px 0 14px;
|
||||
border-radius: 14px 14px 0 0;
|
||||
/* इथे overflow: hidden होते, ते visible केले */
|
||||
overflow-x: auto; /* फक्त horizontal scroll हवा असेल तर */
|
||||
overflow-y: visible;
|
||||
border: 1.5px solid #b8920e;
|
||||
border-bottom: none;
|
||||
box-shadow: 0 -2px 10px rgba(184,146,14,0.10);
|
||||
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;
|
||||
}
|
||||
|
||||
/* ── TABLE WRAPPER ── */
|
||||
.cm-table-wrapper {
|
||||
position: relative;
|
||||
/* इथे max-height, overflow auto काढले, जे inner scroll देत होते */
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
border-top: none;
|
||||
min-width: 1200px;
|
||||
}
|
||||
|
||||
/* ── TABLE STYLES ── */
|
||||
.cm-table {
|
||||
font-size: 12px;
|
||||
min-width: 1100px;
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
}
|
||||
|
||||
/* Sticky header – light header color */
|
||||
.cm-table thead tr th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 3;
|
||||
background: #fde4b3;
|
||||
color: #0a0a09;
|
||||
background: linear-gradient(100deg, #fde4b3 0%, #fbd48a 100%);
|
||||
color: #0c0909;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
padding: 11px 14px;
|
||||
border-bottom: 2px solid #fde4b3;
|
||||
border-right: 1px solid #fde4b3;
|
||||
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.18);
|
||||
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;
|
||||
}
|
||||
|
||||
/* # column – narrower */
|
||||
.cm-table thead tr th:first-child,
|
||||
.cm-table tbody tr td:first-child {
|
||||
width: 46px;
|
||||
@@ -166,8 +271,6 @@
|
||||
max-width: 46px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Body rows */
|
||||
.cm-table tbody tr td {
|
||||
padding: 8px 14px;
|
||||
border-bottom: 1px solid #f0f3fb;
|
||||
@@ -178,28 +281,23 @@
|
||||
vertical-align: middle;
|
||||
background: #fff;
|
||||
transition: background 0.15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cm-table tbody tr td:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
/* Zebra */
|
||||
.cm-table tbody tr:nth-child(even) td {
|
||||
background: #f8f9ff;
|
||||
}
|
||||
/* Hover */
|
||||
.cm-table tbody tr:hover td {
|
||||
background: #edf3ff !important;
|
||||
}
|
||||
|
||||
/* Row number td */
|
||||
.cm-row-num {
|
||||
color: #8a93a6;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Editable cell input */
|
||||
.cm-cell-input {
|
||||
width: 100%;
|
||||
min-width: 90px;
|
||||
@@ -223,104 +321,279 @@
|
||||
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;
|
||||
}
|
||||
|
||||
/* Save button */
|
||||
.cm-save-btn {
|
||||
font-size: 12.5px;
|
||||
.cm-save-btn-floating {
|
||||
position: fixed;
|
||||
right: 26px;
|
||||
bottom: 22px;
|
||||
z-index: 50;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
border-radius: 20px;
|
||||
padding: 7px 18px;
|
||||
border-radius: 22px;
|
||||
padding: 8px 20px;
|
||||
background: linear-gradient(90deg, #4c6fff, #8e54e9);
|
||||
border: none;
|
||||
color: #fff;
|
||||
box-shadow: 0 3px 10px rgba(76,111,255,0.22);
|
||||
box-shadow: 0 4px 16px rgba(76,111,255,0.35);
|
||||
transition: opacity 0.2s, transform 0.15s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.cm-save-btn:hover {
|
||||
opacity: 0.92;
|
||||
.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);
|
||||
}
|
||||
|
||||
/* Empty state */
|
||||
.cm-empty {
|
||||
padding: 30px 20px;
|
||||
color: #8a93a6;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* Scrollbar styling – आता फक्त horizontal ला लागू होईल */
|
||||
.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; }
|
||||
.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-strip { gap: 16px; }
|
||||
.cm-table-scroll-outer { overflow-x: auto; }
|
||||
.cm-table { min-width: 900px; }
|
||||
.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">
|
||||
|
||||
{{-- TOP GRADIENT HEADER --}}
|
||||
<div class="card cm-header-card">
|
||||
<div class="card-body">
|
||||
<div>
|
||||
<h4 class="cm-header-title">
|
||||
Container: {{ $container->container_number }}
|
||||
</h4>
|
||||
<h4 class="cm-header-title">Container {{ $container->container_number }}</h4>
|
||||
<div class="cm-header-sub">
|
||||
Edit loading list directly — scroll horizontally & vertically like Excel.
|
||||
Edit loading list directly – like Excel. TT columns auto‑calculate from CTN, QTY, CBM, KG, PRICE.
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ route('containers.index') }}" class="btn btn-light btn-sm">
|
||||
← Back to list
|
||||
</a>
|
||||
<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>
|
||||
|
||||
{{-- MAIN CARD --}}
|
||||
<div class="card cm-main-card">
|
||||
|
||||
<div class="card-header">
|
||||
<h5>Container Information</h5>
|
||||
@if(!$container->rows->isEmpty())
|
||||
<button type="submit"
|
||||
form="cm-edit-rows-form"
|
||||
class="cm-save-btn">
|
||||
💾 Save Changes
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- INFO STRIP --}}
|
||||
<div class="cm-info-strip">
|
||||
<div class="cm-info-item">
|
||||
<div class="cm-info-label">Container</div>
|
||||
<div class="cm-info-value">{{ $container->container_name }}</div>
|
||||
</div>
|
||||
<div class="cm-info-item">
|
||||
<div class="cm-info-label">Date</div>
|
||||
<div class="cm-info-value">{{ $container->container_date?->format('d-m-Y') }}</div>
|
||||
</div>
|
||||
<div class="cm-info-item">
|
||||
<div class="cm-info-label">Excel File</div>
|
||||
@if($container->excel_file)
|
||||
<div class="cm-info-value">
|
||||
<a href="{{ \Illuminate\Support\Facades\Storage::url($container->excel_file) }}"
|
||||
target="_blank" style="color:#4c6fff;text-decoration:none;font-weight:600;">
|
||||
📄 Download / View
|
||||
</a>
|
||||
{{-- 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>
|
||||
@else
|
||||
<div class="cm-info-value" style="color:#b0b8cc;">Not uploaded</div>
|
||||
@endif
|
||||
</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
|
||||
@@ -333,60 +606,114 @@
|
||||
}
|
||||
@endphp
|
||||
|
||||
{{-- FILTER BAR --}}
|
||||
<div class="cm-filter-bar">
|
||||
<span class="cm-row-count">
|
||||
Total rows: {{ $container->rows->count() }} • Edit cells then click "Save Changes"
|
||||
Total rows: {{ $container->rows->count() }} Edit cells then click "Save Changes".
|
||||
</span>
|
||||
<input type="text"
|
||||
id="cmRowSearch"
|
||||
class="cm-filter-input"
|
||||
placeholder="🔍 Quick search..."
|
||||
onkeyup="cmFilterRows()">
|
||||
<input type="text" id="cmRowSearch" class="cm-filter-input"
|
||||
placeholder="Quick search..." onkeyup="cmFilterRows()">
|
||||
</div>
|
||||
|
||||
{{-- EDITABLE TABLE FORM --}}
|
||||
<form id="cm-edit-rows-form"
|
||||
action="{{ route('containers.rows.update', $container->id) }}"
|
||||
method="POST">
|
||||
<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>
|
||||
<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>
|
||||
</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] ?? ''; @endphp
|
||||
@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;
|
||||
@endphp
|
||||
<td>
|
||||
<input type="text"
|
||||
class="cm-cell-input"
|
||||
name="rows[{{ $row->id }}][{{ $heading }}]"
|
||||
value="{{ $value }}">
|
||||
<input
|
||||
type="text"
|
||||
class="cm-cell-input {{ $isTotalColumn ? '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' }}"
|
||||
@if($isTotalColumn) readonly @endif
|
||||
>
|
||||
</td>
|
||||
@endforeach
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@if(!$container->rows->isEmpty())
|
||||
<button id="cmSaveBtnFloating" class="cm-save-btn-floating">
|
||||
Save Changes
|
||||
</button>
|
||||
<div id="cmToast" class="cm-toast">
|
||||
Changes saved successfully.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<script>
|
||||
function cmFilterRows() {
|
||||
const input = document.getElementById('cmRowSearch');
|
||||
@@ -394,7 +721,6 @@
|
||||
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');
|
||||
@@ -409,5 +735,157 @@
|
||||
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;
|
||||
|
||||
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 () {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user