Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2808: Add a filter by date option to events #2898

Open
wants to merge 52 commits into
base: main
Choose a base branch
from

Conversation

bahaaTuffaha
Copy link
Contributor

Short description

Filtering events by date

Proposed changes

For web:

  • Created a component called CustomDatePicker and used Input type='date' as date picker.

For native:

  • Created a component called CustomDatePicker used react-native TextInput and IconButton to show calendar modal.
  • Another component I added is for Calender Range picker called CalendarRangeModal I used react-native-calendars library and tried to mimic the styling like the design in figma.

Shared:

  • Created a hook called useDateFilter that accepts events then will validate and returns filteredEvents.

Side effects

None.

Resolved issues

Fixes: #2808


Copy link
Member

@steffenkleinle steffenkleinle left a comment

Choose a reason for hiding this comment

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

Hey, thank you for your PR, looks pretty nice already! Nice work 🎉

I just reviewed and tested native so far and everything works and looks as expected. I will review web once the comments have been addressed (I think the PR is quite huge, perhaps it would be better to merge it one by one and split up the PR, if possible).

I have a few remarks regarding coding style and complexity. If you feel like I misunderstood something or your solution makes more sense, please tell.

General changes that would be a good addition:

  • Use DateTime to avoid manual parsing and formatting as well as validating
  • Clicking next to the date picker could/should close it for better UX
  • If you delete a slash in the date picker, you can't readd it (since the keyboard only shows numbers). A better choice would perhaps be to implement fields for day, month, and year separately, so you can only edit either one at a time. Not sure if this is an optimal solution though, so if you have any other ideas let me know.

Keep in mind that I did not mention every occurrence of things that could be improved, please apply the suggestions to other parts in the code, too.

web/tsconfig.json Outdated Show resolved Hide resolved
assets/icons/calendar-events.svg Outdated Show resolved Hide resolved
assets/icons/expand.svg Outdated Show resolved Hide resolved
assets/icons/shrink.svg Outdated Show resolved Hide resolved
native/src/components/CalendarRangeModal.tsx Outdated Show resolved Hide resolved
translations/translations.json Outdated Show resolved Hide resolved
native/src/utils/calendarRangeUtils.ts Outdated Show resolved Hide resolved
native/src/utils/calendarRangeUtils.ts Outdated Show resolved Hide resolved
shared/hooks/useDateFilter.ts Outdated Show resolved Hide resolved
native/src/components/CalendarRangeModal.tsx Outdated Show resolved Hide resolved
native/src/components/CalendarRangeModal.tsx Outdated Show resolved Hide resolved
native/src/components/CalendarRangeModal.tsx Outdated Show resolved Hide resolved
native/src/components/CalendarRangeModal.tsx Outdated Show resolved Hide resolved
native/src/components/CalendarRangeModal.tsx Outdated Show resolved Hide resolved
shared/hooks/useDateFilter.ts Outdated Show resolved Hide resolved
shared/hooks/useDateFilter.ts Outdated Show resolved Hide resolved
translations/translations.json Show resolved Hide resolved
web/src/components/FilterToggle.tsx Show resolved Hide resolved
web/src/components/FilterToggle.tsx Outdated Show resolved Hide resolved
native/src/components/DatePicker.tsx Outdated Show resolved Hide resolved
native/src/components/EventsDateFilter.tsx Outdated Show resolved Hide resolved
native/src/components/EventsDateFilter.tsx Outdated Show resolved Hide resolved
web/src/components/DatePicker.tsx Outdated Show resolved Hide resolved
web/src/components/EventsDateFilter.tsx Outdated Show resolved Hide resolved
web/src/components/EventsDateFilter.tsx Outdated Show resolved Hide resolved
web/src/components/EventsDateFilter.tsx Outdated Show resolved Hide resolved
@steffenkleinle
Copy link
Member

steffenkleinle commented Sep 12, 2024

