Skip to content

Commit b8c97db

Browse files
Fran McDadeFran McDade
authored andcommitted
feat!: implement filter range in category filters (#440)
1 parent 915199f commit b8c97db

45 files changed

Lines changed: 991 additions & 262 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {
2+
CategoryKey,
3+
DataDictionaryAnnotation,
4+
SelectCategoryValueView,
5+
} from "../../entities";
6+
import { RangeViewKind } from "../views/range/types";
7+
import { SelectViewKind } from "../views/select/types";
8+
9+
/**
10+
* Category config.
11+
*/
12+
export type CategoryConfig = RangeCategoryConfig | SelectCategoryConfig;
13+
14+
/**
15+
* Common category config.
16+
*/
17+
export interface CommonCategoryConfig {
18+
annotation?: DataDictionaryAnnotation;
19+
key: CategoryKey;
20+
label: string; // human readable label
21+
}
22+
23+
/**
24+
* Range category config.
25+
*/
26+
export interface RangeCategoryConfig
27+
extends CommonCategoryConfig,
28+
RangeViewKind {
29+
unit?: string; // e.g. "kg"
30+
}
31+
32+
/**
33+
* Select category config.
34+
*/
35+
export interface SelectCategoryConfig
36+
extends CommonCategoryConfig,
37+
SelectViewKind {
38+
enableChartView?: boolean;
39+
mapSelectCategoryValue?: (
40+
selectCategoryValue: SelectCategoryValueView
41+
) => SelectCategoryValueView;
42+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Category } from "../models/types";
2+
import { VIEW_KIND } from "../views/types";
3+
import { CategoryConfig } from "./types";
4+
5+
/**
6+
* Returns the category config for the given category config key and view kind.
7+
* @param viewKind - View kind.
8+
* @param key - Category config key.
9+
* @param configs - Category configs.
10+
* @returns category config.
11+
*/
12+
export function findCategoryConfig<V extends VIEW_KIND>(
13+
viewKind: V,
14+
key: Category["key"],
15+
configs: CategoryConfig[]
16+
): Extract<CategoryConfig, { viewKind: V }> | undefined {
17+
return configs.find(
18+
(c): c is Extract<CategoryConfig, { viewKind: V }> =>
19+
c.viewKind === viewKind && c.key === key
20+
);
21+
}
22+
23+
/**
24+
* Returns the range category config for the given category config key.
25+
* @param key - Category config key.
26+
* @param configs - Category configs.
27+
* @returns category config.
28+
*/
29+
export function findRangeCategoryConfig(
30+
key: Category["key"],
31+
configs: CategoryConfig[]
32+
): Extract<CategoryConfig, { viewKind: VIEW_KIND.RANGE }> | undefined {
33+
return findCategoryConfig(VIEW_KIND.RANGE, key, configs);
34+
}
35+
36+
/**
37+
* Returns the select category config for the given category config key.
38+
* @param key - Category config key.
39+
* @param configs - Category configs.
40+
* @returns category config.
41+
*/
42+
export function findSelectCategoryConfig(
43+
key: Category["key"],
44+
configs: CategoryConfig[]
45+
): Extract<CategoryConfig, { viewKind?: VIEW_KIND.SELECT }> | undefined {
46+
return findCategoryConfig(VIEW_KIND.SELECT, key, configs);
47+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Category } from "../types";
2+
import { RangeCategory, SelectedRange } from "./types";
3+
4+
/**
5+
* Determine if category is a range category.
6+
* @param category - Category to check if range category.
7+
* @returns true if category is a range category.
8+
*/
9+
export function isRangeCategory(category: Category): category is RangeCategory {
10+
return "max" in category && "min" in category;
11+
}
12+
13+
/**
14+
* Determine if value is a selected range.
15+
* @param value - Value to check if selected range.
16+
* @returns true if value is a selected range.
17+
*/
18+
export function isSelectedRange(value: unknown): value is SelectedRange {
19+
return (
20+
Array.isArray(value) &&
21+
value.length === 2 &&
22+
value.every((v) => v === undefined || typeof v === "number")
23+
);
24+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { CategoryKey } from "../../../entities";
2+
3+
/**
4+
* Internal filter model of a range category.
5+
*/
6+
export interface RangeCategory {
7+
key: CategoryKey;
8+
max: number | undefined;
9+
min: number | undefined;
10+
selectedMax: SelectedRange[1];
11+
selectedMin: SelectedRange[0];
12+
}
13+
14+
/**
15+
* Min and max values selected in range category.
16+
*/
17+
export type SelectedRange = [number?, number?];
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { SelectedFilter } from "../../../entities";
2+
import { isSelectedRange } from "./typeGuards";
3+
import { SelectedRange } from "./types";
4+
5+
/**
6+
* Asserts that the given value is a selected range.
7+
* Throws an error if the value is not a selected range.
8+
* @param value - Value to assert is a selected range.
9+
*/
10+
export function assertIsRange(value: unknown): asserts value is SelectedRange {
11+
if (!isSelectedRange(value)) {
12+
throw new Error("Value is not a selected range");
13+
}
14+
}
15+
16+
/**
17+
* Get the selected values for the given category, if any.
18+
* Handles type checking for selected range.
19+
* Falls back to empty array if no selected values or invalid type (should never happen).
20+
* @param categorySelectedFilter - Current filter state for a category.
21+
* @returns The selected filter (i.e. the set of selected values) for the given category.
22+
*/
23+
export function getRangeSelectedValue(
24+
categorySelectedFilter?: SelectedFilter
25+
): SelectedRange {
26+
return isSelectedRange(categorySelectedFilter?.value)
27+
? categorySelectedFilter?.value
28+
: [];
29+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { SelectCategory } from "../../entities";
2+
import { RangeCategory } from "./range/types";
3+
4+
/**
5+
* Internal filter model of a category.
6+
*/
7+
export type Category = SelectCategory | RangeCategory;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { CategoryKey, DataDictionaryAnnotation } from "../../../entities";
2+
3+
/**
4+
* Common properties for category views.
5+
*/
6+
export interface BaseCategoryView {
7+
annotation?: DataDictionaryAnnotation;
8+
isDisabled?: boolean;
9+
key: CategoryKey;
10+
label: string;
11+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { CategoryView } from "../types";
2+
import { RangeCategoryView } from "./types";
3+
4+
/**
5+
* Returns true if the category view is a range category view.
6+
* @param view - Category view.
7+
* @returns true if the category view is a range category view.
8+
*/
9+
export function isRangeCategoryView(
10+
view: CategoryView
11+
): view is RangeCategoryView {
12+
return "max" in view && "min" in view;
13+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { SelectedRange } from "../../models/range/types";
2+
import { BaseCategoryView } from "../common/types";
3+
import { VIEW_KIND } from "../types";
4+
5+
/**
6+
* View model of range category.
7+
*/
8+
export interface RangeCategoryView extends BaseCategoryView {
9+
max: number | undefined;
10+
min: number | undefined;
11+
selectedMax: SelectedRange[1];
12+
selectedMin: SelectedRange[0];
13+
unit?: string;
14+
}
15+
16+
/**
17+
* Model of range category view kind.
18+
*/
19+
export interface RangeViewKind {
20+
viewKind: VIEW_KIND.RANGE;
21+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { SelectedFilter } from "../../../entities";
2+
import { CategoryConfig } from "../../config/types";
3+
import { findRangeCategoryConfig } from "../../config/utils";
4+
import { RangeCategory } from "../../models/range/types";
5+
import { getRangeSelectedValue } from "../../models/range/utils";
6+
import { RangeCategoryView } from "../../views/range/types";
7+
8+
/**
9+
* Build the view-specific model of the given range category.
10+
* @param category - The range category to build a view model of.
11+
* @param categoryConfigs - Category configs indicating accept list as well as label configuration.
12+
* @param categorySelectedFilter - Current filter state for a category.
13+
* @returns Full built range category view, ready for display.
14+
*/
15+
export function buildRangeCategoryView(
16+
category: RangeCategory,
17+
categoryConfigs: CategoryConfig[],
18+
categorySelectedFilter?: SelectedFilter
19+
): RangeCategoryView {
20+
const categoryConfig = findRangeCategoryConfig(category.key, categoryConfigs);
21+
const [selectedMin, selectedMax] = getRangeSelectedValue(
22+
categorySelectedFilter
23+
);
24+
return {
25+
annotation: categoryConfig?.annotation,
26+
isDisabled: false,
27+
key: category.key,
28+
label: categoryConfig?.label || category.key,
29+
max: category.max,
30+
min: category.min,
31+
selectedMax,
32+
selectedMin,
33+
unit: categoryConfig?.unit,
34+
};
35+
}

0 commit comments

Comments
 (0)