diff --git a/messages/en/classroom/en_classroom.json b/messages/en/classroom/en_classroom.json
index b31f79e36..c2f5c6c77 100644
--- a/messages/en/classroom/en_classroom.json
+++ b/messages/en/classroom/en_classroom.json
@@ -16,6 +16,7 @@
"description": "Description",
"descriptionPlaceholder": "Brief description of this classroom...",
"gradeLevel": "Grade Level",
+ "grade": "Grade",
"duration": "Duration",
"selectDuration": "Select Duration",
"startDate": "Start Date",
diff --git a/messages/vi/classroom/vi_classroom.json b/messages/vi/classroom/vi_classroom.json
index 12991308e..a255c54d3 100644
--- a/messages/vi/classroom/vi_classroom.json
+++ b/messages/vi/classroom/vi_classroom.json
@@ -79,6 +79,7 @@
"description": "Mô tả",
"descriptionPlaceholder": "Mô tả ngắn gọn về lớp học này...",
"gradeLevel": "Cấp lớp",
+ "grade": "Lớp",
"selectGrade": "Chọn cấp lớp",
"duration": "Thời lượng",
"selectDuration": "Chọn thời lượng",
diff --git a/src/features/classroom/components/upsert/ClassroomBasicInfo.tsx b/src/features/classroom/components/upsert/ClassroomBasicInfo.tsx
index 421e5e16b..8365e6497 100644
--- a/src/features/classroom/components/upsert/ClassroomBasicInfo.tsx
+++ b/src/features/classroom/components/upsert/ClassroomBasicInfo.tsx
@@ -8,22 +8,40 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { Calendar } from '@/components/shadcn/calendar'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/shadcn/popover'
import { Button } from '@/components/shadcn/button'
-import { CalendarIcon } from 'lucide-react'
+import { CalendarIcon, RefreshCw } from 'lucide-react'
import { format } from 'date-fns'
import { cn } from '@/utils/shadcn/utils'
import { useTranslations } from 'next-intl'
+import { Grade } from '@/features/classroom/types/classroom.type'
type ClassroomBasicInfoProps = {
form: any
organizationSubscriptionData: any
- gradeOptions: { label: string; value: string }[]
minDate: Date | undefined
maxDate: Date | undefined
}
-export default function ClassroomBasicInfo({ form, gradeOptions, minDate, maxDate }: ClassroomBasicInfoProps) {
+// Generate random class code like Google Meet (xxx-yyyy-zzz)
+const generateClassCode = () => {
+ const chars = 'abcdefghijklmnopqrstuvwxyz'
+ const randomString = (length: number) => {
+ let result = ''
+ for (let i = 0; i < length; i++) {
+ result += chars.charAt(Math.floor(Math.random() * chars.length))
+ }
+ return result
+ }
+ return `${randomString(3)}-${randomString(4)}-${randomString(3)}`
+}
+
+export default function ClassroomBasicInfo({ form, minDate, maxDate }: ClassroomBasicInfoProps) {
const tClassroom = useTranslations('classroom.create.step1')
+ const gradeOptions = Object.values(Grade).map((g) => ({
+ label: g.replace('Grade', 'Lớp'),
+ value: g.replace('Grade', 'Lớp')
+ }))
+
const DURATION_OPTIONS = [
{ label: `4 ${tClassroom('weeks')}`, value: '4' },
{ label: `6 ${tClassroom('weeks')}`, value: '6' },
@@ -42,10 +60,21 @@ export default function ClassroomBasicInfo({ form, gradeOptions, minDate, maxDat
const isCustomDuration = durationWeeks === 'custom'
+ // Generate initial class code
+ useEffect(() => {
+ const formClassCode = form.getFieldValue('classCode')
+ if (!formClassCode) {
+ const newCode = generateClassCode()
+ setClassCode(newCode)
+ form.setFieldValue('classCode', newCode)
+ } else {
+ setClassCode(formClassCode)
+ }
+ }, [])
+
// Sync với form khi mount (cho trường hợp edit)
useEffect(() => {
const formName = form.getFieldValue('name')
- const formClassCode = form.getFieldValue('classCode')
const formGrade = form.getFieldValue('grade')
const formDescription = form.getFieldValue('description')
const formDuration = form.getFieldValue('durationWeeks')
@@ -53,7 +82,6 @@ export default function ClassroomBasicInfo({ form, gradeOptions, minDate, maxDat
const formEndDate = form.getFieldValue('endDate')
if (formName) setName(formName)
- if (formClassCode) setClassCode(formClassCode)
if (formGrade) setGrade(formGrade)
if (formDescription) setDescription(formDescription)
if (formDuration) setDurationWeeks(formDuration)
@@ -107,6 +135,12 @@ export default function ClassroomBasicInfo({ form, gradeOptions, minDate, maxDat
}
}, [endDate])
+ const handleRegenerateCode = () => {
+ const newCode = generateClassCode()
+ setClassCode(newCode)
+ form.setFieldValue('classCode', newCode)
+ }
+
return (
@@ -127,12 +161,25 @@ export default function ClassroomBasicInfo({ form, gradeOptions, minDate, maxDat
-
setClassCode(e.target.value)}
- />
+
+
+
+
+
Auto-generated code • Click refresh to generate new
diff --git a/src/features/classroom/components/upsert/UpsertClassroom.tsx b/src/features/classroom/components/upsert/UpsertClassroom.tsx
index ef2e2ba9a..c39ea0f61 100644
--- a/src/features/classroom/components/upsert/UpsertClassroom.tsx
+++ b/src/features/classroom/components/upsert/UpsertClassroom.tsx
@@ -140,10 +140,6 @@ export default function UpsertClassroom({ classroomId, onSuccess }: UpsertClassr
)
const teacherOptions = getOptions(teacherData?.data.items, 'userName', 'imageUrl', 'email')
- const gradeOptions = Object.entries(Grade).map(([key, value]) => ({
- label: value,
- value: value
- }))
const [createClassroom, { isLoading: isCreating }] = useCreateClassroomMutation()
const [updateClassroom, { isLoading: isUpdating }] = useUpdateClassroomMutation()
@@ -230,7 +226,6 @@ export default function UpsertClassroom({ classroomId, onSuccess }: UpsertClassr
diff --git a/src/features/resource/quiz/components/player/QuizPlayerContainer.tsx b/src/features/resource/quiz/components/player/QuizPlayerContainer.tsx
index 60e2acc3b..f093410ad 100644
--- a/src/features/resource/quiz/components/player/QuizPlayerContainer.tsx
+++ b/src/features/resource/quiz/components/player/QuizPlayerContainer.tsx
@@ -3,18 +3,13 @@
import { useEffect } from 'react'
import { useIsMobile } from '@/hooks/use-mobile'
import { useAppDispatch, useAppSelector } from '@/hooks/redux-hooks'
-import QuizResult from '@/features/resource/quiz/components/player/QuizResult'
import QuizSidebar from '@/features/resource/quiz/components/player/QuizSidebar'
import QuizMainContent from '@/features/resource/quiz/components/player/QuizMainContent'
-import { useGetQuizByIdQuery } from '@/features/resource/quiz/api/quizApi'
import { initializeQuiz } from '@/features/resource/quiz/slice/quiz-player-slice'
-import LoadingComponent from '@/components/shared/loading/LoadingComponent'
import SEmpty from '@/components/shared/empty/SEmpty'
export default function QuizPlayerContainer() {
const dispatch = useAppDispatch()
- const { isSubmitted } = useAppSelector((state) => state.quizPlayer)
- const isMobile = useIsMobile()
const selectedQuiz = useAppSelector((state) => state.quizPlayer.selectedQuiz)
useEffect(() => {
diff --git a/src/features/resource/quiz/components/player/question/card/QuestionCard.tsx b/src/features/resource/quiz/components/player/question/card/QuestionCard.tsx
index c51012ab4..1ac175d3a 100644
--- a/src/features/resource/quiz/components/player/question/card/QuestionCard.tsx
+++ b/src/features/resource/quiz/components/player/question/card/QuestionCard.tsx
@@ -17,32 +17,6 @@ type QuestionCardProps = {
export default function QuestionCard({ question }: QuestionCardProps) {
const { isSubmitted, userAnswers, currentQuestionIndex } = useAppSelector((state) => state.quizPlayer)
- const getQuestionTypeLabel = (type: QuestionType) => {
- switch (type) {
- case QuestionType.TRUE_FALSE:
- return 'Đúng/Sai'
- case QuestionType.SINGLE_CHOICE:
- return 'Một đáp án'
- case QuestionType.MULTIPLE_CHOICE:
- return 'Nhiều đáp án'
- default:
- return type
- }
- }
-
- const getQuestionTypeColor = (type: QuestionType) => {
- switch (type) {
- case QuestionType.TRUE_FALSE:
- return 'bg-gradient-to-r from-blue-500 to-cyan-500'
- case QuestionType.SINGLE_CHOICE:
- return 'bg-gradient-to-r from-green-500 to-emerald-500'
- case QuestionType.MULTIPLE_CHOICE:
- return 'bg-gradient-to-r from-purple-500 to-pink-500'
- default:
- return 'bg-gradient-to-r from-gray-500 to-slate-500'
- }
- }
-
const isAnswered = userAnswers[question.id] !== undefined
return (
diff --git a/src/features/resource/quiz/components/player/question/types/TrueFalseQuestion.tsx b/src/features/resource/quiz/components/player/question/types/TrueFalseQuestion.tsx
index 0f946be4c..71351c290 100644
--- a/src/features/resource/quiz/components/player/question/types/TrueFalseQuestion.tsx
+++ b/src/features/resource/quiz/components/player/question/types/TrueFalseQuestion.tsx
@@ -19,87 +19,102 @@ export default function TrueFalseQuestion({ question }: TrueFalseQuestionProps)
const currentSelected = userAnswers[question.id]?.[0]
return (
-
- {[trueAnswer, falseAnswer].map((ans) => {
- if (!ans) return null
- const isChosen = currentSelected === ans.id
- const isCorrect = ans.isCorrect
- const isTrue = ans.content === 'True'
+
+ {/* Question Content */}
+
+
+
+ {question.orderIndex || 1}
+
+
+
{question.content}
+
+
+
- let containerClass = 'group relative overflow-hidden transition-all duration-300'
+ {/* True/False Options */}
+
+ {[trueAnswer, falseAnswer].map((ans) => {
+ if (!ans) return null
+ const isChosen = currentSelected === ans.id
+ const isCorrect = ans.isCorrect
+ const isTrue = ans.content === 'True'
- if (isSubmitted) {
- if (isCorrect) {
- containerClass += isTrue
- ? ' border-2 border-green-500 bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-xl'
- : ' border-2 border-red-500 bg-gradient-to-br from-red-500 to-pink-600 text-white shadow-xl'
- } else if (isChosen && !isCorrect) {
- containerClass += ' border-2 border-gray-400 bg-gray-100 opacity-70'
- } else {
- containerClass += ' border-2 border-gray-200 bg-white opacity-50'
- }
- } else {
- if (isChosen) {
- containerClass += isTrue
- ? ' border-2 border-green-600 bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-xl scale-105'
- : ' border-2 border-red-600 bg-gradient-to-br from-red-500 to-pink-600 text-white shadow-xl scale-105'
+ let containerClass = 'group relative overflow-hidden transition-all duration-300'
+
+ if (isSubmitted) {
+ if (isCorrect) {
+ containerClass += isTrue
+ ? ' border-2 border-green-500 bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-xl'
+ : ' border-2 border-red-500 bg-gradient-to-br from-red-500 to-pink-600 text-white shadow-xl'
+ } else if (isChosen && !isCorrect) {
+ containerClass += ' border-2 border-gray-400 bg-gray-100 opacity-70'
+ } else {
+ containerClass += ' border-2 border-gray-200 bg-white opacity-50'
+ }
} else {
- containerClass += isTrue
- ? ' border-2 border-green-200 bg-gradient-to-br from-green-50 to-emerald-50 hover:border-green-400 hover:shadow-lg hover:scale-105'
- : ' border-2 border-red-200 bg-gradient-to-br from-red-50 to-pink-50 hover:border-red-400 hover:shadow-lg hover:scale-105'
+ if (isChosen) {
+ containerClass += isTrue
+ ? ' border-2 border-green-600 bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-xl scale-105'
+ : ' border-2 border-red-600 bg-gradient-to-br from-red-500 to-pink-600 text-white shadow-xl scale-105'
+ } else {
+ containerClass += isTrue
+ ? ' border-2 border-green-200 bg-gradient-to-br from-green-50 to-emerald-50 hover:border-green-400 hover:shadow-lg hover:scale-105'
+ : ' border-2 border-red-200 bg-gradient-to-br from-red-50 to-pink-50 hover:border-red-400 hover:shadow-lg hover:scale-105'
+ }
}
- }
-
- return (
-
+ )
+ })}
+
)
}