Compare commits

...
12 Commits
21 changed files with 1116 additions and 1050 deletions
+6
View File
@@ -236,6 +236,8 @@ describe('Studyprogramme overview', () => {
describe('Studytrack overview works for basic user', () => {
beforeEach(() => {
cy.init('/study-programme')
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(10000)
cy.contains('a', 'Tietojenkäsittelytieteen kandiohjelma').click()
cy.get('.attached').contains('Studytracks and class statistics').click()
})
@@ -298,6 +300,8 @@ describe('Studyprogramme overview', () => {
describe('Programme courses works for basic user', () => {
beforeEach(() => {
cy.init('/study-programme')
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(10000)
cy.contains('a', 'Tietojenkäsittelytieteen kandiohjelma').click()
cy.get('.attached').contains('Programme courses').click()
})
@@ -374,6 +378,8 @@ describe('Studyprogramme overview', () => {
describe('Basic information -view works for basic user', () => {
beforeEach(() => {
cy.init('/study-programme')
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(10000)
cy.contains('a', 'Tietojenkäsittelytieteen kandiohjelma').click()
cy.get('.attached').contains('Tags').click()
})
+533 -882
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -29,28 +29,28 @@
},
"homepage": "https://github.com/UniversityOfHelsinkiCS/oodikone#readme",
"devDependencies": {
"@babel/core": "^7.20.12",
"@babel/core": "^7.19.3",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-syntax-jsx": "^7.16.7",
"concurrently": "^7.6.0",
"concurrently": "^7.4.0",
"cypress": "^10.9.0",
"eslint": "^8.31.0",
"eslint": "^8.25.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.6.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-babel": "^5.3.1",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prefer-object-spread": "^1.2.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.11",
"eslint-plugin-react": "^7.31.9",
"eslint-plugin-react-hooks": "^4.3.0",
"husky": "^8.0.3",
"lint-staged": "^13.1.0",
"husky": "^8.0.1",
"lint-staged": "^13.0.3",
"node-actionlint": "^1.2.2",
"prettier": "^2.8.2",
"stylelint": "^14.16.1",
"stylelint-config-prettier": "^9.0.4",
"prettier": "^2.5.1",
"stylelint": "^14.13.0",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-standard": "^26.0.0",
"stylelint-prettier": "^2.0.0"
},
+29 -29
View File
@@ -9,7 +9,7 @@
"@sentry/node": "^5.30.0",
"@sentry/tracing": "^6.18.2",
"async": "^3.2.3",
"async-lock": "^1.4.0",
"async-lock": "^1.3.1",
"axios": "^0.21.4",
"bluebird": "^3.7.2",
"cors": "^2.8.5",
@@ -19,11 +19,11 @@
"lodash": "^4.17.21",
"lru-cache": "^5.1.1",
"moment": "^2.29.1",
"moment-timezone": "^0.5.40",
"moment-timezone": "^0.5.37",
"morgan": "^1.10.0",
"pg": "^8.8.0",
"redis": "^3.1.2",
"sequelize": "^6.28.0",
"sequelize": "^6.24.0",
"umzug": "^2.3.0",
"unfuck-utf8-headers-middleware": "^1.0.1",
"uuid": "^3.4.0",
@@ -394,9 +394,9 @@
"dev": true
},
"node_modules/async-lock": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.0.tgz",
"integrity": "sha512-coglx5yIWuetakm3/1dsX9hxCNox22h7+V80RQOu2XUUMidtArxKoZoOtHUPuR84SycKTXzgGzAUR5hJxujyJQ=="
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.3.2.tgz",
"integrity": "sha512-phnXdS3RP7PPcmP6NWWzWMU0sLTeyvtZCxBPpZdkYE3seGLKSQZs9FrmVO/qwypq98FUtWWUEYxziLkdGk5nnA=="
},
"node_modules/atob": {
"version": "2.1.2",
@@ -2209,9 +2209,9 @@
}
},
"node_modules/moment-timezone": {
"version": "0.5.40",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.40.tgz",
"integrity": "sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==",
"version": "0.5.37",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.37.tgz",
"integrity": "sha512-uEDzDNFhfaywRl+vwXxffjjq1q0Vzr+fcQpQ1bU0kbzorfS7zVtZnCnGc8mhWmF39d4g4YriF6kwA75mJKE/Zg==",
"dependencies": {
"moment": ">= 2.9.0"
},
@@ -3013,9 +3013,9 @@
}
},
"node_modules/retry-as-promised": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.3.tgz",
"integrity": "sha512-SEvMa4khHvpU/o6zgh7sK24qm6rxVgKnrSyzb5POeDvZx5N9Bf0s5sQsQ4Fl+HjRp0X+w2UzACGfUnXtx6cJ9Q=="
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-6.1.0.tgz",
"integrity": "sha512-Hj/jY+wFC+SB9SDlIIFWiGOHnNG0swYbGYsOj2BJ8u2HKUaobNKab0OIC0zOLYzDy0mb7A4xA5BMo4LMz5YtEA=="
},
"node_modules/safe-buffer": {
"version": "5.2.1",
@@ -3108,9 +3108,9 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/sequelize": {
"version": "6.28.0",
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.28.0.tgz",
"integrity": "sha512-+WHqvUQgTp19GLkt+gyQ+F6qg+FIEO2O5F9C0TOYV/PjZ2a/XwWvVkL1NCkS4VSIjVVvAUutiW6Wv9ofveGaVw==",
"version": "6.24.0",
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.24.0.tgz",
"integrity": "sha512-mPo7Q7gWkrsstjR2aw8ahkrj8RUJCozz3kedAz2B5ZIdLoQEH1z2tvaeJONI8R6RqeuyRQosx9Yn3s9yQyo6lQ==",
"funding": [
{
"type": "opencollective",
@@ -3127,7 +3127,7 @@
"moment": "^2.29.1",
"moment-timezone": "^0.5.34",
"pg-connection-string": "^2.5.0",
"retry-as-promised": "^7.0.3",
"retry-as-promised": "^6.1.0",
"semver": "^7.3.5",
"sequelize-pool": "^7.1.0",
"toposort-class": "^1.0.1",
@@ -4443,9 +4443,9 @@
"dev": true
},
"async-lock": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.0.tgz",
"integrity": "sha512-coglx5yIWuetakm3/1dsX9hxCNox22h7+V80RQOu2XUUMidtArxKoZoOtHUPuR84SycKTXzgGzAUR5hJxujyJQ=="
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.3.2.tgz",
"integrity": "sha512-phnXdS3RP7PPcmP6NWWzWMU0sLTeyvtZCxBPpZdkYE3seGLKSQZs9FrmVO/qwypq98FUtWWUEYxziLkdGk5nnA=="
},
"atob": {
"version": "2.1.2",
@@ -5888,9 +5888,9 @@
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
},
"moment-timezone": {
"version": "0.5.40",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.40.tgz",
"integrity": "sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==",
"version": "0.5.37",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.37.tgz",
"integrity": "sha512-uEDzDNFhfaywRl+vwXxffjjq1q0Vzr+fcQpQ1bU0kbzorfS7zVtZnCnGc8mhWmF39d4g4YriF6kwA75mJKE/Zg==",
"requires": {
"moment": ">= 2.9.0"
}
@@ -6513,9 +6513,9 @@
"dev": true
},
"retry-as-promised": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.3.tgz",
"integrity": "sha512-SEvMa4khHvpU/o6zgh7sK24qm6rxVgKnrSyzb5POeDvZx5N9Bf0s5sQsQ4Fl+HjRp0X+w2UzACGfUnXtx6cJ9Q=="
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-6.1.0.tgz",
"integrity": "sha512-Hj/jY+wFC+SB9SDlIIFWiGOHnNG0swYbGYsOj2BJ8u2HKUaobNKab0OIC0zOLYzDy0mb7A4xA5BMo4LMz5YtEA=="
},
"safe-buffer": {
"version": "5.2.1",
@@ -6584,9 +6584,9 @@
}
},
"sequelize": {
"version": "6.28.0",
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.28.0.tgz",
"integrity": "sha512-+WHqvUQgTp19GLkt+gyQ+F6qg+FIEO2O5F9C0TOYV/PjZ2a/XwWvVkL1NCkS4VSIjVVvAUutiW6Wv9ofveGaVw==",
"version": "6.24.0",
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.24.0.tgz",
"integrity": "sha512-mPo7Q7gWkrsstjR2aw8ahkrj8RUJCozz3kedAz2B5ZIdLoQEH1z2tvaeJONI8R6RqeuyRQosx9Yn3s9yQyo6lQ==",
"requires": {
"@types/debug": "^4.1.7",
"@types/validator": "^13.7.1",
@@ -6597,7 +6597,7 @@
"moment": "^2.29.1",
"moment-timezone": "^0.5.34",
"pg-connection-string": "^2.5.0",
"retry-as-promised": "^7.0.3",
"retry-as-promised": "^6.1.0",
"semver": "^7.3.5",
"sequelize-pool": "^7.1.0",
"toposort-class": "^1.0.1",
+3 -3
View File
@@ -10,7 +10,7 @@
"@sentry/node": "^5.30.0",
"@sentry/tracing": "^6.18.2",
"async": "^3.2.3",
"async-lock": "^1.4.0",
"async-lock": "^1.3.1",
"axios": "^0.21.4",
"bluebird": "^3.7.2",
"cors": "^2.8.5",
@@ -20,11 +20,11 @@
"lodash": "^4.17.21",
"lru-cache": "^5.1.1",
"moment": "^2.29.1",
"moment-timezone": "^0.5.40",
"moment-timezone": "^0.5.37",
"morgan": "^1.10.0",
"pg": "^8.8.0",
"redis": "^3.1.2",
"sequelize": "^6.28.0",
"sequelize": "^6.24.0",
"umzug": "^2.3.0",
"unfuck-utf8-headers-middleware": "^1.0.1",
"uuid": "^3.4.0",
@@ -0,0 +1,33 @@
const { INTEGER, STRING, ARRAY, DATE } = require('sequelize')
module.exports = {
up: async queryInterface => {
await queryInterface.createTable('progress_criteria', {
code: {
type: STRING,
primaryKey: true,
},
courses_year_one: {
type: ARRAY(STRING),
},
courses_year_two: {
type: ARRAY(STRING),
},
courses_year_three: {
type: ARRAY(STRING),
},
credits_year_one: {
type: INTEGER,
},
credits_year_two: {
type: INTEGER,
},
credits_year_three: {
type: INTEGER,
},
created_at: DATE,
updated_at: DATE,
})
},
down: () => {},
}
@@ -1,3 +1,4 @@
const { STRING } = require('sequelize')
const Sequelize = require('sequelize')
const { sequelizeKone } = require('../database/connection')
@@ -143,6 +144,35 @@ const OpenUniPopulationSearch = sequelizeKone.define(
{ underscored: true, timestamps: true }
)
const StudyProgrammeCriteria = sequelizeKone.define(
'progress_criteria',
{
code: {
primaryKey: true,
type: STRING,
},
coursesYearOne: {
type: Sequelize.ARRAY(Sequelize.STRING),
},
coursesYearTwo: {
type: Sequelize.ARRAY(Sequelize.STRING),
},
coursesYearThree: {
type: Sequelize.ARRAY(Sequelize.STRING),
},
creditsYearOne: {
type: Sequelize.INTEGER,
},
creditsYearTwo: {
type: Sequelize.INTEGER,
},
creditsYearThree: {
type: Sequelize.INTEGER,
},
},
{ underscored: true, timestamps: true }
)
const StudyGuidanceGroupTag = sequelizeKone.define(
'study_guidance_group_tags',
{
@@ -188,4 +218,5 @@ module.exports = {
sequelizeKone,
ExcludedCourse,
StudyGuidanceGroupTag,
StudyProgrammeCriteria,
}
+2
View File
@@ -31,6 +31,7 @@ const programmeModules = require('./routes/programmeModules')
const studyGuidanceGroups = require('./routes/studyGuidanceGroups')
const studyProgramme = require('./routes/studyProgramme')
const customOpenUniSearch = require('./routes/customOpenUniSearch')
const studyProgrammeCriteria = require('./routes/studyProgrammeCriteria')
const initializeSentry = require('./util/sentry')
const errorMiddleware = require('./middleware/errorMiddleware')
@@ -58,6 +59,7 @@ module.exports = (app, url) => {
app.use(url, tags)
app.use(url, programmeModules)
app.use(url, studyProgramme)
app.use(`${url}/programmecriteria`, studyProgrammeCriteria)
app.use(`${url}/openunisearch`, auth.roles(['openUniSearch']), customOpenUniSearch)
app.use(`${url}/faculties`, auth.roles(['facultyStatistics']), faculties)
app.use(`${url}/updater`, auth.roles(['admin']), updater)
@@ -1,5 +1,5 @@
const router = require('express').Router()
var moment = require('moment-timezone')
const moment = require('moment-timezone')
const { getCustomOpenUniCourses } = require('../services/openUni/openUniStats')
const {
getOpenUniSearches,
@@ -0,0 +1,30 @@
const {
getCriteria,
saveYearlyCourseCriteria,
saveYearlyCreditCriteria,
} = require('../services/studyProgrammeCriteria')
const router = require('express').Router()
router.get('/', async (req, res) => {
const studyProgramme = req.query?.programmecode
if (!studyProgramme) return res.status(422).end()
const studyProgrammeCriteria = await getCriteria(studyProgramme)
return res.json(studyProgrammeCriteria)
})
router.post('/courses', async (req, res) => {
const { code, courses, year } = req.body
if (!code || !courses || !year) return res.status(400).end()
const studyProgrammeCriteria = await saveYearlyCourseCriteria(code, courses, year)
return res.json(studyProgrammeCriteria)
})
router.post('/credits', async (req, res) => {
const { code, credits } = req.body
if (!code || !credits) return res.status(400).end()
const studyProgrammeCriteria = await saveYearlyCreditCriteria(code, credits)
return res.json(studyProgrammeCriteria)
})
module.exports = router
@@ -111,7 +111,7 @@ const getFacultyStarters = async (
}
})
allBasics.studentInfo.graphStats.push({ name: 'Started studying', data: startedGraphStats })
allBasics.studentInfo.graphStats.push({ name: 'Started studying (new in faculty)', data: startedGraphStats })
programmeData['started'] = programmeTableStats
Object.keys(startedTableStats).forEach(year => {
@@ -9,7 +9,6 @@ const {
} = require('./openUniHelpers')
const { Op } = require('sequelize')
// 1. iteration: time is hardcoded. Check hyväksytty grades
const getCredits = async (courseCodes, startdate) =>
(
await Credit.findAll({
@@ -0,0 +1,122 @@
const { StudyProgrammeCriteria } = require('../models/models_kone')
const logger = require('../util/logger')
const getCriteriaByStudyProgramme = code =>
StudyProgrammeCriteria.findOne({
where: {
code: code,
},
})
const formattedData = updatedData => {
const criteria = {
courses: {
yearOne: updatedData.coursesYearOne,
yearTwo: updatedData.coursesYearTwo,
yearThree: updatedData.coursesYearThree,
},
credits: {
yearOne: updatedData.creditsYearOne,
yearTwo: updatedData.creditsYearTwo,
yearThree: updatedData.creditsYearThree,
},
}
return criteria
}
const createCriteria = async (studyProgramme, coYear1, coYear2, coYear3, crYear1, crYear2, crYear3) => {
const newProgrammeCriteria = {
code: studyProgramme,
coursesYearOne: coYear1,
coursesYearTwo: coYear2,
coursesYearThree: coYear3,
creditsYearOne: crYear1,
creditsYearTwo: crYear2,
creditsYearThree: crYear3,
}
try {
const createdData = await StudyProgrammeCriteria.create(newProgrammeCriteria)
const criteria = formattedData(createdData)
return criteria
} catch (error) {
logger.error(`Create criteria failed: ${error}`)
}
}
const saveYearlyCreditCriteria = async (studyProgramme, credits) => {
const studyProgrammeToUpdate = await getCriteriaByStudyProgramme(studyProgramme)
if (!studyProgrammeToUpdate) {
return await createCriteria(
studyProgramme,
[],
[],
[],
parseInt(credits.year1),
parseInt(credits.year2),
parseInt(credits.year3)
)
}
const yearlyCredits = {
creditsYearOne: parseInt(credits.year1),
creditsYearTwo: parseInt(credits.year2),
creditsYearThree: parseInt(credits.year3),
}
try {
const updatedData = await studyProgrammeToUpdate.update({ ...yearlyCredits })
const criteria = formattedData(updatedData)
return criteria
} catch (error) {
logger.error(`Update yearly credit criteria failed: ${error}`)
}
}
const saveYearlyCourseCriteria = async (studyProgramme, courses, year) => {
const studyProgrammeToUpdate = await getCriteriaByStudyProgramme(studyProgramme)
if (!studyProgrammeToUpdate) {
const courseObj = { year1: [], year2: [], year3: [] }
if (year === 1) {
courseObj.year1 = courses
} else if (year === 2) {
courseObj.year2 = courses
} else {
courseObj.year3 = courses
}
return await createCriteria(studyProgramme, courseObj.year1, courseObj.year2, courseObj.year3, 0, 0, 0)
}
try {
let updatedData = {}
if (year === 1) updatedData = await studyProgrammeToUpdate.update({ coursesYearOne: courses })
if (year === 2) updatedData = await studyProgrammeToUpdate.update({ coursesYearTwo: courses })
if (year === 3) updatedData = await studyProgrammeToUpdate.update({ coursesYearThree: courses })
const criteria = formattedData(updatedData)
return criteria
} catch (error) {
logger.error(`Update yearly credit criteria failed: ${error}`)
}
}
const getCriteria = async studyProgramme => {
const studyProgrammeCriteria = await getCriteriaByStudyProgramme(studyProgramme)
const criteria = {
courses: {
yearOne: studyProgrammeCriteria ? studyProgrammeCriteria.coursesYearOne : [],
yearTwo: studyProgrammeCriteria ? studyProgrammeCriteria.coursesYearTwo : [],
yearThree: studyProgrammeCriteria ? studyProgrammeCriteria.coursesYearThree : [],
},
credits: {
yearOne: studyProgrammeCriteria ? studyProgrammeCriteria.creditsYearOne : 0,
yearTwo: studyProgrammeCriteria ? studyProgrammeCriteria.creditsYearTwo : 0,
yearThree: studyProgrammeCriteria ? studyProgrammeCriteria.creditsYearThree : 0,
},
}
return criteria
}
module.exports = {
getCriteria,
saveYearlyCourseCriteria,
saveYearlyCreditCriteria,
}
+49 -49
View File
@@ -6,7 +6,7 @@
"": {
"name": "frontend",
"dependencies": {
"@reduxjs/toolkit": "^1.9.1",
"@reduxjs/toolkit": "^1.8.6",
"@sentry/browser": "^6.18.2",
"@sentry/tracing": "^6.18.2",
"axios": "^0.21.4",
@@ -16,7 +16,7 @@
"highcharts-custom-events": "^3.0.10",
"highcharts-exporting": "^0.1.7",
"history": "^4.10.1",
"immer": "^9.0.17",
"immer": "^9.0.12",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"path-to-regexp": "^0.1.7",
@@ -25,7 +25,7 @@
"qs": "^6.10.3",
"query-string": "^6.14.1",
"react": "^16.14.0",
"react-datetime": "^3.2.0",
"react-datetime": "^3.1.1",
"react-dom": "^16.14.0",
"react-highcharts": "^16.1.0",
"react-markdown": "^6.0.3",
@@ -37,7 +37,7 @@
"react-scroll-to-component": "^1.0.2",
"scheduler": "^0.23.0",
"semantic-ui-css": "^2.5.0",
"semantic-ui-react": "^2.1.4",
"semantic-ui-react": "^2.1.2",
"unfuck-spa-shibboleth-session": "^3.1.4",
"use-context-selector": "^1.3.9",
"uuid": "^3.4.0",
@@ -2841,14 +2841,14 @@
}
},
"node_modules/@reduxjs/toolkit": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.1.tgz",
"integrity": "sha512-HikrdY+IDgRfRYlCTGUQaiCxxDDgM1mQrRbZ6S1HFZX5ZYuJ4o8EstNmhTwHdPl2rTmLxzwSu0b3AyeyTlR+RA==",
"version": "1.8.6",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.6.tgz",
"integrity": "sha512-4Ia/Loc6WLmdSOzi7k5ff7dLK8CgG2b8aqpLsCAJhazAzGdp//YBUSaj0ceW6a3kDBDNRrq5CRwyCS0wBiL1ig==",
"dependencies": {
"immer": "^9.0.16",
"redux": "^4.2.0",
"redux-thunk": "^2.4.2",
"reselect": "^4.1.7"
"immer": "^9.0.7",
"redux": "^4.1.2",
"redux-thunk": "^2.4.1",
"reselect": "^4.1.5"
},
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18",
@@ -10186,9 +10186,9 @@
}
},
"node_modules/immer": {
"version": "9.0.17",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.17.tgz",
"integrity": "sha512-+hBruaLSQvkPfxRiTLK/mi4vLH+/VQS6z2KJahdoxlleFOI8ARqzOF17uy12eFDlqWmPoygwc5evgwcp+dlHhg==",
"version": "9.0.15",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.15.tgz",
"integrity": "sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
@@ -16312,15 +16312,15 @@
}
},
"node_modules/react-datetime": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-datetime/-/react-datetime-3.2.0.tgz",
"integrity": "sha512-w5XdeNIGzBht9CadaZIJhKUhEcDTgH0XokKxGPCxeeJRYL7B3HIKA8CM6Q0xej2JFJt0n5d+zi3maMwaY3262A==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/react-datetime/-/react-datetime-3.1.1.tgz",
"integrity": "sha512-gHCTjAniCcMb6jdXpz+MpVe/uCeaHNDOofg+l41nLlJI3uBLBMV40CQbGB2TCTUpCzGT1mCs4vQzKGMjXO/WWQ==",
"dependencies": {
"prop-types": "^15.5.7"
},
"peerDependencies": {
"moment": "^2.16.0",
"react": "^16.5.0 || ^17.0.0 || ^18.0.0"
"react": "^16.5.0 || ^17.0.0"
}
},
"node_modules/react-dev-utils": {
@@ -16923,9 +16923,9 @@
}
},
"node_modules/redux-thunk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
"integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
"peerDependencies": {
"redux": "^4"
}
@@ -17154,9 +17154,9 @@
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
"node_modules/reselect": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz",
"integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A=="
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz",
"integrity": "sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ=="
},
"node_modules/resolve": {
"version": "1.18.1",
@@ -17922,9 +17922,9 @@
}
},
"node_modules/semantic-ui-react": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-2.1.4.tgz",
"integrity": "sha512-7CxjBoFUfH7fUvtn+SPkkIocthUD9kV3niF1mUMa9TbeyPAf2brtRCZBlT2OpHaXmkscFzGjEfhbJo9gKfotzQ==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-2.1.3.tgz",
"integrity": "sha512-dd2pqalMk0ycJoHf7hn4mYd0xohg0NMk7ohlpPVOigCfMLrKk2e3NZdRk0yBetvD+qJXPqZ04jA43bDwqOM5OA==",
"dependencies": {
"@babel/runtime": "^7.10.5",
"@fluentui/react-component-event-listener": "~0.63.0",
@@ -24283,14 +24283,14 @@
"integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw=="
},
"@reduxjs/toolkit": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.1.tgz",
"integrity": "sha512-HikrdY+IDgRfRYlCTGUQaiCxxDDgM1mQrRbZ6S1HFZX5ZYuJ4o8EstNmhTwHdPl2rTmLxzwSu0b3AyeyTlR+RA==",
"version": "1.8.6",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.6.tgz",
"integrity": "sha512-4Ia/Loc6WLmdSOzi7k5ff7dLK8CgG2b8aqpLsCAJhazAzGdp//YBUSaj0ceW6a3kDBDNRrq5CRwyCS0wBiL1ig==",
"requires": {
"immer": "^9.0.16",
"redux": "^4.2.0",
"redux-thunk": "^2.4.2",
"reselect": "^4.1.7"
"immer": "^9.0.7",
"redux": "^4.1.2",
"redux-thunk": "^2.4.1",
"reselect": "^4.1.5"
}
},
"@rollup/plugin-node-resolve": {
@@ -29903,9 +29903,9 @@
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ=="
},
"immer": {
"version": "9.0.17",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.17.tgz",
"integrity": "sha512-+hBruaLSQvkPfxRiTLK/mi4vLH+/VQS6z2KJahdoxlleFOI8ARqzOF17uy12eFDlqWmPoygwc5evgwcp+dlHhg=="
"version": "9.0.15",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.15.tgz",
"integrity": "sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ=="
},
"import-cwd": {
"version": "2.1.0",
@@ -34574,9 +34574,9 @@
}
},
"react-datetime": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-datetime/-/react-datetime-3.2.0.tgz",
"integrity": "sha512-w5XdeNIGzBht9CadaZIJhKUhEcDTgH0XokKxGPCxeeJRYL7B3HIKA8CM6Q0xej2JFJt0n5d+zi3maMwaY3262A==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/react-datetime/-/react-datetime-3.1.1.tgz",
"integrity": "sha512-gHCTjAniCcMb6jdXpz+MpVe/uCeaHNDOofg+l41nLlJI3uBLBMV40CQbGB2TCTUpCzGT1mCs4vQzKGMjXO/WWQ==",
"requires": {
"prop-types": "^15.5.7"
}
@@ -35071,9 +35071,9 @@
}
},
"redux-thunk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
"integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
"requires": {}
},
"regenerate": {
@@ -35248,9 +35248,9 @@
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
"reselect": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz",
"integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A=="
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz",
"integrity": "sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ=="
},
"resolve": {
"version": "1.18.1",
@@ -35841,9 +35841,9 @@
}
},
"semantic-ui-react": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-2.1.4.tgz",
"integrity": "sha512-7CxjBoFUfH7fUvtn+SPkkIocthUD9kV3niF1mUMa9TbeyPAf2brtRCZBlT2OpHaXmkscFzGjEfhbJo9gKfotzQ==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-2.1.3.tgz",
"integrity": "sha512-dd2pqalMk0ycJoHf7hn4mYd0xohg0NMk7ohlpPVOigCfMLrKk2e3NZdRk0yBetvD+qJXPqZ04jA43bDwqOM5OA==",
"requires": {
"@babel/runtime": "^7.10.5",
"@fluentui/react-component-event-listener": "~0.63.0",
+4 -4
View File
@@ -6,7 +6,7 @@
"build": "DISABLE_ESLINT_PLUGIN=true react-scripts build"
},
"dependencies": {
"@reduxjs/toolkit": "^1.9.1",
"@reduxjs/toolkit": "^1.8.6",
"@sentry/browser": "^6.18.2",
"@sentry/tracing": "^6.18.2",
"axios": "^0.21.4",
@@ -16,7 +16,7 @@
"highcharts-custom-events": "^3.0.10",
"highcharts-exporting": "^0.1.7",
"history": "^4.10.1",
"immer": "^9.0.17",
"immer": "^9.0.12",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"path-to-regexp": "^0.1.7",
@@ -25,7 +25,7 @@
"qs": "^6.10.3",
"query-string": "^6.14.1",
"react": "^16.14.0",
"react-datetime": "^3.2.0",
"react-datetime": "^3.1.1",
"react-dom": "^16.14.0",
"react-highcharts": "^16.1.0",
"react-markdown": "^6.0.3",
@@ -37,7 +37,7 @@
"react-scroll-to-component": "^1.0.2",
"scheduler": "^0.23.0",
"semantic-ui-css": "^2.5.0",
"semantic-ui-react": "^2.1.4",
"semantic-ui-react": "^2.1.2",
"unfuck-spa-shibboleth-session": "^3.1.4",
"use-context-selector": "^1.3.9",
"uuid": "^3.4.0",
@@ -180,7 +180,9 @@ const FacultyProgrammeOverview = ({
</div>
{getDivider('Bachelor + Master', 'ProgressOfBachelorMaster', 'no-infobox')}
<Message data-cy="FacultyProgrammesShownInfo">
The starting year is the studyright start in the bachelor programme.
The starting year is the studyright start in the bachelor programme. The credits are comouted by the
start date of the bachelor programme and at the moment, they do not include any transferred credits.
Thus, in these statistics some students have less credits than in reality.
</Message>
<div className="section-container">
<div className="graph-container">
@@ -17,7 +17,7 @@ const CollapsedStackedBar = ({
const transpose = matrix => {
return matrix.reduce((prev, next) => next.map((_item, i) => (prev[i] || []).concat(next[i])), [])
}
if (names[0] === 'Started studying') names[0] += ' (new in faculty)'
const needsExtra = extraHeight === 'EXTRA HEIGHT'
const dataTranspose = transpose(data)
.map((obj, idx) => ({ name: names[idx], data: obj, color: colors[idx] }))
@@ -389,11 +389,6 @@ const GeneralTab = ({
return grade
},
},
transferredFrom: {
key: 'transferredFrom',
title: 'Transferred from',
getRowVal: s => (s.transferredStudyright ? transferFrom(s) : ''),
},
studyTrack: containsStudyTracks && {
key: 'studyTrack',
title: 'Study Track',
@@ -426,8 +421,7 @@ const GeneralTab = ({
},
semesterEnrollments: {
key: 'semesterEnrollments',
title: 'Semesters',
helpText: 'Number of present semesters',
title: 'Semesters\npresent',
getRowVal: s => semesterEnrollments(s.semesterenrollments),
},
tags: {
@@ -435,6 +429,11 @@ const GeneralTab = ({
title: 'Tags',
getRowVal: s => (!s.obfuscated ? tags(s.tags) : ''),
},
transferredFrom: {
key: 'transferredFrom',
title: 'Transferred\nfrom',
getRowVal: s => (s.transferredStudyright ? transferFrom(s) : ''),
},
admissionType: shouldShowAdmissionType && {
key: 'admissionType',
title: 'Admission type',
@@ -1,7 +1,11 @@
import React, { useEffect, useState } from 'react'
import { connect, useSelector, useDispatch } from 'react-redux'
import { string, func } from 'prop-types'
import { Button, Label, Table, Icon } from 'semantic-ui-react'
import { Button, Label, Table, Icon, Dropdown, Container, Message, Form } from 'semantic-ui-react'
import {
useAddProgressCriteriaCourseMutation,
useAddProgressCriteriaCreditsMutation,
} from 'redux/programmeProgressCriteria'
import { getTextIn } from '../../../common'
import { GetMandatoryCourseLabels } from '../../../redux/mandatoryCourseLabels'
import {
@@ -14,17 +18,35 @@ import sendEvent from '../../../common/sendEvent'
const sendAnalytics = sendEvent.degreeCourses
const DegreeCourses = ({ studyProgramme, setExclusion, removeExclusion }) => {
const DegreeCourses = ({ studyProgramme, criteria, setCriteria, setExclusion, removeExclusion }) => {
const { language } = useLanguage()
const dispatch = useDispatch()
const mandatoryCourses = useSelector(({ populationMandatoryCourses }) => populationMandatoryCourses.data)
const [visible, setVisible] = useState({})
const [modules, setModules] = useState([])
const [creditsLimit1, setCreditsLimit1] = useState('')
const [creditsLimit2, setCreditsLimit2] = useState('')
const [creditsLimit3, setCreditsLimit3] = useState('')
const [addProgressCriteriaCourse, { data: courseData }] = useAddProgressCriteriaCourseMutation()
const [addProgressCriteriaCredits, { data: creditsData }] = useAddProgressCriteriaCreditsMutation()
useEffect(() => {
dispatch(getMandatoryCourses(studyProgramme, true))
}, [])
useEffect(() => {
if (courseData) {
setCriteria(courseData)
}
}, [courseData])
useEffect(() => {
if (creditsData) {
setCriteria(creditsData)
}
}, [creditsData])
useEffect(() => {
if (!mandatoryCourses || !mandatoryCourses.length) return
const modules = {}
@@ -51,6 +73,14 @@ const DegreeCourses = ({ studyProgramme, setExclusion, removeExclusion }) => {
}
}, [modules])
const getYear = criterionYear => {
let year = 'yearOne'
if (criterionYear !== 1) {
year = criterionYear === 2 ? 'yearTwo' : 'yearThree'
}
return year
}
const setExclusionButton = course => (
<Button
onClick={() => {
@@ -72,6 +102,63 @@ const DegreeCourses = ({ studyProgramme, setExclusion, removeExclusion }) => {
</Button>
)
const setCriteriaButton = (course, criterionYear) => (
<Button
onClick={() => {
const year = getYear(criterionYear)
const courses = criteria.courses ? [...criteria.courses[year], course.code] : [course.code]
addProgressCriteriaCourse({ programmeCode: studyProgramme, courses, year: criterionYear })
}}
>
{`Set criterion for year ${criterionYear}`}
</Button>
)
const deleteCriteriaButton = (course, criterionYear) => (
<Button
onClick={() => {
const year = getYear(criterionYear)
const filteredCourses = criteria?.courses[year]?.filter(courseCode => courseCode !== course.code)
addProgressCriteriaCourse({ programmeCode: studyProgramme, courses: filteredCourses, year: criterionYear })
}}
>
{`Remove criterion for year ${criterionYear}`}
</Button>
)
const labelDropdown = course => {
return (
<Dropdown text="Modify labels" className="link item">
<Dropdown.Menu>
{course.visible.visibility ? setExclusionButton(course) : deleteButton(course)}
<Dropdown.Divider />
{criteria?.courses?.yearOne?.includes(course.code)
? deleteCriteriaButton(course, 1)
: setCriteriaButton(course, 1)}
<Dropdown.Divider />
{criteria?.courses?.yearTwo?.includes(course.code)
? deleteCriteriaButton(course, 2)
: setCriteriaButton(course, 2)}
<Dropdown.Divider />
{criteria?.courses?.yearThree?.includes(course.code)
? deleteCriteriaButton(course, 3)
: setCriteriaButton(course, 3)}
</Dropdown.Menu>
</Dropdown>
)
}
const setCreditsLimitCriteria = () => {
const credits = {
year1: creditsLimit1 === '' ? criteria.credits.yearOne : creditsLimit1,
year2: creditsLimit2 === '' ? criteria.credits.yearTwo : creditsLimit2,
year3: creditsLimit3 === '' ? criteria.credits.yearThree : creditsLimit3,
}
addProgressCriteriaCredits({ programmeCode: studyProgramme, credits })
setCreditsLimit1('')
setCreditsLimit2('')
setCreditsLimit3('')
}
const excludeAll = code => {
const module = modules.find(({ module }) => module === code)
setExclusion(
@@ -142,56 +229,111 @@ const DegreeCourses = ({ studyProgramme, setExclusion, removeExclusion }) => {
}
return (
<Table>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>Code</Table.HeaderCell>
<Table.HeaderCell>Label</Table.HeaderCell>
<Table.HeaderCell>Set visibility</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{modules.map(({ module, courses }) => (
<>
<Table.Row key={module}>
<Table.Cell style={{ cursor: 'pointer' }} onClick={() => toggleVisible(module)}>
<Icon name={visible[module] ? 'angle down' : 'angle right'} />
<b>{courses[0] && courses[0].label_name ? getTextIn(courses[0].label_name, language) : module}</b>
</Table.Cell>
<Table.Cell>{module}</Table.Cell>
<Table.Cell>
<Label
content={calculateModuleVisibility(module)}
color={moduleVisibilityColor(calculateModuleVisibility(module))}
<>
{studyProgramme.includes('KH') && (
<>
<Message style={{ fontSize: '16px' }}>
<Message.Header>Change visibility of degree courses and select criteria for academic years</Message.Header>
<p>
Here you can change visibility of degree courses as and set course and credits criteria. Each academic
year have their own course and credits criteria. The progress of the students by these criteria will be
shown in class statistics view. These statistics will be available at the latest at Friday on 10.2..
</p>
</Message>
<Container>
<h5>Credit criteria</h5>
<Form>
<Form.Group widths="equal">
<Form.Input
type="number"
fluid
label={`First year (12 months) last set: ${criteria?.credits?.yearOne}`}
onChange={e => setCreditsLimit1(e.target.value)}
/>
</Table.Cell>
<Table.Cell>
{calculateModuleVisibility(module) === 'hidden' ? showAllButton(module) : hideAllButton(module)}
</Table.Cell>
</Table.Row>
{visible[module] &&
courses
.sort((a, b) => a.code.localeCompare(b.code))
.map(course => (
<Table.Row key={`${module}/${course.code}`}>
<Table.Cell>{getTextIn(course.name, language)}</Table.Cell>
<Table.Cell>{course.code}</Table.Cell>
<Table.Cell>
<Label
content={course.visible.visibility ? 'visible' : 'hidden'}
color={course.visible.visibility ? 'green' : 'red'}
/>
</Table.Cell>
<Table.Cell>
{course.visible.visibility ? setExclusionButton(course) : deleteButton(course)}
</Table.Cell>
</Table.Row>
))}
</>
))}
</Table.Body>
</Table>
<Form.Input
type="number"
fluid
label={`Second year (24 months) last set: ${criteria?.credits?.yearTwo}`}
onChange={e => setCreditsLimit2(e.target.value)}
/>
<Form.Input
type="number"
fluid
label={`Third year (36 months) last set: ${criteria?.credits?.yearThree}`}
onChange={e => setCreditsLimit3(e.target.value)}
/>
</Form.Group>
<Form.Button content="Save credit changes" onClick={setCreditsLimitCriteria} />
</Form>
</Container>
</>
)}
<Table>
<Table.Header>
<Table.Row key="HeaderRow">
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>Code</Table.HeaderCell>
<Table.HeaderCell>Visibility Label</Table.HeaderCell>
{studyProgramme.includes('KH') && <Table.HeaderCell>Criterion Labels</Table.HeaderCell>}
<Table.HeaderCell>{studyProgramme.includes('KH') ? 'Set Labels' : 'Set Visibility'}</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{modules.map(({ module, courses }) => (
<React.Fragment key={`fragment-${module}`}>
<Table.Row key={module}>
<Table.Cell style={{ cursor: 'pointer' }} onClick={() => toggleVisible(module)}>
<Icon name={visible[module] ? 'angle down' : 'angle right'} />
<b>{courses[0] && courses[0].label_name ? getTextIn(courses[0].label_name, language) : module}</b>
</Table.Cell>
<Table.Cell>{module}</Table.Cell>
<Table.Cell>
<Label
content={calculateModuleVisibility(module)}
color={moduleVisibilityColor(calculateModuleVisibility(module))}
/>
</Table.Cell>
{studyProgramme.includes('KH') && <Table.Cell />}
<Table.Cell>
{calculateModuleVisibility(module) === 'hidden' ? showAllButton(module) : hideAllButton(module)}
</Table.Cell>
</Table.Row>
{visible[module] &&
courses
.sort((a, b) => a.code.localeCompare(b.code))
.map(course => (
<Table.Row key={`${module}/${course.code}`}>
<Table.Cell>{getTextIn(course.name, language)}</Table.Cell>
<Table.Cell>{course.code}</Table.Cell>
<Table.Cell>
<Label
content={course.visible.visibility ? 'visible' : 'hidden'}
color={course.visible.visibility ? 'green' : 'red'}
/>
</Table.Cell>
{studyProgramme.includes('KH') && (
<Table.Cell>
{criteria?.courses?.yearOne?.includes(course.code) && <Label content="year 1" color="blue" />}
{criteria?.courses?.yearTwo?.includes(course.code) && <Label content="year 2" color="blue" />}
{criteria?.courses?.yearThree?.includes(course.code) && (
<Label content="year 3" color="blue" />
)}
</Table.Cell>
)}
{studyProgramme.includes('KH') ? (
<Table.Cell>{labelDropdown(course)}</Table.Cell>
) : (
<Table.Cell>
{course.visible.visibility ? setExclusionButton(course) : deleteButton(course)}
</Table.Cell>
)}
</Table.Row>
))}
</React.Fragment>
))}
</Table.Body>
</Table>
</>
)
}
@@ -3,6 +3,7 @@ import { useHistory, withRouter } from 'react-router-dom'
import { connect, useDispatch, useSelector } from 'react-redux'
import { Header, Segment, Tab, Menu } from 'semantic-ui-react'
import { useGetAuthorizedUserQuery } from 'redux/auth'
import { useGetProgressCriteriaQuery } from 'redux/programmeProgressCriteria'
import DegreeCoursesTable from './DegreeCourses'
import StudyProgrammeSelector from './StudyProgrammeSelector'
import BasicOverview from './BasicOverview'
@@ -23,19 +24,30 @@ const StudyProgramme = props => {
const dispatch = useDispatch()
const history = useHistory()
const programmes = useSelector(state => state.populationProgrammes?.data?.programmes)
const progressCriteria = useGetProgressCriteriaQuery({ programmeCode: props.match.params.studyProgrammeId })
const { language } = useLanguage()
const { isAdmin, rights } = useGetAuthorizedUserQuery()
const [tab, setTab] = useTabs('p_tab', 0, history)
const [academicYear, setAcademicYear] = useState(false)
const [specialGroups, setSpecialGroups] = useState(false)
const [graduated, setGraduated] = useState(false)
const emptyCriteria = {
courses: { yearOne: [], yearTwo: [], yearThree: [] },
credits: { yearOne: 0, yearTwo: 0, yearThree: 0 },
}
const [criteria, setCriteria] = useState(progressCriteria?.data ? progressCriteria.data : emptyCriteria)
useTitle('Study programmes')
useEffect(() => {
dispatch(getProgrammes())
}, [])
useEffect(() => {
if (progressCriteria.data) {
setCriteria(progressCriteria.data)
}
}, [progressCriteria.data])
const getPanes = () => {
const { match } = props
const { studyProgrammeId } = match.params
@@ -67,13 +79,6 @@ const StudyProgramme = props => {
),
})
if (isAdmin) {
panes.push({
menuItem: 'Update statistics',
render: () => <UpdateView studyprogramme={studyProgrammeId} />,
})
}
if (isAdmin || rights.includes(studyProgrammeId)) {
panes.push({
menuItem: <Menu.Item key="Programme courses">Programme courses</Menu.Item>,
@@ -87,13 +92,21 @@ const StudyProgramme = props => {
})
panes.push({
menuItem: 'Degree Courses',
render: () => <DegreeCoursesTable studyProgramme={studyProgrammeId} />,
render: () => (
<DegreeCoursesTable studyProgramme={studyProgrammeId} criteria={criteria} setCriteria={setCriteria} />
),
})
panes.push({
menuItem: 'Tags',
render: () => <Tags studyprogramme={studyProgrammeId} />,
})
}
if (isAdmin) {
panes.push({
menuItem: 'Update statistics',
render: () => <UpdateView studyprogramme={studyProgrammeId} />,
})
}
return panes
}
@@ -135,7 +148,6 @@ const StudyProgramme = props => {
</Segment>
</div>
)
return (
<div className="segmentContainer">
<Segment className="contentSegment">
@@ -0,0 +1,37 @@
import { RTKApi } from 'apiConnection'
const programmeProgressCriteria = RTKApi.injectEndpoints({
endpoints: builder => ({
getProgressCriteria: builder.query({
query: ({ programmeCode }) => `/programmecriteria?programmecode=${programmeCode}`,
}),
addProgressCriteriaCourse: builder.mutation({
query: ({ programmeCode, courses, year }) => ({
url: '/programmecriteria/courses',
method: 'POST',
body: {
code: programmeCode,
courses,
year,
},
}),
}),
addProgressCriteriaCredits: builder.mutation({
query: ({ programmeCode, credits }) => ({
url: '/programmecriteria/credits',
method: 'POST',
body: {
code: programmeCode,
credits,
},
}),
}),
}),
overrideExisting: false,
})
export const {
useGetProgressCriteriaQuery,
useAddProgressCriteriaCourseMutation,
useAddProgressCriteriaCreditsMutation,
} = programmeProgressCriteria