Skip to content

Assessment: introduce deadline for assessment phase #607

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { assessmentAxiosInstance } from '../assessmentServerConfig'

export const updateDeadline = async (coursePhaseID: string, deadline: Date): Promise<void> => {
try {
await assessmentAxiosInstance.put(
`assessment/api/course_phase/${coursePhaseID}/deadline`,
{ deadline: deadline },
{
headers: {
'Content-Type': 'application/json',
},
},
)
} catch (err) {
console.error(err)
throw err
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { assessmentAxiosInstance } from '../assessmentServerConfig'

export const getDeadline = async (coursePhaseID: string): Promise<Date> => {
const response = await assessmentAxiosInstance.get<Date>(
`assessment/api/course_phase/${coursePhaseID}/deadline`,
{
headers: {
'Content-Type': 'application/json',
},
},
)
return response.data
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { Loader2 } from 'lucide-react'
import { useEffect } from 'react'
import { useParams } from 'react-router-dom'

import { useQuery } from '@tanstack/react-query'

import { CoursePhaseParticipationsWithResolution } from '@tumaet/prompt-shared-state'
import { ErrorPage } from '@tumaet/prompt-ui-components'
import { ErrorPage, LoadingPage } from '@tumaet/prompt-ui-components'
import { getCoursePhaseParticipations } from '@/network/queries/getCoursePhaseParticipations'

import { useGetAllCategoriesWithCompetencies } from './hooks/useGetAllCategoriesWithCompetencies'
import { useGetAllScoreLevels } from './hooks/useGetAllScoreLevels'
import { useGetDeadline } from './hooks/useGetDeadline'

import { useParticipationStore } from '../zustand/useParticipationStore'
import { useCategoryStore } from '../zustand/useCategoryStore'
import { useScoreLevelStore } from '../zustand/useScoreLevelStore'
import { useDeadlineStore } from '../zustand/useDeadlineStore'

interface AssessmentDataShellProps {
children: React.ReactNode
Expand All @@ -24,6 +25,7 @@ export const AssessmentDataShell = ({ children }: AssessmentDataShellProps) => {
const { setParticipations } = useParticipationStore()
const { setCategories } = useCategoryStore()
const { setScoreLevels } = useScoreLevelStore()
const { setDeadline } = useDeadlineStore()

const {
data: coursePhaseParticipations,
Expand All @@ -49,14 +51,26 @@ export const AssessmentDataShell = ({ children }: AssessmentDataShellProps) => {
refetch: refetchScoreLevels,
} = useGetAllScoreLevels()

const isError = isParticipationsError || isCategoriesError || isScoreLevelsError
const {
data: deadline,
isPending: isDeadlinePending,
isError: isDeadlineError,
refetch: refetchDeadline,
} = useGetDeadline()

const isError =
isParticipationsError || isCategoriesError || isScoreLevelsError || isDeadlineError
const isPending =
isCoursePhaseParticipationsPending || isCategoriesPending || isScoreLevelsPending
isCoursePhaseParticipationsPending ||
isCategoriesPending ||
isScoreLevelsPending ||
isDeadlinePending

const refetch = () => {
refetchCoursePhaseParticipations()
refetchCategories()
refetchScoreLevels()
refetchDeadline()
}

useEffect(() => {
Expand All @@ -77,17 +91,13 @@ export const AssessmentDataShell = ({ children }: AssessmentDataShellProps) => {
}
}, [scoreLevels, setScoreLevels])

useEffect(() => {
if (deadline && deadline != null) {
setDeadline(deadline)
}
}, [deadline, setDeadline])

return (
<>
{isError ? (
<ErrorPage onRetry={refetch} />
) : isPending ? (
<div className='flex justify-center items-center h-64'>
<Loader2 className='h-12 w-12 animate-spin text-primary' />
</div>
) : (
<>{children}</>
)}
</>
<>{isError ? <ErrorPage onRetry={refetch} /> : isPending ? <LoadingPage /> : <>{children}</>}</>
)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Loader2 } from 'lucide-react'
import { useParams } from 'react-router-dom'
import { useMemo } from 'react'

import { ErrorPage } from '@tumaet/prompt-ui-components'
import { ErrorPage, LoadingPage } from '@tumaet/prompt-ui-components'

import { useGetStudentAssessment } from './hooks/useGetStudentAssessment'
import { CategoryAssessment } from './components/CategoryAssessment'
Expand Down Expand Up @@ -36,12 +35,7 @@ export const AssessmentPage = (): JSX.Element => {
}, [categories, studentAssessment?.assessments?.length])

if (isStudentAssessmentError) return <ErrorPage onRetry={refetchStudentAssessment} />
if (isStudentAssessmentPending)
return (
<div className='flex justify-center items-center h-64'>
<Loader2 className='h-12 w-12 animate-spin text-primary' />
</div>
)
if (isStudentAssessmentPending) return <LoadingPage />

if (!studentAssessment) {
return (
Expand Down Expand Up @@ -79,7 +73,6 @@ export const AssessmentPage = (): JSX.Element => {

<AssessmentCompletion
studentAssessment={studentAssessment}
deadline='19.06.2025'
completed={studentAssessment.assessmentCompletion.completed}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { useState } from 'react'
import { useParams } from 'react-router-dom'
import { Lock, Unlock } from 'lucide-react'

import { format } from 'date-fns'

import {
Button,
Card,
Expand All @@ -27,20 +29,21 @@ import { AssessmentCompletionDialog } from './components/AssessmentCompletionDia
import { useCreateOrUpdateAssessmentCompletion } from './hooks/useCreateOrUpdateAssessmentCompletion'
import { useMarkAssessmentAsComplete } from './hooks/useMarkAssessmentAsComplete'
import { useUnmarkAssessmentAsCompleted } from './hooks/useUnmarkAssessmentAsCompleted'
import { useDeadlineStore } from '../../../../zustand/useDeadlineStore'

interface AssessmentFeedbackProps {
studentAssessment: StudentAssessment
deadline?: string
completed?: boolean
}

export function AssessmentCompletion({
studentAssessment,
deadline = '19.06.2025',
completed = false,
}: AssessmentFeedbackProps) {
const { phaseId } = useParams<{ phaseId: string }>()

const { deadline } = useDeadlineStore()

const [generalRemarks, setGeneralRemarks] = useState(
studentAssessment.assessmentCompletion?.comment || '',
)
Expand All @@ -64,6 +67,9 @@ export function AssessmentCompletion({

const isPending = isCreatePending || isMarkPending || isUnmarkPending

// Check if deadline has passed
const isDeadlinePassed = deadline ? new Date() > new Date(deadline) : false

const { user } = useAuthStore()
const userName = user ? `${user.firstName} ${user.lastName}` : 'Unknown User'

Expand Down Expand Up @@ -105,6 +111,11 @@ export function AssessmentCompletion({
const handleCompletion = async () => {
try {
if (studentAssessment.assessmentCompletion.completed) {
// Check if deadline has passed before unmarking
if (isDeadlinePassed) {
setError('Cannot unmark assessment as completed: deadline has passed.')
return
}
await unmarkAsCompleted(studentAssessment.courseParticipationID)
} else {
// Validate grade before final submission
Expand Down Expand Up @@ -226,8 +237,29 @@ export function AssessmentCompletion({
)}

<div className='flex justify-between items-center mt-8'>
<div className='text-muted-foreground'>Deadline: {deadline}</div>
<Button size='sm' disabled={isPending} onClick={handleButtonClick}>
<div className='flex flex-col'>
{deadline && (
<div className='text-muted-foreground'>
Deadline: {deadline ? format(new Date(deadline), 'dd.MM.yyyy') : 'No deadline set'}
{isDeadlinePassed && (
<span className='text-red-600 ml-2 font-medium'>(Deadline has passed)</span>
)}
</div>
)}
{isDeadlinePassed && studentAssessment.assessmentCompletion.completed && (
<div className='text-sm text-red-600 mt-1'>
Assessments cannot be unmarked as final after the deadline has passed.
</div>
)}
</div>

<Button
size='sm'
disabled={
isPending || (studentAssessment.assessmentCompletion.completed && isDeadlinePassed)
}
onClick={handleButtonClick}
>
{studentAssessment.assessmentCompletion.completed ? (
<span className='flex items-center gap-1'>
<Unlock className='h-3.5 w-3.5' />
Expand All @@ -250,6 +282,7 @@ export function AssessmentCompletion({
error={error}
setError={setError}
handleConfirm={handleConfirm}
isDeadlinePassed={isDeadlinePassed}
/>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface AssessmentCompletionDialogProps {
error: string | null
setError: (error: string | null) => void
handleConfirm: () => void
isDeadlinePassed?: boolean
}

export function AssessmentCompletionDialog({
Expand All @@ -32,6 +33,7 @@ export function AssessmentCompletionDialog({
error,
setError,
handleConfirm,
isDeadlinePassed = false,
}: AssessmentCompletionDialogProps) {
return (
<div>
Expand Down Expand Up @@ -61,8 +63,19 @@ export function AssessmentCompletionDialog({
Are you sure you want to reopen this assessment for editing? This will allow you
to make changes to the assessment.
</>
) : !isDeadlinePassed ? (
<>
<strong>Are you sure you want to mark this assessment as final?</strong>
<br />
You can still unmark and edit it until the deadline. After the deadline, the
assessment will be locked permanently and no further changes will be possible.
</>
) : (
'Are you sure you want to mark this assessment as final? This will lock the assessment and prevent further changes.'
<>
<strong>Are you sure you want to mark this assessment as final?</strong>
<br />
This will lock the assessment permanently and prevent further changes.
</>
)}
</DialogDescription>
</DialogHeader>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ManagementPageHeader } from '@tumaet/prompt-ui-components'

import { DeadlineSelection } from './components/DeadlineSelection/DeadlineSelection'
import { AssessmentTemplateSelection } from './components/AssessmentTemplateSelection/AssessmentTemplateSelection'
import { CategoryList } from './components/CategoryList'
import { CreateCategoryForm } from './components/CreateCategoryForm'
Expand All @@ -7,7 +9,11 @@ export const SettingsPage = (): JSX.Element => {
return (
<div className='space-y-4'>
<ManagementPageHeader>Assessment Settings</ManagementPageHeader>
<AssessmentTemplateSelection />
<div className='grid xl:grid-cols-3 gap-4'>
<AssessmentTemplateSelection />
<DeadlineSelection />
</div>

<CategoryList />
<CreateCategoryForm />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ export const AssessmentTemplateSelection = () => {
}

return (
<Card className='shadow-sm transition-all hover:shadow-md'>
<CardHeader className='pb-4'>
<Card className='xl:col-span-2 shadow-sm transition-all hover:shadow-md'>
<CardHeader>
<CardTitle className='flex items-center gap-2'>
<FileText className='h-5 w-5' />
Assessment Template
Template
</CardTitle>
</CardHeader>
<CardContent className='space-y-4'>
Expand Down
Loading
Loading