جزئیات تیکت
پیشنمایش تمامصفحهنمای کارشناس: کنشهای تیکت، پیامِ درخواستدهنده با پیوستهای نوعدار، پاسخنویس، و پنلِ جزئیات، درخواستدهنده و ویژگیهای قابلویرایش با دکمهٔ بهروزرسانی.
۱. نصب کامپوننتها
این بلاک از ۱۴ آیتم دیگ استفاده میکند: avatarbadgebreadcrumbbuttoncardcontrol-sizedropdown-menufile-uploadiconslabelpersianselecttextareatoast
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/breadcrumb.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/card.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/dropdown-menu.json https://docs.digdesign.ir/r/file-upload.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/label.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/select.json https://docs.digdesign.ir/r/textarea.json https://docs.digdesign.ir/r/toast.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/breadcrumb.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/card.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/dropdown-menu.json https://docs.digdesign.ir/r/file-upload.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/label.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/select.json https://docs.digdesign.ir/r/textarea.json https://docs.digdesign.ir/r/toast.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/breadcrumb.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/card.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/dropdown-menu.json https://docs.digdesign.ir/r/file-upload.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/label.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/select.json https://docs.digdesign.ir/r/textarea.json https://docs.digdesign.ir/r/toast.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/breadcrumb.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/card.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/dropdown-menu.json https://docs.digdesign.ir/r/file-upload.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/label.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/select.json https://docs.digdesign.ir/r/textarea.json https://docs.digdesign.ir/r/toast.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
ticket-detail.tsx
"use client"
import * as React from "react"
import { AvatarGenderIcon, ClockIcon, DownloadIcon, MoreHorizontalIcon, PaperclipIcon, TrashIcon, UserIcon } from "@/components/icons"
import { Avatar } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { FileTypeIcon } from "@/components/ui/file-upload"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import { toast } from "@/components/ui/toast"
import { formatJalali, toFaDigits } from "@/lib/persian"
import { stockImages } from "@/lib/stock-images"
const ATTACHMENTS = [
{ name: "رسید-پرداخت.pdf", kind: "PDF", size: "۵۷۰ کیلوبایت" },
{ name: "تصاویر-خطا.zip", kind: "ZIP", size: "۲٫۴ مگابایت" },
]
const DETAILS = [
{ label: "شمارهٔ تیکت", value: <Badge color="destructive" variant="soft">#{toFaDigits(1042)}</Badge> },
{ label: "موضوع", value: "افزودن روشِ پرداخت" },
{ label: "مهلتِ اولین پاسخ", value: formatJalali(new Date("2026-10-06T13:48:00"), "long") },
{ label: "مهلتِ حل", value: formatJalali(new Date("2026-10-07T20:22:00"), "long") },
]
/**
* جزئیاتِ تیکت از نگاهِ کارشناس: کنشها بالای گفتوگو، پیامِ درخواستدهنده با
* پیوستها، پاسخنویس پایینِ همان ستون، و پنلِ جزئیات و ویژگیها کنارش. تغییرِ
* ویژگیها با دکمهٔ «بهروزرسانی» ذخیره میشود تا تصادفی عوض نشود.
*/
export default function TicketDetailBlock() {
const [reply, setReply] = React.useState("")
const [dirty, setDirty] = React.useState(false)
const send = () => {
setReply("")
toast.success("پاسخ برای مهسا کریمی فرستاده شد.")
}
return (
<ControlSizeProvider size="xl" asChild>
<main className="mx-auto flex w-full max-w-6xl flex-col gap-6 p-4 sm:p-6">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="#">پشتیبانی</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>جزئیات تیکت</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<div className="grid grid-cols-1 items-start gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]">
<Card className="gap-0 py-0">
{/* کنشهای تیکت؛ کمکاربردها در منوی «بیشتر». */}
<div className="flex flex-wrap items-center gap-2 border-b border-border p-4">
<Button variant="outline">
<UserIcon />
ارجاع
</Button>
<Button variant="outline">بایگانی</Button>
<Button variant="outline">علامتِ خواندهنشده</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" aria-label="کنشهای بیشتر">
<MoreHorizontalIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>افزودنِ کار</DropdownMenuItem>
<DropdownMenuItem>ادغام با تیکتِ دیگر</DropdownMenuItem>
<DropdownMenuItem variant="destructive">
<TrashIcon />
هرزنامه
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="flex flex-col gap-8 p-6">
{/* عنوان و شماره در شروع، زمانِ پیام در پایانِ همان ردیف. */}
<div className="flex flex-wrap items-center justify-between gap-3">
<h1 className="flex flex-wrap items-center gap-3 text-xl font-bold">
افزودن روشِ پرداخت
<Badge color="destructive" variant="soft">
#{toFaDigits(1042)}
</Badge>
</h1>
<span className="flex items-center gap-1.5 text-sm text-muted-foreground">
<ClockIcon aria-hidden className="size-4" />
{toFaDigits("10:34")} (۵ دقیقه پیش)
</span>
</div>
{/* پیام: فرستنده و زمان یک ردیف، متن و پیوستها زیرش. */}
<article className="flex flex-col gap-6">
<header className="flex items-start gap-3">
<Avatar size="lg" alt="مهسا کریمی" src={stockImages.personWoman1.src} icon={<AvatarGenderIcon gender="female" />} />
<div className="flex min-w-0 flex-1 flex-col gap-0.5 text-sm">
<span>
<span className="font-semibold">مهسا کریمی</span>{" "}
<bdi dir="ltr" className="text-muted-foreground">
mahsa.k@example.com
</bdi>
</span>
<span className="text-muted-foreground">به: تیمِ پشتیبانی</span>
</div>
</header>
<div className="flex flex-col gap-4 leading-8">
<p>سلام وقت بخیر،</p>
<p>
میخواهم کارتِ بانکی دومم را بهعنوانِ روشِ پرداخت اضافه کنم، ولی بعد از واردکردنِ رمزِ پویا پیامِ «خطای
ناشناخته» میگیرم. رسید و تصویرِ خطا را پیوست کردهام.
</p>
<p>
ممنون،
<br />
مهسا کریمی
</p>
</div>
<ul className="grid grid-cols-1 gap-3 sm:grid-cols-2">
{ATTACHMENTS.map((file) => (
<li key={file.name} className="flex items-center gap-3 rounded-xl border border-border p-3">
<FileTypeIcon kind={file.kind} className="size-10" />
<span className="flex min-w-0 flex-1 flex-col">
<bdi dir="auto" className="truncate text-sm font-medium">
{file.name}
</bdi>
<span className="text-xs text-muted-foreground">{file.size}</span>
</span>
<Button variant="ghost" size="icon" aria-label={`دانلودِ ${file.name}`}>
<DownloadIcon />
</Button>
</li>
))}
</ul>
</article>
{/* پاسخنویس: برچسب، فیلدِ متن و کنشها؛ ارسال تا متنی نباشد غیرفعال است. */}
<div className="flex flex-col gap-2 border-t border-border pt-8">
<Label htmlFor="ticket-reply" className="font-normal text-muted-foreground">
پاسخ به <span className="font-medium text-foreground">مهسا کریمی</span>
</Label>
<Textarea id="ticket-reply" rows={4} value={reply} onChange={(event) => setReply(event.target.value)} placeholder="پاسخ را بنویسید…" />
<div className="flex items-center gap-2 pt-2">
<Button onClick={send} disabled={!reply.trim()}>
ارسال پاسخ
</Button>
<Button variant="ghost" size="icon" aria-label="پیوستِ فایل">
<PaperclipIcon />
</Button>
<Button variant="ghost" size="icon" aria-label="پاک کردنِ پیشنویس" className="ms-auto" disabled={!reply} onClick={() => setReply("")}>
<TrashIcon />
</Button>
</div>
</div>
</div>
</Card>
{/* پنلِ کناری: جزئیات، درخواستدهنده و ویژگیهای قابلویرایش. */}
<Card className="gap-0 p-6">
<section aria-labelledby="ticket-details" className="flex flex-col gap-4">
<h2 id="ticket-details" className="font-bold">
جزئیات
</h2>
<dl className="flex flex-col gap-3 text-sm">
{DETAILS.map((item) => (
<div key={item.label} className="flex items-center justify-between gap-3">
<dt className="text-muted-foreground">{item.label}</dt>
<dd className="text-end font-medium">{item.value}</dd>
</div>
))}
</dl>
</section>
<section aria-labelledby="ticket-requester" className="mt-8 flex flex-col gap-4 border-t border-border pt-8">
<h2 id="ticket-requester" className="font-bold">
درخواستدهنده
</h2>
<dl className="flex flex-col gap-3 text-sm">
<div className="flex items-center justify-between gap-3">
<dt className="text-muted-foreground">نام</dt>
<dd className="font-medium">مهسا کریمی</dd>
</div>
<div className="flex items-center justify-between gap-3">
<dt className="text-muted-foreground">ایمیل</dt>
<dd className="font-medium">
<bdi dir="ltr">mahsa.k@example.com</bdi>
</dd>
</div>
</dl>
</section>
<section aria-labelledby="ticket-props" className="mt-8 flex flex-col gap-4 border-t border-border pt-8">
<h2 id="ticket-props" className="font-bold">
ویژگیها
</h2>
{[
{ id: "type", label: "نوع", value: "question", options: [["question", "پرسش"], ["bug", "خطا"], ["request", "درخواست"]] },
{ id: "status", label: "وضعیت", value: "open", options: [["open", "باز"], ["pending", "در انتظار مشتری"], ["solved", "حلشده"]] },
{ id: "priority", label: "اولویت", value: "low", options: [["low", "کم"], ["medium", "متوسط"], ["high", "زیاد"]] },
{ id: "assignee", label: "کارشناس", value: "nima", options: [["nima", "نیما رضایی"], ["leila", "لیلا احمدی"], ["arash", "آرش نوری"]] },
].map((field) => (
<div key={field.id} className="flex flex-col gap-2">
<Label id={`ticket-${field.id}`}>{field.label}</Label>
<Select defaultValue={field.value} onValueChange={() => setDirty(true)}>
<SelectTrigger aria-labelledby={`ticket-${field.id}`} className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
{field.options.map(([value, text]) => (
<SelectItem key={value} value={value}>
{text}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
))}
<Button
fullWidth
className="mt-4"
disabled={!dirty}
onClick={() => {
setDirty(false)
toast.success("ویژگیهای تیکت بهروز شد.")
}}
>
بهروزرسانی
</Button>
</section>
</Card>
</div>
</main>
</ControlSizeProvider>
)
}