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 @@ -51,7 +51,6 @@ describe("ChildrenListComponent", () => {
default: "true",
true: "Currently active children",
false: "Currently inactive children",
all: "All children",
} as BooleanFilterConfig,
{
id: "center",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import { Note } from "../model/note";
import { NoteDetailsComponent } from "../note-details/note-details.component";
import { ActivatedRoute } from "@angular/router";
import { EntityMapperService } from "../../../core/entity/entity-mapper/entity-mapper.service";
import { FilterSelectionOption } from "../../../core/filter/filters/filters";
import {
DataFilter,
FilterSelectionOption,
} from "../../../core/filter/filters/filters";
import { FormDialogService } from "../../../core/form-dialog/form-dialog.service";
import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy";
import { LoggingService } from "../../../core/logging/logging.service";
import { EntityListComponent } from "../../../core/entity-list/entity-list/entity-list.component";
import { applyUpdate } from "../../../core/entity/model/entity-update";
import { EntityListConfig } from "../../../core/entity-list/EntityListConfig";
import { EventNote } from "../../attendance/model/event-note";
import { WarningLevel } from "../../warning-level";
import { DynamicComponentConfig } from "../../../core/config/dynamic-components/dynamic-component-config.interface";
import { merge } from "rxjs";
import moment from "moment";
Expand Down Expand Up @@ -58,32 +60,16 @@ export class NotesManagerComponent implements OnInit {
entityConstructor = Note;
notes: Note[];

private statusFS: FilterSelectionOption<Note>[] = [
{
key: "urgent",
label: $localize`:Filter-option for notes:Urgent`,
filter: { "warningLevel.id": WarningLevel.URGENT },
},
{
key: "follow-up",
label: $localize`:Filter-option for notes:Needs Follow-Up`,
filter: {
"warningLevel.id": { $in: [WarningLevel.URGENT, WarningLevel.WARNING] },
},
},
{ 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 Expand Up @@ -147,11 +133,6 @@ export class NotesManagerComponent implements OnInit {
(filter) => filter.type === "prebuilt",
)) {
switch (prebuiltFilter.id) {
case "status": {
prebuiltFilter["options"] = this.statusFS;
prebuiltFilter["default"] = "";
break;
}
case "date": {
prebuiltFilter["options"] = this.dateFS;
prebuiltFilter["default"] = "current-week";
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[];
const configurableEnum = this.getEnum(id);
return configurableEnum ? (configurableEnum.values as T[]) : [];
}

getEnum(id: string): ConfigurableEnum {
getEnum(id: string): ConfigurableEnum | undefined {
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.selectedOptionValues.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.selectedOptionValues = ["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.selectedOptionValues).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.selectedOptionValues).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.selectedOptionValues = [index.toString()];
} else {
this.filter.selectedOption = "_";
this.filter.selectedOptionValues = [];
}
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.selectedOptionValues =
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.selectedOptionValues = ["9"];
component.filterConfig = dateFilter;
expect(component.dateFilter.getFilter()).toEqual({});

jasmine.clock().mockDate(moment("2023-05-18").toDate());
dateFilter.selectedOption = "0";
dateFilter.selectedOptionValues = ["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.selectedOptionValues = ["1"];
component.filterConfig = dateFilter;
expectedDataFilter = {
test: {
Expand All @@ -55,7 +55,7 @@ describe("DateRangeFilterComponent", () => {
};
expect(component.dateFilter.getFilter()).toEqual(expectedDataFilter);

dateFilter.selectedOption = "_";
dateFilter.selectedOptionValues = [];
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.selectedOptionValues = ["1", "2", "3"];
component.filterConfig = dateFilter;
expect(component.dateFilter.getFilter()).toEqual({});

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

dateFilter.selectedOption = "2022-9-18_";
dateFilter.selectedOptionValues = ["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.selectedOptionValues = ["", "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.selectedOptionValues = ["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.selectedOptionValues).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.selectedOptionValues);
}
}

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

openDialog(e: Event) {
Expand Down
4 changes: 1 addition & 3 deletions src/app/core/config/config-fix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,7 @@ export const defaultJsonConfig = {
},
"filters": [
{
"id": "status",
"label": $localize`:Filter label:Status`,
"type": "prebuilt"
"id": "warningLevel"
},
{
"id": "date",
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
5 changes: 2 additions & 3 deletions src/app/core/entity-list/EntityListConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,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 @@ -57,7 +57,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 All @@ -84,7 +84,6 @@ export interface BasicFilterConfig {
export interface BooleanFilterConfig extends BasicFilterConfig {
true: string;
false: string;
all: string;
}

export interface PrebuiltFilterConfig<T> extends BasicFilterConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ describe("EntityListComponent", () => {
default: "true",
true: "Currently active children",
false: "Currently inactive children",
all: "All children",
} as BooleanFilterConfig,
{
id: "center",
Expand Down
Loading