Skip to content

Commit

Permalink
Merge branch 'dev' into prevent-nearby-constant-updates
Browse files Browse the repository at this point in the history
  • Loading branch information
amy-corson-ibigroup authored Dec 17, 2024
2 parents 87e1ac8 + 20a7092 commit 5dadd96
Showing 1 changed file with 111 additions and 86 deletions.
197 changes: 111 additions & 86 deletions lib/components/form/call-taker/date-time-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import { IntlShape, useIntl } from 'react-intl'
import { isMatch, parse } from 'date-fns'
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
import coreUtils from '@opentripplanner/core-utils'
import React, { useEffect, useRef, useState } from 'react'
import React, {
ChangeEvent,
useCallback,
useEffect,
useRef,
useState
} from 'react'

import { AppReduxState, FilterType, SortType } from '../../../util/state-types'
import { DepartArriveTypeMap, DepartArriveValue } from '../date-time-modal'
Expand Down Expand Up @@ -69,6 +75,30 @@ const safeFormat = (date: Date | '', time: string, options?: OptionsWithTZ) => {
}
return ''
}
/**
* Parse a time input expressed in the agency time zone.
* @returns A date if the parsing succeeded, or null.
*/
const parseInputAsTime = (
homeTimezone: string,
timeInput: string = getCurrentTime(homeTimezone),
date: string = getCurrentDate(homeTimezone)
) => {
if (!timeInput) timeInput = getCurrentTime(homeTimezone)

// Match one of the supported time formats
const matchedTimeFormat = SUPPORTED_TIME_FORMATS.find((timeFormat) =>
isMatch(timeInput, timeFormat)
)
if (matchedTimeFormat) {
const resolvedDateTime = format(
parse(timeInput, matchedTimeFormat, new Date()),
'HH:mm:ss'
)
return toDate(`${date}T${resolvedDateTime}`)
}
return ''
}

type Props = {
date?: string
Expand Down Expand Up @@ -121,69 +151,36 @@ const DateTimeOptions = ({
)
const [date, setDate] = useState<string | undefined>(initialDate)
const [time, setTime] = useState<string | undefined>(initialTime)
const [typedTime, setTypedTime] = useState<string | undefined>(initialTime)
const [typedTime, setTypedTime] = useState<string | undefined>(
safeFormat(parseInputAsTime(homeTimezone, time, date), timeFormat, {
timeZone: homeTimezone
})
)

const timeRef = useRef(null)

const intl = useIntl()

/**
* Parse a time input expressed in the agency time zone.
* @returns A date if the parsing succeeded, or null.
*/
const parseInputAsTime = (
timeInput: string = getCurrentTime(homeTimezone),
date: string = getCurrentDate(homeTimezone)
) => {
if (!timeInput) timeInput = getCurrentTime(homeTimezone)

// Match one of the supported time formats
const matchedTimeFormat = SUPPORTED_TIME_FORMATS.find((timeFormat) =>
isMatch(timeInput, timeFormat)
)
if (matchedTimeFormat) {
const resolvedDateTime = format(
parse(timeInput, matchedTimeFormat, new Date()),
'HH:mm:ss'
)
return toDate(`${date}T${resolvedDateTime}`)
}
return ''
}

const dateTime = parseInputAsTime(time, date)
const dateTime = parseInputAsTime(homeTimezone, time, date)

// Update state when external state is updated
useEffect(() => {
if (initialDate !== date) setDate(initialDate)
if (initialTime !== time) {
setTime(initialTime)
handleTimeChange(initialTime || '')
}
// This effect is design to flow from state to component only
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialTime, initialDate])

useEffect(() => {
// Don't update if still typing
if (timeRef.current !== document.activeElement) {
setTypedTime(
safeFormat(dateTime, timeFormat, {
timeZone: homeTimezone
}) ||
// TODO: there doesn't seem to be an intl object present?
'Invalid Time'
)
}
}, [time])

useEffect(() => {
if (initialDepartArrive && departArrive !== initialDepartArrive) {
setDepartArrive(initialDepartArrive)
}
// This effect is design to flow from state to component only
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialDepartArrive])

useEffect(() => {
if (departArrive === 'NOW') setTypedTime('')
}, [departArrive])

// Handler for setting the query parameters
useEffect(() => {
if (safeFormat(dateTime, OTP_API_DATE_FORMAT, {}) !== '' && setQueryParam) {
Expand All @@ -197,42 +194,64 @@ const DateTimeOptions = ({
})
})
}

if (
syncSortWithDepartArrive &&
DepartArriveTypeMap[departArrive] !== sort.type
) {
importedUpdateItineraryFilter({
sort: {
...sort,
type: DepartArriveTypeMap[departArrive]
}
})
}
}, [dateTime, departArrive, homeTimezone, setQueryParam])

