mirror of
https://github.com/UniversityOfHelsinkiCS/oodikone.git
synced 2026-10-12 19:03:39 +03:00
[Frontend][OT] -UNSAFE- Add basic filtering capabilities
This commit is contained in:
committed by
joonas-a
parent
8346c3eee2
commit
945c57d8eb
@@ -9,9 +9,10 @@ type FilterRangeProps = {
|
||||
max: number
|
||||
range: [number, number]
|
||||
setRange: (value: any) => void
|
||||
hideIncrements?: true
|
||||
}
|
||||
|
||||
export const FilterRange = ({ text, min, max, range, setRange }: FilterRangeProps) => (
|
||||
export const FilterRange = ({ text, min, max, range, setRange, hideIncrements }: FilterRangeProps) => (
|
||||
<Stack spacing={2}>
|
||||
<Typography>{text}</Typography>
|
||||
<Slider
|
||||
@@ -27,7 +28,7 @@ export const FilterRange = ({ text, min, max, range, setRange }: FilterRangeProp
|
||||
data-cy="FilterRangeStart"
|
||||
onChange={({ target }) => setRange([target.value === '' || +target.value, range[1]])}
|
||||
size="small"
|
||||
type="number"
|
||||
type={hideIncrements ? "text" : "number"}
|
||||
value={range[0]}
|
||||
/>
|
||||
<Typography component="span" sx={{ alignContent: 'center' }} variant="h6">
|
||||
@@ -37,7 +38,7 @@ export const FilterRange = ({ text, min, max, range, setRange }: FilterRangeProp
|
||||
data-cy="FilterRangeEnd"
|
||||
onChange={({ target }) => setRange([range[0], target.value === '' || +target.value])}
|
||||
size="small"
|
||||
type="number"
|
||||
type={hideIncrements ? "text" : "number"}
|
||||
value={range[1]}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ReactNode } from 'react'
|
||||
import { OodiTableDataRow } from '@/components/OodiTable/components/Cell'
|
||||
import { OodiTableHeaderGroup } from '@/components/OodiTable/components/Header'
|
||||
import { OodiTablePagination } from '@/components/OodiTable/components/Pagination'
|
||||
import { OodiTableFilters } from './components/Filter'
|
||||
|
||||
export const OodiTableContainer = <OTData extends RowData>({
|
||||
table,
|
||||
@@ -70,6 +71,7 @@ export const OodiTableContainer = <OTData extends RowData>({
|
||||
},
|
||||
}}
|
||||
>
|
||||
{OodiTableFilters(table)}
|
||||
{table.getAggregationRowModel().rows.map(row => OodiTableDataRow(row, true))}
|
||||
{table.getRowModel().rows.map(row => OodiTableDataRow(row))}
|
||||
</TableBody>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { TableCell } from "@mui/material"
|
||||
import TableRow from "@mui/material/TableRow"
|
||||
import type { Table } from "@tanstack/react-table"
|
||||
|
||||
export const OodiTableFilters = <TData,>(table: Table<TData>) => {
|
||||
if (!table.options.enableFilters) return null
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
{
|
||||
// NOTE: getVisibleLeafColumns is used as they should be the only ones with filtering capabilities
|
||||
table.getVisibleLeafColumns().map(column => (
|
||||
<TableCell key={column.id}>
|
||||
{ column.getCanFilter() && column.columnDef.meta?.filterComponent?.() }
|
||||
</TableCell>
|
||||
))
|
||||
}
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
@@ -40,4 +40,8 @@ declare module '@tanstack/react-table' {
|
||||
interface Row {
|
||||
getIsAggregationRow?: () => boolean
|
||||
}
|
||||
|
||||
interface ColumnMeta<TData extends RowData, TValue> {
|
||||
filterComponent?: ReactNode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ColumnDef, RowData, Table, TableOptions } from '@tanstack/react-table'
|
||||
import { useReactTable, getCoreRowModel, getPaginationRowModel, getSortedRowModel } from '@tanstack/react-table'
|
||||
import { useReactTable, getCoreRowModel, getPaginationRowModel, getSortedRowModel, getFilteredRowModel } from '@tanstack/react-table'
|
||||
import { useState, type ReactNode } from 'react'
|
||||
|
||||
import { AggregationRowFeature, VerticalHeaderFeature, ZebrastripesFeature } from '@/components/OodiTable/features'
|
||||
@@ -45,6 +45,7 @@ export const OodiTable = <TData extends RowData>({
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
},
|
||||
options
|
||||
)
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ export const getSemesterEnrollmentsContent =
|
||||
firstSemester: number
|
||||
}) =>
|
||||
(
|
||||
student: { studentNumber: string; studyrightEnd: Date | null; secondStudyrightEnd: Date | null },
|
||||
student: { studentNumber: string; studyrightEnd?: Date | null; secondStudyrightEnd: Date | null },
|
||||
studyright: StudentStudyRight | undefined
|
||||
) => {
|
||||
if (!studyright) return []
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import Box from "@mui/material/Box"
|
||||
import TextField from "@mui/material/TextField"
|
||||
|
||||
import { FilterSelect } from "@/components/FilterView/filters/common/FilterSelect"
|
||||
import { useDebounce } from "@/hooks/debounce"
|
||||
import { FilterRange } from "@/components/FilterView/filters/common/FilterRange"
|
||||
|
||||
export const TextSelector = ({ label, value, setValue }) => {
|
||||
const [debouncedValue, setDebounced] = useDebounce<string>(value, setValue)
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<TextField
|
||||
label={label ?? ''}
|
||||
onChange={({ target }) => setDebounced(target.value)}
|
||||
size="small"
|
||||
value={debouncedValue}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
export const CheckBoxSelector = () => {
|
||||
return <p>CheckBox selector</p>
|
||||
}
|
||||
|
||||
export const MultiSelector = ({ value, setValue, options }) => <FilterSelect
|
||||
filterKey={'key'}
|
||||
label="Choose stuff"
|
||||
multiple
|
||||
onChange={({ target }) => setValue(target.value as string[])}
|
||||
value={value as any}
|
||||
options={options}
|
||||
/>
|
||||
|
||||
export const RangeSelector = ({ value, setValue, options }) => {
|
||||
const maxVal = Math.max(...options) || 0
|
||||
const minVal = Math.min(...options) || 0
|
||||
|
||||
const [debouncedValue, setDebounced] = useDebounce<[number, number]>(value ?? [minVal, maxVal], setValue)
|
||||
|
||||
|
||||
return (
|
||||
<Box sx={{ minWidth: '20em' }}>
|
||||
<FilterRange
|
||||
text={""}
|
||||
min={minVal}
|
||||
max={maxVal}
|
||||
range={debouncedValue}
|
||||
setRange={setDebounced}
|
||||
hideIncrements
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export const DateRangeSelector = () => {
|
||||
return <p>Date range selector</p>
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import Box from '@mui/material/Box'
|
||||
import Tab from '@mui/material/Tab'
|
||||
import Tabs from '@mui/material/Tabs'
|
||||
import Tooltip from '@mui/material/Tooltip'
|
||||
import Typography from '@mui/material/Typography'
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
@@ -26,7 +25,10 @@ import { getFullLanguage, Language } from '@oodikone/shared/language'
|
||||
import { range } from '@oodikone/shared/util'
|
||||
import { OodiTable } from '@/components/OodiTable'
|
||||
import { CloseToGraduationData } from '@oodikone/shared/routes/populations'
|
||||
import { createColumnHelper, TableOptions } from '@tanstack/react-table'
|
||||
import { createColumnHelper, type TableOptions } from '@tanstack/react-table'
|
||||
import { CheckBoxSelector, DateRangeSelector, MultiSelector, RangeSelector, TextSelector } from './filters'
|
||||
import uniqBy from 'lodash-es/uniqBy'
|
||||
import Tooltip from '@mui/material/Tooltip'
|
||||
|
||||
const NUMBER_OF_DISPLAYED_SEMESTERS = 6
|
||||
|
||||
@@ -45,7 +47,7 @@ export const CloseToGraduation = () => {
|
||||
const { semesters, currentSemester } = semesterData
|
||||
|
||||
const [selectedTab, setSelectedTab] = useState(0)
|
||||
const { getTextIn } = useLanguage()
|
||||
const { getTextIn, language } = useLanguage()
|
||||
const { getSemesterEnrollmentsContent, getSemesterEnrollmentsVal } = useMemo(
|
||||
() =>
|
||||
getSemestersPresentFunctions({
|
||||
@@ -71,9 +73,70 @@ export const CloseToGraduation = () => {
|
||||
[currentSemesterCode]
|
||||
)
|
||||
|
||||
const [columnFilters, setColumnFilters] = useState({
|
||||
curriculumPeriod: [],
|
||||
faculty: [],
|
||||
programme: [],
|
||||
studyTrack: [],
|
||||
'student.studentNumber': '',
|
||||
'credits.hops': [0, 1000],
|
||||
'credits.all': [0, 1000],
|
||||
numberOfAbsentSemesters: [],
|
||||
numberOfUsedSemesters: [],
|
||||
})
|
||||
|
||||
const displayedData = (selectedTab === 0 ? students?.bachelor : students?.masterAndLicentiate) ?? []
|
||||
|
||||
const curriculumPeriods = [...new Set(
|
||||
displayedData.map(({ curriculumPeriod }) => curriculumPeriod).filter(curr => curr !== null)
|
||||
)].map(val => ({
|
||||
key: val,
|
||||
value: val,
|
||||
text: val,
|
||||
}))
|
||||
.sort(({ key: a }, { key: b }) => b.localeCompare(a))
|
||||
|
||||
const studyProgrammes = uniqBy(
|
||||
displayedData.map(({ programme }) => programme).filter(curr => curr !== null),
|
||||
'code'
|
||||
).map(({ code, name }) => ({
|
||||
key: code,
|
||||
value: code,
|
||||
text: `${code} - ${getTextIn(name)}`,
|
||||
}))
|
||||
.sort(({ key: a }, { key: b }) => a.localeCompare(b))
|
||||
|
||||
const studyTracks = uniqBy(
|
||||
displayedData.map(({ programme }) => programme.studyTrack).filter(curr => curr !== null),
|
||||
language
|
||||
)
|
||||
.map(name => getTextIn(name))
|
||||
.map(val => ({
|
||||
key: val,
|
||||
value: val,
|
||||
text: val,
|
||||
}))
|
||||
.sort(({ key: a }, { key: b }) => a?.localeCompare(b ?? '') ?? 0)
|
||||
|
||||
const faculties = uniqBy(
|
||||
displayedData.map(({ faculty }) => faculty).filter(curr => curr !== null),
|
||||
language
|
||||
)
|
||||
.map(name => getTextIn(name))
|
||||
.map(val => ({
|
||||
key: val,
|
||||
value: val,
|
||||
text: val,
|
||||
}))
|
||||
.sort(({ key: a }, { key: b }) => a?.localeCompare(b ?? '') ?? 0)
|
||||
|
||||
const hopsCredits = displayedData.map(({ credits }) => credits.hops).filter(Number)
|
||||
const allCredits = displayedData.map(({ credits }) => credits.all).filter(Number)
|
||||
|
||||
const ooditableColumns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor('student.studentNumber', {
|
||||
id: 'student.studentNumber',
|
||||
header: 'Student number',
|
||||
cell: cell => (
|
||||
<StudentInfoItem
|
||||
@@ -81,46 +144,65 @@ export const CloseToGraduation = () => {
|
||||
studentNumber={cell.getValue<string>()}
|
||||
/>
|
||||
),
|
||||
// filterFn: 'startsWith',
|
||||
meta: {
|
||||
filterComponent: () => <TextSelector label="Student number" value={columnFilters['student.studentNumber']} setValue={(val) => setColumnFilters(prev => ({ ...prev, 'student.studentNumber': val }))} />
|
||||
}
|
||||
}),
|
||||
columnHelper.accessor('student.name', {
|
||||
id: 'student.name',
|
||||
header: 'Name',
|
||||
// filterFn: 'startsWith',
|
||||
}),
|
||||
columnHelper.accessor('student.phoneNumber', {
|
||||
id: 'student.phoneNumber',
|
||||
header: 'Phone number',
|
||||
}),
|
||||
columnHelper.accessor('student.email', {
|
||||
id: 'student.email',
|
||||
header: 'Email',
|
||||
}),
|
||||
columnHelper.accessor('student.secondaryEmail', {
|
||||
id: 'student.secondaryEmail',
|
||||
header: 'Secondary email',
|
||||
}),
|
||||
columnHelper.accessor(row => getFullLanguage(row.student.preferredLanguage as Language), {
|
||||
id: 'preferredLanguage',
|
||||
header: 'Preferred language',
|
||||
// filterVariant: 'multi-select',
|
||||
filterFn: (row, columnId, filterValue) => !filterValue.length || filterValue.includes(row.original[columnId]),
|
||||
meta: {
|
||||
filterComponent: () => <MultiSelector value={[]} setValue={(val) => setColumnFilters(prev => ({ ...prev, preferredLanguage: val }))} options={[]} />
|
||||
}
|
||||
}),
|
||||
columnHelper.accessor(row => getTextIn(row.faculty), {
|
||||
header: 'Faculty',
|
||||
id: 'faculty',
|
||||
// filterVariant: 'multi-select',
|
||||
filterFn: (row, columnId, filterValue) => !filterValue.length || filterValue.includes(getTextIn(row.original[columnId])),
|
||||
meta: {
|
||||
filterComponent: () => <MultiSelector value={columnFilters.faculty} setValue={(val) => setColumnFilters(prev => ({ ...prev, faculty: val }))} options={faculties} />
|
||||
}
|
||||
}),
|
||||
columnHelper.accessor(row => getTextIn(row.programme.name), {
|
||||
header: 'Programme',
|
||||
id: 'programme',
|
||||
// filterVariant: 'multi-select',
|
||||
filterFn: (row, columnId, filterValue) => !filterValue.length || filterValue.includes(row.original[columnId]?.code),
|
||||
meta: {
|
||||
filterComponent: () => <MultiSelector value={columnFilters.programme} setValue={(val) => setColumnFilters(prev => ({ ...prev, programme: val }))} options={studyProgrammes} />
|
||||
}
|
||||
}),
|
||||
columnHelper.accessor(row => getTextIn(row.programme.studyTrack), {
|
||||
id: 'studyTrack',
|
||||
header: 'Study track',
|
||||
// filterVariant: 'multi-select',
|
||||
filterFn: (row, _, filterValue) => !filterValue.length || filterValue.includes(getTextIn(row.original.programme?.studyTrack)),
|
||||
meta: {
|
||||
filterComponent: () => <MultiSelector value={columnFilters.studyTrack} setValue={(val) => setColumnFilters(prev => ({ ...prev, studyTrack: val }))} options={studyTracks} />
|
||||
}
|
||||
}),
|
||||
columnHelper.accessor(row => new Date(row.studyright.startDate), {
|
||||
id: 'startOfStudyRight',
|
||||
cell: ({ cell }) => reformatDate(cell.getValue<Date>(), DateFormat.ISO_DATE),
|
||||
header: 'Start of study right',
|
||||
// filterVariant: 'date-range',
|
||||
meta: {
|
||||
filterComponent: () => <DateRangeSelector />
|
||||
}
|
||||
}),
|
||||
columnHelper.accessor(row => new Date(row.programme.startedAt), {
|
||||
id: 'startedInProgramme',
|
||||
@@ -131,27 +213,37 @@ export const CloseToGraduation = () => {
|
||||
tooltipText="For students with only a study right in the master’s programme, this date is the same as 'Start of study right'. For students with study rights in both the bachelor’s and master’s programmes, this date represents when they started in the master’s programme (i.e. one day after graduating from the bachelor’s programme)."
|
||||
/>
|
||||
),
|
||||
// filterVariant: 'date-range',
|
||||
meta: {
|
||||
filterComponent: () => <DateRangeSelector />
|
||||
}
|
||||
}),
|
||||
columnHelper.accessor('credits.hops', {
|
||||
id: 'credits.hops',
|
||||
header: 'Completed credits – HOPS',
|
||||
// accessorKey: 'credits.hops',
|
||||
// filterVariant: 'range',
|
||||
// muiTableBodyCellProps: {
|
||||
// align: 'right',
|
||||
// },
|
||||
filterFn: (row, _, filterValue) => {
|
||||
const [min, max] = filterValue
|
||||
const val = row.original.credits?.hops
|
||||
|
||||
return min <= val && val <= max
|
||||
},
|
||||
meta: {
|
||||
filterComponent: () => <RangeSelector value={columnFilters['credits.hops']} setValue={(val) => setColumnFilters(prev => ({ ...prev, 'credits.hops': val }))} options={hopsCredits} />
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor('credits.all', {
|
||||
id: 'credits.all',
|
||||
header: 'Completed credits – Total',
|
||||
// accessorKey: 'credits.all',
|
||||
// filterVariant: 'range',
|
||||
// muiTableBodyCellProps: {
|
||||
// align: 'right',
|
||||
// },
|
||||
filterFn: (row, _, filterValue) => {
|
||||
const [min, max] = filterValue
|
||||
const val = row.original.credits?.all
|
||||
|
||||
return min <= val && val <= max
|
||||
},
|
||||
meta: {
|
||||
filterComponent: () => <RangeSelector value={columnFilters['credits.all']} setValue={(val) => setColumnFilters(prev => ({ ...prev, 'credits.all': val }))} options={allCredits} />
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor('studyright.isBaMa', {
|
||||
// accessorKey: 'studyright.isBaMa',
|
||||
// filterVariant: 'checkbox',
|
||||
cell: cell => <CheckIconWithTitle visible={cell.getValue<boolean>()} />,
|
||||
header: () => (
|
||||
<TableHeaderWithTooltip
|
||||
@@ -159,16 +251,22 @@ export const CloseToGraduation = () => {
|
||||
tooltipText="Indicates whether the student has been granted the study right to complete both a bachelor's and a master's degree."
|
||||
/>
|
||||
),
|
||||
meta: {
|
||||
filterComponent: () => <CheckBoxSelector />
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor('curriculumPeriod', {
|
||||
// accessorKey: 'curriculumPeriod',
|
||||
// filterVariant: 'multi-select',
|
||||
header: () => (
|
||||
<TableHeaderWithTooltip
|
||||
header="Curriculum period"
|
||||
tooltipText="The curriculum period the student has chosen for their primary study plan"
|
||||
/>
|
||||
),
|
||||
enableColumnFilter: true,
|
||||
filterFn: (row, columnId, filterValue) => !filterValue.length || filterValue.includes(row.original[columnId]),
|
||||
meta: {
|
||||
filterComponent: () => <MultiSelector value={columnFilters.curriculumPeriod} setValue={(val) => setColumnFilters(prev => ({ ...prev, curriculumPeriod: val }))} options={curriculumPeriods} />
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor(row => getSemesterEnrollmentsVal(row.studyright), {
|
||||
header: 'Semester enrollments',
|
||||
@@ -188,34 +286,29 @@ export const CloseToGraduation = () => {
|
||||
id: 'semesterEnrollments',
|
||||
}),
|
||||
columnHelper.accessor('numberOfAbsentSemesters', {
|
||||
// accessorKey: 'numberOfAbsentSemesters',
|
||||
// filterVariant: 'range',
|
||||
header: () => (
|
||||
<TableHeaderWithTooltip
|
||||
header="Semesters absent"
|
||||
tooltipText="The number of semesters the student has been absent (both statutory (*lakiperusteinen*) and non-statutory absences) during their study right. The current semester is included."
|
||||
/>
|
||||
),
|
||||
// muiTableBodyCellProps: {
|
||||
// align: 'right',
|
||||
// },
|
||||
meta: {
|
||||
filterComponent: () => <RangeSelector value={columnFilters.numberOfAbsentSemesters} setValue={(val) => setColumnFilters(prev => ({ ...prev, numberOfAbsentSemesters: val }))} options={[]} />
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor('numberOfUsedSemesters', {
|
||||
// accessorKey: 'numberOfUsedSemesters',
|
||||
// filterVariant: 'range',
|
||||
header: () => (
|
||||
<TableHeaderWithTooltip
|
||||
header="Semesters used"
|
||||
tooltipText="The number of semesters the student has either been enrolled as present (*läsnäoleva*) or the enrollment was neglected (*laiminlyöty*) during their study right. The current semester is included."
|
||||
/>
|
||||
),
|
||||
// muiTableBodyCellProps: {
|
||||
// align: 'right',
|
||||
// },
|
||||
meta: {
|
||||
filterComponent: () => <RangeSelector value={columnFilters.numberOfUsedSemesters} setValue={(val) => setColumnFilters(prev => ({ ...prev, numberOfUsedSemesters: val }))} options={[]} />
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor(row => row.thesisInfo != null, {
|
||||
id: 'thesisCompleted',
|
||||
// filterVariant: 'checkbox',
|
||||
cell: cell => (
|
||||
<CheckIconWithTitle
|
||||
title={
|
||||
@@ -236,6 +329,9 @@ export const CloseToGraduation = () => {
|
||||
tooltipText="The thesis attainment must be linked to the correct study right. You can see the attainment date, course code, and grade by hovering over the check mark."
|
||||
/>
|
||||
),
|
||||
meta: {
|
||||
filterComponent: () => <CheckBoxSelector />
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor(row => new Date(row.attainmentDates.latestHops), {
|
||||
id: 'latestAttainmentDateHops',
|
||||
@@ -246,7 +342,9 @@ export const CloseToGraduation = () => {
|
||||
tooltipText="The date when the student last completed a course in their primary study plan"
|
||||
/>
|
||||
),
|
||||
// filterVariant: 'date-range',
|
||||
meta: {
|
||||
filterComponent: () => <DateRangeSelector />
|
||||
}
|
||||
}),
|
||||
columnHelper.accessor(row => new Date(row.attainmentDates.latestTotal), {
|
||||
id: 'latestAttainmentDateTotal',
|
||||
@@ -257,7 +355,9 @@ export const CloseToGraduation = () => {
|
||||
tooltipText="The date when the student last completed any course at the university"
|
||||
/>
|
||||
),
|
||||
// filterVariant: 'date-range',
|
||||
meta: {
|
||||
filterComponent: () => <DateRangeSelector />
|
||||
}
|
||||
}),
|
||||
columnHelper.accessor(row => new Date(row.attainmentDates.earliestHops), {
|
||||
id: 'earlistAttainmentDateHops',
|
||||
@@ -268,7 +368,9 @@ export const CloseToGraduation = () => {
|
||||
tooltipText="The date when the student first completed a course in their primary study plan"
|
||||
/>
|
||||
),
|
||||
// filterVariant: 'date-range',
|
||||
meta: {
|
||||
filterComponent: () => <DateRangeSelector />
|
||||
}
|
||||
}),
|
||||
...semestersToInclude.map(semester => columnHelper.accessor(row => {
|
||||
if (!row.studyright.semesterEnrollments) {
|
||||
@@ -281,7 +383,7 @@ export const CloseToGraduation = () => {
|
||||
header: `Enrollment status – ${getTextIn(semesters[`${semester}`]?.name)}`,
|
||||
})),
|
||||
],
|
||||
[semesters, semestersToInclude]
|
||||
[columnFilters, students, semesters, semestersToInclude]
|
||||
)
|
||||
|
||||
const [columnVisibility, setColumnVisibility] = useState<Record<string, boolean>>({
|
||||
@@ -302,36 +404,34 @@ export const CloseToGraduation = () => {
|
||||
setColumnVisibility(prev => ({ ...prev, ...Object.fromEntries(hiddenColumns.map(key => [key, false])) }))
|
||||
}, [ooditableColumns])
|
||||
|
||||
const displayedData = (selectedTab === 0 ? students?.bachelor : students?.masterAndLicentiate) ?? []
|
||||
|
||||
console.log(columnFilters)
|
||||
const ooditable: Partial<TableOptions<CloseToGraduationData>> = {
|
||||
initialState: {
|
||||
sorting: [{ id: 'programme', desc: false }],
|
||||
},
|
||||
enableFilters: true,
|
||||
state: {
|
||||
columnVisibility,
|
||||
columnFilters: Object.entries(columnFilters).map(([id, value]) => ({ id, value })),
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<PageLayout maxWidth="lg">
|
||||
<PageTitle title="Students close to graduation" />
|
||||
<Box sx={{ my: 3, textAlign: 'center' }}>
|
||||
<InfoBox content={closeToGraduationToolTips} />
|
||||
</Box>
|
||||
<PageTitle title="Students close to graduation">
|
||||
<Box sx={{ my: 3, textAlign: 'center' }}>
|
||||
<InfoBox content={closeToGraduationToolTips} />
|
||||
</Box>
|
||||
</PageTitle>
|
||||
<Tabs centered onChange={(_event, value) => setSelectedTab(value)} value={selectedTab}>
|
||||
<Tab label="Bachelor's programmes" />
|
||||
<Tab label="Master's and licentiate's programmes" />
|
||||
</Tabs>
|
||||
{isFetching ? <LoadingSection /> : <OodiTable options={ooditable} data={displayedData} columns={ooditableColumns} />}
|
||||
<Box sx={{ minHeight: '1.25rem' }}>
|
||||
{students?.lastUpdated ? (
|
||||
{!!students?.lastUpdated && (
|
||||
<Typography color="text.secondary" variant="body2">
|
||||
Last updated: {reformatDate(students.lastUpdated, DateFormat.LONG_DATE_TIME)}
|
||||
</Typography>
|
||||
) : null}
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{isFetching ? <LoadingSection /> : <OodiTable options={ooditable} data={displayedData} columns={ooditableColumns} />}
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user