mirror of
https://github.com/UniversityOfHelsinkiCS/oodikone.git
synced 2026-10-12 19:03:39 +03:00
[Study programme] Rewrite OverallStatsTable with MRT
This commit is contained in:
@@ -132,9 +132,6 @@ export const studyProgrammeToolTips: Record<string, string> = {
|
||||
|
||||
Jos sivun yläosassa on valittuna ”All study rights” (oletus), mukana ovat myös ohjelmaan siirtyneet ja ohjelmasta pois siirtyneet opiskelijat.
|
||||
`,
|
||||
name: `
|
||||
Pitkät kurssinimet on lyhennetty. Näet ne kokonaan laittamalla hiiren nimen yläpuolelle.
|
||||
`,
|
||||
notCompleted: `
|
||||
Sisältää opiskelijat, jotka ovat ilmoittautuneet kurssille, mutta eivät ole suorittaneet sitä ja opiskelijat, jotka ovat saaneet hylätyn arvosanan.
|
||||
`,
|
||||
|
||||
-262
@@ -1,262 +0,0 @@
|
||||
import { range } from 'lodash'
|
||||
import { Link } from 'react-router'
|
||||
import { Item, Icon } from 'semantic-ui-react'
|
||||
|
||||
import { studyProgrammeToolTips } from '@/common/InfoToolTips'
|
||||
import { GetTextIn, useLanguage } from '@/components/LanguagePicker/useLanguage'
|
||||
import { SortableTable } from '@/components/SortableTable'
|
||||
import { StudyProgrammeCourse } from '@/shared/types'
|
||||
|
||||
const getColumns = (getTextIn: GetTextIn, showStudents: boolean) => {
|
||||
let columns = null
|
||||
|
||||
if (showStudents) {
|
||||
columns = [
|
||||
{
|
||||
key: 'code',
|
||||
title: 'Code',
|
||||
getRowVal: course => course.code,
|
||||
formatValue: code => (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '0.5em' }}>
|
||||
{code}
|
||||
<Item
|
||||
as={Link}
|
||||
target="_blank"
|
||||
to={`/coursestatistics?courseCodes=["${encodeURIComponent(code)}"]&separate=false&unifyOpenUniCourses=false`}
|
||||
>
|
||||
<Icon name="level up alternate" />
|
||||
</Item>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
title: 'Name',
|
||||
helpText: studyProgrammeToolTips.name,
|
||||
getRowVal: course => getTextIn(course.name),
|
||||
formatValue: name => (
|
||||
<div style={{ maxWidth: '400px', overflow: 'hidden', textOverflow: 'ellipsis' }}>{name}</div>
|
||||
),
|
||||
cellProps: course => {
|
||||
return { title: getTextIn(course.name) }
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'total',
|
||||
title: 'Total',
|
||||
filterType: 'range',
|
||||
getRowVal: course => course.totalAllStudents,
|
||||
},
|
||||
{
|
||||
key: 'breakdown',
|
||||
title: 'Breakdown of total',
|
||||
parent: true,
|
||||
sortable: false,
|
||||
filterable: false,
|
||||
children: [
|
||||
{
|
||||
key: 'passed',
|
||||
title: 'Passed',
|
||||
filterType: 'range',
|
||||
getRowVal: course => course.totalAllPassed,
|
||||
},
|
||||
{
|
||||
key: 'not-completed',
|
||||
title: 'Not\ncompleted',
|
||||
filterType: 'range',
|
||||
getRowVal: course => course.totalAllNotCompleted,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'breakdown-passed',
|
||||
title: 'Breakdown of passed students',
|
||||
parent: true,
|
||||
sortable: false,
|
||||
filterable: false,
|
||||
children: [
|
||||
{
|
||||
key: 'totalOwnProgramme',
|
||||
title: 'Major\nstudents',
|
||||
filterType: 'range',
|
||||
getRowVal: course => course.totalProgrammeStudents,
|
||||
},
|
||||
{
|
||||
key: 'totalOtherProgramme',
|
||||
title: 'Non-major\nstudents',
|
||||
filterType: 'range',
|
||||
getRowVal: course => course.totalOtherProgrammeStudents,
|
||||
},
|
||||
{
|
||||
key: 'totalWithoutStudyRight',
|
||||
title: 'Non-degree\nstudents',
|
||||
filterType: 'range',
|
||||
getRowVal: course => course.totalWithoutStudyRightStudents,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'exluded',
|
||||
title: 'Not included in\ntotal nor passed',
|
||||
parent: true,
|
||||
sortable: false,
|
||||
filterable: false,
|
||||
children: [
|
||||
{
|
||||
key: 'transfer',
|
||||
title: 'Transferred\nstudents',
|
||||
helpText: studyProgrammeToolTips.transferredCourses,
|
||||
cellStyle: { textAlign: 'right' },
|
||||
filterType: 'range',
|
||||
getRowVal: course => course.totalTransferStudents,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
} else {
|
||||
columns = [
|
||||
{
|
||||
key: 'code',
|
||||
title: 'Code',
|
||||
getRowVal: course => course.code,
|
||||
formatValue: code => (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '0.5em' }}>
|
||||
{code}
|
||||
<Item
|
||||
as={Link}
|
||||
target="_blank"
|
||||
to={`/coursestatistics?courseCodes=["${encodeURIComponent(code)}"]&separate=false&unifyOpenUniCourses=false`}
|
||||
>
|
||||
<Icon name="level up alternate" />
|
||||
</Item>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
title: 'Name',
|
||||
helpText: studyProgrammeToolTips.name,
|
||||
getRowVal: course => getTextIn(course.name),
|
||||
formatValue: name => (
|
||||
<div style={{ maxWidth: '400px', overflow: 'hidden', textOverflow: 'ellipsis' }}>{name}</div>
|
||||
),
|
||||
cellProps: course => ({
|
||||
title: getTextIn(course.name),
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: 'total',
|
||||
title: 'Total\ncredits',
|
||||
filterType: 'range',
|
||||
getRowVal: course => course.totalAllCredits,
|
||||
},
|
||||
{
|
||||
key: 'totalOwnProgramme',
|
||||
title: 'Major\ncredits',
|
||||
filterType: 'range',
|
||||
getRowVal: course => course.totalProgrammeCredits,
|
||||
},
|
||||
{
|
||||
key: 'totalOtherProgramme',
|
||||
title: 'Non-major\ncredits',
|
||||
filterType: 'range',
|
||||
getRowVal: course => course.totalOtherProgrammeCredits,
|
||||
},
|
||||
{
|
||||
key: 'totalWithoutStudyRight',
|
||||
title: 'Non-degree\ncredits',
|
||||
filterType: 'range',
|
||||
getRowVal: course => course.totalWithoutStudyRightCredits,
|
||||
},
|
||||
{
|
||||
key: 'totalTransfer',
|
||||
title: 'Transferred\ncredits',
|
||||
filterType: 'range',
|
||||
getRowVal: course => course.totalTransferCredits,
|
||||
},
|
||||
]
|
||||
}
|
||||
const typeColumn = {
|
||||
key: 'type',
|
||||
title: 'Type',
|
||||
getRowVal: course => (course.isStudyModule ? 'Module' : 'Course'),
|
||||
}
|
||||
return [...columns, typeColumn]
|
||||
}
|
||||
|
||||
const filterDataByYear = (data: StudyProgrammeCourse[], fromYear: number, toYear: number) => {
|
||||
const yearRange = range(fromYear, Number(toYear) + 1)
|
||||
const filteredAndMergedCourses = data
|
||||
.filter(course => {
|
||||
const arr = Object.keys(course.years).some(key => yearRange.includes(Number(key)))
|
||||
return arr
|
||||
})
|
||||
.map(course => {
|
||||
const values = Object.entries(course.years).reduce(
|
||||
(acc, curr) => {
|
||||
if (yearRange.includes(Number(curr[0]))) {
|
||||
acc.totalAllStudents += curr[1].totalAllStudents
|
||||
acc.totalAllPassed += curr[1].totalPassed
|
||||
acc.totalAllNotCompleted += curr[1].totalNotCompleted
|
||||
acc.totalAllCredits += curr[1].totalAllCredits
|
||||
acc.totalProgrammeStudents += curr[1].totalProgrammeStudents
|
||||
acc.totalProgrammeCredits += curr[1].totalProgrammeCredits
|
||||
acc.totalOtherProgrammeStudents += curr[1].totalOtherProgrammeStudents
|
||||
acc.totalOtherProgrammeCredits += curr[1].totalOtherProgrammeCredits
|
||||
acc.totalWithoutStudyRightStudents += curr[1].totalWithoutStudyRightStudents
|
||||
acc.totalWithoutStudyRightCredits += curr[1].totalWithoutStudyRightCredits
|
||||
acc.totalTransferCredits += curr[1].totalTransferCredits
|
||||
acc.totalTransferStudents += curr[1].totalTransferStudents
|
||||
}
|
||||
|
||||
return acc
|
||||
},
|
||||
{
|
||||
totalAllStudents: 0,
|
||||
totalAllPassed: 0,
|
||||
totalAllNotCompleted: 0,
|
||||
totalAllCredits: 0,
|
||||
totalProgrammeStudents: 0,
|
||||
totalProgrammeCredits: 0,
|
||||
totalOtherProgrammeStudents: 0,
|
||||
totalOtherProgrammeCredits: 0,
|
||||
totalWithoutStudyRightStudents: 0,
|
||||
totalWithoutStudyRightCredits: 0,
|
||||
totalTransferCredits: 0,
|
||||
totalTransferStudents: 0,
|
||||
}
|
||||
)
|
||||
return {
|
||||
...values,
|
||||
code: course.code,
|
||||
name: course.name,
|
||||
isStudyModule: course.isStudyModule,
|
||||
}
|
||||
})
|
||||
|
||||
return filteredAndMergedCourses
|
||||
}
|
||||
|
||||
export const OverallStatsTable = ({
|
||||
data,
|
||||
fromYear,
|
||||
showStudents,
|
||||
toYear,
|
||||
}: {
|
||||
data: StudyProgrammeCourse[]
|
||||
fromYear: number
|
||||
showStudents: boolean
|
||||
toYear: number
|
||||
}) => {
|
||||
const { getTextIn } = useLanguage()
|
||||
|
||||
return (
|
||||
<SortableTable
|
||||
columns={getColumns(getTextIn, showStudents)}
|
||||
data={filterDataByYear(data, fromYear, toYear)}
|
||||
defaultSort={['name', 'asc']}
|
||||
featureName="programme_courses"
|
||||
title="Student statistics for study programme courses"
|
||||
/>
|
||||
)
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { NorthEast as NorthEastIcon } from '@mui/icons-material'
|
||||
import { IconButton, Stack, Typography } from '@mui/material'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
export const CodeCell = ({ code }: { code: string }) => {
|
||||
return (
|
||||
<Stack alignItems="center" direction="row" gap={1} justifyContent="space-between">
|
||||
<Typography variant="body2">{code}</Typography>
|
||||
<Link
|
||||
title={`Open course statistics for ${code}`}
|
||||
to={`/coursestatistics?courseCodes=["${encodeURIComponent(code)}"]&separate=false&unifyOpenUniCourses=false`}
|
||||
>
|
||||
<IconButton color="primary" data-cy={`${code.toLowerCase()}-course-statistics-link-button`}>
|
||||
<NorthEastIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Link>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { Box, Stack } from '@mui/material'
|
||||
|
||||
export const HeaderCell = ({ value }: { value: string }) => {
|
||||
return (
|
||||
<Box sx={{ textAlign: 'left' }}>
|
||||
<Stack>
|
||||
{value.split(' ').map(subString => (
|
||||
<span key={subString}>{subString}</span>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
import { MaterialReactTable, MRT_ColumnDef, useMaterialReactTable } from 'material-react-table'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { useLanguage } from '@/components/LanguagePicker/useLanguage'
|
||||
import { Name, StudyProgrammeCourse } from '@/shared/types'
|
||||
import { CodeCell } from './CodeCell'
|
||||
import { HeaderCell } from './HeaderCell'
|
||||
import { filterDataByYear } from './util'
|
||||
export const OverallStatsTable = ({
|
||||
data,
|
||||
fromYear,
|
||||
showStudents,
|
||||
toYear,
|
||||
}: {
|
||||
data: StudyProgrammeCourse[]
|
||||
fromYear: number
|
||||
showStudents: boolean
|
||||
toYear: number
|
||||
}) => {
|
||||
const { getTextIn } = useLanguage()
|
||||
|
||||
const filteredData = useMemo(() => filterDataByYear(data, fromYear, toYear), [data, fromYear, toYear])
|
||||
|
||||
const columns = useMemo<MRT_ColumnDef<any>[]>(
|
||||
() => [
|
||||
{
|
||||
accessorKey: 'isStudyModule',
|
||||
header: 'Type',
|
||||
Cell: ({ cell }) => (cell.getValue<boolean>() ? 'Module' : 'Course'),
|
||||
},
|
||||
{
|
||||
accessorKey: 'code',
|
||||
header: 'Code',
|
||||
Cell: ({ cell }) => <CodeCell code={cell.getValue<string>()} />,
|
||||
},
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: 'Name',
|
||||
Cell: ({ cell }) => getTextIn(cell.getValue<Name>()),
|
||||
},
|
||||
{
|
||||
accessorKey: 'totalAllStudents',
|
||||
header: 'Total',
|
||||
Cell: ({ cell }) => cell.getValue<number>(),
|
||||
muiTableHeadCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
muiTableBodyCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'totalProgrammeCredits',
|
||||
header: 'Major credits',
|
||||
Header: <HeaderCell value="Major credits" />,
|
||||
Cell: ({ cell }) => cell.getValue<number>(),
|
||||
muiTableHeadCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
muiTableBodyCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'totalOtherProgrammeCredits',
|
||||
header: 'Non-major credits',
|
||||
Header: <HeaderCell value="Non-major credits" />,
|
||||
Cell: ({ cell }) => cell.getValue<number>(),
|
||||
muiTableHeadCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
muiTableBodyCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'totalWithoutStudyRightCredits',
|
||||
header: 'Non-degree credits',
|
||||
Header: <HeaderCell value="Non-degree credits" />,
|
||||
Cell: ({ cell }) => cell.getValue<number>(),
|
||||
muiTableHeadCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
muiTableBodyCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'totalTransferCredits',
|
||||
header: 'Transferred credits',
|
||||
Header: <HeaderCell value="Transferred credits" />,
|
||||
Cell: ({ cell }) => cell.getValue<number>(),
|
||||
muiTableHeadCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
muiTableBodyCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
},
|
||||
],
|
||||
[getTextIn]
|
||||
)
|
||||
|
||||
const columnsStudents = useMemo<MRT_ColumnDef<any>[]>(
|
||||
() => [
|
||||
{
|
||||
accessorKey: 'isStudyModule',
|
||||
header: 'Type',
|
||||
Cell: ({ cell }) => (cell.getValue<boolean>() ? 'Module' : 'Course'),
|
||||
},
|
||||
{
|
||||
accessorKey: 'code',
|
||||
header: 'Code',
|
||||
Cell: ({ cell }) => <CodeCell code={cell.getValue<string>()} />,
|
||||
},
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: 'Name',
|
||||
Cell: ({ cell }) => getTextIn(cell.getValue<Name>()),
|
||||
},
|
||||
{
|
||||
accessorKey: 'totalAllStudents',
|
||||
header: 'Total',
|
||||
Cell: ({ cell }) => cell.getValue<number>(),
|
||||
muiTableHeadCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
muiTableBodyCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'breakdown',
|
||||
header: 'Breakdown of total',
|
||||
columns: [
|
||||
{
|
||||
accessorKey: 'totalAllPassed',
|
||||
header: 'Passed',
|
||||
Header: <HeaderCell value="Passed" />,
|
||||
Cell: ({ cell }) => cell.getValue<number>(),
|
||||
muiTableHeadCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
muiTableBodyCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'totalAllNotCompleted',
|
||||
header: 'Not completed',
|
||||
Header: <HeaderCell value="Not completed" />,
|
||||
Cell: ({ cell }) => cell.getValue<number>(),
|
||||
muiTableHeadCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
muiTableBodyCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'breakdown-passed',
|
||||
header: 'Breakdown of passed students',
|
||||
columns: [
|
||||
{
|
||||
accessorKey: 'totalProgrammeStudents',
|
||||
header: 'Major students',
|
||||
Header: <HeaderCell value="Major students" />,
|
||||
Cell: ({ cell }) => cell.getValue<number>(),
|
||||
muiTableHeadCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
muiTableBodyCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'totalOtherProgrammeStudents',
|
||||
header: 'Non-major students',
|
||||
Header: <HeaderCell value="Non-major students" />,
|
||||
Cell: ({ cell }) => cell.getValue<number>(),
|
||||
muiTableHeadCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
muiTableBodyCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'totalWithoutStudyRightStudents',
|
||||
header: 'Non-degree students',
|
||||
Header: <HeaderCell value="Non-degree students" />,
|
||||
Cell: ({ cell }) => cell.getValue<number>(),
|
||||
muiTableHeadCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
muiTableBodyCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'excluded',
|
||||
header: 'Not included in total or passed',
|
||||
columns: [
|
||||
{
|
||||
accessorKey: 'totalTransferStudents',
|
||||
header: 'Transferred\nstudents',
|
||||
Cell: ({ cell }) => cell.getValue<number>(),
|
||||
muiTableHeadCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
muiTableBodyCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[getTextIn]
|
||||
)
|
||||
|
||||
const table = useMaterialReactTable({
|
||||
columns: showStudents ? columnsStudents : columns,
|
||||
data: filteredData,
|
||||
defaultColumn: { size: 0 },
|
||||
enableDensityToggle: false,
|
||||
enableHiding: false,
|
||||
initialState: {
|
||||
density: 'compact',
|
||||
pagination: {
|
||||
pageSize: 20,
|
||||
pageIndex: 0,
|
||||
},
|
||||
sorting: [
|
||||
{
|
||||
id: 'totalAllStudents',
|
||||
desc: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
return <MaterialReactTable table={table} />
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import { range } from 'lodash'
|
||||
|
||||
import { StudyProgrammeCourse } from '@/shared/types'
|
||||
|
||||
export const filterDataByYear = (data: StudyProgrammeCourse[], fromYear: number, toYear: number) => {
|
||||
const yearRange = range(fromYear, Number(toYear) + 1)
|
||||
const filteredAndMergedCourses = data
|
||||
.filter(course => {
|
||||
const arr = Object.keys(course.years).some(key => yearRange.includes(Number(key)))
|
||||
return arr
|
||||
})
|
||||
.map(course => {
|
||||
const values = Object.entries(course.years).reduce(
|
||||
(acc, curr) => {
|
||||
if (yearRange.includes(Number(curr[0]))) {
|
||||
acc.totalAllStudents += curr[1].totalAllStudents
|
||||
acc.totalAllPassed += curr[1].totalPassed
|
||||
acc.totalAllNotCompleted += curr[1].totalNotCompleted
|
||||
acc.totalAllCredits += curr[1].totalAllCredits
|
||||
acc.totalProgrammeStudents += curr[1].totalProgrammeStudents
|
||||
acc.totalProgrammeCredits += curr[1].totalProgrammeCredits
|
||||
acc.totalOtherProgrammeStudents += curr[1].totalOtherProgrammeStudents
|
||||
acc.totalOtherProgrammeCredits += curr[1].totalOtherProgrammeCredits
|
||||
acc.totalWithoutStudyRightStudents += curr[1].totalWithoutStudyRightStudents
|
||||
acc.totalWithoutStudyRightCredits += curr[1].totalWithoutStudyRightCredits
|
||||
acc.totalTransferCredits += curr[1].totalTransferCredits
|
||||
acc.totalTransferStudents += curr[1].totalTransferStudents
|
||||
}
|
||||
|
||||
return acc
|
||||
},
|
||||
{
|
||||
totalAllStudents: 0,
|
||||
totalAllPassed: 0,
|
||||
totalAllNotCompleted: 0,
|
||||
totalAllCredits: 0,
|
||||
totalProgrammeStudents: 0,
|
||||
totalProgrammeCredits: 0,
|
||||
totalOtherProgrammeStudents: 0,
|
||||
totalOtherProgrammeCredits: 0,
|
||||
totalWithoutStudyRightStudents: 0,
|
||||
totalWithoutStudyRightCredits: 0,
|
||||
totalTransferCredits: 0,
|
||||
totalTransferStudents: 0,
|
||||
}
|
||||
)
|
||||
return {
|
||||
...values,
|
||||
code: course.code,
|
||||
name: course.name,
|
||||
isStudyModule: course.isStudyModule,
|
||||
}
|
||||
})
|
||||
|
||||
return filteredAndMergedCourses
|
||||
}
|
||||
Reference in New Issue
Block a user