src\contexts\DetailedTripReviewContext.tsx

mail@pastecode.io avatar
unknown
typescript
9 days ago
14 kB
2
Indexable
Never
import React, { createContext } from 'react'
import { convertUnit, createSelectors } from '@Utils'
import { StoreApi, createStore } from 'zustand'
import { useLocation, useParams } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import {
  fetchStationsByRouteId,
  fetchTrip,
  fetchTripDetails
} from '@Redux/slices/portalDataSlice'
import {
  getFeaturizersInterpolateReverifyPath,
  getFeaturizersTripInformationOnPath
} from '@GraphQL/Api'
import Papa from 'papaparse'
import _ from 'lodash'

type TDetailedTripReviewStore = {
  tripId: string | undefined
  stcId: string | undefined
  trainId: string | undefined
  crewId: string | undefined
  startDateUTC: string | undefined
  endDateUTC: string | undefined
  startLocation: string | undefined
  endLocation: string | undefined
  routeId: string | undefined
  tripDurationSecs: number | undefined
  dataAvailability: string[] | null | undefined
  consistWeight: number | undefined
  consistLength: number | undefined
  extraLocomotiveInfo: any | undefined
  numberOfLocos: number | undefined
  numberOfCars: number | undefined
  tripInformation:
    | {
        lat: number
        lng: number
        speed: number
        speedLimit: number
      }[]
    | null
  showMapPopup: boolean
  selectedMovement: {
    index: number
    info: any
  } | null
  selectedStations:
    | {
        lat: number
        lng: number
        abbreviation: string
        location_name: number
      }[]
    | null
  mapModes: { key: string; label: string }[]
  mapMode: { key: string; label: string }
  mapZoom: number
  mapCenter: { lat: number; lng: number }

  loadingCsvGPSData: boolean | undefined
  loadingCsvInterpolateData: boolean | undefined

  csvGPSData: Papa.ParseResult<any> | null
  csvInterpolateData: Papa.ParseResult<any> | null
  pickedXAxisProperty: string | null
  pickedYAxisProperties: string[]

  pickedVisualizeFile: string | null
  pickedVisualizeDataCells: string[]
  pickedVisualizeLatitude: string | null
  pickedVisualizeLongitude: string | null
  pickedVisualizeLatLngs: { lat: string; lng: string }[]
  showOutputVisualizeModal: boolean
  triggerOutputVisualize: boolean

  downloadFileSize: number
  loadedFileSize: number

  actions: {}
}

export const DetailedTripReviewStoreContext = createContext<
  StoreApi<TDetailedTripReviewStore> | undefined
>(undefined)

export const createDetailedTripReviewStore = () => {
  return createStore<TDetailedTripReviewStore>((set, get) => ({
    loadingCsvGPSData: undefined,
    loadingCsvInterpolateData: undefined,
    tripId: undefined,
    stcId: undefined,
    trainId: undefined,
    crewId: undefined,
    startDateUTC: undefined,
    endDateUTC: undefined,
    startLocation: undefined,
    endLocation: undefined,
    routeId: undefined,
    tripDurationSecs: undefined,
    dataAvailability: undefined,
    tripInformation: null,
    showMapPopup: false,
    selectedMovement: null,
    selectedStations: null,
    consistWeight: undefined,
    consistLength: undefined,
    extraLocomotiveInfo: undefined,
    numberOfLocos: undefined,
    numberOfCars: undefined,
    mapModes: [
      {
        key: 'speed-profile',
        label: 'Speed profile'
      },
      {
        key: 'speed-relative-to-speed-limit',
        label: 'Speed relative to speed limit'
      }
    ],
    mapMode: {
      key: 'speed-profile',
      label: 'Speed profile'
    },
    csvGPSData: null,
    csvInterpolateData: null,
    pickedXAxisProperty: null,
    pickedYAxisProperties: [],
    pickedVisualizeFile: null,
    pickedVisualizeDataCells: [],
    pickedVisualizeLatitude: null,
    pickedVisualizeLongitude: null,
    pickedVisualizeLatLngs: [],
    showOutputVisualizeModal: false,
    triggerOutputVisualize: false,

    mapZoom: 0,
    mapCenter: { lat: 0, lng: 0 },

    downloadFileSize: 0,
    loadedFileSize: 0,

    actions: {}
  }))
}

