Skip to content

Commit

Permalink
Merge pull request #500 from MTES-MCT/499-front-completeforstats-and-…
Browse files Browse the repository at this point in the history
…status

feat(mission): compute status and complete for stats (front)
  • Loading branch information
xtiannyeto authored Jan 16, 2025
2 parents 4269c92 + cb48e4e commit 479a3ae
Show file tree
Hide file tree
Showing 25 changed files with 265 additions and 106 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CompletenessForStatsStatusEnum, Mission, MissionStatusEnum } from '@common/types/mission-types.ts'
import { MissionStatusEnum } from '@common/types/mission-types.ts'

import Text from '@common/components/ui/text.tsx'
import { Accent, Button, Icon, IconButton, Size, TagGroup, THEME } from '@mtes-mct/monitor-ui'
Expand All @@ -7,6 +7,8 @@ import React from 'react'
import { FlexboxGrid, Stack } from 'rsuite'
import styled from 'styled-components'
import { useDate } from '../../hooks/use-date.tsx'
import { useMission } from '../../hooks/use-mission.tsx'
import { Mission } from '../../types/mission-types.ts'
import MissionCompletenessForStatsTag from '../elements/mission-completeness-for-stats-tag.tsx'
import MissionSourceTag from '../elements/mission-source-tag.tsx'
import MissionStatusTag from '../elements/mission-status-tag.tsx'
Expand Down Expand Up @@ -37,7 +39,7 @@ const MissionPageHeaderWrapper: React.FC<MissionPageHeaderProps> = ({
exportLoading
}) => {
const { formatMissionName } = useDate()
const exportRapportEnabled = mission?.completenessForStats?.status === CompletenessForStatsStatusEnum.COMPLETE
const { status, completeForStats, exportRapportEnabled } = useMission(mission)

return (
<>
Expand All @@ -54,11 +56,8 @@ const MissionPageHeaderWrapper: React.FC<MissionPageHeaderProps> = ({
<Stack.Item>
<TagGroup>
<MissionSourceTag missionSource={mission?.missionSource} />
<MissionStatusTag status={mission?.status} />
<MissionCompletenessForStatsTag
missionStatus={mission?.status}
completenessForStats={mission?.completenessForStats}
/>
<MissionStatusTag status={status} />
<MissionCompletenessForStatsTag missionStatus={status} completenessForStats={completeForStats} />
</TagGroup>
</Stack.Item>
</Stack>
Expand Down Expand Up @@ -95,9 +94,7 @@ const MissionPageHeaderWrapper: React.FC<MissionPageHeaderProps> = ({
</FlexboxGrid.Item>
</FlexboxGrid>
</StyledHeader>
{mission?.status === MissionStatusEnum.ENDED && (
<MissionPageHeaderBanner completenessForStats={mission.completenessForStats} />
)}
{status === MissionStatusEnum.ENDED && <MissionPageHeaderBanner completenessForStats={completeForStats} />}
</>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ import {
MissionPatrolExportMutationArgs,
useLazyExportMissionReports
} from '../services/use-lazy-export-mission-reports.tsx'
import { BLOBTYPE, useDownloadFile } from './use-download-file.tsx'
import { ExportMode } from '../types/mission-export-types.ts'
import { BLOBTYPE, useDownloadFile } from './use-download-file.tsx'

interface ExportMissionHook {
loading: boolean
exportIsLoading: boolean
exportMissionReport: (args: MissionPatrolExportMutationArgs) => Promise<void>
}

export function useMissionReportExport(): ExportMissionHook {
const { handleDownload } = useDownloadFile()
const [getMissionReport] = useLazyExportMissionReports()
const [loading, setLoading] = useState<boolean>(false)
const [exportIsLoading, setLoading] = useState<boolean>(false)

const exportMissionReport = async (args: MissionPatrolExportMutationArgs) => {
setLoading(true)
Expand All @@ -36,5 +36,5 @@ export function useMissionReportExport(): ExportMissionHook {
}
}

return { exportMissionReport, loading }
return { exportMissionReport, exportIsLoading }
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { ActionType } from '../types/action-type'
import { MissionNavAction } from '../types/mission-action'
import { MissionSource } from '../types/mission-types'
import { MissionSourceEnum } from '../types/mission-types'

type ActionRegistryInput = { [key in ActionType]?: unknown }
type Input = { missionId: number; startDateTimeUtc: Date }

const ACTION_REGISTRY_INPUT: ActionRegistryInput = {
[ActionType.NOTE]: { endDateTimeUtc: new Date().toISOString() },
Expand All @@ -19,7 +18,7 @@ export function useMissionTimeline<T>(missionId?: number): TimelineHook<T> {
const input = {
missionId: Number(missionId),
actionType,
source: MissionSource.RAPPORTNAV,
source: MissionSourceEnum.RAPPORTNAV,
data: {
...(moreData ?? {}),
...(ACTION_REGISTRY_INPUT[actionType] ?? {}),
Expand Down
24 changes: 11 additions & 13 deletions frontend/src/v2/features/common/hooks/use-mission-type.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { MissionReinforcementTypeEnum, MissionReportTypeEnum, MissionTypeEnum } from '../types/mission-types.ts'

import { MissionReinforcementTypeEnum, MissionReportTypeEnum, MissionType } from '../types/mission-types.ts'

interface MissionHook {
getMissionTypeLabel: (type?: MissionTypeEnum) => string | undefined
getMissionTypeLabel: (type?: MissionType) => string | undefined
getReinforcementTypeLabel: (type?: MissionReinforcementTypeEnum) => string | undefined
getReportTypeLabel: (type?: MissionReportTypeEnum) => string | undefined
missionTypeOptions: { label: string; value: MissionTypeEnum }[]
missionTypeOptions: { label: string; value: MissionType }[]
reinforcementTypeOptions: { label: string; value: MissionReinforcementTypeEnum }[]
reportTypeOptions: { label: string; value: MissionReportTypeEnum }[]
}

const MISSION_TYPE_REGISTRY: Record<MissionTypeEnum, string> = {
[MissionTypeEnum.LAND]: 'Terre',
[MissionTypeEnum.SEA]: 'Mer',
[MissionTypeEnum.AIR]: 'Air'
const MISSION_TYPE_REGISTRY: Record<MissionType, string> = {
[MissionType.LAND]: 'Terre',
[MissionType.SEA]: 'Mer',
[MissionType.AIR]: 'Air'
}

const REINFORCEMENT_TYPE_REGISTRY: Record<MissionReinforcementTypeEnum, string> = {
Expand All @@ -32,8 +31,7 @@ const REPORT_TYPE_REGISTRY: Record<MissionReportTypeEnum, string> = {
}

export function useMissionType(): MissionHook {
const getMissionTypeLabel = (type?: MissionTypeEnum): string | undefined =>
type ? MISSION_TYPE_REGISTRY[type] : ''
const getMissionTypeLabel = (type?: MissionType): string | undefined => (type ? MISSION_TYPE_REGISTRY[type] : '')

const getReinforcementTypeLabel = (type?: MissionReinforcementTypeEnum): string | undefined =>
type ? REINFORCEMENT_TYPE_REGISTRY[type] : ''
Expand All @@ -42,9 +40,9 @@ export function useMissionType(): MissionHook {
type ? REPORT_TYPE_REGISTRY[type] : ''

const getMissionTypeOptions = () =>
Object.keys(MissionTypeEnum).map(key => ({
value: MissionTypeEnum[key as keyof typeof MissionTypeEnum],
label: MISSION_TYPE_REGISTRY[key as keyof typeof MissionTypeEnum]
Object.keys(MissionType).map(key => ({
value: MissionType[key as keyof typeof MissionType],
label: MISSION_TYPE_REGISTRY[key as keyof typeof MissionType]
}))

const getReinforcementTypeOptions = () =>
Expand Down
52 changes: 52 additions & 0 deletions frontend/src/v2/features/common/hooks/use-mission.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {} from '@common/types/env-mission-types'
import { CompletenessForStats, MissionStatusEnum } from '@common/types/mission-types'
import { useStore } from '@tanstack/react-store'
import { isAfter, isBefore, isSameDay, parseISO } from 'date-fns'
import { isNil } from 'lodash'
import { useEffect, useState } from 'react'
import { store } from '../../../store'
import { setMissionStatus } from '../../../store/slices/mission-reducer'
import { CompletenessForStatsStatusEnum, Mission } from '../types/mission-types'

interface MissionHook {
status?: MissionStatusEnum
exportRapportEnabled: boolean
completeForStats?: CompletenessForStats
}

export function useMission(mission?: Mission): MissionHook {
const [status, setStatus] = useState<MissionStatusEnum>()
const [completeForStats, setCompleteForStats] = useState<CompletenessForStats>()
const timelieCompleteForStats = useStore(store, state => state.timeline.completnessForStats)
const exportRapportEnabled = completeForStats?.status === CompletenessForStatsStatusEnum.COMPLETE

useEffect(() => {
if (!mission) return
const status = computeMissionStatus(mission.startDateTimeUtc, mission.endDateTimeUtc)
setStatus(status)
setMissionStatus(status)
}, [mission])

useEffect(() => {
setCompleteForStats({
status: timelieCompleteForStats?.status, //TODO: compute with generalIformation
sources: timelieCompleteForStats?.sources ////TODO: compute with generalIformation
})
}, [timelieCompleteForStats])

const computeMissionStatus = (startDate: string, endDate?: string) => {
const today = new Date()
if (isNil(endDate) || isNil(startDate)) return MissionStatusEnum.UNAVAILABLE
try {
if (isAfter(parseISO(endDate), today)) return MissionStatusEnum.IN_PROGRESS
if (isBefore(parseISO(endDate), today) || isSameDay(parseISO(endDate), today)) return MissionStatusEnum.ENDED
if (isAfter(parseISO(startDate), today) || isSameDay(parseISO(startDate), today))
return MissionStatusEnum.UPCOMING
} catch (e) {
console.error(`calculateMissionStatus - error with startDate: ${startDate}, endDate: ${endDate}`, e)
}
return MissionStatusEnum.UNAVAILABLE
}

return { status, completeForStats, exportRapportEnabled }
}
15 changes: 15 additions & 0 deletions frontend/src/v2/features/common/services/use-mission.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { skipToken, useQuery } from '@tanstack/react-query'
import axios from '../../../../query-client/axios'
import { Mission } from '../types/mission-types'

const useGetMissionQuery = (missionId?: string) => {
const fetchMission = (): Promise<Mission> => axios.get(`missions/${missionId}`).then(response => response.data)

const query = useQuery<Mission>({
queryKey: ['mission', missionId],
queryFn: missionId ? fetchMission : skipToken
})
return query
}

export default useGetMissionQuery
29 changes: 23 additions & 6 deletions frontend/src/v2/features/common/types/mission-types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ControlUnit } from '@common/types/control-unit-types.ts'

export enum MissionTypeEnum {
export enum MissionType {
AIR = 'AIR',
LAND = 'LAND',
SEA = 'SEA'
Expand All @@ -9,7 +9,7 @@ export enum MissionTypeEnum {
export type MissionULAMGeneralInfoInitial = {
startDateTimeUtc: string
endDateTimeUtc: string
missionTypes: MissionTypeEnum[]
missionTypes: MissionType[]
missionReportType?: MissionReportTypeEnum
reinforcementType?: MissionReinforcementTypeEnum
nbHourAtSea?: number
Expand All @@ -30,17 +30,29 @@ export enum MissionReinforcementTypeEnum {
DIRM = 'DIRM'
}

export enum MissionSource {
export enum MissionSourceEnum {
MONITORENV = 'MONITORENV',
MONITORFISH = 'MONITORFISH',
POSEIDON_CACEM = 'POSEIDON_CACEM',
POSEIDON_CNSP = 'POSEIDON_CNSP',
RAPPORTNAV = 'RAPPORTNAV'
}

export type MissionEnv = {
export enum CompletenessForStatsStatusEnum {
COMPLETE = 'COMPLETE',
INCOMPLETE = 'INCOMPLETE'
}

export enum MissionStatus {
UPCOMING = 'UPCOMING',
IN_PROGRESS = 'IN_PROGRESS',
ENDED = 'ENDED',
UNAVAILABLE = 'UNAVAILABLE'
}

export type Mission = {
id?: number
missionTypes: MissionTypeEnum[]
missionTypes: MissionType[]
controlUnits: ControlUnit[]
openBy?: string
completedBy?: string
Expand All @@ -50,8 +62,13 @@ export type MissionEnv = {
geom?: string
startDateTimeUtc: string
endDateTimeUtc?: string
missionSource: MissionSource
missionSource: MissionSourceEnum
hasMissionOrder: boolean
isUnderJdp: boolean
isGeometryComputedFromControls: boolean
}

export type CompletenessForStats = {
sources?: MissionSourceEnum[]
status?: CompletenessForStatsStatusEnum
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { Accent, Icon, IconButton, Size, THEME } from '@mtes-mct/monitor-ui'
import { useNavigate } from 'react-router-dom'
import { Stack } from 'rsuite'
import useDeleteActionMutation from '../../../common/services/use-delete-mission-action'
import { MissionSource } from '../../../common/types/mission-types'
import { MissionSourceEnum } from '../../../common/types/mission-types'

interface MissionActionHeaderActionProps {
actionId: string
missionId: number
source: MissionSource
source: MissionSourceEnum
}

export const MissionActionHeaderAction: React.FC<MissionActionHeaderActionProps> = ({
Expand All @@ -23,7 +23,7 @@ export const MissionActionHeaderAction: React.FC<MissionActionHeaderActionProps>
navigate(`/v2/pam/missions/${missionId}`)
}

const isDeleteDisabled = () => source !== MissionSource.RAPPORTNAV
const isDeleteDisabled = () => source !== MissionSourceEnum.RAPPORTNAV

return (
<Stack direction="row" spacing="0.5rem">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FC } from 'react'
import { MissionAction } from '../../../common/types/mission-action'
import { MissionSource } from '../../../common/types/mission-types'
import { MissionSourceEnum } from '../../../common/types/mission-types'
import MissionActionItemEnvControl from './mission-action-item-env-control'
import MissionActionItemFishControl from './mission-action-item-fish-control'
import MissionActionItemNavControl from './mission-action-item-nav-control'
Expand All @@ -11,9 +11,9 @@ const MissionActionItemControl: FC<{
isMissionFinished?: boolean
}> = ({ action, onChange, isMissionFinished }) => {
switch (action.source) {
case MissionSource.MONITORENV:
case MissionSourceEnum.MONITORENV:
return <MissionActionItemEnvControl action={action} onChange={onChange} />
case MissionSource.MONITORFISH:
case MissionSourceEnum.MONITORFISH:
return <MissionActionItemFishControl action={action} onChange={onChange} isMissionFinished={isMissionFinished} />
default:
return <MissionActionItemNavControl action={action} onChange={onChange} isMissionFinished={isMissionFinished} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { EnvControl, EnvControlInput, useEnvControl } from '../../hooks/use-cont
import MissionControlEnvError from '../ui/mission-control-env-error.tsx'
import { MissionControlTitle } from '../ui/mission-control-title.tsx'

const MAX_CONTROL = 1
export interface MissionControlEnvFormProps {
name: string
isToComplete?: boolean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { THEME } from '@mtes-mct/monitor-ui'
import { find } from 'lodash'
import { createElement, FC, Fragment, FunctionComponent } from 'react'
import { createElement, FC, Fragment, FunctionComponent, useEffect } from 'react'
import { Divider, Stack } from 'rsuite'
import { setTimelineCompleteForStats } from '../../../../store/slices/timeline-complete-for-stats-reducer'
import { useDate } from '../../../common/hooks/use-date'
import { useTimelineCompleteForStats } from '../../hooks/use-timeline-complete-for-stats'
import { MissionTimelineAction } from '../../types/mission-timeline-output'
import MissionTimelineEmpty from '../ui/mission-timeline-empty'
import MissionTimelineError from '../ui/mission-timeline-error'
Expand All @@ -26,6 +28,12 @@ const MissionTimelineWrapper: FC<MissionTimelineProps> = ({
groupBy
}) => {
const { groupByDay } = useDate()
const { computeCompleteForStats } = useTimelineCompleteForStats()
useEffect(() => {
const completenessForStats = computeCompleteForStats(actions)
setTimelineCompleteForStats(completenessForStats)
}, [actions])

Check warning on line 35 in frontend/src/v2/features/mission-timeline/components/layout/mission-timeline-wrapper.tsx

View workflow job for this annotation

GitHub Actions / build-and-test-frontend

React Hook useEffect has a missing dependency: 'computeCompleteForStats'. Either include it or remove the dependency array

if (isLoading) return <MissionTimelineLoader />
if (actions?.length === 0) return <MissionTimelineEmpty />
if (isError) return <MissionTimelineError error={isError} />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { CompletenessForStatsStatusEnum, MissionSourceEnum } from '../../common/types/mission-types'
import { MissionTimelineAction } from '../types/mission-timeline-output'

export function useTimelineCompleteForStats() {
const computeCompleteForStats = (actions: MissionTimelineAction[]) => {
const sources = new Set<MissionSourceEnum>()

actions.forEach(action => {
if (!action.isCompleteForStats && action.source) sources.add(action.source)
})
return {
sources: Array.from(sources),
status: sources.size === 0 ? CompletenessForStatsStatusEnum.COMPLETE : CompletenessForStatsStatusEnum.INCOMPLETE
}
}
return { computeCompleteForStats }
}
Loading

0 comments on commit 479a3ae

Please sign in to comment.