Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(filter): enable multi-selection filter #2173

Merged
merged 12 commits into from
Jan 15, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Angulartics2Module } from "angulartics2";
import { MatMenuModule } from "@angular/material/menu";
import { FaDynamicIconComponent } from "../../../core/common-components/fa-dynamic-icon/fa-dynamic-icon.component";
import { RouteTarget } from "../../../route-target";
import { DataFilter } from "../../../core/common-components/entity-subrecord/entity-subrecord/entity-subrecord-config";

/**
* additional config specifically for NotesManagerComponent
Expand Down Expand Up @@ -63,28 +64,27 @@ export class NotesManagerComponent implements OnInit {
{
key: "urgent",
label: $localize`:Filter-option for notes:Urgent`,
filter: { "warningLevel.id": WarningLevel.URGENT },
filter: { "warningLevel.id": WarningLevel.URGENT } as DataFilter<any>,
},
{
key: "follow-up",
label: $localize`:Filter-option for notes:Needs Follow-Up`,
filter: {
"warningLevel.id": { $in: [WarningLevel.URGENT, WarningLevel.WARNING] },
},
"warningLevel.id": { $in: [WarningLevel.WARNING] },
} as DataFilter<any>,
TheSlimvReal marked this conversation as resolved.
Show resolved Hide resolved
},
{ key: "", label: $localize`All`, filter: {} },
];

private dateFS: FilterSelectionOption<Note>[] = [
{
key: "current-week",
label: $localize`:Filter-option for notes:This Week`,
filter: { date: this.getWeeksFilter(0) },
filter: { date: this.getWeeksFilter(0) } as DataFilter<any>,
},
{
key: "last-week",
label: $localize`:Filter-option for notes:Since Last Week`,
filter: { date: this.getWeeksFilter(1) },
filter: { date: this.getWeeksFilter(1) } as DataFilter<any>,
},
{ key: "", label: $localize`All`, filter: {} },
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ export class ConfigurableEnumService {
getEnumValues<T extends ConfigurableEnumValue = ConfigurableEnumValue>(
id: string,
): T[] {
return this.getEnum(id).values as T[];
let configurableEnum = this.getEnum(id);
return configurableEnum == null ? [] : (configurableEnum.values as T[]);
tomwwinter marked this conversation as resolved.
Show resolved Hide resolved
}

getEnum(id: string): ConfigurableEnum {
getEnum(id: string): ConfigurableEnum | null {
if (!this.enums) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class EnumDropdownComponent implements OnChanges {
if (changes.hasOwnProperty("enumId") || changes.hasOwnProperty("form")) {
this.invalidOptions = this.prepareInvalidOptions();
}
this.options = [...this.enumEntity.values, ...this.invalidOptions];
this.options = [...this.enumEntity?.values, ...this.invalidOptions];
}

private prepareInvalidOptions(): ConfigurableEnumValue[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
(mouseenter)="preselectAllRange()"
(mouseleave)="unselectRange()"
(click)="selectRangeAndClose('all')"
[class.selected-option]="filter.selectedOption === '_'"
[class.selected-option]="filter.selectedOptionsKeys.length === 0"
>
All
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { HarnessLoader } from "@angular/cdk/testing";
import { DateRange } from "@angular/material/datepicker";
import { MatCalendarHarness } from "@angular/material/datepicker/testing";
import moment from "moment";
import { DateFilter } from "../../../../filter/filters/filters";

import { DateFilter } from "../../../../filter/filters/dateFilter";

describe("DateRangeFilterPanelComponent", () => {
let component: DateRangeFilterPanelComponent;
Expand All @@ -22,7 +23,7 @@ describe("DateRangeFilterPanelComponent", () => {

beforeEach(async () => {
dateFilter = new DateFilter("test", "Test", defaultDateFilters);
dateFilter.selectedOption = "1";
dateFilter.selectedOptionsKeys = ["1"];
jasmine.clock().mockDate(moment("2023-04-08").toDate());
await TestBed.configureTestingModule({
imports: [MatNativeDateModule],
Expand Down Expand Up @@ -85,7 +86,7 @@ describe("DateRangeFilterPanelComponent", () => {
moment("2023-04-08").startOf("day").toDate(),
);
expect(filterRange.end).toEqual(moment("2023-04-08").endOf("day").toDate());
expect(dateFilter.selectedOption).toBe("0");
expect(dateFilter.selectedOptionsKeys).toEqual(["0"]);
});

it("should highlight the date range when hovering over a option", async () => {
Expand Down Expand Up @@ -114,9 +115,9 @@ describe("DateRangeFilterPanelComponent", () => {
}
});

it("should return '_' as filter.selectedOption when 'all' option has been chosen", async () => {
it("should return empty array as filter.selectedOption when 'all' option has been chosen", async () => {
component.selectRangeAndClose("all");
expect(dateFilter.selectedOption).toEqual("_");
expect(dateFilter.selectedOptionsKeys).toEqual([]);
});

it("should correctly calculate date ranges based on the config", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Component, Inject } from "@angular/core";
import {
DateRange,
MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER,
MatDatepickerModule,
MatDateSelectionModel,
MatRangeDateSelectionModel,
MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER,
} from "@angular/material/datepicker";
import {
MAT_DIALOG_DATA,
Expand All @@ -16,8 +16,8 @@ import { NgForOf } from "@angular/common";
import { DateRangeFilterConfigOption } from "../../../../entity-list/EntityListConfig";
import moment from "moment";
import { FormsModule } from "@angular/forms";
import { DateFilter } from "../../../../filter/filters/filters";
import { dateToString } from "../../../../../utils/utils";
import { DateFilter } from "../../../../filter/filters/dateFilter";

export const defaultDateFilters: DateRangeFilterConfigOption[] = [
{
Expand Down Expand Up @@ -91,9 +91,9 @@ export class DateRangeFilterPanelComponent {

selectRangeAndClose(index: number | "all"): void {
if (typeof index === "number") {
this.filter.selectedOption = index.toString();
this.filter.selectedOptionsKeys = [index.toString()];
} else {
this.filter.selectedOption = "_";
this.filter.selectedOptionsKeys = [];
}
this.dialogRef.close();
}
Expand All @@ -102,11 +102,11 @@ export class DateRangeFilterPanelComponent {
if (!this.selectedRangeValue?.start || this.selectedRangeValue?.end) {
this.selectedRangeValue = new DateRange(selectedDate, null);
} else {
const start = this.selectedRangeValue.start;
this.filter.selectedOption =
const start: Date = this.selectedRangeValue.start;
this.filter.selectedOptionsKeys =
start < selectedDate
? dateToString(start) + "_" + dateToString(selectedDate)
: dateToString(selectedDate) + "_" + dateToString(start);
? [dateToString(start), dateToString(selectedDate)]
: [dateToString(selectedDate), dateToString(start)];
this.dialogRef.close();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { DateRangeFilterComponent } from "./date-range-filter.component";
import { MatDialog } from "@angular/material/dialog";
import { MatNativeDateModule } from "@angular/material/core";
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { DateFilter } from "../../../filter/filters/filters";
import { defaultDateFilters } from "./date-range-filter-panel/date-range-filter-panel.component";
import moment from "moment";
import { DateFilter } from "../../../filter/filters/dateFilter";

describe("DateRangeFilterComponent", () => {
let component: DateRangeFilterComponent<any>;
Expand All @@ -30,12 +30,12 @@ describe("DateRangeFilterComponent", () => {
it("should set the correct date filter when a new option is selected", () => {
const dateFilter = new DateFilter("test", "Test", defaultDateFilters);

dateFilter.selectedOption = "9";
dateFilter.selectedOptionsKeys = ["9"];
component.filterConfig = dateFilter;
expect(component.dateFilter.getFilter()).toEqual({});

jasmine.clock().mockDate(moment("2023-05-18").toDate());
dateFilter.selectedOption = "0";
dateFilter.selectedOptionsKeys = ["0"];
component.filterConfig = dateFilter;
let expectedDataFilter = {
test: {
Expand All @@ -45,7 +45,7 @@ describe("DateRangeFilterComponent", () => {
};
expect(component.dateFilter.getFilter()).toEqual(expectedDataFilter);

dateFilter.selectedOption = "1";
dateFilter.selectedOptionsKeys = ["1"];
component.filterConfig = dateFilter;
expectedDataFilter = {
test: {
Expand All @@ -55,7 +55,7 @@ describe("DateRangeFilterComponent", () => {
};
expect(component.dateFilter.getFilter()).toEqual(expectedDataFilter);

dateFilter.selectedOption = "_";
dateFilter.selectedOptionsKeys = [];
component.filterConfig = dateFilter;
expect(component.dateFilter.getFilter()).toEqual({});
jasmine.clock().uninstall();
Expand All @@ -64,31 +64,31 @@ describe("DateRangeFilterComponent", () => {
it("should set the correct date filter when inputting a specific date range via the URL", () => {
let dateFilter = new DateFilter("test", "test", []);

dateFilter.selectedOption = "1_2_3";
dateFilter.selectedOptionsKeys = ["1_2_3"];
component.filterConfig = dateFilter;
expect(component.dateFilter.getFilter()).toEqual({});

dateFilter.selectedOption = "_";
dateFilter.selectedOptionsKeys = [];
component.filterConfig = dateFilter;
expect(component.dateFilter.getFilter()).toEqual({});

dateFilter.selectedOption = "2022-9-18_";
dateFilter.selectedOptionsKeys = ["2022-9-18", ""];
component.filterConfig = dateFilter;
let testFilter: { $gte?: string; $lte?: string } = { $gte: "2022-09-18" };
let expectedDateFilter = {
test: testFilter,
};
expect(component.dateFilter.getFilter()).toEqual(expectedDateFilter);

dateFilter.selectedOption = "_2023-01-3";
dateFilter.selectedOptionsKeys = ["", "2023-01-3"];
component.filterConfig = dateFilter;
testFilter = { $lte: "2023-01-03" };
expectedDateFilter = {
test: testFilter,
};
expect(component.dateFilter.getFilter()).toEqual(expectedDateFilter);

dateFilter.selectedOption = "2022-9-18_2023-01-3";
dateFilter.selectedOptionsKeys = ["2022-9-18", "2023-01-3"];
component.filterConfig = dateFilter;
testFilter = {
$gte: "2022-09-18",
Expand All @@ -107,9 +107,10 @@ describe("DateRangeFilterComponent", () => {

component.dateChangedManually();

expect(component.dateFilter.selectedOption).toEqual(
"2021-10-28_2024-02-12",
);
expect(component.dateFilter.selectedOptionsKeys).toEqual([
"2021-10-28",
"2024-02-12",
]);
let expectedDataFilter = {
test: {
$gte: "2021-10-28",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { MatDialog } from "@angular/material/dialog";
import { Entity } from "../../../entity/model/entity";
import { DateFilter, Filter } from "../../../filter/filters/filters";
import { Filter } from "../../../filter/filters/filters";
import { DateRangeFilterPanelComponent } from "./date-range-filter-panel/date-range-filter-panel.component";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatDatepickerModule } from "@angular/material/datepicker";
import { FormsModule } from "@angular/forms";
import { dateToString, isValidDate } from "../../../../utils/utils";
import { DateFilter } from "../../../filter/filters/dateFilter";

@Component({
selector: "app-date-range-filter",
Expand All @@ -20,7 +21,7 @@ export class DateRangeFilterComponent<T extends Entity> {
toDate: Date;
dateFilter: DateFilter<T>;

@Output() selectedOptionChange = new EventEmitter<string>();
@Output() selectedOptionChange = new EventEmitter<string[]>();

@Input() set filterConfig(value: Filter<T>) {
this.dateFilter = value as DateFilter<T>;
Expand All @@ -37,16 +38,16 @@ export class DateRangeFilterComponent<T extends Entity> {
) {
this.fromDate = range.start;
this.toDate = range.end;
this.selectedOptionChange.emit(this.dateFilter.selectedOption);
this.selectedOptionChange.emit(this.dateFilter.selectedOptionsKeys);
}
}

dateChangedManually() {
this.dateFilter.selectedOption =
(isValidDate(this.fromDate) ? dateToString(this.fromDate) : "") +
"_" +
(isValidDate(this.toDate) ? dateToString(this.toDate) : "");
this.selectedOptionChange.emit(this.dateFilter.selectedOption);
this.dateFilter.selectedOptionsKeys = [
isValidDate(this.fromDate) ? dateToString(this.fromDate) : "",
isValidDate(this.toDate) ? dateToString(this.toDate) : "",
];
this.selectedOptionChange.emit(this.dateFilter.selectedOptionsKeys);
}

openDialog(e: Event) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { InteractionType } from "../../../child-dev-project/notes/model/interaction-type.interface";

export const defaultInteractionTypes: InteractionType[] = [
{
id: "",
label: "",
},
{
id: "VISIT",
label: $localize`:Interaction type/Category of a Note:Home Visit`,
Expand Down
4 changes: 2 additions & 2 deletions src/app/core/entity-list/EntityListConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface EntityListConfig {

/**
* Optional config for which columns are displayed.
* By default all columns are shown
* By default, all columns are shown
*/
columnGroups?: ColumnGroupsConfig;

Expand Down Expand Up @@ -55,7 +55,7 @@ export interface ColumnGroupsConfig {
default?: string;

/**
* The name of the group group that should be selected by default on a mobile device.
* The name of the group that should be selected by default on a mobile device.
* Default is the name of the first group.
*/
mobile?: string;
Expand Down
2 changes: 1 addition & 1 deletion src/app/core/entity/model/entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class Entity {
/**
* True if this type's schema has been customized dynamically from the config.
*/
static _isCustomizedType?: boolean;
static _isCustomizedType?: boolean; // todo should be private or renamed to "isCustomizedType"

/**
* Defining which attribute values of an entity should be shown in the `.toString()` method.
Expand Down
Loading