Forráskód Böngészése

feat: add download option in evaluation actions

Mohammad Mahdi Salimi 2 hete
szülő
commit
c41f781585

+ 1 - 1
src/constants/evaluations/evaluations-constants.ts

@@ -4,5 +4,5 @@ export const evaluationsStatus: Record<EvaluationStatus, string> = {
   approved: 'تکمیل شده',
   approved_ai: 'در انتظار تایید',
   pending: 'در حال بررسی',
-  reject: 'رد شده',
+  rejected: 'رد شده',
 }

+ 45 - 1
src/features/evaluations/components/data-table-row-actions.tsx

@@ -1,6 +1,8 @@
+import { useState } from 'react'
 import { DotsHorizontalIcon } from '@radix-ui/react-icons'
 import { type Row } from '@tanstack/react-table'
-import { CircleCheck, Eye, Pencil, RefreshCcw } from 'lucide-react'
+import { CircleCheck, Download, Eye, Pencil, RefreshCcw } from 'lucide-react'
+import { toast } from 'sonner'
 import { Button } from '@/components/ui/button'
 import {
   DropdownMenu,
@@ -10,8 +12,10 @@ import {
   DropdownMenuTrigger,
 } from '@/components/ui/dropdown-menu'
 
+import { evaluationsService } from '@/services/evaluations.service'
 import { type Evaluation } from '@/types/evaluations.types'
 import { useEvaluations } from './evaluations-provider'
+import { downloadFromUrl } from '@/lib/downloadFromUrl'
 
 interface Props {
   row: Row<Evaluation>
@@ -19,6 +23,7 @@ interface Props {
 
 export function DataTableRowActions({ row }: Props) {
   const { setOpen, setCurrentRow } = useEvaluations()
+  const [isDownloading, setIsDownloading] = useState(false)
 
   const evaluations = row.original
 
@@ -42,6 +47,35 @@ export function DataTableRowActions({ row }: Props) {
     setOpen('edit')
   }
 
+  const handleDownloadVideo = async () => {
+    setIsDownloading(true)
+    const toastId = toast.loading('در حال دانلود ویدیو...')
+
+    try {
+      let media = evaluations.media?.[0]
+
+      if (!media?.url) {
+        const detail = await evaluationsService.detail(evaluations.id)
+        media = detail?.media?.[0]
+      }
+
+      if (!media?.url) {
+        toast.error('ویدیویی برای دانلود یافت نشد.', { id: toastId })
+        return
+      }
+
+      const filename =
+        media.name || `evaluation-${evaluations.id}.${media.extension || 'mp4'}`
+
+      await downloadFromUrl(media.url, filename)
+      toast.success('دانلود ویدیو شروع شد.', { id: toastId })
+    } catch {
+      toast.error('خطا در دانلود ویدیو!', { id: toastId })
+    } finally {
+      setIsDownloading(false)
+    }
+  }
+
   return (
     <DropdownMenu>
       <DropdownMenuTrigger asChild>
@@ -68,6 +102,16 @@ export function DataTableRowActions({ row }: Props) {
           </DropdownMenuShortcut>
         </DropdownMenuItem>
 
+        <DropdownMenuItem
+          onClick={handleDownloadVideo}
+          disabled={isDownloading}
+        >
+          دانلود ویدیو
+          <DropdownMenuShortcut>
+            <Download size={16} />
+          </DropdownMenuShortcut>
+        </DropdownMenuItem>
+
         <DropdownMenuItem
           onClick={handleShowConfirmApprove}
           disabled={evaluations?.status !== 'approved_ai'}

+ 1 - 1
src/features/evaluations/components/evaluations-columns.tsx

@@ -115,7 +115,7 @@ export const evaluationColumns: ColumnDef<Evaluation>[] = [
     cell: ({ row }) => {
       const status = row.original?.status
       const variant =
-        status === 'reject'
+        status === 'rejected'
           ? 'destructive'
           : status === 'pending'
             ? 'outline'

+ 1 - 1
src/features/evaluations/components/evaluations-table.tsx

@@ -123,7 +123,7 @@ export function EvaluationsTable({
               { label: 'تکمیل شده', value: 'approved' },
               { label: 'در انتظار تایید', value: 'approved_ai' },
               { label: 'در حال بررسی', value: 'pending' },
-              { label: 'رد شده', value: 'reject' },
+              { label: 'رد شده', value: 'rejected' },
             ],
           },
         ]}

+ 25 - 0
src/lib/downloadFromUrl.ts

@@ -0,0 +1,25 @@
+export async function downloadFromUrl(url: string, filename: string) {
+  try {
+    const response = await fetch(url)
+    if (!response.ok) throw new Error('download failed')
+
+    const blob = await response.blob()
+    const objectUrl = URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = objectUrl
+    link.download = filename
+    document.body.appendChild(link)
+    link.click()
+    link.remove()
+    URL.revokeObjectURL(objectUrl)
+  } catch {
+    const link = document.createElement('a')
+    link.href = url
+    link.download = filename
+    link.target = '_blank'
+    link.rel = 'noopener noreferrer'
+    document.body.appendChild(link)
+    link.click()
+    link.remove()
+  }
+}

+ 1 - 1
src/routes/_authenticated/evaluations/index.tsx

@@ -10,7 +10,7 @@ const evaluationsSearchSchema = z.object({
       z.literal('approved'),
       z.literal('approved_ai'),
       z.literal('pending'),
-      z.literal('reject'),
+      z.literal('rejected'),
     ])
     .optional()
     .catch(undefined),

+ 1 - 1
src/types/evaluations.types.ts

@@ -4,7 +4,7 @@ export type EvaluationStatus =
   | 'approved'
   | 'approved_ai'
   | 'pending'
-  | 'reject'
+  | 'rejected'
 
 export interface Evaluation {
   id: number