Account Section UI Changes

This commit is contained in:
Utkarsh Khedkar
2025-11-27 19:39:36 +05:30
parent 04b00c9db8
commit 97db70c40e
14 changed files with 2876 additions and 523 deletions

View File

@@ -523,6 +523,20 @@ tr:hover td{ background:#fbfdff; }
.entry-summary-cards { gap: 12px; }
.entry-summary-card { min-width: 140px; }
}
/* 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;
}
</style>
<div class="account-container">
@@ -551,6 +565,7 @@ tr:hover td{ background:#fbfdff; }
<!-- Panels -->
<div class="account-panels" id="account-panels">
<!-- Payment Sent Table -->
<div class="panel-card">
<div class="panel-title">
<span>Payment Sent to China</span>
@@ -564,11 +579,34 @@ tr:hover td{ background:#fbfdff; }
</tr>
</thead>
<tbody id="paymentTableBody">
<tr><td colspan="8" class="empty-state">Loading entries...</td></tr>
<!-- Entries will be loaded here -->
</tbody>
</table>
<!-- Pagination for Payment Table -->
<div class="table-pagination-wrapper">
<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>
@@ -583,9 +621,31 @@ tr:hover td{ background:#fbfdff; }
</tr>
</thead>
<tbody id="orderTableBody">
<tr><td colspan="8" class="empty-state">Loading entries...</td></tr>
<!-- Entries will be loaded here -->
</tbody>
</table>
<!-- Pagination for Order Table -->
<div class="table-pagination-wrapper">
<div class="pagination-container">
<div class="pagination-info" id="orderPageInfo">Showing 0 entries</div>
<div class="pagination-controls">
<button class="pagination-img-btn" id="orderPrevBtn" 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="orderPaginationPages">
<!-- Page numbers will be inserted here -->
</div>
<button class="pagination-img-btn" id="orderNextBtn" 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>
@@ -649,29 +709,29 @@ tr:hover td{ background:#fbfdff; }
</tr>
</thead>
<tbody id="consolidateOrdersBody">
<tr><td colspan="14" class="empty-state">Loading available orders...</td></tr>
<!-- Orders will be loaded here -->
</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 -->
<!-- Pagination for Create Order Modal -->
<div class="modal-pagination-wrapper">
<div class="pagination-container">
<div class="pagination-info" id="modalPageInfo">Showing 0 entries</div>
<div class="pagination-controls">
<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>
<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>
@@ -682,8 +742,6 @@ tr:hover td{ background:#fbfdff; }
<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>
@@ -787,7 +845,6 @@ 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'); });
});
}
@@ -796,9 +853,13 @@ let entries = [];
let availableOrders = [];
let currentEntry = null;
/* Pagination state */
/* Single pagination state for both tables */
let currentPage = 1;
const ordersPerPage = 10;
const entriesPerPage = 10;
/* Separate pagination state for modal */
let modalCurrentPage = 1;
const modalOrdersPerPage = 10;
/* small util */
function escapeHtml(s){ if(s === null || s === undefined) return ''; return String(s).replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":"&#39;"}[m])); }
@@ -828,9 +889,17 @@ function bindUI(){
document.getElementById('cancelCreateModal').addEventListener('click', closeCreateOrderModal);
document.getElementById('createOrderInlineForm').addEventListener('submit', submitCreateOrderInline);
// Pagination buttons
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
// Payment Table Pagination
document.getElementById('paymentPrevBtn').addEventListener('click', () => goToPreviousPage());
document.getElementById('paymentNextBtn').addEventListener('click', () => goToNextPage());
// Order Table Pagination
document.getElementById('orderPrevBtn').addEventListener('click', () => goToPreviousPage());
document.getElementById('orderNextBtn').addEventListener('click', () => goToNextPage());
// Modal Pagination
document.getElementById('modalPrevBtn').addEventListener('click', () => goToModalPreviousPage());
document.getElementById('modalNextBtn').addEventListener('click', () => goToModalNextPage());
document.getElementById('refreshBtn').addEventListener('click', () => { loadDashboard(); });
document.getElementById('searchBtn').addEventListener('click', handleSearch);
@@ -848,34 +917,58 @@ function bindUI(){
function goToPreviousPage() {
if (currentPage > 1) {
currentPage--;
renderConsolidateOrders(availableOrders);
updatePaginationControls();
renderBothTables();
updateBothPaginationControls();
}
}
function goToNextPage() {
const totalPages = Math.ceil(availableOrders.length / ordersPerPage);
const totalPages = Math.ceil(entries.length / entriesPerPage);
if (currentPage < totalPages) {
currentPage++;
renderConsolidateOrders(availableOrders);
updatePaginationControls();
renderBothTables();
updateBothPaginationControls();
}
}
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');
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 updateBothPaginationControls() {
const totalPages = Math.ceil(entries.length / entriesPerPage);
// Update both pagination controls
updatePaginationControls('payment', totalPages);
updatePaginationControls('order', totalPages);
}
function updatePaginationControls(tableType, totalPages) {
const prevBtn = document.getElementById(tableType + 'PrevBtn');
const nextBtn = document.getElementById(tableType + 'NextBtn');
const pageInfo = document.getElementById(tableType + 'PageInfo');
const paginationPages = document.getElementById(tableType + '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`;
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 = '';
@@ -908,49 +1001,113 @@ function updatePaginationControls() {
}
}
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 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();
renderBothTables();
updateBothPaginationControls();
});
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 renderBothTables() {
renderPaymentTable(entries);
renderOrderTable(entries);
}
/* ---------- Create Order Modal Functions ---------- */
function openCreateOrderModal(){
const modal = document.getElementById('createOrderModal');
modal.classList.add('modal-open');
loadAvailableOrders();
// focus first input
setTimeout(()=> document.getElementById('inline_description').focus(), 220);
}
function closeCreateOrderModal(){
const modal = document.getElementById('createOrderModal');
modal.classList.remove('modal-open');
// reset form and pagination
document.getElementById('createOrderInlineForm').reset();
currentPage = 1;
modalCurrentPage = 1; // Reset modal pagination when closing
}
/* ---------- 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);
renderBothTables();
document.getElementById('entriesCount').textContent = entries.length;
})
.catch(err => {
@@ -967,14 +1124,14 @@ function loadAvailableOrders(){
.then(res => {
if(!res.success) throw new Error(res.message || 'Failed to load orders');
availableOrders = res.orders || [];
currentPage = 1; // Reset to first page when loading new orders
modalCurrentPage = 1; // Reset to first page when loading new orders
renderConsolidateOrders(availableOrders);
updatePaginationControls();
updateModalPaginationControls();
})
.catch(err => {
console.error(err);
tbody.innerHTML = '<tr><td colspan="14" class="empty-state">Unable to load orders</td></tr>';
updatePaginationControls();
updateModalPaginationControls();
});
}
@@ -982,11 +1139,18 @@ function loadAvailableOrders(){
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 => {
// Calculate pagination
const startIndex = (currentPage - 1) * entriesPerPage;
const endIndex = startIndex + entriesPerPage;
const paginatedEntries = list.slice(startIndex, endIndex);
paginatedEntries.forEach(entry => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${escapeHtml(entry.entry_no)}</td>
@@ -1011,11 +1175,18 @@ function renderPaymentTable(list){
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 => {
// Calculate pagination
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>`;
@@ -1047,12 +1218,13 @@ function renderConsolidateOrders(list){
if(!list || list.length === 0){
body.innerHTML = '<tr><td colspan="14" class="empty-state">No available orders</td></tr>';
updateModalPaginationControls();
return;
}
// Calculate pagination
const startIndex = (currentPage - 1) * ordersPerPage;
const endIndex = startIndex + ordersPerPage;
// Calculate pagination for modal
const startIndex = (modalCurrentPage - 1) * modalOrdersPerPage;
const endIndex = startIndex + modalOrdersPerPage;
const paginatedOrders = list.slice(startIndex, endIndex);
paginatedOrders.forEach(order => {
@@ -1156,14 +1328,19 @@ async function submitCreateOrderInline(e){
/* ---------- Search ---------- */
function handleSearch(){
const q = document.getElementById('main-search').value.trim().toLowerCase();
if(!q){ renderPaymentTable(entries); renderOrderTable(entries); return; }
if(!q){
renderBothTables();
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);
entries = filtered;
currentPage = 1; // Reset to first page when searching
renderBothTables();
updateBothPaginationControls();
}
/* ---------- Entry details & installments ---------- */
@@ -1289,10 +1466,6 @@ async function submitInstallment(e){
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'; });
</script>
@endsection