پرتوپرتو

صفحه تحلیل

الگوی ساخت صفحه تحلیل اینفلوئنسر با ترکیب کامپوننت‌های دامنه‌ای

چه زمانی از این الگو استفاده کنید؟

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

  • صفحه پروفایل اینفلوئنسر: نمایش کامل اطلاعات، نرخ تعامل، احساسات مخاطبان و محتوای اخیر
  • تحلیل عمیق حساب کاربری: بررسی روند رشد دنبال‌کننده، عملکرد محتوا و توزیع احساسات
  • گزارش عملکرد: ارائه خلاصه‌ای از معیارهای کلیدی و تغییرات آن‌ها در بازه زمانی مشخص

نمونه بصری

این الگو یک پیاده‌سازی کامل و کارکننده دارد: بلاک پروفایل اینفلوئنسر. آن را در اندازه‌های مختلف صفحه و در هر دو تم ببینید و کدش را از همان‌جا بردارید؛ صفحه‌ی حاضر توضیح می‌دهد چرا این‌طور چیده شده.

گزارش کمپین تخفیف فصلی

نمای کلی عملکرد کمپین در شبکه‌های اجتماعی

پوشش انتشار

تعداد پست‌های منتشرشده به تفکیک پلتفرم

خلاصهٔ کمپین

داده‌های ۳۰ روز گذشته

بهترین محتوا

۵ پست برتر دوره

بازخورد مخاطبان

خلاصهٔ نظرات دربارهٔ بسته‌بندی جدید
جایگاه نمودار بازخورد

ساختار کلی صفحه

صفحه تحلیل از PageContainer با اندازه large و PageHeader با breadcrumb برای بازگشت به لیست تشکیل شده است. بخش اصلی صفحه از PageSection با orientation="horizontal" استفاده می‌کند تا پروفایل در کنار محتوای اصلی قرار بگیرد.

import {
  PageContainer,
  PageBreadcrumbs,
  PageHeaderRoot,
  PageHeaderMeta,
  PageHeaderSummary,
  PageHeaderTitle,
  PageHeaderDescription,
  PageSection,
  PageSectionContent,
  Breadcrumb,
  BreadcrumbList,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbPage,
} from '@partodata/ui'

function AnalysisPage() {
  return (
    <>
      <PageBreadcrumbs>
        <BreadcrumbList>
          <BreadcrumbItem>
            <BreadcrumbLink href="/influencers">اینفلوئنسرها</BreadcrumbLink>
          </BreadcrumbItem>
          <BreadcrumbItem>
            <BreadcrumbPage>سارا احمدی</BreadcrumbPage>
          </BreadcrumbItem>
        </BreadcrumbList>
      </PageBreadcrumbs>

      <PageHeaderRoot size="large">
        <PageHeaderMeta>
          <PageHeaderSummary>
            <PageHeaderTitle>تحلیل پروفایل</PageHeaderTitle>
            <PageHeaderDescription>بررسی عملکرد و تحلیل محتوای سارا احمدی در شبکه‌های اجتماعی</PageHeaderDescription>
          </PageHeaderSummary>
        </PageHeaderMeta>
      </PageHeaderRoot>

      <PageContainer size="large">
        <PageSection orientation="horizontal">
          {/* ستون باریک (1fr): کارت پروفایل و پلتفرم‌ها */}
          <PageSectionContent>{/* کارت پروفایل و پلتفرم‌ها */}</PageSectionContent>
          {/* ستون پهن (2fr): بخش‌های تحلیل، احساسات، محتوا و معیارها */}
          <PageSectionContent>{/* بخش‌های تحلیل، احساسات، محتوا و معیارها */}</PageSectionContent>
        </PageSection>
      </PageContainer>
    </>
  )
}

بخش پروفایل (Aside)

در ستون کناری، ProfileCard اطلاعات خلاصه اینفلوئنسر را نمایش می‌دهد و SocialPlatformBadge مشخص می‌کند داده‌ها از کدام پلتفرم‌ها جمع‌آوری شده‌اند.

import { ProfileCard, SocialPlatformBadge } from '@partodata/ui'
import { Users } from 'lucide-react'

