Skip to content

Commit cc92d68

Browse files
committed
Make tags selectable
1 parent 33c9a46 commit cc92d68

5 files changed

Lines changed: 182 additions & 84 deletions

File tree

frontend/src/language/en.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,5 +386,7 @@
386386
"No data available in the selected time range": "No data available in the selected time range",
387387
"Data View for Constant Level Oilers": "Data View for Constant Level Oilers",
388388
"Analysis result not available": "Analysis result not available",
389-
"Go to data overview": "Go to data overview"
389+
"Go to data overview": "Go to data overview",
390+
"Latest inspection": "Latest inspection",
391+
"Latest analysis result": "Latest analysis result"
390392
}

frontend/src/language/no.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,5 +386,7 @@
386386
"No data available in the selected time range": "Ingen data tilgjengelig i det valgte tidsrommet",
387387
"Data View for Constant Level Oilers": "Datavisning for Constant Level Oilers",
388388
"Analysis result not available": "Analyseresultat ikke tilgjengelig",
389-
"Go to data overview": "Gå til dataoversikt"
389+
"Go to data overview": "Gå til dataoversikt",
390+
"Latest inspection": "Siste inspeksjon",
391+
"Latest analysis result": "Siste analyseresultat"
390392
}

frontend/src/pages/DataViewPage/CloeDataViewPage.tsx

Lines changed: 119 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Button, ButtonGroup, Chip, Table, Typography } from '@equinor/eds-core-react'
22
import { Mission, MissionStatus } from 'models/Mission'
33
import { useContext, useEffect, useState } from 'react'
4+
import styled from 'styled-components'
5+
import { tokens } from '@equinor/eds-tokens'
46
import { useLanguageContext } from 'components/Contexts/LanguageContext'
57
import { StyledCardsWidth, StyledPage, StyledTable, StyledTableAndMap } from 'components/Styles/StyledComponents'
68
import { SignalREventLabels, useSignalRContext } from 'components/Contexts/SignalRContext'
@@ -11,6 +13,26 @@ import { formatDateTime } from 'utils/StringFormatting'
1113
import { TimeseriesLinePlot, TimeseriesLinePlotData } from 'components/Displays/TimeseriesLinePlot'
1214
import { DescriptionDisplay, TagIdDisplay } from 'components/Displays/TaskDisplay'
1315
import { PlantMap } from 'pages/MissionPage/MapPosition/PointillaMapView'
16+
import { StyledImagesSection } from 'pages/InspectionReportPage/InspectionStyles'
17+
import { InspectionImageWithPlaceholder } from 'pages/InspectionReportPage/InspectionReportImage'
18+
import { AnalysisResultDialogContent } from 'pages/MissionPage/AnalysisResultView'
19+
20+
const CloeMapWrapper = styled.div`
21+
.leaflet-tooltip.circleLabel {
22+
background-color: ${tokens.colors.ui.background__medium.hex} !important;
23+
padding: 0 4px !important;
24+
border-radius: 2px !important;
25+
}
26+
`
27+
const StyledTopAlignedImagesSection = styled(StyledImagesSection)`
28+
align-items: flex-start;
29+
`
30+
const StyledCloeImageCard = styled.div`
31+
display: flex;
32+
flex-direction: column;
33+
gap: 8px;
34+
max-width: 480px;
35+
`
1436

