Ver código fonte

feat: implement approve evaluation

Mohammad Mahdi Salimi 2 semanas atrás
pai
commit
28ca0430fa

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

@@ -1,5 +1,8 @@
-export const evaluationsStatus = {
+import { type EvaluationStatus } from '@/types/evaluations.types'
+
+export const evaluationsStatus: Record<EvaluationStatus, string> = {
   approved: 'تکمیل شده',
+  approved_ai: 'در انتظار تایید',
   pending: 'در حال بررسی',
   reject: 'رد شده',
 }

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

@@ -1,6 +1,6 @@
 import { DotsHorizontalIcon } from '@radix-ui/react-icons'
 import { type Row } from '@tanstack/react-table'
-import { Eye, RefreshCcw } from 'lucide-react'
+import { CircleCheck, Eye, RefreshCcw } from 'lucide-react'
 import { Button } from '@/components/ui/button'
 import {
   DropdownMenu,
@@ -32,6 +32,11 @@ export function DataTableRowActions({ row }: Props) {
     setOpen('view')
   }
 
+  const handleShowConfirmApprove = () => {
+    setCurrentRow(evaluations)
+    setOpen('approve')
+  }
+
   return (
     <DropdownMenu>
       <DropdownMenuTrigger asChild>
@@ -58,6 +63,16 @@ export function DataTableRowActions({ row }: Props) {
           </DropdownMenuShortcut>
         </DropdownMenuItem>
 
+        <DropdownMenuItem
+          onClick={handleShowConfirmApprove}
+          disabled={evaluations?.status !== 'approved_ai'}
+        >
+          تایید ارزیابی
+          <DropdownMenuShortcut>
+            <CircleCheck size={16} />
+          </DropdownMenuShortcut>
+        </DropdownMenuItem>
+
         {/* <DropdownMenuSeparator /> */}
 
         {/* <DropdownMenuItem onClick={handleDelete} className='text-red-600'>

+ 66 - 0
src/features/evaluations/components/evaluations-approve-dialog.tsx

@@ -0,0 +1,66 @@
+import { ConfirmDialog } from '@/components/confirm-dialog'
+import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
+import { useUpdateEvaluationStatus } from '@/hooks/evaluations/useUpdateEvaluationStatus'
+import { type Evaluation } from '@/types/evaluations.types'
+import { CircleCheck } from 'lucide-react'
+import { toast } from 'sonner'
+
+interface Props {
+  open: boolean
+  onOpenChange: (open: boolean) => void
+  currentRow: Evaluation | null
+}
+
+export function EvaluationsApproveDialog({
+  open,
+  onOpenChange,
+  currentRow,
+}: Props) {
+  const { mutate, isPending } = useUpdateEvaluationStatus()
+
+  if (!currentRow) return null
+
+  const handleApprove = () => {
+    mutate(
+      {
+        id: currentRow.id,
+        payload: { status: 'approved' },
+      },
+      {
+        onSuccess: () => {
+          toast.success('ارزیابی با موفقیت تایید شد.')
+          onOpenChange(false)
+        },
+
+        onError: () => {
+          toast.error('خطا در تایید ارزیابی!')
+        },
+      }
+    )
+  }
+
+  return (
+    <ConfirmDialog
+      open={open}
+      onOpenChange={onOpenChange}
+      title={
+        <span>
+          <CircleCheck className='me-1 inline-block' size={18} /> تایید ارزیابی
+        </span>
+      }
+      desc={
+        <Alert variant='default'>
+          <AlertTitle>توجه!</AlertTitle>
+          <AlertDescription>
+            با تایید این ارزیابی، وضعیت آن به تکمیل شده تغییر می‌کند. این عملیات
+            را با اطمینان انجام دهید.
+          </AlertDescription>
+        </Alert>
+      }
+      confirmText='تایید ارزیابی'
+      cancelBtnText='انصراف'
+      handleConfirm={handleApprove}
+      isLoading={isPending}
+    />
+  )
+}

+ 8 - 2
src/features/evaluations/components/evaluations-columns.tsx

@@ -119,9 +119,15 @@ export const evaluationColumns: ColumnDef<Evaluation>[] = [
           ? 'destructive'
           : status === 'pending'
             ? 'outline'
-            : 'secondary'
+            : status === 'approved_ai'
+              ? 'default'
+              : 'secondary'
 
-      return <Badge variant={variant}>{evaluationsStatus[status!]}</Badge>
+      return (
+        <Badge variant={variant}>
+          {status ? evaluationsStatus[status] : '-'}
+        </Badge>
+      )
     },
   },
 

+ 7 - 0
src/features/evaluations/components/evaluations-dialogs.tsx

@@ -1,4 +1,5 @@
 import { EvaluationsActionDialog } from './evaluations-action-dialog'
+import { EvaluationsApproveDialog } from './evaluations-approve-dialog'
 import { useEvaluations } from './evaluations-provider'
 import { EvaluationsRetryDialog } from './evaluations-retry-dialog'
 
@@ -13,6 +14,12 @@ export function EvaluationsDialogs() {
         onOpenChange={() => setOpen(null)}
         currentRow={currentRow}
       />
+
+      <EvaluationsApproveDialog
+        open={open === 'approve'}
+        onOpenChange={() => setOpen(null)}
+        currentRow={currentRow}
+      />
     </>
   )
 }

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

@@ -1,7 +1,7 @@
 import { type Evaluation } from '@/types/evaluations.types'
 import React, { createContext, useContext, useState } from 'react'
 
-type EvaluationsDialogType = 'retry' | 'view'
+type EvaluationsDialogType = 'retry' | 'view' | 'approve'
 
 interface EvaluationsContextType {
   open: EvaluationsDialogType | null

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

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

+ 28 - 0
src/hooks/evaluations/useUpdateEvaluationStatus.tsx

@@ -0,0 +1,28 @@
+import { queryKeys } from '@/core/react-query/keys'
+import { evaluationsService } from '@/services/evaluations.service'
+import { type UpdateEvaluationStatus } from '@/types/evaluations.types'
+import { useMutation, useQueryClient } from '@tanstack/react-query'
+
+type UpdateEvaluationStatusVariables = {
+  id: number
+  payload: UpdateEvaluationStatus
+}
+
+export function useUpdateEvaluationStatus() {
+  const queryClient = useQueryClient()
+
+  return useMutation({
+    mutationFn: ({ id, payload }: UpdateEvaluationStatusVariables) =>
+      evaluationsService.updateStatus(id, payload),
+
+    onSuccess: (_, variables) => {
+      queryClient.invalidateQueries({
+        queryKey: queryKeys.evaluations.detail(variables.id),
+      })
+
+      queryClient.invalidateQueries({
+        queryKey: queryKeys.evaluations.lists(),
+      })
+    },
+  })
+}

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

@@ -6,7 +6,12 @@ const evaluationsSearchSchema = z.object({
   page: z.coerce.number().optional().catch(1),
   pageSize: z.coerce.number().optional().catch(10),
   status: z
-    .union([z.literal('approved'), z.literal('pending'), z.literal('reject')])
+    .union([
+      z.literal('approved'),
+      z.literal('approved_ai'),
+      z.literal('pending'),
+      z.literal('reject'),
+    ])
     .optional()
     .catch(undefined),
 })

+ 12 - 0
src/services/evaluations.service.ts

@@ -5,6 +5,7 @@ import type {
   GetEvaluationDetailResponse,
   GetEvaluationItems,
   GetEvaluationsResponse,
+  UpdateEvaluationStatus,
 } from '@/types/evaluations.types'
 
 const BASE_URL = '/admin/evaluations'
@@ -50,4 +51,15 @@ export const evaluationsService = {
     )
     return res?.data
   },
+
+  updateStatus: async (
+    evaluationId: number | string,
+    payload: UpdateEvaluationStatus
+  ) => {
+    const res = await http.post<ApiResponse<void>>(
+      `${BASE_URL}/${evaluationId}/status`,
+      payload
+    )
+    return res?.data
+  },
 }

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

@@ -1,5 +1,11 @@
 import { type Pagination } from './common.types'
 
+export type EvaluationStatus =
+  | 'approved'
+  | 'approved_ai'
+  | 'pending'
+  | 'reject'
+
 export interface Evaluation {
   id: number
   score: null | string
@@ -7,7 +13,7 @@ export interface Evaluation {
   cons_comment: null | string
   suggestion: null | string
   summary: null | string
-  status: 'approved' | 'pending' | 'reject' | null
+  status: EvaluationStatus | null
   media: Media[]
   jalali_date: string
   items: Item[]
@@ -149,3 +155,7 @@ export interface CreateEvaluation {
     score: number
   }
 }
+
+export interface UpdateEvaluationStatus {
+  status: EvaluationStatus
+}