Skip to content

Commit

Permalink
Fishing map/vessel group events (#2834)
Browse files Browse the repository at this point in the history
  • Loading branch information
j8seangel authored Sep 19, 2024
2 parents a35cc57 + d6e4d2c commit 50ddad9
Show file tree
Hide file tree
Showing 25 changed files with 1,075 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@ export const selectActiveVesselGroupDataviews = createSelector(
(dataviews): UrlDataviewInstance[] => dataviews?.filter((d) => d.config?.visible)
)

export const selectVesselGroupReportDataview = createSelector(
[selectActiveVesselGroupDataviews, selectReportVesselGroupId],
(dataviews, reportVesselGroupId): UrlDataviewInstance | undefined =>
dataviews?.find(({ vesselGroup }) => vesselGroup?.id === reportVesselGroupId)
)

export const selectDetectionsMergedDataviewId = createSelector(
[selectActiveDetectionsDataviews],
(dataviews): string => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
useTimebarVesselGroupConnect,
useTimebarVisualisationConnect,
} from 'features/timebar/timebar.hooks'
import { selectActiveVesselGroupDataviews } from 'features/dataviews/selectors/dataviews.selectors'
import { selectVesselGroupReportDataview } from 'features/dataviews/selectors/dataviews.selectors'
import VGREvents from 'features/vessel-group-report/events/VGREvents'
import { useFetchVesselGroupReport } from './vessel-group-report.hooks'
import {
selectVesselGroupReportData,
Expand All @@ -31,19 +32,18 @@ function VesselGroupReport() {
const vesselGroup = useSelector(selectVesselGroupReportData)!
const reportStatus = useSelector(selectVesselGroupReportStatus)
const reportSection = useSelector(selectVesselGroupReportSection)
const dataviews = useSelector(selectActiveVesselGroupDataviews)
const reportDataview = useSelector(selectVesselGroupReportDataview)
const { dispatchTimebarVisualisation } = useTimebarVisualisationConnect()
const { dispatchTimebarSelectedVGId } = useTimebarVesselGroupConnect()

useEffect(() => {
fetchVesselGroupReport(vesselGroupId)
const reportDataview = dataviews?.find(({ vesselGroup }) => vesselGroup?.id === vesselGroupId)
if (reportDataview) {
dispatchTimebarVisualisation(TimebarVisualisations.VesselGroup)
dispatchTimebarSelectedVGId(reportDataview?.id)
}
}, [
dataviews,
reportDataview,
dispatchTimebarSelectedVGId,
dispatchTimebarVisualisation,
fetchVesselGroupReport,
Expand Down Expand Up @@ -83,16 +83,15 @@ function VesselGroupReport() {
{
id: 'events',
title: t('common.events', 'Events'),
disabled: true,
content: <p>Coming soon</p>,
content: <VGREvents />,
},
],
[t]
)

if (reportStatus === AsyncReducerStatus.Error) {
return <VesselGroupReportError />
}
// if (reportStatus === AsyncReducerStatus.Error) {
// return <VesselGroupReportError />
// }

return (
<div>
Expand All @@ -104,7 +103,7 @@ function VesselGroupReport() {
tabs={sectionTabs}
activeTab={reportSection}
onTabClick={changeTab}
mountAllTabsOnLoad
// mountAllTabsOnLoad
/>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.container {
padding: var(--space-M);
border-bottom: var(--border);
}

.summary {
font: var(--font-L);
color: var(--color-secondary-blue);
margin-block: var(--space-M);
}

.summary strong {
font-weight: 400;
color: var(--color-primary-blue);
}

.flex {
display: flex;
align-items: center;
justify-content: space-between;
}

.error {
display: flex;
align-items: center;
justify-content: center;
height: 30rem;
color: var(--color-danger-red);
}
144 changes: 144 additions & 0 deletions apps/fishing-map/features/vessel-group-report/events/VGREvents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { useSelector } from 'react-redux'
import { Fragment, useEffect, useMemo } from 'react'
import parse from 'html-react-parser'
import { DateTime } from 'luxon'
import {
useGetVesselGroupEventsStatsQuery,
VesselGroupEventsStatsResponseGroups,
} from 'queries/vessel-group-events-stats-api'
import { useTranslation } from 'react-i18next'
import { getFourwingsInterval } from '@globalfishingwatch/deck-loaders'
import VGREventsSubsectionSelector from 'features/vessel-group-report/events/VGREventsSubsectionSelector'
import VGREventsGraph from 'features/vessel-group-report/events/VGREventsGraph'
import {
selectVGREventsSubsection,
selectVGREventsVesselFilter,
selectVGREventsVesselsProperty,
} from 'features/vessel-group-report/vessel-group.config.selectors'
import { selectEventsDataviews } from 'features/dataviews/selectors/dataviews.categories.selectors'
import { useDataviewInstancesConnect } from 'features/workspace/workspace.hook'
import { selectReportVesselGroupId } from 'routes/routes.selectors'
import { useTimerangeConnect } from 'features/timebar/timebar.hooks'
import VesselGroupReportVesselsGraph from 'features/vessel-group-report/vessels/VesselGroupReportVesselsGraph'
import { ENCOUNTER_EVENTS_SOURCE_ID } from 'features/dataviews/dataviews.utils'
import { formatI18nDate } from 'features/i18n/i18nDate'
import VGREventsVesselPropertySelector from 'features/vessel-group-report/events/VGREventsVesselPropertySelector'
import VGREventsVesselsTable from 'features/vessel-group-report/events/VGREventsVesselsTable'
import ReportVesselsFilter from 'features/area-report/vessels/ReportVesselsFilter'
import { COLOR_PRIMARY_BLUE } from 'features/app/app.config'
import styles from './VGREvents.module.css'

function VGREvents() {
const { t } = useTranslation()
const vesselGroupId = useSelector(selectReportVesselGroupId)
const filter = useSelector(selectVGREventsVesselFilter)
const eventsSubsection = useSelector(selectVGREventsSubsection)
const eventsDataviews = useSelector(selectEventsDataviews)
const eventsDataview = eventsDataviews.find(({ id }) => id === eventsSubsection)
const vesselsGroupByProperty = useSelector(selectVGREventsVesselsProperty)
const { upsertDataviewInstance } = useDataviewInstancesConnect()
useEffect(() => {
if (eventsDataview) {
upsertDataviewInstance({
id: eventsDataview.id,
config: {
...eventsDataview.config,
visible: true,
'vessel-groups': [vesselGroupId],
},
})
}
}, [eventsDataview, upsertDataviewInstance, vesselGroupId])

const { start, end } = useTimerangeConnect()
const startMillis = DateTime.fromISO(start).toMillis()
const endMillis = DateTime.fromISO(end).toMillis()
const interval = getFourwingsInterval(startMillis, endMillis)

const { data, error, isLoading } = useGetVesselGroupEventsStatsQuery(
{
includes: ['TIME_SERIES', 'EVENTS_GROUPED'],
dataview: eventsDataview!,
groupBy: vesselsGroupByProperty.toUpperCase(),
vesselGroupId,
interval,
start,
end,
},
{
skip: !vesselGroupId || !eventsDataview,
}
)

let color = eventsDataview?.config?.color || COLOR_PRIMARY_BLUE
if (eventsDataview?.id === ENCOUNTER_EVENTS_SOURCE_ID) {
color = 'rgb(247 222 110)'
}
const filteredGroups = useMemo(() => {
if (!data) return null
if (!filter) return data.groups
const [filterProperty, filterValue] = filter.split(':')
if (vesselsGroupByProperty !== filterProperty) return data.groups
return data.groups.filter(({ name }) => name === filterValue)
}, [data, filter, vesselsGroupByProperty])

if (error) return <p className={styles.error}>{(error as any).message}</p>
if (!data) return null

return (
<Fragment>
<div className={styles.container}>
<VGREventsSubsectionSelector />
<h2 className={styles.summary}>
{parse(
t('vesselGroup.summaryEvents', {
defaultValue:
'<strong>{{vessels}} vessels</strong> from <strong>{{flags}} flags</strong> had <strong>{{activityQuantity}} {{activityUnit}}</strong> globally between <strong>{{start}}</strong> and <strong>{{end}}</strong>',
vessels: data.groups.reduce((acc, group) => acc + group.value, 0),
flags: data.groups.length,
activityQuantity: data.timeseries.reduce((acc, group) => acc + group.value, 0),
activityUnit: `${eventsDataview?.datasets?.[0]?.subcategory?.toLowerCase()} ${t(
'common.events',
'events'
).toLowerCase()}`,
start: formatI18nDate(start, {
format: DateTime.DATE_MED,
}),
end: formatI18nDate(end, {
format: DateTime.DATE_MED,
}),
})
)}
</h2>
<VGREventsGraph
color={color}
start={start}
end={end}
interval={interval}
timeseries={data.timeseries || []}
/>
</div>
<div className={styles.container}>
<div className={styles.flex}>
<label>{t('common.vessels', 'Vessels')}</label>
<VGREventsVesselPropertySelector />
</div>
<VesselGroupReportVesselsGraph
data={filteredGroups as VesselGroupEventsStatsResponseGroups}
color={eventsDataview?.config?.color}
property={vesselsGroupByProperty}
filterQueryParam="vGREventsVesselFilter"
pageQueryParam="vGREventsVesselPage"
/>
<ReportVesselsFilter
filter={filter}
filterQueryParam="vGREventsVesselFilter"
pageQueryParam="vGREventsVesselPage"
/>
<VGREventsVesselsTable />
</div>
</Fragment>
)
}

export default VGREvents
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.graph {
width: 100%;
height: 30rem;
margin-top: var(--space-S);
}

.graph svg {
overflow: visible;
}

.tooltipContainer {
background-color: var(--color-white);
border: var(--border);
padding: var(--space-S);
}

.tooltipRow {
display: flex;
align-items: center;
}

.tooltipLabel {
color: var(--color-secondary-blue);
}

.tooltipValueDot {
width: 1rem;
height: 1rem;
border-radius: 0.5rem;
display: inline-flex;
border: var(--border);
background-color: currentcolor;
margin-right: 0.5rem;
}

.graph :global(.recharts-tooltip-cursor) {
stroke: var(--color-secondary-blue);
}
Loading

0 comments on commit 50ddad9

Please sign in to comment.