Skip to content
Snippets Groups Projects
AdvancedSearch.vue 5.12 KiB
<template>
  <div class="advanced-search">
    <div class="active-filters">
      <div
        v-for="filter in allFilters"
        :key="filter.join('-')"
        class="filter-chip"
      >
        <span>{{ filter[1] }}</span>
        <button
          class="remove-button"
          type="button"
          @click="removeFilter(filter)"
        >
          &#10006;
        </button>
      </div>
    </div>

    <button
      class="advanced-search-button"
      type="button"
      @click="displayAdvancedSearch"
    >
      ADVANCED SEARCH
    </button>
  </div>

  <AppDialog
    v-model="advancedSearchVisible"
    class="advanced-search-dialog"
    @close="updateFilters"
  >
    <header>
      <h2>ADVANCED SEARCH</h2>
    </header>

    <div class="options-group">
      <h3>ORGANISM</h3>
      <div class="checkbox-list">
        <div
          v-for="spe in speciesList"
          :key="spe"
          class="checkbox"
        >
          <input
            :id="spe.replace(' ', '-') + '-checkbox'"
            v-model="filters.species"
            :value="spe"
            type="checkbox"
          >
          <label :for="spe.replace(' ', '-') + '-checkbox'">
            {{ spe }}
          </label>
        </div>
      </div>
    </div>

    <div class="options-group">
      <h3>SOURCES</h3>
      <div class="checkbox-list">
        <div
          v-for="source in sourcesList"
          :key="source"
          class="checkbox"
        >
          <input
            :id="source + '-checkbox'"
            v-model="filters.sources"
            :value="source"
            type="checkbox"
          >
          <label :for="source + '-checkbox'">
            {{ source }}
          </label>
        </div>
      </div>
    </div>

    <footer>
      <button
        class="search-button"
        type="button"
        @click="closeAdvancedSearch"
      >
        SEARCH
      </button>
    </footer>
  </AppDialog>
</template>

<script setup lang="ts">
import { type Ref, computed, onMounted, ref } from 'vue'
import AppDialog from './AppDialog.vue'
import { useStore } from '../store'
import { type AdvancedFilters, type FilterChip } from '../types'

onMounted(() => {
  if (store.antibodiesSources.length === 0) {
    store.getAntibodiesSources()
    store.getAntibodiesSpecies()
  }
})

const emit = defineEmits<{
  filtersUpdate: [filters: AdvancedFilters]
}>()

const store = useStore()
const advancedSearchVisible: Ref<boolean> = ref(false)
const filters: Ref<AdvancedFilters> = ref({
  species: [],
  sources: []
})
const sourcesList = computed(() => store.antibodiesSources)
const speciesList = computed(() => store.antibodiesSpecies)
const allFilters = computed(() => {
  const filterChips: FilterChip[] = []

  filters.value.species.forEach(value => {
    filterChips.push(['species', value])
  })

  filters.value.sources.forEach(value => {
    filterChips.push(['sources', value])
  })

  return filterChips
})

/**
 * Open the dialog box.
 */
function displayAdvancedSearch (): void {
  advancedSearchVisible.value = true
}

/**
 * Close the dialog box.
 */
function closeAdvancedSearch (): void {
  advancedSearchVisible.value = false
}

/**
 * On dialog close, emit the new filters to
 * the parent component.
 */
function updateFilters (): void {
  emit('filtersUpdate', filters.value)
}

/**
 * Remove one of the filter chips and re-emit
 * the filters immediately.
 * @param filterChip - The data of the filter chip to remove
 */
function removeFilter (filterChip: FilterChip): void {
  const valueIndex: number = filters.value[filterChip[0]].indexOf(filterChip[1])
  filters.value[filterChip[0]].splice(valueIndex, 1)
  emit('filtersUpdate', filters.value)
}
</script>

<style scoped>
.active-filters {
  display: flex;
  flex-flow: row wrap;
  gap: calc((var(--spacing)) / 2);
  padding-top: calc((var(--spacing)) / 2);
}

.filter-chip {
  border: 1px solid var(--primary);
  border-radius: 16px;
  color: var(--primary);
  display: flex;
  align-items: center;
  padding-left: 8px;
}

.filter-chip .remove-button {
  color: inherit;
  padding: 6px;
  background: none;
  border: none;
  cursor: pointer;
}

.advanced-search {
  display: flex;
}

.advanced-search-button {
  background: none;
  border: none;
  color: var(--white);
  cursor: pointer;
  margin-left: auto;
  padding-top: calc(var(--spacing) / 2);
}

.advanced-search-button:hover {
  color: var(--primary);
}

.advanced-search-dialog h2 {
  margin-bottom: calc(var(--spacing) * 2);
  margin-top: 0;
}

.options-group .checkbox-list {
  display: flex;
  flex-flow: row wrap;
  gap: var(--spacing);
  padding-left: var(--spacing);
  max-width: 800px;
}

.checkbox {
  display: flex;
  gap: calc(var(--spacing) / 2);
}

.checkbox:hover {
  color: var(--primary);
}

.checkbox input,
.checkbox label {
  cursor: pointer;
}

.checkbox input:checked+label {
  color: var(--primary);
}

.advanced-search-dialog footer {
  align-items: center;
  display: flex;
  justify-content: flex-end;
  margin-top: var(--spacing);
}

.search-button {
  background-color: var(--primary);
  border-radius: var(--radius);
  border: none;
  color: var(--white);
  cursor: pointer;
  font-weight: 700;
  height: 40px;
  padding: 7px var(--spacing);
}
</style>