dateFilter.tsx
TLDR
This file contains a React component called DateFilter
that represents a date filter used in a board view. It allows the user to select a date range and apply the filter to the board view.
Methods
onChange
This method is called when the value of the date filter changes. It adjusts the selected date value based on the time zone offset, updates the filter values, and triggers a view filter change.
getDisplayDate
This method takes a Date
object and returns a formatted string representing the display date.
timeZoneOffset
This method calculates the time zone offset in milliseconds based on a given date.
handleTodayClick
This method is called when the user clicks the "Today" button in the date filter dialog. It sets the selected day to the current day and saves the value.
handleDayClick
This method is called when the user clicks on a specific day in the date filter dialog. It sets the selected day to the clicked day and saves the value.
onClear
This method is called when the user clicks the "Clear" button in the date filter dialog. It clears the selected date value.
saveValue
This method is called to save the selected date value. It triggers the onChange
method and updates the input value.
onClose
This method is called when the date filter dialog is closed. It sets the showDialog
state to false, hiding the dialog.
Classes
None
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useState, useCallback} from 'react'
import {useIntl} from 'react-intl'
import {DateUtils} from 'react-day-picker'
import MomentLocaleUtils from 'react-day-picker/moment'
import DayPicker from 'react-day-picker/DayPicker'
import moment from 'moment'
import mutator from '../../mutator'
import Editable from '../../widgets/editable'
import Button from '../../widgets/buttons/button'
import {BoardView} from '../../blocks/boardView'
import Modal from '../../components/modal'
import ModalWrapper from '../../components/modalWrapper'
import {Utils} from '../../utils'
import 'react-day-picker/lib/style.css'
import './dateFilter.scss'
import {FilterClause} from '../../blocks/filterClause'
import {createFilterGroup} from '../../blocks/filterGroup'
export type DateProperty = {
from?: number
to?: number
includeTime?: boolean
timeZone?: string
}
type Props = {
view: BoardView
filter: FilterClause
}
const loadedLocales: Record<string, moment.Locale> = {}
function DateFilter(props: Props): JSX.Element {
const {filter, view} = props
const [showDialog, setShowDialog] = useState(false)
const filterValue = filter.values
let dateValue: Date | undefined
if (filterValue && filterValue.length > 0) {
dateValue = new Date(parseInt(filterValue[0], 10))
}
const [value, setValue] = useState(dateValue)
const intl = useIntl()
const onChange = useCallback((newValue) => {
if (value !== newValue) {
const adjustedValue = newValue ? new Date(newValue.getTime() - timeZoneOffset(newValue.getTime())) : undefined
setValue(adjustedValue)
const filterIndex = view.fields.filter.filters.indexOf(filter)
Utils.assert(filterIndex >= 0, "Can't find filter")
const filterGroup = createFilterGroup(view.fields.filter)
const newFilter = filterGroup.filters[filterIndex] as FilterClause
Utils.assert(newFilter, `No filter at index ${filterIndex}`)
newFilter.values = []
if (adjustedValue) {
newFilter.values = [adjustedValue.getTime().toString()]
}
mutator.changeViewFilter(view.boardId, view.id, view.fields.filter, filterGroup)
}
}, [value, view.boardId, view.id, view.fields.filter])
const getDisplayDate = (date: Date | null | undefined) => {
let displayDate = ''
if (date) {
displayDate = Utils.displayDate(date, intl)
}
return displayDate
}
const timeZoneOffset = (date: number): number => {
return new Date(date).getTimezoneOffset() * 60 * 1000
}
// Keep date value as UTC, property dates are stored as 12:00 pm UTC
// date will need converted to local time, to ensure date stays consistent
// dateFrom / dateTo will be used for input and calendar dates
const offsetDate = value ? new Date(value.getTime() + timeZoneOffset(value.getTime())) : undefined
const [input, setInput] = useState<string>(getDisplayDate(offsetDate))
const locale = intl.locale.toLowerCase()
if (locale && locale !== 'en' && !loadedLocales[locale]) {
// eslint-disable-next-line global-require
loadedLocales[locale] = require(`moment/locale/${locale}`)
}
const handleTodayClick = (day: Date) => {
day.setHours(12)
saveValue(day)
}
const handleDayClick = (day: Date) => {
saveValue(day)
}
const onClear = () => {
saveValue(undefined)
}
const saveValue = (newValue: Date | undefined) => {
onChange(newValue)
setInput(newValue ? Utils.inputDate(newValue, intl) : '')
}
const onClose = () => {
setShowDialog(false)
}
let displayValue = ''
if (offsetDate) {
displayValue = getDisplayDate(offsetDate)
}
let buttonText = displayValue
if (!buttonText) {
buttonText = intl.formatMessage({id: 'DateFilter.empty', defaultMessage: 'Empty'})
}
const className = 'DateFilter'
return (
<div className={`DateFilter ${displayValue ? '' : 'empty'} `}>
<Button
onClick={() => setShowDialog(true)}
>
{buttonText}
</Button>
{showDialog &&
<ModalWrapper>
<Modal
onClose={() => onClose()}
>
<div
className={className + '-overlayWrapper'}
>
<div className={className + '-overlay'}>
<div className={'inputContainer'}>
<Editable
value={input}
placeholderText={moment.localeData(locale).longDateFormat('L')}
onFocus={() => {
if (offsetDate) {
return setInput(Utils.inputDate(offsetDate, intl))
}
return undefined
}}
onChange={setInput}
onSave={() => {
const newDate = MomentLocaleUtils.parseDate(input, 'L', intl.locale)
if (newDate && DateUtils.isDate(newDate)) {
newDate.setHours(12)
saveValue(newDate)
} else {
setInput(getDisplayDate(offsetDate))
}
}}
onCancel={() => {
setInput(getDisplayDate(offsetDate))
}}
/>
</div>
<DayPicker
onDayClick={handleDayClick}
initialMonth={offsetDate || new Date()}
showOutsideDays={false}
locale={locale}
localeUtils={MomentLocaleUtils}
todayButton={intl.formatMessage({id: 'DateRange.today', defaultMessage: 'Today'})}
onTodayButtonClick={handleTodayClick}
month={offsetDate}
selectedDays={offsetDate}
/>
<hr/>
<div
className='MenuOption menu-option'
>
<Button
onClick={onClear}
>
{intl.formatMessage({id: 'DateRange.clear', defaultMessage: 'Clear'})}
</Button>
</div>
</div>
</div>
</Modal>
</ModalWrapper>
}
</div>
)
}
export default DateFilter