Fix active dealers delete and add city filter chips.
Move the FAB off the trash/edit buttons and let admins filter active representations by city like the public dealers page. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
b4c7b49bc6
commit
ceafa08a05
@@ -1,7 +1,9 @@
|
||||
.fab {
|
||||
position: fixed;
|
||||
bottom: 32px;
|
||||
left: max(24px, calc(5vw + 12px));
|
||||
/* Keep clear of row action buttons (trash/edit sit on the physical left in RTL). */
|
||||
right: max(24px, calc(5vw + 12px));
|
||||
left: auto;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border: 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import PageTitle from '../components/PageTitle'
|
||||
import FabButton from '../components/FabButton'
|
||||
@@ -19,6 +19,8 @@ export default function ActiveRepresentationsPage() {
|
||||
const [items, setItems] = useState<Representation[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState('')
|
||||
const [city, setCity] = useState('')
|
||||
const [deletingId, setDeletingId] = useState<string | null>(null)
|
||||
|
||||
async function load() {
|
||||
setLoading(true)
|
||||
@@ -36,17 +38,37 @@ export default function ActiveRepresentationsPage() {
|
||||
void load()
|
||||
}, [])
|
||||
|
||||
const cities = useMemo(
|
||||
() =>
|
||||
[...new Set(items.map((item) => item.city.trim()).filter(Boolean))].sort((a, b) =>
|
||||
a.localeCompare(b, 'fa'),
|
||||
),
|
||||
[items],
|
||||
)
|
||||
|
||||
const filtered = useMemo(
|
||||
() => (city ? items.filter((item) => item.city === city) : items),
|
||||
[items, city],
|
||||
)
|
||||
|
||||
async function onDelete(id: string) {
|
||||
if (!window.confirm('این نمایندگی حذف شود؟')) return
|
||||
setDeletingId(id)
|
||||
try {
|
||||
await deleteRepresentation(id)
|
||||
setItems((prev) => prev.filter((item) => item.id !== id))
|
||||
} catch (err) {
|
||||
window.alert(err instanceof ApiError ? err.message : 'حذف ناموفق بود')
|
||||
} finally {
|
||||
setDeletingId(null)
|
||||
}
|
||||
}
|
||||
|
||||
const { page, setPage, pageCount, pageItems, offset } = usePagination(items)
|
||||
const { page, setPage, pageCount, pageItems, offset } = usePagination(filtered)
|
||||
|
||||
useEffect(() => {
|
||||
setPage(1)
|
||||
}, [city, setPage])
|
||||
|
||||
return (
|
||||
<section className="panel list-page">
|
||||
@@ -63,46 +85,77 @@ export default function ActiveRepresentationsPage() {
|
||||
|
||||
{!loading && !error ? (
|
||||
<>
|
||||
<div className="list customer-list">
|
||||
{items.length === 0 ? <p className="empty">نمایندگی فعالی نیست</p> : null}
|
||||
{pageItems.map((item, index) => (
|
||||
<article key={item.id} className="request-card request-card--active">
|
||||
<div className="en request-card__index">{offset + index + 1}</div>
|
||||
{cities.length > 0 ? (
|
||||
<div className="city-filter" role="tablist" aria-label="فیلتر شهر">
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={!city}
|
||||
className={!city ? 'is-active' : ''}
|
||||
onClick={() => setCity('')}
|
||||
>
|
||||
همه
|
||||
</button>
|
||||
{cities.map((name) => (
|
||||
<button
|
||||
key={name}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={city === name}
|
||||
className={city === name ? 'is-active' : ''}
|
||||
onClick={() => setCity(name)}
|
||||
>
|
||||
{name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="request-card__person">
|
||||
<strong>{item.name}</strong>
|
||||
<div className="request-card__mobile">{item.owner || item.mobile}</div>
|
||||
</div>
|
||||
<div className="list customer-list">
|
||||
{filtered.length === 0 ? (
|
||||
<p className="empty">
|
||||
{city ? 'نمایندگی فعالی در این شهر نیست' : 'نمایندگی فعالی نیست'}
|
||||
</p>
|
||||
) : null}
|
||||
{pageItems.map((item, index) => (
|
||||
<article key={item.id} className="request-card request-card--active">
|
||||
<div className="en request-card__index">{offset + index + 1}</div>
|
||||
|
||||
<div className="request-card__place">
|
||||
<div className="place-chip">
|
||||
<strong>{item.city}</strong>
|
||||
<span>{item.address}</span>
|
||||
<div className="request-card__person">
|
||||
<strong>{item.name}</strong>
|
||||
<div className="request-card__mobile">{item.owner || item.mobile}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="order-row__actions">
|
||||
<button
|
||||
type="button"
|
||||
className="icon-action icon-action--blue"
|
||||
aria-label="ویرایش"
|
||||
onClick={() => navigate(`/admin/representation/${item.id}/edit`)}
|
||||
>
|
||||
<Icon name="edit" size={16} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="icon-action icon-action--red"
|
||||
aria-label="حذف"
|
||||
onClick={() => void onDelete(item.id)}
|
||||
>
|
||||
<Icon name="trash" size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
<Pagination page={page} pageCount={pageCount} onPageChange={setPage} />
|
||||
<div className="request-card__place">
|
||||
<div className="place-chip">
|
||||
<strong>{item.city}</strong>
|
||||
<span>{item.address}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="order-row__actions">
|
||||
<button
|
||||
type="button"
|
||||
className="icon-action icon-action--blue"
|
||||
aria-label="ویرایش"
|
||||
onClick={() => navigate(`/admin/representation/${item.id}/edit`)}
|
||||
>
|
||||
<Icon name="edit" size={16} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="icon-action icon-action--red"
|
||||
aria-label="حذف"
|
||||
disabled={deletingId === item.id}
|
||||
onClick={() => void onDelete(item.id)}
|
||||
>
|
||||
<Icon name="trash" size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
<Pagination page={page} pageCount={pageCount} onPageChange={setPage} />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -186,6 +186,34 @@
|
||||
background: #b2ebf2;
|
||||
}
|
||||
|
||||
.city-filter {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin: 4px 0 18px;
|
||||
}
|
||||
|
||||
.city-filter button {
|
||||
border: 1px solid #dde3ee;
|
||||
background: #fff;
|
||||
border-radius: 999px;
|
||||
padding: 8px 14px;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.city-filter button.is-active {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.city-filter button:hover:not(.is-active) {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
Reference in New Issue
Block a user