diff --git a/client/src/api/api.ts b/client/src/api/api.ts index 1734ff08..d13b3ae9 100644 --- a/client/src/api/api.ts +++ b/client/src/api/api.ts @@ -39,6 +39,10 @@ export type RecordingLocationsFeatureProperties = { export type RecordingLocationsGeoJson = FeatureCollection; +export interface SpeciesResponse { + items: Species[]; + count: number; +} export interface Species { species_code: string; family: string; @@ -48,6 +52,7 @@ export interface Species { species?: string; id: number; category: "single" | "multiple" | "frequency" | "noid"; + in_range?: boolean; } export interface SpectrogramAnnotation { @@ -408,8 +413,8 @@ async function getSequenceAnnotations(recordingId: string) { ); } -async function getSpecies() { - return axiosInstance.get("/species/"); +async function getSpecies({recordingId, grtsCellId, sampleFrameId}: {recordingId?: number, grtsCellId?: number, sampleFrameId?: number}) { + return axiosInstance.get("/species/", { params: { recording_id: recordingId, grts_cell_id: grtsCellId, sample_frame_id: sampleFrameId } }); } async function patchAnnotation( diff --git a/client/src/components/SingleSpecieEditor.vue b/client/src/components/SingleSpecieEditor.vue index 529dd35e..12b36624 100644 --- a/client/src/components/SingleSpecieEditor.vue +++ b/client/src/components/SingleSpecieEditor.vue @@ -58,21 +58,46 @@ export default defineComponent({ single: "primary", multiple: "secondary", frequency: "warning", + "suggested species by range location": "success", noid: "", }; + const categoryPriority: Record = { + "suggested species by range location": 0, + single: 1, + multiple: 2, + frequency: 3, + noid: 4, + }; + + const inRangeTooltip = + "This species is in the same range as the recording."; + const groupedItems = computed(() => { + const inRangeSpecies = props.speciesList.filter((s) => s.in_range === true); + const rest = props.speciesList.filter((s) => s.in_range !== true); + const groups: Record = {}; - for (const s of props.speciesList) { + for (const s of rest) { const cat = s.category.charAt(0).toUpperCase() + s.category.slice(1); if (!groups[cat]) groups[cat] = []; groups[cat].push(s); } const result: Array< - { type: "subheader"; title: string } | (Species & { category: string }) + { type: "subheader"; title: string } | Species > = []; - const groupsOrder = ["Single", "Multiple", "Frequency", "Noid"]; + if (inRangeSpecies.length > 0) { + result.push({ type: "subheader", title: "Suggested Species by Range Location" }); + const sortedInRange = [...inRangeSpecies].sort((a, b) => { + const aCat = categoryPriority[a.category] ?? 999; + const bCat = categoryPriority[b.category] ?? 999; + if (aCat !== bCat) return aCat - bCat; + return a.species_code.localeCompare(b.species_code); + }); + result.push(...sortedInRange); + } + const groupsOrder = ["In range", "Single", "Multiple", "Frequency", "Noid"]; groupsOrder.forEach((key) => { result.push({ type: "subheader", title: key }); result.push(...(groups[key] ?? [])); @@ -115,6 +140,7 @@ export default defineComponent({ categoryColors, speciesAutocomplete, onClearOrDeleteClick, + inRangeTooltip, }; }, }); @@ -164,6 +190,7 @@ export default defineComponent({ ? `bg-${categoryColors[String(subProps.title).toLowerCase()]}` : '' " + > {{ subProps.title }} @@ -184,6 +211,18 @@ export default defineComponent({ {{ (item.raw as Species).category }} + diff --git a/client/src/components/SingleSpecieInfo.vue b/client/src/components/SingleSpecieInfo.vue index 161cba40..8c46d2ee 100644 --- a/client/src/components/SingleSpecieInfo.vue +++ b/client/src/components/SingleSpecieInfo.vue @@ -49,6 +49,9 @@ export default defineComponent({ const orderedSpecies = ref([]); + const inRangeTooltip = + "This species is in the same range as the recording."; + function sortSpecies(species: Species[], selectedCode: string | null) { const copied = cloneDeep(species); copied.sort((a, b) => { @@ -56,6 +59,10 @@ export default defineComponent({ const bSelected = selectedCode === b.species_code; if (aSelected && !bSelected) return -1; if (!aSelected && bSelected) return 1; + const aIn = a.in_range === true; + const bIn = b.in_range === true; + if (aIn && !bIn) return -1; + if (!aIn && bIn) return 1; const aCat = categoryPriority[a.category] ?? 999; const bCat = categoryPriority[b.category] ?? 999; if (aCat !== bCat) return aCat - bCat; @@ -131,6 +138,7 @@ export default defineComponent({ saveAndClose, buttonLabel, selectedSpecies, + inRangeTooltip, }; }, }); @@ -232,7 +240,12 @@ export default defineComponent({ class="elevation-1 my-recordings" >