From 5c490bfcf44db1361333d16a24169c95d9447d67 Mon Sep 17 00:00:00 2001 From: satellitestudiodesign Date: Tue, 17 Sep 2024 19:10:42 +0200 Subject: [PATCH 1/9] stats query and graphs --- .../selectors/dataviews.selectors.ts | 6 + .../vessel-group-report/VesselGroupReport.tsx | 19 +- .../events/VesselGroupReportEvents.module.css | 29 +++ .../events/VesselGroupReportEvents.tsx | 121 +++++++++++++ .../VesselGroupReportEventsGraph.module.css | 38 ++++ .../events/VesselGroupReportEventsGraph.tsx | 165 ++++++++++++++++++ ...selGroupReportEventsSubsectionSelector.tsx | 67 +++++++ ...roupReportEventsVesselPropertySelector.tsx | 41 +++++ .../vessel-group-report.config.ts | 3 +- .../vessel-group-report.slice.ts | 2 +- .../vessel-group.config.selectors.ts | 3 + .../VesselGroupReportVessels.module.css | 1 - .../vessels/VesselGroupReportVessels.tsx | 20 ++- .../vessels/VesselGroupReportVesselsGraph.tsx | 49 +++--- .../vessel-groups/vessel-groups.types.ts | 8 +- .../public/locales/source/translations.json | 5 +- apps/fishing-map/queries/base.ts | 5 + .../queries/vessel-group-events-stats-api.ts | 60 +++++++ apps/fishing-map/reducers.ts | 2 + apps/fishing-map/store.ts | 2 + 20 files changed, 603 insertions(+), 43 deletions(-) create mode 100644 apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.module.css create mode 100644 apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx create mode 100644 apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsGraph.module.css create mode 100644 apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsGraph.tsx create mode 100644 apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsSubsectionSelector.tsx create mode 100644 apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselPropertySelector.tsx create mode 100644 apps/fishing-map/queries/vessel-group-events-stats-api.ts diff --git a/apps/fishing-map/features/dataviews/selectors/dataviews.selectors.ts b/apps/fishing-map/features/dataviews/selectors/dataviews.selectors.ts index 0e85115401..6058aa36fb 100644 --- a/apps/fishing-map/features/dataviews/selectors/dataviews.selectors.ts +++ b/apps/fishing-map/features/dataviews/selectors/dataviews.selectors.ts @@ -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 => { diff --git a/apps/fishing-map/features/vessel-group-report/VesselGroupReport.tsx b/apps/fishing-map/features/vessel-group-report/VesselGroupReport.tsx index 6545738a43..2c22aaabc2 100644 --- a/apps/fishing-map/features/vessel-group-report/VesselGroupReport.tsx +++ b/apps/fishing-map/features/vessel-group-report/VesselGroupReport.tsx @@ -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 VesselGroupReportEvents from 'features/vessel-group-report/events/VesselGroupReportEvents' import { useFetchVesselGroupReport } from './vessel-group-report.hooks' import { selectVesselGroupReportData, @@ -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, @@ -83,16 +83,15 @@ function VesselGroupReport() { { id: 'events', title: t('common.events', 'Events'), - disabled: true, - content:

Coming soon

, + content: , }, ], [t] ) - if (reportStatus === AsyncReducerStatus.Error) { - return - } + // if (reportStatus === AsyncReducerStatus.Error) { + // return + // } return (
@@ -104,7 +103,7 @@ function VesselGroupReport() { tabs={sectionTabs} activeTab={reportSection} onTabClick={changeTab} - mountAllTabsOnLoad + // mountAllTabsOnLoad />
) diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.module.css b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.module.css new file mode 100644 index 0000000000..eecabc86a4 --- /dev/null +++ b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.module.css @@ -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); +} diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx new file mode 100644 index 0000000000..9331c7712d --- /dev/null +++ b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx @@ -0,0 +1,121 @@ +import { useSelector } from 'react-redux' +import { Fragment, useEffect } from 'react' +import parse from 'html-react-parser' +import { DateTime } from 'luxon' +import { useGetVesselGroupEventsStatsQuery } from 'queries/vessel-group-events-stats-api' +import { useTranslation } from 'react-i18next' +import { getFourwingsInterval } from '@globalfishingwatch/deck-loaders' +import VesselGroupReportEventsSubsectionSelector from 'features/vessel-group-report/events/VesselGroupReportEventsSubsectionSelector' +import VesselGroupReportEventsGraph from 'features/vessel-group-report/events/VesselGroupReportEventsGraph' +import { + selectVesselGroupReportEventsSubsection, + selectVesselGroupReportEventsVesselsProperty, +} 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 VesselGroupReportEventsVesselPropertySelector from 'features/vessel-group-report/events/VesselGroupReportEventsVesselPropertySelector' +import styles from './VesselGroupReportEvents.module.css' + +function VesselGroupReportEvents() { + const { t } = useTranslation() + const vesselGroupId = useSelector(selectReportVesselGroupId) + const eventsSubsection = useSelector(selectVesselGroupReportEventsSubsection) + const eventsDataviews = useSelector(selectEventsDataviews) + const eventsDataview = eventsDataviews.find(({ id }) => id === eventsSubsection) + const vesselsGroupByProperty = useSelector(selectVesselGroupReportEventsVesselsProperty) + 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'], + datasetId: eventsDataview?.datasets?.[0]?.id as string, + groupBy: vesselsGroupByProperty.toUpperCase(), + vesselGroupId, + interval, + start, + end, + }, + { + skip: !vesselGroupId || !eventsDataview, + } + ) + + let color = eventsDataview?.config?.color || 'rgb(22, 63, 137)' + if (eventsDataview?.id === ENCOUNTER_EVENTS_SOURCE_ID) { + color = 'rgb(247 222 110)' + } + + if (error) return

{(error as any).message}

+ if (!data) return null + + return ( + +
+ +

+ {parse( + t('vesselGroup.summaryEvents', { + defaultValue: + '{{vessels}} vessels from {{flags}} flags had {{activityQuantity}} {{activityUnit}} globally between {{start}} and {{end}}', + 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, + }), + }) + )} +