function ProfileAside() {
  return (
    <div className="space-y-4">
      <ProfileCard
        name="سارا احمدی"
        username="sara.ahmadi"
        avatarSrc="/avatars/sara-ahmadi.jpg"
        followers={245000}
        followersIcon={<Users className="h-3.5 w-3.5" />}
        onCardClick={() => console.log('مشاهده پروفایل کامل')}
      />

      <div className="flex flex-wrap gap-2">
        <SocialPlatformBadge platform="instagram" variant="badge" showLabel />
        <SocialPlatformBadge platform="twitter" variant="badge" showLabel />
        <SocialPlatformBadge platform="youtube" variant="badge" showLabel />
      </div>
    </div>
  )
}

اگر به جای کارت عمودی، نمایش افقی‌تری نیاز دارید، از ProfileInfo استفاده کنید:

import { ProfileInfo } from '@partodata/ui'
;<ProfileInfo
  name="سارا احمدی"
  username="sara.ahmadi"
  avatarSrc="/avatars/sara-ahmadi.jpg"
  infoText="آخرین به‌روزرسانی: ۱۵ فروردین ۱۴۰۵"
/>

بخش تحلیل نرخ تعامل

نسخه کامل — EngagementRate

برای تحلیل اصلی صفحه از نسخه کامل استفاده کنید. این کامپوننت نوار تقسیم‌بندی شده با ۶ سطح، مثلث شاخص و کارت اطلاعات دسته‌بندی را نمایش می‌دهد.

import { EngagementRate } from '@partodata/ui'
;<EngagementRate currentRate={0.0342} followers={245000} locale="fa" showCategoryCard={true} />

مقدار currentRate به صورت اعشاری وارد می‌شود (مثلا 0.0342 برای 3.42%). کامپوننت به صورت خودکار بر اساس تعداد دنبال‌کننده‌ها، دسته مناسب (نانو، میکرو، متوسط، ماکرو، مگا) را تشخیص می‌دهد و سطح نرخ تعامل را مشخص می‌کند.

نسخه فشرده — EngagementRateBar

برای نمایش در کارت‌ها، جداول مقایسه‌ای یا فضاهای محدود از نسخه فشرده استفاده کنید:

import { EngagementRateBar } from '@partodata/ui'
;<EngagementRateBar currentRate={0.0342} followers={245000} locale="fa" />

چه زمانی از کدام نسخه استفاده کنید؟

حالتکامپوننتدلیل
صفحه تحلیل اصلیEngagementRateنمایش کامل با کارت دسته‌بندی و جزئیات
کارت خلاصه در داشبوردEngagementRateBarفضای محدود، فقط نوار و درصد
جدول مقایسه اینفلوئنسرهاEngagementRateBarنمایش فشرده در سلول جدول
صفحه جزئیات پروفایلEngagementRateکاربر انتظار تحلیل عمیق دارد

بخش تحلیل احساسات

توزیع کلی احساسات

از SentimentDistribution با حالت bars برای نمایش توزیع کلی احساسات نظرات استفاده کنید:

import { SentimentDistribution } from '@partodata/ui'
;<SentimentDistribution data={{ positive: 3420, negative: 890, neutral: 1540 }} variant="bars" showCounts />

نشان‌های احساسات فردی

برای نمایش برچسب‌های احساسات در کنار محتوا یا نظرات:

import { SentimentBadge } from '@partodata/ui'
;<div className="flex flex-wrap gap-2">
  <SentimentBadge sentiment="positive" count={3420} />
  <SentimentBadge sentiment="negative" count={890} />
  <SentimentBadge sentiment="neutral" count={1540} />
  <SentimentBadge sentiment="mixed" count={230} />
</div>

نمودار تفکیک احساسات بر اساس موضوع

برای نمایش توزیع احساسات در موضوعات مختلف از PartoBarChart یا PartoPieChart استفاده کنید:

import { PartoBarChart } from '@partodata/ui'

const sentimentByTopic = [
  { موضوع: 'کیفیت محصول', مثبت: 820, منفی: 120, خنثی: 340 },
  { موضوع: 'خدمات پشتیبانی', مثبت: 540, منفی: 380, خنثی: 210 },
  { موضوع: 'قیمت‌گذاری', مثبت: 310, منفی: 490, خنثی: 180 },
  { موضوع: 'تجربه کاربری', مثبت: 670, منفی: 95, خنثی: 420 },
]