// Handler for updating the time and date fields when NOW is selected
useEffect(() => {
if (departArrive === 'NOW') {
setTime(getCurrentTime(homeTimezone))
setDate(getCurrentDate(homeTimezone))
setTypedTime(
safeFormat(dateTime, timeFormat, {
timeZone: homeTimezone
const handleDepartArriveChange = useCallback(
(e: ChangeEvent<HTMLSelectElement>) => {
const newValue = e.target.value as DepartArriveValue
setDepartArrive(newValue)

// Handler for updating the time and date fields when NOW is selected
if (newValue === 'NOW') {
handleTimeChange(getCurrentTime(homeTimezone))
setDate(getCurrentDate(homeTimezone))
setTypedTime(
safeFormat(dateTime, timeFormat, {
timeZone: homeTimezone
})
)
}

// Update sort type if needed
if (
syncSortWithDepartArrive &&
DepartArriveTypeMap[newValue] !== sort.type
) {
importedUpdateItineraryFilter({
sort: {
...sort,
type: DepartArriveTypeMap[newValue]
}
})
)
}
}, [departArrive, setTime, setDate, homeTimezone])
}
},
[syncSortWithDepartArrive, sort, importedUpdateItineraryFilter]
)

const unsetNow = () => {
const unsetNow = useCallback(() => {
if (departArrive === 'NOW') setDepartArrive('DEPART')
}
}, [departArrive])

const handleTimeChange = useCallback(
(newTime: string) => {
setTime(newTime)
// Only update typed time if not actively typing
if (timeRef.current !== document.activeElement) {
setTypedTime(
safeFormat(dateTime, timeFormat, {
timeZone: homeTimezone
}) || 'Invalid Time'
)
}
},
[dateTime, timeFormat, homeTimezone]
)

return (
<>
<select
onBlur={(e) => setDepartArrive(e.target.value as DepartArriveValue)}
onChange={(e) => setDepartArrive(e.target.value as DepartArriveValue)}
onBlur={handleDepartArriveChange}
onChange={handleDepartArriveChange}
onKeyDown={onKeyDown}
value={departArrive}
>
Expand All @@ -257,11 +276,14 @@ const DateTimeOptions = ({
>
<input
className="datetime-slim"
onChange={(e) => {
setTime(e.target.value)
setTypedTime(e.target.value)
unsetNow()
}}
onChange={useCallback(
(e) => {
handleTimeChange(e.target.value)
setTypedTime(e.target.value)
unsetNow()
},
[handleTimeChange, setTypedTime, unsetNow]
)}
onFocus={(e) => e.target.select()}
onKeyDown={onKeyDown}
ref={timeRef}
Expand All @@ -278,15 +300,18 @@ const DateTimeOptions = ({
<input
className="datetime-slim"
disabled={!dateTime}
onChange={(e) => {
if (!e.target.value) {
e.preventDefault()
// TODO: prevent selection from advancing to next field
return
}
setDate(e.target.value)
unsetNow()
}}
onChange={useCallback(
(e) => {
if (!e.target.value) {
e.preventDefault()
// TODO: prevent selection from advancing to next field
return
}
setDate(e.target.value)
unsetNow()
},
[unsetNow, setDate]
)}
onKeyDown={onKeyDown}
style={{
fontSize: '14px',
Expand Down

0 comments on commit 5dadd96

Please sign in to comment.