Source

components/shared/SimplicitiMap/PositionMarkerPopup.vue

<template lang="pug">
.position_marker_popup
    MapPopup(:title="$t('popup.realtime.vehicle.title')" :sections="popupSections")
      
      template(v-slot:datetime="")
        span {{datetime}}
      template(v-slot:address_label="")
        span {{$t('location.history_tab.position.address')}}
      template(v-slot:address="")
        span {{address}}
      template(v-slot:speed="")
        span {{speedKm}}

      template(v-slot:vehicleImmatriculation="")
        span {{vehicleRegistrationPlate }}
      template(v-slot:driverName="")
        span {{driverName||''}}

      template(v-slot:gear_area="" v-if="showPositionAnalysisButton&&hasFeatureRight('location_position_analysis')")
        .col-12
          .row.mb-1
            .offset-10
            .col-2
              img(src="./assets/gear-icon.svg" 
              style="cursor:pointer;"
              @click="openPositionAnalysisPopup")
      
      template(v-slot:sensor="props")
        span(v-show="typeof props.value === 'boolean'" :style="props.value?'color:#70BD95':'color:#FF4545'") {{props.value?$t('common.enabled'):$t('common.disabled')}}
        span(v-show="typeof props.value !== 'boolean'") {{props.value}}
    
</template>
<script>
import moment from 'moment'
import CollapsibleSection from './CollapsibleSection.vue'
import Vue from 'vue'
import MapPopup from '@c/shared/MapPopup/MapPopup.vue'
import { mapGetters } from 'vuex'
import sensorsService from '@/services/sensors-service.js'
import vehicleService from '@/services/vehicle-service.js'
import { isValidPositionId } from '@/services/position-service.js'
//import APIUrls from '@/config/simpliciti-apis'
import { APIV2RequestDatetimeFormat } from '@/config/simpliciti-apis.js'
import { getVehicleHistoryPositionDetailsFromDateLegacy } from '@/services/history-service.js'

