Skip to content

Commit

Permalink
Add safe zone banner
Browse files Browse the repository at this point in the history
  • Loading branch information
mrica-equinor committed Mar 22, 2024
1 parent bbfb666 commit 06d10c7
Show file tree
Hide file tree
Showing 15 changed files with 179 additions and 54 deletions.
17 changes: 15 additions & 2 deletions frontend/src/components/Alerts/AlertsBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,34 @@ const Horizontal = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
`

const Center = styled.div`
align-items: center;
`

export enum AlertCategory {
ERROR,
WARNING,
SUCCESS,
}

interface AlertProps {
children: ReactNode
dismissAlert: () => void
alertCategory: AlertCategory
}

export const AlertBanner = ({ children, dismissAlert }: AlertProps) => {
export const AlertBanner = ({ children, dismissAlert, alertCategory }: AlertProps) => {
let bannerColor = tokens.colors.ui.background__danger.hex

if (alertCategory === AlertCategory.WARNING) bannerColor = tokens.colors.interactive.warning__resting.hex
if (alertCategory === AlertCategory.SUCCESS) bannerColor = tokens.colors.interactive.success__resting.hex

return (
<>
<StyledCard variant="danger" style={{ boxShadow: tokens.elevation.raised }}>
<StyledCard style={{ boxShadow: tokens.elevation.raised, backgroundColor: bannerColor }}>
<Horizontal>
<Center>{children}</Center>
<Button variant="ghost_icon" onClick={dismissAlert}>
Expand Down
47 changes: 35 additions & 12 deletions frontend/src/components/Contexts/AlertContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,29 @@ import { BlockedRobotAlertContent } from 'components/Alerts/BlockedRobotAlert'
import { RobotStatus } from 'models/Robot'
import { FailedAlertContent } from 'components/Alerts/FailedAlertContent'
import { convertUTCDateToLocalDate } from 'utils/StringFormatting'

type AlertDictionaryType = { [key in AlertType]?: { content: ReactNode | undefined; dismissFunction: () => void } }
import { AlertCategory } from 'components/Alerts/AlertsBanner'

export enum AlertType {
MissionFail,
RequestFail,
SafeZoneFail,
BlockedRobot,
RequestSafeZone,
DismissSafeZone,
}

const alertTypeEnumMap: { [key: string]: AlertType } = {
safeZoneFailure: AlertType.SafeZoneFail,
scheduleFailure: AlertType.RequestFail,
}

type AlertDictionaryType = {
[key in AlertType]?: { content: ReactNode | undefined; dismissFunction: () => void; alertCategory: AlertCategory }
}

interface IAlertContext {
alerts: AlertDictionaryType
setAlert: (source: AlertType, alert: ReactNode) => void
setAlert: (source: AlertType, alert: ReactNode, category: AlertCategory) => void
clearAlerts: () => void
clearAlert: (source: AlertType) => void
}
Expand All @@ -39,7 +44,7 @@ interface Props {

const defaultAlertInterface = {
alerts: {},
setAlert: (source: AlertType, alert: ReactNode) => {},
setAlert: (source: AlertType, alert: ReactNode, category: AlertCategory) => {},
clearAlerts: () => {},
clearAlert: (source: AlertType) => {},
}
Expand All @@ -61,17 +66,26 @@ export const AlertProvider: FC<Props> = ({ children }) => {
const maxTimeInterval: number = 60
const dismissMissionFailTimeKey: string = 'lastMissionFailDismissalTime'

const setAlert = (source: AlertType, alert: ReactNode) =>
setAlerts({ ...alerts, [source]: { content: alert, dismissFunction: () => clearAlert(source) } })
const setAlert = (source: AlertType, alert: ReactNode, category: AlertCategory) => {
setAlerts((oldAlerts) => {
return {
...oldAlerts,
[source]: { content: alert, dismissFunction: () => clearAlert(source), alertCategory: category },
}
})
}

const clearAlerts = () => setAlerts({})

const clearAlert = (source: AlertType) => {
if (source === AlertType.MissionFail)
sessionStorage.setItem(dismissMissionFailTimeKey, JSON.stringify(Date.now()))
let newAlerts = { ...alerts }
delete newAlerts[source]
setAlerts(newAlerts)

setAlerts((oldAlerts) => {
let newAlerts = { ...oldAlerts }
delete newAlerts[source]
return newAlerts
})
}

const getLastDismissalTime = (): Date => {
Expand Down Expand Up @@ -151,7 +165,8 @@ export const AlertProvider: FC<Props> = ({ children }) => {
}
setAlert(
alertType,
<FailedAlertContent title={backendAlert.alertTitle} message={backendAlert.alertMessage} />
<FailedAlertContent title={backendAlert.alertTitle} message={backendAlert.alertMessage} />,
AlertCategory.ERROR
)
})
}
Expand All @@ -160,7 +175,11 @@ export const AlertProvider: FC<Props> = ({ children }) => {

useEffect(() => {
if (newFailedMissions.length > 0) {
setAlert(AlertType.MissionFail, <FailedMissionAlertContent missions={newFailedMissions} />)
setAlert(
AlertType.MissionFail,
<FailedMissionAlertContent missions={newFailedMissions} />,
AlertCategory.ERROR
)
setNewFailedMissions([])
}
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand All @@ -181,7 +200,11 @@ export const AlertProvider: FC<Props> = ({ children }) => {

if (isBlockedRobotNamesModifyed) {
if (newBlockedRobotNames.length > 0) {
setAlert(AlertType.BlockedRobot, <BlockedRobotAlertContent robotNames={newBlockedRobotNames} />)
setAlert(
AlertType.BlockedRobot,
<BlockedRobotAlertContent robotNames={newBlockedRobotNames} />,
AlertCategory.WARNING
)
} else {
clearAlert(AlertType.BlockedRobot)
}
Expand Down
48 changes: 39 additions & 9 deletions frontend/src/components/Contexts/SafeZoneContext.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,64 @@
import { createContext, FC, useContext, useState } from 'react'
import { createContext, FC, useContext, useEffect, useState } from 'react'
import { useRobotContext } from './RobotContext'
import { useInstallationContext } from './InstallationContext'
import { AlertType, useAlertContext } from './AlertContext'
import { SafeZoneBanner } from 'components/Pages/FrontPage/MissionOverview/SafeZoneBanner'
import { AlertCategory } from 'components/Alerts/AlertsBanner'

interface ISafeZoneContext {
safeZoneStatus: boolean
switchSafeZoneStatus: (newSafeZoneStatus: boolean) => void
}

interface Props {
children: React.ReactNode
}

const defaultSafeZoneInterface = {
safeZoneStatus: JSON.parse(localStorage.getItem('safeZoneStatus') ?? 'false'),
switchSafeZoneStatus: (newSafeZoneStatus: boolean) => {},
safeZoneStatus: false,
}

export const SafeZoneContext = createContext<ISafeZoneContext>(defaultSafeZoneInterface)

export const SafeZoneProvider: FC<Props> = ({ children }) => {
const [safeZoneStatus, setSafeZoneStatus] = useState<boolean>(defaultSafeZoneInterface.safeZoneStatus)
const { enabledRobots } = useRobotContext()
const { installationCode } = useInstallationContext()
const { setAlert, clearAlert } = useAlertContext()

const switchSafeZoneStatus = (newSafeZoneStatus: boolean) => {
localStorage.setItem('safeZoneStatus', String(newSafeZoneStatus))
setSafeZoneStatus(newSafeZoneStatus)
}
useEffect(() => {
const missionQueueFozenStatus = enabledRobots
.filter(
(robot) =>
robot.currentInstallation.installationCode.toLocaleLowerCase() ===
installationCode.toLocaleLowerCase()
)
.map((robot) => robot.missionQueueFrozen)
.filter((status) => status === true)

if (missionQueueFozenStatus.length > 0 && safeZoneStatus === false) {
setSafeZoneStatus((oldStatus) => !oldStatus)
clearAlert(AlertType.DismissSafeZone)
setAlert(
AlertType.RequestSafeZone,
<SafeZoneBanner alertCategory={AlertCategory.WARNING} />,
AlertCategory.WARNING
)
} else if (missionQueueFozenStatus.length === 0 && safeZoneStatus === true) {
setSafeZoneStatus((oldStatus) => !oldStatus)
clearAlert(AlertType.RequestSafeZone)
setAlert(
AlertType.DismissSafeZone,
<SafeZoneBanner alertCategory={AlertCategory.SUCCESS} />,
AlertCategory.SUCCESS
)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [enabledRobots])

return (
<SafeZoneContext.Provider
value={{
safeZoneStatus,
switchSafeZoneStatus,
}}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Robot } from 'models/Robot'
import { useEffect, useState } from 'react'
import { ScheduleMissionWithLocalizationVerificationDialog } from 'components/Displays/ConfirmScheduleDialogs/LocalizationVerification/ScheduleMissionWithLocalizationVerification'
import { useRobotContext } from 'components/Contexts/RobotContext'
import {
InsufficientBatteryDialog,
InsufficientPressureDialog,
} from 'components/Displays/ConfirmScheduleDialogs/InsufficientValueDialogs'
import { ScheduleMissionWithLocalizationVerificationDialog } from './LocalizationVerification/ScheduleMissionWithLocalizationVerification'

interface ConfirmScheduleDialogProps {
scheduleMissions: () => void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { AlertType, useAlertContext } from 'components/Contexts/AlertContext'
import { FailedRequestAlertContent } from 'components/Alerts/FailedRequestAlert'
import { Mission } from 'models/Mission'
import { ScheduleMissionWithConfirmDialogs } from '../ConfirmScheduleDialogs/ConfirmScheduleDialog'
import { AlertCategory } from 'components/Alerts/AlertsBanner'

const Centered = styled.div`
display: flex;
Expand Down Expand Up @@ -48,7 +49,8 @@ export const MissionRestartButton = ({ mission, hasFailedTasks }: MissionProps)
.catch(() =>
setAlert(
AlertType.RequestFail,
<FailedRequestAlertContent translatedMessage={TranslateText('Failed to rerun mission')} />
<FailedRequestAlertContent translatedMessage={TranslateText('Failed to rerun mission')} />,
AlertCategory.ERROR
)
)
setIsLocationVerificationOpen(false)
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { AlertBanner } from 'components/Alerts/AlertsBanner'
import { FrontPageSectionId } from 'models/FrontPageSectionId'

