Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
@@ -1,20 +1,21 @@
import { forbidden, notFound } from 'next/navigation'
import { getCourseByShareToken } from '@/database/course/select'
import { getCourseById } from '@/database/course/select'
import { getExaminationAttemptById } from '@/database/examination/select'
import { QuestionScoresLineChart } from '@/src/components/charts/QuestionScoresLineChart'
import ExamQuestionResultTable from '@/src/components/results/examination/ExamQuestionResultTable'
import { ExamResultAttemptResultsBreadcrumbs } from '@/src/components/results/examination/ExamResultsBreadcrumbs'
import PageHeading from '@/src/components/Shared/PageHeading'
import { getScopedI18n } from '@/src/i18n/server-localization'
import requireAuthentication from '@/src/lib/auth/requireAuthentication'
import hasCollaborativePermissions from '@/src/lib/courses/hasCollaborativePermissions'
import getDummyExamAttempts from '@/src/lib/dummy/getDummyExamAttempts'
import ExamQuestionResultTable from '../../../../../../components/courses/[share_token]/results/ExamQuestionResultTable'

export default async function ExamAttemptResultPage({ params }: { params: Promise<{ share_token: string; attemptId: string }> }) {
export default async function ExamAttemptResultPage({ params }: { params: Promise<{ courseId: string; attemptId: string }> }) {
const t = await getScopedI18n('Courses.ExaminatonResults.ExamAttemptResultPage')
const { share_token, attemptId } = await params
const { courseId, attemptId } = await params
const { user } = await requireAuthentication()

let [course, attempt] = await Promise.all([getCourseByShareToken(share_token), getExaminationAttemptById(parseInt(attemptId))])
let [course, attempt] = await Promise.all([getCourseById(courseId), getExaminationAttemptById(parseInt(attemptId))])

// dummy examinationAttempt data when a given attempt does not exist
if (!attempt) attempt = (await getDummyExamAttempts(1))[0]
Expand All @@ -24,6 +25,7 @@ export default async function ExamAttemptResultPage({ params }: { params: Promis

return (
<>
<ExamResultAttemptResultsBreadcrumbs userName={attempt.user.name} courseId={courseId} />
<PageHeading title={t('title')} description={t('description', { name: attempt.user.name })} />

<div className='flex flex-col gap-14'>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
'use server'

import { forbidden, notFound } from 'next/navigation'
import { getCourseByShareToken } from '@/database/course/select'
import { getCourseById } from '@/database/course/select'
import { getCourseExaminationAttempts } from '@/database/examination/select'
import { ExaminationSuccessPieChart } from '@/src/components/charts/ExaminationSuccessPieChart'
import { ExamQuestionDurationChart } from '@/src/components/charts/QuestionDurationChart'
import { QuestionScoresLineChartCard } from '@/src/components/charts/QuestionScoresLineChart'
import { UserTypePieChart } from '@/src/components/charts/UserTypePieChart'
import { ExaminationAttemptTable } from '@/src/components/courses/[share_token]/ExaminationAttemptTable'
import { ExamResultsBreadcrumbs } from '@/src/components/results/examination/ExamResultsBreadcrumbs'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/src/components/shadcn/card'
import PageHeading from '@/src/components/Shared/PageHeading'
import { getScopedI18n } from '@/src/i18n/server-localization'
Expand All @@ -24,11 +25,11 @@ export type ExaminationAttemptTableStructure = Pick<ExaminationAttmept, 'score'
answerCount: number
}

export default async function ExaminationResultsPage({ params }: { params: Promise<{ share_token: string }> }) {
const { share_token } = await params
export default async function ExaminationResultsPage({ params }: { params: Promise<{ courseId: string }> }) {
const { courseId } = await params
const { user } = await requireAuthentication()

const course = await getCourseByShareToken(share_token)
const course = await getCourseById(courseId)

if (!course) notFound()
if (!hasCollaborativePermissions(course, user.id)) forbidden()
Expand All @@ -38,6 +39,7 @@ export default async function ExaminationResultsPage({ params }: { params: Promi

return (
<>
<ExamResultsBreadcrumbs courseId={courseId} />
<PageHeading title={t('title')} description={t('description')} />

<div className='mx-6 mt-2 flex flex-col gap-16'>
Expand Down
2 changes: 1 addition & 1 deletion src/app/[locale]/results/[courseId]/practice/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getCourseById } from '@/database/course/select'
import { QuestionCorrectnessPieChart } from '@/src/components/charts/QuestionCorrectnessPieChart'
import { ExamQuestionDurationChart } from '@/src/components/charts/QuestionDurationChart'
import { QuestionScorePlotCard } from '@/src/components/charts/QuestionScorePlot'
import ExamQuestionResultTable from '@/src/components/courses/[share_token]/results/ExamQuestionResultTable'
import ExamQuestionResultTable from '@/src/components/results/examination/ExamQuestionResultTable'
import { PracticeResultsBreadcrumbs } from '@/src/components/results/practice/PracticeResultsBreadcrumbs'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/src/components/shadcn/card'
import PageHeading from '@/src/components/Shared/PageHeading'
Expand Down
9 changes: 8 additions & 1 deletion src/components/courses/(hamburger-menu)/CourseActionMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

import { useState } from 'react'
import { ArrowUpRightIcon, CopyPlusIcon, EllipsisIcon, Share2Icon, SquarePenIcon, TrashIcon } from 'lucide-react'
import { ArrowUpRightIcon, ChartSplineIcon, CopyPlusIcon, EllipsisIcon, Share2Icon, SquarePenIcon, TrashIcon } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { toast } from 'react-toastify'
Expand Down Expand Up @@ -142,6 +142,13 @@ export default function CourseActionMenu({ id, questions, share_key, owner_id, c
<SquarePenIcon className='size-3.5 text-neutral-600 group-data-disabled:text-inherit dark:text-neutral-400 dark:group-data-disabled:text-inherit' />
</Link>
</DropdownMenuItem>

<DropdownMenuItem disabled={!isOwner && !isContributor} enableTooltip={!isOwner && !isContributor} tooltipOptions={{ ...baseTooltipOptions, content: t('show_exam_results.tooltip') }}>
<Link href={`/results/${id}/exam`} className='flex flex-1 justify-between'>
{t('show_exam_results.label')}
<ChartSplineIcon className='size-3.5 text-neutral-600 group-data-disabled:text-inherit dark:text-neutral-400 dark:group-data-disabled:text-inherit' />
</Link>
</DropdownMenuItem>
Comment thread
Marty-Byrde marked this conversation as resolved.
<DropdownMenuItem className='justify-between' disabled>
{t('clone_course.label')}
<CopyPlusIcon className='size-4 text-neutral-600 group-data-disabled:text-inherit dark:text-neutral-400 dark:group-data-disabled:text-inherit' />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export function ExaminationAttemptTable({ data: initialData }: { data: ExamAttem
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end' className={cn('w-40', currentLocale === 'de' && 'w-48')}>
<Link href={`results/${row.original.id}`}>
<Link href={`exam/${row.original.id}`}>
<DropdownMenuItem className='justify-between'>
{t('columns.actions_menu.show_results_label')}
<ChartColumnIcon className='size-3.5' />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import { IconCircleCheckFilled } from '@tabler/icons-react'
import { ColumnDef } from '@tanstack/react-table'
import { EyeIcon, XIcon } from 'lucide-react'
import ExamQuestionTable_ActionMenu from '@/src/components/courses/[share_token]/results/ExamQuestionTable_ActionMenu'
import ShowAnswerDrawer_TableCell from '@/src/components/courses/[share_token]/results/PreviewQuestionResultDrawer'
import ExamQuestionTable_ActionMenu from '@/src/components/results/examination/ExamQuestionTable_ActionMenu'
import ShowAnswerDrawer_TableCell from '@/src/components/results/examination/PreviewQuestionResultDrawer'
import { Badge } from '@/src/components/shadcn/badge'
import { Button } from '@/src/components/shadcn/button'
import { Checkbox } from '@/src/components/shadcn/checkbox'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { useState } from 'react'
import { IconDotsVertical } from '@tabler/icons-react'
import { Row } from '@tanstack/react-table'
import { ChartColumnIcon, TrashIcon } from 'lucide-react'
import { PreviewQuestionResult_QuestionItem } from '@/src/components/courses/[share_token]/results/ExamQuestionResultTable'
import ShowAnswerDrawer_TableCell from '@/src/components/courses/[share_token]/results/PreviewQuestionResultDrawer'
import { PreviewQuestionResult_QuestionItem } from '@/src/components/results/examination/ExamQuestionResultTable'
Comment thread
Marty-Byrde marked this conversation as resolved.
import ShowAnswerDrawer_TableCell from '@/src/components/results/examination/PreviewQuestionResultDrawer'
import { Button } from '@/src/components/shadcn/button'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/src/components/shadcn/dropdown-menu'
import { useCurrentLocale, useScopedI18n } from '@/src/i18n/client-localization'
Expand Down
79 changes: 79 additions & 0 deletions src/components/results/examination/ExamResultsBreadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use server'
Comment thread
Marty-Byrde marked this conversation as resolved.

import { DetailedHTMLProps, HTMLAttributes } from 'react'
import Link from 'next/link'
import { getCourseById } from '@/database/course/select'
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from '@/src/components/shadcn/breadcrumb'
import { getScopedI18n } from '@/src/i18n/server-localization'
import { Course } from '@/src/schemas/CourseSchema'

async function BaseBreadcrumbs({
courseName,
courseId,
children,
...props
}: { children: React.ReactNode; courseId: Course['id']; courseName: Course['name'] } & DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>) {
const t = await getScopedI18n('Shared.Breadcrumbs')
return (
<Breadcrumb {...props}>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href={`/`}>{t('root')}</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href={`/courses`}>{t('courses')}</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href={`/courses/${courseId}/edit`}>{courseName}</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
{children}
</BreadcrumbList>
</Breadcrumb>
)
}

export async function ExamResultsBreadcrumbs({ courseId, ...props }: { courseId: string } & DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>) {
const t = await getScopedI18n('Shared.Breadcrumbs')
const course = await getCourseById(courseId)
Comment thread
Marty-Byrde marked this conversation as resolved.
if (!course) return null

return (
<BaseBreadcrumbs courseId={courseId} courseName={course.name} {...props}>
<BreadcrumbPage>
<BreadcrumbItem>{t('examination_results')}</BreadcrumbItem>
</BreadcrumbPage>
</BaseBreadcrumbs>
)
}

export async function ExamResultAttemptResultsBreadcrumbs({ courseId, userName, ...props }: { courseId: string; userName: string } & DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>) {
const t = await getScopedI18n('Shared.Breadcrumbs')
const course = await getCourseById(courseId)
if (!course) return null

return (
<BaseBreadcrumbs courseId={courseId} courseName={course.name} {...props}>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href={`/results/${course.id}/exam`}>{t('examination_results')}</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />

<BreadcrumbPage>
<BreadcrumbItem>
{userName}&apos;s {t('results')}
Comment thread
Marty-Byrde marked this conversation as resolved.
</BreadcrumbItem>
</BreadcrumbPage>
</BaseBreadcrumbs>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import React, { Dispatch, SetStateAction, useState } from 'react'
import { ChevronRightIcon, UserCheckIcon, UserIcon } from 'lucide-react'
import { QuestionScoresLineChart } from '@/src/components/charts/QuestionScoresLineChart'
import { PreviewQuestionResult_QuestionItem } from '@/src/components/courses/[share_token]/results/ExamQuestionResultTable'
import { PreviewQuestionResult_QuestionItem } from '@/src/components/results/examination/ExamQuestionResultTable'
import { Button } from '@/src/components/shadcn/button'
import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger } from '@/src/components/shadcn/drawer'
import { Input } from '@/src/components/shadcn/input'
Expand Down
22 changes: 21 additions & 1 deletion src/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,23 @@
"minute_label#other": "minuten",
"minute_label": "minuten"
},
"jump_back_button_label": "Bearbeiten"
"jump_back_button_label": "Bearbeiten",
"Breadcrumbs": {
"root": "Home",
"courses": "Kurse",
"discover": "Entdecken",
"practice": "Üben",
"edit": "Bearbeiten",
"create": "Erstellen",
"account": "Account",
"start": "Start",
"results": "Ergebnisse",
"examination_results": "Prüfungsergebnisse",
"practice_results": "Üben",
"not-allowed": "Nicht erlaubt",
"attempt-limit": "Versuchslimit erreicht",
"attempt-not-possible": "Versuch abgebrochen"
}
},
"Home": {
"description": "Create your own Course to boost your knowledge to the next level. (de)"
Expand Down Expand Up @@ -421,6 +437,10 @@
"clone_course": {
"label": "Kurs duplizieren"
},
"show_exam_results": {
"label": "Prüfungs Ergebnisse",
"tooltip": "Dir fehlen Berechtigungen um Prüfungsergebnisse einzusehen!"
},
"inspect_statistics": {
"label": "Statistik anzeigen"
},
Expand Down
22 changes: 21 additions & 1 deletion src/i18n/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,23 @@ export default {
'minute_label#other': 'minuten',
minute_label: 'minuten'
},
jump_back_button_label: 'Bearbeiten'
jump_back_button_label: 'Bearbeiten',
Breadcrumbs: {
root: 'Home',
courses: 'Kurse',
discover: 'Entdecken',
practice: 'Üben',
edit: 'Bearbeiten',
create: 'Erstellen',
account: 'Account',
start: 'Start',
results: 'Ergebnisse',
examination_results: 'Prüfungsergebnisse',
practice_results: 'Üben',
'not-allowed': 'Nicht erlaubt',
'attempt-limit': 'Versuchslimit erreicht',
'attempt-not-possible': 'Versuch abgebrochen'
}
},
Home: {
description: 'Create your own Course to boost your knowledge to the next level. (de)'
Expand Down Expand Up @@ -425,6 +441,10 @@ export default {
clone_course: {
label: 'Kurs duplizieren'
},
show_exam_results: {
label: 'Prüfungs Ergebnisse',
tooltip: 'Dir fehlen Berechtigungen um Prüfungsergebnisse einzusehen!'
},
inspect_statistics: {
label: 'Statistik anzeigen'
},
Expand Down
23 changes: 22 additions & 1 deletion src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,24 @@
"minute_label#other": "minutes",
"minute_label": "minutes"
},
"jump_back_button_label": "Jump back"
"jump_back_button_label": "Jump back",
"Breadcrumbs": {
"root": "Home",
"courses": "Courses",
"discover": "Discover",
"practice": "Practice",
"edit": "Edit",
"create": "Create",
"account": "Account",
"start": "Start",
"results": "Results",
"examination_results": "Examination Results",
"practice_results": "Practice",

"not-allowed": "Not Allowed",
"attempt-limit": "Attempt Limit Reached",
"attempt-not-possible": "Attempt aborted"
}
},
"Home": {
"description": "Create your own Courses to boost your knowledge to the next level. (en)"
Expand Down Expand Up @@ -430,6 +447,10 @@
"clone_course": {
"label": "Clone Course"
},
"show_exam_results": {
"label": "Show Examination Results",
"tooltip": "You are not allowed to view exam results!"
},
"inspect_statistics": {
"label": "Inspect Statistics"
},
Expand Down
22 changes: 21 additions & 1 deletion src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,23 @@ export default {
'minute_label#other': 'minutes',
minute_label: 'minutes'
},
jump_back_button_label: 'Jump back'
jump_back_button_label: 'Jump back',
Breadcrumbs: {
root: 'Home',
courses: 'Courses',
discover: 'Discover',
practice: 'Practice',
edit: 'Edit',
create: 'Create',
account: 'Account',
start: 'Start',
results: 'Results',
examination_results: 'Examination Results',
practice_results: 'Practice',
'not-allowed': 'Not Allowed',
'attempt-limit': 'Attempt Limit Reached',
'attempt-not-possible': 'Attempt aborted'
}
},
Home: {
description: 'Create your own Courses to boost your knowledge to the next level. (en)'
Expand Down Expand Up @@ -431,6 +447,10 @@ export default {
clone_course: {
label: 'Clone Course'
},
show_exam_results: {
label: 'Show Examination Results',
tooltip: 'You are not allowed to view exam results!'
},
inspect_statistics: {
label: 'Inspect Statistics'
},
Expand Down
Loading