بازگشت به وبلاگ
مهندسی رابط کاربری

داشبورد مدرن با Tailwind و TypeScript؛ از جدول خالی تا ابزار تصمیم‌گیری

بیشتر داشبوردها زیبا هستند و بی‌فایده. آنچه از ساختن پنل مدیریت یک پلتفرم فروش یاد گرفتم — درباره‌ی اولویت اعداد، حالت‌های خالی، و کارت‌هایی که باید کلیک‌پذیر باشند.

Mehran HatamiMehran Hatami6 دقیقه مطالعهTailwind CSSTypeScriptDashboards

داشبورد اولِ هر پروژه‌ای شبیه هم است: چهار کارت آمار بالای صفحه، یک نمودار، یک جدول. مشکل جایی شروع می‌شود که کاربر عدد «درآمد کل» را می‌بیند، حس می‌کند اشتباه است، رویش کلیک می‌کند و هیچ اتفاقی نمی‌افتد. آن لحظه، داشبورد از ابزار به پوستر تبدیل می‌شود.

اول تصمیم را بنویس، بعد کامپوننت را

نکته کلیدی

هر عددی که روی داشبورد نشان می‌دهی باید به جایی وصل باشد که کاربر بتواند کاری درباره‌اش بکند. عدد بدون مقصد، فقط دکور است.

قبل از هر کدی، این جمله را می‌نویسم: «کاربر این صفحه را باز می‌کند تا تصمیم بگیرد که ...». اگر نتوانم جمله را تمام کنم، صفحه هنوز آماده‌ی ساخته شدن نیست. برای پنل فروشگاه، جواب این بود: «تا بفهمد امروز چه چیزی نیاز به دخالتش دارد». همین یک جمله ترتیب کل صفحه را عوض کرد — سفارش‌های معطل رفتند بالا و نمودار درآمد سالانه رفت پایین.

کارت آمار باید مقصد داشته باشد

این ساده‌ترین بهبودی است که می‌شود به هر داشبوردی داد و بیشترین اثر را دارد: هر کارت آمار، یک لینک باشد به همان چیزی که شمرده است. کارت «۱۲ سفارش در انتظار» باید به فهرست همان ۱۲ سفارش برود، با فیلتر از پیش اعمال‌شده. کاربر بلافاصله می‌فهمد عدد از کجا آمده و اگر مشکوک بود، می‌تواند بررسی کند.

export function StatCard({ label, value, href }: {
  label: string; value: string; href?: string;
}) {
  const inner = (
    <>
      <p className="text-sm text-foreground/60">{label}</p>
      <p className="mt-1 text-2xl font-bold tabular-nums">{value}</p>
    </>
  );

  if (!href) return <div className="rounded-card p-5">{inner}</div>;

  return (
    <Link
      href={href}
      className="rounded-card p-5 transition hover:bg-foreground/5
                 focus-visible:outline focus-visible:outline-2"
    >
      {inner}
    </Link>
  );
}

نکته‌ی ریز اما مهم: tabular-nums. بدون آن، عددها هنگام به‌روزرسانی می‌لرزند چون عرض ارقام یکسان نیست. با آن، ستون عددها ثابت می‌ماند و صفحه آرام‌تر به نظر می‌رسد.

حالت خالی، جدی‌ترین حالت صفحه است

هر کاربر تازه، اول همین حالت را می‌بیند. یک جدول خالی با نوشته‌ی «داده‌ای موجود نیست» به کاربر می‌گوید محصول کار نمی‌کند. همان جدول با یک جمله‌ی توضیح و یک دکمه‌ی «اولین محصول را اضافه کنید»، به او می‌گوید قدم بعدی چیست. حالت خالی را باید مثل یک صفحه‌ی راهنما طراحی کرد، نه مثل یک خطا.

همین منطق برای حالت بارگذاری هم صدق می‌کند. اسکلتی که شکل محتوای نهایی را دارد، بهتر از یک اسپینر وسط صفحه است؛ چون چشم کاربر از قبل جای اطلاعات را پیدا می‌کند و پرش چیدمان هم رخ نمی‌دهد.

TypeScript را جایی بگذار که خطا گران است

در داشبورد، گران‌ترین خطاها مربوط به واحد و نوع داده‌اند: تومان با ریال جابه‌جا شود، درصد به‌جای نسبت بیاید، یا تاریخ میلادی جای شمسی بنشیند. تایپ‌های برند‌شده این‌ها را در زمان کامپایل می‌گیرند و هزینه‌شان چند خط کد است.

type Toman = number & { readonly __brand: "Toman" };
type Ratio = number & { readonly __brand: "Ratio" }; // 0..1

const toman = (n: number) => n as Toman;
const ratio = (n: number) => n as Ratio;

function formatPercent(r: Ratio) {
  return `${(r * 100).toFixed(1)}%`;
}

formatPercent(ratio(0.42)); // ✅ "42.0%"
// formatPercent(toman(42)); // ❌ خطای کامپایل

با Tailwind، توکن بساز نه کلاس تکراری

وسوسه‌ی Tailwind این است که همه‌جا rounded-xl border border-white/10 bg-white/5 backdrop-blur بنویسی. بعد از بیستمین بار، تغییر ظاهر کارت‌ها یعنی جست‌وجو در کل پروژه. راه‌حل ساده است: رنگ‌ها و شعاع‌ها را به‌صورت متغیر CSS تعریف کن و در تم Tailwind ثبتشان کن. آن‌وقت تغییر یک متغیر، کل داشبورد را عوض می‌کند.

:root {
  --surface: 0 0% 100% / 0.04;
  --hairline: 0 0% 100% / 0.10;
  --r-md: 0.875rem;
}

.card {
  background: hsl(var(--surface));
  border: 1px solid hsl(var(--hairline));
  border-radius: var(--r-md);
}

چند چیز که کاربران همیشه می‌خواهند و ما فراموش می‌کنیم

  • امکان حذف داده‌ی آزمایشی. کاربر با چند سفارش تستی شروع می‌کند؛ اگر نتواند پاکشان کند، آمار برای همیشه اشتباه می‌ماند.
  • حالت فعال هر آیتم منو. بدون آن، کاربر در پنل‌های چندسطحی گم می‌شود.
  • تأیید برای عملیات برگشت‌ناپذیر — اما فقط برای همان‌ها. تأیید گرفتن برای هر کاری، کاربر را به کلیک خودکار عادت می‌دهد.
  • نمایش زمان آخرین به‌روزرسانی داده. بدون آن، کاربر نمی‌داند عددی که می‌بیند مال الان است یا کش دیروز.

معیار نهایی

داشبورد خوب آن نیست که بیشترین اطلاعات را نشان می‌دهد؛ آن است که کاربر بعد از ده ثانیه می‌داند امروز باید چه کار کند. اگر برای رسیدن به این جواب باید سه بار اسکرول کند و دو منو باز کند، مشکل از داده‌ها نیست — از ترتیب است.