تیکت پشتیبانی
پیشنمایش تمامصفحهثبت تیکت با بخش، پیام، توضیح صوتی و پیوست، و فهرست تیکتهای قبلی.
۱. نصب کامپوننتها
این بلاک از ۱۲ آیتم دیگ استفاده میکند: badgebuttoncontrol-sizefile-uploadiconslabelpersianradio-grouptext-fieldtextareatoastvoice-recorder
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.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/radio-group.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/textarea.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/voice-recorder.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.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/radio-group.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/textarea.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/voice-recorder.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.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/radio-group.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/textarea.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/voice-recorder.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.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/radio-group.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/textarea.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/voice-recorder.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
support-ticket.tsx
"use client"
import * as React from "react"
import { ChevronLeftIcon, ClockIcon, CreditCardIcon, SettingsIcon, TruckIcon } from "@/components/icons"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { FileUpload } from "@/components/ui/file-upload"
import { Label } from "@/components/ui/label"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { TextField } from "@/components/ui/text-field"
import { Textarea } from "@/components/ui/textarea"
import { toast } from "@/components/ui/toast"
import { VoiceRecorder } from "@/components/ui/voice-recorder"
import { formatRelativeTime, toFaDigits } from "@/lib/persian"
type Status = "open" | "answered" | "closed"
const STATUS: Record<Status, { text: string; color: "warning" | "success" | "default" }> = {
open: { text: "در انتظار پاسخ", color: "warning" },
answered: { text: "پاسخ داده شد", color: "success" },
closed: { text: "بسته", color: "default" },
}
const TICKETS: { id: string; subject: string; date: string; status: Status }[] = [
{ id: "5021", subject: "کسر وجه بدون ثبت سفارش", date: "2026-09-29T14:00:00", status: "answered" },
{ id: "5003", subject: "فاکتورِ رسمی برای شرکت", date: "2026-09-26T10:15:00", status: "open" },
{ id: "4988", subject: "تغییر آدرس تحویل", date: "2026-09-24T09:30:00", status: "closed" },
]
const DEPARTMENTS = [
{ value: "financial", label: "مالی و پرداخت", icon: <CreditCardIcon /> },
{ value: "orders", label: "سفارش و ارسال", icon: <TruckIcon /> },
{ value: "technical", label: "فنی", icon: <SettingsIcon /> },
]
const optionCard =
"group flex items-center gap-3 rounded-xl border border-border p-4 transition-colors has-[[data-state=checked]]:border-primary has-[[data-state=checked]]:bg-primary-subtle has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring"
/**
* درخواستِ پشتیبانی: انتخابِ بخش با کارتهای آیکوندار، موضوع و شرح، پیامِ صوتی و پیوست؛
* ستونِ کنار زمانِ پاسخ و درخواستهای قبلی را بهصورتِ ردیفهای قابلِ کلیک نشان میدهد.
*/
export default function SupportTicketBlock() {
const [sending, setSending] = React.useState(false)
const [department, setDepartment] = React.useState("financial")
async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault()
setSending(true)
await new Promise((resolve) => setTimeout(resolve, 800))
setSending(false)
toast.success("تیکت ثبت شد.", { description: "کد پیگیری ۵۰۲۲" })
}
return (
<ControlSizeProvider size="xl" asChild>
<main className="mx-auto grid grid-cols-1 w-full max-w-5xl items-start gap-8 p-4 sm:p-6 lg:grid-cols-[minmax(0,1fr)_20rem]">
<form onSubmit={onSubmit} className="flex flex-col gap-8 rounded-xl border border-border bg-card p-6 sm:p-8">
<div className="flex flex-col gap-1">
<h1 className="text-2xl font-bold">درخواستِ پشتیبانی</h1>
<p className="text-muted-foreground">هر چه دقیقتر بنویسید، سریعتر پاسخ میگیرید.</p>
</div>
<fieldset className="flex flex-col gap-3">
<legend className="mb-3 text-sm font-medium">بخش</legend>
<RadioGroup name="department" value={department} onValueChange={setDepartment} className="grid grid-cols-1 gap-3 sm:grid-cols-3">
{DEPARTMENTS.map((item) => (
<div key={item.value} className={optionCard}>
<RadioGroupItem id={`dep-${item.value}`} value={item.value} className="sr-only" />
<span aria-hidden className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-subtle group-has-[[data-state=checked]]:bg-background [&_svg]:size-5">
{item.icon}
</span>
<Label htmlFor={`dep-${item.value}`} className="flex-1">
{item.label}
</Label>
</div>
))}
</RadioGroup>
</fieldset>
<div className="flex flex-col gap-5">
<TextField label="موضوع" name="subject" required />
<div className="flex flex-col gap-2">
<Label htmlFor="message">شرحِ درخواست</Label>
<Textarea id="message" name="message" rows={5} placeholder="چه اتفاقی افتاد و انتظار داشتید چه شود؟" required />
</div>
<div className="flex flex-col gap-2">
<Label>توضیحِ صوتی</Label>
<VoiceRecorder name="voice" maxDuration={60} />
</div>
<FileUpload name="attachments" multiple maxFiles={3} accept="image/*,.pdf" hint="تصویر یا PDF، حداکثر ۳ فایل" />
</div>
<div className="flex justify-end border-t border-border pt-8">
<Button type="submit" loading={sending}>
ثبتِ درخواست
</Button>
</div>
</form>
<aside className="flex flex-col gap-6">
<div className="flex items-center gap-3 rounded-xl bg-subtle p-5">
<span aria-hidden className="flex size-10 items-center justify-center rounded-lg bg-success-subtle text-success [&_svg]:size-5">
<ClockIcon />
</span>
<div className="flex flex-col">
<span className="font-semibold">پاسخ در کمتر از ۴ ساعت</span>
<span className="text-sm text-muted-foreground">شنبه تا پنجشنبه، ۸ تا ۲۰</span>
</div>
</div>
<section aria-labelledby="tickets-title" className="flex flex-col gap-3">
<h2 id="tickets-title" className="font-semibold">
درخواستهای قبلی
</h2>
<ul className="flex flex-col gap-2">
{TICKETS.map((ticket) => (
<li key={ticket.id}>
<Button variant="outline" className="h-auto w-full justify-between gap-3 py-3 text-start font-normal">
<span className="flex flex-col items-start gap-1">
<span className="font-medium">{ticket.subject}</span>
<span className="text-xs text-muted-foreground">
#{toFaDigits(ticket.id)} · {formatRelativeTime(ticket.date)}
</span>
<Badge variant="soft" color={STATUS[ticket.status].color}>
{STATUS[ticket.status].text}
</Badge>
</span>
<ChevronLeftIcon aria-hidden />
</Button>
</li>
))}
</ul>
</section>
</aside>
</main>
</ControlSizeProvider>
)
}