Source

components/shared/geocoding/LocateAddressLatLng.vue

<template>
  <div class="locate-address-latlng">
    <b-alert
      v-model="hasErrors"
      class="mt-2 mx-auto"
      dismissible
      variant="danger"
      fade
      @dismissed="resetErrors"
    >
      {{ errorMessage }}
    </b-alert>

    <form-builder
      v-if="state === 'search'"
      v-model="formData"
      :sections="sections"
      has-valid-btn
      class="mt-2 form-builder-wrapper"
      theme="theme-simpliciti"
      @update-column-value="setColumnValue"
      @valid="validForm"
      @invalid="setInvalidForm"
    />

    <LocateAddressResults
      v-if="state === 'results'"
      :items="results"
      :selected-item="selectedItem"
      @select="select"
      @preview="centerOnMarker"
      @goBack="resetForm"
    />
  </div>
</template>

<script>
import FormBuilder from '../Builders/FormBuilder'
import L from 'leaflet'
import LocateAddressResults from '@c/shared/geocoding/LocateAddressResults.vue'
import {
  normalizeApiAddressResponse,
  getFormattedAddressAlt,
} from '@/utils/address.js'
import geocodingMixin from './geocoding-mixin.js'
import { useMapContextMenu } from '@/components/shared/SimplicitiMap/MapContextMenu.vue'
import { watchEffect } from 'vue'

const { menuContextSelectedOption, menuContextLatLng } = useMapContextMenu()

/**
 * @namespace components
 * @category components
 * @subcategory shared/geocoding
 * @module LocateAddressLatLng
 */
export default {
  name: 'LocateAddressLatLng',
  components: {
    LocateAddressResults,
    FormBuilder,
  },
  mixins: [geocodingMixin],
  data() {
    return {
      formData: {
        latitude: '',
        longitude: '',
      },
      hasErrors: false,
      state: 'search',
      response: null,
      errorMessage: '',
      selectedItem: null,
      sections: [
        {
          name: 'coordinates_section',
          header: false,
          collapsable: false,
          rowsClass: 'mb-3',
          rows: [
            {
              id: 'latitude_row',
              columns: [
                {
                  label: 'latitude',
                  type: 'text',
                  title: 'latitude',
                  value: '',
                  validation: {
                    message: '',
                    state: null,
                    required: true,
                    hint: 'Ce champ est requis',
                  },
                },
              ],
            },
            {
              id: 'longitude_row',
              columns: [
                {
                  label: 'longitude',
                  type: 'text',
                  title: 'longitude',
                  value: '',
                  validation: {
                    message: '',
                    state: null,
                    required: true,
                    hint: 'Ce champ est requis',
                  },
                },
              ],
            },
          ],
        },
      ],
    }
  },
  computed: {
    results() {
      return this.response.map((item) => {
        let formatted = getFormattedAddressAlt(
          normalizeApiAddressResponse(this.response[0])
        )
        return {
          value: formatted,
          item: {
            ...item,
            formatted,
          },
        }
      })
    },
  },
  watch: {
    async selectedItem() {
      this.drawMarkerAndZoom()
    },
  },
  async mounted() {
    this.bindFormAutofillOnMapClick()

    watchEffect(
      this.reactToMapMenuContextNearbyVehiclesOptionAndPeformOperation
    )
  },
  destroyed() {
    this.unbindFormAutofillOnMapClick()
  },
  methods: {
    /**
     * If the user clicks the nearby vehicle options in the map menu context, the reverse operation will be performed automatically.
     */
    reactToMapMenuContextNearbyVehiclesOptionAndPeformOperation() {
      if (
        menuContextSelectedOption.value === 'nearbyVehicles' &&
        this.state === 'search'
      ) {
        this.validateFormUsingMapMenuContextLatLng()
      }
    },
    async drawMarkerAndZoom() {
      if (this.selectedItem) {
        await this.addLocateAddressMarker(
          this.selectedItem.lat || this.selectedItem.latitude,
          this.selectedItem.lng || this.selectedItem.longitude,
          this.selectedItem.formatted
        )
        this.centerOnMarker()
      }
    },
    unbindFormAutofillOnMapClick() {
      try {
        this.simplicitiMap &&
          this.simplicitiMap
            .getLeafletMapWrapper()
            .map.off('click', this.mapClickHandler)
      } catch (err) {
        console.warn('Fail to unbind form auto fill on map click', err)
      }
    },
    bindFormAutofillOnMapClick() {
      let simplicitiMap = (this.simplicitiMap = this.$map.getSimplicitiMapVM())
      simplicitiMap &&
        simplicitiMap.waitForMapRef(() => {
          simplicitiMap.getLeafletMapWrapper().map.on(
            'click',
            (this.mapClickHandler = async (e) => {
              //Skip if form is not visible in the screen
              if (this.$el.style.display === 'none') {
                return
              }

              this.formData.latitude = e.latlng.lat
              this.formData.longitude = e.latlng.lng
            })
          )
        })
    },
    validateFormUsingMapMenuContextLatLng() {
      if (
        menuContextLatLng.value.lat !== null &&
        menuContextLatLng.value.lng !== null
      ) {
        this.formData.latitude = menuContextLatLng.value.lat
        this.formData.longitude = menuContextLatLng.value.lng
        this.validForm()
      }
    },
    async validForm() {
      this.hasErrors = false
      this.errorMessage = ''
      const coords = {
        latitude: parseFloat(this.formData.latitude),
        longitude: parseFloat(this.formData.longitude),
      }

      await this.$geocoding.reverseGeocoding(coords).then(async (response) => {
        if (response.latitude && response.longitude) {
          this.response = [response]
          this.selectedItem = this.results[0].item
          this.state = 'results'
        } else {
          this.hasErrors = true
          this.errorMessage = this.$t('geocoding.not_address_found')
        }
      })
    },
    select(event) {
      this.selectedItem = event
      this.centerOnMarker(this.selectedItem)
    },
    resetForm() {
      this.state = 'search'
      this.response = null
      this.$map.getLeafletWrapperVM().layerGroups.locateAddressMarker.remove()
    },
    centerOnMarker() {
      if (this.selectedItem) {
        this.$map
          .getLeafletInstance()
          .flyTo([this.selectedItem.latitude, this.selectedItem.longitude], 14)
      }
    },
    setColumnValue(options) {
      let column = null
      this.sections.find((section) => {
        !!section.rows.find((row) => {
          column = row.columns.find((column) => column.label === options.label)
          return !!column
        })
      })

      if (column && event.type !== 'blur') {
        column.value = options.event
      }

      if (column.validation) {
        if (!column.value && column.validation.required) {
          column.validation.state = false
          column.validation.message = column.validation.hint
        } else {
          column.validation.state = true
        }
      }
    },
    setInvalidForm(event) {
      let column
      this.sections.find((section) => {
        section.rows.find((row) => {
          column = row.columns.find((column) => column.label === event.label)
          if (column !== undefined && column.validation) {
            column.validation.state = false
            column.validation.message = column.validation.hint
          }
        })
      })
    },
    resetErrors() {
      this.errorMessage = ''
      this.hasErrors = false
    },
  },
}
</script>

<style lang="scss" scoped>
.form-builder-wrapper {
  font-size: 12px;
}
.map-popup-wrapper {
  border-radius: 5px;
  border: 1px var(--color-denim) solid;
}
</style>