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:
Alireza Hassani
2026-09-09 20:59:07 +03:30
co-authored by Cursor
parent b4c7b49bc6
commit ceafa08a05
3 changed files with 122 additions and 39 deletions
+3 -1
View File
@@ -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;
+91 -38
View File
@@ -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}
+28
View File
@@ -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;