diff --git a/backend/api/Controllers/InspectionController.cs b/backend/api/Controllers/InspectionController.cs index 41c275884..779c967af 100644 --- a/backend/api/Controllers/InspectionController.cs +++ b/backend/api/Controllers/InspectionController.cs @@ -1,7 +1,7 @@ -using Api.Controllers.Models; +using System.Globalization; +using Api.Controllers.Models; using Api.Database.Models; using Api.Services; -using Api.Services.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -50,6 +50,11 @@ public async Task> GetInspectionImageById([FromRoute] s if (inspectionData == null) return NotFound($"Could not find inspection data for inspection with Id {inspection.Id}."); + if (!inspectionData.BlobName.ToLower(CultureInfo.CurrentCulture).Equals(installationCode.ToLower(CultureInfo.CurrentCulture), StringComparison.Ordinal)) + { + return NotFound($"Could not find inspection data for inspection with Id {inspection.Id} because blob name {inspectionData.BlobName} does not match installation {installationCode}."); + } + try { byte[] inspectionStream = await inspectionService.FetchInpectionImage(inspectionData.BlobName, inspectionData.BlobContainer, inspectionData.StorageAccount); diff --git a/backend/api/Services/InspectionService.cs b/backend/api/Services/InspectionService.cs index affe9f91d..8b777b22d 100644 --- a/backend/api/Services/InspectionService.cs +++ b/backend/api/Services/InspectionService.cs @@ -5,11 +5,9 @@ using Api.Controllers.Models; using Api.Database.Context; using Api.Database.Models; -using Api.Options; using Api.Services.Models; using Api.Utilities; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Options; using Microsoft.Identity.Abstractions; namespace Api.Services { @@ -114,8 +112,7 @@ private IQueryable GetInspections(bool readOnly = true) public async Task GetInspectionStorageInfo(string inspectionId) { - var inspectionID = "finaltest"; - string relativePath = $"InspectionData/{inspectionID}/inspection-data-storage-location"; + string relativePath = $"InspectionData/{inspectionId}/inspection-data-storage-location"; var response = await idaApi.CallApiForUserAsync( ServiceName, diff --git a/frontend/src/components/Pages/MissionPage/InpectionView/InpectionView.tsx b/frontend/src/components/Pages/MissionPage/InpectionView/InpectionView.tsx deleted file mode 100644 index 028a60f28..000000000 --- a/frontend/src/components/Pages/MissionPage/InpectionView/InpectionView.tsx +++ /dev/null @@ -1,326 +0,0 @@ -import { Button, Card, Dialog, Icon, Typography } from '@equinor/eds-core-react' -import { tokens } from '@equinor/eds-tokens' -import { useCallback, useEffect, useRef, useState } from 'react' -import styled from 'styled-components' -import NoMap from 'mediaAssets/NoMap.png' -import { BackendAPICaller } from 'api/ApiCaller' -import { Task, TaskStatus } from 'models/Task' -import { useInstallationContext } from 'components/Contexts/InstallationContext' -import { Icons } from 'utils/icons' -import { useLanguageContext } from 'components/Contexts/LanguageContext' -import { formatDateTime } from 'utils/StringFormatting' - -const StyledInspection = styled.canvas` - flex: 1 0 0; - align-self: stretch; - max-height: 60vh; - width: 80vh; -` - -const StyledInspectionImage = styled.canvas` - flex: 1 0 0; - align-self: center; - max-width: 100%; -` - -const StyledDialog = styled(Dialog)` - display: flex; - width: 100%; - max-height: 80vh; -` -const StyledCloseButton = styled(Button)` - width: 24px; - height: 24px; -` -const StyledDialogContent = styled(Dialog.Content)` - display: flex; - flex-direction: column; - gap: 10px; -` -const StyledDialogHeader = styled.div` - display: flex; - padding: 16px; - justify-content: space-between; - align-items: center; - align-self: stretch; - border-bottom: 1px solid ${tokens.colors.ui.background__medium.hex}; - height: 24px; -` - -const StyledBottomContent = styled.div` - display: flex; - padding: 16px; - justify-content: space-between; - align-items: center; - align-self: stretch; -` - -const StyledInfoContent = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; -` - -const StyledSection = styled.div` - display: flex; - padding: 24px; - min-width: 240px; - flex-direction: column; - align-items: flex-start; - gap: 8px; - border-radius: 6px; - border: 1.194px solid ${tokens.colors.ui.background__medium.hex}; - background: ${tokens.colors.ui.background__default.hex}; - overflow-y: scroll; - max-height: 60vh; -` - -const StyledImagesSection = styled.div` - display: flex; - align-items: center; - gap: 16px; -` - -const StyledImageCard = styled(Card)` - display: flex; - max-height: 280px; - max-width: 210px; - align-self: stretch; - padding: 4px; - flex-direction: column; - align-items: flex-start; - gap: 2px; - flex: auto; - border-radius: 2px; - border: 1px solid ${tokens.colors.ui.background__medium.hex}; - background: ${tokens.colors.ui.background__default.hex}; - box-shadow: - 0px 2.389px 4.778px 0px ${tokens.colors.ui.background__light.hex}, - 0px 3.583px 4.778px 0px ${tokens.colors.ui.background__light.hex}; - cursor: pointer; -` - -const StyledInspectionData = styled.div` - display: flex; - flex-direction: column; - justify-content: center; - align-items: flex-start; - gap: 8px; -` -const StyledInspectionContent = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; -` -const StyledDialogInspectionView = styled.div` - display: flex; - flex-direction: row; - gap: 16px; -` - -const StyledInspectionCards = styled.div` - display: flex; - justify-content: center; - align-items: flex-start; - align-content: flex-start; - gap: 8px; - align-self: stretch; - flex-wrap: wrap; -` - -interface InspectionDialogViewProps { - task: Task - setInspectionTask: (inspectionTask: Task | undefined) => void - tasks: Task[] -} - -export const getMeta = async (url: string) => { - const image = new Image() - image.src = url - await image.decode() - return image -} - -export const InspectionDialogView = ({ task, setInspectionTask, tasks }: InspectionDialogViewProps) => { - const { TranslateText } = useLanguageContext() - const { installationCode, installationName } = useInstallationContext() - const [inspectionCanvas, setInspectionCanvas] = useState(document.createElement('canvas')) - const [inspectionImage, setInspectionImage] = useState(document.createElement('img')) - const [inspectionContext, setInspectionContext] = useState() - const imageObjectURL = useRef('') - - let taskId = task.isarTaskId! - - useEffect(() => { - BackendAPICaller.getInspection(installationCode, taskId) - .then((imageBlob) => { - imageObjectURL.current = URL.createObjectURL(imageBlob) - }) - .then(() => { - getMeta(imageObjectURL.current).then((img) => { - const inspectionCanvas = document.getElementById('inspectionCanvas') as HTMLCanvasElement - if (inspectionCanvas) { - inspectionCanvas.width = img.width - inspectionCanvas.height = img.height - let context = inspectionCanvas.getContext('2d') - if (context) { - setInspectionContext(context) - context.drawImage(img, 0, 0) - } - setInspectionCanvas(inspectionCanvas) - } - setInspectionImage(img) - }) - }) - .catch((e) => {}) - }, [installationCode, taskId, task]) - - return ( - <> - {imageObjectURL.current != '' && ( - - - - {TranslateText('Inspection report')} - setInspectionTask(undefined)}> - - - - -
- - - - {TranslateText('Installation') + ':'} - {installationName} - - - {TranslateText('Tag') + ':'} - {task.tagId} - - {task.description && ( - - {TranslateText('Description' + ':')} - {task.description} - - )} - {task.endTime && ( - - {TranslateText('Timestanp' + ':')} - - {formatDateTime(task.endTime, 'dd.MM.yy - HH:mm')} - - - )} - -
- -
-
-
- - )} - - ) -} - -interface InspectionsViewSectionProps { - tasks: Task[] - setInspectionTask: (inspectionTask: Task | undefined) => void - widthValue?: string | undefined -} - -export const InspectionsViewSection = ({ tasks, setInspectionTask, widthValue }: InspectionsViewSectionProps) => { - const { TranslateText } = useLanguageContext() - const imageObjectURL = useRef('') - - return ( - <> - {imageObjectURL.current != '' && ( - - - {!widthValue && {TranslateText('Last completed inspection')}} - - - {Object.keys(tasks).length > 0 && - tasks.map( - (task) => - task.status === TaskStatus.Successful && ( - setInspectionTask(task)}> - - - {task.tagId && ( - - - {TranslateText('Tag') + ':'} - - {task.tagId} - - )} - {task.endTime && ( - - - {TranslateText('Timestamp') + ':'} - - - {formatDateTime(task.endTime!, 'dd.MM.yy - HH:mm')} - - - )} - - - ) - )} - - - - - )} - - - ) -} - -interface IGetInspectionImageProps { - task: Task - imageObjectURL: React.MutableRefObject -} - -const GetInspectionImage = ({ task, imageObjectURL }: IGetInspectionImageProps) => { - const { installationCode } = useInstallationContext() - const [inspectionCanvas, setInspectionCanvas] = useState(document.createElement('canvas')) - const [inspectionImage, setInspectionImage] = useState(document.createElement('img')) - const [inspectionContext, setInspectionContext] = useState() - - useEffect(() => { - BackendAPICaller.getInspection(installationCode, task.isarTaskId!) - .then((imageBlob) => { - imageObjectURL.current = URL.createObjectURL(imageBlob) - }) - .then(() => { - getMeta(imageObjectURL.current).then((img) => { - const inspectionCanvas = document.getElementById(task.isarTaskId!) as HTMLCanvasElement - if (inspectionCanvas) { - inspectionCanvas.width = img.width - inspectionCanvas.height = img.height - let context = inspectionCanvas.getContext('2d') - if (context) { - setInspectionContext(context) - context.drawImage(img, 0, 0) - } - setInspectionCanvas(inspectionCanvas) - } - setInspectionImage(img) - }) - }) - .catch((e) => {}) - }, [installationCode, task.isarTaskId, task]) - - return -} diff --git a/frontend/src/components/Pages/MissionPage/InpectionView/InspectionView.tsx b/frontend/src/components/Pages/MissionPage/InpectionView/InspectionView.tsx new file mode 100644 index 000000000..ffe9f3a90 --- /dev/null +++ b/frontend/src/components/Pages/MissionPage/InpectionView/InspectionView.tsx @@ -0,0 +1,332 @@ +import { Button, Card, Dialog, Icon, Typography } from '@equinor/eds-core-react' +import { tokens } from '@equinor/eds-tokens' +import { useEffect, useRef, useState } from 'react' +import styled from 'styled-components' +import { BackendAPICaller } from 'api/ApiCaller' +import { Task, TaskStatus } from 'models/Task' +import { useInstallationContext } from 'components/Contexts/InstallationContext' +import { Icons } from 'utils/icons' +import { useLanguageContext } from 'components/Contexts/LanguageContext' +import { formatDateTime } from 'utils/StringFormatting' + +const StyledInspection = styled.canvas` + flex: 1 0 0; + align-self: stretch; + max-height: 60vh; + width: 80vh; +` + +const StyledInspectionImage = styled.canvas` + flex: 1 0 0; + align-self: center; + max-width: 100%; +` + +const StyledDialog = styled(Dialog)` + display: flex; + width: 100%; + max-height: 80vh; +` +const StyledCloseButton = styled(Button)` + width: 24px; + height: 24px; +` +const StyledDialogContent = styled(Dialog.Content)` + display: flex; + flex-direction: column; + gap: 10px; +` +const StyledDialogHeader = styled.div` + display: flex; + padding: 16px; + justify-content: space-between; + align-items: center; + align-self: stretch; + border-bottom: 1px solid ${tokens.colors.ui.background__medium.hex}; + height: 24px; +` + +const StyledBottomContent = styled.div` + display: flex; + padding: 16px; + justify-content: space-between; + align-items: center; + align-self: stretch; +` + +const StyledInfoContent = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; +` + +const StyledSection = styled.div` + display: flex; + padding: 24px; + min-width: 240px; + flex-direction: column; + align-items: flex-start; + gap: 8px; + border-radius: 6px; + border: 1.194px solid ${tokens.colors.ui.background__medium.hex}; + background: ${tokens.colors.ui.background__default.hex}; + overflow-y: scroll; + max-height: 60vh; +` + +const StyledImagesSection = styled.div` + display: flex; + align-items: center; + gap: 16px; +` + +const StyledImageCard = styled(Card)` + display: flex; + max-height: 280px; + max-width: 210px; + align-self: stretch; + padding: 4px; + flex-direction: column; + align-items: flex-start; + gap: 2px; + flex: 1 0 0; + border-radius: 2px; + border: 1px solid ${tokens.colors.ui.background__medium.hex}; + background: ${tokens.colors.ui.background__default.hex}; + box-shadow: + 0px 2.389px 4.778px 0px ${tokens.colors.ui.background__light.hex}, + 0px 3.583px 4.778px 0px ${tokens.colors.ui.background__light.hex}; + cursor: pointer; +` + +const StyledInspectionData = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: 8px; +` +const StyledInspectionContent = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; +` +const StyledDialogInspectionView = styled.div` + display: flex; + flex-direction: row; + gap: 16px; +` + +const StyledInspectionCards = styled.div` + display: flex; + justify-content: center; + align-items: flex-start; + align-content: flex-start; + gap: 8px; + align-self: stretch; + flex-wrap: wrap; +` + +interface InspectionDialogViewProps { + task: Task + setInspectionTask: (inspectionTask: Task | undefined) => void + tasks: Task[] +} + +const getMeta = async (url: string) => { + const image = new Image() + image.src = url + await image.decode() + return image +} + +export const InspectionDialogView = ({ task, setInspectionTask, tasks }: InspectionDialogViewProps) => { + const { TranslateText } = useLanguageContext() + const { installationCode, installationName } = useInstallationContext() + const [inspectionImage, setInspectionImage] = useState(document.createElement('img')) + const imageObjectURL = useRef('') + + let taskId = task.isarTaskId! + + useEffect(() => { + if (task.isarTaskId !== undefined) { + BackendAPICaller.getInspection(installationCode, task.isarTaskId!) + .then((imageBlob) => { + imageObjectURL.current = URL.createObjectURL(imageBlob) + }) + .then(() => { + getMeta(imageObjectURL.current).then((img) => { + const inspectionCanvas = document.getElementById('inspectionCanvas') as HTMLCanvasElement + if (inspectionCanvas) { + inspectionCanvas.width = img.width + inspectionCanvas.height = img.height + let context = inspectionCanvas.getContext('2d') + if (context) { + context.drawImage(img, 0, 0) + } + } + setInspectionImage(img) + }) + }) + .catch(() => {}) + } + }, [installationCode, taskId, task, inspectionImage]) + + return ( + <> + {imageObjectURL.current !== '' && ( + + + + + {TranslateText('Inspection report')} + + setInspectionTask(undefined)}> + + + + +
+ + + + {TranslateText('Installation') + ':'} + {installationName} + + + {TranslateText('Tag') + ':'} + {task.tagId} + + {task.description && ( + + + {TranslateText('Description') + ':'} + + {task.description} + + )} + {task.endTime && ( + + + {TranslateText('Timestamp') + ':'} + + + {formatDateTime(task.endTime, 'dd.MM.yy - HH:mm')} + + + )} + +
+ +
+
+
+ )} + + ) +} + +interface InspectionsViewSectionProps { + tasks: Task[] + setInspectionTask: (inspectionTask: Task | undefined) => void + widthValue?: string | undefined +} + +export const InspectionsViewSection = ({ tasks, setInspectionTask, widthValue }: InspectionsViewSectionProps) => { + const { TranslateText } = useLanguageContext() + + return ( + <> + + {!widthValue && {TranslateText('Last completed inspection')}} + + + {Object.keys(tasks).length > 0 && + tasks.map( + (task) => + task.status === TaskStatus.Successful && ( + setInspectionTask(task)}> + + + {task.tagId && ( + + + {TranslateText('Tag') + ':'} + + {task.tagId} + + )} + {task.endTime && ( + + + {TranslateText('Timestamp') + ':'} + + + {formatDateTime(task.endTime!, 'dd.MM.yy - HH:mm')} + + + )} + + + ) + )} + + + + + ) +} + +const GetInspectionImage = ({ task }: { task: Task }) => { + const { installationCode } = useInstallationContext() + const imageObjectURL = useRef('') + const [inspectionImage, setInspectionImage] = useState(document.createElement('img')) + const [shouldFetchImage, setShouldFetchImage] = useState(true) + const [shouldFetch, setShouldFetch] = useState(false) + const refreshInterval = 10000 + + useEffect(() => { + if (shouldFetchImage && task.isarTaskId !== undefined) { + BackendAPICaller.getInspection(installationCode, task.isarTaskId!) + .then((imageBlob) => { + imageObjectURL.current = URL.createObjectURL(imageBlob) + }) + .then(() => { + getMeta(imageObjectURL.current).then((img) => { + const inspectionCanvas = document.getElementById(task.isarTaskId!) as HTMLCanvasElement + if (inspectionCanvas) { + inspectionCanvas.width = img.width + inspectionCanvas.height = img.height + let context = inspectionCanvas.getContext('2d') + if (context) { + context.drawImage(img, 0, 0) + } + } + setShouldFetchImage(false) + setInspectionImage(img) + }) + }) + .catch(() => {}) + } + }, [installationCode, task.isarTaskId, task, inspectionImage, shouldFetchImage]) + + useEffect(() => { + setShouldFetch((shouldFetch) => !shouldFetch) + const id = setInterval(() => { + setShouldFetch((shouldFetch) => !shouldFetch) + }, refreshInterval) + return () => clearInterval(id) + }, [refreshInterval, setShouldFetch, shouldFetch]) + + return +} diff --git a/frontend/src/components/Pages/MissionPage/MissionPage.tsx b/frontend/src/components/Pages/MissionPage/MissionPage.tsx index 4f6387906..ccc38e3f4 100644 --- a/frontend/src/components/Pages/MissionPage/MissionPage.tsx +++ b/frontend/src/components/Pages/MissionPage/MissionPage.tsx @@ -18,7 +18,7 @@ import { useMediaStreamContext } from 'components/Contexts/MediaStreamContext' import { tokens } from '@equinor/eds-tokens' import { StyledPage } from 'components/Styles/StyledComponents' import { Task } from 'models/Task' -import { InspectionDialogView, InspectionsViewSection } from './InpectionView/InpectionView' +import { InspectionDialogView, InspectionsViewSection } from './InpectionView/InspectionView' const StyledMissionPage = styled(StyledPage)` background-color: ${tokens.colors.ui.background__light.hex}; diff --git a/frontend/src/components/Pages/MissionPage/TaskOverview/TaskTable.tsx b/frontend/src/components/Pages/MissionPage/TaskOverview/TaskTable.tsx index cdd267df9..b37283af9 100644 --- a/frontend/src/components/Pages/MissionPage/TaskOverview/TaskTable.tsx +++ b/frontend/src/components/Pages/MissionPage/TaskOverview/TaskTable.tsx @@ -1,4 +1,4 @@ -import { Button, Chip, Dialog, Table, Typography } from '@equinor/eds-core-react' +import { Button, Chip, Table, Typography } from '@equinor/eds-core-react' import styled from 'styled-components' import { TaskStatusDisplay } from './TaskStatusDisplay' import { useLanguageContext } from 'components/Contexts/LanguageContext' @@ -6,8 +6,6 @@ import { Task, TaskStatus } from 'models/Task' import { tokens } from '@equinor/eds-tokens' import { getColorsFromTaskStatus } from 'utils/MarkerStyles' import { StyledTableBody, StyledTableCaptionGray, StyledTableCell } from 'components/Styles/StyledComponents' -import { useState } from 'react' -import { InspectionDialogView } from '../InpectionView/InpectionView' import { InspectionType } from 'models/Inspection' const StyledTable = styled(Table)` @@ -140,10 +138,7 @@ const InspectionTypesDisplay = ({ task, setInspectionTask }: InspectionTypesDisp ) : ( -