Source

store/location_module/history.js

import historyApi from '@/api/historyApi.js'
import { linestringsToPolylines } from '@/mixins/map'
import moment from 'moment'
import Vue from 'vue'
import { getFormattedAddress } from '@/utils/address'
import { getNestedValue } from '@/utils/object.js'
import { formatTimeWithSeconds } from '@/utils/dates.js'
import { isUnitTest } from '@/utils/unit.js'
import { APIV2RequestDatetimeFormat } from '@/config/simpliciti-apis.js'
import { normalizePositionV2 } from '@/services/entities/position-entity.js'

const scope = {
  currentTripHistoryLoading: false,
}

/**
 *
 * @param {*} err
 * @param {*} dispatch
 * @returns {*}
 */
export async function dispatchAlertForAPIV2Error(err, dispatch) {
  if (err.message.includes(`Vous n'avez pas accès aux véhicules fournis`)) {
    return dispatch(
      'alert/addAlert',
      {
        type: 'warning',
        title: '403',
        text: "Vous n'avez pas accès aux véhicules fournis",
      },
      {
        root: true,
      }
    )
  }
  console.error(err)
}

export default {
  state: {
    currentTripHistory: [],
    currentChartTripHistory: [],
  },
  getters: {
    currentTripHistory(state) {
      return state.currentTripHistory || []
    },
  },
  mutations: {
    currentTripHistory(state, tripHistory = []) {
      state.currentTripHistory = tripHistory || []
    },
    currentChartTripHistory: (state, currentChartTripHistory = []) =>
      (state.currentChartTripHistory = currentChartTripHistory),
  },
  actions: {
    /**
     * Updates simpliciti_map store position markers data from
     * v2/historique/positions_capteurs
     *
     * @TODO Skip if positions already present
     */
    async updateMapPositionMarkers({ dispatch, rootGetters }, options = {}) {
      const { vehicleId, date } = options
      if (!vehicleId) {
        return
      }

      //TripHistoryTable.spec.js will fail due to unauthorized API call
      if (!isUnitTest()) {
        await dispatch(
          'map_options/syncSensorConfig',
          {
            vehicleId,
          },
          {
            root: true,
          }
        )
      }

      let shouldSkipPositionsFetch =
        isUnitTest() && options.fakePositions && options.fakeSensorConfig

      let response = shouldSkipPositionsFetch
        ? {}
        : await historyApi.getVehiclePositionsV3(vehicleId, date, options)

      if (response) {
        let positions = []
        ;(response?.positions || []).forEach((item) => {
          positions.push(normalizePositionV2(item))
        })

        //reComputeSensorsConfigActive: Otherwise, sensor config items remain grey out!
        let config = rootGetters['map_options/sensorsConfig']
        if (isUnitTest() && options.fakePositions && options.fakeSensorConfig) {
          positions = options.fakePositions
          config = options.fakeSensorConfig
        }
        ;(config?.sensors || []).forEach((sensor) => {
          sensor.active = positions.some(
            (position) =>
              position.code == sensor.code ||
              position.code_capteurs.includes(sensor.code)
          )
        })
        dispatch('map_options/setSensorsConfig', config, {
          root: true,
        })
        dispatch(
          'simpliciti_map/setDataset',
          {
            type: 'vehiclePositionMarkers',
            data: positions,
          },
          {
            root: true,
          }
        )
      }
    },
    /**
     * Used by Location - History details
     *
     * Commits normalized trip history from results
     * Commits polylines from results
     * @returns {Array} Non-normalized trip history (APIV2 troncons_details)
     */
    async getTripHistoryFromVehicleDate(
      { dispatch, commit, rootGetters },
      { id, date }
    ) {
      //@TODO: Refactor try catch into a reusable function trycatchAPIErrors(async()=>[1,2,3], [])

      try {
        scope.currentTripHistoryLoading = true
        let tripHistoryData =
          (await historyApi.getTripHistoryFromVehicleId(id, date)) || []
        //  Set trip history infos and linestring infos (polylines) separately
        let tripHistory = tripHistoryData && tripHistoryData[0]
        if (tripHistory) {
          let currentTripHistory = tripHistory.troncons.map(
            normalizeTripHistoryItem(id)
          )
          commit('currentTripHistory', currentTripHistory)
          scope.currentTripHistoryLoading = false

          let polylines = linestringsToPolylines(currentTripHistory, {
            //If selected item has tripHistoryPolyline.color, use that color to render the trip history
            ...((rootGetters['location_module/selectedItem'] || {})
              ?.tripHistoryPolyline?.color
              ? {
                  color: (rootGetters['location_module/selectedItem'] || {})
                    ?.tripHistoryPolyline?.color,
                }
              : {}),
          })

          dispatch(
            'simpliciti_map/setDataset',
            {
              type: 'singleTripHistoryPolylines',
              data: polylines,
            },
            {
              root: true,
            }
          )

          dispatch(
            'simpliciti_map/setDataset',
            {
              type: 'speedPolylines',
              data: currentTripHistory.reduce((acum, currTripHistory) => {
                let linestrings = (currTripHistory.speedLinestring || []).map(
                  (linestringColor) => {
                    return {
                      ...currTripHistory,
                      linestring: linestringColor.linestring,
                      color: linestringColor.couleur,
                    }
                  }
                )
                return acum.concat(linestringsToPolylines(linestrings))
              }, []),
            },
            {
              root: true,
            }
          )

          dispatch('simpliciti_map/setTripStepMarkers', polylines, {
            root: true,
          })
        } else {
          commit('currentTripHistory', [])

          dispatch(
            'simpliciti_map/setDataset',
            {
              type: 'singleTripHistoryPolylines',
              data: [],
            },
            {
              root: true,
            }
          )

          dispatch('simpliciti_map/setTripStepMarkers', [], {
            root: true,
          })
        }
        //  non-normalized api response
        return tripHistoryData
      } catch (err) {
        console.warn('ERR', err)
        dispatchAlertForAPIV2Error(err, dispatch)
        return []
      }
    },
    /**
     * Retrieves one vehicle trip history (normalized for a vertical chart)
     *
     * Updates the vehicle positions (To render trip positions markers)
     * Updates the vehicle trip history linestring (To render trip polylines)
     *
     * @param {int} id Vehicle ID
     * @param {string} date YYYY-MM-DD
     */
    async getChartTripHistoryFromVehicleId(
      { dispatch, commit, state },
      { id, date }
    ) {
      /*
      //Re-use data from store 
      if (
        !!state.currentChartTripHistory &&
        state.currentChartTripHistory.length > 0
      ) {
        return state.currentChartTripHistory;
      }*/

      //@TODO: Refactor try catch into a reusable function trycatchAPIErrors(async()=>[1,2,3], [])
      try {
        commit(
          'currentChartTripHistory',
          await historyApi.getChartTripHistoryFromVehicleId(
            id,
            date,
            await dispatch('getTripHistoryFromVehicleDate', { id, date })
          )
        )
        return state.currentChartTripHistory
      } catch (err) {
        console.warn('ERR', err)
        dispatchAlertForAPIV2Error(err, dispatch)
        return []
      }
    },
  },
}

