refactor: simplify Topbar component and remove unused code

- Removed breadcrumb functionality and related code from Topbar.
- Consolidated mobile and desktop sidebar toggle logic.
- Adjusted icon sizes and styles for consistency.
- Cleaned up unused imports and props in Topbar.

refactor: enhance DoctorDetailPage session editor UI

- Improved layout and styling of session editor for better usability.
- Removed advanced settings toggle and integrated relevant fields directly.
- Added visual indicators for session slots and rest periods.

refactor: streamline DoctorsPage component

- Removed bulk selection functionality and related state management.
- Simplified rendering logic for doctor list and adjusted table structure.
- Cleaned up unused imports and components for better readability.

refactor: optimize UsersPage component

- Removed bulk selection and deletion functionality.
- Simplified user table structure and adjusted column spans.
- Cleaned up unused state and imports for improved clarity.
This commit is contained in:
hamed
2026-06-10 21:08:53 +03:30
parent 7fb805be27
commit 5f4a8d197b
5 changed files with 362 additions and 492 deletions
+5 -86
View File
@@ -5,8 +5,6 @@ import {
MagnifyingGlassIcon, PlusIcon, EyeIcon, TrashIcon, ArrowPathIcon,
CheckCircleIcon, XCircleIcon, TableCellsIcon, Squares2X2Icon,
} from '@heroicons/react/24/outline';
import { StarIcon as StarSolid } from '@heroicons/react/24/solid';
import { StarIcon } from '@heroicons/react/24/outline';
import { XMarkIcon } from '@heroicons/react/24/outline';
import { toast } from 'sonner';
import { api } from '../lib/api';
@@ -87,18 +85,6 @@ function DoctorAvatar({ name, id, image, size = 'sm' }: {
);
}
function StarRating({ rate }: { rate: number }) {
return (
<span className="stars" dir="ltr">
{Array.from({ length: 5 }).map((_, i) =>
i < Math.round(rate)
? <StarSolid key={i} style={{ width: 13, height: 13 }} />
: <StarIcon key={i} style={{ width: 13, height: 13, color: 'var(--border-2)' }} />
)}
</span>
);
}
// ── Main Page ─────────────────────────────────────────────────────────────
export default function DoctorsPage() {
@@ -112,17 +98,14 @@ export default function DoctorsPage() {
const [status, setStatus] = useState('');
const [specialtyId, setSpecialty] = useState('');
const [view, setView] = useState<'table' | 'grid'>('table');
const [selected, setSelected] = useState<string[]>([]);
const [deleteTarget, setDeleteTarget] = useState<AdminDoctor | null>(null);
const [confirmBulkDel, setConfirmBulkDel] = useState(false);
const [bulkDeleting, setBulkDeleting] = useState(false);
useEffect(() => {
const t = setTimeout(() => { setSearch(searchInput); setPage(1); }, 350);
return () => clearTimeout(t);
}, [searchInput]);
useEffect(() => { setPage(1); setSelected([]); }, [status, specialtyId]);
useEffect(() => { setPage(1); }, [status, specialtyId]);
// ── Queries ──
@@ -182,26 +165,6 @@ export default function DoctorsPage() {
onError: (e: Error) => toast.error(e.message),
});
// ── Bulk ──
const allSelected = items.length > 0 && items.every((d) => selected.includes(d.uuid));
const toggleAll = () => setSelected(allSelected ? [] : items.map((d) => d.uuid));
const toggleOne = (uuid: string) =>
setSelected((prev) => prev.includes(uuid) ? prev.filter((id) => id !== uuid) : [...prev, uuid]);
const handleBulkDelete = async () => {
setBulkDeleting(true);
let count = 0;
for (const uuid of selected) {
try { await api.delete(`/api/v1/doctor/${uuid}`); count++; } catch {}
}
setBulkDeleting(false);
setConfirmBulkDel(false);
setSelected([]);
toast.success(`${formatNumber(count)} پزشک حذف شد`);
qc.invalidateQueries({ queryKey: ['admin-doctors'] });
qc.invalidateQueries({ queryKey: ['doctors-stats'] });
};
const kpiCards = [
{ label: 'کل پزشکان', value: stats?.total, bg: 'var(--surface-3)', color: 'var(--text-2)' },
@@ -293,33 +256,12 @@ export default function DoctorsPage() {
</div>
</div>
{/* Bulk bar */}
{selected.length > 0 && (
<div style={{
padding: '10px 16px', background: 'var(--primary-soft)',
borderBottom: '1px solid var(--border)',
display: 'flex', alignItems: 'center', gap: 12,
}}>
<span style={{ fontSize: 13, color: 'var(--primary-700)', fontWeight: 600 }}>
{formatNumber(selected.length)} پزشک انتخاب شده
</span>
<div style={{ display: 'flex', gap: 8, marginRight: 'auto' }}>
<button className="btn danger sm" onClick={() => setConfirmBulkDel(true)}>حذف انتخابشدهها</button>
<button className="btn ghost sm" onClick={() => setSelected([])}>لغو</button>
</div>
</div>
)}
{/* Table view */}
{view === 'table' && (
<div className="table-wrap">
<table className="t">
<thead>
<tr>
<th style={{ width: 42 }}>
<input type="checkbox" checked={allSelected} onChange={toggleAll}
style={{ accentColor: 'var(--primary)', cursor: 'pointer' }} />
</th>
<th>پزشک</th>
<th>تخصص</th>
<th>درجه</th>
@@ -332,7 +274,7 @@ export default function DoctorsPage() {
<tbody>
{doctorsQ.isLoading && Array.from({ length: 6 }).map((_, i) => (
<tr key={i}>
{Array.from({ length: 8 }).map((_, j) => (
{Array.from({ length: 7 }).map((_, j) => (
<td key={j}>
<div className="skeleton" style={{ height: 14, borderRadius: 6, width: j === 1 ? '70%' : '55%' }} />
</td>
@@ -342,7 +284,7 @@ export default function DoctorsPage() {
{!doctorsQ.isLoading && items.length === 0 && (
<tr>
<td colSpan={8}>
<td colSpan={7}>
<div className="empty">هیچ پزشکی یافت نشد</div>
</td>
</tr>
@@ -350,11 +292,6 @@ export default function DoctorsPage() {
{!doctorsQ.isLoading && items.map((doc) => (
<tr key={doc.uuid} style={{ cursor: 'pointer' }} onClick={() => navigate(`/admin/doctors/${doc.uuid}`)}>
<td onClick={(e) => e.stopPropagation()}>
<input type="checkbox" checked={selected.includes(doc.uuid)}
onChange={() => toggleOne(doc.uuid)}
style={{ accentColor: 'var(--primary)', cursor: 'pointer' }} />
</td>
<td>
<div className="cell-user">
<DoctorAvatar name={doc.name} id={doc.id} image={doc.profile_image} />
@@ -380,12 +317,7 @@ export default function DoctorsPage() {
</span>
)}
</td>
<td>
<div className="flex" style={{ gap: 5 }}>
<StarRating rate={doc.rate} />
<b style={{ fontSize: 12.5 }}>{doc.rate.toFixed(1)}</b>
</div>
</td>
<td><b style={{ fontSize: 12.5 }}>{doc.rate.toFixed(1)}</b></td>
<td style={{ fontFamily: 'monospace', direction: 'ltr', textAlign: 'right' }} className="muted">
{doc.mobile ?? '—'}
</td>
@@ -461,10 +393,7 @@ export default function DoctorsPage() {
</span>
</div>
<div className="flex" style={{ justifyContent: 'space-between', fontSize: 12.5 }}>
<span className="flex" style={{ gap: 5 }}>
<StarRating rate={doc.rate} />
<b>{doc.rate.toFixed(1)}</b>
</span>
<b>{doc.rate.toFixed(1)}</b>
<span className="muted">{formatDate(doc.created_at)}</span>
</div>
</div>
@@ -491,16 +420,6 @@ export default function DoctorsPage() {
onCancel={() => setDeleteTarget(null)}
/>
<ConfirmDialog
open={confirmBulkDel}
title="حذف گروهی"
message={`آیا از حذف ${formatNumber(selected.length)} پزشک انتخاب‌شده اطمینان دارید؟`}
confirmLabel={`حذف ${formatNumber(selected.length)} پزشک`}
danger
loading={bulkDeleting}
onConfirm={handleBulkDelete}
onCancel={() => setConfirmBulkDel(false)}
/>
</div>
);
}