refactor(dashboard): drop the services list from the staff dashboard

The assigned-services card and its stat card are gone. The staff role has one
job — today's sessions — and with /admin/my-services already removed, a list of
services the operator cannot act on was the last thing on the page competing
with the work itself.

The remaining stat card no longer stretches the page: `.stat-grid` is built for
four cards and collapses to one full-width column with a single child, so the
staff grid is capped.

The endpoint still returns `services` and `stats.services`; the field is
documented and cheap, and nothing else needed to change to stop rendering it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-07 14:38:21 +03:30
co-authored by Claude Opus 5
parent 93c11139fd
commit e43e8ec93e
2 changed files with 22 additions and 51 deletions
+9 -3
View File
@@ -220,14 +220,20 @@ describe('داشبورد پرسنل', () => {
expect(screen.getByText('محمد رستمی')).toBeInTheDocument();
});
it('کارت جلسات لینک است و کارت سرویس‌ها نیست', async () => {
it('کارت جلسات لینک است', async () => {
renderWithProviders(<DashboardPage />);
const sessions = (await screen.findByText('جلسات امروز من')).closest('a');
expect(sessions).toHaveAttribute('href', '/admin/my-sessions');
});
// صفحهٔ «سرویس‌های من» حذف شد؛ کارتِ بدون مقصد نباید لینک باشد.
expect(screen.getByText('سرویس‌های من').closest('a')).toBeNull();
/** سرویس‌ها کارِ اپراتور نیستند؛ داشبورد فقط کارِ امروز را نشان می‌دهد. */
it('هیچ‌جای داشبورد سرویس‌ها را نشان نمی‌دهد', async () => {
renderWithProviders(<DashboardPage />);
await screen.findByText('محمد رسولی');
expect(screen.queryByText('سرویس‌های من')).not.toBeInTheDocument();
expect(screen.queryByText('سرویس‌های تخصیص‌یافته')).not.toBeInTheDocument();
});
it('هر ردیف کار به همان جلسه می‌رود', async () => {
+13 -48
View File
@@ -1015,13 +1015,6 @@ function StaffDashboard() {
hint: sessions.length > 0 ? `${formatNumber(settled)} انجام شده` : 'کاری برای امروز نیست',
icon: CalendarDaysIcon, color: 'var(--warning)', bg: 'var(--warning-bg)',
},
{
to: null,
label: 'سرویس‌های من',
value: formatNumber(d.stats?.services ?? 0),
hint: 'سرویس‌هایی که مجاز به انجامشان هستید',
icon: ClockIcon, color: 'var(--info)', bg: 'var(--info-bg)',
},
];
return (
@@ -1044,25 +1037,19 @@ function StaffDashboard() {
</button>
</div>
<div className="stat-grid">
{kpiCards.map(c => {
const body = (
<>
<div className="ico" style={{ background: c.bg, color: c.color }}>
<c.icon style={{ width: 21, height: 21 }} />
</div>
<div className="lbl">{c.label}</div>
<div className="val">{c.value}</div>
<div className="hint">{c.hint}</div>
</>
);
// فقط کارتی لینک می‌شود که صفحه‌ای پشتش باشد؛ سرویس‌ها صفحهٔ جدا ندارند و
// همین‌جا پایین‌تر فهرست می‌شوند.
return c.to === null
? <div key={c.label} className="stat">{body}</div>
: <Link key={c.label} to={c.to} className="stat" style={{ display: 'block', color: 'inherit', textDecoration: 'none' }}>{body}</Link>;
})}
{/* یک کارت تنها نباید تمام عرض صفحه را بگیرد؛ `.stat-grid` برای چهار کارت
طراحی شده و با یک فرزند به یک ستونِ تمام‌عرض تبدیل می‌شود. */}
<div className="stat-grid" style={{ maxWidth: 360 }}>
{kpiCards.map(c => (
<Link key={c.label} to={c.to} className="stat" style={{ display: 'block', color: 'inherit', textDecoration: 'none' }}>
<div className="ico" style={{ background: c.bg, color: c.color }}>
<c.icon style={{ width: 21, height: 21 }} />
</div>
<div className="lbl">{c.label}</div>
<div className="val">{c.value}</div>
<div className="hint">{c.hint}</div>
</Link>
))}
</div>
<div className="card card-pad" style={{ marginTop: 'var(--gap)' }}>
@@ -1107,28 +1094,6 @@ function StaffDashboard() {
)}
</div>
<div className="card card-pad" style={{ marginTop: 'var(--gap)' }}>
<div className="card-title-row">
<h3 style={{ fontSize: 16 }}>سرویسهای تخصیصیافته</h3>
</div>
{d.services.length === 0 ? (
<p className="muted" style={{ fontSize: 13.5, padding: '1.5rem 0', textAlign: 'center' }}>
هنوز سرویسی به شما تخصیص نیافته است.
</p>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 8 }}>
{d.services.slice(0, 5).map(s => (
<div key={s.uuid} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 12px', borderRadius: 'var(--r-sm)', background: 'var(--surface-2)' }}>
<div>
<div style={{ fontWeight: 600, fontSize: 14 }}>{s.name}</div>
<div className="muted" style={{ fontSize: 12, marginTop: 2 }}>{s.section_name}</div>
</div>
<div style={{ fontSize: 13 }}>{formatRial(s.price_rials)}</div>
</div>
))}
</div>
)}
</div>
{/* نوبتِ مستقیمِ اختصاص‌یافته فقط وقتی نشان داده می‌شود که وجود داشته باشد؛
یک جدول همیشه-خالی زیر کارِ واقعی، فقط صفحه را بلند می‌کرد. */}