Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 83 additions & 12 deletions src/components/EngagementEditor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,34 @@ const EngagementEditor = ({
onDelete,
resolvedAssignedMembers
}) => {
const timeZoneOptions = useMemo(() => {
const zones = moment.tz.names().map(zone => ({
label: formatTimeZoneLabel(zone),
value: zone
}))
return [ANY_OPTION, ...zones]
const { timeZoneOptions, timeZoneOptionByZone } = useMemo(() => {
const optionByLabel = new Map()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ design]
The use of Map for optionByLabel is appropriate for ensuring unique labels, but the conversion of labels to lowercase (normalizedLabel) might lead to unexpected behavior if labels are case-sensitive or if different labels are intended to be distinct. Consider whether case-insensitivity is necessary and document this decision.

moment.tz.names().forEach((zone) => {
const label = formatTimeZoneLabel(zone)
if (!label) {
return
}
const normalizedLabel = label.toLowerCase()
let option = optionByLabel.get(normalizedLabel)
if (!option) {
option = { label, value: label, zones: [] }
optionByLabel.set(normalizedLabel, option)
}
option.zones.push(zone)
})

const options = Array.from(optionByLabel.values())
const optionByZone = new Map()
options.forEach((option) => {
option.zones.forEach((zone) => {
optionByZone.set(zone, option)
})
})

return {
timeZoneOptions: [ANY_OPTION, ...options],
timeZoneOptionByZone: optionByZone
}
}, [])

const countryOptions = useMemo(() => {
Expand All @@ -111,11 +133,38 @@ const EngagementEditor = ({
}, {})
}, [countryOptions])

const selectedTimeZones = (engagement.timezones || []).map(zone => (
zone === ANY_OPTION.value
? ANY_OPTION
: { label: formatTimeZoneLabel(zone), value: zone }
))
const selectedTimeZones = useMemo(() => {
const timeZones = Array.isArray(engagement.timezones) ? engagement.timezones : []
if (timeZones.includes(ANY_OPTION.value)) {
return [ANY_OPTION]
}

const selected = []
const seen = new Set()
timeZones.forEach((zone) => {
const option = timeZoneOptionByZone.get(zone)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The selectedTimeZones logic assumes that timeZoneOptionByZone will always have a mapping for each zone. If formatTimeZoneLabel returns a falsy value or if a zone is not found in timeZoneOptionByZone, it might lead to unexpected results. Ensure that all possible zone values are covered or handle the case where a zone is not found.

if (option) {
const key = option.value.toLowerCase()
if (!seen.has(key)) {
seen.add(key)
selected.push(option)
}
return
}
const label = formatTimeZoneLabel(zone)
if (!label) {
return
}
const normalizedLabel = label.toLowerCase()
if (seen.has(normalizedLabel)) {
return
}
seen.add(normalizedLabel)
selected.push({ label, value: label, zones: [zone] })
})

return selected
}, [engagement.timezones, timeZoneOptionByZone])

const selectedCountries = (engagement.countries || []).map(code => {
return countryOptionsByValue[code] || { label: code, value: code }
Expand Down Expand Up @@ -418,10 +467,32 @@ const EngagementEditor = ({
value={selectedTimeZones}
onChange={(values) => {
const normalized = normalizeAnySelection(values)
const timezones = []
const seen = new Set()
normalized.forEach((option) => {
if (!option) {
return
}
if (option.value === ANY_OPTION.value) {
timezones.length = 0
timezones.push(ANY_OPTION.value)
return
}
if (!Array.isArray(option.zones) || option.zones.length === 0) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The check !Array.isArray(option.zones) || option.zones.length === 0 is used to skip options without zones. Ensure that all valid options have non-empty zones arrays, or handle cases where zones might be empty to avoid potential issues.

return
}
option.zones.forEach((zone) => {
if (seen.has(zone)) {
return
}
seen.add(zone)
timezones.push(zone)
})
})
onUpdateInput({
target: {
name: 'timezones',
value: normalized.map(option => option.value)
value: timezones
}
})
}}
Expand Down
13 changes: 12 additions & 1 deletion src/util/timezones.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,16 @@ export const formatTimeZoneList = (timeZones, fallback = 'Any') => {
.map(zone => formatTimeZoneLabel(zone))
.filter(Boolean)

return labels.length ? labels.join(', ') : fallback
const uniqueLabels = []
const seenLabels = new Set()
labels.forEach((label) => {
const normalized = label.toLowerCase()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ correctness]
Consider using label.trim().toLowerCase() to normalize labels, as this will handle cases where labels have leading or trailing whitespace differences.

if (seenLabels.has(normalized)) {
return
}
seenLabels.add(normalized)
uniqueLabels.push(label)
})

return uniqueLabels.length ? uniqueLabels.join(', ') : fallback
}
Loading