[Completed courses search] Delete old components

This commit is contained in:
Valtteri Kantanen
2024-12-19 15:07:28 +02:00
parent 5fb7168d17
commit 6dd9679267
3 changed files with 0 additions and 454 deletions
@@ -1,233 +0,0 @@
import qs from 'query-string'
import { useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router'
import { Button, Form, Loader, Modal, TextArea } from 'semantic-ui-react'
import { SearchHistory } from '@/components/SearchHistory'
import {
useCreateCourseListMutation,
useDeleteCourseListMutation,
useGetSavedCourseListsQuery,
useUpdateCourseListMutation,
} from '@/redux/completedCoursesSearch'
export const CompletedCoursesSearch = ({ setValues }) => {
const location = useLocation()
const navigate = useNavigate()
const [modal, setModal] = useState(false)
const [courseInput, setCourseInput] = useState('')
const [studentInput, setStudentInput] = useState('')
const [name, setName] = useState('')
const courseLists = useGetSavedCourseListsQuery()
const [searchList, setSearches] = useState(null)
const [selectedSearchId, setSelectedSearchId] = useState('')
const isFetchingOrLoading = courseLists.isLoading || courseLists.isFetching
const isError = courseLists.isError || (courseLists.isSuccess && !courseLists.data)
const [updateCourseList, { isLoading: updateIsLoading, data: updatedData }] = useUpdateCourseListMutation()
const [createCourseList, { isLoading: createIsLoading, data: createdData }] = useCreateCourseListMutation()
const [deleteCourseList, { isLoading: deleteIsLoading, data: deletedData }] = useDeleteCourseListMutation()
const parseQueryFromUrl = () => {
const query = qs.parse(location.search)
if (!Array.isArray(query.courseList)) query.courseList = [query.courseList]
if (!Array.isArray(query.studentList)) query.studentList = [query.studentList]
return query
}
useEffect(() => {
if (courseLists.isSuccess && courseLists.data) {
setSearches(courseLists.data)
}
}, [courseLists])
useEffect(() => {
if (updatedData) {
const updatedSearches = searchList.map(s => (s.id === updatedData.id ? updatedData : s))
setSearches(updatedSearches)
}
}, [updatedData])
useEffect(() => {
if (createdData && !createdData.error) {
const newList = searchList.concat(createdData)
setSearches(newList)
}
}, [createdData])
useEffect(() => {
if (deletedData) {
const filteredSearches = searchList.filter(s => s.id !== deletedData)
setSearches(filteredSearches)
}
}, [deletedData])
useEffect(() => {
if (location.search) {
const query = parseQueryFromUrl()
setValues(query)
}
}, [location.search])
const clearForm = () => {
setCourseInput('')
setStudentInput('')
setSelectedSearchId('')
}
const pushQueryToUrl = query => {
const searchString = qs.stringify(query)
navigate({ search: searchString })
}
const handleClose = () => {
setModal(false)
clearForm()
}
const onDelete = () => {
deleteCourseList({ id: selectedSearchId })
clearForm()
}
const onSave = () => {
const courseList = courseInput
.split(/[\s,]+/)
.map(code => code.trim())
.filter(code => code !== '')
if (selectedSearchId !== '') {
updateCourseList({ id: selectedSearchId, courseList })
} else {
createCourseList({ courseList, name })
}
}
const onSelectSearch = selectedId => {
if (!selectedId) {
clearForm()
return
}
const { id: selectedSearchId } = selectedId
const selectedSearch = searchList?.find(search => search.id === selectedSearchId)
if (selectedSearch) {
setCourseInput(selectedSearch.courseList.join(', '))
setName(selectedSearch.name)
setSelectedSearchId(selectedSearch.id)
}
}
const onClicker = event => {
event.preventDefault()
const courseList = courseInput
.split(/[\s,]+/)
.map(code => code.trim())
.filter(code => code !== '')
const studentList = studentInput
.split(/[;\s,]+/)
.map(code => code.trim())
.filter(s => s !== '')
.map(s => (s.length === 8 ? `0${s}` : s))
if (courseList.length === 0 || studentList.length === 0 || studentList[0] === null) {
return
}
setValues({
studentList,
courseList,
})
pushQueryToUrl({ courseList, studentList })
handleClose()
}
if (isError) return <h3>Something went wrong, please try refreshing the page.</h3>
if (isFetchingOrLoading) return <Loader active style={{ marginTop: '15em' }} />
return (
<Modal
onClose={handleClose}
open={modal}
size="small"
trigger={
<Button color="blue" data-cy="open-completed-courses-modal-button" onClick={() => setModal(true)} size="small">
Search completed courses of students
</Button>
}
>
<Modal.Content>
<Form>
<h2>Search completed courses of students</h2>
<Form.Field>
<em>Insert one or more student numbers, separated by a space, a newline, a comma, or a semicolon.</em>
<TextArea
data-cy="student-no-input"
onChange={event => setStudentInput(event.target.value)}
placeholder="012345678, 12345678"
value={studentInput}
/>
</Form.Field>
<Form.Field>
<em>Insert one or more courses, separated by a space, a newline, or a comma.</em>
<TextArea
data-cy="course-list-input"
onChange={event => setCourseInput(event.target.value)}
placeholder="TKT12345, PSYK-123"
value={courseInput}
/>
</Form.Field>
<Form.Field>
<Form.Field data-cy="search-name">
<em> Insert name for this course list if you wish to save it </em>
<Form.Input
disabled={selectedSearchId !== ''}
onChange={event => setName(event.target.value)}
placeholder="name"
value={name}
/>
</Form.Field>
</Form.Field>
<SearchHistory
handleSearch={onSelectSearch}
header="Saved courselists"
items={
searchList
? searchList.map(list => ({
...list,
text: list.name,
timestamp: new Date(list.updatedAt),
params: { id: list.id },
}))
: []
}
updateItem={() => null}
/>
<Modal.Actions>
<Button
content="Save"
data-cy="save-courselist"
disabled={!name || updateIsLoading || createIsLoading}
floated="left"
icon="save"
loading={updateIsLoading || createIsLoading}
onClick={onSave}
/>
<Button
content="Delete"
disabled={!selectedSearchId || deleteIsLoading}
floated="left"
icon="trash"
negative
onClick={onDelete}
/>
<Button onClick={handleClose}>Cancel</Button>
<Button data-cy="completed-courses-search-button" onClick={event => onClicker(event)} positive>
Search
</Button>
</Modal.Actions>
</Form>
</Modal.Content>
</Modal>
)
}
@@ -1,183 +0,0 @@
import moment from 'moment'
import { Icon, Loader } from 'semantic-ui-react'
import { hiddenNameAndEmailForExcel } from '@/common/columns'
import { StudentInfoItem } from '@/components/common/StudentInfoItem'
import { useLanguage } from '@/components/LanguagePicker/useLanguage'
import { RightsNotification } from '@/components/RightsNotification'
import { SortableTable } from '@/components/SortableTable'
import { StudentNameVisibilityToggle, useStudentNameVisibility } from '@/components/StudentNameVisibilityToggle'
import { ISO_DATE_FORMAT } from '@/constants/date'
import { useGetCompletedCoursesQuery } from '@/redux/completedCoursesSearch'
import { isWithinSixMonths } from '@/util/timeAndDate'
const getColumns = (courses, showStudentNames, getTextIn) => {
const isPassed = credit => [4, 7, 9].includes(credit)
// 4=completed, 7=improved, 9=transferred, 10=failed
const getCompletion = (student, courseCode, { icon }) => {
const completion = student.credits.find(credit => credit.courseCode === courseCode && isPassed(credit.creditType))
const enrollment = student.enrollments[courseCode]
if (completion === undefined) {
if (!enrollment) {
return icon ? null : ''
}
if (icon) {
if (isWithinSixMonths(enrollment.date)) {
return <Icon color="yellow" fitted name="minus" />
}
return <Icon color="grey" fitted name="minus" />
}
return `Latest enrollment: ${moment(enrollment.date).format(ISO_DATE_FORMAT)}`
}
const substitutionString = completion.substitution ? ` as ${completion.substitution}` : ''
return icon ? <Icon color="green" fitted name="check" /> : `Passed${substitutionString}`
}
const getTotalPassed = student => student.credits.filter(credit => isPassed(credit.creditType)).length
const getTotalUnfinished = student => Object.values(student.enrollments).length
const style = {
verticalAlign: 'middle',
textAlign: 'center',
}
const studentNbrColumn = [
{
key: 'studentnumber',
title: 'Student number',
cellProps: { style },
getRowVal: student => student.studentNumber,
getRowContent: student => <StudentInfoItem showSisuLink student={student} />,
},
]
const getCellProps = (student, courseCode) => {
const credit = student.credits.find(credit => credit.courseCode === courseCode)
const enrollment = student.enrollments[courseCode]
if (!credit && !enrollment) {
return { style }
}
const title = credit
? `Passed on ${moment(credit.date).format(ISO_DATE_FORMAT)}\nCourse code: ${
credit.substitution ? credit.substitution : credit.courseCode
}`
: `Last enrollment on ${moment(enrollment.date).format(ISO_DATE_FORMAT)}\nCourse code ${
enrollment.substitution ? enrollment.substitution : enrollment.courseCode
}`
return { style, title }
}
const completionStatusColumns = courses.map(course => {
return {
key: course.code,
title: (
<div key={course.code} style={{ marginLeft: '-6px', width: '8em', whiteSpace: 'normal' }}>
<div key={`key1${course.code}`}>{course.code}</div>
<div key={`key2${course.code}`} style={{ color: 'gray', fontWeight: 'normal' }}>
{getTextIn(course.name)}
</div>
</div>
),
textTitle: course.code,
cellProps: student => getCellProps(student, course.code),
headerProps: { title: course.code },
getRowVal: student => getCompletion(student, course.code, { icon: false }),
getRowContent: student => getCompletion(student, course.code, { icon: true }),
}
})
const statisticColumns = [
{
key: 'passed',
title: 'Passed',
cellProps: { style },
headerProps: { title: 'Passed' },
getRowVal: student => getTotalPassed(student),
},
{
key: 'unfinished',
title: 'Unfinished',
headerProps: { title: 'Unfinished' },
getRowVal: student => getTotalUnfinished(student),
cellProps: { style },
},
]
const statisticHeader = {
key: 'statistics',
title: <b>Totals:</b>,
export: false,
textTitle: null,
children: statisticColumns,
}
const emailColumn = showStudentNames
? [
{
key: 'email',
title: 'Email',
cellProps: { style },
headerProps: { title: 'Email' },
getRowVal: student => (student.email ? student.email : ''),
export: false,
},
]
: []
const nameColumns = showStudentNames
? [
{
key: 'lastname',
title: 'Last name',
cellProps: { style },
getRowVal: student => student.lastname,
export: false,
},
{
key: 'firstnames',
title: 'First names',
cellProps: { style },
getRowVal: student => student.firstnames,
export: false,
},
]
: []
return [...nameColumns, ...studentNbrColumn, ...completionStatusColumns, statisticHeader, ...emailColumn]
}
export const CompletedCoursesSearchResults = ({ searchValues }) => {
const { courseList, studentList } = searchValues
const showStudentNames = useStudentNameVisibility()
const { data, isLoading, isFetching, isError } = useGetCompletedCoursesQuery({ courseList, studentList })
const { getTextIn } = useLanguage()
if (isError) return <h3>Something went wrong, please try refreshing the page.</h3>
if (isFetching || isLoading || !data) return <Loader active style={{ marginTop: '15em' }} />
return (
<div>
<StudentNameVisibilityToggle />
{data.discardedStudentNumbers?.length > 0 && (
<RightsNotification discardedStudentNumbers={data.discardedStudentNumbers} />
)}
<div
data-cy="completed-courses-table-div"
style={{ maxWidth: '100vh', overflowX: 'auto', paddingBottom: '50px', padding: '0.5em' }}
>
<SortableTable
columns={getColumns(data.courses, showStudentNames.visible, getTextIn)}
data={data.students}
featureName="completed_courses"
onlyExportColumns={hiddenNameAndEmailForExcel}
title="Completed courses search"
/>
</div>
</div>
)
}
@@ -1,38 +0,0 @@
import { useState } from 'react'
import { Icon, Message } from 'semantic-ui-react'
import { useTitle } from '@/common/hooks'
import { CompletedCoursesSearch } from './CompletedCoursesSearch'
import { CompletedCoursesSearchResults } from './CompletedCoursesSearchResults'
export const CompletedCourses = () => {
useTitle('Search completed courses')
const [searchValues, setValues] = useState({ courseList: [], studentList: [] })
return (
<div className="segmentContainer">
<Message style={{ maxWidth: '800px', fontSize: '16px' }}>
<Message.Header>Check completed courses</Message.Header>
<p>
Here you can search by a list of student and course numbers to see whether students have completed certain
courses yet. The tool will also show if students have enrolled on the course, if they have not yet completed
it. Course substitutions are taken into account.
</p>
<p>
<Icon color="green" fitted name="check" />: Student has completed the course with a passing grade. <br />
<Icon color="yellow" fitted name="minus" />: Student has not completed the course, but has an active
enrollment from less than 6 months ago. <br />
<Icon color="grey" fitted name="minus" />: Student has not completed the course, but has an enrollment from
more than 6 months ago. <br />
<b>Empty cell</b>: Student has no completion or enrollment for the course.
</p>
</Message>
<CompletedCoursesSearch setValues={setValues} />
<div style={{ paddingTop: '25px' }}>
{searchValues && searchValues.courseList?.length > 0 && searchValues.studentList?.length > 0 && (
<CompletedCoursesSearchResults searchValues={searchValues} />
)}
</div>
</div>
)
}