const StyledTopBar = styled(TopBar)`
margin-bottom: 2rem;
align-items: center;
box-shadow: none;
`
Expand All @@ -35,6 +34,7 @@ const StyledAlertList = styled.div`
export const Header = ({ page }: { page: string }) => {
const { alerts } = useAlertContext()
const { installationName } = useInstallationContext()

return (
<>
<StyledTopBar id={FrontPageSectionId.TopBar}>
Expand Down Expand Up @@ -67,7 +67,7 @@ export const Header = ({ page }: { page: string }) => {
{Object.entries(alerts).length > 0 && installationName && page !== 'root' && (
<StyledAlertList>
{Object.entries(alerts).map(([key, value]) => (
<AlertBanner key={key} dismissAlert={value.dismissFunction}>
<AlertBanner key={key} dismissAlert={value.dismissFunction} alertCategory={value.alertCategory}>
{value.content}
</AlertBanner>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useInstallationContext } from 'components/Contexts/InstallationContext'
import { AlertType, useAlertContext } from 'components/Contexts/AlertContext'
import { FailedRequestAlertContent } from 'components/Alerts/FailedRequestAlert'
import { FrontPageSectionId } from 'models/FrontPageSectionId'
import { AlertCategory } from 'components/Alerts/AlertsBanner'

const StyledMissionView = styled.div`
display: grid;
Expand Down Expand Up @@ -39,7 +40,8 @@ export const MissionQueueView = (): JSX.Element => {
BackendAPICaller.deleteMission(mission.id).catch((_) =>
setAlert(
AlertType.RequestFail,
<FailedRequestAlertContent translatedMessage={TranslateText('Failed to delete mission from queue')} />
<FailedRequestAlertContent translatedMessage={TranslateText('Failed to delete mission from queue')} />,
AlertCategory.ERROR
)
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Button, Icon, Typography } from '@equinor/eds-core-react'
import { AlertCategory } from 'components/Alerts/AlertsBanner'
import { useLanguageContext } from 'components/Contexts/LanguageContext'
import styled from 'styled-components'

const StyledDiv = styled.div`
flex-direction: column;
`

const StyledAlertTitle = styled.div`
display: flex;
gap: 0.3em;
align-items: flex-end;
`

const StyledButton = styled(Button)`
text-align: left;
height: auto;
padding: 5px 5px;
`

interface SafeZoneBannerProps {
alertCategory: AlertCategory
}

export const SafeZoneBanner = ({ alertCategory }: SafeZoneBannerProps): JSX.Element => {
const { TranslateText } = useLanguageContext()

return (
<StyledDiv>
<StyledAlertTitle>
<Icon name="error_outlined" />
<Typography>
{alertCategory === AlertCategory.WARNING ? TranslateText('WARNING') : TranslateText('INFO')}
</Typography>
</StyledAlertTitle>
<StyledButton variant="ghost" color="secondary">
{alertCategory === AlertCategory.WARNING
? TranslateText('Safe zone banner text')
: TranslateText('Dismiss safe zone banner text')}
</StyledButton>
</StyledDiv>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { BackendAPICaller } from 'api/ApiCaller'
import { useMissionsContext } from 'components/Contexts/MissionRunsContext'
import { AlertType, useAlertContext } from 'components/Contexts/AlertContext'
import { FailedRequestAlertContent } from 'components/Alerts/FailedRequestAlert'
import { AlertCategory } from 'components/Alerts/AlertsBanner'

const StyledMissionDialog = styled.div`
display: flex;
Expand Down Expand Up @@ -58,7 +59,8 @@ export const SelectMissionsToScheduleDialog = ({ echoMissionsList, closeDialog }
translatedMessage={
TranslateText('Failed to schedule mission') + ` '${mission.name}'. ${e.message}`
}
/>
/>,
AlertCategory.ERROR
)
setLoadingMissionSet((currentSet: Set<string>) => {
const updatedSet: Set<string> = new Set(currentSet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,20 +145,24 @@ export const StopRobotDialog = (): JSX.Element => {
<StyledDialog open={isStopRobotDialogOpen} isDismissable>
<Dialog.Header>
<Dialog.Title>
<Typography variant="h5">{TranslateText('Send robots to safe zone') + '?'}</Typography>
<Typography variant="h5">
{!safeZoneStatus
? TranslateText('Send robots to safe zone') + '?'
: TranslateText('Dismiss robots from safe zone') + '?'}
</Typography>
</Dialog.Title>
</Dialog.Header>
<Dialog.CustomContent>
<StyledText>
<Typography variant="body_long">
{!safeZoneStatus
? TranslateText('Send robots to safe zone long text')
: TranslateText('Dismiss robots from safe zone') + '?'}
: TranslateText('Dismiss robots from safe zone long text')}
</Typography>
<Typography variant="body_long">
{!safeZoneStatus
? TranslateText('Send robots to safe confirmation text')
: TranslateText('Dismiss robots from safe zone long text')}
: TranslateText('Dismiss robots from safe confirmation text')}
</Typography>
</StyledText>
</Dialog.CustomContent>
Expand Down
Loading

0 comments on commit 06d10c7

Please sign in to comment.