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

@@ -9,11 +9,13 @@ use Illuminate\Support\Facades\Hash;
class AdminCustomerController extends Controller class AdminCustomerController extends Controller
{ {
// --------------------------------------------------------- // ---------------------------------------------------------
// LIST CUSTOMERS (with search + status filter) // LIST CUSTOMERS (with search + status filter)
// --------------------------------------------------------- // ---------------------------------------------------------
public function index(Request $request) public function index(Request $request)
{ {
$search = $request->search; $search = $request->search;
$status = $request->status; $status = $request->status;
@@ -34,10 +36,14 @@ class AdminCustomerController extends Controller
$query->where('status', $status); $query->where('status', $status);
} }
$customers = $query->get(); // Get all customers for statistics (without pagination)
$allCustomers = $query->get();
return view('admin.customers', compact('customers', 'search', 'status')); // Get paginated customers for the table (10 per page)
} $customers = $query->paginate(10);
return view('admin.customers', compact('customers', 'allCustomers', 'search', 'status'));
}
// --------------------------------------------------------- // ---------------------------------------------------------
// SHOW ADD CUSTOMER FORM // SHOW ADD CUSTOMER FORM
@@ -131,4 +137,5 @@ class AdminCustomerController extends Controller
return back()->with('success', 'Customer status updated.'); return back()->with('success', 'Customer status updated.');
} }
} }

View File

@@ -44,6 +44,8 @@ class RequestController extends Controller
'pincode' => $request->pincode, 'pincode' => $request->pincode,
'date' => Carbon::now()->toDateString(), // Auto current date 'date' => Carbon::now()->toDateString(), // Auto current date
'status' => 'pending', // Default status 'status' => 'pending', // Default status
]); ]);
// ✅ Response // ✅ Response

Binary file not shown.

View File

