2025-11-06 17:09:52 +05:30
@ extends ( 'admin.layouts.app' )
2025-11-11 14:51:35 +05:30
@ section ( 'page-title' , 'Account Dashboard' )
2025-11-06 17:09:52 +05:30
@ section ( 'content' )
2025-11-21 16:07:43 +05:30
< meta name = " csrf-token " content = " { { csrf_token() }} " >
2025-11-11 14:51:35 +05:30
< style >
2025-11-21 16:07:43 +05:30
/* ---------- Base ---------- */
: root {
-- primary - 1 : #1a2951;
-- primary - 2 : #243a72;
-- muted : #9ba5bb;
-- card - bg : linear - gradient ( 180 deg , #ffffff,#f5f7fb);
-- glass : rgba ( 255 , 255 , 255 , 0.6 );
-- success : #34c86c;
-- danger : #ef4f4f;
-- accent : #276dea;
-- rounded : 12 px ;
}
body {
2025-11-12 19:56:06 +05:30
font - family : 'Segoe UI' , Arial , sans - serif ;
2025-11-21 16:07:43 +05:30
background : linear - gradient ( 135 deg , #eef2f7, #f9fbff);
margin : 0 ; padding : 0 ;
color : #253047;
- webkit - font - smoothing : antialiased ;
}
/* container */
. account - container {
padding : 28 px 34 px ;
2025-11-26 23:07:12 +05:30
max - width : 1400 px ;
2025-11-21 16:07:43 +05:30
margin : 18 px auto ;
box - sizing : border - box ;
}
/* header */
. account - header {
margin - bottom : 18 px ;
background : linear - gradient ( 90 deg , var ( -- primary - 1 ), var ( -- primary - 2 ));
padding : 22 px 26 px ;
border - radius : var ( -- rounded );
box - shadow : 0 6 px 18 px rgba ( 34 , 50 , 90 , 0.12 );
2025-11-12 19:56:06 +05:30
color : #fff;
2025-11-21 16:07:43 +05:30
}
. account - header h2 { font - size : 26 px ; font - weight : 700 ; margin : 0 0 6 px 0 ; }
. account - header p { font - size : 14 px ; margin : 2 px 0 ; opacity : 0.95 }
/* top actions row */
. top - actions {
display : flex ; align - items : center ; justify - content : space - between ;
gap : 12 px ; margin : 16 px 0 20 px 0 ; flex - wrap : wrap ;
}
. top - actions . left {
display : flex ; gap : 12 px ; align - items : center ; flex - wrap : wrap ;
}
. search - row input {
width : 360 px ; padding : 10 px 14 px ; border - radius : 8 px ; border : 1 px 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 6 px 18 px rgba ( 26 , 41 , 81 , 0.06 );}
. btn {
display : inline - flex ; align - items : center ; justify - content : center ; gap : 8 px ;
background : linear - gradient ( 90 deg , var ( -- primary - 1 ), var ( -- primary - 2 ));
color : #fff; border:none; padding:10px 16px; border-radius:10px; font-weight:600;
cursor : pointer ; transition : transform . 15 s ease , box - shadow . 15 s ;
}
. btn . ghost { background : transparent ; color : var ( -- primary - 1 ); border : 1 px solid #dbe4f5; box-shadow:none; }
. btn : hover { transform : translateY ( - 3 px ); box - shadow : 0 8 px 26 px rgba ( 36 , 58 , 114 , 0.12 ); }
/* account panels */
2025-11-26 23:07:12 +05:30
. account - panels {
display : flex ;
gap : 22 px ;
align - items : flex - start ;
flex - wrap : wrap ;
}
2025-11-21 16:07:43 +05:30
. panel - card {
2025-11-26 23:07:12 +05:30
background : var ( -- card - bg );
border - radius : 12 px ;
box - shadow : 0 8 px 20 px rgba ( 25 , 40 , 80 , 0.06 );
flex : 1 ;
min - width : 48 % ;
padding : 22 px ;
box - sizing : border - box ;
overflow - x : auto ;
transition : transform . 12 s , box - shadow . 12 s ;
min - height : 480 px ;
display : flex ;
flex - direction : column ;
2025-11-21 16:07:43 +05:30
}
. panel - card : hover { transform : translateY ( - 4 px ); box - shadow : 0 12 px 28 px rgba ( 25 , 40 , 80 , 0.08 ); }
2025-11-26 23:07:12 +05:30
. panel - title {
font - weight : 700 ;
font - size : 16 px ;
color : var ( -- primary - 1 );
margin - bottom : 16 px ;
display : flex ;
align - items : center ;
justify - content : space - between ;
}
2025-11-21 16:07:43 +05:30
/* table */
2025-11-26 23:07:12 +05:30
table {
width : 100 % ;
border - collapse : collapse ;
min - width : 720 px ;
font - size : 14 px ;
flex : 1 ;
}
th , td {
padding : 12 px 14 px ;
text - align : left ;
border - bottom : 1 px solid #eef3fb;
white - space : nowrap ;
color : #2d3b53;
}
th {
background : linear - gradient ( 90 deg , #f6f9ff,#fbfdff);
color : #4a5570;
font - weight : 700 ;
font - size : 13 px ;
}
2025-11-21 16:07:43 +05:30
tr : hover td { background : #fbfdff; }
. entry - link { color : var ( -- accent ); text - decoration : underline ; cursor : pointer ; font - weight : 700 ; }
/* badges */
2025-11-26 23:07:12 +05:30
/* === Modern Status Badges === */
. status - badge {
display : inline - block ;
padding : 6 px 16 px ;
border - radius : 999 px ;
font - size : 13 px ;
font - weight : 700 ;
color : #fff;
min - width : 76 px ;
text - align : center ;
box - shadow : 0 2 px 8 px rgba ( 33 , 43 , 90 , 0.07 );
letter - spacing : 0.1 px ;
background : #6b7280; /* fallback */
transition : box - shadow 0.22 s , transform 0.17 s , background 0.22 s ;
vertical - align : middle ;
margin : 3 px 2 px ;
cursor : default ;
/* subtle glass effect */
backdrop - filter : blur ( 2 px );
width : 99 px ;
}
. status - badge : hover {
transform : translateY ( - 3 px ) scale ( 1.045 );
box - shadow : 0 4 px 16 px rgba ( 33 , 43 , 90 , 0.16 );
opacity : 0.96 ;
}
/* High-impact, soft gradients for each status */
. status - unpaid {
background : linear - gradient ( 90 deg , #ff5959, #dc3545);
border : 1.5 px solid #d42c3f21;
width : 95 px ;
}
. status - paid {
background : linear - gradient ( 90 deg , #36d399 0%, #4ade80 100%);
border : 1.5 px solid #31b47a1a;
width : 95 px ;
}
. status - loading {
background : linear - gradient ( 90 deg , #509cf8 0%, #3f79d3 100%);
border : 1.5 px solid #1665c320;
width : 95 px ;
}
. status - dispatched {
background : linear - gradient ( 90 deg , #9775fa 0%, #845ef7 100%);
border : 1.5 px solid #9775fa40;
width : 95 px ;
}
. pending - badge - red {
background : linear - gradient ( 90 deg , #f43f5e, #ef4444);
border : 1.5 px solid #f43f5e41;
width : 95 px ;
}
. pending - badge - green {
background : linear - gradient ( 90 deg , #10b981 0%, #22d3ee 100%);
border : 1.5 px solid #10b98141;
width : 95 px ;
}
. status - delivered {
background : linear - gradient ( 90 deg , #22c55e 0%, #16a34a 100%);
border : 1.5 px solid #22c55e44;
width : 95 px ;
}
2025-11-21 16:07:43 +05:30
/* 3-state toggle */
. toggle - switch - btn {
appearance : none ;
- webkit - appearance : none ;
width : 60 px ;
height : 24 px ;
background : #f25b5b; /* RED */
border : 2 px solid #f25b5b;
border - radius : 999 px ;
position : relative ;
outline : none ;
cursor : pointer ;
transition : background . 22 s , border . 22 s ;
}
. toggle - switch - btn :: before {
content : " " ;
width : 20 px ;
height : 20 px ;
background : #fff;
border - radius : 50 % ;
position : absolute ;
top : 1.7 px ;
left : 2 px ;
transition : transform . 22 s ;
box - shadow : 0 2 px 8 px rgba ( 0 , 0 , 0 , 0.15 );
}
/* YELLOW */
. toggle - switch - btn . mid {
background : #f2c94c;
border - color : #f2c94c;
}
. toggle - switch - btn . mid :: before {
transform : translateX ( 20 px );
}
/* GREEN */
. toggle - switch - btn . checked {
background : #43d05c;
border - color : #43d05c;
}
. toggle - switch - btn . checked :: before {
transform : translateX ( 38 px );
}
/* plus button */
2025-11-26 23:07:12 +05:30
. plus - btn {
display : inline - block ;
width : 36 px ;
height : 36 px ;
border - radius : 10 px ;
background : #fff;
color : var ( -- primary - 1 );
border : 1.5 px solid #e6edf8;
font - size : 1.15 rem ;
font - weight : 700 ;
text - align : center ;
line - height : 34 px ;
cursor : pointer ;
transition : transform . 12 s ;
}
2025-11-21 16:07:43 +05:30
. plus - btn : hover { transform : translateY ( - 3 px ); box - shadow : 0 8 px 16 px rgba ( 33 , 47 , 90 , 0.04 ); }
2025-11-26 23:07:12 +05:30
/* ---------- 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 : 18 px ;
}
. create - order - modal . modal - open { display : flex ; }
. create - order - modal . modal - box {
background : #fff;
border - radius : 12 px ;
padding : 20 px 24 px ;
box - shadow : 0 14 px 40 px rgba ( 18 , 30 , 60 , 0.12 );
max - width : 1200 px ;
width : 100 % ;
max - height : 92 vh ;
overflow : auto ;
}
2025-11-21 16:07:43 +05:30
/* consolidated orders area */
. consolidate - area {
background : linear - gradient ( 90 deg , #fbfbff,#f9fcff);
2025-11-26 23:07:12 +05:30
border : 1 px solid #eef5ff;
padding : 14 px ;
border - radius : 10 px ;
box - shadow : inset 0 1 px 0 rgba ( 255 , 255 , 255 , 0.6 );
margin - top : 12 px ;
overflow : auto ;
min - height : 400 px ;
display : flex ;
flex - direction : column ;
2025-11-21 16:07:43 +05:30
}
/* compact table in consolidate */
2025-11-26 23:07:12 +05:30
#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 : 15 px ;
padding : 12 px 0 ;
border - top : 1 px solid #eef3fb;
}
. pagination - info {
font - size : 13 px ;
color : var ( -- muted );
font - weight : 600 ;
}
. pagination - controls {
display : flex ;
align - items : center ;
gap : 8 px ;
}
. pagination - btn {
background : #fff;
border : 1 px solid #e3eaf6;
color : var ( -- primary - 1 );
padding : 8 px 12 px ;
border - radius : 6 px ;
font - size : 13 px ;
font - weight : 600 ;
cursor : pointer ;
transition : all 0.3 s ease ;
display : flex ;
align - items : center ;
justify - content : center ;
min - width : 40 px ;
height : 32 px ;
}
. 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 : 1 px solid #e3eaf6;
color : var ( -- primary - 1 );
padding : 6 px 12 px ;
border - radius : 6 px ;
font - size : 13 px ;
font - weight : 600 ;
cursor : pointer ;
transition : all 0.3 s ease ;
min - width : 36 px ;
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 : 4 px ;
align - items : center ;
}
. pagination - ellipsis {
color : var ( -- muted );
font - size : 13 px ;
padding : 0 4 px ;
}
/* Image-based pagination buttons */
. pagination - img - btn {
background : #fff;
border : 1 px solid #e3eaf6;
border - radius : 6 px ;
cursor : pointer ;
transition : all 0.3 s ease ;
display : flex ;
align - items : center ;
justify - content : center ;
min - width : 40 px ;
height : 32 px ;
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 : 16 px ;
height : 16 px ;
filter : brightness ( 0 ) saturate ( 100 % ) invert ( 26 % ) sepia ( 89 % ) saturate ( 748 % ) hue - rotate ( 201 deg ) brightness ( 93 % ) contrast ( 89 % );
transition : filter 0.3 s ease ;
}
. pagination - img - btn : hover : not ( : disabled ) img {
filter : brightness ( 0 ) saturate ( 100 % ) invert ( 100 % ) sepia ( 100 % ) saturate ( 0 % ) hue - rotate ( 288 deg ) brightness ( 106 % ) contrast ( 101 % );
}
. pagination - img - btn : disabled img {
filter : brightness ( 0 ) saturate ( 100 % ) invert ( 84 % ) sepia ( 8 % ) saturate ( 165 % ) hue - rotate ( 179 deg ) brightness ( 89 % ) contrast ( 86 % );
}
2025-11-21 16:07:43 +05:30
/* ---------- Entry Details Modal (existing) ---------- */
2025-11-26 23:07:12 +05:30
. 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 : 18 px ;
}
2025-11-21 16:07:43 +05:30
. modal - fade1 . modal - open { display : flex ; }
2025-11-26 23:07:12 +05:30
. modal - box1 {
background : #fff;
border - radius : 12 px ;
padding : 20 px 24 px ;
box - shadow : 0 14 px 40 px rgba ( 18 , 30 , 60 , 0.12 );
max - width : 1200 px ;
width : 100 % ;
max - height : 92 vh ;
overflow : auto ;
min - height : 500 px ;
}
2025-11-21 16:07:43 +05:30
/* entry summary cards */
2025-11-26 23:07:12 +05:30
. entry - summary - cards {
display : flex ;
gap : 16 px ;
margin - bottom : 20 px ;
flex - wrap : wrap ;
}
. entry - summary - card {
background : #fbfdff;
border : 1 px solid #eef6ff;
padding : 16 px ;
border - radius : 10 px ;
min - width : 180 px ;
box - shadow : 0 6 px 18 px rgba ( 22 , 36 , 72 , 0.03 );
flex : 1 ;
}
2025-11-21 16:07:43 +05:30
. entry - summary - label { font - size : 12 px ; color : var ( -- muted ); }
. entry - summary - value { font - size : 18 px ; 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 : 12 px ; color : #7687a3; margin-top:6px; display:block; }
. empty - state { padding : 18 px ; text - align : center ; color : #6f7b8f; }
. kv { font - weight : 700 ; color : #26364f; }
2025-11-26 23:07:12 +05:30
/* form styles */
. input , select {
width : 100 % ;
padding : 12 px 14 px ;
border - radius : 8 px ;
border : 1.3 px solid #e3eaf6;
background : #fff;
font - size : 14 px ;
box - sizing : border - box ;
}
. create - grid {
display : grid ;
grid - template - columns : 1 fr 1 fr ;
gap : 16 px ;
align - items : start ;
margin - bottom : 16 px ;
}
. create - actions {
display : flex ;
gap : 12 px ;
justify - content : flex - end ;
margin - top : 16 px ;
}
2025-11-21 16:07:43 +05:30
/* responsive */
@ media ( max - width : 980 px ){
2025-11-26 23:07:12 +05:30
. create - grid { grid - template - columns : 1 fr ; }
2025-11-21 16:07:43 +05:30
. panel - card { min - width : 100 % ; }
. search - row input { width : 220 px ; }
2025-11-26 23:07:12 +05:30
. pagination - container { flex - direction : column ; gap : 10 px ; align - items : stretch ; }
. pagination - controls { justify - content : center ; }
. account - container { padding : 20 px ; }
. panel - card { padding : 18 px ; }
}
@ media ( max - width : 768 px ){
. account - header { padding : 18 px ; }
. top - actions { flex - direction : column ; align - items : stretch ; }
. top - actions . left { justify - content : center ; }
. entry - summary - cards { gap : 12 px ; }
. entry - summary - card { min - width : 140 px ; }
2025-11-21 16:07:43 +05:30
}
2025-11-11 14:51:35 +05:30
</ style >
< div class = " account-container " >
<!-- Header -->
< div class = " account-header " >
< h2 > Account Dashboard </ h2 >
2025-11-21 16:07:43 +05:30
< p > Viewing : All Regions — Workflow : Manage payments and dispatch .</ p >
2025-11-11 14:51:35 +05:30
</ div >
2025-11-21 16:07:43 +05:30
<!-- Top actions -->
< div class = " top-actions " >
< div class = " left " >
< 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 >
< div style = " display:flex; align-items:center; gap:8px; margin-left:6px; " >
2025-11-26 23:07:12 +05:30
< button class = " btn " id = " openCreateModalBtn " >+ Create New Order </ button >
2025-11-21 16:07:43 +05:30
</ div >
</ div >
2025-11-11 14:51:35 +05:30
< div >
2025-11-21 16:07:43 +05:30
< button class = " btn " id = " refreshBtn " > ⟳ Refresh </ button >
2025-11-11 14:51:35 +05:30
</ div >
</ div >
2025-11-21 16:07:43 +05:30
<!-- Panels -->
2025-11-11 14:51:35 +05:30
< div class = " account-panels " id = " account-panels " >
< div class = " panel-card " >
2025-11-21 16:07:43 +05:30
< 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 >
2025-11-11 14:51:35 +05:30
< table id = " main-payment-table " >
< thead >
< tr >
< th > Entry No </ th >< th > Date </ th >< th > Description </ th >
2025-11-21 16:07:43 +05:30
< th > Order Quantity </ th >< th > Region </ th >< th > Payment </ th >< th > Amount </ th >< th > Status </ th >
2025-11-11 14:51:35 +05:30
</ tr >
</ thead >
2025-11-21 16:07:43 +05:30
< tbody id = " paymentTableBody " >
< tr >< td colspan = " 8 " class = " empty-state " > Loading entries ...</ td ></ tr >
2025-11-11 14:51:35 +05:30
</ tbody >
</ table >
</ div >
2025-11-21 16:07:43 +05:30
2025-11-11 14:51:35 +05:30
< div class = " panel-card " >
2025-11-21 16:07:43 +05:30
< div class = " panel-title " >
< span > Order Dispatch Status </ span >
< span style = " font-size:13px;color:var(--muted) " > Actions : < strong >+ Add Installment </ strong ></ span >
</ div >
2025-11-11 14:51:35 +05:30
< 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 >
2025-11-21 16:07:43 +05:30
< tbody id = " orderTableBody " >
< tr >< td colspan = " 8 " class = " empty-state " > Loading entries ...</ td ></ tr >
2025-11-11 14:51:35 +05:30
</ tbody >
</ table >
</ div >
</ div >
</ div >
2025-11-26 23:07:12 +05:30
<!-- 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 Order </ 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 " >
< tr >< td colspan = " 14 " class = " empty-state " > Loading available orders ...</ td ></ tr >
</ tbody >
</ table >
<!-- Pagination Controls -->
< div class = " pagination-container " >
< div class = " pagination-info " id = " pageInfo " > Showing 1 to 10 of 0 entries </ div >
< div class = " pagination-controls " >
< button class = " pagination-img-btn " id = " prevPageBtn " title = " Previous page " >
<!-- Left arrow SVG -->
< svg width = " 16 " height = " 16 " viewBox = " 0 0 16 16 " fill = " none " xmlns = " http://www.w3.org/2000/svg " >
< path d = " M10 12L6 8L10 4 " stroke = " currentColor " stroke - width = " 2 " stroke - linecap = " round " stroke - linejoin = " round " />
</ svg >
</ button >
< div class = " pagination-pages " id = " paginationPages " >
<!-- Page numbers will be inserted here -->
</ div >
< button class = " pagination-img-btn " id = " nextPageBtn " title = " Next page " >
<!-- Right arrow SVG -->
< 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 = " create-actions " >
< button type = " button " class = " btn ghost " id = " cancelCreateModal " > Cancel </ button >
< button type = " submit " class = " btn " > Create Order </ button >
</ div >
</ form >
< div class = " helper-note " > Tip : Select orders from the list to include them in this consolidated entry . You can also search orders above .</ div >
</ div >
</ div >
2025-11-21 16:07:43 +05:30
<!-- ENTRY DETAILS MODAL -->
2025-11-12 19:56:06 +05:30
< div class = " modal-fade1 " id = " entryDetailsModal " >
< div class = " modal-box1 entry-details-modal " >
2025-11-21 16:07:43 +05:30
< 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 >
2025-11-12 19:56:06 +05:30
</ div >
2025-11-21 16:07:43 +05:30
2025-11-12 19:56:06 +05:30
< div class = " entry-summary-cards " >
< div class = " entry-summary-card " >
< div class = " entry-summary-label " > Original Amount </ div >
2025-11-21 16:07:43 +05:30
< div class = " entry-summary-value " id = " originalAmount " >-</ div >
2025-11-12 19:56:06 +05:30
</ div >
< div class = " entry-summary-card " >
< div class = " entry-summary-label " > Total Processed </ div >
2025-11-21 16:07:43 +05:30
< div class = " entry-summary-value " id = " totalProcessed " >-</ div >
2025-11-12 19:56:06 +05:30
</ div >
< div class = " entry-summary-card " >
< div class = " entry-summary-label " > Pending Balance </ div >
2025-11-21 16:07:43 +05:30
< div class = " entry-summary-value " id = " pendingBalance " >-</ div >
2025-11-12 19:56:06 +05:30
</ div >
< div class = " entry-summary-card " >
< div class = " entry-summary-label " > Total Installments </ div >
2025-11-21 16:07:43 +05:30
< div class = " entry-summary-value " id = " totalInstallments " >-</ div >
2025-11-12 19:56:06 +05:30
</ div >
</ div >
2025-11-21 16:07:43 +05:30
< table class = " entry-installments-table " style = " width:100%; border-collapse:collapse; " >
2025-11-12 19:56:06 +05:30
< 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 " >
2025-11-21 16:07:43 +05:30
< tr >< td colspan = " 6 " class = " empty-state " > No installments yet </ td ></ tr >
2025-11-12 19:56:06 +05:30
</ tbody >
</ table >
2025-11-21 16:07:43 +05:30
< 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 >
2025-11-12 19:56:06 +05:30
</ div >
</ div >
</ div >
2025-11-21 16:07:43 +05:30
<!-- Installment Modal -->
2025-11-11 14:51:35 +05:30
< div class = " modal-fade1 " id = " installmentModal " >
2025-11-21 16:07:43 +05:30
< 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 >
2025-11-26 23:07:12 +05:30
< button class = " btn ghost " onclick = " closeInstallmentModal() " > ✕ </ button >
2025-11-11 14:51:35 +05:30
</ div >
2025-11-21 16:07:43 +05:30
< 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 >
2025-11-11 14:51:35 +05:30
< form id = " installmentForm " autocomplete = " off " >
2025-11-21 16:07:43 +05:30
< 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') }} " >
2025-11-11 14:51:35 +05:30
</ div >
2025-11-21 16:07:43 +05:30
< 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 >
2025-11-11 14:51:35 +05:30
</ div >
2025-11-21 16:07:43 +05:30
< 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 >
2025-11-11 14:51:35 +05:30
</ select >
</ div >
</ div >
2025-11-21 16:07:43 +05:30
< 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 >
2025-11-11 14:51:35 +05:30
</ div >
</ form >
</ div >
</ div >
2025-11-21 16:07:43 +05:30
< script >
/* ---------- Helpers & state ---------- */
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 => {
// attempt to parse json even on non-ok to get backend message
return r . json () . catch (() => { throw new Error ( 'Invalid server response' ); });
});
}
let entries = [];
let availableOrders = [];
let currentEntry = null ;
2025-11-26 23:07:12 +05:30
/* Pagination state */
let currentPage = 1 ;
const ordersPerPage = 10 ;
2025-11-21 16:07:43 +05:30
/* small util */
function escapeHtml ( s ){ if ( s === null || s === undefined ) return '' ; return String ( s ) . replace ( / [ &<> " ']/g, m => ( { '&':'&','<':'<','>':'>',' " ':' & quot ; ',"' " : " & #39;"}[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' ;
default : return 'status-loading' ;
}
}
/* ---------- Init ---------- */
window . addEventListener ( 'DOMContentLoaded' , () => {
bindUI ();
loadDashboard ();
});
2025-11-12 19:56:06 +05:30
2025-11-21 16:07:43 +05:30
/* ---------- UI binding ---------- */
function bindUI (){
2025-11-26 23:07:12 +05:30
// Create Order Modal
document . getElementById ( 'openCreateModalBtn' ) . addEventListener ( 'click' , openCreateOrderModal );
document . getElementById ( 'closeCreateModal' ) . addEventListener ( 'click' , closeCreateOrderModal );
document . getElementById ( 'cancelCreateModal' ) . addEventListener ( 'click' , closeCreateOrderModal );
2025-11-21 16:07:43 +05:30
document . getElementById ( 'createOrderInlineForm' ) . addEventListener ( 'submit' , submitCreateOrderInline );
2025-11-26 23:07:12 +05:30
// Pagination buttons
document . getElementById ( 'prevPageBtn' ) . addEventListener ( 'click' , goToPreviousPage );
document . getElementById ( 'nextPageBtn' ) . addEventListener ( 'click' , goToNextPage );
document . getElementById ( 'refreshBtn' ) . addEventListener ( 'click' , () => { loadDashboard (); });
2025-11-21 16:07:43 +05:30
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 );
}
2025-11-26 23:07:12 +05:30
/* ---------- Pagination Functions ---------- */
function goToPreviousPage () {
if ( currentPage > 1 ) {
currentPage -- ;
renderConsolidateOrders ( availableOrders );
updatePaginationControls ();
}
}
function goToNextPage () {
const totalPages = Math . ceil ( availableOrders . length / ordersPerPage );
if ( currentPage < totalPages ) {
currentPage ++ ;
renderConsolidateOrders ( availableOrders );
updatePaginationControls ();
}
}
function updatePaginationControls () {
const totalPages = Math . ceil ( availableOrders . length / ordersPerPage );
const prevBtn = document . getElementById ( 'prevPageBtn' );
const nextBtn = document . getElementById ( 'nextPageBtn' );
const pageInfo = document . getElementById ( 'pageInfo' );
const paginationPages = document . getElementById ( 'paginationPages' );
prevBtn . disabled = currentPage === 1 ;
nextBtn . disabled = currentPage === totalPages || totalPages === 0 ;
// Update page info text
const startIndex = ( currentPage - 1 ) * ordersPerPage + 1 ;
const endIndex = Math . min ( currentPage * ordersPerPage , availableOrders . length );
pageInfo . textContent = `Showing ${startIndex} to ${endIndex} of ${availableOrders.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 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 ;
renderConsolidateOrders ( availableOrders );
updatePaginationControls ();
});
container . appendChild ( button );
2025-11-21 16:07:43 +05:30
}
2025-11-26 23:07:12 +05:30
/* ---------- Create Order Modal Functions ---------- */
function openCreateOrderModal (){
const modal = document . getElementById ( 'createOrderModal' );
modal . classList . add ( 'modal-open' );
2025-11-21 16:07:43 +05:30
loadAvailableOrders ();
// focus first input
setTimeout (() => document . getElementById ( 'inline_description' ) . focus (), 220 );
}
2025-11-26 23:07:12 +05:30
function closeCreateOrderModal (){
const modal = document . getElementById ( 'createOrderModal' );
modal . classList . remove ( 'modal-open' );
// reset form and pagination
document . getElementById ( 'createOrderInlineForm' ) . reset ();
currentPage = 1 ;
2025-11-21 16:07:43 +05:30
}
/* ---------- Loaders ---------- */
function loadDashboard (){
// show loading placeholders
document . getElementById ( 'paymentTableBody' ) . innerHTML = '<tr><td colspan="8" class="empty-state">Loading entries...</td></tr>' ;
document . getElementById ( 'orderTableBody' ) . innerHTML = '<tr><td colspan="8" class="empty-state">Loading entries...</td></tr>' ;
jsonFetch ( '/admin/account/dashboard' )
. then ( res => {
if ( ! res . success ) throw new Error ( res . message || 'Failed to load dashboard' );
entries = res . entries || [];
renderPaymentTable ( entries );
renderOrderTable ( entries );
document . getElementById ( 'entriesCount' ) . textContent = entries . length ;
})
. catch ( err => {
console . error ( err );
document . getElementById ( 'paymentTableBody' ) . innerHTML = '<tr><td colspan="8" 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' );
2025-11-26 23:07:12 +05:30
tbody . innerHTML = '<tr><td colspan="14" class="empty-state">Loading available orders...</td></tr>' ;
2025-11-21 16:07:43 +05:30
jsonFetch ( '/admin/account/available-orders' )
. then ( res => {
if ( ! res . success ) throw new Error ( res . message || 'Failed to load orders' );
availableOrders = res . orders || [];
2025-11-26 23:07:12 +05:30
currentPage = 1 ; // Reset to first page when loading new orders
2025-11-21 16:07:43 +05:30
renderConsolidateOrders ( availableOrders );
2025-11-26 23:07:12 +05:30
updatePaginationControls ();
2025-11-21 16:07:43 +05:30
})
. catch ( err => {
console . error ( err );
2025-11-26 23:07:12 +05:30
tbody . innerHTML = '<tr><td colspan="14" class="empty-state">Unable to load orders</td></tr>' ;
updatePaginationControls ();
2025-11-21 16:07:43 +05:30
});
}
/* ---------- Renderers ---------- */
function renderPaymentTable ( list ){
const body = document . getElementById ( 'paymentTableBody' );
body . innerHTML = '' ;
if ( ! list || list . length === 0 ){
body . innerHTML = '<tr><td colspan="8" class="empty-state">No entries found</td></tr>' ;
return ;
}
list . forEach ( entry => {
const tr = document . createElement ( 'tr' );
tr . innerHTML = `
< td > $ { escapeHtml ( entry . entry_no )} </ td >
< td > $ { escapeHtml ( entry . entry_date )} </ td >
< td > $ { escapeHtml ( entry . description )} </ td >
< td > $ { entry . order_quantity ? ? '-' } </ 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>
` ;
body . appendChild ( tr );
const btn = tr . querySelector ( '.toggle-switch-btn' );
setToggleVisual ( btn , Number ( entry . toggle_pos ));
btn . addEventListener ( 'click' , () => cycleToggle ( btn ));
});
}
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 ;
}
list . 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 = '' ;
2025-11-26 23:07:12 +05:30
2025-11-21 16:07:43 +05:30
if ( ! list || list . length === 0 ){
2025-11-26 23:07:12 +05:30
body . innerHTML = '<tr><td colspan="14" class="empty-state">No available orders</td></tr>' ;
2025-11-21 16:07:43 +05:30
return ;
}
2025-11-26 23:07:12 +05:30
// Calculate pagination
const startIndex = ( currentPage - 1 ) * ordersPerPage ;
const endIndex = startIndex + ordersPerPage ;
const paginatedOrders = list . slice ( startIndex , endIndex );
paginatedOrders . forEach ( order => {
2025-11-21 16:07:43 +05:30
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 );
});
}
2025-11-12 19:56:06 +05:30
2025-11-21 16:07:43 +05:30
function setToggleVisual ( btn , pos ) {
btn . classList . remove ( 'mid' , 'checked' );
2025-11-12 19:56:06 +05:30
2025-11-21 16:07:43 +05:30
// 0 = Red (default)
if ( pos === 1 ) {
btn . classList . add ( 'mid' ); // Yellow
}
else if ( pos === 2 ) {
btn . classList . add ( 'checked' ); // Green
}
2025-11-12 19:56:06 +05:30
2025-11-21 16:07:43 +05:30
btn . dataset . pos = pos ;
}
2025-11-12 19:56:06 +05:30
2025-11-21 16:07:43 +05:30
function cycleToggle ( btn ) {
let pos = Number ( btn . dataset . pos ) || 0 ;
pos = ( pos + 1 ) % 3 ; // 0 → 1 → 2 → 0
// Live update toggle
setToggleVisual ( btn , pos );
// Save to backend
jsonFetch ( '/admin/account/toggle-payment' , {
method : 'POST' ,
body : {
entry_no : btn . dataset . entry ,
toggle_pos : pos
}
})
. then ( res => {
if ( ! res . success ) alert ( res . message || 'Failed to update' );
loadDashboard (); // sync data
})
. catch ( err => {
console . error ( err );
loadDashboard ();
});
}
2025-11-26 23:07:12 +05:30
/* ---------- Create Order Inline (Now in Popup) ---------- */
2025-11-21 16:07:43 +05:30
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 ();
2025-11-26 23:07:12 +05:30
closeCreateOrderModal ();
2025-11-21 16:07:43 +05:30
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 Order' ; }
}
}
/* ---------- Search ---------- */
function handleSearch (){
const q = document . getElementById ( 'main-search' ) . value . trim () . toLowerCase ();
if ( ! q ){ renderPaymentTable ( entries ); renderOrderTable ( entries ); 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 );
});
renderPaymentTable ( filtered );
renderOrderTable ( filtered );
}
/* ---------- 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' : '' } > Pending </ option >
< option value = " Loading " $ { ins . status === 'Loading' ? 'selected' : '' } > Loading </ option >
< option value = " Packed " $ { ins . status === 'Packed' ? 'selected' : '' } > Packed </ option >
< option value = " Dispatched " $ { ins . status === 'Dispatched' ? 'selected' : '' } > Dispatched </ option >
< option value = " Delivered " $ { ins . status === 'Delivered' ? 'selected' : '' } > 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' );
2025-11-12 19:56:06 +05:30
}
}
function closeEntryDetailsModal () {
document . getElementById ( 'entryDetailsModal' ) . classList . remove ( 'modal-open' );
}
2025-11-21 16:07:43 +05:30
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 >
2025-11-12 19:56:06 +05:30
` ;
2025-11-21 16:07:43 +05:30
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' ; }
}
}
/* ---------- Utilities ---------- */
// ensure consolidate area visible by default
document . addEventListener ( 'DOMContentLoaded' , () => { document . getElementById ( 'consolidateArea' ) . style . display = 'block' ; });
2025-11-11 14:51:35 +05:30
</ script >
2025-11-21 16:07:43 +05:30
2025-11-26 23:07:12 +05:30
@ endsection