SpeciesFileGroup/taxonworks

View on GitHub
app/javascript/vue/tasks/nomenclature/new_taxon_name/components/Combination/CombinationMain.vue

Summary

Maintainability
Test Coverage
<template>
  <block-layout
    :spinner="!taxon.id"
    anchor="subsequent-combination"
  >
    <template #header>
      <h3>Subsequent combination</h3>
    </template>
    <template #body>
      <CombinationCurrent
        v-if="!isCurrentTaxonInCombination"
        :combination-ranks="combinationRanks"
        @on-set="(item) => (combination = item)"
      />
      <CombinationRank
        v-for="(group, groupName) in combinationRanks"
        :key="groupName"
        v-model="combination"
        :nomenclature-group="groupName"
        :rank-group="Object.keys(group)"
        :disabled="!isCurrentTaxonInCombination"
        :options="{
          animation: 150,
          filter: '.item-filter'
        }"
        :group="{
          name: groupName,
          put: [groupName],
          pull: false
        }"
      />

      <div class="original-combination margin-medium-top margin-medium-bottom">
        <div class="rank-name-label" />
        <CombinationVerbatim v-model="currentCombination.verbatim_name" />
      </div>
      <template v-if="Object.keys(combination).length">
        <CombinationCitation
          :taxon="taxon"
          v-model="citationData"
        />
        <hr />

        <template v-if="isBotanyCode">
          <h3>Classification</h3>
          <ClassificationMain
            :taxon-id="taxon.id"
            @select="addClassification"
          />

          <DisplayList
            v-if="currentCombination.id"
            :list="classifications"
            label="object_tag"
            annotator
            @delete="removeClassification"
          />
          <DisplayList
            v-else
            :list="queueClassification"
            label="name"
            :delete-warning="false"
            soft-delete
            @delete-index="queueClassification.splice($event, 1)"
          />
        </template>
      </template>

      <div class="margin-medium-top">
        <VBtn
          class="margin-small-right"
          color="create"
          medium
          :disabled="!isCurrentTaxonInCombination"
          @click="saveCombination"
        >
          {{ currentCombination.id ? 'Update' : 'Create' }}
        </VBtn>
        <VBtn
          color="primary"
          medium
          @click="newCombination"
        >
          New
        </VBtn>
      </div>
      <hr />
      <CombinationList
        :list="combinationList"
        @edit="loadCombination"
        @delete="removeCombination"
      />
    </template>
  </block-layout>
</template>

<script setup>
import { ref, computed, reactive, watch } from 'vue'
import { useStore } from 'vuex'
import { GetterNames } from '../../store/getters/getters.js'
import { ActionNames } from '../../store/actions/actions.js'
import {
  subsequentCombinationType,
  combinationIcnType
} from '../../const/combinationTypes'
import { COMBINATION, NOMENCLATURE_CODE_BOTANY } from '@/constants/index.js'
import { addToArray, removeFromArray } from '@/helpers/arrays.js'
import { TaxonNameClassification } from '@/routes/endpoints'
import VBtn from '@/components/ui/VBtn/index.vue'
import BlockLayout from '@/components/layout/BlockLayout.vue'
import CombinationRank from './CombinationRank.vue'
import CombinationCurrent from './CombinationCurrent.vue'
import CombinationVerbatim from './CombinationVerbatim.vue'
import CombinationCitation from './Author/AuthorMain.vue'
import CombinationList from './CombinationList.vue'
import ClassificationMain from '../Classification/ClassificationMain.vue'
import makeCitationObject from '@/factory/Citation.js'
import DisplayList from '@/components/displayList.vue'

const store = useStore()
const combination = ref({})
const combinationList = computed(
  () => store.getters[GetterNames.GetCombinations]
)
const taxon = computed(() => store.getters[GetterNames.GetTaxon])
const currentCombination = ref({})
const currentCombinationId = computed(() => currentCombination.value.id)
const isCurrentTaxonInCombination = computed(
  () =>
    !!Object.entries(combination.value).find(
      ([_, protonym]) => protonym?.id === taxon.value.id
    )
)
const isBotanyCode = computed(
  () =>
    store.getters[GetterNames.GetTaxon].nomenclatural_code ===
    NOMENCLATURE_CODE_BOTANY
)
const nomenclatureRanks = computed(() =>
  isBotanyCode.value ? combinationIcnType : subsequentCombinationType
)
const isGenusGroup = computed(() =>
  Object.keys(nomenclatureRanks.value.genusGroup).includes(taxon.value.rank)
)
const combinationRanks = computed(() =>
  isGenusGroup.value
    ? { genusGroup: nomenclatureRanks.value.genusGroup }
    : nomenclatureRanks.value
)

