diff --git a/services/backend/oodikone2-backend/src/services/course_stats_counter.js b/services/backend/oodikone2-backend/src/services/course_stats_counter.js
index a29296c0ed..f85b43e5ca 100644
--- a/services/backend/oodikone2-backend/src/services/course_stats_counter.js
+++ b/services/backend/oodikone2-backend/src/services/course_stats_counter.js
@@ -76,8 +76,6 @@ class CourseStatsCounter {
if (passed) {
if (!improved && !this.students.passed[studentnumber]) {
this.markPassedSemester(semester)
- } else if (studentnumber === '012327001') {
- this.markPassedSemester(semester)
}
this.markPassingGrade(studentnumber)
} else if (improved) {
diff --git a/services/oodikone2-frontend/src/common/index.js b/services/oodikone2-frontend/src/common/index.js
index a802a18bf7..0ab36755f5 100644
--- a/services/oodikone2-frontend/src/common/index.js
+++ b/services/oodikone2-frontend/src/common/index.js
@@ -1,8 +1,10 @@
+import { useState, useEffect } from 'react'
import moment from 'moment'
import jwtDecode from 'jwt-decode'
import Datetime from 'react-datetime'
import { uniqBy, filter } from 'lodash'
import pathToRegexp from 'path-to-regexp'
+import qs from 'query-string'
import { API_DATE_FORMAT, DISPLAY_DATE_FORMAT, TOKEN_NAME } from '../constants'
import toskaLogo from '../assets/toska.png'
import irtomikko from '../assets/irtomikko.png'
@@ -213,3 +215,38 @@ export const getTextIn = (texts, language) => {
}
return null
}
+
+export const useTabs = (id, initialTab, { location, replace }) => {
+ const [tab, setTab] = useState(null)
+ const [didMount, setDidMount] = useState(false)
+
+ const pushToUrl = (newTab) => {
+ replace({
+ pathname: location.pathname,
+ search: qs.stringify({ ...qs.parse(location.search), [id]: newTab })
+ })
+ }
+
+ useEffect(() => {
+ const params = qs.parse(location.search)
+ let queryTab = params[id]
+ if (queryTab === undefined) {
+ setTab(initialTab)
+ } else {
+ queryTab = JSON.parse(queryTab)
+ if (tab !== queryTab) setTab(queryTab)
+ }
+ setDidMount(true)
+ }, [])
+
+ useEffect(() => {
+ if (tab !== undefined && didMount) pushToUrl(tab)
+ }, [tab])
+
+ return [
+ tab,
+ (e, { activeIndex }) => {
+ setTab(activeIndex)
+ }
+ ]
+}
diff --git a/services/oodikone2-frontend/src/components/CourseStatistics/ResultTabs/index.jsx b/services/oodikone2-frontend/src/components/CourseStatistics/ResultTabs/index.jsx
index ab16318d6f..47af492b2d 100644
--- a/services/oodikone2-frontend/src/components/CourseStatistics/ResultTabs/index.jsx
+++ b/services/oodikone2-frontend/src/components/CourseStatistics/ResultTabs/index.jsx
@@ -1,9 +1,12 @@
-import React, { Component } from 'react'
+import React, { useState } from 'react'
import { Tab, Grid, Radio, Menu } from 'semantic-ui-react'
+import { withRouter } from 'react-router-dom'
+import { shape } from 'prop-types'
import { dataSeriesType, viewModeNames } from './Panes/util'
import PassRate from './Panes/passRate'
import Distribution from './Panes/distribution'
import Tables from './Panes/tables'
+import { useTabs } from '../../../common'
import './resultTabs.css'
@@ -13,84 +16,29 @@ const paneViewIndex = {
GRADE_DISTRIBUTION: 2
}
-class ResultTabs extends Component {
- state = {
- activeIndex: paneViewIndex.TABLE,
- viewMode: viewModeNames.CUMULATIVE,
- isRelative: false
- }
-
- getPanes = () => {
- const { primary, comparison } = this.props
- const { viewMode, isRelative } = this.state
-
- const paneMenuItems = [
- {
- menuItem: { key: 'Table', icon: 'table', content: 'Table' },
- renderFn: () =>
- ()
- },
- {
- menuItem: { key: 'pass', icon: 'balance', content: 'Pass rate chart' },
- renderFn: () =>
- ()
- },
- {
- menuItem: { key: 'grade', icon: 'chart bar', content: 'Grade distribution chart' },
- renderFn: () =>
- ()
- }
- ]
-
- return paneMenuItems.map((p) => {
- const { menuItem, renderFn } = p
- return {
- menuItem,
- render: () => (
-
-
- {this.renderViewModeSelector()}
-
- {renderFn()}
-
- )
- }
- })
- }
-
- handleTabChange = (e, { activeIndex }) => {
- const { viewMode, activeIndex: oldIndex } = this.state
- const resetViewMode = oldIndex === paneViewIndex.TABLE
+const ResultTabs = (props) => {
+ const [tab, setTab] = useTabs(
+ 'cs_tab',
+ 0,
+ props.history
+ )
+ const [viewMode, setViewMode] = useState(viewModeNames.CUMULATIVE)
+ const [isRelative, setIsRelative] = useState(false)
+
+ const handleTabChange = (...params) => {
+ const resetViewMode = params[1].activeIndex === paneViewIndex.TABLE
&& viewMode === viewModeNames.GRADES
- this.setState({
- activeIndex,
- viewMode: resetViewMode ? viewModeNames.CUMULATIVE : viewMode
- })
+ setTab(...params)
+ setViewMode(resetViewMode ? viewModeNames.CUMULATIVE : viewMode)
}
- handleModeChange = (viewMode) => {
- this.setState({ viewMode })
+ const handleModeChange = (newViewMode) => {
+ setViewMode(newViewMode)
}
- renderViewModeSelector = () => {
- const { activeIndex, viewMode } = this.state
-
- const isTogglePane = activeIndex !== 0
+ const renderViewModeSelector = () => {
+ const isTogglePane = tab !== 0
const getButtonMenu = () => (
)
@@ -116,17 +64,17 @@ class ResultTabs extends Component {
id={toggleId}
checked={isToggleChecked}
toggle
- onChange={() => this.handleModeChange(newMode)}
+ onChange={() => handleModeChange(newMode)}
/>
- {this.props.comparison &&
+ {props.comparison &&
this.setState({ isRelative: !this.state.isRelative })}
+ checked={isRelative}
+ onChange={() => setIsRelative(!isRelative)}
/>
@@ -142,24 +90,76 @@ class ResultTabs extends Component {
)
}
- render() {
- return (
-
-
-
)
+ const getPanes = () => {
+ const { primary, comparison } = props
+
+ const paneMenuItems = [
+ {
+ menuItem: { key: 'Table', icon: 'table', content: 'Table' },
+ renderFn: () =>
+ ()
+ },
+ {
+ menuItem: { key: 'pass', icon: 'balance', content: 'Pass rate chart' },
+ renderFn: () =>
+ ()
+ },
+ {
+ menuItem: { key: 'grade', icon: 'chart bar', content: 'Grade distribution chart' },
+ renderFn: () =>
+ ()
+ }
+ ]
+
+ return paneMenuItems.map((p) => {
+ const { menuItem, renderFn } = p
+ return {
+ menuItem,
+ render: () => (
+
+
+ {renderViewModeSelector()}
+
+ {renderFn()}
+
+ )
+ }
+ })
}
+
+ return (
+
+
+
+ )
}
ResultTabs.propTypes = {
primary: dataSeriesType.isRequired,
- comparison: dataSeriesType
+ comparison: dataSeriesType,
+ history: shape({}).isRequired
}
ResultTabs.defaultProps = {
comparison: undefined
}
-export default ResultTabs
+export default withRouter(ResultTabs)
diff --git a/services/oodikone2-frontend/src/components/PopulationCourseStats/index.jsx b/services/oodikone2-frontend/src/components/PopulationCourseStats/index.jsx
index 140f7f887d..bb949c6461 100644
--- a/services/oodikone2-frontend/src/components/PopulationCourseStats/index.jsx
+++ b/services/oodikone2-frontend/src/components/PopulationCourseStats/index.jsx
@@ -275,7 +275,7 @@ class PopulationCourseStats extends Component {
reversed={reversed}
/>
- {courseGradesTypes.map(g => )}
+ {courseGradesTypes.map(g => )}
diff --git a/services/oodikone2-frontend/src/components/StudyProgramme/StudyProgrammeMandatoryCourses/index.jsx b/services/oodikone2-frontend/src/components/StudyProgramme/StudyProgrammeMandatoryCourses/index.jsx
index 5fea8c9bb8..2510f7cfc1 100644
--- a/services/oodikone2-frontend/src/components/StudyProgramme/StudyProgrammeMandatoryCourses/index.jsx
+++ b/services/oodikone2-frontend/src/components/StudyProgramme/StudyProgrammeMandatoryCourses/index.jsx
@@ -1,89 +1,104 @@
-import React, { Component } from 'react'
+import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import { func, shape, string } from 'prop-types'
import { Message, Tab } from 'semantic-ui-react'
+import { withRouter } from 'react-router-dom'
import {
- getMandatoryCourses,
- addMandatoryCourse,
- deleteMandatoryCourse,
- setMandatoryCourseLabel
+ getMandatoryCourses as getMandatoryCoursesAction,
+ addMandatoryCourse as addMandatoryCourseAction,
+ deleteMandatoryCourse as deleteMandatoryCourseAction,
+ setMandatoryCourseLabel as setMandatoryCourseLabelAction
} from '../../../redux/populationMandatoryCourses'
import MandatoryCourseTable from '../MandatoryCourseTable'
import AddMandatoryCourses from '../AddMandatoryCourses'
import MandatoryCourseLabels from '../MandatoryCourseLabels'
+import { useTabs } from '../../../common'
-class StudyProgrammeMandatoryCourses extends Component {
- static propTypes = {
- getMandatoryCourses: func.isRequired,
- addMandatoryCourse: func.isRequired,
- deleteMandatoryCourse: func.isRequired,
- setMandatoryCourseLabel: func.isRequired,
- studyProgramme: string.isRequired,
- mandatoryCourses: shape({}).isRequired,
- language: string.isRequired
- }
+const StudyProgrammeMandatoryCourses = (props) => {
+ const {
+ getMandatoryCourses,
+ addMandatoryCourse,
+ deleteMandatoryCourse,
+ setMandatoryCourseLabel,
+ studyProgramme,
+ mandatoryCourses,
+ language,
+ history
+ } = props
+ const [tab, setTab] = useTabs(
+ 'p_m_tab',
+ 0,
+ history
+ )
- componentDidMount() {
- const { studyProgramme } = this.props
+ useEffect(() => {
if (studyProgramme) {
- this.props.getMandatoryCourses(studyProgramme)
+ getMandatoryCourses(studyProgramme)
}
- }
+ }, [studyProgramme])
- componentDidUpdate(prevProps) {
- const { studyProgramme } = this.props
- if (studyProgramme !== prevProps.studyProgramme) {
- this.props.getMandatoryCourses(this.props.studyProgramme)
+ if (!studyProgramme) return null
+
+ const panes = [
+ {
+ menuItem: 'Mandatory courses',
+ render: () => (
+
+
+
+
+ )
+ },
+ {
+ menuItem: 'Group labels',
+ render: () => (
+
+
+
+ )
}
- }
+ ]
- render() {
- const { studyProgramme, mandatoryCourses, language } = this.props
- if (!studyProgramme) return null
+ return (
+
+
+
+
+ )
+}
- const panes = [
- {
- menuItem: 'Mandatory courses',
- render: () => (
-
-
-
-
- )
- },
- {
- menuItem: 'Group labels',
- render: () => (
-
-
-
- )
- }
- ]
+StudyProgrammeMandatoryCourses.propTypes = {
+ getMandatoryCourses: func.isRequired,
+ addMandatoryCourse: func.isRequired,
+ deleteMandatoryCourse: func.isRequired,
+ setMandatoryCourseLabel: func.isRequired,
+ studyProgramme: string.isRequired,
+ mandatoryCourses: shape({}).isRequired,
+ language: string.isRequired,
+ history: shape({}).isRequired
+}
- return (
-
-
-
-
- )
- }
+const mapDispatchToProps = {
+ getMandatoryCourses: getMandatoryCoursesAction,
+ addMandatoryCourse: addMandatoryCourseAction,
+ deleteMandatoryCourse: deleteMandatoryCourseAction,
+ setMandatoryCourseLabel: setMandatoryCourseLabelAction
}
export default connect(
@@ -91,5 +106,5 @@ export default connect(
mandatoryCourses: populationMandatoryCourses,
language: settings.language
}),
- { getMandatoryCourses, addMandatoryCourse, deleteMandatoryCourse, setMandatoryCourseLabel }
-)(StudyProgrammeMandatoryCourses)
+ mapDispatchToProps
+)(withRouter(StudyProgrammeMandatoryCourses))
diff --git a/services/oodikone2-frontend/src/components/StudyProgramme/index.jsx b/services/oodikone2-frontend/src/components/StudyProgramme/index.jsx
index 9a2c401c90..c7107de56c 100644
--- a/services/oodikone2-frontend/src/components/StudyProgramme/index.jsx
+++ b/services/oodikone2-frontend/src/components/StudyProgramme/index.jsx
@@ -1,4 +1,4 @@
-import React, { Component } from 'react'
+import React from 'react'
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
import { shape, string } from 'prop-types'
@@ -10,35 +10,17 @@ import Overview from './Overview'
import AggregateView from '../CourseGroups/AggregateView'
import ThesisCourses from './ThesisCourses'
import '../PopulationQueryCard/populationQueryCard.css'
-import { getRolesWithoutRefreshToken, getRightsWithoutRefreshToken, getTextIn } from '../../common'
+import { getRolesWithoutRefreshToken, getRightsWithoutRefreshToken, getTextIn, useTabs } from '../../common'
import Tags from './Tags'
-class StudyProgramme extends Component {
- static propTypes = {
- match: shape({
- params: shape({
- studyProgrammeId: string,
- courseGroupId: string
- })
- }),
- programmes: shape({}),
- language: string.isRequired,
- history: shape({}).isRequired
- }
-
- static defaultProps = {
- match: {
- params: { studyProgrammeId: undefined }
- },
- programmes: {}
- }
-
- state = {
- selected: this.props.match.params.courseGroupId ? 2 : 0
- }
-
- getPanes() {
- const { match } = this.props
+const StudyProgramme = (props) => {
+ const [tab, setTab] = useTabs(
+ 'p_tab',
+ props.match.params.courseGroupId ? 2 : 0,
+ props.history
+ )
+ const getPanes = () => {
+ const { match } = props
const { studyProgrammeId, courseGroupId } = match.params
const panes = []
panes.push(
@@ -73,51 +55,63 @@ class StudyProgramme extends Component {
return panes
}
- handleSelect = (programme) => {
- this.props.history.push(`/study-programme/${programme}`, { selected: programme })
+ const handleSelect = (programme) => {
+ props.history.push(`/study-programme/${programme}`, { selected: programme })
}
- select = (e, { activeIndex }) => {
- this.setState({ selected: activeIndex })
- }
+ const { match, programmes, language } = props
+ const { studyProgrammeId } = match.params
+ const programmeName = programmes[studyProgrammeId] && getTextIn(programmes[studyProgrammeId].name, language)
+ const panes = getPanes()
+ return (
+
+
+
+
+ {
+ studyProgrammeId ? (
+
+
+
+
+ {programmeName}
+ props.history.push('/study-programme')}
+ />
+
+
+
+
+
+
+ ) : null
+ }
+
+
+ )
+}
- render() {
- const { selected } = this.state
- const { match, programmes, language } = this.props
- const { studyProgrammeId } = match.params
- const programmeName = programmes[studyProgrammeId] && getTextIn(programmes[studyProgrammeId].name, language)
- const panes = this.getPanes()
- return (
-
-
-
-
- {
- studyProgrammeId ? (
-
-
-
-
- {programmeName}
- this.props.history.push('/study-programme')}
- />
-
-
-
-
-
-
- ) : null
- }
-
-
- )
- }
+StudyProgramme.propTypes = {
+ match: shape({
+ params: shape({
+ studyProgrammeId: string,
+ courseGroupId: string
+ })
+ }),
+ programmes: shape({}),
+ language: string.isRequired,
+ history: shape({}).isRequired
+}
+
+StudyProgramme.defaultProps = {
+ match: {
+ params: { studyProgrammeId: undefined }
+ },
+ programmes: {}
}
const mapStateToProps = ({ populationDegreesAndProgrammes, settings }) => {
diff --git a/services/oodikone2-frontend/src/components/Teachers/index.jsx b/services/oodikone2-frontend/src/components/Teachers/index.jsx
index 8a47db8673..65a3221627 100644
--- a/services/oodikone2-frontend/src/components/Teachers/index.jsx
+++ b/services/oodikone2-frontend/src/components/Teachers/index.jsx
@@ -6,7 +6,7 @@ import TeacherSearchTab from '../TeacherSearchTab'
import TeacherPage from '../TeacherPage'
import TeacherStatistics from '../TeacherStatistics'
import TeacherLeaderBoard from '../TeacherLeaderBoard'
-import { userRoles } from '../../common'
+import { userRoles, useTabs } from '../../common'
const pane = (title, Content, icon) => ({
menuItem: { key: title, content: title, icon },
@@ -17,7 +17,12 @@ const pane = (title, Content, icon) => ({
)
})
-const TeachersTabs = ({ admin }) => {
+const TeachersTabs = withRouter(({ admin, history }) => {
+ const [tab, setTab] = useTabs(
+ 't_tab',
+ 0,
+ history
+ )
const panes = admin ? [
pane('Statistics', TeacherStatistics, 'table'),
pane('Leaderboard', TeacherLeaderBoard, 'trophy'),
@@ -28,9 +33,12 @@ const TeachersTabs = ({ admin }) => {
)
-}
+})
+
class Teachers extends Component {
state = {}