feat(ui-kit): tauri pastel stat cards on doctor/clinic dashboards

Exact tauri stat-card palette tokens; doctor and clinic dashboards now
render 4 pastel StatCards (amber/violet/green/pink) matching the
clinic-pro-tauri reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-06-23 17:41:18 +03:30
co-authored by Claude Opus 4.8
parent 0bd18d74b7
commit 0cef1518d7
2 changed files with 29 additions and 41 deletions
+24 -36
View File
@@ -12,6 +12,7 @@ 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 StatCard from '../components/ui/StatCard';
// ── Shared Status Maps ────────────────────────────────────────────────────
@@ -593,14 +594,15 @@ function ClinicDashboard() {
if (q.isLoading) return <LoadingSkeleton />;
const kpiCards = [
{ label: 'پزشکان', value: formatNumber(d?.stats.total_doctors ?? 0), icon: HeartIcon, color: 'var(--success)', bg: 'var(--success-bg)' },
{ label: 'نوبت‌های امروز', value: formatNumber(d?.stats.today_appointments ?? 0), icon: CalendarDaysIcon, color: 'var(--warning)', bg: 'var(--warning-bg)' },
{ label: 'نوبت‌های این ماه', value: formatNumber(d?.stats.this_month_appointments ?? 0), icon: ClockIcon, color: 'var(--info)', bg: 'var(--info-bg)' },
{ label: 'دعوتنامه در انتظار', value: formatNumber(d?.stats.pending_invitations ?? 0), icon: BellAlertIcon, color: 'var(--danger)', bg: 'var(--danger-bg)' },
...(d?.stats.sms_wallet_balance != null ? [{ label: 'موجودی پیامک', value: formatRial(d.stats.sms_wallet_balance), icon: CreditCardIcon, color: 'var(--primary)', bg: 'var(--primary-soft)' }] : []),
...(d?.stats.unique_patients_count != null ? [{ label: 'بیماران یکتا', value: formatNumber(d.stats.unique_patients_count), icon: UserGroupIcon, color: 'var(--violet)', bg: 'var(--violet-bg)' }] : []),
...(d?.stats.revenue_period_rials != null ? [{ label: 'درآمد دوره', value: formatRial(d.stats.revenue_period_rials), icon: CreditCardIcon, color: 'var(--success)', bg: 'var(--success-bg)' }] : []),
const statCards: { tone: 'amber' | 'violet' | 'green' | 'pink'; label: string; value: React.ReactNode; icon: React.ReactNode }[] = [
{ tone: 'amber', label: 'تعداد نوبت‌های امروز', value: `${formatNumber(d?.stats.today_appointments ?? 0)}+`, icon: <CalendarDaysIcon style={{ width: 22, height: 22 }} /> },
{ tone: 'violet', label: 'نوبت‌های این ماه', value: formatNumber(d?.stats.this_month_appointments ?? 0), icon: <ClockIcon style={{ width: 22, height: 22 }} /> },
{ tone: 'green', label: d?.stats.revenue_period_rials != null ? 'درآمد دوره' : 'پزشکان',
value: d?.stats.revenue_period_rials != null ? formatRial(d.stats.revenue_period_rials) : formatNumber(d?.stats.total_doctors ?? 0),
icon: d?.stats.revenue_period_rials != null ? <CreditCardIcon style={{ width: 22, height: 22 }} /> : <HeartIcon style={{ width: 22, height: 22 }} /> },
{ tone: 'pink', label: d?.stats.unique_patients_count != null ? 'بیماران یکتا' : 'دعوتنامه در انتظار',
value: d?.stats.unique_patients_count != null ? formatNumber(d.stats.unique_patients_count) : formatNumber(d?.stats.pending_invitations ?? 0),
icon: <UserGroupIcon style={{ width: 22, height: 22 }} /> },
];
return (
@@ -627,15 +629,9 @@ function ClinicDashboard() {
</div>
</div>
<div className="stat-grid">
{kpiCards.map(c => (
<div key={c.label} className="stat">
<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>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 16 }}>
{statCards.map(c => (
<StatCard key={c.label} tone={c.tone} label={c.label} value={c.value} icon={c.icon} />
))}
</div>
@@ -823,13 +819,13 @@ function DoctorDashboard() {
if (q.isLoading) return <LoadingSkeleton />;
const kpiCards = [
{ label: 'نوبت‌های امروز', value: formatNumber(d?.stats.today_appointments ?? 0), icon: CalendarDaysIcon, color: 'var(--warning)', bg: 'var(--warning-bg)' },
{ label: 'نوبت‌های فردا', value: formatNumber(d?.stats.tomorrow_appointments ?? 0), icon: ClockIcon, color: 'var(--info)', bg: 'var(--info-bg)' },
{ label: 'نوبت‌های این ماه', value: formatNumber(d?.stats.this_month_appointments ?? 0), icon: CalendarDaysIcon, color: 'var(--success)', bg: 'var(--success-bg)' },
{ label: 'میانگین امتیاز', value: d?.stats.avg_rating != null ? String(d.stats.avg_rating) : '—', icon: StarIcon, color: 'var(--primary)', bg: 'var(--primary-soft)' },
...(d?.stats.unique_patients_count != null ? [{ label: 'بیماران یکتا', value: formatNumber(d.stats.unique_patients_count), icon: UserGroupIcon, color: 'var(--violet)', bg: 'var(--violet-bg)' }] : []),
...(d?.stats.revenue_period_rials != null ? [{ label: 'درآمد دوره', value: formatRial(d.stats.revenue_period_rials), icon: CreditCardIcon, color: 'var(--success)', bg: 'var(--success-bg)' }] : []),
const statCards: { tone: 'amber' | 'violet' | 'green' | 'pink'; label: string; value: React.ReactNode; icon: React.ReactNode }[] = [
{ tone: 'amber', label: 'تعداد نوبت‌های امروز', value: `${formatNumber(d?.stats.today_appointments ?? 0)}+`, icon: <CalendarDaysIcon style={{ width: 22, height: 22 }} /> },
{ tone: 'violet', label: 'نوبت‌های فردا', value: `${formatNumber(d?.stats.tomorrow_appointments ?? 0)}+`, icon: <ClockIcon style={{ width: 22, height: 22 }} /> },
{ tone: 'green', label: 'نوبت‌های این ماه', value: formatNumber(d?.stats.this_month_appointments ?? 0), icon: <UserGroupIcon style={{ width: 22, height: 22 }} /> },
{ tone: 'pink', label: d?.stats.revenue_period_rials != null ? 'درآمد دوره' : 'میانگین امتیاز',
value: d?.stats.revenue_period_rials != null ? formatRial(d.stats.revenue_period_rials) : (d?.stats.avg_rating != null ? String(d.stats.avg_rating) : '—'),
icon: d?.stats.revenue_period_rials != null ? <CreditCardIcon style={{ width: 22, height: 22 }} /> : <StarIcon style={{ width: 22, height: 22 }} /> },
];
return (
@@ -852,21 +848,13 @@ function DoctorDashboard() {
</div>
</div>
<div className="stat-grid">
{kpiCards.map(c => (
<div key={c.label} className="stat">
<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>
{c.label === 'میانگین امتیاز' && d?.stats.total_ratings ? (
<div className="hint">{formatNumber(d.stats.total_ratings)} نظر</div>
) : null}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 16 }}>
{statCards.map(c => (
<StatCard key={c.label} tone={c.tone} label={c.label} value={c.value} icon={c.icon} />
))}
</div>
<div style={{ height: 'var(--gap)' }} />
<DoctorClinicInvitationsCard />
<div className="grid-2" style={{ marginTop: 'var(--gap)' }}>
+5 -5
View File
@@ -47,11 +47,11 @@
--accent: #f0682a;
--accent-600: #db5a1f;
--accent-bg: #fdeee4;
/* رنگ‌های پاستلی stat-cards داشبورد (هم‌خوان با tauri) */
--stat-amber-bg: #fdf0e3; --stat-amber-fg: #e08a1e;
--stat-violet-bg: #efeefb; --stat-violet-fg: #6d5cf0;
--stat-green-bg: #e7f6ef; --stat-green-fg: #16a35a;
--stat-pink-bg: #fdeee9; --stat-pink-fg: #f0682a;
/* رنگ‌های پاستلی stat-cards داشبورد (دقیقاً مطابق clinic-pro-tauri) */
--stat-amber-bg: rgba(255,192,81,0.11); --stat-amber-fg: #FFC051;
--stat-violet-bg: rgba(85,89,206,0.08); --stat-violet-fg: #5559CE;
--stat-green-bg: rgba(0,157,121,0.10); --stat-green-fg: #009D79;
--stat-pink-bg: rgba(241,119,50,0.10); --stat-pink-fg: #F17732;
--ring: color-mix(in oklch, var(--primary) 32%, transparent);
--shadow-sm: 0 1px 2px rgba(15,27,46,.06), 0 1px 3px rgba(15,27,46,.05);
--shadow: 0 2px 6px rgba(15,27,46,.06), 0 8px 24px rgba(15,27,46,.06);