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 { .fab {
position: fixed; position: fixed;
bottom: 32px; 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; width: 56px;
height: 56px; height: 56px;
border: 0; 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 { useNavigate } from 'react-router-dom'
import PageTitle from '../components/PageTitle' import PageTitle from '../components/PageTitle'
import FabButton from '../components/FabButton' import FabButton from '../components/FabButton'
@@ -19,6 +19,8 @@ export default function ActiveRepresentationsPage() {
const [items, setItems] = useState<Representation[]>([]) const [items, setItems] = useState<Representation[]>([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [error, setError] = useState('') const [error, setError] = useState('')
const [city, setCity] = useState('')
const [deletingId, setDeletingId] = useState<string | null>(null)
async function load() { async function load() {
setLoading(true) setLoading(true)
@@ -36,17 +38,37 @@ export default function ActiveRepresentationsPage() {
void load() 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) { async function onDelete(id: string) {
if (!window.confirm('این نمایندگی حذف شود؟')) return if (!window.confirm('این نمایندگی حذف شود؟')) return
setDeletingId(id)
try { try {
await deleteRepresentation(id) await deleteRepresentation(id)
setItems((prev) => prev.filter((item) => item.id !== id)) setItems((prev) => prev.filter((item) => item.id !== id))
} catch (err) { } catch (err) {
window.alert(err instanceof ApiError ? err.message : 'حذف ناموفق بود') 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 ( return (
<section className="panel list-page"> <section className="panel list-page">
@@ -63,46 +85,77 @@ export default function ActiveRepresentationsPage() {
{!loading && !error ? ( {!loading && !error ? (
<> <>
<div className="list customer-list"> {cities.length > 0 ? (
{items.length === 0 ? <p className="empty">نمایندگی فعالی نیست</p> : null} <div className="city-filter" role="tablist" aria-label="فیلتر شهر">
{pageItems.map((item, index) => ( <button
<article key={item.id} className="request-card request-card--active"> type="button"
<div className="en request-card__index">{offset + index + 1}</div> 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"> <div className="list customer-list">
<strong>{item.name}</strong> {filtered.length === 0 ? (
<div className="request-card__mobile">{item.owner || item.mobile}</div> <p className="empty">
</div> {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="request-card__person">
<div className="place-chip"> <strong>{item.name}</strong>
<strong>{item.city}</strong> <div className="request-card__mobile">{item.owner || item.mobile}</div>
<span>{item.address}</span>
</div> </div>
</div>
<div className="order-row__actions"> <div className="request-card__place">
<button <div className="place-chip">
type="button" <strong>{item.city}</strong>
className="icon-action icon-action--blue" <span>{item.address}</span>
aria-label="ویرایش" </div>
onClick={() => navigate(`/admin/representation/${item.id}/edit`)} </div>
>
<Icon name="edit" size={16} /> <div className="order-row__actions">
</button> <button
<button type="button"
type="button" className="icon-action icon-action--blue"
className="icon-action icon-action--red" aria-label="ویرایش"
aria-label="حذف" onClick={() => navigate(`/admin/representation/${item.id}/edit`)}
onClick={() => void onDelete(item.id)} >
> <Icon name="edit" size={16} />
<Icon name="trash" size={16} /> </button>
</button> <button
</div> type="button"
</article> className="icon-action icon-action--red"
))} aria-label="حذف"
</div> disabled={deletingId === item.id}
<Pagination page={page} pageCount={pageCount} onPageChange={setPage} /> onClick={() => void onDelete(item.id)}
>
<Icon name="trash" size={16} />
</button>
</div>
</article>
))}
</div>
<Pagination page={page} pageCount={pageCount} onPageChange={setPage} />
</> </>
) : null} ) : null}
+28
View File
@@ -186,6 +186,34 @@
background: #b2ebf2; 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 { .filters {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;