const saveCombination = () => {
  const combObj = Object.assign(
    {},
    {
      id: currentCombination.value.id,
      verbatim_name: currentCombination.value.verbatim_name,
      ...citationData
    },
    ...removeOldRelationships(combination.value),
    ...makeCombinationParams()
  )

  store
    .dispatch(ActionNames.CreateCombination, combObj)
    .then(({ body }) => {
      combination.value = {}
      currentCombination.value = {}
      setCitationData()
      processQueueCombination(body.id)
    })
    .catch(() => {})
}

const removeOldRelationships = (protonyms) => {
  const removeRanks = []
  const oldProtonyms = currentCombination.value.protonyms

  for (const rank in oldProtonyms) {
    const oldTaxon = oldProtonyms[rank]
    const newTaxon = protonyms[rank]

    if (oldTaxon && !newTaxon) {
      removeRanks.push({
        [`${rank}_taxon_name_relationship_attributes`]: {
          id: oldTaxon.taxon_name_relationship_id,
          _destroy: true
        }
      })
    }
  }

  return removeRanks
}

const makeCombinationParams = () =>
  Object.entries(combination.value).map(([rank, taxon]) => ({
    [`${rank}_id`]: taxon?.id || null
  }))

const newCombination = () => {
  combination.value = {}
  currentCombination.value = {}
  queueClassification.value = []
  setCitationData()
}

const loadCombination = (data) => {
  currentCombination.value = { ...data }
  combination.value = data.protonyms
  setCitationData(data)
}

const removeCombination = (data) => {
  if (data.id === currentCombination.value.id) {
    currentCombination.value = {}
    combination.value = {}
    setCitationData()
  }

  store.dispatch(ActionNames.RemoveCombination, data.id)
}

// ======================================
// Citation
// ======================================

const citationData = reactive({
  origin_citation_attributes: makeCitationObject(COMBINATION),
  verbatim_author: undefined,
  year_of_publication: undefined,
  roles_attributes: []
})

const setCitationData = (combination = {}) => {
  citationData.verbatim_author = combination.verbatim_author
  citationData.year_of_publication = combination.year_of_publication
  citationData.roles_attributes = combination.taxon_name_author_roles || []
  citationData.origin_citation_attributes = combination.origin_citation
    ? {
        id: combination.origin_citation.id,
        source_id: combination.origin_citation.source.id,
        pages: combination.origin_citation.pages,
        global_id: combination.origin_citation.global_id
      }
    : makeCitationObject(COMBINATION)
}

// ======================================
// Classifications
// ======================================

const classifications = ref([])
const queueClassification = ref([])

const addClassification = (type) => {
  queueClassification.value.push(type)
}

const removeClassification = (item) => {
  TaxonNameClassification.destroy(item.id).then((_) => {
    removeFromArray(classifications.value, item)
  })
}

const processQueueCombination = (combinationId) => {
  if (!queueClassification.value.length) {
    return
  }

  const promise = queueClassification.value.map(({ type }) =>
    TaxonNameClassification.create({
      taxon_name_classification: {
        taxon_name_id: combinationId,
        type
      }
    }).then(({ body }) => {
      addToArray(classifications.value, body)
    })
  )

  Promise.allSettled(promise).then((_) => {
    queueClassification.value = []
  })
}

watch(
  queueClassification,
  (_) => {
    if (currentCombinationId.value) {
      processQueueCombination(currentCombinationId.value)
    }
  },
  { deep: true }
)

watch(currentCombinationId, async (newId) => {
  classifications.value = newId
    ? (await TaxonNameClassification.where({ taxon_name_id: newId })).body
    : []

  queueClassification.value = []
})
</script>