Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JC feedback 4 #2867

Merged
12 changes: 11 additions & 1 deletion apps/fishing-map/features/datasets/datasets.selectors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'
import { uniqBy } from 'es-toolkit'
import { DatasetCategory, DatasetTypes } from '@globalfishingwatch/api-types'
import { DatasetCategory, DatasetStatus, DatasetTypes } from '@globalfishingwatch/api-types'
import { selectAllDatasets } from './datasets.slice'

const EMPTY_ARRAY: [] = []
Expand All @@ -25,6 +25,16 @@ const selectDatasetsByType = (type: DatasetTypes) => {
export const selectFourwingsDatasets = selectDatasetsByType(DatasetTypes.Fourwings)
export const selectVesselsDatasets = selectDatasetsByType(DatasetTypes.Vessels)

export const selectVesselGroupCompatibleDatasets = createSelector(
[selectVesselsDatasets],
(datasets) => {
return datasets.filter(
(d) =>
d.status !== DatasetStatus.Deleted && d.configuration?.apiSupportedVersions?.includes('v3')
satellitestudiodesign marked this conversation as resolved.
Show resolved Hide resolved
)
}
)

export const selectActivityDatasets = createSelector([selectFourwingsDatasets], (datasets) => {
return datasets.filter((d) => d.category === DatasetCategory.Activity)
})
5 changes: 3 additions & 2 deletions apps/fishing-map/features/reports/events/VGREvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
selectVGREventsVesselsFlags,
selectVGREventsVesselsGrouped,
} from 'features/reports/events/vgr-events.selectors'
import { formatI18nNumber } from 'features/i18n/i18nNumber'
import styles from './VGREvents.module.css'

