DHL Dispatcher

This is a private web app, created with React, specifically for the needs of DHL company. It is currently in use in several countries, including Germany and France.

Logistics platform for dispatching and tracking packages and trucks. The goal of the project was to create a new, up to date, front end application which had to work with their existing back end setup, in order to replace the previous, outdated system.

For this, our team developed an entirely new web based app from the ground up.

Requirements and features:
- full integration with DHL's back end system - support of corporate approved browsers, including some of the older versions of Internet Explorer
- introduction of new features, based on the client's requirements and approval
- updated UX and layout design
- emphasis on precision and reliability

I worked on various components of the main Dispatch screen. Most important task I was assigned was to develop an external, reusable calendar module, responsible for tracking time. I developed this custom library to allow reusability in the main application, by providing different configurations and combinations of pickers, working synchronously with one another. The module uses a 2-way data communication model. It can translate user input to the main application or receive and display time from the main application to the user. All time manipulation actions, in the main DHL Dispatch application are handled by this module.

Main features of Calendar component:
- set/display dates (days, months, years)
- set/display times (minutes, hours)
- set/display time-zones

Additional features:
- various keyboard shortcuts
- calculating periods of time (ex.: typing inside textInput: "14/08/2020 + 3 weeks")
- dynamic parsing (ex.: typing inside textInput: "today", or "nextweek + 2 days")
- scrolling through months with advanced algorithm

Code snippet from main Calendar class, inside the render function
          
        <div
            tabIndex={0}
            id={"mainCalendarContainer"}
            className={rangeLvl == 0 ? "scrollContainer" : "scrollContainerMonths"}
            type={(rangeLvl > 0 && "range") || "month"}
            onWheel={({ deltaY }) => {
                this.setState({
                    scroll: scroll - deltaY, // use "+" for inverted scroll
                })
            }}
        >
            {rangeLvl == 0 &&
              combinedWeeks.map((week, key) => (
                <div
                      key={key}
                      className="week"
                      {...weekProps}
                      // scroll algorithm: it scrolls each week row individually into place
                      style={{
                          top: scrollOffset + ((scroll + [
                              scroll <= 0 ? (scroll < -20 ? 80 : -60) : -60,
                              scroll <= 0 ? (scroll < -40 ? 100 : -40) : (scroll > 120 ? -180 : -40),
                              scroll <= 0 ? (scroll < -60 ? 120 : -20) : (scroll > 100 ? -160 : -20),
                              scroll <= 0 ? (scroll < -80 ? 140 : 0) : (scroll > 80 ? -140 : 0),
                              scroll <= 0 ? (scroll < -100 ? 160 : 20) : (scroll > 60 ? -120 : 20),
                              scroll <= 0 ? (scroll < -120 ? 180 : 40) : (scroll > 40 ? -100 : 40),
                              scroll <= 0 ? 60 : (scroll > 20 ? -80 : 60),
                          ][key]) % 140),

                          position: "absolute",
                          width: "100%",
                      }}
                  >
                      {week && week.map(({ date }, key) => (
                        <div key={key} className="dateButtonContainer">
                          <button
                                  disabled={
                                      (min &&
                                          moment(date)
                                              .startOf("day")
                                              .isBefore(min, "day"))
                                      || (max &&
                                          moment(date)
                                              .endOf("day")
                                              .isAfter(max, "day"))
                                      || moment(date)
                                          .startOf("day")
                                          .isBefore(moment().add(minGlobalRange, "y"), "day")
                                      || moment(date)
                                          .endOf("day")
                                          .isAfter(moment().add(maxGlobalRange, "y"), "day")
                                  }
                                  key={key}
                                  className={[
                                      "day",
                                      ...(date.clone().startOf("month").isSame(month.clone().startOf("month"))
                                          && [] || ["differentMonth"]),
                                      ...((hoverStart &&
                                          date.isBetween(
                                              moment.min(hoverStart, hoverEnd).startOf("day"),
                                              moment.max(hoverStart, hoverEnd).endOf("day"),
                                              null,
                                              "[]"
                                          ) && ["hovered"]) ||
                                          []),
                                      ...(startTime && endTime && (date.isBetween(
                                          startTime.clone().startOf("day"),
                                          enablePeriodSelection && endTime.clone().startOf("day")
                                          || startTime.clone().startOf("day"),
                                          null,
                                          "[]"
                                      ) && [preselectedDateTime && "selected" || ""]) || [,])
                                  ].join(" ")}
                                  onMouseOver={() => {
                                      if (enableUserInteraction && hoverStart != null) {
                                          this.setState({
                                              hoverEnd: moment(date).startOf("day")
                                          });
                                      }
                                  }}
                                  onClick={() => {

                                      if (enablePeriodSelection) {
                                          this.setState(
                                              (enablePeriodSelection &&
                                                  hoverStart == null && {
                                                  hoverStart: moment(date).startOf("day"),
                                                  hoverEnd: moment(date).startOf("day")
                                              }) || {
                                                  start: moment(hoverStart || date),//.startOf("day"),
                                                  end: moment(hoverEnd || date),//.endOf("day"),
                                                  hoverStart: null,
                                                  hoverEnd: null
                                              }
                                          );
                                          if (hoverStart !== null) {
                                              setDate(
                                                  moment(hoverStart),
                                                  date,
                                                  true
                                              )
                                              selectionInProgress(false)
                                          } else {
                                              selectionInProgress(true)
                                          }
                                      } else {
                                          setDate(
                                              date,
                                              date,
                                              true
                                          )
                                          getInputStr && getInputStr(reversedDynamicParsing(date), true)
                                      }
                                  }}
                              >
                                  {date.format("D")}
                                </button>
                              </div>
                        ))}
                      </div>
                  ))}
                </div>
            
              ...

          
        

