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

مدیریت فایل‌ها

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

بارگذاری، فضای مصرفی، جست‌وجو و جدول فایل‌ها با عملیات.

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

این بلاک از ۱۱ آیتم دیگ استفاده می‌کند: buttoncontrol-sizedropdown-menuempty-statefile-uploadiconspersianprogresssearch-fieldtabletoast

پکیج‌منیجر پیش‌فرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیج‌منیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/dropdown-menu.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/file-upload.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/progress.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/table.json https://docs.digdesign.ir/r/toast.json
pnpm dlx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/dropdown-menu.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/file-upload.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/progress.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/table.json https://docs.digdesign.ir/r/toast.json
yarn dlx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/dropdown-menu.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/file-upload.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/progress.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/table.json https://docs.digdesign.ir/r/toast.json
bunx --bun digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/dropdown-menu.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/file-upload.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/progress.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/table.json https://docs.digdesign.ir/r/toast.json

۲. کد صفحه

کد را در app/<route>/page.tsx بگذارید و متن‌ها، داده و هندلرها را با محصول خودتان عوض کنید.

file-manager.tsx
"use client"

import * as React from "react"

import { DownloadIcon, EditIcon, MoreHorizontalIcon, TrashIcon } from "@/components/icons"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { EmptyState, EmptyStateDescription, EmptyStateTitle } from "@/components/ui/empty-state"
import { FileUpload } from "@/components/ui/file-upload"
import { Progress } from "@/components/ui/progress"
import { SearchField } from "@/components/ui/search-field"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { toast } from "@/components/ui/toast"
import { formatJalali, formatNumber, toSearchKey } from "@/lib/persian"

type StoredFile = { id: string; name: string; size: number; date: string }

const INITIAL: StoredFile[] = [
  { id: "1", name: "قرارداد-همکاری-۱۴۰۵.pdf", size: 1_240_000, date: "2026-09-28" },
  { id: "2", name: "گزارش-فروش-شهریور.xlsx", size: 380_000, date: "2026-09-22" },
  { id: "3", name: "لوگو-نهایی.png", size: 96_000, date: "2026-09-10" },
]
const QUOTA = 50 * 1024 * 1024

function formatSize(bytes: number) {
  if (bytes >= 1024 * 1024) return `${formatNumber(Number((bytes / 1024 / 1024).toFixed(1)))} مگابایت`
  return `${formatNumber(Math.round(bytes / 1024))} کیلوبایت`
}

export default function FileManagerBlock() {
  const [files, setFiles] = React.useState(INITIAL)
  const [query, setQuery] = React.useState("")
  const used = files.reduce((sum, file) => sum + file.size, 0)
  const visible = files.filter((file) => toSearchKey(file.name).includes(toSearchKey(query)))

  return (
    <ControlSizeProvider size="xl" asChild>
    <main className="mx-auto flex w-full max-w-4xl flex-col gap-6 p-4 sm:p-6">
      <div className="flex flex-wrap items-end justify-between gap-4">
        <div className="flex flex-col gap-1">
          <h1 className="text-2xl font-bold">فایل‌ها</h1>
          <p className="text-sm text-muted-foreground">اسناد و تصاویر حساب؛ هر فایل تا ۱۰ مگابایت.</p>
        </div>
        <div className="flex w-full max-w-xs flex-col gap-1 text-sm">
          <Progress value={(used / QUOTA) * 100} size="sm" aria-label="فضای استفاده‌شده" />
          <span className="text-muted-foreground">
            {formatSize(used)} از {formatSize(QUOTA)}
          </span>
        </div>
      </div>

      <FileUpload
        name="files"
        multiple
        maxSize={10 * 1024 * 1024}
        hint="هر فایل تا ۱۰ مگابایت"
        onFilesChange={(added) =>
          setFiles((list) => [
            ...added
              .filter((file) => !list.some((item) => item.name === file.name))
              .map((file) => ({ id: `${file.name}-${file.size}`, name: file.name, size: file.size, date: new Date().toISOString() })),
            ...list,
          ])
        }
      />

      <SearchField aria-label="جست‌وجو در فایل‌ها" placeholder="نام فایل" value={query} onValueChange={setQuery} className="max-w-sm" />

      {visible.length === 0 ? (
        <EmptyState variant="bordered">
          <EmptyStateTitle>{files.length === 0 ? "هنوز فایلی بارگذاری نکرده‌اید" : "فایلی با این نام پیدا نشد"}</EmptyStateTitle>
          <EmptyStateDescription>فایل‌ها را در کادر بالا رها کنید.</EmptyStateDescription>
        </EmptyState>
      ) : (
        <Table stackOnMobile>
          <TableHeader>
            <TableRow>
              <TableHead>نام</TableHead>
              <TableHead>حجم</TableHead>
              <TableHead>تاریخ</TableHead>
              <TableHead className="w-12">
                <span className="sr-only">عملیات</span>
              </TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            {visible.map((file) => (
              <TableRow key={file.id}>
                <TableCell className="max-w-64 truncate">
                  <bdi>{file.name}</bdi>
                </TableCell>
                <TableCell className="text-muted-foreground">{formatSize(file.size)}</TableCell>
                <TableCell className="text-muted-foreground">{formatJalali(new Date(file.date))}</TableCell>
                <TableCell>
                  <DropdownMenu>
                    <DropdownMenuTrigger asChild>
                      <Button variant="ghost" size="icon" aria-label={`عملیات ${file.name}`}>
                        <MoreHorizontalIcon />
                      </Button>
                    </DropdownMenuTrigger>
                    <DropdownMenuContent align="end">
                      <DropdownMenuItem>
                        <DownloadIcon />
                        دانلود
                      </DropdownMenuItem>
                      <DropdownMenuItem>
                        <EditIcon />
                        تغییر نام
                      </DropdownMenuItem>
                      <DropdownMenuSeparator />
                      <DropdownMenuItem
                        variant="destructive"
                        onSelect={() => {
                          const index = files.findIndex((item) => item.id === file.id)
                          setFiles((list) => list.filter((item) => item.id !== file.id))
                          toast(`«${file.name}» حذف شد.`, { action: { label: "برگردان", onClick: () => setFiles((list) => (list.some((item) => item.id === file.id) ? list : [...list.slice(0, index), file, ...list.slice(index)])) } })
                        }}
                      >
                        <TrashIcon />
                        حذف
                      </DropdownMenuItem>
                    </DropdownMenuContent>
                  </DropdownMenu>
                </TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
      )}
    </main>
    </ControlSizeProvider>
  )
}