function VGREvents() {
Expand Down Expand Up @@ -87,8 +88,8 @@ function VGREvents() {
t('vesselGroup.summaryEvents', {
defaultValue:
'<strong>{{vessels}} vessels</strong> from <strong>{{flags}} flags</strong> had <strong>{{activityQuantity}} {{activityUnit}}</strong> globally between <strong>{{start}}</strong> and <strong>{{end}}</strong>',
vessels: vessels?.length,
flags: vesselFlags,
vessels: formatI18nNumber(vessels?.length || 0),
satellitestudiodesign marked this conversation as resolved.
Show resolved Hide resolved
flags: vesselFlags?.size,
activityQuantity: data.timeseries.reduce((acc, group) => acc + group.value, 0),
activityUnit: `${eventsDataview?.datasets?.[0]?.subcategory?.toLowerCase()} ${t(
'common.events',
Expand Down
67 changes: 50 additions & 17 deletions apps/fishing-map/features/reports/events/vgr-events.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ import { getVesselsFiltered } from 'features/reports/areas/area-reports.utils'
import { REPORT_FILTER_PROPERTIES } from 'features/reports/vessel-groups/vessels/vessel-group-report-vessels.selectors'
import { selectVGREventsSubsectionDataview } from 'features/reports/vessel-groups/vessel-group-report.selectors'
import { OTHER_CATEGORY_LABEL } from 'features/reports/vessel-groups/vessel-group-report.config'
import { formatInfoField } from 'utils/info'
import { EMPTY_FIELD_PLACEHOLDER, formatInfoField } from 'utils/info'
import { MAX_CATEGORIES } from 'features/reports/areas/area-reports.config'
import { t } from 'features/i18n/i18n'

export const selectFetchVGREventsVesselsParams = createSelector(
[selectTimeRange, selectReportVesselGroupId, selectVGREventsSubsectionDataview],
Expand Down Expand Up @@ -53,7 +54,8 @@ export const selectVGREventsVessels = createSelector(
if (!data || !vesselGroup) {
return
}
const insightVessels = vesselGroup?.vessels?.flatMap((vessel) => {
const vesselsWithoutDuplicates = vesselGroup?.vessels.filter((v) => v.identity !== undefined)
const insightVessels = vesselsWithoutDuplicates?.flatMap((vessel) => {
const vesselWithEvents = data?.find((v) => v.vesselId === vessel.vesselId)
if (!vesselWithEvents) {
return []
Expand All @@ -68,7 +70,7 @@ export const selectVGREventsVessels = createSelector(
.sort()
.map((g) => formatInfoField(g, 'geartypes'))
.join(', ') || OTHER_CATEGORY_LABEL,
flagTranslated: formatInfoField(identity.flag, 'flag'),
flagTranslated: t(`flags:${identity.flag as string}` as any),
}
})
return insightVessels.sort((a, b) => b.numEvents - a.numEvents)
Expand All @@ -90,39 +92,70 @@ export const selectVGREventsVesselsPaginated = createSelector(
return vessels.slice(resultsPerPage * page, resultsPerPage * (page + 1))
}
)
type GraphDataGroup = {
name: string
value: number
}

export const selectVGREventsVesselsGrouped = createSelector(
[selectVGREventsVesselsFiltered, selectVGREventsVesselsProperty],
(vessels, property) => {
if (!vessels?.length) return []
const groups: { name: string; value: number }[] = Object.entries(
const orderedGroups: { name: string; value: number }[] = Object.entries(
groupBy(vessels, (vessel) => {
return property === 'flag' ? (vessel.flagTranslated as string) : (vessel.geartype as string)
return property === 'flag' ? vessel.flagTranslated : (vessel.geartype as string)
})
)
.map(([key, value]) => ({ name: key, property: key, value: value.length }))
.sort((a, b) => b.value - a.value)

if (groups.length <= MAX_CATEGORIES) {
return groups
const groupsWithoutOther: GraphDataGroup[] = []
const otherGroups: GraphDataGroup[] = []
orderedGroups.forEach((group) => {
if (
group.name === 'null' ||
group.name.toLowerCase() === OTHER_CATEGORY_LABEL.toLowerCase() ||
group.name === EMPTY_FIELD_PLACEHOLDER
) {
otherGroups.push(group)
} else {
groupsWithoutOther.push(group)
}
})
const allGroups =
otherGroups.length > 0
? [
...groupsWithoutOther,
{
name: OTHER_CATEGORY_LABEL,
value: otherGroups.reduce((acc, group) => acc + group.value, 0),
},
]
: groupsWithoutOther
if (allGroups.length <= MAX_CATEGORIES) {
return allGroups
}

const firstNine = groups.slice(0, MAX_CATEGORIES)
const other = groups.slice(MAX_CATEGORIES)

const firstGroups = allGroups.slice(0, MAX_CATEGORIES)
const restOfGroups = allGroups.slice(MAX_CATEGORIES)
return [
...firstNine,
...firstGroups,
{
name: OTHER_CATEGORY_LABEL,
property: other.map((g) => g.name).join(', '),
value: other.reduce((acc, group) => acc + group.value, 0),
value: restOfGroups.reduce((acc, group) => acc + group.value, 0),
},
]
] as GraphDataGroup[]
}
)
export const selectVGREventsVesselsFlags = createSelector([selectVGREventsVessels], (vessels) => {
if (!vessels?.length) return []
return Object.keys(groupBy(vessels, (v) => v.flag)).length
if (!vessels?.length) return null
let flags = new Set<string>()
vessels.forEach((vessel) => {
if (vessel.flagTranslated && vessel.flagTranslated !== 'null') {
flags.add(vessel.flagTranslated as string)
}
})
console.log('flags:', flags)
satellitestudiodesign marked this conversation as resolved.
Show resolved Hide resolved
return flags
})

export const selectVGREventsVesselsPagination = createSelector(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
setVesselGroupModalVessels,
setVesselGroupsModalOpen,
} from 'features/vessel-groups/vessel-groups-modal.slice'
import { formatInfoField } from 'utils/info'
import { useLocationConnect } from 'routes/routes.hook'
import { selectHasOtherVesselGroupDataviews } from 'features/dataviews/selectors/dataviews.selectors'
import {
Expand All @@ -23,6 +22,8 @@ import {
selectVGRVesselsTimeRange,
} from 'features/reports/vessel-groups/vessels/vessel-group-report-vessels.selectors'
import { formatI18nDate } from 'features/i18n/i18nDate'
import { formatI18nNumber } from 'features/i18n/i18nNumber'
import { getVesselGroupVesselsCount } from 'features/vessel-groups/vessel-groups.utils'
import styles from './VesselGroupReportTitle.module.css'
import { VesselGroupReport } from './vessel-group-report.slice'
import { selectViewOnlyVesselGroup } from './vessel-group.config.selectors'
Expand Down Expand Up @@ -94,7 +95,7 @@ export default function VesselGroupReportTitle({ vesselGroup, loading }: ReportT
t('vesselGroup.summary', {
defaultValue:
'<strong>{{vessels}} vessels</strong> from <strong>{{flags}} flags</strong> active from <strong>{{start}}</strong> to <strong>{{end}}</strong>',
vessels: vessels?.length,
vessels: formatI18nNumber(getVesselGroupVesselsCount(vesselGroup)),
flags: flags?.size,
start: formatI18nDate(timeRange.start, {
format: DateTime.DATE_MED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
VesselGroupInsight,
VesselGroupInsightResponse,
} from '@globalfishingwatch/api-types'
import { getSearchIdentityResolved, getVesselId } from 'features/vessel/vessel.utils'
import { getSearchIdentityResolved } from 'features/vessel/vessel.utils'
import { VesselLastIdentity } from 'features/search/search.slice'
import {
selectVGRFishingInsightData,
Expand All @@ -36,7 +36,8 @@ export const selectVGRVesselsByInsight = <Insight = any>(
if (!data || !vesselGroup) {
return []
}
const insightVessels = vesselGroup?.vessels?.flatMap((vessel) => {
const vesselsWithoutDuplicates = vesselGroup?.vessels.filter((v) => v.identity !== undefined)
satellitestudiodesign marked this conversation as resolved.
Show resolved Hide resolved
const insightVessels = vesselsWithoutDuplicates.flatMap((vessel) => {
const vesselWithInsight = data?.[insightProperty]?.find((v) => v.vesselId === vessel.vesselId)
if (!vesselWithInsight || (insightCounter && get(vesselWithInsight, insightCounter) === 0)) {
return []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ 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 { TrackCategory, trackEvent } from 'features/app/analytics.hooks'
import { selectVGRData } from 'features/reports/vessel-groups/vessel-group-report.slice'
import { formatInfoField } from 'utils/info'
import { selectVGRVesselFilter } from 'features/reports/vessel-groups/vessel-group.config.selectors'
import { getVesselProperty } from 'features/vessel/vessel.utils'
import styles from './VesselGroupReportVesselsTableFooter.module.css'
import {
selectVGRVesselsFiltered,
Expand All @@ -32,20 +31,17 @@ export default function VesselGroupReportVesselsTableFooter() {

const onDownloadVesselsClick = () => {
const vessels = allVessels?.map((vessel) => {
const vesselRegistryInfo = !!vessel.identity?.registryInfo?.length
? vessel.identity?.registryInfo[0]
: null
return {
dataset: vessel.dataset,
flag: vesselRegistryInfo?.flag,
flag: getVesselProperty(vessel.identity!, 'flag'),
'flag translated': vessel.flagTranslated,
'GFW vessel type': vessel.vesselType,
'GFW gear type': vessel.geartype,
sources: vessel.source,
name: vessel.shipName,
MMSI: vesselRegistryInfo?.ssvid,
IMO: vesselRegistryInfo?.imo,
'call sign': vesselRegistryInfo?.callsign,
MMSI: getVesselProperty(vessel.identity!, 'ssvid'),
IMO: getVesselProperty(vessel.identity!, 'imo'),
'call sign': getVesselProperty(vessel.identity!, 'callsign'),
vesselId: vessel.vesselId,
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export const selectVGRVesselsFlags = createSelector([selectVGRVesselsParsed], (v
if (!vessels?.length) return null
let flags = new Set<string>()
vessels.forEach((vessel) => {
if (vessel.flagTranslated) {
if (vessel.flagTranslated && vessel.flagTranslated !== 'null') {
flags.add(vessel.flagTranslated)
}
})
Expand Down
6 changes: 5 additions & 1 deletion apps/fishing-map/features/search/SearchActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@ function SearchActions() {

return (
<Fragment>
<VesselGroupAddButton vessels={vesselsSelected} onAddToVesselGroup={onAddToVesselGroup}>
<VesselGroupAddButton
vessels={vesselsSelected}
onAddToVesselGroup={onAddToVesselGroup}
keepOpenWhileAdding
>
<VesselGroupAddActionButton className={cx(styles.footerAction, styles.vesselGroupButton)} />
</VesselGroupAddButton>
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type VesselGroupAddButtonProps = {
vesselsToResolve?: string[]
datasetsToResolve?: string[]
onAddToVesselGroup?: (vesselGroupId: string) => void
keepOpenWhileAdding?: boolean
}

type VesselGroupAddButtonToggleProps = {
Expand Down Expand Up @@ -84,6 +85,7 @@ function VesselGroupAddButton(props: VesselGroupAddButtonProps) {
datasetsToResolve,
onAddToVesselGroup,
children = <VesselGroupAddActionButton vesselsToResolve={vesselsToResolve} />,
keepOpenWhileAdding,
} = props
const addVesselsToVesselGroup = useVesselGroupsUpdate()
const createVesselGroupWithVessels = useVesselGroupsModal()
Expand Down Expand Up @@ -138,6 +140,7 @@ function VesselGroupAddButton(props: VesselGroupAddButtonProps) {
onAddToVesselGroup={handleAddToVesselGroupClick}
vessels={vesselGroupVessels}
children={children}
keepOpenWhileAdding={keepOpenWhileAdding}
/>
)
}
Expand Down
27 changes: 23 additions & 4 deletions apps/fishing-map/features/vessel-groups/VesselGroupListTooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,61 @@
import { useCallback, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import cx from 'classnames'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import React from 'react'
import { toast } from 'react-toastify'
import { Popover, Spinner } from '@globalfishingwatch/ui-components'
import {
NEW_VESSEL_GROUP_ID,
useVesselGroupsOptions,
} from 'features/vessel-groups/vessel-groups.hooks'
import { selectHasUserGroupsPermissions } from 'features/user/selectors/user.permissions.selectors'
import { selectVesselGroupsStatusId } from 'features/vessel-groups/vessel-groups.slice'
import styles from './VesselGroupListTooltip.module.css'
import { VesselGroupVesselIdentity } from './vessel-groups-modal.slice'

type VesselGroupListTooltipProps = {
children?: React.ReactNode
vessels?: VesselGroupVesselIdentity[]
onAddToVesselGroup?: (vesselGroupId: string) => void
keepOpenWhileAdding?: boolean
}

function VesselGroupListTooltip(props: VesselGroupListTooltipProps) {
const { onAddToVesselGroup, children } = props
const { onAddToVesselGroup, children, keepOpenWhileAdding = false } = props
const { t } = useTranslation()
const hasUserGroupsPermissions = useSelector(selectHasUserGroupsPermissions)
const vesselGroupOptions = useVesselGroupsOptions()
const vesselGroupsStatusId = useSelector(selectVesselGroupsStatusId)
const [addingToGroup, setAddingToGroup] = useState(false)
const [vesselGroupsOpen, setVesselGroupsOpen] = useState(false)

const toggleVesselGroupsOpen = useCallback(() => {
setVesselGroupsOpen(!vesselGroupsOpen)
}, [vesselGroupsOpen])

useEffect(() => {
if (addingToGroup && !vesselGroupsStatusId) {
toast(t('vesselGroup.vesselAddedToGroup', 'Your vessel group was updated'), {
toastId: 'vesselAddedToGroup',
})
setVesselGroupsOpen(false)
setAddingToGroup(false)
}
}, [vesselGroupsStatusId, t, addingToGroup])

const handleVesselGroupClick = useCallback(
(vesselGroupId: string) => {
if (onAddToVesselGroup) {
onAddToVesselGroup(vesselGroupId)
setVesselGroupsOpen(false)
if (keepOpenWhileAdding) {
setAddingToGroup(true)
} else {
setVesselGroupsOpen(false)
}
}
},
[onAddToVesselGroup]
[keepOpenWhileAdding, onAddToVesselGroup]
)

return (
Expand Down
Loading
Loading