داشبوردِ عملکردِ تیم
پیشنمایش تمامصفحهسلام و تاریخ، سه شاخص با آیکون و تغییر، نمودارِ ناحیهایِ این هفته/هفتهٔ قبل، کارهای جاری با وضعیت و ساعت، و ستونِ کنار با کارتِ همتیمی، فعالیتها با پیام و فایل و نوشتنِ پیام.
۱. نصب کامپوننتها
این بلاک از ۹ آیتم دیگ استفاده میکند: avatarbuttoncardchart-presetscontrol-sizeiconspersiantext-fieldtoggle-group
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/card.json https://docs.digdesign.ir/r/chart-presets.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/toggle-group.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/card.json https://docs.digdesign.ir/r/chart-presets.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/toggle-group.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/card.json https://docs.digdesign.ir/r/chart-presets.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/toggle-group.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/card.json https://docs.digdesign.ir/r/chart-presets.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/toggle-group.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
dashboard-team.tsx
"use client"
import * as React from "react"
import { ActivityIcon, CalendarIcon, ClockIcon, DownloadIcon, MoreHorizontalIcon, PaperclipIcon, PhoneIcon, ThumbsUpIcon, TrendingUpIcon } from "@/components/icons"
import { Avatar } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { AreaChart } from "@/components/ui/chart-presets"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { TextField } from "@/components/ui/text-field"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { toFaDigits } from "@/lib/persian"
import { avatarPhoto, stockImages } from "@/lib/stock-images"
const KPIS = [
{ icon: <ThumbsUpIcon />, label: "انجامشده", value: "۱۸ کار", delta: "+۸ کار", good: true },
{ icon: <ClockIcon />, label: "ساعتِ ثبتشده", value: "۳۱ ساعت", delta: "−۶ ساعت", good: false },
{ icon: <TrendingUpIcon />, label: "بهرهوری", value: "٪۹۳", delta: "+٪۱۲", good: true },
]
const PERFORMANCE = [
{ label: "شنبه", now: 4, last: 6 },
{ label: "یکشنبه", now: 5, last: 4 },
{ label: "دوشنبه", now: 7, last: 6 },
{ label: "سهشنبه", now: 9, last: 5 },
{ label: "چهارشنبه", now: 7, last: 3 },
{ label: "پنجشنبه", now: 6, last: 4 },
{ label: "جمعه", now: 8, last: 5 },
]
type Status = "progress" | "hold" | "done"
const STATUS: Record<Status, { label: string; dot: string }> = {
progress: { label: "در جریان", dot: "bg-[var(--dig-orange-500)]" },
hold: { label: "متوقف", dot: "bg-[var(--dig-blue-500)]" },
done: { label: "انجام شد", dot: "bg-success" },
}
const TASKS: { id: string; title: string; status: Status; hours: number; icon: React.ReactNode }[] = [
{ id: "t1", title: "بازبینیِ محصول برای بازارِ تازه", status: "progress", hours: 4, icon: <ActivityIcon /> },
{ id: "t2", title: "پژوهشِ کاربر برای اپِ بانکی", status: "hold", hours: 8, icon: <CalendarIcon /> },
{ id: "t3", title: "طراحی و توسعهٔ داشبوردِ فروش", status: "done", hours: 32, icon: <TrendingUpIcon /> },
]
const ACTIVITY = [
{ name: "امیر رضایی", gender: "male" as const, action: "روی پروژهٔ بانکِ آینده نظر داد", time: "۱۰:۱۵", message: "هفتهٔ بعد پروژهٔ تازه را شروع میکنیم؛ جزئیات را بعد میگویم." },
{ name: "سارا محمدی", gender: "female" as const, action: "فایلی به پروژهٔ کافه رست افزود", time: "۰۹:۴۰", file: { name: "صفحه-اصلی.fig", size: "۱۳٫۴ مگابایت" } },
{ name: "نگار احمدی", gender: "female" as const, action: "روی داشبوردِ فروش نظر داد", time: "دیروز" },
]
/**
* داشبوردِ عملکردِ تیم: سلام و توضیحِ کوتاه با تاریخ، سه شاخص با آیکون و تغییرِ رنگی (کلمهٔ «کار»
* و «ساعت» کنارِ عدد)، نمودارِ ناحیهایِ این هفته در برابرِ هفتهٔ قبل، کارهای جاری با وضعیتِ نقطهای و
* ساعت، و ستونِ کنار با کارتِ همتیمی (تماس و تماسِ تصویری)، فعالیتها با پیام و فایل، و نوشتنِ پیام.
*/
export default function DashboardTeamBlock() {
const [range, setRange] = React.useState("week")
const [message, setMessage] = React.useState("")
return (
<ControlSizeProvider size="xl" asChild>
<main className="mx-auto grid w-full max-w-6xl grid-cols-1 items-start gap-6 p-4 sm:p-6 lg:grid-cols-[minmax(0,1fr)_21rem]">
<div className="flex min-w-0 flex-col gap-8">
<header className="flex flex-wrap items-start justify-between gap-x-4 gap-y-4">
<div className="flex flex-col gap-1">
<h1 className="text-3xl leading-snug font-black">سلام آرمیتا</h1>
<p className="text-muted-foreground">پیشرفتِ تیم را اینجا ببین؛ تا هدفِ هفته چیزی نمانده.</p>
</div>
<span className="flex items-center gap-2 rounded-full border border-border px-4 py-2 text-sm [&>svg]:size-4 [&>svg]:text-muted-foreground">
<CalendarIcon aria-hidden />
۱۶ مهر ۱۴۰۵
</span>
</header>
<ul className="grid grid-cols-1 divide-y divide-border border-y border-border sm:grid-cols-3 sm:divide-x sm:divide-y-0 rtl:sm:divide-x-reverse">
{KPIS.map((kpi) => (
<li key={kpi.label} className="flex items-center gap-3 py-5 sm:px-5 sm:first:ps-0">
<span aria-hidden className="flex size-12 shrink-0 items-center justify-center rounded-full bg-muted [&>svg]:size-5">
{kpi.icon}
</span>
<span className="flex flex-col gap-0.5">
<span className="text-sm text-muted-foreground">{kpi.label}</span>
<span className="flex items-baseline gap-2">
<span className="text-xl font-black">{kpi.value}</span>
<span className={`text-xs font-semibold ${kpi.good ? "text-success" : "text-destructive"}`}>{kpi.delta}</span>
</span>
</span>
</li>
))}
</ul>
<section aria-labelledby="perf-title" className="flex flex-col gap-4">
<div className="flex items-center justify-between gap-3">
<h2 id="perf-title" className="text-lg font-bold">
عملکرد (ساعت)
</h2>
<ToggleGroup type="single" variant="segmented" value={range} onValueChange={(value) => value && setRange(value as string)} aria-label="بازه">
<ToggleGroupItem value="week">هفته</ToggleGroupItem>
<ToggleGroupItem value="days">۴ روز</ToggleGroupItem>
</ToggleGroup>
</div>
<AreaChart
className="h-60"
data={range === "days" ? PERFORMANCE.slice(-4) : PERFORMANCE}
categoryKey="label"
smooth
showPoints={false}
series={[
{ key: "now", label: "این هفته", color: "var(--dig-blue-500)" },
{ key: "last", label: "هفتهٔ قبل", color: "var(--dig-amber-500)" },
]}
valueFormatter={(value) => `${toFaDigits(value)} ساعت`}
/>
</section>
<section aria-labelledby="tasks-title" className="flex flex-col gap-2">
<div className="flex items-center gap-4">
<h2 id="tasks-title" className="text-lg font-bold">
کارهای جاری
</h2>
<span className="text-sm text-muted-foreground">٪۳۰ انجام شده</span>
</div>
<ul className="flex flex-col">
{TASKS.map((task) => (
<li key={task.id} className="flex items-center gap-3 py-3">
<span aria-hidden className="flex size-12 shrink-0 items-center justify-center rounded-full bg-muted [&>svg]:size-5">
{task.icon}
</span>
<span className="flex min-w-0 flex-1 flex-col gap-1 sm:flex-row sm:items-center sm:gap-4">
<span className="flex-1 truncate font-medium">{task.title}</span>
<span className="flex items-center gap-4 text-sm">
<span className="flex w-24 items-center gap-2">
<span aria-hidden className={`size-2 rounded-full ${STATUS[task.status].dot}`} />
{STATUS[task.status].label}
</span>
<span className="flex items-center gap-1 text-muted-foreground tabular-nums [&>svg]:size-4">
<ClockIcon aria-hidden />
{toFaDigits(task.hours)} ساعت
</span>
</span>
</span>
<Button variant="ghost" size="icon" aria-label={`گزینههای «${task.title}»`} className="shrink-0">
<MoreHorizontalIcon />
</Button>
</li>
))}
</ul>
</section>
</div>
<aside className="flex flex-col gap-6">
<Card className="items-center bg-subtle text-center shadow-none">
<CardContent className="flex flex-col items-center gap-4">
<span className="relative">
<Avatar alt="مهسا کریمی" src={stockImages.personWoman1.src} className="size-24 ring-4 ring-card" />
<span aria-label="آنلاین" className="absolute end-1 bottom-1 size-4 rounded-full bg-success ring-2 ring-subtle" />
</span>
<span className="flex flex-col">
<span className="font-bold">مهسا کریمی</span>
<bdi dir="ltr" className="text-sm text-muted-foreground">
@mahsa.karimi
</bdi>
</span>
<div className="flex gap-3">
<Button variant="outline" size="icon" aria-label="تماس" className="rounded-full bg-card">
<PhoneIcon />
</Button>
<Button variant="outline" size="icon" aria-label="گزینههای بیشتر" className="rounded-full bg-card">
<MoreHorizontalIcon />
</Button>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-lg">فعالیتها</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-5">
<ul className="flex flex-col gap-5">
{ACTIVITY.map((item) => (
<li key={item.name + item.time} className="flex items-start gap-3">
<Avatar alt={item.name} src={avatarPhoto(item.name, item.gender)} className="size-10" />
<span className="flex min-w-0 flex-1 flex-col gap-2">
<span className="flex flex-col gap-0.5">
<span className="flex items-center justify-between gap-2">
<span className="text-sm font-bold">{item.name}</span>
<span className="text-xs text-muted-foreground tabular-nums">{item.time}</span>
</span>
<span className="text-sm text-muted-foreground">{item.action}</span>
</span>
{item.message && <span className="rounded-xl rounded-ss-sm bg-[color-mix(in_oklab,var(--dig-blue-500)_10%,var(--card))] p-3 text-sm leading-6">{item.message}</span>}
{item.file && (
<span className="flex items-center gap-3 rounded-xl bg-[color-mix(in_oklab,var(--dig-blue-500)_10%,var(--card))] p-3">
<span className="flex min-w-0 flex-1 flex-col">
<span className="truncate text-sm font-semibold">{item.file.name}</span>
<span className="text-xs text-muted-foreground">{item.file.size}</span>
</span>
<ControlSizeProvider size="lg" asChild>
<Button variant="ghost" size="icon" aria-label={`دریافتِ ${item.file.name}`} className="shrink-0">
<DownloadIcon />
</Button>
</ControlSizeProvider>
</span>
)}
</span>
</li>
))}
</ul>
<form
className="flex items-end gap-2"
onSubmit={(event) => {
event.preventDefault()
setMessage("")
}}
>
<TextField aria-label="پیام به تیم" placeholder="پیامی بنویس…" value={message} onValueChange={setMessage} className="flex-1" startContent={<PaperclipIcon aria-hidden />} />
</form>
</CardContent>
</Card>
</aside>
</main>
</ControlSizeProvider>
)
}