diff --git a/src/app/child-dev-project/schools/activities-overview/activities-overview.component.html b/src/app/child-dev-project/schools/activities-overview/activities-overview.component.html new file mode 100644 index 0000000000..6d80f2895a --- /dev/null +++ b/src/app/child-dev-project/schools/activities-overview/activities-overview.component.html @@ -0,0 +1,7 @@ + diff --git a/src/app/child-dev-project/schools/activities-overview/activities-overview.component.scss b/src/app/child-dev-project/schools/activities-overview/activities-overview.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/child-dev-project/schools/activities-overview/activities-overview.component.spec.ts b/src/app/child-dev-project/schools/activities-overview/activities-overview.component.spec.ts new file mode 100644 index 0000000000..252b79e2f3 --- /dev/null +++ b/src/app/child-dev-project/schools/activities-overview/activities-overview.component.spec.ts @@ -0,0 +1,74 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { RecurringActivity } from "app/child-dev-project/attendance/model/recurring-activity"; +import { EntityMapperService } from "app/core/entity/entity-mapper.service"; +import { + mockEntityMapper, + MockEntityMapperService, +} from "app/core/entity/mock-entity-mapper-service"; +import { UpdatedEntity } from "app/core/entity/model/entity-update"; +import { Subject } from "rxjs"; +import { School } from "../model/school"; + +import { ActivitiesOverviewComponent } from "./activities-overview.component"; + +describe("ActivitiesOverviewComponent", () => { + let component: ActivitiesOverviewComponent; + let fixture: ComponentFixture; + + let entityMapper: MockEntityMapperService; + + beforeEach(async () => { + entityMapper = mockEntityMapper(); + await TestBed.configureTestingModule({ + declarations: [ActivitiesOverviewComponent], + providers: [{ provide: EntityMapperService, useValue: entityMapper }], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ActivitiesOverviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + it("should fetch all and only recurring activities having the selected school as a linkedGroup", async () => { + const school1 = new School("school1"); + const activity1 = new RecurringActivity(); + activity1.linkedGroups = ["school1"]; + const activity2 = new RecurringActivity(); + activity2.linkedGroups = ["school1", "school2"]; + const activity3 = new RecurringActivity(); + activity3.linkedGroups = ["school3"]; + entityMapper.addAll([activity1, activity2, activity3]); + + await component.onInitFromDynamicConfig({ entity: school1 }); + expect(component.records).toEqual([activity1, activity2]); + }); + + it("should create a new recurring activity having the current school as a linkedGroup", () => { + component.entity = new School("school1"); + const newRecurringActivity = component.generateNewRecordFactory(); + expect(newRecurringActivity().linkedGroups).toEqual(["school1"]); + }); + + it("should remove the recurring activity from the table view if the current school is removed as a group of this recurring activity", async () => { + const school1 = new School("school1"); + const activity1 = new RecurringActivity(); + activity1.linkedGroups = ["school1"]; + const activity2 = new RecurringActivity(); + activity1.linkedGroups = ["school1", "school2", "school3"]; + entityMapper.addAll([activity1, activity2]); + const subject = new Subject>(); + spyOn(entityMapper, "receiveUpdates").and.returnValue(subject); + await component.onInitFromDynamicConfig({ entity: school1 }); + + activity2.linkedGroups = ["school2", "school3"]; + subject.next({ entity: activity2, type: "update" }); + + expect(component.records).toEqual([activity1]); + }); +}); diff --git a/src/app/child-dev-project/schools/activities-overview/activities-overview.component.ts b/src/app/child-dev-project/schools/activities-overview/activities-overview.component.ts new file mode 100644 index 0000000000..5f11e79e02 --- /dev/null +++ b/src/app/child-dev-project/schools/activities-overview/activities-overview.component.ts @@ -0,0 +1,58 @@ +import { Component } from "@angular/core"; +import { RecurringActivity } from "app/child-dev-project/attendance/model/recurring-activity"; +import { FormFieldConfig } from "app/core/entity-components/entity-form/entity-form/FormConfig"; +import { EntityListConfig } from "app/core/entity-components/entity-list/EntityListConfig"; +import { EntityMapperService } from "app/core/entity/entity-mapper.service"; +import { Entity } from "app/core/entity/model/entity"; +import { DynamicComponent } from "app/core/view/dynamic-components/dynamic-component.decorator"; +import { OnInitDynamicComponent } from "app/core/view/dynamic-components/on-init-dynamic-component.interface"; + +@DynamicComponent("ActivitiesOverview") +@Component({ + selector: "app-activities-overview", + templateUrl: "./activities-overview.component.html", + styleUrls: ["./activities-overview.component.scss"], +}) +export class ActivitiesOverviewComponent implements OnInitDynamicComponent { + columns: FormFieldConfig[] = [ + { id: "title" }, + { id: "type" }, + { id: "assignedTo" }, + { id: "linkedGroups" }, + ]; + + entity: Entity; + records: RecurringActivity[] = []; + listConfig: EntityListConfig; + activityConstructor = RecurringActivity; + + constructor(private entityMapper: EntityMapperService) {} + + async onInitFromDynamicConfig(config: any) { + if (config?.config?.columns) { + this.columns = config.config.columns; + } + + this.entity = config.entity; + this.records = ( + await this.entityMapper.loadType(RecurringActivity) + ).filter((activity) => activity.linkedGroups.includes(this.entity.getId())); + this.entityMapper + .receiveUpdates(RecurringActivity) + .subscribe((updateEntity) => { + if (updateEntity.type === "update") { + this.records = this.records.filter((activity) => + activity.linkedGroups.includes(this.entity.getId()) + ); + } + }); + } + + generateNewRecordFactory(): () => RecurringActivity { + return () => { + const newRecurringActivity = new RecurringActivity(); + newRecurringActivity.linkedGroups.push(this.entity.getId()); + return newRecurringActivity; + }; + } +} diff --git a/src/app/child-dev-project/schools/schools.module.ts b/src/app/child-dev-project/schools/schools.module.ts index da9ee0559e..49c2fee77d 100644 --- a/src/app/child-dev-project/schools/schools.module.ts +++ b/src/app/child-dev-project/schools/schools.module.ts @@ -27,6 +27,7 @@ import { EntityListModule } from "../../core/entity-components/entity-list/entit import { EntitySubrecordModule } from "../../core/entity-components/entity-subrecord/entity-subrecord.module"; import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; import { ViewModule } from "../../core/view/view.module"; +import { ActivitiesOverviewComponent } from "./activities-overview/activities-overview.component"; @NgModule({ imports: [ @@ -70,10 +71,18 @@ import { ViewModule } from "../../core/view/view.module"; FontAwesomeModule, ViewModule, ], - declarations: [SchoolBlockComponent, ChildrenOverviewComponent], + declarations: [ + SchoolBlockComponent, + ChildrenOverviewComponent, + ActivitiesOverviewComponent, + ], exports: [SchoolBlockComponent], providers: [DatePipe], }) export class SchoolsModule { - static dynamicComponents = [ChildrenOverviewComponent, SchoolBlockComponent]; + static dynamicComponents = [ + ChildrenOverviewComponent, + SchoolBlockComponent, + ActivitiesOverviewComponent, + ]; } diff --git a/src/app/core/config/config-fix.ts b/src/app/core/config/config-fix.ts index 84bbf8db6b..a49f6e7004 100644 --- a/src/app/core/config/config-fix.ts +++ b/src/app/core/config/config-fix.ts @@ -424,6 +424,15 @@ export const defaultJsonConfig = { "component": "ChildrenOverview", } ] + }, + { + "title": $localize`:Panel title:Activities`, + "components": [ + { + "title": "", + "component": "ActivitiesOverview", + } + ] } ], "icon": "university" diff --git a/src/app/core/registry/dynamic-registry.ts b/src/app/core/registry/dynamic-registry.ts index cca4683825..528b912589 100644 --- a/src/app/core/registry/dynamic-registry.ts +++ b/src/app/core/registry/dynamic-registry.ts @@ -31,8 +31,9 @@ export abstract class Registry extends Map { public get(key: string): T { if (!this.has(key)) { throw Error( - `${this.constructor.name}: Requested item ${key} is not registered` + `${this.constructor.name}: Requested item ${key} is not registered. See dynamic-registry.ts for more details.` ); + // To register a component, add @DynamicComponent("COMPONENTNAME") to the components .ts-file and implement the onInitFromDynamicConfig method, e.g. onInitFromDynamicConfig(config: any) {} } return super.get(key); }