;<div className="h-[400px]">
  <PartoBarChart
    data={sentimentByTopic}
    keys={['مثبت', 'منفی', 'خنثی']}
    indexBy="موضوع"
    margin={{ top: 20, right: 20, bottom: 50, left: 80 }}
    padding={0.3}
    groupMode="stacked"
  />
</div>

برای نمایش نسبت کلی احساسات در نمودار دایره‌ای:

import { PartoPieChart } from '@partodata/ui'

const sentimentData = [
  { id: 'مثبت', label: 'مثبت', value: 3420 },
  { id: 'منفی', label: 'منفی', value: 890 },
  { id: 'خنثی', label: 'خنثی', value: 1540 },
]

;<div className="h-[300px]">
  <PartoPieChart
    data={sentimentData}
    innerRadius={0.5}
    padAngle={0.7}
    cornerRadius={3}
    activeOuterRadiusOffset={8}
    enableArcLinkLabels={false}
  />
</div>

بخش محتوا

گرید پست‌های اخیر

از PostCard با تراکم comfortable برای نمایش پست‌های اخیر اینفلوئنسر استفاده کنید. این کامپوننت از تمام پلتفرم‌ها پشتیبانی می‌کند و نسبت ابعادی عکس‌ها (1:1، 4:5، 9:16، 16:9) را با بک‌دراپ بلور به‌شکل یکنواخت رندر می‌کند.

import { PostCard, type PostData } from '@partodata/ui'

const recentPosts: PostData[] = [
  {
    id: 'p1',
    source: 'instagram',
    author: { name: 'Sara Ahmadi', handle: 'sara.ahmadi', avatarUrl: '/avatars/sara-ahmadi.jpg' },
    timestamp: new Date('2025-03-28'),
    body: {
      type: 'media-single',
      media: { url: '/posts/post-1.jpg', aspectRatio: 1, alt: 'رویداد فناوری تهران' },
      caption: 'امروز در رویداد فناوری تهران شرکت کردیم. تجربه فوق‌العاده‌ای بود! #تکنولوژی #تهران',
    },
    sentiment: 'positive',
    metrics: { likes: 18420, comments: 342, views: 52300, shares: 128 },
  },
  {
    id: 'p2',
    source: 'instagram',
    author: { name: 'Sara Ahmadi', handle: 'sara.ahmadi', avatarUrl: '/avatars/sara-ahmadi.jpg' },
    timestamp: new Date('2025-03-25'),
    body: {
      type: 'media-single',
      media: { url: '/posts/post-2.jpg', aspectRatio: 1, alt: 'معرفی محصول جدید' },
      caption: 'معرفی محصول جدید! خیلی هیجان‌زده هستم که بالاخره می‌توانم با شما به اشتراک بگذارم.',
    },
    sentiment: 'positive',
    metrics: { likes: 24100, comments: 567, views: 71200, shares: 245 },
  },
]

;<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
  {recentPosts.map((post) => (
    <PostCard key={post.id} post={post} view="comfortable" />
  ))}
</div>

تحلیل نظرات

از CommentCard برای نمایش نظرات به همراه برچسب‌های تحلیلی و شاخص احساسات استفاده کنید:

import { CommentCard } from '@partodata/ui'
;<div className="space-y-3">
  <CommentCard
    text="محتوای خیلی خوبی بود. کیفیت تصاویر و نوع ارائه واقعاً حرفه‌ای است. منتظر محتوای بیشتر هستم."
    tags={[
      { title: 'رضایت از کیفیت', probability: 94, color: 'var(--sentiment-positive)' },
      { title: 'درخواست محتوای بیشتر', probability: 87, color: 'var(--sentiment-positive)' },
    ]}
    sentiment="positive"
  />

  <CommentCard
    text="متاسفانه اطلاعات ارائه‌شده دقیق نبود و منابع معتبری ذکر نشده بود. لطفاً بیشتر تحقیق کنید."
    tags={[
      { title: 'عدم دقت اطلاعات', probability: 91, color: 'var(--sentiment-negative)' },
      { title: 'نبود منبع معتبر', probability: 85, color: 'var(--sentiment-negative)' },
    ]}
    sentiment="negative"
  />

  <CommentCard
    text="موضوع جالبی بود ولی کاش بیشتر توضیح می‌دادید. نه خیلی خوب بود نه خیلی بد."
    tags={[{ title: 'نظر ترکیبی', probability: 78, color: 'var(--sentiment-neutral)' }]}
    sentiment="neutral"
  />
</div>