export default {
  name: 'PositionMarkerPopup',
  components: {
    CollapsibleSection,
    MapPopup,
  },
  mixins: [Vue.$mixins.userRightsMixin],
  inject: {
    /**
     * Hides the gear icon (i.g Diagnostics module)
     */
    showPositionAnalysisButton: {
      default: true,
    },
    skipPositionMarkerPopupDetailsFetch: {
      default: false,
    },
  },
  props: {
    geometry: {
      type: Object,
      default: () => ({}),
    },
  },
  data() {
    return {
      torSectionVisible: false,
      canSectionVisible: false,
      details: {},
      popupSections: [
        {
          title: this.$t('location_module.popup.position_marker.title'),
          rows: [
            {
              singleColumn: {
                name: 'datetime',
                label: this.$t('common.date'),
              },
            },
            {
              singleColumn: {
                name: 'address',
              },
            },
            {
              singleColumn: {
                name: 'speed',
                label: 'common.Vitesse',
              },
            },
          ],
          collapsed: true,
        },
        {
          title: this.$t('popup.realtime.vehicle.vehicle_section_title'),
          rows: [
            {
              twoColumns: [
                {
                  name: 'vehicleImmatriculation',
                  label: this.$t('common.matriculation'),
                },
                {
                  name: 'driverName',
                  label: this.$t('common.Conducteur'),
                },
              ],
            },
          ],
        },
        {
          name: 'gear_area',
          collapsable: false,
          header: false,
          visible: this.showPositionAnalysisButton,
        },
        {
          title: 'common.capteurs',
          name: 'sensors',
          visible: false,
          rows: [],
        },
        {
          title: 'CAN',
          name: 'can',
          visible: false,
          rows: [],
        },
      ],
    }
  },
  computed: {
    ...mapGetters({
      //Attention: Only for Location module (This component will be also used in Diagnostics module)
      selectedItem: 'location_module/selectedItem',
    }),
    driverName() {
      return this.selectedItem.driverName || this.getVal('driverName')
    },
    canVRM() {
      return this.getVal('can_vrm')
    },
    canInfos() {
      const infos = [
        [
          'sensorCanCruise',
          'can_cruise' /*APIV2*/,
          'sensorCanCruise' /*normalized*/,
        ],

        [
          'sensorCanRPM',
          'vrm',
          'can_vrm' /*APIV2*/,
          'sensorCanRPM' /*normalized*/,
        ],

        ['km_fms', 'can_km_fms' /*APIV2*/], //unused (location realtime only?)

        [
          'sensorCanFuelPerc',
          'can_fuel' /*APIV2*/,
          'sensorCanFuelPerc' /*normalized*/,
        ],

        ['sensorCanFuelLiters', 'sensorCanFuelLiters' /*normalized*/],
        ['niveau_carburant', 'can_niveau_carburant' /*APIV2*/],

        [
          'conso',
          'can_conso' /*APIV2*/,
          'sensorCanConsumptionLiters' /*normalized*/,
        ],

        [
          'accelerateur',
          'can_accelerateur' /*APIV2*/,
          'sensorCanThrottle' /*normalized*/,
        ],

        [
          'sensorCanBrakePedal',
          'can_freinage' /*APIV2*/,
          'sensorCanBrakePedal' /*normalized*/,
        ],

        [
          'batterie',
          'can_batterie' /*APIV2*/,
          'sensorCanBatteryPerc' /*normalized*/,
        ],

        ['vehicleDistance', 'vehicleDistance' /*normalized*/],

        ['canServiceDistanceMeters' /*normalized*/],

        ['embrayage', 'can_embrayage'], //unused (location realtime only?)

        ['speHarshBraking' /*normalized*/],
        ['speHarshAcceleration' /*normalized*/],
        ['speHarshCornering' /*normalized*/],
        ['speExcessiveSpeed' /*normalized*/],
      ]
      return infos
        .map((propNames) => {
          propNames = propNames instanceof Array ? propNames : [propNames]
          let code = propNames[0]

          let value = null

          propNames.forEach((propName) => {
            if (value === null && !!this.getVal(propName)) {
              value = this.getVal(propName)
            }
          })

          return {
            code,
            rawValue: value,
            name: this.$te(sensorsService.getSensorI18nCode(code))
              ? this.$t(sensorsService.getSensorI18nCode(code))
              : this.$t(`can_sensors.${code}`),
            exists: !!value && ![null, 'null', undefined].includes(value),
            value: sensorsService.formatSensorValue(code, value),
          }
        })
        .filter((can) => can.exists)
    },
    anaInfos() {
      if (this.getVal('sensorAna')) {
        return this.getVal('sensorAna') //Already normalized
      }

      //From APIV2 response, require normalization
      if (this.getVal('ana')) {
        return this.getVal('ana', [])
          .filter((ana) => {
            return (
              (ana.etat !== undefined && ana.etat !== null) || ana.val !== null
            )
          })
          .map((ana) => {
            return {
              name: (ana.nom && `${ana.nom} (ANA)`) || `ANA${ana.num}`,
              //enabled: ana.val.toString() !== '0',
              value: ana.val.toString(),
            }
          })
      }

      return []
    },
    torInfos() {
      //Already normalized
      if (this.getVal('sensorTor')) {
        return this.getVal('sensorTor', [])
      } else {
        //From APIV2 response, require normalization
        return this.getVal('tor', [])
          .filter((tor) => {
            return (
              (tor.etat !== undefined && tor.etat !== null) || tor.val !== null
            )
          })
          .map((tor) => {
            let enabled =
              tor.etat === undefined ? tor.val : tor.etat.toString() === '1'
            return {
              name: tor.nom || `TOR${tor.num}`,
              enabled,
            }
          })
      }
    },
    datetime() {
      return this.$date.formatDatetimeWithSeconds(
        this.getVal('dateheure') || this.getVal('datetime')
      )
    },
    speedKm() {
      return (this.getVal('vitesse', 0) || this.getVal('speed', 0)) + ' Km/h'
    },
    vehicleRegistrationPlate() {
      return (
        this.getVal('vehicule_immatriculation') ||
        this.getVal('vehicleRegistrationPlate') ||
        this.selectedItem.vehicleMatriculation ||
        '...'
      )
    },
    address() {
      if (this.getVal('addressFormatted')) {
        return this.getVal('addressFormatted')
      }
      let r = this.details?.streetNumber || ''
      r = (r ? r + ` ` : r + ``) + (this.details?.street || '')
      r = (r ? r + `, ` : r + ``) + (this.details?.zipCode || '')
      r = (r ? r + `, ` : r + ``) + (this.details?.city || '')
      return r || 'Non renseigné'
    },
    vehicleId() {
      let vehicleId = this.getVal('vehicleId')

      //This only applies if Location module
      if (!vehicleId) {
        vehicleId =
          this.$store.getters['location_module/selectedItem'].vehicleId ||
          this.$store.getters['location_module/selectedItem'].id
      }
      return vehicleId
    },
  },
  watch: {
    canInfos() {
      this.updateCanSection()
    },
    torInfos() {
      this.updateTorAnaSection()
    },
    anaInfos() {
      this.updateTorAnaSection()
    },
  },
  /**
   * @todo Remove fixtures feature without braking unit test
   */
  async mounted() {
    //Diagnostic module: Position already contains sensor information (Unused?)
    if (this.skipPositionMarkerPopupDetailsFetch) {
      return
    }

    this.updateTorAnaSection()
    this.updateCanSection()

    if (this.vehicleId) {
      await this.updatePositionDetails()

      //Location (Segment analysis): Position analysis can be executed when we select 2nd position.
      if (this.$store.getters['location_module/positionAnalysisCanCompare']) {
        this.launchLocationModulePositionAnalysis()
      }
    }
  },
  methods: {
    async getPositionDetailsFromAPIV3(positionId) {
      return await vehicleService.getVehiclePosition(positionId)
    },
    /*async getPositionDetailsFromAPIV2(vehicleId, datetime) {
      return getVehicleHistoryPositionDetailsFromDateLegacy(vehicleId, datetime)


      console.log('getPositionDetailsFromAPIV2', {
        vehicleId,
        datetime,
      })
      const res =
        (await this.$api.loadFixtures(
          `${APIUrls.APIV2_POSITIONS_GPS.substring(1)}/${vehicleId}_${datetime
            .split(' ')
            .join('__')
            .split(':')
            .join('_')}`
        )) ||
        (
          await this.$api.v2.get(
            `${APIUrls.APIV2_POSITIONS_GPS}?vehicule_id=${vehicleId}&dateheure_debut=${datetime}&dateheure_fin=${datetime}&groups=infos_complementaires,capteurs,capteurs_details,can,localisation_adresse`
          )
        ).data
      return res && res[0] && res[0].positions && res[0].positions.length
        ? {
            ...res[0],
            ...res[0].positions[0],
            positions: undefined,
          }
        : null
    },
    */
    /**
     *  Fetch single position details using APIV3.
     *
     *  @todo Remove commented code
     *
     *  02/02/2023  @JAR  If Diagnostics module - Replay - Vehicle marker, id is located at 'positionId'. 'id' is reserved for matching the marker geometry for updates and contains a fixed value 'vehicle'.
     */
    async updatePositionDetails() {
      let positionId = this.getVal('positionId') || this.getVal('id')
      let detailsFromAPI = null
      //Diagnostic modules already includes a valid identifier
      //if (isValidPositionId(positionId)) {
      //Sometimes we generate a fake id starting with id_
      detailsFromAPI = await this.getPositionDetailsFromAPIV3(positionId)
      //}

      /*  else {
        console.log('passe toujour ici')
        const datetime = moment(
          this.getVal('dateheure') || this.getVal('datetime')
        )
        detailsFromAPI = await this.getPositionDetailsFromAPIV2(
          this.vehicleId,
          datetime
        )
      } */

      if (detailsFromAPI) {
        this.details = detailsFromAPI
        this.updateTorAnaSection()
        this.updateCanSection()
      }
    },
    launchLocationModulePositionAnalysis() {
      let promise = this.$store.dispatch('location_module/analyzeSegment', {
        vehicleId: this.vehicleId,
        positions: [
          {
            ...((this.geometry && this.geometry.properties) || {}),
            ...(this.details || {}),
          },
        ],
      })
      this.$loader.show(promise)
    },
    /**
     * Will update popupSections with Tor/Ana information from API
     * @function updateTorAnaSection
     */
    updateTorAnaSection() {
      console.log('position-marker-popup: Update sensors section')
      let section = this.popupSections.find((s) => s.name === 'sensors')
      const mapTorAnaHandlerToDynamicSectionRowColumn = (sensorItem) => {
        return {
          singleColumn: {
            name: 'sensor',
            label: sensorItem.name,
            value:
              sensorItem.enabled !== undefined
                ? sensorItem.enabled
                : sensorItem.value +
                  (sensorItem.unit ? ` ${sensorItem.unit}` : ''),
            inline: true,
          },
        }
      }
      section.rows = this.torInfos
        .map(mapTorAnaHandlerToDynamicSectionRowColumn)
        .concat(this.anaInfos.map(mapTorAnaHandlerToDynamicSectionRowColumn))
      section.visible = section.rows.length > 0
    },
    /**
     * Will update Can section with Can information from API
     * @function updateCanSection
     */
    updateCanSection() {
      let section = this.popupSections.find((s) => s.name === 'can')
      section.rows = this.canInfos
        .filter((canItem) => !!canItem.value)
        .map((canItem) => {
          return {
            singleColumn: {
              label: canItem.name,
              value: canItem.value,
              inline: true,
            },
          }
        })
      section.visible = section.rows.length > 0
    },
    openPositionAnalysisPopup() {
      //Case: Set positions to compare (from, null)
      this.$store.dispatch('location_module/positionAnalysisSelectPositions', [
        {
          ...((this.geometry && this.geometry.properties) || {}),
          ...(this.details || {}),
        },
        null,
      ])

      //Case: Popup is already open
      if (this.$store.getters['location_module/positionAnalysisPopupOpened']) {
        return
      }

      this.$store.dispatch('location_module/togglePositionAnalysisPopup', true)

      /*
      @deprecated: Instanciates the analysis popup on top of the map. Replaced by: The analysis section is shown directly in a new tab (vehicle details)
      const PositionAnalysisPopupClass = Vue.extend(PositionAnalysisPopup);
      let instance = new PositionAnalysisPopupClass({
        parent: this.$parent.$parent,
        propsData: {},
      });
      instance.$mount();
      let simplicitiMapEl = document.querySelector(".simpliciti_map");
      let leafletMapEl = simplicitiMapEl.querySelector(".map");
      simplicitiMapEl.insertBefore(instance.$el, leafletMapEl);
      */
    },
    /**
     * Get value either from geometry properties or from the position details (positions_gps)
     * @private
     */
    getVal(name, defaultValue = '') {
      //Give priority to available data
      if (
        this.geometry &&
        this.geometry.properties &&
        this.geometry.properties[name] !== undefined
      ) {
        return this.geometry.properties[name]
      }

      //Fallback to new data fetched from API (position details)
      if (this.details && this.details[name] !== undefined) {
        return this.details[name]
      }

      return defaultValue
    },
  },
}
</script>
<style lang="scss" scoped></style>