Skip to content

Commit c49f7ef

Browse files
2 parents 6001982 + 340f4b7 commit c49f7ef

24 files changed

Lines changed: 550 additions & 384 deletions

apps/fishing-map/features/help/HelpHub.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,43 @@ function HelpHub() {
2929
const onHelpClick = () => {
3030
trackEvent({
3131
category: TrackCategory.HelpHints,
32-
action: `Pressing the '?' on the left of the screen to restore help hints after they've been dismissed`,
33-
label: percentageOfHintsSeen.toString(),
32+
action: `restore help hints after they've been dismissed`,
33+
label: `percentage of hints seen: ${percentageOfHintsSeen.toString()}%`,
3434
})
3535
dispatch(resetHints())
3636
}
3737

3838
const getUserGuideLink = () => {
39+
trackEvent({
40+
category: TrackCategory.HelpHints,
41+
action: `redirect to user guide`,
42+
label: i18n.language,
43+
})
44+
3945
if (i18n.language === 'es') return 'https://globalfishingwatch.org/es/guia-de-usuario/'
4046
if (i18n.language === 'fr') return 'https://globalfishingwatch.org/user-guide-french/'
4147
if (i18n.language === 'pt') return 'https://globalfishingwatch.org/user-guide-portuguese/'
4248
return 'https://globalfishingwatch.org/user-guide/'
4349
}
4450

4551
const getFAQsLink = () => {
52+
trackEvent({
53+
category: TrackCategory.HelpHints,
54+
action: `redirect to FAQs`,
55+
label: i18n.language,
56+
})
57+
4658
if (i18n.language === 'es') return 'https://globalfishingwatch.org/es/ayuda-faqs/'
4759
return 'https://globalfishingwatch.org/help-faqs/'
4860
}
4961

5062
const getVideoTutorialsLink = () => {
63+
trackEvent({
64+
category: TrackCategory.HelpHints,
65+
action: `redirect to video tutorials`,
66+
label: i18n.language,
67+
})
68+
5169
if (i18n.language === 'es') return 'https://globalfishingwatch.org/tutoriales'
5270
return 'https://globalfishingwatch.org/tutorials'
5371
}

apps/fishing-map/features/help/Hint.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ function Hint({ id, className }: HintProps) {
6262
}, [id])
6363

6464
const onOpenChange: PopoverProps['onOpenChange'] = (nextOpen: boolean) => {
65+
trackEvent({
66+
category: TrackCategory.HelpHints,
67+
action: 'clicked on help hint popup',
68+
label: id,
69+
})
6570
setVisible(nextOpen)
6671
}
6772

apps/fishing-map/features/help/UserGuideLink.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import cx from 'classnames'
33

44
import { IconButton } from '@globalfishingwatch/ui-components'
55

6+
import { TrackCategory, trackEvent } from 'features/app/analytics.hooks'
7+
68
import styles from './UserGuideLink.module.css'
79

810
export type UserGuideSection =
@@ -70,6 +72,13 @@ function UserGuideLink({ section, className }: UserGuideLinkProps) {
7072
return (
7173
<a
7274
className={cx(styles.link, className)}
75+
onClick={() =>
76+
trackEvent({
77+
category: TrackCategory.HelpHints,
78+
action: `redirect to user guide to specific section`,
79+
label: `${i18n.language} - ${section}`,
80+
})
81+
}
7382
href={`${userGuideLink}${userGuideSections[section]}`}
7483
target="_blank"
7584
rel="noreferrer"

apps/fishing-map/features/user/UserVesselGroups.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,11 @@ function UserVesselGroups() {
111111
<span className={cx(styles.secondary, styles.marginLeft)}>
112112
({getVesselGroupVesselsCount(vesselGroup)})
113113
</span>
114-
<IconButton icon="analysis" className={styles.right} />
114+
<IconButton
115+
icon="analysis"
116+
className={styles.right}
117+
tooltip={t((t) => t.vesselGroupReport.clickToSee)}
118+
/>
115119
</span>
116120
</VesselGroupReportLink>
117121
)}

apps/fishing-map/features/vessel-groups/VesselGroupModal.module.css

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,22 @@
2323
gap: var(--space-S);
2424
}
2525