بخش معیارها و آمار

گرید کارت‌های متریک

از MetricCard برای نمایش معیارهای کلیدی مانند رشد دنبال‌کننده، روند تعامل و فراوانی انتشار استفاده کنید:

import {
  MetricCard,
  MetricCardHeader,
  MetricCardLabel,
  MetricCardContent,
  MetricCardValue,
  MetricCardDifferential,
  MetricCardSparkline,
} from '@partodata/ui'
import { Users, Heart, FileText } from 'lucide-react'

const followerData = [
  { value: 238000, timestamp: '1404-12-01' },
  { value: 239200, timestamp: '1404-12-08' },
  { value: 240100, timestamp: '1404-12-15' },
  { value: 241800, timestamp: '1404-12-22' },
  { value: 243500, timestamp: '1404-12-29' },
  { value: 245000, timestamp: '1405-01-07' },
]

const engagementData = [
  { value: 3.1, timestamp: '1404-12-01' },
  { value: 3.3, timestamp: '1404-12-08' },
  { value: 3.0, timestamp: '1404-12-15' },
  { value: 3.5, timestamp: '1404-12-22' },
  { value: 3.4, timestamp: '1404-12-29' },
  { value: 3.42, timestamp: '1405-01-07' },
]

const postFrequencyData = [
  { value: 12, timestamp: '1404-12-01' },
  { value: 15, timestamp: '1404-12-08' },
  { value: 10, timestamp: '1404-12-15' },
  { value: 14, timestamp: '1404-12-22' },
  { value: 18, timestamp: '1404-12-29' },
  { value: 16, timestamp: '1405-01-07' },
]

;<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
  <MetricCard>
    <MetricCardHeader>
      <MetricCardLabel icon={<Users className="h-3.5 w-3.5" />}>تعداد دنبال‌کننده‌ها</MetricCardLabel>
    </MetricCardHeader>
    <MetricCardContent>
      <MetricCardValue>۲۴۵,۰۰۰</MetricCardValue>
      <MetricCardDifferential variant="positive">+۲.۹٪</MetricCardDifferential>
    </MetricCardContent>
    <MetricCardSparkline data={followerData} dataKey="value" />
  </MetricCard>

  <MetricCard>
    <MetricCardHeader>
      <MetricCardLabel icon={<Heart className="h-3.5 w-3.5" />}>نرخ تعامل</MetricCardLabel>
    </MetricCardHeader>
    <MetricCardContent>
      <MetricCardValue>۳.۴۲٪</MetricCardValue>
      <MetricCardDifferential variant="positive">+۰.۱۲٪</MetricCardDifferential>
    </MetricCardContent>
    <MetricCardSparkline data={engagementData} dataKey="value" />
  </MetricCard>

  <MetricCard>
    <MetricCardHeader>
      <MetricCardLabel icon={<FileText className="h-3.5 w-3.5" />}>تعداد پست ماهانه</MetricCardLabel>
    </MetricCardHeader>
    <MetricCardContent>
      <MetricCardValue>۱۶</MetricCardValue>
      <MetricCardDifferential variant="negative">-۱.۲٪</MetricCardDifferential>
    </MetricCardContent>
    <MetricCardSparkline data={postFrequencyData} dataKey="value" />
  </MetricCard>
</div>

نشانگر روند و نمایش آمار

برای نمایش سریع اعداد کلیدی با نشانگر تغییر از StatDisplay و TrendIndicator استفاده کنید:

import { StatDisplay, TrendIndicator } from '@partodata/ui'
import { Eye, MessageCircle, Share2 } from 'lucide-react'
;<div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
  <StatDisplay value={245000} label="دنبال‌کننده‌ها" icon={<Users />} trend={<TrendIndicator value={2.9} />} />

  <StatDisplay value={52300} label="میانگین بازدید" icon={<Eye />} trend={<TrendIndicator value={5.2} />} />

  <StatDisplay value={342} label="میانگین نظرات" icon={<MessageCircle />} trend={<TrendIndicator value={-1.3} />} />

  <StatDisplay value={128} label="میانگین اشتراک‌گذاری" icon={<Share2 />} trend={<TrendIndicator value={8.7} />} />
</div>

حالت بارگذاری

تمامی کامپوننت‌های دامنه‌ای از prop isLoading پشتیبانی می‌کنند. برای بخش‌هایی که isLoading ندارند از Skeleton استفاده کنید.

