2953 lines
93 KiB
PHP
2953 lines
93 KiB
PHP
@extends('admin.layouts.app')
|
||
|
||
@section('page-title', 'Account Dashboard')
|
||
|
||
@section('content')
|
||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||
<style>
|
||
/* ---------- Base ---------- */
|
||
:root{
|
||
--primary-1:#1a2951;
|
||
--primary-2:#243a72;
|
||
--muted:#9ba5bb;
|
||
--card-bg: linear-gradient(180deg,#ffffff,#f5f7fb);
|
||
--glass: rgba(255,255,255,0.6);
|
||
--success:#34c86c;
|
||
--danger:#ef4f4f;
|
||
--accent:#276dea;
|
||
--rounded:12px;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Segoe UI', Arial, sans-serif;
|
||
background: linear-gradient(135deg, #eef2f7, #f9fbff);
|
||
margin:0; padding:0;
|
||
color:#253047;
|
||
-webkit-font-smoothing:antialiased;
|
||
}
|
||
|
||
/* container */
|
||
.account-container {
|
||
padding: 28px 34px;
|
||
max-width:1400px;
|
||
margin: 18px auto;
|
||
box-sizing:border-box;
|
||
}
|
||
|
||
/* header */
|
||
.account-header {
|
||
margin-bottom: 18px;
|
||
background: linear-gradient(90deg,var(--primary-1),var(--primary-2));
|
||
padding: 22px 26px;
|
||
border-radius: var(--rounded);
|
||
box-shadow: 0 6px 18px rgba(34,50,90,0.12);
|
||
color: #fff;
|
||
}
|
||
.account-header h2 { font-size: 26px; font-weight:700; margin:0 0 6px 0; }
|
||
.account-header p { font-size:14px; margin:2px 0; opacity:0.95 }
|
||
|
||
/* top actions row */
|
||
.top-actions {
|
||
display:flex; align-items:center; justify-content:space-between;
|
||
gap:12px; margin:16px 0 20px 0; flex-wrap:wrap;
|
||
}
|
||
.top-actions .left {
|
||
display:flex; gap:12px; align-items:center; flex-wrap:wrap;
|
||
}
|
||
.search-row input {
|
||
width:360px; padding:10px 14px; border-radius:8px; border:1px solid #d6dde9;
|
||
background: #fff; font-size:14px; box-shadow:0 1px 3px rgba(0,0,0,0.03);
|
||
}
|
||
.search-row input:focus{ outline:none; border-color:var(--primary-1); box-shadow:0 6px 18px rgba(26,41,81,0.06);}
|
||
|
||
.btn {
|
||
display:inline-flex; align-items:center; justify-content:center; gap:8px;
|
||
background: linear-gradient(90deg,var(--primary-1),var(--primary-2));
|
||
color:#fff; border:none; padding:10px 16px; border-radius:10px; font-weight:600;
|
||
cursor:pointer; transition: transform .15s ease, box-shadow .15s;
|
||
}
|
||
.btn.ghost { background: transparent; color:var(--primary-1); border:1.5px solid #dbe4f5; box-shadow:none; }
|
||
.btn:hover{ transform: translateY(-3px); box-shadow: 0 8px 26px rgba(36,58,114,0.12); }
|
||
|
||
/* account panels */
|
||
.account-panels {
|
||
display:flex;
|
||
gap:22px;
|
||
align-items:stretch;
|
||
flex-wrap:wrap;
|
||
}
|
||
|
||
/* Payment block with pagination */
|
||
.payment-block {
|
||
flex:1;
|
||
min-width:48%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.panel-card {
|
||
background: var(--card-bg);
|
||
border-radius:12px;
|
||
box-shadow:0 8px 20px rgba(25,40,80,0.06);
|
||
padding:22px;
|
||
box-sizing:border-box;
|
||
overflow-x:auto;
|
||
transition: transform .12s, box-shadow .12s;
|
||
min-height: 520px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 1;
|
||
height: 100%;
|
||
}
|
||
.panel-card:hover{ transform: translateY(-4px); box-shadow:0 12px 28px rgba(25,40,80,0.08); }
|
||
.panel-title {
|
||
font-weight:700;
|
||
font-size:16px;
|
||
color:var(--primary-1);
|
||
margin-bottom:16px;
|
||
display:flex;
|
||
align-items:center;
|
||
justify-content:space-between;
|
||
}
|
||
|
||
/* table */
|
||
table {
|
||
width:100%;
|
||
border-collapse:collapse;
|
||
min-width:720px;
|
||
font-size:14px;
|
||
flex:1;
|
||
}
|
||
th, td {
|
||
padding:12px 14px;
|
||
text-align:left;
|
||
border-bottom:1px solid #eef3fb;
|
||
white-space:nowrap;
|
||
color:#2d3b53;
|
||
}
|
||
th {
|
||
background: linear-gradient(90deg,#f6f9ff,#fbfdff);
|
||
color:#4a5570;
|
||
font-weight:700;
|
||
font-size:13px;
|
||
}
|
||
tr:hover td{ background:#fbfdff; }
|
||
.entry-link{ color:var(--accent); text-decoration:underline; cursor:pointer; font-weight:700; }
|
||
|
||
/* badges */
|
||
|
||
/* === Modern Status Badges === */
|
||
.status-badge {
|
||
display: inline-block;
|
||
padding: 6px 16px;
|
||
border-radius: 999px;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
min-width: 76px;
|
||
text-align: center;
|
||
box-shadow: 0 2px 8px rgba(33, 43, 90, 0.07);
|
||
letter-spacing: 0.1px;
|
||
background: #6b7280;
|
||
transition: box-shadow 0.22s, transform 0.17s, background 0.22s;
|
||
vertical-align: middle;
|
||
margin: 3px 2px;
|
||
cursor: default;
|
||
backdrop-filter: blur(2px);
|
||
width: 99px;
|
||
}
|
||
|
||
.status-badge:hover {
|
||
transform: translateY(-3px) scale(1.045);
|
||
box-shadow: 0 4px 16px rgba(33, 43, 90, 0.16);
|
||
opacity: 0.96;
|
||
}
|
||
|
||
/* High-impact, soft gradients for each status */
|
||
.status-unpaid {
|
||
background: linear-gradient(90deg, #ff5959, #dc3545);
|
||
border: 1.5px solid #d42c3f21;
|
||
width: 95px;
|
||
}
|
||
.status-paid {
|
||
background: linear-gradient(90deg, #36d399 0%, #4ade80 100%);
|
||
border: 1.5px solid #31b47a1a;
|
||
width: 95px;
|
||
}
|
||
.status-loading {
|
||
background: linear-gradient(90deg, #509cf8 0%, #3f79d3 100%);
|
||
border: 1.5px solid #1665c320;
|
||
width: 95px;
|
||
}
|
||
.status-dispatched {
|
||
background: linear-gradient(90deg, #9775fa 0%, #845ef7 100%);
|
||
border: 1.5px solid #9775fa40;
|
||
width: 95px;
|
||
}
|
||
.pending-badge-red {
|
||
background: linear-gradient(90deg, #f43f5e, #ef4444);
|
||
border: 1.5px solid #f43f5e41;
|
||
width: 95px;
|
||
}
|
||
.pending-badge-green {
|
||
background: linear-gradient(90deg, #10b981 0%, #22d3ee 100%);
|
||
border: 1.5px solid #10b98141;
|
||
width: 95px;
|
||
}
|
||
|
||
.status-delivered {
|
||
background: linear-gradient(90deg, #22c55e 0%, #16a34a 100%);
|
||
border: 1.5px solid #22c55e44;
|
||
width: 95px;
|
||
}
|
||
|
||
/* 3-state toggle */
|
||
.toggle-switch-btn {
|
||
appearance:none;
|
||
-webkit-appearance:none;
|
||
width:60px;
|
||
height:24px;
|
||
background:#f25b5b;
|
||
border:2px solid #f25b5b;
|
||
border-radius:999px;
|
||
position:relative;
|
||
outline:none;
|
||
cursor:pointer;
|
||
transition: background .22s, border .22s;
|
||
}
|
||
|
||
.toggle-switch-btn::before{
|
||
content:"";
|
||
width:20px;
|
||
height:20px;
|
||
background:#fff;
|
||
border-radius:50%;
|
||
position:absolute;
|
||
top:1.7px;
|
||
left:2px;
|
||
transition: transform .22s;
|
||
box-shadow:0 2px 8px rgba(0,0,0,0.15);
|
||
}
|
||
|
||
/* YELLOW */
|
||
.toggle-switch-btn.mid {
|
||
background:#f2c94c;
|
||
border-color:#f2c94c;
|
||
}
|
||
.toggle-switch-btn.mid::before {
|
||
transform: translateX(20px);
|
||
}
|
||
|
||
/* GREEN */
|
||
.toggle-switch-btn.checked {
|
||
background:#43d05c;
|
||
border-color:#43d05c;
|
||
}
|
||
.toggle-switch-btn.checked::before {
|
||
transform: translateX(38px);
|
||
}
|
||
|
||
/* plus button */
|
||
.plus-btn {
|
||
display:inline-block;
|
||
width:36px;
|
||
height:36px;
|
||
border-radius:10px;
|
||
background:#fff;
|
||
color:var(--primary-1);
|
||
border:1.5px solid #e6edf8;
|
||
font-size:1.15rem;
|
||
font-weight:700;
|
||
text-align:center;
|
||
line-height:34px;
|
||
cursor:pointer;
|
||
transition: transform .12s;
|
||
}
|
||
.plus-btn:hover{ transform: translateY(-3px); box-shadow:0 8px 16px rgba(33,47,90,0.04); }
|
||
|
||
/* ---------- Create Order Popup Modal ---------- */
|
||
.create-order-modal {
|
||
position:fixed;
|
||
inset:0;
|
||
background:rgba(16,24,50,0.44);
|
||
display:none;
|
||
align-items:center;
|
||
justify-content:center;
|
||
z-index:1200;
|
||
padding:18px;
|
||
}
|
||
.create-order-modal.modal-open { display:flex; }
|
||
.create-order-modal .modal-box {
|
||
background:#fff;
|
||
border-radius:12px;
|
||
padding:20px 24px;
|
||
box-shadow:0 14px 40px rgba(18,30,60,0.12);
|
||
max-width:1200px;
|
||
width:100%;
|
||
max-height:92vh;
|
||
overflow:auto;
|
||
}
|
||
|
||
/* consolidated orders area */
|
||
.consolidate-area {
|
||
background: linear-gradient(90deg,#fbfbff,#f9fcff);
|
||
border:1px solid #eef5ff;
|
||
padding:14px;
|
||
border-radius:10px;
|
||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.6);
|
||
margin-top:12px;
|
||
overflow:auto;
|
||
min-height: 400px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* compact table in consolidate */
|
||
#consolidateOrdersTable th, #consolidateOrdersTable td { padding:10px 12px; font-size:13px; border-bottom:1px solid #f1f6ff; }
|
||
|
||
/* ---------- Pagination Styles ---------- */
|
||
.pagination-container {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-top: 15px;
|
||
padding: 12px 0;
|
||
border-top: 1px solid #eef3fb;
|
||
margin-right:550px;
|
||
}
|
||
|
||
.pagination-info {
|
||
font-size: 13px;
|
||
color: var(--muted);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.pagination-controls {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-right:-1050px;
|
||
|
||
}
|
||
.pagination-controls1 {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-right:-550px;
|
||
|
||
}
|
||
|
||
.pagination-controls2 {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-right:-550px;
|
||
|
||
}
|
||
|
||
|
||
.pagination-btn {
|
||
background: #fff;
|
||
border: 1px solid #e3eaf6;
|
||
color: var(--primary-1);
|
||
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;
|
||
}
|
||
|
||
.pagination-btn:hover:not(:disabled) {
|
||
background: var(--primary-1);
|
||
color: white;
|
||
border-color: var(--primary-1);
|
||
}
|
||
|
||
.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: var(--primary-1);
|
||
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;
|
||
}
|
||
|
||
.pagination-page-btn:hover {
|
||
background: var(--primary-1);
|
||
color: white;
|
||
border-color: var(--primary-1);
|
||
}
|
||
|
||
.pagination-page-btn.active {
|
||
background: var(--primary-1);
|
||
color: white;
|
||
border-color: var(--primary-1);
|
||
}
|
||
|
||
.pagination-pages {
|
||
display: flex;
|
||
gap: 4px;
|
||
align-items: center;
|
||
}
|
||
|
||
.pagination-ellipsis {
|
||
color: var(--muted);
|
||
font-size: 13px;
|
||
padding: 0 4px;
|
||
}
|
||
|
||
/* Image-based pagination buttons */
|
||
.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: var(--primary-1);
|
||
border-color: var(--primary-1);
|
||
}
|
||
|
||
.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%);
|
||
}
|
||
|
||
/* ---------- Entry Details Modal (existing) ---------- */
|
||
.modal-fade1 {
|
||
position:fixed;
|
||
inset:0;
|
||
background:rgba(16,24,50,0.44);
|
||
display:none;
|
||
align-items:center;
|
||
justify-content:center;
|
||
z-index:1200;
|
||
padding:18px;
|
||
}
|
||
.modal-fade1.modal-open { display:flex; }
|
||
.modal-box1 {
|
||
background:#fff;
|
||
border-radius:12px;
|
||
padding:20px 24px;
|
||
box-shadow:0 14px 40px rgba(18,30,60,0.12);
|
||
max-width:1200px;
|
||
width:100%;
|
||
max-height:92vh;
|
||
overflow:auto;
|
||
min-height: 500px;
|
||
}
|
||
#entryOrdersModal {
|
||
z-index: 1300;
|
||
}
|
||
|
||
/* entry summary cards */
|
||
.entry-summary-cards {
|
||
display:flex;
|
||
gap:16px;
|
||
margin-bottom:20px;
|
||
flex-wrap:wrap;
|
||
}
|
||
.entry-summary-card {
|
||
background:#fbfdff;
|
||
border:1px solid #eef6ff;
|
||
padding:16px;
|
||
border-radius:10px;
|
||
min-width:180px;
|
||
box-shadow:0 6px 18px rgba(22,36,72,0.03);
|
||
flex:1;
|
||
}
|
||
.entry-summary-label{ font-size:12px; color:var(--muted); }
|
||
.entry-summary-value{ font-size:18px; font-weight:700; color:#253047; margin-top:6px; }
|
||
|
||
/* installment modal */
|
||
#installmentModal .modal-box1 { max-width:720px; min-width:380px; }
|
||
|
||
/* small helpers */
|
||
.helper-note{ font-size:12px; color:#7687a3; margin-top:6px; display:block; }
|
||
.empty-state{ padding:18px; text-align:center; color:#6f7b8f; }
|
||
.kv { font-weight:700; color:#26364f; }
|
||
|
||
/* form styles */
|
||
.input, select {
|
||
width:100%;
|
||
padding:12px 14px;
|
||
border-radius:8px;
|
||
border:1.3px solid #e3eaf6;
|
||
background:#fff;
|
||
font-size:14px;
|
||
box-sizing:border-box;
|
||
}
|
||
|
||
/* Additional styling for the installment dropdown - doesn't affect original class */
|
||
.installment-status-dropdown {
|
||
cursor: pointer;
|
||
appearance: none;
|
||
background-image: url('data:image/svg+xml;charset=US-ASCII,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="%236b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>');
|
||
background-repeat: no-repeat;
|
||
background-position: right 12px center;
|
||
padding-right: 40px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.installment-status-dropdown:hover {
|
||
border-color: #c2d1f0;
|
||
}
|
||
|
||
.installment-status-dropdown:focus {
|
||
outline: none;
|
||
border-color: #4d7cfe;
|
||
box-shadow: 0 0 0 3px rgba(77, 124, 254, 0.1);
|
||
}
|
||
|
||
/* Status-specific styling for selected option */
|
||
.installment-status-dropdown option:checked {
|
||
font-weight: 600;
|
||
}
|
||
.create-grid {
|
||
display:grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap:16px;
|
||
align-items:start;
|
||
margin-bottom:16px;
|
||
}
|
||
.create-actions {
|
||
display:flex;
|
||
gap:12px;
|
||
justify-content:flex-end;
|
||
margin-top:16px;
|
||
}
|
||
|
||
/* Combined filters row styling */
|
||
.combined-filters-row {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
flex-wrap: wrap;
|
||
padding: 14px 16px;
|
||
background: linear-gradient(90deg, #f8fbff, #f5f9ff);
|
||
border-radius: 10px;
|
||
border: 1px solid #eef3fb;
|
||
width: 365px;
|
||
}
|
||
|
||
.right{
|
||
margin-left:auto;
|
||
margin-top:-16px;
|
||
}
|
||
|
||
.filter-group1 {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
width: 159px;
|
||
}
|
||
.filter-group2 {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
width: 160px;
|
||
}
|
||
|
||
.filter-group3 {
|
||
flex: 1;
|
||
width: 159px;
|
||
}
|
||
|
||
.filter-label {
|
||
display: block;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: #44546a;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.payment-filters-section {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
gap: 16px;
|
||
}
|
||
|
||
.filter-group4 {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
width: 160px;
|
||
}
|
||
|
||
|
||
.filter-label {
|
||
display: block;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: #44546a;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
/* Status option colors */
|
||
.filter-control option {
|
||
padding: 10px 12px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.filter-control option[value=""] {
|
||
color: #6b778c;
|
||
}
|
||
|
||
.filter-control option[value="pending"] {
|
||
color: #f59e0b;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.filter-control option[value="loading"] {
|
||
color: #3b82f6;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.filter-control option[value="packed"] {
|
||
color: #8b5cf6;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.filter-control option[value="dispatched"] {
|
||
color: #10b981;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.filter-control option[value="delivered"] {
|
||
color: #0c6b2e;
|
||
font-weight: 500;
|
||
}
|
||
|
||
|
||
|
||
|
||
.status-color {
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.status-color.pending { background-color: #f59e0b; }
|
||
.status-color.loading { background-color: #3b82f6; }
|
||
.status-color.packed { background-color: #8b5cf6; }
|
||
.status-color.dispatched { background-color: #10b981; }
|
||
.status-color.delivered { background-color: #0c6b2e; }
|
||
|
||
|
||
|
||
|
||
/* Style for options with icons */
|
||
.filter-control option {
|
||
padding: 8px 12px;
|
||
}
|
||
|
||
.filter-control option[value=""] {
|
||
color: #6b778c;
|
||
}
|
||
|
||
.filter-control option[value="paid"] {
|
||
color: #0c6b2e;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.filter-control option[value="unpaid"] {
|
||
color: #c9372c;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.filter-control option[value="pending"] {
|
||
color: #f59e0b;
|
||
font-weight: 500;
|
||
}
|
||
|
||
|
||
.filter-group4 {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
width: 510px;
|
||
}
|
||
|
||
/* Modern filter alternative with icons */
|
||
.filter-status-buttons {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.status-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 6px 12px;
|
||
border-radius: 6px;
|
||
border: 1px solid #e3eaf6;
|
||
background: #fff;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.status-btn:hover {
|
||
background-color: #f8fafc;
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.status-btn.active {
|
||
border-color: transparent;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
||
}
|
||
|
||
.status-btn[data-status="paid"] {
|
||
color: #0c6b2e;
|
||
background-color: #f0f9f0;
|
||
}
|
||
|
||
.status-btn[data-status="paid"].active {
|
||
background-color: #e1f7e1;
|
||
}
|
||
|
||
.status-btn[data-status="paid"] i {
|
||
color: #0c6b2e;
|
||
}
|
||
|
||
.status-btn[data-status="unpaid"] {
|
||
color: #c9372c;
|
||
background-color: #fef0f0;
|
||
}
|
||
|
||
.status-btn[data-status="unpaid"].active {
|
||
background-color: #fde8e8;
|
||
}
|
||
|
||
.status-btn[data-status="unpaid"] i {
|
||
color: #c9372c;
|
||
}
|
||
|
||
.status-btn[data-status="pending"] {
|
||
color: #f59e0b;
|
||
background-color: #fff7e6;
|
||
}
|
||
|
||
.status-btn[data-status="pending"].active {
|
||
background-color: #fff0cc;
|
||
}
|
||
|
||
.status-btn[data-status="pending"] i {
|
||
color: #f59e0b;
|
||
}
|
||
|
||
.status-btn[data-status="all"] {
|
||
color: #44546a;
|
||
background-color: #f8fafc;
|
||
}
|
||
|
||
.status-btn[data-status="all"].active {
|
||
background-color: #f1f5f9;
|
||
}
|
||
|
||
.status-btn[data-status="all"] i {
|
||
color: #44546a;
|
||
}
|
||
|
||
|
||
|
||
.filter-label {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--primary-1);
|
||
}
|
||
|
||
.filter-control {
|
||
padding: 8px 12px;
|
||
border-radius: 8px;
|
||
border: 1px solid #e3eaf6;
|
||
background: #fff;
|
||
font-size: 14px;
|
||
min-width: 140px;
|
||
}
|
||
|
||
.filter-control:focus {
|
||
outline: none;
|
||
border-color: var(--primary-1);
|
||
box-shadow: 0 0 0 2px rgba(26, 41, 81, 0.1);
|
||
}
|
||
|
||
/* Table-specific filter sections */
|
||
.payment-filters-section, .order-filters-section {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
margin-bottom: 16px;
|
||
padding: 12px 14px;
|
||
background: linear-gradient(90deg, #f9fbff, #f7faff);
|
||
border-radius: 8px;
|
||
border: 1px solid #eef3fb;
|
||
}
|
||
|
||
/* Action buttons in table */
|
||
.action-btn {
|
||
background: transparent;
|
||
border: none;
|
||
cursor: pointer;
|
||
padding: 6px 8px;
|
||
border-radius: 6px;
|
||
transition: all 0.2s ease;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.edit-btn {
|
||
color: var(--primary-1);
|
||
}
|
||
|
||
.edit-btn:hover {
|
||
background: rgba(26, 41, 81, 0.08);
|
||
}
|
||
|
||
.delete-btn {
|
||
color: var(--danger);
|
||
}
|
||
|
||
.delete-btn:hover {
|
||
background: rgba(239, 79, 79, 0.08);
|
||
}
|
||
|
||
.action-btns {
|
||
display: flex;
|
||
gap: 4px;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* Edit modal styles */
|
||
.edit-modal .modal-box1 {
|
||
max-width: 800px;
|
||
z-index: 1250;
|
||
}
|
||
|
||
.edit-form-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 16px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.edit-form-actions {
|
||
display: flex;
|
||
gap: 12px;
|
||
justify-content: flex-end;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
/* Order management in edit modal */
|
||
.order-management-section {
|
||
margin-top: 20px;
|
||
border-top: 1px solid #eef3fb;
|
||
padding-top: 16px;
|
||
}
|
||
|
||
.order-management-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.order-management-title {
|
||
font-weight: 700;
|
||
color: var(--primary-1);
|
||
font-size: 16px;
|
||
}
|
||
|
||
.orders-table-container {
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
border: 1px solid #eef3fb;
|
||
border-radius: 8px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.orders-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.orders-table th {
|
||
background: #f8fbff;
|
||
padding: 10px 12px;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
color: var(--primary-1);
|
||
border-bottom: 1px solid #eef3fb;
|
||
position: sticky;
|
||
top: 0;
|
||
}
|
||
|
||
.orders-table td {
|
||
padding: 10px 12px;
|
||
border-bottom: 1px solid #f1f6ff;
|
||
}
|
||
|
||
.order-actions {
|
||
display: flex;
|
||
gap: 6px;
|
||
}
|
||
|
||
.remove-order-btn {
|
||
background: var(--danger);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
padding: 4px 8px;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
transition: background 0.2s;
|
||
}
|
||
|
||
.remove-order-btn:hover {
|
||
background: #d42c3f;
|
||
}
|
||
|
||
/* Add Order Modal */
|
||
.add-order-modal .modal-box1 {
|
||
max-width: 1000px;
|
||
}
|
||
|
||
.add-order-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 12px;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
/* responsive */
|
||
@media (max-width:980px){
|
||
.create-grid { grid-template-columns: 1fr; }
|
||
.panel-card { min-width:100%; }
|
||
.search-row input{ width:220px; }
|
||
.pagination-container { flex-direction: column; gap: 10px; align-items: stretch; }
|
||
.pagination-controls { justify-content: center; }
|
||
.account-container { padding: 20px; }
|
||
.panel-card { padding: 18px; }
|
||
.combined-filters-row { flex-direction: column; align-items: stretch; }
|
||
.filter-group { width: 100%; }
|
||
.filter-control { min-width: auto; }
|
||
.edit-form-grid { grid-template-columns: 1fr; }
|
||
.payment-filters-section, .order-filters-section { flex-direction: column; align-items: stretch; }
|
||
}
|
||
|
||
@media (max-width:768px){
|
||
.account-header { padding: 18px; }
|
||
.top-actions { flex-direction: column; align-items: stretch; }
|
||
.top-actions .left { justify-content: center; }
|
||
.entry-summary-cards { gap: 12px; }
|
||
.entry-summary-card { min-width: 140px; }
|
||
.edit-form-grid { grid-template-columns: 1fr; }
|
||
.order-management-header { flex-direction: column; align-items: flex-start; gap: 10px; }
|
||
}
|
||
|
||
/* Table pagination wrapper */
|
||
.table-pagination-wrapper {
|
||
margin-top: 20px;
|
||
border-top: 1px solid #eef3fb;
|
||
padding-top: 15px;
|
||
}
|
||
|
||
/* Modal pagination wrapper */
|
||
.modal-pagination-wrapper {
|
||
margin-top: 15px;
|
||
border-top: 1px solid #eef3fb;
|
||
padding-top: 12px;
|
||
}
|
||
|
||
/* Right-aligned pagination */
|
||
.pagination-right {
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
/* Combined top row styling */
|
||
.combined-top-row {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
</style>
|
||
|
||
<div class="account-container">
|
||
<!-- Header -->
|
||
<div class="account-header">
|
||
<h2>Account Dashboard</h2>
|
||
<p>Viewing: All Regions — Workflow: Manage payments and dispatch.</p>
|
||
</div>
|
||
|
||
<!-- Combined Top Row - Search, Create Installment, Date Filters, Refresh -->
|
||
<div class="top-actions">
|
||
<div class="combined-top-row">
|
||
<!-- Search Section -->
|
||
<div class="search-row">
|
||
<input type="text" id="main-search" placeholder="Search by Entry No or Description" aria-label="Search entries">
|
||
<button class="btn ghost" id="searchBtn">Search</button>
|
||
</div>
|
||
|
||
<!-- Create Installment Button -->
|
||
<button class="btn" id="openCreateModalBtn">+ Create New Installment</button>
|
||
|
||
<!-- Date Filters -->
|
||
<div class="combined-filters-row">
|
||
<div class="filter-group1">
|
||
<label class="filter-label">From Date</label>
|
||
<input type="date" id="fromDateFilter" class="filter-control">
|
||
</div>
|
||
<div class="filter-group2">
|
||
<label class="filter-label">To Date</label>
|
||
<input type="date" id="toDateFilter" class="filter-control">
|
||
</div>
|
||
</div>
|
||
<div class="right">
|
||
<!-- Refresh Button -->
|
||
<button class="btn" id="refreshBtn">⟳ Refresh</button>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- Panels -->
|
||
<div class="account-panels" id="account-panels">
|
||
<!-- Payment Sent block + pagination एकत्र -->
|
||
<div class="payment-block">
|
||
<div class="panel-card">
|
||
<div class="panel-title">
|
||
<span>Payment Sent to China</span>
|
||
<span style="font-size:13px;color:var(--muted)">Total entries: <span id="entriesCount">0</span></span>
|
||
</div>
|
||
|
||
<!-- PAYMENT TABLE FILTERS (inside table) -->
|
||
<div class="payment-filters-section">
|
||
<div class="filter-group3">
|
||
<label class="filter-label">Payment Status</label>
|
||
<select id="paymentStatusFilter" class="filter-control">
|
||
<option value="">📋 All Statuses</option>
|
||
<option value="paid">✅ Paid</option>
|
||
<option value="unpaid">❌ Unpaid</option>
|
||
<option value="pending">⏳ Pending</option>
|
||
</select>
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<table id="main-payment-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Entry No</th><th>Date</th><th>Description</th>
|
||
<th>Order Quantity</th><th>Region</th><th>Payment</th><th>Amount</th><th>Status</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="paymentTableBody">
|
||
<!-- Entries will be loaded here -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Pagination for Payment Table OUTSIDE table but SAME column -->
|
||
<div class="table-pagination-wrapper pagination-right">
|
||
<div class="pagination-container">
|
||
<div class="pagination-info" id="paymentPageInfo">Showing 0 entries</div>
|
||
<div class="pagination-controls">
|
||
<button class="pagination-img-btn" id="paymentPrevBtn" 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="paymentPaginationPages">
|
||
<!-- Page numbers will be inserted here -->
|
||
</div>
|
||
<button class="pagination-img-btn" id="paymentNextBtn" 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>
|
||
|
||
<!-- Order Dispatch Table -->
|
||
<div class="panel-card">
|
||
<div class="panel-title">
|
||
<span>Order Dispatch Status</span>
|
||
<span style="font-size:13px;color:var(--muted)">Actions: <strong>+ Add Installment</strong></span>
|
||
</div>
|
||
|
||
<!-- ORDER TABLE FILTERS (inside table) -->
|
||
<div class="order-filters-section">
|
||
<div class="filter-group4">
|
||
<label class="filter-label">Dispatch Status</label>
|
||
<select id="orderStatusFilter" class="filter-control">
|
||
<option value="">📋 All Statuses</option>
|
||
<option value="pending">⏳ Pending</option>
|
||
<option value="loading">📦 Loading</option>
|
||
<option value="packed">📦 Packed</option>
|
||
<option value="dispatched">🚚 Dispatched</option>
|
||
<option value="delivered">✅ Delivered</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<table id="main-order-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Entry No</th><th>Date</th><th>Description</th>
|
||
<th>Region</th><th>Amount</th><th>Status</th>
|
||
<th>Pending</th><th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="orderTableBody">
|
||
<!-- Entries will be loaded here -->
|
||
</tbody>
|
||
</table>
|
||
<!-- No pagination for second table -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- CREATE ORDER POPUP MODAL -->
|
||
<div class="create-order-modal" id="createOrderModal">
|
||
<div class="modal-box">
|
||
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:16px;">
|
||
<div style="font-size:20px; font-weight:800; color:var(--primary-1)">Create New Installment</div>
|
||
<button class="btn ghost" id="closeCreateModal" title="Close create form">✕</button>
|
||
</div>
|
||
|
||
<form id="createOrderInlineForm" autocomplete="off">
|
||
<div class="create-grid">
|
||
<div>
|
||
<label for="inline_description">Description *</label>
|
||
<input class="input" type="text" id="inline_description" name="description" required placeholder="Short description for consolidated entry">
|
||
</div>
|
||
<div>
|
||
<label for="inline_region">Region *</label>
|
||
<select id="inline_region" name="region" class="input" required>
|
||
<option value="China">China</option>
|
||
<option value="Europe">Europe</option>
|
||
<option value="US">US</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="inline_amount">Total Amount (₹) *</label>
|
||
<input class="input" type="number" id="inline_amount" name="amount" min="0" required>
|
||
</div>
|
||
<div>
|
||
<label for="inline_entry_date">Entry Date *</label>
|
||
<input class="input" type="date" id="inline_entry_date" name="entry_date" value="{{ date('Y-m-d') }}" required>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top:16px;">
|
||
<div style="display:flex; align-items:center; margin-bottom:8px;">
|
||
<div style="font-weight:700; color:#233063; margin-right:6px;">Consolidated Orders</div>
|
||
<div style="margin-left:auto; font-size:13px; color:var(--muted);">Select orders to include in the consolidated entry</div>
|
||
</div>
|
||
|
||
<div class="consolidate-area" id="consolidateArea">
|
||
<table id="consolidateOrdersTable">
|
||
<thead>
|
||
<tr>
|
||
<th></th>
|
||
<th>Order ID</th>
|
||
<th>Mark No</th>
|
||
<th>Origin</th>
|
||
<th>Destination</th>
|
||
<th>CTN</th>
|
||
<th>QTY</th>
|
||
<th>TTL/QTY</th>
|
||
<th>Total Amount (₹)</th>
|
||
<th>CBM</th>
|
||
<th>TTL CBM</th>
|
||
<th>KG</th>
|
||
<th>TTL KG</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="consolidateOrdersBody">
|
||
<!-- Orders will be loaded here -->
|
||
</tbody>
|
||
</table>
|
||
|
||
<!-- Pagination for Create Order Modal -->
|
||
<div class="modal-pagination-wrapper pagination-right">
|
||
<div class="pagination-container">
|
||
<div class="pagination-info" id="modalPageInfo">Showing 0 entries</div>
|
||
<div class="pagination-controls2">
|
||
<button class="pagination-img-btn" id="modalPrevBtn" 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="modalPaginationPages">
|
||
<!-- Page numbers will be inserted here -->
|
||
</div>
|
||
<button class="pagination-img-btn" id="modalNextBtn" 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 class="create-actions">
|
||
<button type="button" class="btn ghost" id="cancelCreateModal">Cancel</button>
|
||
<button type="submit" class="btn">Create Installment</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ENTRY DETAILS MODAL -->
|
||
<div class="modal-fade1" id="entryDetailsModal">
|
||
<div class="modal-box1 entry-details-modal">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
||
<div>
|
||
<h2 style="margin:0;font-size:20px;color:#223256;font-weight:800">Entry Details — <span id="entryDetailsId">-</span></h2>
|
||
<div style="font-size:13px;color:var(--muted)">Complete view of all installments for this entry.</div>
|
||
</div>
|
||
<div><button class="btn ghost" onclick="closeEntryDetailsModal()">Close</button></div>
|
||
</div>
|
||
|
||
<div class="entry-summary-cards">
|
||
<div class="entry-summary-card">
|
||
<div class="entry-summary-label">Original Amount</div>
|
||
<div class="entry-summary-value" id="originalAmount">-</div>
|
||
</div>
|
||
<div class="entry-summary-card">
|
||
<div class="entry-summary-label">Total Processed</div>
|
||
<div class="entry-summary-value" id="totalProcessed">-</div>
|
||
</div>
|
||
<div class="entry-summary-card">
|
||
<div class="entry-summary-label">Pending Balance</div>
|
||
<div class="entry-summary-value" id="pendingBalance">-</div>
|
||
</div>
|
||
<div class="entry-summary-card">
|
||
<div class="entry-summary-label">Total Installments</div>
|
||
<div class="entry-summary-value" id="totalInstallments">-</div>
|
||
</div>
|
||
</div>
|
||
|
||
<table class="entry-installments-table" style="width:100%; border-collapse:collapse;">
|
||
<thead>
|
||
<tr>
|
||
<th>Installment</th>
|
||
<th>Date</th>
|
||
<th>Description</th>
|
||
<th>Region</th>
|
||
<th>Amount</th>
|
||
<th>Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="installmentsTableBody">
|
||
<tr><td colspan="6" class="empty-state">No installments yet</td></tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<div style="display:flex; justify-content: flex-end; gap:12px; margin-top:16px;">
|
||
<button type="button" class="btn ghost" onclick="closeEntryDetailsModal()">Close</button>
|
||
<button type="button" class="btn" id="addInstallmentFromDetails">+ Add New Installment</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ENTRY ORDERS MODAL -->
|
||
<div class="modal-fade1" id="entryOrdersModal">
|
||
<div class="modal-box1" style="max-width: 1000px;">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;">
|
||
<div>
|
||
<h2 style="margin:0;font-size:20px;color:#223256;font-weight:800;">
|
||
Entry Orders
|
||
<span id="entryOrdersEntryNo-span" style="font-size:14px;color:#6b7280;margin-left:8px;"></span>
|
||
</h2>
|
||
<div style="font-size:13px;color:#6f7b8f;">
|
||
All orders associated with this entry.
|
||
</div>
|
||
</div>
|
||
<button class="btn ghost" type="button" onclick="closeEntryOrdersModal()">
|
||
Close
|
||
</button>
|
||
</div>
|
||
|
||
<div class="orders-table-container">
|
||
<table class="orders-table" style="width:100%;border-collapse:collapse;font-size:13px;">
|
||
<thead>
|
||
<tr>
|
||
<th>Order ID</th>
|
||
<th>Mark No</th>
|
||
<th>Origin</th>
|
||
<th>Destination</th>
|
||
<th>CTN</th>
|
||
<th>QTY</th>
|
||
<th>Amount</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="entryOrdersTableBody">
|
||
<tr>
|
||
<td colspan="7" class="empty-state">No orders associated with this entry</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
|
||
<!-- Installment Modal -->
|
||
<div class="modal-fade1" id="installmentModal">
|
||
<div class="modal-box1" style="max-width:720px;">
|
||
<div style="display:flex;align-items:center; justify-content:space-between; margin-bottom:12px;">
|
||
<div style="font-size:18px;font-weight:800;color:#243a72;">+ Add New Installment</div>
|
||
<button class="btn ghost" onclick="closeInstallmentModal()">✕</button>
|
||
</div>
|
||
|
||
<div style="font-size:14px;color:#6f7b8f;margin-bottom:14px;">Create a new processing entry for the remaining pending amount</div>
|
||
|
||
<div id="instDetailsRow" style="display:flex; gap:18px; margin-bottom:12px; flex-wrap:wrap;"></div>
|
||
|
||
<form id="installmentForm" autocomplete="off">
|
||
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
|
||
<div>
|
||
<label>Processing Date *</label>
|
||
<input type="date" name="proc_date" required style="width:100%; padding:10px; border-radius:8px; border:1.4px solid #e7eefb;" value="{{ date('Y-m-d') }}">
|
||
</div>
|
||
<div>
|
||
<label>Processing Amount *</label>
|
||
<input type="number" min="1" name="proc_amount" required placeholder="Enter amount" style="width:100%; padding:10px; border-radius:8px; border:1.4px solid #e7eefb;">
|
||
<span class="helper-note">Maximum allowed: <strong id="maxPending">-</strong></span>
|
||
</div>
|
||
<div>
|
||
<label>Status</label>
|
||
<select name="status" required style="width:100%; padding:10px; border-radius:8px; border:1.4px solid #e7eefb;">
|
||
<option>Pending</option><option>Loading</option><option>Packed</option><option>Dispatched</option><option>Delivered</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="display:flex; justify-content:flex-end; gap:12px; margin-top:14px;">
|
||
<button type="button" class="btn ghost" onclick="closeInstallmentModal()">Cancel</button>
|
||
<button type="submit" class="btn">Create Installment</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Edit Entry Modal -->
|
||
<div class="modal-fade1 edit-modal" id="editEntryModal">
|
||
<div class="modal-box1">
|
||
<div style="display:flex;align-items:center; justify-content:space-between; margin-bottom:12px;">
|
||
<div style="font-size:18px;font-weight:800;color:#243a72;">Edit Entry</div>
|
||
<button class="btn ghost" onclick="closeEditModal()">✕</button>
|
||
</div>
|
||
|
||
<div style="font-size:14px;color:#6f7b8f;margin-bottom:14px;">Update the description and order quantity for this entry</div>
|
||
|
||
<form id="editEntryForm" autocomplete="off">
|
||
<input type="hidden" id="edit_entry_no" name="entry_no">
|
||
|
||
<div class="edit-form-grid">
|
||
<div>
|
||
<label>Description *</label>
|
||
<input type="text" id="edit_description" name="description" required class="input" placeholder="Entry description">
|
||
</div>
|
||
<div>
|
||
<label>
|
||
Order Quantity
|
||
<span style="font-size:12px; color:#6b7280; margin-left:4px;">
|
||
(Auto Update)
|
||
</span>
|
||
</label>
|
||
|
||
<input
|
||
type="number"
|
||
id="edit_order_quantity"
|
||
name="order_quantity"
|
||
min="0"
|
||
class="input"
|
||
placeholder="Order quantity"
|
||
readonly
|
||
style="cursor:pointer;"
|
||
onclick="openEntryOrdersModal(document.getElementById('edit_entry_no').value)"
|
||
>
|
||
|
||
<small class="helper-note">
|
||
Click on quantity to view associated orders.
|
||
</small>
|
||
</div>
|
||
|
||
|
||
<!-- <div>
|
||
<label>Payment Status</label>
|
||
<select id="edit_payment_status" name="payment_status" class="input">
|
||
<option value="unpaid">Unpaid</option>
|
||
<option value="paid">Paid</option>
|
||
<option value="pending">Pending</option>
|
||
</select>
|
||
</div>-->
|
||
<div>
|
||
<label>Region</label>
|
||
<select id="edit_region" name="region" class="input" required>
|
||
<option value="China">China</option>
|
||
<option value="Europe">Europe</option>
|
||
<option value="US">US</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label>Amount (₹) *</label>
|
||
<input type="number" id="edit_amount" name="amount" min="0" required class="input" placeholder="Entry amount">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Order Management Section -->
|
||
<div class="order-management-section">
|
||
<div class="order-management-header">
|
||
<div class="order-management-title">Associated Orders</div>
|
||
<button type="button" class="btn ghost" id="addOrderBtn">+ Add Orders</button>
|
||
</div>
|
||
|
||
<div class="orders-table-container">
|
||
<table class="orders-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Order ID</th>
|
||
<th>Mark No</th>
|
||
<th>Origin</th>
|
||
<th>Destination</th>
|
||
<th>CTN</th>
|
||
<th>QTY</th>
|
||
<th>Amount (₹)</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="editOrdersTableBody">
|
||
<!-- Orders will be loaded here -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div id="editOrdersEmptyState" class="empty-state" style="display:none;">
|
||
No orders associated with this entry
|
||
</div>
|
||
</div>
|
||
|
||
<div class="edit-form-actions">
|
||
<button type="button" class="btn ghost" onclick="closeEditModal()">Cancel</button>
|
||
<button type="submit" class="btn">Update Entry</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add Order Modal -->
|
||
<div class="modal-fade1 add-order-modal" id="addOrderModal">
|
||
<div class="modal-box1">
|
||
<div style="display:flex;align-items:center; justify-content:space-between; margin-bottom:12px;">
|
||
<div style="font-size:18px;font-weight:800;color:#243a72;">Add Orders to Entry</div>
|
||
<button class="btn ghost" onclick="closeAddOrderModal()">✕</button>
|
||
</div>
|
||
|
||
<div style="font-size:14px;color:#6f7b8f;margin-bottom:14px;">Select orders to associate with this entry</div>
|
||
|
||
<div class="consolidate-area" style="min-height: 400px;">
|
||
<table id="addOrderTable">
|
||
<thead>
|
||
<tr>
|
||
<th></th>
|
||
<th>Order ID</th>
|
||
<th>Mark No</th>
|
||
<th>Origin</th>
|
||
<th>Destination</th>
|
||
<th>CTN</th>
|
||
<th>QTY</th>
|
||
<th>Amount (₹)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="addOrderTableBody">
|
||
<!-- Orders will be loaded here -->
|
||
</tbody>
|
||
</table>
|
||
|
||
<!-- Pagination for Add Order Modal -->
|
||
<div class="modal-pagination-wrapper pagination-right">
|
||
<div class="pagination-container">
|
||
<div class="pagination-info" id="addOrderPageInfo">Showing 0 entries</div>
|
||
<div class="pagination-controls1">
|
||
<button class="pagination-img-btn" id="addOrderPrevBtn" 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="addOrderPaginationPages">
|
||
<!-- Page numbers will be inserted here -->
|
||
</div>
|
||
<button class="pagination-img-btn" id="addOrderNextBtn" 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 class="add-order-actions">
|
||
<button type="button" class="btn ghost" onclick="closeAddOrderModal()">Cancel</button>
|
||
<button type="button" class="btn" id="confirmAddOrdersBtn">Add Selected Orders</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
/* ---------- Helpers & state ---------- */
|
||
|
||
function setToggleVisual(btn, pos) {
|
||
// pos: 0 = unpaid (red), 1 = pending (yellow), 2 = paid (green)
|
||
|
||
btn.classList.remove('mid', 'checked');
|
||
|
||
if (pos === 1) {
|
||
btn.classList.add('mid'); // yellow
|
||
} else if (pos === 2) {
|
||
btn.classList.add('checked'); // green
|
||
}
|
||
}
|
||
|
||
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||
|
||
function jsonFetch(url, opts = {}) {
|
||
opts.headers = Object.assign(
|
||
{ 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
|
||
opts.headers || {}
|
||
);
|
||
|
||
if (opts.body && typeof opts.body !== 'string') {
|
||
opts.body = JSON.stringify(opts.body);
|
||
}
|
||
|
||
return fetch(url, opts).then(r =>
|
||
r.json().catch(() => {
|
||
throw new Error('Invalid server response');
|
||
})
|
||
);
|
||
}
|
||
|
||
|
||
let entries = [];
|
||
let availableOrders = [];
|
||
let currentEntry = null;
|
||
let currentEditEntry = null;
|
||
|
||
/* Single pagination state for both tables */
|
||
let currentPage = 1;
|
||
const entriesPerPage = 10;
|
||
|
||
/* Separate pagination state for modals */
|
||
let modalCurrentPage = 1;
|
||
const modalOrdersPerPage = 10;
|
||
let addOrderCurrentPage = 1;
|
||
|
||
/* Date filter state */
|
||
let fromDateFilter = '';
|
||
let toDateFilter = '';
|
||
|
||
/* Status filter state */
|
||
let paymentStatusFilter = '';
|
||
let orderStatusFilter = '';
|
||
|
||
/* small util */
|
||
function escapeHtml(s){ if(s === null || s === undefined) return ''; return String(s).replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":"'"}[m])); }
|
||
function formatCurrency(v){ return '₹' + Number(v || 0).toLocaleString(undefined, { minimumFractionDigits:0, maximumFractionDigits:2 }); }
|
||
function capitalize(s){ if(!s) return ''; s = String(s); return s.charAt(0).toUpperCase() + s.slice(1); }
|
||
function statusClass(status){
|
||
switch(String(status || '').toLowerCase()){
|
||
case 'unpaid': return 'status-unpaid';
|
||
case 'paid': return 'status-paid';
|
||
case 'loading': return 'status-loading';
|
||
case 'dispatched': return 'status-dispatched';
|
||
case 'packed': return 'status-loading';
|
||
case 'delivered': return 'status-delivered';
|
||
case 'pending': return 'pending-badge-red';
|
||
default: return 'status-loading';
|
||
}
|
||
}
|
||
|
||
/* ---------- Init ---------- */
|
||
window.addEventListener('DOMContentLoaded', () => {
|
||
bindUI();
|
||
loadDashboard();
|
||
});
|
||
|
||
/* ---------- UI binding ---------- */
|
||
function bindUI(){
|
||
// Create Order Modal
|
||
document.getElementById('openCreateModalBtn').addEventListener('click', openCreateOrderModal);
|
||
document.getElementById('closeCreateModal').addEventListener('click', closeCreateOrderModal);
|
||
document.getElementById('cancelCreateModal').addEventListener('click', closeCreateOrderModal);
|
||
document.getElementById('createOrderInlineForm').addEventListener('submit', submitCreateOrderInline);
|
||
|
||
// Payment Table Pagination (only pagination controls)
|
||
document.getElementById('paymentPrevBtn').addEventListener('click', () => goToPreviousPage());
|
||
document.getElementById('paymentNextBtn').addEventListener('click', () => goToNextPage());
|
||
|
||
// No pagination controls for order table
|
||
|
||
// Modal Pagination
|
||
document.getElementById('modalPrevBtn').addEventListener('click', () => goToModalPreviousPage());
|
||
document.getElementById('modalNextBtn').addEventListener('click', () => goToModalNextPage());
|
||
|
||
// Add Order Modal Pagination
|
||
document.getElementById('addOrderPrevBtn').addEventListener('click', () => goToAddOrderPreviousPage());
|
||
document.getElementById('addOrderNextBtn').addEventListener('click', () => goToAddOrderNextPage());
|
||
|
||
document.getElementById('refreshBtn').addEventListener('click', () => {
|
||
loadDashboard();
|
||
clearDateFilters();
|
||
clearStatusFilters();
|
||
});
|
||
document.getElementById('searchBtn').addEventListener('click', handleSearch);
|
||
document.getElementById('addInstallmentFromDetails').addEventListener('click', () => {
|
||
if(!currentEntry) return;
|
||
openInstallmentModal(currentEntry.entry_no, currentEntry.description, currentEntry.region, currentEntry.pending_amount);
|
||
closeEntryDetailsModal();
|
||
});
|
||
|
||
// Installment form submit
|
||
document.getElementById('installmentForm').addEventListener('submit', submitInstallment);
|
||
|
||
// Edit form submit
|
||
document.getElementById('editEntryForm').addEventListener('submit', submitEditEntry);
|
||
|
||
// Add Order button in Edit Modal
|
||
document.getElementById('addOrderBtn').addEventListener('click', openAddOrderModal);
|
||
|
||
// Confirm Add Orders button
|
||
document.getElementById('confirmAddOrdersBtn').addEventListener('click', confirmAddOrders);
|
||
|
||
// Date Filter bindings
|
||
document.getElementById('fromDateFilter').addEventListener('change', (e) => {
|
||
fromDateFilter = e.target.value;
|
||
applyAllFilters();
|
||
});
|
||
|
||
document.getElementById('toDateFilter').addEventListener('change', (e) => {
|
||
toDateFilter = e.target.value;
|
||
applyAllFilters();
|
||
});
|
||
|
||
// Status Filter bindings
|
||
document.getElementById('paymentStatusFilter').addEventListener('change', (e) => {
|
||
paymentStatusFilter = e.target.value;
|
||
applyAllFilters();
|
||
});
|
||
|
||
document.getElementById('orderStatusFilter').addEventListener('change', (e) => {
|
||
orderStatusFilter = e.target.value;
|
||
applyAllFilters();
|
||
});
|
||
}
|
||
|
||
/* ---------- Filter Functions ---------- */
|
||
function applyAllFilters() {
|
||
let filteredEntries = [...entries];
|
||
|
||
// Apply date filters (both tables)
|
||
if (fromDateFilter) {
|
||
filteredEntries = filteredEntries.filter(entry => entry.entry_date >= fromDateFilter);
|
||
}
|
||
|
||
if (toDateFilter) {
|
||
filteredEntries = filteredEntries.filter(entry => entry.entry_date <= toDateFilter);
|
||
}
|
||
|
||
// Apply payment status filter (ONLY for payment table)
|
||
let paymentFilteredEntries = [...filteredEntries];
|
||
if (paymentStatusFilter) {
|
||
paymentFilteredEntries = paymentFilteredEntries.filter(entry =>
|
||
entry.payment_status.toLowerCase() === paymentStatusFilter.toLowerCase()
|
||
);
|
||
}
|
||
|
||
// Apply order status filter (ONLY for order table)
|
||
let orderFilteredEntries = [...filteredEntries];
|
||
if (orderStatusFilter) {
|
||
orderFilteredEntries = orderFilteredEntries.filter(entry =>
|
||
entry.dispatch_status.toLowerCase() === orderStatusFilter.toLowerCase()
|
||
);
|
||
}
|
||
|
||
// Reset to first page when filtering
|
||
currentPage = 1;
|
||
|
||
// Render each table with its own filtered data
|
||
renderPaymentTable(paymentFilteredEntries);
|
||
renderOrderTable(orderFilteredEntries);
|
||
updatePaginationControls();
|
||
}
|
||
|
||
function applyOrderTableFilters(entriesList) {
|
||
let filtered = [...entriesList];
|
||
|
||
// Apply order status filter (for order table only)
|
||
if (orderStatusFilter) {
|
||
filtered = filtered.filter(entry =>
|
||
entry.dispatch_status.toLowerCase() === orderStatusFilter.toLowerCase()
|
||
);
|
||
}
|
||
|
||
return filtered;
|
||
}
|
||
|
||
function clearDateFilters() {
|
||
document.getElementById('fromDateFilter').value = '';
|
||
document.getElementById('toDateFilter').value = '';
|
||
fromDateFilter = '';
|
||
toDateFilter = '';
|
||
}
|
||
|
||
function clearStatusFilters() {
|
||
document.getElementById('paymentStatusFilter').value = '';
|
||
document.getElementById('orderStatusFilter').value = '';
|
||
paymentStatusFilter = '';
|
||
orderStatusFilter = '';
|
||
}
|
||
|
||
/* ---------- Pagination Functions ---------- */
|
||
function goToPreviousPage() {
|
||
if (currentPage > 1) {
|
||
currentPage--;
|
||
renderBothTables();
|
||
updatePaginationControls();
|
||
}
|
||
}
|
||
|
||
function goToNextPage() {
|
||
const totalPages = Math.ceil(entries.length / entriesPerPage);
|
||
if (currentPage < totalPages) {
|
||
currentPage++;
|
||
renderBothTables();
|
||
updatePaginationControls();
|
||
}
|
||
}
|
||
|
||
function goToModalPreviousPage() {
|
||
if (modalCurrentPage > 1) {
|
||
modalCurrentPage--;
|
||
renderConsolidateOrders(availableOrders);
|
||
updateModalPaginationControls();
|
||
}
|
||
}
|
||
|
||
function goToModalNextPage() {
|
||
const totalPages = Math.ceil(availableOrders.length / modalOrdersPerPage);
|
||
if (modalCurrentPage < totalPages) {
|
||
modalCurrentPage++;
|
||
renderConsolidateOrders(availableOrders);
|
||
updateModalPaginationControls();
|
||
}
|
||
}
|
||
|
||
function goToAddOrderPreviousPage() {
|
||
if (addOrderCurrentPage > 1) {
|
||
addOrderCurrentPage--;
|
||
renderAddOrderModal(availableOrders);
|
||
updateAddOrderPaginationControls();
|
||
}
|
||
}
|
||
|
||
function goToAddOrderNextPage() {
|
||
const totalPages = Math.ceil(availableOrders.length / modalOrdersPerPage);
|
||
if (addOrderCurrentPage < totalPages) {
|
||
addOrderCurrentPage++;
|
||
renderAddOrderModal(availableOrders);
|
||
updateAddOrderPaginationControls();
|
||
}
|
||
}
|
||
|
||
function updatePaginationControls() {
|
||
const totalPages = Math.ceil(entries.length / entriesPerPage);
|
||
const prevBtn = document.getElementById('paymentPrevBtn');
|
||
const nextBtn = document.getElementById('paymentNextBtn');
|
||
const pageInfo = document.getElementById('paymentPageInfo');
|
||
const paginationPages = document.getElementById('paymentPaginationPages');
|
||
|
||
prevBtn.disabled = currentPage === 1;
|
||
nextBtn.disabled = currentPage === totalPages || totalPages === 0;
|
||
|
||
// Update page info text
|
||
const startIndex = (currentPage - 1) * entriesPerPage + 1;
|
||
const endIndex = Math.min(currentPage * entriesPerPage, entries.length);
|
||
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${entries.length} entries`;
|
||
|
||
// Generate page numbers
|
||
paginationPages.innerHTML = '';
|
||
|
||
if (totalPages <= 7) {
|
||
// Show all pages
|
||
for (let i = 1; i <= totalPages; i++) {
|
||
addPageButton(i, paginationPages);
|
||
}
|
||
} else {
|
||
// Show first page, current page range, and last page
|
||
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 updateModalPaginationControls() {
|
||
const totalPages = Math.ceil(availableOrders.length / modalOrdersPerPage);
|
||
const prevBtn = document.getElementById('modalPrevBtn');
|
||
const nextBtn = document.getElementById('modalNextBtn');
|
||
const pageInfo = document.getElementById('modalPageInfo');
|
||
const paginationPages = document.getElementById('modalPaginationPages');
|
||
|
||
prevBtn.disabled = modalCurrentPage === 1;
|
||
nextBtn.disabled = modalCurrentPage === totalPages || totalPages === 0;
|
||
|
||
// Update page info text
|
||
const startIndex = (modalCurrentPage - 1) * modalOrdersPerPage + 1;
|
||
const endIndex = Math.min(modalCurrentPage * modalOrdersPerPage, availableOrders.length);
|
||
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${availableOrders.length} orders`;
|
||
|
||
// Generate page numbers
|
||
paginationPages.innerHTML = '';
|
||
|
||
if (totalPages <= 7) {
|
||
// Show all pages
|
||
for (let i = 1; i <= totalPages; i++) {
|
||
addModalPageButton(i, paginationPages);
|
||
}
|
||
} else {
|
||
// Show first page, current page range, and last page
|
||
addModalPageButton(1, paginationPages);
|
||
|
||
if (modalCurrentPage > 3) {
|
||
paginationPages.innerHTML += '<span class="pagination-ellipsis">...</span>';
|
||
}
|
||
|
||
const start = Math.max(2, modalCurrentPage - 1);
|
||
const end = Math.min(totalPages - 1, modalCurrentPage + 1);
|
||
|
||
for (let i = start; i <= end; i++) {
|
||
addModalPageButton(i, paginationPages);
|
||
}
|
||
|
||
if (modalCurrentPage < totalPages - 2) {
|
||
paginationPages.innerHTML += '<span class="pagination-ellipsis">...</span>';
|
||
}
|
||
|
||
addModalPageButton(totalPages, paginationPages);
|
||
}
|
||
}
|
||
|
||
function updateAddOrderPaginationControls() {
|
||
const totalPages = Math.ceil(availableOrders.length / modalOrdersPerPage);
|
||
const prevBtn = document.getElementById('addOrderPrevBtn');
|
||
const nextBtn = document.getElementById('addOrderNextBtn');
|
||
const pageInfo = document.getElementById('addOrderPageInfo');
|
||
const paginationPages = document.getElementById('addOrderPaginationPages');
|
||
|
||
prevBtn.disabled = addOrderCurrentPage === 1;
|
||
nextBtn.disabled = addOrderCurrentPage === totalPages || totalPages === 0;
|
||
|
||
// Update page info text
|
||
const startIndex = (addOrderCurrentPage - 1) * modalOrdersPerPage + 1;
|
||
const endIndex = Math.min(addOrderCurrentPage * modalOrdersPerPage, availableOrders.length);
|
||
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${availableOrders.length} orders`;
|
||
|
||
// Generate page numbers
|
||
paginationPages.innerHTML = '';
|
||
|
||
if (totalPages <= 7) {
|
||
// Show all pages
|
||
for (let i = 1; i <= totalPages; i++) {
|
||
addAddOrderPageButton(i, paginationPages);
|
||
}
|
||
} else {
|
||
// Show first page, current page range, and last page
|
||
addAddOrderPageButton(1, paginationPages);
|
||
|
||
if (addOrderCurrentPage > 3) {
|
||
paginationPages.innerHTML += '<span class="pagination-ellipsis">...</span>';
|
||
}
|
||
|
||
const start = Math.max(2, addOrderCurrentPage - 1);
|
||
const end = Math.min(totalPages - 1, addOrderCurrentPage + 1);
|
||
|
||
for (let i = start; i <= end; i++) {
|
||
addAddOrderPageButton(i, paginationPages);
|
||
}
|
||
|
||
if (addOrderCurrentPage < totalPages - 2) {
|
||
paginationPages.innerHTML += '<span class="pagination-ellipsis">...</span>';
|
||
}
|
||
|
||
addAddOrderPageButton(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;
|
||
renderBothTables();
|
||
updatePaginationControls();
|
||
});
|
||
container.appendChild(button);
|
||
}
|
||
|
||
function addModalPageButton(pageNumber, container) {
|
||
const button = document.createElement('button');
|
||
button.className = 'pagination-page-btn';
|
||
|
||
if (pageNumber === modalCurrentPage) {
|
||
button.classList.add('active');
|
||
}
|
||
|
||
button.textContent = pageNumber;
|
||
button.addEventListener('click', () => {
|
||
modalCurrentPage = pageNumber;
|
||
renderConsolidateOrders(availableOrders);
|
||
updateModalPaginationControls();
|
||
});
|
||
container.appendChild(button);
|
||
}
|
||
|
||
function addAddOrderPageButton(pageNumber, container) {
|
||
const button = document.createElement('button');
|
||
button.className = 'pagination-page-btn';
|
||
|
||
if (pageNumber === addOrderCurrentPage) {
|
||
button.classList.add('active');
|
||
}
|
||
|
||
button.textContent = pageNumber;
|
||
button.addEventListener('click', () => {
|
||
addOrderCurrentPage = pageNumber;
|
||
renderAddOrderModal(availableOrders);
|
||
updateAddOrderPaginationControls();
|
||
});
|
||
container.appendChild(button);
|
||
}
|
||
|
||
function renderBothTables() {
|
||
renderPaymentTable(entries);
|
||
renderOrderTable(entries);
|
||
}
|
||
|
||
/* ---------- Create Order Modal Functions ---------- */
|
||
function openCreateOrderModal(){
|
||
const modal = document.getElementById('createOrderModal');
|
||
modal.classList.add('modal-open');
|
||
loadAvailableOrders();
|
||
setTimeout(()=> document.getElementById('inline_description').focus(), 220);
|
||
}
|
||
|
||
function closeCreateOrderModal(){
|
||
const modal = document.getElementById('createOrderModal');
|
||
modal.classList.remove('modal-open');
|
||
document.getElementById('createOrderInlineForm').reset();
|
||
modalCurrentPage = 1; // Reset modal pagination when closing
|
||
}
|
||
|
||
/* ---------- Loaders ---------- */
|
||
function loadDashboard(){
|
||
jsonFetch('/admin/account/dashboard')
|
||
.then(res => {
|
||
if(!res.success) throw new Error(res.message || 'Failed to load dashboard');
|
||
entries = res.entries || [];
|
||
renderBothTables();
|
||
updatePaginationControls(); // Initialize pagination after loading
|
||
document.getElementById('entriesCount').textContent = entries.length;
|
||
})
|
||
.catch(err => {
|
||
console.error(err);
|
||
document.getElementById('paymentTableBody').innerHTML = '<tr><td colspan="9" class="empty-state">Unable to load entries</td></tr>';
|
||
document.getElementById('orderTableBody').innerHTML = '<tr><td colspan="8" class="empty-state">Unable to load entries</td></tr>';
|
||
});
|
||
}
|
||
|
||
|
||
function loadAvailableOrders() {
|
||
const tbody = document.getElementById('consolidateOrdersBody');
|
||
tbody.innerHTML = '<tr><td colspan="14" class="empty-state">Loading available orders...</td></tr>';
|
||
|
||
jsonFetch('/admin/account/available-orders', { method: 'GET' })
|
||
.then(res => {
|
||
if (!res.success) throw new Error(res.message || 'Failed to load orders');
|
||
|
||
availableOrders = res.orders || [];
|
||
modalCurrentPage = 1; // Reset to first page when loading new orders
|
||
renderConsolidateOrders(availableOrders);
|
||
updateModalPaginationControls();
|
||
})
|
||
.catch(err => {
|
||
console.error(err);
|
||
tbody.innerHTML = '<tr><td colspan="14" class="empty-state">Unable to load orders</td></tr>';
|
||
updateModalPaginationControls();
|
||
});
|
||
}
|
||
|
||
|
||
|
||
|
||
function loadAvailableOrdersForAddModal() {
|
||
const tbody = document.getElementById('addOrderTableBody');
|
||
if (tbody) {
|
||
tbody.innerHTML = `
|
||
<tr>
|
||
<td colspan="8" class="empty-state">Loading available orders...</td>
|
||
</tr>
|
||
`;
|
||
}
|
||
|
||
jsonFetch('/admin/account/available-orders', {
|
||
method: 'GET'
|
||
})
|
||
.then(res => {
|
||
if (!res.success) {
|
||
throw new Error(res.message || 'Failed to load available orders');
|
||
}
|
||
|
||
availableOrders = res.orders || [];
|
||
addOrderCurrentPage = 1;
|
||
|
||
renderAddOrderModal(availableOrders);
|
||
updateAddOrderPaginationControls();
|
||
})
|
||
.catch(err => {
|
||
console.error(err);
|
||
if (tbody) {
|
||
tbody.innerHTML = `
|
||
<tr>
|
||
<td colspan="8" class="empty-state">Unable to load available orders</td>
|
||
</tr>
|
||
`;
|
||
}
|
||
updateAddOrderPaginationControls();
|
||
});
|
||
}
|
||
|
||
|
||
|
||
/* ---------- Renderers ---------- */
|
||
function renderPaymentTable(list){
|
||
const body = document.getElementById('paymentTableBody');
|
||
body.innerHTML = '';
|
||
|
||
if (!list || list.length === 0) {
|
||
body.innerHTML = '<tr><td colspan="9" class="empty-state">No entries found</td></tr>';
|
||
return;
|
||
}
|
||
|
||
const startIndex = (currentPage - 1) * entriesPerPage;
|
||
const endIndex = startIndex + entriesPerPage;
|
||
const paginatedEntries = list.slice(startIndex, endIndex);
|
||
|
||
paginatedEntries.forEach(entry => {
|
||
const tr = document.createElement('tr');
|
||
|
||
// फक्त unpaid/pending वरच actions दिसतील
|
||
const canActions = ['unpaid', 'pending'].includes(
|
||
String(entry.payment_status).toLowerCase()
|
||
);
|
||
|
||
tr.innerHTML = `
|
||
<td>${escapeHtml(entry.entry_no)}</td>
|
||
<td>${escapeHtml(entry.entry_date)}</td>
|
||
<td>${escapeHtml(entry.description)}</td>
|
||
|
||
<!-- CLICKABLE ORDER QUANTITY -->
|
||
<td>
|
||
<button
|
||
type="button"
|
||
class="entry-link"
|
||
onclick="openEntryOrdersModal('${escapeHtml(entry.entry_no)}')"
|
||
>
|
||
${entry.order_quantity ?? '-'}
|
||
</button>
|
||
</td>
|
||
|
||
<td>${escapeHtml(entry.region)}</td>
|
||
<td>
|
||
<button class="toggle-switch-btn"
|
||
data-entry="${escapeHtml(entry.entry_no)}"
|
||
data-pos="${Number(entry.toggle_pos) || 0}"
|
||
aria-label="Toggle payment state"></button>
|
||
</td>
|
||
<td>${formatCurrency(entry.amount)}</td>
|
||
<td>
|
||
<span class="status-badge ${statusClass(entry.payment_status)}">
|
||
${capitalize(entry.payment_status)}
|
||
</span>
|
||
</td>
|
||
|
||
<!-- इथे तुझा action-btns block paste कर -->
|
||
<td>
|
||
<div class="action-btns">
|
||
${canActions ? `
|
||
<button class="action-btn edit-btn" data-entry="${escapeHtml(entry.entry_no)}" title="Edit entry">
|
||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M11.3333 1.99996C11.5084 1.82485 11.7163 1.686 11.945 1.59124C12.1737 1.49649 12.4189 1.44775 12.6667 1.44775C12.9144 1.44775 13.1596 1.49649 13.3883 1.59124C13.617 1.686 13.8249 1.82485 14 1.99996C14.1751 2.17507 14.314 2.38297 14.4087 2.61167C14.5035 2.84037 14.5522 3.08556 14.5522 3.33329C14.5522 3.58102 14.5035 3.82621 14.4087 4.05491C14.314 4.28361 14.1751 4.49151 14 4.66663L4.99998 13.6666L1.33331 14.6666L2.33331 11L11.3333 1.99996Z"
|
||
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
</button>
|
||
<button class="action-btn delete-btn" data-entry="${escapeHtml(entry.entry_no)}" title="Delete entry">
|
||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M2 4H3.33333H14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||
<path d="M5.33331 4V2.66667C5.33331 2.31305 5.47379 1.97391 5.72384 1.72386C5.97389 1.47381 6.31303 1.33333 6.66665 1.33333H9.33331C9.68693 1.33333 10.0261 1.47381 10.2761 1.72386C10.5262 1.97391 10.6666 2.31305 10.6666 2.66667V4M12.6666 4V13.3333C12.6666 13.6869 12.5262 14.0261 12.2761 14.2761C12.0261 14.5262 11.6869 14.6667 11.3333 14.6667H4.66665C4.31303 14.6667 3.97389 14.5262 3.72384 14.2761C3.47379 14.0261 3.33331 13.6869 3.33331 13.3333V4H12.6666Z"
|
||
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
</button>
|
||
` : ''}
|
||
</div>
|
||
</td>
|
||
`;
|
||
|
||
|
||
body.appendChild(tr);
|
||
const btn = tr.querySelector('.toggle-switch-btn');
|
||
btn.dataset.entry = entry.entry_no; // entry_no from API
|
||
btn.dataset.pos = entry.toggle_pos ?? 0;
|
||
setToggleVisual(btn, Number(btn.dataset.pos));
|
||
btn.addEventListener('click', () => cycleToggle(btn));
|
||
|
||
|
||
const actions = tr.querySelector('.action-btns');
|
||
if (actions) {
|
||
if (entry.payment_status.toLowerCase() === 'paid') {
|
||
actions.style.display = 'none';
|
||
} else {
|
||
actions.style.display = 'flex';
|
||
}
|
||
}
|
||
|
||
|
||
|
||
if (canActions) {
|
||
const editBtn = tr.querySelector('.edit-btn');
|
||
editBtn.addEventListener('click', () => openEditModal(entry));
|
||
|
||
const deleteBtn = tr.querySelector('.delete-btn');
|
||
deleteBtn.addEventListener('click', () => deleteEntry(entry.entry_no));
|
||
}
|
||
});
|
||
}
|
||
function cycleToggle(btn) {
|
||
// वर्तमान position घेऊन पुढचा स्टेट कॅल्क्युलेट करा
|
||
let pos = parseInt(btn.dataset.pos || '0', 10); // 0 = unpaid, 1 = pending, 2 = paid
|
||
pos = (pos + 1) % 3;
|
||
btn.dataset.pos = pos;
|
||
setToggleVisual(btn, pos); // रंग वगैरे अपडेट
|
||
|
||
const entryNo = btn.dataset.entry;
|
||
|
||
jsonFetch('/admin/account/toggle-payment', {
|
||
method: 'POST',
|
||
body: {
|
||
entry_no: entryNo, // controller मध्ये जी नावे आहेत तीच
|
||
toggle_pos: pos
|
||
}
|
||
}).then(res => {
|
||
if (!res.success) {
|
||
alert(res.message || 'Failed to update payment status');
|
||
|
||
// error असेल तर UI परत जुन्या स्टेटला ने
|
||
pos = (pos + 2) % 3;
|
||
btn.dataset.pos = pos;
|
||
setToggleVisual(btn, pos);
|
||
return;
|
||
}
|
||
|
||
// 1) global entries array update करा (dashboard data)
|
||
if (Array.isArray(entries)) {
|
||
const idx = entries.findIndex(e => e.entry_no === entryNo);
|
||
if (idx !== -1) {
|
||
entries[idx].toggle_pos = res.entry.toggle_pos;
|
||
entries[idx].payment_status = res.entry.payment_status;
|
||
}
|
||
}
|
||
|
||
// 2) current row मधला status badge आणि actions अपडेट करा
|
||
const row = btn.closest('tr');
|
||
if (row) {
|
||
// status badge
|
||
const statusCell = row.querySelector('.status-badge');
|
||
if (statusCell) {
|
||
statusCell.textContent = res.entry.payment_status;
|
||
statusCell.className = 'status-badge ' + statusClass(res.entry.payment_status);
|
||
}
|
||
|
||
// paid झाल्यावर edit/delete लपवा
|
||
const actions = row.querySelector('.action-btns');
|
||
if (actions) {
|
||
if (res.entry.payment_status.toLowerCase() === 'paid') {
|
||
actions.style.display = 'none';
|
||
} else {
|
||
actions.style.display = 'flex'; // किंवा तुझा default layout
|
||
}
|
||
}
|
||
}
|
||
}).catch(() => {
|
||
alert('Network error while updating payment status');
|
||
|
||
// network error – UI मागे घे
|
||
pos = (pos + 2) % 3;
|
||
btn.dataset.pos = pos;
|
||
setToggleVisual(btn, pos);
|
||
});
|
||
}
|
||
|
||
|
||
function renderOrderTable(list){
|
||
const body = document.getElementById('orderTableBody');
|
||
body.innerHTML = '';
|
||
|
||
if(!list || list.length === 0){
|
||
body.innerHTML = '<tr><td colspan="8" class="empty-state">No entries found</td></tr>';
|
||
return;
|
||
}
|
||
|
||
// Calculate pagination (using same currentPage as first table)
|
||
const startIndex = (currentPage - 1) * entriesPerPage;
|
||
const endIndex = startIndex + entriesPerPage;
|
||
const paginatedEntries = list.slice(startIndex, endIndex);
|
||
|
||
paginatedEntries.forEach(entry => {
|
||
const tr = document.createElement('tr');
|
||
const pending = Number(entry.pending_amount || 0);
|
||
const pendingHtml = pending <= 0 ? '<span class="status-badge pending-badge-green">Completed</span>' : `<span class="status-badge pending-badge-red">${formatCurrency(pending)}</span>`;
|
||
tr.innerHTML = `
|
||
<td><a class="entry-link" data-entry="${escapeHtml(entry.entry_no)}">${escapeHtml(entry.entry_no)}</a></td>
|
||
<td>${escapeHtml(entry.entry_date)}</td>
|
||
<td>${escapeHtml(entry.description)}</td>
|
||
<td>${escapeHtml(entry.region)}</td>
|
||
<td>${formatCurrency(entry.amount)}</td>
|
||
<td><span class="status-badge ${statusClass(entry.dispatch_status)}">${capitalize(entry.dispatch_status)}</span></td>
|
||
<td>${pendingHtml}</td>
|
||
<td>${pending > 0 ? `<button class="plus-btn" data-entry="${escapeHtml(entry.entry_no)}" title="Add installment">+</button>` : ''}</td>
|
||
`;
|
||
body.appendChild(tr);
|
||
|
||
// bind entry link
|
||
const link = tr.querySelector('.entry-link');
|
||
link.addEventListener('click', (e) => openEntryDetailsModal(e.target.dataset.entry));
|
||
|
||
// bind plus
|
||
const plus = tr.querySelector('.plus-btn');
|
||
if(plus) plus.addEventListener('click', (e) => openInstallmentModal(e.target.dataset.entry, entry.description, entry.region, entry.pending_amount));
|
||
});
|
||
}
|
||
|
||
function renderConsolidateOrders(list){
|
||
const body = document.getElementById('consolidateOrdersBody');
|
||
body.innerHTML = '';
|
||
|
||
if(!list || list.length === 0){
|
||
body.innerHTML = '<tr><td colspan="14" class="empty-state">No available orders</td></tr>';
|
||
updateModalPaginationControls();
|
||
return;
|
||
}
|
||
|
||
// Calculate pagination for modal
|
||
const startIndex = (modalCurrentPage - 1) * modalOrdersPerPage;
|
||
const endIndex = startIndex + modalOrdersPerPage;
|
||
const paginatedOrders = list.slice(startIndex, endIndex);
|
||
|
||
paginatedOrders.forEach(order => {
|
||
const tr = document.createElement('tr');
|
||
tr.innerHTML = `
|
||
<td><input type="checkbox" value="${order.id}"></td>
|
||
<td><a href="#" class="order-link" data-id="${order.id}">${escapeHtml(order.order_id || ('#'+order.id))}</a></td>
|
||
<td>${escapeHtml(order.mark_no || '')}</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 ?? ''}</td>
|
||
<td>${order.cbm ?? ''}</td>
|
||
<td>${order.ttl_cbm ?? ''}</td>
|
||
<td>${order.kg ?? ''}</td>
|
||
<td>${order.ttl_kg ?? ''}</td>
|
||
`;
|
||
body.appendChild(tr);
|
||
});
|
||
}
|
||
|
||
function renderAddOrderModal(orders) {
|
||
const tbody = document.getElementById('addOrderTableBody');
|
||
tbody.innerHTML = '';
|
||
|
||
if (!orders || !orders.length) {
|
||
tbody.innerHTML = `
|
||
<tr>
|
||
<td colspan="8" class="empty-state">No orders found</td>
|
||
</tr>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
const start = (addOrderCurrentPage - 1) * modalOrdersPerPage;
|
||
const end = Math.min(start + modalOrdersPerPage, orders.length);
|
||
const pageOrders = orders.slice(start, end);
|
||
|
||
// Hya entry sathi already associated order IDs collect kara
|
||
const attachedIds = new Set(
|
||
(currentEditEntry?.orders || []).map(o => o.id)
|
||
);
|
||
|
||
pageOrders.forEach(order => {
|
||
const tr = document.createElement('tr');
|
||
|
||
const idString = (order.orderid ?? order.id ?? '').toString().trim();
|
||
const numericId = parseInt(idString, 10);
|
||
const formattedId = isNaN(numericId)
|
||
? escapeHtml(idString)
|
||
: 'KNT-25-' + String(numericId).padStart(8, '0');
|
||
|
||
const isAttached = attachedIds.has(order.id);
|
||
|
||
tr.innerHTML = `
|
||
<td>
|
||
<input type="checkbox"
|
||
class="add-order-checkbox"
|
||
value="${order.id}"
|
||
${isAttached ? 'checked disabled' : ''}>
|
||
</td>
|
||
<td>${formattedId}</td>
|
||
<td>${escapeHtml(order.markno ?? order.mark_no ?? '')}</td>
|
||
<td>${escapeHtml(order.origin ?? '')}</td>
|
||
<td>${escapeHtml(order.destination ?? '')}</td>
|
||
<td>${escapeHtml(order.ctn ?? '')}</td>
|
||
<td>${escapeHtml(order.qty ?? '')}</td>
|
||
<td>${formatCurrency(order.ttl_amount ?? order.ttlamount ?? order.total_amount ?? order.amount ?? 0)}</td>
|
||
`;
|
||
|
||
tbody.appendChild(tr);
|
||
});
|
||
}
|
||
|
||
|
||
/* ---------- Edit Entry Functions ---------- */
|
||
function openEditModal(entry) {
|
||
currentEditEntry = entry;
|
||
|
||
document.getElementById('edit_entry_no').value = entry.entry_no;
|
||
document.getElementById('edit_description').value = entry.description || '';
|
||
document.getElementById('edit_order_quantity').value = entry.order_quantity || '';
|
||
// document.getElementById('edit_payment_status').value = entry.payment_status || 'unpaid';
|
||
document.getElementById('edit_region').value = entry.region || 'China';
|
||
document.getElementById('edit_amount').value = entry.amount || '';
|
||
|
||
// Load associated orders
|
||
loadEntryOrders(entry.entry_no);
|
||
|
||
document.getElementById('editEntryModal').classList.add('modal-open');
|
||
}
|
||
|
||
function closeEditModal() {
|
||
document.getElementById('editEntryModal').classList.remove('modal-open');
|
||
currentEditEntry = null;
|
||
}
|
||
|
||
function loadEntryOrders(entryNo) {
|
||
const tbody = document.getElementById('editOrdersTableBody');
|
||
const emptyState = document.getElementById('editOrdersEmptyState');
|
||
|
||
tbody.innerHTML = '<tr><td colspan="8" class="empty-state">Loading orders...</td></tr>';
|
||
|
||
jsonFetch(`/admin/account/entry-orders/${encodeURIComponent(entryNo)}`)
|
||
.then(res => {
|
||
if (!res.success) throw new Error(res.message || 'Failed to load orders');
|
||
|
||
tbody.innerHTML = '';
|
||
|
||
if (!res.orders || res.orders.length === 0) {
|
||
emptyState.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
emptyState.style.display = 'none';
|
||
|
||
res.orders.forEach(order => {
|
||
const tr = document.createElement('tr');
|
||
tr.innerHTML = `
|
||
<td>${escapeHtml(order.order_id || ('#'+order.id))}</td>
|
||
<td>${escapeHtml(order.mark_no || '')}</td>
|
||
<td>${order.origin ?? ''}</td>
|
||
<td>${order.destination ?? ''}</td>
|
||
<td>${order.ctn ?? ''}</td>
|
||
<td>${order.qty ?? ''}</td>
|
||
<td>${order.ttl_amount ?? ''}</td>
|
||
<td>
|
||
<div class="order-actions">
|
||
<button type="button" class="remove-order-btn" data-order-id="${order.id}">Remove</button>
|
||
|
||
</div>
|
||
</td>
|
||
`;
|
||
tbody.appendChild(tr);
|
||
|
||
// Bind remove button
|
||
const removeBtn = tr.querySelector('.remove-order-btn');
|
||
removeBtn.addEventListener('click', () => removeOrderFromEntry(order.id, entryNo));
|
||
});
|
||
})
|
||
.catch(err => {
|
||
console.error(err);
|
||
tbody.innerHTML = '<tr><td colspan="8" class="empty-state">Unable to load orders</td></tr>';
|
||
});
|
||
}
|
||
|
||
|
||
async function refreshEntryHeader(entryNo) {
|
||
try {
|
||
const res = await jsonFetch('/admin/account/entry/' + encodeURIComponent(entryNo));
|
||
if (!res.success) return;
|
||
|
||
const entry = res.entry;
|
||
|
||
const qtyInput = document.getElementById('editorderquantity');
|
||
if (qtyInput) qtyInput.value = entry.order_quantity ?? 0;
|
||
|
||
|
||
const idx = entries.findIndex(e => e.entry_no === entry.entry_no);
|
||
|
||
if (idx !== -1) {
|
||
entries[idx].order_quantity = entry.order_quantity;
|
||
renderBothTables();
|
||
updatePaginationControls();
|
||
}
|
||
} catch (e) {
|
||
console.error(e);
|
||
}
|
||
}
|
||
async function removeOrderFromEntry(orderId, entryNo) {
|
||
if (!confirm('Are you sure you want to remove this order from the entry?')) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const res = await jsonFetch('/admin/account/remove-order-from-entry', {
|
||
method: 'POST',
|
||
body: {
|
||
entry_no: entryNo,
|
||
order_id: orderId
|
||
}
|
||
});
|
||
|
||
if (!res.success) throw new Error(res.message || 'Failed to remove order');
|
||
|
||
// नवीन quantity backend कडून घ्या
|
||
const newQty = res.entry.order_quantity ?? res.entry.orderquantity ?? 0;
|
||
|
||
// 1) currentEditEntry update
|
||
if (currentEditEntry) {
|
||
currentEditEntry.order_quantity = newQty;
|
||
}
|
||
|
||
// 2) Edit popup मधला field लगेच update
|
||
const oqInput = document.getElementById('edit_order_quantity');
|
||
if (oqInput) {
|
||
oqInput.value = newQty;
|
||
}
|
||
|
||
// 3) इतर UI refresh
|
||
loadEntryOrders(entryNo);
|
||
loadAvailableOrders();
|
||
await refreshEntryHeader(entryNo);
|
||
} catch (err) {
|
||
alert(err.message || 'Failed to remove order');
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
async function submitEditEntry(e) {
|
||
e.preventDefault();
|
||
const form = e.target;
|
||
const payload = {
|
||
entry_no: form.entry_no.value,
|
||
description: form.description.value.trim(),
|
||
order_quantity: Number(form.order_quantity.value) || 0,
|
||
// payment_status: form.payment_status.value,
|
||
region: form.region.value,
|
||
amount: Number(form.amount.value) || 0
|
||
};
|
||
|
||
if (!payload.description) {
|
||
alert('Please enter a valid description.');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const btn = form.querySelector('button[type="submit"]');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Updating...';
|
||
|
||
const res = await jsonFetch('/admin/account/update-entry', {
|
||
method: 'POST',
|
||
body: payload
|
||
});
|
||
|
||
if (!res.success) throw new Error(res.message || 'Update failed');
|
||
|
||
// Success
|
||
closeEditModal();
|
||
loadDashboard();
|
||
} catch (err) {
|
||
alert(err.message || 'Failed to update entry');
|
||
console.error(err);
|
||
} finally {
|
||
const btn = form.querySelector('button[type="submit"]');
|
||
if (btn) {
|
||
btn.disabled = false;
|
||
btn.textContent = 'Update Entry';
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
function openEntryOrdersModal(entryNo) {
|
||
// header la entry no show kar
|
||
document.getElementById('entryOrdersEntryNo-span').textContent = `(${entryNo})`;
|
||
|
||
// table clean / loading state
|
||
const tbody = document.getElementById('entryOrdersTableBody');
|
||
tbody.innerHTML = `
|
||
<tr>
|
||
<td colspan="7" class="empty-state">Loading orders...</td>
|
||
</tr>
|
||
`;
|
||
|
||
// API call: /admin/account/entry-orders/{entryno}
|
||
jsonFetch(`/admin/account/entry-orders/${encodeURIComponent(entryNo)}`, {
|
||
method: 'GET'
|
||
})
|
||
.then(res => {
|
||
if (!res.success) {
|
||
tbody.innerHTML = `
|
||
<tr>
|
||
<td colspan="7" class="empty-state">Failed to load orders</td>
|
||
</tr>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
const orders = res.orders || [];
|
||
if (!orders.length) {
|
||
tbody.innerHTML = `
|
||
<tr>
|
||
<td colspan="7" class="empty-state">No orders associated with this entry</td>
|
||
</tr>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = '';
|
||
orders.forEach(order => {
|
||
const tr = document.createElement('tr');
|
||
|
||
const idString = (order.orderid ?? order.id ?? '').toString().trim();
|
||
const numericId = parseInt(idString, 10);
|
||
const formattedId = isNaN(numericId)
|
||
? escapeHtml(idString)
|
||
: 'KNT-25-' + String(numericId).padStart(8, '0');
|
||
|
||
// इथे वेगवेगळी शक्य keys try कर
|
||
const amountValue =
|
||
order.ttl_amount ??
|
||
order.ttlamount ??
|
||
order.total_amount ??
|
||
order.order_amount ??
|
||
order.amount ??
|
||
0;
|
||
|
||
tr.innerHTML = `
|
||
<td>${formattedId}</td>
|
||
<td>${escapeHtml(order.markno ?? order.mark_no ?? '')}</td>
|
||
<td>${escapeHtml(order.origin ?? '')}</td>
|
||
<td>${escapeHtml(order.destination ?? '')}</td>
|
||
<td>${escapeHtml(order.ctn ?? '')}</td>
|
||
<td>${escapeHtml(order.qty ?? '')}</td>
|
||
<td>${formatCurrency(amountValue)}</td>
|
||
`;
|
||
tbody.appendChild(tr);
|
||
});
|
||
|
||
|
||
})
|
||
.catch(() => {
|
||
tbody.innerHTML = `
|
||
<tr>
|
||
<td colspan="7" class="empty-state">Error loading orders</td>
|
||
</tr>
|
||
`;
|
||
});
|
||
|
||
document.getElementById('entryOrdersModal').classList.add('modal-open');
|
||
}
|
||
|
||
function closeEntryOrdersModal() {
|
||
document.getElementById('entryOrdersModal').classList.remove('modal-open');
|
||
}
|
||
|
||
|
||
|
||
|
||
/* ---------- Add Order Modal Functions ---------- */
|
||
function openAddOrderModal() {
|
||
if (!currentEditEntry) return;
|
||
|
||
document.getElementById('addOrderModal').classList.add('modal-open');
|
||
|
||
// नेहमी इथेच ताजे orders load कर
|
||
addOrderCurrentPage = 1;
|
||
loadAvailableOrdersForAddModal();
|
||
}
|
||
|
||
|
||
function closeAddOrderModal() {
|
||
document.getElementById('addOrderModal').classList.remove('modal-open');
|
||
}
|
||
async function confirmAddOrders() {
|
||
if (!currentEditEntry) return;
|
||
|
||
const selected = Array.from(
|
||
document.querySelectorAll('#addOrderTableBody input[type="checkbox"]:checked')
|
||
).map(i => Number(i.value));
|
||
|
||
if (selected.length === 0) {
|
||
alert('Please select at least one order to add.');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const res = await jsonFetch('/admin/account/add-orders-to-entry', {
|
||
method: 'POST',
|
||
body: {
|
||
entry_no: currentEditEntry.entry_no,
|
||
order_ids: selected
|
||
}
|
||
});
|
||
|
||
if (!res.success) throw new Error(res.message || 'Failed to add orders');
|
||
|
||
loadEntryOrders(currentEditEntry.entry_no);
|
||
loadAvailableOrders();
|
||
await refreshEntryHeader(currentEditEntry.entry_no); // ⬅ इथे नवं
|
||
closeAddOrderModal();
|
||
} catch (err) {
|
||
alert(err.message || 'Failed to add orders');
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
|
||
/* ---------- Delete Entry Function ---------- */
|
||
async function deleteEntry(entryNo) {
|
||
if (!confirm(`Are you sure you want to delete entry ${entryNo}? This action cannot be undone.`)) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const res = await jsonFetch('/admin/account/delete-entry', {
|
||
method: 'POST',
|
||
body: { entry_no: entryNo }
|
||
});
|
||
|
||
if (!res.success) throw new Error(res.message || 'Delete failed');
|
||
|
||
// Success
|
||
loadDashboard();
|
||
} catch (err) {
|
||
alert(err.message || 'Failed to delete entry');
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
/* ---------- Create Order Inline (Now in Popup) ---------- */
|
||
async function submitCreateOrderInline(e){
|
||
e.preventDefault();
|
||
const form = e.target;
|
||
const selected = Array.from(document.querySelectorAll('#consolidateOrdersBody input[type=checkbox]:checked')).map(i=>Number(i.value));
|
||
const payload = {
|
||
description: form.description.value.trim(),
|
||
region: form.region.value,
|
||
amount: Number(form.amount.value) || 0,
|
||
entry_date: form.entry_date.value,
|
||
selected_orders: selected
|
||
};
|
||
|
||
if(!payload.description || payload.amount <= 0){
|
||
alert('Please enter a valid description and amount.');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const btn = form.querySelector('button[type="submit"]');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Creating...';
|
||
const res = await jsonFetch('/admin/account/create-order', { method:'POST', body: payload });
|
||
if(!res.success) throw new Error(res.message || 'Create failed');
|
||
// success
|
||
form.reset();
|
||
closeCreateOrderModal();
|
||
loadDashboard();
|
||
} catch(err){
|
||
alert(err.message || 'Failed to create order');
|
||
console.error(err);
|
||
} finally {
|
||
const btn = form.querySelector('button[type="submit"]');
|
||
if(btn){ btn.disabled = false; btn.textContent = 'Create Installment'; }
|
||
}
|
||
}
|
||
|
||
/* ---------- Search ---------- */
|
||
function handleSearch(){
|
||
const q = document.getElementById('main-search').value.trim().toLowerCase();
|
||
if(!q){
|
||
renderBothTables();
|
||
updatePaginationControls();
|
||
return;
|
||
}
|
||
const filtered = entries.filter(e => {
|
||
return String(e.entry_no || '').toLowerCase().includes(q) ||
|
||
String(e.description || '').toLowerCase().includes(q) ||
|
||
String(e.region || '').toLowerCase().includes(q);
|
||
});
|
||
entries = filtered;
|
||
currentPage = 1; // Reset to first page when searching
|
||
renderBothTables();
|
||
updatePaginationControls();
|
||
}
|
||
|
||
/* ---------- Entry details & installments ---------- */
|
||
async function openEntryDetailsModal(entryNo) {
|
||
try {
|
||
const res = await jsonFetch('/admin/account/entry/' + encodeURIComponent(entryNo));
|
||
|
||
if (!res.success) throw new Error(res.message || 'Failed to load entry');
|
||
|
||
const entry = res.entry;
|
||
currentEntry = entry;
|
||
|
||
document.getElementById('entryDetailsId').textContent = entry.entry_no;
|
||
document.getElementById('originalAmount').textContent = formatCurrency(entry.amount);
|
||
|
||
const totalProcessed = Number(entry.amount) - Number(entry.pending_amount);
|
||
document.getElementById('totalProcessed').textContent = formatCurrency(totalProcessed);
|
||
|
||
document.getElementById('pendingBalance').textContent = formatCurrency(entry.pending_amount);
|
||
document.getElementById('totalInstallments').textContent = entry.installments.length;
|
||
|
||
const tbody = document.getElementById('installmentsTableBody');
|
||
tbody.innerHTML = '';
|
||
|
||
entry.installments.forEach((ins, idx) => {
|
||
const tr = document.createElement('tr');
|
||
tr.innerHTML = `
|
||
<td>${idx === 0 ? 'Original Entry' : 'Installment ' + idx}</td>
|
||
<td>${escapeHtml(ins.proc_date)}</td>
|
||
<td>${escapeHtml(ins.description)}</td>
|
||
<td>${escapeHtml(ins.region)}</td>
|
||
<td>${formatCurrency(ins.amount)}</td>
|
||
|
||
<td>
|
||
<select class="installment-status-dropdown"
|
||
data-id="${ins.id}"
|
||
onchange="updateInstallmentStatus(${ins.id}, this.value)">
|
||
<option value="Pending" ${ins.status === 'Pending' ? 'selected' : ''}
|
||
style="color: #f59e0b; font-weight: 500; padding: 10px;">
|
||
⏳ Pending
|
||
</option>
|
||
<option value="Loading" ${ins.status === 'Loading' ? 'selected' : ''}
|
||
style="color: #3b82f6; font-weight: 500; padding: 10px;">
|
||
📦 Loading
|
||
</option>
|
||
<option value="Packed" ${ins.status === 'Packed' ? 'selected' : ''}
|
||
style="color: #8b5cf6; font-weight: 500; padding: 10px;">
|
||
📦 Packed
|
||
</option>
|
||
<option value="Dispatched" ${ins.status === 'Dispatched' ? 'selected' : ''}
|
||
style="color: #10b981; font-weight: 500; padding: 10px;">
|
||
🚚 Dispatched
|
||
</option>
|
||
<option value="Delivered" ${ins.status === 'Delivered' ? 'selected' : ''}
|
||
style="color: #0c6b2e; font-weight: 500; padding: 10px;">
|
||
✅ Delivered
|
||
</option>
|
||
</select>
|
||
|
||
</td>
|
||
`;
|
||
tbody.appendChild(tr);
|
||
});
|
||
|
||
document.getElementById('entryDetailsModal').classList.add('modal-open');
|
||
|
||
} catch (err) {
|
||
console.error(err);
|
||
alert('Unable to load entry details');
|
||
}
|
||
}
|
||
|
||
function closeEntryDetailsModal() {
|
||
document.getElementById('entryDetailsModal').classList.remove('modal-open');
|
||
}
|
||
|
||
async function updateInstallmentStatus(id, status) {
|
||
try {
|
||
const res = await jsonFetch('/admin/account/installment/update-status', {
|
||
method: 'POST',
|
||
body: { installment_id: id, status: status }
|
||
});
|
||
|
||
if (!res.success) {
|
||
alert('Failed to update status');
|
||
return;
|
||
}
|
||
|
||
// Refresh details modal instantly
|
||
openEntryDetailsModal(res.entry.entry_no);
|
||
|
||
} catch (err) {
|
||
console.error(err);
|
||
alert('Unable to update installment status');
|
||
}
|
||
}
|
||
|
||
/* ---------- Installment modal ---------- */
|
||
function openInstallmentModal(entryNo, desc, region, pending){
|
||
currentEntry = { entry_no: entryNo, description: desc, region: region, pending_amount: pending };
|
||
document.getElementById('instDetailsRow').innerHTML = `
|
||
<div style="min-width:120px"><div class="kv">Entry No</div><div>${escapeHtml(entryNo)}</div></div>
|
||
<div style="min-width:160px"><div class="kv">Description</div><div>${escapeHtml(desc)}</div></div>
|
||
<div style="min-width:120px"><div class="kv">Region</div><div>${escapeHtml(region)}</div></div>
|
||
<div style="min-width:120px"><div class="kv">Pending</div><div>${formatCurrency(pending)}</div></div>
|
||
`;
|
||
document.getElementById('maxPending').textContent = formatCurrency(pending);
|
||
document.getElementById('installmentModal').classList.add('modal-open');
|
||
|
||
// set example default value to pending (but not exceed)
|
||
const amountInput = document.querySelector('#installmentForm input[name="proc_amount"]');
|
||
if(amountInput) amountInput.value = pending ? Number(pending) : '';
|
||
}
|
||
|
||
function closeInstallmentModal(){ document.getElementById('installmentModal').classList.remove('modal-open'); }
|
||
|
||
async function submitInstallment(e){
|
||
e.preventDefault();
|
||
if(!currentEntry){ alert('No entry selected'); return; }
|
||
const form = e.target;
|
||
const payload = { entry_no: currentEntry.entry_no, proc_date: form.proc_date.value, amount: Number(form.proc_amount.value) || 0, status: form.status.value };
|
||
|
||
if(payload.amount > Number(currentEntry.pending_amount || 0)){
|
||
alert('Amount cannot exceed pending amount'); return;
|
||
}
|
||
try {
|
||
const btn = form.querySelector('button[type="submit"]');
|
||
btn.disabled = true; btn.textContent = 'Submitting...';
|
||
const res = await jsonFetch('/admin/account/installment/create', { method:'POST', body: payload });
|
||
if(!res.success) throw new Error(res.message || 'Failed to create installment');
|
||
closeInstallmentModal();
|
||
await loadDashboard();
|
||
openEntryDetailsModal(currentEntry.entry_no); // open updated details
|
||
} catch(err){
|
||
console.error(err);
|
||
alert(err.message || 'Failed to create installment');
|
||
} finally {
|
||
const btn = form.querySelector('button[type="submit"]');
|
||
if(btn){ btn.disabled = false; btn.textContent = 'Create Installment'; }
|
||
}
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
@endsection |