From 0203af467c9af28ec5e29733b7a644d15d0d5a5f Mon Sep 17 00:00:00 2001 From: Anna Viklund Date: Wed, 25 Mar 2026 12:07:31 +0100 Subject: [PATCH 01/26] fix: replace score label for confirmed occurrences --- ui/src/components/determination-score.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/components/determination-score.tsx b/ui/src/components/determination-score.tsx index 1267612aa..ea8a10eeb 100644 --- a/ui/src/components/determination-score.tsx +++ b/ui/src/components/determination-score.tsx @@ -21,7 +21,7 @@ export const DeterminationScore = ({
- {scoreLabel} + {confirmed ? translate(STRING.CONFIRMED) : scoreLabel}
) From 020a188b2b293c6e2d3f68a72454f906df436284 Mon Sep 17 00:00:00 2001 From: Anna Viklund Date: Wed, 25 Mar 2026 12:18:28 +0100 Subject: [PATCH 02/26] feat: add tooltip and adjust copy for score column --- ui/src/components/determination-score.tsx | 8 ++++---- .../filters/verification-status-filter.tsx | 4 ++-- ui/src/pages/occurrences/occurrence-columns.tsx | 3 ++- ui/src/utils/language.ts | 13 ++++++++++--- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/ui/src/components/determination-score.tsx b/ui/src/components/determination-score.tsx index ea8a10eeb..3ab0735be 100644 --- a/ui/src/components/determination-score.tsx +++ b/ui/src/components/determination-score.tsx @@ -3,15 +3,15 @@ import { IdentificationScore } from 'nova-ui-kit' import { STRING, translate } from 'utils/language' export const DeterminationScore = ({ - confirmed, score, scoreLabel, tooltip, + verified, }: { - confirmed?: boolean score?: number scoreLabel?: string tooltip?: string + verified?: boolean }) => { if (score === undefined || scoreLabel === undefined) { return {translate(STRING.VALUE_NOT_AVAILABLE)} @@ -20,8 +20,8 @@ export const DeterminationScore = ({ return (
- - {confirmed ? translate(STRING.CONFIRMED) : scoreLabel} + + {verified ? translate(STRING.VERIFIED) : scoreLabel}
) diff --git a/ui/src/components/filtering/filters/verification-status-filter.tsx b/ui/src/components/filtering/filters/verification-status-filter.tsx index ee5d5a3b7..e87681f48 100644 --- a/ui/src/components/filtering/filters/verification-status-filter.tsx +++ b/ui/src/components/filtering/filters/verification-status-filter.tsx @@ -4,8 +4,8 @@ import { booleanToString, stringToBoolean } from '../utils' import { FilterProps } from './types' const OPTIONS = [ - { value: true, label: 'Verified' }, - { value: false, label: 'Not verified' }, + { value: true, label: translate(STRING.VERIFIED) }, + { value: false, label: translate(STRING.NOT_VERIFIED) }, ] export const VerificationStatusFilter = ({ diff --git a/ui/src/pages/occurrences/occurrence-columns.tsx b/ui/src/pages/occurrences/occurrence-columns.tsx index b550aff3f..2091b730a 100644 --- a/ui/src/pages/occurrences/occurrence-columns.tsx +++ b/ui/src/pages/occurrences/occurrence-columns.tsx @@ -68,11 +68,11 @@ export const columns: ( { id: 'score', name: translate(STRING.FIELD_LABEL_SCORE), + tooltip: translate(STRING.TOOLTIP_SCORE), sortField: 'determination_score', renderCell: (item: Occurrence) => ( ), diff --git a/ui/src/utils/language.ts b/ui/src/utils/language.ts index e1808b590..32ba70153 100644 --- a/ui/src/utils/language.ts +++ b/ui/src/utils/language.ts @@ -256,6 +256,7 @@ export enum STRING { TOOLTIP_OCCURRENCE, TOOLTIP_PIPELINE, TOOLTIP_PROCESSING_SERVICE, + TOOLTIP_SCORE, TOOLTIP_SESSION, TOOLTIP_SITE, TOOLTIP_STATUS, @@ -285,6 +286,7 @@ export enum STRING { MANAGE_ACCESS_FOR, NEW_ID, NOT_CONNECTED, + NOT_VERIFIED, OR, PIPELINES, RECENT, @@ -294,10 +296,10 @@ export enum STRING { REMOVE_TAXA_LIST_TAXON, RESULTS_MEMBERS, RESULTS, - SELECT, SELECT_COLUMNS, SELECT_PLACEHOLDER, SELECT_TAXON_PLACEHOLDER, + SELECT, SET_PASSWORD_DETAILS, SET_PASSWORD, SETTINGS, @@ -313,6 +315,7 @@ export enum STRING { USER_INFO, VALUE_NOT_AVAILABLE, VERIFIED_BY, + VERIFIED, YOU, } @@ -433,7 +436,7 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = { [STRING.FIELD_LABEL_ROLE]: 'Role', [STRING.FIELD_LABEL_SAMPLE_CAPTURES]: 'Sample images', [STRING.FIELD_LABEL_SAMPLING_METHOD]: 'Sampling method', - [STRING.FIELD_LABEL_SCORE]: 'Score', + [STRING.FIELD_LABEL_SCORE]: 'Prediction score', [STRING.FIELD_LABEL_SESSION]: 'Session', [STRING.FIELD_LABEL_SESSIONS]: 'Sessions', [STRING.FIELD_LABEL_SITE]: 'Site', @@ -602,6 +605,8 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = { 'A pipeline is a set of algorithms used for processing. A pipeline is picked from a list of algorithm bundle options when a processing job is defined.', [STRING.TOOLTIP_PROCESSING_SERVICE]: 'A processing service is a group of pipelines used for processing captures.', + [STRING.TOOLTIP_SCORE]: + 'This is a model derived prediction score, not a real-world probability. Think of it as a relative metric that will vary based model calibration and available training data.', [STRING.TOOLTIP_SESSION]: 'A session is a fixed period of time of monitoring for one station. The period is typically one night.', [STRING.TOOLTIP_SITE]: @@ -635,6 +640,7 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = { [STRING.MANAGE_ACCESS_FOR]: 'Manage access for {{user}}.', [STRING.NEW_ID]: 'New ID', [STRING.NOT_CONNECTED]: 'Not connected', + [STRING.NOT_VERIFIED]: 'Not verified', [STRING.OR]: 'Or', [STRING.PIPELINES]: 'Pipelines', [STRING.RECENT]: 'Recent', @@ -644,10 +650,10 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = { [STRING.REMOVE_TAXA_LIST_TAXON]: 'Remove taxon', [STRING.RESULTS_MEMBERS]: '{{total}} member(s)', [STRING.RESULTS]: '{{total}} result(s)', - [STRING.SELECT]: 'Select', [STRING.SELECT_COLUMNS]: 'Select columns', [STRING.SELECT_PLACEHOLDER]: 'Select a value', [STRING.SELECT_TAXON_PLACEHOLDER]: 'Select a taxon', + [STRING.SELECT]: 'Select', [STRING.SET_PASSWORD_DETAILS]: 'Please set a new password for your acccount.', [STRING.SET_PASSWORD]: 'Set password', [STRING.SETTINGS]: 'Settings', @@ -663,6 +669,7 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = { [STRING.USER_INFO]: 'User info', [STRING.VALUE_NOT_AVAILABLE]: 'n/a', [STRING.VERIFIED_BY]: 'Verified by\n{{name}}', + [STRING.VERIFIED]: 'Verified', [STRING.YOU]: 'You', } From e5c97996326230de4bd51cb583c2283613fe9e5d Mon Sep 17 00:00:00 2001 From: Anna Viklund Date: Wed, 25 Mar 2026 12:44:27 +0100 Subject: [PATCH 03/26] feat: add more info to filter controls --- .../components/filtering/filter-control.tsx | 39 +++++++++++-------- .../filtering/filters/image-filter.tsx | 2 +- .../filtering/filters/session-filter.tsx | 2 +- .../capture-job/capture-job-dialog.tsx | 2 +- ui/src/utils/useFilters.ts | 34 ++++++++++++++++ 5 files changed, 59 insertions(+), 20 deletions(-) diff --git a/ui/src/components/filtering/filter-control.tsx b/ui/src/components/filtering/filter-control.tsx index bdbb04b66..fa63b023f 100644 --- a/ui/src/components/filtering/filter-control.tsx +++ b/ui/src/components/filtering/filter-control.tsx @@ -79,9 +79,7 @@ export const FilterControl = ({ {filter.label} - {filter.info ? ( - - ) : null} + {filter.info ? : null}
( +export const FilterInfo = ({ + info, +}: { + info: { + title: string + text: string + to?: string + } +}) => ( - - -

{text}

- {to ? ( + +

{info.text}

+ {info.to ? ( - Configure + {info.title} ) : null} diff --git a/ui/src/components/filtering/filters/image-filter.tsx b/ui/src/components/filtering/filters/image-filter.tsx index ad0a88ebe..787bc7dfc 100644 --- a/ui/src/components/filtering/filters/image-filter.tsx +++ b/ui/src/components/filtering/filters/image-filter.tsx @@ -10,7 +10,7 @@ export const ImageFilter = ({ value }: FilterProps) => { })() return ( -
+
{label}
) diff --git a/ui/src/components/filtering/filters/session-filter.tsx b/ui/src/components/filtering/filters/session-filter.tsx index a4c3a4a4f..03001f201 100644 --- a/ui/src/components/filtering/filters/session-filter.tsx +++ b/ui/src/components/filtering/filters/session-filter.tsx @@ -15,7 +15,7 @@ export const SessionFilter = ({ value }: FilterProps) => { })() return ( -
+
{label}
) diff --git a/ui/src/pages/session-details/playback/capture-details/capture-job/capture-job-dialog.tsx b/ui/src/pages/session-details/playback/capture-details/capture-job/capture-job-dialog.tsx index 3bb9bbb05..e330af474 100644 --- a/ui/src/pages/session-details/playback/capture-details/capture-job/capture-job-dialog.tsx +++ b/ui/src/pages/session-details/playback/capture-details/capture-job/capture-job-dialog.tsx @@ -12,7 +12,7 @@ export const CaptureJobDialog = ({ id }: { id: string }) => { const [isOpen, setIsOpen] = useState(false) const { job, isLoading, isFetching, error } = useJobDetails(id, isOpen) const title = translate(STRING.ENTITY_DETAILS, { - type: _.capitalize(translate(STRING.ENTITY_TYPE_DEPLOYMENT)), + type: _.capitalize(translate(STRING.ENTITY_TYPE_JOB)), }) return ( diff --git a/ui/src/utils/useFilters.ts b/ui/src/utils/useFilters.ts index 39a49421e..fab2e9279 100644 --- a/ui/src/utils/useFilters.ts +++ b/ui/src/utils/useFilters.ts @@ -8,6 +8,7 @@ interface FilterConfig { label: string field: string info?: { + title: string text: string to?: string } @@ -21,11 +22,17 @@ export const AVAILABLE_FILTERS = (projectId: string): FilterConfig[] => [ { label: 'Include algorithm', field: 'algorithm', + info: { + title: translate(STRING.NAV_ITEM_ALGORITHMS), + text: translate(STRING.TOOLTIP_ALGORITHM), + to: APP_ROUTES.ALGORITHMS({ projectId }), + }, }, { label: translate(STRING.FIELD_LABEL_CAPTURE_SET), field: 'collection', // This is for viewing occurrences by capture set. @TODO: Can we update this key to "capture_set_id" to streamline? info: { + title: translate(STRING.NAV_ITEM_CAPTURE_SETS), text: translate(STRING.TOOLTIP_CAPTURE_SET), to: APP_ROUTES.CAPTURE_SETS({ projectId }), }, @@ -34,6 +41,7 @@ export const AVAILABLE_FILTERS = (projectId: string): FilterConfig[] => [ label: translate(STRING.FIELD_LABEL_CAPTURE_SET), field: 'source_image_collection', // This is for viewing jobs by capture set. @TODO: Can we update this key to "capture_set_id" to streamline? info: { + title: translate(STRING.NAV_ITEM_CAPTURE_SETS), text: translate(STRING.TOOLTIP_CAPTURE_SET), to: APP_ROUTES.CAPTURE_SETS({ projectId }), }, @@ -42,6 +50,7 @@ export const AVAILABLE_FILTERS = (projectId: string): FilterConfig[] => [ label: translate(STRING.FIELD_LABEL_CAPTURE_SET), field: 'collections', // This is for viewing captures by capture set. @TODO: Can we update this key to "capture_set_id" to streamline? info: { + title: translate(STRING.NAV_ITEM_CAPTURE_SETS), text: translate(STRING.TOOLTIP_CAPTURE_SET), to: APP_ROUTES.CAPTURE_SETS({ projectId }), }, @@ -49,6 +58,11 @@ export const AVAILABLE_FILTERS = (projectId: string): FilterConfig[] => [ { label: 'Station', field: 'deployment', + info: { + title: translate(STRING.NAV_ITEM_DEPLOYMENTS), + text: translate(STRING.TOOLTIP_DEPLOYMENT), + to: APP_ROUTES.DEPLOYMENTS({ projectId }), + }, }, { label: 'End date', @@ -91,18 +105,38 @@ export const AVAILABLE_FILTERS = (projectId: string): FilterConfig[] => [ { label: translate(STRING.FIELD_LABEL_CAPTURE), field: 'detections__source_image', // This is for viewing occurrences by capture. @TODO: Can we update this key to "capture_id" to streamline? + info: { + title: translate(STRING.NAV_ITEM_CAPTURES), + text: translate(STRING.TOOLTIP_CAPTURE), + to: APP_ROUTES.CAPTURES({ projectId }), + }, }, { label: 'Session', field: 'event', + info: { + title: translate(STRING.NAV_ITEM_SESSIONS), + text: translate(STRING.TOOLTIP_SESSION), + to: APP_ROUTES.SESSIONS({ projectId }), + }, }, { label: 'Pipeline', field: 'pipeline', + info: { + title: translate(STRING.NAV_ITEM_PIPELINES), + text: translate(STRING.TOOLTIP_PIPELINE), + to: APP_ROUTES.PIPELINES({ projectId }), + }, }, { label: 'Exclude algorithm', field: 'not_algorithm', + info: { + title: translate(STRING.NAV_ITEM_ALGORITHMS), + text: translate(STRING.TOOLTIP_ALGORITHM), + to: APP_ROUTES.ALGORITHMS({ projectId }), + }, }, { label: 'Include tag', From 46802e630b28813eb9ae1c97b546138ecd490390 Mon Sep 17 00:00:00 2001 From: Anna Viklund Date: Wed, 25 Mar 2026 12:51:44 +0100 Subject: [PATCH 04/26] layout: tweak column order for pipeline table --- .../project/pipelines/pipelines-columns.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ui/src/pages/project/pipelines/pipelines-columns.tsx b/ui/src/pages/project/pipelines/pipelines-columns.tsx index 61b1231a4..47287c5eb 100644 --- a/ui/src/pages/project/pipelines/pipelines-columns.tsx +++ b/ui/src/pages/project/pipelines/pipelines-columns.tsx @@ -32,16 +32,6 @@ export const columns: ( ), }, - { - id: 'description', - name: translate(STRING.FIELD_LABEL_DESCRIPTION), - renderCell: (item: Pipeline) => ( - - ), - }, { id: 'default', name: '', @@ -54,6 +44,16 @@ export const columns: ( <> ), }, + { + id: 'description', + name: translate(STRING.FIELD_LABEL_DESCRIPTION), + renderCell: (item: Pipeline) => ( + + ), + }, { id: 'processing-services-online', name: 'Processing services online', From 55e83259c25d6028d44625b490c79b8716d06506 Mon Sep 17 00:00:00 2001 From: Anna Viklund Date: Thu, 26 Mar 2026 09:35:42 +0100 Subject: [PATCH 05/26] feat: add filter control tooltips and streamline UI --- .../filtering/default-filter-control.tsx | 118 +++++++++--------- .../components/filtering/filter-control.tsx | 48 +------ .../design-system/components/info-tooltip.tsx | 53 ++++++++ .../components/input/input.module.scss | 20 --- .../design-system/components/input/input.tsx | 6 +- .../upload-images-dialog.tsx | 40 +++--- .../pipelines/default-pipeline-badge.tsx | 30 ++--- ui/src/pages/project/sidebar/sidebar.tsx | 2 +- .../session-details/playback/playback.tsx | 4 +- ui/src/utils/language.ts | 2 + ui/src/utils/useFilters.ts | 80 +++++++----- 11 files changed, 207 insertions(+), 196 deletions(-) create mode 100644 ui/src/design-system/components/info-tooltip.tsx diff --git a/ui/src/components/filtering/default-filter-control.tsx b/ui/src/components/filtering/default-filter-control.tsx index d35c09804..085f7eb25 100644 --- a/ui/src/components/filtering/default-filter-control.tsx +++ b/ui/src/components/filtering/default-filter-control.tsx @@ -1,13 +1,10 @@ -import { - FormActions, - FormRow, - FormSection, -} from 'components/form/layout/layout' +import classNames from 'classnames' +import { FormRow } from 'components/form/layout/layout' import { useProjectDetails } from 'data-services/hooks/projects/useProjectDetails' import { ProjectDetails } from 'data-services/models/project-details' import { InputValue } from 'design-system/components/input/input' import { ChevronRightIcon, InfoIcon } from 'lucide-react' -import { Button, buttonVariants, Popover, Switch } from 'nova-ui-kit' +import { Button, buttonVariants, Switch, Tooltip } from 'nova-ui-kit' import { Link, useParams } from 'react-router-dom' import { APP_ROUTES } from 'utils/constants' import { STRING, translate } from 'utils/language' @@ -30,7 +27,7 @@ export const DefaultFiltersControl = ({ field }: { field: string }) => { {filter.label} - {project ? : null} + {project ? : null}
{ ) } -export const DefaultFiltersPopover = ({ +export const DefaultFiltersTooltip = ({ className, project, }: { className?: string project: ProjectDetails }) => ( - - - - - - - - - - - taxon.name) - .join(', ')} - /> - taxon.name) - .join(', ')} - /> - - - {project.canUpdate ? ( - - - Configure - - - - ) : null} - - + + + + + + +
+
+

+ Data is filtered by default based on global project configuration. +

+ + + + + taxon.name) + .join(', ')} + /> + taxon.name) + .join(', ')} + /> + +
+ {project.canUpdate ? ( + + {translate(STRING.CONFIGURE)} + + + ) : null} +
+
+
+
) diff --git a/ui/src/components/filtering/filter-control.tsx b/ui/src/components/filtering/filter-control.tsx index fa63b023f..d5000abf0 100644 --- a/ui/src/components/filtering/filter-control.tsx +++ b/ui/src/components/filtering/filter-control.tsx @@ -1,7 +1,6 @@ -import classNames from 'classnames' -import { ChevronRightIcon, InfoIcon, XIcon } from 'lucide-react' -import { Button, buttonVariants, Tooltip } from 'nova-ui-kit' -import { Link } from 'react-router-dom' +import { InfoTooltip } from 'design-system/components/info-tooltip' +import { XIcon } from 'lucide-react' +import { Button } from 'nova-ui-kit' import { STRING, translate } from 'utils/language' import { useFilters } from 'utils/useFilters' import { AlgorithmFilter, NotAlgorithmFilter } from './filters/algorithm-filter' @@ -79,7 +78,7 @@ export const FilterControl = ({ {filter.label} - {filter.info ? : null} + {filter.tooltip ? : null}
) } - -export const FilterInfo = ({ - info, -}: { - info: { - title: string - text: string - to?: string - } -}) => ( - - - - - - -

{info.text}

- {info.to ? ( - - {info.title} - - - ) : null} -
-
-
-) diff --git a/ui/src/design-system/components/info-tooltip.tsx b/ui/src/design-system/components/info-tooltip.tsx new file mode 100644 index 000000000..3bb81df39 --- /dev/null +++ b/ui/src/design-system/components/info-tooltip.tsx @@ -0,0 +1,53 @@ +import classNames from 'classnames' +import { ChevronRightIcon, InfoIcon } from 'lucide-react' +import { Button, buttonVariants, Tooltip } from 'nova-ui-kit' +import { Link } from 'react-router-dom' +import { STRING, translate } from 'utils/language' + +export const InfoTooltip = (props: { + text: string + link?: { + text: string + to: string + } +}) => ( + + + + + + + + + + +) + +export const Info = ({ + link, + text, +}: { + text: string + link?: { + text: string + to: string + } +}) => ( +
+

{text}

+ {link ? ( + + {link.text} + + + ) : null} +
+) diff --git a/ui/src/design-system/components/input/input.module.scss b/ui/src/design-system/components/input/input.module.scss index 64c4c91ad..d9097e29c 100644 --- a/ui/src/design-system/components/input/input.module.scss +++ b/ui/src/design-system/components/input/input.module.scss @@ -12,14 +12,6 @@ margin-bottom: 4px; } -.label { - display: block; - @include paragraph-small(); - font-weight: 600; - color: $color-neutral-700; - white-space: nowrap; -} - .error { display: block; @include paragraph-xx-small(); @@ -32,18 +24,6 @@ } } -.value { - display: block; - @include paragraph-small(); - color: $color-neutral-300; - white-space: pre-wrap; - - &.link { - color: $color-primary-1-600; - font-weight: 600; - } -} - .description { display: block; @include paragraph-small(); diff --git a/ui/src/design-system/components/input/input.tsx b/ui/src/design-system/components/input/input.tsx index 9b8a283d2..9e45be6dc 100644 --- a/ui/src/design-system/components/input/input.tsx +++ b/ui/src/design-system/components/input/input.tsx @@ -57,7 +57,7 @@ export const Input = forwardRef( return (
-