From d88bee685262d8f8a2a44e56589ca0ca7c5d35da Mon Sep 17 00:00:00 2001 From: Rich Birch Date: Thu, 26 Sep 2024 14:53:44 +0100 Subject: [PATCH 1/4] DRTII-696 Add titles to all admin sections --- react/package.json | 2 + react/src/App.tsx | 10 +- react/src/components/ExportConfig.tsx | 9 +- react/src/components/Home.tsx | 40 +++-- .../accessrequests/AccessRequests.tsx | 65 ++++--- react/src/components/alerts/Alerts.tsx | 43 +++-- .../downloadmanager/DownloadManager.tsx | 79 +++++---- .../components/dropins/DropInSessionsList.tsx | 89 +++++----- ...ureGuide.tsx => FeatureGuideAddOrEdit.tsx} | 99 ++++++----- ...ureGuidesList.tsx => FeatureGuideList.tsx} | 109 ++++++------ .../featureguides/PreviewComponent.tsx | 2 +- .../src/components/feedback/FeedbackList.tsx | 69 ++++---- .../HealthCheckPausesEditor.tsx | 159 +++++++++--------- .../components/healthchecks/HealthChecks.tsx | 81 +++++---- react/src/components/users/UsersList.tsx | 37 ++-- react/src/store/featureGuides.ts | 2 +- react/src/utils/common.ts | 3 + react/yarn.lock | 29 +++- 18 files changed, 526 insertions(+), 401 deletions(-) rename react/src/components/featureguides/{AddOrEditFeatureGuide.tsx => FeatureGuideAddOrEdit.tsx} (59%) rename react/src/components/featureguides/{FeatureGuidesList.tsx => FeatureGuideList.tsx} (53%) create mode 100644 react/src/utils/common.ts diff --git a/react/package.json b/react/package.json index 4aa550d1..ca2234c8 100644 --- a/react/package.json +++ b/react/package.json @@ -18,6 +18,7 @@ "@types/node": "^20.8.10", "@types/react": "^18.2.36", "@types/react-dom": "^18.2.14", + "@types/react-helmet": "^6.1.11", "@types/sinon": "^17.0.3", "@types/validator": "^13.11.7", "axios": "^0.21.1", @@ -35,6 +36,7 @@ "react": "^18.2.0", "react-chartjs-2": "^5.2.0", "react-dom": "^18.2.0", + "react-helmet": "^6.1.0", "react-markdown": "^8.0.7", "react-redux": "^8.1.3", "react-router": "^6.18.0", diff --git a/react/src/App.tsx b/react/src/App.tsx index fd8804fd..fe37be3e 100644 --- a/react/src/App.tsx +++ b/react/src/App.tsx @@ -22,8 +22,8 @@ import {AddOrEditDropInSession} from "./components/dropins/AddOrEditDropInSessio import {DropInSessionRegistrations} from "./components/dropins/DropInSessionRegistrations"; import {SnackbarProvider} from 'notistack'; import Link from "@mui/material/Link"; -import {FeatureGuidesList} from "./components/featureguides/FeatureGuidesList"; -import {AddOrEditFeatureGuide} from "./components/featureguides/AddOrEditFeatureGuide"; +import {FeatureGuideList} from "./components/featureguides/FeatureGuideList"; +import {FeatureGuideAddOrEdit} from "./components/featureguides/FeatureGuideAddOrEdit"; import {FeedbackForms} from "./components/feedback/FeedbackForms"; import {FeedbackList} from "./components/feedback/FeedbackList"; import DownloadManager from './components/downloadmanager/DownloadManager'; @@ -106,9 +106,9 @@ export const App = () => { }/> }/> - }/> - }/> - }/> + }/> + }/> + }/> }/> diff --git a/react/src/components/ExportConfig.tsx b/react/src/components/ExportConfig.tsx index ebc31254..03da1256 100644 --- a/react/src/components/ExportConfig.tsx +++ b/react/src/components/ExportConfig.tsx @@ -2,9 +2,14 @@ import React from "react"; import Button from "@mui/material/Button"; import FileDownloadIcon from "@mui/icons-material/FileDownload"; import ApiClient from "../services/ApiClient"; +import {Helmet} from "react-helmet"; +import {adminPageTitleSuffix} from "../utils/common"; export function ExportConfig() { - return
+ return <> + + Export Config {adminPageTitleSuffix} +

Export Config

