diff --git a/backend/api/Controllers/AreaController.cs b/backend/api/Controllers/AreaController.cs index a6789ec30..ee98d5628 100644 --- a/backend/api/Controllers/AreaController.cs +++ b/backend/api/Controllers/AreaController.cs @@ -272,7 +272,7 @@ public async Task> GetAreaById([FromRoute] string id) } /// - /// Lookup area by specified id. + /// Lookup area by specified deck id. /// [HttpGet] [Authorize(Roles = Role.Any)] diff --git a/backend/api/Controllers/MissionDefinitionController.cs b/backend/api/Controllers/MissionDefinitionController.cs index f8e7f116f..7ffee2274 100644 --- a/backend/api/Controllers/MissionDefinitionController.cs +++ b/backend/api/Controllers/MissionDefinitionController.cs @@ -71,10 +71,10 @@ [FromQuery] MissionDefinitionQueryStringParameters parameters } /// - /// List all mission definitions in the Flotilla database + /// List all consended mission definitions in the Flotilla database /// /// - /// This query gets all mission definitions + /// This query gets all consended mission definitions /// [HttpGet("condensed")] [Authorize(Roles = Role.Any)] diff --git a/frontend/src/api/ApiCaller.tsx b/frontend/src/api/ApiCaller.tsx index 1736565e9..bcce63977 100644 --- a/frontend/src/api/ApiCaller.tsx +++ b/frontend/src/api/ApiCaller.tsx @@ -431,7 +431,7 @@ export class BackendAPICaller { return result.content } - static async getAreadByDeckID(deckId: string): Promise { + static async getAreasByDeckId(deckId: string): Promise { const path: string = 'areas/deck/' + deckId const result = await this.GET(path).catch((e) => { console.error(`Failed to GET /${path}: ` + e) diff --git a/frontend/src/components/Pages/InspectionPage/DeckCards.tsx b/frontend/src/components/Pages/InspectionPage/DeckCards.tsx new file mode 100644 index 000000000..e42fee90f --- /dev/null +++ b/frontend/src/components/Pages/InspectionPage/DeckCards.tsx @@ -0,0 +1,146 @@ +import { Deck } from 'models/Deck' +import { DeckAreas, DeckMissionType, Inspection, ScheduledMissionType } from './InspectionSection' +import { useLanguageContext } from 'components/Contexts/LanguageContext' +import { CardMissionInformation, StyledDict, compareInspections, getDeadlineInspection } from './InspectionUtilities' +import { Button, Icon, Tooltip, Typography } from '@equinor/eds-core-react' +import { Icons } from 'utils/icons' +import { tokens } from '@equinor/eds-tokens' +import { useEffect, useState } from 'react' +import { BackendAPICaller } from 'api/ApiCaller' + +interface IDeckCardProps { + deckMissions: DeckMissionType + setSelectedDeck: React.Dispatch> + selectedDeck: Deck | undefined + ongoingMissions: ScheduledMissionType + handleScheduleAll: (inspections: Inspection[]) => void +} + +export function DeckCards({ + deckMissions, + setSelectedDeck, + selectedDeck, + ongoingMissions, + handleScheduleAll, +}: IDeckCardProps) { + const { TranslateText } = useLanguageContext() + const [areas, setAreas] = useState({}) + + const getCardColor = (deckId: string) => { + const inspections = deckMissions[deckId].inspections + if (inspections.length === 0) return 'gray' + const sortedInspections = inspections.sort(compareInspections) + + if (sortedInspections.length === 0) return 'green' + + const nextInspection = sortedInspections[0] + + if (!nextInspection.deadline) { + if (!nextInspection.missionDefinition.inspectionFrequency) return 'gray' + else return 'red' + } + + return getDeadlineInspection(nextInspection.deadline) + } + + useEffect(() => { + const newAreas: DeckAreas = {} + + Object.keys(deckMissions).forEach((deckId) => { + BackendAPICaller.getAreasByDeckId(deckMissions[deckId].deck.id).then((areas) => { + const formattedAreaNames = areas + .map((area) => { + return area.areaName.toLocaleUpperCase() + }) + .sort() + .join(' | ') + newAreas[deckMissions[deckId].deck.id] = { + areaString: formattedAreaNames, + } + }) + }) + setAreas(newAreas) + }, [deckMissions]) + + return ( + <> + + {Object.keys(deckMissions).length > 0 ? ( + Object.keys(deckMissions).map((deckId) => ( + + + 0 + ? () => setSelectedDeck(deckMissions[deckId].deck) + : undefined + } + style={ + selectedDeck === deckMissions[deckId].deck + ? { border: `solid ${getCardColor(deckId)} 2px` } + : {} + } + > + + + + {deckMissions[deckId].deck.deckName.toString()} + + {deckMissions[deckId].inspections + .filter((i) => + Object.keys(ongoingMissions).includes(i.missionDefinition.id) + ) + .map((inspection) => ( + + + {TranslateText('InProgress')} + + ))} + + {Object.keys(areas).includes(deckId) && ( + {areas[deckId].areaString} + )} + {deckMissions[deckId].inspections && ( + + )} + + + 0 + ? '' + : TranslateText('No planned inspection') + } + > + + + + + + )) + ) : ( + + + {TranslateText('No deck inspections available')} + + + )} + + + ) +} diff --git a/frontend/src/components/Pages/InspectionPage/InspectionSection.tsx b/frontend/src/components/Pages/InspectionPage/InspectionSection.tsx index 7159b7ee3..860558de8 100644 --- a/frontend/src/components/Pages/InspectionPage/InspectionSection.tsx +++ b/frontend/src/components/Pages/InspectionPage/InspectionSection.tsx @@ -1,108 +1,13 @@ -import { Card, Typography, Button, Icon, Tooltip } from '@equinor/eds-core-react' -import styled from 'styled-components' -import { useLanguageContext } from 'components/Contexts/LanguageContext' import { useState, useEffect } from 'react' import { BackendAPICaller } from 'api/ApiCaller' import { Deck } from 'models/Deck' import { useInstallationContext } from 'components/Contexts/InstallationContext' -import { tokens } from '@equinor/eds-tokens' import { CondensedMissionDefinition } from 'models/MissionDefinition' -import { ScheduleMissionDialog } from './ScheduleMissionDialog' -import { getDeadlineInDays, getInspectionDeadline } from 'utils/StringFormatting' +import { ScheduleMissionDialog } from './ScheduleMissionDialogs' +import { getInspectionDeadline } from 'utils/StringFormatting' import { InspectionTable } from './InspectionTable' -import { Icons } from 'utils/icons' - -const StyledCard = styled(Card)` - display: flex; - min-height: 150px; - padding: 16px; - flex-direction: column; - justify-content: space-between; - flex: 1 0 0; - cursor: pointer; - border-radius: 0px 4px 4px 0px; -` - -const StyledCardComponent = styled.div` - display: flex; - padding-right: 16px; - justify-content: flex-end; - gap: 10px; - width: 100%; -` - -const StyledDeckCards = styled.div` - display: grid; - grid-template-columns: repeat(auto-fill, 450px); - gap: 24px; -` - -const StyledDeckText = styled.div` - display: grid; - grid-template-rows: 25px 35px; - align-self: stretch; -` -const StyledTopDeckText = styled.div` - display: flex; - justify-content: space-between; - margin-right: 5px; -` - -const Rectangle = styled.div` - display: flex-start; - width: 24px; - height: 100%; - border-radius: 6px 0px 0px 6px; -` - -const DeckCard = styled.div` - display: flex; - min-width: 400px; - max-width: 450px; - flex: 1 0 0; - border-radius: 6px; - min-height: 180px; - box-shadow: - 0px 3px 4px 0px rgba(0, 0, 0, 0.12), - 0px 2px 4px 0px rgba(0, 0, 0, 0.14); -` - -const StyledCircle = styled.div` - width: 13px; - height: 13px; - border-radius: 50px; -` - -const StyledMissionComponents = styled.div` - display: flex; - flex-direction: row; - align-items: center; - gap: 4px; -` - -const StyledDeckOverview = styled.div` - display: flex; - flex-direction: column; - gap: 25px; -` - -const StyledMissionInspections = styled.div` - display: flex; - flex-direction: column; - gap: 2px; -` - -const StyledPlaceholder = styled.div` - padding: 24px; - border: 1px solid #dcdcdc; - border-radius: 4px; -` - -const StyledContent = styled.div` - display: flex; - align-items: centre; - gap: 5px; -` +import { StyledDict, compareInspections } from './InspectionUtilities' +import { DeckCards } from './DeckCards' export interface Inspection { missionDefinition: CondensedMissionDefinition @@ -140,40 +45,15 @@ interface IInspectionProps { ongoingMissions: ScheduledMissionType } -const getDeadlineInspection = (deadline: Date) => { - const deadlineDays = getDeadlineInDays(deadline) - switch (true) { - case deadlineDays <= 1: - return 'red' - case deadlineDays > 1 && deadlineDays <= 7: - return 'red' - case deadlineDays > 7 && deadlineDays <= 14: - return 'orange' - case deadlineDays > 7 && deadlineDays <= 30: - return 'green' - } - return 'green' -} - -export const compareInspections = (i1: Inspection, i2: Inspection) => { - if (!i1.missionDefinition.inspectionFrequency) return 1 - if (!i2.missionDefinition.inspectionFrequency) return -1 - if (!i1.missionDefinition.lastRun) return -1 - if (!i2.missionDefinition.lastRun) return 1 - else return i1.deadline!.getTime() - i2.deadline!.getTime() -} - export function InspectionSection({ refreshInterval, updateScheduledMissionsMap, scheduledMissions, ongoingMissions, }: IInspectionProps) { - const { TranslateText } = useLanguageContext() const { installationCode } = useInstallationContext() const [deckMissions, setDeckMissions] = useState({}) const [selectedDeck, setSelectedDeck] = useState() - const [areas, setAreas] = useState({}) const [selectedMissions, setSelectedMissions] = useState() const [isDialogOpen, setIsDialogOpen] = useState(false) const [unscheduledMissions, setUnscheduledMissions] = useState([]) @@ -242,135 +122,17 @@ export function InspectionSection({ return () => clearInterval(id) }, [deckMissions]) - const getCardColor = (deckId: string) => { - const inspections = deckMissions[deckId].inspections - if (inspections.length === 0) return 'gray' - const sortedInspections = inspections.sort(compareInspections) - - if (sortedInspections.length === 0) return 'green' - - const nextInspection = sortedInspections[0] - - if (!nextInspection.deadline) { - if (!nextInspection.missionDefinition.inspectionFrequency) return 'gray' - else return 'red' - } - - return getDeadlineInspection(nextInspection.deadline) - } - - useEffect(() => { - const newAreas: DeckAreas = {} - - Object.keys(deckMissions).map((deckId) => { - let string: string = '' - let areaNames: String[] = [] - BackendAPICaller.getAreadByDeckID(deckMissions[deckId].deck.id).then((areas) => { - areaNames = [] - string = '' - if (areas != null) { - areas.forEach((area) => { - if (!areaNames.includes(area.areaName)) - areaNames = areaNames.concat(area.areaName.toLocaleUpperCase()) - }) - areaNames = areaNames.sort() - console.log(areaNames) - areaNames.forEach((areaName) => { - console.log(areaName) - if (areaNames[areaNames.length - 1] == areaName) string = string + ' ' + areaName - else string = string + ' ' + areaName + ' |' - }) - - newAreas[deckMissions[deckId].deck.id] = { - areaString: string, - } - setAreas(newAreas) - } - }) - }) - }, [deckMissions]) - return ( <> - - - {Object.keys(deckMissions).length > 0 ? ( - Object.keys(deckMissions).map((deckId) => ( - - - 0 - ? () => setSelectedDeck(deckMissions[deckId].deck) - : undefined - } - style={ - selectedDeck === deckMissions[deckId].deck - ? { border: `solid ${getCardColor(deckId)} 2px` } - : {} - } - > - - - - {deckMissions[deckId].deck.deckName.toString()} - - {deckMissions[deckId].inspections - .filter((i) => - Object.keys(ongoingMissions).includes(i.missionDefinition.id) - ) - .map((inspection) => ( - - - {TranslateText('InProgress')} - - ))} - - {Object.keys(areas).includes(deckId) && ( - {areas[deckId].areaString} - )} - {deckMissions[deckId].inspections && ( - - )} - - - 0 - ? '' - : TranslateText('No planned inspection') - } - > - - - - - - )) - ) : ( - - - {TranslateText('No deck inspections available')} - - - )} - + + + {selectedDeck && ( )} - + {isDialogOpen && ( ) } - -interface ICardMissionInformationProps { - deckId: string - deckMissions: DeckMissionType -} - -function CardMissionInformation({ deckId, deckMissions }: ICardMissionInformationProps) { - const { TranslateText } = useLanguageContext() - - var colorsCount: DeckMissionCount = { - red: { count: 0, message: 'Must be inspected this week' }, - orange: { count: 0, message: 'Must be inspected within two weeks' }, - green: { count: 0, message: 'Up to date' }, - grey: { count: 0, message: '' }, - } - - deckMissions[deckId].inspections.forEach((inspection) => { - if (!inspection.deadline) { - if (!inspection.missionDefinition.lastRun && inspection.missionDefinition.inspectionFrequency) { - colorsCount['red'].count++ - } else { - colorsCount['green'].count++ - } - } else { - const dealineColor = getDeadlineInspection(inspection.deadline) - colorsCount[dealineColor!].count++ - } - }) - - return ( - - {Object.keys(colorsCount) - .filter((color) => colorsCount[color].count > 0) - .map((color) => ( - - - - {colorsCount[color].count > 1 && - colorsCount[color].count + - ' ' + - TranslateText('Missions').toLowerCase() + - ' ' + - TranslateText(colorsCount[color].message).toLowerCase()} - {colorsCount[color].count === 1 && - colorsCount[color].count + - ' ' + - TranslateText('Mission').toLowerCase() + - ' ' + - TranslateText(colorsCount[color].message).toLowerCase()} - - - ))} - - ) -} diff --git a/frontend/src/components/Pages/InspectionPage/InspectionTable.tsx b/frontend/src/components/Pages/InspectionPage/InspectionTable.tsx index 3923442bb..050cb9349 100644 --- a/frontend/src/components/Pages/InspectionPage/InspectionTable.tsx +++ b/frontend/src/components/Pages/InspectionPage/InspectionTable.tsx @@ -7,9 +7,10 @@ import { CondensedMissionDefinition } from 'models/MissionDefinition' import { NavigateFunction, useNavigate } from 'react-router-dom' import { config } from 'config' import { Icons } from 'utils/icons' -import { Inspection, ScheduledMissionType, compareInspections } from './InspectionSection' +import { Inspection, ScheduledMissionType } from './InspectionSection' +import { compareInspections } from './InspectionUtilities' import { getDeadlineInDays } from 'utils/StringFormatting' -import { AlreadyScheduledMissionDialog, ScheduleMissionDialog } from './ScheduleMissionDialog' +import { AlreadyScheduledMissionDialog, ScheduleMissionDialog } from './ScheduleMissionDialogs' import { useEffect, useState } from 'react' import { refreshInterval } from '../FrontPage/FrontPage' import { TranslateTextWithContext } from 'components/Contexts/LanguageContext' diff --git a/frontend/src/components/Pages/InspectionPage/InspectionUtilities.tsx b/frontend/src/components/Pages/InspectionPage/InspectionUtilities.tsx new file mode 100644 index 000000000..678b3010d --- /dev/null +++ b/frontend/src/components/Pages/InspectionPage/InspectionUtilities.tsx @@ -0,0 +1,167 @@ +import { Card, Typography } from '@equinor/eds-core-react' +import styled from 'styled-components' +import { DeckMissionCount, DeckMissionType, Inspection } from './InspectionSection' +import { getDeadlineInDays } from 'utils/StringFormatting' +import { tokens } from '@equinor/eds-tokens' +import { useLanguageContext } from 'components/Contexts/LanguageContext' + +export const StyledDict = { + Card: styled(Card)` + display: flex; + min-height: 150px; + padding: 16px; + flex-direction: column; + justify-content: space-between; + flex: 1 0 0; + cursor: pointer; + border-radius: 0px 4px 4px 0px; + `, + CardComponent: styled.div` + display: flex; + padding-right: 16px; + justify-content: flex-end; + gap: 10px; + width: 100%; + `, + DeckCards: styled.div` + display: grid; + grid-template-columns: repeat(auto-fill, 450px); + gap: 24px; + `, + DeckText: styled.div` + display: grid; + grid-template-rows: 25px 35px; + align-self: stretch; + `, + TopDeckText: styled.div` + display: flex; + justify-content: space-between; + margin-right: 5px; + `, + Rectangle: styled.div` + display: flex-start; + width: 24px; + height: 100%; + border-radius: 6px 0px 0px 6px; + `, + DeckCard: styled.div` + display: flex; + min-width: 400px; + max-width: 450px; + flex: 1 0 0; + border-radius: 6px; + min-height: 180px; + box-shadow: + 0px 3px 4px 0px rgba(0, 0, 0, 0.12), + 0px 2px 4px 0px rgba(0, 0, 0, 0.14); + `, + Circle: styled.div` + width: 13px; + height: 13px; + border-radius: 50px; + `, + MissionComponents: styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 4px; + `, + DeckOverview: styled.div` + display: flex; + flex-direction: column; + gap: 25px; + `, + MissionInspections: styled.div` + display: flex; + flex-direction: column; + gap: 2px; + `, + Placeholder: styled.div` + padding: 24px; + border: 1px solid #dcdcdc; + border-radius: 4px; + `, + Content: styled.div` + display: flex; + align-items: centre; + gap: 5px; + `, +} + +export const getDeadlineInspection = (deadline: Date) => { + const deadlineDays = getDeadlineInDays(deadline) + switch (true) { + case deadlineDays <= 1: + return 'red' + case deadlineDays > 1 && deadlineDays <= 7: + return 'red' + case deadlineDays > 7 && deadlineDays <= 14: + return 'orange' + case deadlineDays > 7 && deadlineDays <= 30: + return 'green' + } + return 'green' +} + +export const compareInspections = (i1: Inspection, i2: Inspection) => { + if (!i1.missionDefinition.inspectionFrequency) return 1 + if (!i2.missionDefinition.inspectionFrequency) return -1 + if (!i1.missionDefinition.lastRun) return -1 + if (!i2.missionDefinition.lastRun) return 1 + else return i1.deadline!.getTime() - i2.deadline!.getTime() +} + +interface ICardMissionInformationProps { + deckId: string + deckMissions: DeckMissionType +} + +export function CardMissionInformation({ deckId, deckMissions }: ICardMissionInformationProps) { + const { TranslateText } = useLanguageContext() + + var colorsCount: DeckMissionCount = { + red: { count: 0, message: 'Must be inspected this week' }, + orange: { count: 0, message: 'Must be inspected within two weeks' }, + green: { count: 0, message: 'Up to date' }, + grey: { count: 0, message: '' }, + } + + deckMissions[deckId].inspections.forEach((inspection) => { + if (!inspection.deadline) { + if (!inspection.missionDefinition.lastRun && inspection.missionDefinition.inspectionFrequency) { + colorsCount['red'].count++ + } else { + colorsCount['green'].count++ + } + } else { + const dealineColor = getDeadlineInspection(inspection.deadline) + colorsCount[dealineColor!].count++ + } + }) + + return ( + + {Object.keys(colorsCount) + .filter((color) => colorsCount[color].count > 0) + .map((color) => ( + + + + {colorsCount[color].count > 1 && + colorsCount[color].count + + ' ' + + TranslateText('Missions').toLowerCase() + + ' ' + + TranslateText(colorsCount[color].message).toLowerCase()} + {colorsCount[color].count === 1 && + colorsCount[color].count + + ' ' + + TranslateText('Mission').toLowerCase() + + ' ' + + TranslateText(colorsCount[color].message).toLowerCase()} + + + ))} + + ) +} diff --git a/frontend/src/components/Pages/InspectionPage/ScheduleMissionDialog.tsx b/frontend/src/components/Pages/InspectionPage/ScheduleMissionDialogs.tsx similarity index 100% rename from frontend/src/components/Pages/InspectionPage/ScheduleMissionDialog.tsx rename to frontend/src/components/Pages/InspectionPage/ScheduleMissionDialogs.tsx