کارت کار (JobCard)
یک کار async — تحلیل، خزش، ورود فایل — با مرحله، صف، پیشرفت شمارشی، علت شکست و یک اقدام اصلی برای هر مرحله، در سه چیدمان کارت، ردیف و سربرگ صفحه.
معرفی
JobCard هر کار async را نشان میدهد: تحلیل کامنت، ارزیابی صفحه، ورود فایل، تولید گزارش. دادهی کار را بدهید، سیستم طراحی آن را میچیند: برچسب و رنگ وضعیت، نوار پیشرفت، جملهی مرحله یا علت شکست، و فقط یک اقدام اصلی متناسب با مرحله (بازبینی، تلاش مجدد، آپلود مجدد، لغو) بههمراه منوی «بیشتر». اقدامها هیچوقت داخل لینک باز شدن کارت نیستند.
از 4.0 JobCard فقط از زیرمسیر وارد میشود: @partodata/ui/job-card (یا @partodata/ui/social کنار EntityCollection).
چه زمانی استفاده کنیم:
- فهرست «تحلیلهای من» / «کارها» در داشبورد، بهصورت کارت یا ردیف (
EntityCollection entity="job") - سربرگ صفحهی نتیجهی یک کار در حال اجرا (
layout="header") - صف کارهای مدیریتی با سازنده و هزینه
چه زمانی استفاده نکنیم:
- نمایش محتوا — پست:
Post، حساب:Account - متریک تکعدد با روند —
MetricCard - مراحل یک فرایند چندگامی که کاربر پر میکند —
WizardPage

تحلیل کامنتهای کمپین تخفیف فصلی
- 1250
بازخورد بستهبندی جدید
نتیجه آماده است و منتظر بازبینی شماست.
- 842
- 12
استفاده
'use client'
import { JobCard } from '@partodata/ui/job-card'
export function AnalysisCard({ job }) {
return (
<JobCard
phase={job.phase} // 'queued' | 'running' | 'needs-action' | 'failed' | …
stage={job.stageLabel} // «در حال برچسبگذاری»
title={job.title}
jobRef={`#${job.number}`}
target={{ kind: 'post', platform: 'instagram', image: job.thumbnail }}
progress={{ done: job.done, total: job.total, failed: job.failed, unit: 'پست' }}
queue={{ position: job.queuePosition, etaMinutes: job.eta }}
failure={job.error ? { reason: job.error, stage: job.errorStage, retryable: job.retryable } : undefined}
createdAt={job.createdAt}
counts={[{ label: 'کامنت', value: job.comments }]}
href={`/analyses/${job.id}`}
onRetry={() => retry(job.id)}
onCancel={() => cancel(job.id)}
onReview={() => router.push(`/analyses/${job.id}/review`)}
menu={[{ id: 'delete', label: 'حذف', tone: 'destructive', onSelect: () => remove(job.id) }]}
/>
)
}مرحلهها
phase: draft | queued | running | needs-action | paused | completed | partial | failed | cancelled | expired
ارزیابی 8 صفحهی تبلیغاتی

تحلیل کامنتهای رونمایی محصول
برچسبهای پیشنهادی کمپین پاییز
نتیجه آماده است و منتظر بازبینی شماست.
تحلیل 5 پست همکاری با اینفلوئنسر
ورود فایل کامنتها
ستون «متن کامنت» در فایل پیدا نشد.
گفتمان دربارهی برند در تلگرام
- 3200
- 18
تحلیل سادهی پست معرفی
این کار لغو شد و نتیجهای ندارد.

تحلیل کامنتهای ماه گذشته
دادههای این کار منقضی شده است.
تحلیل جدید
هنوز ارسال نشده است.
- برچسب وضعیت = نام مرحله در حال اجرا (
stage)، «خطا — {مرحله}» در شکست، «نفر n در صف · حدود m دقیقه» در صف. - اقدام اصلی را سیستم طراحی انتخاب میکند:
needs-action→ بازبینی؛failed→ تلاش مجدد (یا آپلود مجدد برای هدف فایل)؛partial→ تلاش مجدد برای ناموفقها؛ صف و اجرا → لغو؛ تمامشده → بدون دکمه (کل کارت باز میشود). - رنگ فقط در نوار 2 پیکسلی لبهی آغاز کارت و نشان وضعیت: اجرا برند، نیازمند اقدام و جزئی هشدار، شکست قرمز؛ کارتهای تمامشده آرامتریناند.
- پیشرفت هرگز عقب نمیرود؛ با
done/total/failedنوار بخشبندیشده و «2 از 5 پست · 1 ناموفق». در صف و اجرا کارت باز نمیشود مگرopenWhileRunning. - زنده بودن: با
live={{ updatedAt }}، اگر بیش از 30 ثانیه بهروزرسانی نرسد متن «آخرین بهروزرسانی … پیش» نمایش داده میشود (نه قرمز).
ردیف در فهرست
EntityCollection entity="job" چیدمانهای card و row، اسکلتون، انتخاب گروهی و حرکت با کلیدهای جهت را آماده دارد.
سربرگ صفحهی نتیجه

