diff --git a/Frontend/grader-frontend/src/app/instructor/class/[class_id]/assignments/[assignment_id]/page.tsx b/Frontend/grader-frontend/src/app/instructor/class/[class_id]/assignments/[assignment_id]/page.tsx index 74cd64e7..5a354e6c 100644 --- a/Frontend/grader-frontend/src/app/instructor/class/[class_id]/assignments/[assignment_id]/page.tsx +++ b/Frontend/grader-frontend/src/app/instructor/class/[class_id]/assignments/[assignment_id]/page.tsx @@ -1,17 +1,28 @@ -'use client'; +"use client"; -import { AssignmentForm, AssignmentFormResult } from "@/components/assignment-form"; +import { + AssignmentForm, + AssignmentFormResult, +} from "@/components/assignment-form"; import { api } from "@/lib/api"; import { UpdateAssignmentPayload } from "@/lib/api/type"; import { parseDateTime } from "@internationalized/date"; -import { useMutation, useQueryClient, useSuspenseQuery } from "@tanstack/react-query"; +import { + useMutation, + useQueryClient, + useSuspenseQuery, +} from "@tanstack/react-query"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; import { use } from "react"; import { useClassData } from "../../class-data-context"; -export default function Page({ params }: { params: Promise<{ assignment_id: string; }>; }) { +export default function Page({ + params, +}: { + params: Promise<{ assignment_id: string }>; +}) { const { assignment_id } = use(params); const { classData } = useClassData(); const t = useTranslations(); @@ -21,7 +32,7 @@ export default function Page({ params }: { params: Promise<{ assignment_id: stri const queryClient = useQueryClient(); const { data: assignment } = useSuspenseQuery({ - queryKey: ['class', classData.id, 'assignment', assignmentId], + queryKey: ["class", classData.id, "assignment", assignmentId], queryFn: () => api.assignments.getByIdI(assignmentId), }); @@ -59,9 +70,11 @@ export default function Page({ params }: { params: Promise<{ assignment_id: stri const promises: Promise[] = []; if (data.toRemoveExistingFileIds.length > 0) { - promises.push(...data.toRemoveExistingFileIds.map(fileId => - api.assignments.removeFile(fileId) - )); + promises.push( + ...data.toRemoveExistingFileIds.map((fileId) => + api.assignments.removeFile(fileId) + ) + ); } promises.push(api.assignments.update(assignmentId, payload)); @@ -69,19 +82,20 @@ export default function Page({ params }: { params: Promise<{ assignment_id: stri await Promise.all(promises); }, onSuccess: async () => { - await queryClient.invalidateQueries({ queryKey: ['class', classData.id, 'assignment'] }); - toast.success(t('assignment.form.messages.updateSuccess')); + await queryClient.invalidateQueries({ + queryKey: ["class", classData.id, "assignment"], + }); + toast.success(t("assignment.form.messages.updateSuccess")); router.push(`/instructor/class/${classData.id}/assignments`); }, onError: (error) => { console.error(error); - toast.error(t('assignment.form.messages.updateError'), { + toast.error(t("assignment.form.messages.updateError"), { description: error.message, }); }, }); - function submit(data: AssignmentFormResult) { mutation.mutate(data); } @@ -96,14 +110,21 @@ export default function Page({ params }: { params: Promise<{ assignment_id: stri classId={classData.id} isPending={mutation.isPending} submit={submit} - cancel={() => router.push(`/instructor/class/${classData.id}/assignments`)} - existingFiles={assignment.additionalFileIds?.map(id => ({ id, name: `File ${id}` })) || []} + cancel={() => + router.push(`/instructor/class/${classData.id}/assignments`) + } + existingFiles={ + assignment.additionalFileIds?.map((id) => ({ + id, + name: `File ${id}`, + })) || [] + } prefill={{ name: assignment.name, number: assignment.number, publish: assignment.publish.toString(), due: assignment.due.toString(), - languageIds: assignment.languages.map(it => it.id), + languageIds: assignment.languages.map((it) => it.id), examMode: assignment.examMode, allowLateSubmission: !assignment.closeOnDue, showScoreOnLock: assignment.showScoreOnLock, @@ -111,7 +132,7 @@ export default function Page({ params }: { params: Promise<{ assignment_id: stri assignedGroupIds: assignment.assignedGroupIds, testCode: assignment.testCode, secretTestCode: assignment.secretTestCode, - questions: assignment.questions.map(q => ({ + questions: assignment.questions.map((q) => ({ name: q.name, description: q.description, template: q.template, diff --git a/Frontend/grader-frontend/src/app/student/[class_id]/assignment/page.tsx b/Frontend/grader-frontend/src/app/student/[class_id]/assignment/page.tsx index d6610c40..1a825ee1 100644 --- a/Frontend/grader-frontend/src/app/student/[class_id]/assignment/page.tsx +++ b/Frontend/grader-frontend/src/app/student/[class_id]/assignment/page.tsx @@ -1,10 +1,16 @@ -'use client'; +"use client"; import { Badge } from "@/components/ui/badge"; import { api } from "@/lib/api"; import type { StudentAssignment, AssignmentStatus } from "@/lib/api/type"; import { useSuspenseQuery } from "@tanstack/react-query"; -import { AlertCircle, Calendar, CheckCircle, Clock, XCircle } from "lucide-react"; +import { + AlertCircle, + Calendar, + CheckCircle, + Clock, + XCircle, +} from "lucide-react"; import { useLocale } from "next-intl"; import Link from "next/link"; import { useParams } from "next/navigation"; @@ -16,7 +22,7 @@ export default function Page() { // const classId = 420; const assignmentsQuery = useSuspenseQuery({ queryKey: ["student", "class", classId, "assignments"], - queryFn: () => api.assignments.listByClass(classId) + queryFn: () => api.assignments.listByClass(classId), }); return ( @@ -38,34 +44,45 @@ function StudentAssignmentList({ const processedAssignments = useMemo(() => { const now = new Date(); - return assignments.map(assignment => { - const publishDate = assignment.publish.toDate(Intl.DateTimeFormat().resolvedOptions().timeZone); - const dueDate = assignment.due.toDate(Intl.DateTimeFormat().resolvedOptions().timeZone); + return assignments + .map((assignment) => { + const publishDate = assignment.publish.toDate( + Intl.DateTimeFormat().resolvedOptions().timeZone + ); + const dueDate = assignment.due.toDate( + Intl.DateTimeFormat().resolvedOptions().timeZone + ); - // Determine if assignment is available - const isAvailable = publishDate <= now; - const isOverdue = dueDate < now; - const isDueSoon = !isOverdue && dueDate.getTime() - now.getTime() < 24 * 60 * 60 * 1000; // 24 hours + // Determine if assignment is available + const isAvailable = publishDate <= now; + const isOverdue = dueDate < now; + const isDueSoon = + !isOverdue && dueDate.getTime() - now.getTime() < 24 * 60 * 60 * 1000; // 24 hours - return { - ...assignment, - publishDate, - dueDate, - isAvailable, - isOverdue, - isDueSoon, - }; - }).sort((a, b) => { - // Sort by due date, with available assignments first - if (a.isAvailable !== b.isAvailable) { - return a.isAvailable ? -1 : 1; - } - return a.dueDate.getTime() - b.dueDate.getTime(); - }); + return { + ...assignment, + publishDate, + dueDate, + isAvailable, + isOverdue, + isDueSoon, + }; + }) + .sort((a, b) => { + // Sort by due date, with available assignments first + if (a.isAvailable !== b.isAvailable) { + return a.isAvailable ? -1 : 1; + } + return a.dueDate.getTime() - b.dueDate.getTime(); + }); }, [assignments]); - const todoAssignments = processedAssignments.filter(a => a.isAvailable && a.status !== "completed"); - const doneAssignments = processedAssignments.filter(a => a.status === "completed"); + const todoAssignments = processedAssignments.filter( + (a) => a.isAvailable && a.status !== "completed" + ); + const doneAssignments = processedAssignments.filter( + (a) => a.status === "completed" + ); return (
@@ -82,18 +99,20 @@ function StudentAssignmentList({
Dates
Score
-
{todoAssignments.map((assignment) => ( - - ))} +
{" "} +
+ {" "} + {todoAssignments.map((assignment) => ( + + ))}
)} - {/* Done Assignments */} {doneAssignments.length > 0 && (
@@ -107,21 +126,27 @@ function StudentAssignmentList({
Dates
Score
-
{doneAssignments.map((assignment) => ( - - ))} +
{" "} +
+ {" "} + {doneAssignments.map((assignment) => ( + + ))}
- )} {/* Empty State */} + )}{" "} + {/* Empty State */} {assignments.length === 0 && (
No assignments available
-
Check back later for new assignments
+
+ Check back later for new assignments +
)} @@ -139,7 +164,7 @@ type ProcessedAssignment = StudentAssignment & { function StudentAssignmentCard({ assignment, borderColor, - isOverdue + isOverdue, }: { assignment: ProcessedAssignment; borderColor: string; @@ -149,12 +174,12 @@ function StudentAssignmentCard({ const formatDateTime = (date: Date) => { return new Intl.DateTimeFormat(locale, { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - hour12: false + year: "numeric", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + hour12: false, }).format(date); }; const getStatusInfo = (assignment: ProcessedAssignment) => { @@ -163,7 +188,7 @@ function StudentAssignmentCard({ icon: , text: "Not Available", variant: "secondary" as const, - color: "text-gray-600" + color: "text-gray-600", }; } @@ -172,7 +197,7 @@ function StudentAssignmentCard({ icon: , text: "Missing", variant: "destructive" as const, - color: "text-red-600" + color: "text-red-600", }; } @@ -181,7 +206,7 @@ function StudentAssignmentCard({ icon: , text: "Due Soon", variant: "outline" as const, - color: "text-orange-600" + color: "text-orange-600", }; } @@ -192,59 +217,66 @@ function StudentAssignmentCard({ icon: , text: "Submitted", variant: "default" as const, - color: "text-green-600" + color: "text-green-600", }; case "partially-completed": return { icon: , text: "Draft", variant: "outline" as const, - color: "text-blue-600" + color: "text-blue-600", }; case "new": return { icon: , text: "Assigned", variant: "outline" as const, - color: "text-gray-600" + color: "text-gray-600", }; case "lated": return { icon: , text: "Missing", variant: "destructive" as const, - color: "text-red-600" + color: "text-red-600", }; case "due-soon": return { icon: , text: "Due Soon", variant: "outline" as const, - color: "text-orange-600" + color: "text-orange-600", }; default: return { icon: , text: "Available", variant: "outline" as const, - color: "text-gray-600" + color: "text-gray-600", }; } }; const statusInfo = getStatusInfo(assignment); - const actualBorderColor = isOverdue ? 'border-l-red-500' : borderColor; + const actualBorderColor = isOverdue ? "border-l-red-500" : borderColor; return ( -
+
{assignment.number}
{assignment.name}
- + {statusInfo.icon} {statusInfo.text} @@ -255,13 +287,21 @@ function StudentAssignmentCard({ {formatDateTime(assignment.publishDate)}
- + {formatDateTime(assignment.dueDate)}
{assignment.score || 0} points
-
{statusInfo.text}
+
+ {statusInfo.text} +
diff --git a/Frontend/grader-frontend/src/app/student/[class_id]/layout.tsx b/Frontend/grader-frontend/src/app/student/[class_id]/layout.tsx index 49c67b64..5fb7a26b 100644 --- a/Frontend/grader-frontend/src/app/student/[class_id]/layout.tsx +++ b/Frontend/grader-frontend/src/app/student/[class_id]/layout.tsx @@ -7,17 +7,91 @@ import { useTranslations } from "next-intl"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { ReactNode } from "react"; +import { api } from "@/lib/api"; +import { notFound } from "next/navigation"; + +import LayoutHeader from "./layoutHeader"; +import { useSuspenseQuery } from "@tanstack/react-query"; +import { useParams } from "next/navigation"; + +interface ClassData { + id: number; + name: string; + courseId: string; + year: number; + semester: string; + headerImageUrl?: string; +} + +// async function getClassDetails(classId: number): Promise { +// const target = await api.classes.getById(classId); +// if (!target) { +// notFound(); +// } +// return { +// // ...target, +// id: classId, +// name: target.courseName, +// semester: "1", +// year: 2025, +// headerImageUrl: target.imageUrl, +// courseId: String(target.courseId), +// }; +// } export default function StudentLayout({ children }: { children: ReactNode }) { const pathname = usePathname(); + const params = useParams(); + const classId = parseInt(params.class_id as string); const t = useTranslations("student"); + const classInfoQuery = useSuspenseQuery({ + queryKey: ["classInfo", classId], + queryFn: async () => { + try { + return await api.classes.getById(classId); + } catch (e) { + console.error("Class not found:", e); + // Mock Data -> api.class.getById cannot fetch data + return { + // ...target, + id: classId, + name: "Programming", + semester: "1", + year: 2025, + headerImageUrl: "Image.url", + courseId: "1", + }; + } + }, + }); + + // const classInfoQuery = useSuspenseQuery({ + // queryKey: ["classInfo"], + // queryFn: async () => { + // try { + // return await getClassDetails(classId); + // } catch (err) { + // console.error(err); + // notFound(); + // } + // }, + // }); + + // console.log(JSON.stringify(classInfoQuery, null, 2)); + return (
-
-
+ +
+
@@ -52,7 +126,7 @@ export default function StudentLayout({ children }: { children: ReactNode }) {
-
{children}
+
{children}
diff --git a/Frontend/grader-frontend/src/app/student/[class_id]/layoutHeader.tsx b/Frontend/grader-frontend/src/app/student/[class_id]/layoutHeader.tsx new file mode 100644 index 00000000..e54f0293 --- /dev/null +++ b/Frontend/grader-frontend/src/app/student/[class_id]/layoutHeader.tsx @@ -0,0 +1,73 @@ +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Settings, Trash2 } from "lucide-react"; +import { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import Link from "next/link"; + +interface Props { + className: string; + classSem: string; + classYear: string; + courseId: string; +} + +export default function layoutHeader({ + className, + classSem, + classYear, + courseId, +}: Props) { + return ( +
+
+
+ + Class header image +
+
+

+ {" "} + {className} ({classYear}/{classSem}){" "} +

+

{courseId}

+
+
+
+
+ ); +} diff --git a/Frontend/grader-frontend/src/app/student/[class_id]/profile/ProfleTab.tsx b/Frontend/grader-frontend/src/app/student/[class_id]/profile/ProfleTab.tsx index 0be39c2b..e0ee1ca9 100644 --- a/Frontend/grader-frontend/src/app/student/[class_id]/profile/ProfleTab.tsx +++ b/Frontend/grader-frontend/src/app/student/[class_id]/profile/ProfleTab.tsx @@ -33,14 +33,13 @@ export default function ProflieTab() { return ( <>
-
-

{t("scoreSum")}

+
-
+

{t("ranking")}

diff --git a/Frontend/grader-frontend/src/app/student/[class_id]/profile/scoreCard.tsx b/Frontend/grader-frontend/src/app/student/[class_id]/profile/scoreCard.tsx index d7b55185..394856d8 100644 --- a/Frontend/grader-frontend/src/app/student/[class_id]/profile/scoreCard.tsx +++ b/Frontend/grader-frontend/src/app/student/[class_id]/profile/scoreCard.tsx @@ -9,7 +9,7 @@ export default function scoreCard() { return ( <> - +

{t("scoreSum")}

diff --git a/Frontend/grader-frontend/src/app/student/studentCard.tsx b/Frontend/grader-frontend/src/app/student/studentCard.tsx index 8ddc38e3..088032ab 100644 --- a/Frontend/grader-frontend/src/app/student/studentCard.tsx +++ b/Frontend/grader-frontend/src/app/student/studentCard.tsx @@ -68,9 +68,7 @@ function studentCard({ class_id, class_name, image, semester }: Props) { const router = useRouter(); const toAssignmentPage = () => { - router.push( - `/student/${class_id}/${semester.replace("/", "-")}/1/assignment` - ); + router.push(`/student/${class_id}/assignment`); }; const [progress, setProgress] = React.useState(13); @@ -101,7 +99,7 @@ function studentCard({ class_id, class_name, image, semester }: Props) {

toAssignmentPage()} // Temporary Change to test the Popover > {class_name} ({semester}) diff --git a/Frontend/grader-frontend/src/components/assignment-form.tsx b/Frontend/grader-frontend/src/components/assignment-form.tsx index d82231c0..5b6c3dbe 100644 --- a/Frontend/grader-frontend/src/components/assignment-form.tsx +++ b/Frontend/grader-frontend/src/components/assignment-form.tsx @@ -1,4 +1,4 @@ -'use client'; +"use client"; // 99% of this is by sonnet 4 @@ -14,7 +14,15 @@ import { } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; -import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; @@ -28,6 +36,9 @@ import { useTranslations } from "next-intl"; import { useEffect, useMemo, useState } from "react"; import { Control, useFieldArray, useForm, useWatch } from "react-hook-form"; import { z } from "zod"; +import { useRouter } from "next/navigation"; + +import { cn } from "@/lib/utils"; const createSchemas = (t: ReturnType) => { const testcaseSchema = z.object({ @@ -36,10 +47,14 @@ const createSchemas = (t: ReturnType) => { }); const questionSchema = z.object({ - name: z.string().min(1, t('assignment.form.validation.question.name.required')), + name: z + .string() + .min(1, t("assignment.form.validation.question.name.required")), description: z.string(), template: z.string(), - maxScore: z.coerce.number().min(0, t('assignment.form.validation.question.maxScore.min')), + maxScore: z.coerce + .number() + .min(0, t("assignment.form.validation.question.maxScore.min")), answer: z.string(), testCode: z.string(), secretTestCode: z.string(), @@ -48,11 +63,17 @@ const createSchemas = (t: ReturnType) => { }); const assignmentSchema = z.object({ - name: z.string().min(1, t('assignment.form.validation.name.required')), - number: z.coerce.number().min(1, t('assignment.form.validation.number.min')), - publish: z.string().min(1, t('assignment.form.validation.publish.required')), - due: z.string().min(1, t('assignment.form.validation.due.required')), - languageIds: z.array(z.number()).min(1, t('assignment.form.validation.languages.min')), + name: z.string().min(1, t("assignment.form.validation.name.required")), + number: z.coerce + .number() + .min(1, t("assignment.form.validation.number.min")), + publish: z + .string() + .min(1, t("assignment.form.validation.publish.required")), + due: z.string().min(1, t("assignment.form.validation.due.required")), + languageIds: z + .array(z.number()) + .min(1, t("assignment.form.validation.languages.min")), examMode: z.boolean(), allowLateSubmission: z.boolean(), showScoreOnLock: z.boolean(), @@ -60,30 +81,38 @@ const createSchemas = (t: ReturnType) => { assignedGroupIds: z.array(z.string()), testCode: z.string(), secretTestCode: z.string(), - questions: z.array(questionSchema).min(1, t('assignment.form.validation.questions.min')), + questions: z + .array(questionSchema) + .min(1, t("assignment.form.validation.questions.min")), }); return { assignmentSchema, questionSchema, testcaseSchema }; }; // Define the form data type using the schema inference from createSchemas -type AssignmentFormData = z.infer['assignmentSchema']>; +type AssignmentFormData = z.infer< + ReturnType["assignmentSchema"] +>; -function AssignmentName({ control }: { control: Control; }) { +function AssignmentName({ control }: { control: Control }) { const name = useWatch({ control, - name: 'name', - defaultValue: "" + name: "name", + defaultValue: "", }); return

{name.length === 0 ? "Name" : name}

; } -function AssignmentNumber({ control }: { control: Control; }) { +function AssignmentNumber({ + control, +}: { + control: Control; +}) { const number = useWatch({ control, - name: 'number', - defaultValue: 1 + name: "number", + defaultValue: 1, }); return

Lab {number}

; @@ -111,24 +140,29 @@ export interface AttachmentMetadata { // size maybe } -export function AssignmentForm({ submit, cancel, classId, prefill, existingFiles = [], isPending }: AssignmentFormProps) { +export function AssignmentForm({ + submit, + cancel, + classId, + prefill, + existingFiles = [], + isPending, +}: AssignmentFormProps) { const t = useTranslations(); - const [ - { data: supportedLanguages }, - { data: availableGroups } - ] = useSuspenseQueries({ - queries: [ - { - queryKey: ['supportedLanguages'], - queryFn: () => api.supportedLanguages.list(), - }, - { - queryKey: ['groups', classId], - queryFn: () => api.groups.listByClassId(classId), - } - ] - }); + const [{ data: supportedLanguages }, { data: availableGroups }] = + useSuspenseQueries({ + queries: [ + { + queryKey: ["supportedLanguages"], + queryFn: () => api.supportedLanguages.list(), + }, + { + queryKey: ["groups", classId], + queryFn: () => api.groups.listByClassId(classId), + }, + ], + }); const { assignmentSchema } = useMemo(() => createSchemas(t), [t]); @@ -187,6 +221,7 @@ export function AssignmentForm({ submit, cancel, classId, prefill, existingFiles const [showDiscardDialog, setShowDiscardDialog] = useState(false); const [showSaveDialog, setShowSaveDialog] = useState(false); + const router = useRouter(); // we shuold move this out @@ -194,7 +229,7 @@ export function AssignmentForm({ submit, cancel, classId, prefill, existingFiles submit({ ...data, toRemoveExistingFileIds, - additionalFiles: [...attachmentDropzone.files] as File[] + additionalFiles: [...attachmentDropzone.files] as File[], }); } @@ -212,8 +247,9 @@ export function AssignmentForm({ submit, cancel, classId, prefill, existingFiles }; const confirmDiscard = () => { - setShowDiscardDialog(false); + router.push(`/instructor/class/${classId}/assignments`); // For now since cancel() is not working cancel(); + setShowDiscardDialog(false); }; const addQuestion = () => { @@ -230,14 +266,20 @@ export function AssignmentForm({ submit, cancel, classId, prefill, existingFiles }); }; - const [toRemoveExistingFileIds, setToRemoveExistingFileIds] = useState([] as number[]); - const filteredExistingFiles = existingFiles.filter(it => !toRemoveExistingFileIds.includes(it.id)); + const [toRemoveExistingFileIds, setToRemoveExistingFileIds] = useState( + [] as number[] + ); + const filteredExistingFiles = existingFiles.filter( + (it) => !toRemoveExistingFileIds.includes(it.id) + ); function removeExistingFile(fileId: number) { setToRemoveExistingFileIds([...toRemoveExistingFileIds, fileId]); } // This run like shit, // const name = form.watch("name"); + const isExamMode = form.watch("examMode"); + console.log(isExamMode); return ( <> @@ -249,42 +291,63 @@ export function AssignmentForm({ submit, cancel, classId, prefill, existingFiles

+ -
-
-
+
-
- + + {/* Basic Information */}
- {/* Lab name */}
-
-
+
( - {t('assignment.form.fields.name.label')} + + {t("assignment.form.fields.name.label")} + - + @@ -299,9 +362,16 @@ export function AssignmentForm({ submit, cancel, classId, prefill, existingFiles name="number" render={({ field }) => ( - {t('assignment.form.fields.number.label')} + + {t("assignment.form.fields.number.label")} + - + @@ -315,33 +385,29 @@ export function AssignmentForm({ submit, cancel, classId, prefill, existingFiles name="publish" render={({ field }) => ( - {t('assignment.form.fields.publish.label')} + + {t("assignment.form.fields.publish.label")} + - + )} /> -
- dots -
+
dots
( - {t('assignment.form.fields.due.label')} + + {t("assignment.form.fields.due.label")} + - + @@ -361,9 +427,16 @@ export function AssignmentForm({ submit, cancel, classId, prefill, existingFiles - {t('assignment.form.fields.examMode.label')} + + {t("assignment.form.fields.examMode.label")} + )} /> @@ -374,17 +447,21 @@ export function AssignmentForm({ submit, cancel, classId, prefill, existingFiles name="examPin" render={({ field }) => ( - {t('assignment.form.fields.examPin.label')} + + {t("assignment.form.fields.examPin.label")} + - {t('assignment.form.fields.examPin.description')} + {t("assignment.form.fields.examPin.description")} @@ -396,7 +473,9 @@ export function AssignmentForm({ submit, cancel, classId, prefill, existingFiles
{/* Assigned Groups */}
-

{t('assignment.form.sections.assignedGroups')}

+

+ {t("assignment.form.sections.assignedGroups")} +

{ return checked - ? field.onChange([...field.value, group]) + ? field.onChange([ + ...field.value, + group, + ]) : field.onChange( - field.value?.filter( - (value) => value !== group - ) - ); + field.value?.filter( + (value) => value !== group + ) + ); }} /> @@ -445,7 +527,9 @@ export function AssignmentForm({ submit, cancel, classId, prefill, existingFiles {/* Languages */}
-

{t('assignment.form.sections.languages')}

+

+ {t("assignment.form.sections.languages")} +

{ return checked - ? field.onChange([...field.value, language.id]) + ? field.onChange([ + ...field.value, + language.id, + ]) : field.onChange( - field.value?.filter( - (value) => value !== language.id - ) - ); + field.value?.filter( + (value) => + value !== language.id + ) + ); }} /> @@ -504,9 +594,18 @@ export function AssignmentForm({ submit, cancel, classId, prefill, existingFiles - {t('assignment.form.fields.allowLateSubmission.label')} + + {t( + "assignment.form.fields.allowLateSubmission.label" + )} + )} /> @@ -520,35 +619,45 @@ export function AssignmentForm({ submit, cancel, classId, prefill, existingFiles - {t('assignment.form.fields.showScoreOnLock.label')} + + {t("assignment.form.fields.showScoreOnLock.label")} + )} />
-
-
-
{/* Test Code */}
-

{t('assignment.form.sections.globalTestCode')}

+

+ {t("assignment.form.sections.globalTestCode")} +

-
+
( - {t('assignment.form.fields.testCode.label')} + + {t("assignment.form.fields.testCode.label")} +