Skip to content
Snippets Groups Projects
utils.ts 12.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • import { captureException } from '@sentry/react'
    
    import {
      FluidSlugType,
      FluidState,
      FluidType,
      KonnectorUpdate,
      Season,
    } from 'enums'
    
    import get from 'lodash/get'
    
    import { DateTime, Interval } from 'luxon'
    
    import { FluidStatus, Relation } from 'models'
    
    import challengeData from '../db/challengeEntity.json'
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
    
    
    /** Array of elec, water & gas */
    export const allFluids = [FluidType.ELECTRICITY, FluidType.WATER, FluidType.GAS]
    
    
    export function getKonnectorSlug(
      fluidType: Exclude<FluidType, FluidType.MULTIFLUID>
    ) {
    
      switch (fluidType) {
        case FluidType.ELECTRICITY:
          return FluidSlugType.ELECTRICITY
        case FluidType.WATER:
          return FluidSlugType.WATER
        case FluidType.GAS:
          return FluidSlugType.GAS
        default:
          throw new Error('unknown fluidtype')
      }
    }
    
    /**
     * Return lowercase fluidtype
    
     * @example FluidType.ELECTRICITY => 'electricity'
     */
    export function getFluidName(fluidType: FluidType) {
      return FluidType[fluidType].toLowerCase()
    }
    
    
    export const getFluidTypeTranslation = (
      fluidType: Exclude<FluidType, FluidType.MULTIFLUID>
    ) => {
      switch (fluidType) {
        case FluidType.GAS:
          return 'de gaz'
        case FluidType.ELECTRICITY:
          return "d'électricité"
        case FluidType.WATER:
          return "d'eau"
        default:
          throw new Error('unexpected fluidtype')
      }
    }
    
    export const getPartnerKey = (fluidType: FluidType) => {
      switch (fluidType) {
        case FluidType.ELECTRICITY:
          return 'enedis'
        case FluidType.WATER:
          return 'egl'
        case FluidType.GAS:
          return 'grdf'
        default:
          throw new Error('unknown fluidtype')
      }
    }
    
    
    export const getFluidLabel = (fluidType: FluidType) => {
      switch (fluidType) {
        case FluidType.ELECTRICITY:
          return 'elec'
        case FluidType.GAS:
          return 'gaz'
        case FluidType.WATER:
          return 'water'
        case FluidType.MULTIFLUID:
          return 'multi'
      }
    }
    
    
    export function getKonnectorUpdateError(type: string) {
      switch (type.toUpperCase()) {
        case 'USER_ACTION_NEEDED.OAUTH_OUTDATED':
    
        case 'USER_ACTION_NEEDED.SCA_REQUIRED':
    
          return KonnectorUpdate.ERROR_UPDATE_OAUTH
    
        case 'LOGIN_FAILED':
          return KonnectorUpdate.LOGIN_FAILED
    
        default:
          return KonnectorUpdate.ERROR_UPDATE
      }
    }
    
    export function isKonnectorActive(
      fluidStatus: FluidStatus[],
      fluidType: FluidType
    ): boolean {
      if (fluidType === FluidType.MULTIFLUID) {
        if (
          fluidStatus.filter(
            fluid =>
              fluid.status === FluidState.NOT_CONNECTED ||
    
              fluid.status === FluidState.KONNECTOR_NOT_FOUND
    
          ).length === 3
        ) {
          return false
        } else {
          return true
        }
      }
    
      if (
        fluidStatus[fluidType].status === FluidState.NOT_CONNECTED ||
        fluidStatus[fluidType].status === FluidState.KONNECTOR_NOT_FOUND
      ) {
    
        return false
      } else return true
    }
    
    export function formatNumberValues(
    
      value: number | null,
    
      fluidStyle?: string,
      toBeCompared = false
    ) {
    
    Romain CREY's avatar
    Romain CREY committed
      if (value || value === 0) {
    
        const localeValue = value.toLocaleString('fr-FR', {
    
          minimumFractionDigits: 2,
          maximumFractionDigits: 2,
        })
    
        const noSpaceValue = parseInt(localeValue.replace(/\s/g, ''))
        if (toBeCompared) return noSpaceValue
        if (fluidStyle && noSpaceValue >= 1000) {
          const convertedValue = (noSpaceValue / 1000).toFixed(2).replace('.', ',')
          return convertedValue
        } else return localeValue
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
    }
    
    
    /**
     * Get one relation in doc
     * @param {object} doc - DocumentEntity
     * @param {string} relName - Name of the relation
     */
    export function getRelationship<D>(doc: D, relName: string): Relation {
      return get(doc, `relationships.${relName}.data`, [])
    }
    
    
    /**
     * Get array of items in one relation in doc
     * @param {object} doc - DocumentEntity
     * @param {string} relName - Name of the relation
     */
    export function getRelationshipHasMany<D>(doc: D, relName: string): Relation[] {
      return get(doc, `relationships.${relName}.data`, [])
    }
    
    
     * Import a svg file with format : id.svg
    
    export const importIconById = async (
      id: string,
      pathType: string
    ): Promise<string | undefined> => {
    
      let importedChallengeIcon
    
        importedChallengeIcon = await import(
    
          /* webpackMode: "eager" */ `assets/icons/visu/${pathType}/${id}.svg`
    
        if (importedChallengeIcon) {
          return importedChallengeIcon.default
        }
      } catch (e) {
        console.error(`Could not import icon ${pathType}/${id}`)
        captureException(e)
    
    export const getMonthFullName = (month: number) => {
      const monthNames = [
        'Janvier',
        'Février',
        'Mars',
        'Avril',
        'Mai',
        'Juin',
        'Juillet',
        'Août',
        'Septembre',
        'Octobre',
        'Novembre',
        'Décembre',
      ] as const
      if (month < 1 || month > 12) throw new Error('Invalid month')
      return monthNames[month - 1]
    }
    
    
    /**
     * Return month string according to month index
    
     * @variation Equivalent to date.monthLong
    
     * @param date - DateTime
     * @returns month in french
     */
    
    export const getMonthName = (date: DateTime) => {
    
        'février',
        'mars',
        'avril',
        'mai',
        'juin',
        'juillet',
        'août',
        'septembre',
        'octobre',
        'novembre',
        'décembre',
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
      ] as const
    
      return monthNames[date.month - 1]
    
    /**
     * Return month string according to month index
     * @param date - DateTime
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
     * @returns "de" month in french
    
     */
    export const getMonthNameWithPrep = (date: DateTime) => {
      const monthNames = [
    
        'de février',
        'de mars',
    
        'de mai',
        'de juin',
        'de juillet',
    
        'de septembre',
    
        'de novembre',
        'de décembre',
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
      ] as const
    
      return monthNames[date.month - 1]
    
    /**
     * Return season according to following rules
     * - Winter is : 1/11/XXXX => 1/3/XXXX
     * - Summer is : 1/6/XXXX => 1/9/XXXX
     * @returns Season
     */
    export const getSeason = (): Season => {
    
      const currentDate = DateTime.local().setZone('utc', {
        keepLocalTime: true,
      })
      const currentYear = currentDate.year
      const winterStart = DateTime.local(currentYear, 11, 1).setZone('utc', {
        keepLocalTime: true,
      })
      const winterEnd = DateTime.local(currentYear + 1, 3, 1).setZone('utc', {
    
        keepLocalTime: true,
      })
    
    
      const summerStart = DateTime.local(currentYear, 6, 1).setZone('utc', {
        keepLocalTime: true,
      })
      const summerEnd = DateTime.local(currentYear, 9, 1).setZone('utc', {
    
        keepLocalTime: true,
      })
    
      const summerInterval = Interval.fromDateTimes(summerStart, summerEnd)
      const winterInterval = Interval.fromDateTimes(winterStart, winterEnd)
    
    
      if (summerInterval.contains(currentDate)) {
        return Season.SUMMER
      } else if (winterInterval.contains(currentDate)) {
        return Season.WINTER
      } else {
        return Season.NONE
      }
    }
    
    
    /**
     *  Returns the challenge title with line return ( \n ). The result is coming from challengeEntity.json
     * @param userChallengeId EXPLORATION001
     * @returns Simone\nVEILLE
     */
    export const getChallengeTitleWithLineReturn = (userChallengeId: string) => {
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
      for (const challenge of challengeData) {
        if (challenge._id === userChallengeId) {
          return challenge.title_line_return
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
    
    /**
     * Returns today's date, example: 2022-09-28T00:00:00.000Z
     * @returns DateTime
     */
    export const getTodayDate = () =>
      DateTime.local()
        .setZone('utc', {
          keepLocalTime: true,
        })
        .startOf('day')
    
    
    /**
     * Formats an array of strings into a list with commas and an "et" (and) before the last element.
     * @param {string[]} array - The array of strings to be formatted.
     * @returns {string} The formatted list string.
     *
     * If the array is empty, an empty string is returned.
     * If the array has only one element, that element is returned as is.
     * If the array has two elements, they are joined with " et " (and).
     * If the array has more than two elements, all but the last element are joined with commas,
     * and " et " (and) is placed before the last element.
     * @example
     * // Returns "pomme, banane et cerise"
     * formatListWithAnd(['pomme', 'banane', 'cerise']);
     */
    export const formatListWithAnd = (array: string[]) => {
      if (array.length === 0) {
        return ''
      } else if (array.length === 1) {
        return array[0]
      } else if (array.length === 2) {
        return array.join(' et ')
      } else {
        const lastElement = array.pop()
        return array.join(', ') + ' et ' + lastElement
      }
    }
    
    export interface OffPeakHours {
    
      start: { hour: number; minute: number }
      end: { hour: number; minute: number }
    }
    
    /**
     * Check if a string is a valid off-peak hour format
     * @example
     * isValidOffPeakHours("6H15-14H15") => true
     * isValidOffPeakHours("68H78_12Hab") => false
     */
    export const isValidOffPeakHours = (range: string) => {
      const offPeakHoursRegex =
        /^(0?\d|1\d|2[0-3])H[0-5]?\d-(0?\d|1\d|2[0-3])H[0-5]?\d$/
      return offPeakHoursRegex.test(range)
    }
    
    /**
     * Parse the string representation of off-peak hours from Enedis to an array of time ranges object
     */
    export const parseOffPeakHours = (timeString: string): OffPeakHours[] => {
      const timeRanges = timeString.split(';')
      if (!timeRanges.every(range => isValidOffPeakHours(range))) {
        console.error(`Error parsing time range "${timeString}"`)
        return []
      }
    
      const intervals: OffPeakHours[] = []
    
      for (const range of timeRanges) {
        const [startStr, endStr] = range.split('-')
    
        const startTime = DateTime.fromFormat(startStr, "H'H'mm")
        const endTime = DateTime.fromFormat(endStr, "H'H'mm")
    
        intervals.push({
          start: { hour: startTime.hour, minute: startTime.minute },
          end: { hour: endTime.hour, minute: endTime.minute },
        })
      }
    
      return intervals
    }
    
    /**
     * Format a number into a 2-digits string, padded with 0
     * @example formatTwoDigits(5) returns "05"
     */
    export const formatTwoDigits = (num: number): string => {
      return num.toString().padStart(2, '0')
    }
    
    /**
     * Format off-peak hours object into a human-readable string
     * @example formatOffPeakHours({ start: { hour: 2, minute: 0 }, end: { hour: 10, minute: 0 }}) returns "02H00-10H00"
     */
    export const formatOffPeakHours = (offPeakHours: OffPeakHours): string => {
      const { start, end } = offPeakHours
      const startTime = `${formatTwoDigits(start.hour)}H${formatTwoDigits(
        start.minute
      )}`
      const endTime = `${formatTwoDigits(end.hour)}H${formatTwoDigits(end.minute)}`
    
      return `${startTime}-${endTime}`
    }
    
    /**
     * Split off-peak hours that cross midnight
     * @example The range "22H00-6H00" becomes "22H00-23H59" and "0H00-6H00"
     */
    export const splitOffPeakHours = (
      offPeakHours: OffPeakHours[]
    ): OffPeakHours[] => {
      return offPeakHours.reduce((acc: OffPeakHours[], offPeakHour) => {
        if (offPeakHour.start.hour > offPeakHour.end.hour) {
          acc.push({
            start: {
              hour: offPeakHour.start.hour,
              minute: offPeakHour.start.minute,
            },
            end: {
              hour: 23,
              minute: 59,
            },
          })
          acc.push({
            start: {
              hour: 0,
              minute: 0,
            },
            end: {
              hour: offPeakHour.end.hour,
              minute: offPeakHour.end.minute,
            },
          })
        } else {
          acc.push(offPeakHour)
        }
        return acc
      }, [])
    }
    
    export const roundToNearestHalfHour = (
      hour: number,
      minute: number,
      isEnd: boolean
    ): { hour: number; minute: number } => {
      let roundedMinute = Math.round(minute / 30) * 30 // Round to the nearest half-hour
      let roundedHour = hour
    
      // If rounding to the next hour (except for midnight), adjust the hour and reset the minute
      if (roundedMinute === 60 && roundedHour !== 23) {
        roundedHour += 1
        roundedMinute = 0
      }
    
      // Don't round to midnight for the off-peak hours end, instead round to 23:59
      if (
        (roundedMinute === 60 && roundedHour === 23) ||
        (roundedMinute === 0 && roundedHour === 0)
      ) {
        if (isEnd) {
          roundedHour = 23
          roundedMinute = 59
        } else {
          roundedHour = 0
          roundedMinute = 0
        }
      }
    
      return { hour: roundedHour, minute: roundedMinute }
    }
    
    /**
     * Round off-peak hours to the nearest half-hour
     * @example "6H50-14H50" becomes "7H00-15H00"
     */
    export const roundOffPeakHours = (
      offPeakHours: OffPeakHours[]
    ): OffPeakHours[] => {
      return offPeakHours.map(({ start, end }) => ({
        start: roundToNearestHalfHour(start.hour, start.minute, false),
        end: roundToNearestHalfHour(end.hour, end.minute, true),
      }))
    }
    
    
    export const getFluidUnit = (fluidType: FluidType) => {
      switch (fluidType) {
        case FluidType.ELECTRICITY:
        case FluidType.GAS:
          return 'kWh'
        case FluidType.WATER:
          return 'L'
        case FluidType.MULTIFLUID:
          return ''
        default:
          throw new Error('unknown fluidtype')
      }
    }