feat(dashboard): enhance appointment table and link to specific dates

This commit is contained in:
hamed
2026-07-18 21:54:08 +03:30
parent 62a9fd87c3
commit 4ce1b39a83
5 changed files with 81 additions and 73 deletions
+32
View File
@@ -98,6 +98,38 @@ describe('DashboardPage (ported clinic dashboard)', () => {
// ستون «وضعیت» از داشبورد حذف شده — تغییر وضعیت فقط در صفحه نوبت‌هاست
expect(screen.queryByText('وضعیت')).not.toBeInTheDocument();
expect(screen.queryByText('ویزیت شده')).not.toBeInTheDocument();
// کارت «پزشکان کلینیک» از داشبورد حذف شده
expect(screen.queryByText('پزشکان کلینیک')).not.toBeInTheDocument();
});
it('«مشاهده» به همان روزِ نوبت در لیست نوبت‌ها لینک می‌دهد', async () => {
renderWithProviders(<DashboardPage />, { route: '/admin/dashboard' });
await screen.findByText('دنیا خلیلی');
const d = new Date(1_718_000_000 * 1000);
const day = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
expect(screen.getByText('مشاهده').closest('a')).toHaveAttribute('href', `/admin/appointments?date=${day}`);
});
it('سلول‌های جدول با هدرهایشان هم‌تراز هستند (همه text-start جز ستون عملیات)', async () => {
const { container } = renderWithProviders(<DashboardPage />, { route: '/admin/dashboard' });
await screen.findByText('دنیا خلیلی');
const heads = Array.from(container.querySelectorAll('thead th'));
const cells = Array.from(container.querySelectorAll('tbody td'));
expect(heads).toHaveLength(cells.length);
heads.slice(0, -1).forEach(th => expect(th.className).toContain('text-start'));
cells.slice(0, -1).forEach(td => expect(td.className).toContain('text-start'));
// ستون آخر «عملیات» در هر دو سطر وسط‌چین است
expect(heads[heads.length - 1].className).toContain('text-center');
expect(cells[cells.length - 1].className).toContain('text-center');
// dir=ltr روی سلول نمی‌نشیند (جهت را برمی‌گرداند و هم‌ترازی را می‌شکند)
cells.forEach(td => expect(td.getAttribute('dir')).toBeNull());
});
it('calls the real clinic dashboard endpoint', async () => {
-55
View File
@@ -11,7 +11,6 @@ import { api } from '../lib/api';
import type { ApiResponse } from '../lib/api';
import { formatNumber, formatRial, formatDateTime } from '../lib/utils';
import { useAuthStore } from '../stores/authStore';
import InviteDoctorModal from '../components/ui/InviteDoctorModal';
import { TauriDashboardView } from '../components/dashboard/TauriDashboardView';
// eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -638,8 +637,6 @@ interface ClinicDashboardData {
}
function ClinicDashboard() {
const { context, dbUuid } = useAuthStore();
const [inviteOpen, setInviteOpen] = useState(false);
const preset: DatePreset = 'this_month';
const range = getRange(preset);
const chartPeriod = useJalaliChartPeriod();
@@ -651,7 +648,6 @@ function ClinicDashboard() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const d = useMemo<ClinicDashboardData | undefined>(() => (q.data?.data as any)?.data ?? q.data?.data, [q.data]);
const clinicUuid = d?.clinic.uuid ?? dbUuid ?? '';
if (q.isLoading) return <LoadingSkeleton />;
@@ -677,57 +673,6 @@ function ClinicDashboard() {
currentJalaliYear={chartPeriod.currentJalaliYear}
/>
{/* پزشکان کلینیک — بخش عملکردی موجود clinicpro، حفظ‌شده زیر نمای منتقل‌شده */}
<div className="grid-2" style={{ marginTop: 'var(--gap)' }}>
<div className="card card-pad">
<div className="card-title-row">
<h3 style={{ fontSize: 16 }}>پزشکان کلینیک</h3>
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<button className="btn ghost sm" onClick={() => q.refetch()}>
<ArrowPathIcon style={{ width: 14, height: 14 }} />
بهروزرسانی
</button>
<button className="btn ghost sm" style={{ fontSize: 11, padding: '3px 10px' }} onClick={() => setInviteOpen(true)}>
+ دعوت جدید
</button>
{clinicUuid && (
<Link to={`/admin/clinics/${clinicUuid}`} className="link">مدیریت</Link>
)}
</div>
</div>
{!d?.doctors.length ? (
<div style={{ textAlign: 'center', padding: '24px 0' }}>
<p className="muted" style={{ fontSize: 13.5, marginBottom: 12 }}>هنوز پزشکی دعوت نشده</p>
<button className="btn primary sm" onClick={() => setInviteOpen(true)}>
دعوت اولین پزشک
</button>
</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column' }}>
{d.doctors.map((doc, i) => (
<div
key={doc.uuid}
style={{ display: 'flex', alignItems: 'center', gap: 11, padding: '10px 0', borderBottom: i < d.doctors.length - 1 ? '1px solid var(--border)' : 'none' }}
>
<AvatarEl initials={doc.name.slice(0, 1)} hue={162} size="sm" />
<div style={{ flex: 1 }}>
<b style={{ fontSize: 13.5 }}>دکتر {doc.name}</b>
</div>
<span className="badge blue"><span className="bdot" />{formatNumber(doc.today_count)} امروز</span>
</div>
))}
</div>
)}
</div>
</div>
{inviteOpen && clinicUuid && (
<InviteDoctorModal
clinicUuid={clinicUuid}
onClose={() => setInviteOpen(false)}
onInvited={() => q.refetch()}
/>
)}
</div>
);
}