diff --git a/messages/en/common/en_common.json b/messages/en/common/en_common.json index d86a72fe6..80a05b487 100644 --- a/messages/en/common/en_common.json +++ b/messages/en/common/en_common.json @@ -223,6 +223,12 @@ "endsoon": "End Soon", "inprogress": "In Progress" }, + "level": { + "all": "All Levels", + "beginner": "Beginner", + "intermidiate": "Intermidiate", + "advanced": "Advanced" + }, "accountType": { "accountTypeLabel": "Account Type", "admin": "Admin", diff --git a/messages/vi/common/vi_common.json b/messages/vi/common/vi_common.json index 95162d84a..435904dda 100644 --- a/messages/vi/common/vi_common.json +++ b/messages/vi/common/vi_common.json @@ -195,6 +195,12 @@ "badge": { "selected": "Chọn" }, + "level": { + "all": "Tất Cả Trình Độ", + "beginner": "Mới Bắt Đầu", + "intermidiate": "Trung Bình", + "advanced": "Nâng Cao" + }, "status": { "all": "Tất Cả", "filterBy": "Lọc Theo", diff --git a/src/features/assignment/components/detail/dialog/GradeAssignmentModal.tsx b/src/features/assignment/components/detail/dialog/GradeAssignmentModal.tsx index 97b78fd2c..6ea3924ab 100644 --- a/src/features/assignment/components/detail/dialog/GradeAssignmentModal.tsx +++ b/src/features/assignment/components/detail/dialog/GradeAssignmentModal.tsx @@ -69,7 +69,8 @@ export default function GradeAssignmentModal({ studentAssignmentId, onClose, onS attemptData.questionAttempts.forEach((qAttempt) => { initialScores[qAttempt.id] = {} qAttempt.rubricScore.forEach((criterion) => { - initialScores[qAttempt.id][criterion.rubricCriterionId] = (criterion as any).currentPoints ?? null + // accept either `currentPoints` or `points` depending on API response + initialScores[qAttempt.id][criterion.rubricCriterionId] = (criterion as any).currentPoints ?? (criterion as any).points ?? null }) }) @@ -203,7 +204,14 @@ export default function GradeAssignmentModal({ studentAssignmentId, onClose, onS : `Question #${index + 1}` const questionContent = originalQuestion?.content || '' - const currentTotalScore = calculateQuestionTotal(qAttempt.id) + const currentTotalScore = (() => { + const questionScores = scores[qAttempt.id] + if (questionScores) { + return Object.values(questionScores).reduce((acc, curr) => (acc || 0) + (curr || 0), 0) || 0 + } + // fallback to API values + return qAttempt.rubricScore.reduce((acc: number, c: any) => acc + ((c.currentPoints ?? c.points) || 0), 0) + })() const maxQuestionScore = calculateMaxPoints(qAttempt.rubricScore) return ( @@ -247,9 +255,10 @@ export default function GradeAssignmentModal({ studentAssignmentId, onClose, onS type="number" className='h-10 w-full rounded-md border-slate-200 bg-slate-50 pr-4 text-slate-900 focus:bg-white' value={ + // prefer edited state, otherwise fallback to API fields scores[qAttempt.id] && scores[qAttempt.id][criterion.rubricCriterionId] != null ? String(scores[qAttempt.id][criterion.rubricCriterionId]) - : '' + : String((criterion as any).currentPoints ?? (criterion as any).points ?? '') } onChange={(e) => handleScoreChange(qAttempt.id, criterion.rubricCriterionId, e.target.value)} placeholder="Nhập điểm" diff --git a/src/features/assignment/components/detail/dialog/SubmissionReviewDialog.tsx b/src/features/assignment/components/detail/dialog/SubmissionReviewDialog.tsx index 9cdd0d539..89c07b327 100644 --- a/src/features/assignment/components/detail/dialog/SubmissionReviewDialog.tsx +++ b/src/features/assignment/components/detail/dialog/SubmissionReviewDialog.tsx @@ -83,8 +83,9 @@ export function SubmissionReviewDialog({ submission, studentAssignmentId, onSucc attemptData.questionAttempts.forEach((qAttempt) => { initialScores[qAttempt.id] = {} qAttempt.rubricScore.forEach((criterion) => { + // accept either `currentPoints` (server-side naming) or `points` (alternate payload) initialScores[qAttempt.id][criterion.rubricCriterionId] = - (criterion as any).currentPoints ?? null + (criterion as any).currentPoints ?? (criterion as any).points ?? null }) }) @@ -273,10 +274,13 @@ export function SubmissionReviewDialog({ submission, studentAssignmentId, onSucc max={criterion.maxPoints} min={0} disabled={isReviewed || isGrading} - // 'null ?? ""' -> "" - // '0 ?? ""' -> 0 - // '5 ?? ""' -> 5 - value={scores[question.id]?.[criterion.rubricCriterionId] ?? ''} + value={ + // prefer edited state, otherwise fallback to API fields + scores[question.id]?.[criterion.rubricCriterionId] ?? + (criterion as any).currentPoints ?? + (criterion as any).points ?? + '' + } onChange={(e) => handleScoreChange(question.id, criterion.rubricCriterionId, e.target.value) } @@ -295,10 +299,10 @@ export function SubmissionReviewDialog({ submission, studentAssignmentId, onSucc

{t('modal.total')}:{' '} {question.rubricScore.reduce((sum, c) => { - const score = - scores[question.id]?.[c.rubricCriterionId] !== undefined - ? scores[question.id][c.rubricCriterionId] || 0 - : 0 + const key = c.rubricCriterionId + const scoreFromState = scores[question.id]?.[key] + const scoreFromApi = (c as any).currentPoints ?? (c as any).points + const score = scoreFromState !== undefined ? scoreFromState || 0 : scoreFromApi ?? 0 return sum + score }, 0)}{' '} / {question.rubricScore.reduce((sum, c) => sum + c.maxPoints, 0)} diff --git a/src/features/assignment/components/detail/table/AssignmentTable.tsx b/src/features/assignment/components/detail/table/AssignmentTable.tsx index 820a94138..bffa7db5e 100644 --- a/src/features/assignment/components/detail/table/AssignmentTable.tsx +++ b/src/features/assignment/components/detail/table/AssignmentTable.tsx @@ -48,7 +48,8 @@ function mapApiToSubmissions(students: StudentStatistic[], assignmentTitle: stri const latestAttempt = student.attempts.length > 0 ? student.attempts[student.attempts.length - 1] : null let grade: string | null = null - if (latestAttempt && (latestAttempt.status === 'Graded' || latestAttempt.status === 'Passed')) { + // Show grade for attempts that have been reviewed (Graded, Passed, or Failed) + if (latestAttempt && (latestAttempt.status === 'Graded' || latestAttempt.status === 'Passed' || latestAttempt.status === 'Failed')) { grade = `${latestAttempt.totalScore}` } diff --git a/src/features/resource/course/components/detail/AdminCourseDetail.tsx b/src/features/resource/course/components/detail/AdminCourseDetail.tsx index 6a1697718..94989dc21 100644 --- a/src/features/resource/course/components/detail/AdminCourseDetail.tsx +++ b/src/features/resource/course/components/detail/AdminCourseDetail.tsx @@ -19,20 +19,19 @@ import { import LessonTable from '@/features/resource/lesson/components/list/LessonTable' import { useModal } from '@/providers/ModalProvider' import { getLevelBadgeClass, getStatusBadgeClass } from '@/utils/badgeColor' -import { capitalizeFirst } from '@/utils/index' +import { capitalizeFirst, useLevelTranslation, useStatusTranslation } from '@/utils/index' import { useAppDispatch, useAppSelector } from '@/hooks/redux-hooks' import { resetParams, setPageIndex, setPageSize } from '@/features/resource/lesson/slice/lessonSlice' -import { UserRole } from '@/types/userRole' import { useLazyExportToRSAQuery } from '@/features/resource/export/api/exportApi' import ExportRSAButton from '@/components/shared/button/ExportRSAButton' -import CurriculumKitList from '@/features/resource/kit/components/list/KitListSection' import KitListSection from '@/features/resource/kit/components/list/KitListSection' -import { Card } from '@/components/shadcn/card' export default function AdminCourseDetail() { const t = useTranslations('Admin.course_details') const tt = useTranslations('toast') const tc = useTranslations('common') + const translateStatus = useStatusTranslation() + const translateLevel = useLevelTranslation() // Get current user const user = useAppSelector((state) => state.auth.user) @@ -148,14 +147,14 @@ export default function AdminCourseDetail() {

{t('status')}:{' '} - {capitalizeFirst(course.data.status)} + {translateStatus(course.data.status)} {t('age')}: {course.data.ageRangeLabel} {t('level')}:{' '} - {capitalizeFirst(course.data.level)} + {translateLevel(course.data.level)}

diff --git a/src/features/resource/course/components/upsert/UpsertCourse.tsx b/src/features/resource/course/components/upsert/UpsertCourse.tsx index ecf3eeaa3..69c0cd873 100644 --- a/src/features/resource/course/components/upsert/UpsertCourse.tsx +++ b/src/features/resource/course/components/upsert/UpsertCourse.tsx @@ -29,8 +29,7 @@ const defaultCourseData: CourseFormData = { prerequisites: '', studentTasks: '', level: CourseLevel.BEGINNER, - imageUrl: null as any, - price: 1000 + imageUrl: null as any } async function CreateCourseJsonPayload(data: CourseFormData, userId: string) { @@ -50,8 +49,7 @@ async function CreateCourseJsonPayload(data: CourseFormData, userId: string) { studentTasks: data.studentTasks, prerequisites: data.prerequisites, level: data.level, - image: imageBase64, - price: data.price + image: imageBase64 } } @@ -68,7 +66,6 @@ async function PatchCourseJsonPayload(oldData: CourseFormData, newData: CourseFo if (oldData.code !== newData.code) patchData.code = newData.code if (oldData.prerequisites !== newData.prerequisites) patchData.prerequisites = newData.prerequisites if (oldData.level !== newData.level) patchData.level = newData.level - if (oldData.price !== newData.price) patchData.price = newData.price if (newData.imageUrl && typeof newData.imageUrl !== 'string') { const base64 = await fileToBase64(newData.imageUrl) @@ -101,8 +98,7 @@ function mapCourseToFormData(course: ApiSuccessResponse): CourseFormData prerequisites: course.data.prerequisites ?? '', ageRangeId: course.data.ageRangeId?.toString() ?? '', imageUrl: null as any, - imagePreviewUrl: course.data.imageUrl ?? undefined, - price: course.data.price ?? 0 + imagePreviewUrl: course.data.imageUrl ?? undefined } } diff --git a/src/features/resource/course/forms/courseForm.schema.ts b/src/features/resource/course/forms/courseForm.schema.ts index 3661b0e11..1c544baac 100644 --- a/src/features/resource/course/forms/courseForm.schema.ts +++ b/src/features/resource/course/forms/courseForm.schema.ts @@ -14,8 +14,7 @@ export function useCourseSchemas() { slug: z.string().optional(), description: z.string().min(50, tv('course.description', { length: 50 })), ageRangeId: z.string().min(1, tv('course.ageRangeId')), - imagePreviewUrl: z.string().optional(), - price: z.number().min(1000, tv('course.priceMin', { min: 1000 })) + imagePreviewUrl: z.string().optional() }) const createCourseSchema = baseCourseSchema.extend({ diff --git a/src/utils/index.ts b/src/utils/index.ts index d8c3470d5..a35add1b0 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -175,3 +175,10 @@ export const useStatusTranslation = () => { return tc(status.toLowerCase()) } } + +export const useLevelTranslation = () => { + const tc = useTranslations('common.level') + return (level: string) => { + return tc(level.toLowerCase()) + } +}