mirror of
https://git.meshkee.com/Meshkee/dashboards.git
synced 2026-08-11 22:30:58 +04:30
Add store items pagination and fix modal backdrop blur across dashboards.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
4d480799b0
commit
e6670ed06f
@@ -24,6 +24,39 @@
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
padding: 0 4px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
min-height: 38px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.paginationMeta {
|
||||
grid-column: 1;
|
||||
justify-self: start;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 38px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 38px;
|
||||
font-family: var(--font-ui);
|
||||
transform: translateY(2px);
|
||||
}
|
||||
|
||||
.pagination > nav {
|
||||
grid-column: 2;
|
||||
justify-self: center;
|
||||
height: 38px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.fabDock {
|
||||
position: fixed;
|
||||
inset-inline-end: 32px;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { Plus, RotateCcw, Search } from 'lucide-react'
|
||||
import { Breadcrumbs } from '../components/Breadcrumbs'
|
||||
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
|
||||
import { CreateStoreItemsModal } from '../components/CreateStoreItemsModal'
|
||||
import { EditStoreItemsModal } from '../components/EditStoreItemsModal'
|
||||
import { Pagination } from '../components/Pagination'
|
||||
import { PickStoreVariantModal } from '../components/PickStoreVariantModal'
|
||||
import { ShoppingCartModal } from '../components/ShoppingCartModal'
|
||||
import { StoreItemCard } from '../components/StoreItemCard'
|
||||
@@ -15,7 +17,8 @@ import { useToast } from '../context/ToastContext'
|
||||
import { ApiError } from '../lib/api'
|
||||
import {
|
||||
deleteStoreItemsByProduct,
|
||||
listStoreItems,
|
||||
listAllStoreItems,
|
||||
STORE_ITEMS_PER_PAGE,
|
||||
type StoreItem,
|
||||
} from '../services/storeItemService'
|
||||
import type { ShoppingCard } from '../services/shoppingCardService'
|
||||
@@ -37,6 +40,11 @@ import filterStyles from '../components/ListFiltersPanel.module.css'
|
||||
import pageStyles from '../components/PageContent.module.css'
|
||||
import styles from './StoreItemsPage.module.css'
|
||||
|
||||
function readPage(searchParams: URLSearchParams) {
|
||||
const pageRaw = Number(searchParams.get('page'))
|
||||
return Number.isFinite(pageRaw) && pageRaw >= 1 ? Math.floor(pageRaw) : 1
|
||||
}
|
||||
|
||||
export function StoreItemsPage() {
|
||||
return <StoreItemsPageContent />
|
||||
}
|
||||
@@ -44,6 +52,8 @@ export function StoreItemsPage() {
|
||||
function StoreItemsPageContent() {
|
||||
const t = useT()
|
||||
const { locale } = useLocale()
|
||||
const [searchParams, setSearchParams] = useSearchParams()
|
||||
const currentPage = readPage(searchParams)
|
||||
const { itemCount, hasItems, addVariant, loadShoppingCard } = useDraftCart()
|
||||
const { showToast } = useToast()
|
||||
const [items, setItems] = useState<StoreItem[]>([])
|
||||
@@ -97,6 +107,20 @@ function StoreItemsPageContent() {
|
||||
[listings, appliedFilters],
|
||||
)
|
||||
|
||||
const totalListings = filteredListings.length
|
||||
const totalPages = Math.max(1, Math.ceil(totalListings / STORE_ITEMS_PER_PAGE))
|
||||
const safePage = Math.min(currentPage, totalPages)
|
||||
const pagedListings = useMemo(() => {
|
||||
const start = (safePage - 1) * STORE_ITEMS_PER_PAGE
|
||||
return filteredListings.slice(start, start + STORE_ITEMS_PER_PAGE)
|
||||
}, [filteredListings, safePage])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && currentPage > totalPages) {
|
||||
writePage(totalPages)
|
||||
}
|
||||
}, [isLoading, currentPage, totalPages])
|
||||
|
||||
const productItems = useMemo(() => {
|
||||
const target = editTarget ?? discountTarget ?? festivalTarget
|
||||
if (!target) return []
|
||||
@@ -108,6 +132,13 @@ function StoreItemsPageContent() {
|
||||
[listings],
|
||||
)
|
||||
|
||||
function writePage(page: number) {
|
||||
const next = new URLSearchParams(searchParams)
|
||||
if (page > 1) next.set('page', String(page))
|
||||
else next.delete('page')
|
||||
setSearchParams(next, { replace: true })
|
||||
}
|
||||
|
||||
function applyFilters() {
|
||||
setAppliedFilters({
|
||||
name: draftName.trim(),
|
||||
@@ -115,6 +146,7 @@ function StoreItemsPageContent() {
|
||||
maxPrice: parseIrtInput(draftMaxPrice),
|
||||
onlyDiscounted: draftOnlyDiscounted,
|
||||
})
|
||||
writePage(1)
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
@@ -123,6 +155,12 @@ function StoreItemsPageContent() {
|
||||
setDraftMaxPrice('')
|
||||
setDraftOnlyDiscounted(false)
|
||||
setAppliedFilters(EMPTY_STORE_LISTING_FILTERS)
|
||||
writePage(1)
|
||||
}
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
writePage(page)
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -136,8 +174,8 @@ function StoreItemsPageContent() {
|
||||
setError('')
|
||||
|
||||
try {
|
||||
const data = await listStoreItems(1, 50, signal)
|
||||
setItems(data.items)
|
||||
const data = await listAllStoreItems(signal)
|
||||
setItems(data)
|
||||
} catch (err) {
|
||||
if (err instanceof DOMException && err.name === 'AbortError') return
|
||||
if (err instanceof ApiError) {
|
||||
@@ -215,6 +253,12 @@ function StoreItemsPageContent() {
|
||||
await deleteStoreItemsByProduct(deleteTarget.productId)
|
||||
setItems((prev) => prev.filter((item) => item.productId !== deleteTarget.productId))
|
||||
setDeleteTarget(null)
|
||||
|
||||
const nextTotal = Math.max(0, totalListings - 1)
|
||||
const nextTotalPages = Math.max(1, Math.ceil(nextTotal / STORE_ITEMS_PER_PAGE))
|
||||
if (safePage > nextTotalPages) {
|
||||
writePage(nextTotalPages)
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError) {
|
||||
setError(err.message)
|
||||
@@ -329,20 +373,40 @@ function StoreItemsPageContent() {
|
||||
) : filteredListings.length === 0 ? (
|
||||
<p className={styles.empty}>{t('storeItems.emptyFiltered')}</p>
|
||||
) : (
|
||||
<div className={styles.grid}>
|
||||
{filteredListings.map((listing) => (
|
||||
<StoreItemCard
|
||||
key={listing.productId}
|
||||
listing={listing}
|
||||
onOpen={openEdit}
|
||||
onEdit={openEdit}
|
||||
onDiscount={setDiscountTarget}
|
||||
onFestival={setFestivalTarget}
|
||||
onRemove={setDeleteTarget}
|
||||
onAddToCart={handleAddToCart}
|
||||
<>
|
||||
<div className={styles.grid}>
|
||||
{pagedListings.map((listing) => (
|
||||
<StoreItemCard
|
||||
key={listing.productId}
|
||||
listing={listing}
|
||||
onOpen={openEdit}
|
||||
onEdit={openEdit}
|
||||
onDiscount={setDiscountTarget}
|
||||
onFestival={setFestivalTarget}
|
||||
onRemove={setDeleteTarget}
|
||||
onAddToCart={handleAddToCart}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className={styles.pagination}>
|
||||
<div className={styles.paginationMeta}>
|
||||
{t('storeItems.pagination', {
|
||||
page: safePage,
|
||||
totalPages,
|
||||
perPage: STORE_ITEMS_PER_PAGE,
|
||||
total: totalListings,
|
||||
})}
|
||||
</div>
|
||||
<Pagination
|
||||
currentPage={safePage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={handlePageChange}
|
||||
disabled={isLoading}
|
||||
variant="inline"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className={styles.fabDock}>
|
||||
|
||||
Reference in New Issue
Block a user