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 {
|
.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;
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user