From a17a820ef9ff6e6ecf0e411ad2a2be404848b84d Mon Sep 17 00:00:00 2001 From: j8seangel Date: Wed, 28 Aug 2024 20:40:28 +0200 Subject: [PATCH] create vesselGroups from vessel section --- .../features/dataviews/dataviews.utils.ts | 26 +++++----- .../vessel-groups/VesselGroupAddButton.tsx | 52 ++++++++++++++----- .../vessel-groups/vessel-groups.slice.ts | 28 ++++++++-- .../workspace/vessels/VesselsSection.tsx | 47 +++++++++++++++-- .../public/locales/source/translations.json | 1 + libs/api-types/src/vesselGroups.ts | 2 +- 6 files changed, 122 insertions(+), 34 deletions(-) diff --git a/apps/fishing-map/features/dataviews/dataviews.utils.ts b/apps/fishing-map/features/dataviews/dataviews.utils.ts index 8eae64d91c..bd4203b28c 100644 --- a/apps/fishing-map/features/dataviews/dataviews.utils.ts +++ b/apps/fishing-map/features/dataviews/dataviews.utils.ts @@ -216,20 +216,22 @@ export const getContextDataviewInstance = (datasetId: string): DataviewInstance< export const getVesselGroupDataviewInstance = ( vesselGroupId: string -): DataviewInstance => { - const contextDataviewInstance = { - id: `${VESSEL_GROUP_DATAVIEW_PREFIX}${Date.now()}`, - category: DataviewCategory.VesselGroups, - config: { - colorCyclingType: 'fill' as ColorCyclingType, - visible: true, - filters: { - 'vessel-groups': [vesselGroupId], +): DataviewInstance | undefined => { + if (vesselGroupId) { + const contextDataviewInstance = { + id: `${VESSEL_GROUP_DATAVIEW_PREFIX}${Date.now()}`, + category: DataviewCategory.VesselGroups, + config: { + colorCyclingType: 'fill' as ColorCyclingType, + visible: true, + filters: { + 'vessel-groups': [vesselGroupId], + }, }, - }, - dataviewId: PRESENCE_DATAVIEW_SLUG, + dataviewId: PRESENCE_DATAVIEW_SLUG, + } + return contextDataviewInstance } - return contextDataviewInstance } export const getDataviewInstanceFromDataview = (dataview: Dataview) => { diff --git a/apps/fishing-map/features/vessel-groups/VesselGroupAddButton.tsx b/apps/fishing-map/features/vessel-groups/VesselGroupAddButton.tsx index 843123a58d..bc35529ee0 100644 --- a/apps/fishing-map/features/vessel-groups/VesselGroupAddButton.tsx +++ b/apps/fishing-map/features/vessel-groups/VesselGroupAddButton.tsx @@ -3,18 +3,22 @@ import cx from 'classnames' import { useTranslation } from 'react-i18next' import React from 'react' import { Button, ButtonType, ButtonSize } from '@globalfishingwatch/ui-components' +import { VesselGroupUpsert } from '@globalfishingwatch/api-types' import { VesselLastIdentity } from 'features/search/search.slice' import { setVesselGroupEditId, setNewVesselGroupSearchVessels, setVesselGroupsModalOpen, MAX_VESSEL_GROUP_VESSELS, + updateVesselGroupVesselsThunk, + createVesselGroupThunk, } from 'features/vessel-groups/vessel-groups.slice' import { useAppDispatch } from 'features/app/app.hooks' import { IdentityVesselData } from 'features/vessel/vessel.slice' import { ReportVesselWithDatasets } from 'features/area-report/reports.selectors' +import { getCurrentIdentityVessel } from 'features/vessel/vessel.utils' import styles from './VesselGroupListTooltip.module.css' -import VesselGroupListTooltip from './VesselGroupListTooltip' +import VesselGroupListTooltip, { NEW_VESSEL_GROUP_ID } from './VesselGroupListTooltip' type VesselGroupAddButtonProps = { mode?: 'auto' | 'manual' @@ -79,12 +83,36 @@ function VesselGroupAddButton(props: VesselGroupAddButtonProps) { const handleAddToVesselGroupClick = useCallback( async (vesselGroupId?: string) => { if (mode === 'auto') { - console.log('TODO') - // const vesselGroup = { - // id: vesselGroupId, - // vessels, - // } - // dispatchedAction = await dispatch(updateVesselGroupThunk(vesselGroup)) + const vesselGroup: VesselGroupUpsert = { + vessels: vessels.flatMap((vessel) => { + const { id, dataset } = getCurrentIdentityVessel(vessel as IdentityVesselData) + if (!id || !dataset) { + return [] + } + return { + vesselId: id, + dataset: dataset as any, + } + }), + } + const thunkFn: any = + vesselGroupId === NEW_VESSEL_GROUP_ID + ? createVesselGroupThunk + : updateVesselGroupVesselsThunk + if (vesselGroupId === NEW_VESSEL_GROUP_ID) { + const name = prompt(t('vesselGroup.enterName', 'Enter vessel group name')) + if (name) { + vesselGroup.name = name + } + } else { + vesselGroup.id = vesselGroupId + } + const dispatchedAction = await dispatch(thunkFn(vesselGroup)) + if (thunkFn.fulfilled.match(dispatchedAction)) { + if (onAddToVesselGroup) { + onAddToVesselGroup(dispatchedAction.payload.id) + } + } } else { const vesselsWithDataset = vessels.map((vessel) => ({ ...vessel, @@ -94,7 +122,7 @@ function VesselGroupAddButton(props: VesselGroupAddButtonProps) { (vessel as ReportVesselWithDatasets)?.infoDataset?.id, })) if (vesselsWithDataset?.length) { - if (vesselGroupId) { + if (vesselGroupId && vesselGroupId !== NEW_VESSEL_GROUP_ID) { dispatch(setVesselGroupEditId(vesselGroupId)) } dispatch(setNewVesselGroupSearchVessels(vesselsWithDataset)) @@ -102,12 +130,12 @@ function VesselGroupAddButton(props: VesselGroupAddButtonProps) { } else { console.warn('No related activity datasets founds for', vesselsWithDataset) } - } - if (onAddToVesselGroup) { - onAddToVesselGroup(vesselGroupId) + if (onAddToVesselGroup) { + onAddToVesselGroup(vesselGroupId) + } } }, - [dispatch, mode, onAddToVesselGroup, vessels] + [dispatch, mode, onAddToVesselGroup, t, vessels] ) return ( diff --git a/apps/fishing-map/features/vessel-groups/vessel-groups.slice.ts b/apps/fishing-map/features/vessel-groups/vessel-groups.slice.ts index 0bf84b7a91..9646fa2580 100644 --- a/apps/fishing-map/features/vessel-groups/vessel-groups.slice.ts +++ b/apps/fishing-map/features/vessel-groups/vessel-groups.slice.ts @@ -336,7 +336,7 @@ export const createVesselGroupThunk = createAsyncThunk( async (vesselGroupCreate: VesselGroupUpsert, { dispatch, getState }) => { const vesselGroupUpsert: VesselGroupUpsert = { ...vesselGroupCreate, - vessels: removeDuplicatedVesselGroupvessels(vesselGroupCreate.vessels), + vessels: removeDuplicatedVesselGroupvessels(vesselGroupCreate.vessels || []), } const saveVesselGroup: any = async (vesselGroup: VesselGroupUpsert, tries = 0) => { let vesselGroupUpdated: VesselGroup @@ -366,12 +366,11 @@ export const updateVesselGroupThunk = createAsyncThunk( 'vessel-groups/update', async (vesselGroupUpsert: VesselGroupUpsert & { id: string }) => { const { id, ...rest } = vesselGroupUpsert - const url = `/vessel-groups/${id}` const vesselGroup: VesselGroupUpsert = { ...rest, - vessels: removeDuplicatedVesselGroupvessels(rest.vessels), + vessels: removeDuplicatedVesselGroupvessels(rest.vessels || []), } - const vesselGroupUpdated = await GFWAPI.fetch(url, { + const vesselGroupUpdated = await GFWAPI.fetch(`/vessel-groups/${id}`, { method: 'PATCH', body: vesselGroup, } as FetchOptions) @@ -379,6 +378,27 @@ export const updateVesselGroupThunk = createAsyncThunk( } ) +export const updateVesselGroupVesselsThunk = createAsyncThunk( + 'vessel-groups/update-vessels', + async ( + { id, vessels = [] }: Pick & { id: string }, + { getState, dispatch } + ) => { + let vesselGroup = selectVesselGroupById(id)(getState() as any) + if (!vesselGroup) { + vesselGroup = await GFWAPI.fetch(`/vessel-groups/${id}`) + } + if (vesselGroup) { + return dispatch( + updateVesselGroupThunk({ + id: vesselGroup.id, + vessels: [...vesselGroup.vessels, ...vessels], + }) + ) + } + } +) + export const deleteVesselGroupThunk = createAsyncThunk< VesselGroup, string, diff --git a/apps/fishing-map/features/workspace/vessels/VesselsSection.tsx b/apps/fishing-map/features/workspace/vessels/VesselsSection.tsx index d4f0129f99..1d2b7c9084 100644 --- a/apps/fishing-map/features/workspace/vessels/VesselsSection.tsx +++ b/apps/fishing-map/features/workspace/vessels/VesselsSection.tsx @@ -4,6 +4,8 @@ import { SortableContext } from '@dnd-kit/sortable' import cx from 'classnames' import { useTranslation, Trans } from 'react-i18next' import { IconButton, Switch } from '@globalfishingwatch/ui-components' +import { DatasetTypes, ResourceStatus } from '@globalfishingwatch/api-types' +import { resolveDataviewDatasetResource } from '@globalfishingwatch/dataviews-client' import { useLocationConnect } from 'routes/routes.hook' import styles from 'features/workspace/shared/Sections.module.css' import { isBasicSearchAllowed } from 'features/search/search.selectors' @@ -21,9 +23,13 @@ import { } from 'features/timebar/timebar-vessel.hooks' import { getVesselLabel } from 'utils/info' import { selectResources, ResourcesState } from 'features/resources/resources.slice' -import { VESSEL_DATAVIEW_INSTANCE_PREFIX } from 'features/dataviews/dataviews.utils' +import { + getVesselGroupDataviewInstance, + VESSEL_DATAVIEW_INSTANCE_PREFIX, +} from 'features/dataviews/dataviews.utils' import { selectReadOnly } from 'features/app/selectors/app.selectors' import VesselGroupAddButton from 'features/vessel-groups/VesselGroupAddButton' +import { NEW_VESSEL_GROUP_ID } from 'features/vessel-groups/VesselGroupListTooltip' import VesselEventsLegend from './VesselEventsLegend' import VesselLayerPanel from './VesselLayerPanel' import VesselsFromPositions from './VesselsFromPositions' @@ -68,9 +74,20 @@ function VesselsSection(): React.ReactElement { deleteDataviewInstance(dataviews.map((d) => d.id)) }, [dataviews, deleteDataviewInstance]) - const onAddToVesselGroupClick = useCallback(() => { - console.log('todo') - }, []) + const onAddToVesselGroupClick = useCallback( + (vesselGroupId?: string) => { + if (vesselGroupId && vesselGroupId !== NEW_VESSEL_GROUP_ID) { + const dataviewInstance = getVesselGroupDataviewInstance(vesselGroupId) + if (dataviewInstance) { + const dataviewsToDelete = dataviews.flatMap((d) => + d.config?.visible ? { id: d.id, deleted: true } : [] + ) + upsertDataviewInstance([...dataviewsToDelete, dataviewInstance]) + } + } + }, + [dataviews, upsertDataviewInstance] + ) const onSetSortOrderClick = useCallback(() => { sortOrder.current = sortOrder.current === 'ASC' ? 'DESC' : 'ASC' @@ -104,6 +121,20 @@ function VesselsSection(): React.ReactElement { }) }, [dispatchLocation, workspace]) + const vesselResources = dataviews.flatMap((dataview) => { + if (!dataview.config?.visible) { + return [] + } + const { url: infoUrl } = resolveDataviewDatasetResource(dataview, DatasetTypes.Vessels) + return resources[infoUrl] || [] + }) + const areVesselsLoading = vesselResources.some( + (resource) => resource.status === ResourceStatus.Loading + ) + const vesselsToVesselGroup = areVesselsLoading + ? [] + : vesselResources.map((resource) => resource.data) + return (
@@ -123,9 +154,15 @@ function VesselsSection(): React.ReactElement {
{dataviews.length > 1 && ( - + +export type VesselGroupUpsert = Partial>