Skip to content

Commit

Permalink
Merge branch 'main' into package-entity
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosthe19916 committed Oct 29, 2024
2 parents c6df5df + dad2832 commit b34a57a
Show file tree
Hide file tree
Showing 15 changed files with 644 additions and 201 deletions.
114 changes: 114 additions & 0 deletions client/src/app/components/FilterPanel/DateRangeFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { FormEvent, useState } from "react";

import {
DatePicker,
Form,
FormGroup,
isValidDate as isValidJSDate,
} from "@patternfly/react-core";

import {
americanDateFormat,
isValidAmericanShortDate,
isValidInterval,
parseAmericanDate,
parseInterval,
toISODateInterval,
} from "../FilterToolbar/dateUtils";
import { IFilterControlProps } from "./FilterControl";

/**
* This Filter type enables selecting an closed date range.
* Precisely given range [A,B] a date X in the range if A <= X <= B.
*
* **Props are interpreted as follows**:<br>
* 1) filterValue - date range encoded as ISO 8601 time interval string ("dateFrom/dateTo"). Only date part is used (no time).<br>
* 2) setFilterValue - accepts the list of ranges.<br>
*
*/

export const DateRangeFilter = <TItem,>({
category,
filterValue,
setFilterValue,
isDisabled = false,
}: React.PropsWithChildren<
IFilterControlProps<TItem, string>
>): JSX.Element | null => {
const selectedFilters = filterValue ?? [];

const validFilters =
selectedFilters?.filter((interval) =>
isValidInterval(parseInterval(interval))
) ?? [];
const [from, setFrom] = useState<Date>();
const [to, setTo] = useState<Date>();

// Update it if it changes externally
React.useEffect(() => {
if (filterValue?.[0]) {
const [from, to] = parseInterval(filterValue?.[0]);
setFrom(from.toDate());
setTo(to.toDate());
} else {
setFrom(undefined);
setTo(undefined);
}
}, [filterValue]);

const onFromDateChange = (
event: FormEvent<HTMLInputElement>,
value: string
) => {
if (isValidAmericanShortDate(value)) {
setFrom(parseAmericanDate(value));
setTo(undefined);
}
};

const onToDateChange = (even: FormEvent<HTMLInputElement>, value: string) => {
if (isValidAmericanShortDate(value)) {
const newTo = parseAmericanDate(value);
setTo(newTo);
const target = toISODateInterval(from, newTo);
if (target) {
setFilterValue([target]);
}
}
};

return (
<Form>
<FormGroup role="group" isInline label="From">
<DatePicker
value={from ? americanDateFormat(from) : ""}
dateFormat={americanDateFormat}
dateParse={parseAmericanDate}
onChange={onFromDateChange}
aria-label="Interval start"
placeholder="MM/DD/YYYY"
// disable error text (no space in toolbar scenario)
invalidFormatText={""}
// default value ("parent") creates collision with sticky table header
appendTo={document.body}
isDisabled={isDisabled}
/>
</FormGroup>
<FormGroup role="group" isInline label="To">
<DatePicker
value={to ? americanDateFormat(to) : ""}
onChange={onToDateChange}
isDisabled={isDisabled || !isValidJSDate(from)}
dateFormat={americanDateFormat}
dateParse={parseAmericanDate}
// disable error text (no space in toolbar scenario)
invalidFormatText={""}
rangeStart={from}
aria-label="Interval end"
placeholder="MM/DD/YYYY"
appendTo={document.body}
/>
</FormGroup>
</Form>
);
};
4 changes: 4 additions & 0 deletions client/src/app/components/FilterPanel/FilterControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { SearchFilterControl } from "./SearchFilterControl";
import { RadioFilterControl } from "./RadioFilterControl";
import { CheckboxFilterControl } from "./CheckboxFilterControl";
import { DateRangeFilter } from "./DateRangeFilter";

export interface IFilterControlProps<TItem, TFilterCategoryKey extends string> {
category: FilterCategory<TItem, TFilterCategoryKey>;
Expand Down Expand Up @@ -56,5 +57,8 @@ export const FilterControl = <TItem, TFilterCategoryKey extends string>({
/>
);
}
if (category.type === FilterType.dateRange) {
return <DateRangeFilter category={category} {...props} />;
}
return null;
};
92 changes: 63 additions & 29 deletions client/src/app/components/FilterPanel/FilterPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import * as React from "react";

import { Card, CardBody, CardTitle } from "@patternfly/react-core";
import {
Button,
Stack,
StackItem,
Text,
TextContent,
} from "@patternfly/react-core";

import { FilterControl } from "./FilterControl";
import {
Expand All @@ -14,15 +20,15 @@ export interface IFilterPanelProps<TItem, TFilterCategoryKey extends string> {
filterValues: IFilterValues<TFilterCategoryKey>;
setFilterValues: (values: IFilterValues<TFilterCategoryKey>) => void;
isDisabled?: boolean;
ommitFilterCategoryKeys?: TFilterCategoryKey[];
omitFilterCategoryKeys?: TFilterCategoryKey[];
}

export const FilterPanel = <TItem, TFilterCategoryKey extends string>({
filterCategories,
filterValues,
setFilterValues,
isDisabled = false,
ommitFilterCategoryKeys = [],
omitFilterCategoryKeys = [],
}: React.PropsWithChildren<
IFilterPanelProps<TItem, TFilterCategoryKey>
>): JSX.Element | null => {
Expand All @@ -31,34 +37,62 @@ export const FilterPanel = <TItem, TFilterCategoryKey extends string>({
newValue: FilterValue
) => setFilterValues({ ...filterValues, [category.categoryKey]: newValue });

const clearAllFilters = () => {
const filtersToBeCleared = filterCategories
.filter((filterCategory) => {
return (
omitFilterCategoryKeys.find(
(categoryKey) => categoryKey === filterCategory.categoryKey
) === undefined
);
})
.reduce((prev, current) => {
return { ...prev, [current.categoryKey]: undefined };
}, {});
setFilterValues({ ...filterValues, ...filtersToBeCleared });
};

return (
<>
{filterCategories
.filter((filterCategory) => {
return (
ommitFilterCategoryKeys.find(
(categoryKey) => categoryKey === filterCategory.categoryKey
) === undefined
);
})
.map((category) => {
return (
<Card key={category.categoryKey} isPlain>
<CardTitle>{category.title}</CardTitle>
<CardBody>
<FilterControl<TItem, TFilterCategoryKey>
category={category}
filterValue={filterValues[category.categoryKey]}
setFilterValue={(newValue) =>
setFilterValue(category, newValue)
}
isDisabled={isDisabled}
isSidebar
/>
</CardBody>
</Card>
);
})}
<Stack hasGutter>
<StackItem>
<Button variant="link" isInline onClick={clearAllFilters}>
Clear all filters
</Button>
</StackItem>
<StackItem>
{filterCategories
.filter((filterCategory) => {
return (
omitFilterCategoryKeys.find(
(categoryKey) => categoryKey === filterCategory.categoryKey
) === undefined
);
})
.map((category) => {
return (
<Stack key={category.categoryKey} hasGutter>
<StackItem>
<TextContent>
<Text component="h4">{category.title}</Text>
</TextContent>
</StackItem>
<StackItem>
<FilterControl<TItem, TFilterCategoryKey>
category={category}
filterValue={filterValues[category.categoryKey]}
setFilterValue={(newValue) =>
setFilterValue(category, newValue)
}
isDisabled={isDisabled}
isSidebar
/>
</StackItem>
</Stack>
);
})}
</StackItem>
</Stack>
</>
);
};
Loading

0 comments on commit b34a57a

Please sign in to comment.