تحلیل کامنتهای کمپین تخفیف فصلی
در حال برچسبگذاریزمین بازی
تحلیل گفتوگوهای کمپین
- 1200
- 5300
progress نامعین (indeterminate)
واکشی پروفایل از اینستاگرام
وقتی کار در حال انجام است ولی درصد پیشرفت مشخص نیست (مثل «منتظر پاسخ API»)، progress را ندهید یا value را خالی بگذارید: نوار نامعین میشود. shimmer از start به end حرکت میکند و با prefers-reduced-motion ساکن میشود.
thumbnail با تصویر
تحلیل کامنتهای پست کافه لاکچری
- 3420
- 680
- 1200
target.imageتصویر هدف کار است (مثلاً پیشنمایش پست)؛ اگر بار نشود یا نباشد، نشانهٔ نوع هدف (target.kind) جایش مینشیند
ترکیب با FilterPanel برای صفحه Jobs
در صفحهی مدیریت کارها (مثل «تحلیلهای من»)، الگوی کامل معمولاً این شکل است:
<div className="flex gap-6">
<aside className="w-72">
<FilterPanel>
<FilterPanelHeader>
<FilterPanelTitle activeCount={activeCount} />
</FilterPanelHeader>
<FilterPanelBody>
<FilterSection title="مرحله">{/* checkboxes for JobPhase */}</FilterSection>
<FilterSection title="بازه زمانی">{/* DateRangePicker */}</FilterSection>
</FilterPanelBody>
</FilterPanel>
</aside>
<div className="flex flex-1 flex-col gap-3">
{jobs.map((job) => (
<JobCard key={job.id} layout="row" {...job} onOpen={() => open(job.id)} />
))}
</div>
</div>Props
JobCard
راهنمای استفاده
بکنید
- مراحل کار خودتان را به
phaseنگاشت کنید (CommentSanjpending_review→runningباstageاختصاصی). چرخهٔ عمر را با همین مرحلهها مدل کنید، نه برعکس. - در
completedنتیجه را باcountsنشان دهید (حداکثر سه عدد؛total«x از y» مینویسد)، نه با پیشرفت. - در
failedهمیشهfailure.reasonبدهید تا کاربر دلیل و قدم بعد (onRetry) را بداند. - برای فهرست بلند
layout="row"وonOpenیاhref؛ جزئیات را در صفحهٔ جدا یا Drawer باز کنید.
نکنید
- مرحلهٔ سفارشی به
JobPhaseاضافه نکنید؛ زیرمرحله را باstageبنویسید. - برای پیشرفت چندمرحلهای (pipeline) از این استفاده نکنید؛
StatusFlowبرای آن است.
دسترسیپذیری
- با
onOpenیاhrefکل کارت یک دکمه یا پیوند واقعی است؛ دکمههای اقدام کارت مستقل میمانند. - نوار پیشرفت
role="progressbar"باaria-valuenow/min/maxاست؛ وقتی مقدار نامعلوم استaria-valuenowندارد. - انیمیشنها (چرخش و shimmer) با
prefers-reduced-motionساکن میشوند.
کامپوننتهای مرتبط
- Post — پست شبکه اجتماعی، خبر یا بخش پخش؛ از این برای محتوا استفاده کنید، از
JobCardبرای کار - MetricCard — کارت متریک تکعدد با trend؛ برای KPI ها
- Card — کارت سطح پایین بدون semantic ویژه
- SeverityBadge — badge وضعیت operational (critical/warning/middle/low) — مستقل از lifecycle کار
- FilterPanel — برای فیلتر کردن لیست کارها
- StatusFlow — برای نمایش pipeline چندمرحلهای داخل کار