Some thoughts on the behavior:

  • My personal opinion is that if I never selected a time period, no filtering should be done and all events should be shown. Only if I manually set a start and end date, I want the events to be filtered.
  • Usually, confirming actions, i.e. ok in this case, are placed on the right side while `cancel´ should be placed on the left side of the screen (at least for LTR languages): https://m2.material.io/components/dialogs#actions. This is also the case in the designs.
  • If an invalid date range is selected with the date picker (i.e. no start date, no end date, end date before start date) it should not be possible to press ok (as nothing will happen anyway and the user will be confused about that). Instead the button should be disabled, greyed out.
  • If a date is selected that is before the current fromDate (and if set, the toDate), it should be set as new fromDate and toDate should be cleared. Currently, if only a fromDate is selected, the following happens (see screenshot)

One more general question:
Is there any reason you chose the names fromDate and toDate over e.g. startDate and endDate?

@steffenkleinle
Copy link
Member

Sadly I just realized we have another problem. A lot of our events are recurring, i.e. happening multiple times, for example every week. Our filtering should reflect that and include events that have recurrences in the selected time period. This might not be that easy, so perhaps we should do that in a separate PR. Has to be done before this is good to go though sadly :/

@bahaaTuffaha
Copy link
Contributor Author

Sadly I just realized we have another problem. A lot of our events are recurring, i.e. happening multiple times, for example every week. Our filtering should reflect that and include events that have recurrences in the selected time period. This might not be that easy, so perhaps we should do that in a separate PR. Has to be done before this is good to go though sadly :/

I think it can be done by modifying the filter maybe using rrule.between() event.date has recurrenceRule type RRuleType | null

@bahaaTuffaha bahaaTuffaha marked this pull request as ready for review September 13, 2024 11:11
Copy link
Contributor

@LeandraH LeandraH left a comment

Choose a reason for hiding this comment

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

Nice work, we're getting there! I have a few more general comments:

  1. As you already noticed, you haven't dealt with the animation yet, and I think it would also be nice to have one in the web.
  2. While I agree that we should show recurring events in the filtered list, we definitely need to show them with the date that is within that time frame, or show in some other way why these events are shown even though the filter should be hiding them.
  3. In native, I still find the interactions in the CalendarRangeModal confusing. My expectations from the way other date pickers work would be that the calendar closes when I chose an end date. There are a few other behaviors that I don't expect but I think they would all be covered by closing the calendar when the second date is selected.
  4. I like your solution for dealing with someone opening an input field a second time, very nice! But I think we still need a better solution for people putting in invalid formats. I think we should let them enter what they want and validate on blur. I would expect a number that is too high to be turned into the maximum possible number, and otherwise invalid dates to either be blanked or get a red outline and an explaining text. I guess an error would be ideal but it’s also more work to add an error behavior for something that, realistically speaking, won’t be happening all that much.

native/src/components/CalendarRangeModal.tsx Outdated Show resolved Hide resolved
native/src/components/CalendarRangeModal.tsx Outdated Show resolved Hide resolved
native/src/components/CalendarRangeModal.tsx Outdated Show resolved Hide resolved
native/src/components/CalendarRangeModal.tsx Outdated Show resolved Hide resolved
Comment on lines +111 to +112
setTempStartDate(startDate)
setTempEndDate(endDate)
Copy link
Contributor

Choose a reason for hiding this comment

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

❓ Why are you setting these temporary values as you're closing the modal? Wouldn't they be filled in correctly anyway with the next opening?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Canceling means putting back old values for calendarModal. When you open the modal again it should show what being selected at inputs.

Copy link
Contributor

Choose a reason for hiding this comment

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

But those are props that are handed in when opening the modal. Have you tested if it works without this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I test it without ... If I selected a date and pressed cancel and opened the calendar again it will show the same date I selected before canceling. so I think these are needed to get the previous value back

web/src/components/EventsDateFilter.tsx Outdated Show resolved Hide resolved
web/src/components/EventsDateFilter.tsx Outdated Show resolved Hide resolved
web/src/components/EventsDateFilter.tsx Outdated Show resolved Hide resolved
web/src/components/FilterToggle.tsx Outdated Show resolved Hide resolved
web/src/components/FilterToggle.tsx Outdated Show resolved Hide resolved
Copy link
Member

@ztefanie ztefanie left a comment

Choose a reason for hiding this comment

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

Looks good. Tested on Android, works as expected

Can you please add release notes to release-notes/unreleased folder.

Can you please squash the commits?

web/src/components/DatePicker.tsx Outdated Show resolved Hide resolved
native/src/components/Accordion.tsx Outdated Show resolved Hide resolved
native/src/components/Accordion.tsx Outdated Show resolved Hide resolved
@bahaaTuffaha
Copy link
Contributor Author

Looks good. Tested on Android, works as expected

Can you please add release notes to release-notes/unreleased folder.

Can you please squash the commits?

I already added one : Release note

Squashing into one commit?

Copy link
Member

@ztefanie ztefanie left a comment

Choose a reason for hiding this comment

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

Thanks for this PR and this new feature. I tested this again on web and everything works as expected.

Good work for staying patient and implement all the requested changes 🚀
Next time it would be easier for you and reviewers to split up PRs in multiple smaller ones.

@ztefanie
Copy link
Member

Squashing into one commit?

Yes, please

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: OK

Change in average Code Health of affected files: -0.00 (9.84 -> 9.84)

  • Declining Code Health: 5 findings(s) 🚩

View detailed results in CodeScene

Comment on lines 11 to 60
export const getMarkedDates = (
startDate: DateTime | null,
endDate: DateTime | null,
theme: ThemeType,
currentInput: string,
): Record<string, MarkedDateType> => {
const markedDateStyling = {
color: theme.colors.themeColor,
textColor: theme.colors.textColor,
}

const markedDates: Record<string, MarkedDateType> = {}

const cutoffStartDate = DateTime.now().minus({ years: 2 })
const cutoffEndDate = DateTime.now().plus({ years: 2 })

if (!startDate || startDate < cutoffStartDate || (endDate && endDate > cutoffEndDate)) {
return {}
}

if (currentInput === 'to' && endDate === null) {
markedDates[startDate.toISODate()] = {
selected: true,
startingDay: false,
endingDay: true,
...markedDateStyling,
}
} else if (currentInput === 'from' && endDate === null) {
markedDates[startDate.toISODate()] = {
selected: true,
startingDay: true,
endingDay: false,
...markedDateStyling,
}
} else {
let runningDate = startDate
const safeEndDate = endDate ?? startDate
while (runningDate <= safeEndDate) {
markedDates[runningDate.toISODate()] = {
selected: true,
startingDay: startDate.equals(runningDate),
endingDay: safeEndDate.equals(runningDate),
...markedDateStyling,
}
runningDate = runningDate.plus({ days: 1 })
}
}

return markedDates
}

Choose a reason for hiding this comment

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

❌ New issue: Complex Method
getMarkedDates has a cyclomatic complexity of 10, threshold = 9

Suppress

native/src/utils/calendarRangeUtils.ts Outdated Show resolved Hide resolved
Comment on lines 69 to 151
const DatePicker = ({ title, date, setDate, error, modalOpen, setModalOpen }: DatePickerProps): ReactElement => {
const { t } = useTranslation('events')
const [inputDay, setInputDay] = useState(date?.toFormat('dd'))
const [inputMonth, setInputMonth] = useState(date?.toFormat('MM'))
const [inputYear, setInputYear] = useState(date?.toFormat('yyyy'))
const [datePickerError, setDatePickerError] = useState('')
const dayRef = useRef<TextInput>(null)
const monthRef = useRef<TextInput>(null)
const yearRef = useRef<TextInput>(null)

useEffect(() => {
try {
setDatePickerError('')
setDate(DateTime.fromISO(`${inputYear}-${inputMonth}-${inputDay}`))
} catch (e) {
// This will detect out of range for days
if (!String(e).includes('ISO')) {
setDatePickerError(String(e).replace('Error: ', ''))
}
}
}, [inputDay, inputMonth, inputYear, setDate])

useEffect(() => {
if (date) {
setInputDay(date.toFormat('dd'))
setInputMonth(date.toFormat('MM'))
setInputYear(date.toFormat('yyyy'))
} else {
setInputDay('')
setInputMonth('')
setInputYear('')
}
}, [date])

return (
<DateContainer>
<StyledTitle>{title}</StyledTitle>
<StyledInputWrapper>
<Wrapper>
<DatePickerInput
ref={dayRef}
placeholder={t('dd')}
nextTargetRef={monthRef}
inputValue={inputDay}
setInputValue={setInputDay}
type='day'
/>
<Text>.</Text>
<DatePickerInput
ref={monthRef}
placeholder={t('mm')}
nextTargetRef={yearRef}
prevTargetRef={dayRef}
inputValue={inputMonth}
setInputValue={setInputMonth}
type='month'
/>
<Text>.</Text>
<DatePickerInput
style={{ marginLeft: 6 }}
ref={yearRef}
prevTargetRef={monthRef}
placeholder={t('yyyy')}
inputValue={inputYear}
setInputValue={setInputYear}
type='year'
/>
</Wrapper>
<StyledIconButton
$isModalOpen={modalOpen}
icon={<Icon Icon={CalendarTodayIcon} />}
accessibilityLabel={t('common:openCalendar')}
onPress={() => {
setModalOpen(true)
}}
/>
</StyledInputWrapper>
<View style={{ width: '80%' }}>
{!!(error || datePickerError) && <StyledError>{error || datePickerError}</StyledError>}
</View>
</DateContainer>
)
}

Choose a reason for hiding this comment

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

❌ New issue: Complex Method
DatePicker has a cyclomatic complexity of 10, threshold = 10

Suppress

Comment on lines +24 to +35
it('should render the given props correctly', () => {
const { getByText } = renderCustomDatePicker({
modalOpen: false,
setModalOpen,
setDate,
title: 'Test DatePicker',
date: DateTime.now(),
error: '',
})

expect(getByText('Test DatePicker')).toBeTruthy()
})

Choose a reason for hiding this comment

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

❌ New issue: Code Duplication
The module contains 5 functions with similar structure: 'should format the day with leading zero on blur','should not allow day greater than 31','should not allow month greater than 12','should render the given props correctly' and 1 more functions

Suppress

Comment on lines +34 to +45
it('should return true when event is within the date range', () => {
const event = createEvent(
1,
now.minus({ days: 1 }), // 2024-11-06
now.plus({ days: 1 }), // 2024-11-08
)
const startDate = now.minus({ days: 2 }) // 2024-11-05
const endDate = now.plus({ days: 2 }) // 2024-11-09

const result = isEventWithinRange(event, startDate, endDate)
expect(result).toBe(true)
})

Choose a reason for hiding this comment

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

❌ New issue: Code Duplication
The module contains 5 functions with similar structure: 'should return false when event is outside the date range','should return true when event ends exactly on the endDate','should return true when event has future recurrences within the date range','should return true when event is within the date range' and 1 more functions

Suppress

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: OK

Change in average Code Health of affected files: -0.00 (9.84 -> 9.84)

  • Declining Code Health: 5 findings(s) 🚩

View detailed results in CodeScene

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: OK

Change in average Code Health of affected files: +0.33 (9.20 -> 9.53)

  • Declining Code Health: 5 findings(s) 🚩

  • Affected Hotspots: 1 files(s) 🔥

View detailed results in CodeScene

@@ -570,4 +570,101 @@ describe('DateModel', () => {
])
})
})

Choose a reason for hiding this comment

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

ℹ Getting worse: Code Duplication
introduced similar code in: 'should return recurrences starting from filterStartDate','should return recurrences up to filterEndDate','should return recurrences within the filter date range'

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: OK

Change in average Code Health of affected files: +0.33 (9.20 -> 9.53)

  • Declining Code Health: 5 findings(s) 🚩

  • Affected Hotspots: 1 files(s) 🔥

View detailed results in CodeScene

Copy link
Contributor

@LeandraH LeandraH left a comment

Choose a reason for hiding this comment

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

This looks very good already, thank you for slogging through!

Since we would like to get this live before the winter break, I went with the less pedagogic approach of telling you what exactly to correct instead of trying to lead you there, sorry!

native/src/components/Accordion.tsx Show resolved Hide resolved
native/src/components/Accordion.tsx Outdated Show resolved Hide resolved
native/src/components/CalendarRangeModal.tsx Outdated Show resolved Hide resolved
native/src/components/CalendarRangeModal.tsx Outdated Show resolved Hide resolved
native/src/components/CalendarRangeModal.tsx Outdated Show resolved Hide resolved
native/src/components/CalendarRangeModal.tsx Outdated Show resolved Hide resolved
native/src/components/EventsDateFilter.tsx Outdated Show resolved Hide resolved
native/src/components/EventsDateFilter.tsx Show resolved Hide resolved
try {
setDate(DateTime.fromISO(event.target.value))
} catch (_) {
// Invalid date format, do not update the state
Copy link
Contributor

Choose a reason for hiding this comment

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

❌ We need to handle this error.
image

web/src/components/DatePicker.tsx Outdated Show resolved Hide resolved
@LeandraH
Copy link
Contributor

LeandraH commented Dec 3, 2024

Oh, one more thing that I forgot to mention earlier, sorry: the date in web is still shown as 01/01/1990 instead of 01.01.1990

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a filter by date option to events
5 participants