import {
  PageContainer,
  PageHeaderRoot,
  PageHeaderMeta,
  PageHeaderSummary,
  PageSection,
  PageSectionContent,
  Skeleton,
  EngagementRate,
  SentimentDistribution,
  MetricCard,
  MetricCardHeader,
  MetricCardLabel,
  MetricCardContent,
  StatDisplay,
} from '@partodata/ui'

function AnalysisPageSkeleton() {
  return (
    <>
      <PageHeaderRoot size="large">
        <PageHeaderMeta>
          <PageHeaderSummary>
            <Skeleton className="h-4 w-32" />
            <Skeleton className="h-8 w-48 mt-2" />
            <Skeleton className="h-4 w-64 mt-1" />
          </PageHeaderSummary>
        </PageHeaderMeta>
      </PageHeaderRoot>

      <PageContainer size="large">
        <PageSection orientation="horizontal">
          <PageSectionContent>
            {/* اسکلتون پروفایل */}
            <div className="space-y-4">
              <div className="flex flex-col items-center gap-3 rounded-lg border border-border p-6">
                <Skeleton shape="circle" size="lg" />
                <Skeleton className="h-5 w-32" />
                <Skeleton className="h-4 w-24" />
                <Skeleton className="h-4 w-20" />
              </div>
              <div className="flex gap-2">
                <Skeleton className="h-6 w-24 rounded-full" />
                <Skeleton className="h-6 w-20 rounded-full" />
                <Skeleton className="h-6 w-22 rounded-full" />
              </div>
            </div>
          </PageSectionContent>

          <PageSectionContent>
            {/* اسکلتون نرخ تعامل */}
            <EngagementRate currentRate={0} followers={0} isLoading />

            {/* اسکلتون احساسات */}
            <div className="mt-6">
              <SentimentDistribution data={{ positive: 0, negative: 0, neutral: 0 }} isLoading />
            </div>

            {/* اسکلتون معیارها */}
            <div className="mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
              {Array.from({ length: 3 }).map((_, i) => (
                <MetricCard key={i}>
                  <MetricCardHeader>
                    <MetricCardLabel>
                      <Skeleton className="h-4 w-24" />
                    </MetricCardLabel>
                  </MetricCardHeader>
                  <MetricCardContent>
                    <Skeleton className="h-8 w-20" />
                  </MetricCardContent>
                </MetricCard>
              ))}
            </div>

            {/* اسکلتون آمار */}
            <div className="mt-6 grid grid-cols-2 gap-4 sm:grid-cols-4">
              {Array.from({ length: 4 }).map((_, i) => (
                <StatDisplay key={i} value={0} label="" isLoading />
              ))}
            </div>

            {/* اسکلتون پست‌ها */}
            <div className="mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
              {Array.from({ length: 3 }).map((_, i) => (
                <div key={i} className="rounded-lg border border-border overflow-hidden">
                  <Skeleton className="h-64 w-full" />
                  <div className="p-3 space-y-2">
                    <div className="flex items-center gap-2">
                      <Skeleton shape="circle" size="sm" />
                      <Skeleton className="h-4 w-24" />
                    </div>
                    <Skeleton className="h-3 w-full" />
                    <Skeleton className="h-3 w-3/4" />
                  </div>
                </div>
              ))}
            </div>
          </PageSectionContent>
        </PageSection>
      </PageContainer>
    </>
  )
}

بهترین شیوه‌ها

چیدمان

  • کارت پروفایل (ProfileCard) را در ستون باریک (اولین PageSectionContent در چیدمان افقی، عرض ۱fr) بگذارید و بخش‌های تحلیل را در ستون پهن (PageSectionContent دوم، عرض ۲fr). توجه: PageSectionAside یک خوشهٔ اقدامات است، نه یک ستون محتوا.
  • از PageSection با orientation="horizontal" برای چیدمان پروفایل در کنار محتوای تحلیلی استفاده کنید.
  • در صفحات موبایل، چیدمان افقی به صورت خودکار به عمودی تبدیل می‌شود.

نرخ تعامل

  • در صفحه تحلیل اصلی از EngagementRate (نسخه کامل با کارت دسته‌بندی) استفاده کنید.
  • از EngagementRateBar برای نماهای مقایسه‌ای، جداول و کارت‌های خلاصه استفاده کنید.
  • مقدار currentRate همیشه به صورت اعشاری وارد می‌شود (0.0342 نه 3.42).