26-
.disclaimerFooter {
26+
.textAlign {
2727
display: flex;
2828
align-items: center;
29+
gap: var(--space-XS);
2930
}
3031

3132
.parameters {
3233
display: flex;
3334
flex-wrap: wrap;
34-
row-gap: 1.5rem;
3535
margin-bottom: 2rem;
3636
align-items: center;
37+
gap: var(--space-M);
3738
}
3839

3940
.parameters > * {
4041
flex: 1;
41-
max-width: 25rem;
42-
margin-right: 2rem;
4342
}
4443

4544
.vesselGroupSearchContainer {

apps/fishing-map/features/vessel-groups/VesselGroupModal.tsx

Lines changed: 91 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ import {
3232
} from 'features/reports/report-vessel-group/vessel-group-report.slice'
3333
import { selectSearchQuery } from 'features/search/search.config.selectors'
3434
import { resetSidebarScroll } from 'features/sidebar/sidebar.utils'
35-
import { selectUserData } from 'features/user/selectors/user.selectors'
35+
import { selectIsGFWUser, selectUserData } from 'features/user/selectors/user.selectors'
36+
import { DEFAULT_VESSEL_IDENTITY_ID } from 'features/vessel/vessel.config'
3637
import {
3738
selectHasVesselGroupSearchVessels,
3839
selectHasVesselGroupVesselsOverflow,
@@ -87,6 +88,7 @@ import {
8788
selectVesselGroupModalOpen,
8889
selectVesselGroupModalSearchIdField,
8990
selectVesselGroupModalSources,
91+
selectVesselGroupModalUnmatchedIDs,
9092
selectVesselGroupModalVessels,
9193
selectVesselGroupSearchStatus,
9294
setVesselGroupModalName,
@@ -101,10 +103,12 @@ function VesselGroupModal(): React.ReactElement<any> {
101103
const dispatch = useAppDispatch()
102104
const [buttonLoading, setButtonLoading] = useState<VesselGroupConfirmationMode | ''>('')
103105
const userData = useSelector(selectUserData)
106+
const isGFWUser = useSelector(selectIsGFWUser)
104107
const isModalOpen = useSelector(selectVesselGroupModalOpen)
105108
const confirmationMode = useSelector(selectVesselGroupConfirmationMode)
106109
const searchIdField = useSelector(selectVesselGroupModalSearchIdField)
107110
const csvData = useSelector(selectVesselGroupModalCsvData)
111+
const unmatchedIDs = useSelector(selectVesselGroupModalUnmatchedIDs)
108112
const selectedCsvColumns = useSelector(selectVesselGroupModalCsvColumns)
109113
const editingVesselGroupId = useSelector(selectVesselGroupEditId)
110114
const vesselGroupModalSearchIds = useSelector(selectVesselGroupsModalSearchIds)
@@ -156,21 +160,24 @@ function VesselGroupModal(): React.ReactElement<any> {
156160
if (editingVesselGroup?.name) {
157161
dispatch(setVesselGroupModalName(editingVesselGroup?.name))
158162
}
159-
}, [editingVesselGroup?.name])
163+
}, [dispatch, editingVesselGroup?.name])
164+
165+
const vesselGroupModalSources = useSelector(selectVesselGroupModalSources)
160166

161167
const sourceOptions = useMemo(
162168
() =>
163-
vesselDatasets.map((d) => ({
164-
id: d.id,
165-
label: getDatasetLabel(d),
166-
})),
167-
[vesselDatasets]
169+
isGFWUser
170+
? vesselDatasets.map((d) => ({
171+
id: d.id,
172+
label: getDatasetLabel(d),
173+
}))
174+
: [],
175+
[vesselDatasets, isGFWUser]
168176
)
169-
const vesselGroupModalSources = useSelector(selectVesselGroupModalSources)
170177

171178
const sourcesSelected = useMemo(
172-
() => sourceOptions.filter((s) => vesselGroupModalSources?.includes(s.id)),
173-
[sourceOptions, vesselGroupModalSources]
179+
() => (isGFWUser ? sourceOptions.filter((s) => vesselGroupModalSources?.includes(s.id)) : []),
180+
[isGFWUser, sourceOptions, vesselGroupModalSources]
174181
)
175182

176183
const setGroupName = useCallback(
@@ -213,9 +220,23 @@ function VesselGroupModal(): React.ReactElement<any> {
213220
csvData?: VesselGroupCsvData[]
214221
csvColumns?: string[]
215222
}) => {
216-
const datasets = sourcesSelected.length
217-
? sourcesSelected.map(({ id }) => id)
218-
: sourceOptions.map(({ id }) => id)
223+
const datasets = isGFWUser
224+
? sourcesSelected.length
225+
? sourcesSelected.map(({ id }) => id)
226+
: sourceOptions.map(({ id }) => id)
227+
: [DEFAULT_VESSEL_IDENTITY_ID]
228+
229+
trackEvent({
230+
category: TrackCategory.VesselGroups,
231+
action: `match vessels from ${ids ? 'IDs' : csvData && 'CSV'} to create a vessel group`,
232+
label: getEventLabel([
233+
transmissionDateFrom && `active after: ${transmissionDateFrom}`,
234+
transmissionDateTo && `active before: ${transmissionDateTo}`,
235+
datasets && `datasets: ${datasets.join(', ')}`,
236+
searchIdField && `id field: ${searchIdField}`,
237+
]),
238+
})
239+
219240
searchVesselGroupsVesselsRef.current = dispatch(
220241
searchVesselGroupsVesselsThunk({
221242
ids,
@@ -239,7 +260,16 @@ function VesselGroupModal(): React.ReactElement<any> {
239260
setError((action.payload as any)?.message || '')
240261
}
241262
},
242-
[dispatch, sourcesSelected, sourceOptions, t, transmissionDateFrom, transmissionDateTo]
263+
[
264+
isGFWUser,
265+
sourcesSelected,
266+
sourceOptions,
267+
transmissionDateFrom,
268+
transmissionDateTo,
269+
searchIdField,
270+
dispatch,
271+
t,
272+
]
243273
)
244274

245275
useEffect(() => {
@@ -303,11 +333,11 @@ function VesselGroupModal(): React.ReactElement<any> {
303333
dispatchSearchVesselsGroupsThunk({ csvData, csvColumns: selectedCsvColumns })
304334
}
305335
}, [
306-
dispatchSearchVesselsGroupsThunk,
307336
vesselGroupModalSearchIds,
308337
searchIdField,
309338
csvData,
310339
selectedCsvColumns,
340+
dispatchSearchVesselsGroupsThunk,
311341
])
312342

313343
const onCreateGroupClick = useCallback(
@@ -396,8 +426,6 @@ function VesselGroupModal(): React.ReactElement<any> {
396426
dispatch(resetVesselGroupReportData())
397427
dispatch(fetchVesselGroupReportThunk({ vesselGroupId: editingVesselGroupId }))
398428
}
399-
close()
400-
setButtonLoading('')
401429
trackEvent({
402430
category: TrackCategory.VesselGroups,
403431
action: `${editingVesselGroupId ? 'Edit' : 'Create new'} vessel group`,
@@ -407,6 +435,8 @@ function VesselGroupModal(): React.ReactElement<any> {
407435
]),
408436
value: `number of vessels: ${vessels.length}`,
409437
})
438+
close()
439+
setButtonLoading('')
410440
}
411441
},
412442
[
@@ -486,7 +516,17 @@ function VesselGroupModal(): React.ReactElement<any> {
486516
return (
487517
<Modal
488518
appSelector={ROOT_DOM_ELEMENT}
489-
title={t((t) => t.vesselGroup.vesselGroup)}
519+
title={
520+
<div className={styles.textAlign}>
521+
{t((t) => t.vesselGroup.vesselGroup)}
522+
<IconButton
523+
size="small"
524+
icon="info"
525+
type="default"
526+
tooltip={t((t) => t.vesselGroup.vesselGroupTooltip)}
527+
/>
528+
</div>
529+
}
490530
isOpen={isModalOpen}
491531
className={styles.modal}
492532
contentClassName={styles.modalContainer}
@@ -504,23 +544,28 @@ function VesselGroupModal(): React.ReactElement<any> {
504544
/>
505545
{!fullModalLoading && !hasVesselGroupsVessels && (
506546
<Fragment>
507-
<MultiSelect
508-
label={t((t) => t.layer.sources)}
509-
placeholder={getPlaceholderBySelections({
510-
selection: sourcesSelected.map(({ id }) => id),
511-
options: sourceOptions,
512-
})}
513-
options={sourceOptions}
514-
selectedOptions={sourcesSelected}
515-
onSelect={onSelectSourceClick}
516-
onRemove={sourcesSelected?.length > 1 ? onRemoveSourceClick : undefined}
517-
/>
547+
<div>
548+
{isGFWUser && (
549+
<MultiSelect
550+
label={t((t) => t.layer.sources)}
551+
placeholder={getPlaceholderBySelections({
552+
selection: sourcesSelected.map(({ id }) => id),
553+
options: sourceOptions,
554+
})}
555+
options={sourceOptions}
556+
selectedOptions={sourcesSelected}
557+
onSelect={onSelectSourceClick}
558+
onRemove={sourcesSelected?.length > 1 ? onRemoveSourceClick : undefined}
559+
/>
560+
)}
561+
</div>
518562
<div>
519563
<InputDate
520564
value={transmissionDateTo || ''}
521565
max={AVAILABLE_END.slice(0, 10) as string}
522566
min={AVAILABLE_START.slice(0, 10) as string}
523567
label={t((t) => t.common.active_after)}
568+
labelTooltip={t((t) => t.vesselGroup.activeAfterTooltip)}
524569
onChange={(e) => {
525570
setTransmissionDateTo(e.target.value)
526571
}}
@@ -535,6 +580,7 @@ function VesselGroupModal(): React.ReactElement<any> {
535580
max={AVAILABLE_END.slice(0, 10) as string}
536581
min={AVAILABLE_START.slice(0, 10) as string}
537582
label={t((t) => t.common.active_before)}
583+
labelTooltip={t((t) => t.vesselGroup.activeBeforeTooltip)}
538584
onChange={(e) => {
539585
setTransmissionDateFrom(e.target.value)
540586
}}
@@ -585,6 +631,21 @@ function VesselGroupModal(): React.ReactElement<any> {
585631
} as VesselGroup),
586632
})}
587633
</label>
634+
{unmatchedIDs && (
635+
<label className={styles.textAlign}>
636+
<Icon icon="warning" type="warning" />
637+
{t((t) => t.vesselGroup.unmatchedIDs, {
638+
field: searchIdField,
639+
ids: unmatchedIDs.join(', '),
640+
})}
641+
<IconButton
642+
size="small"
643+
icon="copy"
644+
tooltip={t((t) => t.common.copy, { text: 'IDs' })}
645+
onClick={() => navigator.clipboard.writeText(unmatchedIDs.join(', '))}
646+
/>
647+
</label>
648+
)}
588649
<div className={styles.vesselsTableContainer}>
589650
<VesselGroupVessels searchIdField={searchIdField || 'imo'} />
590651
</div>
@@ -608,7 +669,7 @@ function VesselGroupModal(): React.ReactElement<any> {
608669
<div className={styles.footerMsg}>
609670
{error && <span className={styles.errorMsg}>{error}</span>}
610671
{datasetsWithoutRelatedEvents.length >= 1 && (
611-
<div className={styles.disclaimerFooter}>
672+
<div className={styles.textAlign}>
612673
<Icon icon="warning" type="warning" />
613674
{t((t) => t.vesselGroup.disclaimerFeaturesNotAvailable, {
614675
features: t((t) => t.vesselGroup.disclaimerFeaturesNotAvailableGenericPrefix),

apps/fishing-map/features/vessel-groups/VesselGroupModalVessels.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const VesselGroupVesselRow = memo(function VesselGroupVesselRow({
5151
flag,
5252
ssvid,
5353
imo,
54+
callsign,
5455
transmissionDateFrom,
5556
transmissionDateTo,
5657
geartypes,
@@ -80,6 +81,14 @@ const VesselGroupVesselRow = memo(function VesselGroupVesselRow({
8081
<td className={cx({ [styles.highlighted]: normalisedselectedCsvColumns?.includes('imo') })}>
8182
<span>{hiddenProperties.includes('imo') ? '' : imo || EMPTY_FIELD_PLACEHOLDER}</span>
8283
</td>
84+
<td
85+
className={cx({ [styles.highlighted]: normalisedselectedCsvColumns?.includes('callsign') })}
86+
>
87+
<span>
88+
{hiddenProperties.includes('callsign') ? '' : callsign || EMPTY_FIELD_PLACEHOLDER}
89+
</span>
90+
</td>
91+
8392
<td>
8493
<span>{vesselName}</span>
8594
</td>
@@ -179,6 +188,7 @@ function VesselGroupVesselsComponent({ searchIdField }: { searchIdField: IdField
179188
<tr>
180189
<th>{t((t) => t.vessel.mmsi)}</th>
181190
<th>{t((t) => t.vessel.imo)}</th>
191+
<th>{t((t) => t.vessel.callsign)}</th>
182192
<th>{t((t) => t.common.name)}</th>
183193
<th>{t((t) => t.vessel.flag)}</th>
184194
<th>{t((t) => t.vessel.gearType_short)}</th>

0 commit comments

Comments
 (0)