Files
Kent-logistics-Laravel/resources/views/admin/dashboard.blade.php
Utkarsh Khedkar e4c07cb838 shipment Changes
2025-12-24 13:36:50 +05:30

2303 lines
63 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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', 'Dashboard')
@php
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Shipment;
use App\Models\Invoice;
use App\Models\User;
use App\Models\Admin;
$totalOrders = Order::count();
$pendingOrders = Order::where('status', 'pending')->count();
$totalShipments = Shipment::count();
$totalItems = OrderItem::count();
$totalRevenue = Invoice::sum('final_amount_with_gst');
// USERS (CUSTOMERS)
$activeCustomers = User::where('status', 'active')->count();
$inactiveCustomers = User::where('status', 'inactive')->count();
// STAFF (FROM ADMINS TABLE)
$totalStaff = Admin::where('type', 'staff')->count();
$orders = Order::latest()->get();
@endphp
@section('content')
<style>
/* ===== GLOBAL RESPONSIVE STYLES ===== */
html, body {
overflow-x: hidden !important;
max-width: 100%;
}
body, .container-fluid {
background: #f4f7fc;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.container-fluid {
padding-left: 15px;
padding-right: 15px;
}
/* === SIDEBAR GAP FIX (IMPORTANT) === */
/* Zoom-out / 1200px खाली गेले की sidebar hide करून main-content full-width करा */
@media (max-width: 1200px) {
.sidebar {
display: none !important;
}
.main-content {
margin-left: 0 !important;
width: 100vw !important;
}
.content-wrapper,
.container-fluid {
padding-left: 10px;
padding-right: 10px;
max-width: 100% !important;
}
}
/* ===== DASHBOARD TITLE ===== */
.dash-top-titlebox {
margin-bottom: 2px;
background: transparent;
padding-left: 3px;
padding-top: 2px;
margin-left: 0px;
}
.dash-title-main {
font-size: 1.54rem;
font-weight: 800;
letter-spacing: .018em;
color: #18213e;
margin-bottom: 2px;
font-family: 'Inter', sans-serif;
}
.dash-title-desc {
font-size: 1rem;
color: #6577a3;
margin-bottom: 5px;
font-weight: 500;
letter-spacing: .01em;
font-family: 'Inter', sans-serif;
}
/* ===== STATS CARDS - RESPONSIVE GRID ===== */
.stats-row-wrap {
margin-bottom: 33px;
}
.stats-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 18px;
margin-bottom: 14px;
}
.stats-card {
background: rgba(255,255,255,0.77);
border-radius: 16px;
box-shadow: 0 7px 32px 0 rgba(19, 39, 78, 0.13), 0 2px 7px 0 rgba(160,180,224,0.14), 0 2px 30px #c5dcf940;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
overflow: hidden;
position: relative;
display: flex;
align-items: center;
gap: 14px;
padding: 18px 16px;
border: 1.2px solid #e8f0fd80;
transition: box-shadow .2s cubic-bezier(.45,.57,.46,.99), transform .15s cubic-bezier(.45,.57,.46,.99);
z-index: 1;
}
.stats-card::after {
content:"";
position: absolute;
inset: 0;
border-radius: 16px;
pointer-events:none;
background: linear-gradient(120deg,rgba(74,156,255,.15) 5%,rgba(210,228,255,.06) 54%,rgba(120,150,255,.13) 99%);
z-index:0;
}
.stats-card:hover, .stats-card:focus-within {
box-shadow: 0 18px 48px 0 rgba(62,87,177,0.16), 0 3px 19px 0 #82ccfc3b;
transform: scale(1.031) translateY(-7px);
z-index: 2;
}
.stats-icon {
font-size:1.65rem;
width:48px;
height:48px;
border-radius:13px;
display:flex;
align-items:center;
justify-content:center;
background:#f5f7fd;
transition:.22s cubic-bezier(.57,.75,.45,.97);
box-shadow:0 1.7px 13px #edf1fd38;
}
.stats-card-blue .stats-icon {
background:linear-gradient(135deg,#3492f8 55%,#1256cc 110%);
color:#fff;
}
.stats-card-green .stats-icon {
background:linear-gradient(135deg,#18ce77 60%,#08ac52 110%);
color:#fff;
}
.stats-card-red .stats-icon {
background:linear-gradient(135deg,#ff5a4e 65%,#d90010 110%);
color:#fff;
}
.stats-card-orng .stats-icon {
background:linear-gradient(135deg,#ffb23c 53%,#e17800 110%);
color:#fff;
}
.stats-label {
font-size:13px;
color:#63709b;
font-weight:600;
letter-spacing:.28px;
font-family: 'Inter', sans-serif;
}
.stats-value {
font-size:1.25rem;
font-weight:700;
color:#194073;
font-family: 'Inter', sans-serif;
}
.stats-card:hover .stats-icon {
transform: scale(1.09) rotate(7deg);
box-shadow:0 0 13px #2396f33b;
}
/* ===== ORDER MANAGEMENT SECTION ===== */
.order-mgmt-box {
background: #fff;
border-radius:17px;
box-shadow:0 7px 38px #dde3fa77, 0 2px 9px #e5e7ff80;
margin-bottom: 33px;
margin-top: 10px;
padding-bottom: 0;
}
.order-mgmt-bar {
display:flex;
justify-content:space-between;
align-items:center;
border-radius:17px 17px 0 0;
background: #fceeb8ff;
min-height: 54px;
padding: 15px 26px 10px 22px;
border-bottom: 1.4px solid #e8e2cf;
box-shadow:0 1px 13px #ffe2a888;
}
.order-mgmt-title {
font-size:1.32rem;
font-weight:800;
color:#2451af;
letter-spacing:.08em;
display: flex;
align-items: center;
gap: 11px;
font-family: 'Inter', sans-serif;
}
.order-mgmt-title i {
font-size: 1.12em;
color: #336ad3;
}
.create-order-btn {
background: linear-gradient(90deg,#226ad6,#46b4fd 123%);
padding:9px 26px;
font-weight:600;
border:none;
border-radius:9px;
color:#fff;
font-size:16.2px;
box-shadow: 0 2px 13px #dde7fa42;
transition: background 0.16s, box-shadow .17s;
display: flex;
align-items: center;
gap: 8px;
font-family: 'Inter', sans-serif;
}
.create-order-btn:hover {
background: linear-gradient(90deg,#3264f8,#3acfff 140%);
box-shadow:0 4px 25px #5ab8f880;
}
.card-body, .order-mgmt-main {
background: #fff;
border-radius: 0 0 17px 17px;
padding:20px;
margin-top: 15px;
}
.card {
border-radius:17px;
box-shadow:0 7px 29px #e7eefd8c;
border:none;
margin-bottom:23px!important;
margin-top: 15px;
}
/* ===== IMPROVED TABLE STYLES ===== */
.table thead tr {
background: #feebbe !important;
border-radius: 12px 12px 0 0;
box-shadow: 0 2px 19px #f8cf667e;
}
.table thead th {
background: transparent !important;
border: none;
font-weight: 700;
color: #343535;
letter-spacing: 0.02em;
font-size: 15px;
padding: 16px 12px;
font-family: 'Inter', sans-serif;
border-bottom: 2px solid #e9ecef;
}
.table thead th:first-child { border-radius: 9px 0 0 0;}
.table thead th:last-child { border-radius: 0 9px 0 0;}
.table {
background:#fff;
border-radius:9px;
box-shadow:0 2px 12px #e2ebf941;
border-collapse: separate;
border-spacing: 0 8px;
font-family: 'Inter', sans-serif;
font-size: 14px;
}
.table-striped tbody tr {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.04);
margin-bottom: 8px;
transition: all 0.3s ease;
height: 60px;
}
.table-striped tbody tr:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
transform: translateY(-2px);
background: #f8fafd;
}
.table-striped tbody tr:nth-of-type(odd) {
background:#f9fafc;
}
.table-striped tbody tr:nth-of-type(even) {
background:#ffffff;
}
.table td {
padding: 14px 12px;
border: none;
position: relative;
vertical-align: middle;
font-family: 'Inter', sans-serif;
font-size: 14px;
font-weight: 500;
color: #495057;
line-height: 1.5;
border-bottom: 1px solid #f1f3f4;
}
.table td:first-child {
border-radius: 8px 0 0 8px;
border-left: 3px solid transparent;
}
.table td:last-child {
border-radius: 0 8px 8px 0;
}
.table-responsive {
border-radius:10px;
}
/* ===== UPDATED STATUS BADGE STYLES ===== */
.badge {
padding: 7px 17px !important;
border-radius: 20px !important;
font-weight: 600 !important;
font-size: 13px !important;
border: 2px solid transparent !important;
min-width: 40px !important;
text-align: center !important;
display: inline-flex !important;
align-items: center;
justify-content: center;
line-height: 1.2 !important;
font-family: 'Inter', sans-serif;
gap: 6px;
width: 110px;
}
.status-icon {
font-size: 13px;
display: flex;
align-items: center;
justify-content: center;
}
/* Pending Status */
.badge-pending {
background: linear-gradient(135deg, #fef3c7, #fde68a) !important;
color: #d97706 !important;
border-color: #f59e0b !important;
}
.badge-loading {
background: linear-gradient(135deg, #e3f2fd, #90caf9) !important;
color: #1565c0 !important;
border-color: #2196f3 !important;
width: 110px;
}
/* In Transit Status */
.badge-in_transit {
background: linear-gradient(135deg, #dbeafe, #93c5fd) !important;
color: #1e40af !important;
border-color: #3b82f6 !important;
}
/* Dispatched Status */
.badge-dispatched {
background: linear-gradient(135deg, #e9d5ff, #c4b5fd) !important;
color: #6b21a8 !important;
border-color: #8b5cf6 !important;
}
/* Delivered Status */
.badge-delivered {
background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important;
color: #065f46 !important;
border-color: #10b981 !important;
}
/* Default badges */
.badge.bg-info {
background: linear-gradient(135deg, #4cc9f0, #4361ee) !important;
color: white !important;
}
.badge.bg-success {
background: linear-gradient(135deg, #4ade80, #22c55e) !important;
color: white !important;
}
.badge.bg-warning {
background: linear-gradient(135deg, #fbbf24, #f59e0b) !important;
color: white !important;
}
.badge.bg-danger {
background: linear-gradient(135deg, #f87171, #ef4444) !important;
color: white !important;
}
.form-label {
font-weight:600;
color:#1d3159;
font-size:15px;
font-family: 'Inter', sans-serif;
}
.form-control, .form-select {
border-radius:8px!important;
font-size:15.8px;
background: #f7f9fe;
border:1.2px solid #c7dbfa;
font-weight:500;
font-family: 'Inter', sans-serif;
}
.form-control:focus, .form-select:focus {
border-color:#2469d6;
box-shadow:0 0 4px #226ad688;
}
.table td a {
color:#2469d6;
font-weight:600;
text-decoration:underline;
font-family: 'Inter', sans-serif;
}
/* ===== HORIZONTAL SCROLL CONTAINER ===== */
.table-wrapper {
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
border-radius: 12px;
background: #fff;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
padding: 12px;
}
.table-wrapper::-webkit-scrollbar {
height: 8px;
}
.table-wrapper::-webkit-scrollbar-thumb {
background: linear-gradient(90deg, #a7b8ff, #6c8eff);
border-radius: 10px;
}
.table-wrapper::-webkit-scrollbar-thumb:hover {
background: linear-gradient(90deg, #5a78ff, #3f63e0);
}
.table-wrapper::-webkit-scrollbar-track {
background: #f1f1f1;
}
.table {
min-width: 2000px;
border-radius: 10px;
}
/* ===== CREATE ORDER MODAL STYLES ===== */
.create-order-modal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: #f8fafc;
display: none;
justify-content: flex-start;
align-items: stretch;
z-index: 9999;
overflow-y: auto;
}
.create-order-modal.show {
display: block;
}
.create-order-modal .modal-card {
background: #fff;
border-radius: 0;
box-shadow: none;
width: 100%;
max-width: 100%;
min-height: 100vh;
max-height: none;
overflow: visible;
}
.create-order-modal .modal-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px 25px;
border-radius: 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.create-order-modal .modal-title {
font-size: 1.4rem;
font-weight: 700;
margin: 0;
font-family: 'Inter', sans-serif;
}
.create-order-modal .close-btn {
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
font-size: 1.5rem;
width: 35px;
height: 35px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.3s;
}
.create-order-modal .close-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
.create-order-modal .modal-body {
padding: 25px;
background: #f8fafc;
}
.create-order-modal .form-label {
font-weight: 600;
color: #1d3159;
font-size: 15px;
margin-bottom: 5px;
font-family: 'Inter', sans-serif;
}
.create-order-modal .form-control,
.create-order-modal .form-select {
border-radius: 8px;
background: #fff;
border: 1.2px solid #c7dbfa;
font-size: 15px;
font-weight: 500;
padding: 8px 12px;
font-family: 'Inter', sans-serif;
}
.create-order-modal .form-control:focus,
.create-order-modal .form-select:focus {
border-color: #2469d6;
box-shadow: 0 0 0 3px rgba(36, 105, 214, 0.1);
}
.create-order-modal .btn-info {
background: #24a0eb;
border: none;
padding: 10px 20px;
font-weight: 600;
border-radius: 8px;
font-family: 'Inter', sans-serif;
}
.create-order-modal .btn-info:hover {
background: #1d8fd8;
}
.create-order-modal .btn-success {
background: #28a745;
border: none;
padding: 12px 30px;
font-weight: 600;
border-radius: 8px;
font-family: 'Inter', sans-serif;
}
.create-order-modal .btn-success:hover {
background: #218838;
}
.create-order-modal .btn-warning {
background: #ffc107;
border: none;
color: #000;
font-weight: 600;
font-family: 'Inter', sans-serif;
}
.create-order-modal .btn-warning:hover {
background: #e0a800;
}
.create-order-modal .btn-danger {
background: #dc3545;
border: none;
font-family: 'Inter', sans-serif;
}
.create-order-modal .btn-danger:hover {
background: #c82333;
}
.create-order-modal .table-wrapper {
width: 100%;
max-height: 400px;
overflow-y: auto;
overflow-x: hidden !important; /* NO HORIZONTAL SCROLL */
border: 1px solid #e9ecef;
border-radius: 12px;
background: #fff;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
padding: 12px;
position: relative;
}
.create-order-modal .table-wrapper .table {
margin-bottom: 0;
background: #fff;
min-width: 100% !important; /* FULL RESPONSIVE WIDTH */
font-size: 13px;
border-collapse: separate;
border-spacing: 0;
}
.create-order-modal .table th,
.create-order-modal .table td {
padding: 8px 6px !important;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 120px;
vertical-align: middle;
}
.create-order-modal .table th:nth-child(2), /* Description */
.create-order-modal .table td:nth-child(2) { max-width: 160px; }
.create-order-modal .table th:nth-child(9), /* TTL Amount */
.create-order-modal .table td:nth-child(9) { max-width: 90px; font-weight: 600; }
.create-order-modal .table .items-input {
width: 100%;
padding: 4px 6px;
font-size: 12px;
min-width: 60px;
max-width: 120px;
}
/* items table inputs */
.items-input {
width: 100%;
padding: 4px 6px;
font-size: 13px;
}
/* ===== ORDER DETAILS MODAL STYLES ===== */
.modal.fade .modal-dialog {
transition: transform 0.3s ease-out;
transform: translate(0, -50px);
}
.modal.show .modal-dialog {
transform: none;
}
.modal-content {
border: none;
border-radius: 20px;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.modal-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-bottom: none;
padding: 11px 16px;
border-radius: 20px 20px 0 0;
}
.modal-title {
font-size: 1.5rem;
font-weight: 700;
color: white;
margin: 0;
font-family: 'Inter', sans-serif;
}
.modal-header .btn-close {
filter: invert(1);
opacity: 0.8;
transition: all 0.3s ease;
}
.modal-header .btn-close:hover {
opacity: 1;
transform: rotate(90deg);
}
.modal-body {
padding: 30px;
background: #f8fafc;
max-height: 70vh;
overflow-y: auto;
font-family: 'Inter', sans-serif;
}
/* ===== PAGINATION STYLES ===== */
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding: 12px 0;
border-top: 1px solid #eef3fb;
}
.pagination-info {
font-size: 13px;
color: #9ba5bb;
font-weight: 600;
font-family: 'Inter', sans-serif;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
font-family: 'Inter', sans-serif;
}
.pagination-btn:hover:not(:disabled) {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-btn:disabled {
background: #f8fafc;
color: #cbd5e0;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.6;
}
.pagination-page-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 36px;
text-align: center;
font-family: 'Inter', sans-serif;
}
.pagination-page-btn:hover {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-page-btn.active {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-pages {
display: flex;
gap: 4px;
align-items: center;
}
.pagination-ellipsis {
color: #9ba5bb;
font-size: 13px;
padding: 0 4px;
font-family: 'Inter', sans-serif;
}
.pagination-img-btn {
background: #fff;
border: 1px solid #e3eaf6;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
padding: 0;
}
.pagination-img-btn:hover:not(:disabled) {
background: #1a2951;
border-color: #1a2951;
}
.pagination-img-btn:disabled {
background: #f8fafc;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.5;
}
.pagination-img-btn img {
width: 16px;
height: 16px;
filter: brightness(0) saturate(100%) invert(26%) sepia(89%) saturate(748%) hue-rotate(201deg) brightness(93%) contrast(89%);
transition: filter 0.3s ease;
}
.pagination-img-btn:hover:not(:disabled) img {
filter: brightness(0) saturate(100%) invert(100%) sepia(100%) saturate(0%) hue-rotate(288deg) brightness(106%) contrast(101%);
}
.pagination-img-btn:disabled img {
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
}
/* ===== RESPONSIVE BREAKPOINTS ===== */
/* Laptop (1200px and below) */
@media (max-width: 1200px) {
.stats-row {
grid-template-columns: repeat(3, 1fr);
}
.table {
min-width: 1000px;
}
}
/* Tablet (992px and below) */
@media (max-width: 992px) {
.stats-row {
grid-template-columns: repeat(2, 1fr);
gap: 15px;
}
.order-mgmt-bar {
padding: 15px 20px;
}
.order-mgmt-title {
font-size: 1.2rem;
}
.create-order-btn {
padding: 8px 20px;
font-size: 15px;
}
.table {
min-width: 900px;
font-size: 0.9rem;
}
.table th,
.table td {
padding: 12px 8px;
}
.pagination-container {
flex-direction: column;
gap: 10px;
align-items: stretch;
}
.pagination-controls {
justify-content: center;
}
.create-order-modal .modal-card {
width: 98%;
margin: 10px;
}
.create-order-modal .modal-body {
padding: 15px;
}
.create-order-modal .modal-header {
padding: 15px 20px;
}
.modal-body {
padding: 20px 15px;
}
.modal-header {
padding: 10px 15px;
}
}
/* Mobile Landscape (768px and below) */
@media (max-width: 768px) {
.container-fluid {
padding-left: 10px;
padding-right: 10px;
}
.dash-title-main {
font-size: 1.3rem;
}
.dash-title-desc {
font-size: 0.9rem;
}
.stats-row-wrap {
margin-bottom: 25px;
}
.stats-card {
padding: 15px 12px;
gap: 12px;
}
.stats-icon {
width: 42px;
height: 42px;
font-size: 1.4rem;
}
.stats-value {
font-size: 1.1rem;
}
.stats-label {
font-size: 12px;
}
.order-mgmt-bar {
flex-direction: column;
align-items: flex-start;
gap: 10px;
padding: 12px 15px;
min-height: auto;
}
.create-order-btn {
width: 100%;
justify-content: center;
padding: 10px;
font-size: 15px;
}
.card-body, .order-mgmt-main {
padding: 20px 15px;
}
.table-wrapper {
padding: 5px;
border-radius: 8px;
}
.table {
min-width: 800px;
font-size: 0.85rem;
}
.table th,
.table td {
padding: 10px 6px;
}
.badge {
font-size: 0.75rem;
padding: 4px 8px;
}
.btn-sm {
padding: 4px 8px;
font-size: 0.75rem;
}
.create-order-modal .modal-card {
width: 98%;
margin: 10px;
}
.create-order-modal .modal-body {
padding: 15px;
}
.create-order-modal .modal-header {
padding: 15px 20px;
}
.modal-body {
padding: 20px 15px;
}
.modal-header {
padding: 10px 15px;
}
}
/* Mobile Portrait (576px and below) */
@media (max-width: 576px) {
.container-fluid {
padding-left: 8px;
padding-right: 8px;
}
.dash-title-main {
font-size: 1.2rem;
}
.dash-title-desc {
font-size: 0.85rem;
}
.stats-row {
grid-template-columns: 1fr;
gap: 12px;
}
.stats-card {
padding: 12px 10px;
}
.stats-icon {
width: 38px;
height: 38px;
font-size: 1.3rem;
}
.stats-value {
font-size: 1rem;
}
.order-mgmt-title {
font-size: 1.1rem;
gap: 8px;
}
.order-mgmt-box {
border-radius: 12px;
margin-bottom: 25px;
}
.order-mgmt-bar {
border-radius: 12px 12px 0 0;
padding: 10px 12px;
}
.card-body, .order-mgmt-main {
padding: 15px 10px;
border-radius: 0 0 12px 12px;
}
.card {
border-radius: 12px;
margin-bottom: 20px !important;
}
.table {
min-width: 700px;
font-size: 0.8rem;
}
.table th,
.table td {
padding: 8px 4px;
}
.form-control, .form-select {
font-size: 14px;
padding: 6px 10px;
}
.create-order-modal .modal-card {
width: 100%;
margin: 5px;
border-radius: 12px;
}
.create-order-modal .modal-header {
padding: 12px 15px;
border-radius: 12px 12px 0 0;
}
.create-order-modal .modal-body {
padding: 12px;
}
.create-order-modal .row.g-3 > [class*="col-"] {
margin-bottom: 10px;
}
}
/* Small Mobile (400px and below) */
@media (max-width: 400px) {
.container-fluid {
padding-left: 5px;
padding-right: 5px;
}
.stats-card {
flex-direction: column;
text-align: center;
gap: 8px;
padding: 15px 8px;
}
.order-mgmt-title {
font-size: 1rem;
}
.create-order-btn {
font-size: 14px;
padding: 8px;
}
.table {
min-width: 650px;
}
.btn-sm {
font-size: 0.7rem;
padding: 3px 6px;
}
}
/* High-resolution displays */
@media (min-width: 1400px) {
.container-fluid {
max-width: 1400px;
margin: 0 auto;
}
.stats-row {
gap: 20px;
}
.stats-card {
padding: 20px;
}
}
/* Print styles */
@media print {
.create-order-btn,
.order-mgmt-bar {
display: none !important;
}
.stats-card {
break-inside: avoid;
}
}
/* =====================================================
GLOBAL EDGE-TO-EDGE + ZOOM SAFE PATCH (CSS ONLY)
===================================================== */
/* 1⃣ Kill boxed layouts on desktop & zoom */
html, body {
width: 100%;
max-width: 100%;
overflow-x: clip;
}
/* 2⃣ Force container-fluid to truly span full width */
.container-fluid {
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
padding-left: clamp(12px, 1.8vw, 28px) !important;
padding-right: clamp(12px, 1.8vw, 28px) !important;
}
/* 3⃣ Zoom-safe scaling (VERY IMPORTANT) */
body {
font-size: clamp(14px, 0.95vw, 16px);
}
/* =====================================================
DASHBOARD CARD & GRID FIXES (NO HTML CHANGE)
===================================================== */
/* 4⃣ Make stat grids auto-adjust on zoom */
.stats-row,
.shipment-totals-row {
display: grid !important;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)) !important;
gap: clamp(12px, 1.5vw, 20px) !important;
}
/* 5⃣ Prevent hover zoom breaking layout */
.stats-card:hover,
.card:hover,
.table tbody tr:hover {
transform: translateY(-4px) !important;
}
/* =====================================================
TABLE ZOOM FIX (NO MORE CRUSHING / OVERFLOW)
===================================================== */
/* 6⃣ Tables behave like shipment page */
.table-responsive {
width: 100%;
overflow-x: auto;
}
/* 7⃣ Remove hard min-widths that break zoom */
.table,
.custom-table-modal,
.shipment-details-table {
width: 100% !important;
min-width: max-content !important;
}
/* 8⃣ Let text wrap naturally when zoomed */
.table td,
.table th {
white-space: nowrap;
}
/* =====================================================
MODALS EDGE TO EDGE WITHOUT TOUCHING MARKUP
===================================================== */
.modal-xl {
max-width: 96vw !important;
width: 96vw !important;
margin: 1vh auto !important;
}
@media (max-width: 768px) {
.modal-xl {
max-width: 100vw !important;
width: 100vw !important;
margin: 0 !important;
height: 100vh !important;
}
}
/* =====================================================
FINAL SAFETY PREVENT LAYOUT SHRINK ON ZOOM
===================================================== */
* {
box-sizing: border-box;
}
</style>
<div class="container-fluid py-3">
<!-- DASHBOARD TITLE -->
<div class="dash-top-titlebox">
<div class="dash-title-main">Admin Dashboard</div>
<div class="dash-title-desc">Monitor operations and manage system</div>
</div>
<!-- STATS CARDS -->
<div class="stats-row-wrap">
<div class="stats-row">
<div class="stats-card stats-card-blue">
<span class="stats-icon">📦</span>
<div>
<div class="stats-label">Total Shipments</div>
<div class="stats-value">{{ $totalShipments }}</div>
</div>
</div>
<div class="stats-card stats-card-blue">
<span class="stats-icon">👥</span>
<div>
<div class="stats-label">Active Customers</div>
<div class="stats-value">{{ $activeCustomers }}</div>
</div>
</div>
<div class="stats-card stats-card-green">
<span class="stats-icon">💰</span>
<div>
<div class="stats-label">Total Revenue</div>
<div class="stats-value">{{ number_format($totalRevenue, 2) }}</div>
</div>
</div>
<div class="stats-card stats-card-red">
<span class="stats-icon"></span>
<div>
<div class="stats-label">Pending Order</div>
<div class="stats-value">{{ $pendingOrders }}</div>
</div>
</div>
</div>
<div class="stats-row">
<div class="stats-card stats-card-blue">
<span class="stats-icon">📦</span>
<div>
<div class="stats-label">Total Orders</div>
<div class="stats-value">{{ $totalOrders }}</div>
</div>
</div>
<div class="stats-card stats-card-blue">
<span class="stats-icon">🧑‍💼</span>
<div>
<div class="stats-label">Total Staff</div>
<div class="stats-value">{{ $totalStaff }}</div>
</div>
</div>
<div class="stats-card stats-card-blue">
<span class="stats-icon">📦</span>
<div>
<div class="stats-label">Total Items</div>
<div class="stats-value">{{ $totalItems }}</div>
</div>
</div>
<div class="stats-card stats-card-orng">
<span class="stats-icon"></span>
<div>
<div class="stats-label">Inactive Customers</div>
<div class="stats-value">{{ $inactiveCustomers }}</div>
</div>
</div>
</div>
</div>
<!-- ORDER MANAGEMENT -->
<div class="order-mgmt-box">
<div class="order-mgmt-bar">
<span class="order-mgmt-title">
<i class="bi bi-table"></i> Order Management
</span>
@can('order.create')
<button class="create-order-btn" id="openCreateOrderModal">
<i class="bi bi-plus-circle"></i> Create Order
</button>
@endcan
</div>
<div class="order-mgmt-main">
<!-- RECENT ORDERS TABLE -->
<div class="card shadow-sm">
<div class="card-header bg-light">
<strong>Recent Orders</strong>
<span style="font-size:13px;color:#6577a3;margin-left:10px;">
Total orders: <span id="ordersCount">{{ $orders->count() }}</span>
</span>
</div>
<div class="card-body table-responsive">
<div class="table-wrapper">
<table class="table table-striped table-bordered align-middle text-center">
<thead class="table-light">
<tr>
<th>#</th>
<th>Order ID</th>
<th>Mark No</th>
<th>Date</th>
<th>Origin</th>
<th>Destination</th>
<th>Total CTN</th>
<th>Total QTY</th>
<th>Total TTL/QTY</th>
<th>Total Amount ()</th>
<th>Total CBM</th>
<th>Total TTL CBM</th>
<th>Total KG</th>
<th>Total TTL KG</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="ordersTableBody">
@forelse($orders as $order)
<tr>
<td>{{ $order->id }}</td>
<td>
<a href="javascript:void(0)"
class="fw-semibold text-primary open-order-modal"
data-id="{{ $order->id }}">
{{ $order->order_id }}
</a>
</td>
<td>{{ $order->mark_no }}</td>
<td>{{ $order->created_at->format('d-m-Y') }}</td>
<td>{{ $order->origin }}</td>
<td>{{ $order->destination }}</td>
<td>{{ $order->ctn }}</td>
<td>{{ $order->qty }}</td>
<td>{{ $order->ttl_qty }}</td>
<td>{{ number_format($order->ttl_amount, 2) }}</td>
<td>{{ $order->cbm }}</td>
<td>{{ $order->ttl_cbm }}</td>
<td>{{ $order->kg }}</td>
<td>{{ $order->ttl_kg }}</td>
<td>
@php
// Badge color mapping
$badgeMap = [
'order_placed' => 'secondary',
'order_confirmed' => 'info',
'supplier_warehouse' => 'warning',
'consolidate_warehouse' => 'warning',
'export_custom' => 'primary',
'international_transit' => 'primary',
'arrived_india' => 'info',
'import_custom' => 'info',
'warehouse' => 'dark',
'domestic_distribution' => 'primary',
'out_for_delivery' => 'success',
'delivered' => 'success',
];
// Icon mapping
$iconMap = [
'order_placed' => 'bi-clock-fill',
'order_confirmed' => 'bi-check-circle',
'supplier_warehouse' => 'bi-box-seam',
'consolidate_warehouse' => 'bi-boxes',
'export_custom' => 'bi-upload',
'international_transit' => 'bi-truck',
'arrived_india' => 'bi-geo-alt',
'import_custom' => 'bi-download',
'warehouse' => 'bi-building',
'domestic_distribution' => 'bi-diagram-3',
'out_for_delivery' => 'bi-truck-flatbed',
'delivered' => 'bi-check-circle-fill',
];
$badgeClass = $badgeMap[$order->status] ?? 'secondary';
$iconClass = $iconMap[$order->status] ?? 'bi-info-circle';
@endphp
<span class="badge bg-{{ $badgeClass }}">
<i class="bi {{ $iconClass }} status-icon"></i>
{{ $order->status_label }}
</span>
</td>
<td>
<a href="{{ route('admin.orders.show', $order->id) }}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i> View
</a>
</td>
</tr>
@empty
<tr>
<td colspan="16" class="text-muted">No orders found</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="pagination-container">
<div class="pagination-info" id="pageInfo">
Showing 1 to {{ min($orders->count(), 10) }} of {{ $orders->count() }} entries
</div>
<div class="pagination-controls">
<button class="pagination-img-btn" id="prevPageBtn" title="Previous page">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M10 12L6 8L10 4"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"/>
</svg>
</button>
<div class="pagination-pages" id="paginationPages">
<!-- Page numbers will be inserted here -->
</div>
<button class="pagination-img-btn" id="nextPageBtn" title="Next page">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M6 4L10 8L6 12"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"/>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- CREATE ORDER MODAL -->
<div class="create-order-modal" id="createOrderModal">
<div class="modal-card">
<div class="modal-header">
<h5 class="modal-title">Create New Order</h5>
<button class="close-btn" id="closeCreateOrderModal">&times;</button>
</div>
<div class="modal-body">
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
<!-- CREATE ORDER FORM -->
<form action="{{ route('admin.orders.temp.add') }}" method="POST" id="createOrderForm">
@csrf
<div class="row g-3">
{{-- MARK NO --}}
<div class="col-md-4">
<label class="form-label">Mark No</label>
@if(session('temp_order_items'))
<input type="text" class="form-control" value="{{ session('mark_no') }}" disabled>
<input type="hidden" name="mark_no" value="{{ session('mark_no') }}">
@else
<select class="form-select" id="markNoSelect" name="mark_no" required>
<option value="">Select Mark No</option>
@foreach($markList as $mark)
<option value="{{ $mark->mark_no }}"
data-origin="{{ $mark->origin }}"
data-destination="{{ $mark->destination }}"
@if(session('mark_no') == $mark->mark_no) selected @endif>
{{ $mark->mark_no }} - {{ $mark->customer_name }}
</option>
@endforeach
</select>
@endif
</div>
{{-- ORIGIN --}}
<div class="col-md-4">
<label class="form-label">Origin</label>
<input type="text" class="form-control"
id="originField"
name="origin"
readonly required
value="{{ session('origin') }}">
</div>
{{-- DESTINATION --}}
<div class="col-md-4">
<label class="form-label">Destination</label>
<input type="text" class="form-control"
id="destinationField"
name="destination"
readonly required
value="{{ session('destination') }}">
</div>
</div>
<hr class="my-3">
{{-- ITEM INPUTS --}}
<h6 class="text-primary">Add Item</h6>
{{-- NEW ITEMS TABLE --}}
<div class="table-wrapper mb-3">
<table class="table table-bordered table-sm align-middle text-center" id="itemsTable">
<thead class="table-light">
<tr>
<th>#</th>
<th>Description</th>
<th>CTN</th>
<th>QTY</th>
<th>TTL/QTY</th>
<th>Unit</th>
<th>Price</th>
<th>TTL Amount</th>
<th>CBM</th>
<th>TTL CBM</th>
<th>KG</th>
<th>TTL KG</th>
<th>Shop No</th>
<th>Remove</th>
</tr>
</thead>
<tbody id="itemsTableBody">
{{-- JS will create default 2 blank rows --}}
</tbody>
</table>
</div>
<div class="row g-3">
<div class="col-md-12 text-end mt-3">
<button type="button" class="btn btn-secondary clear-form-btn" id="clearForm">
<i class="bi bi-arrow-clockwise"></i> Clear Form
</button>
<button type="submit" class="btn btn-info" id="addItemBtn">
<i class="bi bi-plus-circle"></i> Add Item
</button>
<input type="file" id="excelInput" accept=".xlsx,.xls" hidden>
<button type="button" class="btn btn-outline-success" id="uploadExcelBtn">
<i class="bi bi-file-earmark-excel"></i> Upload Excel
</button>
</div>
</div>
</form>
{{-- RESET ORDER BUTTON --}}
@if(session('temp_order_items'))
<div class="text-start mt-2">
<form action="{{ route('admin.orders.temp.reset') }}" method="POST">
@csrf
<button type="submit" class="btn btn-warning btn-sm">
<i class="bi bi-arrow-repeat"></i> Reset Order
</button>
</form>
</div>
@endif
{{-- TEMPORARY ITEMS TABLE --}}
@if(session('temp_order_items') && count(session('temp_order_items')) > 0)
<hr class="my-4">
<h5 class="text-success">Temporary Items ({{ count(session('temp_order_items')) }} items)</h5>
<div class="table-wrapper">
<table class="table table-bordered text-center align-middle">
<thead class="table-light">
<tr>
<th>#</th>
<th>Description</th>
<th>CTN</th>
<th>QTY</th>
<th>TTL/QTY</th>
<th>Unit</th>
<th>Price</th>
<th>TTL Amount</th>
<th>CBM</th>
<th>TTL CBM</th>
<th>KG</th>
<th>TTL KG</th>
<th>Shop No</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
@foreach(session('temp_order_items') as $index => $item)
<tr>
<td>{{ $index + 1 }}</td>
<td>{{ $item['description'] }}</td>
<td>{{ $item['ctn'] }}</td>
<td>{{ $item['qty'] }}</td>
<td>{{ $item['ttl_qty'] }}</td>
<td>{{ $item['unit'] }}</td>
<td>{{ $item['price'] }}</td>
<td>{{ $item['ttl_amount'] }}</td>
<td>{{ $item['cbm'] }}</td>
<td>{{ $item['ttl_cbm'] }}</td>
<td>{{ $item['kg'] }}</td>
<td>{{ $item['ttl_kg'] }}</td>
<td>{{ $item['shop_no'] }}</td>
<td>
<form action="{{ route('admin.orders.temp.delete') }}" method="POST">
@csrf
<input type="hidden" name="index" value="{{ $index }}">
<button type="submit" class="btn btn-danger btn-sm">
<i class="bi bi-trash"></i>
</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="text-end mt-3">
<form action="{{ route('admin.orders.finish') }}" method="POST">
@csrf
<input type="hidden" name="mark_no" value="{{ session('mark_no') }}">
<input type="hidden" name="origin" value="{{ session('origin') }}">
<input type="hidden" name="destination" value="{{ session('destination') }}">
<button type="submit" class="btn btn-success btn-lg">
<i class="bi bi-check-circle"></i> Finish & Save Order
</button>
</form>
</div>
@endif
</div>
</div>
</div>
<!-- MODERN ORDER DETAILS MODAL -->
<div class="modal fade" id="orderDetailsModal" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Order Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="orderDetailsBody">
<p class="text-center text-muted">Loading...</p>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const modal = document.getElementById('createOrderModal');
const openBtn = document.getElementById('openCreateOrderModal');
const closeBtn = document.getElementById('closeCreateOrderModal');
const clearFormBtn = document.getElementById('clearForm');
let currentPage = 1;
const ordersPerPage = 10;
let allOrders = @json($orders->values());
const itemsTableBody = document.getElementById('itemsTableBody');
function addRow(index) {
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="align-middle fw-bold">${index + 1}</td>
<td>
<input type="text" class="form-control form-control-sm items-input"name="items[${index}][description]"data-field="description">
</td>
<td>
<input type="number" class="form-control form-control-sm items-input"name="items[${index}][ctn]"data-field="ctn">
</td>
<td>
<input type="number" class="form-control form-control-sm items-input"name="items[${index}][qty]"data-field="qty">
</td>
<!-- 🔒 AUTO: TTL/QTY = CTN * QTY -->
<td>
<input type="number" class="form-control form-control-sm bg-light"name="items[${index}][ttl_qty]"data-field="ttl_qty"readonly>
</td>
<td>
<input type="text" class="form-control form-control-sm items-input"name="items[${index}][unit]"data-field="unit">
</td>
<td>
<input type="number" class="form-control form-control-sm items-input"name="items[${index}][price]"data-field="price"step="0.01">
</td>
<!-- 🔒 AUTO: TTL AMOUNT = TTL/QTY * PRICE -->
<td>
<input type="number" class="form-control form-control-sm bg-light"name="items[${index}][ttl_amount]"data-field="ttl_amount"step="0.001"readonly>
</td>
<td>
<input type="number" class="form-control form-control-sm items-input"name="items[${index}][cbm]"data-field="cbm"step="0.0001">
</td>
<!-- 🔒 AUTO: TTL CBM = CBM * QTY -->
<td>
<input type="number" class="form-control form-control-sm bg-light"name="items[${index}][ttl_cbm]"data-field="ttl_cbm"step="0.0001"readonly>
</td>
<td>
<input type="number" class="form-control form-control-sm items-input"name="items[${index}][kg]"data-field="kg"step="0.0001">
</td>
<!-- 🔒 AUTO: TTL KG = CTN * KG -->
<td>
<input type="number" class="form-control form-control-sm bg-light"name="items[${index}][ttl_kg]"data-field="ttl_kg"step="0.0001"readonly>
</td>
<td>
<input type="text" class="form-control form-control-sm items-input"name="items[${index}][shop_no]"data-field="shop_no">
</td>
<td>
<button type="button" class="btn btn-sm btn-danger remove-row-btn">&times;</button>
</td>
`;
itemsTableBody.appendChild(tr);
}
function calculateRow(row) {
const ctn = parseFloat(row.querySelector('[data-field="ctn"]')?.value) || 0;
const qty = parseFloat(row.querySelector('[data-field="qty"]')?.value) || 0;
const price = parseFloat(row.querySelector('[data-field="price"]')?.value) || 0;
const cbm = parseFloat(row.querySelector('[data-field="cbm"]')?.value) || 0;
const kg = parseFloat(row.querySelector('[data-field="kg"]')?.value) || 0;
const ttlQty = ctn * qty;
const ttlAmount = ttlQty * price;
const ttlCbm = cbm * ctn;
const ttlKg = ctn * kg;
row.querySelector('[data-field="ttl_qty"]').value = ttlQty.toFixed(2);
row.querySelector('[data-field="ttl_amount"]').value = ttlAmount.toFixed(2);
row.querySelector('[data-field="ttl_cbm"]').value = ttlCbm.toFixed(3);
row.querySelector('[data-field="ttl_kg"]').value = ttlKg.toFixed(3);
}
itemsTableBody.addEventListener('input', function (e) {
const row = e.target.closest('tr');
if (!row) return;
const calcFields = ['ctn', 'qty', 'price', 'cbm', 'kg'];
if (calcFields.includes(e.target.dataset.field)) {
calculateRow(row);
}
});
function generateDefaultRows() {
itemsTableBody.innerHTML = '';
addRow(0);
addRow(1);
focusFirstInput();
}
function reindexRows() {
const rows = itemsTableBody.querySelectorAll('tr');
rows.forEach((tr, idx) => {
tr.querySelector('td:first-child').textContent = idx + 1;
tr.querySelectorAll('input').forEach(input => {
const field = input.getAttribute('data-field');
input.name = `items[${idx}][${field}]`;
});
});
}
function rowHasData(row) {
const inputs = row.querySelectorAll('input');
return Array.from(inputs).some(inp => inp.value.trim() !== '');
}
function focusFirstInput() {
const first = itemsTableBody.querySelector('tr:first-child input');
if (first) first.focus();
}
initializePagination();
const resetTempData = () => {
fetch('{{ route("admin.orders.temp.reset") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}'
}
});
};
const openModal = () => {
modal.classList.add('show');
document.body.style.overflow = 'hidden';
document.querySelector('.alert-success')?.remove();
generateDefaultRows();
};
const closeModal = () => {
modal.classList.remove('show');
document.body.style.overflow = '';
@if(session('temp_order_items') && count(session('temp_order_items')) > 0)
setTimeout(() => {
resetTempData();
window.location.reload();
}, 100);
@endif
};
const clearForm = () => {
generateDefaultRows();
};
if (openBtn) openBtn.addEventListener('click', openModal);
if (closeBtn) closeBtn.addEventListener('click', closeModal);
if (clearFormBtn) clearFormBtn.addEventListener('click', clearForm);
modal.addEventListener('click', (e) => e.target === modal && closeModal());
document.addEventListener('keydown', (e) => e.key === 'Escape' && modal.classList.contains('show') && closeModal());
const markNoSelect = document.getElementById('markNoSelect');
if (markNoSelect) {
markNoSelect.addEventListener('change', function() {
const locked = {{ session('temp_order_items') ? 'true' : 'false' }};
if (locked) {
alert("You must finish the current order before changing Mark No.");
this.value = "{{ session('mark_no') }}";
return;
}
const option = this.options[this.selectedIndex];
document.getElementById('originField').value = option.dataset.origin || '';
document.getElementById('destinationField').value = option.dataset.destination || '';
});
}
@if(session('temp_order_items') && count(session('temp_order_items')) > 0)
modal.classList.add('show');
document.body.style.overflow = 'hidden';
generateDefaultRows();
@endif
document.querySelectorAll('form[action="{{ route("admin.orders.temp.reset") }}"]')
.forEach(form => form.addEventListener('submit', e => {
if (!confirm('Are you sure you want to reset the current order? All temporary items will be lost.')) {
e.preventDefault();
}
}));
document.querySelectorAll('.open-order-modal').forEach(button => {
button.addEventListener('click', function() {
let id = this.dataset.id;
let modalInstance = new bootstrap.Modal(document.getElementById('orderDetailsModal'));
document.getElementById('orderDetailsBody').innerHTML =
"<p class='text-center text-muted'>Loading...</p>";
modalInstance.show();
fetch(`/admin/orders/view/${id}`)
.then(response => response.text())
.then(html => {
document.getElementById('orderDetailsBody').innerHTML = html;
})
.catch(() => {
document.getElementById('orderDetailsBody').innerHTML =
"<p class='text-danger text-center'>Failed to load order details.</p>";
});
});
});
function initializePagination() {
renderOrdersTable(allOrders);
updatePaginationControls();
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
}
function goToPreviousPage() {
if (currentPage > 1) {
currentPage--;
renderOrdersTable(allOrders);
updatePaginationControls();
}
}
function goToNextPage() {
const totalPages = Math.ceil(allOrders.length / ordersPerPage);
if (currentPage < totalPages) {
currentPage++;
renderOrdersTable(allOrders);
updatePaginationControls();
}
}
function updatePaginationControls() {
const totalPages = Math.ceil(allOrders.length / ordersPerPage);
const prevBtn = document.getElementById('prevPageBtn');
const nextBtn = document.getElementById('nextPageBtn');
const pageInfo = document.getElementById('pageInfo');
const paginationPages = document.getElementById('paginationPages');
prevBtn.disabled = currentPage === 1;
nextBtn.disabled = currentPage === totalPages || totalPages === 0;
const startIndex = (currentPage - 1) * ordersPerPage + 1;
const endIndex = Math.min(currentPage * ordersPerPage, allOrders.length);
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${allOrders.length} entries`;
paginationPages.innerHTML = '';
if (totalPages <= 7) {
for (let i = 1; i <= totalPages; i++) {
addPageButton(i, paginationPages);
}
} else {
addPageButton(1, paginationPages);
if (currentPage > 3) {
paginationPages.innerHTML += '<span class="pagination-ellipsis">...</span>';
}
const start = Math.max(2, currentPage - 1);
const end = Math.min(totalPages - 1, currentPage + 1);
for (let i = start; i <= end; i++) {
addPageButton(i, paginationPages);
}
if (currentPage < totalPages - 2) {
paginationPages.innerHTML += '<span class="pagination-ellipsis">...</span>';
}
addPageButton(totalPages, paginationPages);
}
}
function addPageButton(pageNumber, container) {
const button = document.createElement('button');
button.className = 'pagination-page-btn';
if (pageNumber === currentPage) {
button.classList.add('active');
}
button.textContent = pageNumber;
button.addEventListener('click', () => {
currentPage = pageNumber;
renderOrdersTable(allOrders);
updatePaginationControls();
});
container.appendChild(button);
}
function renderOrdersTable(orders) {
const tbody = document.getElementById('ordersTableBody');
tbody.innerHTML = '';
if (!orders || orders.length === 0) {
tbody.innerHTML = '<tr><td colspan="16" class="text-muted">No orders found</td></tr>';
return;
}
const startIndex = (currentPage - 1) * ordersPerPage;
const endIndex = startIndex + ordersPerPage;
const paginatedOrders = orders.slice(startIndex, endIndex);
paginatedOrders.forEach(order => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${order.id}</td>
<td>
<a href="javascript:void(0)"
class="fw-semibold text-primary open-order-modal"
data-id="${order.id}">
${order.order_id}
</a>
</td>
<td>${order.mark_no || ''}</td>
<td>${new Date(order.created_at).toLocaleDateString('en-GB')}</td>
<td>${order.origin || ''}</td>
<td>${order.destination || ''}</td>
<td>${order.ctn || ''}</td>
<td>${order.qty || ''}</td>
<td>${order.ttl_qty || ''}</td>
<td>₹${order.ttl_amount ? Number(order.ttl_amount).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '0.00'}</td>
<td>${order.cbm || ''}</td>
<td>${order.ttl_cbm || ''}</td>
<td>${order.kg || ''}</td>
<td>${order.ttl_kg || ''}</td>
<td>
<span class="badge bg-${getBadgeClass(order.status)}">
<i class="bi ${getIconClass(order.status)} status-icon"></i>
${order.status_label || order.status}
</span>
</td>
<td>
<a href="/admin/orders/${order.id}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i> View
</a>
</td>
`;
tbody.appendChild(tr);
const orderLink = tr.querySelector('.open-order-modal');
if (orderLink) {
orderLink.addEventListener('click', function() {
let id = this.dataset.id;
let modalInstance = new bootstrap.Modal(document.getElementById('orderDetailsModal'));
document.getElementById('orderDetailsBody').innerHTML =
"<p class='text-center text-muted'>Loading...</p>";
modalInstance.show();
fetch(`/admin/orders/view/${id}`)
.then(response => response.text())
.then(html => {
document.getElementById('orderDetailsBody').innerHTML = html;
})
.catch(() => {
document.getElementById('orderDetailsBody').innerHTML =
"<p class='text-danger text-center'>Failed to load order details.</p>";
});
});
}
});
}
function getBadgeClass(status) {
const badgeMap = {
'order_placed': 'secondary',
'order_confirmed': 'info',
'supplier_warehouse': 'warning',
'consolidate_warehouse': 'warning',
'export_custom': 'primary',
'international_transit': 'primary',
'arrived_india': 'info',
'import_custom': 'info',
'warehouse': 'dark',
'domestic_distribution': 'primary',
'out_for_delivery': 'success',
'delivered': 'success',
'pending': 'warning',
'in_transit': 'info',
'dispatched': 'primary',
'delivered': 'success'
};
return badgeMap[status] || 'secondary';
}
function getIconClass(status) {
const iconMap = {
'order_placed': 'bi-clock-fill',
'order_confirmed': 'bi-check-circle',
'supplier_warehouse': 'bi-box-seam',
'consolidate_warehouse': 'bi-boxes',
'export_custom': 'bi-upload',
'international_transit': 'bi-truck',
'arrived_india': 'bi-geo-alt',
'import_custom': 'bi-download',
'warehouse': 'bi-building',
'domestic_distribution': 'bi-diagram-3',
'out_for_delivery': 'bi-truck-flatbed',
'delivered': 'bi-check-circle-fill',
'pending': 'bi-clock-fill',
'in_transit': 'bi-truck',
'dispatched': 'bi-box-seam',
'delivered': 'bi-check-circle-fill'
};
return iconMap[status] || 'bi-info-circle';
}
itemsTableBody.addEventListener('keydown', function(e) {
if (e.key !== 'Enter' || e.target.tagName !== 'INPUT') return;
e.preventDefault();
const currentInput = e.target;
const currentRow = currentInput.closest('tr');
const rows = Array.from(itemsTableBody.querySelectorAll('tr'));
const currentRowIndex = rows.indexOf(currentRow);
const inputs = Array.from(currentRow.querySelectorAll('input'));
const currentInputIndex = inputs.indexOf(currentInput);
const isLastRow = currentRowIndex === rows.length - 1;
const hasData = rowHasData(currentRow);
if (currentInputIndex < inputs.length - 1) {
inputs[currentInputIndex + 1].focus();
return;
}
if (!isLastRow) {
const nextRow = rows[currentRowIndex + 1];
const firstInputNextRow = nextRow.querySelector('input');
if (firstInputNextRow) firstInputNextRow.focus();
return;
}
if (isLastRow && hasData) {
const newIndex = rows.length;
addRow(newIndex);
reindexRows();
const newRow = itemsTableBody.querySelector('tr:last-child');
const firstInput = newRow.querySelector('input');
if (firstInput) firstInput.focus();
}
});
itemsTableBody.addEventListener('click', function(e) {
if (!e.target.classList.contains('remove-row-btn')) return;
const rows = itemsTableBody.querySelectorAll('tr');
if (rows.length <= 1) {
alert('At least one row must exist.');
return;
}
e.target.closest('tr').remove();
reindexRows();
});
// ===== EXCEL UPLOAD LOGIC =====
const uploadExcelBtn = document.getElementById('uploadExcelBtn');
const excelInput = document.getElementById('excelInput');
if (uploadExcelBtn && excelInput) {
uploadExcelBtn.addEventListener('click', () => excelInput.click());
excelInput.addEventListener('change', function () {
const file = this.files[0];
if (!file) return;
const formData = new FormData();
formData.append('excel', file);
formData.append('_token', '{{ csrf_token() }}');
fetch('{{ route("admin.orders.upload.excel.preview") }}', {
method: 'POST',
body: formData,
headers: {
'Accept': 'application/json'
}
})
.then(async res => {
if (!res.ok) {
const text = await res.text();
throw new Error(text);
}
return res.json();
})
.then(res => {
if (!res.success) {
alert('Invalid Excel file');
return;
}
populateItemsTable(res.items);
})
.catch(err => {
console.error(err);
alert('Excel upload failed');
});
});
}
function populateItemsTable(items) {
itemsTableBody.innerHTML = '';
function populateItemsTable(items) {
itemsTableBody.innerHTML = '';
items.forEach((item, index) => {
addRow(index);
const row = itemsTableBody.children[index];
row.querySelector('[data-field="description"]').value = item.description ?? '';
row.querySelector('[data-field="ctn"]').value = item.ctn ?? 0;
row.querySelector('[data-field="qty"]').value = item.qty ?? 0;
row.querySelector('[data-field="unit"]').value = item.unit ?? '';
row.querySelector('[data-field="price"]').value= item.price ?? 0;
row.querySelector('[data-field="cbm"]').value = item.cbm ?? 0;
row.querySelector('[data-field="kg"]').value = item.kg ?? 0;
row.querySelector('[data-field="shop_no"]').value = item.shop_no ?? '';
// 🔥 ALWAYS RECALCULATE
calculateRow(row);
});
reindexRows();
}
});
</script>
<script>
document.addEventListener("hidden.bs.modal", function () {
document.querySelectorAll(".modal-backdrop").forEach(el => el.remove());
document.body.classList.remove("modal-open");
document.body.style.overflow = "";
});
</script>
@endsection