function getTime(item, field) {
  return formatTimeWithSeconds(item[field], {
    inputFormat: APIV2RequestDatetimeFormat,
  })
}
function getMilli(item, field) {
  return (
    (item[field] &&
      moment(item[field], APIV2RequestDatetimeFormat)._d.getTime()) ||
    0
  )
}
function getAddress(
  item,
  streetNumber,
  streetAddress,
  zipCode,
  city,
  useZone,
  zone
) {
  return getFormattedAddress(item, {
    streetNumber,
    streetAddress,
    zipCode,
    city,
    useZone: item[useZone],
    zone,
  })
}

function getFormattedDurationFromSeconds(seconds) {
  let d = moment.duration(seconds * 1000)
  return (
    Math.floor(d.asHours()) + moment.utc(seconds * 1000).format('[h]mm[m]ss[s]')
  )
}

/**
 * @TODO: Use getNestedValue helper function to normalize attributes
 */
function normalizeTripHistoryItem(vehicleId) {
  return function (item, index) {
    let mappedItem = {
      number: parseInt(index + 1),
      vehicleId: vehicleId,
      distance: item.distance,
      tripDuration: item.duree_trajet,
      tripDurationFormatted: getFormattedDurationFromSeconds(item.duree_trajet),
      date: moment(item.date, 'YYYY-MM-DD')._d,
      stopDurationSeconds: item.duree_arret,
      stopDuration: getFormattedDurationFromSeconds(item.duree_arret),
      fromTimeMilli: getMilli(item, 'dh_trajet_deb'),
      fromTime: getTime(item, 'dh_trajet_deb'),
      fromAddress: getAddress(
        item,
        '',
        'adresse_deb',
        'cp_deb',
        'ville_deb',
        'in_zoneinterne_deb',
        'zone_deb'
      ),
      toTime: getTime(item, 'dh_trajet_fin'),
      toTimeMilli: getMilli(item, 'dh_trajet_fin'),
      toAddress: getAddress(
        item,
        '',
        'adresse_fin',
        'cp_fin',
        'ville_fin',
        'in_zoneinterne_fin',
        'zone_fin'
      ),
      datetimeContactON: getTime(item, 'dh_contact_on'),
      datetimeContactONMilli: getMilli(item, 'dh_contact_on'),
      datetimeContactOFF: getTime(item, 'dh_contact_off'),
      datetimeContactOFFMilli: getMilli(item, 'dh_contact_off'),
      linestring: item.linestring,
      speedLinestring: getNestedValue(
        item,
        ['linestring_mouvement', 'linestring_couleur'],
        []
      ),
    }
    mappedItem.id = require('object-hash')(mappedItem)
    return mappedItem
  }
}

function waitSomeTime(seconds) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(), seconds * 1000)
  })
}