پلتفرم‌ها

  • همیشه SocialPlatformBadge را نمایش دهید تا کاربر بداند داده‌ها از کدام پلتفرم جمع‌آوری شده‌اند.
  • اگر داده‌ها از چند پلتفرم هستند، همه نشان‌ها را کنار هم نمایش دهید.

بارگذاری

  • برای کامپوننت‌هایی که prop isLoading دارند (مانند EngagementRate، SentimentDistribution، StatDisplay) از همین prop استفاده کنید.
  • برای بخش‌های سفارشی از Skeleton با ابعاد مناسب استفاده کنید.
  • حالت بارگذاری را برای تمام بخش‌های صفحه پیاده‌سازی کنید تا کاربر ساختار صفحه را قبل از بارگذاری کامل ببیند.

داده‌ها

  • از اعداد واقع‌گرایانه در نمونه‌ها و تست‌ها استفاده کنید. نرخ تعامل 3.42% برای یک اینفلوئنسر با 245,000 دنبال‌کننده (دسته میکرو) واقع‌بینانه است.
  • برای SentimentDistribution داده‌های خام عددی ارسال کنید — کامپوننت به صورت خودکار درصدها را محاسبه می‌کند.
  • در CommentCard مقدار probability عددی بین ۰ تا ۱۰۰ است و نشان‌دهنده اطمینان مدل تحلیل احساسات است.

دام‌های رایج

خواص فیزیکی CSS در کد اتصال صفحه

مشکل: استفاده از ml/pl/text-left در گریدها و wrapper هایی که ستون پروفایل را به بخش‌های تحلیل وصل می‌کنند.

// ❌ غلط — در چیدمان RTL فاصله و خط جداکننده به سمت اشتباه می‌روند
<div className="ml-4 border-l pl-6 text-left">{/* بخش‌های تحلیل */}</div>

// ✅ درست — Logical Properties در RTL و LTR هر دو درست عمل می‌کنند
<div className="ms-4 border-s ps-6 text-start">{/* بخش‌های تحلیل */}</div>

چرا اشتباه است: صفحه تحلیل با dir="rtl" رندر می‌شود و در RTL ابتدای خط سمت راست است؛ ml-4 حاشیه را به چپ می‌چسباند و چیدمان دو ستونی را به‌هم می‌ریزد. کامپوننت‌های @partodata/ui خودشان با قانون ESLint no-physical-css-properties محافظت می‌شوند، اما کد اتصالی که در صفحه خودتان می‌نویسید چنین محافظی ندارد — آن‌جا فقط انضباط خودتان جلوی این خطا را می‌گیرد.

رنگ احساسات به‌صورت hardcode

