Skip to content

Commit

Permalink
Date range filter
Browse files Browse the repository at this point in the history
Signed-off-by: Radoslaw Szwajkowski <[email protected]>
  • Loading branch information
rszwajko committed Oct 10, 2023
1 parent 483d74c commit 7f99ef4
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 3 deletions.
100 changes: 100 additions & 0 deletions packages/common/src/components/Filter/DateRangeFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { FormEvent, useState } from 'react';
import { DateTime, Interval } from 'luxon';

import { DatePicker, InputGroup, ToolbarFilter } from '@patternfly/react-core';

import { FilterTypeProps } from './types';

/**
* This Filter type enables selecting an exclusive date range.
* Precisely given range [A,B) a date X in the range if A <= X < B.
*
* **FilterTypeProps are interpreted as follows**:<br>
* 1) selectedFilters - date range encoded as ISO 8601 time interval string ("dateFrom/dateTo").<br>
* 2) onFilterUpdate - accepts the list of ranges.<br>
*
* [<img src="static/media/src/components-stories/assets/github-logo.svg"><i class="fi fi-brands-github">
* <font color="green">View component source on GitHub</font>](https://github.com/kubev2v/forklift-console-plugin/blob/main/packages/common/src/components/Filter/DateRangeFilter.tsx)
*/
export const DateRangeFilter = ({
selectedFilters = [],
onFilterUpdate,
title,
filterId,
placeholderLabel,
showFilter = true,
}: FilterTypeProps) => {
const validFilters =
selectedFilters
?.map((str) => Interval.fromISO(str))
?.filter((range: Interval) => range.isValid) ?? [];

const [from, setFrom] = useState<DateTime>();
const [to, setTo] = useState<DateTime>();

const rangeToOption = (range: Interval): string => range.toISODate().replace('/', ' - ');

const optionToRange = (option: string): Interval => Interval.fromISO(option.replace(' - ', '/'));

const clearSingleRange = (option) => {
const target = optionToRange(option);
onFilterUpdate([
...validFilters.filter((range) => range.equals(target)).map((range) => range.toISODate()),
]);
};

const onFromDateChange = (even: FormEvent<HTMLInputElement>, value: string) => {
if (value?.length === 10 && DateTime.fromISO(value).isValid) {
setFrom(DateTime.fromISO(value));
setTo(undefined);
}
};

const onToDateChange = (even: FormEvent<HTMLInputElement>, value: string) => {
if (value?.length === 10 && DateTime.fromISO(value).isValid) {
const newTo = DateTime.fromISO(value);
setTo(newTo);
const target = Interval.fromDateTimes(from, newTo);
if (target.isValid) {
onFilterUpdate(
[...validFilters.filter((range) => !range.equals(target)), target].map((range) =>
range.toISODate(),
),
);
}
}
};
return (
<ToolbarFilter
key={filterId}
chips={validFilters.map(rangeToOption)}
deleteChip={(category, option) => clearSingleRange(option)}
deleteChipGroup={() => onFilterUpdate([])}
categoryName={title}
showToolbarItem={showFilter}
>
<InputGroup>
<DatePicker
value={from?.toISODate()}
dateFormat={(date) => DateTime.fromJSDate(date).toISODate()}
dateParse={(str) => DateTime.fromISO(str).toJSDate()}
onChange={onFromDateChange}
aria-label={'Interval start'}
placeholder={placeholderLabel}
invalidFormatText={placeholderLabel}
// default value ("parent") creates collision with sticky table header
appendTo={document.body}
/>
<DatePicker
value={to?.toISODate()}
onChange={onToDateChange}
isDisabled={!from?.isValid}
rangeStart={from?.toJSDate()}
aria-label="Interval end"
placeholder="YYYY-MM-DD"
appendTo={document.body}
/>
</InputGroup>
</ToolbarFilter>
);
};
1 change: 1 addition & 0 deletions packages/common/src/components/Filter/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @index(['./*', /__/g], f => `export * from '${f.path}';`)
export * from './DateFilter';
export * from './DateRangeFilter';
export * from './EnumFilter';
export * from './FreetextFilter';
export * from './GroupedEnumFilter';
Expand Down
19 changes: 17 additions & 2 deletions packages/common/src/components/FilterGroup/matchers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import jsonpath from 'jsonpath';
import { DateTime } from 'luxon';
import { DateTime, Interval } from 'luxon';

import { ResourceField } from '../../utils';
import { DateFilter, EnumFilter, FreetextFilter, GroupedEnumFilter, SwitchFilter } from '../Filter';
import {
DateFilter,
DateRangeFilter,
EnumFilter,
FreetextFilter,
GroupedEnumFilter,
SwitchFilter,
} from '../Filter';

import { FilterRenderer, ValueMatcher } from './types';

Expand Down Expand Up @@ -103,6 +110,12 @@ const dateMatcher = {
DateTime.fromISO(value).toUTC().hasSame(DateTime.fromISO(filter).toUTC(), 'day'),
};

const dateRangeMatcher = {
filterType: 'dateRange',
matchValue: (value: string) => (filter: string) =>
Interval.fromISO(filter).contains(DateTime.fromISO(value)),
};

const sliderMatcher = {
filterType: 'slider',
matchValue: (value: string) => (filter: string) => Boolean(value).toString() === filter || !value,
Expand All @@ -114,10 +127,12 @@ export const defaultValueMatchers: ValueMatcher[] = [
groupedEnumMatcher,
sliderMatcher,
dateMatcher,
dateRangeMatcher,
];

export const defaultSupportedFilters: Record<string, FilterRenderer> = {
date: DateFilter,
dateRange: DateRangeFilter,
enum: EnumFilter,
freetext: FreetextFilter,
groupedEnum: GroupedEnumFilter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const fieldsMetadataFactory: ResourceFieldFactory = (t) => [
label: t('Migration started'),
isVisible: true,
filter: {
type: 'date',
type: 'dateRange',
placeholderLabel: 'YYYY-MM-DD',
},
sortable: true,
Expand Down

0 comments on commit 7f99ef4

Please sign in to comment.