refactor: unify Letzshop pagination to use standard macro
Consolidate all tab-specific pagination into a unified pagination object and use the shared pagination macro for consistent look and feel across Orders, Products, Jobs, and Exceptions tabs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
{# app/templates/admin/partials/letzshop-exceptions-tab.html #}
|
||||
{# Exceptions tab for admin Letzshop management - Order Item Exception Resolution #}
|
||||
{% from 'shared/macros/pagination.html' import pagination %}
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
@@ -73,13 +74,13 @@
|
||||
<input
|
||||
type="text"
|
||||
x-model="exceptionsSearch"
|
||||
@input.debounce.300ms="exceptionsPage = 1; loadExceptions()"
|
||||
@input.debounce.300ms="pagination.page = 1; loadExceptions()"
|
||||
placeholder="Search by GTIN, product name, or order..."
|
||||
class="w-full pl-9 pr-8 py-2 text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md focus:border-purple-400 focus:outline-none"
|
||||
/>
|
||||
<button
|
||||
x-show="exceptionsSearch"
|
||||
@click="exceptionsSearch = ''; exceptionsPage = 1; loadExceptions()"
|
||||
@click="exceptionsSearch = ''; pagination.page = 1; loadExceptions()"
|
||||
class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<span x-html="$icon('x', 'w-4 h-4')"></span>
|
||||
@@ -89,7 +90,7 @@
|
||||
<!-- Status filter -->
|
||||
<select
|
||||
x-model="exceptionsFilter"
|
||||
@change="exceptionsPage = 1; loadExceptions()"
|
||||
@change="pagination.page = 1; loadExceptions()"
|
||||
class="px-3 py-2 text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md focus:border-purple-400 focus:outline-none"
|
||||
>
|
||||
<option value="">All Status</option>
|
||||
@@ -205,35 +206,5 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Pagination -->
|
||||
<div x-show="totalExceptions > exceptionsLimit" class="grid px-4 py-3 text-xs font-semibold tracking-wide text-gray-500 uppercase border-t dark:border-gray-700 bg-gray-50 sm:grid-cols-9 dark:text-gray-400 dark:bg-gray-800">
|
||||
<span class="flex items-center col-span-3">
|
||||
Showing <span x-text="((exceptionsPage - 1) * exceptionsLimit) + 1" class="mx-1"></span>-<span x-text="Math.min(exceptionsPage * exceptionsLimit, totalExceptions)" class="mx-1"></span> of <span x-text="totalExceptions" class="mx-1"></span>
|
||||
</span>
|
||||
<span class="col-span-2"></span>
|
||||
<span class="flex col-span-4 mt-2 sm:mt-auto sm:justify-end">
|
||||
<nav aria-label="Table navigation">
|
||||
<ul class="inline-flex items-center">
|
||||
<li>
|
||||
<button
|
||||
@click="exceptionsPage--; loadExceptions()"
|
||||
:disabled="exceptionsPage <= 1"
|
||||
class="px-3 py-1 rounded-md rounded-l-lg focus:outline-none focus:shadow-outline-purple disabled:opacity-50"
|
||||
>
|
||||
<span x-html="$icon('chevron-left', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
@click="exceptionsPage++; loadExceptions()"
|
||||
:disabled="exceptionsPage * exceptionsLimit >= totalExceptions"
|
||||
class="px-3 py-1 rounded-md rounded-r-lg focus:outline-none focus:shadow-outline-purple disabled:opacity-50"
|
||||
>
|
||||
<span x-html="$icon('chevron-right', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</span>
|
||||
</div>
|
||||
{{ pagination(show_condition="!loadingExceptions && pagination.total > 0") }}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{# app/templates/admin/partials/letzshop-jobs-table.html #}
|
||||
{# Unified jobs table for admin Letzshop management - Import, Export, and Sync jobs #}
|
||||
{% from 'shared/macros/pagination.html' import pagination %}
|
||||
|
||||
<div class="bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-6">
|
||||
@@ -161,61 +162,7 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Numbered Pagination -->
|
||||
<div x-show="jobsPagination.total > jobsPagination.per_page" class="flex flex-col sm:flex-row items-center justify-between gap-4 mt-4 pt-4 border-t dark:border-gray-700">
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Showing <span x-text="((jobsPagination.page - 1) * jobsPagination.per_page) + 1"></span>-<span x-text="Math.min(jobsPagination.page * jobsPagination.per_page, jobsPagination.total)"></span> of <span x-text="jobsPagination.total"></span> jobs
|
||||
</span>
|
||||
<div class="flex items-center gap-1">
|
||||
{# First page #}
|
||||
<button
|
||||
@click="jobsPagination.page = 1; loadJobs()"
|
||||
:disabled="jobsPagination.page <= 1"
|
||||
class="px-2 py-1 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="First page"
|
||||
>
|
||||
<span x-html="$icon('chevron-double-left', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
{# Previous page #}
|
||||
<button
|
||||
@click="jobsPagination.page--; loadJobs()"
|
||||
:disabled="jobsPagination.page <= 1"
|
||||
class="px-2 py-1 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="Previous page"
|
||||
>
|
||||
<span x-html="$icon('chevron-left', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
{# Page numbers #}
|
||||
<template x-for="p in getPageNumbers()" :key="p">
|
||||
<button
|
||||
@click="goToPage(p)"
|
||||
class="px-3 py-1 text-sm font-medium rounded-md border transition-colors"
|
||||
:class="p === jobsPagination.page
|
||||
? 'bg-purple-600 text-white border-purple-600 dark:bg-purple-500 dark:border-purple-500'
|
||||
: 'text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600'"
|
||||
x-text="p"
|
||||
></button>
|
||||
</template>
|
||||
{# Next page #}
|
||||
<button
|
||||
@click="jobsPagination.page++; loadJobs()"
|
||||
:disabled="jobsPagination.page >= jobsTotalPages"
|
||||
class="px-2 py-1 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="Next page"
|
||||
>
|
||||
<span x-html="$icon('chevron-right', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
{# Last page #}
|
||||
<button
|
||||
@click="jobsPagination.page = jobsTotalPages; loadJobs()"
|
||||
:disabled="jobsPagination.page >= jobsTotalPages"
|
||||
class="px-2 py-1 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="Last page"
|
||||
>
|
||||
<span x-html="$icon('chevron-double-right', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{ pagination(show_condition="!loadingJobs && pagination.total > 0") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{# app/templates/admin/partials/letzshop-orders-tab.html #}
|
||||
{# Orders tab for admin Letzshop management #}
|
||||
{% from 'shared/macros/pagination.html' import pagination %}
|
||||
|
||||
<!-- Header with Import Buttons -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
@@ -144,13 +145,13 @@
|
||||
<input
|
||||
type="text"
|
||||
x-model="ordersSearch"
|
||||
@input.debounce.300ms="ordersPage = 1; loadOrders()"
|
||||
@input.debounce.300ms="pagination.page = 1; loadOrders()"
|
||||
placeholder="Search by order #, name, or email..."
|
||||
class="w-full pl-9 pr-8 py-2 text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md focus:border-purple-400 focus:outline-none"
|
||||
/>
|
||||
<button
|
||||
x-show="ordersSearch"
|
||||
@click="ordersSearch = ''; ordersPage = 1; loadOrders()"
|
||||
@click="ordersSearch = ''; pagination.page = 1; loadOrders()"
|
||||
class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<span x-html="$icon('x', 'w-4 h-4')"></span>
|
||||
@@ -160,7 +161,7 @@
|
||||
<!-- Status filter -->
|
||||
<select
|
||||
x-model="ordersFilter"
|
||||
@change="ordersHasDeclinedItems = false; ordersPage = 1; loadOrders()"
|
||||
@change="ordersHasDeclinedItems = false; pagination.page = 1; loadOrders()"
|
||||
class="px-3 py-2 text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md focus:border-purple-400 focus:outline-none"
|
||||
>
|
||||
<option value="">All Status</option>
|
||||
@@ -172,7 +173,7 @@
|
||||
|
||||
<!-- Declined items filter -->
|
||||
<button
|
||||
@click="ordersFilter = ''; ordersHasDeclinedItems = !ordersHasDeclinedItems; ordersPage = 1; loadOrders()"
|
||||
@click="ordersFilter = ''; ordersHasDeclinedItems = !ordersHasDeclinedItems; pagination.page = 1; loadOrders()"
|
||||
:class="ordersHasDeclinedItems ? 'bg-red-100 dark:bg-red-900 border-red-300 dark:border-red-700 text-red-700 dark:text-red-300' : 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300'"
|
||||
class="px-3 py-2 text-sm border rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
:title="ordersHasDeclinedItems ? 'Showing orders with declined items' : 'Show only orders with declined items'"
|
||||
@@ -302,35 +303,5 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Pagination -->
|
||||
<div x-show="totalOrders > ordersLimit" class="grid px-4 py-3 text-xs font-semibold tracking-wide text-gray-500 uppercase border-t dark:border-gray-700 bg-gray-50 sm:grid-cols-9 dark:text-gray-400 dark:bg-gray-800">
|
||||
<span class="flex items-center col-span-3">
|
||||
Showing <span x-text="((ordersPage - 1) * ordersLimit) + 1" class="mx-1"></span>-<span x-text="Math.min(ordersPage * ordersLimit, totalOrders)" class="mx-1"></span> of <span x-text="totalOrders" class="mx-1"></span>
|
||||
</span>
|
||||
<span class="col-span-2"></span>
|
||||
<span class="flex col-span-4 mt-2 sm:mt-auto sm:justify-end">
|
||||
<nav aria-label="Table navigation">
|
||||
<ul class="inline-flex items-center">
|
||||
<li>
|
||||
<button
|
||||
@click="ordersPage--; loadOrders()"
|
||||
:disabled="ordersPage <= 1"
|
||||
class="px-3 py-1 rounded-md rounded-l-lg focus:outline-none focus:shadow-outline-purple disabled:opacity-50"
|
||||
>
|
||||
<span x-html="$icon('chevron-left', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
@click="ordersPage++; loadOrders()"
|
||||
:disabled="ordersPage * ordersLimit >= totalOrders"
|
||||
class="px-3 py-1 rounded-md rounded-r-lg focus:outline-none focus:shadow-outline-purple disabled:opacity-50"
|
||||
>
|
||||
<span x-html="$icon('chevron-right', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</span>
|
||||
</div>
|
||||
{{ pagination(show_condition="!loadingOrders && pagination.total > 0") }}
|
||||
</div>
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
<!-- Status Filter -->
|
||||
<select
|
||||
x-model="productFilters.is_active"
|
||||
@change="productsPage = 1; loadProducts()"
|
||||
@change="pagination.page = 1; loadProducts()"
|
||||
class="px-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none"
|
||||
>
|
||||
<option value="">All Status</option>
|
||||
@@ -242,36 +242,7 @@
|
||||
</tbody>
|
||||
{% endcall %}
|
||||
|
||||
<!-- Pagination -->
|
||||
<div x-show="totalProducts > productsLimit" class="mt-4">
|
||||
<div class="flex items-center justify-between px-4 py-3 bg-white border-t border-gray-200 dark:bg-gray-800 dark:border-gray-700 rounded-b-lg">
|
||||
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400">
|
||||
<span>Showing</span>
|
||||
<span class="font-medium mx-1" x-text="((productsPage - 1) * productsLimit) + 1"></span>
|
||||
<span>to</span>
|
||||
<span class="font-medium mx-1" x-text="Math.min(productsPage * productsLimit, totalProducts)"></span>
|
||||
<span>of</span>
|
||||
<span class="font-medium mx-1" x-text="totalProducts"></span>
|
||||
<span>products</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@click="productsPage--; loadProducts()"
|
||||
:disabled="productsPage <= 1"
|
||||
class="px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<button
|
||||
@click="productsPage++; loadProducts()"
|
||||
:disabled="productsPage * productsLimit >= totalProducts"
|
||||
class="px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ pagination(show_condition="!loadingProducts && pagination.total > 0") }}
|
||||
</div>
|
||||
|
||||
<!-- Import Modal -->
|
||||
|
||||
@@ -90,11 +90,16 @@ function adminMarketplaceLetzshop() {
|
||||
},
|
||||
savingCarrierSettings: false,
|
||||
|
||||
// Unified pagination (shared across tabs, updated based on activeTab)
|
||||
pagination: {
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
total: 0,
|
||||
pages: 0
|
||||
},
|
||||
|
||||
// Orders
|
||||
orders: [],
|
||||
totalOrders: 0,
|
||||
ordersPage: 1,
|
||||
ordersLimit: 20,
|
||||
ordersFilter: '',
|
||||
ordersSearch: '',
|
||||
ordersHasDeclinedItems: false,
|
||||
@@ -102,9 +107,6 @@ function adminMarketplaceLetzshop() {
|
||||
|
||||
// Exceptions
|
||||
exceptions: [],
|
||||
totalExceptions: 0,
|
||||
exceptionsPage: 1,
|
||||
exceptionsLimit: 20,
|
||||
exceptionsFilter: '',
|
||||
exceptionsSearch: '',
|
||||
exceptionStats: { pending: 0, resolved: 0, ignored: 0, total: 0, orders_with_exceptions: 0 },
|
||||
@@ -113,13 +115,9 @@ function adminMarketplaceLetzshop() {
|
||||
// Jobs
|
||||
jobs: [],
|
||||
jobsFilter: { type: '', status: '' },
|
||||
jobsPagination: { page: 1, per_page: 10, total: 0 },
|
||||
|
||||
// Products Tab
|
||||
products: [],
|
||||
totalProducts: 0,
|
||||
productsPage: 1,
|
||||
productsLimit: 20,
|
||||
loadingProducts: false,
|
||||
productFilters: { search: '', is_active: '' },
|
||||
productStats: { total: 0, active: 0, inactive: 0, last_sync: null },
|
||||
@@ -140,6 +138,93 @@ function adminMarketplaceLetzshop() {
|
||||
searchingProducts: false,
|
||||
submittingResolve: false,
|
||||
|
||||
// Computed: Total pages
|
||||
get totalPages() {
|
||||
return this.pagination.pages || Math.ceil(this.pagination.total / this.pagination.per_page) || 0;
|
||||
},
|
||||
|
||||
// Computed: Start index for pagination display
|
||||
get startIndex() {
|
||||
if (this.pagination.total === 0) return 0;
|
||||
return (this.pagination.page - 1) * this.pagination.per_page + 1;
|
||||
},
|
||||
|
||||
// Computed: End index for pagination display
|
||||
get endIndex() {
|
||||
const end = this.pagination.page * this.pagination.per_page;
|
||||
return end > this.pagination.total ? this.pagination.total : end;
|
||||
},
|
||||
|
||||
// Computed: Page numbers for pagination
|
||||
get pageNumbers() {
|
||||
const pages = [];
|
||||
const totalPages = this.totalPages;
|
||||
const current = this.pagination.page;
|
||||
|
||||
if (totalPages <= 7) {
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
} else {
|
||||
pages.push(1);
|
||||
if (current > 3) {
|
||||
pages.push('...');
|
||||
}
|
||||
const start = Math.max(2, current - 1);
|
||||
const end = Math.min(totalPages - 1, current + 1);
|
||||
for (let i = start; i <= end; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
if (current < totalPages - 2) {
|
||||
pages.push('...');
|
||||
}
|
||||
pages.push(totalPages);
|
||||
}
|
||||
return pages;
|
||||
},
|
||||
|
||||
// Pagination: Previous page
|
||||
previousPage() {
|
||||
if (this.pagination.page > 1) {
|
||||
this.pagination.page--;
|
||||
this._loadCurrentTabData();
|
||||
}
|
||||
},
|
||||
|
||||
// Pagination: Next page
|
||||
nextPage() {
|
||||
if (this.pagination.page < this.totalPages) {
|
||||
this.pagination.page++;
|
||||
this._loadCurrentTabData();
|
||||
}
|
||||
},
|
||||
|
||||
// Pagination: Go to specific page
|
||||
goToPage(pageNum) {
|
||||
if (pageNum !== '...' && pageNum >= 1 && pageNum <= this.totalPages) {
|
||||
this.pagination.page = pageNum;
|
||||
this._loadCurrentTabData();
|
||||
}
|
||||
},
|
||||
|
||||
// Helper: Load data for current active tab
|
||||
_loadCurrentTabData() {
|
||||
switch (this.activeTab) {
|
||||
case 'orders':
|
||||
this.loadOrders();
|
||||
break;
|
||||
case 'products':
|
||||
this.loadProducts();
|
||||
break;
|
||||
case 'jobs':
|
||||
this.loadJobs();
|
||||
break;
|
||||
case 'exceptions':
|
||||
this.loadExceptions();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
async init() {
|
||||
marketplaceLetzshopLog.info('init() called');
|
||||
|
||||
@@ -153,10 +238,7 @@ function adminMarketplaceLetzshop() {
|
||||
// Load platform settings for pagination
|
||||
if (window.PlatformSettings) {
|
||||
const rowsPerPage = await window.PlatformSettings.getRowsPerPage();
|
||||
this.ordersLimit = rowsPerPage;
|
||||
this.exceptionsLimit = rowsPerPage;
|
||||
this.productsLimit = rowsPerPage;
|
||||
this.jobsPagination.per_page = rowsPerPage;
|
||||
this.pagination.per_page = rowsPerPage;
|
||||
marketplaceLetzshopLog.info('Loaded rows per page setting:', rowsPerPage);
|
||||
}
|
||||
|
||||
@@ -168,11 +250,14 @@ function adminMarketplaceLetzshop() {
|
||||
// Watch for tab changes to reload relevant data
|
||||
this.$watch('activeTab', async (newTab) => {
|
||||
marketplaceLetzshopLog.info('Tab changed to:', newTab);
|
||||
// Reset pagination to page 1 when switching tabs
|
||||
this.pagination.page = 1;
|
||||
this.pagination.total = 0;
|
||||
this.pagination.pages = 0;
|
||||
|
||||
if (newTab === 'jobs') {
|
||||
// Load jobs for selected vendor or all Letzshop jobs
|
||||
await this.loadJobs();
|
||||
} else if (newTab === 'products') {
|
||||
// Load products for selected vendor or all Letzshop products
|
||||
await this.loadProducts();
|
||||
} else if (newTab === 'orders') {
|
||||
await this.loadOrders();
|
||||
@@ -451,8 +536,8 @@ function adminMarketplaceLetzshop() {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
marketplace: 'Letzshop',
|
||||
skip: ((this.productsPage - 1) * this.productsLimit).toString(),
|
||||
limit: this.productsLimit.toString()
|
||||
skip: ((this.pagination.page - 1) * this.pagination.per_page).toString(),
|
||||
limit: this.pagination.per_page.toString()
|
||||
});
|
||||
|
||||
// Filter by vendor if one is selected
|
||||
@@ -470,14 +555,15 @@ function adminMarketplaceLetzshop() {
|
||||
|
||||
const response = await apiClient.get(`/admin/products?${params}`);
|
||||
this.products = response.products || [];
|
||||
this.totalProducts = response.total || 0;
|
||||
this.pagination.total = response.total || 0;
|
||||
this.pagination.pages = Math.ceil(this.pagination.total / this.pagination.per_page);
|
||||
|
||||
// Load stats separately
|
||||
await this.loadProductStats();
|
||||
} catch (error) {
|
||||
marketplaceLetzshopLog.error('Failed to load products:', error);
|
||||
this.products = [];
|
||||
this.totalProducts = 0;
|
||||
this.pagination.total = 0;
|
||||
} finally {
|
||||
this.loadingProducts = false;
|
||||
}
|
||||
@@ -656,8 +742,8 @@ function adminMarketplaceLetzshop() {
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
skip: ((this.ordersPage - 1) * this.ordersLimit).toString(),
|
||||
limit: this.ordersLimit.toString()
|
||||
skip: ((this.pagination.page - 1) * this.pagination.per_page).toString(),
|
||||
limit: this.pagination.per_page.toString()
|
||||
});
|
||||
|
||||
if (this.ordersFilter) {
|
||||
@@ -680,7 +766,8 @@ function adminMarketplaceLetzshop() {
|
||||
|
||||
const response = await apiClient.get(`${url}?${params}`);
|
||||
this.orders = response.orders || [];
|
||||
this.totalOrders = response.total || 0;
|
||||
this.pagination.total = response.total || 0;
|
||||
this.pagination.pages = Math.ceil(this.pagination.total / this.pagination.per_page);
|
||||
|
||||
// Use server-side stats (counts all orders, not just visible page)
|
||||
if (response.stats) {
|
||||
@@ -1235,8 +1322,8 @@ function adminMarketplaceLetzshop() {
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
skip: ((this.exceptionsPage - 1) * this.exceptionsLimit).toString(),
|
||||
limit: this.exceptionsLimit.toString()
|
||||
skip: ((this.pagination.page - 1) * this.pagination.per_page).toString(),
|
||||
limit: this.pagination.per_page.toString()
|
||||
});
|
||||
|
||||
if (this.exceptionsFilter) {
|
||||
@@ -1254,7 +1341,8 @@ function adminMarketplaceLetzshop() {
|
||||
|
||||
const response = await apiClient.get(`/admin/order-exceptions?${params}`);
|
||||
this.exceptions = response.exceptions || [];
|
||||
this.totalExceptions = response.total || 0;
|
||||
this.pagination.total = response.total || 0;
|
||||
this.pagination.pages = Math.ceil(this.pagination.total / this.pagination.per_page);
|
||||
} catch (error) {
|
||||
marketplaceLetzshopLog.error('Failed to load exceptions:', error);
|
||||
this.error = error.message || 'Failed to load exceptions';
|
||||
@@ -1390,44 +1478,6 @@ function adminMarketplaceLetzshop() {
|
||||
// JOBS TABLE
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Get total pages for jobs pagination
|
||||
*/
|
||||
get jobsTotalPages() {
|
||||
return Math.ceil(this.jobsPagination.total / this.jobsPagination.per_page);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get array of page numbers to display for jobs pagination
|
||||
*/
|
||||
getPageNumbers() {
|
||||
const total = this.jobsTotalPages;
|
||||
const current = this.jobsPagination.page;
|
||||
const maxVisible = 5;
|
||||
|
||||
if (total <= maxVisible) {
|
||||
return Array.from({length: total}, (_, i) => i + 1);
|
||||
}
|
||||
|
||||
const half = Math.floor(maxVisible / 2);
|
||||
let start = Math.max(1, current - half);
|
||||
let end = Math.min(total, start + maxVisible - 1);
|
||||
|
||||
if (end - start < maxVisible - 1) {
|
||||
start = Math.max(1, end - maxVisible + 1);
|
||||
}
|
||||
|
||||
return Array.from({length: end - start + 1}, (_, i) => start + i);
|
||||
},
|
||||
|
||||
/**
|
||||
* Go to specific page for jobs
|
||||
*/
|
||||
goToPage(page) {
|
||||
this.jobsPagination.page = page;
|
||||
this.loadJobs();
|
||||
},
|
||||
|
||||
/**
|
||||
* Load jobs for selected vendor or all vendors
|
||||
*/
|
||||
@@ -1436,8 +1486,8 @@ function adminMarketplaceLetzshop() {
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
skip: ((this.jobsPagination.page - 1) * this.jobsPagination.per_page).toString(),
|
||||
limit: this.jobsPagination.per_page.toString()
|
||||
skip: ((this.pagination.page - 1) * this.pagination.per_page).toString(),
|
||||
limit: this.pagination.per_page.toString()
|
||||
});
|
||||
|
||||
if (this.jobsFilter.type) {
|
||||
@@ -1454,7 +1504,8 @@ function adminMarketplaceLetzshop() {
|
||||
|
||||
const response = await apiClient.get(endpoint);
|
||||
this.jobs = response.jobs || [];
|
||||
this.jobsPagination.total = response.total || 0;
|
||||
this.pagination.total = response.total || 0;
|
||||
this.pagination.pages = Math.ceil(this.pagination.total / this.pagination.per_page);
|
||||
} catch (error) {
|
||||
marketplaceLetzshopLog.error('Failed to load jobs:', error);
|
||||
// Don't show error for jobs - not critical
|
||||
|
||||
Reference in New Issue
Block a user