Skip to content

Commit

Permalink
JC feedback 4 (#2867)
Browse files Browse the repository at this point in the history
  • Loading branch information
satellitestudiodesign authored Oct 22, 2024
2 parents 9f24217 + 5c66644 commit 4bfc5eb
Show file tree
Hide file tree
Showing 19 changed files with 236 additions and 115 deletions.
14 changes: 13 additions & 1 deletion apps/fishing-map/features/datasets/datasets.selectors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
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 { VESSEL_GROUPS_MIN_API_VERSION } from 'features/vessel-groups/vessel-groups.config'
import { selectAllDatasets } from './datasets.slice'

const EMPTY_ARRAY: [] = []
Expand All @@ -25,6 +26,17 @@ 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(VESSEL_GROUPS_MIN_API_VERSION)
)
}
)

export const selectActivityDatasets = createSelector([selectFourwingsDatasets], (datasets) => {
return datasets.filter((d) => d.category === DatasetCategory.Activity)
})
7 changes: 4 additions & 3 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 All @@ -37,7 +38,7 @@ function VGREvents() {
const filter = useSelector(selectVGREventsVesselFilter)
const eventsDataview = useSelector(selectVGREventsSubsectionDataview)
const vesselsGroupByProperty = useSelector(selectVGREventsVesselsProperty)
const vessels = useSelector(selectVGREventsVessels)
const vesselsWithEvents = useSelector(selectVGREventsVessels)
const vesselFlags = useSelector(selectVGREventsVesselsFlags)
const vesselGroups = useSelector(selectVGREventsVesselsGrouped)

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(vesselsWithEvents?.length || 0),
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,10 @@ 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'
import { getVesselsWithoutDuplicates } from 'features/vessel-groups/vessel-groups.utils'

export const selectFetchVGREventsVesselsParams = createSelector(
[selectTimeRange, selectReportVesselGroupId, selectVGREventsSubsectionDataview],
Expand Down Expand Up @@ -53,7 +55,8 @@ export const selectVGREventsVessels = createSelector(
if (!data || !vesselGroup) {
return
}
const insightVessels = vesselGroup?.vessels?.flatMap((vessel) => {
const vesselsWithoutDuplicates = getVesselsWithoutDuplicates(vesselGroup.vessels)
const insightVessels = vesselsWithoutDuplicates?.flatMap((vessel) => {
const vesselWithEvents = data?.find((v) => v.vesselId === vessel.vesselId)
if (!vesselWithEvents) {
return []
Expand All @@ -68,7 +71,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 +93,69 @@ 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)
}
})
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,8 +12,9 @@ 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 { getVesselsWithoutDuplicates } from 'features/vessel-groups/vessel-groups.utils'
import {
selectVGRFishingInsightData,
selectVGRFlagChangeInsightData,
Expand All @@ -36,7 +37,8 @@ export const selectVGRVesselsByInsight = <Insight = any>(
if (!data || !vesselGroup) {
return []
}
const insightVessels = vesselGroup?.vessels?.flatMap((vessel) => {
const vesselsWithoutDuplicates = getVesselsWithoutDuplicates(vesselGroup.vessels)
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

0 comments on commit 4bfc5eb

Please sign in to comment.