I used "React.createContext" to handle the high volume of data between different components of the Calendar module, instead of using props which became icreasingly messier as the module grew. This allowed me to subscribe only those components to listen for the change in context that needed to. For this I created 3 separate context providers which covered the needs of all calendar pickers and their configs. They worked as wrappers to those components.

Code snippet from TimeProvider (context provider) class
          
            export default class TimeProvider extends Component {

              static defaultProps = {
                  time: null,
                  min: null,
                  max: null,
                  enableUserInteraction: true,
                  placeHolder: null,
                  onChange: () => { },
                  onError: () => { },
                  calendarStyle: {},
                  datePropsError: false,
                  enableTodayButton: false,
                  timeZone: moment().tz(),
                  limits: "()",
                  parseInput: true,
                  id: 'default',
                  onInput: () => { },
              };
          
              static getDerivedStateFromProps(props, state) {
                  let {
                      time: propsTime,
                      min: propsMin,
                      max: propsMax,
                      timeZone: propsZone,
                      datePropsError: propsError,
                      enableTodayButton,
                      limits,
                      parseInput,
                      id
                  } = props;
          
                  let {
                      startTime: contextTime,
                      timeZone: contextZone,
                      oldPropsTime,
                      oldPropsZone,
                      datePropsError: contextError,
                      preselectedDateTime,
                      inputStr: contextInputStr
                  } = state;
          
                  if (typeof propsTime == 'string') {
                      parseInput = false
                  }
          
                  let tz = propsZone != oldPropsZone ? propsZone
                      : contextZone;
          
                  if (tz == null || typeof tz == 'undefined') {
                      tz = moment().tz() || moment.tz.guess(true)
                  }
          
                  let time = parseInput ?
                      (moment(propsTime != oldPropsTime ? moment(propsTime) : contextTime).tz(tz))
                      : propsTime != oldPropsTime ? dynamicParsing(propsTime, 2, "return moment") : moment(contextTime)
          
                  let m = moment(moment(time).clone().tz(tz)).format("YYYY-MM-DD HH:mm")
                  let minTZ = moment(moment(propsMin).clone().tz(tz)).format("YYYY-MM-DD HH:mm")
                  let maxTZ = moment(moment(propsMax).clone().tz(tz)).format("YYYY-MM-DD HH:mm")
          
                  let error =
                      (propsMin || propsMax) && dateTimeError(moment(m), minTZ, maxTZ, limits)
                      || outOfGlobalRange(moment(m), null)
                      || propsError
          
                  if (preselectedDateTime == false) {
                      error = true
                  }
          
                  let inputStr = propsTime != oldPropsTime
                      ? propsTime : contextInputStr
          
                  return {
                      ...state,
                      startTime: time,
                      endTime: time,
                      oldPropsTime: propsTime,
                      timeZone: tz,
                      oldPropsZone: propsZone,
                      min: propsMin && moment(propsMin).tz(tz) || null,
                      max: propsMax && moment(propsMax).tz(tz) || null,
                      datePropsError: error,
                      enableTodayButton,
                      preselectedDateTime: preselectedDateTime || (propsTime != null),
                      parseInput,
                      id,
                      inputStr
                  }
              }
          
              constructor(props) {
                  super(props);
          
                  this.state = {
                      startTime: moment(),
                      endTime: moment(),
                      min: null,
                      max: null,
                      timeZone: moment().tz(),
                      datePropsError: false,
                      preselectedDateTime: false,
                      prevSelected: [{ startTime: moment(), endTime: moment() }],
                      hoursOnFocus: true,
                      inputStr: "",
                      isValidStr: true,
          
                      setTime: this.setTime,
                      setDate: this.setDate,
                      setTimeZone: this.setTimeZone,
                      setPrevSelected: this.setPrevSelected,
                      setHoursOnFocus: this.setHoursOnFocus,
                      getInputStr: this.getInputStr
                  };
              }
          
              componentDidUpdate(prevProps, prevState) {
                  let {
                      onChange,
                      onError,
                      onInput,
                      translationLabel
                  } = this.props;
                  let {
                      startTime: oldStartTime,
                      timeZone: oldTimeZone,
                      datePropsError: oldError,
                      inputStr: oldInputStr
                  } = prevState;
                  let {
                      startTime: newStartTime,
                      datePropsError: contextError,
                      timeZone: newTimeZone,
                      prevSelected,
                      inputStr,
                      parseInput,
                      isValidStr
                  } = this.state;
          
                  if (parseInput && newStartTime.isSame(oldStartTime) == false
                      || oldTimeZone != newTimeZone) {
                      onChange(newStartTime.valueOf(), newTimeZone)
                  }
                  if (contextError != oldError) {
                      onError(contextError)
                  }
                  if (moment(prevSelected[prevSelected.length - 1].startTime)
                      .isSame(newStartTime) == false
                  ) {
                      prevSelected.push({ startTime: newStartTime, endTime: newStartTime })
                  }
                  if (inputStr != oldInputStr) {
          
                      // callback returns unparsed string from text input
                      onInput(inputStr, isValidStr)
                  }
              }
          
              setTime = (start, end, preselected) => {
                  let { startTime, preselectedDateTime } = this.state;
          
                  this.setState({
                      startTime: concatDateTime(
                          startTime,
                          start,
                      ),
                      preselectedDateTime: preselected && typeof preselected != 'undefined'
                          ? preselected : preselectedDateTime
                  })
              }
          
              setDate = (start, end, preselected) => {
                  let { startTime, preselectedDateTime } = this.state;
          
                  this.setState({
                      startTime: concatDateTime(
                          start,
                          startTime,
                      ),
                      preselectedDateTime: preselected && typeof preselected != 'undefined'
                          ? preselected : preselectedDateTime
                  })
              }
          
              setTimeZone = (timeZone) => {
          
                  this.setState({
                      timeZone
                  })
              }
              setPrevSelected = () => {
                  let { prevSelected } = this.state;
          
                  if (prevSelected.length > 1) {
                      prevSelected.pop()
                  }
              }
          
              setHoursOnFocus = (hoursOnFocus) => {
          
                  this.setState({
                      hoursOnFocus
                  })
              }
          
              getInputStr = (inputStr, isValidStr) => {
          
                  this.setState({
                      inputStr,
                      isValidStr
                  })
              }
          
              render() {
                  let { children } = this.props;
          
                  return (
                    <TimeContext.Provider
                          value={this.state}
                      >
                          {children}
                          </TimeContext.Provider>
          
                  )
              }
          }
          
        