1537
enum TimeRange {
1638
SevenDays = '7days',
@@ -29,7 +51,15 @@ const checkIfAnalysableDescription = (description: string) => {
2951
return Object.values(CloeAnalysableDescriptions).includes(description as CloeAnalysableDescriptions)
3052
}
3153

32-
const CloeDataTable = ({ tasks }: { tasks: Task[] }) => {
54+
const CloeDataTable = ({
55+
tasks,
56+
selectedTagId,
57+
onSelectTag,
58+
}: {
59+
tasks: Task[]
60+
selectedTagId: string | null
61+
onSelectTag: (tagId: string | null) => void
62+
}) => {
3363
const { TranslateText } = useLanguageContext()
3464

3565
return (
@@ -45,37 +75,52 @@ const CloeDataTable = ({ tasks }: { tasks: Task[] }) => {
4575
</Table.Head>
4676
<Table.Body>
4777
{tasks &&
48-
tasks.map((task, index) => (
49-
<Table.Row key={task.id}>
50-
<Table.Cell>
51-
<Chip>
52-
<Typography variant="body_short_bold">{index + 1}</Typography>
53-
</Chip>
54-
</Table.Cell>
55-
<Table.Cell>
56-
<TagIdDisplay task={task} />
57-
</Table.Cell>
58-
<Table.Cell>
59-
<DescriptionDisplay task={task} />
60-
</Table.Cell>
61-
{task.inspection.analysisResult?.value ? (
78+
tasks.map((task, index) => {
79+
const isSelected = !!task.tagId && task.tagId === selectedTagId
80+
return (
81+
<Table.Row
82+
key={task.id}
83+
onClick={() => {
84+
if (!task.tagId) return
85+
onSelectTag(isSelected ? null : task.tagId)
86+
}}
87+
style={{
88+
cursor: task.tagId ? 'pointer' : 'default',
89+
backgroundColor: isSelected
90+
? tokens.colors.interactive.primary__selected_highlight.rgba
91+
: undefined,
92+
}}
93+
>
6294
<Table.Cell>
63-
<Typography>
64-
{Math.round(parseFloat(task.inspection.analysisResult?.value)) + '%'}
65-
</Typography>
95+
<Chip>
96+
<Typography variant="body_short_bold">{index + 1}</Typography>
97+
</Chip>
6698
</Table.Cell>
67-
) : (
6899
<Table.Cell>
69-
<Typography>{TranslateText('Analysis result not available')}</Typography>
100+
<TagIdDisplay task={task} />
70101
</Table.Cell>
71-
)}
72-
{task.endTime && (
73102
<Table.Cell>
74-
<Typography>{formatDateTime(task.endTime, 'dd.MM.yy - HH:mm')}</Typography>
103+
<DescriptionDisplay task={task} />
75104
</Table.Cell>
76-
)}
77-
</Table.Row>
78-
))}
105+
{task.inspection.analysisResult?.value ? (
106+
<Table.Cell>
107+
<Typography>
108+
{Math.round(parseFloat(task.inspection.analysisResult?.value)) + '%'}
109+
</Typography>
110+
</Table.Cell>
111+
) : (
112+
<Table.Cell>
113+
<Typography>{TranslateText('Analysis result not available')}</Typography>
114+
</Table.Cell>
115+
)}
116+
{task.endTime && (
117+
<Table.Cell>
118+
<Typography>{formatDateTime(task.endTime, 'dd.MM.yy - HH:mm')}</Typography>
119+
</Table.Cell>
120+
)}
121+
</Table.Row>
122+
)
123+
})}
79124
</Table.Body>
80125
</StyledTable>
81126
)
@@ -87,6 +132,7 @@ export const CloeDataViewPage = () => {
87132
const { registerEvent, connectionReady } = useSignalRContext()
88133
const [cloeMissions, setCloeMissions] = useState<Mission[]>([])
89134
const [timeRange, setTimeRange] = useState<TimeRange>(TimeRange.OneMonth)
135+
const [selectedTagId, setSelectedTagId] = useState<string | null>(null)
90136
const backendApi = useBackendApi()
91137

92138
const fetchMissions = (): Promise<Mission[]> => {
@@ -160,6 +206,7 @@ export const CloeDataViewPage = () => {
160206
? new Date((task.endTime ?? task.startTime) as unknown as string)
161207
: missionTimestamp
162208
if (!tagId || !rawFillLevel || !sampleTimestamp) return
209+
if (selectedTagId && tagId !== selectedTagId) return
163210
if (!Object.hasOwn(accumulatedData, tagId)) {
164211
accumulatedData[tagId] = []
165212
}
@@ -170,16 +217,59 @@ export const CloeDataViewPage = () => {
170217
{} as TimeseriesLinePlotData
171218
)
172219

220+
const mapMission = (() => {
221+
if (!latestCloeMission) return undefined
222+
if (!selectedTagId) return latestCloeMission
223+
const selectedIndex = latestCloeMission.tasks.findIndex((t) => t.tagId === selectedTagId)
224+
if (selectedIndex < 0) return latestCloeMission
225+
const selectedTask = latestCloeMission.tasks[selectedIndex]
226+
const paddedTasks: Task[] = Array.from({ length: selectedIndex + 1 }, (_, i) => ({
227+
...selectedTask,
228+
id: `${selectedTask.id}__pad_${i}`,
229+
}))
230+
paddedTasks[selectedIndex] = selectedTask
231+
return { ...latestCloeMission, tasks: paddedTasks }
232+
})()
233+
234+
const selectedTask = selectedTagId ? latestCloeMission?.tasks.find((t) => t.tagId === selectedTagId) : undefined
235+
173236
return (
174237
<StyledPage>
175238
<StyledCardsWidth>
176239
<Typography variant="h2">{TranslateText('Data View for Constant Level Oilers')}</Typography>
177240
<StyledTableAndMap>
178-
{latestCloeMission && <CloeDataTable tasks={latestCloeMission.tasks} />}
179-
{plantCode && latestCloeMission && (
180-
<PlantMap plantCode={plantCode} floorId="0" mission={latestCloeMission} />
241+
{latestCloeMission && (
242+
<CloeDataTable
243+
tasks={latestCloeMission.tasks}
244+
selectedTagId={selectedTagId}
245+
onSelectTag={setSelectedTagId}
246+
/>
247+
)}
248+
{plantCode && mapMission && (
249+
<CloeMapWrapper>
250+
<PlantMap
251+
key={selectedTagId ?? 'all'}
252+
plantCode={plantCode}
253+
floorId="0"
254+
mission={mapMission}
255+
/>
256+
</CloeMapWrapper>
181257
)}
182258
</StyledTableAndMap>
259+
{selectedTask && selectedTask.inspection.isarInspectionId && (
260+
<StyledTopAlignedImagesSection>
261+
<StyledCloeImageCard>
262+
<Typography variant="h4">{TranslateText('Latest inspection')}</Typography>
263+
<InspectionImageWithPlaceholder task={selectedTask} isLargeImage={true} />
264+
</StyledCloeImageCard>
265+
{selectedTask.inspection.analysisResult?.storageAccount && (
266+
<StyledCloeImageCard>
267+
<Typography variant="h4">{TranslateText('Latest analysis result')}</Typography>
268+
<AnalysisResultDialogContent currentTask={selectedTask} />
269+
</StyledCloeImageCard>
270+
)}
271+
</StyledTopAlignedImagesSection>
272+
)}
183273
{cloeMissions.length > 0 && (
184274
<>
185275
<Typography variant="h4">{TranslateText('Measured oil level')}</Typography>

frontend/src/pages/InspectionReportPage/InspectionReportImage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export const PendingResultPlaceholder = ({ isLargeImage }: { isLargeImage: boole
8080
)
8181
}
8282

83-
const InspectionImageWithPlaceholder = ({ task, isLargeImage }: { task: Task; isLargeImage: boolean }) => {
83+
export const InspectionImageWithPlaceholder = ({ task, isLargeImage }: { task: Task; isLargeImage: boolean }) => {
8484
const { useImageData } = useInspectionsContext()
8585
const { data, isPending, isError } = useImageData(task.inspection.isarInspectionId)
8686
if (isError || !data) {

frontend/src/pages/MissionPage/AnalysisResultView.tsx

Lines changed: 56 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,61 @@ const AnalysisImage = ({ inspectionId }: { inspectionId: string }) => {
3636
return <StyledImage $otherContentHeight="0px" src={data} />
3737
}
3838

39+
export const AnalysisResultDialogContent = ({ currentTask }: { currentTask: Task }) => {
40+
const { TranslateText } = useLanguageContext()
41+
42+
return (
43+
<div>
44+
{currentTask.inspection.analysisResult?.storageAccount ? (
45+
<AnalysisImage inspectionId={currentTask.inspection.isarInspectionId} />
46+
) : (
47+
<>{/* No image to display*/}</>
48+
)}
49+
<StyledBottomContent>
50+
<StyledInfoContent>
51+
<Typography variant="caption">{TranslateText('Tag') + ':'}</Typography>
52+
<Typography variant="body_short">{currentTask.tagId}</Typography>
53+
</StyledInfoContent>
54+
{currentTask.description && (
55+
<StyledInfoContent>
56+
<Typography variant="caption">{TranslateText('Description') + ':'}</Typography>
57+
<Typography variant="body_short">{currentTask.description}</Typography>
58+
</StyledInfoContent>
59+
)}
60+
{currentTask.endTime && (
61+
<StyledInfoContent>
62+
<Typography variant="caption">{TranslateText('Timestamp') + ':'}</Typography>
63+
<Typography variant="body_short">
64+
{formatDateTime(currentTask.endTime, 'dd.MM.yy - HH:mm')}
65+
</Typography>
66+
</StyledInfoContent>
67+
)}
68+
{currentTask.inspection.analysisResult?.warning && (
69+
<StyledInfoContent>
70+
<Typography variant="caption">{TranslateText('Warning') + ':'}</Typography>
71+
<Typography variant="body_short">{currentTask.inspection.analysisResult.warning}</Typography>
72+
</StyledInfoContent>
73+
)}
74+
{currentTask.inspection.analysisResult?.value && (
75+
<StyledInfoContent>
76+
<Typography variant="caption">{TranslateText('Value') + ':'}</Typography>
77+
<Typography variant="body_short">{currentTask.inspection.analysisResult.value}</Typography>
78+
</StyledInfoContent>
79+
)}
80+
{currentTask.inspection.analysisResult?.confidence !== undefined &&
81+
currentTask.inspection.analysisResult?.confidence !== null && (
82+
<StyledInfoContent>
83+
<Typography variant="caption">{TranslateText('Confidence') + ':'}</Typography>
84+
<Typography variant="body_short">
85+
{Math.round(currentTask.inspection.analysisResult.confidence) + '%'}
86+
</Typography>
87+
</StyledInfoContent>
88+
)}
89+
</StyledBottomContent>
90+
</div>
91+
)
92+
}
93+
3994
export const AnalysisResultDialogView = ({ selectedAnalysisId, tasks }: InspectionDialogViewProps) => {
4095
const { TranslateText } = useLanguageContext()
4196
const { switchSelectedAnalysisId } = useInspectionId()
@@ -69,58 +124,7 @@ export const AnalysisResultDialogView = ({ selectedAnalysisId, tasks }: Inspecti
69124
</StyledCloseButton>
70125
</StyledDialogHeader>
71126
<StyledDialogInspectionView>
72-
<div>
73-
{currentTask.inspection.analysisResult?.storageAccount ? (
74-
<AnalysisImage inspectionId={currentTask.inspection.isarInspectionId} />
75-
) : (
76-
<>{/* No image to display*/}</>
77-
)}
78-
<StyledBottomContent>
79-
<StyledInfoContent>
80-
<Typography variant="caption">{TranslateText('Tag') + ':'}</Typography>
81-
<Typography variant="body_short">{currentTask.tagId}</Typography>
82-
</StyledInfoContent>
83-
{currentTask.description && (
84-
<StyledInfoContent>
85-
<Typography variant="caption">{TranslateText('Description') + ':'}</Typography>
86-
<Typography variant="body_short">{currentTask.description}</Typography>
87-
</StyledInfoContent>
88-
)}
89-
{currentTask.endTime && (
90-
<StyledInfoContent>
91-
<Typography variant="caption">{TranslateText('Timestamp') + ':'}</Typography>
92-
<Typography variant="body_short">
93-
{formatDateTime(currentTask.endTime, 'dd.MM.yy - HH:mm')}
94-
</Typography>
95-
</StyledInfoContent>
96-
)}
97-
{currentTask.inspection.analysisResult?.warning && (
98-
<StyledInfoContent>
99-
<Typography variant="caption">{TranslateText('Warning') + ':'}</Typography>
100-
<Typography variant="body_short">
101-
{currentTask.inspection.analysisResult.warning}
102-
</Typography>
103-
</StyledInfoContent>
104-
)}
105-
{currentTask.inspection.analysisResult?.value && (
106-
<StyledInfoContent>
107-
<Typography variant="caption">{TranslateText('Value') + ':'}</Typography>
108-
<Typography variant="body_short">
109-
{currentTask.inspection.analysisResult.value}
110-
</Typography>
111-
</StyledInfoContent>
112-
)}
113-
{currentTask.inspection.analysisResult?.confidence !== undefined &&
114-
currentTask.inspection.analysisResult?.confidence !== null && (
115-
<StyledInfoContent>
116-
<Typography variant="caption">{TranslateText('Confidence') + ':'}</Typography>
117-
<Typography variant="body_short">
118-
{Math.round(currentTask.inspection.analysisResult.confidence) + '%'}
119-
</Typography>
120-
</StyledInfoContent>
121-
)}
122-
</StyledBottomContent>
123-
</div>
127+
<AnalysisResultDialogContent currentTask={currentTask} />
124128
</StyledDialogInspectionView>
125129
</StyledDialogContent>
126130
</StyledDialog>

0 commit comments

Comments
 (0)