import { DateRangeFilterMode, DateRangePreset, Filter, getDatesFromPreset, RangeItem, ToggleState, Translate, } from '@wix/bex-core'; import { action, makeObservable, observable, reaction } from 'mobx'; import { EventEmitter } from '@wix/bex-core/events'; import { FilterToolbarState } from '../providers'; export interface DateRangePickerStateParams { toolbar: FilterToolbarState; translate: Translate; filter: Filter>; filterByTime?: boolean; mode?: DateRangeFilterMode; } export class DateRangePickerState { readonly translate: DateRangePickerStateParams['translate']; readonly _filter: DateRangePickerStateParams['filter']; readonly dropdown = new ToggleState(); readonly events = new EventEmitter(); readonly toolbar; from: Date | null = null; to: Date | null = null; isFromTimeSet: boolean = false; isToTimeSet: boolean = false; _mode; _selectedPreset: string = DateRangePreset.NONE; filterByTime: boolean; get filter() { return this.toolbar.getPendingFilter(this._filter); } set selectedPreset(preset: string) { this._selectedPreset = this._mode === 'ONLY_CUSTOM' ? DateRangePreset.CUSTOM : preset; } get selectedPreset() { return this._mode === 'ONLY_CUSTOM' ? DateRangePreset.CUSTOM : this._selectedPreset; } constructor(params: DateRangePickerStateParams) { this.toolbar = params.toolbar; this.translate = params.translate; this._filter = params.filter; this.filterByTime = params.filterByTime ?? false; this._mode = params.mode; makeObservable(this, { from: observable, to: observable, isFromTimeSet: observable.ref, isToTimeSet: observable.ref, _selectedPreset: observable, init: action.bound, clear: action.bound, onFromSelected: action.bound, onToSelected: action.bound, onFromTimeSelected: action.bound, onToTimeSelected: action.bound, onPresetChanged: action.bound, }); } clearTime() { this.isFromTimeSet = false; this.isToTimeSet = false; } clear() { this.from = null; this.to = null; this.clearTime(); this.refreshIfChanged({ actionType: 'remove' }); } remove() { this.selectedPreset = DateRangePreset.NONE; this.clear(); } private convertToBIFormat(from: Date | null, to: Date | null) { return `${this.filter.itemKey({ from, to })}, ${this.selectedPreset}`; } private refreshIfChanged( options: { actionType?: 'add' | 'remove'; clickedValueKey?: string } = {}, ) { const { from, to, filter } = this; if (filter.itemKey(filter.value) !== filter.itemKey({ from, to })) { filter.setValue( { from, to, preset: this.selectedPreset, }, { emitEvents: ['change', 'beforeRefresh', 'refresh'], ...options, }, ); return true; } return false; } close() { const { dropdown, events } = this; dropdown.setOff(); events.emit('close'); } init() { const { filter } = this; const disposer = reaction( () => filter.value, () => { if (!filter.value.from && !filter.value.to) { if ( filter.value.preset && filter.value.preset !== DateRangePreset.CUSTOM && filter.value.preset !== DateRangePreset.NONE ) { this.onPresetChanged(filter.value.preset); return; } filter.value.preset ? this.clear() : this.remove(); return; } if ( filter.value.from?.valueOf() === this.from?.valueOf() && filter.value.to?.valueOf() === this.to?.valueOf() ) { return; } if ( filter.value.preset && filter.value.preset !== DateRangePreset.CUSTOM ) { this.onPresetChanged(filter.value.preset); return; } this.from = filter.value.from; this.to = filter.value.to; this.selectedPreset = DateRangePreset.CUSTOM; this.isFromTimeSet = !!this.from; this.isToTimeSet = !!this.to; }, { fireImmediately: true, }, ); return () => { disposer(); }; } onPresetChanged(newPreset: string) { this.selectedPreset = newPreset; if (this.selectedPreset === DateRangePreset.CUSTOM) { if (!this.from && !this.to) { return; } this.filter.setValue( { from: this.from, to: this.to, preset: this.selectedPreset, }, { emitEvents: ['change'], }, ); return; } if (this.selectedPreset === DateRangePreset.NONE) { return this.clear(); } const { from, to } = getDatesFromPreset(this.selectedPreset); this.from = from; this.to = to; this.isFromTimeSet = true; this.isToTimeSet = true; this.refreshIfChanged({ actionType: 'add', clickedValueKey: this.convertToBIFormat(from, to), }); } onDateSelected( newDate: Date | null, curDate: Date | null, isInvalid: (date: Date) => boolean, isTimeSet: boolean, type: 'from' | 'to', ) { if (!newDate) { return { date: null, isTimeSet: false }; } const timeDate = new Date( type === 'from' ? newDate.setHours(0, 0, 0, 0) : newDate.setHours(23, 59, 59, 999), ); // takes the current selected date, and copies its time selection to the new selected date const resDate = curDate ? new Date(newDate.setHours(curDate.getHours(), curDate.getMinutes())) : timeDate; if (isInvalid(resDate)) { return { date: timeDate, isTimeSet: false }; } return { date: resDate, isTimeSet }; } onFromSelected(from: Date | null) { const { isTimeSet, date } = this.onDateSelected( from, this.from, (d) => !!(this.to && d > this.to), this.isFromTimeSet, 'from', ); this.from = date; this.isFromTimeSet = isTimeSet; } onToSelected(to: Date | null) { const { isTimeSet, date } = this.onDateSelected( to, this.to, (d) => !!(this.from && d < this.from), this.isToTimeSet, 'to', ); this.to = date; this.isToTimeSet = isTimeSet; } onTimeSelected(newTime: Date | null, curDate: Date) { const fromCpy = new Date(curDate); return { isTimeSet: !!newTime, date: new Date( newTime ? fromCpy.setHours(newTime.getHours(), newTime.getMinutes()) : fromCpy.setHours(0, 0), ), }; } onFromTimeSelected(fromTime: Date | null) { if (!this.from) { return; } const { isTimeSet, date } = this.onTimeSelected(fromTime, this.from); this.isFromTimeSet = isTimeSet; this.from = date; this.refreshIfChanged({ actionType: 'add', clickedValueKey: this.convertToBIFormat(this.from, this.to), }); } onToTimeSelected(toTime: Date | null) { if (!this.to) { return; } const { isTimeSet, date } = this.onTimeSelected(toTime, this.to); this.isToTimeSet = isTimeSet; this.to = date; this.refreshIfChanged({ actionType: 'add', clickedValueKey: this.convertToBIFormat(this.from, this.to), }); } onFromClosed() { this.refreshIfChanged({ actionType: 'add', clickedValueKey: this.convertToBIFormat(this.from, this.to), }); } onToClosed() { this.refreshIfChanged({ actionType: 'add', clickedValueKey: this.convertToBIFormat(this.from, this.to), }); } static _isBiggerOrSameDate(date1: Date, date2: Date) { return ( date1.getFullYear() > date2.getFullYear() || (date1.getFullYear() === date2.getFullYear() && date1.getMonth() > date2.getMonth()) || (date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() >= date2.getDate()) ); } static _isBiggerDate(date1: Date, date2: Date) { return ( date1.getFullYear() > date2.getFullYear() || (date1.getFullYear() === date2.getFullYear() && date1.getMonth() > date2.getMonth()) || (date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() > date2.getDate()) ); } filterDateFrom(date: Date) { return !this.to || DateRangePickerState._isBiggerOrSameDate(this.to, date); } filterDateTo(date: Date) { return ( !this.from || DateRangePickerState._isBiggerOrSameDate(date, this.from) ); } filterDateTimeFrom(date: Date) { if (!this.from) { return false; } return ( !this.isToTimeSet || !this.to || DateRangePickerState._isBiggerDate(this.to, this.from) || this.to.getHours() * 60 + this.to.getMinutes() >= date.getHours() * 60 + date.getMinutes() ); } filterDateTimeTo(date: Date) { if (!this.to) { return false; } return ( !this.isFromTimeSet || !this.from || DateRangePickerState._isBiggerDate(this.to, this.from) || this.from.getHours() * 60 + this.from.getMinutes() <= date.getHours() * 60 + date.getMinutes() ); } }