mirror of
https://github.com/UniversityOfHelsinkiCS/oodikone.git
synced 2026-10-13 03:13:40 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d552a5811 | ||
|
|
624a103f08 | ||
|
|
9f56554ed9 | ||
|
|
0d1bc3c519 | ||
|
|
aa1d21bc09 | ||
|
|
eaf5d7574e | ||
|
|
9db14a839f | ||
|
|
b55d649622 | ||
|
|
47c792e1f1 | ||
|
|
fffa3d3ee1 | ||
|
|
c9407ff4b0 | ||
|
|
1b43d4fc39 |
@@ -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()
|
||||
})
|
||||
|
||||
Generated
+533
-882
File diff suppressed because it is too large
Load Diff
+10
-10
@@ -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"
|
||||
},
|
||||
|
||||
Generated
+29
-29
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
+33
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
Generated
+49
-49
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
+3
-1
@@ -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">
|
||||
|
||||
+1
-1
@@ -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
|
||||
Reference in New Issue
Block a user