@@ -523,6 +523,20 @@ tr:hover td{ background:#fbfdff; }
.entry-summary-cards { gap: 12px; } .entry-summary-cards { gap: 12px; }
.entry-summary-card { min-width: 140px; } .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> </style>
<div class="account-container"> <div class="account-container">
@@ -551,6 +565,7 @@ tr:hover td{ background:#fbfdff; }
<!-- Panels --> <!-- Panels -->
<div class="account-panels" id="account-panels"> <div class="account-panels" id="account-panels">
<!-- Payment Sent Table -->
<div class="panel-card"> <div class="panel-card">
<div class="panel-title"> <div class="panel-title">
<span>Payment Sent to China</span> <span>Payment Sent to China</span>
@@ -564,11 +579,34 @@ tr:hover td{ background:#fbfdff; }
</tr> </tr>
</thead> </thead>
<tbody id="paymentTableBody"> <tbody id="paymentTableBody">
<tr><td colspan="8" class="empty-state">Loading entries...</td></tr> <!-- Entries will be loaded here -->
</tbody> </tbody>
</table> </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> </div>
<!-- Order Dispatch Table -->
<div class="panel-card"> <div class="panel-card">
<div class="panel-title"> <div class="panel-title">
<span>Order Dispatch Status</span> <span>Order Dispatch Status</span>
@@ -583,9 +621,31 @@ tr:hover td{ background:#fbfdff; }
</tr> </tr>
</thead> </thead>
<tbody id="orderTableBody"> <tbody id="orderTableBody">
<tr><td colspan="8" class="empty-state">Loading entries...</td></tr> <!-- Entries will be loaded here -->
</tbody> </tbody>
</table> </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> </div>
</div> </div>
@@ -649,25 +709,24 @@ tr:hover td{ background:#fbfdff; }
</tr> </tr>
</thead> </thead>
<tbody id="consolidateOrdersBody"> <tbody id="consolidateOrdersBody">
<tr><td colspan="14" class="empty-state">Loading available orders...</td></tr> <!-- Orders will be loaded here -->
</tbody> </tbody>
</table> </table>
<!-- Pagination Controls --> <!-- Pagination for Create Order Modal -->
<div class="modal-pagination-wrapper">
<div class="pagination-container"> <div class="pagination-container">
<div class="pagination-info" id="pageInfo">Showing 1 to 10 of 0 entries</div> <div class="pagination-info" id="modalPageInfo">Showing 0 entries</div>
<div class="pagination-controls"> <div class="pagination-controls">
<button class="pagination-img-btn" id="prevPageBtn" title="Previous page"> <button class="pagination-img-btn" id="modalPrevBtn" 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"> <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"/> <path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>
</button> </button>
<div class="pagination-pages" id="paginationPages"> <div class="pagination-pages" id="modalPaginationPages">
<!-- Page numbers will be inserted here --> <!-- Page numbers will be inserted here -->
</div> </div>
<button class="pagination-img-btn" id="nextPageBtn" title="Next page"> <button class="pagination-img-btn" id="modalNextBtn" 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"> <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"/> <path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>
@@ -676,14 +735,13 @@ tr:hover td{ background:#fbfdff; }
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="create-actions"> <div class="create-actions">
<button type="button" class="btn ghost" id="cancelCreateModal">Cancel</button> <button type="button" class="btn ghost" id="cancelCreateModal">Cancel</button>
<button type="submit" class="btn">Create Order</button> <button type="submit" class="btn">Create Order</button>
</div> </div>
</form> </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>
</div> </div>
@@ -787,7 +845,6 @@ function jsonFetch(url, opts = {}) {
opts.headers = Object.assign({'Content-Type':'application/json','X-CSRF-TOKEN': csrfToken}, opts.headers || {}); 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); if(opts.body && typeof opts.body !== 'string') opts.body = JSON.stringify(opts.body);
return fetch(url, opts).then(r => { 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'); }); return r.json().catch(() => { throw new Error('Invalid server response'); });
}); });
} }
@@ -796,9 +853,13 @@ let entries = [];
let availableOrders = []; let availableOrders = [];
let currentEntry = null; let currentEntry = null;
/* Pagination state */ /* Single pagination state for both tables */
let currentPage = 1; let currentPage = 1;
const ordersPerPage = 10; const entriesPerPage = 10;
/* Separate pagination state for modal */
let modalCurrentPage = 1;
const modalOrdersPerPage = 10;
/* small util */ /* small util */
function escapeHtml(s){ if(s === null || s === undefined) return ''; return String(s).replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":"&#39;"}[m])); } 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('cancelCreateModal').addEventListener('click', closeCreateOrderModal);
document.getElementById('createOrderInlineForm').addEventListener('submit', submitCreateOrderInline); document.getElementById('createOrderInlineForm').addEventListener('submit', submitCreateOrderInline);
// Pagination buttons // Payment Table Pagination
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage); document.getElementById('paymentPrevBtn').addEventListener('click', () => goToPreviousPage());
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage); 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('refreshBtn').addEventListener('click', () => { loadDashboard(); });
document.getElementById('searchBtn').addEventListener('click', handleSearch); document.getElementById('searchBtn').addEventListener('click', handleSearch);
@@ -848,34 +917,58 @@ function bindUI(){
function goToPreviousPage() { function goToPreviousPage() {
if (currentPage > 1) { if (currentPage > 1) {
currentPage--; currentPage--;
renderConsolidateOrders(availableOrders); renderBothTables();
updatePaginationControls(); updateBothPaginationControls();
} }
} }
function goToNextPage() { function goToNextPage() {
const totalPages = Math.ceil(availableOrders.length / ordersPerPage); const totalPages = Math.ceil(entries.length / entriesPerPage);
if (currentPage < totalPages) { if (currentPage < totalPages) {
currentPage++; currentPage++;
renderConsolidateOrders(availableOrders); renderBothTables();
updatePaginationControls(); updateBothPaginationControls();
} }
} }
function updatePaginationControls() { function goToModalPreviousPage() {
const totalPages = Math.ceil(availableOrders.length / ordersPerPage); if (modalCurrentPage > 1) {
const prevBtn = document.getElementById('prevPageBtn'); modalCurrentPage--;
const nextBtn = document.getElementById('nextPageBtn'); renderConsolidateOrders(availableOrders);
const pageInfo = document.getElementById('pageInfo'); updateModalPaginationControls();
const paginationPages = document.getElementById('paginationPages'); }
}
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; prevBtn.disabled = currentPage === 1;
nextBtn.disabled = currentPage === totalPages || totalPages === 0; nextBtn.disabled = currentPage === totalPages || totalPages === 0;
// Update page info text // Update page info text
const startIndex = (currentPage - 1) * ordersPerPage + 1; const startIndex = (currentPage - 1) * entriesPerPage + 1;
const endIndex = Math.min(currentPage * ordersPerPage, availableOrders.length); const endIndex = Math.min(currentPage * entriesPerPage, entries.length);
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${availableOrders.length} entries`; pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${entries.length} entries`;
// Generate page numbers // Generate page numbers
paginationPages.innerHTML = ''; 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) { function addPageButton(pageNumber, container) {
const button = document.createElement('button'); const button = document.createElement('button');
button.className = 'pagination-page-btn'; button.className = 'pagination-page-btn';
if (pageNumber === currentPage) { if (pageNumber === currentPage) {
button.classList.add('active'); button.classList.add('active');
} }
button.textContent = pageNumber; button.textContent = pageNumber;
button.addEventListener('click', () => { button.addEventListener('click', () => {
currentPage = pageNumber; currentPage = pageNumber;
renderConsolidateOrders(availableOrders); renderBothTables();
updatePaginationControls(); updateBothPaginationControls();
}); });
container.appendChild(button); 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 ---------- */ /* ---------- Create Order Modal Functions ---------- */
function openCreateOrderModal(){ function openCreateOrderModal(){
const modal = document.getElementById('createOrderModal'); const modal = document.getElementById('createOrderModal');
modal.classList.add('modal-open'); modal.classList.add('modal-open');
loadAvailableOrders(); loadAvailableOrders();
// focus first input
setTimeout(()=> document.getElementById('inline_description').focus(), 220); setTimeout(()=> document.getElementById('inline_description').focus(), 220);
} }
function closeCreateOrderModal(){ function closeCreateOrderModal(){
const modal = document.getElementById('createOrderModal'); const modal = document.getElementById('createOrderModal');
modal.classList.remove('modal-open'); modal.classList.remove('modal-open');
// reset form and pagination
document.getElementById('createOrderInlineForm').reset(); document.getElementById('createOrderInlineForm').reset();
currentPage = 1; modalCurrentPage = 1; // Reset modal pagination when closing
} }
/* ---------- Loaders ---------- */ /* ---------- Loaders ---------- */
function loadDashboard(){ 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') jsonFetch('/admin/account/dashboard')
.then(res => { .then(res => {
if(!res.success) throw new Error(res.message || 'Failed to load dashboard'); if(!res.success) throw new Error(res.message || 'Failed to load dashboard');
entries = res.entries || []; entries = res.entries || [];
renderPaymentTable(entries); renderBothTables();
renderOrderTable(entries);
document.getElementById('entriesCount').textContent = entries.length; document.getElementById('entriesCount').textContent = entries.length;
}) })
.catch(err => { .catch(err => {
@@ -967,14 +1124,14 @@ function loadAvailableOrders(){
.then(res => { .then(res => {
if(!res.success) throw new Error(res.message || 'Failed to load orders'); if(!res.success) throw new Error(res.message || 'Failed to load orders');
availableOrders = res.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); renderConsolidateOrders(availableOrders);
updatePaginationControls(); updateModalPaginationControls();
}) })
.catch(err => { .catch(err => {
console.error(err); console.error(err);
tbody.innerHTML = '<tr><td colspan="14" class="empty-state">Unable to load orders</td></tr>'; 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){ function renderPaymentTable(list){
const body = document.getElementById('paymentTableBody'); const body = document.getElementById('paymentTableBody');
body.innerHTML = ''; body.innerHTML = '';
if(!list || list.length === 0){ if(!list || list.length === 0){
body.innerHTML = '<tr><td colspan="8" class="empty-state">No entries found</td></tr>'; body.innerHTML = '<tr><td colspan="8" class="empty-state">No entries found</td></tr>';
return; 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 tr = document.createElement('tr');
tr.innerHTML = ` tr.innerHTML = `
<td>${escapeHtml(entry.entry_no)}</td> <td>${escapeHtml(entry.entry_no)}</td>
@@ -1011,11 +1175,18 @@ function renderPaymentTable(list){
function renderOrderTable(list){ function renderOrderTable(list){
const body = document.getElementById('orderTableBody'); const body = document.getElementById('orderTableBody');
body.innerHTML = ''; body.innerHTML = '';
if(!list || list.length === 0){ if(!list || list.length === 0){
body.innerHTML = '<tr><td colspan="8" class="empty-state">No entries found</td></tr>'; body.innerHTML = '<tr><td colspan="8" class="empty-state">No entries found</td></tr>';
return; 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 tr = document.createElement('tr');
const pending = Number(entry.pending_amount || 0); 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>`; 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){ if(!list || list.length === 0){
body.innerHTML = '<tr><td colspan="14" class="empty-state">No available orders</td></tr>'; body.innerHTML = '<tr><td colspan="14" class="empty-state">No available orders</td></tr>';
updateModalPaginationControls();
return; return;
} }
// Calculate pagination // Calculate pagination for modal
const startIndex = (currentPage - 1) * ordersPerPage; const startIndex = (modalCurrentPage - 1) * modalOrdersPerPage;
const endIndex = startIndex + ordersPerPage; const endIndex = startIndex + modalOrdersPerPage;
const paginatedOrders = list.slice(startIndex, endIndex); const paginatedOrders = list.slice(startIndex, endIndex);
paginatedOrders.forEach(order => { paginatedOrders.forEach(order => {
@@ -1156,14 +1328,19 @@ async function submitCreateOrderInline(e){
/* ---------- Search ---------- */ /* ---------- Search ---------- */
function handleSearch(){ function handleSearch(){
const q = document.getElementById('main-search').value.trim().toLowerCase(); 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 => { const filtered = entries.filter(e => {
return String(e.entry_no || '').toLowerCase().includes(q) || return String(e.entry_no || '').toLowerCase().includes(q) ||
String(e.description || '').toLowerCase().includes(q) || String(e.description || '').toLowerCase().includes(q) ||
String(e.region || '').toLowerCase().includes(q); String(e.region || '').toLowerCase().includes(q);
}); });
renderPaymentTable(filtered); entries = filtered;
renderOrderTable(filtered); currentPage = 1; // Reset to first page when searching
renderBothTables();
updateBothPaginationControls();
} }
/* ---------- Entry details & installments ---------- */ /* ---------- Entry details & installments ---------- */
@@ -1289,10 +1466,6 @@ async function submitInstallment(e){
if(btn){ btn.disabled = false; btn.textContent = 'Create Installment'; } 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> </script>
@endsection @endsection

View File

@@ -289,6 +289,139 @@
gap: 8px; gap: 8px;
} }
/* ---------- Pagination Styles (Same as Account Dashboard) ---------- */
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding: 12px 0;
border-top: 1px solid #eef3fb;
}
.pagination-info {
font-size: 13px;
color: #9ba5bb;
font-weight: 600;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
}
.pagination-btn:hover:not(:disabled) {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-btn:disabled {
background: #f8fafc;
color: #cbd5e0;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.6;
}
.pagination-page-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 36px;
text-align: center;
}
.pagination-page-btn:hover {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-page-btn.active {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-pages {
display: flex;
gap: 4px;
align-items: center;
}
.pagination-ellipsis {
color: #9ba5bb;
font-size: 13px;
padding: 0 4px;
}
/* Image-based pagination buttons */
.pagination-img-btn {
background: #fff;
border: 1px solid #e3eaf6;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
padding: 0;
}
.pagination-img-btn:hover:not(:disabled) {
background: #1a2951;
border-color: #1a2951;
}
.pagination-img-btn:disabled {
background: #f8fafc;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.5;
}
.pagination-img-btn img {
width: 16px;
height: 16px;
filter: brightness(0) saturate(100%) invert(26%) sepia(89%) saturate(748%) hue-rotate(201deg) brightness(93%) contrast(89%);
transition: filter 0.3s ease;
}
.pagination-img-btn:hover:not(:disabled) img {
filter: brightness(0) saturate(100%) invert(100%) sepia(100%) saturate(0%) hue-rotate(288deg) brightness(106%) contrast(101%);
}
.pagination-img-btn:disabled img {
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
}
@media (max-width: 768px) { @media (max-width: 768px) {
.search-filter-container { .search-filter-container {
flex-direction: column; flex-direction: column;
@@ -303,6 +436,16 @@
justify-content: center; justify-content: center;
flex-wrap: wrap; flex-wrap: wrap;
} }
.pagination-container {
flex-direction: column;
gap: 10px;
align-items: stretch;
}
.pagination-controls {
justify-content: center;
}
} }
</style> </style>
@@ -315,7 +458,7 @@
<!-- Stats Cards with REAL DATA --> <!-- Stats Cards with REAL DATA -->
<div class="stats-row"> <div class="stats-row">
<div class="stats-card"> <div class="stats-card">
<div class="stats-count">{{ $customers->count() }}</div> <div class="stats-count">{{ $allCustomers->count() }}</div>
<div class="stats-label">Total Customers</div> <div class="stats-label">Total Customers</div>
<i class="bi bi-people-fill stats-icon"></i> <i class="bi bi-people-fill stats-icon"></i>
</div> </div>
@@ -323,7 +466,7 @@
<div class="stats-card" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);"> <div class="stats-card" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
<div class="stats-count"> <div class="stats-count">
@php @php
$newThisMonth = $customers->filter(function($customer) { $newThisMonth = $allCustomers->filter(function($customer) {
return $customer->created_at->format('Y-m') === now()->format('Y-m'); return $customer->created_at->format('Y-m') === now()->format('Y-m');
})->count(); })->count();
@endphp @endphp
@@ -336,7 +479,7 @@
<div class="stats-card" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);"> <div class="stats-card" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
<div class="stats-count"> <div class="stats-count">
@php @php
$activeCustomers = $customers->where('status', 'active')->count(); $activeCustomers = $allCustomers->where('status', 'active')->count();
@endphp @endphp
{{ $activeCustomers }} {{ $activeCustomers }}
</div> </div>
@@ -347,7 +490,7 @@
<div class="stats-card" style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);"> <div class="stats-card" style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);">
<div class="stats-count"> <div class="stats-count">
@php @php
$premiumCount = $customers->where('customer_type', 'premium')->count(); $premiumCount = $allCustomers->where('customer_type', 'premium')->count();
@endphp @endphp
{{ $premiumCount }} {{ $premiumCount }}
</div> </div>
@@ -413,7 +556,7 @@
<th class="table-header" width="120">Actions</th> <th class="table-header" width="120">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody id="customersTableBody">
@forelse($customers as $c) @forelse($customers as $c)
<tr> <tr>
<!-- Customer Info Column --> <!-- Customer Info Column -->
@@ -489,6 +632,64 @@
</table> </table>
</div> </div>
</div> </div>
<!-- Pagination Controls -->
<div class="pagination-container">
<div class="pagination-info" id="pageInfo">
Showing {{ $customers->firstItem() ?? 0 }} to {{ $customers->lastItem() ?? 0 }} of {{ $customers->total() }} entries
</div>
<div class="pagination-controls">
<button class="pagination-img-btn" id="prevPageBtn" title="Previous page" {{ $customers->onFirstPage() ? 'disabled' : '' }}>
<!-- 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">
@for ($i = 1; $i <= $customers->lastPage(); $i++)
<a href="{{ $customers->url($i) }}"
class="pagination-page-btn {{ $customers->currentPage() == $i ? 'active' : '' }}">
{{ $i }}
</a>
@endfor
</div>
<button class="pagination-img-btn" id="nextPageBtn" title="Next page" {{ $customers->hasMorePages() ? '' : 'disabled' }}>
<!-- 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>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Add hover effects to table rows
const tableRows = document.querySelectorAll('.table tbody tr');
tableRows.forEach(row => {
row.addEventListener('mouseenter', function() {
this.style.transform = 'translateX(5px)';
});
row.addEventListener('mouseleave', function() {
this.style.transform = 'translateX(0)';
});
});
// Pagination button handlers
document.getElementById('prevPageBtn').addEventListener('click', function() {
@if(!$customers->onFirstPage())
window.location.href = '{{ $customers->previousPageUrl() }}';
@endif
});
document.getElementById('nextPageBtn').addEventListener('click', function() {
@if($customers->hasMorePages())
window.location.href = '{{ $customers->nextPageUrl() }}';
@endif
});
});
</script>
@endsection @endsection

View File

@@ -556,6 +556,138 @@ body, .container-fluid {
overflow-y: auto; overflow-y: auto;
} }
/* ===== PAGINATION STYLES ===== */
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding: 12px 0;
border-top: 1px solid #eef3fb;
}
.pagination-info {
font-size: 13px;
color: #9ba5bb;
font-weight: 600;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
}
.pagination-btn:hover:not(:disabled) {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-btn:disabled {
background: #f8fafc;
color: #cbd5e0;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.6;
}
.pagination-page-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 36px;
text-align: center;
}
.pagination-page-btn:hover {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-page-btn.active {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-pages {
display: flex;
gap: 4px;
align-items: center;
}
.pagination-ellipsis {
color: #9ba5bb;
font-size: 13px;
padding: 0 4px;
}
.pagination-img-btn {
background: #fff;
border: 1px solid #e3eaf6;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
padding: 0;
}
.pagination-img-btn:hover:not(:disabled) {
background: #1a2951;
border-color: #1a2951;
}
.pagination-img-btn:disabled {
background: #f8fafc;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.5;
}
.pagination-img-btn img {
width: 16px;
height: 16px;
filter: brightness(0) saturate(100%) invert(26%) sepia(89%) saturate(748%) hue-rotate(201deg) brightness(93%) contrast(89%);
transition: filter 0.3s ease;
}
.pagination-img-btn:hover:not(:disabled) img {
filter: brightness(0) saturate(100%) invert(100%) sepia(100%) saturate(0%) hue-rotate(288deg) brightness(106%) contrast(101%);
}
.pagination-img-btn:disabled img {
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
}
/* ===== RESPONSIVE BREAKPOINTS ===== */ /* ===== RESPONSIVE BREAKPOINTS ===== */
/* Laptop (1200px and below) */ /* Laptop (1200px and below) */
@@ -598,6 +730,16 @@ body, .container-fluid {
.table td { .table td {
padding: 10px 5px; padding: 10px 5px;
} }
.pagination-container {
flex-direction: column;
gap: 10px;
align-items: stretch;
}
.pagination-controls {
justify-content: center;
}
} }
/* Mobile Landscape (768px and below) */ /* Mobile Landscape (768px and below) */
@@ -900,6 +1042,9 @@ body, .container-fluid {
<div class="card shadow-sm"> <div class="card shadow-sm">
<div class="card-header bg-light"> <div class="card-header bg-light">
<strong>Recent Orders</strong> <strong>Recent Orders</strong>
<span style="font-size:13px;color:#6577a3;margin-left:10px;">
Total orders: <span id="ordersCount">{{ $orders->count() }}</span>
</span>
</div> </div>
<div class="card-body table-responsive"> <div class="card-body table-responsive">
<table class="table table-striped table-bordered align-middle text-center"> <table class="table table-striped table-bordered align-middle text-center">
@@ -923,7 +1068,7 @@ body, .container-fluid {
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody id="ordersTableBody">
@forelse($orders as $order) @forelse($orders as $order)
<tr> <tr>
<td>{{ $order->id }}</td> <td>{{ $order->id }}</td>
@@ -963,6 +1108,28 @@ body, .container-fluid {
@endforelse @endforelse
</tbody> </tbody>
</table> </table>
<!-- Pagination Controls -->
<div class="pagination-container">
<div class="pagination-info" id="pageInfo">Showing 1 to 10 of {{ $orders->count() }} 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> </div>
</div> </div>
@@ -1167,6 +1334,14 @@ document.addEventListener('DOMContentLoaded', function() {
const closeBtn = document.getElementById('closeCreateOrderModal'); const closeBtn = document.getElementById('closeCreateOrderModal');
const clearFormBtn = document.getElementById('clearForm'); const clearFormBtn = document.getElementById('clearForm');
// Pagination state
let currentPage = 1;
const ordersPerPage = 10;
let allOrders = @json($orders->values());
// Initialize pagination
initializePagination();
// Reset temp data function // Reset temp data function
const resetTempData = () => { const resetTempData = () => {
fetch('{{ route("admin.orders.temp.reset") }}', { fetch('{{ route("admin.orders.temp.reset") }}', {
@@ -1265,6 +1440,168 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
}); });
}); });
/* ---------- Pagination Functions ---------- */
function initializePagination() {
renderOrdersTable(allOrders);
updatePaginationControls();
// Bind pagination buttons
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
}
function goToPreviousPage() {
if (currentPage > 1) {
currentPage--;
renderOrdersTable(allOrders);
updatePaginationControls();
}
}
function goToNextPage() {
const totalPages = Math.ceil(allOrders.length / ordersPerPage);
if (currentPage < totalPages) {
currentPage++;
renderOrdersTable(allOrders);
updatePaginationControls();
}
}
function updatePaginationControls() {
const totalPages = Math.ceil(allOrders.length / ordersPerPage);
const prevBtn = document.getElementById('prevPageBtn');
const nextBtn = document.getElementById('nextPageBtn');
const pageInfo = document.getElementById('pageInfo');
const paginationPages = document.getElementById('paginationPages');
prevBtn.disabled = currentPage === 1;
nextBtn.disabled = currentPage === totalPages || totalPages === 0;
// Update page info text
const startIndex = (currentPage - 1) * ordersPerPage + 1;
const endIndex = Math.min(currentPage * ordersPerPage, allOrders.length);
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${allOrders.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;
renderOrdersTable(allOrders);
updatePaginationControls();
});
container.appendChild(button);
}
function renderOrdersTable(orders) {
const tbody = document.getElementById('ordersTableBody');
tbody.innerHTML = '';
if (!orders || orders.length === 0) {
tbody.innerHTML = '<tr><td colspan="16" class="text-muted">No orders found</td></tr>';
return;
}
// Calculate pagination
const startIndex = (currentPage - 1) * ordersPerPage;
const endIndex = startIndex + ordersPerPage;
const paginatedOrders = orders.slice(startIndex, endIndex);
paginatedOrders.forEach(order => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${order.id}</td>
<td>
<a href="javascript:void(0)"
class="fw-semibold text-primary open-order-modal"
data-id="${order.id}">
${order.order_id}
</a>
</td>
<td>${order.mark_no || ''}</td>
<td>${order.origin || ''}</td>
<td>${order.destination || ''}</td>
<td>${order.ctn || ''}</td>
<td>${order.qty || ''}</td>
<td>${order.ttl_qty || ''}</td>
<td>${order.ttl_amount ? Number(order.ttl_amount).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '0.00'}</td>
<td>${order.cbm || ''}</td>
<td>${order.ttl_cbm || ''}</td>
<td>${order.kg || ''}</td>
<td>${order.ttl_kg || ''}</td>
<td>
<span class="badge bg-info text-dark">${order.status ? order.status.charAt(0).toUpperCase() + order.status.slice(1) : ''}</span>
</td>
<td>${new Date(order.created_at).toLocaleDateString('en-GB')}</td>
<td>
<a href="/admin/orders/${order.id}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i> View
</a>
</td>
`;
tbody.appendChild(tr);
// Re-bind order details modal for newly rendered rows
const orderLink = tr.querySelector('.open-order-modal');
if (orderLink) {
orderLink.addEventListener('click', function() {
let id = this.dataset.id;
let modal = new bootstrap.Modal(document.getElementById('orderDetailsModal'));
document.getElementById('orderDetailsBody').innerHTML =
"<p class='text-center text-muted'>Loading...</p>";
modal.show();
fetch(`/admin/orders/view/${id}`)
.then(response => response.text())
.then(html => {
document.getElementById('orderDetailsBody').innerHTML = html;
})
.catch(() => {
document.getElementById('orderDetailsBody').innerHTML =
"<p class='text-danger text-center'>Failed to load order details.</p>";
});
});
}
});
}
}); });
</script> </script>

View File

@@ -737,6 +737,139 @@
border: none; border: none;
} }
/* ---------- Pagination Styles (Same as Account Dashboard) ---------- */
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding: 12px 25px;
border-top: 1px solid #eef3fb;
}
.pagination-info {
font-size: 13px;
color: #9ba5bb;
font-weight: 600;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
}
.pagination-btn:hover:not(:disabled) {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-btn:disabled {
background: #f8fafc;
color: #cbd5e0;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.6;
}
.pagination-page-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 36px;
text-align: center;
}
.pagination-page-btn:hover {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-page-btn.active {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-pages {
display: flex;
gap: 4px;
align-items: center;
}
.pagination-ellipsis {
color: #9ba5bb;
font-size: 13px;
padding: 0 4px;
}
/* Image-based pagination buttons */
.pagination-img-btn {
background: #fff;
border: 1px solid #e3eaf6;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
padding: 0;
}
.pagination-img-btn:hover:not(:disabled) {
background: #1a2951;
border-color: #1a2951;
}
.pagination-img-btn:disabled {
background: #f8fafc;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.5;
}
.pagination-img-btn img {
width: 16px;
height: 16px;
filter: brightness(0) saturate(100%) invert(26%) sepia(89%) saturate(748%) hue-rotate(201deg) brightness(93%) contrast(89%);
transition: filter 0.3s ease;
}
.pagination-img-btn:hover:not(:disabled) img {
filter: brightness(0) saturate(100%) invert(100%) sepia(100%) saturate(0%) hue-rotate(288deg) brightness(106%) contrast(101%);
}
.pagination-img-btn:disabled img {
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
}
/* Responsive Design */ /* Responsive Design */
@media (max-width: 1200px) { @media (max-width: 1200px) {
.invoice-tools-row { .invoice-tools-row {
@@ -835,6 +968,16 @@
grid-template-columns: 1fr; grid-template-columns: 1fr;
margin: 15px; margin: 15px;
} }
.pagination-container {
flex-direction: column;
gap: 10px;
align-items: stretch;
}
.pagination-controls {
justify-content: center;
}
} }
@media (max-width: 576px) { @media (max-width: 576px) {
@@ -944,7 +1087,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody id="invoicesTableBody">
@php @php
$totalInvoices = $invoices->count(); $totalInvoices = $invoices->count();
$sortedInvoices = $invoices->sortByDesc('created_at'); // Latest first $sortedInvoices = $invoices->sortByDesc('created_at'); // Latest first
@@ -1006,7 +1149,7 @@
</div> </div>
<!-- ALL INVOICES - Mobile Cards --> <!-- ALL INVOICES - Mobile Cards -->
<div class="mobile-invoices-container"> <div class="mobile-invoices-container" id="mobileInvoicesContainer">
@php @php
$totalInvoices = $invoices->count(); $totalInvoices = $invoices->count();
$sortedInvoices = $invoices->sortByDesc('created_at'); // Latest first $sortedInvoices = $invoices->sortByDesc('created_at'); // Latest first
@@ -1073,6 +1216,28 @@
<div class="text-muted text-center py-4">No invoices found</div> <div class="text-muted text-center py-4">No invoices found</div>
@endforelse @endforelse
</div> </div>
<!-- Pagination Controls -->
<div class="pagination-container">
<div class="pagination-info" id="pageInfo">Showing 1 to {{ $invoices->count() }} of {{ $invoices->count() }} entries</div>
<div class="pagination-controls">
<button class="pagination-img-btn" id="prevPageBtn" title="Previous page" disabled>
<!-- 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" disabled>
<!-- 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> </div>
</div> </div>
@@ -1095,6 +1260,20 @@
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Pagination state
let currentPage = 1;
const itemsPerPage = 10;
let allInvoices = @json($invoices);
let filteredInvoices = [...allInvoices];
// Initialize pagination
renderTable();
updatePaginationControls();
// Bind pagination events
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
// Invoice popup functionality // Invoice popup functionality
document.addEventListener('click', function(e) { document.addEventListener('click', function(e) {
if (e.target.closest('.open-invoice-popup')) { if (e.target.closest('.open-invoice-popup')) {
@@ -1131,69 +1310,44 @@ document.addEventListener('DOMContentLoaded', function() {
const startDateInput = document.getElementById('startDate'); const startDateInput = document.getElementById('startDate');
const endDateInput = document.getElementById('endDate'); const endDateInput = document.getElementById('endDate');
// Desktop table elements
const table = document.getElementById('invoicesTable');
const tableRows = table ? table.getElementsByTagName('tbody')[0].getElementsByTagName('tr') : [];
// Mobile card elements
const mobileCards = document.querySelectorAll('.mobile-invoice-card');
function filterInvoices() { function filterInvoices() {
const searchTerm = searchInput.value.toLowerCase(); const searchTerm = searchInput.value.toLowerCase();
const statusValue = statusFilter.value; const statusValue = statusFilter.value;
const startDate = startDateInput.value; const startDate = startDateInput.value;
const endDate = endDateInput.value; const endDate = endDateInput.value;
// Filter desktop table rows filteredInvoices = allInvoices.filter(invoice => {
for (let row of tableRows) { let include = true;
const cells = row.getElementsByTagName('td');
if (cells.length < 9) continue;
const invoiceNumber = cells[1].textContent.toLowerCase(); // Search filter
const customerName = cells[2].textContent.toLowerCase(); if (searchTerm) {
const status = cells[6].textContent.toLowerCase(); const matchesSearch =
const invoiceDate = cells[7].textContent; invoice.invoice_number.toLowerCase().includes(searchTerm) ||
invoice.customer_name.toLowerCase().includes(searchTerm);
if (!matchesSearch) include = false;
}
const matchesSearch = invoiceNumber.includes(searchTerm) || customerName.includes(searchTerm); // Status filter
const matchesStatus = !statusValue || status.includes(statusValue); if (statusValue && invoice.status !== statusValue) {
include = false;
}
// Date filtering // Date filter
let matchesDate = true;
if (startDate || endDate) { if (startDate || endDate) {
const cellDate = new Date(invoiceDate); const invoiceDate = new Date(invoice.invoice_date);
const start = startDate ? new Date(startDate) : null; const start = startDate ? new Date(startDate) : null;
const end = endDate ? new Date(endDate) : null; const end = endDate ? new Date(endDate) : null;
if (start && cellDate < start) matchesDate = false; if (start && invoiceDate < start) include = false;
if (end && cellDate > end) matchesDate = false; if (end && invoiceDate > end) include = false;
} }
row.style.display = matchesSearch && matchesStatus && matchesDate ? '' : 'none'; return include;
} });
// Filter mobile cards currentPage = 1;
for (let card of mobileCards) { renderTable();
const invoiceNumber = card.querySelector('.mobile-invoice-number-text').textContent.toLowerCase(); updatePaginationControls();
const customerName = card.querySelector('.mobile-detail-item:nth-child(1) .mobile-detail-value').textContent.toLowerCase();
const status = card.querySelector('.badge').textContent.toLowerCase();
const invoiceDate = card.querySelector('.mobile-detail-item:nth-child(5) .mobile-detail-value').textContent;
const matchesSearch = invoiceNumber.includes(searchTerm) || customerName.includes(searchTerm);
const matchesStatus = !statusValue || status.includes(statusValue);
// Date filtering
let matchesDate = true;
if (startDate || endDate) {
const cellDate = new Date(invoiceDate);
const start = startDate ? new Date(startDate) : null;
const end = endDate ? new Date(endDate) : null;
if (start && cellDate < start) matchesDate = false;
if (end && cellDate > end) matchesDate = false;
}
card.style.display = matchesSearch && matchesStatus && matchesDate ? '' : 'none';
}
} }
// Add event listeners for filtering // Add event listeners for filtering
@@ -1202,12 +1356,214 @@ document.addEventListener('DOMContentLoaded', function() {
startDateInput.addEventListener('change', filterInvoices); startDateInput.addEventListener('change', filterInvoices);
endDateInput.addEventListener('change', filterInvoices); endDateInput.addEventListener('change', filterInvoices);
// Add hover effects to table rows // Pagination Functions
for (let row of tableRows) { function goToPreviousPage() {
row.addEventListener('mouseenter', function() { if (currentPage > 1) {
this.style.cursor = 'pointer'; currentPage--;
renderTable();
updatePaginationControls();
}
}
function goToNextPage() {
const totalPages = Math.ceil(filteredInvoices.length / itemsPerPage);
if (currentPage < totalPages) {
currentPage++;
renderTable();
updatePaginationControls();
}
}
function updatePaginationControls() {
const totalPages = Math.ceil(filteredInvoices.length / itemsPerPage);
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) * itemsPerPage + 1;
const endIndex = Math.min(currentPage * itemsPerPage, filteredInvoices.length);
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${filteredInvoices.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;
renderTable();
updatePaginationControls();
});
container.appendChild(button);
}
// Render Table Function
function renderTable() {
const tbody = document.getElementById('invoicesTableBody');
const mobileContainer = document.getElementById('mobileInvoicesContainer');
if (filteredInvoices.length === 0) {
tbody.innerHTML = '<tr><td colspan="10" class="text-muted">No invoices found</td></tr>';
mobileContainer.innerHTML = '<div class="text-muted text-center py-4">No invoices found</div>';
return;
}
// Calculate pagination
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const paginatedItems = filteredInvoices.slice(startIndex, endIndex);
// Sort by creation date (newest first)
const sortedItems = [...paginatedItems].sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
// Render desktop table
tbody.innerHTML = '';
sortedItems.forEach((invoice, index) => {
const displayIndex = filteredInvoices.length - (startIndex + index);
const row = document.createElement('tr');
row.innerHTML = `
<td>${displayIndex}</td>
<td>
<div class="invoice-number-cell">
<div class="invoice-icon invoice-icon-${(displayIndex % 8) + 1}">
<i class="bi bi-file-earmark-text"></i>
</div>
<a href="#" class="invoice-number-link open-invoice-popup" data-id="${invoice.id}">
${invoice.invoice_number}
</a>
</div>
</td>
<td class="customer-cell">${invoice.customer_name}</td>
<td class="amount-cell">${parseFloat(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
<td class="gst-cell">${invoice.gst_percent}%</td>
<td class="amount-cell">${parseFloat(invoice.final_amount_with_gst).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
<td>
<span class="badge badge-${invoice.status}">
${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''}
${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''}
${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''}
${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
</span>
</td>
<td class="date-cell">${invoice.invoice_date}</td>
<td class="date-cell">${invoice.due_date}</td>
<td>
<a href="/admin/invoices/${invoice.id}/edit" class="btn-entry">
<i class="bi bi-pencil"></i> Entry
</a>
</td>
`;
tbody.appendChild(row);
});
// Render mobile cards
mobileContainer.innerHTML = '';
sortedItems.forEach((invoice, index) => {
const displayIndex = filteredInvoices.length - (startIndex + index);
const card = document.createElement('div');
card.className = 'mobile-invoice-card';
card.setAttribute('data-invoice-id', invoice.id);
card.innerHTML = `
<div class="mobile-invoice-header">
<div class="mobile-invoice-number">
<div class="mobile-invoice-icon invoice-icon-${(displayIndex % 8) + 1}">
<i class="bi bi-file-earmark-text"></i>
</div>
<span class="mobile-invoice-number-text">${invoice.invoice_number}</span>
</div>
<span class="badge badge-${invoice.status} mobile-invoice-status">
${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''}
${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''}
${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''}
${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
</span>
</div>
<div class="mobile-invoice-details">
<div class="mobile-detail-item">
<span class="mobile-detail-label">Customer</span>
<span class="mobile-detail-value">${invoice.customer_name}</span>
</div>
<div class="mobile-detail-item">
<span class="mobile-detail-label">Amount</span>
<span class="mobile-detail-value">${parseFloat(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</span>
</div>
<div class="mobile-detail-item">
<span class="mobile-detail-label">GST</span>
<span class="mobile-detail-value">${invoice.gst_percent}%</span>
</div>
<div class="mobile-detail-item">
<span class="mobile-detail-label">Total</span>
<span class="mobile-detail-value">${parseFloat(invoice.final_amount_with_gst).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</span>
</div>
<div class="mobile-detail-item">
<span class="mobile-detail-label">Invoice Date</span>
<span class="mobile-detail-value">${invoice.invoice_date}</span>
</div>
<div class="mobile-detail-item">
<span class="mobile-detail-label">Due Date</span>
<span class="mobile-detail-value">${invoice.due_date}</span>
</div>
</div>
<div class="mobile-invoice-actions">
<a href="#" class="mobile-action-btn view open-invoice-popup" data-id="${invoice.id}">
<i class="bi bi-eye"></i> View
</a>
<a href="/admin/invoices/${invoice.id}/edit" class="mobile-action-btn entry">
<i class="bi bi-pencil"></i> Entry
</a>
</div>
`;
mobileContainer.appendChild(card);
}); });
} }
// Add hover effects to table rows
document.addEventListener('mouseover', function(e) {
if (e.target.closest('tr')) {
const row = e.target.closest('tr');
if (row.parentElement.tagName === 'TBODY') {
row.style.cursor = 'pointer';
}
}
});
}); });
</script> </script>

View File

@@ -218,7 +218,7 @@
<form method="POST" action="{{ route('admin.logout') }}" class="mt-4 px-3"> <form method="POST" action="{{ route('admin.logout') }}" class="mt-4 px-3">
@csrf @csrf
<button type="submit" class="btn btn-danger w-100"><i class="bi bi-box-arrow-right"></i> Logout</button> <!-- <button type="submit" class="btn btn-danger w-100"><i class="bi bi-box-arrow-right"></i> Logout</button>-->
</form> </form>
</div> </div>

View File

@@ -277,6 +277,139 @@
from { box-shadow: 0 0 0px #b4a02455 inset; } from { box-shadow: 0 0 0px #b4a02455 inset; }
to { box-shadow: 0 0 10px #b4a024aa inset; } to { box-shadow: 0 0 10px #b4a024aa inset; }
} }
/* ---------- Pagination Styles ---------- */
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding: 12px 0;
border-top: 1px solid #eef3fb;
}
.pagination-info {
font-size: 13px;
color: #9ba5bb;
font-weight: 600;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
}
.pagination-btn:hover:not(:disabled) {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-btn:disabled {
background: #f8fafc;
color: #cbd5e0;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.6;
}
.pagination-page-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 36px;
text-align: center;
}
.pagination-page-btn:hover {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-page-btn.active {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-pages {
display: flex;
gap: 4px;
align-items: center;
}
.pagination-ellipsis {
color: #9ba5bb;
font-size: 13px;
padding: 0 4px;
}
/* Image-based pagination buttons */
.pagination-img-btn {
background: #fff;
border: 1px solid #e3eaf6;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
padding: 0;
}
.pagination-img-btn:hover:not(:disabled) {
background: #1a2951;
border-color: #1a2951;
}
.pagination-img-btn:disabled {
background: #f8fafc;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.5;
}
.pagination-img-btn img {
width: 16px;
height: 16px;
filter: brightness(0) saturate(100%) invert(26%) sepia(89%) saturate(748%) hue-rotate(201deg) brightness(93%) contrast(89%);
transition: filter 0.3s ease;
}
.pagination-img-btn:hover:not(:disabled) img {
filter: brightness(0) saturate(100%) invert(100%) sepia(100%) saturate(0%) hue-rotate(288deg) brightness(106%) contrast(101%);
}
.pagination-img-btn:disabled img {
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
}
</style> </style>
@if(session('success')) @if(session('success'))
@@ -304,63 +437,207 @@
<th>Action</th> <th>Action</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody id="markListTableBody">
@foreach($markList as $mark) <!-- Data will be loaded dynamically -->
<tr>
<td>{{ $mark->id }}</td>
<td>{{ $mark->mark_no }}</td>
<td>{{ $mark->origin }}</td>
<td>{{ $mark->destination }}</td>
<td>{{ $mark->customer_name }}</td>
<td>{{ $mark->customer_id }}</td>
<td>{{ $mark->mobile_no }}</td>
<td>{{ \Carbon\Carbon::parse($mark->date)->format('d-m-Y') }}</td>
<td>
@if($mark->status == 'active')
<span class="badge badge-active">
<i class="bi bi-check-circle-fill status-icon"></i>
Active
</span>
@else
<span class="badge badge-inactive">
<i class="bi bi-exclamation-triangle-fill status-icon"></i>
In-Active
</span>
@endif
</td>
<td>
@if($mark->status == 'active')
<a href="{{ route('admin.marklist.toggle', $mark->id) }}" class="btn-action btn-action-deactivate">
<i class="bi bi-power btn-icon"></i>
Deactivate
</a>
@else
<a href="{{ route('admin.marklist.toggle', $mark->id) }}" class="btn-action btn-action-activate">
<i class="bi bi-play-circle btn-icon"></i>
Activate
</a>
@endif
</td>
</tr>
@endforeach
</tbody> </tbody>
</table> </table>
@if($markList->isEmpty()) <!-- Pagination Controls -->
<div class="py-4 text-center text-muted fst-italic">No mark numbers found.</div> <div class="pagination-container">
@endif <div class="pagination-info" id="pageInfo">Showing 0 entries</div>
<div class="pagination-controls">
<button class="pagination-img-btn" id="prevPageBtn" title="Previous page" disabled>
<!-- 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" disabled>
<!-- 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 id="noResults" class="py-4 text-center text-muted fst-italic" style="display: none;">No mark numbers found.</div>
</div> </div>
<script> <script>
document.getElementById('globalSearch').addEventListener('input', function () { // Pagination state
const filter = this.value.toLowerCase(); let currentPage = 1;
const rows = document.querySelectorAll('#markListTable tbody tr'); const itemsPerPage = 10;
let allMarkList = @json($markList);
let filteredMarkList = [...allMarkList];
rows.forEach(row => { // Initialize pagination
const text = row.textContent.toLowerCase(); document.addEventListener('DOMContentLoaded', function() {
row.style.display = text.includes(filter) ? '' : 'none'; renderTable();
updatePaginationControls();
// Bind pagination events
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
// Bind search event
document.getElementById('globalSearch').addEventListener('input', handleSearch);
}); });
// Pagination Functions
function goToPreviousPage() {
if (currentPage > 1) {
currentPage--;
renderTable();
updatePaginationControls();
}
}
function goToNextPage() {
const totalPages = Math.ceil(filteredMarkList.length / itemsPerPage);
if (currentPage < totalPages) {
currentPage++;
renderTable();
updatePaginationControls();
}
}
function updatePaginationControls() {
const totalPages = Math.ceil(filteredMarkList.length / itemsPerPage);
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) * itemsPerPage + 1;
const endIndex = Math.min(currentPage * itemsPerPage, filteredMarkList.length);
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${filteredMarkList.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;
renderTable();
updatePaginationControls();
}); });
container.appendChild(button);
}
// Search Function
function handleSearch() {
const filter = this.value.toLowerCase();
filteredMarkList = allMarkList.filter(mark => {
return Object.values(mark).some(value =>
String(value).toLowerCase().includes(filter)
);
});
currentPage = 1;
renderTable();
updatePaginationControls();
}
// Render Table Function - FIXED: Using direct URL construction
function renderTable() {
const tbody = document.getElementById('markListTableBody');
const noResults = document.getElementById('noResults');
tbody.innerHTML = '';
if (filteredMarkList.length === 0) {
tbody.innerHTML = '';
noResults.style.display = 'block';
return;
}
noResults.style.display = 'none';
// Calculate pagination
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const paginatedItems = filteredMarkList.slice(startIndex, endIndex);
paginatedItems.forEach(mark => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${mark.id}</td>
<td>${mark.mark_no}</td>
<td>${mark.origin}</td>
<td>${mark.destination}</td>
<td>${mark.customer_name}</td>
<td>${mark.customer_id}</td>
<td>${mark.mobile_no}</td>
<td>${new Date(mark.date).toLocaleDateString('en-GB')}</td>
<td>
${mark.status == 'active'
? `<span class="badge badge-active">
<i class="bi bi-check-circle-fill status-icon"></i>
Active
</span>`
: `<span class="badge badge-inactive">
<i class="bi bi-exclamation-triangle-fill status-icon"></i>
In-Active
</span>`
}
</td>
<td>
${mark.status == 'active'
? `<a href="/admin/mark-list/status/${mark.id}" class="btn-action btn-action-deactivate">
<i class="bi bi-power btn-icon"></i>
Deactivate
</a>`
: `<a href="/admin/mark-list/status/${mark.id}" class="btn-action btn-action-activate">
<i class="bi bi-play-circle btn-icon"></i>
Activate
</a>`
}
</td>
`;
tbody.appendChild(row);
});
}
</script> </script>
</div> </div>
</div> </div>

View File

@@ -146,6 +146,139 @@ html, body {
padding: 16px; padding: 16px;
} }
} }
/* ---------- Pagination Styles (Same as Account Dashboard) ---------- */
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding: 12px 0;
border-top: 1px solid #eef3fb;
}
.pagination-info {
font-size: 13px;
color: #9ba5bb;
font-weight: 600;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
}
.pagination-btn:hover:not(:disabled) {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-btn:disabled {
background: #f8fafc;
color: #cbd5e0;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.6;
}
.pagination-page-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 36px;
text-align: center;
}
.pagination-page-btn:hover {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-page-btn.active {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-pages {
display: flex;
gap: 4px;
align-items: center;
}
.pagination-ellipsis {
color: #9ba5bb;
font-size: 13px;
padding: 0 4px;
}
/* Image-based pagination buttons */
.pagination-img-btn {
background: #fff;
border: 1px solid #e3eaf6;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
padding: 0;
}
.pagination-img-btn:hover:not(:disabled) {
background: #1a2951;
border-color: #1a2951;
}
.pagination-img-btn:disabled {
background: #f8fafc;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.5;
}
.pagination-img-btn img {
width: 16px;
height: 16px;
filter: brightness(0) saturate(100%) invert(26%) sepia(89%) saturate(748%) hue-rotate(201deg) brightness(93%) contrast(89%);
transition: filter 0.3s ease;
}
.pagination-img-btn:hover:not(:disabled) img {
filter: brightness(0) saturate(100%) invert(100%) sepia(100%) saturate(0%) hue-rotate(288deg) brightness(106%) contrast(101%);
}
.pagination-img-btn:disabled img {
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
}
</style> </style>
{{-- Make sure you include Font Awesome CDN in your main layout file, e.g., in <head>: {{-- Make sure you include Font Awesome CDN in your main layout file, e.g., in <head>:
@@ -179,9 +312,8 @@ html, body {
</tr> </tr>
</thead> </thead>
<tbody> <tbody id="ordersTableBody">
@foreach($orders as $order) @foreach($orders as $order)
@php @php
$mark = $order->markList ?? null; $mark = $order->markList ?? null;
$invoice = $order->invoice ?? null; $invoice = $order->invoice ?? null;
@@ -239,11 +371,32 @@ html, body {
</a> </a>
</td> </td>
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>
</table> </table>
</div> {{-- End table-wrapper --}} </div> {{-- End table-wrapper --}}
<!-- Pagination Controls -->
<div class="pagination-container">
<div class="pagination-info" id="pageInfo">Showing 1 to {{ min(10, count($orders)) }} of {{ count($orders) }} entries</div>
<div class="pagination-controls">
<button class="pagination-img-btn" id="prevPageBtn" title="Previous page" disabled>
<!-- 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" {{ count($orders) > 10 ? '' : 'disabled' }}>
<!-- 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>
@else @else
<p class="no-data"> <p class="no-data">
<i class="fas fa-info-circle mr-2"></i> No orders found. <i class="fas fa-info-circle mr-2"></i> No orders found.
@@ -251,4 +404,158 @@ html, body {
@endif @endif
</div> </div>
<script>
// Pagination state
let currentPage = 1;
const itemsPerPage = 10;
let allOrders = @json($orders);
let filteredOrders = [...allOrders];
// Initialize pagination
document.addEventListener('DOMContentLoaded', function() {
renderTable();
updatePaginationControls();
// Bind pagination events
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
});
// Pagination Functions
function goToPreviousPage() {
if (currentPage > 1) {
currentPage--;
renderTable();
updatePaginationControls();
}
}
function goToNextPage() {
const totalPages = Math.ceil(filteredOrders.length / itemsPerPage);
if (currentPage < totalPages) {
currentPage++;
renderTable();
updatePaginationControls();
}
}
function updatePaginationControls() {
const totalPages = Math.ceil(filteredOrders.length / itemsPerPage);
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) * itemsPerPage + 1;
const endIndex = Math.min(currentPage * itemsPerPage, filteredOrders.length);
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${filteredOrders.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;
renderTable();
updatePaginationControls();
});
container.appendChild(button);
}
// Render Table Function
function renderTable() {
const tbody = document.getElementById('ordersTableBody');
tbody.innerHTML = '';
if (filteredOrders.length === 0) {
tbody.innerHTML = '<tr><td colspan="14" class="text-center py-4 text-muted">No orders found.</td></tr>';
return;
}
// Calculate pagination
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const paginatedItems = filteredOrders.slice(startIndex, endIndex);
paginatedItems.forEach(order => {
const mark = order.markList || null;
const invoice = order.invoice || null;
const shipment = order.shipments?.[0] || null;
const invoiceStatus = (invoice?.status || '').toLowerCase();
const shipmentStatus = (shipment?.status || '').toLowerCase().replace('_', ' ');
const row = document.createElement('tr');
row.innerHTML = `
<td>${order.order_id || '-'}</td>
<td>${shipment?.shipment_id || '-'}</td>
<td>${mark?.customer_id || '-'}</td>
<td>${mark?.company_name || '-'}</td>
<td>${mark?.origin || order.origin || '-'}</td>
<td>${mark?.destination || order.destination || '-'}</td>
<td>${order.created_at ? new Date(order.created_at).toLocaleDateString('en-GB') : '-'}</td>
<td>${invoice?.invoice_number || '-'}</td>
<td>${invoice?.invoice_date ? new Date(invoice.invoice_date).toLocaleDateString('en-GB') : '-'}</td>
<td>${invoice?.final_amount ? '₹' + Number(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '-'}</td>
<td>${invoice?.final_amount_with_gst ? '₹' + Number(invoice.final_amount_with_gst).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '-'}</td>
<td>
${invoice?.status
? `<span class="status-badge status-${invoiceStatus}">${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}</span>`
: '<span class="status-badge status-pending">Pending</span>'
}
</td>
<td>
${shipment?.status
? `<span class="status-badge status-${shipmentStatus.replace(' ', '_')}">${shipment.status.charAt(0).toUpperCase() + shipment.status.slice(1)}</span>`
: '<span class="shipstatus">-</span>'
}
</td>
<td class="text-center">
<a href="/admin/orders/${order.id}" title="View Details">
<i class="fas fa-eye action-btn"></i>
</a>
</td>
`;
tbody.appendChild(row);
});
}
</script>
@endsection @endsection

View File

@@ -541,6 +541,139 @@
background: #a8a8a8; background: #a8a8a8;
} }
/* ---------- Pagination Styles (Same as Account Dashboard) ---------- */
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding: 12px 0;
border-top: 1px solid #eef3fb;
}
.pagination-info {
font-size: 13px;
color: #9ba5bb;
font-weight: 600;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
}
.pagination-btn:hover:not(:disabled) {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-btn:disabled {
background: #f8fafc;
color: #cbd5e0;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.6;
}
.pagination-page-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 36px;
text-align: center;
}
.pagination-page-btn:hover {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-page-btn.active {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-pages {
display: flex;
gap: 4px;
align-items: center;
}
.pagination-ellipsis {
color: #9ba5bb;
font-size: 13px;
padding: 0 4px;
}
/* Image-based pagination buttons */
.pagination-img-btn {
background: #fff;
border: 1px solid #e3eaf6;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
padding: 0;
}
.pagination-img-btn:hover:not(:disabled) {
background: #1a2951;
border-color: #1a2951;
}
.pagination-img-btn:disabled {
background: #f8fafc;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.5;
}
.pagination-img-btn img {
width: 16px;
height: 16px;
filter: brightness(0) saturate(100%) invert(26%) sepia(89%) saturate(748%) hue-rotate(201deg) brightness(93%) contrast(89%);
transition: filter 0.3s ease;
}
.pagination-img-btn:hover:not(:disabled) img {
filter: brightness(0) saturate(100%) invert(100%) sepia(100%) saturate(0%) hue-rotate(288deg) brightness(106%) contrast(101%);
}
.pagination-img-btn:disabled img {
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
}
@media (max-width: 1024px) { @media (max-width: 1024px) {
.stats-container { .stats-container {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
@@ -604,6 +737,16 @@
min-width: 100px; min-width: 100px;
padding: 10px 8px; padding: 10px 8px;
} }
.pagination-container {
flex-direction: column;
gap: 10px;
align-items: stretch;
}
.pagination-controls {
justify-content: center;
}
} }
@media (max-width: 480px) { @media (max-width: 480px) {
@@ -833,12 +976,42 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Pagination Controls -->
<div class="pagination-container">
<div class="pagination-info" id="pageInfo">Showing 1 to {{ min(10, count($reports)) }} of {{ count($reports) }} entries</div>
<div class="pagination-controls">
<button class="pagination-img-btn" id="prevPageBtn" title="Previous page" disabled>
<!-- 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" {{ count($reports) > 10 ? '' : 'disabled' }}>
<!-- 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>
<script> <script>
// Pagination state
let currentPage = 1;
const itemsPerPage = 10;
let allReports = @json($reports);
let filteredReports = [...allReports];
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Initialize statistics // Initialize statistics and pagination
updateStatistics(); updateStatistics();
renderTable();
updatePaginationControls();
// Set default dates for demo purposes // Set default dates for demo purposes
const today = new Date(); const today = new Date();
@@ -857,6 +1030,10 @@
document.getElementById('companyFilter').addEventListener('change', filterTable); document.getElementById('companyFilter').addEventListener('change', filterTable);
document.getElementById('statusFilter').addEventListener('change', filterTable); document.getElementById('statusFilter').addEventListener('change', filterTable);
// Bind pagination events
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
function updateStatistics() { function updateStatistics() {
const rows = document.querySelectorAll('#reportTableBody tr'); const rows = document.querySelectorAll('#reportTableBody tr');
let totalShipments = 0; let totalShipments = 0;
@@ -893,62 +1070,218 @@
document.getElementById('overdueInvoices').textContent = overdueInvoices; document.getElementById('overdueInvoices').textContent = overdueInvoices;
} }
// Pagination Functions
function goToPreviousPage() {
if (currentPage > 1) {
currentPage--;
renderTable();
updatePaginationControls();
}
}
function goToNextPage() {
const totalPages = Math.ceil(filteredReports.length / itemsPerPage);
if (currentPage < totalPages) {
currentPage++;
renderTable();
updatePaginationControls();
}
}
function updatePaginationControls() {
const totalPages = Math.ceil(filteredReports.length / itemsPerPage);
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) * itemsPerPage + 1;
const endIndex = Math.min(currentPage * itemsPerPage, filteredReports.length);
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${filteredReports.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;
renderTable();
updatePaginationControls();
});
container.appendChild(button);
}
// Render Table Function
function renderTable() {
const tbody = document.getElementById('reportTableBody');
tbody.innerHTML = '';
if (filteredReports.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="12">
<div class="empty-state">
<div class="empty-icon">
<i class="fas fa-inbox"></i>
</div>
<h3>No Shipping Reports Found</h3>
<p>There are no shipping reports matching your current filters</p>
</div>
</td>
</tr>
`;
return;
}
// Calculate pagination
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const paginatedItems = filteredReports.slice(startIndex, endIndex);
paginatedItems.forEach(report => {
const ist = (report.invoice_status || '').toLowerCase();
const row = document.createElement('tr');
row.setAttribute('data-status', report.shipment_status);
row.setAttribute('data-invoice-status', ist);
row.setAttribute('data-company', report.company_name || '');
row.setAttribute('data-date', report.shipment_date);
row.innerHTML = `
<td>
<span class="data-highlight" title="${report.order_id}">
<i class="fas fa-hashtag"></i>
${report.order_id}
</span>
</td>
<td>
<span class="data-highlight" title="${report.shipment_id}">
<i class="fas fa-barcode"></i>
${report.shipment_id}
</span>
</td>
<td title="${report.company_name || '-'}">${report.company_name || '-'}</td>
<td title="${report.customer_name || '-'}">${report.customer_name || '-'}</td>
<td>
<span class="data-highlight" title="${report.origin}">
<i class="fas fa-map-marker-alt"></i>
${report.origin}
</span>
</td>
<td>
<span class="data-highlight" title="${report.destination}">
<i class="fas fa-location-arrow"></i>
${report.destination}
</span>
</td>
<td>${new Date(report.shipment_date).toLocaleDateString('en-GB')}</td>
<td>
<span class="data-highlight" title="${report.invoice_number}">
<i class="fas fa-file-invoice"></i>
${report.invoice_number}
</span>
</td>
<td>${new Date(report.invoice_date).toLocaleDateString('en-GB')}</td>
<td>
<span class="data-highlight" title="${Number(report.final_amount).toLocaleString('en-IN')}">
<i class="fas fa-rupee-sign"></i>
${Number(report.final_amount).toLocaleString('en-IN')}
</span>
</td>
<td>
<span class="status-badge
${ist === 'paid' ? 'status-paid' : ''}
${ist === 'pending' ? 'status-pending' : ''}
${ist === 'overdue' ? 'status-overdue' : ''}">
<i class="fas fa-circle"></i>
${ist.charAt(0).toUpperCase() + ist.slice(1)}
</span>
</td>
<td>
<span class="status-badge ship-${report.shipment_status}">
<i class="fas fa-circle"></i>
${report.shipment_status.charAt(0).toUpperCase() + report.shipment_status.slice(1).replace('_', ' ')}
</span>
</td>
`;
tbody.appendChild(row);
});
}
function filterTable() { function filterTable() {
const fromDate = document.getElementById('fromDate').value; const fromDate = document.getElementById('fromDate').value;
const toDate = document.getElementById('toDate').value; const toDate = document.getElementById('toDate').value;
const companyFilter = document.getElementById('companyFilter').value; const companyFilter = document.getElementById('companyFilter').value;
const statusFilter = document.getElementById('statusFilter').value; const statusFilter = document.getElementById('statusFilter').value;
const rows = document.querySelectorAll('#reportTableBody tr'); filteredReports = allReports.filter(report => {
let visibleCount = 0; let include = true;
rows.forEach(row => {
// Skip empty state row
if (row.querySelector('.empty-state')) return;
let showRow = true;
// Date filter // Date filter
if (fromDate && toDate) { if (fromDate && toDate) {
const rowDate = row.getAttribute('data-date'); const reportDate = new Date(report.shipment_date);
if (rowDate) {
const shipmentDate = new Date(rowDate);
const from = new Date(fromDate); const from = new Date(fromDate);
const to = new Date(toDate); const to = new Date(toDate);
to.setHours(23, 59, 59, 999); // End of day to.setHours(23, 59, 59, 999);
if (shipmentDate < from || shipmentDate > to) { if (reportDate < from || reportDate > to) {
showRow = false; include = false;
}
} }
} }
// Company filter // Company filter
if (companyFilter) { if (companyFilter && report.company_name !== companyFilter) {
const companyName = row.getAttribute('data-company'); include = false;
if (companyName !== companyFilter) {
showRow = false;
}
} }
// Status filter // Status filter
if (statusFilter && row.getAttribute('data-status') !== statusFilter) { if (statusFilter && report.shipment_status !== statusFilter) {
showRow = false; include = false;
} }
// Show/hide row return include;
row.style.display = showRow ? '' : 'none';
if (showRow) visibleCount++;
}); });
// Show empty state if no rows visible currentPage = 1;
const emptyState = document.querySelector('.empty-state'); renderTable();
if (emptyState) { updatePaginationControls();
const emptyRow = emptyState.closest('tr');
emptyRow.style.display = visibleCount === 0 ? '' : 'none';
}
// Update statistics with filtered data
updateStatistics(); updateStatistics();
} }

View File

@@ -5,298 +5,325 @@
@section('content') @section('content')
<div class="container-fluid px-0"> <div class="container-fluid px-0">
@php
$perPage = 5; // ✅ FORCED to show pagination even with few records
$currentPage = request()->get('page', 1);
$currentPage = max(1, (int)$currentPage);
$total = $requests->count();
$totalPages = ceil($total / $perPage);
$currentItems = $requests->slice(($currentPage - 1) * $perPage, $perPage);
@endphp
<style> <style>
/* 🌟 Smooth fade-in animation */ /* [ALL YOUR ORIGINAL CSS HERE - SAME AS BEFORE] */
@keyframes fadeInUp { @keyframes fadeInUp {0% { transform: translateY(20px); opacity: 0; }100% { transform: translateY(0); opacity: 1; }}
0% { .card, .custom-table-wrapper { animation: fadeInUp 0.8s ease both; }
transform: translateY(20px); .custom-table tbody tr { transition: all 0.25s ease-in-out; }
opacity: 0; .custom-table tbody tr:hover { background-color: #fffbea; transform: scale(1.01); box-shadow: 0 2px 6px rgba(0,0,0,0.05); }
}
100% {
transform: translateY(0);
opacity: 1;
}
}
/* ✨ Container animation */
.card, .custom-table-wrapper {
animation: fadeInUp 0.8s ease both;
}
/* 🪄 Table hover effect */
.custom-table tbody tr {
transition: all 0.25s ease-in-out;
}
.custom-table tbody tr:hover {
background-color: #fffbea;
transform: scale(1.01);
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
/* 🎯 Priority Badges */
.priority-badge { .priority-badge {
display: inline-flex; display: inline-flex; align-items: center; font-size: 13.5px; padding: 6px 16px; border-radius: 12px; font-weight: 600;
align-items: center; box-shadow: 0 1px 2px 0 rgba(130,130,130,0.15); width: 90px; min-height: 28px; justify-content: center;
font-size: 13.5px; color: #fff; margin: 2px 0; transition: transform 0.2s ease-in-out;
padding: 6px 16px;
border-radius: 12px;
font-weight: 600;
box-shadow: 0 1px 2px 0 rgba(130,130,130,0.15);
width: 90px;
min-height: 28px;
justify-content: center;
color: #fff;
margin: 2px 0;
transition: transform 0.2s ease-in-out;
}
.priority-badge:hover {
transform: scale(1.08);
} }
.priority-badge:hover { transform: scale(1.08); }
.priority-high { background: linear-gradient(135deg, #ff8a8a, #d12929); } .priority-high { background: linear-gradient(135deg, #ff8a8a, #d12929); }
.priority-medium { background: linear-gradient(135deg, #ffe390, #f5b041); } .priority-medium { background: linear-gradient(135deg, #ffe390, #f5b041); }
.priority-low { background: linear-gradient(135deg, #b8f0c2, #1d8660); } .priority-low { background: linear-gradient(135deg, #b8f0c2, #1d8660); }
/* 🎨 Table Header (keep original bg-light color) */
.custom-table thead th { .custom-table thead th {
text-align: center; text-align: center; font-weight: 700; color: #000; padding: 14px; font-size: 17px; letter-spacing: 0.5px;
font-weight: 700; border-bottom: 2px solid #bfbfbf; background-color: #fde4b3;
color: #000;
padding: 14px;
font-size: 17px;
letter-spacing: 0.5px;
border-bottom: 2px solid #bfbfbf;
background-color: #fde4b3; /* Bootstrap bg-light */
} }
.custom-table thead tr:first-child th:first-child { border-top-left-radius: 12px; }
/* 🟢 Rounded Corners for Header */ .custom-table thead tr:first-child th:last-child { border-top-right-radius: 12px; }
.custom-table thead tr:first-child th:first-child { .custom-table tbody tr:last-child td:first-child { border-bottom-left-radius: 12px; }
border-top-left-radius: 12px; .custom-table tbody tr:last-child td:last-child { border-bottom-right-radius: 12px; }
} .input-group input { border-radius: 10px 0 0 10px; border: 1px solid #ccc; box-shadow: inset 0 1px 3px rgba(0,0,0,0.05); }
.custom-table thead tr:first-child th:last-child { .input-group .btn { border-radius: 0 10px 10px 0; transition: all 0.2s ease-in-out; }
border-top-right-radius: 12px; .input-group .btn:hover { background: #ffd65a; border-color: #ffd65a; color: #000; }
} .card { border-radius: 16px; border: none; box-shadow: 0 4px 10px rgba(0,0,0,0.08); background: #fff; }
/* 🧾 Table bottom corners */
.custom-table tbody tr:last-child td:first-child {
border-bottom-left-radius: 12px;
}
.custom-table tbody tr:last-child td:last-child {
border-bottom-right-radius: 12px;
}
/* 💫 Search box styling */
.input-group input {
border-radius: 10px 0 0 10px;
border: 1px solid #ccc;
box-shadow: inset 0 1px 3px rgba(0,0,0,0.05);
}
.input-group .btn {
border-radius: 0 10px 10px 0;
transition: all 0.2s ease-in-out;
}
.input-group .btn:hover {
background: #ffd65a;
border-color: #ffd65a;
color: #000;
}
/* 🎨 Card Style */
.card {
border-radius: 16px;
border: none;
box-shadow: 0 4px 10px rgba(0,0,0,0.08);
background: #fff;
}
/* ⚡ Status Badges - NEW ATTRACTIVE STYLES */
.badge { .badge {
font-size: 11px !important; font-size: 11px !important; font-weight: 600 !important; padding: 7px 13px !important; border-radius: 20px !important;
font-weight: 600 !important; text-transform: uppercase; letter-spacing: 0.3px; display: inline-flex !important; align-items: center; justify-content: center;
padding: 7px 13px !important; color: #fff !important; text-shadow: 0 1px 2px rgba(0,0,0,0.3); border: 2px solid transparent !important;
border-radius: 20px !important; line-height: 1.2; gap: 6px; animation: pulse 2s infinite; width: 99px;
text-transform: uppercase; }
letter-spacing: 0.3px; .status-icon { font-size: 0px; display: flex; align-items: center; justify-content: center; }
display: inline-flex !important; .badge.badge-pending { background: linear-gradient(135deg, #fef3c7, #fde68a) !important; color: #d97706 !important; border-color: #f59e0b !important; width: 85px; }
.badge.badge-approved { background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important; color: #065f46 !important; border-color: #10b981 !important; width: 85px; }
.badge.badge-rejected { background: linear-gradient(135deg, #fecaca, #fca5a5) !important; color: #991b1b !important; border-color: #ef4444 !important; width: 85px; }
@keyframes pulse {0% { box-shadow: 0 0 8px rgba(0,0,0,0.1); }50% { box-shadow: 0 0 14px rgba(0,0,0,0.15); }100% { box-shadow: 0 0 8px rgba(0,0,0,0.1); }}
.count-badge { --bs-badge-padding-x: 0.65em; --bs-badge-padding-y: 0.35em; --bs-badge-font-size: 0.75em; --bs-badge-font-weight: 700; --bs-badge-color: #fff; --bs-badge-border-radius: var(--bs-border-radius); }
h4.fw-bold { background: linear-gradient(90deg, #000000ff 0%, #030302ff 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 800; letter-spacing: 1px; }
.custom-table tbody td:last-child { text-align: center !important; vertical-align: middle !important; }
/* ===== PAGINATION STYLES ===== */
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center; align-items: center;
justify-content: center; margin-top: 15px;
background-size: cover !important; padding: 12px 0;
background-position: center !important; border-top: 1px solid #eef3fb;
color: #fff !important;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
border: 2px solid transparent !important;
box-sizing: border-box;
line-height: 1.2;
gap: 6px;
animation: pulse 2s infinite;
width: 99px;
} }
/* Status icons */ .pagination-info {
.status-icon { font-size: 13px;
font-size: 0px; color: #9ba5bb;
font-weight: 600;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-width: 40px;
height: 32px;
} }
/* Custom status badge backgrounds */ .pagination-btn:hover:not(:disabled) {
.badge-pending { background: #1a2951;
background: url('/images/status-bg-pending.png') !important; color: white;
border-color: #1a2951;
} }
.badge-approved { .pagination-btn:disabled {
background: url('/images/status-bg-approved.png') !important; background: #f8fafc;
color: #cbd5e0;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.6;
} }
.badge-rejected { .pagination-page-btn {
background: url('/images/status-bg-rejected.png') !important; background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 36px;
text-align: center;
text-decoration: none;
} }
/* Fallback colors if images don't load */ .pagination-page-btn:hover {
.badge.badge-pending { background: #1a2951;
background: linear-gradient(135deg, #fef3c7, #fde68a) !important; color: white;
color: #d97706 !important; border-color: #1a2951;
border-color: #f59e0b !important;
width: 85px;
} }
.badge.badge-approved { .pagination-page-btn.active {
background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important; background: #1a2951;
color: #065f46 !important; color: white;
border-color: #10b981 !important; border-color: #1a2951;
width: 85px;
} }
.badge.badge-rejected { .pagination-pages {
background: linear-gradient(135deg, #fecaca, #fca5a5) !important; display: flex;
color: #991b1b !important; gap: 4px;
border-color: #ef4444 !important; align-items: center;
width: 85px;
} }
/* Animation effects for badges */ .pagination-ellipsis {
@keyframes pulse { color: #9ba5bb;
0% { box-shadow: 0 0 8px rgba(0,0,0,0.1); } font-size: 13px;
50% { box-shadow: 0 0 14px rgba(0,0,0,0.15); } padding: 0 4px;
100% { box-shadow: 0 0 8px rgba(0,0,0,0.1); }
} }
/* Count badges at top */ .pagination-img-btn {
.count-badge { background: #fff;
--bs-badge-padding-x: 0.65em; border: 1px solid #e3eaf6;
--bs-badge-padding-y: 0.35em; border-radius: 6px;
--bs-badge-font-size: 0.75em; cursor: pointer;
--bs-badge-font-weight: 700; transition: all 0.3s ease;
--bs-badge-color: #fff; display: flex;
--bs-badge-border-radius: var(--bs-border-radius); align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
padding: 0;
text-decoration: none;
} }
/* ✨ Heading style */ .pagination-img-btn:hover:not(:disabled) {
h4.fw-bold { background: #1a2951;
background: linear-gradient(90deg, #000000ff 0%, #030302ff 100%); border-color: #1a2951;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 800;
letter-spacing: 1px;
} }
/* ✅ Center align for last column */ .pagination-img-btn:disabled {
.custom-table tbody td:last-child { background: #f8fafc;
text-align: center !important; border-color: #e2e8f0;
vertical-align: middle !important; cursor: not-allowed;
opacity: 0.5;
}
.pagination-img-btn svg {
width: 16px;
height: 16px;
transition: filter 0.3s ease;
}
.pagination-img-btn:hover:not(:disabled) svg {
filter: brightness(0) saturate(100%) invert(100%) sepia(100%) saturate(0%) hue-rotate(288deg) brightness(106%) contrast(101%);
}
.pagination-img-btn:disabled svg {
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
}
/* Responsive pagination */
@media (max-width: 768px) {
.pagination-container {
flex-direction: column;
gap: 10px;
align-items: stretch;
}
.pagination-controls {
justify-content: center;
}
} }
</style> </style>
<!-- Heading and status badges row --> <!-- Counts -->
<div class="d-flex justify-content-between align-items-center mb-2 mt-3"> <div class="d-flex justify-content-between align-items-center mb-2 mt-3">
<h4 class="fw-bold mb-0">User Requests</h4> <h4 class="fw-bold mb-0">User Requests (Total: {{ $total }})</h4>
<div> <div>
<span class="count-badge badge rounded-pill bg-warning text-dark me-1"> <span class="count-badge badge rounded-pill bg-warning text-dark me-1">{{ $requests->where('status', 'pending')->count() }} Pending</span>
{{ $requests->where('status', 'pending')->count() }} Pending <span class="count-badge badge rounded-pill bg-success me-1">{{ $requests->where('status', 'approved')->count() }} Approved</span>
</span> <span class="count-badge badge rounded-pill bg-danger">{{ $requests->where('status', 'rejected')->count() }} Rejected</span>
<span class="count-badge badge rounded-pill bg-success me-1">
{{ $requests->where('status', 'approved')->count() }} Approved
</span>
<span class="count-badge badge rounded-pill bg-danger">
{{ $requests->where('status', 'rejected')->count() }} Rejected
</span>
</div> </div>
</div> </div>
<!-- Search + Table -->
<div class="card mb-4 shadow-sm"> <div class="card mb-4 shadow-sm">
<div class="card-body pb-1"> <div class="card-body pb-1">
<form method="GET" action=""> <form method="GET" action="">
<div class="input-group mb-2"> <div class="input-group mb-2">
<input type="text" name="search" value="{{ old('search', request('search')) }}" class="form-control" placeholder="Search Request by name, email, Company or Request ID"> <input type="text" name="search" value="{{ request('search') }}" class="form-control" placeholder="🔍 Search by name, email, company...">
<button class="btn btn-outline-primary" type="submit"><i class="bi bi-search"></i></button> <button class="btn btn-outline-primary" type="submit"><i class="bi bi-search"></i></button>
</div> </div>
</form> </form>
<div class="table-responsive custom-table-wrapper"> <div class="table-responsive custom-table-wrapper">
<table class="table align-middle mb-0 custom-table"> <table class="table align-middle mb-0 custom-table">
<thead class="bg-light"> <thead><tr>
<tr> <th>#</th><th>Request ID</th><th>Name</th><th>Company</th><th>Email</th><th>Mobile</th><th>Address</th><th>Priority</th><th>Date</th><th>Status</th><th>Actions</th>
<th>#</th> </tr></thead>
<th>Request ID</th>
<th>Name</th>
<th>Company</th>
<th>Email</th>
<th>Mobile</th>
<th>Address</th>
<th>Priority</th>
<th>Date</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody> <tbody>
@foreach($requests as $index => $req) @forelse($currentItems as $index => $req)
<tr> <tr>
<td>{{ $requests->count() - $index }}</td> <td><strong>{{ ($currentPage - 1) * $perPage + $index + 1 }}</strong></td>
<td>{{ $req->request_id }}</td> <td>{{ $req->request_id }}</td>
<td>{{ $req->customer_name }}</td> <td>{{ $req->customer_name }}</td>
<td>{{ $req->company_name }}</td> <td>{{ $req->company_name }}</td>
<td>{{ $req->email }}</td> <td>{{ $req->email }}</td>
<td>{{ $req->mobile_no }}</td> <td>{{ $req->mobile_no }}</td>
<td>{{ $req->address }}</td> <td>{{ Str::limit($req->address, 30) }}</td>
<td> <td>
@if(strtolower($req->priority) == 'high') @if(strtolower($req->priority) == 'high')<span class="priority-badge priority-high">High</span>
<span class="priority-badge priority-high">High</span> @elseif(strtolower($req->priority) == 'medium')<span class="priority-badge priority-medium">Medium</span>
@elseif(strtolower($req->priority) == 'medium') @elseif(strtolower($req->priority) == 'low')<span class="priority-badge priority-low">Low</span>
<span class="priority-badge priority-medium">Medium</span> @else{{ $req->priority ?? 'N/A' }}@endif
@elseif(strtolower($req->priority) == 'low')
<span class="priority-badge priority-low">Low</span>
@else
{{ $req->priority }}
@endif
</td> </td>
<td>{{ $req->date }}</td> <td>{{ $req->date }}</td>
<td> <td>
@if($req->status == 'approved') @if($req->status == 'approved')<span class="badge badge-approved"><i class="bi bi-check-circle-fill status-icon"></i>Approved</span>
<span class="badge badge-approved"> @elseif($req->status == 'rejected')<span class="badge badge-rejected"><i class="bi bi-x-circle-fill status-icon"></i>Rejected</span>
<i class="bi bi-check-circle-fill status-icon"></i> @else<span class="badge badge-pending"><i class="bi bi-clock-fill status-icon"></i>Pending</span>@endif
Approved
</span>
@elseif($req->status == 'rejected')
<span class="badge badge-rejected">
<i class="bi bi-x-circle-fill status-icon"></i>
Rejected
</span>
@else
<span class="badge badge-pending">
<i class="bi bi-clock-fill status-icon"></i>
Pending
</span>
@endif
</td> </td>
<td>N/A</td> <td>N/A</td>
</tr> </tr>
@endforeach @empty
<tr><td colspan="11" class="text-center text-muted py-4">No records found.</td></tr>
@endforelse
</tbody> </tbody>
</table> </table>
@if($requests->isEmpty()) </div>
<div class="py-4 text-center text-muted">No records found.</div>
{{-- PAGINATION - WITH ARROW BUTTONS --}}
<div class="pagination-container">
<div class="pagination-info">
Showing {{ ($currentPage - 1) * $perPage + 1 }} to {{ min($currentPage * $perPage, $total) }} of {{ $total }} entries
</div>
<div class="pagination-controls">
{{-- Previous Page --}}
@if($currentPage > 1)
<a href="{{ request()->fullUrlWithQuery(['page' => $currentPage - 1]) }}" class="pagination-img-btn" 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>
</a>
@else
<button class="pagination-img-btn" disabled 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>
@endif @endif
{{-- Page Numbers --}}
<div class="pagination-pages">
@php
$start = max(1, $currentPage - 1);
$end = min($totalPages, $currentPage + 1);
@endphp
@if($start > 1)
<a href="{{ request()->fullUrlWithQuery(['page' => 1]) }}" class="pagination-page-btn">1</a>
@if($start > 2)
<span class="pagination-ellipsis">...</span>
@endif
@endif
@for($i = $start; $i <= $end; $i++)
@if($i == $currentPage)
<span class="pagination-page-btn active">{{ $i }}</span>
@else
<a href="{{ request()->fullUrlWithQuery(['page' => $i]) }}" class="pagination-page-btn">{{ $i }}</a>
@endif
@endfor
@if($end < $totalPages)
@if($end < $totalPages - 1)
<span class="pagination-ellipsis">...</span>
@endif
<a href="{{ request()->fullUrlWithQuery(['page' => $totalPages]) }}" class="pagination-page-btn">{{ $totalPages }}</a>
@endif
</div>
{{-- Next Page --}}
@if($currentPage < $totalPages)
<a href="{{ request()->fullUrlWithQuery(['page' => $currentPage + 1]) }}" class="pagination-img-btn" 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>
</a>
@else
<button class="pagination-img-btn" disabled 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>
@endif
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -917,6 +917,151 @@
.shipment-row.visible { .shipment-row.visible {
display: table-row; display: table-row;
} }
/* ---------- Pagination Styles (Same as Account Dashboard) ---------- */
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding: 12px 25px;
border-top: 1px solid #eef3fb;
}
.pagination-info {
font-size: 13px;
color: #9ba5bb;
font-weight: 600;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
}
.pagination-btn:hover:not(:disabled) {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-btn:disabled {
background: #f8fafc;
color: #cbd5e0;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.6;
}
.pagination-page-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 36px;
text-align: center;
}
.pagination-page-btn:hover {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-page-btn.active {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-pages {
display: flex;
gap: 4px;
align-items: center;
}
.pagination-ellipsis {
color: #9ba5bb;
font-size: 13px;
padding: 0 4px;
}
/* Image-based pagination buttons */
.pagination-img-btn {
background: #fff;
border: 1px solid #e3eaf6;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
padding: 0;
}
.pagination-img-btn:hover:not(:disabled) {
background: #1a2951;
border-color: #1a2951;
}
.pagination-img-btn:disabled {
background: #f8fafc;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.5;
}
.pagination-img-btn img {
width: 16px;
height: 16px;
filter: brightness(0) saturate(100%) invert(26%) sepia(89%) saturate(748%) hue-rotate(201deg) brightness(93%) contrast(89%);
transition: filter 0.3s ease;
}
.pagination-img-btn:hover:not(:disabled) img {
filter: brightness(0) saturate(100%) invert(100%) sepia(100%) saturate(0%) hue-rotate(288deg) brightness(106%) contrast(101%);
}
.pagination-img-btn:disabled img {
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
}
@media (max-width: 768px) {
.pagination-container {
flex-direction: column;
gap: 10px;
align-items: stretch;
}
.pagination-controls {
justify-content: center;
}
}
</style> </style>
<div class="container-fluid py-4"> <div class="container-fluid py-4">
@@ -1153,6 +1298,28 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Pagination Controls -->
<div class="pagination-container">
<div class="pagination-info" id="pageInfo">Showing 1 to {{ $shipments->count() }} of {{ $shipments->count() }} entries</div>
<div class="pagination-controls">
<button class="pagination-img-btn" id="prevPageBtn" title="Previous page" disabled>
<!-- 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" disabled>
<!-- 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> </div>
@@ -1185,12 +1352,25 @@
<!-- MODAL LOAD SCRIPT (AJAX) --> <!-- MODAL LOAD SCRIPT (AJAX) -->
<!-- ========================= --> <!-- ========================= -->
<script> <script>
// Status Filter Functionality // Pagination state
let currentPage = 1;
const itemsPerPage = 10;
let allShipments = @json($shipments);
let filteredShipments = [...allShipments];
// Initialize pagination on page load
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
renderTable();
updatePaginationControls();
// Bind pagination events
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
// Status Filter Functionality
const statusFilter = document.getElementById('statusFilter'); const statusFilter = document.getElementById('statusFilter');
const searchInput = document.getElementById('searchInput'); const searchInput = document.getElementById('searchInput');
const carrierFilter = document.getElementById('carrierFilter'); const carrierFilter = document.getElementById('carrierFilter');
const shipmentRows = document.querySelectorAll('.shipment-row');
// Function to filter shipments // Function to filter shipments
function filterShipments() { function filterShipments() {
@@ -1198,48 +1378,34 @@ document.addEventListener('DOMContentLoaded', function() {
const searchTerm = searchInput.value.toLowerCase(); const searchTerm = searchInput.value.toLowerCase();
const selectedCarrier = carrierFilter.value; const selectedCarrier = carrierFilter.value;
shipmentRows.forEach(row => { filteredShipments = allShipments.filter(shipment => {
const status = row.getAttribute('data-status'); let include = true;
const shipmentId = row.getAttribute('data-shipment-id').toLowerCase();
const origin = row.cells[2].textContent.toLowerCase();
const destination = row.cells[3].textContent.toLowerCase();
let statusMatch = selectedStatus === 'all' || status === selectedStatus; // Status filter
let searchMatch = searchTerm === '' || if (selectedStatus !== 'all' && shipment.status !== selectedStatus) {
shipmentId.includes(searchTerm) || include = false;
origin.includes(searchTerm) ||
destination.includes(searchTerm);
let carrierMatch = selectedCarrier === 'all'; // You can add carrier data attribute if needed
if (statusMatch && searchMatch && carrierMatch) {
row.classList.remove('hidden');
row.classList.add('visible');
} else {
row.classList.add('hidden');
row.classList.remove('visible');
} }
// Search filter
if (searchTerm) {
const matchesSearch =
shipment.shipment_id.toLowerCase().includes(searchTerm) ||
shipment.origin.toLowerCase().includes(searchTerm) ||
shipment.destination.toLowerCase().includes(searchTerm);
if (!matchesSearch) include = false;
}
// Carrier filter (you can add carrier data attribute if needed)
if (selectedCarrier !== 'all') {
// Add carrier filtering logic here if you have carrier data
}
return include;
}); });
// Show message if no results currentPage = 1;
const visibleRows = document.querySelectorAll('.shipment-row:not(.hidden)'); renderTable();
const noResultsRow = document.querySelector('.shipment-row[colspan]'); updatePaginationControls();
if (visibleRows.length === 0 && !noResultsRow) {
// Add no results message
const tbody = document.getElementById('shipmentsTableBody');
const noResultsHtml = `
<tr>
<td colspan="11" class="text-center py-5 text-muted">
<i class="bi bi-search display-4 d-block mb-3"></i>
No shipments found matching your criteria
</td>
</tr>
`;
tbody.innerHTML = noResultsHtml;
} else if (visibleRows.length > 0 && noResultsRow) {
// Remove no results message
noResultsRow.remove();
}
} }
// Event listeners for filters // Event listeners for filters
@@ -1251,6 +1417,171 @@ document.addEventListener('DOMContentLoaded', function() {
filterShipments(); filterShipments();
}); });
// Pagination Functions
function goToPreviousPage() {
if (currentPage > 1) {
currentPage--;
renderTable();
updatePaginationControls();
}
}
function goToNextPage() {
const totalPages = Math.ceil(filteredShipments.length / itemsPerPage);
if (currentPage < totalPages) {
currentPage++;
renderTable();
updatePaginationControls();
}
}
function updatePaginationControls() {
const totalPages = Math.ceil(filteredShipments.length / itemsPerPage);
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) * itemsPerPage + 1;
const endIndex = Math.min(currentPage * itemsPerPage, filteredShipments.length);
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${filteredShipments.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;
renderTable();
updatePaginationControls();
});
container.appendChild(button);
}
// Render Table Function
function renderTable() {
const tbody = document.getElementById('shipmentsTableBody');
if (filteredShipments.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="11" class="text-center py-5 text-muted">
<i class="bi bi-search display-4 d-block mb-3"></i>
No shipments found matching your criteria
</td>
</tr>
`;
return;
}
// Calculate pagination
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const paginatedItems = filteredShipments.slice(startIndex, endIndex);
// Sort by creation date (newest first)
const sortedItems = [...paginatedItems].sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
// Render table rows
tbody.innerHTML = '';
sortedItems.forEach((shipment, index) => {
const displayIndex = filteredShipments.length - (startIndex + index);
const row = document.createElement('tr');
row.className = 'shipment-row';
row.setAttribute('data-status', shipment.status);
row.setAttribute('data-shipment-id', shipment.shipment_id);
row.innerHTML = `
<td class="fw-bold">${displayIndex}</td>
<td>
<a href="#" class="text-primary fw-bold" onclick="openShipmentDetails(${shipment.id})">
${shipment.shipment_id}
</a>
</td>
<td>${shipment.origin}</td>
<td>${shipment.destination}</td>
<td><span class="badge bg-light text-dark">${shipment.total_qty}</span></td>
<td><span class="badge bg-light text-dark">${shipment.total_kg} kg</span></td>
<td><span class="badge bg-light text-dark">${shipment.total_cbm} CBM</span></td>
<td class="fw-bold text-success">${parseFloat(shipment.total_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
<td>
<span class="badge badge-${shipment.status}">
${shipment.status.charAt(0).toUpperCase() + shipment.status.slice(1).replace('_', ' ')}
</span>
</td>
<td>${new Date(shipment.shipment_date).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })}</td>
<td>
<div class="action-container">
<button type="button" class="btn-edit-status" onclick="toggleStatusDropdown(this, ${shipment.id})" title="Edit Status">
<i class="bi bi-pencil"></i>
</button>
<div class="status-dropdown" id="statusDropdown-${shipment.id}">
<form action="/admin/shipments/update-status" method="POST" class="status-form">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<input type="hidden" name="shipment_id" value="${shipment.id}">
<button type="submit" name="status" value="pending" class="status-option pending">
<span class="status-indicator pending"></span>
Pending
</button>
<button type="submit" name="status" value="in_transit" class="status-option in_transit">
<span class="status-indicator in_transit"></span>
In Transit
</button>
<button type="submit" name="status" value="dispatched" class="status-option dispatched">
<span class="status-indicator dispatched"></span>
Dispatched
</button>
<button type="submit" name="status" value="delivered" class="status-option delivered">
<span class="status-indicator delivered"></span>
Delivered
</button>
</form>
</div>
</div>
</td>
`;
tbody.appendChild(row);
});
}
function openShipmentDetails(id) { function openShipmentDetails(id) {
let modal = new bootstrap.Modal(document.getElementById('shipmentDetailsModal')); let modal = new bootstrap.Modal(document.getElementById('shipmentDetailsModal'));
let content = document.getElementById('shipmentDetailsContent'); let content = document.getElementById('shipmentDetailsContent');

View File

@@ -32,6 +32,8 @@ Route::prefix('admin')->group(function () {
Route::post('/logout', [AdminAuthController::class, 'logout']) Route::post('/logout', [AdminAuthController::class, 'logout'])
->name('admin.logout'); ->name('admin.logout');
}); });