diff --git a/cypress/e2e/Course_statistics.js b/cypress/e2e/Course_statistics.js index 3ac1dc5f3d..f6d5424122 100644 --- a/cypress/e2e/Course_statistics.js +++ b/cypress/e2e/Course_statistics.js @@ -912,7 +912,7 @@ describe('Course Statistics tests', () => { it('Some features of Course Statistics are hidden for courseStatistics-users without other rights', () => { cy.init('/coursestatistics', 'onlycoursestatistics') - cy.get('[data-cy=navbar-courseStatistics]').click() + cy.get('[data-cy=nav-bar-button-courseStatistics]').click() cy.get('[data-cy=course-code-input]').type('TKT10002') cy.contains('tr', 'TKT10002').click() cy.contains('Filter statistics by study programmes').should('not.exist') diff --git a/cypress/e2e/Users.js b/cypress/e2e/Users.js index 6113b86693..5191ba40f1 100644 --- a/cypress/e2e/Users.js +++ b/cypress/e2e/Users.js @@ -1,47 +1,60 @@ /// const visibleLinks = { - norights: ['University', 'Faculties', 'Special populations', 'Give feedback', 'Logout'], - onlycoursestatistics: ['University', 'Courses', 'Special populations', 'Give feedback', 'Logout'], + norights: ['University', 'Faculties', 'Special populations', 'Feedback'], + onlycoursestatistics: ['University', 'Courses', 'Special populations', 'Feedback'], } visibleLinks.basic = [...visibleLinks.onlycoursestatistics, 'Faculties', 'Programmes', 'Students'] -visibleLinks.admin = [...visibleLinks.basic, 'Teachers', 'Users', 'Updater'] +visibleLinks.admin = [...visibleLinks.basic, 'Teachers', 'Admin'] + +const hasVisibleItems = visibleItems => { + cy.get('[data-cy=nav-bar]').children().should('have.length', visibleItems) +} + +const containsLinks = links => { + cy.get('[data-cy=nav-bar]').within(() => { + for (const link of links) { + cy.contains(link) + } + }) +} + +const userButtonWorks = (username, mocking = false) => { + cy.get('[data-cy=nav-bar-user-button]').click() + cy.contains(mocking ? `Mocking as ${username}` : `Logged in as ${username}`) + cy.contains('Language') + cy.contains('suomi') + cy.contains('English') + cy.contains('svenska') + cy.contains(mocking ? 'Stop mocking' : 'Log out') +} describe('Users tests', () => { - describe('Using user with just grp-oodikone-user, no other rights', () => { + describe('Using as user with just grp-oodikone-user, no other rights', () => { it('shows correct tabs', () => { cy.init('', 'norights') - cy.get('[data-cy=navBar]').children().should('have.length', 7) - cy.get('[data-cy=navBar]').within(() => { - for (const link of visibleLinks.norights) { - cy.contains(link) - } - }) + hasVisibleItems(7) + containsLinks(visibleLinks.norights) + userButtonWorks('norights') }) }) describe('Using as coursestatistics user', () => { it('shows correct tabs', () => { cy.init('', 'onlycoursestatistics') - cy.get('[data-cy=navBar]').children().should('have.length', 7) - cy.get('[data-cy=navBar]').within(() => { - for (const link of visibleLinks.onlycoursestatistics) { - cy.contains(link) - } - }) + hasVisibleItems(7) + containsLinks(visibleLinks.onlycoursestatistics) + userButtonWorks('onlycoursestatistics') }) }) describe('Using as basic user', () => { it('shows correct tabs', () => { cy.init('') - cy.get('[data-cy=navBar]').children().should('have.length', 10) - cy.get('[data-cy=navBar]').within(() => { - for (const link of visibleLinks.basic) { - cy.contains(link) - } - }) + hasVisibleItems(10) + containsLinks(visibleLinks.basic) + userButtonWorks('basic') }) }) @@ -51,27 +64,32 @@ describe('Users tests', () => { }) it('should see more stuff than others', () => { - cy.get('[data-cy=navBar]').children().should('have.length', 13) - cy.get('[data-cy=navBar]').within(() => { - for (const link of visibleLinks.admin) { - cy.contains(link) - } - }) + hasVisibleItems(13) + containsLinks(visibleLinks.admin) + userButtonWorks('admin') }) - it("mocking normal user shows only the mocked user's programmes", () => { - cy.contains('mocking').should('not.exist') - cy.cs('user-edit-button-basic').click() - cy.get('i.spy').click() - cy.contains('mocking as basic') - cy.contains('Programmes').click().siblings().contains('Class statistics').click() - cy.contains('label', 'Study programme') - .siblings() - .within(() => { - cy.get("div[role='option']").should('have.length', 2) - cy.get("div[role='option']").eq(0).contains('Matemaattisten tieteiden kandiohjelma') - cy.get("div[role='option']").eq(1).contains('Matematiikan ja tilastotieteen maisteriohjelma') - }) + describe('can mock as other users', () => { + beforeEach(() => { + cy.cs('user-edit-button-basic').click() + cy.get('i.spy').click() + }) + + it('user button shows mocked user', () => { + userButtonWorks('basic', true) + }) + + it("only the mocked user's programmes are visible", () => { + cy.get('[data-cy=nav-bar-button-studyProgramme]').click() + cy.get('[data-cy=nav-bar-button-class]').click() + cy.contains('label', 'Study programme') + .siblings() + .within(() => { + cy.get("div[role='option']").should('have.length', 2) + cy.get("div[role='option']").eq(0).contains('Matemaattisten tieteiden kandiohjelma') + cy.get("div[role='option']").eq(1).contains('Matematiikan ja tilastotieteen maisteriohjelma') + }) + }) }) }) }) diff --git a/services/frontend/package-lock.json b/services/frontend/package-lock.json index b2cee3ba6a..bf312c326c 100644 --- a/services/frontend/package-lock.json +++ b/services/frontend/package-lock.json @@ -6,6 +6,10 @@ "": { "name": "frontend", "dependencies": { + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", + "@mui/icons-material": "^6.1.6", + "@mui/material": "^6.1.6", "@reduxjs/toolkit": "^2.2.3", "@sentry/browser": "^8.33.1", "axios": "^0.28.1", @@ -39,10 +43,87 @@ "vite": "^5.2.10" } }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/runtime": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", - "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -50,6 +131,197 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", + "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.2.0", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.13.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", + "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.13.3", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", + "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/cache": "^11.13.0", + "@emotion/serialize": "^1.3.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", + "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz", + "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.0", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", + "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz", + "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -453,6 +725,323 @@ "react-dom": "^16.8.0 || ^17 || ^18" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.6.tgz", + "integrity": "sha512-nz1SlR9TdBYYPz4qKoNasMPRiGb4PaIHFkzLzhju0YVYS5QSuFF2+n7CsiHMIDcHv3piPu/xDWI53ruhOqvZwQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.6.tgz", + "integrity": "sha512-5r9urIL2lxXb/sPN3LFfFYEibsXJUb986HhhIeu1gOcte460pwdSiEhBSxkAuyT8Dj7jvu9MjqSBmSumQELo8A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^6.1.6", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.6.tgz", + "integrity": "sha512-1yvejiQ/601l5AK3uIdUlAVElyCxoqKnl7QA+2oFB/2qYPWfRwDgavW/MoywS5Y2gZEslcJKhe0s2F3IthgFgw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.1.6", + "@mui/system": "^6.1.6", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.1.6", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^18.3.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^6.1.6", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/@mui/private-theming": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.6.tgz", + "integrity": "sha512-ioAiFckaD/fJSnTrUMWgjl9HYBWt7ixCh7zZw7gDZ+Tae7NuprNV6QJK95EidDT7K0GetR2rU3kAeIR61Myttw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.1.6", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.6.tgz", + "integrity": "sha512-I+yS1cSuSvHnZDBO7e7VHxTWpj+R7XlSZvTC4lS/OIbUNJOMMSd3UDP6V2sfwzAdmdDNBi7NGCRv2SZ6O9hGDA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.1", + "@emotion/serialize": "^1.3.2", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.6.tgz", + "integrity": "sha512-qOf1VUE9wK8syiB0BBCp82oNBAVPYdj4Trh+G1s+L+ImYiKlubWhhqlnvWt3xqMevR+D2h1CXzA1vhX2FvA+VQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.1.6", + "@mui/styled-engine": "^6.1.6", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.1.6", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/system/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@mui/types": { + "version": "7.2.19", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.19.tgz", + "integrity": "sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.6.tgz", + "integrity": "sha512-sBS6D9mJECtELASLM+18WUcXF6RH3zNxBRFeyCRg8wad6NbyNrdxLuwK+Ikvc38sTZwBzAz691HmSofLqHd9sQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.19", + "@types/prop-types": "^15.7.13", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -1125,10 +1714,17 @@ "undici-types": "~6.13.0" } }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.12", @@ -1150,6 +1746,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/unist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", @@ -1195,6 +1800,21 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -1205,6 +1825,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -1284,10 +1913,33 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/csstype": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", - "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" }, "node_modules/debug": { "version": "4.3.5", @@ -1358,6 +2010,25 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -1397,6 +2068,18 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/estree-util-is-identifier-name": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", @@ -1430,6 +2113,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/follow-redirects": { "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", @@ -1476,6 +2165,36 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz", @@ -1563,6 +2282,22 @@ "url": "https://opencollective.com/immer" } }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/inline-style-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz", @@ -1593,6 +2328,27 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-decimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", @@ -1640,11 +2396,35 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/keyboard-key": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keyboard-key/-/keyboard-key-1.1.0.tgz", "integrity": "sha512-qkBzPTi3rlAKvX7k0/ub44sqOfXeLc/jcnGGmj5c7BJpU8eDrEVPyhCvNYAaoubbsLm9uGWwQJO1ytQK1a9/dQ==" }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -2330,6 +3110,18 @@ "node": ">=0.10.0" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-entities": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", @@ -2356,6 +3148,30 @@ "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==", "license": "MIT" }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, "node_modules/path-to-regexp": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", @@ -2365,11 +3181,19 @@ "isarray": "0.0.1" } }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "dev": true, "license": "ISC" }, "node_modules/postcss": { @@ -2611,6 +3435,22 @@ "react": ">=15" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", @@ -2669,6 +3509,32 @@ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz", "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==" }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/resolve-pathname": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", @@ -2755,6 +3621,15 @@ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -2809,6 +3684,24 @@ "inline-style-parser": "0.2.3" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/tiny-invariant": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", @@ -3103,6 +3996,15 @@ "node": ">=0.8" } }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/services/frontend/package.json b/services/frontend/package.json index af7f5f14da..3822569744 100644 --- a/services/frontend/package.json +++ b/services/frontend/package.json @@ -9,6 +9,10 @@ "tsc": "tsc" }, "dependencies": { + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", + "@mui/icons-material": "^6.1.6", + "@mui/material": "^6.1.6", "@reduxjs/toolkit": "^2.2.3", "@sentry/browser": "^8.33.1", "axios": "^0.28.1", diff --git a/services/frontend/src/components/App/index.jsx b/services/frontend/src/components/App/index.jsx index 63753d3f75..e7e9d4485a 100644 --- a/services/frontend/src/components/App/index.jsx +++ b/services/frontend/src/components/App/index.jsx @@ -1,9 +1,10 @@ +import { createTheme, ThemeProvider } from '@mui/material/styles' import * as Sentry from '@sentry/browser' import { useEffect } from 'react' import { initShibbolethPinger } from 'unfuck-spa-shibboleth-session' import { AccessDenied } from '@/components/AccessDenied' -import { NavigationBar } from '@/components/NavigationBar' +import { NavigationBar } from '@/components/material/NavigationBar' import { Routes } from '@/components/Routes' import { SegmentDimmer } from '@/components/SegmentDimmer' import { isProduction } from '@/conf' @@ -11,17 +12,21 @@ import { useGetAuthorizedUserQuery } from '@/redux/auth' import './app.css' const addUserDetailsToLoggers = ({ id, username, mockedBy }) => { - if (!isProduction || !id || !username) return + if (!isProduction || !id || !username) { + return + } Sentry.setUser({ id, username, mockedBy }) } +const theme = createTheme({}) + const Layout = ({ children }) => (
- - {children} + {children} +
) @@ -39,7 +44,9 @@ export const App = () => { addUserDetailsToLoggers({ id, username, mockedBy }) }, [id, username, mockedBy]) - if (error) return + if (error) { + return + } return {isLoading ? : } } diff --git a/services/frontend/src/components/NavigationBar/index.jsx b/services/frontend/src/components/NavigationBar/index.jsx deleted file mode 100644 index 169796a92d..0000000000 --- a/services/frontend/src/components/NavigationBar/index.jsx +++ /dev/null @@ -1,212 +0,0 @@ -import { Link, NavLink, useLocation } from 'react-router-dom' -import { Button, Dropdown, Label, Menu } from 'semantic-ui-react' - -import { checkUserAccess, getFullStudyProgrammeRights, isDefaultServiceProvider } from '@/common' -import { LanguagePicker } from '@/components/LanguagePicker' -import { adminerUrls, isDev, languageCenterViewEnabled } from '@/conf' -import { useGetAuthorizedUserQuery, useLogoutMutation, useShowAsUser } from '@/redux/auth' -import './navigationBar.css' - -const allNavigationItems = { - university: { key: 'university', label: 'University', path: '/university' }, - faculty: { key: 'faculties', label: 'Faculties', path: '/faculties' }, - populations: { - key: 'studyProgramme', - items: [ - { key: 'class', label: 'Class statistics', path: '/populations' }, - { key: 'overview', label: 'Overview', path: '/study-programme' }, - ], - label: 'Programmes', - }, - courseStatistics: { key: 'courseStatistics', label: 'Courses', path: '/coursestatistics' }, - students: { key: 'students', label: 'Students', path: '/students' }, - teachers: { key: 'teachers', label: 'Teachers', path: '/teachers', reqRights: ['teachers'] }, - users: { key: 'users', label: 'Users', path: '/users', reqRights: ['admin'] }, - studyGuidanceGroups: { - key: 'studyGuidanceGroups', - label: 'Guidance groups', - path: '/studyguidancegroups', - reqRights: ['studyGuidanceGroups'], - }, - customPopulations: { - key: 'customPopulation', - items: [ - { key: 'customSearch', label: 'Custom population', path: '/custompopulation' }, - { key: 'openUniSearch', label: 'Fetch open uni students by courses', path: '/openunipopulation' }, - { key: 'completedCoursesSearch', label: 'Completed courses of students', path: '/completedcoursessearch' }, - { key: 'languageCenterView', label: 'Language center view', path: '/languagecenterview' }, - { key: 'closeToGraduation', label: 'Students close to graduation', path: '/close-to-graduation' }, - ], - label: 'Special populations', - }, - updater: { key: 'updater', label: 'Updater', path: '/updater', reqRights: ['admin'] }, - feedback: { key: 'feedback', label: 'Give feedback', path: '/feedback' }, -} - -export const NavigationBar = () => { - const { isLoading, iamGroups, mockedBy, username, roles, isAdmin, programmeRights, fullAccessToStudentData } = - useGetAuthorizedUserQuery() - const fullStudyProgrammeRights = getFullStudyProgrammeRights(programmeRights) - const location = useLocation() - const showAsUser = useShowAsUser() - const [logout] = useLogoutMutation() - - const refreshNavigationRoutes = () => { - const visibleNavigationItems = {} - if (isLoading) { - return visibleNavigationItems - } - Object.keys(allNavigationItems).forEach(key => { - if (key === 'populations') { - if (!fullAccessToStudentData && programmeRights.length === 0) return - } - if (key === 'students') { - if ( - !checkUserAccess(['admin', 'fullSisuAccess', 'studyGuidanceGroups'], roles) && - fullStudyProgrammeRights.length === 0 - ) - return - } else if (key === 'courseStatistics') { - if ( - !checkUserAccess(['admin', 'fullSisuAccess', 'courseStatistics'], roles) && - fullStudyProgrammeRights.length === 0 - ) - return - } else if (key === 'faculty') { - if (!checkUserAccess(['admin', 'fullSisuAccess', 'facultyStatistics'], roles)) return - } else if (key === 'feedback') { - if (!isDefaultServiceProvider()) return - } - const { reqRights } = allNavigationItems[key] - if (!reqRights || reqRights.every(r => roles.includes(r) || (key === 'teachers' && isAdmin))) { - visibleNavigationItems[key] = allNavigationItems[key] - } - }) - return { ...visibleNavigationItems } - } - - const renderHome = () => ( - - -

oodikone

-
- {isDev && } -
- ) - - const showSearch = item => { - if (item.key === 'class' || item.key === 'overview') return true - if ( - checkUserAccess(['admin', 'openUniSearch'], roles) && - item.key === 'openUniSearch' && - isDefaultServiceProvider() - ) - return true - if ( - (checkUserAccess(['admin', 'fullSisuAccess', 'studyGuidanceGroups'], roles) || - fullStudyProgrammeRights.length > 0) && - item.key === 'customSearch' - ) - return true - if (item.key === 'completedCoursesSearch') return true - if ( - (checkUserAccess(['admin'], roles) || iamGroups.includes('grp-kielikeskus-esihenkilot')) && - item.key === 'languageCenterView' && - languageCenterViewEnabled - ) - return true - if (item.key === 'closeToGraduation' && checkUserAccess(['admin', 'fullSisuAccess', 'studyGuidanceGroups'], roles)) - return true - return false - } - - const visibleNavigationItems = refreshNavigationRoutes() - - const renderNavigationRoutes = () => - Object.values(visibleNavigationItems).map(({ items, key, label, path, tag }) => - items ? ( - location.pathname.includes(item.path))} - as={Dropdown} - data-cy={`navbar-${key}`} - key={`menu-item-drop-${key}`} - tabIndex="-1" - text={label} - > - - {items.map( - item => - showSearch(item) && ( - - {item.label} - - ) - )} - - - ) : ( - - {label} - {tag && ( -
- -
- )} -
- ) - ) - - const renderUserMenu = () => - isDev ? ( - - - {adminerUrls.map(({ url, text }) => ( - { - const win = window.open(url, '_blank') - win.focus() - }} - text={text} - /> - ))} - logout()} text="Logout" /> - - - ) : ( - logout()} tabIndex="-1"> - Logout - - ) - - const renderLanguagePicker = () => ( - - - - ) - - const renderStopMockingButton = () => ( - - - - ) - - return ( - - {renderHome()} - {!isLoading && renderNavigationRoutes()} - {!isLoading && renderUserMenu()} - {!isLoading && renderLanguagePicker()} - {!isLoading && mockedBy && renderStopMockingButton()} - - ) -} diff --git a/services/frontend/src/components/NavigationBar/navigationBar.css b/services/frontend/src/components/NavigationBar/navigationBar.css deleted file mode 100644 index 56e4b90297..0000000000 --- a/services/frontend/src/components/NavigationBar/navigationBar.css +++ /dev/null @@ -1,17 +0,0 @@ -.navBar { - display: grid !important; - grid-auto-flow: column !important; - border-radius: 0 !important; - border-top: none !important; -} - -.navBar::after { - content: none !important; -} - -.navBar .item { - display: flex; - justify-content: center; - align-items: center; - background-color: pink; -} diff --git a/services/frontend/src/components/material/NavigationBar/Logo.tsx b/services/frontend/src/components/material/NavigationBar/Logo.tsx new file mode 100644 index 0000000000..770e0948f9 --- /dev/null +++ b/services/frontend/src/components/material/NavigationBar/Logo.tsx @@ -0,0 +1,28 @@ +import { Chip, Stack, Typography } from '@mui/material' +import { Link } from 'react-router-dom' + +import { isDev } from '@/conf' + +export const Logo = () => { + return ( + + + oodikone + + {isDev && } + + ) +} diff --git a/services/frontend/src/components/material/NavigationBar/NavigationButton.tsx b/services/frontend/src/components/material/NavigationBar/NavigationButton.tsx new file mode 100644 index 0000000000..d8b63e5b15 --- /dev/null +++ b/services/frontend/src/components/material/NavigationBar/NavigationButton.tsx @@ -0,0 +1,118 @@ +import { ArrowDropDown } from '@mui/icons-material' +import { Button, Menu, MenuItem } from '@mui/material' +import { useState } from 'react' +import { Link, useLocation } from 'react-router-dom' + +import { checkUserAccess, getFullStudyProgrammeRights, isDefaultServiceProvider } from '@/common' +import { languageCenterViewEnabled } from '@/conf' +import { useGetAuthorizedUserQuery } from '@/redux/auth' +import { NavigationItem } from './navigationItems' + +export const NavigationButton = ({ item }: { item: NavigationItem }) => { + const { iamGroups, programmeRights, roles } = useGetAuthorizedUserQuery() + const fullStudyProgrammeRights = getFullStudyProgrammeRights(programmeRights) + const location = useLocation() + const [anchorEl, setAnchorEl] = useState(null) + + const { key, label, path, items } = item + + const showItem = (subItemKey: string) => { + if (['class', 'completedCoursesSearch', 'overview'].includes(subItemKey)) { + return true + } + + if ( + checkUserAccess(['admin', 'openUniSearch'], roles) && + subItemKey === 'openUniSearch' && + isDefaultServiceProvider() + ) { + return true + } + + if ( + (checkUserAccess(['admin', 'fullSisuAccess', 'studyGuidanceGroups'], roles) || + fullStudyProgrammeRights.length > 0) && + subItemKey === 'customSearch' + ) { + return true + } + + if ( + (checkUserAccess(['admin'], roles) || iamGroups.includes('grp-kielikeskus-esihenkilot')) && + subItemKey === 'languageCenterView' && + languageCenterViewEnabled + ) { + return true + } + + if ( + subItemKey === 'closeToGraduation' && + checkUserAccess(['admin', 'fullSisuAccess', 'studyGuidanceGroups'], roles) + ) { + return true + } + + if (['users', 'updater'].includes(subItemKey) && checkUserAccess(['admin'], roles)) { + return true + } + + return false + } + + const isActivePath = (mainPath: string | undefined, subPaths: (string | undefined)[] = []) => { + const allPaths = [mainPath, ...subPaths].filter(Boolean) + return allPaths.some(currentPath => location.pathname.includes(currentPath!)) + } + + const subItemPaths = items ? items.map(subItem => subItem.path) : [] + const isActive = isActivePath(path, subItemPaths) + + const buttonStyle = { + color: 'inherit', + fontWeight: isActive ? 'bold' : 'normal', + textTransform: 'none', + whiteSpace: 'nowrap', + '&:hover': { + color: 'inherit', + textDecoration: 'underline', + }, + } + + if (items) { + return ( + <> + + setAnchorEl(null)} open={Boolean(anchorEl)}> + {items.map( + subItem => + showItem(subItem.key) && ( + setAnchorEl(null)} + selected={location.pathname.includes(subItem.path)} + to={subItem.path} + > + {subItem.label} + + ) + )} + + + ) + } + + return ( + + ) +} diff --git a/services/frontend/src/components/material/NavigationBar/NavigationDivider.tsx b/services/frontend/src/components/material/NavigationBar/NavigationDivider.tsx new file mode 100644 index 0000000000..40dece6e0d --- /dev/null +++ b/services/frontend/src/components/material/NavigationBar/NavigationDivider.tsx @@ -0,0 +1,5 @@ +import { Divider } from '@mui/material' + +export const NavigationDivider = () => { + return +} diff --git a/services/frontend/src/components/material/NavigationBar/UserButton/LogOutButton.jsx b/services/frontend/src/components/material/NavigationBar/UserButton/LogOutButton.jsx new file mode 100644 index 0000000000..104252b33e --- /dev/null +++ b/services/frontend/src/components/material/NavigationBar/UserButton/LogOutButton.jsx @@ -0,0 +1,18 @@ +import { Logout } from '@mui/icons-material' +import { ListItemIcon, MenuItem, Typography } from '@mui/material' + +import { isDev } from '@/conf' +import { useLogoutMutation } from '@/redux/auth' + +export const LogOutButton = () => { + const [logout] = useLogoutMutation() + + return ( + logout()}> + + + + Log out + + ) +} diff --git a/services/frontend/src/components/material/NavigationBar/UserButton/StopMockingButton.tsx b/services/frontend/src/components/material/NavigationBar/UserButton/StopMockingButton.tsx new file mode 100644 index 0000000000..349de29a33 --- /dev/null +++ b/services/frontend/src/components/material/NavigationBar/UserButton/StopMockingButton.tsx @@ -0,0 +1,17 @@ +import { ExitToApp } from '@mui/icons-material' +import { ListItemIcon, MenuItem, Typography } from '@mui/material' + +import { useShowAsUser } from '@/redux/auth' + +export const StopMockingButton = () => { + const showAsUser = useShowAsUser() + + return ( + showAsUser(null)}> + + + + Stop mocking + + ) +} diff --git a/services/frontend/src/components/material/NavigationBar/UserButton/index.tsx b/services/frontend/src/components/material/NavigationBar/UserButton/index.tsx new file mode 100644 index 0000000000..13cafeaa23 --- /dev/null +++ b/services/frontend/src/components/material/NavigationBar/UserButton/index.tsx @@ -0,0 +1,75 @@ +import { AccountCircle, Check, Language } from '@mui/icons-material' +import { Box, Divider, IconButton, ListItemIcon, Menu, MenuItem, Typography } from '@mui/material' +import { useState } from 'react' + +import { useLanguage } from '@/components/LanguagePicker/useLanguage' +import { useGetAuthorizedUserQuery } from '@/redux/auth' +import { LANGUAGE_CODES, LANGUAGE_TEXTS } from '@/shared/language' +import { LogOutButton } from './LogOutButton' +import { StopMockingButton } from './StopMockingButton' + +export const UserButton = () => { + const { language, setLanguage } = useLanguage() + const { isLoading, mockedBy, username } = useGetAuthorizedUserQuery() + const [anchorEl, setAnchorEl] = useState(null) + + const currentLanguage: string = language as unknown as string // TODO: Fix the type in the origin + + const languageOptions = LANGUAGE_CODES.map(code => ({ + key: code, + text: LANGUAGE_TEXTS[code], + value: code, + })) + + return ( + + setAnchorEl(event.currentTarget)} + sx={{ p: 0 }} + > + + + setAnchorEl(null)} + open={Boolean(anchorEl)} + sx={{ mt: '45px' }} + transformOrigin={{ + horizontal: 'right', + vertical: 'top', + }} + > + {!isLoading && username && ( + + + {mockedBy ? 'Mocking' : 'Logged in'} as {username} + + + )} + + + + + + Language + + {languageOptions.map(({ key, text, value }) => ( + setLanguage(value)} selected={currentLanguage === value}> + {currentLanguage === value ? : null} + {text} + + ))} + + {mockedBy ? : } + + + ) +} diff --git a/services/frontend/src/components/material/NavigationBar/index.tsx b/services/frontend/src/components/material/NavigationBar/index.tsx new file mode 100644 index 0000000000..ea67b1951d --- /dev/null +++ b/services/frontend/src/components/material/NavigationBar/index.tsx @@ -0,0 +1,80 @@ +import { AppBar, Box, Container, Toolbar } from '@mui/material' +import { Fragment } from 'react' + +import { checkUserAccess, getFullStudyProgrammeRights, isDefaultServiceProvider } from '@/common' +import { useGetAuthorizedUserQuery } from '@/redux/auth' +import { Logo } from './Logo' +import { NavigationButton } from './NavigationButton' +import { NavigationDivider } from './NavigationDivider' +import { NavigationItem, navigationItems } from './navigationItems' +import { UserButton } from './UserButton' + +export const NavigationBar = () => { + const { fullAccessToStudentData, isAdmin, isLoading, programmeRights, roles } = useGetAuthorizedUserQuery() + const fullStudyProgrammeRights = getFullStudyProgrammeRights(programmeRights) + + const getVisibleNavigationItems = () => { + const visibleNavigationItems: Record = {} + if (isLoading) { + return visibleNavigationItems + } + Object.keys(navigationItems).forEach(key => { + if (key === 'populations') { + if (!fullAccessToStudentData && programmeRights.length === 0) return + } + if (key === 'students') { + if ( + !checkUserAccess(['admin', 'fullSisuAccess', 'studyGuidanceGroups'], roles) && + fullStudyProgrammeRights.length === 0 + ) { + return + } + } else if (key === 'courseStatistics') { + if ( + !checkUserAccess(['admin', 'fullSisuAccess', 'courseStatistics'], roles) && + fullStudyProgrammeRights.length === 0 + ) { + return + } + } else if (key === 'faculty') { + if (!checkUserAccess(['admin', 'fullSisuAccess', 'facultyStatistics'], roles)) return + } else if (key === 'feedback') { + if (!isDefaultServiceProvider()) return + } else if (key === 'admin') { + if (!isAdmin) return + } + const { reqRights } = navigationItems[key] + if (!reqRights || reqRights.every(r => roles.includes(r) || (key === 'teachers' && isAdmin))) { + visibleNavigationItems[key] = navigationItems[key] + } + }) + return { ...visibleNavigationItems } + } + + const visibleNavigationItems = getVisibleNavigationItems() + + return ( + + + + + {!isLoading && ( + <> + + + {Object.values(visibleNavigationItems).map(item => ( + + {['feedback', 'admin'].includes(item.key) && } + + + ))} + + + + + )} + + + + ) +} diff --git a/services/frontend/src/components/material/NavigationBar/navigationItems.ts b/services/frontend/src/components/material/NavigationBar/navigationItems.ts new file mode 100644 index 0000000000..0007b760cc --- /dev/null +++ b/services/frontend/src/components/material/NavigationBar/navigationItems.ts @@ -0,0 +1,49 @@ +export type NavigationItem = { + key: string + label: string + path?: string + reqRights?: string[] + items?: NavigationItem[] +} + +export const navigationItems: Record = { + university: { key: 'university', label: 'University', path: '/university' }, + faculty: { key: 'faculties', label: 'Faculties', path: '/faculties' }, + populations: { + key: 'studyProgramme', + items: [ + { key: 'class', label: 'Class statistics', path: '/populations' }, + { key: 'overview', label: 'Overview', path: '/study-programme' }, + ], + label: 'Programmes', + }, + courseStatistics: { key: 'courseStatistics', label: 'Courses', path: '/coursestatistics' }, + students: { key: 'students', label: 'Students', path: '/students' }, + teachers: { key: 'teachers', label: 'Teachers', path: '/teachers', reqRights: ['teachers'] }, + studyGuidanceGroups: { + key: 'studyGuidanceGroups', + label: 'Guidance groups', + path: '/studyguidancegroups', + reqRights: ['studyGuidanceGroups'], + }, + customPopulations: { + key: 'customPopulation', + items: [ + { key: 'customSearch', label: 'Custom population', path: '/custompopulation' }, + { key: 'openUniSearch', label: 'Fetch open uni students by courses', path: '/openunipopulation' }, + { key: 'completedCoursesSearch', label: 'Completed courses of students', path: '/completedcoursessearch' }, + { key: 'languageCenterView', label: 'Language center view', path: '/languagecenterview' }, + { key: 'closeToGraduation', label: 'Students close to graduation', path: '/close-to-graduation' }, + ], + label: 'Special populations', + }, + feedback: { key: 'feedback', label: 'Feedback', path: '/feedback' }, + admin: { + key: 'admin', + items: [ + { key: 'users', label: 'Users', path: '/users', reqRights: ['admin'] }, + { key: 'updater', label: 'Updater', path: '/updater', reqRights: ['admin'] }, + ], + label: 'Admin', + }, +} diff --git a/services/shared/language.ts b/services/shared/language.ts index 8f61d41731..146962890d 100644 --- a/services/shared/language.ts +++ b/services/shared/language.ts @@ -1,2 +1,3 @@ export const DEFAULT_LANG = 'fi' export const LANGUAGE_CODES = ['fi', 'en', 'sv'] as const +export const LANGUAGE_TEXTS = { fi: 'suomi', en: 'English', sv: 'svenska' } as const