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 ;
max - width : 1300 px ;
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 */
. account - panels { display : flex ; gap : 22 px ; align - items : flex - start ; flex - wrap : wrap ; }
. panel - card {
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 : 18 px ; box - sizing : border - box ; overflow - x : auto ; transition : transform . 12 s , box - shadow . 12 s ;
}
. panel - card : hover { transform : translateY ( - 4 px ); box - shadow : 0 12 px 28 px rgba ( 25 , 40 , 80 , 0.08 ); }
. panel - title { font - weight : 700 ; font - size : 16 px ; color : var ( -- primary - 1 ); margin - bottom : 12 px ; display : flex ; align - items : center ; justify - content : space - between ; }
/* table */
table { width : 100 % ; border - collapse : collapse ; min - width : 720 px ; font - size : 14 px ; }
th , td { padding : 10 px 12 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:13px; }
tr : hover td { background : #fbfdff; }
. entry - link { color : var ( -- accent ); text - decoration : underline ; cursor : pointer ; font - weight : 700 ; }
/* badges */
. status - badge { display : inline - block ; padding : 6 px 12 px ; border - radius : 20 px ; color : #fff; font-size:13px; font-weight:600; }
. status - unpaid { background : var ( -- danger ); }
. status - paid { background : var ( -- success ); }
. status - loading { background : #509cf8; }
. status - dispatched { background : #20c5c7; }
. pending - badge - red { background : var ( -- danger ); }
. pending - badge - green { background : var ( -- success ); }
/* 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 */
. plus - btn { display : inline - block ; width : 36 px ; height : 36 px ; border - radius : 10 px ; 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 ( - 3 px ); box - shadow : 0 8 px 16 px rgba ( 33 , 47 , 90 , 0.04 ); }
/* ---------- Expandable Create Order Card (Option D) ---------- */
. create - card {
margin : 10 px 0 18 px 0 ;
2025-11-12 19:56:06 +05:30
border - radius : 12 px ;
2025-11-21 16:07:43 +05:30
background : linear - gradient ( 180 deg , #ffffff,#fbfdff);
box - shadow : 0 10 px 30 px rgba ( 20 , 40 , 80 , 0.04 );
overflow : hidden ; transition : max - height . 28 s ease , padding . 22 s ease ;
max - height : 0 ; padding : 0 18 px ; opacity : 0 ; pointer - events : none ;
}
. create - card . open { max - height : 1500 px ; padding : 18 px ; opacity : 1 ; pointer - events : auto ; }
. create - card . create - inner { display : flex ; gap : 14 px ; flex - direction : column ; }
. create - card . create - grid { display : grid ; grid - template - columns : 1 fr 1 fr ; gap : 12 px ; align - items : start ; }
. create - card label { font - weight : 700 ; color : #28384f; margin-bottom:6px; font-size:13px; }
. input , select { width : 100 % ; padding : 10 px 12 px ; border - radius : 8 px ; border : 1.3 px solid #e3eaf6; background:#fff; font-size:14px; box-sizing:border-box; }
. create - actions { display : flex ; gap : 10 px ; justify - content : flex - end ; margin - top : 12 px ; }
/* consolidated orders area */
. consolidate - area {
background : linear - gradient ( 90 deg , #fbfbff,#f9fcff);
border : 1 px solid #eef5ff; padding:10px; border-radius:10px; box-shadow: inset 0 1px 0 rgba(255,255,255,0.6);
margin - top : 8 px ; overflow : auto ;
}
. consolidate - toggle { display : flex ; gap : 10 px ; align - items : center ; margin - bottom : 8 px ; }
. consolidate - tab - btn { background : #fff; border:1px solid #e6edf9; padding:8px 10px; border-radius:8px; font-weight:700; cursor:pointer; }
. consolidate - tab - btn . active { background : linear - gradient ( 90 deg , var ( -- primary - 1 ), var ( -- primary - 2 )); color : #fff; border-color:transparent; box-shadow:0 6px 18px rgba(36,58,114,0.08); }
/* compact table in consolidate */
#consolidateOrdersTable th, #consolidateOrdersTable td { padding:8px 9px; font-size:13px; border-bottom:1px solid #f1f6ff; }
/* ---------- 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 : 18 px ; }
. modal - fade1 . modal - open { display : flex ; }
. modal - box1 { background : #fff; border-radius:12px; padding:16px 18px; box-shadow:0 14px 40px rgba(18,30,60,0.12); max-width:1100px; width:100%; max-height:92vh; overflow:auto; }
/* entry summary cards */
. entry - summary - cards { display : flex ; gap : 12 px ; margin - bottom : 14 px ; flex - wrap : wrap ; }
. entry - summary - card { background : #fbfdff; border:1px solid #eef6ff; padding:12px; border-radius:10px; min-width:160px; box-shadow:0 6px 18px rgba(22,36,72,0.03); }
. 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; }
/* responsive */
@ media ( max - width : 980 px ){
. create - card . create - grid { grid - template - columns : 1 fr ; }
. panel - card { min - width : 100 % ; }
. search - row input { width : 220 px ; }
}
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; " >
< button class = " btn " id = " toggleCreateBtn " aria - expanded = " false " >+ Create New Order </ button >
</ 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
<!-- Expandable Create Order Card ( Option D ) -->
< div class = " create-card " id = " createCard " >
< div class = " create-inner " >
< div style = " display:flex; align-items:center; justify-content:space-between; " >
< div style = " font-size:18px; font-weight:800; color:var(--primary-1) " > Create New Order </ div >
< button class = " consolidate-tab-btn " id = " closeCreateInline " title = " Close create form " > ✕ </ button >
</ div >
< form id = " createOrderInlineForm " autocomplete = " off " >
< div class = " create-grid " style = " margin-top:6px; " >
< 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:10px; " >
< div class = " consolidate-toggle " >
< button type = " button " id = " toggleConsolidatedBtn " class = " consolidate-tab-btn active " > Hide Orders </ button >
< div style = " font-weight:700; color:#233063; margin-left: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 >
</ div >
</ div >
< div class = " create-actions " >
< button type = " button " class = " btn ghost " id = " cancelCreateInline " > 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 >
<!-- 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-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 >
< button class = " consolidate-tab-btn " 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 ;
/* 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 ();
loadAvailableOrders ();
});
2025-11-12 19:56:06 +05:30
2025-11-21 16:07:43 +05:30
/* ---------- UI binding ---------- */
function bindUI (){
document . getElementById ( 'toggleCreateBtn' ) . addEventListener ( 'click' , toggleCreateCard );
document . getElementById ( 'closeCreateInline' ) . addEventListener ( 'click' , () => closeCreateCard ());
document . getElementById ( 'cancelCreateInline' ) . addEventListener ( 'click' , () => closeCreateCard ());
document . getElementById ( 'createOrderInlineForm' ) . addEventListener ( 'submit' , submitCreateOrderInline );
document . getElementById ( 'refreshBtn' ) . addEventListener ( 'click' , () => { loadDashboard (); loadAvailableOrders (); });
document . getElementById ( 'toggleConsolidatedBtn' ) . addEventListener ( 'click' , toggleConsolidateVisibility );
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 );
}
/* ---------- Toggle create inline ---------------- */
function toggleCreateCard (){
const card = document . getElementById ( 'createCard' );
const btn = document . getElementById ( 'toggleCreateBtn' );
if ( card . classList . contains ( 'open' )){
closeCreateCard ();
} else {
openCreateCard ();
}
}
function openCreateCard (){
const card = document . getElementById ( 'createCard' );
card . classList . add ( 'open' );
document . getElementById ( 'toggleCreateBtn' ) . setAttribute ( 'aria-expanded' , 'true' );
// refresh orders
loadAvailableOrders ();
// focus first input
setTimeout (() => document . getElementById ( 'inline_description' ) . focus (), 220 );
}
function closeCreateCard (){
const card = document . getElementById ( 'createCard' );
card . classList . remove ( 'open' );
document . getElementById ( 'toggleCreateBtn' ) . setAttribute ( 'aria-expanded' , 'false' );
}
/* ---------- 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' );
tbody . innerHTML = '<tr><td colspan="10" class="empty-state">Loading available orders...</td></tr>' ;
jsonFetch ( '/admin/account/available-orders' )
. then ( res => {
if ( ! res . success ) throw new Error ( res . message || 'Failed to load orders' );
availableOrders = res . orders || [];
renderConsolidateOrders ( availableOrders );
})
. catch ( err => {
console . error ( err );
tbody . innerHTML = '<tr><td colspan="10" class="empty-state">Unable to load orders</td></tr>' ;
});
}
/* ---------- 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 = '' ;
if ( ! list || list . length === 0 ){
body . innerHTML = '<tr><td colspan="10" class="empty-state">No available orders</td></tr>' ;
return ;
}
list . 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 );
});
}
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 ();
});
}
/* ---------- Create Order Inline (Option D) ---------- */
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 ();
closeCreateCard ();
loadDashboard ();
loadAvailableOrders ();
} 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' ; }
}
}
/* ---------- Consolidate toggle ---------- */
function toggleConsolidateVisibility (){
const btn = document . getElementById ( 'toggleConsolidatedBtn' );
const area = document . getElementById ( 'consolidateArea' );
if ( btn . classList . contains ( 'active' )){
btn . classList . remove ( 'active' ); btn . textContent = 'Show Orders' ;
area . style . display = 'none' ;
} else {
btn . classList . add ( 'active' ); btn . textContent = 'Hide Orders' ;
area . style . display = 'block' ;
}
}
/* ---------- 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
@ endsection