مشکل: نوشتن رنگ مستقیم (مثلا #22c55e یا text-green-500) برای برچسب‌ها و سلول‌های احساسات به‌جای توکن‌های معنایی.

// ❌ غلط — فقط در یکی از دو تم درست دیده می‌شود
<CommentCard
  text="کیفیت بسته‌بندی محصول عالی بود."
  tags={[{ title: 'رضایت از کیفیت', probability: 94, color: '#22c55e' }]}
  sentiment="positive"
/>

// ✅ درست — توکن معنایی در هر دو تم مقدار صحیح خودش را دارد
<CommentCard
  text="کیفیت بسته‌بندی محصول عالی بود."
  tags={[{ title: 'رضایت از کیفیت', probability: 94, color: 'var(--sentiment-positive)' }]}
  sentiment="positive"
/>

چرا اشتباه است: توکن‌های این سیستم dark-first هستند — مقدار پایه :root تمِ تاریک است و تم روشن با [data-theme='light'] فعال می‌شود، بنابراین رنگ ثابت در تم مقابل کنتراست را از دست می‌دهد. علاوه بر این، نمودارها رنگ‌ها را در زمان اجرا از همین توکن‌ها می‌خوانند؛ مقدار hardcode با رنگ احساسات در بقیه بخش‌های صفحه ناهماهنگ می‌شود.

صفحه‌بندی سمت کلاینت برای نظرات حجیم

مشکل: دریافت همه نظرات یک اینفلوئنسر در یک درخواست و برش (slice) آن‌ها در مرورگر، یا نمایش کل لیست بدون صفحه‌بندی.

// ❌ غلط — کل داده به کلاینت منتقل می‌شود
const all = await fetchComments(influencerId) // ده‌ها هزار ردیف
const page = all.slice((currentPage - 1) * 20, currentPage * 20)

// ✅ درست — DataTable سرور-محور صفحه‌بندی می‌شود
<DataTable
  columns={columns}
  data={pageData} // فقط ردیف‌های صفحه جاری
  isLoading={isFetching}
  pagination={{
    currentPage,
    totalPages,
    onPageChange: setCurrentPage,
    pageSize,
    onPageSizeChange: setPageSize,
    totalRows, // تعداد کل ردیف‌ها طبق گزارش سرور
  }}
/>

چرا اشتباه است: صفحات تحلیل روی داده‌های حجیم شنود اجتماعی می‌نشینند و یک حساب پرتعامل ممکن است ده‌ها هزار نظر داشته باشد؛ دریافت یک‌جای آن‌ها حجم پاسخ، مصرف حافظه و زمان رندر را چند برابر می‌کند. قرارداد DataTable از اساس server-paged است: کامپوننت تعداد صفحات را از داده استنتاج نمی‌کند و totalPages و totalRows را از سرور می‌گیرد.

نمودار بدون ارتفاع مشخص

مشکل: قرار دادن PartoBarChart یا PartoPieChart در یک div بدون ارتفاع صریح.

// ❌ غلط — نمودار با ارتفاع صفر رندر می‌شود و بدون هیچ خطایی خالی می‌ماند
<div>
  <PartoBarChart data={sentimentByTopic} keys={['مثبت', 'منفی', 'خنثی']} indexBy="موضوع" />
</div>

// ✅ درست — والد ارتفاع صریح دارد
<div className="h-[400px]">
  <PartoBarChart data={sentimentByTopic} keys={['مثبت', 'منفی', 'خنثی']} indexBy="موضوع" />
</div>

چرا اشتباه است: نمودارها داخل ResponsiveContainer با width="100%" height="100%" رندر می‌شوند؛ وقتی والد ارتفاع نداشته باشد، «۱۰۰٪» از ارتفاعِ صفر محاسبه می‌شود و نمودار بدون هیچ پیام خطایی ناپدید می‌شود. به همین دلیل همه نمونه‌های این صفحه wrapper با h-[400px] یا h-[300px] دارند.

فارسی غیررسمی و ارقام ناهماهنگ در متن رابط

مشکل: لحن غیررسمی در متن‌های صفحه و ارقام لاتین در میان متن فارسی.

// ❌ غلط — لحن غیررسمی و ارقام لاتین
<PageHeaderDescription>عملکرد سارا رو ببین — 245000 دنبال‌کننده</PageHeaderDescription>

// ✅ درست — فارسی رسمی و ارقام فارسی هماهنگ با بقیه صفحه
<PageHeaderDescription>بررسی عملکرد و تحلیل محتوای سارا احمدی — ۲۴۵,۰۰۰ دنبال‌کننده</PageHeaderDescription>

چرا اشتباه است: متن رابط در این سیستم همیشه فارسی رسمی است («استفاده کنید» نه «استفاده کن») و کامپوننت‌های عددی مانند EngagementRate با locale="fa" ارقام فارسی تولید می‌کنند؛ اگر متن‌های دستی شما ارقام لاتین داشته باشند، صفحه ترکیبی ناهماهنگ از دو نوع رقم نمایش می‌دهد. برای تبدیل ارقام در مقادیر پویا از value استفاده کنید.


صفحات مرتبط

  • صفحه جدول داده — اگر خروجی تحلیل به‌جای گرید کارت، لیستی قابل جستجو و فیلتر از پست‌ها یا نظرات است، از این الگو استفاده کنید.
  • ترکیب داشبورد — اگر به‌جای تحلیل عمیق یک حساب، نمای کلی چند متریک و چند حساب در یک صفحه لازم دارید، این الگو مناسب‌تر است.
  • چیدمان — برای انتخاب اندازه PageContainer و ساختار بخش‌ها با خانواده Page* ابتدا این مرجع را ببینید.
  • اشتباهات رایج — فهرست کامل ضدالگوهای سراسری (رنگ hardcode، خواص فیزیکی و…) که دام‌های بالا نمونه‌های تحلیلی آن‌ها هستند.