Source

components/shared/SearchModule/SMSuggestion.vue

<template>
  <div
    v-show="visible"
    ref="root"
    class="search_module_suggestions_search popOver"
    :class="{ with_back_arrow: backArrow }"
  >
    <SearchModuleDatePicker
      ref="datePicker"
      :show-input="false"
      @onDateSelection="onDateSelection"
    />
    <slot name="input-left" />
    <input
      ref="input"
      v-model="query"
      type="text"
      :placeholder="$t('searchModule.placeholder.filter')"
      @click="onInputClick"
      @keyup="queryKeyUp"
    />

    <div v-show="autocomplete && query" ref="optionsList" class="options">
      <div class="list-group">
        <div
          v-for="vehicle in suggestedVehicles"
          :key="'vehicle_' + vehicle.id"
          class="list-group-item list-group-item-action"
          :class="{ selected: selected == vehicle.id }"
          @click="selectItem($event, vehicle.id, 'vehicle')"
        >
          <img class="item_icon" src="./assets/truck.svg" />
          <span
            v-if="false"
            class="iconify item_icon"
            data-icon="mdi:dump-truck"
            data-inline="false"
            data-width="20"
            data-height="20"
          />
          &nbsp;{{ vehicle.name }}
        </div>

        <div
          v-for="driver in suggestedDrivers"
          :key="'driver_' + driver.id"
          class="list-group-item list-group-item-action"
          :class="{ selected: selected == driver.id }"
          @click="selectItem($event, driver.id, 'driver')"
        >
          <span
            class="iconify item_icon"
            data-icon="mdi:account-hard-hat"
            data-inline="false"
            data-width="20"
            data-height="20"
          />
          &nbsp;{{ driver.name }}
        </div>

        <div
          v-for="circuit in suggestedCircuits"
          :key="circuit.id"
          class="list-group-item list-group-item-action"
          :class="{ selected: selected == circuit.id }"
          @click="selectItem($event, circuit.id, 'circuit')"
        >
          <span
            class="iconify item_icon"
            data-icon="mdi:road-variant"
            data-inline="false"
            data-width="20"
            data-height="20"
          />
          &nbsp;{{ circuit.name }}
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import SearchModuleDatePicker from './SMDatePicker.vue'
import { mapGetters } from 'vuex'

/**
 * Floating window to show results (Floating search module feature)
 */
export default {
  components: {
    SearchModuleDatePicker,
  },
  inject: {
    isSearchModuleDatePickerEnabled: {
      default: () => () => true,
    },
  },
  props: {
    backArrow: {
      type: Boolean,
      default: false,
    },
    visible: {
      type: Boolean,
      default: true,
    },
    autocomplete: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      query: '',
      selected: null,
    }
  },

  computed: {
    ...mapGetters({
      vehicles: 'search_module/getVehicles',
      drivers: 'search_module/getDrivers',
      circuits: 'search_module/getCircuits',
    }),
    suggestedVehicles() {
      let ids = this.vehicles.map((v) => v.id)
      return ids
        .filter((id, index) => {
          if (!(ids.indexOf(id) == index)) {
            this.$log.debug(
              'Skiping duplicate vehicle',
              id,
              this.vehicles.find((v) => v.id == id)
            )
          }
          return ids.indexOf(id) == index
        })
        .map((id) => this.vehicles.find((v) => v.id == id))
        .filter((item) =>
          (item.name || '').toLowerCase().includes(this.query.toLowerCase())
        )
    },
    suggestedDrivers() {
      return this.drivers.filter((item) =>
        (item.name || '').toLowerCase().includes(this.query.toLowerCase())
      )
    },
    suggestedCircuits() {
      return this.circuits.filter((item) =>
        (item.name || '').toLowerCase().includes(this.query.toLowerCase())
      )
    },
  },
  watch: {
    query() {
      this.$emit('onQuery', this.query)
    },
  },
  mounted() {
    this.bindEscape()
  },
  methods: {
    onDateSelection(ranges) {
      this.$store.dispatch('search_module/clearDateSelection')
      ranges.forEach((range) => {
        this.selectItem(null, range, 'date_range')
      })
    },
    queryKeyUp(e) {
      if (!this.isSearchModuleDatePickerEnabled()) {
        return
      }

      //Open automatically if the user writes '/'
      if (this.query.indexOf('/') !== -1) {
        this.$refs.datePicker.open(this.query.split('/').join('-'))
        this.query = ''
        return
      }
      if (e.key === 'Enter' && this.query.indexOf('-') !== -1) {
        if (e.ctrlKey) {
          //Select a single date period without datepicker if the date contains '-'
          this.$log.debug('will set date using', this.query)
          let range = this.$refs.datePicker.setFromString(this.query)
          if (range.length === 2) {
            this.onDateSelection(range)
          } else {
            return
          }
        } else {
          //Open manually if the user writes the date with '-' and presses enter
          this.$log.debug('will open date picker', this.query)
          this.$refs.datePicker.open(this.query)
        }
        this.query = ''
      }
    },
    onInputClick(e) {
      this.$emit('inputClick')
      e.stopPropagation()
    },
    bindEscape() {
      if (!this.isSearchModuleDatePickerEnabled()) {
        return
      }

      setTimeout(() => {
        this.$refs.root &&
          this.$refs.root.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') {
              if (this.$refs.datePicker.visible) {
                this.$refs.datePicker.cancel()
              } else {
                this.$emit('escape')
              }

              e.preventDefault()
              e.stopPropagation()
            }
          })
      }, 1000)
    },
    focusInput() {
      this.$nextTick(() => {
        this.$refs.input.focus()
      })
    },
    selectItem(e, value, type) {
      this.$store.dispatch('search_module/selectItem', { value, type })
      e && e.stopPropagation()
      e && e.preventDefault()
    },
    clearQuery() {
      this.query = ''
    },
  },
}
</script>
<style lang="scss" scoped>
.search_module_suggestions_search input {
  border: none;
  padding-left: 8px;
  background-color: transparent;
  box-shadow: none;
  outline: none;
}
.search_module_suggestions_search {
  display: flex;
  flex-direction: column;
}
.search_module_suggestions_search.with_back_arrow {
  flex-direction: row;
}
.popOver {
  padding: 10px;
  width: auto;
  top: 46px;
}
.popOver input {
  width: 100%;
  //margin-top: 5px;
  font-size: 16px;
}
.options {
  max-height: 150px;
  overflow-y: scroll;
  margin-top: 5px;
}
.options ul {
  list-style-type: none;
  text-align: left;
  padding-left: 0;
}
.options ul li {
  border-bottom: 1px solid lightgray;
  padding: 10px;
  cursor: pointer;
  background: #f1f1f1;
}
.options ul li:first-child {
  border-top: 2px solid #d6d6d6;
}

.options ul li:not(.selected):hover {
  background: #8c8c8c;
  color: #fff;
}
.options ul li.selected {
  background: #8c8c8c;
  color: #fff;
  font-weight: 600;
}
.list-group-item {
  cursor: pointer;
}
.item_icon {
  color: var(--color-dark-blue);
  max-width: 20px;
}
.list-group-item {
  border: 0px;
}
</style>