From 99bb908811cf355d68386086d9a06bb76a48f632 Mon Sep 17 00:00:00 2001 From: Karl Andin Date: Tue, 20 Aug 2024 14:52:30 +0200 Subject: [PATCH 01/19] feat(KUI-1149): add "show more" feature for item with long content in Round info grid. --- i18n/messages.en.js | 4 ++ i18n/messages.se.js | 4 ++ public/css/kursinfo-web.scss | 36 ++++++++++++++ .../RoundInformationInfoGrid.jsx | 48 ++++++++++++++++++- 4 files changed, 90 insertions(+), 2 deletions(-) diff --git a/i18n/messages.en.js b/i18n/messages.en.js index 18e988de..1b356541 100644 --- a/i18n/messages.en.js +++ b/i18n/messages.en.js @@ -39,6 +39,10 @@ module.exports = { menu_panel_close: 'Close', menu_panel_menu: 'Menu', }, + showMoreContent: { + show: 'Show more', + hide: 'Hide', + }, bankIdAlertText: `Please note that Students not located in Sweden may have problems attending a course at KTH.
You could meet obstacles if you're required to pay fees or if you do not have a Swedish Mobile BankID. `, courseLabels: { label_course_description: 'Introduction to course', diff --git a/i18n/messages.se.js b/i18n/messages.se.js index 93ebd0ca..f6a475d7 100644 --- a/i18n/messages.se.js +++ b/i18n/messages.se.js @@ -40,6 +40,10 @@ module.exports = { menu_panel_close: 'Stäng', menu_panel_menu: 'Meny', }, + showMoreContent: { + show: 'Visa mer', + hide: 'Dölj', + }, bankIdAlertText: 'Du behöver ett KTH-konto för att läsa en kurs på KTH, kontot aktiveras med Mobilt BankID eller genom att besöka KTH:s campus. Det enda sättet att starta en kurs utan att besöka campus, är om du har Mobilt BankID.', courseLabels: { diff --git a/public/css/kursinfo-web.scss b/public/css/kursinfo-web.scss index d3a6665a..df1c22eb 100644 --- a/public/css/kursinfo-web.scss +++ b/public/css/kursinfo-web.scss @@ -140,6 +140,42 @@ margin-bottom: 1.5rem; } + .roundInformation__infoGridItem { + display: flex; + flex-direction: column; + + .roundInformation__infoGridItemContent { + position: relative; + overflow: hidden; + max-height: 96px; + transition: max-height 200ms ease-in; + + &.hidden::after { + content: ''; + position: absolute; + bottom: 0; + height: 24px; + width: 100%; + background: linear-gradient(to bottom, transparent, var(--color-background-alt)); + display: block; + } + } + + .roundInformation__infoGridItemShowMoreButton { + background-color: transparent; + border: none; + padding: 0; + margin-top: 4px; + color: var(--color-tertiary); + cursor: pointer; + align-self: flex-end; + + &:hover { + text-decoration: underline; + } + } + } + .kth-alert { background-color: var(--color-background); } diff --git a/public/js/app/components/RoundInformation/RoundInformationInfoGrid.jsx b/public/js/app/components/RoundInformation/RoundInformationInfoGrid.jsx index 8ab06e78..b14f5104 100644 --- a/public/js/app/components/RoundInformation/RoundInformationInfoGrid.jsx +++ b/public/js/app/components/RoundInformation/RoundInformationInfoGrid.jsx @@ -1,12 +1,49 @@ -import React from 'react' +import React, { useEffect, useState, useRef } from 'react' import { useLanguage } from '../../hooks/useLanguage' import InfoModal from '../InfoModal' import { CourseMemoLink } from './CourseMemoLink' import { CourseScheduleLink } from './CourseScheduleLink' import { PlannedModules } from './PlannedModules' +// Calculates if a "Show more" button should be displayed, and creates props for content and button elements. +const useShowMoreContent = content => { + const ref = useRef(null) + const [hasMoreContent, setHasMoreContent] = useState(false) + const [contentHeight, setHeight] = useState(undefined) + const { translation } = useLanguage() + + const showMoreContent = hasMoreContent && !!contentHeight + const hideMoreContent = hasMoreContent && !showMoreContent + useEffect(() => { + if (ref.current) { + const el = ref.current + const isOverflowing = el.clientHeight < el.scrollHeight + setHasMoreContent(isOverflowing) + } + }, [content]) + + const contentProps = { + ref, + style: { maxHeight: contentHeight }, // set max height for animation from the max value specified in css to actual content height + className: `roundInformation__infoGridItemContent ${hideMoreContent ? 'hidden' : ''}`, + } + + const showMoreButtonProps = hasMoreContent + ? { + onClick: () => { + setHeight(showMoreContent ? undefined : ref.current.scrollHeight) + }, + children: showMoreContent ? translation.showMoreContent.hide : translation.showMoreContent.show, + className: 'roundInformation__infoGridItemShowMoreButton', + } + : undefined + + return { contentProps, showMoreButtonProps } +} + const Item = ({ children, html, title, infoModalContent }) => { const { translation } = useLanguage() + const { contentProps, showMoreButtonProps } = useShowMoreContent(html ?? children) return (
@@ -21,7 +58,14 @@ const Item = ({ children, html, title, infoModalContent }) => { /> )} - {html ?
:
{children}
} +
+ {html ? ( +
+ ) : ( +
{children}
+ )} + {showMoreButtonProps &&
) } From 7e692538a4cc283c7c91d75d7905fba317adc107 Mon Sep 17 00:00:00 2001 From: Karl Andin Date: Tue, 20 Aug 2024 15:45:47 +0200 Subject: [PATCH 02/19] fix(KUI-1149): set fixed with of rounds dropdown --- public/css/kursinfo-web.scss | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/public/css/kursinfo-web.scss b/public/css/kursinfo-web.scss index df1c22eb..224214d3 100644 --- a/public/css/kursinfo-web.scss +++ b/public/css/kursinfo-web.scss @@ -93,11 +93,18 @@ gap: 1rem; flex-direction: column; - align-items: flex-start; + align-items: stretch; + + .select-wrapper { + width: 100%; + } @media (min-width: 768px) { flex-direction: row; align-items: flex-end; + .select-wrapper { + width: 360px; + } } } } From d590ef41baebdc1b1cddfe4e081492022ee6f296 Mon Sep 17 00:00:00 2001 From: Karl Andin Date: Wed, 21 Aug 2024 10:52:20 +0200 Subject: [PATCH 03/19] fix(KUI-1149): hide intro text if one selection is needed --- .../RoundSelector/RoundSelector.jsx | 13 +++-- .../__tests__/useSemesterRoundState.test.js | 55 +++++++++++++++++++ public/js/app/hooks/useSemesterRoundState.js | 5 ++ 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/public/js/app/components/RoundSelector/RoundSelector.jsx b/public/js/app/components/RoundSelector/RoundSelector.jsx index b469bcf6..4c9559ea 100644 --- a/public/js/app/components/RoundSelector/RoundSelector.jsx +++ b/public/js/app/components/RoundSelector/RoundSelector.jsx @@ -14,6 +14,7 @@ export const RoundSelector = ({ activeSemesters, semesterRoundState }) => { showRoundData, selectedSemester, activeSemesterOnlyHasOneRound, + hasOnlyOneRound, roundsForSelectedSemester, activeRound, } = semesterRoundState @@ -34,13 +35,13 @@ export const RoundSelector = ({ activeSemesters, semesterRoundState }) => { ariaLabel={translation.courseLabels.header_dropdown_menu_aria_label} /> -

- {hasActiveSemesters ? ( - translation.courseLabels.header_dropdown_menu_navigation - ) : ( + {hasActiveSemesters ? ( + !hasOnlyOneRound &&

{translation.courseLabels.header_dropdown_menu_navigation}

+ ) : ( +

{translation.courseLabels.lable_no_rounds} - )} -

+

+ )}
diff --git a/public/js/app/hooks/__tests__/useSemesterRoundState.test.js b/public/js/app/hooks/__tests__/useSemesterRoundState.test.js index 6f1b158a..a8420778 100644 --- a/public/js/app/hooks/__tests__/useSemesterRoundState.test.js +++ b/public/js/app/hooks/__tests__/useSemesterRoundState.test.js @@ -477,4 +477,59 @@ describe('useSemesterRoundsLogic', () => { await waitFor(() => expect(result.current.showRoundData).toBe(false)) await waitFor(() => expect(result.current.activeRound).toEqual({})) }) + + describe('set hasOnlyOneRound and activeSemesterOnlyHasOneRound correctly', () => { + test('one semester with one round', async () => { + const { result } = renderHook(() => + useSemesterRoundState({ + initiallySelectedRoundIndex: undefined, + initiallySelectedSemester: 20222, + roundsBySemester: { 20222: roundsBySemester[20222] }, + syllabusList, + activeSemesters: [{ year: '2022', semesterNumber: '2', semester: '20222' }], + }) + ) + expect(result.current.activeSemesterOnlyHasOneRound).toBe(true) + expect(result.current.hasOnlyOneRound).toBe(true) + }) + test('one semester with multiple rounds', async () => { + const { result } = renderHook(() => + useSemesterRoundState({ + initiallySelectedRoundIndex: undefined, + initiallySelectedSemester: 20232, + roundsBySemester: { 20232: roundsBySemester[20232] }, + syllabusList, + activeSemesters: [{ year: '2023', semesterNumber: '2', semester: '20232' }], + }) + ) + expect(result.current.activeSemesterOnlyHasOneRound).toBe(false) + expect(result.current.hasOnlyOneRound).toBe(false) + }) + test('multiple semesters - selected has multiple rounds', async () => { + const { result } = renderHook(() => + useSemesterRoundState({ + initiallySelectedRoundIndex: undefined, + initiallySelectedSemester: 20232, + roundsBySemester, + syllabusList, + activeSemesters, + }) + ) + expect(result.current.activeSemesterOnlyHasOneRound).toBe(false) + expect(result.current.hasOnlyOneRound).toBe(false) + }) + test('multiple semesters - selected has one round', async () => { + const { result } = renderHook(() => + useSemesterRoundState({ + initiallySelectedRoundIndex: undefined, + initiallySelectedSemester: 20222, + roundsBySemester, + syllabusList, + activeSemesters, + }) + ) + expect(result.current.activeSemesterOnlyHasOneRound).toBe(true) + expect(result.current.hasOnlyOneRound).toBe(false) + }) + }) }) diff --git a/public/js/app/hooks/useSemesterRoundState.js b/public/js/app/hooks/useSemesterRoundState.js index 6447d274..d796b2a6 100644 --- a/public/js/app/hooks/useSemesterRoundState.js +++ b/public/js/app/hooks/useSemesterRoundState.js @@ -34,6 +34,10 @@ const useSemesterRoundState = ({ () => determineSemesterOnlyHasOneRound(roundsBySemester, selectedSemester), [roundsBySemester, selectedSemester] ) + const hasOnlyOneRound = useMemo(() => { + if (activeSemesters.length !== 1) return false + return activeSemesterOnlyHasOneRound + }, [activeSemesterOnlyHasOneRound, activeSemesters.length]) const showRoundData = useMemo(() => { if (isSetSelectedRoundIndex || activeSemesterOnlyHasOneRound) { @@ -93,6 +97,7 @@ const useSemesterRoundState = ({ roundsForSelectedSemester, selectedSemester, showRoundData, + hasOnlyOneRound, activeSemesterOnlyHasOneRound, hasActiveSemesters, activeSyllabus, From 62bc04e7b6b1c56637893d74f149da18641bc80c Mon Sep 17 00:00:00 2001 From: amirhossein-haerian Date: Mon, 26 Aug 2024 15:18:16 +0200 Subject: [PATCH 04/19] feat(KUI-1400): upgrade kth/style --- package-lock.json | 8 ++++---- package.json | 2 +- server/views/helpers/index.js | 4 +++- server/views/partials/headerLogotype.handlebars | 5 ++++- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4f745037..4267b611 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@kth/monitor": "^4.3.1", "@kth/server": "^4.1.0", "@kth/session": "^3.0.9", - "@kth/style": "^0.18.12", + "@kth/style": "^1.3.0", "@kth/ug-rest-api-helper": "^1.0.26", "axios": "^1.7.2", "body-parser": "^1.20.2", @@ -3539,9 +3539,9 @@ } }, "node_modules/@kth/style": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@kth/style/-/style-0.18.12.tgz", - "integrity": "sha512-QtRdFEJcpHNgtUdqrpCn9EVXOQw9Iu6tYLsFO0qgCfgbWWMzSq8d52NoV6rJ6JSNeqhA8tnwdXHprhtLTstA/w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@kth/style/-/style-1.3.0.tgz", + "integrity": "sha512-/3ObH/f+KeolQoFXBhLRCUxcihDGnc1PDSsqsajtABgy4Q5oF45KB4OW7xL1gFQqyMfJfdU1CJfGSLGSKrFjgw==", "peerDependencies": { "react": "*" } diff --git a/package.json b/package.json index 9c41771e..059d5e3b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@kth/monitor": "^4.3.1", "@kth/server": "^4.1.0", "@kth/session": "^3.0.9", - "@kth/style": "^0.18.12", + "@kth/style": "^1.3.0", "@kth/ug-rest-api-helper": "^1.0.26", "axios": "^1.7.2", "body-parser": "^1.20.2", diff --git a/server/views/helpers/index.js b/server/views/helpers/index.js index 66658a90..ee1d44ff 100644 --- a/server/views/helpers/index.js +++ b/server/views/helpers/index.js @@ -1,5 +1,5 @@ 'use strict' - +const handlebars = require('handlebars') const registerHeaderContentHelper = require('@kth/kth-node-web-common/lib/handlebars/helpers/headerContent') const { registerBreadcrumbHelper } = require('@kth/kth-node-web-common/lib/handlebars/helpers/breadcrumbs') const { registerLanguageLinkHelper } = require('@kth/kth-node-web-common/lib/handlebars/helpers/languageLink') @@ -41,3 +41,5 @@ require('@kth/kth-node-web-common/lib/handlebars/helpers/contentedit') const i18n = require('../../../i18n') require('@kth/kth-node-web-common/lib/handlebars/helpers/createI18nHelper')(i18n) require('@kth/kth-node-web-common/lib/handlebars/helpers/safe') + +handlebars.registerHelper('eq', (var1, var2) => var1.toString() === var2.toString()) diff --git a/server/views/partials/headerLogotype.handlebars b/server/views/partials/headerLogotype.handlebars index f9e8d7b7..6239219e 100644 --- a/server/views/partials/headerLogotype.handlebars +++ b/server/views/partials/headerLogotype.handlebars @@ -1,6 +1,9 @@
- Till KTH:s startsida + {{#if (eq theme "external")}} Till KTH:s startsida + {{else}} + Till KTH:s startsida + {{/if}}
\ No newline at end of file From c928365d97860fed7f619b4da838d990de20a720 Mon Sep 17 00:00:00 2001 From: amirhossein-haerian Date: Tue, 27 Aug 2024 08:52:19 +0200 Subject: [PATCH 05/19] feat(KUI-1400): upgrade kth/style local-navigation --- public/css/_shared.scss | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/public/css/_shared.scss b/public/css/_shared.scss index e3581ddc..8861cf5b 100644 --- a/public/css/_shared.scss +++ b/public/css/_shared.scss @@ -4,6 +4,15 @@ #app { padding-top: 30px; padding-bottom: 30px; + .kth-local-navigation--mobile { + display: block; + width: auto; + padding: 0; + + @media (min-width: 992px) { + display: none; + } + } & > .row { margin: 0; From 8cbd06b73ecf0ebf36459596b141371d27a09aeb Mon Sep 17 00:00:00 2001 From: amirhossein-haerian Date: Tue, 27 Aug 2024 20:20:13 +0200 Subject: [PATCH 06/19] feat(KUI-1400): revert local-navigation changes --- package-lock.json | 8 ++++---- package.json | 2 +- public/css/_shared.scss | 9 --------- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4267b611..f3f7c209 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@kth/monitor": "^4.3.1", "@kth/server": "^4.1.0", "@kth/session": "^3.0.9", - "@kth/style": "^1.3.0", + "@kth/style": "^1.4.2", "@kth/ug-rest-api-helper": "^1.0.26", "axios": "^1.7.2", "body-parser": "^1.20.2", @@ -3539,9 +3539,9 @@ } }, "node_modules/@kth/style": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@kth/style/-/style-1.3.0.tgz", - "integrity": "sha512-/3ObH/f+KeolQoFXBhLRCUxcihDGnc1PDSsqsajtABgy4Q5oF45KB4OW7xL1gFQqyMfJfdU1CJfGSLGSKrFjgw==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@kth/style/-/style-1.4.2.tgz", + "integrity": "sha512-Z6EhjUVzhw+QITpnFsdLu51xogsgEXQiB1E+hEI2HsaFxTOrxkq6qbnN/uK1WWeTuKfi+A6szaVubPXswWRtyw==", "peerDependencies": { "react": "*" } diff --git a/package.json b/package.json index 059d5e3b..07a4a1c8 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@kth/monitor": "^4.3.1", "@kth/server": "^4.1.0", "@kth/session": "^3.0.9", - "@kth/style": "^1.3.0", + "@kth/style": "^1.4.2", "@kth/ug-rest-api-helper": "^1.0.26", "axios": "^1.7.2", "body-parser": "^1.20.2", diff --git a/public/css/_shared.scss b/public/css/_shared.scss index 8861cf5b..e3581ddc 100644 --- a/public/css/_shared.scss +++ b/public/css/_shared.scss @@ -4,15 +4,6 @@ #app { padding-top: 30px; padding-bottom: 30px; - .kth-local-navigation--mobile { - display: block; - width: auto; - padding: 0; - - @media (min-width: 992px) { - display: none; - } - } & > .row { margin: 0; From 3931bff4cdc3dd5601aa0e078e7f85c41f2bde0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Bj=C3=B6rkqvist?= Date: Tue, 27 Aug 2024 13:29:43 +0200 Subject: [PATCH 07/19] font-heading-xs is only applied to elements in #app now --- public/css/_shared.scss | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/public/css/_shared.scss b/public/css/_shared.scss index e3581ddc..b1dda206 100644 --- a/public/css/_shared.scss +++ b/public/css/_shared.scss @@ -16,6 +16,12 @@ } } } + label:has(+ .form-group), + label:has(+ input) { + @include typography.font-heading-xs; + display: block; + margin-bottom: 0.25rem; + } } #mainContent { @@ -159,10 +165,3 @@ p { .select-wrapper::after { background-color: var(--color-primary); } - -label:has(+ .form-group), -label:has(+ input) { - @include typography.font-heading-xs; - display: block; - margin-bottom: 0.25rem; -} From 97dcd51bba4e03d4ee7bb87429a0018871fa9b94 Mon Sep 17 00:00:00 2001 From: amirhossein-haerian Date: Tue, 27 Aug 2024 17:05:48 +0200 Subject: [PATCH 08/19] feat(KUI-1378): refactored the way of getting data --- public/css/kursinfo-web.scss | 33 ++++++++++++++ .../RoundInformation/PlannedModules.jsx | 10 +---- .../RoundInformation/RoundInformation.jsx | 43 +++++++++++++++---- .../RoundInformationContacts.jsx | 8 +--- .../RoundInformationInfoGrid.jsx | 4 +- 5 files changed, 71 insertions(+), 27 deletions(-) diff --git a/public/css/kursinfo-web.scss b/public/css/kursinfo-web.scss index 224214d3..b317d4e4 100644 --- a/public/css/kursinfo-web.scss +++ b/public/css/kursinfo-web.scss @@ -110,6 +110,7 @@ } .roundInformation { + position: relative; padding: 20px; margin-bottom: 30px; background-color: var(--color-background-alt); @@ -187,6 +188,38 @@ background-color: var(--color-background); } } +.shimmer-effect::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(-45deg, #eee 40%, #fafafa 50%, #eee 60%); + background-size: 300%; + background-position-x: 100%; + animation: shimmer 1s infinite linear; + z-index: 10; + pointer-events: none; +} +.fadeIn { + animation: fadeIn .5s ease forwards; +} + +@keyframes shimmer { + to { + background-position-x: 0%; + } +} + +@keyframes fadeIn { + from { + opacity: .2; + } + to { + opacity: 1; + } +} .course-section-list { @include prose.prose; diff --git a/public/js/app/components/RoundInformation/PlannedModules.jsx b/public/js/app/components/RoundInformation/PlannedModules.jsx index 07190655..5bab6da4 100644 --- a/public/js/app/components/RoundInformation/PlannedModules.jsx +++ b/public/js/app/components/RoundInformation/PlannedModules.jsx @@ -1,11 +1,3 @@ import React from 'react' -import { usePlannedModules } from '../../hooks/usePlannedModules' -export const PlannedModules = ({ courseCode, courseRound, selectedSemester }) => { - const { plannedModules } = usePlannedModules({ - courseCode, - semester: selectedSemester, - applicationCode: courseRound.round_application_code, - }) - return -} +export const PlannedModules = ({ plannedModules }) => diff --git a/public/js/app/components/RoundInformation/RoundInformation.jsx b/public/js/app/components/RoundInformation/RoundInformation.jsx index 75addb3c..39489fa1 100644 --- a/public/js/app/components/RoundInformation/RoundInformation.jsx +++ b/public/js/app/components/RoundInformation/RoundInformation.jsx @@ -1,9 +1,11 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import Alert from '../../components-shared/Alert' import BankIdAlert from '../../components/BankIdAlert' import { useLanguage } from '../../hooks/useLanguage' import { useRoundUtils } from '../../hooks/useRoundUtils' +import { useCourseEmployees } from '../../hooks/useCourseEmployees' +import { usePlannedModules } from '../../hooks/usePlannedModules' import { RoundInformationInfoGrid } from './RoundInformationInfoGrid' import { RoundInformationContacts } from './RoundInformationContacts' @@ -14,13 +16,41 @@ function RoundInformation({ courseCode, courseData, courseRound, semesterRoundSt const selectedRoundHeader = createRoundHeader(courseRound) const { selectedSemester } = semesterRoundState + const [pending, setPending] = useState(true) + + const { courseRoundEmployees } = useCourseEmployees({ + courseCode, + selectedSemester, + applicationCode: courseRound?.round_application_code, + }) + + const { plannedModules } = usePlannedModules({ + courseCode, + semester: selectedSemester, + applicationCode: courseRound.round_application_code, + }) + + useEffect(() => { + setPending(true) + }, [courseRound]) + + useEffect(() => { + if (courseRoundEmployees && plannedModules) { + setPending(false) + } + }, [courseRoundEmployees, plannedModules]) + return ( -
+

{translation.courseRoundInformation.round_header} {selectedRoundHeader}

- + @@ -29,12 +59,7 @@ function RoundInformation({ courseCode, courseData, courseRound, semesterRoundSt )}

{translation.courseLabels.header_contact}

- +
) } diff --git a/public/js/app/components/RoundInformation/RoundInformationContacts.jsx b/public/js/app/components/RoundInformation/RoundInformationContacts.jsx index a80bfdde..2c9a100e 100644 --- a/public/js/app/components/RoundInformation/RoundInformationContacts.jsx +++ b/public/js/app/components/RoundInformation/RoundInformationContacts.jsx @@ -1,16 +1,10 @@ import React from 'react' import { useLanguage } from '../../hooks/useLanguage' import { useMissingInfo } from '../../hooks/useMissingInfo' -import { useCourseEmployees } from '../../hooks/useCourseEmployees' -function RoundInformationContacts({ courseCode, courseData, courseRound, selectedSemester }) { +function RoundInformationContacts({ courseData, courseRoundEmployees }) { const { translation } = useLanguage() const { missingInfoLabel } = useMissingInfo() - const { courseRoundEmployees } = useCourseEmployees({ - courseCode, - selectedSemester, - applicationCode: courseRound?.round_application_code, - }) return (
diff --git a/public/js/app/components/RoundInformation/RoundInformationInfoGrid.jsx b/public/js/app/components/RoundInformation/RoundInformationInfoGrid.jsx index b14f5104..ceb880a6 100644 --- a/public/js/app/components/RoundInformation/RoundInformationInfoGrid.jsx +++ b/public/js/app/components/RoundInformation/RoundInformationInfoGrid.jsx @@ -70,7 +70,7 @@ const Item = ({ children, html, title, infoModalContent }) => { ) } -function RoundInformationInfoGrid({ courseCode, courseRound, selectedSemester }) { +function RoundInformationInfoGrid({ courseCode, courseRound, plannedModules }) { const { translation } = useLanguage() return ( @@ -118,7 +118,7 @@ function RoundInformationInfoGrid({ courseCode, courseRound, selectedSemester }) - + From b6af49b706a93d878856d9b06cd02b98de439d36 Mon Sep 17 00:00:00 2001 From: amirhossein-haerian Date: Tue, 27 Aug 2024 20:16:58 +0200 Subject: [PATCH 09/19] feat(KUI-1378): the loder and test cases are ready --- public/css/kursinfo-web.scss | 24 ++++-- .../RoundInformation/RoundInformation.jsx | 8 +- .../__tests__/RoundInformation.test.js | 4 +- .../RoundInformationContacts.test.js | 79 ++----------------- public/js/app/hooks/useCourseEmployees.js | 17 ++-- 5 files changed, 37 insertions(+), 95 deletions(-) diff --git a/public/css/kursinfo-web.scss b/public/css/kursinfo-web.scss index b317d4e4..8a4aefd8 100644 --- a/public/css/kursinfo-web.scss +++ b/public/css/kursinfo-web.scss @@ -114,6 +114,14 @@ padding: 20px; margin-bottom: 30px; background-color: var(--color-background-alt); + .person { + min-height: 31px; + img { + max-width: 40px; + overflow: hidden; + white-space: nowrap; + } + } h3 { margin-block-end: 1rem; @@ -195,15 +203,15 @@ left: 0; width: 100%; height: 100%; - background: linear-gradient(-45deg, #eee 40%, #fafafa 50%, #eee 60%); + background: linear-gradient(-45deg, var(--color-background-alt) 40%, #fafafa 50%, var(--color-background-alt) 60%); background-size: 300%; background-position-x: 100%; animation: shimmer 1s infinite linear; z-index: 10; pointer-events: none; } -.fadeIn { - animation: fadeIn .5s ease forwards; +.fadeIn > * { + animation: fadeIn 1.5s ease forwards; } @keyframes shimmer { @@ -213,14 +221,16 @@ } @keyframes fadeIn { - from { - opacity: .2; + 0% { + opacity: 0; } - to { + 33.33% { + opacity: 0; + } + 100% { opacity: 1; } } - .course-section-list { @include prose.prose; diff --git a/public/js/app/components/RoundInformation/RoundInformation.jsx b/public/js/app/components/RoundInformation/RoundInformation.jsx index 39489fa1..29d424dd 100644 --- a/public/js/app/components/RoundInformation/RoundInformation.jsx +++ b/public/js/app/components/RoundInformation/RoundInformation.jsx @@ -18,13 +18,13 @@ function RoundInformation({ courseCode, courseData, courseRound, semesterRoundSt const [pending, setPending] = useState(true) - const { courseRoundEmployees } = useCourseEmployees({ + const { courseRoundEmployees, isError: courseEmployeesError } = useCourseEmployees({ courseCode, selectedSemester, applicationCode: courseRound?.round_application_code, }) - const { plannedModules } = usePlannedModules({ + const { plannedModules, isError: plannedModulesError } = usePlannedModules({ courseCode, semester: selectedSemester, applicationCode: courseRound.round_application_code, @@ -35,10 +35,10 @@ function RoundInformation({ courseCode, courseData, courseRound, semesterRoundSt }, [courseRound]) useEffect(() => { - if (courseRoundEmployees && plannedModules) { + if ((courseRoundEmployees && plannedModules) || plannedModulesError || courseEmployeesError) { setPending(false) } - }, [courseRoundEmployees, plannedModules]) + }, [courseRoundEmployees, plannedModules, plannedModulesError, courseEmployeesError]) return (
diff --git a/public/js/app/components/__tests__/RoundInformation.test.js b/public/js/app/components/__tests__/RoundInformation.test.js index 047838c2..1cc2adeb 100644 --- a/public/js/app/components/__tests__/RoundInformation.test.js +++ b/public/js/app/components/__tests__/RoundInformation.test.js @@ -25,7 +25,7 @@ const defaultSemesterRoundState = { } describe('Component ', () => { - beforeAll(() => { + beforeEach(() => { usePlannedModules.mockReturnValue({ plannedModules: 'somePlannedModules', }) @@ -72,7 +72,7 @@ describe('Component ', () => { courseRound: mockCourseRound, } - useCourseEmployees.mockReturnValueOnce({ + useCourseEmployees.mockReturnValue({ courseRoundEmployees: { examiners: `${examinersData}'`, responsibles: `${responsiblesData}`, diff --git a/public/js/app/components/__tests__/RoundInformationContacts.test.js b/public/js/app/components/__tests__/RoundInformationContacts.test.js index 201aea16..4871bcfe 100644 --- a/public/js/app/components/__tests__/RoundInformationContacts.test.js +++ b/public/js/app/components/__tests__/RoundInformationContacts.test.js @@ -22,14 +22,7 @@ const withinNextSibling = element => within(nextSibling(element)) describe('Component ', () => { describe('examiners, responsibles and teachers', () => { test('shoud show headers with "missing info" text when data is missing', () => { - render( - - ) + render() const examinerLabel = screen.getByText('Examiner') expect(nextSibling(examinerLabel)).toHaveTextContent('No information inserted') @@ -41,19 +34,14 @@ describe('Component ', () => { }) test('shoud show headers with data inserted as html', () => { - useCourseEmployees.mockReturnValue({ - courseRoundEmployees: { - examiners: '

Test examiners

', - responsibles: '

Test responsibles

', - teachers: '

Test teachers

', - }, - }) render( Test examiners

', + responsibles: '

Test responsibles

', + teachers: '

Test teachers

', + }} /> ) const examinerLabel = screen.getByText('Examiner') @@ -71,58 +59,12 @@ describe('Component ', () => { expect(teacherLink).toHaveTextContent('Test teachers') expect(teacherLink).toHaveAttribute('href', '/profile/testteachers/') }) - - test('should update data when prop changes', () => { - useCourseEmployees.mockImplementation(({ applicationCode }) => - applicationCode === '1111' - ? { courseRoundEmployees: { examiners: 'Examiner for 1111' } } - : applicationCode === '3333' - ? { courseRoundEmployees: { examiners: 'Examiner for 3333' } } - : { courseRoundEmployees: { examiners: '' } } - ) - - const { rerender } = render( - - ) - - expect(nextSibling(screen.getByText('Examiner'))).toHaveTextContent('Examiner for 1111') - - rerender( - - ) - expect(nextSibling(screen.getByText('Examiner'))).toHaveTextContent('No information inserted') - - rerender( - - ) - expect(nextSibling(screen.getByText('Examiner'))).toHaveTextContent('Examiner for 3333') - }) }) describe('cource contact', () => { test('should show header and content for course contact name', () => { render( - + ) const contactLabel = screen.getByText('Contact') expect(contactLabel).toBeInTheDocument() @@ -131,12 +73,7 @@ describe('Component ', () => { "shoud NOT show header if contact name is '%s'", contactNameArg => { render( - + ) const contactLabel = screen.queryByText('Contact') expect(contactLabel).not.toBeInTheDocument() diff --git a/public/js/app/hooks/useCourseEmployees.js b/public/js/app/hooks/useCourseEmployees.js index 0b0a0df8..5517c2f3 100644 --- a/public/js/app/hooks/useCourseEmployees.js +++ b/public/js/app/hooks/useCourseEmployees.js @@ -8,17 +8,12 @@ export const useCourseEmployees = ({ courseCode, selectedSemester, applicationCo const { uri } = context.paths.api.employees - const { data, isError, setApiParams } = useApi( - getCourseEmployees, - { - uri, - courseCode, - selectedSemester, - applicationCode, - }, - {}, - {} - ) + const { data, isError, setApiParams } = useApi(getCourseEmployees, { + uri, + courseCode, + selectedSemester, + applicationCode, + }) useEffect(() => { setApiParams({ uri, courseCode, selectedSemester, applicationCode }) From dcab4855be8b54e14501f2f9558746a2a56e4305 Mon Sep 17 00:00:00 2001 From: amirhossein-haerian Date: Thu, 29 Aug 2024 08:46:29 +0200 Subject: [PATCH 10/19] feat(KUI-1378): the optimization is ready --- .../RoundInformation/RoundInformation.jsx | 44 ++++++++++--------- public/js/app/hooks/__tests__/useApi.test.js | 32 +++++++------- public/js/app/hooks/useApi.js | 24 ++++++---- public/js/app/hooks/useCourseEmployees.js | 8 +--- public/js/app/hooks/usePlannedModules.js | 8 +--- 5 files changed, 59 insertions(+), 57 deletions(-) diff --git a/public/js/app/components/RoundInformation/RoundInformation.jsx b/public/js/app/components/RoundInformation/RoundInformation.jsx index 29d424dd..dddc929a 100644 --- a/public/js/app/components/RoundInformation/RoundInformation.jsx +++ b/public/js/app/components/RoundInformation/RoundInformation.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react' +import React, { useMemo } from 'react' import Alert from '../../components-shared/Alert' import BankIdAlert from '../../components/BankIdAlert' @@ -16,32 +16,34 @@ function RoundInformation({ courseCode, courseData, courseRound, semesterRoundSt const selectedRoundHeader = createRoundHeader(courseRound) const { selectedSemester } = semesterRoundState - const [pending, setPending] = useState(true) + const memoizedCourseRound = useMemo(() => courseRound, [courseRound]) - const { courseRoundEmployees, isError: courseEmployeesError } = useCourseEmployees({ - courseCode, - selectedSemester, - applicationCode: courseRound?.round_application_code, - }) + const memoizedParams = useMemo( + () => ({ + courseCode, + selectedSemester, + applicationCode: memoizedCourseRound?.round_application_code, + }), + [courseCode, selectedSemester, memoizedCourseRound?.round_application_code] + ) - const { plannedModules, isError: plannedModulesError } = usePlannedModules({ - courseCode, - semester: selectedSemester, - applicationCode: courseRound.round_application_code, - }) + const { + courseRoundEmployees, + isError: courseEmployeesError, + isLoading: courseEmployeesLoading, + } = useCourseEmployees(memoizedParams) - useEffect(() => { - setPending(true) - }, [courseRound]) + const { + plannedModules, + isError: plannedModulesError, + isLoading: plannedModulesIsLoading, + } = usePlannedModules(memoizedParams) - useEffect(() => { - if ((courseRoundEmployees && plannedModules) || plannedModulesError || courseEmployeesError) { - setPending(false) - } - }, [courseRoundEmployees, plannedModules, plannedModulesError, courseEmployeesError]) + const isLoading = courseEmployeesLoading || plannedModulesIsLoading + const isError = courseEmployeesError || plannedModulesError return ( -
+

{translation.courseRoundInformation.round_header} {selectedRoundHeader}

diff --git a/public/js/app/hooks/__tests__/useApi.test.js b/public/js/app/hooks/__tests__/useApi.test.js index 13f6cfcd..f3b64173 100644 --- a/public/js/app/hooks/__tests__/useApi.test.js +++ b/public/js/app/hooks/__tests__/useApi.test.js @@ -90,28 +90,28 @@ describe('useApi', () => { }) ) - const { result } = renderHook(() => useApi(apiToCall, defaultParams, defaultValue, null)) + const { result: firstResult } = renderHook(() => useApi(apiToCall, defaultParams, defaultValue, null)) - expect(result.current.data).toStrictEqual(defaultValue) - expect(result.current.isError).toStrictEqual(false) + expect(firstResult.current.data).toStrictEqual(defaultValue) + expect(firstResult.current.isError).toStrictEqual(false) jest.advanceTimersByTime(100) expect(apiToCall).toHaveBeenCalledTimes(1) - await waitFor(() => expect(result.current.data).toStrictEqual('someNewPlannedModules')) - await waitFor(() => expect(result.current.isError).toStrictEqual(false)) + await waitFor(() => expect(firstResult.current.data).toStrictEqual('someNewPlannedModules')) + await waitFor(() => expect(firstResult.current.isError).toStrictEqual(false)) - result.current.setApiParams({ other: 'params' }) + const { result: secodResult } = renderHook(() => useApi(apiToCall, defaultParams, defaultValue, null)) - await waitFor(() => expect(result.current.data).toStrictEqual(defaultValue)) - await waitFor(() => expect(result.current.isError).toStrictEqual(false)) + await waitFor(() => expect(secodResult.current.data).toStrictEqual(defaultValue)) + await waitFor(() => expect(secodResult.current.isError).toStrictEqual(false)) jest.advanceTimersByTime(100) expect(apiToCall).toHaveBeenCalledTimes(2) - await waitFor(() => expect(result.current.data).toStrictEqual('someOtherPlannedModules')) - await waitFor(() => expect(result.current.isError).toStrictEqual(false)) + await waitFor(() => expect(secodResult.current.data).toStrictEqual('someOtherPlannedModules')) + await waitFor(() => expect(secodResult.current.isError).toStrictEqual(false)) jest.useRealTimers() }) @@ -186,18 +186,20 @@ describe('useApi', () => { }) ) - const { result } = renderHook(() => useApi(apiToCall, defaultParams, null, null)) + const { result: firstResult } = renderHook(() => useApi(apiToCall, defaultParams, null, null)) jest.advanceTimersByTime(100) - await waitFor(() => expect(result.current.isError).toStrictEqual(true)) + await waitFor(() => expect(firstResult.current.isError).toStrictEqual(true)) - result.current.setApiParams({ ...defaultParams, applicationCode: 54321 }) + const { result: secodResult } = renderHook(() => + useApi(apiToCall, { ...defaultParams, applicationCode: 54321 }, null, null) + ) - await waitFor(() => expect(result.current.isError).toStrictEqual(false)) + await waitFor(() => expect(secodResult.current.isError).toStrictEqual(false)) jest.advanceTimersByTime(100) - await waitFor(() => expect(result.current.isError).toStrictEqual(true)) + await waitFor(() => expect(secodResult.current.isError).toStrictEqual(true)) jest.useRealTimers() }) diff --git a/public/js/app/hooks/useApi.js b/public/js/app/hooks/useApi.js index f8b70421..1dd60b59 100644 --- a/public/js/app/hooks/useApi.js +++ b/public/js/app/hooks/useApi.js @@ -1,20 +1,26 @@ import { useEffect, useState } from 'react' import { STATUS } from './api/status' -export const useApi = (apiToCall, initialApiParams, defaultValue, defaulValueIfNullResponse) => { - const [apiParams, setApiParams] = useState(initialApiParams) +export const useApi = (apiToCall, apiParams, defaultValue, defaulValueIfNullResponse) => { const [data, setData] = useState(defaultValue) const [isError, setIsError] = useState(false) + const [isLoading, setIsLoading] = useState(true) useEffect(() => { const fetchData = async () => { + setIsLoading(true) setData(defaultValue) setIsError(false) - - const result = await apiToCall(apiParams) - - setData(result.data || defaulValueIfNullResponse) - setIsError(result.status === STATUS.ERROR) + try { + const result = await apiToCall(apiParams) + setData(result.data || defaulValueIfNullResponse) + setIsError(result.status === STATUS.ERROR) + } catch (error) { + setIsError(true) + setData(defaulValueIfNullResponse) + } finally { + setIsLoading(false) + } } fetchData() @@ -22,11 +28,11 @@ export const useApi = (apiToCall, initialApiParams, defaultValue, defaulValueIfN // we do not want to react on defaultValue and defaulValueIfNullResponse, because otherwise // we cannot use empty objects // eslint-disable-next-line react-hooks/exhaustive-deps - }, [apiToCall, apiParams]) + }, [apiToCall, JSON.stringify(apiParams)]) return { data, isError, - setApiParams, + isLoading, } } diff --git a/public/js/app/hooks/useCourseEmployees.js b/public/js/app/hooks/useCourseEmployees.js index 5517c2f3..6e68bf38 100644 --- a/public/js/app/hooks/useCourseEmployees.js +++ b/public/js/app/hooks/useCourseEmployees.js @@ -1,4 +1,3 @@ -import { useEffect } from 'react' import { useWebContext } from '../context/WebContext' import { useApi } from './useApi' import { getCourseEmployees } from './api/getCourseEmployees' @@ -8,19 +7,16 @@ export const useCourseEmployees = ({ courseCode, selectedSemester, applicationCo const { uri } = context.paths.api.employees - const { data, isError, setApiParams } = useApi(getCourseEmployees, { + const { data, isError, isLoading } = useApi(getCourseEmployees, { uri, courseCode, selectedSemester, applicationCode, }) - useEffect(() => { - setApiParams({ uri, courseCode, selectedSemester, applicationCode }) - }, [applicationCode, courseCode, selectedSemester, setApiParams, uri]) - return { courseRoundEmployees: data, isError, + isLoading, } } diff --git a/public/js/app/hooks/usePlannedModules.js b/public/js/app/hooks/usePlannedModules.js index 018dce15..da724f15 100644 --- a/public/js/app/hooks/usePlannedModules.js +++ b/public/js/app/hooks/usePlannedModules.js @@ -1,4 +1,3 @@ -import { useEffect } from 'react' import { useWebContext } from '../context/WebContext' import { getPlannedModules } from './api/getPlannedModules' import { useApi } from './useApi' @@ -12,21 +11,18 @@ export const usePlannedModules = ({ courseCode, semester, applicationCode }) => const basePath = context.paths.api.plannedSchemaModules.uri - const { data, isError, setApiParams } = useApi( + const { data, isError, isLoading } = useApi( getPlannedModules, { basePath, courseCode, semester, applicationCode }, null, MISSING_INFO ) - useEffect(() => { - setApiParams({ basePath, courseCode, semester, applicationCode }) - }, [applicationCode, basePath, courseCode, semester, setApiParams]) - const plannedModules = data === MISSING_INFO ? missingInfoLabel : data return { plannedModules, isError, + isLoading, } } From d148a59f4bc7df40f403a9118fd54bce00557ff5 Mon Sep 17 00:00:00 2001 From: amirhossein-haerian Date: Thu, 29 Aug 2024 10:39:26 +0200 Subject: [PATCH 11/19] fix(KUI-1378): the json.stringify is now removed and more memorization has been used --- public/js/app/hooks/__tests__/useApi.test.js | 19 +++++++++++-------- public/js/app/hooks/useApi.js | 2 +- public/js/app/hooks/useCourseEmployees.js | 18 ++++++++++++------ public/js/app/hooks/usePlannedModules.js | 16 +++++++++++----- 4 files changed, 35 insertions(+), 20 deletions(-) diff --git a/public/js/app/hooks/__tests__/useApi.test.js b/public/js/app/hooks/__tests__/useApi.test.js index f3b64173..04d8e07a 100644 --- a/public/js/app/hooks/__tests__/useApi.test.js +++ b/public/js/app/hooks/__tests__/useApi.test.js @@ -39,9 +39,10 @@ describe('useApi', () => { }) test('calls apiToCall with given parameters', async () => { - const { result } = renderHook(() => useApi(apiToCall, { one: 'one', two: 2 }, null, null)) + const apiParams1 = { one: 'one', two: 2 } + const { result } = renderHook(() => useApi(apiToCall, apiParams1, null, null)) - expect(apiToCall).toHaveBeenLastCalledWith({ one: 'one', two: 2 }) + expect(apiToCall).toHaveBeenLastCalledWith(apiParams1) await waitFor(() => expect(result.current.data).toStrictEqual('somePlannedModules')) @@ -50,15 +51,18 @@ describe('useApi', () => { data: 'someData', }) - const { result: result2 } = renderHook(() => useApi(anotherApiToCall, { two: 'two', three: 3 }, null, null)) + const apiParams2 = { two: 'two', three: 3 } - expect(anotherApiToCall).toHaveBeenLastCalledWith({ two: 'two', three: 3 }) + const { result: result2 } = renderHook(() => useApi(anotherApiToCall, apiParams2, null, null)) + + expect(anotherApiToCall).toHaveBeenLastCalledWith(apiParams2) await waitFor(() => expect(result2.current.data).toStrictEqual('someData')) }) test('returns data from apiToCall', async () => { - const { result } = renderHook(() => useApi(apiToCall, {}, null, null)) + const params = {} + const { result } = renderHook(() => useApi(apiToCall, params, null, null)) await waitFor(() => expect(result.current.data).toStrictEqual('somePlannedModules')) }) @@ -192,9 +196,8 @@ describe('useApi', () => { await waitFor(() => expect(firstResult.current.isError).toStrictEqual(true)) - const { result: secodResult } = renderHook(() => - useApi(apiToCall, { ...defaultParams, applicationCode: 54321 }, null, null) - ) + const newParams = { ...defaultParams, applicationCode: 54321 } + const { result: secodResult } = renderHook(() => useApi(apiToCall, newParams, null, null)) await waitFor(() => expect(secodResult.current.isError).toStrictEqual(false)) jest.advanceTimersByTime(100) diff --git a/public/js/app/hooks/useApi.js b/public/js/app/hooks/useApi.js index 1dd60b59..2dd6d7c0 100644 --- a/public/js/app/hooks/useApi.js +++ b/public/js/app/hooks/useApi.js @@ -28,7 +28,7 @@ export const useApi = (apiToCall, apiParams, defaultValue, defaulValueIfNullResp // we do not want to react on defaultValue and defaulValueIfNullResponse, because otherwise // we cannot use empty objects // eslint-disable-next-line react-hooks/exhaustive-deps - }, [apiToCall, JSON.stringify(apiParams)]) + }, [apiToCall, apiParams]) return { data, diff --git a/public/js/app/hooks/useCourseEmployees.js b/public/js/app/hooks/useCourseEmployees.js index 6e68bf38..fb0186bf 100644 --- a/public/js/app/hooks/useCourseEmployees.js +++ b/public/js/app/hooks/useCourseEmployees.js @@ -1,3 +1,4 @@ +import { useMemo } from 'react' import { useWebContext } from '../context/WebContext' import { useApi } from './useApi' import { getCourseEmployees } from './api/getCourseEmployees' @@ -7,12 +8,17 @@ export const useCourseEmployees = ({ courseCode, selectedSemester, applicationCo const { uri } = context.paths.api.employees - const { data, isError, isLoading } = useApi(getCourseEmployees, { - uri, - courseCode, - selectedSemester, - applicationCode, - }) + const requestData = useMemo( + () => ({ + uri, + courseCode, + selectedSemester, + applicationCode, + }), + [uri, courseCode, selectedSemester, applicationCode] + ) + + const { data, isError, isLoading } = useApi(getCourseEmployees, requestData) return { courseRoundEmployees: data, diff --git a/public/js/app/hooks/usePlannedModules.js b/public/js/app/hooks/usePlannedModules.js index da724f15..6588cb41 100644 --- a/public/js/app/hooks/usePlannedModules.js +++ b/public/js/app/hooks/usePlannedModules.js @@ -1,3 +1,4 @@ +import { useMemo } from 'react' import { useWebContext } from '../context/WebContext' import { getPlannedModules } from './api/getPlannedModules' import { useApi } from './useApi' @@ -11,13 +12,18 @@ export const usePlannedModules = ({ courseCode, semester, applicationCode }) => const basePath = context.paths.api.plannedSchemaModules.uri - const { data, isError, isLoading } = useApi( - getPlannedModules, - { basePath, courseCode, semester, applicationCode }, - null, - MISSING_INFO + const requestData = useMemo( + () => ({ + basePath, + courseCode, + semester, + applicationCode, + }), + [basePath, courseCode, semester, applicationCode] ) + const { data, isError, isLoading } = useApi(getPlannedModules, requestData, null, MISSING_INFO) + const plannedModules = data === MISSING_INFO ? missingInfoLabel : data return { From 8452a071b47331b5e0db2d1c6a11ee6c1b1bd300 Mon Sep 17 00:00:00 2001 From: amirhossein-haerian Date: Fri, 30 Aug 2024 08:00:34 +0200 Subject: [PATCH 12/19] fix(KUI-1378): improve the code and fix an issue --- .../js/app/components/RoundInformation/PlannedModules.jsx | 3 --- .../RoundInformation/RoundInformationInfoGrid.jsx | 5 +---- public/js/app/hooks/usePlannedModules.js | 6 +++--- 3 files changed, 4 insertions(+), 10 deletions(-) delete mode 100644 public/js/app/components/RoundInformation/PlannedModules.jsx diff --git a/public/js/app/components/RoundInformation/PlannedModules.jsx b/public/js/app/components/RoundInformation/PlannedModules.jsx deleted file mode 100644 index 5bab6da4..00000000 --- a/public/js/app/components/RoundInformation/PlannedModules.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import React from 'react' - -export const PlannedModules = ({ plannedModules }) => diff --git a/public/js/app/components/RoundInformation/RoundInformationInfoGrid.jsx b/public/js/app/components/RoundInformation/RoundInformationInfoGrid.jsx index ceb880a6..cdd2cb98 100644 --- a/public/js/app/components/RoundInformation/RoundInformationInfoGrid.jsx +++ b/public/js/app/components/RoundInformation/RoundInformationInfoGrid.jsx @@ -3,7 +3,6 @@ import { useLanguage } from '../../hooks/useLanguage' import InfoModal from '../InfoModal' import { CourseMemoLink } from './CourseMemoLink' import { CourseScheduleLink } from './CourseScheduleLink' -import { PlannedModules } from './PlannedModules' // Calculates if a "Show more" button should be displayed, and creates props for content and button elements. const useShowMoreContent = content => { @@ -117,9 +116,7 @@ function RoundInformationInfoGrid({ courseCode, courseRound, plannedModules }) {

{courseRound.round_seats || translation.courseRoundInformation.round_no_seats_limit}

- - - + diff --git a/public/js/app/hooks/usePlannedModules.js b/public/js/app/hooks/usePlannedModules.js index 6588cb41..82e840c0 100644 --- a/public/js/app/hooks/usePlannedModules.js +++ b/public/js/app/hooks/usePlannedModules.js @@ -6,7 +6,7 @@ import { useMissingInfo } from './useMissingInfo' const MISSING_INFO = '' -export const usePlannedModules = ({ courseCode, semester, applicationCode }) => { +export const usePlannedModules = ({ courseCode, selectedSemester, applicationCode }) => { const context = useWebContext() const { missingInfoLabel } = useMissingInfo() @@ -16,10 +16,10 @@ export const usePlannedModules = ({ courseCode, semester, applicationCode }) => () => ({ basePath, courseCode, - semester, + selectedSemester, applicationCode, }), - [basePath, courseCode, semester, applicationCode] + [basePath, courseCode, selectedSemester, applicationCode] ) const { data, isError, isLoading } = useApi(getPlannedModules, requestData, null, MISSING_INFO) From ef2f3de8b24f37d907a11734af970cfddb032c6e Mon Sep 17 00:00:00 2001 From: amirhossein-haerian Date: Fri, 30 Aug 2024 14:52:30 +0200 Subject: [PATCH 13/19] fix(KUI-1378): the issue for the getPlannedModule is now fixed --- public/js/app/hooks/usePlannedModules.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/app/hooks/usePlannedModules.js b/public/js/app/hooks/usePlannedModules.js index 82e840c0..5132ff96 100644 --- a/public/js/app/hooks/usePlannedModules.js +++ b/public/js/app/hooks/usePlannedModules.js @@ -16,7 +16,7 @@ export const usePlannedModules = ({ courseCode, selectedSemester, applicationCod () => ({ basePath, courseCode, - selectedSemester, + semester: selectedSemester, applicationCode, }), [basePath, courseCode, selectedSemester, applicationCode] From 113e220fcf5742c2e2a4f6c3474c54e9056a6fa3 Mon Sep 17 00:00:00 2001 From: amirhossein-haerian Date: Tue, 3 Sep 2024 10:14:56 +0200 Subject: [PATCH 14/19] feat(KUI-1378): delay is added to the shimmer effect --- .../RoundInformation/RoundInformation.jsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/public/js/app/components/RoundInformation/RoundInformation.jsx b/public/js/app/components/RoundInformation/RoundInformation.jsx index dddc929a..638f7d4c 100644 --- a/public/js/app/components/RoundInformation/RoundInformation.jsx +++ b/public/js/app/components/RoundInformation/RoundInformation.jsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react' +import React, { useEffect, useMemo, useState } from 'react' import Alert from '../../components-shared/Alert' import BankIdAlert from '../../components/BankIdAlert' @@ -42,8 +42,22 @@ function RoundInformation({ courseCode, courseData, courseRound, semesterRoundSt const isLoading = courseEmployeesLoading || plannedModulesIsLoading const isError = courseEmployeesError || plannedModulesError + const [isLoaderVisible, setIsLoaderVisible] = useState(false) + + useEffect(() => { + let timer + if (isLoading) { + setIsLoaderVisible(true) + } else { + timer = setTimeout(() => { + setIsLoaderVisible(false) + }, 300) + } + return () => clearTimeout(timer) + }, [isLoading]) + return ( -
+

{translation.courseRoundInformation.round_header} {selectedRoundHeader}

From 255bf9116a1c551c1b32d957124b524f9737379c Mon Sep 17 00:00:00 2001 From: amirhossein-haerian Date: Tue, 3 Sep 2024 13:00:10 +0200 Subject: [PATCH 15/19] fix(KUI-1394): change the popup text --- i18n/messages.en.js | 2 +- i18n/messages.se.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/messages.en.js b/i18n/messages.en.js index 1b356541..b7b668c2 100644 --- a/i18n/messages.en.js +++ b/i18n/messages.en.js @@ -104,7 +104,7 @@ module.exports = { 'Course offering may be cancelled if number of admitted are less than minimum of places. If there are more applicants than number of places selection will be made.', round_seats_info: 'The selection results are based on:', syllabus_info: - '

• A course goes different course offerings. To see information about a specific course offering, choose semester and course offering. The course syllabus information will be updated depending on the chosen semester. Information from the course syllabus is marked with *.

• Please note: regulations in course syllabus are rules that are generally applicable and binding for both employees and students.

• If you have not chosen semester and course offering, you will see course information from the current or future course syllabus. The valid period of the course syllabus is stated on the page.

', + '

A course goes different course offerings. To see information about a specific course offering, choose semester and course offering. The course syllabus information will be updated depending on the chosen semester. Information from the course syllabus is marked with *

', sideMenu: { aria_label: 'Sub menu', page_about_course: 'About course', diff --git a/i18n/messages.se.js b/i18n/messages.se.js index f6a475d7..230d3360 100644 --- a/i18n/messages.se.js +++ b/i18n/messages.se.js @@ -106,7 +106,7 @@ module.exports = { 'Kursomgången kan komma att ställas in om antalet antagna understiger minimiantalet platser. Vid fler sökande än platser kommer urval att ske.', round_seats_info: 'Urvalet sker baserat på:', syllabus_info: - '

• En kurs undervisas i olika kursomgångar. För att se information om en specifik kursomgång behöver du välja termin och kursomgång. Information från kursplan kommer att uppdateras beroende på vald termin. Information från kursplan är markerad med *.

• Observera: bestämmelser i kursplaner är regler som är generellt tillämpbara och bindande för såväl anställda som studenter.

• Har du inte valt termin och kursomgång ser du kursinformation från nuvarande eller kommande kursplan. På sidan anges den period som information från kursplan gäller för.

', + '

En kurs undervisas i olika kursomgångar. För att se information om en specifik kursomgång behöver du välja termin och kursomgång. Information från kursplan kommer att uppdateras beroende på vald termin. Information från kursplan är markerad med *

', sideMenu: { aria_label: 'Undermeny', page_about_course: 'Om kursen ', From a1411e56a67696ac6fa0e79880a7915afe56a916 Mon Sep 17 00:00:00 2001 From: amirhossein-haerian Date: Wed, 4 Sep 2024 16:17:51 +0200 Subject: [PATCH 16/19] fix(KUI-1434): the padding issue for local navigation in mobile view is now fixed --- public/css/_shared.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/css/_shared.scss b/public/css/_shared.scss index b1dda206..2bea5ad9 100644 --- a/public/css/_shared.scss +++ b/public/css/_shared.scss @@ -16,6 +16,9 @@ } } } + .kth-local-navigation--mobile { + padding: 0; + } label:has(+ .form-group), label:has(+ input) { @include typography.font-heading-xs; From 8ddf8321bfe7c3843dcd4a7ed8a7244fc5d38253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Bj=C3=B6rkqvist?= Date: Mon, 30 Sep 2024 13:47:22 +0200 Subject: [PATCH 17/19] removed supplementaryInfoUrl from mapping --- package-lock.json | 126 +++++++++--------- server/apiCalls/getFilteredData.js | 2 - .../controllers/__tests__/courseCtrl.test.js | 1 - .../mocks/mockedDiscontinuedCourse.js | 1 - 4 files changed, 63 insertions(+), 67 deletions(-) diff --git a/package-lock.json b/package-lock.json index f3f7c209..0cc1ebb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4339,17 +4339,6 @@ "@types/json-schema": "*" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -6007,9 +5996,9 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -6020,7 +6009,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -7950,18 +7939,18 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/enhanced-resolve": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", - "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "license": "MIT", "dependencies": { @@ -9032,37 +9021,37 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -9347,13 +9336,13 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -14036,10 +14025,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "license": "MIT" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -14068,9 +14060,9 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -14993,9 +14985,9 @@ "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", "license": "MIT" }, "node_modules/path-type": { @@ -15672,12 +15664,12 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -16562,9 +16554,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -16600,6 +16592,15 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -16617,15 +16618,15 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -18628,13 +18629,12 @@ } }, "node_modules/webpack": { - "version": "5.93.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz", - "integrity": "sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==", + "version": "5.95.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", + "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", "dev": true, "license": "MIT", "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", @@ -18643,7 +18643,7 @@ "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", diff --git a/server/apiCalls/getFilteredData.js b/server/apiCalls/getFilteredData.js index 1df3ab1a..ddb31eb0 100644 --- a/server/apiCalls/getFilteredData.js +++ b/server/apiCalls/getFilteredData.js @@ -48,8 +48,6 @@ function _parseCourseDefaultInformation(courseDetails, language) { course_recruitment_text: parseOrSetEmpty(course.recruitmentText, language, true), course_required_equipment: parseOrSetEmpty(course.requiredEquipment, language), course_suggested_addon_studies: parseOrSetEmpty(course.addOn, language), - course_supplemental_information_url: parseOrSetEmpty(course.supplementaryInfoUrl, language), - course_supplemental_information_url_text: parseOrSetEmpty(course.supplementaryInfoUrlName, language), course_state: parseOrSetEmpty(course.state, language, true), } } diff --git a/server/controllers/__tests__/courseCtrl.test.js b/server/controllers/__tests__/courseCtrl.test.js index b57945d8..9097cbe9 100644 --- a/server/controllers/__tests__/courseCtrl.test.js +++ b/server/controllers/__tests__/courseCtrl.test.js @@ -48,7 +48,6 @@ jest.mock('../../apiCalls/kursinfoApi', () => ({ getCourseInfo: () => ({ sellingText: { sv: '

Fantastisk kurs

', en: '

This course is awesome

' }, courseDisposition: { sv: '

Kursupplägg på svenska

', en: '

Course Disposition in english

' }, - supplementaryInfo: { sv: '

Övrig info

', en: '

Extra info

' }, imageInfo: 'own_image', }), })) diff --git a/server/controllers/mocks/mockedDiscontinuedCourse.js b/server/controllers/mocks/mockedDiscontinuedCourse.js index ee6f4011..c535ad6d 100644 --- a/server/controllers/mocks/mockedDiscontinuedCourse.js +++ b/server/controllers/mocks/mockedDiscontinuedCourse.js @@ -11,7 +11,6 @@ const mockedDiscontinuedCourse = { }, educationalLevelCode: 'RESEARCH', gradeScaleCode: 'PF', - supplementaryInfo: '

Ersätter kurs FBB3640.

', title: 'Kolhydratteknik inom glykovetenskap', titleOther: 'Carbohydrate Technologies in Glycoscience', cancelled: false, From 022ff43682cc77e4d55bbc6e83e74687cac55ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Bj=C3=B6rkqvist?= Date: Mon, 30 Sep 2024 13:49:41 +0200 Subject: [PATCH 18/19] updated snapshot --- .../controllers/__tests__/courseCtrl.test.js | 246 +++++++++--------- 1 file changed, 122 insertions(+), 124 deletions(-) diff --git a/server/controllers/__tests__/courseCtrl.test.js b/server/controllers/__tests__/courseCtrl.test.js index 9097cbe9..9a3ea5d7 100644 --- a/server/controllers/__tests__/courseCtrl.test.js +++ b/server/controllers/__tests__/courseCtrl.test.js @@ -111,131 +111,129 @@ describe('Discontinued course to test', () => { ) expect(testResponse.html).toMatchInlineSnapshot(` - { - "applicationStore": {}, - "basename": "/student/kurser/kurs", - "context": { - "activeSemesters": [], - "browserConfig": { - "session": {}, - "sessionSecret": "xxx", - }, - "courseCode": "FCK3305", - "courseData": { - "courseInfo": { - "course_application_info": "

Kursen ges inte läsåret 22/23.

Kontakta examinator / kursansvarig för information.

", - "course_code": "FCK3305", - "course_contact_name": "Ingen information tillagd", - "course_department": "CBH/Kemi", - "course_department_code": "CE", - "course_department_link": "CBH/Kemi", - "course_disposition": "

Kursupplägg på svenska

", - "course_education_type_id": null, - "course_examiners": "

Examiner 1

", - "course_grade_scale": "P, F", - "course_last_exam": [], - "course_level_code": "RESEARCH", - "course_literature": "

Litteratur anvisas vid kursstart.

", - "course_main_subject": "Denna kurs tillhör inget huvudområde.", - "course_possibility_to_addition": "Ingen information tillagd", - "course_possibility_to_completions": "Ingen information tillagd", - "course_prerequisites": "Ingen information tillagd", - "course_recruitment_text": "

Teori och metoder inom glykovetenskap.

", - "course_required_equipment": "Ingen information tillagd", - "course_state": "ESTABLISHED", - "course_suggested_addon_studies": "Ingen information tillagd", - "course_supplemental_information": "

Övrig info

", - "course_supplemental_information_url": "Ingen information tillagd", - "course_supplemental_information_url_text": "Ingen information tillagd", - "imageFromAdmin": "own_image", - "sellingText": "

Fantastisk kurs

", - }, - "courseTitleData": { - "course_code": "FCK3305", - "course_credits": 7.5, - "course_credits_text": "hp", - "course_other_title": "Carbohydrate Technologies in Glycoscience", - "course_title": "Kolhydratteknik inom glykovetenskap", - }, - "emptySyllabusData": { - "course_additional_regulations": "", - "course_content": "Ingen information tillagd", - "course_decision_to_discontinue": "", - "course_eligibility": "Ingen information tillagd", - "course_ethical": "", - "course_examination": "Ingen information tillagd", - "course_examination_comments": "", - "course_goals": "Ingen information tillagd", - "course_literature": "Ingen information tillagd", - "course_literature_comment": "Ingen information tillagd", - "course_required_equipment": "", - "course_requirments_for_final_grade": "", - "course_transitional_reg": "", - "course_valid_from": undefined, - "course_valid_to": undefined, - }, - "language": "sv", - "roundsBySemester": {}, - "syllabusList": [ - { - "course_additional_regulations": "", - "course_content": "

Glykovetenskap är ett tvärvetenskapligt forskningsområde som fokuserar på att förstå strukturer och funktionella roller för glykaner (kolhydrater) i biologiska system. Det täcker ämnesområden som biologi, biokemi, kemi, medicin, materialvetenskap, nanoteknologi och beräkningsvetenskap. Kursens mål är att ge en översikt över aktuell kunskap och teknik inom glykovetenskap, utmaningar och möjligheter för bred tillämpning av kolhydratteknik inom hälsa, energi och materialvetenskap, samt god teoretisk insikt och praktiska färdigheter i hur kolhydratteknik kan bidra till hållbar utveckling inom energi- och materialvetenskap.

Ämnen som avhandlas omfattar kolhydratteknik inom hälsa, energi och materialvetenskap, glykaners funktioner, kolhydratanalys av komplexa kolhydrater, glykaner och glykokonjugat, uppbyggnaden av växters cellvägg, kolhydrataktiva enzymer, enzymatisk nedbrytning av växtbiomassa och modifiering av växtbaserade fibrer, biosyntes och av cellulosa och kitin, skapande av nya kompositer genom bioteknologisk modifiering av växtcellväggen, modifiering av glykaner genom att förändra syntesvägar, omvandling av växtbiomassa till finkemikalier och råmaterial, polymera material och nanomaterial, användande av biomassabaserade nanomaterial för nya material och tillämpningar.

", - "course_decision_to_discontinue": "Ingen information tillagd", - "course_eligibility": "

Behörig till studier på forskarnivå. Goda kunskaper i engelska.

", - "course_ethical": "
  • Vid grupparbete har alla i gruppen ansvar för gruppens arbete.
  • Vid examination ska varje student ärligt redovisa hjälp som erhållits och källor som använts.
  • Vid muntlig examination ska varje student kunna redogöra för hela uppgiften och hela lösningen.
", - "course_examination": "
  • INL1 - - Inlämningsuppgift, - 2,0 hp, - betygsskala: P, F -
  • LAB1 - - Laborationer, - 4,0 hp, - betygsskala: P, F -
  • TEN1 - - Skriftlig tentamen, - 1,5 hp, - betygsskala: P, F -
", - "course_examination_comments": "Examinator beslutar, baserat på rekommendation från KTH:s samordnare för funktionsnedsättning, om eventuell anpassad examination för studenter med dokumenterad, varaktig funktionsnedsättning.

Examinator får medge annan examinationsform vid omexamination av enstaka studenter.

Betygskriterier redovisas i kurs-PM.

", - "course_goals": "

Efter fullföljande av kursen förväntas studenten kunna

  • Visa kunskap om kolhydraters mångfald, dess betydelse för biologiska system, samt hur de kan förändra struktur och funktion hos andra biologiska molekyler.
  • Visa kunskap om cellväggens struktur och funktion hos vedbildande växter, samt övergripande förståelse för hur dess sammansättning kan förändras för att möjliggöra nya tillämpningar, t.ex. för att underlätta bearbetning för energi- och biomaterialproduktion.
  • Visa förmåga att redogöra och reflektera över koncept och metoder som används för att producera byggstenar från växtbiomassa, och hur de kan sättas ihop till nya material med skräddarsydda egenskaper och funktionaliteter.
  • Visa förmåga att planera och utföra praktiska experiment inom kolhydratteknik, samt att analysera och redogöra resultaten i form av skriftliga rapporter.
  • Visa förmåga att identifiera och diskutera hur kolhydratteknik kan bidra till en hållbar samhällsutveckling inom konsumtion, produktion och material, t.ex. genom att återanvända redan existerande produkter, eller tillverkning av nya resurssmarta och förnyelsebara material.
", - "course_literature": "Ingen information tillagd", - "course_literature_comment": "Ingen information tillagd", - "course_required_equipment": "Ingen information tillagd", - "course_requirments_for_final_grade": "

Godkänd skriftlig tentamen, godkända inlämningsuppgifter kopplade till föreläsningarna, 100% närvaro på laborationer och slutförande av laborationer, samt godkända laborationsrapporter.

", - "course_transitional_reg": "", - "course_valid_from": { - "semesterNumber": 2, - "year": 2019, - }, - "course_valid_to": undefined, - }, - ], - }, - "employees": { - "responsibles": [], - "teachers": [], - }, - "hostUrl": undefined, - "initiallySelectedRoundIndex": undefined, - "initiallySelectedSemester": null, - "isCancelledOrDeactivated": false, - "lang": "sv", - "paths": { - "system": { - "monitor": { - "uri": "/_monitor", - }, - "robots": { - "uri": "/robots.txt", - }, - }, - }, - "proxyPrefixPath": { - "uri": "/student/kurser/kurs", +{ + "applicationStore": {}, + "basename": "/student/kurser/kurs", + "context": { + "activeSemesters": [], + "browserConfig": { + "session": {}, + "sessionSecret": "xxx", + }, + "courseCode": "FCK3305", + "courseData": { + "courseInfo": { + "course_application_info": "

Kursen ges inte läsåret 22/23.

Kontakta examinator / kursansvarig för information.

", + "course_code": "FCK3305", + "course_contact_name": "Ingen information tillagd", + "course_department": "CBH/Kemi", + "course_department_code": "CE", + "course_department_link": "CBH/Kemi", + "course_disposition": "

Kursupplägg på svenska

", + "course_education_type_id": null, + "course_examiners": "

Examiner 1

", + "course_grade_scale": "P, F", + "course_last_exam": [], + "course_level_code": "RESEARCH", + "course_literature": "

Litteratur anvisas vid kursstart.

", + "course_main_subject": "Denna kurs tillhör inget huvudområde.", + "course_possibility_to_addition": "Ingen information tillagd", + "course_possibility_to_completions": "Ingen information tillagd", + "course_prerequisites": "Ingen information tillagd", + "course_recruitment_text": "

Teori och metoder inom glykovetenskap.

", + "course_required_equipment": "Ingen information tillagd", + "course_state": "ESTABLISHED", + "course_suggested_addon_studies": "Ingen information tillagd", + "course_supplemental_information": "", + "imageFromAdmin": "own_image", + "sellingText": "

Fantastisk kurs

", + }, + "courseTitleData": { + "course_code": "FCK3305", + "course_credits": 7.5, + "course_credits_text": "hp", + "course_other_title": "Carbohydrate Technologies in Glycoscience", + "course_title": "Kolhydratteknik inom glykovetenskap", + }, + "emptySyllabusData": { + "course_additional_regulations": "", + "course_content": "Ingen information tillagd", + "course_decision_to_discontinue": "", + "course_eligibility": "Ingen information tillagd", + "course_ethical": "", + "course_examination": "Ingen information tillagd", + "course_examination_comments": "", + "course_goals": "Ingen information tillagd", + "course_literature": "Ingen information tillagd", + "course_literature_comment": "Ingen information tillagd", + "course_required_equipment": "", + "course_requirments_for_final_grade": "", + "course_transitional_reg": "", + "course_valid_from": undefined, + "course_valid_to": undefined, + }, + "language": "sv", + "roundsBySemester": {}, + "syllabusList": [ + { + "course_additional_regulations": "", + "course_content": "

Glykovetenskap är ett tvärvetenskapligt forskningsområde som fokuserar på att förstå strukturer och funktionella roller för glykaner (kolhydrater) i biologiska system. Det täcker ämnesområden som biologi, biokemi, kemi, medicin, materialvetenskap, nanoteknologi och beräkningsvetenskap. Kursens mål är att ge en översikt över aktuell kunskap och teknik inom glykovetenskap, utmaningar och möjligheter för bred tillämpning av kolhydratteknik inom hälsa, energi och materialvetenskap, samt god teoretisk insikt och praktiska färdigheter i hur kolhydratteknik kan bidra till hållbar utveckling inom energi- och materialvetenskap.

Ämnen som avhandlas omfattar kolhydratteknik inom hälsa, energi och materialvetenskap, glykaners funktioner, kolhydratanalys av komplexa kolhydrater, glykaner och glykokonjugat, uppbyggnaden av växters cellvägg, kolhydrataktiva enzymer, enzymatisk nedbrytning av växtbiomassa och modifiering av växtbaserade fibrer, biosyntes och av cellulosa och kitin, skapande av nya kompositer genom bioteknologisk modifiering av växtcellväggen, modifiering av glykaner genom att förändra syntesvägar, omvandling av växtbiomassa till finkemikalier och råmaterial, polymera material och nanomaterial, användande av biomassabaserade nanomaterial för nya material och tillämpningar.

", + "course_decision_to_discontinue": "Ingen information tillagd", + "course_eligibility": "

Behörig till studier på forskarnivå. Goda kunskaper i engelska.

", + "course_ethical": "
  • Vid grupparbete har alla i gruppen ansvar för gruppens arbete.
  • Vid examination ska varje student ärligt redovisa hjälp som erhållits och källor som använts.
  • Vid muntlig examination ska varje student kunna redogöra för hela uppgiften och hela lösningen.
", + "course_examination": "
  • INL1 - + Inlämningsuppgift, + 2,0 hp, + betygsskala: P, F +
  • LAB1 - + Laborationer, + 4,0 hp, + betygsskala: P, F +
  • TEN1 - + Skriftlig tentamen, + 1,5 hp, + betygsskala: P, F +
", + "course_examination_comments": "Examinator beslutar, baserat på rekommendation från KTH:s samordnare för funktionsnedsättning, om eventuell anpassad examination för studenter med dokumenterad, varaktig funktionsnedsättning.

Examinator får medge annan examinationsform vid omexamination av enstaka studenter.

Betygskriterier redovisas i kurs-PM.

", + "course_goals": "

Efter fullföljande av kursen förväntas studenten kunna

  • Visa kunskap om kolhydraters mångfald, dess betydelse för biologiska system, samt hur de kan förändra struktur och funktion hos andra biologiska molekyler.
  • Visa kunskap om cellväggens struktur och funktion hos vedbildande växter, samt övergripande förståelse för hur dess sammansättning kan förändras för att möjliggöra nya tillämpningar, t.ex. för att underlätta bearbetning för energi- och biomaterialproduktion.
  • Visa förmåga att redogöra och reflektera över koncept och metoder som används för att producera byggstenar från växtbiomassa, och hur de kan sättas ihop till nya material med skräddarsydda egenskaper och funktionaliteter.
  • Visa förmåga att planera och utföra praktiska experiment inom kolhydratteknik, samt att analysera och redogöra resultaten i form av skriftliga rapporter.
  • Visa förmåga att identifiera och diskutera hur kolhydratteknik kan bidra till en hållbar samhällsutveckling inom konsumtion, produktion och material, t.ex. genom att återanvända redan existerande produkter, eller tillverkning av nya resurssmarta och förnyelsebara material.
", + "course_literature": "Ingen information tillagd", + "course_literature_comment": "Ingen information tillagd", + "course_required_equipment": "Ingen information tillagd", + "course_requirments_for_final_grade": "

Godkänd skriftlig tentamen, godkända inlämningsuppgifter kopplade till föreläsningarna, 100% närvaro på laborationer och slutförande av laborationer, samt godkända laborationsrapporter.

", + "course_transitional_reg": "", + "course_valid_from": { + "semesterNumber": 2, + "year": 2019, }, + "course_valid_to": undefined, }, - "location": undefined, - } - `) + ], + }, + "employees": { + "responsibles": [], + "teachers": [], + }, + "hostUrl": undefined, + "initiallySelectedRoundIndex": undefined, + "initiallySelectedSemester": null, + "isCancelledOrDeactivated": false, + "lang": "sv", + "paths": { + "system": { + "monitor": { + "uri": "/_monitor", + }, + "robots": { + "uri": "/robots.txt", + }, + }, + }, + "proxyPrefixPath": { + "uri": "/student/kurser/kurs", + }, + }, + "location": undefined, +} +`) }) }) From b2446d8130766d89571e9d484347ccade7a9f8ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Bj=C3=B6rkqvist?= Date: Mon, 30 Sep 2024 15:11:48 +0200 Subject: [PATCH 19/19] Requested changes --- public/js/app/components/CourseSectionList.jsx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/public/js/app/components/CourseSectionList.jsx b/public/js/app/components/CourseSectionList.jsx index 211da55d..0116dd3e 100644 --- a/public/js/app/components/CourseSectionList.jsx +++ b/public/js/app/components/CourseSectionList.jsx @@ -157,16 +157,6 @@ function CourseSectionList({ courseInfo = {}, partToShow, syllabus = {}, syllabu header: translation.courseInformation.course_supplemental_information, text: courseInfo.course_supplemental_information, }) - if (!isMissingInfoLabel(courseInfo.course_supplemental_information_url)) - prepare.push({ - header: translation.courseInformation.course_supplemental_information_url, - text: courseInfo.course_supplemental_information_url, - }) - if (!isMissingInfoLabel(courseInfo.course_supplemental_information_url_text)) - prepare.push({ - header: translation.courseInformation.course_supplemental_information_url_text, - text: courseInfo.course_supplemental_information_url_text, - }) if (!isContractEducation() && syllabus.course_additional_regulations !== '') prepare.push({