-
+ } diff --git a/react/src/components/Home.tsx b/react/src/components/Home.tsx index 11fd61c9..41b88208 100644 --- a/react/src/components/Home.tsx +++ b/react/src/components/Home.tsx @@ -1,28 +1,34 @@ -import React from 'react'; +import React from 'react' -import AccessRequestForm from "./AccessRequestForm"; -import {PortList} from "./PortList"; -import {Box} from "@mui/material"; -import {UserProfile} from "../model/User"; -import {ConfigValues} from "../model/Config"; +import AccessRequestForm from "./AccessRequestForm" +import {PortList} from "./PortList" +import {Box} from "@mui/material" +import {UserProfile} from "../model/User" +import {ConfigValues} from "../model/Config" +import {Helmet} from "react-helmet"; interface IProps { - user: UserProfile; - config: ConfigValues; + user: UserProfile + config: ConfigValues } export const Home = (props: IProps) => { - const isRccUser = () => { - return props.user.roles.includes("rcc:central") || props.user.roles.includes("rcc:heathrow") || props.user.roles.includes("rcc:north") || props.user.roles.includes("rcc:south") - } + const isRccUser = () => { + return props.user.roles.includes("rcc:central") || props.user.roles.includes("rcc:heathrow") || props.user.roles.includes("rcc:north") || props.user.roles.includes("rcc:south") + } - return - {props.user.ports.length === 0 && !isRccUser() ? - : - - } + return <> + + DRT + + + {props.user.ports.length === 0 && !isRccUser() ? + : + + } + } diff --git a/react/src/components/accessrequests/AccessRequests.tsx b/react/src/components/accessrequests/AccessRequests.tsx index f9fbe3ae..f4f20375 100644 --- a/react/src/components/accessrequests/AccessRequests.tsx +++ b/react/src/components/accessrequests/AccessRequests.tsx @@ -13,6 +13,8 @@ import {columns, KeyCloakUser} from "./AccessRequestCommon"; import {GridRowSelectionModel} from "@mui/x-data-grid/models/gridRowSelectionModel"; import {Link} from "react-router-dom"; import Typography from "@mui/material/Typography"; +import { Helmet } from 'react-helmet'; +import {adminPageTitleSuffix} from "../../utils/common"; export default function AccessRequests() { const [accessRequestListRequested, setAccessRequestListRequested] = React.useState(false); @@ -127,16 +129,18 @@ export default function AccessRequests() { }} /> {(openModal && rowDetails) ? : + setOpenModal={setOpenModal} + accessRequest={rowDetails} + receivedUserDetails={receivedUserDetails} + setReceivedUserDetails={setReceivedUserDetails} + status={""}/> : } - - + + } @@ -191,25 +195,30 @@ export default function AccessRequests() { } } - return - - - Home - - Access requests - - - - - - - -
{accessRequestOrApprovedList()}
-
-
+ return <> + + Access requests {adminPageTitleSuffix} + + + + + Home + + Access requests + + + + + + + +
{accessRequestOrApprovedList()}
+
+
+ } diff --git a/react/src/components/alerts/Alerts.tsx b/react/src/components/alerts/Alerts.tsx index 64382704..5a1a0789 100644 --- a/react/src/components/alerts/Alerts.tsx +++ b/react/src/components/alerts/Alerts.tsx @@ -8,6 +8,8 @@ import {UserProfile} from "../../model/User"; import {PortRegion} from "../../model/Config"; import {Link} from "react-router-dom"; import Typography from "@mui/material/Typography"; +import {Helmet} from "react-helmet"; +import {adminPageTitleSuffix} from "../../utils/common"; moment.locale("en-gb"); @@ -22,22 +24,27 @@ export default function Alerts(props: IProps) { const changeTabs = (event: React.ChangeEvent, newValue: number) => setSelectedTab(newValue); - return - - - Home - - Alerts - - - - - - - setSelectedTab(1)}/> - - - - - ; + return <> + + Alerts {adminPageTitleSuffix} + + + + + Home + + Alerts + + + + + + + setSelectedTab(1)}/> + + + + + + } diff --git a/react/src/components/downloadmanager/DownloadManager.tsx b/react/src/components/downloadmanager/DownloadManager.tsx index 81538b87..2769742f 100644 --- a/react/src/components/downloadmanager/DownloadManager.tsx +++ b/react/src/components/downloadmanager/DownloadManager.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import {connect, MapDispatchToProps} from 'react-redux'; import { - Alert, + Alert, AlertTitle, Box, Button, @@ -23,7 +23,9 @@ import {checkDownloadStatus, PortTerminal, requestDownload} from './downloadMana import {RootState} from '../../store/redux'; import {UserProfile} from "../../model/User"; import {ConfigValues, PortRegion} from "../../model/Config"; -import { FormError } from '../../services/ValidationService'; +import {FormError} from '../../services/ValidationService'; +import {Helmet} from "react-helmet"; +import {adminPageTitleSuffix} from "../../utils/common"; interface DownloadDates { start: Moment; @@ -31,7 +33,7 @@ interface DownloadDates { } interface ErrorFieldMapping { - [key:string]: boolean + [key: string]: boolean } interface DownloadManagerProps { @@ -45,7 +47,16 @@ interface DownloadManagerProps { checkDownloadStatus: (createdAt: string) => void; } -const DownloadManager = ({status, createdAt, downloadUrl, errors, requestDownload, user, config, checkDownloadStatus}: DownloadManagerProps) => { +const DownloadManager = ({ + status, + createdAt, + downloadUrl, + errors, + requestDownload, + user, + config, + checkDownloadStatus + }: DownloadManagerProps) => { const [modalOpen, setModalOpen] = React.useState(false); const [selectedPorts, setSelectedPorts] = React.useState([]); const [dates, setDate] = React.useState({ @@ -55,19 +66,19 @@ const DownloadManager = ({status, createdAt, downloadUrl, errors, requestDownloa const [exportType, setExportType] = React.useState('passengers-port'); const [daily, setDaily] = React.useState(false); - const isRccRegion = (regionName : string) => { + const isRccRegion = (regionName: string) => { return user.roles.includes("rcc:" + regionName.toLowerCase()) } const errorFieldMapping: ErrorFieldMapping = {} errors.forEach((error: FormError) => errorFieldMapping[error.field] = true); - let interval: {current: ReturnType | null | any} = React.useRef(null); + let interval: { current: ReturnType | null | any } = React.useRef(null); React.useEffect(() => { if (createdAt && status === 'preparing') { setModalOpen(true) - interval.current = setInterval(()=>{ + interval.current = setInterval(() => { checkDownloadStatus(createdAt); }, 2000); } else if (status === 'failed') { @@ -101,7 +112,7 @@ const DownloadManager = ({status, createdAt, downloadUrl, errors, requestDownloa const handlePortChange = (event: SelectChangeEvent) => { const { - target: { value }, + target: {value}, } = event; setSelectedPorts(typeof value === 'string' ? value.split(',') : value); }; @@ -112,14 +123,14 @@ const DownloadManager = ({status, createdAt, downloadUrl, errors, requestDownloa const handlePortCheckboxChange = (event: React.ChangeEvent) => { const { - target: { name }, + target: {name}, } = event; setSelectedPorts(selectedPorts.includes(name) ? selectedPorts.filter(port => port !== name) : [...selectedPorts, name]); } const handlePortCheckboxGroupChange = (event: React.ChangeEvent) => { const { - target: { name, checked }, + target: {name, checked}, } = event; const region = userPortsByRegion.filter(region => region.name === name)[0] if (checked) { @@ -135,13 +146,13 @@ const DownloadManager = ({status, createdAt, downloadUrl, errors, requestDownloa } } - const disablePassengerExportType = (): boolean => { + const disablePassengerExportType = (): boolean => { return (exportType == 'arrivals') || (Math.abs(dates.start.diff(dates.end, 'days')) < 1) } - const handleSubmit = () :void => { + const handleSubmit = (): void => { - const portsWithTerminals :PortTerminal[] = config.ports.filter((port) => selectedPorts.includes(port.iata)).map(port => { + const portsWithTerminals: PortTerminal[] = config.ports.filter((port) => selectedPorts.includes(port.iata)).map(port => { return { port: port.iata, terminals: port.terminals @@ -156,41 +167,44 @@ const DownloadManager = ({status, createdAt, downloadUrl, errors, requestDownloa requestDownload(portsWithTerminals, exportString, dates.start, dates.end); } - return ( + return <> + + Download Manager {adminPageTitleSuffix} +

Download Manager

- { errors.length > 0 && + {errors.length > 0 && There is an issue with the options you have selected:
    - { errors.map(((error, index) =>
  • {error.message}
  • ))} + {errors.map(((error, index) =>
  • {error.message}
  • ))}
} - +

Date Range

- onDateChange('start', newValue)}/> - onDateChange('end', newValue)}/> @@ -204,7 +218,7 @@ const DownloadManager = ({status, createdAt, downloadUrl, errors, requestDownloa handleRemovePort={handleRemovePort} portsByRegion={userPortsByRegion} selectedPorts={selectedPorts} - /> + />

Passenger totals breakdown

@@ -216,13 +230,15 @@ const DownloadManager = ({status, createdAt, downloadUrl, errors, requestDownloa onChange={handleExportTypeChange} value={exportType} > - } label="By port" /> - } label="By terminal" /> - } label="By flight" /> + } label="By port"/> + } label="By terminal"/> + } label="By flight"/> - setDaily(!daily)} inputProps={{ 'aria-label': 'controlled' }} />} label={'Daily passenger breakdown'} /> + setDaily(!daily)} + inputProps={{'aria-label': 'controlled'}}/>} label={'Daily passenger breakdown'}/> @@ -233,10 +249,11 @@ const DownloadManager = ({status, createdAt, downloadUrl, errors, requestDownloa - +
- ) + } const mapState = (state: RootState) => { @@ -248,7 +265,7 @@ const mapState = (state: RootState) => { }; } -const mapDispatch = (dispatch :MapDispatchToProps) => { +const mapDispatch = (dispatch: MapDispatchToProps) => { return { checkDownloadStatus: (createdAt: string) => { dispatch(checkDownloadStatus(createdAt)); diff --git a/react/src/components/dropins/DropInSessionsList.tsx b/react/src/components/dropins/DropInSessionsList.tsx index b6c5662b..58ff3c72 100644 --- a/react/src/components/dropins/DropInSessionsList.tsx +++ b/react/src/components/dropins/DropInSessionsList.tsx @@ -21,6 +21,8 @@ import Loading from "../Loading"; import {useDropInSessions} from "../../store/dropInSessions"; import {DialogComponent} from "../DialogComponent"; import ApiClient from "../../services/ApiClient"; +import {adminPageTitleSuffix} from "../../utils/common"; +import {Helmet} from "react-helmet"; moment.locale('en-gb') @@ -185,63 +187,68 @@ export function DropInSessionsList() { }) } - return - - - Home - - Drop-in sessions - - - - setShowAll(!showAll)} color="primary"/>} - /> - - {loading ? - : - failed ? - Sorry, I couldn't load the existing pauses : - dropInSessions.length === 0 ? - There are no current or upcoming pauses : - - rowsData.id} - rows={dropInSessions} - columns={dropInColumns} - pageSizeOptions={[5]} - /> - - } - - {showDelete && + + Drop-in sessions {adminPageTitleSuffix} + + + + + Home + + Drop-in sessions + + + + setShowAll(!showAll)} color="primary"/>} + /> + + {loading ? + : + failed ? + Sorry, I couldn't load the existing pauses : + dropInSessions.length === 0 ? + There are no current or upcoming pauses : + + rowsData.id} + rows={dropInSessions} + columns={dropInColumns} + pageSizeOptions={[5]} + /> + + } + + {showDelete && setShowDelete(false)} onConfirm={() => { deleteSession(rowDetails?.id as string) setShowDelete(false) }}/>} - {publish && setShowDelete(false)} onConfirm={() => { updatePublished(rowDetails?.id as string, true) setPublish(false) }}/>} - {unPublish && setShowDelete(false)} onConfirm={() => { updatePublished(rowDetails?.id as string, false) setUnPublish(false) }}/>} - + + } diff --git a/react/src/components/featureguides/AddOrEditFeatureGuide.tsx b/react/src/components/featureguides/FeatureGuideAddOrEdit.tsx similarity index 59% rename from react/src/components/featureguides/AddOrEditFeatureGuide.tsx rename to react/src/components/featureguides/FeatureGuideAddOrEdit.tsx index 351b4b70..f20b3400 100644 --- a/react/src/components/featureguides/AddOrEditFeatureGuide.tsx +++ b/react/src/components/featureguides/FeatureGuideAddOrEdit.tsx @@ -7,11 +7,13 @@ import {Breadcrumbs, Stack} from "@mui/material" import {Link, useNavigate, useParams} from "react-router-dom" import Typography from "@mui/material/Typography" import {enqueueSnackbar} from "notistack" -import {FeatureGuide} from "./FeatureGuidesList" +import {FeatureGuide} from "./FeatureGuideList" import TextField from "@mui/material/TextField" import ApiClient from "../../services/ApiClient"; +import {Helmet} from "react-helmet"; +import {adminPageTitleSuffix} from "../../utils/common"; -export const AddOrEditFeatureGuide = () => { +export const FeatureGuideAddOrEdit = () => { const navigate = useNavigate() const [file, setFile] = useState(null) @@ -80,53 +82,58 @@ export const AddOrEditFeatureGuide = () => { .catch(() => enqueueSnackbar('Feature guide upload failed', {variant: 'error'})) } - return - - - Home - - - Feature guides - - {guideId ? 'Edit' : 'Add'} - -
- {!guideId && + return <> + + Feature Guides {adminPageTitleSuffix} + + + + + Home + + + Feature guides + + {guideId ? 'Edit' : 'Add'} + + + {!guideId &&
-
} -
- setTitle(event.target.value)}/> -
-
- - setMarkdownContent(event.target.value)} - /> -
- - - - - - {markdownContent.length > 0 ? -
- -
: } +
} +
+ setTitle(event.target.value)}/> +
+
+ + setMarkdownContent(event.target.value)} + /> +
+ + + + + + {markdownContent.length > 0 ? +
+ +
: } - {openPreview && + {openPreview && setOpenPreview(false)} - actionsAvailable={false} + guide={{ + id: '', + title: title, + markdownContent: markdownContent, + fileName: '', + uploadTime: '' + } as FeatureGuide} + fileUrl={fileUrl} + onClose={() => setOpenPreview(false)} + actionsAvailable={false} />} -
+
+ } diff --git a/react/src/components/featureguides/FeatureGuidesList.tsx b/react/src/components/featureguides/FeatureGuideList.tsx similarity index 53% rename from react/src/components/featureguides/FeatureGuidesList.tsx rename to react/src/components/featureguides/FeatureGuideList.tsx index c1e9ec62..b6cee9ad 100644 --- a/react/src/components/featureguides/FeatureGuidesList.tsx +++ b/react/src/components/featureguides/FeatureGuideList.tsx @@ -16,6 +16,8 @@ import {deleteFeatureGuide, updatePublishedStatus, useFeatureGuides} from "../.. import Loading from "../Loading" import moment from "moment-timezone" import Button from "@mui/material/Button"; +import {Helmet} from "react-helmet"; +import {adminPageTitleSuffix} from "../../utils/common"; export interface FeatureGuide { id: string @@ -25,7 +27,7 @@ export interface FeatureGuide { markdownContent: string } -export const FeatureGuidesList = () => { +export const FeatureGuideList = () => { const navigate = useNavigate() const [previewGuide, setPreviewGuide] = React.useState(undefined) @@ -99,59 +101,64 @@ export const FeatureGuidesList = () => { const [requestedAt, setRequestedAt] = useState(moment().valueOf()) const {features, loading, failed} = useFeatureGuides(requestedAt) - const [deleteId, setDeleteId] = useState(undefined) - const [publishId, setPublishId] = useState(undefined) - const [unpublishId, setUnpublishId] = useState(undefined) + const [deleteId, setDeleteId] = useState(undefined) + const [publishId, setPublishId] = useState(undefined) + const [unpublishId, setUnpublishId] = useState(undefined) - return - - - Home - - Feature guides - - - {loading ? - : - failed ? Failed to load features. Try refreshing the page : - - rowsData.id} - rows={features} - columns={featureColumns} - pageSizeOptions={[5]} - /> - - } - {previewGuide && { - setPreviewGuide(undefined) - setRequestedAt(moment().valueOf()) - }} - actionsAvailable={true}/>} - {publishId && { - publishId && updatePublishedStatus(publishId, true, () => { - setPublishId(undefined) - setRequestedAt(moment().valueOf()) - }) - }} - onCancel={() => setPublishId(undefined)}/>} - {unpublishId && + + Feature Guides {adminPageTitleSuffix} + + + + + Home + + Feature guides + + + {loading ? + : + failed ? Failed to load features. Try refreshing the page : + + rowsData.id} + rows={features} + columns={featureColumns} + pageSizeOptions={[5]} + /> + + } + {previewGuide && { + setPreviewGuide(undefined) + setRequestedAt(moment().valueOf()) + }} + actionsAvailable={true}/>} + {publishId && { - unpublishId && updatePublishedStatus(unpublishId, false, () => { - setUnpublishId(undefined) + publishId && updatePublishedStatus(publishId, true, () => { + setPublishId(undefined) setRequestedAt(moment().valueOf()) }) }} - onCancel={() => setUnpublishId(undefined)}/>} - {deleteId && { - deleteId && deleteFeatureGuide(deleteId) - setDeleteId(undefined) - setRequestedAt(moment().valueOf()) - }} - onCancel={() => setDeleteId(undefined)}/>} - + onCancel={() => setPublishId(undefined)}/>} + {unpublishId && { + unpublishId && updatePublishedStatus(unpublishId, false, () => { + setUnpublishId(undefined) + setRequestedAt(moment().valueOf()) + }) + }} + onCancel={() => setUnpublishId(undefined)}/>} + {deleteId && { + deleteId && deleteFeatureGuide(deleteId) + setDeleteId(undefined) + setRequestedAt(moment().valueOf()) + }} + onCancel={() => setDeleteId(undefined)}/>} + + } diff --git a/react/src/components/featureguides/PreviewComponent.tsx b/react/src/components/featureguides/PreviewComponent.tsx index 9c3863a1..fcb07b00 100644 --- a/react/src/components/featureguides/PreviewComponent.tsx +++ b/react/src/components/featureguides/PreviewComponent.tsx @@ -8,7 +8,7 @@ import EditIcon from '@mui/icons-material/Edit'; import {useNavigate} from "react-router-dom"; import {DialogComponent} from "../DialogComponent"; import {deleteFeatureGuide} from "../../store/featureGuides"; -import {FeatureGuide} from "./FeatureGuidesList"; +import {FeatureGuide} from "./FeatureGuideList"; import ReactMarkdown from "react-markdown"; interface Props { diff --git a/react/src/components/feedback/FeedbackList.tsx b/react/src/components/feedback/FeedbackList.tsx index 86b63851..0151a249 100644 --- a/react/src/components/feedback/FeedbackList.tsx +++ b/react/src/components/feedback/FeedbackList.tsx @@ -10,6 +10,8 @@ import FileDownloadIcon from "@mui/icons-material/FileDownload"; import {Breadcrumbs, Stack, Link as MuiLink} from "@mui/material"; import {Link} from "react-router-dom"; import ApiClient from "../../services/ApiClient"; +import {adminPageTitleSuffix} from "../../utils/common"; +import {Helmet} from "react-helmet"; export function FeedbackList() { const [requestedAt, setRequestedAt] = useState(moment().valueOf()) @@ -69,36 +71,41 @@ export function FeedbackList() { ]; - return - - - Home - - User feedback responses - - - + return <> + + Feedback {adminPageTitleSuffix} + + + + + Home + + User feedback responses + + + + + {loading ? : + failed ? + Sorry, I couldn't load the existing pauses : + userFeedbacks.length === 0 ? + There are no current or upcoming pauses : + + rowsData.email + '_' + rowsData.createdAt} + rows={userFeedbacks} + columns={abFeatureColumns} + pageSizeOptions={[5]} + /> + + } - {loading ? : - failed ? - Sorry, I couldn't load the existing pauses : - userFeedbacks.length === 0 ? - There are no current or upcoming pauses : - - rowsData.email + '_' + rowsData.createdAt} - rows={userFeedbacks} - columns={abFeatureColumns} - pageSizeOptions={[5]} - /> - - } - + } diff --git a/react/src/components/healthcheckpauseseditor/HealthCheckPausesEditor.tsx b/react/src/components/healthcheckpauseseditor/HealthCheckPausesEditor.tsx index b134b698..04631ae8 100644 --- a/react/src/components/healthcheckpauseseditor/HealthCheckPausesEditor.tsx +++ b/react/src/components/healthcheckpauseseditor/HealthCheckPausesEditor.tsx @@ -14,6 +14,8 @@ import {DataGrid} from "@mui/x-data-grid" import {Alert} from "@mui/lab" import {Link} from "react-router-dom" import {enqueueSnackbar} from "notistack"; +import {Helmet} from "react-helmet"; +import {adminPageTitleSuffix} from "../../utils/common"; type ConfirmOpen = { kind: 'open' @@ -146,84 +148,89 @@ export const HealthCheckEditor = () => { const rows = [...healthCheckPauses] .sort((a, b) => -1 * (b.startsAt.valueOf() - a.startsAt.valueOf())) - return - - - - Home - - Health check pauses - - <> - { - e.preventDefault() - addNewPause() - }}> - - <> - {newPause && + return <> + + Health Check Pauses {adminPageTitleSuffix} + + + + + + Home + + Health check pauses + + <> + { + e.preventDefault() + addNewPause() + }}> + + <> + {newPause && - - Add a pause - - - - {!newPauseIsValid && The start time must be before the end time} - - - - - - - - + + Add a pause + + + + {!newPauseIsValid && The start time must be before the end time} + + + + + + + + } - {confirm.kind === 'open' && + {confirm.kind === 'open' && - {confirm.message} - - - - + {confirm.message} + + + + } - {loading ? - : - failed ? - Sorry, I couldn't load the existing pauses : - healthCheckPauses.length === 0 ? - There are no current or upcoming pauses : - - r.createdAt.valueOf().toString()} - disableRowSelectionOnClick={true} - /> - - } - - - + {loading ? + : + failed ? + Sorry, I couldn't load the existing pauses : + healthCheckPauses.length === 0 ? + There are no current or upcoming pauses : + + r.createdAt.valueOf().toString()} + disableRowSelectionOnClick={true} + /> + + } + + + + } diff --git a/react/src/components/healthchecks/HealthChecks.tsx b/react/src/components/healthchecks/HealthChecks.tsx index ae4c26de..3a1da968 100644 --- a/react/src/components/healthchecks/HealthChecks.tsx +++ b/react/src/components/healthchecks/HealthChecks.tsx @@ -14,6 +14,8 @@ import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import CheckCircleOutlineRoundedIcon from '@mui/icons-material/CheckCircleOutlineRounded'; import ErrorOutlineRoundedIcon from '@mui/icons-material/ErrorOutlineRounded'; +import {Helmet} from "react-helmet"; +import {adminPageTitleSuffix} from "../../utils/common"; interface Props { portsByRegion: PortRegion[] @@ -33,46 +35,51 @@ export const HealthChecks = (props: Props) => { set() }, []); - return -

