From 132713c3686cda854cdb5bd6aa56762186bc0e6b Mon Sep 17 00:00:00 2001 From: jackson Date: Thu, 25 Jul 2024 11:19:16 +0100 Subject: [PATCH] Navigation and Theme --- package-lock.json | 15 +- package.json | 6 +- .../__tests__/FlightFlagger.test.tsx | 508 +++++++++--------- .../{SiteFrame => Header}/Crest.tsx | 2 +- src/components/Header/DynamicIcon.tsx | 14 + src/components/Header/Header.stories.tsx | 67 +++ src/components/Header/Header.tsx | 176 ++++++ src/components/Header/PortSelector.tsx | 77 +++ .../Header/__tests__/Header.test.tsx | 136 +++++ .../Header/__tests__/PortSelector.test.tsx | 44 ++ src/components/Header/index.ts | 2 + src/components/PortSelector/PortMap.css | 3 - src/components/PortSelector/PortMap.tsx | 437 --------------- src/components/PortSelector/PortPanel.tsx | 128 ----- src/components/PortSelector/PortSelector.tsx | 98 ---- src/components/PortSelector/index.ts | 2 - src/components/SiteFrame/Navigation.tsx | 93 ---- .../SiteFrame/SiteFrame.stories.tsx | 16 - src/components/SiteFrame/SiteFrame.tsx | 281 ---------- src/components/SiteFrame/UserMenu.tsx | 61 --- src/components/SiteFrame/index.ts | 2 - src/components/index.ts | 3 + src/drt-theme.ts | 15 + src/index.ts | 1 + 24 files changed, 805 insertions(+), 1382 deletions(-) rename src/components/{SiteFrame => Header}/Crest.tsx (99%) create mode 100644 src/components/Header/DynamicIcon.tsx create mode 100644 src/components/Header/Header.stories.tsx create mode 100644 src/components/Header/Header.tsx create mode 100644 src/components/Header/PortSelector.tsx create mode 100644 src/components/Header/__tests__/Header.test.tsx create mode 100644 src/components/Header/__tests__/PortSelector.test.tsx create mode 100644 src/components/Header/index.ts delete mode 100644 src/components/PortSelector/PortMap.css delete mode 100644 src/components/PortSelector/PortMap.tsx delete mode 100644 src/components/PortSelector/PortPanel.tsx delete mode 100644 src/components/PortSelector/PortSelector.tsx delete mode 100644 src/components/PortSelector/index.ts delete mode 100644 src/components/SiteFrame/Navigation.tsx delete mode 100644 src/components/SiteFrame/SiteFrame.stories.tsx delete mode 100644 src/components/SiteFrame/SiteFrame.tsx delete mode 100644 src/components/SiteFrame/UserMenu.tsx delete mode 100644 src/components/SiteFrame/index.ts diff --git a/package-lock.json b/package-lock.json index d085841..36dd4f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,10 +21,9 @@ "@storybook/icons": "^1.2.9", "@svgr/webpack": "^8.1.0", "css-loader": "^7.1.2", + "css-mediaquery": "^0.1.2", "install-peers": "^1.0.4", "postcss": "^8.4.38", - "react": "18.2.0", - "react-dom": "18.2.0", "sass-loader": "^14.2.1", "style-loader": "^4.0.0" }, @@ -58,6 +57,7 @@ "@svgr/rollup": "^8.1.0", "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^14.0.0", + "@types/css-mediaquery": "^0.1.4", "@types/jest": "^29.5.12", "@types/react": "^18.0.33", "@typescript-eslint/eslint-plugin": "^5.57.1", @@ -6082,6 +6082,12 @@ "@types/node": "*" } }, + "node_modules/@types/css-mediaquery": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@types/css-mediaquery/-/css-mediaquery-0.1.4.tgz", + "integrity": "sha512-DZyHAz716ZUctpqkUU2COwUoZ4gI6mZK2Q1oIz/fvNS6XHVpKSJgDnE7vRxZUBn9vjJHDVelCVW0dkshKOLFsA==", + "dev": true + }, "node_modules/@types/detect-port": { "version": "1.3.5", "dev": true, @@ -8426,6 +8432,11 @@ } } }, + "node_modules/css-mediaquery": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", + "integrity": "sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==" + }, "node_modules/css-select": { "version": "4.3.0", "dev": true, diff --git a/package.json b/package.json index abf522e..895cc10 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test:generate-output": "jest --json --outputFile=.jest-test-results.json || true", "build": "rimraf dist && rollup -c", "dev": "rollup -c -w", - "test": "jest -ci", + "test": "DEBUG_PRINT_LIMIT=100000 jest -ci --verbose", "test-ci": "jest -ci", "lint": "eslint src", "prebuild:storybook": "npm run test:generate-output", @@ -54,6 +54,7 @@ "@svgr/rollup": "^8.1.0", "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^14.0.0", + "@types/css-mediaquery": "^0.1.4", "@types/jest": "^29.5.12", "@types/react": "^18.0.33", "@typescript-eslint/eslint-plugin": "^5.57.1", @@ -101,10 +102,9 @@ "@storybook/icons": "^1.2.9", "@svgr/webpack": "^8.1.0", "css-loader": "^7.1.2", + "css-mediaquery": "^0.1.2", "install-peers": "^1.0.4", "postcss": "^8.4.38", - "react": "18.2.0", - "react-dom": "18.2.0", "sass-loader": "^14.2.1", "style-loader": "^4.0.0" } diff --git a/src/components/FlightFlagger/__tests__/FlightFlagger.test.tsx b/src/components/FlightFlagger/__tests__/FlightFlagger.test.tsx index 0c831ed..700f3a9 100644 --- a/src/components/FlightFlagger/__tests__/FlightFlagger.test.tsx +++ b/src/components/FlightFlagger/__tests__/FlightFlagger.test.tsx @@ -15,258 +15,256 @@ const nationalities = [ ]; const ageGroups = ['0-9', '10-24', '24+']; -describe("Flight Flagger", () => { - test("displays all flight results", async () => { - - render( console.log(payload)}/>); - - const tableRows = await screen.getByTestId('flight-flagger-results-table').querySelectorAll('tbody tr'); - expect(tableRows).toHaveLength(ExampleFlights.length) - }) - - test("hides and shows non-highlighted flights correctly", async () => { - render( console.log(payload)}/>); - - fireEvent.click(screen.getByTestId('show-highlighted-only')); - - let tableRows = await screen.getByTestId('flight-flagger-results-table').querySelectorAll('tbody tr'); - expect(tableRows).toHaveLength(1) - - fireEvent.click(screen.getByTestId('show-all-flights')); - - tableRows = await screen.getByTestId('flight-flagger-results-table').querySelectorAll('tbody tr'); - expect(tableRows).toHaveLength(ExampleFlights.length) - }) - - test("accepts an initial state", async () => { - render( console.log(payload)}/>); - - fireEvent.click(screen.getByTestId('show-highlighted-only')); - - let tableRows = await screen.getByTestId('flight-flagger-results-table').querySelectorAll('tbody tr'); - expect(tableRows).toHaveLength(1) - - fireEvent.click(screen.getByTestId('show-all-flights')); - - tableRows = await screen.getByTestId('flight-flagger-results-table').querySelectorAll('tbody tr'); - expect(tableRows).toHaveLength(ExampleFlights.length) - }) - - test("displays the circular spinner and hides results when loading prop is true", async () => { - - render( console.log(payload)}/>); - - const table = await screen.queryByTestId('flight-flagger-results-table') - const loadingSpinner = await screen.queryByTestId('flight-flagger-loading-spinner') - expect(table).toBeNull() - expect(loadingSpinner).toBeTruthy() - }) - - test("hides and shows the search filters", async () => { - render( console.log(payload)}/>); - - let filters = await screen.queryByTestId('flight-flagger-filters') - await waitFor(() => { - expect(filters).toHaveStyle(`height: 0px`) - }); - - await fireEvent.click(screen.getByTestId('show-filters')); - filters = await screen.queryByTestId('flight-flagger-filters') - await waitFor(() => { - expect(filters).toHaveStyle(`height: auto`) - }); - - fireEvent.click(screen.getByTestId('show-filters')); - filters = await screen.queryByTestId('flight-flagger-filters') - await waitFor(() => { - expect(filters).toHaveStyle(`height: 0px`) - }); - }) - - test("calls the submitCallback with the correct filters", async () => { - - const callBack = jest.fn(); - - const expectedPayload = { - selectedNationalities: [{"code": "GBR", "name": "Great Britain"}], - selectedAgeGroups: ['0-9'], - showTransitPaxNumber: false, - showNumberOfVisaNationals: true, - requireAllSelected: true, - flightNumber: 'BA1234' - } - - render(); - - await fireEvent.click(screen.getByTestId('show-filters')); - const filters = await screen.queryByTestId('flight-flagger-filters') - await waitFor(() => { - expect(filters).toHaveStyle(`height: auto`) - }); - - const flightNumber = screen.getByLabelText('Enter flight details') - fireEvent.change(flightNumber, {target: {value: 'BA1234'}}) - - const nationalitiesAutocomplete = screen.getByTestId('nationalities-autocomplete'); - const nationalitiesInput = within(nationalitiesAutocomplete).getByRole('combobox') - nationalitiesAutocomplete.focus() - - fireEvent.change(nationalitiesInput, {target: {value: 'G'}}) - fireEvent.keyDown(nationalitiesAutocomplete, {key: 'ArrowDown'}) - fireEvent.keyDown(nationalitiesAutocomplete, {key: 'Enter'}) - - const ageAutocomplete = screen.getByTestId('age-autocomplete'); - const ageInput = within(ageAutocomplete).getByRole('combobox') - ageAutocomplete.focus() - - fireEvent.change(ageInput, {target: {value: 'G'}}) - fireEvent.keyDown(ageAutocomplete, {key: 'ArrowDown'}) - fireEvent.keyDown(ageAutocomplete, {key: 'Enter'}) - - await fireEvent.click(screen.getByTestId('show-visa-nationals-check')); - await fireEvent.click(screen.getByTestId('require-all-selected-check')); - - fireEvent.click(screen.getByTestId('flight-flagger-filter-submit')); - expect(callBack).toHaveBeenCalledWith(expectedPayload) - }) - - test("calls the submitCallback when the user hits enter on the flight number input", async () => { - - const callBack = jest.fn(); - - const expectedPayload = { - selectedNationalities: [], - selectedAgeGroups: [], - showTransitPaxNumber: false, - showNumberOfVisaNationals: false, - requireAllSelected: false, - flightNumber: 'BA1234' - } - - render(); - - await fireEvent.click(screen.getByTestId('show-filters')); - const filters = await screen.queryByTestId('flight-flagger-filters') - await waitFor(() => { - expect(filters).toHaveStyle(`height: auto`) - }); - - const flightNumber = screen.getByLabelText('Enter flight details') - fireEvent.change(flightNumber, {target: {value: 'BA1234'}}) - fireEvent.keyDown(flightNumber, {key: 'Enter'}) - - expect(callBack).toHaveBeenCalledWith(expectedPayload) - }) - - test("calls the submitCallback when the user clears selected filters", async () => { - - const callBack = jest.fn(); - - const expectedPayload = { - selectedNationalities: [], - selectedAgeGroups: [], - showTransitPaxNumber: false, - showNumberOfVisaNationals: false, - requireAllSelected: false, - flightNumber: 'BA1234' - } - - render(); - - await fireEvent.click(screen.getByTestId('show-filters')); - const filters = await screen.queryByTestId('flight-flagger-filters') - await waitFor(() => { - expect(filters).toHaveStyle(`height: auto`) - }); - - const flightNumber = screen.getByLabelText('Enter flight details') - fireEvent.change(flightNumber, {target: {value: 'BA1234'}}) - fireEvent.keyDown(flightNumber, {key: 'Enter'}) - - const nationalitiesAutocomplete = screen.getByTestId('nationalities-autocomplete'); - const nationalitiesInput = within(nationalitiesAutocomplete).getByRole('combobox') - nationalitiesAutocomplete.focus() - - fireEvent.change(nationalitiesInput, {target: {value: 'G'}}) - fireEvent.keyDown(nationalitiesAutocomplete, {key: 'ArrowDown'}) - fireEvent.keyDown(nationalitiesAutocomplete, {key: 'Enter'}) - - const ageAutocomplete = screen.getByTestId('age-autocomplete'); - const ageInput = within(ageAutocomplete).getByRole('combobox') - ageAutocomplete.focus() - - fireEvent.change(ageInput, {target: {value: 'G'}}) - fireEvent.keyDown(ageAutocomplete, {key: 'ArrowDown'}) - fireEvent.keyDown(ageAutocomplete, {key: 'Enter'}) - - await fireEvent.click(screen.getByTestId('show-visa-nationals-check')); - await fireEvent.click(screen.getByTestId('require-all-selected-check')); - - await fireEvent.click(screen.getByTestId('flight-flagger-clear-filters')); - - await fireEvent.click(screen.getByTestId('flight-flagger-filter-submit')); - expect(callBack).toHaveBeenCalledWith(expectedPayload) - }) - - test("renders the mobile view on small devices", async () => { - window.matchMedia = jest.fn().mockImplementation(query => ({ - matches: query !== '(max-width: 400px)', - media: '', - onchange: null, - addListener: jest.fn(), - removeListener: jest.fn() - })); - - render( console.log(payload)}/>); - - const desktopResults = await screen.queryByTestId('flight-flagger-desktop-results') - const mobileResults = await screen.queryByTestId('flight-flagger-mobile-results') - expect(desktopResults).toBeNull() - expect(mobileResults).toBeTruthy() - }) -}); +test("displays all flight results", async () => { + + render( console.log(payload)} />); + + const tableRows = await screen.getByTestId('flight-flagger-results-table').querySelectorAll('tbody tr'); + expect(tableRows).toHaveLength(ExampleFlights.length) +}) + +test("hides and shows non-highlighted flights correctly", async () => { + render( console.log(payload)} />); + + fireEvent.click(screen.getByTestId('show-highlighted-only')); + + let tableRows = await screen.getByTestId('flight-flagger-results-table').querySelectorAll('tbody tr'); + expect(tableRows).toHaveLength(1) + + fireEvent.click(screen.getByTestId('show-all-flights')); + + tableRows = await screen.getByTestId('flight-flagger-results-table').querySelectorAll('tbody tr'); + expect(tableRows).toHaveLength(ExampleFlights.length) +}) + +test("accepts an initial state", async () => { + render( console.log(payload)} />); + + fireEvent.click(screen.getByTestId('show-highlighted-only')); + + let tableRows = await screen.getByTestId('flight-flagger-results-table').querySelectorAll('tbody tr'); + expect(tableRows).toHaveLength(1) + + fireEvent.click(screen.getByTestId('show-all-flights')); + + tableRows = await screen.getByTestId('flight-flagger-results-table').querySelectorAll('tbody tr'); + expect(tableRows).toHaveLength(ExampleFlights.length) +}) + +test("displays the circular spinner and hides results when loading prop is true", async () => { + + render( console.log(payload)} />); + + const table = await screen.queryByTestId('flight-flagger-results-table') + const loadingSpinner = await screen.queryByTestId('flight-flagger-loading-spinner') + expect(table).toBeNull() + expect(loadingSpinner).toBeTruthy() +}) + +test("hides and shows the search filters", async () => { + render( console.log(payload) } />); + + let filters = await screen.queryByTestId('flight-flagger-filters') + await waitFor(() => { + expect(filters).toHaveStyle(`height: 0px`) + }); + + await fireEvent.click(screen.getByTestId('show-filters')); + filters = await screen.queryByTestId('flight-flagger-filters') + await waitFor(() => { + expect(filters).toHaveStyle(`height: auto`) + }); + + fireEvent.click(screen.getByTestId('show-filters')); + filters = await screen.queryByTestId('flight-flagger-filters') + await waitFor(() => { + expect(filters).toHaveStyle(`height: 0px`) + }); +}) + +test("calls the submitCallback with the correct filters", async () => { + + const callBack = jest.fn(); + + const expectedPayload = { + selectedNationalities: ['GBR'], + selectedAgeGroups: ['0-9'], + showTransitPaxNumber: false, + showNumberOfVisaNationals: true, + requireAllSelected: true, + flightNumber: 'BA1234' + } + + render(); + + await fireEvent.click(screen.getByTestId('show-filters')); + const filters = await screen.queryByTestId('flight-flagger-filters') + await waitFor(() => { + expect(filters).toHaveStyle(`height: auto`) + }); + + const flightNumber = screen.getByLabelText('Enter flight details') + fireEvent.change(flightNumber, {target: {value: 'BA1234'}}) + + const nationalitiesAutocomplete = screen.getByTestId('nationalities-autocomplete'); + const nationalitiesInput = within(nationalitiesAutocomplete).getByRole('combobox') + nationalitiesAutocomplete.focus() + + fireEvent.change(nationalitiesInput, { target: { value: 'G' } }) + fireEvent.keyDown(nationalitiesAutocomplete, { key: 'ArrowDown' }) + fireEvent.keyDown(nationalitiesAutocomplete, { key: 'Enter' }) + + const ageAutocomplete = screen.getByTestId('age-autocomplete'); + const ageInput = within(ageAutocomplete).getByRole('combobox') + ageAutocomplete.focus() + + fireEvent.change(ageInput, { target: { value: 'G' } }) + fireEvent.keyDown(ageAutocomplete, { key: 'ArrowDown' }) + fireEvent.keyDown(ageAutocomplete, { key: 'Enter' }) + + await fireEvent.click(screen.getByTestId('show-visa-nationals-check')); + await fireEvent.click(screen.getByTestId('require-all-selected-check')); + + fireEvent.click(screen.getByTestId('flight-flagger-filter-submit')); + expect(callBack).toHaveBeenCalledWith(expectedPayload) +}) + +test("calls the submitCallback when the user hits enter on the flight number input", async () => { + + const callBack = jest.fn(); + + const expectedPayload = { + selectedNationalities: [], + selectedAgeGroups: [], + showTransitPaxNumber: false, + showNumberOfVisaNationals: false, + requireAllSelected: false, + flightNumber: 'BA1234' + } + + render(); + + await fireEvent.click(screen.getByTestId('show-filters')); + const filters = await screen.queryByTestId('flight-flagger-filters') + await waitFor(() => { + expect(filters).toHaveStyle(`height: auto`) + }); + + const flightNumber = screen.getByLabelText('Enter flight details') + fireEvent.change(flightNumber, {target: {value: 'BA1234'}}) + fireEvent.keyDown(flightNumber, { key: 'Enter' }) + + expect(callBack).toHaveBeenCalledWith(expectedPayload) +}) + +test("calls the submitCallback when the user clears selected filters", async () => { + + const callBack = jest.fn(); + + const expectedPayload = { + selectedNationalities: [], + selectedAgeGroups: [], + showTransitPaxNumber: false, + showNumberOfVisaNationals: false, + requireAllSelected: false, + flightNumber: 'BA1234' + } + + render(); + + await fireEvent.click(screen.getByTestId('show-filters')); + const filters = await screen.queryByTestId('flight-flagger-filters') + await waitFor(() => { + expect(filters).toHaveStyle(`height: auto`) + }); + + const flightNumber = screen.getByLabelText('Enter flight details') + fireEvent.change(flightNumber, {target: {value: 'BA1234'}}) + fireEvent.keyDown(flightNumber, { key: 'Enter' }) + + const nationalitiesAutocomplete = screen.getByTestId('nationalities-autocomplete'); + const nationalitiesInput = within(nationalitiesAutocomplete).getByRole('combobox') + nationalitiesAutocomplete.focus() + + fireEvent.change(nationalitiesInput, { target: { value: 'G' } }) + fireEvent.keyDown(nationalitiesAutocomplete, { key: 'ArrowDown' }) + fireEvent.keyDown(nationalitiesAutocomplete, { key: 'Enter' }) + + const ageAutocomplete = screen.getByTestId('age-autocomplete'); + const ageInput = within(ageAutocomplete).getByRole('combobox') + ageAutocomplete.focus() + + fireEvent.change(ageInput, { target: { value: 'G' } }) + fireEvent.keyDown(ageAutocomplete, { key: 'ArrowDown' }) + fireEvent.keyDown(ageAutocomplete, { key: 'Enter' }) + + await fireEvent.click(screen.getByTestId('show-visa-nationals-check')); + await fireEvent.click(screen.getByTestId('require-all-selected-check')); + + await fireEvent.click(screen.getByTestId('flight-flagger-clear-filters')); + + await fireEvent.click(screen.getByTestId('flight-flagger-filter-submit')); + expect(callBack).toHaveBeenCalledWith(expectedPayload) +}) + +test("renders the mobile view on small devices", async () => { + window.matchMedia = jest.fn().mockImplementation(query => ({ + matches: query !== '(max-width: 400px)', + media: '', + onchange: null, + addListener: jest.fn(), + removeListener: jest.fn() + })); + + render( console.log(payload)} />); + + const desktopResults = await screen.queryByTestId('flight-flagger-desktop-results') + const mobileResults = await screen.queryByTestId('flight-flagger-mobile-results') + expect(desktopResults).toBeNull() + expect(mobileResults).toBeTruthy() +}) diff --git a/src/components/SiteFrame/Crest.tsx b/src/components/Header/Crest.tsx similarity index 99% rename from src/components/SiteFrame/Crest.tsx rename to src/components/Header/Crest.tsx index 47853b6..53eb68a 100644 --- a/src/components/SiteFrame/Crest.tsx +++ b/src/components/Header/Crest.tsx @@ -1,7 +1,7 @@ import React from "react"; import { useTheme } from "@mui/material"; -export const Crest = ({}) => { +export const Crest = () => { const theme = useTheme(); const crestColor = theme.palette.mode == 'light' ? theme.palette.common.black : theme.palette.common.white; diff --git a/src/components/Header/DynamicIcon.tsx b/src/components/Header/DynamicIcon.tsx new file mode 100644 index 0000000..b67f188 --- /dev/null +++ b/src/components/Header/DynamicIcon.tsx @@ -0,0 +1,14 @@ +import React, { FC } from 'react' +import * as Icons from '@mui/icons-material' + +export type IconNames = keyof typeof Icons +export type IconProps = { + iconName: IconNames +} + +export const DynamicIcon: FC = ({ + iconName, +}) => { + const Icon = Icons[iconName] + return +} diff --git a/src/components/Header/Header.stories.tsx b/src/components/Header/Header.stories.tsx new file mode 100644 index 0000000..9b11bbc --- /dev/null +++ b/src/components/Header/Header.stories.tsx @@ -0,0 +1,67 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { default as HeaderComponent } from "."; + +const meta: Meta = { + title: "DRT Components/Header", + component: HeaderComponent, +}; + +export default meta; +type Story = StoryObj; + +export const Header: Story = { + args: { + user: { + roles: [ + 'manage-users', + 'download-manager', + 'health-checks:edit', + ] + }, + portMenuItems: [ + { label: 'National Dashboard', link: '/regional-dashboard' }, + { label: 'CWL (Cardiff)', link: '/cwi' } + ], + adminMenuItems: [ + { label: 'Home', link: '/', roles: []}, + { label: 'Access requests', link: '/access-requests', roles: ['manage-users']}, + { label: 'Alert notices', link: '/alerts', roles: ['manage-users']}, + { label: 'Drop-in sessions', link: '/drop-in-sessions', roles: ['manage-users']}, + { label: 'Download Manager', link: '/download', roles: ['download-manager']}, + { label: 'Export Config', link: '/export-config', roles: ['manage-users']}, + { label: 'Feature guides', link: '/feature-guides', roles: ['manage-users']}, + { label: 'Health checks', link: '/health-checks', roles: ['health-checks:edit']}, + { label: 'Health check pauses', link: '/health-check-pauses', roles: ['health-checks:edit']}, + { label: 'Feedback', link: '/user-feedback', roles: ['manage-users']}, + { label: 'Users', link: '/users', roles: ['manage-users']}, + ], + leftMenuItems: [ + { + label: 'Port config', + link: '/port-config', + icon: 'Settings', + }, + { + label: 'Feed', + link: '/feed', + icon: 'Equalizer', + } + ], + rightMenuItems: [ + { + label: "What's new", + link: '/whats-new', + icon: 'Article', + }, + { + label: 'Training', + link: '/training', + icon: 'MenuBook', + } + ], + routingFunction: (string) => console.log(string), + logoutLink: () => {}, + }, + parameters: {}, +}; diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx new file mode 100644 index 0000000..970eb4c --- /dev/null +++ b/src/components/Header/Header.tsx @@ -0,0 +1,176 @@ +import React from "react"; +import { Box, Button, AppBar, Typography, Grid, Menu, MenuItem, Link } from "@mui/material"; +import ManageAccountsIcon from '@mui/icons-material/ManageAccounts'; +import LogoutIcon from '@mui/icons-material/Logout'; +import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; +import { Crest } from './Crest'; +import { DynamicIcon, IconNames } from './DynamicIcon'; +import PortSelector from "./PortSelector"; + +export type MenuItem = { + label: string, + link: string, + roles?: string[], + icon?: IconNames | string, +} + +export interface IHeader { + user: { + roles: string[] + }, + config: any, + adminMenuItems: MenuItem[], + leftMenuItems?: MenuItem[], + rightMenuItems?: MenuItem[], + portMenuItems: MenuItem[], + routingFunction: (route:string) => void, + logoutLink: () => void, +} + +const linkStyles = { + textTransform: 'none', + color: '#000', +} + +const Header = ({user, config, adminMenuItems, rightMenuItems, leftMenuItems, portMenuItems, routingFunction, logoutLink}: IHeader) => { + const [anchorEl, setAnchorEl] = React.useState(null); + const open = Boolean(anchorEl); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + + const adminMenuRoles = adminMenuItems.map(menuItem => menuItem.roles).flat(1); + const hasAdminMenuRoles = user.roles.filter(role => adminMenuRoles.includes(role)).length > 0; + + return ( + + + + + + + Border Force + Dynamic Response Tool + + + + + Contact: drtpoiseteam@homeoffice.gov.uk + + + { hasAdminMenuRoles && + + + + { adminMenuItems.map((item) => { + const hasRole = item.roles && (item.roles.filter(role => user.roles.includes(role)).length > 0); + return hasRole && routingFunction(item.link)}> + {item.label} + + })} + + } + + + + + + + + + + { + leftMenuItems && leftMenuItems.map((menuItem) => { + + return ( + + + + ) + }) + } + { hasAdminMenuRoles && + + } + { + rightMenuItems && rightMenuItems.map((menuItem) => { + + return ( + + + + ) + }) + } + + + + + + + + + + ) +} + +export default Header; diff --git a/src/components/Header/PortSelector.tsx b/src/components/Header/PortSelector.tsx new file mode 100644 index 0000000..365b0be --- /dev/null +++ b/src/components/Header/PortSelector.tsx @@ -0,0 +1,77 @@ +import React from "react"; +import { ListItemText, MenuItem, FormControl, Select, SelectProps } from "@mui/material"; +import { styled } from '@mui/material/styles'; +import { SelectChangeEvent } from "@mui/material"; +import { MenuItem as MenuItemType } from "./Header"; + +const StyledSelect = styled(Select)(({ theme }) => ({ + minWidth: '280px', + width: '100%', + outline: 0, + marginRight: theme.spacing(1), + ':after, :before': { + borderBottom: 'none !important' + }, + '& .MuiSelect-select': { + borderWidth: '0 !important', + display: 'flex', + padding: ` 0 40px 0 0 !important`, + backgroundColor: 'transparent', + '& >*': { + display: 'flex', + alignItems: 'center', + minWidth: 0, + }, + '& .MuiListItemIcon-root': { + marginRight: theme.spacing(2), + '& svg': { + fontSize: '1rem', + fill: '#000' + } + } + }, + '& .MuiListItemText-root *': { + fontWeight: 'bold !important', + fontSize: '0.875rem !important', + marginTop: '6px !important', + } +})); + + +export interface IPortSelector { + handleChangePort: (path: string) => void, + options: MenuItemType[], + selectedOption: string, +} + +const PortSelector = ({handleChangePort, options, selectedOption}: IPortSelector) => { + const [selected, setSelected] = React.useState(selectedOption); + + const onChange = (event: SelectChangeEvent) => { + handleChangePort(event.target.value); + setSelected(event.target.value); + } + + return ( + + + {options?.map((option) => { + return ( + + { option.label } + + ) + })} + + + ) +} +export default PortSelector diff --git a/src/components/Header/__tests__/Header.test.tsx b/src/components/Header/__tests__/Header.test.tsx new file mode 100644 index 0000000..81d0e0d --- /dev/null +++ b/src/components/Header/__tests__/Header.test.tsx @@ -0,0 +1,136 @@ +import React from "react"; +import { render } from "../../TestProviderRenderer"; +import { screen } from "@testing-library/dom"; +import { fireEvent } from "@testing-library/react"; +import Header from "../Header"; +import { within } from "@testing-library/react"; +import '@testing-library/jest-dom'; + + +const headerProps = { + config: {}, + user: { + roles: [] as string[] + }, + portMenuItems: [ + { label: 'National Dashboard', link: '/regional-dashboard' }, + { label: 'CWL (Cardiff)', link: '/cwi' } + ], + adminMenuItems: [ + { label: 'Home', link: '/', roles: []}, + { label: 'Access requests', link: '/access-requests', roles: ['manage-users']}, + { label: 'Alert notices', link: '/alerts', roles: ['manage-users']}, + { label: 'Drop-in sessions', link: '/drop-in-sessions', roles: ['manage-users']}, + { label: 'Download Manager', link: '/download', roles: ['download-manager']}, + { label: 'Export Config', link: '/export-config', roles: ['manage-users']}, + { label: 'Feature guides', link: '/feature-guides', roles: ['manage-users']}, + { label: 'Health checks', link: '/health-checks', roles: ['health-checks:edit']}, + { label: 'Health check pauses', link: '/health-check-pauses', roles: ['health-checks:edit']}, + { label: 'Feedback', link: '/user-feedback', roles: ['manage-users']}, + { label: 'Users', link: '/users', roles: ['manage-users']}, + ], + leftMenuItems: [ + { + label: 'Port config', + link: '/port-config', + icon: 'Settings', + }, + { + label: 'Feed', + link: '/feed', + icon: 'Equalizer', + } + ], + rightMenuItems: [ + { + label: "What's new", + link: '/whats-new', + icon: 'Article', + }, + { + label: 'Training', + link: '/training', + icon: 'MenuBook', + } + ], + routingFunction: jest.fn(), + logoutLink: jest.fn(), +} +let testHeaderProps = {...headerProps} + +describe("When the user has admin roles", () => { + + beforeEach(() => { + headerProps.user.roles = ['health-checks:edit']; + }); + + test("it renders the admin menu options if the role is available", async () => { + + render(
); + await fireEvent.click(screen.getByTestId('desktop-admin-menu-trigger')); + + + expect(await screen.getByTestId('menu-/health-checks')).toBeTruthy(); + expect(await screen.getByTestId('menu-/health-check-pauses')).toBeTruthy(); + }) + + test("it does not render admin menu options if the role is missing", async () => { + + render(
); + await fireEvent.click(screen.getByTestId('desktop-admin-menu-trigger')); + + const feedbackOption = await screen.queryByTestId('menu-/user-feedback') + const accessRequestsOption = await screen.queryByTestId('menu-/access-requests') + expect(feedbackOption).toBeNull(); + expect(accessRequestsOption).toBeNull(); + }) +}) + +test("it renders the port menu items", async () => { + + render(
); + + const selectCompoEl = screen.getByTestId('port-selector-trigger'); + const trigger = within(selectCompoEl).getByRole('button'); + await fireEvent.mouseDown(trigger); + + expect(await screen.getByTestId('port-selector-/cwi')).toBeTruthy(); + expect(await screen.getByTestId('port-selector-/regional-dashboard')).toBeTruthy(); + expect(await screen.queryByTestId('port-selector-/lhr')).toBeNull(); +}) + +test("it renders the left menu items", async () => { + + render(
); + + expect(await screen.getByTestId('left-menu-/port-config')).toBeTruthy(); +}) + +test("it renders the right menu items", async () => { + + render(
); + + expect(await screen.getByTestId('right-menu-/whats-new')).toBeTruthy(); +}) + +test("it calls the logout function", async () => { + render(
); + await fireEvent.click(screen.getByTestId('logout')); + expect(headerProps.logoutLink).toHaveBeenCalled(); +}) + +describe("It calls the routing function", () => { + + test("when right menu items are clicked on", async () => { + render(
); + await fireEvent.click(screen.getByTestId('right-menu-/whats-new')); + expect(headerProps.routingFunction).toHaveBeenCalledWith('/whats-new'); + }) + + test("when left menu items are clicked on", async () => { + render(
); + await fireEvent.click(screen.getByTestId('left-menu-/port-config')); + expect(headerProps.routingFunction).toHaveBeenCalledWith('/port-config'); + }) + +}); diff --git a/src/components/Header/__tests__/PortSelector.test.tsx b/src/components/Header/__tests__/PortSelector.test.tsx new file mode 100644 index 0000000..7433f3e --- /dev/null +++ b/src/components/Header/__tests__/PortSelector.test.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import { render } from "../../TestProviderRenderer"; +import { screen, prettyDOM } from "@testing-library/dom"; +import { fireEvent } from "@testing-library/react"; +import { ThemeProvider } from "@mui/material"; +import PortSelector from "../PortSelector"; +import { waitFor, within } from "@testing-library/react"; +import '@testing-library/jest-dom' + +const portSelectorProps = { + options: [ + { label: 'National Dashboard', link: '/regional-dashboard' }, + { label: 'CWL (Cardiff)', link: '/cwi' } + ], + selectedOption: '/regional-dashboard', + handleChangePort: jest.fn(), +} + +test("it selects options based on the selectedOption prop", async () => { + + render(); + const selector = await screen.getByTestId('port-selector-trigger'); + const trigger = within(selector).getByDisplayValue('/regional-dashboard'); + + expect(trigger).toBeTruthy(); +}) + +test("it calls the handleChangePort function correctly", async () => { + + render(); + const selectCompoEl = await screen.getByTestId('port-selector-trigger'); + const trigger = within(selectCompoEl).getByRole('button'); + await fireEvent.mouseDown(trigger); + + const listbox = within(screen.getByRole('presentation')).getByRole( + 'listbox' + ); + const option = within(listbox).getByTestId('port-selector-/cwi') + + await fireEvent.click(option); + + expect(portSelectorProps.handleChangePort).toHaveBeenCalledTimes(1); + expect(portSelectorProps.handleChangePort).toHaveBeenCalledWith('/cwi'); +}) diff --git a/src/components/Header/index.ts b/src/components/Header/index.ts new file mode 100644 index 0000000..f23def1 --- /dev/null +++ b/src/components/Header/index.ts @@ -0,0 +1,2 @@ +export { default } from "./Header"; +export type {IHeader, MenuItem} from './Header' diff --git a/src/components/PortSelector/PortMap.css b/src/components/PortSelector/PortMap.css deleted file mode 100644 index 8ed3941..0000000 --- a/src/components/PortSelector/PortMap.css +++ /dev/null @@ -1,3 +0,0 @@ -#port-map g rect:hover { - cursor: pointer; -} diff --git a/src/components/PortSelector/PortMap.tsx b/src/components/PortSelector/PortMap.tsx deleted file mode 100644 index 21a7d5c..0000000 --- a/src/components/PortSelector/PortMap.tsx +++ /dev/null @@ -1,437 +0,0 @@ -import React, {LegacyRef, RefObject, createRef, useState} from "react"; -import { Dispatch, SetStateAction, } from "react"; -import { Tooltip, useTheme, Popover, PopoverProps, Box } from "@mui/material"; -import { Instance } from '@popperjs/core'; -import './PortMap.css'; - -export interface IPortMap { - handlePortClick: (portCode: string) => void, - handlePortHover: Dispatch>, - selectedPort: string, - hoveredPort: string, -} - -type PortPin = { - x: string; - y: string; - portCode: string; - region: string; -} - - -const PortMap = ({handlePortClick, handlePortHover, hoveredPort, selectedPort}: IPortMap) => { - const theme = useTheme(); - const areaRef = React.useRef(null); - const mapBgColor = theme.palette.divider; - - const isHovered = (portCode: string, region: string) => { - return (portCode == hoveredPort)|| (region == hoveredPort) - } - - const isSelected= (portCode: string, region: string) => { - return (portCode == selectedPort) || (region == selectedPort) - } - - const ports = [ - { x: "242.378", y: "383.200", portCode: "BHX", region: "central" }, - { x: "256.338", y: "357.642", portCode: "EMA", region: "central" }, - { x: "283.670", y: "421.495", portCode: "LCY", region: "central" }, - { x: "324.924", y: "408.769", portCode: "SEN", region: "central" }, - { x: "311.127", y: "395.999", portCode: "STN", region: "central" }, - { x: "269.818", y: "395.993", portCode: "LTN", region: "central" }, - { x: "324.694", y: "370.412", portCode: "NWI", region: "central" }, - { x: "228.680", y: "447.085", portCode: "BOH", region: "south" }, - { x: "215.021", y: "409.242", portCode: "BRS", region: "south" }, - { x: "187.549", y: "408.827", portCode: "CWL", region: "south" }, - { x: "201.331", y: "447.079", portCode: "EXT", region: "south" }, - { x: "283.523", y: "434.320", portCode: "LGW", region: "south" }, - { x: "160.014", y: "459.847", portCode: "NQY", region: "south" }, - { x: "242.442", y: "447.111", portCode: "SOU", region: "south" }, - { x: "256.017", y: "421.476", portCode: 'LHR', region: "heathrow" }, - { x: "228.472", y: "165.852", portCode: "ABZ", region: "north" }, - { x: "132.673", y: "294.091", portCode: "BFS", region: "north" }, - { x: "119.057", y: "280.923", portCode: "BHD", region: "north" }, - { x: "201.325", y: "229.801", portCode: "EDI", region: "north" }, - { x: "160.186", y: "229.808", portCode: "GLA", region: "north" }, - { x: "160.063", y: "255.311", portCode: "PIK", region: "north" }, - { x: "283.562", y: "319.208", portCode: "HUY", region: "north" }, - { x: "173.810", y: "153.050", portCode: "INV", region: "north" }, - { x: "256.578", y: "306.429", portCode: "LBA", region: "north" }, - { x: "214.962", y: "332.008", portCode: "LPL", region: "north" }, - { x: "242.448", y: "332.073", portCode: "MAN", region: "north" }, - { x: "242.586", y: "280.851", portCode: "MME", region: "north" }, - { x: "242.427", y: "255.312", portCode: "NCL", region: "north" }, - ] - - const handleMouseEnterPort = (event: React.MouseEvent, portCode : string) => { - handlePortHover(portCode); - } - - const handleMouseLeavePort = (event: React.MouseEvent, portCode : string) => { - handlePortHover('') - } - - const renderTooltip = (id: string, _x: string, _y: string, portCode: string, color: string) => { - const x = Number(_x); - const y = Number(_y); - return - - - - {portCode} - - - } - - const renderPort = (port: PortPin, index: number) => { - const color = isSelected(port.portCode, port.region) ? theme.palette.primary.light : - isHovered(port.portCode, port.region) ? theme.palette.primary.light : theme.palette.grey[500] - - return ( - { port.portCode == hoveredPort && port.portCode != selectedPort && renderTooltip('tooltip-hovered', port.x, port.y, port.portCode, color) } - { port.portCode == selectedPort && renderTooltip('tooltip-selected', port.x, port.y, port.portCode, color) } - handlePortClick(port.portCode)} - onMouseEnter={(event) => handleMouseEnterPort(event, port.portCode)} - onMouseLeave={(event) => handleMouseLeavePort(event, port.portCode)} - fill={color} /> - ) - } - - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { ports.map(renderPort) } - - - - - - ) -} -export default PortMap diff --git a/src/components/PortSelector/PortPanel.tsx b/src/components/PortSelector/PortPanel.tsx deleted file mode 100644 index ef42906..0000000 --- a/src/components/PortSelector/PortPanel.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import React, {useState} from "react"; -import { ListItemIcon,ListItemText, Typography, MenuItem, Paper, Select, SelectProps, Grid, MenuList, useMediaQuery } from "@mui/material"; -import airports from "../../aiports"; -import ArrowRightIcon from '@mui/icons-material/ArrowRight'; -import LocalAirportIcon from '@mui/icons-material/LocalAirport'; -import TravelExploreIcon from '@mui/icons-material/TravelExplore'; -import Map from '../ukmap.svg' -import { Airport } from "../../aiports"; -import PortMap from "./PortMap"; -import { Theme } from "@mui/material"; - -export interface IPortPanel { - handlePortClick: (portCode: string) => void, - selectedPort: string, -} - -const PortPanel = ({handlePortClick, selectedPort}: IPortPanel) => { - const isMobile = useMediaQuery((theme: Theme) => theme.breakpoints.down('sm')); - - - const [portHover, setPortHover] = useState(''); - - const renderRegion = (name: string, regionCode: string) => { - return ( - setPortHover(regionCode)} - onMouseLeave={() => setPortHover('')} - onClick={() => handlePortClick(regionCode)} - > - - { name } - - - ) - } - - const renderPort = (airport: Airport, index: number) => { - return ( - setPortHover(airport.code)} - onMouseLeave={() => setPortHover('')} - onClick={() => handlePortClick(airport.code)} - > - { airport.code } - { airport.name } - - - ) - } - - return ( - - - {/* MAP */} - `1px solid ${theme.palette.divider}`, - }}> - - - - - - - {/* CENTRAL REGION */} - `1px solid ${theme.palette.divider}`, - borderBottom: (theme) => `1px solid ${theme.palette.divider}`, - }}> - - { renderRegion(airports.central.name, airports.central.code)} - { airports.central.ports.map(renderPort)} - - - - {/* SOUTH REGION */} - `1px solid ${theme.palette.divider}`, - borderBottom: (theme) => `1px solid ${theme.palette.divider}`, - }}> - - { renderRegion(airports.south.name, airports.south.code)} - { airports.south.ports.map(renderPort)} - - - - {/* HEATHROW*/} - `1px solid ${theme.palette.divider}`, - }}> - - { renderRegion(airports.heathrow.name, airports.heathrow.code)} - { airports.heathrow.ports.map(renderPort)} - - - - - {/* NORTHERN REGION */} - - - - { renderRegion(airports.north.name, airports.north.code)} - { airports.north.ports.filter((port, index) => index < (airports.north.ports.length / 2)).map(renderPort)} - { isMobile && airports.north.ports.filter((port, index) => index > (airports.north.ports.length / 2)).map(renderPort) } - - - { !isMobile && `1px solid ${theme.palette.divider}`, - }}> - - { airports.north.ports.filter((port, index) => index > (airports.north.ports.length / 2)).map(renderPort)} - - } - - - - - ) -} -export default PortPanel diff --git a/src/components/PortSelector/PortSelector.tsx b/src/components/PortSelector/PortSelector.tsx deleted file mode 100644 index 7b58db9..0000000 --- a/src/components/PortSelector/PortSelector.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React from "react"; -import { ListItemIcon,ListItemText, Typography, MenuItem, FormControl, Select, SelectProps, Box, FormLabel, Divider } from "@mui/material"; -import airports from "../../aiports"; -import { styled } from '@mui/material/styles'; -import TravelExploreIcon from '@mui/icons-material/TravelExplore'; -import { SelectChangeEvent } from "@mui/material"; - -const StyledSelect = styled(Select)(({ theme }) => ({ - minWidth: '280px', - outline: 0, - marginRight: theme.spacing(1), - '& .MuiSelect-select': { - borderWidth: '0 !important', - display: 'flex', - paddingRight: '40px !important', - '& >*': { - display: 'flex', - alignItems: 'center', - minWidth: 0, - }, - '& .MuiListItemIcon-root': { - marginRight: theme.spacing(2) - } - } -})); - - -export interface IPortSelector { - handleChangePort: (event: SelectChangeEvent) => void, - port: string, -} - -const PortSelector = ({handleChangePort, port}: IPortSelector) => { - - return ( - - - Select an
airport/region: -
- - - - - { airports.central.name } - - { airports.central.ports.map((airport, index) => - - {airport.code} - {airport.name} - - )} - - - - { airports.south.name } - - { airports.south.ports.map((airport, index) => - - {airport.code} - {airport.name} - - )} - - - - { airports.north.name } - - { airports.north.ports.map((airport, index) => - - {airport.code} - {airport.name} - - )} - - - - { airports.heathrow.name } - - { airports.heathrow.ports.map((airport, index) => - - {airport.code} - {airport.name} - - )} - - -
- ) -} -export default PortSelector diff --git a/src/components/PortSelector/index.ts b/src/components/PortSelector/index.ts deleted file mode 100644 index 1e728d9..0000000 --- a/src/components/PortSelector/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./PortSelector"; -export type {IPortSelector} from './PortSelector' diff --git a/src/components/SiteFrame/Navigation.tsx b/src/components/SiteFrame/Navigation.tsx deleted file mode 100644 index 6501f71..0000000 --- a/src/components/SiteFrame/Navigation.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React from "react"; -import { ListItemIcon,ListItemText, ListItemButton, List, Divider, Badge, useTheme } from "@mui/material"; -import ChevronRightIcon from "@mui/icons-material/ChevronRight"; -import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; -import DashboardIcon from "@mui/icons-material/Dashboard"; -import BarChartIcon from "@mui/icons-material/BarChart"; -import CalendarMonthIcon from '@mui/icons-material/CalendarMonth'; -import ManageAccountsIcon from '@mui/icons-material/ManageAccounts'; -import PublicIcon from '@mui/icons-material/Public'; -import SchoolIcon from '@mui/icons-material/School'; -import SpeedIcon from '@mui/icons-material/Speed'; -import ConnectingAirportsIcon from '@mui/icons-material/ConnectingAirports'; -import { getAirportByCode } from "../../aiports"; -import DarkModeIcon from '@mui/icons-material/DarkMode'; -import LightModeIcon from '@mui/icons-material/LightMode'; - -interface IPageNav { - toggleDrawer: () => void; - drawerOpen: boolean; - selectedPort: string; - themeToggle: () => void; -} - -export const PageNav = ({toggleDrawer, drawerOpen, selectedPort, themeToggle}: IPageNav) => { - const theme = useTheme(); - const isDarkMode = theme.palette.mode === 'dark'; - - const isPortSelected = selectedPort !== '' - return ( - - - - {drawerOpen ? : } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {isDarkMode ? : } - - - - - ) -} diff --git a/src/components/SiteFrame/SiteFrame.stories.tsx b/src/components/SiteFrame/SiteFrame.stories.tsx deleted file mode 100644 index 46fa77f..0000000 --- a/src/components/SiteFrame/SiteFrame.stories.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import { default as SiteFrameComponent } from "./"; - -const meta: Meta = { - title: "DRT Components/WIP", - component: SiteFrameComponent, -}; - -export default meta; -type Story = StoryObj; - -export const SiteFrame: Story = { - args: {}, - parameters: {}, -}; diff --git a/src/components/SiteFrame/SiteFrame.tsx b/src/components/SiteFrame/SiteFrame.tsx deleted file mode 100644 index 6185d70..0000000 --- a/src/components/SiteFrame/SiteFrame.tsx +++ /dev/null @@ -1,281 +0,0 @@ -import React, {createRef, useEffect, useState, useContext} from "react"; -import { Box, CssBaseline, Badge, Toolbar, IconButton, Drawer as MuiDrawer, Typography, Grid, useMediaQuery, CircularProgress, Backdrop, BoxProps, Paper, LinearProgress, IconButtonProps, BackdropProps, SelectChangeEvent } from "@mui/material"; -import MuiAppBar, { AppBarProps as MuiAppBarProps } from '@mui/material/AppBar'; -import { ThemeProvider } from '@mui/material/styles'; -import MenuIcon from "@mui/icons-material/Menu"; -import NotificationsIcon from '@mui/icons-material/Notifications'; -import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; -import HelpIcon from '@mui/icons-material/Help'; -import { Theme } from "@mui/material"; -import { styled } from '@mui/material/styles'; -import { Crest } from "./Crest"; -import { getAirportByCode } from "../../aiports"; -import {PageNav} from "./Navigation"; - -import PortSelector from "../PortSelector"; -import PortPanel from "../PortSelector/PortPanel"; -import {createDRTTheme} from "../../drt-theme"; -import UserMenu from "./UserMenu"; - -export interface ISiteFrame {} - - -const drawerWidth: number = 240; -const ColorModeContext = React.createContext({ toggleColorMode: () => {} }); - -const Wrapper = styled(Box)(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - height: '100%', - alignItems: 'flex-start', - overflow: 'hidden', -})); - -const ScrollToTopBtn = styled(IconButton)(({ theme }) => ({ - backgroundColor: theme.palette.primary.main, - color: theme.palette.common.white, - width: '40px', - height: '40px', - '&:hover': { - backgroundColor: theme.palette.primary.dark, - color: theme.palette.common.white, - } -})); - -const PageContent = styled(Box)(({ theme }) => ({ - display: 'flex', - height: '100%', - flexGrow: 1, - backgroundColor: theme.palette.mode === 'light' ? theme.palette.grey[100] : 'transparent', - alignItems: 'stretch', -})); - -interface AppBarProps extends MuiAppBarProps { - open?: boolean; -} - -const AppBar = styled(MuiAppBar, { - shouldForwardProp: (prop) => prop !== 'open', -})(({ theme, open }) => ({ - backgroundColor: theme.palette.mode === 'dark' ? 'transparent' : theme.palette.common.white, - borderBottom: `1px solid ${theme.palette.divider}`, - zIndex: theme.zIndex.drawer + 2, - transition: theme.transitions.create(['width', 'margin'], { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - ...(open && { - marginLeft: drawerWidth, - width: `calc(100% - ${drawerWidth}px)`, - transition: theme.transitions.create(['width', 'margin'], { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.enteringScreen, - }), - }), -})); - -const DrtBackdrop = styled(Backdrop)(({ theme }) => ({ - backgroundColor: 'rgba(0,0,0,0.1)', - backdropFilter: 'blur(2px)', - zIndex: theme.zIndex.drawer + 1, -})); - -const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })( - ({ theme, open }) => ({ - height: '100%', - '& .MuiDrawer-paper': { - position: 'relative', - whiteSpace: 'nowrap', - width: drawerWidth, - transition: theme.transitions.create('width', { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.enteringScreen, - }), - ...(!open && { - transition: theme.transitions.create('width', { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - width: 0, - [theme.breakpoints.down('sm')]: { - borderRight: 'none', - }, - [theme.breakpoints.up('sm')]: { - width: '58px', - }, - }), - }, - }), -); - - -const SiteFrame = ({}: ISiteFrame) => { - const isMobile = useMediaQuery((theme: Theme) => theme.breakpoints.down('sm')); - const [mode, setMode] = React.useState<'light' | 'dark'>('light'); - const colorMode = React.useMemo( - () => ({ - toggleColorMode: () => { - setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light')); - }, - }), - [], - ); - const drtTheme = createDRTTheme(mode); - - const scrollRef = createRef() - useEffect(() => { - const temp = scrollRef.current!; - temp.addEventListener("scroll", handleScroll); - return () => temp.removeEventListener("scroll", handleScroll); - }); - - - const [drawerOpen, setDrawerOpen] = useState(false); - const toggleDrawer = () => { - setDrawerOpen(!drawerOpen); - }; - - - const [port, setPort] = useState(''); - const handleChangePort = (event: SelectChangeEvent) => { - setPort(event.target.value as string); - flashLoading() - }; - - const [pos, setPos] = useState(false); - const handleScroll = () => { - if (scrollRef.current!.scrollTop > 50) { - if (!pos) setPos(true); - } else { - if (pos) setPos(false); - } - }; - const handleTop = () => { - scrollRef.current!.scrollTo({ - top: 0, - behavior: 'smooth', - }) - setPos(false); - }; - - const [loading, setLoading] = useState(false); - const flashLoading = () => { - setLoading(true); - setTimeout(() =>{ - setLoading(false); - }, 2000) - }; - - const handlePortClick = (port: string) => { - setPort(port); - flashLoading() - } - - - return - - - - - - {isMobile && - - } - - - DRT - - - - - - - - - - - - - - - - {/* MOBILE DRAWER */} - theme.zIndex.drawer + 3}}> - - - - {/* PROGRESS STRIP */} - - - - {/* DESKTOP DRAWER */} - - - - - - {/* PAGE CONTENT */} - - - - {port != '' ? getAirportByCode(port) : "Select an airport or region"} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {/* SCROLL TO TOP */} - - - - - {/* LOADING BACKDROP */} - - - - - - - - -} - -export default SiteFrame; diff --git a/src/components/SiteFrame/UserMenu.tsx b/src/components/SiteFrame/UserMenu.tsx deleted file mode 100644 index 24f9cb8..0000000 --- a/src/components/SiteFrame/UserMenu.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React, {useState} from "react"; -import { Box, IconButton, Typography, Avatar, Button,Popover, Card, CardContent, CardActions } from "@mui/material"; -import PersonIcon from '@mui/icons-material/Person'; - - - -const UserMenu = () => { - const [anchorEl, setAnchorEl] = useState(null); - const open = Boolean(anchorEl); - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - const handleClose = () => { - setAnchorEl(null); - }; - - return ( - - - - - - - - EC - - Example Caseworker - - - example.caseworker@example. - - - - - - - - - - ) -} - -export default UserMenu; diff --git a/src/components/SiteFrame/index.ts b/src/components/SiteFrame/index.ts deleted file mode 100644 index 51617a1..0000000 --- a/src/components/SiteFrame/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./SiteFrame"; -export type {ISiteFrame} from './SiteFrame' diff --git a/src/components/index.ts b/src/components/index.ts index 50295f2..3f2e5e4 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -5,3 +5,6 @@ export type { IFlightFlagger, IFlightFlaggerFilters, IFlightHighlightChip, Autoc export { StatusTag } from "./StatusTags"; export type { IStatusTag } from "./StatusTags"; + +export {default as Header} from './Header' +export type { IHeader, MenuItem} from './Header'; diff --git a/src/drt-theme.ts b/src/drt-theme.ts index 714dc18..f56bbcd 100644 --- a/src/drt-theme.ts +++ b/src/drt-theme.ts @@ -15,12 +15,16 @@ declare module '@mui/material/styles' { interface TypographyVariants { portCode: React.CSSProperties; pageTitle: React.CSSProperties; + logoTitle: React.CSSProperties; + logoStrap: React.CSSProperties; } // allow configuration using `createTheme` interface TypographyVariantsOptions { portCode?: React.CSSProperties; pageTitle?: React.CSSProperties; + logoTitle?: React.CSSProperties; + logoStrap?: React.CSSProperties; } } @@ -29,10 +33,13 @@ declare module '@mui/material/Paper' { appbar: true; } } + declare module "@mui/material/Typography" { interface TypographyPropsVariantOverrides { portCode: true; pageTitle: true; + logoTitle: true; + logoStrap: true; } } @@ -103,7 +110,15 @@ drtTheme = createTheme({ [drtTheme.breakpoints.up("sm")]: { fontSize: 36 } + }, + logoTitle: { + fontSize: '1.6em' + + }, + logoStrap: { + } + }, components: { MuiPaper: { diff --git a/src/index.ts b/src/index.ts index 705f4b9..37cfb83 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ export * from './components'; +export {default as drtTheme } from './drt-theme';