+ +
+
+
+ + +
+ +
+
+ ) +} + +export default VesselGroupReportEvents diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsGraph.module.css b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsGraph.module.css new file mode 100644 index 0000000000..646f38de19 --- /dev/null +++ b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsGraph.module.css @@ -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); +} diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsGraph.tsx b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsGraph.tsx new file mode 100644 index 0000000000..b8dea16ade --- /dev/null +++ b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsGraph.tsx @@ -0,0 +1,165 @@ +import React, { useMemo } from 'react' +import { + ResponsiveContainer, + CartesianGrid, + XAxis, + YAxis, + Tooltip, + Line, + ComposedChart, +} from 'recharts' +import min from 'lodash/min' +import max from 'lodash/max' +import { DateTime, Duration, DurationUnit } from 'luxon' +import { useTranslation } from 'react-i18next' +import { FourwingsInterval } from '@globalfishingwatch/deck-loaders' +import i18n from 'features/i18n/i18n' +import { formatDateForInterval, getUTCDateTime } from 'utils/dates' +import { formatI18nNumber } from 'features/i18n/i18nNumber' +import { tickFormatter } from 'features/area-report/reports.utils' +import styles from './VesselGroupReportEventsGraph.module.css' + +type VesselGroupReportEventsGraphTooltipProps = { + active: boolean + payload: { + name: string + dataKey: string + label: number + value: number + payload: any + color: string + unit: string + }[] + label: number + timeChunkInterval: FourwingsInterval +} + +const ReportGraphTooltip = (props: any) => { + const { active, payload, label, timeChunkInterval } = + props as VesselGroupReportEventsGraphTooltipProps + + if (active && payload && payload.length) { + const date = getUTCDateTime(label).setLocale(i18n.language) + const formattedLabel = formatDateForInterval(date, timeChunkInterval) + return ( +
+

{formattedLabel}

+

+ {formatI18nNumber(payload[0].payload.value)} {payload[0].unit} +

+
+ ) + } + + return null +} + +const formatDateTicks = (tick: string, timeChunkInterval: FourwingsInterval) => { + const date = getUTCDateTime(tick).setLocale(i18n.language) + return formatDateForInterval(date, timeChunkInterval) +} + +const graphMargin = { top: 0, right: 0, left: -20, bottom: -10 } + +export default function VesselGroupReportEventsGraph({ + color = 'rgb(22, 63, 137)', + end, + interval, + start, + timeseries, +}: { + color?: string + end: string + interval: FourwingsInterval + start: string + timeseries: { date: string; value: number }[] +}) { + const { t } = useTranslation() + const startMillis = DateTime.fromISO(start).toMillis() + const endMillis = DateTime.fromISO(end).toMillis() + + const domain = useMemo(() => { + if (start && end && interval) { + const cleanEnd = DateTime.fromISO(end, { zone: 'utc' }) + .minus({ [interval]: 1 }) + .toISO() as string + return [new Date(start).getTime(), new Date(cleanEnd).getTime()] + } + }, [start, end, interval]) + + const dataMin: number = timeseries.length + ? (min(timeseries.map(({ value }: { value: number }) => value)) as number) + : 0 + const dataMax: number = timeseries.length + ? (max(timeseries.map(({ value }: { value: number }) => value)) as number) + : 0 + + const domainPadding = (dataMax - dataMin) / 8 + const paddedDomain: [number, number] = [ + Math.max(0, Math.floor(dataMin - domainPadding)), + Math.ceil(dataMax + domainPadding), + ] + const intervalDiff = Math.floor( + Duration.fromMillis(endMillis - startMillis).as(interval.toLowerCase() as DurationUnit) + ) + const fullTimeseries = useMemo(() => { + if (!timeseries || !domain) { + return [] + } + return Array(intervalDiff) + .fill(0) + .map((_, i) => i) + .map((i) => { + const d = DateTime.fromMillis(startMillis, { zone: 'UTC' }) + .plus({ [interval]: i }) + .toISO() + return { + date: d, + value: timeseries.find(({ date }: { date: string }) => date === d)?.value || 0, + } + }) + }, [timeseries, domain, intervalDiff, startMillis, interval]) + + if (!fullTimeseries.length || !domain) { + return null + } + + return ( +
+ + + + formatDateTicks(tick, interval)} + axisLine={paddedDomain[0] === 0} + /> + + {timeseries?.length && ( + } /> + )} + + + +
+ ) +} diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsSubsectionSelector.tsx b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsSubsectionSelector.tsx new file mode 100644 index 0000000000..db4fa9b968 --- /dev/null +++ b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsSubsectionSelector.tsx @@ -0,0 +1,67 @@ +import { useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { Choice, ChoiceOption } from '@globalfishingwatch/ui-components' +import { useLocationConnect } from 'routes/routes.hook' +import { selectVesselGroupReportStatus } from 'features/vessel-group-report/vessel-group-report.slice' +import { AsyncReducerStatus } from 'utils/async-slice' +import { VesselGroupReportEventsSubsection } from 'features/vessel-groups/vessel-groups.types' +import { selectVesselGroupReportEventsSubsection } from '../vessel-group.config.selectors' + +function VesselGroupReportEventsSubsectionSelector() { + const { t } = useTranslation() + const { dispatchQueryParams } = useLocationConnect() + const vesselGroupReportStatus = useSelector(selectVesselGroupReportStatus) + const subsection = useSelector(selectVesselGroupReportEventsSubsection) + const loading = vesselGroupReportStatus === AsyncReducerStatus.Loading + const options: ChoiceOption[] = [ + { + id: 'encounter-events', + label: t('event.encounter_other', 'Encounters'), + disabled: loading, + }, + { + id: 'loitering', + label: t('event.loitering_other', 'Loitering events'), + disabled: true, + tooltip: t('common.comingSoon', 'Coming Soon!'), + tooltipPlacement: 'top', + }, + { + id: 'gaps', + label: t('event.gap_other', 'AIS off events'), + disabled: true, + tooltip: t('common.comingSoon', 'Coming Soon!'), + tooltipPlacement: 'top', + }, + { + id: 'port_visits', + label: t('event.port_visit_other', 'Port visits'), + disabled: true, + tooltip: t('common.comingSoon', 'Coming Soon!'), + tooltipPlacement: 'top', + }, + ] + + const onSelectSubsection = (option: ChoiceOption) => { + if (subsection !== option.id) { + // trackEvent({ + // category: TrackCategory.Analysis, + // action: `Click on ${option.id} activity graph`, + // }) + dispatchQueryParams({ vesselGroupReportEventsSubsection: option.id }) + } + } + + const selectedOption = subsection ? options.find((o) => o.id === subsection) : options[0] + + return ( + + ) +} + +export default VesselGroupReportEventsSubsectionSelector diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselPropertySelector.tsx b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselPropertySelector.tsx new file mode 100644 index 0000000000..d497ce03aa --- /dev/null +++ b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselPropertySelector.tsx @@ -0,0 +1,41 @@ +import { useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { Choice, ChoiceOption } from '@globalfishingwatch/ui-components' +import { useLocationConnect } from 'routes/routes.hook' +import { VesselGroupReportEventsVesselsProperty } from 'features/vessel-groups/vessel-groups.types' +import { selectVesselGroupReportEventsVesselsProperty } from '../vessel-group.config.selectors' + +function VesselGroupReportEventsVesselPropertySelector() { + const { t } = useTranslation() + const { dispatchQueryParams } = useLocationConnect() + const property = useSelector(selectVesselGroupReportEventsVesselsProperty) + const options: ChoiceOption[] = [ + { + id: 'flag', + label: t('common.flag', 'Flag'), + }, + { + id: 'geartype', + label: t('common.geartype', 'Gear type'), + }, + ] + + const onSelectSubsection = (option: ChoiceOption) => { + if (property !== option.id) { + dispatchQueryParams({ vesselGroupReportEventsVesselsProperty: option.id }) + } + } + + const selectedOption = property ? options.find((o) => o.id === property) : options[0] + + return ( + + ) +} + +export default VesselGroupReportEventsVesselPropertySelector diff --git a/apps/fishing-map/features/vessel-group-report/vessel-group-report.config.ts b/apps/fishing-map/features/vessel-group-report/vessel-group-report.config.ts index 2e2fadf24b..eb933c0d09 100644 --- a/apps/fishing-map/features/vessel-group-report/vessel-group-report.config.ts +++ b/apps/fishing-map/features/vessel-group-report/vessel-group-report.config.ts @@ -8,7 +8,8 @@ export const DEFAULT_VESSEL_GROUP_REPORT_STATE: VesselGroupReportState = { vesselGroupReportSection: 'vessels', vesselGroupReportVesselsSubsection: 'flag', vesselGroupReportActivitySubsection: 'fishing-effort', - vesselGroupReportEventsSubsection: 'encounters', + vesselGroupReportEventsSubsection: 'encounter-events', + vesselGroupReportEventsVesselsProperty: 'flag', vesselGroupReportVesselPage: 0, vesselGroupReportResultsPerPage: REPORT_VESSELS_PER_PAGE, vesselGroupReportVesselsOrderProperty: 'shipname', diff --git a/apps/fishing-map/features/vessel-group-report/vessel-group-report.slice.ts b/apps/fishing-map/features/vessel-group-report/vessel-group-report.slice.ts index dc8a71a28b..e771088d4d 100644 --- a/apps/fishing-map/features/vessel-group-report/vessel-group-report.slice.ts +++ b/apps/fishing-map/features/vessel-group-report/vessel-group-report.slice.ts @@ -51,7 +51,7 @@ export const fetchVesselGroupReportThunk = createAsyncThunk( { condition: (params: FetchVesselGroupReportThunkParams, { getState }) => { const { status } = (getState() as VesselGroupReportSliceState)?.vesselGroupReport - if (status === AsyncReducerStatus.Loading) { + if (status === AsyncReducerStatus.Loading || status === AsyncReducerStatus.Finished) { return false } return true diff --git a/apps/fishing-map/features/vessel-group-report/vessel-group.config.selectors.ts b/apps/fishing-map/features/vessel-group-report/vessel-group.config.selectors.ts index 3164a0e0e6..0eed9ece40 100644 --- a/apps/fishing-map/features/vessel-group-report/vessel-group.config.selectors.ts +++ b/apps/fishing-map/features/vessel-group-report/vessel-group.config.selectors.ts @@ -33,6 +33,9 @@ export const selectVesselGroupReportActivitySubsection = selectVesselGroupReport export const selectVesselGroupReportEventsSubsection = selectVesselGroupReportStateProperty( 'vesselGroupReportEventsSubsection' ) +export const selectVesselGroupReportEventsVesselsProperty = selectVesselGroupReportStateProperty( + 'vesselGroupReportEventsVesselsProperty' +) export const selectVesselGroupReportVesselFilter = selectVesselGroupReportStateProperty( 'vesselGroupReportVesselFilter' diff --git a/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVessels.module.css b/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVessels.module.css index 3e20a354e6..e94a925b46 100644 --- a/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVessels.module.css +++ b/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVessels.module.css @@ -1,6 +1,5 @@ .container { padding: var(--space-M); - border-bottom: var(--border); } .summary { diff --git a/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVessels.tsx b/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVessels.tsx index bf4881d9d5..44e4cd7533 100644 --- a/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVessels.tsx +++ b/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVessels.tsx @@ -6,21 +6,31 @@ import ReportVesselsFilter from 'features/area-report/vessels/ReportVesselsFilte import { selectVesselGroupReportVessels } from 'features/vessel-group-report/vessel-group-report.slice' import { selectVesselGroupReportVesselsFlags, + selectVesselGroupReportVesselsGraphDataGrouped, selectVesselGroupReportVesselsTimeRange, } from 'features/vessel-group-report/vessels/vessel-group-report-vessels.selectors' import { formatI18nDate } from 'features/i18n/i18nDate' -import { selectVesselGroupReportVesselFilter } from '../vessel-group.config.selectors' +import { selectVesselGroupReportDataview } from 'features/dataviews/selectors/dataviews.selectors' +import { + selectVesselGroupReportVesselFilter, + selectVesselGroupReportVesselsSubsection, +} from '../vessel-group.config.selectors' import VesselGroupReportVesselsGraphSelector from './VesselGroupReportVesselsGraphSelector' -import VesselGroupReportVesselsGraph from './VesselGroupReportVesselsGraph' +import VesselGroupReportVesselsGraph, { + VesselGroupReportVesselsGraphProperty, +} from './VesselGroupReportVesselsGraph' import VesselGroupReportVesselsTable from './VesselGroupReportVesselsTable' import styles from './VesselGroupReportVessels.module.css' function VesselGroupReportVessels() { const { t } = useTranslation() const vessels = useSelector(selectVesselGroupReportVessels) + const subsection = useSelector(selectVesselGroupReportVesselsSubsection) + const reportDataview = useSelector(selectVesselGroupReportDataview) const timeRange = useSelector(selectVesselGroupReportVesselsTimeRange) const flags = useSelector(selectVesselGroupReportVesselsFlags) const filter = useSelector(selectVesselGroupReportVesselFilter) + const data = useSelector(selectVesselGroupReportVesselsGraphDataGrouped) return (
{timeRange && vessels && flags && ( @@ -42,7 +52,11 @@ function VesselGroupReportVessels() { )} - + { @@ -37,6 +35,8 @@ const ReportGraphTooltip = (props: any) => { if (EMPTY_API_VALUES.includes(label)) parsedLabel = t('common.unknown', 'Unknown') else if (type === 'flag') { parsedLabel = formatInfoField(label, 'flag') as string + } else if (type === 'geartype') { + parsedLabel = formatInfoField(label, 'geartypes') as string } if (active && payload && payload.length) { return ( @@ -61,18 +61,19 @@ const ReportGraphTooltip = (props: any) => { } const CustomTick = (props: any) => { - const { x, y, payload, width, visibleTicksCount } = props + const { x, y, payload, width, visibleTicksCount, property } = props const { t } = useTranslation() - const subsection = useSelector(selectVesselGroupReportVesselsSubsection) const { dispatchQueryParams } = useLocationConnect() const isOtherCategory = payload.value === OTHERS_CATEGORY_LABEL const isCategoryInteractive = !EMPTY_API_VALUES.includes(payload.value) const getTickLabel = (label: string) => { if (EMPTY_API_VALUES.includes(label)) return t('analysis.unknown', 'Unknown') - switch (subsection) { + switch (property) { case 'flag': return formatInfoField(label, 'flag') as string + case 'geartype': + return formatInfoField(label, 'geartypes') as string default: return label } @@ -86,7 +87,9 @@ const CustomTick = (props: any) => { const onLabelClick = () => { dispatchQueryParams({ - vesselGroupReportVesselFilter: `${filterProperties[subsection]}:${payload.value}`, + vesselGroupReportVesselFilter: `${ + filterProperties[property as VesselGroupReportVesselsSubsection] + }:${payload.value}`, vesselGroupReportVesselPage: 0, }) } @@ -126,15 +129,17 @@ const CustomTick = (props: any) => { ) } -export default function VesselGroupReportVesselsGraph() { - const dataviews = useSelector(selectActiveVesselGroupDataviews) - const reportVesselGroupId = useSelector(selectReportVesselGroupId) - const reportDataview = dataviews?.find( - ({ vesselGroup }) => vesselGroup?.id === reportVesselGroupId - ) +export type VesselGroupReportVesselsGraphProperty = 'flag' | 'geartype' - const data = useSelector(selectVesselGroupReportVesselsGraphDataGrouped) - const selectedReportVesselGraph = useSelector(selectVesselGroupReportVesselsSubsection) +export default function VesselGroupReportVesselsGraph({ + data, + color = 'rgb(22, 63, 137)', + property, +}: { + data: VesselGroupEventsStatsResponseGroups + color?: string + property: VesselGroupReportVesselsGraphProperty +}) { return (
@@ -151,14 +156,8 @@ export default function VesselGroupReportVesselsGraph() { bottom: 0, }} > - {data && ( - } /> - )} - + {data && } />} + formatI18nNumber(entry.value)} @@ -169,7 +168,7 @@ export default function VesselGroupReportVesselsGraph() { interval="equidistantPreserveStart" tickLine={false} minTickGap={-1000} - tick={} + tick={} tickMargin={0} /> diff --git a/apps/fishing-map/features/vessel-groups/vessel-groups.types.ts b/apps/fishing-map/features/vessel-groups/vessel-groups.types.ts index bc074964b2..00ee668f82 100644 --- a/apps/fishing-map/features/vessel-groups/vessel-groups.types.ts +++ b/apps/fishing-map/features/vessel-groups/vessel-groups.types.ts @@ -1,7 +1,12 @@ export type VesselGroupReportSection = 'vessels' | 'insights' | 'activity' | 'events' export type VesselGroupReportVesselsSubsection = 'flag' | 'shiptypes' | 'geartypes' | 'source' export type VesselGroupReportActivitySubsection = 'fishing-effort' | 'presence' -export type VesselGroupReportEventsSubsection = 'fishing' | 'encounters' | 'port' | 'loitering' +export type VesselGroupReportEventsVesselsProperty = 'flag' | 'geartype' +export type VesselGroupReportEventsSubsection = + | 'encounter-events' + | 'loitering' + | 'gaps' + | 'port_visits' export type VesselGroupReportVesselsOrderProperty = 'shipname' | 'flag' | 'shiptype' export type VesselGroupReportVesselsOrderDirection = 'asc' | 'desc' @@ -11,6 +16,7 @@ export type VesselGroupReportState = { vesselGroupReportVesselsSubsection?: VesselGroupReportVesselsSubsection vesselGroupReportActivitySubsection?: VesselGroupReportActivitySubsection vesselGroupReportEventsSubsection?: VesselGroupReportEventsSubsection + vesselGroupReportEventsVesselsProperty?: VesselGroupReportEventsVesselsProperty vesselGroupReportVesselPage?: number vesselGroupReportResultsPerPage?: number vesselGroupReportVesselFilter?: string diff --git a/apps/fishing-map/public/locales/source/translations.json b/apps/fishing-map/public/locales/source/translations.json index 9c9f4781eb..8faa565338 100644 --- a/apps/fishing-map/public/locales/source/translations.json +++ b/apps/fishing-map/public/locales/source/translations.json @@ -62,7 +62,7 @@ "statsDisclaimerDynamic": "During this time, the minimum and maximum values at any given {{interval}} and place inside your area were {{min}} {{unit}} and {{max}} {{unit}}.", "statsDisclaimerStatic": "The average value for your area is {{mean}} {{unit}}. The minimum and maximum are {{min}} {{unit}} and {{max}} {{unit}}.", "summary": "{{vessels}} vessels{{sources}} had {{activityQuantity}} {{activityUnit}} of {{activityType}} in the area between {{start}} and {{end}}", - "summaryNoVessels": "{{sources}} {{activityQuantity}} {{activityUnit}} {{activityType}} in the area between {{start}} and {{end}}", + "summaryNoVessels": "{{vessels}} vessels from {{flags}} flags had {{activityQuantity}} {{activityUnit}} globally between {{start}} and {{end}}", "timeoutError": "This is taking more than expected, please wait", "timeRangeTooLong": "Reports are only allowed for time ranges up to one year", "title": "Analysis", @@ -497,6 +497,8 @@ "fishing_other": "Fishing events", "fishingAction": "Fishing started at {{start}} for {{duration}}", "fishingActionIn": "Fishing in {{regionName}}", + "gap": "AIS Off event", + "gap_other": "AIS Off events", "gapActionIn": "Likely disabling in {{regionName}}", "hourAbbreviated": "{{count}}h", "hourAbbreviated_one": "{{count}}h", @@ -1024,6 +1026,7 @@ "saveForLaterTooltip": "You'll find the group in the activity layers filters or your user panel button", "searchLimit": "Search is limited up to {{limit}} vessels", "summary": "This group contains {{vessels}} vessels from {{flags}} flags active from {{start}} to {{end}}", + "summaryEvents": "This group contains {{vessels}} vessels from {{flags}} flags active from {{start}} to {{end}}", "tooManyVessels_one": "Maximum number of vessels is {{count}}", "tooManyVessels_other": "Maximum number of vessels is {{count}}", "uploadPublic": "Allow other users to see this vessel group when you share a workspace", diff --git a/apps/fishing-map/queries/base.ts b/apps/fishing-map/queries/base.ts index 487e654fea..bc7a2ac36d 100644 --- a/apps/fishing-map/queries/base.ts +++ b/apps/fishing-map/queries/base.ts @@ -1,6 +1,11 @@ import { BaseQueryFn } from '@reduxjs/toolkit/query/react' +import { stringify } from 'qs' import { GFWAPI, ParsedAPIError, parseAPIError } from '@globalfishingwatch/api-client' +export function getQueryParamsResolved(params: Record) { + return stringify(params, { arrayFormat: 'indices', addQueryPrefix: true }) +} + export const gfwBaseQuery = ( { baseUrl, method }: { baseUrl: string; method?: 'GET' | 'POST' } = { diff --git a/apps/fishing-map/queries/vessel-group-events-stats-api.ts b/apps/fishing-map/queries/vessel-group-events-stats-api.ts new file mode 100644 index 0000000000..6f3b5558a5 --- /dev/null +++ b/apps/fishing-map/queries/vessel-group-events-stats-api.ts @@ -0,0 +1,60 @@ +import { createApi } from '@reduxjs/toolkit/query/react' +import { getQueryParamsResolved, gfwBaseQuery } from 'queries/base' +import { RootState } from 'reducers' +import { FourwingsInterval } from '@globalfishingwatch/deck-loaders' + +export type VesselGroupEventsStatsParams = { + vesselGroupId: string + datasetId: string + includes: string[] + start: string + end: string + interval: FourwingsInterval + groupBy: string // 'FLAG' | 'GEARTYPE' +} + +export type VesselGroupEventsStatsResponseGroups = { name: string; value: number }[] + +export type VesselGroupEventsStatsResponse = { + timeseries: { date: string; value: number }[] + groups: VesselGroupEventsStatsResponseGroups +} + +// Define a service using a base URL and expected endpoints +export const vesselGroupEventsStatsApi = createApi({ + reducerPath: 'vesselGroupEventsStatsApi', + baseQuery: gfwBaseQuery({ + baseUrl: `/events/stats`, + }), + endpoints: (builder) => ({ + getVesselGroupEventsStats: builder.query< + VesselGroupEventsStatsResponse, + VesselGroupEventsStatsParams + >({ + query: ({ vesselGroupId, datasetId, includes, start, end, interval, groupBy }) => { + const query = { + includes, + 'vessel-groups': [vesselGroupId], + 'start-date': start, + 'end-date': end, + datasets: [datasetId], + 'timeseries-interval': interval, + 'group-by': groupBy, + } + return { + url: `${getQueryParamsResolved(query)}`, + } + }, + }), + }), +}) + +// Export hooks for usage in functional components, which are +// auto-generated based on the defined endpoints +export const { useGetVesselGroupEventsStatsQuery } = vesselGroupEventsStatsApi + +export const selectVesselGroupEventsStatsApiSlice = (state: RootState) => + state.vesselGroupEventsStatsApi + +export const selectVesselGroupEventsStats = (params: VesselGroupEventsStatsParams) => + vesselGroupEventsStatsApi.endpoints.getVesselGroupEventsStats.select(params) diff --git a/apps/fishing-map/reducers.ts b/apps/fishing-map/reducers.ts index 89f457efa2..fa23319509 100644 --- a/apps/fishing-map/reducers.ts +++ b/apps/fishing-map/reducers.ts @@ -3,6 +3,7 @@ import { dataviewStatsApi } from 'queries/stats-api' import { vesselEventsApi } from 'queries/vessel-events-api' import { vesselInsightApi } from 'queries/vessel-insight-api' import { vesselSearchApi } from 'queries/search-api' +import { vesselGroupEventsStatsApi } from 'queries/vessel-group-events-stats-api' import areasReducer from 'features/areas/areas.slice' import bigQueryReducer from 'features/bigquery/bigquery.slice' import connectedRoutes from 'routes/routes' @@ -36,6 +37,7 @@ export const rootReducer = combineReducers({ [vesselSearchApi.reducerPath]: vesselSearchApi.reducer, [vesselEventsApi.reducerPath]: vesselEventsApi.reducer, [vesselInsightApi.reducerPath]: vesselInsightApi.reducer, + [vesselGroupEventsStatsApi.reducerPath]: vesselGroupEventsStatsApi.reducer, areas: areasReducer, bigQuery: bigQueryReducer, datasets: datasetsReducer, diff --git a/apps/fishing-map/store.ts b/apps/fishing-map/store.ts index e9645f51c6..99cc908486 100644 --- a/apps/fishing-map/store.ts +++ b/apps/fishing-map/store.ts @@ -11,6 +11,7 @@ import { dataviewStatsApi } from 'queries/stats-api' import { vesselSearchApi } from 'queries/search-api' import { vesselEventsApi } from 'queries/vessel-events-api' import { vesselInsightApi } from 'queries/vessel-insight-api' +import { vesselGroupEventsStatsApi } from 'queries/vessel-group-events-stats-api' import connectedRoutes from 'routes/routes' import { routerQueryMiddleware, routerWorkspaceMiddleware } from 'routes/routes.middlewares' import { rootReducer } from './reducers' @@ -58,6 +59,7 @@ const makeStore = () => { vesselSearchApi.middleware, vesselEventsApi.middleware, vesselInsightApi.middleware, + vesselGroupEventsStatsApi.middleware, routerQueryMiddleware, routerWorkspaceMiddleware, routerMiddleware as Middleware From 915e1984acfd0d48b4c2138fc45b2eae214fc3a1 Mon Sep 17 00:00:00 2001 From: satellitestudiodesign Date: Wed, 18 Sep 2024 10:33:36 +0200 Subject: [PATCH 2/9] vessels table --- .../events/VesselGroupReportEvents.tsx | 2 + .../VesselGroupReportEventsVesselsTable.tsx | 106 ++++++++++++ ...selGroupReportEventsVesselsTableFooter.tsx | 162 ++++++++++++++++++ .../vessel-group-report-events.selectors.ts | 108 ++++++++++++ .../vessel-group-report.config.ts | 2 + .../vessel-group.config.selectors.ts | 10 ++ .../VesselGroupReportVesselsTableFooter.tsx | 16 +- .../vessel-group-report-vessels.selectors.ts | 2 +- .../vessel-groups/vessel-groups.types.ts | 3 + .../queries/vessel-group-events-stats-api.ts | 36 +++- 10 files changed, 437 insertions(+), 10 deletions(-) create mode 100644 apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTable.tsx create mode 100644 apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTableFooter.tsx create mode 100644 apps/fishing-map/features/vessel-group-report/events/vessel-group-report-events.selectors.ts diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx index 9331c7712d..2d4bc126c7 100644 --- a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx +++ b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx @@ -19,6 +19,7 @@ import VesselGroupReportVesselsGraph from 'features/vessel-group-report/vessels/ import { ENCOUNTER_EVENTS_SOURCE_ID } from 'features/dataviews/dataviews.utils' import { formatI18nDate } from 'features/i18n/i18nDate' import VesselGroupReportEventsVesselPropertySelector from 'features/vessel-group-report/events/VesselGroupReportEventsVesselPropertySelector' +import VesselGroupReportEventsVesselsTable from 'features/vessel-group-report/events/VesselGroupReportEventsVesselsTable' import styles from './VesselGroupReportEvents.module.css' function VesselGroupReportEvents() { @@ -113,6 +114,7 @@ function VesselGroupReportEvents() { color={eventsDataview?.config?.color} property={vesselsGroupByProperty} /> +
) diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTable.tsx b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTable.tsx new file mode 100644 index 0000000000..e6fbc471f9 --- /dev/null +++ b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTable.tsx @@ -0,0 +1,106 @@ +import { useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' +import cx from 'classnames' +import { Fragment } from 'react' +import { + useGetVesselGroupEventsVesselsQuery, + VesselGroupEventsVesselsParams, +} from 'queries/vessel-group-events-stats-api' +import { EMPTY_FIELD_PLACEHOLDER, formatInfoField } from 'utils/info' +import { useLocationConnect } from 'routes/routes.hook' +import VesselLink from 'features/vessel/VesselLink' +import VesselPin from 'features/vessel/VesselPin' +import { selectWorkspaceStatus } from 'features/workspace/workspace.selectors' +import { AsyncReducerStatus } from 'utils/async-slice' +import { + selectFetchVesselGroupReportEventsVesselsParams, + selectVesselGroupReportEventsVesselsPaginated, +} from 'features/vessel-group-report/events/vessel-group-report-events.selectors' +import VesselGroupReportEventsVesselsTableFooter from 'features/vessel-group-report/events/VesselGroupReportEventsVesselsTableFooter' +import styles from '../vessels/VesselGroupReportVesselsTable.module.css' + +export default function VesselGroupReportEventsVesselsTable() { + const { t } = useTranslation() + const { dispatchQueryParams } = useLocationConnect() + const params = useSelector(selectFetchVesselGroupReportEventsVesselsParams) + const workspaceStatus = useSelector(selectWorkspaceStatus) + const { error, isLoading } = useGetVesselGroupEventsVesselsQuery( + params as VesselGroupEventsVesselsParams, + { + skip: !params, + } + ) + const vessels = useSelector(selectVesselGroupReportEventsVesselsPaginated) + + const onPinClick = () => { + dispatchQueryParams({ viewOnlyVesselGroup: false }) + } + + return ( + +
+
+
+ {t('common.name', 'Name')} +
+
{t('vessel.mmsi', 'mmsi')}
+
{t('layer.flagState_one', 'Flag state')}
+
{t('common.events', 'events')}
+ {/*
+ {t('vessel.vessel_type', 'Vessel Type')} +
*/} + {vessels?.map((vessel, i) => { + const { vesselId, numEvents, identity } = vessel + const { shipname, flag, ssvid, dataset } = identity || {} + console.log('identity:', identity) + const isLastRow = i === vessels.length - 1 + const name = formatInfoField(shipname, 'name') as string + const workspaceReady = workspaceStatus === AsyncReducerStatus.Finished + return ( + +
+ +
+
+ {workspaceReady ? ( + + {name} + + ) : ( + name + )} +
+
+ {ssvid || EMPTY_FIELD_PLACEHOLDER} +
+
+ {formatInfoField(flag, 'flag') || EMPTY_FIELD_PLACEHOLDER} +
+
+ {numEvents} +
+
+ ) + })} +
+
+ +
+ ) +} diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTableFooter.tsx b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTableFooter.tsx new file mode 100644 index 0000000000..c830badd1e --- /dev/null +++ b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTableFooter.tsx @@ -0,0 +1,162 @@ +import { useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' +import cx from 'classnames' +import { Fragment } from 'react' +import { unparse as unparseCSV } from 'papaparse' +import { saveAs } from 'file-saver' +import { Button, IconButton } from '@globalfishingwatch/ui-components' +import I18nNumber from 'features/i18n/i18nNumber' +import { useLocationConnect } from 'routes/routes.hook' +import { selectTimeRange } from 'features/app/selectors/app.timebar.selectors' +import { REPORT_SHOW_MORE_VESSELS_PER_PAGE, REPORT_VESSELS_PER_PAGE } from 'data/config' +import { selectVesselGroupReportData } from 'features/vessel-group-report/vessel-group-report.slice' +import { formatInfoField } from 'utils/info' +import { + selectVesselGroupReportEventsVessels, + selectVesselGroupReportEventsVesselsPagination, +} from 'features/vessel-group-report/events/vessel-group-report-events.selectors' +import { selectVesselGroupReportVesselFilter } from '../vessel-group.config.selectors' +import styles from '../vessels/VesselGroupReportVesselsTableFooter.module.css' + +export default function VesselGroupReportVesselsTableFooter() { + const { t } = useTranslation() + const { dispatchQueryParams } = useLocationConnect() + const vesselGroup = useSelector(selectVesselGroupReportData) + const allVessels = useSelector(selectVesselGroupReportEventsVessels) + const reportVesselFilter = useSelector(selectVesselGroupReportVesselFilter) + const pagination = useSelector(selectVesselGroupReportEventsVesselsPagination) + const { start, end } = useSelector(selectTimeRange) + + if (!allVessels?.length) return null + + const onDownloadVesselsClick = () => { + const vessels = allVessels?.map((vessel) => { + const { ...rest } = vessel + return rest + }) + if (vessels?.length) { + // trackEvent({ + // category: TrackCategory.Analysis, + // action: `Click 'Download CSV'`, + // label: `region name: ${reportAreaName} | timerange: ${start} - ${end} | filters: ${reportVesselFilter}`, + // }) + const csv = unparseCSV(vessels) + const blob = new Blob([csv], { type: 'text/plain;charset=utf-8' }) + saveAs(blob, `${formatInfoField(vesselGroup?.name, 'name')}-${start}-${end}.csv`) + } + } + + const onPrevPageClick = () => { + dispatchQueryParams({ vesselGroupReportEventsVesselPage: pagination.page - 1 }) + } + const onNextPageClick = () => { + dispatchQueryParams({ vesselGroupReportEventsVesselPage: pagination.page + 1 }) + } + const onShowMoreClick = () => { + dispatchQueryParams({ + vesselGroupReportEventsResultsPerPage: REPORT_SHOW_MORE_VESSELS_PER_PAGE, + vesselGroupReportEventsVesselPage: 0, + }) + // trackEvent({ + // category: TrackCategory.Analysis, + // action: `Click on show more vessels`, + // }) + } + const onShowLessClick = () => { + dispatchQueryParams({ + vesselGroupReportEventsResultsPerPage: REPORT_VESSELS_PER_PAGE, + reportVesselPage: 0, + }) + // trackEvent({ + // category: TrackCategory.Analysis, + // action: `Click on show less vessels`, + // }) + } + // const onAddToVesselGroup = () => { + // const dataviewIds = heatmapDataviews.map(({ id }) => id) + // dispatch(setVesselGroupConfirmationMode('saveAndNavigate')) + // if (dataviewIds?.length) { + // dispatch(setVesselGroupCurrentDataviewIds(dataviewIds)) + // } + // trackEvent({ + // category: TrackCategory.VesselGroups, + // action: 'add_to_vessel_group', + // label: 'report', + // }) + // } + + const isShowingMore = pagination.resultsPerPage === REPORT_SHOW_MORE_VESSELS_PER_PAGE + const hasLessVesselsThanAPage = + pagination.page === 0 && pagination?.resultsNumber < pagination?.resultsPerPage + const isLastPaginationPage = + pagination?.offset + pagination?.resultsPerPage >= pagination?.totalFiltered + + return ( +
+
+ +
+ + + {`${pagination?.offset + 1} - ${ + isLastPaginationPage + ? pagination?.totalFiltered + : pagination?.offset + pagination?.resultsPerPage + }`}{' '} + + +
+ + + {reportVesselFilter && ( + + {t('common.of', 'of')}{' '} + + )} + {' '} + {t('common.vessel', { count: pagination?.total })} + +
+
+
+ {/* */} + +
+
+ ) +} diff --git a/apps/fishing-map/features/vessel-group-report/events/vessel-group-report-events.selectors.ts b/apps/fishing-map/features/vessel-group-report/events/vessel-group-report-events.selectors.ts new file mode 100644 index 0000000000..3d1b23f1fb --- /dev/null +++ b/apps/fishing-map/features/vessel-group-report/events/vessel-group-report-events.selectors.ts @@ -0,0 +1,108 @@ +import { createSelector } from '@reduxjs/toolkit' +import { + selectVesselGroupEventsStatsApiSlice, + selectVesselGroupEventsVessels, + VesselGroupEventsVesselsParams, +} from 'queries/vessel-group-events-stats-api' +import { selectVesselGroupReportData } from 'features/vessel-group-report/vessel-group-report.slice' +import { getSearchIdentityResolved, getVesselId } from 'features/vessel/vessel.utils' +import { selectTimeRange } from 'features/app/selectors/app.timebar.selectors' +import { selectReportVesselGroupId } from 'routes/routes.selectors' +import { selectEventsDataviews } from 'features/dataviews/selectors/dataviews.categories.selectors' +import { + selectVesselGroupReportEventsResultsPerPage, + selectVesselGroupReportEventsSubsection, + selectVesselGroupReportEventsVesselPage, + selectVesselGroupReportVesselFilter, +} from 'features/vessel-group-report/vessel-group.config.selectors' +import { getVesselsFiltered } from 'features/area-report/reports.utils' +import { REPORT_FILTER_PROPERTIES } from 'features/vessel-group-report/vessels/vessel-group-report-vessels.selectors' + +export const selectFetchVesselGroupReportEventsVesselsParams = createSelector( + [ + selectTimeRange, + selectReportVesselGroupId, + selectEventsDataviews, + selectVesselGroupReportEventsSubsection, + ], + ({ start, end }, reportVesselGroupId, eventsDataviews, eventsSubsection) => { + const eventsDataview = eventsDataviews.find(({ id }) => id === eventsSubsection) + if (!reportVesselGroupId || !eventsDataview) { + return + } + return { + datasetId: eventsDataview?.datasets?.[0]?.id as string, + vesselGroupId: reportVesselGroupId, + start, + end, + } as VesselGroupEventsVesselsParams + } +) + +export const selectVesselGroupReportEventsVesselsData = createSelector( + [selectVesselGroupEventsStatsApiSlice, selectFetchVesselGroupReportEventsVesselsParams], + (vesselGroupEventsStatsApi, params) => { + if (!params) { + return + } + return selectVesselGroupEventsVessels(params)({ vesselGroupEventsStatsApi })?.data + } +) + +export const selectVesselGroupReportEventsVessels = createSelector( + [selectVesselGroupReportEventsVesselsData, selectVesselGroupReportData], + (data, vesselGroup) => { + if (!data || !vesselGroup) { + return + } + const insightVessels = vesselGroup?.vessels?.flatMap((vessel) => { + const vesselWithEvents = data?.find((v) => v.vesselId === getVesselId(vessel)) + if (!vesselWithEvents) { + return [] + } + return { ...vesselWithEvents, identity: getSearchIdentityResolved(vessel) } + }) + return insightVessels.sort((a, b) => b.numEvents - a.numEvents) + } +) + +export const selectVesselGroupReportEventsVesselsFiltered = createSelector( + [selectVesselGroupReportEventsVessels, selectVesselGroupReportVesselFilter], + (vessels, filter) => { + if (!vessels?.length) return null + return getVesselsFiltered(vessels, filter, REPORT_FILTER_PROPERTIES) + } +) + +export const selectVesselGroupReportEventsVesselsPaginated = createSelector( + [ + selectVesselGroupReportEventsVesselsFiltered, + selectVesselGroupReportEventsVesselPage, + selectVesselGroupReportEventsResultsPerPage, + ], + (vessels, page, resultsPerPage) => { + if (!vessels?.length) return [] + return vessels.slice(resultsPerPage * page, resultsPerPage * (page + 1)) + } +) + +export const selectVesselGroupReportEventsVesselsPagination = createSelector( + [ + selectVesselGroupReportEventsVesselsPaginated, + selectVesselGroupReportEventsVessels, + selectVesselGroupReportEventsVesselsFiltered, + selectVesselGroupReportEventsVesselPage, + selectVesselGroupReportEventsResultsPerPage, + ], + (vessels, allVessels, allVesselsFiltered, page = 0, resultsPerPage) => { + return { + page, + offset: resultsPerPage * page, + resultsPerPage: + typeof resultsPerPage === 'number' ? resultsPerPage : parseInt(resultsPerPage), + resultsNumber: vessels!?.length, + totalFiltered: allVesselsFiltered!?.length, + total: allVessels!?.length, + } + } +) diff --git a/apps/fishing-map/features/vessel-group-report/vessel-group-report.config.ts b/apps/fishing-map/features/vessel-group-report/vessel-group-report.config.ts index eb933c0d09..af8e70d991 100644 --- a/apps/fishing-map/features/vessel-group-report/vessel-group-report.config.ts +++ b/apps/fishing-map/features/vessel-group-report/vessel-group-report.config.ts @@ -12,6 +12,8 @@ export const DEFAULT_VESSEL_GROUP_REPORT_STATE: VesselGroupReportState = { vesselGroupReportEventsVesselsProperty: 'flag', vesselGroupReportVesselPage: 0, vesselGroupReportResultsPerPage: REPORT_VESSELS_PER_PAGE, + vesselGroupReportEventsVesselPage: 0, + vesselGroupReportEventsResultsPerPage: REPORT_VESSELS_PER_PAGE, vesselGroupReportVesselsOrderProperty: 'shipname', vesselGroupReportVesselsOrderDirection: 'asc', } diff --git a/apps/fishing-map/features/vessel-group-report/vessel-group.config.selectors.ts b/apps/fishing-map/features/vessel-group-report/vessel-group.config.selectors.ts index 0eed9ece40..b78100e46d 100644 --- a/apps/fishing-map/features/vessel-group-report/vessel-group.config.selectors.ts +++ b/apps/fishing-map/features/vessel-group-report/vessel-group.config.selectors.ts @@ -52,3 +52,13 @@ export const selectVesselGroupReportVesselsOrderProperty = selectVesselGroupRepo export const selectVesselGroupReportVesselsOrderDirection = selectVesselGroupReportStateProperty( 'vesselGroupReportVesselsOrderDirection' ) + +export const selectVesselGroupReportEventsVesselFilter = selectVesselGroupReportStateProperty( + 'vesselGroupReportEventsVesselFilter' +) +export const selectVesselGroupReportEventsVesselPage = selectVesselGroupReportStateProperty( + 'vesselGroupReportEventsVesselPage' +) +export const selectVesselGroupReportEventsResultsPerPage = selectVesselGroupReportStateProperty( + 'vesselGroupReportEventsResultsPerPage' +) diff --git a/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVesselsTableFooter.tsx b/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVesselsTableFooter.tsx index 6d759bedfa..4ebb8c9fc3 100644 --- a/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVesselsTableFooter.tsx +++ b/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVesselsTableFooter.tsx @@ -58,20 +58,20 @@ export default function VesselGroupReportVesselsTableFooter() { vesselGroupReportResultsPerPage: REPORT_SHOW_MORE_VESSELS_PER_PAGE, vesselGroupReportVesselPage: 0, }) - trackEvent({ - category: TrackCategory.Analysis, - action: `Click on show more vessels`, - }) + // trackEvent({ + // category: TrackCategory.Analysis, + // action: `Click on show more vessels`, + // }) } const onShowLessClick = () => { dispatchQueryParams({ vesselGroupReportResultsPerPage: REPORT_VESSELS_PER_PAGE, reportVesselPage: 0, }) - trackEvent({ - category: TrackCategory.Analysis, - action: `Click on show less vessels`, - }) + // trackEvent({ + // category: TrackCategory.Analysis, + // action: `Click on show less vessels`, + // }) } // const onAddToVesselGroup = () => { // const dataviewIds = heatmapDataviews.map(({ id }) => id) diff --git a/apps/fishing-map/features/vessel-group-report/vessels/vessel-group-report-vessels.selectors.ts b/apps/fishing-map/features/vessel-group-report/vessels/vessel-group-report-vessels.selectors.ts index 88a735b008..92d87557d1 100644 --- a/apps/fishing-map/features/vessel-group-report/vessels/vessel-group-report-vessels.selectors.ts +++ b/apps/fishing-map/features/vessel-group-report/vessels/vessel-group-report-vessels.selectors.ts @@ -60,7 +60,7 @@ export const selectVesselGroupReportVesselsParsed = createSelector( ) type ReportFilterProperty = FilterProperty | 'source' -const REPORT_FILTER_PROPERTIES: Record = { +export const REPORT_FILTER_PROPERTIES: Record = { ...FILTER_PROPERTIES, source: ['source'], } diff --git a/apps/fishing-map/features/vessel-groups/vessel-groups.types.ts b/apps/fishing-map/features/vessel-groups/vessel-groups.types.ts index 00ee668f82..3fd89c670e 100644 --- a/apps/fishing-map/features/vessel-groups/vessel-groups.types.ts +++ b/apps/fishing-map/features/vessel-groups/vessel-groups.types.ts @@ -20,6 +20,9 @@ export type VesselGroupReportState = { vesselGroupReportVesselPage?: number vesselGroupReportResultsPerPage?: number vesselGroupReportVesselFilter?: string + vesselGroupReportEventsVesselPage?: number + vesselGroupReportEventsResultsPerPage?: number + vesselGroupReportEventsVesselFilter?: string vesselGroupReportVesselsOrderProperty?: VesselGroupReportVesselsOrderProperty vesselGroupReportVesselsOrderDirection?: VesselGroupReportVesselsOrderDirection } diff --git a/apps/fishing-map/queries/vessel-group-events-stats-api.ts b/apps/fishing-map/queries/vessel-group-events-stats-api.ts index 6f3b5558a5..4cae0fe3d8 100644 --- a/apps/fishing-map/queries/vessel-group-events-stats-api.ts +++ b/apps/fishing-map/queries/vessel-group-events-stats-api.ts @@ -13,12 +13,26 @@ export type VesselGroupEventsStatsParams = { groupBy: string // 'FLAG' | 'GEARTYPE' } +export type VesselGroupEventsVesselsParams = { + vesselGroupId: string + datasetId: string + start: string + end: string +} + export type VesselGroupEventsStatsResponseGroups = { name: string; value: number }[] export type VesselGroupEventsStatsResponse = { timeseries: { date: string; value: number }[] groups: VesselGroupEventsStatsResponseGroups } +export type VesselGroupEventsVesselsResponse = { + numEvents: number + portCountry: string + portName: string + totalDuration: number + vesselId: string +}[] // Define a service using a base URL and expected endpoints export const vesselGroupEventsStatsApi = createApi({ @@ -46,15 +60,35 @@ export const vesselGroupEventsStatsApi = createApi({ } }, }), + getVesselGroupEventsVessels: builder.query< + VesselGroupEventsVesselsResponse, + VesselGroupEventsVesselsParams + >({ + query: ({ vesselGroupId, datasetId, start, end }) => { + const query = { + 'vessel-groups': [vesselGroupId], + 'start-date': start, + 'end-date': end, + dataset: datasetId, + } + return { + url: `-by-vessel${getQueryParamsResolved(query)}`, + } + }, + }), }), }) // Export hooks for usage in functional components, which are // auto-generated based on the defined endpoints -export const { useGetVesselGroupEventsStatsQuery } = vesselGroupEventsStatsApi +export const { useGetVesselGroupEventsStatsQuery, useGetVesselGroupEventsVesselsQuery } = + vesselGroupEventsStatsApi export const selectVesselGroupEventsStatsApiSlice = (state: RootState) => state.vesselGroupEventsStatsApi export const selectVesselGroupEventsStats = (params: VesselGroupEventsStatsParams) => vesselGroupEventsStatsApi.endpoints.getVesselGroupEventsStats.select(params) + +export const selectVesselGroupEventsVessels = (params: VesselGroupEventsVesselsParams) => + vesselGroupEventsStatsApi.endpoints.getVesselGroupEventsVessels.select(params) From d042d72d5d376553f0cef2b07a776923a18dbb0f Mon Sep 17 00:00:00 2001 From: satellitestudiodesign Date: Wed, 18 Sep 2024 10:48:17 +0200 Subject: [PATCH 3/9] add vessel filter --- .../events/VesselGroupReportEvents.tsx | 10 ++++++++ .../VesselGroupReportEventsVesselsTable.tsx | 7 +----- ...selGroupReportEventsVesselsTableFooter.tsx | 4 ++-- .../vessel-group-report-events.selectors.ts | 6 ++--- .../vessels/VesselGroupReportVessels.tsx | 2 ++ .../vessels/VesselGroupReportVesselsGraph.tsx | 23 ++++++++++++++----- 6 files changed, 35 insertions(+), 17 deletions(-) diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx index 2d4bc126c7..819c7708f6 100644 --- a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx +++ b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx @@ -9,6 +9,7 @@ import VesselGroupReportEventsSubsectionSelector from 'features/vessel-group-rep import VesselGroupReportEventsGraph from 'features/vessel-group-report/events/VesselGroupReportEventsGraph' import { selectVesselGroupReportEventsSubsection, + selectVesselGroupReportEventsVesselFilter, selectVesselGroupReportEventsVesselsProperty, } from 'features/vessel-group-report/vessel-group.config.selectors' import { selectEventsDataviews } from 'features/dataviews/selectors/dataviews.categories.selectors' @@ -20,11 +21,13 @@ import { ENCOUNTER_EVENTS_SOURCE_ID } from 'features/dataviews/dataviews.utils' import { formatI18nDate } from 'features/i18n/i18nDate' import VesselGroupReportEventsVesselPropertySelector from 'features/vessel-group-report/events/VesselGroupReportEventsVesselPropertySelector' import VesselGroupReportEventsVesselsTable from 'features/vessel-group-report/events/VesselGroupReportEventsVesselsTable' +import ReportVesselsFilter from 'features/area-report/vessels/ReportVesselsFilter' import styles from './VesselGroupReportEvents.module.css' function VesselGroupReportEvents() { const { t } = useTranslation() const vesselGroupId = useSelector(selectReportVesselGroupId) + const filter = useSelector(selectVesselGroupReportEventsVesselFilter) const eventsSubsection = useSelector(selectVesselGroupReportEventsSubsection) const eventsDataviews = useSelector(selectEventsDataviews) const eventsDataview = eventsDataviews.find(({ id }) => id === eventsSubsection) @@ -113,6 +116,13 @@ function VesselGroupReportEvents() { data={data.groups} color={eventsDataview?.config?.color} property={vesselsGroupByProperty} + filterQueryParam="vesselGroupReportEventsVesselFilter" + pageQueryParam="vesselGroupReportEventsVesselPage" + /> +
diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTable.tsx b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTable.tsx index e6fbc471f9..b3646f80f3 100644 --- a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTable.tsx +++ b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTable.tsx @@ -49,13 +49,8 @@ export default function VesselGroupReportEventsVesselsTable() {
{t('vessel.mmsi', 'mmsi')}
{t('layer.flagState_one', 'Flag state')}
{t('common.events', 'events')}
- {/*
- {t('vessel.vessel_type', 'Vessel Type')} -
*/} {vessels?.map((vessel, i) => { - const { vesselId, numEvents, identity } = vessel - const { shipname, flag, ssvid, dataset } = identity || {} - console.log('identity:', identity) + const { vesselId, numEvents, shipname, flag, ssvid, dataset } = vessel const isLastRow = i === vessels.length - 1 const name = formatInfoField(shipname, 'name') as string const workspaceReady = workspaceStatus === AsyncReducerStatus.Finished diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTableFooter.tsx b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTableFooter.tsx index c830badd1e..6b74f62ab6 100644 --- a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTableFooter.tsx +++ b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTableFooter.tsx @@ -15,7 +15,7 @@ import { selectVesselGroupReportEventsVessels, selectVesselGroupReportEventsVesselsPagination, } from 'features/vessel-group-report/events/vessel-group-report-events.selectors' -import { selectVesselGroupReportVesselFilter } from '../vessel-group.config.selectors' +import { selectVesselGroupReportEventsVesselFilter } from '../vessel-group.config.selectors' import styles from '../vessels/VesselGroupReportVesselsTableFooter.module.css' export default function VesselGroupReportVesselsTableFooter() { @@ -23,7 +23,7 @@ export default function VesselGroupReportVesselsTableFooter() { const { dispatchQueryParams } = useLocationConnect() const vesselGroup = useSelector(selectVesselGroupReportData) const allVessels = useSelector(selectVesselGroupReportEventsVessels) - const reportVesselFilter = useSelector(selectVesselGroupReportVesselFilter) + const reportVesselFilter = useSelector(selectVesselGroupReportEventsVesselFilter) const pagination = useSelector(selectVesselGroupReportEventsVesselsPagination) const { start, end } = useSelector(selectTimeRange) diff --git a/apps/fishing-map/features/vessel-group-report/events/vessel-group-report-events.selectors.ts b/apps/fishing-map/features/vessel-group-report/events/vessel-group-report-events.selectors.ts index 3d1b23f1fb..99c5a5ad37 100644 --- a/apps/fishing-map/features/vessel-group-report/events/vessel-group-report-events.selectors.ts +++ b/apps/fishing-map/features/vessel-group-report/events/vessel-group-report-events.selectors.ts @@ -12,8 +12,8 @@ import { selectEventsDataviews } from 'features/dataviews/selectors/dataviews.ca import { selectVesselGroupReportEventsResultsPerPage, selectVesselGroupReportEventsSubsection, + selectVesselGroupReportEventsVesselFilter, selectVesselGroupReportEventsVesselPage, - selectVesselGroupReportVesselFilter, } from 'features/vessel-group-report/vessel-group.config.selectors' import { getVesselsFiltered } from 'features/area-report/reports.utils' import { REPORT_FILTER_PROPERTIES } from 'features/vessel-group-report/vessels/vessel-group-report-vessels.selectors' @@ -60,14 +60,14 @@ export const selectVesselGroupReportEventsVessels = createSelector( if (!vesselWithEvents) { return [] } - return { ...vesselWithEvents, identity: getSearchIdentityResolved(vessel) } + return { ...vesselWithEvents, ...getSearchIdentityResolved(vessel) } }) return insightVessels.sort((a, b) => b.numEvents - a.numEvents) } ) export const selectVesselGroupReportEventsVesselsFiltered = createSelector( - [selectVesselGroupReportEventsVessels, selectVesselGroupReportVesselFilter], + [selectVesselGroupReportEventsVessels, selectVesselGroupReportEventsVesselFilter], (vessels, filter) => { if (!vessels?.length) return null return getVesselsFiltered(vessels, filter, REPORT_FILTER_PROPERTIES) diff --git a/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVessels.tsx b/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVessels.tsx index 44e4cd7533..62427ed98b 100644 --- a/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVessels.tsx +++ b/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVessels.tsx @@ -56,6 +56,8 @@ function VesselGroupReportVessels() { data={data} color={reportDataview?.config?.color} property={subsection as VesselGroupReportVesselsGraphProperty} + filterQueryParam="vesselGroupReportVesselFilter" + pageQueryParam="vesselGroupReportVesselPage" /> { } const CustomTick = (props: any) => { - const { x, y, payload, width, visibleTicksCount, property } = props + const { x, y, payload, width, visibleTicksCount, property, filterQueryParam, pageQueryParam } = + props const { t } = useTranslation() const { dispatchQueryParams } = useLocationConnect() const isOtherCategory = payload.value === OTHERS_CATEGORY_LABEL @@ -87,10 +88,10 @@ const CustomTick = (props: any) => { const onLabelClick = () => { dispatchQueryParams({ - vesselGroupReportVesselFilter: `${ - filterProperties[property as VesselGroupReportVesselsSubsection] - }:${payload.value}`, - vesselGroupReportVesselPage: 0, + [filterQueryParam]: `${filterProperties[property as VesselGroupReportVesselsSubsection]}:${ + payload.value + }`, + [pageQueryParam]: 0, }) } @@ -135,10 +136,14 @@ export default function VesselGroupReportVesselsGraph({ data, color = 'rgb(22, 63, 137)', property, + filterQueryParam, + pageQueryParam, }: { data: VesselGroupEventsStatsResponseGroups color?: string property: VesselGroupReportVesselsGraphProperty + filterQueryParam: string + pageQueryParam: string }) { return ( @@ -168,7 +173,13 @@ export default function VesselGroupReportVesselsGraph({ interval="equidistantPreserveStart" tickLine={false} minTickGap={-1000} - tick={} + tick={ + + } tickMargin={0} /> From 67778090f00d1c45b5e35e5fc1a090ba9a038980 Mon Sep 17 00:00:00 2001 From: satellitestudiodesign Date: Wed, 18 Sep 2024 11:08:39 +0200 Subject: [PATCH 4/9] apply filter to graph --- .../events/VesselGroupReportEvents.tsx | 16 +++++++++++++--- .../vessel-group-report-events.selectors.ts | 7 ++++++- .../vessels/VesselGroupReportVesselsGraph.tsx | 5 ++--- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx index 819c7708f6..894be17497 100644 --- a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx +++ b/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx @@ -1,8 +1,11 @@ import { useSelector } from 'react-redux' -import { Fragment, useEffect } from 'react' +import { Fragment, useEffect, useMemo } from 'react' import parse from 'html-react-parser' import { DateTime } from 'luxon' -import { useGetVesselGroupEventsStatsQuery } from 'queries/vessel-group-events-stats-api' +import { + useGetVesselGroupEventsStatsQuery, + VesselGroupEventsStatsResponseGroups, +} from 'queries/vessel-group-events-stats-api' import { useTranslation } from 'react-i18next' import { getFourwingsInterval } from '@globalfishingwatch/deck-loaders' import VesselGroupReportEventsSubsectionSelector from 'features/vessel-group-report/events/VesselGroupReportEventsSubsectionSelector' @@ -70,6 +73,13 @@ function VesselGroupReportEvents() { 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

{(error as any).message}

if (!data) return null @@ -113,7 +123,7 @@ function VesselGroupReportEvents() { b.numEvents - a.numEvents) } diff --git a/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVesselsGraph.tsx b/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVesselsGraph.tsx index afd1955104..439d54b153 100644 --- a/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVesselsGraph.tsx +++ b/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVesselsGraph.tsx @@ -1,13 +1,11 @@ import React, { Fragment } from 'react' import cx from 'classnames' -import { useSelector } from 'react-redux' import { BarChart, Bar, XAxis, Tooltip, ResponsiveContainer, LabelList } from 'recharts' import { useTranslation } from 'react-i18next' import { VesselGroupEventsStatsResponseGroups } from 'queries/vessel-group-events-stats-api' import I18nNumber, { formatI18nNumber } from 'features/i18n/i18nNumber' import { EMPTY_API_VALUES, OTHERS_CATEGORY_LABEL } from 'features/area-report/reports.config' import { formatInfoField } from 'utils/info' -import { selectVesselGroupReportVesselsSubsection } from 'features/vessel-group-report/vessel-group.config.selectors' import { useLocationConnect } from 'routes/routes.hook' import { VesselGroupReportVesselsSubsection } from 'features/vessel-groups/vessel-groups.types' import styles from './VesselGroupReportVesselsGraph.module.css' @@ -79,10 +77,11 @@ const CustomTick = (props: any) => { return label } } - const filterProperties: Record = { + const filterProperties: Record = { flag: 'flag', shiptypes: 'type', geartypes: 'gear', + geartype: 'gear', source: 'source', } From eb2a145d1444b24d9fe66d5a0ae67b775da4b0cb Mon Sep 17 00:00:00 2001 From: satellitestudiodesign Date: Wed, 18 Sep 2024 16:13:06 +0200 Subject: [PATCH 5/9] shorten file and variable names --- .../vessel-group-report/VesselGroupReport.tsx | 4 +- ...Events.module.css => VGREvents.module.css} | 0 ...selGroupReportEvents.tsx => VGREvents.tsx} | 42 ++++++++-------- ...h.module.css => VGREventsGraph.module.css} | 0 ...portEventsGraph.tsx => VGREventsGraph.tsx} | 9 ++-- ...or.tsx => VGREventsSubsectionSelector.tsx} | 12 ++--- ...sx => VGREventsVesselPropertySelector.tsx} | 12 ++--- ...elsTable.tsx => VGREventsVesselsTable.tsx} | 14 +++--- ...er.tsx => VGREventsVesselsTableFooter.tsx} | 24 ++++----- ...s.selectors.ts => vgr-events.selectors.ts} | 49 ++++++++----------- .../vessel-group-report.config.ts | 8 +-- .../vessel-group.config.selectors.ts | 22 +++------ .../vessel-groups/vessel-groups.types.ts | 18 +++---- 13 files changed, 97 insertions(+), 117 deletions(-) rename apps/fishing-map/features/vessel-group-report/events/{VesselGroupReportEvents.module.css => VGREvents.module.css} (100%) rename apps/fishing-map/features/vessel-group-report/events/{VesselGroupReportEvents.tsx => VGREvents.tsx} (76%) rename apps/fishing-map/features/vessel-group-report/events/{VesselGroupReportEventsGraph.module.css => VGREventsGraph.module.css} (100%) rename apps/fishing-map/features/vessel-group-report/events/{VesselGroupReportEventsGraph.tsx => VGREventsGraph.tsx} (94%) rename apps/fishing-map/features/vessel-group-report/events/{VesselGroupReportEventsSubsectionSelector.tsx => VGREventsSubsectionSelector.tsx} (79%) rename apps/fishing-map/features/vessel-group-report/events/{VesselGroupReportEventsVesselPropertySelector.tsx => VGREventsVesselPropertySelector.tsx} (62%) rename apps/fishing-map/features/vessel-group-report/events/{VesselGroupReportEventsVesselsTable.tsx => VGREventsVesselsTable.tsx} (87%) rename apps/fishing-map/features/vessel-group-report/events/{VesselGroupReportEventsVesselsTableFooter.tsx => VGREventsVesselsTableFooter.tsx} (86%) rename apps/fishing-map/features/vessel-group-report/events/{vessel-group-report-events.selectors.ts => vgr-events.selectors.ts} (66%) diff --git a/apps/fishing-map/features/vessel-group-report/VesselGroupReport.tsx b/apps/fishing-map/features/vessel-group-report/VesselGroupReport.tsx index 2c22aaabc2..f7f84aac2a 100644 --- a/apps/fishing-map/features/vessel-group-report/VesselGroupReport.tsx +++ b/apps/fishing-map/features/vessel-group-report/VesselGroupReport.tsx @@ -13,7 +13,7 @@ import { useTimebarVisualisationConnect, } from 'features/timebar/timebar.hooks' import { selectVesselGroupReportDataview } from 'features/dataviews/selectors/dataviews.selectors' -import VesselGroupReportEvents from 'features/vessel-group-report/events/VesselGroupReportEvents' +import VGREvents from 'features/vessel-group-report/events/VGREvents' import { useFetchVesselGroupReport } from './vessel-group-report.hooks' import { selectVesselGroupReportData, @@ -83,7 +83,7 @@ function VesselGroupReport() { { id: 'events', title: t('common.events', 'Events'), - content: , + content: , }, ], [t] diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.module.css b/apps/fishing-map/features/vessel-group-report/events/VGREvents.module.css similarity index 100% rename from apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.module.css rename to apps/fishing-map/features/vessel-group-report/events/VGREvents.module.css diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx b/apps/fishing-map/features/vessel-group-report/events/VGREvents.tsx similarity index 76% rename from apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx rename to apps/fishing-map/features/vessel-group-report/events/VGREvents.tsx index 894be17497..4f2dc0953c 100644 --- a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEvents.tsx +++ b/apps/fishing-map/features/vessel-group-report/events/VGREvents.tsx @@ -8,12 +8,12 @@ import { } from 'queries/vessel-group-events-stats-api' import { useTranslation } from 'react-i18next' import { getFourwingsInterval } from '@globalfishingwatch/deck-loaders' -import VesselGroupReportEventsSubsectionSelector from 'features/vessel-group-report/events/VesselGroupReportEventsSubsectionSelector' -import VesselGroupReportEventsGraph from 'features/vessel-group-report/events/VesselGroupReportEventsGraph' +import VGREventsSubsectionSelector from 'features/vessel-group-report/events/VGREventsSubsectionSelector' +import VGREventsGraph from 'features/vessel-group-report/events/VGREventsGraph' import { - selectVesselGroupReportEventsSubsection, - selectVesselGroupReportEventsVesselFilter, - selectVesselGroupReportEventsVesselsProperty, + 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' @@ -22,19 +22,19 @@ 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 VesselGroupReportEventsVesselPropertySelector from 'features/vessel-group-report/events/VesselGroupReportEventsVesselPropertySelector' -import VesselGroupReportEventsVesselsTable from 'features/vessel-group-report/events/VesselGroupReportEventsVesselsTable' +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 styles from './VesselGroupReportEvents.module.css' +import styles from './VGREvents.module.css' -function VesselGroupReportEvents() { +function VGREvents() { const { t } = useTranslation() const vesselGroupId = useSelector(selectReportVesselGroupId) - const filter = useSelector(selectVesselGroupReportEventsVesselFilter) - const eventsSubsection = useSelector(selectVesselGroupReportEventsSubsection) + const filter = useSelector(selectVGREventsVesselFilter) + const eventsSubsection = useSelector(selectVGREventsSubsection) const eventsDataviews = useSelector(selectEventsDataviews) const eventsDataview = eventsDataviews.find(({ id }) => id === eventsSubsection) - const vesselsGroupByProperty = useSelector(selectVesselGroupReportEventsVesselsProperty) + const vesselsGroupByProperty = useSelector(selectVGREventsVesselsProperty) const { upsertDataviewInstance } = useDataviewInstancesConnect() useEffect(() => { if (eventsDataview) { @@ -87,7 +87,7 @@ function VesselGroupReportEvents() { return (
- +

{parse( t('vesselGroup.summaryEvents', { @@ -109,7 +109,7 @@ function VesselGroupReportEvents() { }) )}

-
- +
- +
) } -export default VesselGroupReportEvents +export default VGREvents diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsGraph.module.css b/apps/fishing-map/features/vessel-group-report/events/VGREventsGraph.module.css similarity index 100% rename from apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsGraph.module.css rename to apps/fishing-map/features/vessel-group-report/events/VGREventsGraph.module.css diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsGraph.tsx b/apps/fishing-map/features/vessel-group-report/events/VGREventsGraph.tsx similarity index 94% rename from apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsGraph.tsx rename to apps/fishing-map/features/vessel-group-report/events/VGREventsGraph.tsx index b8dea16ade..a96ef9b31c 100644 --- a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsGraph.tsx +++ b/apps/fishing-map/features/vessel-group-report/events/VGREventsGraph.tsx @@ -17,9 +17,9 @@ import i18n from 'features/i18n/i18n' import { formatDateForInterval, getUTCDateTime } from 'utils/dates' import { formatI18nNumber } from 'features/i18n/i18nNumber' import { tickFormatter } from 'features/area-report/reports.utils' -import styles from './VesselGroupReportEventsGraph.module.css' +import styles from './VGREventsGraph.module.css' -type VesselGroupReportEventsGraphTooltipProps = { +type VGREventsGraphTooltipProps = { active: boolean payload: { name: string @@ -35,8 +35,7 @@ type VesselGroupReportEventsGraphTooltipProps = { } const ReportGraphTooltip = (props: any) => { - const { active, payload, label, timeChunkInterval } = - props as VesselGroupReportEventsGraphTooltipProps + const { active, payload, label, timeChunkInterval } = props as VGREventsGraphTooltipProps if (active && payload && payload.length) { const date = getUTCDateTime(label).setLocale(i18n.language) @@ -61,7 +60,7 @@ const formatDateTicks = (tick: string, timeChunkInterval: FourwingsInterval) => const graphMargin = { top: 0, right: 0, left: -20, bottom: -10 } -export default function VesselGroupReportEventsGraph({ +export default function VGREventsGraph({ color = 'rgb(22, 63, 137)', end, interval, diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsSubsectionSelector.tsx b/apps/fishing-map/features/vessel-group-report/events/VGREventsSubsectionSelector.tsx similarity index 79% rename from apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsSubsectionSelector.tsx rename to apps/fishing-map/features/vessel-group-report/events/VGREventsSubsectionSelector.tsx index db4fa9b968..0eccc7dfb0 100644 --- a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsSubsectionSelector.tsx +++ b/apps/fishing-map/features/vessel-group-report/events/VGREventsSubsectionSelector.tsx @@ -4,16 +4,16 @@ import { Choice, ChoiceOption } from '@globalfishingwatch/ui-components' import { useLocationConnect } from 'routes/routes.hook' import { selectVesselGroupReportStatus } from 'features/vessel-group-report/vessel-group-report.slice' import { AsyncReducerStatus } from 'utils/async-slice' -import { VesselGroupReportEventsSubsection } from 'features/vessel-groups/vessel-groups.types' -import { selectVesselGroupReportEventsSubsection } from '../vessel-group.config.selectors' +import { VGREventsSubsection } from 'features/vessel-groups/vessel-groups.types' +import { selectVGREventsSubsection } from '../vessel-group.config.selectors' function VesselGroupReportEventsSubsectionSelector() { const { t } = useTranslation() const { dispatchQueryParams } = useLocationConnect() const vesselGroupReportStatus = useSelector(selectVesselGroupReportStatus) - const subsection = useSelector(selectVesselGroupReportEventsSubsection) + const subsection = useSelector(selectVGREventsSubsection) const loading = vesselGroupReportStatus === AsyncReducerStatus.Loading - const options: ChoiceOption[] = [ + const options: ChoiceOption[] = [ { id: 'encounter-events', label: t('event.encounter_other', 'Encounters'), @@ -42,13 +42,13 @@ function VesselGroupReportEventsSubsectionSelector() { }, ] - const onSelectSubsection = (option: ChoiceOption) => { + const onSelectSubsection = (option: ChoiceOption) => { if (subsection !== option.id) { // trackEvent({ // category: TrackCategory.Analysis, // action: `Click on ${option.id} activity graph`, // }) - dispatchQueryParams({ vesselGroupReportEventsSubsection: option.id }) + dispatchQueryParams({ vGREventsSubsection: option.id }) } } diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselPropertySelector.tsx b/apps/fishing-map/features/vessel-group-report/events/VGREventsVesselPropertySelector.tsx similarity index 62% rename from apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselPropertySelector.tsx rename to apps/fishing-map/features/vessel-group-report/events/VGREventsVesselPropertySelector.tsx index d497ce03aa..0b4ea58e26 100644 --- a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselPropertySelector.tsx +++ b/apps/fishing-map/features/vessel-group-report/events/VGREventsVesselPropertySelector.tsx @@ -2,14 +2,14 @@ import { useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import { Choice, ChoiceOption } from '@globalfishingwatch/ui-components' import { useLocationConnect } from 'routes/routes.hook' -import { VesselGroupReportEventsVesselsProperty } from 'features/vessel-groups/vessel-groups.types' -import { selectVesselGroupReportEventsVesselsProperty } from '../vessel-group.config.selectors' +import { VGREventsVesselsProperty } from 'features/vessel-groups/vessel-groups.types' +import { selectVGREventsVesselsProperty } from '../vessel-group.config.selectors' function VesselGroupReportEventsVesselPropertySelector() { const { t } = useTranslation() const { dispatchQueryParams } = useLocationConnect() - const property = useSelector(selectVesselGroupReportEventsVesselsProperty) - const options: ChoiceOption[] = [ + const property = useSelector(selectVGREventsVesselsProperty) + const options: ChoiceOption[] = [ { id: 'flag', label: t('common.flag', 'Flag'), @@ -20,9 +20,9 @@ function VesselGroupReportEventsVesselPropertySelector() { }, ] - const onSelectSubsection = (option: ChoiceOption) => { + const onSelectSubsection = (option: ChoiceOption) => { if (property !== option.id) { - dispatchQueryParams({ vesselGroupReportEventsVesselsProperty: option.id }) + dispatchQueryParams({ vGREventsVesselsProperty: option.id }) } } diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTable.tsx b/apps/fishing-map/features/vessel-group-report/events/VGREventsVesselsTable.tsx similarity index 87% rename from apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTable.tsx rename to apps/fishing-map/features/vessel-group-report/events/VGREventsVesselsTable.tsx index b3646f80f3..c9270b71fd 100644 --- a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTable.tsx +++ b/apps/fishing-map/features/vessel-group-report/events/VGREventsVesselsTable.tsx @@ -13,16 +13,16 @@ import VesselPin from 'features/vessel/VesselPin' import { selectWorkspaceStatus } from 'features/workspace/workspace.selectors' import { AsyncReducerStatus } from 'utils/async-slice' import { - selectFetchVesselGroupReportEventsVesselsParams, - selectVesselGroupReportEventsVesselsPaginated, -} from 'features/vessel-group-report/events/vessel-group-report-events.selectors' -import VesselGroupReportEventsVesselsTableFooter from 'features/vessel-group-report/events/VesselGroupReportEventsVesselsTableFooter' + selectFetchVGREventsVesselsParams, + selectVGREventsVesselsPaginated, +} from 'features/vessel-group-report/events/vgr-events.selectors' +import VGREventsVesselsTableFooter from 'features/vessel-group-report/events/VGREventsVesselsTableFooter' import styles from '../vessels/VesselGroupReportVesselsTable.module.css' export default function VesselGroupReportEventsVesselsTable() { const { t } = useTranslation() const { dispatchQueryParams } = useLocationConnect() - const params = useSelector(selectFetchVesselGroupReportEventsVesselsParams) + const params = useSelector(selectFetchVGREventsVesselsParams) const workspaceStatus = useSelector(selectWorkspaceStatus) const { error, isLoading } = useGetVesselGroupEventsVesselsQuery( params as VesselGroupEventsVesselsParams, @@ -30,7 +30,7 @@ export default function VesselGroupReportEventsVesselsTable() { skip: !params, } ) - const vessels = useSelector(selectVesselGroupReportEventsVesselsPaginated) + const vessels = useSelector(selectVGREventsVesselsPaginated) const onPinClick = () => { dispatchQueryParams({ viewOnlyVesselGroup: false }) @@ -95,7 +95,7 @@ export default function VesselGroupReportEventsVesselsTable() { })} - +
) } diff --git a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTableFooter.tsx b/apps/fishing-map/features/vessel-group-report/events/VGREventsVesselsTableFooter.tsx similarity index 86% rename from apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTableFooter.tsx rename to apps/fishing-map/features/vessel-group-report/events/VGREventsVesselsTableFooter.tsx index 6b74f62ab6..7e915b7a4b 100644 --- a/apps/fishing-map/features/vessel-group-report/events/VesselGroupReportEventsVesselsTableFooter.tsx +++ b/apps/fishing-map/features/vessel-group-report/events/VGREventsVesselsTableFooter.tsx @@ -12,19 +12,19 @@ import { REPORT_SHOW_MORE_VESSELS_PER_PAGE, REPORT_VESSELS_PER_PAGE } from 'data import { selectVesselGroupReportData } from 'features/vessel-group-report/vessel-group-report.slice' import { formatInfoField } from 'utils/info' import { - selectVesselGroupReportEventsVessels, - selectVesselGroupReportEventsVesselsPagination, -} from 'features/vessel-group-report/events/vessel-group-report-events.selectors' -import { selectVesselGroupReportEventsVesselFilter } from '../vessel-group.config.selectors' + selectVGREventsVessels, + selectVGREventsVesselsPagination, +} from 'features/vessel-group-report/events/vgr-events.selectors' +import { selectVGREventsVesselFilter } from '../vessel-group.config.selectors' import styles from '../vessels/VesselGroupReportVesselsTableFooter.module.css' export default function VesselGroupReportVesselsTableFooter() { const { t } = useTranslation() const { dispatchQueryParams } = useLocationConnect() const vesselGroup = useSelector(selectVesselGroupReportData) - const allVessels = useSelector(selectVesselGroupReportEventsVessels) - const reportVesselFilter = useSelector(selectVesselGroupReportEventsVesselFilter) - const pagination = useSelector(selectVesselGroupReportEventsVesselsPagination) + const allVessels = useSelector(selectVGREventsVessels) + const reportVesselFilter = useSelector(selectVGREventsVesselFilter) + const pagination = useSelector(selectVGREventsVesselsPagination) const { start, end } = useSelector(selectTimeRange) if (!allVessels?.length) return null @@ -47,15 +47,15 @@ export default function VesselGroupReportVesselsTableFooter() { } const onPrevPageClick = () => { - dispatchQueryParams({ vesselGroupReportEventsVesselPage: pagination.page - 1 }) + dispatchQueryParams({ vGREventsVesselPage: pagination.page - 1 }) } const onNextPageClick = () => { - dispatchQueryParams({ vesselGroupReportEventsVesselPage: pagination.page + 1 }) + dispatchQueryParams({ vGREventsVesselPage: pagination.page + 1 }) } const onShowMoreClick = () => { dispatchQueryParams({ - vesselGroupReportEventsResultsPerPage: REPORT_SHOW_MORE_VESSELS_PER_PAGE, - vesselGroupReportEventsVesselPage: 0, + vGREventsResultsPerPage: REPORT_SHOW_MORE_VESSELS_PER_PAGE, + vGREventsVesselPage: 0, }) // trackEvent({ // category: TrackCategory.Analysis, @@ -64,7 +64,7 @@ export default function VesselGroupReportVesselsTableFooter() { } const onShowLessClick = () => { dispatchQueryParams({ - vesselGroupReportEventsResultsPerPage: REPORT_VESSELS_PER_PAGE, + vGREventsResultsPerPage: REPORT_VESSELS_PER_PAGE, reportVesselPage: 0, }) // trackEvent({ diff --git a/apps/fishing-map/features/vessel-group-report/events/vessel-group-report-events.selectors.ts b/apps/fishing-map/features/vessel-group-report/events/vgr-events.selectors.ts similarity index 66% rename from apps/fishing-map/features/vessel-group-report/events/vessel-group-report-events.selectors.ts rename to apps/fishing-map/features/vessel-group-report/events/vgr-events.selectors.ts index 5f5994a10b..22a904d308 100644 --- a/apps/fishing-map/features/vessel-group-report/events/vessel-group-report-events.selectors.ts +++ b/apps/fishing-map/features/vessel-group-report/events/vgr-events.selectors.ts @@ -10,21 +10,16 @@ import { selectTimeRange } from 'features/app/selectors/app.timebar.selectors' import { selectReportVesselGroupId } from 'routes/routes.selectors' import { selectEventsDataviews } from 'features/dataviews/selectors/dataviews.categories.selectors' import { - selectVesselGroupReportEventsResultsPerPage, - selectVesselGroupReportEventsSubsection, - selectVesselGroupReportEventsVesselFilter, - selectVesselGroupReportEventsVesselPage, + selectVGREventsResultsPerPage, + selectVGREventsSubsection, + selectVGREventsVesselFilter, + selectVGREventsVesselPage, } from 'features/vessel-group-report/vessel-group.config.selectors' import { getVesselsFiltered } from 'features/area-report/reports.utils' import { REPORT_FILTER_PROPERTIES } from 'features/vessel-group-report/vessels/vessel-group-report-vessels.selectors' -export const selectFetchVesselGroupReportEventsVesselsParams = createSelector( - [ - selectTimeRange, - selectReportVesselGroupId, - selectEventsDataviews, - selectVesselGroupReportEventsSubsection, - ], +export const selectFetchVGREventsVesselsParams = createSelector( + [selectTimeRange, selectReportVesselGroupId, selectEventsDataviews, selectVGREventsSubsection], ({ start, end }, reportVesselGroupId, eventsDataviews, eventsSubsection) => { const eventsDataview = eventsDataviews.find(({ id }) => id === eventsSubsection) if (!reportVesselGroupId || !eventsDataview) { @@ -39,8 +34,8 @@ export const selectFetchVesselGroupReportEventsVesselsParams = createSelector( } ) -export const selectVesselGroupReportEventsVesselsData = createSelector( - [selectVesselGroupEventsStatsApiSlice, selectFetchVesselGroupReportEventsVesselsParams], +export const selectVGREventsVesselsData = createSelector( + [selectVesselGroupEventsStatsApiSlice, selectFetchVGREventsVesselsParams], (vesselGroupEventsStatsApi, params) => { if (!params) { return @@ -49,8 +44,8 @@ export const selectVesselGroupReportEventsVesselsData = createSelector( } ) -export const selectVesselGroupReportEventsVessels = createSelector( - [selectVesselGroupReportEventsVesselsData, selectVesselGroupReportData], +export const selectVGREventsVessels = createSelector( + [selectVGREventsVesselsData, selectVesselGroupReportData], (data, vesselGroup) => { if (!data || !vesselGroup) { return @@ -71,33 +66,29 @@ export const selectVesselGroupReportEventsVessels = createSelector( } ) -export const selectVesselGroupReportEventsVesselsFiltered = createSelector( - [selectVesselGroupReportEventsVessels, selectVesselGroupReportEventsVesselFilter], +export const selectVGREventsVesselsFiltered = createSelector( + [selectVGREventsVessels, selectVGREventsVesselFilter], (vessels, filter) => { if (!vessels?.length) return null return getVesselsFiltered(vessels, filter, REPORT_FILTER_PROPERTIES) } ) -export const selectVesselGroupReportEventsVesselsPaginated = createSelector( - [ - selectVesselGroupReportEventsVesselsFiltered, - selectVesselGroupReportEventsVesselPage, - selectVesselGroupReportEventsResultsPerPage, - ], +export const selectVGREventsVesselsPaginated = createSelector( + [selectVGREventsVesselsFiltered, selectVGREventsVesselPage, selectVGREventsResultsPerPage], (vessels, page, resultsPerPage) => { if (!vessels?.length) return [] return vessels.slice(resultsPerPage * page, resultsPerPage * (page + 1)) } ) -export const selectVesselGroupReportEventsVesselsPagination = createSelector( +export const selectVGREventsVesselsPagination = createSelector( [ - selectVesselGroupReportEventsVesselsPaginated, - selectVesselGroupReportEventsVessels, - selectVesselGroupReportEventsVesselsFiltered, - selectVesselGroupReportEventsVesselPage, - selectVesselGroupReportEventsResultsPerPage, + selectVGREventsVesselsPaginated, + selectVGREventsVessels, + selectVGREventsVesselsFiltered, + selectVGREventsVesselPage, + selectVGREventsResultsPerPage, ], (vessels, allVessels, allVesselsFiltered, page = 0, resultsPerPage) => { return { diff --git a/apps/fishing-map/features/vessel-group-report/vessel-group-report.config.ts b/apps/fishing-map/features/vessel-group-report/vessel-group-report.config.ts index af8e70d991..3612ff4b2a 100644 --- a/apps/fishing-map/features/vessel-group-report/vessel-group-report.config.ts +++ b/apps/fishing-map/features/vessel-group-report/vessel-group-report.config.ts @@ -8,12 +8,12 @@ export const DEFAULT_VESSEL_GROUP_REPORT_STATE: VesselGroupReportState = { vesselGroupReportSection: 'vessels', vesselGroupReportVesselsSubsection: 'flag', vesselGroupReportActivitySubsection: 'fishing-effort', - vesselGroupReportEventsSubsection: 'encounter-events', - vesselGroupReportEventsVesselsProperty: 'flag', + vGREventsSubsection: 'encounter-events', + vGREventsVesselsProperty: 'flag', vesselGroupReportVesselPage: 0, vesselGroupReportResultsPerPage: REPORT_VESSELS_PER_PAGE, - vesselGroupReportEventsVesselPage: 0, - vesselGroupReportEventsResultsPerPage: REPORT_VESSELS_PER_PAGE, + vGREventsVesselPage: 0, + vGREventsResultsPerPage: REPORT_VESSELS_PER_PAGE, vesselGroupReportVesselsOrderProperty: 'shipname', vesselGroupReportVesselsOrderDirection: 'asc', } diff --git a/apps/fishing-map/features/vessel-group-report/vessel-group.config.selectors.ts b/apps/fishing-map/features/vessel-group-report/vessel-group.config.selectors.ts index b78100e46d..36ae19d559 100644 --- a/apps/fishing-map/features/vessel-group-report/vessel-group.config.selectors.ts +++ b/apps/fishing-map/features/vessel-group-report/vessel-group.config.selectors.ts @@ -30,11 +30,9 @@ export const selectVesselGroupReportVesselsSubsection = selectVesselGroupReportS export const selectVesselGroupReportActivitySubsection = selectVesselGroupReportStateProperty( 'vesselGroupReportActivitySubsection' ) -export const selectVesselGroupReportEventsSubsection = selectVesselGroupReportStateProperty( - 'vesselGroupReportEventsSubsection' -) -export const selectVesselGroupReportEventsVesselsProperty = selectVesselGroupReportStateProperty( - 'vesselGroupReportEventsVesselsProperty' +export const selectVGREventsSubsection = selectVesselGroupReportStateProperty('vGREventsSubsection') +export const selectVGREventsVesselsProperty = selectVesselGroupReportStateProperty( + 'vGREventsVesselsProperty' ) export const selectVesselGroupReportVesselFilter = selectVesselGroupReportStateProperty( @@ -53,12 +51,8 @@ export const selectVesselGroupReportVesselsOrderDirection = selectVesselGroupRep 'vesselGroupReportVesselsOrderDirection' ) -export const selectVesselGroupReportEventsVesselFilter = selectVesselGroupReportStateProperty( - 'vesselGroupReportEventsVesselFilter' -) -export const selectVesselGroupReportEventsVesselPage = selectVesselGroupReportStateProperty( - 'vesselGroupReportEventsVesselPage' -) -export const selectVesselGroupReportEventsResultsPerPage = selectVesselGroupReportStateProperty( - 'vesselGroupReportEventsResultsPerPage' -) +export const selectVGREventsVesselFilter = + selectVesselGroupReportStateProperty('vGREventsVesselFilter') +export const selectVGREventsVesselPage = selectVesselGroupReportStateProperty('vGREventsVesselPage') +export const selectVGREventsResultsPerPage = + selectVesselGroupReportStateProperty('vGREventsResultsPerPage') diff --git a/apps/fishing-map/features/vessel-groups/vessel-groups.types.ts b/apps/fishing-map/features/vessel-groups/vessel-groups.types.ts index 3fd89c670e..a9394822f3 100644 --- a/apps/fishing-map/features/vessel-groups/vessel-groups.types.ts +++ b/apps/fishing-map/features/vessel-groups/vessel-groups.types.ts @@ -1,12 +1,8 @@ export type VesselGroupReportSection = 'vessels' | 'insights' | 'activity' | 'events' export type VesselGroupReportVesselsSubsection = 'flag' | 'shiptypes' | 'geartypes' | 'source' export type VesselGroupReportActivitySubsection = 'fishing-effort' | 'presence' -export type VesselGroupReportEventsVesselsProperty = 'flag' | 'geartype' -export type VesselGroupReportEventsSubsection = - | 'encounter-events' - | 'loitering' - | 'gaps' - | 'port_visits' +export type VGREventsVesselsProperty = 'flag' | 'geartype' +export type VGREventsSubsection = 'encounter-events' | 'loitering' | 'gaps' | 'port_visits' export type VesselGroupReportVesselsOrderProperty = 'shipname' | 'flag' | 'shiptype' export type VesselGroupReportVesselsOrderDirection = 'asc' | 'desc' @@ -15,14 +11,14 @@ export type VesselGroupReportState = { vesselGroupReportSection: VesselGroupReportSection vesselGroupReportVesselsSubsection?: VesselGroupReportVesselsSubsection vesselGroupReportActivitySubsection?: VesselGroupReportActivitySubsection - vesselGroupReportEventsSubsection?: VesselGroupReportEventsSubsection - vesselGroupReportEventsVesselsProperty?: VesselGroupReportEventsVesselsProperty + vGREventsSubsection?: VGREventsSubsection + vGREventsVesselsProperty?: VGREventsVesselsProperty vesselGroupReportVesselPage?: number vesselGroupReportResultsPerPage?: number vesselGroupReportVesselFilter?: string - vesselGroupReportEventsVesselPage?: number - vesselGroupReportEventsResultsPerPage?: number - vesselGroupReportEventsVesselFilter?: string + vGREventsVesselPage?: number + vGREventsResultsPerPage?: number + vGREventsVesselFilter?: string vesselGroupReportVesselsOrderProperty?: VesselGroupReportVesselsOrderProperty vesselGroupReportVesselsOrderDirection?: VesselGroupReportVesselsOrderDirection } From c6d86fcb2a61a258509321f9de047b2755886940 Mon Sep 17 00:00:00 2001 From: satellitestudiodesign Date: Thu, 19 Sep 2024 09:09:52 +0200 Subject: [PATCH 6/9] filter encounter-types --- .../vessel-group-report/events/VGREvents.tsx | 2 +- .../events/vgr-events.selectors.ts | 7 +++++- .../queries/vessel-group-events-stats-api.ts | 23 ++++++++++++++----- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/apps/fishing-map/features/vessel-group-report/events/VGREvents.tsx b/apps/fishing-map/features/vessel-group-report/events/VGREvents.tsx index 4f2dc0953c..010ed037b7 100644 --- a/apps/fishing-map/features/vessel-group-report/events/VGREvents.tsx +++ b/apps/fishing-map/features/vessel-group-report/events/VGREvents.tsx @@ -57,7 +57,7 @@ function VGREvents() { const { data, error, isLoading } = useGetVesselGroupEventsStatsQuery( { includes: ['TIME_SERIES', 'EVENTS_GROUPED'], - datasetId: eventsDataview?.datasets?.[0]?.id as string, + dataview: eventsDataview!, groupBy: vesselsGroupByProperty.toUpperCase(), vesselGroupId, interval, diff --git a/apps/fishing-map/features/vessel-group-report/events/vgr-events.selectors.ts b/apps/fishing-map/features/vessel-group-report/events/vgr-events.selectors.ts index 22a904d308..0e72d337cf 100644 --- a/apps/fishing-map/features/vessel-group-report/events/vgr-events.selectors.ts +++ b/apps/fishing-map/features/vessel-group-report/events/vgr-events.selectors.ts @@ -22,14 +22,19 @@ export const selectFetchVGREventsVesselsParams = createSelector( [selectTimeRange, selectReportVesselGroupId, selectEventsDataviews, selectVGREventsSubsection], ({ start, end }, reportVesselGroupId, eventsDataviews, eventsSubsection) => { const eventsDataview = eventsDataviews.find(({ id }) => id === eventsSubsection) + const encounterTypes = + eventsDataview?.datasets?.[0]?.subcategory === 'encounter' + ? eventsDataview?.datasetsConfig?.[0]?.filters?.encounter_type + : undefined if (!reportVesselGroupId || !eventsDataview) { return } return { - datasetId: eventsDataview?.datasets?.[0]?.id as string, + dataview: eventsDataview, vesselGroupId: reportVesselGroupId, start, end, + encounterTypes, } as VesselGroupEventsVesselsParams } ) diff --git a/apps/fishing-map/queries/vessel-group-events-stats-api.ts b/apps/fishing-map/queries/vessel-group-events-stats-api.ts index 4cae0fe3d8..a1ab3a43ad 100644 --- a/apps/fishing-map/queries/vessel-group-events-stats-api.ts +++ b/apps/fishing-map/queries/vessel-group-events-stats-api.ts @@ -2,22 +2,23 @@ import { createApi } from '@reduxjs/toolkit/query/react' import { getQueryParamsResolved, gfwBaseQuery } from 'queries/base' import { RootState } from 'reducers' import { FourwingsInterval } from '@globalfishingwatch/deck-loaders' +import { UrlDataviewInstance } from '@globalfishingwatch/dataviews-client' export type VesselGroupEventsStatsParams = { vesselGroupId: string - datasetId: string includes: string[] start: string end: string interval: FourwingsInterval groupBy: string // 'FLAG' | 'GEARTYPE' + dataview: UrlDataviewInstance } export type VesselGroupEventsVesselsParams = { vesselGroupId: string - datasetId: string start: string end: string + dataview: UrlDataviewInstance } export type VesselGroupEventsStatsResponseGroups = { name: string; value: number }[] @@ -45,15 +46,20 @@ export const vesselGroupEventsStatsApi = createApi({ VesselGroupEventsStatsResponse, VesselGroupEventsStatsParams >({ - query: ({ vesselGroupId, datasetId, includes, start, end, interval, groupBy }) => { + query: ({ vesselGroupId, dataview, includes, start, end, interval, groupBy }) => { + const encounterTypes = + dataview?.datasets?.[0]?.subcategory === 'encounter' + ? dataview?.datasetsConfig?.[0]?.filters?.encounter_type + : undefined const query = { includes, 'vessel-groups': [vesselGroupId], 'start-date': start, 'end-date': end, - datasets: [datasetId], + datasets: [dataview.datasets?.[0]?.id], 'timeseries-interval': interval, 'group-by': groupBy, + ...(encounterTypes && { 'encounter-types': encounterTypes }), } return { url: `${getQueryParamsResolved(query)}`, @@ -64,12 +70,17 @@ export const vesselGroupEventsStatsApi = createApi({ VesselGroupEventsVesselsResponse, VesselGroupEventsVesselsParams >({ - query: ({ vesselGroupId, datasetId, start, end }) => { + query: ({ vesselGroupId, dataview, start, end }) => { + const encounterTypes = + dataview?.datasets?.[0]?.subcategory === 'encounter' + ? dataview?.datasetsConfig?.[0]?.filters?.encounter_type + : undefined const query = { 'vessel-groups': [vesselGroupId], 'start-date': start, 'end-date': end, - dataset: datasetId, + dataset: dataview.datasets?.[0]?.id as string, + ...(encounterTypes && { 'encounter-types': encounterTypes }), } return { url: `-by-vessel${getQueryParamsResolved(query)}`, From b555cb81bcb54f344c84bfe9ac25cd639cd8afb2 Mon Sep 17 00:00:00 2001 From: satellitestudiodesign Date: Thu, 19 Sep 2024 09:41:50 +0200 Subject: [PATCH 7/9] use color variable --- .../features/vessel-group-report/events/VGREvents.tsx | 3 ++- .../features/vessel-group-report/events/VGREventsGraph.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/fishing-map/features/vessel-group-report/events/VGREvents.tsx b/apps/fishing-map/features/vessel-group-report/events/VGREvents.tsx index 010ed037b7..813875aeb4 100644 --- a/apps/fishing-map/features/vessel-group-report/events/VGREvents.tsx +++ b/apps/fishing-map/features/vessel-group-report/events/VGREvents.tsx @@ -25,6 +25,7 @@ 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() { @@ -69,7 +70,7 @@ function VGREvents() { } ) - let color = eventsDataview?.config?.color || 'rgb(22, 63, 137)' + let color = eventsDataview?.config?.color || COLOR_PRIMARY_BLUE if (eventsDataview?.id === ENCOUNTER_EVENTS_SOURCE_ID) { color = 'rgb(247 222 110)' } diff --git a/apps/fishing-map/features/vessel-group-report/events/VGREventsGraph.tsx b/apps/fishing-map/features/vessel-group-report/events/VGREventsGraph.tsx index a96ef9b31c..f741fceb82 100644 --- a/apps/fishing-map/features/vessel-group-report/events/VGREventsGraph.tsx +++ b/apps/fishing-map/features/vessel-group-report/events/VGREventsGraph.tsx @@ -17,6 +17,7 @@ import i18n from 'features/i18n/i18n' import { formatDateForInterval, getUTCDateTime } from 'utils/dates' import { formatI18nNumber } from 'features/i18n/i18nNumber' import { tickFormatter } from 'features/area-report/reports.utils' +import { COLOR_PRIMARY_BLUE } from 'features/app/app.config' import styles from './VGREventsGraph.module.css' type VGREventsGraphTooltipProps = { @@ -61,7 +62,7 @@ const formatDateTicks = (tick: string, timeChunkInterval: FourwingsInterval) => const graphMargin = { top: 0, right: 0, left: -20, bottom: -10 } export default function VGREventsGraph({ - color = 'rgb(22, 63, 137)', + color = COLOR_PRIMARY_BLUE, end, interval, start, From 2676184ddbd29df3f9889a560340a9ff582b5760 Mon Sep 17 00:00:00 2001 From: satellitestudiodesign Date: Thu, 19 Sep 2024 09:50:10 +0200 Subject: [PATCH 8/9] remove unnecesary parameter --- .../vessel-group-report/events/vgr-events.selectors.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/fishing-map/features/vessel-group-report/events/vgr-events.selectors.ts b/apps/fishing-map/features/vessel-group-report/events/vgr-events.selectors.ts index 0e72d337cf..8d5bcfa757 100644 --- a/apps/fishing-map/features/vessel-group-report/events/vgr-events.selectors.ts +++ b/apps/fishing-map/features/vessel-group-report/events/vgr-events.selectors.ts @@ -22,10 +22,6 @@ export const selectFetchVGREventsVesselsParams = createSelector( [selectTimeRange, selectReportVesselGroupId, selectEventsDataviews, selectVGREventsSubsection], ({ start, end }, reportVesselGroupId, eventsDataviews, eventsSubsection) => { const eventsDataview = eventsDataviews.find(({ id }) => id === eventsSubsection) - const encounterTypes = - eventsDataview?.datasets?.[0]?.subcategory === 'encounter' - ? eventsDataview?.datasetsConfig?.[0]?.filters?.encounter_type - : undefined if (!reportVesselGroupId || !eventsDataview) { return } @@ -34,7 +30,6 @@ export const selectFetchVGREventsVesselsParams = createSelector( vesselGroupId: reportVesselGroupId, start, end, - encounterTypes, } as VesselGroupEventsVesselsParams } ) From d6e4d2c411a9cae2836f65c71c5ab3ab17e94b29 Mon Sep 17 00:00:00 2001 From: j8seangel Date: Thu, 19 Sep 2024 09:50:50 +0200 Subject: [PATCH 9/9] better props typing --- .../vessels/VesselGroupReportVesselsGraph.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVesselsGraph.tsx b/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVesselsGraph.tsx index 439d54b153..d6af95b680 100644 --- a/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVesselsGraph.tsx +++ b/apps/fishing-map/features/vessel-group-report/vessels/VesselGroupReportVesselsGraph.tsx @@ -7,7 +7,10 @@ import I18nNumber, { formatI18nNumber } from 'features/i18n/i18nNumber' import { EMPTY_API_VALUES, OTHERS_CATEGORY_LABEL } from 'features/area-report/reports.config' import { formatInfoField } from 'utils/info' import { useLocationConnect } from 'routes/routes.hook' -import { VesselGroupReportVesselsSubsection } from 'features/vessel-groups/vessel-groups.types' +import { + VesselGroupReportState, + VesselGroupReportVesselsSubsection, +} from 'features/vessel-groups/vessel-groups.types' import styles from './VesselGroupReportVesselsGraph.module.css' type ReportGraphTooltipProps = { @@ -141,8 +144,8 @@ export default function VesselGroupReportVesselsGraph({ data: VesselGroupEventsStatsResponseGroups color?: string property: VesselGroupReportVesselsGraphProperty - filterQueryParam: string - pageQueryParam: string + filterQueryParam: keyof VesselGroupReportState + pageQueryParam: keyof VesselGroupReportState }) { return (