diff --git a/angular.json b/angular.json index f39f78efcb..8f6b059d48 100644 --- a/angular.json +++ b/angular.json @@ -36,10 +36,7 @@ "node_modules/leaflet/dist/leaflet.css" ], "stylePreprocessorOptions": { - "includePaths": [ - "node_modules/", - "src/styles" - ] + "includePaths": ["node_modules/", "src/styles"] }, "vendorChunk": true, "extractLicenses": false, @@ -105,20 +102,14 @@ "builder": "@angular-devkit/build-angular:karma", "options": { "karmaConfig": "./karma.conf.js", - "polyfills": [ - "src/polyfills.ts", - "zone.js/testing" - ], + "polyfills": ["src/polyfills.ts", "zone.js/testing"], "tsConfig": "src/tsconfig.spec.json", "styles": [ "src/styles/styles.scss", "src/styles/themes/ndb-theme.scss" ], "stylePreprocessorOptions": { - "includePaths": [ - "node_modules/", - "src/styles" - ] + "includePaths": ["node_modules/", "src/styles"] }, "assets": [ "src/assets", @@ -152,10 +143,7 @@ "lint": { "builder": "@angular-eslint/builder:lint", "options": { - "lintFilePatterns": [ - "src/**/*.ts", - "src/**/*.html" - ] + "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"] } }, "cypress-open": { @@ -199,8 +187,6 @@ }, "cli": { "analytics": "0bd2cce5-bfb0-4375-af96-a8222d782810", - "schematicCollections": [ - "@angular-eslint/schematics" - ] + "schematicCollections": ["@angular-eslint/schematics"] } } diff --git a/build/Dockerfile b/build/Dockerfile index b4747058c3..1e11e6fee3 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -70,7 +70,7 @@ RUN if [ "$SENTRY_AUTH_TOKEN" != "" ] ; then \ ### PROD image -FROM nginx:1.25.3-alpine +FROM nginx:1.25.4-alpine COPY ./build/default.conf /etc/nginx/templates/default.conf COPY --from=builder /app/dist/ /usr/share/nginx/html # The port on which the app will run in the Docker container diff --git a/package-lock.json b/package-lock.json index 4a9d414714..2cc56db2a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "@fortawesome/free-regular-svg-icons": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1", "@ngneat/until-destroy": "^10.0.0", - "@sentry/browser": "^7.93.0", + "@sentry/browser": "^7.98.0", "angulartics2": "^12.2.1", "assert": "^2.1.0", "crypto-es": "^2.1.0", @@ -6932,87 +6932,102 @@ } }, "node_modules/@sentry-internal/feedback": { - "version": "7.93.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.93.0.tgz", - "integrity": "sha512-4G7rMeQbYGfCHxEoFroABX+UREYc2BSbFqjLmLbIcWowSpgzcwweLLphWHKOciqK6f7DnNDK0jZzx3u7NrkWHw==", + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.98.0.tgz", + "integrity": "sha512-t/mATvwkLcQLKRlx8SO5vlUjaadF6sT3lfR0PdWYyBy8qglbMTHDW4KP6JKh1gdzTVQGnwMByy+/4h9gy4AVzw==", "dependencies": { - "@sentry/core": "7.93.0", - "@sentry/types": "7.93.0", - "@sentry/utils": "7.93.0" + "@sentry/core": "7.98.0", + "@sentry/types": "7.98.0", + "@sentry/utils": "7.98.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.98.0.tgz", + "integrity": "sha512-vAR6KIycyazaY9HwxG5UONrPTe8jeKtZr6k04svPC8OvcoI0xF+l1jMEYcarffuzKpZlPfssYb5ChHtKuXCB+Q==", + "dependencies": { + "@sentry/core": "7.98.0", + "@sentry/replay": "7.98.0", + "@sentry/types": "7.98.0", + "@sentry/utils": "7.98.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry-internal/tracing": { - "version": "7.93.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.93.0.tgz", - "integrity": "sha512-DjuhmQNywPp+8fxC9dvhGrqgsUb6wI/HQp25lS2Re7VxL1swCasvpkg8EOYP4iBniVQ86QK0uITkOIRc5tdY1w==", + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.98.0.tgz", + "integrity": "sha512-FnhD2uMLIAJvv4XsYPv3qsTTtxrImyLxiZacudJyaWFhxoeVQ8bKKbWJ/Ar68FAwqTtjXMeY5evnEBbRMcQlaA==", "dependencies": { - "@sentry/core": "7.93.0", - "@sentry/types": "7.93.0", - "@sentry/utils": "7.93.0" + "@sentry/core": "7.98.0", + "@sentry/types": "7.98.0", + "@sentry/utils": "7.98.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/browser": { - "version": "7.93.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.93.0.tgz", - "integrity": "sha512-MtLTcQ7y3rfk+aIvnnwCfSJvYhTJnIJi+Mf6y/ap6SKObdlsKMbQoJLlRViglGLq+nKxHLAvU0fONiCEmKfV6A==", + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.98.0.tgz", + "integrity": "sha512-/MzTS31N2iM6Qwyh4PSpHihgmkVD5xdfE5qi1mTlwQZz5Yz8t7MdMriX8bEDPlLB8sNxl7+D6/+KUJO8akX0nQ==", "dependencies": { - "@sentry-internal/feedback": "7.93.0", - "@sentry-internal/tracing": "7.93.0", - "@sentry/core": "7.93.0", - "@sentry/replay": "7.93.0", - "@sentry/types": "7.93.0", - "@sentry/utils": "7.93.0" + "@sentry-internal/feedback": "7.98.0", + "@sentry-internal/replay-canvas": "7.98.0", + "@sentry-internal/tracing": "7.98.0", + "@sentry/core": "7.98.0", + "@sentry/replay": "7.98.0", + "@sentry/types": "7.98.0", + "@sentry/utils": "7.98.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/core": { - "version": "7.93.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.93.0.tgz", - "integrity": "sha512-vZQSUiDn73n+yu2fEcH+Wpm4GbRmtxmnXnYCPgM6IjnXqkVm3awWAkzrheADblx3kmxrRiOlTXYHw9NTWs56fg==", + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.98.0.tgz", + "integrity": "sha512-baRUcpCNGyk7cApQHMfqEZJkXdvAKK+z/dVWiMqWc5T5uhzMnPE8/gjP1JZsMtJSQ8g5nHimBdI5TwOyZtxPaA==", "dependencies": { - "@sentry/types": "7.93.0", - "@sentry/utils": "7.93.0" + "@sentry/types": "7.98.0", + "@sentry/utils": "7.98.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/replay": { - "version": "7.93.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.93.0.tgz", - "integrity": "sha512-dMlLU8v+OkUeGCrPvTu5NriH7BGj3el4rGHWWAYicfJ2QXqTTq50vfasQBP1JeVNcFqnf1y653TdEIvo4RH4tw==", + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.98.0.tgz", + "integrity": "sha512-CQabv/3KnpMkpc2TzIquPu5krpjeMRAaDIO0OpTj5SQeH2RqSq3fVWNZkHa8tLsADxcfLFINxqOg2jd1NxvwxA==", "dependencies": { - "@sentry-internal/tracing": "7.93.0", - "@sentry/core": "7.93.0", - "@sentry/types": "7.93.0", - "@sentry/utils": "7.93.0" + "@sentry-internal/tracing": "7.98.0", + "@sentry/core": "7.98.0", + "@sentry/types": "7.98.0", + "@sentry/utils": "7.98.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry/types": { - "version": "7.93.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.93.0.tgz", - "integrity": "sha512-UnzUccNakhFRA/esWBWP+0v7cjNg+RilFBQC03Mv9OEMaZaS29zSbcOGtRzuFOXXLBdbr44BWADqpz3VW0XaNw==", + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.98.0.tgz", + "integrity": "sha512-pc034ziM0VTETue4bfBcBqTWGy4w0okidtoZJjGVrYAfE95ObZnUGVj/XYIQ3FeCYWIa7NFN2MvdsCS0buwivQ==", "engines": { "node": ">=8" } }, "node_modules/@sentry/utils": { - "version": "7.93.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.93.0.tgz", - "integrity": "sha512-Iovj7tUnbgSkh/WrAaMrd5UuYjW7AzyzZlFDIUrwidsyIdUficjCG2OIxYzh76H6nYIx9SxewW0R54Q6XoB4uA==", + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.98.0.tgz", + "integrity": "sha512-0/LY+kpHxItVRY0xPDXPXVsKRb95cXsGSQf8sVMtfSjz++0bLL1U4k7PFz1c5s2/Vk0B8hS6duRrgMv6dMIZDw==", "dependencies": { - "@sentry/types": "7.93.0" + "@sentry/types": "7.98.0" }, "engines": { "node": ">=8" diff --git a/package.json b/package.json index a697660414..bd33fa645a 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@fortawesome/free-regular-svg-icons": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1", "@ngneat/until-destroy": "^10.0.0", - "@sentry/browser": "^7.93.0", + "@sentry/browser": "^7.98.0", "angulartics2": "^12.2.1", "assert": "^2.1.0", "crypto-es": "^2.1.0", diff --git a/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.spec.ts b/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.spec.ts index 6b2878b523..d14996e52b 100644 --- a/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.spec.ts +++ b/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.spec.ts @@ -51,7 +51,7 @@ describe("RollCallComponent", () => { participant2 = new Child("child2"); participant3 = new Child("child3"); - mockLoggingService = jasmine.createSpyObj(["warn"]); + mockLoggingService = jasmine.createSpyObj(["warn", "debug"]); TestBed.configureTestingModule({ imports: [ @@ -107,7 +107,7 @@ describe("RollCallComponent", () => { expect(component.children).toEqual([participant1]); expect(component.eventEntity.children).not.toContain(nonExistingChildId); - expect(mockLoggingService.warn).toHaveBeenCalled(); + expect(mockLoggingService.debug).toHaveBeenCalled(); flush(); })); diff --git a/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.ts b/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.ts index 4100eaf235..9d295c5eb2 100644 --- a/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.ts +++ b/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.ts @@ -176,7 +176,7 @@ export class RollCallComponent implements OnChanges { try { child = await this.entityMapper.load(Child, childId); } catch (e) { - this.loggingService.warn( + this.loggingService.debug( "Could not find child " + childId + " for event " + diff --git a/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.html b/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.html index f801786c3a..e0c5fd0e8a 100644 --- a/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.html +++ b/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.html @@ -1,76 +1,59 @@ - - -
- - - - - - - - -
+ + + + + + + + + + + + - + + + + + + - - - + +
+ + +
- -
-
- - - -
-
-
-
no absences recorded
- - -
-
+ + diff --git a/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.spec.ts b/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.spec.ts index 2c5ce30059..d9b7e87bd1 100644 --- a/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.spec.ts +++ b/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.spec.ts @@ -71,8 +71,7 @@ describe("AttendanceWeekDashboardComponent", () => { await component.ngOnInit(); - expect(component.loadingDone).toBeTrue(); - expect(component.tableDataSource.data).toEqual([ + expect(component.entries).toEqual([ [ { childId: absentChild.getId(), diff --git a/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.ts b/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.ts index 2778c7310d..08039dc1ef 100644 --- a/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.ts +++ b/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.ts @@ -1,10 +1,4 @@ -import { - AfterViewInit, - Component, - Input, - OnInit, - ViewChild, -} from "@angular/core"; +import { Component, Input, OnInit } from "@angular/core"; import { Router } from "@angular/router"; import { Child } from "../../../children/model/child"; import { AttendanceLogicalStatus } from "../../model/attendance-status"; @@ -14,16 +8,14 @@ import { ActivityAttendance } from "../../model/activity-attendance"; import { RecurringActivity } from "../../model/recurring-activity"; import moment, { Moment } from "moment"; import { groupBy } from "../../../../utils/utils"; -import { MatTableDataSource, MatTableModule } from "@angular/material/table"; -import { MatPaginator, MatPaginatorModule } from "@angular/material/paginator"; +import { MatTableModule } from "@angular/material/table"; import { DynamicComponent } from "../../../../core/config/dynamic-components/dynamic-component.decorator"; import { NgForOf, NgIf } from "@angular/common"; import { DisplayEntityComponent } from "../../../../core/basic-datatypes/entity/display-entity/display-entity.component"; -import { DashboardWidgetComponent } from "../../../../core/dashboard/dashboard-widget/dashboard-widget.component"; import { AttendanceDayBlockComponent } from "./attendance-day-block/attendance-day-block.component"; -import { WidgetContentComponent } from "../../../../core/dashboard/dashboard-widget/widget-content/widget-content.component"; import { DashboardWidget } from "../../../../core/dashboard/dashboard-widget/dashboard-widget"; import { EventNote } from "../../model/event-note"; +import { DashboardListWidgetComponent } from "../../../../core/dashboard/dashboard-list-widget/dashboard-list-widget.component"; interface AttendanceWeekRow { childId: string; @@ -36,21 +28,19 @@ interface AttendanceWeekRow { selector: "app-attendance-week-dashboard", templateUrl: "./attendance-week-dashboard.component.html", styleUrls: ["./attendance-week-dashboard.component.scss"], + standalone: true, imports: [ NgIf, MatTableModule, NgForOf, - MatPaginatorModule, DisplayEntityComponent, - DashboardWidgetComponent, - WidgetContentComponent, AttendanceDayBlockComponent, + DashboardListWidgetComponent, ], - standalone: true, }) export class AttendanceWeekDashboardComponent extends DashboardWidget - implements OnInit, AfterViewInit + implements OnInit { static getRequiredEntities() { return EventNote.ENTITY_TYPE; @@ -90,10 +80,7 @@ export class AttendanceWeekDashboardComponent */ @Input() attendanceStatusType: string; - @ViewChild("paginator") paginator: MatPaginator; - tableDataSource = new MatTableDataSource(); - - loadingDone = false; + entries: AttendanceWeekRow[][]; constructor( private attendanceService: AttendanceService, @@ -137,10 +124,9 @@ export class AttendanceWeekDashboardComponent } const groups = groupBy(records, "childId"); - this.tableDataSource.data = groups + this.entries = groups .filter(([childId]) => lowAttendanceCases.has(childId)) .map(([_, attendance]) => attendance); - this.loadingDone = true; } private generateRowsFromActivityAttendance( @@ -196,8 +182,4 @@ export class AttendanceWeekDashboardComponent goToChild(childId: string) { this.router.navigate([Child.route, childId]); } - - ngAfterViewInit() { - this.tableDataSource.paginator = this.paginator; - } } diff --git a/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.html b/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.html index b7dd268cac..bed67fd18d 100644 --- a/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.html +++ b/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.html @@ -1,47 +1,45 @@ - - -
- - - - - - - - - +
+
- -
+ + + + + + + + + + + + - - - + +
+ + + + >  + + {{ entityNoteInfo.daysSinceLastNote | number: "1.0-0" }} days + - - >  - - {{ entityNoteInfo.daysSinceLastNote | number: "1.0-0" }} days -
- - -
-
+
- - - +
+ diff --git a/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.spec.ts b/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.spec.ts index f62ed2e634..1547930e35 100644 --- a/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.spec.ts +++ b/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.spec.ts @@ -59,7 +59,7 @@ describe("NotesDashboardComponent", () => { component.ngOnInit(); tick(); - expect(component.dataSource.data).toHaveSize(3); + expect(component.entries).toHaveSize(3); })); }); @@ -92,9 +92,9 @@ describe("NotesDashboardComponent", () => { tick(); - expect(component.dataSource.data).toHaveSize(3); + expect(component.entries).toHaveSize(3); - expect(component.dataSource.data[0]).toEqual({ + expect(component.entries[0]).toEqual({ entityId: "5", daysSinceLastNote: 50, moreThanDaysSince: false, @@ -110,11 +110,11 @@ describe("NotesDashboardComponent", () => { component.ngOnInit(); tick(); - expect(component.dataSource.data).toHaveSize(1); + expect(component.entries).toHaveSize(1); - expect(component.dataSource.data[0].entityId).toBe(childId1); - expect(component.dataSource.data[0].moreThanDaysSince).toBeTrue(); - expect(component.dataSource.data[0].daysSinceLastNote).toBeFinite(); + expect(component.entries[0].entityId).toBe(childId1); + expect(component.entries[0].moreThanDaysSince).toBeTrue(); + expect(component.entries[0].daysSinceLastNote).toBeFinite(); })); it("should load notes related to the configured entity", () => { diff --git a/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.ts b/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.ts index 1329575419..32ea5ff410 100644 --- a/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.ts +++ b/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.ts @@ -1,24 +1,16 @@ -import { - AfterViewInit, - Component, - Input, - OnInit, - ViewChild, -} from "@angular/core"; +import { Component, Input, OnInit } from "@angular/core"; import { ChildrenService } from "../../../children/children.service"; import moment from "moment"; -import { MatTableDataSource, MatTableModule } from "@angular/material/table"; -import { MatPaginator, MatPaginatorModule } from "@angular/material/paginator"; +import { MatTableModule } from "@angular/material/table"; import { DynamicComponent } from "../../../../core/config/dynamic-components/dynamic-component.decorator"; import { Child } from "../../../children/model/child"; import { EntityRegistry } from "../../../../core/entity/database-entity.decorator"; import { EntityConstructor } from "../../../../core/entity/model/entity"; import { DecimalPipe, NgIf } from "@angular/common"; import { DisplayEntityComponent } from "../../../../core/basic-datatypes/entity/display-entity/display-entity.component"; -import { DashboardWidgetComponent } from "../../../../core/dashboard/dashboard-widget/dashboard-widget.component"; -import { WidgetContentComponent } from "../../../../core/dashboard/dashboard-widget/widget-content/widget-content.component"; import { DashboardWidget } from "../../../../core/dashboard/dashboard-widget/dashboard-widget"; import { Note } from "../../model/note"; +import { DashboardListWidgetComponent } from "../../../../core/dashboard/dashboard-list-widget/dashboard-list-widget.component"; interface NotesDashboardConfig { entity?: string; @@ -38,20 +30,18 @@ interface NotesDashboardConfig { selector: "app-no-recent-notes-dashboard", templateUrl: "./notes-dashboard.component.html", styleUrls: ["./notes-dashboard.component.scss"], + standalone: true, imports: [ NgIf, MatTableModule, DisplayEntityComponent, DecimalPipe, - MatPaginatorModule, - DashboardWidgetComponent, - WidgetContentComponent, + DashboardListWidgetComponent, ], - standalone: true, }) export class NotesDashboardComponent extends DashboardWidget - implements OnInit, AfterViewInit, NotesDashboardConfig + implements OnInit, NotesDashboardConfig { static getRequiredEntities(config: NotesDashboardConfig) { return config?.entity || Note.ENTITY_TYPE; @@ -78,14 +68,10 @@ export class NotesDashboardComponent /** * Entities displayed in the template with additional "daysSinceLastNote" field */ - dataSource = new MatTableDataSource(); + entries: EntityWithRecentNoteInfo[]; subtitle: string; - @ViewChild("paginator") paginator: MatPaginator; - - isLoading = true; - constructor( private childrenService: ChildrenService, private entities: EntityRegistry, @@ -116,10 +102,6 @@ export class NotesDashboardComponent } } - ngAfterViewInit() { - this.dataSource.paginator = this.paginator; - } - private async loadConcernedEntities( filter: (stat: [string, number]) => boolean, dayRangeBoundary: number, @@ -133,12 +115,10 @@ export class NotesDashboardComponent this._entity.ENTITY_TYPE, queryRange, ); - this.dataSource.data = Array.from(recentNotesMap) + this.entries = Array.from(recentNotesMap) .filter(filter) .map((stat) => statsToEntityWithRecentNoteInfo(stat, queryRange)) .sort((a, b) => order * (b.daysSinceLastNote - a.daysSinceLastNote)); - - this.isLoading = false; } get tooltip(): string { diff --git a/src/app/core/config/dynamic-components/dynamic-component.directive.ts b/src/app/core/config/dynamic-components/dynamic-component.directive.ts index ea92b0b1c8..3a40bd8012 100644 --- a/src/app/core/config/dynamic-components/dynamic-component.directive.ts +++ b/src/app/core/config/dynamic-components/dynamic-component.directive.ts @@ -48,11 +48,12 @@ export class DynamicComponentDirective implements OnChanges { this.appDynamicComponent.component, )(); } catch (e) { - this.logger.error( - `Failed to load dynamic component:\n${JSON.stringify( + this.logger.error({ + message: `Failed to load dynamic component:\n${JSON.stringify( this.appDynamicComponent, )}`, - ); + error: e, + }); // abort if component failed to load return; } diff --git a/src/app/core/config/dynamic-routing/not-found/not-found.component.spec.ts b/src/app/core/config/dynamic-routing/not-found/not-found.component.spec.ts index 49b62a4d6b..135941522d 100644 --- a/src/app/core/config/dynamic-routing/not-found/not-found.component.spec.ts +++ b/src/app/core/config/dynamic-routing/not-found/not-found.component.spec.ts @@ -11,7 +11,7 @@ describe("NotFoundComponent", () => { let mockLogging: jasmine.SpyObj; beforeEach(async () => { - mockLogging = jasmine.createSpyObj(LoggingService.name, ["warn"]); + mockLogging = jasmine.createSpyObj(LoggingService.name, ["debug"]); await TestBed.configureTestingModule({ imports: [NotFoundComponent, RouterTestingModule], providers: [ @@ -32,8 +32,8 @@ describe("NotFoundComponent", () => { }); it("should call logging service with current route", () => { - expect(mockLogging.warn).toHaveBeenCalledWith( - "Could not find component for route: /some/path", + expect(mockLogging.debug).toHaveBeenCalledWith( + "Could not find route: /some/path", ); }); }); diff --git a/src/app/core/config/dynamic-routing/not-found/not-found.component.ts b/src/app/core/config/dynamic-routing/not-found/not-found.component.ts index 3cf6987ac6..e83ad0db73 100644 --- a/src/app/core/config/dynamic-routing/not-found/not-found.component.ts +++ b/src/app/core/config/dynamic-routing/not-found/not-found.component.ts @@ -18,8 +18,10 @@ export class NotFoundComponent implements OnInit { ) {} ngOnInit() { - this.loggingService.warn( - "Could not find component for route: " + this.location.pathname, - ); + if (!this.location.pathname.endsWith("/404")) { + this.loggingService.debug( + "Could not find route: " + this.location.pathname, + ); + } } } diff --git a/src/app/core/logging/logging.service.ts b/src/app/core/logging/logging.service.ts index 63eee4030a..a91e5fd709 100644 --- a/src/app/core/logging/logging.service.ts +++ b/src/app/core/logging/logging.service.ts @@ -1,8 +1,8 @@ import { Injectable } from "@angular/core"; import { LogLevel } from "./log-level"; import * as Sentry from "@sentry/browser"; -import { environment } from "../../../environments/environment"; import { BrowserOptions, SeverityLevel } from "@sentry/browser"; +import { environment } from "../../../environments/environment"; /* eslint-disable no-console */ @@ -123,7 +123,11 @@ export class LoggingService { private logToRemoteMonitoring(message: any, logLevel: LogLevel) { if (logLevel === LogLevel.ERROR) { - Sentry.captureException(message); + if (message instanceof Error) { + Sentry.captureException(message); + } else { + Sentry.captureException(new Error(message?.error ?? message), message); + } } else { Sentry.captureMessage(message, this.translateLogLevel(logLevel)); } diff --git a/src/app/core/permissions/permission-guard/abstract-permission.guard.spec.ts b/src/app/core/permissions/permission-guard/abstract-permission.guard.spec.ts new file mode 100644 index 0000000000..cc44dc4353 --- /dev/null +++ b/src/app/core/permissions/permission-guard/abstract-permission.guard.spec.ts @@ -0,0 +1,46 @@ +import { TestBed } from "@angular/core/testing"; +import { Router, Routes } from "@angular/router"; +import { AbstractPermissionGuard } from "./abstract-permission.guard"; +import { Injectable } from "@angular/core"; +import { DynamicComponentConfig } from "../../config/dynamic-components/dynamic-component-config.interface"; + +@Injectable() +class TestPermissionGuard extends AbstractPermissionGuard { + constructor(router: Router) { + super(router); + } + + protected async canAccessRoute( + routeData: DynamicComponentConfig, + ): Promise { + return routeData?.config; + } +} + +describe("EntityPermissionGuard", () => { + let guard: TestPermissionGuard; + + let testRoutes: Routes; + + beforeEach(() => { + testRoutes = [{ path: "**", data: { config: true } }]; + + TestBed.configureTestingModule({ + providers: [ + TestPermissionGuard, + { provide: Router, useValue: { config: testRoutes } }, + ], + }); + guard = TestBed.inject(TestPermissionGuard); + }); + + it("should be created", () => { + expect(guard).toBeTruthy(); + }); + + it("should get route config also for '**' path", async () => { + const result = await guard.checkRoutePermissions("url"); + + expect(result).toBeTrue(); + }); +}); diff --git a/src/app/core/permissions/permission-guard/abstract-permission.guard.ts b/src/app/core/permissions/permission-guard/abstract-permission.guard.ts index a59e038f59..cd0d0e76d6 100644 --- a/src/app/core/permissions/permission-guard/abstract-permission.guard.ts +++ b/src/app/core/permissions/permission-guard/abstract-permission.guard.ts @@ -63,6 +63,7 @@ export abstract class AbstractPermissionGuard implements CanActivate { function isPathMatch(genericPath: string, path: string) { const routeRegex = genericPath + .replace(/\*/g, ".*") // allow for wildcard routes in regex .split("/") // replace params with wildcard regex .map((part) => (part.startsWith(":") ? "[^/]*" : part)) diff --git a/src/app/core/session/auth/keycloak/keycloak-auth.service.ts b/src/app/core/session/auth/keycloak/keycloak-auth.service.ts index 4dd986844f..b4560a8339 100644 --- a/src/app/core/session/auth/keycloak/keycloak-auth.service.ts +++ b/src/app/core/session/auth/keycloak/keycloak-auth.service.ts @@ -57,6 +57,10 @@ export class KeycloakAuthService { } private processToken(token: string): SessionInfo { + if (!token) { + throw new Error("No token received from Keycloak"); + } + this.accessToken = token; this.logSuccessfulAuth(); const parsedToken: ParsedJWT = parseJwt(this.accessToken); diff --git a/src/app/core/support/support/support.component.ts b/src/app/core/support/support/support.component.ts index f29da86cf9..997737b433 100644 --- a/src/app/core/support/support/support.component.ts +++ b/src/app/core/support/support/support.component.ts @@ -133,7 +133,7 @@ export class SupportComponent implements OnInit { user: { name: this.sessionInfo.name }, level: "debug", extra: { - currentUser: this.currentUser.getId(), + currentUser: this.currentUser?.getId(), currentSyncState: this.currentSyncState, lastSync: this.lastSync, lastRemoteLogin: this.lastRemoteLogin, diff --git a/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.html b/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.html index 9824d9f747..ea31f4a2af 100644 --- a/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.html +++ b/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.html @@ -1,51 +1,36 @@ - - -
- - no birthdays in the next {{ threshold - 1 }} days - -
-
- - - - - - - - - - - - - - - - -
- - - {{ entity.birthday | date: "E dd.MM" }} - {{ entity.newAge }} yrs
-
- - -
-
+
+ + + + + + + + + + + + + + + + +
+ + + {{ entity.birthday | date: "E dd.MM" }} + {{ entity.newAge }} yrs
+
+ diff --git a/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.spec.ts b/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.spec.ts index f058325831..1903e95c5d 100644 --- a/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.spec.ts +++ b/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.spec.ts @@ -65,7 +65,7 @@ describe("BirthdayDashboardComponent", () => { tick(); const expectedNextBirthday = birthdaySoon.add(10, "years"); - expect(component.dataSource.data).toEqual([ + expect(component.entries).toEqual([ { entity: child1, birthday: expectedNextBirthday.toDate(), newAge: 10 }, ]); })); @@ -90,7 +90,7 @@ describe("BirthdayDashboardComponent", () => { const expectedFirstBirthday = firstBirthday.add(12, "years"); const expectedSecondBirthday = secondBirthday.add(15, "years"); - expect(component.dataSource.data).toEqual([ + expect(component.entries).toEqual([ { entity: child1, birthday: expectedFirstBirthday.toDate(), newAge: 12 }, { entity: child2, birthday: expectedSecondBirthday.toDate(), newAge: 15 }, ]); @@ -123,7 +123,7 @@ describe("BirthdayDashboardComponent", () => { component.ngOnInit(); tick(); - expect(component.dataSource.data).toEqual([ + expect(component.entries).toEqual([ { entity: e1, birthday: moment().add(1, "day").startOf("day").toDate(), diff --git a/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.ts b/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.ts index 69610e9273..c53d14b9f7 100644 --- a/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.ts +++ b/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.ts @@ -1,21 +1,14 @@ -import { - AfterViewInit, - Component, - Input, - OnInit, - ViewChild, -} from "@angular/core"; +import { Component, Input, OnInit } from "@angular/core"; import { EntityMapperService } from "../../../../core/entity/entity-mapper/entity-mapper.service"; import { Child } from "../../../../child-dev-project/children/model/child"; import { DynamicComponent } from "../../../../core/config/dynamic-components/dynamic-component.decorator"; -import { MatTableDataSource, MatTableModule } from "@angular/material/table"; -import { MatPaginator, MatPaginatorModule } from "@angular/material/paginator"; +import { MatTableModule } from "@angular/material/table"; import { Entity } from "../../../../core/entity/model/entity"; import { DatePipe, NgIf } from "@angular/common"; import { DisplayEntityComponent } from "../../../../core/basic-datatypes/entity/display-entity/display-entity.component"; -import { DashboardWidgetComponent } from "../../../../core/dashboard/dashboard-widget/dashboard-widget.component"; -import { WidgetContentComponent } from "../../../../core/dashboard/dashboard-widget/widget-content/widget-content.component"; + import { DashboardWidget } from "../../../../core/dashboard/dashboard-widget/dashboard-widget"; +import { DashboardListWidgetComponent } from "../../../../core/dashboard/dashboard-list-widget/dashboard-list-widget.component"; interface BirthdayDashboardConfig { entities: EntityPropertyMap; @@ -27,26 +20,23 @@ interface BirthdayDashboardConfig { selector: "app-birthday-dashboard", templateUrl: "./birthday-dashboard.component.html", styleUrls: ["./birthday-dashboard.component.scss"], + standalone: true, imports: [ NgIf, MatTableModule, DisplayEntityComponent, DatePipe, - MatPaginatorModule, - DashboardWidgetComponent, - WidgetContentComponent, + DashboardListWidgetComponent, ], - standalone: true, }) export class BirthdayDashboardComponent extends DashboardWidget - implements BirthdayDashboardConfig, OnInit, AfterViewInit + implements BirthdayDashboardConfig, OnInit { static getRequiredEntities(config: BirthdayDashboardConfig) { return config?.entities ? Object.keys(config.entities) : Child.ENTITY_TYPE; } - @ViewChild(MatPaginator) paginator: MatPaginator; private readonly today: Date; /** @@ -64,8 +54,7 @@ export class BirthdayDashboardComponent */ @Input() threshold = 32; - dataSource = new MatTableDataSource(); - isLoading = true; + entries: EntityWithBirthday[]; constructor(private entityMapper: EntityMapperService) { super(); @@ -91,12 +80,7 @@ export class BirthdayDashboardComponent data.sort( (a, b) => this.daysUntil(a.birthday) - this.daysUntil(b.birthday), ); - this.dataSource.data = data; - this.isLoading = false; - } - - ngAfterViewInit() { - this.dataSource.paginator = this.paginator; + this.entries = data; } private getNextBirthday(dateOfBirth: Date): Date { diff --git a/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html b/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html index cbf45a7d32..e1debc6bc1 100644 --- a/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html +++ b/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html @@ -1,13 +1,13 @@ - - +
@@ -50,5 +50,5 @@ - - + + diff --git a/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.scss b/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.scss index 36511c292b..3ac3adfea4 100644 --- a/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.scss +++ b/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.scss @@ -15,4 +15,8 @@ position: absolute; left: 0; bottom: 0; + margin: 4px; +} +.widget-container { + position: relative; } diff --git a/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.ts b/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.ts index 71d7268521..b852f7e699 100644 --- a/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.ts +++ b/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.ts @@ -13,25 +13,23 @@ import { MatTableModule } from "@angular/material/table"; import { MatProgressBarModule } from "@angular/material/progress-bar"; import { MatButtonModule } from "@angular/material/button"; import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; -import { DashboardWidgetComponent } from "../../../../core/dashboard/dashboard-widget/dashboard-widget.component"; -import { WidgetContentComponent } from "../../../../core/dashboard/dashboard-widget/widget-content/widget-content.component"; import { SyncStateSubject } from "../../../../core/session/session-type"; import { DashboardWidget } from "../../../../core/dashboard/dashboard-widget/dashboard-widget"; +import { DashboardListWidgetComponent } from "../../../../core/dashboard/dashboard-list-widget/dashboard-list-widget.component"; @Component({ selector: "app-progress-dashboard", templateUrl: "./progress-dashboard.component.html", styleUrls: ["./progress-dashboard.component.scss"], + standalone: true, imports: [ PercentPipe, MatTableModule, MatProgressBarModule, MatButtonModule, FontAwesomeModule, - DashboardWidgetComponent, - WidgetContentComponent, + DashboardListWidgetComponent, ], - standalone: true, }) @DynamicComponent("ProgressDashboard") export class ProgressDashboardComponent