Dynamic parsing util
            
              export function dynamicParsing(str, parcing = 2, returnType = "timestamp") {

                // SUPPORTED KEY WORDS
                const dictionary_A = {
                    "now": moment(),
                    "today": moment().startOf('day'),
                    "yesterday": moment().startOf('day').add(-1, 'day'),
                    "yestarday": moment().startOf('day').add(-1, 'day'), // same as "yesterday"
                    "previousday": moment().startOf('day').add(-1, 'day'), // same as "yesterday"
                    "tomorrow": moment().startOf('day').add(1, 'day'),
                    "nextday": moment().startOf('day').add(1, 'day'), // same as "tomorrow"
                    "thisweek": moment().startOf('isoWeek'),
                    "lastweek": moment().startOf('isoWeek').add(-1, 'week'),
                    "nextweek": moment().startOf('isoWeek').add(1, 'week'),
                    "thismonth": moment().startOf('month'),
                    "lastmonth": moment().startOf('month').add(-1, 'month'),
                    "nextmonth": moment().startOf('month').add(1, 'month'),
                    "thisyear": moment().startOf('year'),
                    "lastyear": moment().startOf('year').add(-1, 'year'),
                    "nextyear": moment().startOf('year').add(1, 'year'),
                }
            
                // SUPPORTED PERIOD TYPE KEY WORDS
                const dictionary_B = {
                    "m": "minutes",
                    "minute": "minutes",
                    "minutes": "minutes",
                    "h": "hours",
                    "hour": "hours",
                    "hours": "hours",
                    "d": "days",
                    "day": "days",
                    "days": "days",
                    "w": "weeks",
                    "week": "weeks",
                    "weeks": "weeks",
                    "mon": "months",
                    "month": "months",
                    "months": "months",
                    "y": "years",
                    "year": "years",
                    "years": "years",
                }
            
                let isDateValue = parcing == 2 || parcing == null
            
                // BEFORE PARSING, MAKE SOME CHECKS AND EXIT THE FUNCION IF NECCESSARY
                // ============================================================================
                if (typeof str !== 'string') {
                    // if it's not a string, then it's a timestamp or moment() obj
                    return str // do nothing
                }
                if (str == "") return ""
            
                if (str.split(" ").length < 2 // only 1 word or less
                    && str.match("\\d") !== null // numbers detected
                ) {
                    if (str.match(/[a-zA-Z]/) !== null) { // letters detected
                        // if there are letters mixed with the numbers, it's invalid str
                        return false
                    }
                    else {
                        // if it's a simple date/time value (11/08/2020 or 12:30), do not parse
                        return returnType == "timestamp"
                            ? moment(moment(str, isDateValue && validCalendarFormat || format24Hours).format()).valueOf()
                            : moment(moment(str, isDateValue && validCalendarFormat || format24Hours).format())
                    }
                }
                if (str.split(" ").length < 2 // only 1 word or less
                    // && str.match("\\d") == null // no numbers detected
                ) {
                    // check if string is a valid keyword (from dictionary) before continue parsing
                    if (dictionary_A[str] == null || typeof dictionary_A[str] == 'undefined') {
                        return false
                    }
                }
                // ============================================================================
            
                // remove unnacessary double spaces from the string before parcing
                // we need this to ignore random spaces and still treat the str as valid
                str = str.replace(/\s+/g, " "); // remove spaces in the middle of str
                str = str.trim(); // trim() method > IE8 // remove spaces at the start and end of str
            
                let strParts = str.toLowerCase().split(" ")
                // console.log('strParts ', strParts)
            
                // Correct the string. Turn this (now +1d) into this (now + 1d)
                if (strParts.length == 2) {
                    strParts = [strParts[0], strParts[1].substring(0, 1), strParts[1].substring(1,)]
                }
            
                // Detect additional case when time value is added like this (+ 14:30)
                // if the last word of str contains ":" symbol and it contains numbers,
                // then it must be something like this (14:30) 
                if (strParts[2] && isDateValue == false
                    && strParts[2].indexOf(":") > 0 && strParts[2].match("\\d") !== null) {
            
                    strParts = [
                        strParts[0], strParts[1],
                        `${moment.duration(strParts[2]).asMinutes()}`, // turn 1:20 to 80 minutes
                        'minutes'
                    ]
                }
            
                // Correct the string. Turn this (now +1 d) into this (now + 1 d)
                if (strParts.length == 3 && strParts[2].match("\\d") == null) {
                    strParts =
                        [strParts[0], strParts[1].substring(0, 1), strParts[1].substring(1,), strParts[2]]
                }
            
                // Correct the string and get the values.
                // Turn this (now + 1d) into this (now + 1 d)
                let strSubParts = strParts[2] && strParts[2].match(/[a-z]+|[^a-z]+/gi) || [""];
                let periodAmount = strParts.length > 3 ? strParts[2] : strSubParts[0]
                let periodType = strParts.length > 3
                    ? dictionary_B[strParts[3]] : dictionary_B[strSubParts[1]]
            
                let date = dictionary_A[strParts[0]]
                    || moment(strParts[0], isDateValue && validCalendarFormat || format24Hours).format()
            
                const operators = {
                    "+": parseInt(periodAmount),
                    "-": -parseInt(periodAmount)
                }
                let operator = operators[strParts[1]]
            
                if (strParts.length > 1 && (operators[strParts[1]] == null
                    || typeof operators[strParts[1]] == 'undefined')) {
                    // if string > 1 and no "+" or "-" symbol are present, str is invalid
                    return false
                }
                if (strParts.length > 2 && strParts[2].match("\\d") == null) {
                    // if the 3rd word does not contain numbers, str is invalid
                    return false
                }
                // console.log("date: ", moment(date).format("DD/MM/YYYY HH:mm"))
                // console.log("strSubParts: ", strSubParts)
                // console.log("periodAmount: ", periodAmount)
                // console.log("periodType: ", periodType)
                // console.log("testDate_2: ", moment(date, validCalendarFormat).format())
            
                let result;
                if (strParts.length == 1) {
                    result = moment(dictionary_A[strParts[0]])
                    periodType = true
                }
                else {
                    result = moment(date).add(operator, periodType)
                }
            
                if (result.isValid() && periodType) {
                    // console.log("result1: ", moment(result).format("DD/MM/YYYY HH:mm"))
                    // console.log("result.tz() ", result.tz())
                    // console.log("result2: ", result.valueOf())
            
                    if (returnType == "timestamp") {
                        return result.valueOf()
                    }
                    else {
                        return result
                    }
                }
                else return false
            }
            
          

Back to projects