بلاک‌ها · پنل مدیریت

داشبوردِ عملکردِ تیم

پیش‌نمایش تمام‌صفحه

سلام و تاریخ، سه شاخص با آیکون و تغییر، نمودارِ ناحیه‌ایِ این هفته/هفتهٔ قبل، کارهای جاری با وضعیت و ساعت، و ستونِ کنار با کارتِ هم‌تیمی، فعالیت‌ها با پیام و فایل و نوشتنِ پیام.

۱. نصب کامپوننت‌ها

این بلاک از ۹ آیتم دیگ استفاده می‌کند: 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.json
pnpm 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.json
yarn 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.json
bunx --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>
  )
}