Health Checks

- {loading &&

Loading...

} - {failed &&

Failed to load health checks

} + return <> + + Health Checks {adminPageTitleSuffix} + + +

Health Checks

+ {loading &&

Loading...

} + {failed &&

Failed to load health checks

} - - - - - - - Region - - {healthChecks.healthChecks.map((healthCheck, index) => { - return - - {healthCheck.name} - + +
+ + + + + Region - })} - - - - {props.portsByRegion.map((portRegion, index) => { - const regionAlarms = portRegion.ports - .filter(port => healthCheckAlarms.some(alarm => alarm.port === port)) - .sort((a, b) => a.localeCompare(b)) - .map(port => healthCheckAlarms.find(alarm => alarm.port === port)) - .filter(alarm => !!alarm) as PortHealthCheckAlarms[] + {healthChecks.healthChecks.map((healthCheck, index) => { + return + + {healthCheck.name} + + + })} + + + + {props.portsByRegion.map((portRegion, index) => { + const regionAlarms = portRegion.ports + .filter(port => healthCheckAlarms.some(alarm => alarm.port === port)) + .sort((a, b) => a.localeCompare(b)) + .map(port => healthCheckAlarms.find(alarm => alarm.port === port)) + .filter(alarm => !!alarm) as PortHealthCheckAlarms[] - if (regionAlarms.length !== 0 && regionAlarms) - return - else - return <> - })} - -
-
-
+ if (regionAlarms.length !== 0 && regionAlarms) + return + else + return <> + })} + + + +
+ } function Row(props: { region: string, regionPortAlarms: PortHealthCheckAlarms[], healthChecks: HealthCheck[] }) { diff --git a/react/src/components/users/UsersList.tsx b/react/src/components/users/UsersList.tsx index 5581a696..67fa741d 100644 --- a/react/src/components/users/UsersList.tsx +++ b/react/src/components/users/UsersList.tsx @@ -9,6 +9,8 @@ import moment from "moment-timezone"; import {Breadcrumbs, Stack} from "@mui/material"; import Typography from "@mui/material/Typography"; import {Link} from "react-router-dom"; +import {adminPageTitleSuffix} from "../../utils/common"; +import {Helmet} from "react-helmet"; const formatDate = (param: GridValueFormatterParams) => { return moment(param?.value).format("YYYY-MM-DD HH:mm") @@ -68,19 +70,24 @@ export default function UsersList() { fetchUsers(); }, []); - return - - - Home - - Users - - - - - + return <> + + Users {adminPageTitleSuffix} + + + + + Home + + Users + + + + + + } diff --git a/react/src/store/featureGuides.ts b/react/src/store/featureGuides.ts index 34385a47..36132ad0 100644 --- a/react/src/store/featureGuides.ts +++ b/react/src/store/featureGuides.ts @@ -1,7 +1,7 @@ import axios from "axios"; import {enqueueSnackbar} from "notistack"; import {useEffect, useState} from "react"; -import {FeatureGuide} from "../components/featureguides/FeatureGuidesList"; +import {FeatureGuide} from "../components/featureguides/FeatureGuideList"; import ApiClient from "../services/ApiClient"; export const deleteFeatureGuide = (id: string) => { diff --git a/react/src/utils/common.ts b/react/src/utils/common.ts new file mode 100644 index 00000000..7740ff36 --- /dev/null +++ b/react/src/utils/common.ts @@ -0,0 +1,3 @@ + +export const customerPageTitleSuffix = ' - DRT' +export const adminPageTitleSuffix = ' – Admin - DRT' diff --git a/react/yarn.lock b/react/yarn.lock index bfd70c77..fc457d9b 100644 --- a/react/yarn.lock +++ b/react/yarn.lock @@ -3011,6 +3011,13 @@ dependencies: "@types/react" "*" +"@types/react-helmet@^6.1.11": + version "6.1.11" + resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.11.tgz#8cafcafff38f75361f451563ba7b406b0c5d3907" + integrity sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g== + dependencies: + "@types/react" "*" + "@types/react-test-renderer@^18.3.0": version "18.3.0" resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-18.3.0.tgz#839502eae70058a4ae161f63385a8e7929cef4c0" @@ -9503,7 +9510,7 @@ prompts@^2.0.1, prompts@^2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.0.0, prop-types@^15.6.2, prop-types@^15.8.1: +prop-types@^15.0.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -9656,6 +9663,21 @@ react-fast-compare@^2.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== +react-fast-compare@^3.1.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" + integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== + +react-helmet@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" + integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw== + dependencies: + object-assign "^4.1.1" + prop-types "^15.7.2" + react-fast-compare "^3.1.1" + react-side-effect "^2.1.0" + "react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" @@ -9792,6 +9814,11 @@ react-shallow-renderer@^16.15.0: object-assign "^4.1.1" react-is "^16.12.0 || ^17.0.0 || ^18.0.0" +react-side-effect@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.2.tgz#dc6345b9e8f9906dc2eeb68700b615e0b4fe752a" + integrity sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw== + react-test-renderer@^18.3.0: version "18.3.0" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-18.3.0.tgz#579dec2312b9841f7a0cafc1dfbdfdc582be0ea4" From e4fa7421d12f313779dccaa5960f2d37e56116e9 Mon Sep 17 00:00:00 2001 From: Rich Birch Date: Fri, 27 Sep 2024 11:06:55 +0100 Subject: [PATCH 2/4] DRTII-696 Add titles to remaining pages --- react/src/App.tsx | 86 ++-- react/src/components/RegionPage.tsx | 95 ++-- .../dropins/AddOrEditDropInSession.tsx | 11 +- .../dropins/DropInSessionRegistrations.tsx | 75 ++-- .../src/components/feedback/FeedbackForms.tsx | 38 +- .../regionalpressure/NationalDashboard.tsx | 98 +++++ .../regionalpressure/RegionalDashboard.tsx | 406 +++++++++++++++--- .../RegionalDashboardDetail.tsx | 359 ---------------- react/src/utils/StringUtils.ts | 6 +- 9 files changed, 607 insertions(+), 567 deletions(-) create mode 100644 react/src/components/regionalpressure/NationalDashboard.tsx delete mode 100644 react/src/components/regionalpressure/RegionalDashboardDetail.tsx diff --git a/react/src/App.tsx b/react/src/App.tsx index fe37be3e..b2ab4aa0 100644 --- a/react/src/App.tsx +++ b/react/src/App.tsx @@ -1,66 +1,66 @@ -import React, {useEffect} from 'react'; -import './App.css'; -import useMediaQuery from '@mui/material/useMediaQuery'; -import { useTheme } from '@mui/material/styles'; -import {Home} from './components/Home'; -import Alerts from './components/alerts/Alerts'; -import AccessRequests from './components/accessrequests/AccessRequests'; -import UsersList from './components/users/UsersList'; -import {Route, Routes} from "react-router-dom"; -import Loading from "./components/Loading"; -import Navigation from "./components/Navigation"; -import {useConfig} from "./store/config"; -import {Container} from "@mui/material"; -import {styled} from "@mui/material/styles"; -import {RegionPage} from "./components/RegionPage"; -import axios from "axios"; -import ApiClient from "./services/ApiClient"; -import {HealthCheckEditor} from "./components/healthcheckpauseseditor/HealthCheckPausesEditor"; -import {useUser} from "./store/user"; -import {DropInSessionsList} from "./components/dropins/DropInSessionsList"; -import {AddOrEditDropInSession} from "./components/dropins/AddOrEditDropInSession"; -import {DropInSessionRegistrations} from "./components/dropins/DropInSessionRegistrations"; -import {SnackbarProvider} from 'notistack'; -import Link from "@mui/material/Link"; -import {FeatureGuideList} from "./components/featureguides/FeatureGuideList"; -import {FeatureGuideAddOrEdit} from "./components/featureguides/FeatureGuideAddOrEdit"; -import {FeedbackForms} from "./components/feedback/FeedbackForms"; -import {FeedbackList} from "./components/feedback/FeedbackList"; -import DownloadManager from './components/downloadmanager/DownloadManager'; -import {ExportConfig} from "./components/ExportConfig"; -import {HealthChecks} from "./components/healthchecks/HealthChecks"; -import RegionalDashboard from './components/regionalpressure/RegionalDashboard'; -import RegionalDashboardDetail from './components/regionalpressure/RegionalDashboardDetail'; +import React, {useEffect} from 'react' +import './App.css' +import useMediaQuery from '@mui/material/useMediaQuery' +import { useTheme } from '@mui/material/styles' +import {Home} from './components/Home' +import Alerts from './components/alerts/Alerts' +import AccessRequests from './components/accessrequests/AccessRequests' +import UsersList from './components/users/UsersList' +import {Route, Routes} from "react-router-dom" +import Loading from "./components/Loading" +import Navigation from "./components/Navigation" +import {useConfig} from "./store/config" +import {Container} from "@mui/material" +import {styled} from "@mui/material/styles" +import {RegionPage} from "./components/RegionPage" +import axios from "axios" +import ApiClient from "./services/ApiClient" +import {HealthCheckEditor} from "./components/healthcheckpauseseditor/HealthCheckPausesEditor" +import {useUser} from "./store/user" +import {DropInSessionsList} from "./components/dropins/DropInSessionsList" +import {AddOrEditDropInSession} from "./components/dropins/AddOrEditDropInSession" +import {DropInSessionRegistrations} from "./components/dropins/DropInSessionRegistrations" +import {SnackbarProvider} from 'notistack' +import Link from "@mui/material/Link" +import {FeatureGuideList} from "./components/featureguides/FeatureGuideList" +import {FeatureGuideAddOrEdit} from "./components/featureguides/FeatureGuideAddOrEdit" +import {FeedbackForms} from "./components/feedback/FeedbackForms" +import {FeedbackList} from "./components/feedback/FeedbackList" +import DownloadManager from './components/downloadmanager/DownloadManager' +import {ExportConfig} from "./components/ExportConfig" +import {HealthChecks} from "./components/healthchecks/HealthChecks" +import RegionalDashboard from './components/regionalpressure/RegionalDashboard' +import NationalDashboard from "./components/regionalpressure/NationalDashboard" const StyledDiv = styled('div')(() => ({ textAlign: 'center', -})); +})) const StyledContainer = styled(Container)(() => ({ textAlign: 'left', minHeight: 500, display: 'inline-block', -})); +})) export const App = () => { const {user} = useUser() const {config} = useConfig() - const currentLocation = window.document.location; + const currentLocation = window.document.location const logoutLink = "/oauth2/sign_out?redirect=" + currentLocation.toString() - const theme = useTheme(); - const is_mobile = useMediaQuery(theme.breakpoints.down('md')); + const theme = useTheme() + const is_mobile = useMediaQuery(theme.breakpoints.down('md')) useEffect(() => { const trackUser = async () => axios .get(ApiClient.userTrackingEndPoint) .catch(reason => { - console.log('Unable to user tracking' + reason); + console.log('Unable to user tracking' + reason) }) - trackUser(); - }, []); + trackUser() + }, []) return (user.kind === "SignedInUser" && config.kind === "LoadedConfig") ? @@ -101,8 +101,8 @@ export const App = () => { }/> }/> } /> - } /> - } /> + } /> + } /> }/> }/> diff --git a/react/src/components/RegionPage.tsx b/react/src/components/RegionPage.tsx index 2c72647a..fa6625fd 100644 --- a/react/src/components/RegionPage.tsx +++ b/react/src/components/RegionPage.tsx @@ -12,6 +12,8 @@ import axios from "axios"; import {useParams} from "react-router-dom"; import {StringUtils} from "../utils/StringUtils"; import ApiClient from "../services/ApiClient"; +import {customerPageTitleSuffix} from "../utils/common"; +import {Helmet} from "react-helmet"; interface IProps { user: UserProfile; @@ -31,7 +33,7 @@ export const RegionPage = (props: IProps) => { const [downloads, setDownloads] = React.useState(undefined) - const { regionName } = useParams() as { regionName: string } + const {regionName = ''} = useParams() const fetchDownloads = () => { axios @@ -62,48 +64,53 @@ export const RegionPage = (props: IProps) => { return moment(date).format("DD/MM/YYYY") } - const sortedDownloads = downloads ? downloads.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1)) : undefined + const sortedDownloads = downloads ? downloads.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1)) : undefined - return
- - DRT - {StringUtils.ucFirst(regionName)} - - {props.user.roles.includes("rcc:" + regionName.toLowerCase()) ? - -

{StringUtils.ucFirst(regionName)} region

-

You can download an arrivals export covering all port terminals in - this region.

- -

Downloads

- {sortedDownloads ? - - Created - Date range - - {sortedDownloads.map(download => { - const downloadUrl = `${ApiClient.exportRegionEndpoint}/${download.region}/${download.createdAt}` - return <> - {formatDateDDMMYYYYHHmm(new Date(download.createdAt))} - - {formatDateDDMMYYYY(download.startDate)} - {formatDateDDMMYYYY(download.endDate)} - - - {download.status === 'complete' ? - Download : - download.status - } - - - })} - : -

No downloads yet.

- } -
: - -

You don't have access to this page. To request access please get in touch with us at {props.config.teamEmail}.

-
- } -
+ return <> + + {StringUtils.ucFirst(regionName)} - Regional Dashboard {customerPageTitleSuffix} + +
+ + DRT + {StringUtils.ucFirst(regionName)} + + {props.user.roles.includes("rcc:" + regionName.toLowerCase()) ? + +

{StringUtils.ucFirst(regionName)} region

+

You can download an arrivals export covering all port terminals in + this region.

+ +

Downloads

+ {sortedDownloads ? + + Created + Date range + + {sortedDownloads.map(download => { + const downloadUrl = `${ApiClient.exportRegionEndpoint}/${download.region}/${download.createdAt}` + return <> + {formatDateDDMMYYYYHHmm(new Date(download.createdAt))} + + {formatDateDDMMYYYY(download.startDate)} - {formatDateDDMMYYYY(download.endDate)} + + + {download.status === 'complete' ? + Download : + download.status + } + + + })} + : +

No downloads yet.

+ } +
: + +

You don't have access to this page. To request access please get in touch with us at {props.config.teamEmail}.

+
+ } +
+ } diff --git a/react/src/components/dropins/AddOrEditDropInSession.tsx b/react/src/components/dropins/AddOrEditDropInSession.tsx index 7b1c40bc..fd91a439 100644 --- a/react/src/components/dropins/AddOrEditDropInSession.tsx +++ b/react/src/components/dropins/AddOrEditDropInSession.tsx @@ -14,6 +14,8 @@ import {Link, useNavigate, useParams} from "react-router-dom"; import Typography from "@mui/material/Typography"; import {enqueueSnackbar} from "notistack"; import ApiClient from "../../services/ApiClient"; +import {adminPageTitleSuffix} from "../../utils/common"; +import {Helmet} from "react-helmet"; export function jsonDropInData(startTime: Moment | null, endTime: Moment | null, title: string, meetingLink: string) { const startTimeString = startTime?.valueOf() @@ -101,7 +103,11 @@ export function AddOrEditDropInSession() { } }; - return + return <> + + {sessionId ? 'Edit' : 'Add'} Drop-in session {adminPageTitleSuffix} + + Home @@ -109,7 +115,7 @@ export function AddOrEditDropInSession() { Drop-in sessions - Add session + {sessionId ? 'Edit' : 'Add'} session {errorText && {errorText}}
@@ -178,4 +184,5 @@ export function AddOrEditDropInSession() {
+ } diff --git a/react/src/components/dropins/DropInSessionRegistrations.tsx b/react/src/components/dropins/DropInSessionRegistrations.tsx index 8b274543..288b5ba2 100644 --- a/react/src/components/dropins/DropInSessionRegistrations.tsx +++ b/react/src/components/dropins/DropInSessionRegistrations.tsx @@ -11,6 +11,8 @@ import {Alert, DialogComponent} from "../DialogComponent"; import {enqueueSnackbar} from "notistack"; import ApiClient from "../../services/ApiClient"; import Typography from "@mui/material/Typography"; +import {adminPageTitleSuffix} from "../../utils/common"; +import {Helmet} from "react-helmet"; export interface DropInRegisteredUsers { email: string; @@ -101,38 +103,43 @@ export function DropInSessionRegistrations() { }) } - return - - - Home - - - Drop-in sessions - - Registrations :: {dropInSession?.title} - - setError(false)}> - setError(false)} severity="error" sx={{width: '100%'}}> - There was a problem booking drop-ins. Please try reloading the page. - - - - rowsData.email + '_' + rowsData.dropInId} - rows={rowsData} - columns={columns} - pageSizeOptions={[5]} - /> - - {unregister && setUnregister(false)} - onConfirm={() => { - removeRegistration(rowDetails?.dropInId as string, rowDetails?.email as string) - }} - />} - + return <> + + Drop-in session registrations {adminPageTitleSuffix} + + + + + Home + + + Drop-in sessions + + Registrations :: {dropInSession?.title} + + setError(false)}> + setError(false)} severity="error" sx={{width: '100%'}}> + There was a problem booking drop-ins. Please try reloading the page. + + + + rowsData.email + '_' + rowsData.dropInId} + rows={rowsData} + columns={columns} + pageSizeOptions={[5]} + /> + + {unregister && setUnregister(false)} + onConfirm={() => { + removeRegistration(rowDetails?.dropInId as string, rowDetails?.email as string) + }} + />} + + } diff --git a/react/src/components/feedback/FeedbackForms.tsx b/react/src/components/feedback/FeedbackForms.tsx index 4fc0a1a7..75f967d5 100644 --- a/react/src/components/feedback/FeedbackForms.tsx +++ b/react/src/components/feedback/FeedbackForms.tsx @@ -1,22 +1,24 @@ import React from 'react'; -import {Formik, Form, Field} from 'formik'; +import {Field, Form, Formik} from 'formik'; import { - RadioGroup, - FormControlLabel, - Radio, + Button, FormControl, + FormControlLabel, FormLabel, - Button, - Typography, - TextField + Radio, + RadioGroup, + Stack, + TextField, + Typography } from '@mui/material'; -import {Stack} from "@mui/material"; import Grid from "@mui/material/Grid"; import Link from "@mui/material/Link"; import axios, {AxiosResponse} from "axios"; import {useParams} from "react-router-dom"; import ApiClient from "../../services/ApiClient"; import drtTheme from "../../drtTheme"; +import {customerPageTitleSuffix} from "../../utils/common"; +import {Helmet} from "react-helmet"; interface FeedbackData { feedbackType: string; @@ -102,7 +104,7 @@ export function FeedbackForms() {

- + )} @@ -159,7 +161,7 @@ export function FeedbackForms() {

- + )} @@ -371,8 +373,8 @@ export function FeedbackForms() { You may now close this window. : - + } @@ -395,11 +397,15 @@ export function FeedbackForms() { } }; - return ( + return <> + + Feedback {customerPageTitleSuffix} + - DRT - Feedback + + DRT Feedback + {displayQuestion()} - ); + } diff --git a/react/src/components/regionalpressure/NationalDashboard.tsx b/react/src/components/regionalpressure/NationalDashboard.tsx new file mode 100644 index 00000000..1d00ba8a --- /dev/null +++ b/react/src/components/regionalpressure/NationalDashboard.tsx @@ -0,0 +1,98 @@ +import * as React from 'react'; +import {connect, MapDispatchToProps} from 'react-redux'; +import { + Box, + Grid, + CircularProgress, + Typography +} from "@mui/material"; +import {UserProfile} from "../../model/User"; +import {ConfigValues, PortRegion} from "../../model/Config"; +import {RootState} from '../../store/redux'; +import {requestPaxTotals} from './regionalPressureSagas'; +import {FormError} from '../../services/ValidationService'; +import RegionalPressureDates from './RegionalPressureDates'; +import RegionalPressureChart from './RegionalPressureChart'; +import RegionalPressureExport from './RegionalPressureExport'; +import RegionalPressureForm from './RegionalPressureForm'; +import {Helmet} from "react-helmet"; +import {customerPageTitleSuffix} from "../../utils/common"; + +interface NationalDashboardProps { + user: UserProfile; + config: ConfigValues; + errors: FormError[]; + status: string; + type?: string; + start?: string; + end?: string; +} + +const NationalDashboard = ({config, user, status}: NationalDashboardProps) => { + + let userPortsByRegion: PortRegion[] = config.portsByRegion.map(region => { + const userPorts: string[] = user.ports.filter(p => region.ports.includes(p)); + return {...region, ports: userPorts} as PortRegion + }).filter(r => r.ports.length > 0) + + const availablePorts = config.ports.map(port => port.iata); + + return <> + + National Dashboard {customerPageTitleSuffix} + + + + National Dashboard + Compare pax arrivals with previous year + + + + + {status === 'loading' && + + + + + } + + {status !== 'loading' && + +

Regional Overview

+
+ + + + + + + {userPortsByRegion.map(region => { + const regionPorts = region.name === 'Heathrow' ? ['LHR-T2', 'LHR-T2', 'LHR-T4', 'LHR-T5'] : region.ports + return + + + })} +
} +
+ +} +const mapDispatch = (dispatch: MapDispatchToProps) => { + return { + requestRegionExport: (userPorts: string[], availablePorts: string[], searchType: string, startDate: string, endDate: string, isExport: boolean) => { + dispatch(requestPaxTotals(userPorts, availablePorts, searchType, startDate, endDate, isExport)); + } + }; +}; + +const mapState = (state: RootState) => { + return { + errors: state.pressureDashboard?.errors, + type: state.pressureDashboard?.type, + startDate: state.pressureDashboard?.start, + endDate: state.pressureDashboard?.end, + status: state.pressureDashboard?.status, + }; +} + + +export default connect(mapState, mapDispatch)(NationalDashboard); diff --git a/react/src/components/regionalpressure/RegionalDashboard.tsx b/react/src/components/regionalpressure/RegionalDashboard.tsx index 08a8dd5c..6963c83f 100644 --- a/react/src/components/regionalpressure/RegionalDashboard.tsx +++ b/react/src/components/regionalpressure/RegionalDashboard.tsx @@ -1,94 +1,368 @@ import * as React from 'react'; -import {connect, MapDispatchToProps} from 'react-redux'; +import {connect} from 'react-redux'; +import {UserProfile} from "../../model/User"; +import {useParams} from 'react-router'; +import pattern from 'patternomaly' import { + Alert, Box, Grid, - CircularProgress, - Typography + Card, + CardContent, + CardHeader, + Button, + IconButton, + Stack, + FormControl, + FormGroup, + FormLabel, + FormControlLabel, + Checkbox, + useMediaQuery, + Theme } from "@mui/material"; -import {UserProfile} from "../../model/User"; -import {ConfigValues, PortRegion} from "../../model/Config"; +import {Link} from 'react-router-dom'; +import {ConfigValues} from "../../model/Config"; import {RootState} from '../../store/redux'; -import { requestPaxTotals } from './regionalPressureSagas'; -import { FormError } from '../../services/ValidationService'; +import drtTheme from '../../drtTheme'; +import {Chart} from 'react-chartjs-2'; +import { + Chart as ChartJS, + registerables, +} from 'chart.js'; +import 'chartjs-adapter-moment'; +import moment from 'moment'; + +ChartJS.register(...registerables); +import {ArrowBack} from '@mui/icons-material'; +import {TerminalDataPoint} from './regionalPressureSagas'; import RegionalPressureDates from './RegionalPressureDates'; -import RegionalPressureChart from './RegionalPressureChart'; -import RegionalPressureExport from './RegionalPressureExport'; import RegionalPressureForm from './RegionalPressureForm'; +import RegionalPressureExport from './RegionalPressureExport'; +import {getHistoricDateByDay} from './regionalPressureSagas'; +import {Helmet} from "react-helmet"; +import {customerPageTitleSuffix} from "../../utils/common"; +import {StringUtils} from "../../utils/StringUtils"; -interface RegionalPressureDashboardProps { - user: UserProfile; + +interface RegionalDashboardProps { config: ConfigValues; - errors: FormError[]; - status: string; - type?: string; - start?: string; - end?: string; + user: UserProfile; + title?: string; + interval?: string; + type: string; + portData: { + [key: string]: TerminalDataPoint[] + }; + historicPortData: { + [key: string]: TerminalDataPoint[] + }; } -const RegionalPressureDashboard = ({config, user, status}: RegionalPressureDashboardProps) => { - - let userPortsByRegion: PortRegion[] = config.portsByRegion.map(region => { - const userPorts: string[] = user.ports.filter(p => region.ports.includes(p)); - return {...region, ports: userPorts} as PortRegion - }).filter(r => r.ports.length > 0) +const RegionalDashboard = ({config, portData, historicPortData, interval, type}: RegionalDashboardProps) => { + const {region = ''} = useParams(); + let regionPorts = config.portsByRegion.filter((r) => r.name.toLowerCase() === region!.toLowerCase())[0].ports; + let title = `${region} Region`; + let portLabel = 'Airports:' + if (region === 'heathrow') { + title = 'Heathrow' + portLabel == 'Aiport terminals:' + regionPorts = ['LHR-T2', 'LHR-T3', 'LHR-T4', 'LHR-T5']; + } + regionPorts.sort(); + + const [visiblePorts, setVisiblePorts] = React.useState([...regionPorts]); const availablePorts = config.ports.map(port => port.iata); + const timeUnits = interval; - return ( - - - National Dashboard - Compare pax arrivals with previous year - - - + const is_mobile = useMediaQuery((theme: Theme) => theme.breakpoints.down('md')); + + const handleTogglePort = (port: string) => { + if (visiblePorts.includes(port)) { + const newPorts = [...visiblePorts]; + newPorts.splice(visiblePorts.indexOf(port), 1); + setVisiblePorts(newPorts) + } else { + setVisiblePorts([ + ...visiblePorts, + port + ]) + } + } - { status === 'loading' && - - - - - } - - { status !== 'loading' && - -

Regional Overview

+ return <> + + {StringUtils.ucFirst(region)} - Regional Dashboard {customerPageTitleSuffix} + + + + + + + +

{title}

- - + + + + + + + - - + + + {portLabel} + + + { + regionPorts && regionPorts.map((port: string) => { + return } + onClick={() => handleTogglePort(port)} + label={port.toUpperCase().replace("-", ' ')} + labelPlacement="end" + /> + }) + } + + + - { userPortsByRegion.map(region => { - const regionPorts = region.name === 'Heathrow' ? ['LHR-T2', 'LHR-T2', 'LHR-T4', 'LHR-T5'] : region.ports - return - - - })} - } + + + + + + {regionPorts && regionPorts.map((port: string) => { + const portName = port.replace("-", ' ') + return visiblePorts.includes(port) && portData[port] && ( + + + View {portName} arrivals + } + /> + + Pax exceed previous year at highlighted times + { + if (context.tooltipItems.length > 1) { + let pax = context.tooltipItems[0].parsed.y + let historicPax = context.tooltipItems[1].parsed.y + let percentage = 100 * (pax - historicPax) / historicPax + if (percentage > 0) { + return '#FFB3BA' + } else { + return '#C3E072' + } + } + return '#fff' + }, + footerFont: { + size: 16 + }, + callbacks: { + title: function (context): string[] { + let formattedPaxPercent = '' + if (context.length > 1) { + let pax = context[0].parsed.y + let historicPax = context[1].parsed.y + let percentage = 100 * (pax - historicPax) / historicPax + formattedPaxPercent = new Intl.NumberFormat("en-US", { + signDisplay: "exceptZero", + maximumSignificantDigits: 2 + + }).format(percentage); + percentage = isNaN(percentage) ? 0 : percentage; + } + return [`${formattedPaxPercent}% pax expected`] + }, + label: function (context): string[] { + let date = moment(context.parsed.x) + let dateFormat = timeUnits == 'hour' ? 'HH:mm ddd D MMM YYYY ' : 'ddd D MMM YYYY' + switch (context.dataset.label) { + case 'Pax arrivals': + return [`${date.format(dateFormat)}:`, ` ${context.parsed.y} pax`] + case 'Previous year': + return [`${getHistoricDateByDay(date).format(dateFormat)}:`, ` ${context.parsed.y} pax`] + default: + return [''] + } + } + } + } + }, + interaction: { + mode: 'nearest', + axis: 'x', + intersect: false, + }, + scales: { + x: { + position: 'bottom', + border: { + display: false + }, + type: 'time', + time: { + unit: timeUnits as "hour" + }, + grid: { + display: true, + drawOnChartArea: true, + drawTicks: true + }, + offset: true, + ticks: { + callback: (label) => { + let date = moment(label) + return timeUnits == 'hour' ? date.format('HH:mm') : date.format('ddd D MMM') + } + } + }, + y: { + type: 'linear', + min: 0, + grace: '10%', + grid: { + display: true, + }, + }, + } + }} + plugins={[ + { + id: "increase-legend-spacing", + beforeInit(chart) { + // Get reference to the original fit function + const originalFit = (chart.legend as any).fit; + // Override the fit function + (chart.legend as any).fit = function fit() { + // Call original function and bind scope in order to use `this` correctly inside it + originalFit.bind(chart.legend)(); + this.height += 20; + }; + } + } + ]} + data={{ + datasets: [ + { + label: `Pax arrivals`, + type: 'line', + backgroundColor: [ + pattern.draw('diagonal', '#C94900'), + ], + borderColor: '#005ea5', + borderDash: [0, 0], + borderWidth: 4, + pointStyle: 'rectRot', + pointRadius: 10, + pointHoverRadius: 12, + pointBackgroundColor: '#005ea5', + pointBorderColor: '#ffffff', + pointBorderWidth: 3, + pointHoverBorderWidth: 3, + pointHoverBorderColor: '#ffffff', + pointHoverBackgroundColor: '#0E2560', + xAxisID: 'x', + fill: { + target: '1', + below: 'transparent', + }, + data: portData[port].map((datapoint: TerminalDataPoint) => { + const pointDate = moment(datapoint.date) + if (interval === 'hour') { + pointDate.add(datapoint.hour, 'hours') + } + return { + x: pointDate.format('MM/DD/YYYY HH:mm'), + y: datapoint.totalPcpPax, + } + }) + }, + { + label: `Previous year`, + type: 'line', + borderColor: drtTheme.palette.grey[800], + borderDash: [5, 5], + borderWidth: 2, + pointStyle: 'circle', + pointRadius: 5, + pointHoverRadius: 8, + pointHoverBorderWidth: 3, + pointBackgroundColor: '#ffffff', + pointHoverBackgroundColor: '#ffffff', + data: historicPortData[port].map((datapoint: TerminalDataPoint, index: number) => { + const paxDate = moment(portData[port][index].date) + const pointDate = moment(datapoint.date) + let historicDayOffset = 0; + if (interval === 'hour') { + pointDate.set('date', paxDate.date()) + pointDate.add(datapoint.hour, 'hours') + } else { + historicDayOffset = moment.duration(pointDate.diff(moment(paxDate).subtract(1, 'y'))).asDays(); + } + return { + x: pointDate.add(1, 'year').subtract(historicDayOffset, 'days').format('MM/DD/YYYY HH:mm'), + y: datapoint.totalPcpPax, + } + }) + } + ] + }} + /> + + + + + ) + }) + } +
- ) - + + } -const mapDispatch = (dispatch :MapDispatchToProps) => { - return { - requestRegionExport: (userPorts: string[], availablePorts: string[], searchType: string, startDate: string, endDate: string, isExport: boolean) => { - dispatch(requestPaxTotals(userPorts, availablePorts, searchType, startDate, endDate, isExport)); - } - }; -}; + const mapState = (state: RootState) => { - return { + return { errors: state.pressureDashboard?.errors, - type: state.pressureDashboard?.type, startDate: state.pressureDashboard?.start, endDate: state.pressureDashboard?.end, - status: state.pressureDashboard?.status, - }; + portData: state.pressureDashboard?.portData, + historicPortData: state.pressureDashboard?.historicPortData, + interval: state.pressureDashboard?.interval, + type: state.pressureDashboard?.type, + }; } - -export default connect(mapState, mapDispatch)(RegionalPressureDashboard); +export default connect(mapState)(RegionalDashboard); diff --git a/react/src/components/regionalpressure/RegionalDashboardDetail.tsx b/react/src/components/regionalpressure/RegionalDashboardDetail.tsx deleted file mode 100644 index dd61c63a..00000000 --- a/react/src/components/regionalpressure/RegionalDashboardDetail.tsx +++ /dev/null @@ -1,359 +0,0 @@ -import * as React from 'react'; -import { connect } from 'react-redux'; -import { UserProfile } from "../../model/User"; -import { useParams } from 'react-router'; -import pattern from 'patternomaly' -import { - Alert, - Box, - Grid, - Card, - CardContent, - CardHeader, - Button, - IconButton, - Stack, - FormControl, - FormGroup, - FormLabel, - FormControlLabel, - Checkbox, - useMediaQuery, - Theme -} from "@mui/material"; -import { Link } from 'react-router-dom'; -import { ConfigValues } from "../../model/Config"; -import { RootState } from '../../store/redux'; -import drtTheme from '../../drtTheme'; -import { Chart } from 'react-chartjs-2'; -import { - Chart as ChartJS, - registerables, -} from 'chart.js'; -import 'chartjs-adapter-moment'; -import moment from 'moment'; -ChartJS.register(...registerables); -import { ArrowBack } from '@mui/icons-material'; -import { TerminalDataPoint } from './regionalPressureSagas'; -import RegionalPressureDates from './RegionalPressureDates'; -import RegionalPressureForm from './RegionalPressureForm'; -import RegionalPressureExport from './RegionalPressureExport'; -import { getHistoricDateByDay } from './regionalPressureSagas'; - - -interface RegionalPressureDetailProps { - config: ConfigValues; - user: UserProfile; - title?: string; - interval?: string; - type: string; - portData: { - [key: string]: TerminalDataPoint[] - }; - historicPortData: { - [key: string]: TerminalDataPoint[] - }; -} - -const RegionalPressureDetail = ({ config, portData, historicPortData, interval, type }: RegionalPressureDetailProps) => { - const { region } = useParams() || ''; - let regionPorts = config.portsByRegion.filter((r) => r.name.toLowerCase() === region!.toLowerCase())[0].ports; - let title = `${region} Region`; - let portLabel = 'Airports:' - if (region === 'heathrow') { - title = 'Heathrow' - portLabel == 'Aiport terminals:' - regionPorts = ['LHR-T2', 'LHR-T3', 'LHR-T4', 'LHR-T5']; - } - regionPorts.sort(); - - - const [visiblePorts, setVisiblePorts] = React.useState([...regionPorts]); - const availablePorts = config.ports.map(port => port.iata); - const timeUnits = interval; - - const is_mobile = useMediaQuery((theme: Theme) => theme.breakpoints.down('md')); - - const handleTogglePort = (port: string) => { - if (visiblePorts.includes(port)) { - const newPorts = [...visiblePorts]; - newPorts.splice(visiblePorts.indexOf(port), 1); - setVisiblePorts(newPorts) - } else { - setVisiblePorts([ - ...visiblePorts, - port - ]) - } - } - - return ( - - - - - - -

{ title }

-
-
- - - - - - - - - - { portLabel } - - - { - regionPorts && regionPorts.map((port: string) => { - return } - onClick={() => handleTogglePort(port)} - label={port.toUpperCase().replace("-", ' ')} - labelPlacement="end" - /> - }) - } - - - - - - - - - - {regionPorts && regionPorts.map((port: string) => { - const portName = port.replace("-", ' ') - return visiblePorts.includes(port) && portData[port] && ( - - - View {portName} arrivals - } - /> - - Pax exceed previous year at highlighted times - { - if (context.tooltipItems.length > 1) { - let pax = context.tooltipItems[0].parsed.y - let historicPax = context.tooltipItems[1].parsed.y - let percentage = 100 * (pax - historicPax) / historicPax - if (percentage > 0) { - return '#FFB3BA' - } else { - return '#C3E072' - } - } - return '#fff' - }, - footerFont: { - size: 16 - }, - callbacks: { - title: function(context): string[] { - let formattedPaxPercent = '' - if (context.length > 1) { - let pax = context[0].parsed.y - let historicPax = context[1].parsed.y - let percentage = 100 * (pax - historicPax) / historicPax - formattedPaxPercent = new Intl.NumberFormat("en-US", { - signDisplay: "exceptZero", - maximumSignificantDigits: 2 - - }).format(percentage); - percentage = isNaN(percentage) ? 0 : percentage; - } - return [`${formattedPaxPercent}% pax expected`] - }, - label: function(context) : string[] { - let date = moment(context.parsed.x) - let dateFormat = timeUnits == 'hour' ? 'HH:mm ddd D MMM YYYY ' : 'ddd D MMM YYYY' - switch (context.dataset.label) { - case 'Pax arrivals': - return [`${date.format(dateFormat)}:`,` ${context.parsed.y} pax`] - case 'Previous year': - return [`${getHistoricDateByDay(date).format(dateFormat)}:`,` ${context.parsed.y} pax`] - default: - return [''] - } - } - } - } - }, - interaction: { - mode: 'nearest', - axis: 'x', - intersect: false, - }, - scales: { - x: { - position: 'bottom', - border: { - display: false - }, - type: 'time', - time: { - unit: timeUnits as "hour" - }, - grid: { - display: true, - drawOnChartArea: true, - drawTicks: true - }, - offset: true, - ticks: { - callback: (label) => { - let date = moment(label) - return timeUnits == 'hour' ? date.format('HH:mm') : date.format('ddd D MMM') - } - } - }, - y: { - type: 'linear', - min: 0, - grace: '10%', - grid: { - display: true, - }, - }, - } - }} - plugins={[ - { - id: "increase-legend-spacing", - beforeInit(chart) { - // Get reference to the original fit function - const originalFit = (chart.legend as any).fit; - // Override the fit function - (chart.legend as any).fit = function fit() { - // Call original function and bind scope in order to use `this` correctly inside it - originalFit.bind(chart.legend)(); - this.height += 20; - }; - } - } - ]} - data={{ - datasets: [ - { - label: `Pax arrivals`, - type: 'line', - backgroundColor: [ - pattern.draw('diagonal', '#C94900'), - ], - borderColor: '#005ea5', - borderDash: [0, 0], - borderWidth: 4, - pointStyle: 'rectRot', - pointRadius: 10, - pointHoverRadius: 12, - pointBackgroundColor: '#005ea5', - pointBorderColor: '#ffffff', - pointBorderWidth: 3, - pointHoverBorderWidth: 3, - pointHoverBorderColor: '#ffffff', - pointHoverBackgroundColor: '#0E2560', - xAxisID: 'x', - fill: { - target: '1', - below: 'transparent', - }, - data: portData[port].map((datapoint: TerminalDataPoint) => { - const pointDate = moment(datapoint.date) - if (interval === 'hour') { - pointDate.add(datapoint.hour, 'hours') - } - return { - x: pointDate.format('MM/DD/YYYY HH:mm'), - y: datapoint.totalPcpPax, - } - }) - }, - { - label: `Previous year`, - type: 'line', - borderColor: drtTheme.palette.grey[800], - borderDash: [5, 5], - borderWidth: 2, - pointStyle: 'circle', - pointRadius: 5, - pointHoverRadius: 8, - pointHoverBorderWidth: 3, - pointBackgroundColor: '#ffffff', - pointHoverBackgroundColor: '#ffffff', - data: historicPortData[port].map((datapoint: TerminalDataPoint, index: number) => { - const paxDate = moment(portData[port][index].date) - const pointDate = moment(datapoint.date) - let historicDayOffset = 0; - if (interval === 'hour') { - pointDate.set('date', paxDate.date()) - pointDate.add(datapoint.hour, 'hours') - } else { - historicDayOffset = moment.duration(pointDate.diff(moment(paxDate).subtract(1,'y'))).asDays(); - } - return { - x: pointDate.add(1, 'year').subtract(historicDayOffset, 'days').format('MM/DD/YYYY HH:mm'), - y: datapoint.totalPcpPax, - } - }) - } - ] - }} - /> - - - - - ) - }) - } - -
- ) - -} - - -const mapState = (state: RootState) => { - return { - errors: state.pressureDashboard?.errors, - startDate: state.pressureDashboard?.start, - endDate: state.pressureDashboard?.end, - portData: state.pressureDashboard?.portData, - historicPortData: state.pressureDashboard?.historicPortData, - interval: state.pressureDashboard?.interval, - type: state.pressureDashboard?.type, - }; -} - -export default connect(mapState)(RegionalPressureDetail); diff --git a/react/src/utils/StringUtils.ts b/react/src/utils/StringUtils.ts index 363ef3ce..7058da7c 100644 --- a/react/src/utils/StringUtils.ts +++ b/react/src/utils/StringUtils.ts @@ -1,5 +1,5 @@ export const StringUtils = { - ucFirst: (str: string) => { - return str.charAt(0).toUpperCase() + str.slice(1) - } + ucFirst: (str: string) => { + return str.charAt(0).toUpperCase() + str.slice(1) + } } From 47252f4a1e2e6157003cbc959ef38b5f0b066021 Mon Sep 17 00:00:00 2001 From: Rich Birch Date: Mon, 30 Sep 2024 09:53:56 +0100 Subject: [PATCH 3/4] DRTII-696 Update drt-lib --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index c23e0275..4f774a7d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ import sbt.Keys.resolvers -lazy val drtLibVersion = "v858" +lazy val drtLibVersion = "v898" lazy val drtCiriumVersion = "203" lazy val akkaHttpVersion = "10.5.3" lazy val akkaVersion = "2.8.5" From 71fc6cabaa448ac23d840a46d3bd539cc9a1926c Mon Sep 17 00:00:00 2001 From: Rich Birch Date: Mon, 30 Sep 2024 09:56:52 +0100 Subject: [PATCH 4/4] DRTII-696 Update drt-lib --- src/main/scala/uk/gov/homeoffice/drt/Server.scala | 1 + src/main/scala/uk/gov/homeoffice/drt/db/ProdDatabase.scala | 1 + .../scala/uk/gov/homeoffice/drt/routes/FeedbackRoutes.scala | 3 ++- .../MockScheduledHealthCheckPausePersistence.scala | 2 +- .../uk/gov/homeoffice/drt/routes/FeedbackRoutesSpec.scala | 4 +++- 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/scala/uk/gov/homeoffice/drt/Server.scala b/src/main/scala/uk/gov/homeoffice/drt/Server.scala index 0c1182f9..05f7774a 100644 --- a/src/main/scala/uk/gov/homeoffice/drt/Server.scala +++ b/src/main/scala/uk/gov/homeoffice/drt/Server.scala @@ -14,6 +14,7 @@ import akka.stream.Materializer import akka.util.Timeout import org.slf4j.LoggerFactory import uk.gov.homeoffice.drt.db._ +import uk.gov.homeoffice.drt.db.dao.UserFeedbackDao import uk.gov.homeoffice.drt.healthchecks._ import uk.gov.homeoffice.drt.notifications.{EmailClient, EmailNotifications, SlackClient} import uk.gov.homeoffice.drt.persistence.{ExportPersistenceImpl, ScheduledHealthCheckPausePersistenceImpl} diff --git a/src/main/scala/uk/gov/homeoffice/drt/db/ProdDatabase.scala b/src/main/scala/uk/gov/homeoffice/drt/db/ProdDatabase.scala index 17e84dfb..fcdc7157 100644 --- a/src/main/scala/uk/gov/homeoffice/drt/db/ProdDatabase.scala +++ b/src/main/scala/uk/gov/homeoffice/drt/db/ProdDatabase.scala @@ -3,6 +3,7 @@ package uk.gov.homeoffice.drt.db import slick.dbio.{DBIOAction, NoStream} import slick.jdbc.JdbcProfile import slick.lifted.TableQuery +import uk.gov.homeoffice.drt.db.tables.UserFeedbackTable import scala.concurrent.Future diff --git a/src/main/scala/uk/gov/homeoffice/drt/routes/FeedbackRoutes.scala b/src/main/scala/uk/gov/homeoffice/drt/routes/FeedbackRoutes.scala index 825b522b..ad5d917d 100644 --- a/src/main/scala/uk/gov/homeoffice/drt/routes/FeedbackRoutes.scala +++ b/src/main/scala/uk/gov/homeoffice/drt/routes/FeedbackRoutes.scala @@ -9,7 +9,8 @@ import akka.http.scaladsl.server.Route import akka.stream.scaladsl.Source import akka.util.ByteString import spray.json.{RootJsonFormat, enrichAny} -import uk.gov.homeoffice.drt.db.{UserFeedbackDao, UserFeedbackRow} +import uk.gov.homeoffice.drt.db.dao.UserFeedbackDao +import uk.gov.homeoffice.drt.db.tables.UserFeedbackRow import uk.gov.homeoffice.drt.json.DefaultTimeJsonProtocol import java.sql.Timestamp diff --git a/src/test/scala/uk/gov/homeoffice/drt/persistence/MockScheduledHealthCheckPausePersistence.scala b/src/test/scala/uk/gov/homeoffice/drt/persistence/MockScheduledHealthCheckPausePersistence.scala index 87de729c..24d65daa 100644 --- a/src/test/scala/uk/gov/homeoffice/drt/persistence/MockScheduledHealthCheckPausePersistence.scala +++ b/src/test/scala/uk/gov/homeoffice/drt/persistence/MockScheduledHealthCheckPausePersistence.scala @@ -11,7 +11,7 @@ object MockScheduledHealthCheckPausePersistence extends ScheduledHealthCheckPaus } override def get(maybeAfter: Option[Long]): Future[Seq[ScheduledPause]] = Future.successful(pauses) -0 + override def delete(from: Long, to: Long): Future[Int] = { pauses = pauses.filter(p => p.startsAt.getMillis != from && p.endsAt.getMillis != to) Future.successful(1) diff --git a/src/test/scala/uk/gov/homeoffice/drt/routes/FeedbackRoutesSpec.scala b/src/test/scala/uk/gov/homeoffice/drt/routes/FeedbackRoutesSpec.scala index 32ea5258..6531328c 100644 --- a/src/test/scala/uk/gov/homeoffice/drt/routes/FeedbackRoutesSpec.scala +++ b/src/test/scala/uk/gov/homeoffice/drt/routes/FeedbackRoutesSpec.scala @@ -15,7 +15,9 @@ import slick.dbio.DBIO import slick.jdbc.PostgresProfile.api._ import spray.json._ import uk.gov.homeoffice.drt.auth.Roles.BorderForceStaff -import uk.gov.homeoffice.drt.db.{TestDatabase, UserFeedbackDao, UserFeedbackRow} +import uk.gov.homeoffice.drt.db.TestDatabase +import uk.gov.homeoffice.drt.db.dao.UserFeedbackDao +import uk.gov.homeoffice.drt.db.tables.UserFeedbackRow import java.sql.Timestamp import java.time.Instant