export const DetailedTripReviewStoreProvider = ({
  children
}: {
  children: React.ReactNode
}) => {
  const storeRef = React.useRef<StoreApi<TDetailedTripReviewStore>>()
  if (!storeRef.current) {
    storeRef.current = createDetailedTripReviewStore()
  }

  const storeSelectors = createSelectors(storeRef.current)
  const startLocation = storeSelectors.use.startLocation()
  const endLocation = storeSelectors.use.endLocation()
  const routeId = storeSelectors.use.routeId()
  const stcId = storeSelectors.use.stcId()
  const showOutputVisualizeModal = storeSelectors.use.showOutputVisualizeModal()
  const csvInterpolateData = storeSelectors.use.csvInterpolateData()
  const triggerOutputVisualize = storeSelectors.use.triggerOutputVisualize()

  const location: any = useLocation()
  let { tripId } = useParams<{ tripId: string }>()
  const state = location?.state
  const dispatch = useDispatch()

  const { localizationUnits } = useSelector((state: any) => state.portalData)

  const {
    data: { railroadId }
  } = useSelector((state: any) => state.userInfo)

  const {
    trip: { data: tripData }
  } = useSelector((state: any) => state.portalData)

  const {
    routesByRailroad: { data: routesData }
  } = useSelector((state: any) => state.portalData!)

  React.useEffect(() => {
    // State from trips pages
    storeSelectors.setState({
      trainId: state?.trainId,
      stcId: state?.stcId,
      crewId: state?.crewId,
      startDateUTC: state?.startDateUTC,
      endDateUTC: state?.endDateUTC,
      startLocation: state?.startLocation,
      endLocation: state?.endLocation,
      tripDurationSecs: state?.tripDurationSecs
    })

    if (state?.dataAvailability) {
      storeSelectors.setState({
        dataAvailability: state?.dataAvailability
      })
    }

    if (tripId) {
      storeSelectors.setState({ tripId: tripId })
    }

    if (
      !state?.trainId ||
      !state?.stcId ||
      !state?.crewId ||
      !state?.startDateUTC ||
      !state?.endDateUTC ||
      !state?.startLocation ||
      !state?.endLocation ||
      !state?.tripDurationSecs ||
      state?.dataAvailability === undefined ||
      !routeId
    ) {
      dispatch(fetchTrip({ tripId: tripId }))
    }
  }, [])

  React.useEffect(() => {
    // State from trip details query
    if (tripData && tripData?.trip_id === tripId) {
      storeSelectors.setState({
        trainId: tripData?.train_id,
        stcId: tripData?.simple_train_consist_id,
        crewId: tripData?.crew_id,
        startDateUTC: tripData?.start_date_utc,
        endDateUTC: tripData?.end_date_utc,
        startLocation: tripData?.route_id?.split('_')[0],
        endLocation: tripData?.route_id?.split('_')[1],
        routeId: tripData?.route_id,
        tripDurationSecs: tripData?.trip_duration_secs,
        dataAvailability: tripData.data_availability,
        consistWeight: tripData?.total_weight_kg,
        consistLength: tripData?.total_length_m,
        numberOfLocos: tripData?.locomotive_count,
        numberOfCars: tripData?.car_count,
        extraLocomotiveInfo: tripData.extra_locomotive_info
      })
    }
  }, [tripData])

  React.useEffect(() => {
    dispatch(fetchTripDetails({ tripId }))
  }, [tripId])

  React.useEffect(() => {
    if (routeId) return

    if (!routesData) return

    const targetRouteData = routesData.find((route: any) => {
      return (
        (route.route_id.startsWith(startLocation) &&
          route.route_id.endsWith(endLocation)) ||
        (route.route_id.startsWith(endLocation) &&
          route.route_id.endsWith(startLocation))
      )
    })

    if (!targetRouteData) return

    storeSelectors.setState({ routeId: targetRouteData.route_id })
  }, [routesData, startLocation, endLocation, railroadId])

  React.useEffect(() => {
    if (!routeId) return

    dispatch(
      fetchStationsByRouteId({
        railroadId: railroadId,
        routeId: routeId
      })
    )
  }, [routeId])

  React.useEffect(() => {
    if (!stcId) {
      storeSelectors.setState({
        tripInformation: null
      })
      return
    }
    ;(async () => {
      try {
        storeSelectors.setState({
          loadingCsvGPSData: true
        })

        const csvUrl = await getFeaturizersTripInformationOnPath({
          expirySecs: 60,
          simpleTrainConsistId: stcId
        }).then(res => res.presigned_url)

        const csvText = await fetch(csvUrl)
          .then(response => response.text())
          .then(text => {
            return text
          })

        React.startTransition(() => {
          const csvData = Papa.parse<any>(csvText, {
            header: true
          })

          storeSelectors.setState({
            csvGPSData: {
              ...csvData,
              data: _.values(
                _.chain(csvData.data)
                  .omitBy(item =>
                    Object.values(item).some(
                      value => _.isNaN(value) || value === ''
                    )
                  )
                  .value()
              )
            }
          })

          const _tripInformation = csvData.data.map((row: any) => {
            // Temporary logic - TODO: should be based on tier
            if (row?.evr_gps_filled_latitude_track_deg) {
              return {
                lat: parseFloat(row?.evr_gps_filled_latitude_track_deg),
                lng: parseFloat(row?.evr_gps_filled_longitude_track_deg),
                speed: convertUnit({
                  measurementType: 'speed_unit',
                  value: Math.max(parseFloat(row?.evr_gps_speed_gps_mph), 0),
                  fromUnit: 'mph',
                  toUnit: localizationUnits.data?.speed_unit || 'mph',
                  decimalPlaces: 0
                }).value,
                speedLimit: convertUnit({
                  measurementType: 'speed_unit',
                  value: Math.max(parseFloat(row?.speed_limit_mph), 0),
                  fromUnit: 'mph',
                  toUnit: localizationUnits.data?.speed_unit || 'mph',
                  decimalPlaces: 0
                }).value
              }
            }
            return {
              lat: parseFloat(row?.app_gps_filled_latitude_track_deg),
              lng: parseFloat(row?.app_gps_filled_longitude_track_deg),
              speed: convertUnit({
                measurementType: 'speed_unit',
                value: Math.max(parseFloat(row?.app_gps_speed_gps_mph), 0),
                fromUnit: 'mph',
                toUnit: localizationUnits.data?.speed_unit || 'mph',
                decimalPlaces: 0
              }).value,
              speedLimit: convertUnit({
                measurementType: 'speed_unit',
                value: Math.max(parseFloat(row?.speed_limit_mph), 0),
                fromUnit: 'mph',
                toUnit: localizationUnits.data?.speed_unit || 'mph',
                decimalPlaces: 0
              }).value
            }
          })

          const _tripInformationValidated = _.chain(_tripInformation)
            .omitBy(
              item =>
                _.isNaN(item.lat) || _.isNaN(item.lng) || _.isNaN(item.speed)
            )
            .value()

          const _uniqueTripInformationValidated = _.uniqBy(
            _.values(_tripInformationValidated),
            item => `${item.lat},${item.lng}`
          )

          storeSelectors.setState({
            tripInformation: _uniqueTripInformationValidated
          })
        })
      } catch (error) {
        throw new Error(`Oops, can't fetch GPS: ${error}`)
      } finally {
        storeSelectors.setState({
          loadingCsvGPSData: false
        })
      }
    })()
  }, [stcId])

  React.useEffect(() => {
    if (csvInterpolateData) return
    if (!stcId) return
    if (!triggerOutputVisualize) return
    ;(async () => {
      try {
        storeSelectors.setState({
          loadingCsvInterpolateData: true
        })

        const csvUrl = await getFeaturizersInterpolateReverifyPath({
          expirySecs: 60,
          simpleTrainConsistId: stcId
        }).then(res => res.presigned_url)

        const csvText = await fetch(csvUrl)
          .then(response => {
            const responseClone = response.clone()
            let loadedMB = 0
            const reader = response?.body?.getReader()
            const contentLength = response?.headers.get('Content-Length')

            storeRef.current?.setState({
              downloadFileSize: parseInt(contentLength || '0') / 1024 / 1024
            })

            reader?.read().then(function processData(result): any {
              if (result.done) {
                return
              }
              loadedMB += result.value.length
              storeRef.current?.setState({
                loadedFileSize: loadedMB / 1024 / 1024
              })
              return reader.read().then(processData)
            })
            return responseClone.text()
          })
          .then(text => {
            return text
          })

        React.startTransition(() => {
          const csvData = Papa.parse<any>(csvText, {
            header: true
          })

          storeSelectors.setState({
            csvInterpolateData: csvData
          })
        })
      } catch (error) {
        throw new Error(
          `Oops, can't fetch CSV Interpolate Reverify Data: ${error}`
        )
      } finally {
        storeSelectors.setState({
          loadingCsvInterpolateData: false
        })
      }
    })()
  }, [stcId, triggerOutputVisualize])

  return (
    <DetailedTripReviewStoreContext.Provider value={storeRef.current}>
      {children}
    </DetailedTripReviewStoreContext.Provider>
  )
}

export const useDetailedTripReviewStore = () => {
  const context = React.useContext(DetailedTripReviewStoreContext)
  if (!context) {
    throw new Error(
      'DetailedTripReviewStore must be used within a DetailedTripReviewStoreProvider'
    )
  }
  return createSelectors(context)
}
Leave a Comment