diff --git a/cypress/e2e/Population_statistics.js b/cypress/e2e/Population_statistics.js index fb513040b6..efc1411a14 100644 --- a/cypress/e2e/Population_statistics.js +++ b/cypress/e2e/Population_statistics.js @@ -354,7 +354,7 @@ describe('Population statistics tests', () => { cy.contains('Tags').click() cy.contains('No tags defined. You can define them here.').find('a').click() }) - cy.url().should('include', '/study-programme/KH50_001?p_m_tab=0&p_tab=4') + cy.url().should('include', '/study-programme/KH50_001?tab=4') cy.contains('Matemaattisten tieteiden kandiohjelma') cy.contains('Create tags for study programme') cy.contains('button', 'Create a new tag').should('be.disabled') diff --git a/cypress/e2e/Study_programme_overview.js b/cypress/e2e/Study_programme_overview.js index 4fe2895b0f..1f1f246952 100644 --- a/cypress/e2e/Study_programme_overview.js +++ b/cypress/e2e/Study_programme_overview.js @@ -37,7 +37,7 @@ describe('Study programme overview', () => { }) }) - describe('Basic information view works for basic user', () => { + describe('Basic information tab works for basic user', () => { beforeEach(() => { cy.init('/study-programme') cy.contains('a', 'Matemaattisten tieteiden kandiohjelma').click({ force: true }) @@ -261,15 +261,15 @@ describe('Study programme overview', () => { }) }) - describe('Study track overview works for basic user', () => { + describe('Study tracks and class statistics tab works for basic user', () => { beforeEach(() => { cy.init('/study-programme') cy.contains('a', 'Matemaattisten tieteiden kandiohjelma').click() - cy.get('.attached').contains('Study tracks and class statistics').click() + cy.cs('study-tracks-and-class-statistics-tab').click() }) // If the backend breaks for one of the sections, the section header is not rendered and this will fail - it('Study tracks and class statistics -tab loads', () => { + it('Study tracks and class statistics', () => { cy.get('[data-cy=Section-studyTrackOverview]') cy.get('[data-cy=Section-studyTrackProgress]') cy.get('[data-cy=Section-averageGraduationTimesStudyTracks]') @@ -494,11 +494,11 @@ describe('Study programme overview', () => { }) }) - describe('Programme courses works for basic user', () => { + describe('Programme courses tab works for basic user', () => { beforeEach(() => { cy.init('/study-programme') cy.contains('a', 'Matemaattisten tieteiden kandiohjelma').click() - cy.get('.attached').contains('Programme courses').click() + cy.cs('programme-courses-tab').click() }) it('content loads', () => { @@ -681,11 +681,11 @@ describe('Study programme overview', () => { }) }) - describe('Degree courses works for basic user', () => { + describe('Degree courses tab works for basic user', () => { beforeEach(() => { cy.init('/study-programme') cy.contains('a', 'Matemaattisten tieteiden kandiohjelma').click() - cy.get('.attached').contains('Degree courses').click() + cy.cs('degree-courses-tab').click() }) it('content loads', () => { @@ -702,11 +702,11 @@ describe('Study programme overview', () => { }) }) - describe('Tags view works for basic user', () => { + describe('Tags tab works for basic user', () => { beforeEach(() => { cy.init('/study-programme') cy.contains('a', 'Matemaattisten tieteiden kandiohjelma').click({ force: true }) - cy.get('.attached').contains('Tags').click() + cy.cs('tags-tab').click() }) it('can create and delete tags for population', () => { @@ -751,7 +751,9 @@ describe('Study programme overview', () => { cy.contains('Successfully added tags to students.') - cy.contains('td', name).get('i.level.up.alternate.icon').click() + cy.contains('td', name).within(() => { + cy.get('i.level.up.alternate.icon').click() + }) cy.contains('Matemaattisten tieteiden kandiohjelma 2022 - 2023') cy.contains(`Tagged with: ${name}`) @@ -772,7 +774,7 @@ describe('Study programme overview', () => { } cy.get('a').contains('Matemaattisten tieteiden kandiohjelma').invoke('removeAttr', 'target').click() - cy.url().should('include', '/study-programme/KH50_001?p_tab=4') + cy.url().should('include', '/study-programme/KH50_001?tab=4') deleteTag(name) }) @@ -813,7 +815,7 @@ describe('Study programme overview', () => { }) it('can access study tracks', () => { - cy.get('.attached').contains('Study tracks and class statistics').click() + cy.cs('study-tracks-and-class-statistics-tab').click() cy.get('[data-cy=Section-studyTrackOverview]') cy.get('[data-cy=Section-studyTrackProgress]') @@ -821,7 +823,9 @@ describe('Study programme overview', () => { }) it("doesn't see other tabs", () => { - cy.get('div.ui.tabular.menu a').should('have.length', 2) + cy.cs('degree-courses-tab').should('not.exist') + cy.cs('tags-tab').should('not.exist') + cy.cs('update-statistics-tab').should('not.exist') }) }) }) diff --git a/services/frontend/src/components/LanguagePicker/useLanguage.tsx b/services/frontend/src/components/LanguagePicker/useLanguage.tsx index e0c9aa579e..acc79a1f96 100644 --- a/services/frontend/src/components/LanguagePicker/useLanguage.tsx +++ b/services/frontend/src/components/LanguagePicker/useLanguage.tsx @@ -54,3 +54,5 @@ export const useLanguage = () => { getTextIn, } } + +export type GetTextIn = ReturnType['getTextIn'] diff --git a/services/frontend/src/components/PopulationDetails/PopulationCourses/FilterDegreeCoursesModal.jsx b/services/frontend/src/components/PopulationDetails/PopulationCourses/FilterDegreeCoursesModal.jsx index 73796a5a51..2ff396eef7 100644 --- a/services/frontend/src/components/PopulationDetails/PopulationCourses/FilterDegreeCoursesModal.jsx +++ b/services/frontend/src/components/PopulationDetails/PopulationCourses/FilterDegreeCoursesModal.jsx @@ -1,7 +1,7 @@ import { useState } from 'react' import { Button, Icon, Modal, Container } from 'semantic-ui-react' -import { DegreeCoursesTable } from '@/pages/StudyProgramme/DegreeCourses' +import { DegreeCoursesTab } from '@/pages/StudyProgramme/DegreeCoursesTab' export const FilterDegreeCoursesModal = ({ studyProgramme, year }) => { const [open, setOpen] = useState(false) @@ -25,7 +25,7 @@ export const FilterDegreeCoursesModal = ({ studyProgramme, year }) => { Hide degree courses - + - - -
-

Update data on Study tracks and class statistics view

- - -
- - ) -} diff --git a/services/frontend/src/pages/StudyProgramme/index.jsx b/services/frontend/src/pages/StudyProgramme/index.jsx deleted file mode 100644 index 0f32eaa488..0000000000 --- a/services/frontend/src/pages/StudyProgramme/index.jsx +++ /dev/null @@ -1,149 +0,0 @@ -import { useState, useCallback } from 'react' -import { useParams, useLocation, useNavigate } from 'react-router' -import { Header, Menu, Segment, Tab } from 'semantic-ui-react' - -import { isDefaultServiceProvider } from '@/common' -import { useLanguage } from '@/components/LanguagePicker/useLanguage' -import { useSemanticTabs } from '@/hooks/tabs' -import { useTitle } from '@/hooks/title' -import { useGetAuthorizedUserQuery } from '@/redux/auth' -import { useGetProgrammesQuery } from '@/redux/populations' -import { getFullStudyProgrammeRights } from '@/util/access' -import { getCombinedProgrammeName } from '@/util/combinedProgramme' -import { BasicOverview } from './BasicOverview' -import { DegreeCoursesTable } from './DegreeCourses' -import { ProgrammeCourses } from './ProgrammeCourses' -import { StudyProgrammeSelector } from './StudyProgrammeSelector' -import { StudyTrackOverview } from './StudyTrackOverview' -import { Tags } from './Tags' -import { UpdateView } from './UpdateView' - -const createName = (studyProgrammeId, combibedProgrammeId, programmes, language, getTextIn) => { - if (combibedProgrammeId && programmes?.[studyProgrammeId] && programmes?.[combibedProgrammeId]) - return getCombinedProgrammeName( - getTextIn(programmes?.[studyProgrammeId].name), - getTextIn(programmes?.[combibedProgrammeId].name), - language - ) - return programmes?.[studyProgrammeId] && getTextIn(programmes?.[studyProgrammeId].name) -} - -export const StudyProgramme = () => { - const navigate = useNavigate() - const location = useLocation() - const { studyProgrammeId } = useParams() - const { data: programmes } = useGetProgrammesQuery() - const { language, getTextIn } = useLanguage() - const { isAdmin, fullAccessToStudentData, programmeRights } = useGetAuthorizedUserQuery() - const fullStudyProgrammeRights = getFullStudyProgrammeRights(programmeRights) - const replace = useCallback(options => navigate(options, { replace: true }), [navigate]) - const [tab, setTab] = useSemanticTabs('p_tab', 0, { location, replace }) - const [academicYear, setAcademicYear] = useState(false) - const [specialGroupsExcluded, setSpecialGroupsExcluded] = useState(false) - const [graduated, setGraduated] = useState(false) - useTitle('Study programmes') - - if (!studyProgrammeId) { - return - } - - const programmeId = - studyProgrammeId && studyProgrammeId.includes('+') ? studyProgrammeId.split('+')[0] : studyProgrammeId - const secondProgrammeId = studyProgrammeId && studyProgrammeId.includes('+') ? studyProgrammeId.split('+')[1] : '' - - const getPanes = () => { - const panes = [] - panes.push({ - menuItem: 'Basic information', - render: () => ( - - ), - }) - panes.push({ - menuItem: 'Study tracks and class statistics', - render: () => ( - - ), - }) - - if ( - fullAccessToStudentData || - fullStudyProgrammeRights.includes(programmeId) || - fullStudyProgrammeRights.includes(secondProgrammeId) - ) { - if (isDefaultServiceProvider()) { - panes.push({ - menuItem: Programme courses, - render: () => ( - - ), - }) - } - panes.push({ - menuItem: 'Degree courses', - render: () => ( - - ), - }) - panes.push({ - menuItem: 'Tags', - render: () => , - }) - } - if (isAdmin) { - panes.push({ - menuItem: 'Update statistics', - render: () => , - }) - } - return panes - } - - const programmeName = createName(programmeId, secondProgrammeId, programmes, language, getTextIn) - const programmeLetterId = programmes?.[programmeId]?.progId - const secondProgrammeLetterId = programmes?.[secondProgrammeId]?.progId - const panes = getPanes() - - return ( -
- -
-
{programmeName}
- - {programmeLetterId ? `${programmeLetterId} - ` : ''} {programmeId} - -
- {secondProgrammeId && ( - - {secondProgrammeLetterId ? `${secondProgrammeLetterId} - ` : ''} {secondProgrammeId} - - )} -
- -
-
- ) -} diff --git a/services/frontend/src/pages/StudyProgramme/index.tsx b/services/frontend/src/pages/StudyProgramme/index.tsx new file mode 100644 index 0000000000..37eea6ec83 --- /dev/null +++ b/services/frontend/src/pages/StudyProgramme/index.tsx @@ -0,0 +1,181 @@ +import { Container, Stack, Tab, Tabs } from '@mui/material' +import { useState } from 'react' +import { useParams } from 'react-router' + +import { isDefaultServiceProvider } from '@/common' +import { GetTextIn, useLanguage } from '@/components/LanguagePicker/useLanguage' +import { PageTitle } from '@/components/material/PageTitle' +import { useTabs } from '@/hooks/tabs' +import { useTitle } from '@/hooks/title' +import { useGetAuthorizedUserQuery } from '@/redux/auth' +import { useGetProgrammesQuery } from '@/redux/populations' +import { Language } from '@/shared/language' +import { DegreeProgramme } from '@/types/api/faculty' +import { getFullStudyProgrammeRights } from '@/util/access' +import { getCombinedProgrammeName } from '@/util/combinedProgramme' +import { BasicInformationTab } from './BasicInformationTab' +import { DegreeCoursesTab } from './DegreeCoursesTab' +import { ProgrammeCoursesTab } from './ProgrammeCoursesTab' +import { StudyProgrammeSelector } from './StudyProgrammeSelector' +import { StudyTracksAndClassStatisticsTab } from './StudyTracksAndClassStatisticsTab' +import { TagsTab } from './TagsTab' +import { UpdateStatisticsTab } from './UpdateStatisticsTab' + +const getProgrammeName = ( + studyProgrammeId: string, + combibedProgrammeId: string, + programmes: Record | undefined, + language: Language, + getTextIn: GetTextIn +) => { + if (combibedProgrammeId && programmes?.[studyProgrammeId] && programmes?.[combibedProgrammeId]) { + return getCombinedProgrammeName( + getTextIn(programmes?.[studyProgrammeId].name)!, + getTextIn(programmes?.[combibedProgrammeId].name)!, + language + ) + } + return programmes?.[studyProgrammeId] && getTextIn(programmes?.[studyProgrammeId].name) +} + +const getSubtitle = (programmeId: string, programmeLetterId?: string, secondProgrammeLetterId?: string) => { + if (!programmeLetterId) { + return programmeId + } + if (secondProgrammeLetterId) { + return `${programmeLetterId}+${secondProgrammeLetterId} - ${programmeId}` + } + return `${programmeLetterId} - ${programmeId}` +} + +export const StudyProgramme = () => { + const { studyProgrammeId } = useParams() + const { data: programmes } = useGetProgrammesQuery() + const { language, getTextIn } = useLanguage() + const { isAdmin, fullAccessToStudentData, programmeRights } = useGetAuthorizedUserQuery() + const fullStudyProgrammeRights = getFullStudyProgrammeRights(programmeRights) + const [currentTab, setCurrentTab] = useTabs(isAdmin ? 6 : 5) + const [academicYear, setAcademicYear] = useState(false) + const [specialGroupsExcluded, setSpecialGroupsExcluded] = useState(false) + const [graduated, setGraduated] = useState(false) + + useTitle('Study programmes') // TODO: Include programme name if a programme is selected + + if (!studyProgrammeId) { + return + } + + const programmeId = studyProgrammeId?.includes('+') ? studyProgrammeId.split('+')[0] : studyProgrammeId + const secondProgrammeId = studyProgrammeId?.includes('+') ? studyProgrammeId.split('+')[1] : '' + const programmeName = getProgrammeName(programmeId, secondProgrammeId, programmes, language, getTextIn) + const programmeLetterId = programmes?.[programmeId]?.progId + const secondProgrammeLetterId = programmes?.[secondProgrammeId]?.progId + + const otherTabsVisible: boolean = + fullAccessToStudentData || + fullStudyProgrammeRights.includes(programmeId) || + fullStudyProgrammeRights.includes(secondProgrammeId) + + const tabs = [ + { + key: 'BasicInformationTab', + cypress: 'basic-information-tab', + label: 'Basic information', + component: ( + + ), + }, + { + key: 'StudyTracksAndClassStatisticsTab', + cypress: 'study-tracks-and-class-statistics-tab', + label: 'Study tracks and class statistics', + component: ( + + ), + }, + ] + + if (otherTabsVisible && isDefaultServiceProvider()) { + tabs.push({ + key: 'ProgrammeCoursesTab', + cypress: 'programme-courses-tab', + label: 'Programme courses', + component: ( + + ), + }) + } + + if (otherTabsVisible) { + tabs.push( + { + key: 'DegreeCoursesTab', + cypress: 'degree-courses-tab', + label: 'Degree courses', + component: ( + + ), + }, + { + key: 'TagsTab', + cypress: 'tags-tab', + label: 'Tags', + component: , + } + ) + } + + if (isAdmin) { + tabs.push({ + key: 'UpdateStatisticsTab', + cypress: 'update-statistics-tab', + label: 'Update statistics', + component: , + }) + } + + return ( + + + + setCurrentTab(newTab)} + value={currentTab} + variant="scrollable" + > + {tabs.map(tab => ( + + ))} + + {tabs.map(tab => currentTab === tabs.indexOf(tab) && tab.component)} + + + ) +}