diff --git a/client/package.json b/client/package.json index 8884480d..360c7be6 100644 --- a/client/package.json +++ b/client/package.json @@ -9,7 +9,7 @@ "watch": "ng build --watch --configuration development", "test": "ng test", "test:ci": "ng test --no-watch --no-progress --code-coverage --browsers=ChromeHeadless", - "i18n:extract": "transloco-keys-manager extract -d \"\"", + "i18n:extract": "transloco-keys-manager extract -d \"\" -l de", "i18n:find": "transloco-keys-manager find", "cypress": "cypress open", "sentry:sourcemaps": "sentry-cli sourcemaps inject --org localcrag --project localcrag-client ./dist/client && sentry-cli sourcemaps upload --org localcrag --project localcrag-client ./dist/client", @@ -84,6 +84,9 @@ "**/*.{js,ts}": [ "prettier --write .", "eslint" + ], + "src/assets/i18n/**/*.{json}": [ + "prettier --write ." ] } } diff --git a/client/src/app/models/scale.ts b/client/src/app/models/scale.ts index 5cdd299e..3d2ce067 100644 --- a/client/src/app/models/scale.ts +++ b/client/src/app/models/scale.ts @@ -5,6 +5,13 @@ export interface Grade { value: number; } +export type StackedChartBrackets = number[]; +export type BarChartBracket = { + name: string; + value: number; +}; +export type BarChartBrackets = BarChartBracket[]; + export type GradeDistribution = Record< LineType, Record> @@ -18,7 +25,10 @@ export class Scale { lineType: LineType; name: string; grades?: Grade[]; - gradeBrackets: number[]; + gradeBrackets: { + stackedChartBrackets: StackedChartBrackets; + barChartBrackets: BarChartBrackets; + }; public static deserialize(payload: any): Scale { const scale = new Scale(); diff --git a/client/src/app/modules/archive/archive-button/archive-button.component.ts b/client/src/app/modules/archive/archive-button/archive-button.component.ts index a5ba08df..c416a5b4 100644 --- a/client/src/app/modules/archive/archive-button/archive-button.component.ts +++ b/client/src/app/modules/archive/archive-button/archive-button.component.ts @@ -5,7 +5,6 @@ import { SharedModule } from 'primeng/api'; import { Store } from '@ngrx/store'; import { TranslocoDirective, TranslocoService } from '@jsverse/transloco'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { Line } from '../../../models/line'; import { Crag } from '../../../models/crag'; import { Sector } from '../../../models/sector'; @@ -57,19 +56,13 @@ export class ArchiveButtonComponent { const resultHandler = { next: (_) => { this.store.dispatch( - toastNotification( - this.getCurrentState() - ? NotificationIdentifier.ARCHIVED - : NotificationIdentifier.UNARCHIVED, - ), + toastNotification(this.getCurrentState() ? 'ARCHIVED' : 'UNARCHIVED'), ); }, error: () => { this.store.dispatch( toastNotification( - this.getCurrentState() - ? NotificationIdentifier.ARCHIVED_ERROR - : NotificationIdentifier.UNARCHIVED_ERROR, + this.getCurrentState() ? 'ARCHIVED_ERROR' : 'UNARCHIVED_ERROR', ), ); }, diff --git a/client/src/app/modules/area/area-form/area-form.component.ts b/client/src/app/modules/area/area-form/area-form.component.ts index 1ea490d8..a7672fcf 100644 --- a/client/src/app/modules/area/area-form/area-form.component.ts +++ b/client/src/app/modules/area/area-form/area-form.component.ts @@ -16,7 +16,6 @@ import { ConfirmationService, SelectItem } from 'primeng/api'; import { catchError, map } from 'rxjs/operators'; import { forkJoin, of } from 'rxjs'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { environment } from '../../../../environments/environment'; import { marker } from '@jsverse/transloco-keys-manager/marker'; import { Area } from '../../../models/area'; @@ -227,9 +226,7 @@ export class AreaFormComponent implements OnInit { if (this.area) { area.slug = this.area.slug; this.areasService.updateArea(area).subscribe((area) => { - this.store.dispatch( - toastNotification(NotificationIdentifier.AREA_UPDATED), - ); + this.store.dispatch(toastNotification('AREA_UPDATED')); this.router.navigate([ '/topo', this.cragSlug, @@ -240,9 +237,7 @@ export class AreaFormComponent implements OnInit { }); } else { this.areasService.createArea(area, this.sectorSlug).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.AREA_CREATED), - ); + this.store.dispatch(toastNotification('AREA_CREATED')); this.router.navigate([ '/topo', this.cragSlug, @@ -286,9 +281,7 @@ export class AreaFormComponent implements OnInit { */ public deleteArea() { this.areasService.deleteArea(this.area).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.AREA_DELETED), - ); + this.store.dispatch(toastNotification('AREA_DELETED')); this.router.navigate(['/topo', this.cragSlug, this.sectorSlug, 'areas']); this.loadingState = LoadingState.DEFAULT; }); diff --git a/client/src/app/modules/ascent/ascent-form/ascent-form.component.ts b/client/src/app/modules/ascent/ascent-form/ascent-form.component.ts index 576848a4..9e730369 100644 --- a/client/src/app/modules/ascent/ascent-form/ascent-form.component.ts +++ b/client/src/app/modules/ascent/ascent-form/ascent-form.component.ts @@ -22,7 +22,6 @@ import { Line } from '../../../models/line'; import { Store } from '@ngrx/store'; import { yearOfDateNotInFutureValidator } from '../../../utility/validators/year-not-in-future.validator'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { Ascent } from '../../../models/ascent'; import { AscentsService } from '../../../services/crud/ascents.service'; import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; @@ -223,9 +222,7 @@ export class AscentFormComponent implements OnInit { ascent.line = this.line; if (!this.editMode) { this.ascentsService.createAscent(ascent).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.ASCENT_ADDED), - ); + this.store.dispatch(toastNotification('ASCENT_ADDED')); this.loadingState = LoadingState.DEFAULT; this.store.dispatch( reloadAfterAscent({ ascendedLineId: this.line.id }), @@ -235,9 +232,7 @@ export class AscentFormComponent implements OnInit { } else { ascent.id = this.ascent.id; this.ascentsService.updateAscent(ascent).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.ASCENT_UPDATED), - ); + this.store.dispatch(toastNotification('ASCENT_UPDATED')); this.loadingState = LoadingState.DEFAULT; this.store.dispatch( reloadAfterAscent({ ascendedLineId: this.line.id }), diff --git a/client/src/app/modules/ascent/ascent-list/ascent-list.component.ts b/client/src/app/modules/ascent/ascent-list/ascent-list.component.ts index e3a37ef1..ffe82c90 100644 --- a/client/src/app/modules/ascent/ascent-list/ascent-list.component.ts +++ b/client/src/app/modules/ascent/ascent-list/ascent-list.component.ts @@ -1,4 +1,6 @@ import { + ChangeDetectionStrategy, + ChangeDetectorRef, Component, HostListener, Input, @@ -39,7 +41,6 @@ import { AscentFormComponent } from '../ascent-form/ascent-form.component'; import { AscentFormTitleComponent } from '../ascent-form-title/ascent-form-title.component'; import { environment } from '../../../../environments/environment'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { reloadAfterAscent } from '../../../ngrx/actions/ascent.actions'; import { Actions, ofType } from '@ngrx/effects'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; @@ -88,6 +89,7 @@ import { RegionService } from '../../../services/crud/region.service'; styleUrl: './ascent-list.component.scss', encapsulation: ViewEncapsulation.None, providers: [DialogService, ConfirmationService], + changeDetection: ChangeDetectionStrategy.OnPush, }) @UntilDestroy() export class AscentListComponent implements OnInit { @@ -133,6 +135,7 @@ export class AscentListComponent implements OnInit { private translocoService: TranslocoService, protected scalesService: ScalesService, private regionService: RegionService, + private cdr: ChangeDetectorRef, ) {} ngOnInit() { @@ -292,6 +295,7 @@ export class AscentListComponent implements OnInit { this.hasNextPage = ascents.hasNext; this.loadingFirstPage = LoadingState.DEFAULT; this.loadingAdditionalPage = LoadingState.DEFAULT; + this.cdr.detectChanges(); }); } } @@ -332,9 +336,7 @@ export class AscentListComponent implements OnInit { public deleteAscent(ascent: Ascent) { this.ascentsService.deleteAscent(ascent).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.ASCENT_DELETED), - ); + this.store.dispatch(toastNotification('ASCENT_DELETED')); this.store.dispatch( reloadAfterAscent({ ascendedLineId: ascent.line.id }), ); diff --git a/client/src/app/modules/ascent/project-climbed-form/project-climbed-form.component.ts b/client/src/app/modules/ascent/project-climbed-form/project-climbed-form.component.ts index e4917157..3a6400fa 100644 --- a/client/src/app/modules/ascent/project-climbed-form/project-climbed-form.component.ts +++ b/client/src/app/modules/ascent/project-climbed-form/project-climbed-form.component.ts @@ -18,7 +18,6 @@ import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; import { Store } from '@ngrx/store'; import { AscentsService } from '../../../services/crud/ascents.service'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { TranslocoDirective } from '@jsverse/transloco'; @Component({ @@ -75,9 +74,7 @@ export class ProjectClimbedFormComponent implements OnInit { .sendProjectClimbedMessage(message, this.line.id) .subscribe(() => { this.store.dispatch( - toastNotification( - NotificationIdentifier.PROJECT_CLIMBED_MESSAGE_SENT, - ), + toastNotification('PROJECT_CLIMBED_MESSAGE_SENT'), ); this.loadingState = LoadingState.DEFAULT; this.ref.close(); diff --git a/client/src/app/modules/blog/post-form/post-form.component.ts b/client/src/app/modules/blog/post-form/post-form.component.ts index 7e51d92c..38514dfc 100644 --- a/client/src/app/modules/blog/post-form/post-form.component.ts +++ b/client/src/app/modules/blog/post-form/post-form.component.ts @@ -17,7 +17,6 @@ import { of } from 'rxjs'; import { marker } from '@jsverse/transloco-keys-manager/marker'; import { environment } from '../../../../environments/environment'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { Post } from '../../../models/post'; import { PostsService } from '../../../services/crud/posts.service'; import { ButtonModule } from 'primeng/button'; @@ -158,17 +157,13 @@ export class PostFormComponent implements OnInit { if (this.post) { post.slug = this.post.slug; this.postsService.updatePost(post).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.POST_UPDATED), - ); + this.store.dispatch(toastNotification('POST_UPDATED')); this.router.navigate(['/news']); this.loadingState = LoadingState.DEFAULT; }); } else { this.postsService.createPost(post).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.POST_CREATED), - ); + this.store.dispatch(toastNotification('POST_CREATED')); this.router.navigate(['/news']); this.loadingState = LoadingState.DEFAULT; }); @@ -207,9 +202,7 @@ export class PostFormComponent implements OnInit { */ public deletePost() { this.postsService.deletePost(this.post).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.POST_DELETED), - ); + this.store.dispatch(toastNotification('POST_DELETED')); this.router.navigate(['/news']); this.loadingState = LoadingState.DEFAULT; }); diff --git a/client/src/app/modules/core/account-form/account-form.component.ts b/client/src/app/modules/core/account-form/account-form.component.ts index 3f064b81..998ab201 100644 --- a/client/src/app/modules/core/account-form/account-form.component.ts +++ b/client/src/app/modules/core/account-form/account-form.component.ts @@ -19,7 +19,6 @@ import { selectInstanceName } from '../../../ngrx/selectors/instance-settings.se import { marker } from '@jsverse/transloco-keys-manager/marker'; import { User } from '../../../models/user'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { selectCurrentUser } from '../../../ngrx/selectors/auth.selectors'; import { take } from 'rxjs/operators'; import { AvatarUploadComponent } from '../../shared/forms/controls/avatar-upload/avatar-upload.component'; @@ -94,9 +93,7 @@ export class AccountFormComponent implements OnInit { user.avatar = this.accountForm.get('avatar').value; this.usersService.updateAccount(user).subscribe((updatedUser) => { this.store.dispatch(updateAccountSettings({ user: updatedUser })); - this.store.dispatch( - toastNotification(NotificationIdentifier.ACCOUNT_SETTINGS_UPDATED), - ); + this.store.dispatch(toastNotification('ACCOUNT_SETTINGS_UPDATED')); this.loadingState = LoadingState.DEFAULT; this.emailChangedPostSave = emailChanged; }); diff --git a/client/src/app/modules/core/change-password/change-password.component.ts b/client/src/app/modules/core/change-password/change-password.component.ts index 600ca756..d90e2c0e 100644 --- a/client/src/app/modules/core/change-password/change-password.component.ts +++ b/client/src/app/modules/core/change-password/change-password.component.ts @@ -10,7 +10,6 @@ import { Store } from '@ngrx/store'; import { AppState } from '../../../ngrx/reducers'; import { AuthCrudService } from '../../../services/crud/auth-crud.service'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { passwordsValidator } from '../../../utility/validators/passwords.validator'; import { FormDirective } from '../../shared/forms/form.directive'; import { Router } from '@angular/router'; @@ -73,9 +72,7 @@ export class ChangePasswordComponent implements OnInit { .subscribe( () => { this.loading = false; - this.store.dispatch( - toastNotification(NotificationIdentifier.CHANGE_PASSWORD_SUCCESS), - ); + this.store.dispatch(toastNotification('CHANGE_PASSWORD_SUCCESS')); this.router.navigate(['']); }, () => { diff --git a/client/src/app/modules/core/instance-settings-form/instance-settings-form.component.ts b/client/src/app/modules/core/instance-settings-form/instance-settings-form.component.ts index 34318e04..034ac278 100644 --- a/client/src/app/modules/core/instance-settings-form/instance-settings-form.component.ts +++ b/client/src/app/modules/core/instance-settings-form/instance-settings-form.component.ts @@ -14,7 +14,6 @@ import { TranslocoDirective } from '@jsverse/transloco'; import { catchError } from 'rxjs/operators'; import { of } from 'rxjs'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { InstanceSettings } from '../../../models/instance-settings'; import { InstanceSettingsService } from '../../../services/crud/instance-settings.service'; import { ButtonModule } from 'primeng/button'; @@ -183,11 +182,7 @@ export class InstanceSettingsFormComponent implements OnInit { .updateInstanceSettings(instanceSettings) .subscribe({ next: (instanceSettings) => { - this.store.dispatch( - toastNotification( - NotificationIdentifier.INSTANCE_SETTINGS_UPDATED, - ), - ); + this.store.dispatch(toastNotification('INSTANCE_SETTINGS_UPDATED')); this.loadingState = LoadingState.DEFAULT; this.store.dispatch( updateInstanceSettings({ settings: instanceSettings }), @@ -198,13 +193,11 @@ export class InstanceSettingsFormComponent implements OnInit { if (e.error?.message == 'MIGRATION_IMPOSSIBLE') { this.store.dispatch( toastNotification( - NotificationIdentifier.INSTANCE_SETTINGS_ERROR_MIGRATION_IMPOSSIBLE, + 'INSTANCE_SETTINGS_ERROR_MIGRATION_IMPOSSIBLE', ), ); } else { - this.store.dispatch( - toastNotification(NotificationIdentifier.UNKNOWN_ERROR), - ); + this.store.dispatch(toastNotification('UNKNOWN_ERROR')); } }, }); diff --git a/client/src/app/modules/core/register/register.component.ts b/client/src/app/modules/core/register/register.component.ts index e0a06c53..62537d81 100644 --- a/client/src/app/modules/core/register/register.component.ts +++ b/client/src/app/modules/core/register/register.component.ts @@ -18,7 +18,6 @@ import { Store } from '@ngrx/store'; import { AppState } from '../../../ngrx/reducers'; import { User } from '../../../models/user'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { UsersService } from '../../../services/crud/users.service'; import { selectInstanceName } from '../../../ngrx/selectors/instance-settings.selectors'; import { marker } from '@jsverse/transloco-keys-manager/marker'; @@ -86,9 +85,7 @@ export class RegisterComponent implements OnInit { this.registrationForm.get('emails.email').value as string ).toLowerCase(); this.usersService.registerUser(user).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.USER_REGISTERED), - ); + this.store.dispatch(toastNotification('USER_REGISTERED')); this.router.navigate(['/register-check-mailbox']); this.loadingState = LoadingState.DEFAULT; }); diff --git a/client/src/app/modules/core/reset-password/reset-password.component.ts b/client/src/app/modules/core/reset-password/reset-password.component.ts index efca6cd0..0a96f1d4 100644 --- a/client/src/app/modules/core/reset-password/reset-password.component.ts +++ b/client/src/app/modules/core/reset-password/reset-password.component.ts @@ -18,7 +18,6 @@ import { } from '../../../ngrx/selectors/auth.selectors'; import { take } from 'rxjs/operators'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { resetPassword } from 'src/app/ngrx/actions/auth.actions'; import { passwordsValidator } from '../../../utility/validators/passwords.validator'; import { Title } from '@angular/platform-browser'; @@ -64,9 +63,7 @@ export class ResetPasswordComponent implements OnInit, OnDestroy { if (isLoggedIn) { this.router.navigate(['/']); this.store.dispatch( - toastNotification( - NotificationIdentifier.LOG_OUT_TO_USE_THIS_FUNCTION, - ), + toastNotification('LOG_OUT_TO_USE_THIS_FUNCTION'), ); } }); diff --git a/client/src/app/modules/crag/crag-form/crag-form.component.ts b/client/src/app/modules/crag/crag-form/crag-form.component.ts index 0d3fbcc8..cf7e77ce 100644 --- a/client/src/app/modules/crag/crag-form/crag-form.component.ts +++ b/client/src/app/modules/crag/crag-form/crag-form.component.ts @@ -13,7 +13,6 @@ import { Crag } from '../../../models/crag'; import { environment } from '../../../../environments/environment'; import { Store } from '@ngrx/store'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { ActivatedRoute, Router } from '@angular/router'; import { forkJoin, of } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; @@ -211,17 +210,13 @@ export class CragFormComponent implements OnInit { if (this.crag) { crag.slug = this.crag.slug; this.cragsService.updateCrag(crag).subscribe((crag) => { - this.store.dispatch( - toastNotification(NotificationIdentifier.CRAG_UPDATED), - ); + this.store.dispatch(toastNotification('CRAG_UPDATED')); this.router.navigate(['/topo', crag.slug]); this.loadingState = LoadingState.DEFAULT; }); } else { this.cragsService.createCrag(crag).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.CRAG_CREATED), - ); + this.store.dispatch(toastNotification('CRAG_CREATED')); this.router.navigate(['/topo']); this.loadingState = LoadingState.DEFAULT; }); @@ -260,9 +255,7 @@ export class CragFormComponent implements OnInit { */ public deleteCrag() { this.cragsService.deleteCrag(this.crag).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.CRAG_DELETED), - ); + this.store.dispatch(toastNotification('CRAG_DELETED')); this.router.navigate(['/topo']); this.loadingState = LoadingState.DEFAULT; }); diff --git a/client/src/app/modules/gallery/gallery-form/gallery-form.component.ts b/client/src/app/modules/gallery/gallery-form/gallery-form.component.ts index 9a4eef06..8792ae56 100644 --- a/client/src/app/modules/gallery/gallery-form/gallery-form.component.ts +++ b/client/src/app/modules/gallery/gallery-form/gallery-form.component.ts @@ -24,7 +24,6 @@ import { SearchService } from '../../../services/crud/search.service'; import { ObjectType, Tag } from '../../../models/tag'; import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { Store } from '@ngrx/store'; import { EMPTY, Observable } from 'rxjs'; import { LinesService } from '../../../services/crud/lines.service'; @@ -172,9 +171,7 @@ export class GalleryFormComponent implements OnInit { .subscribe((galleryImage) => { this.loadingState = LoadingState.DEFAULT; this.ref.close(galleryImage); - this.store.dispatch( - toastNotification(NotificationIdentifier.GALLERY_IMAGE_UPDATED), - ); + this.store.dispatch(toastNotification('GALLERY_IMAGE_UPDATED')); }); } else { this.galleryService @@ -182,9 +179,7 @@ export class GalleryFormComponent implements OnInit { .subscribe((galleryImage) => { this.loadingState = LoadingState.DEFAULT; this.ref.close(galleryImage); - this.store.dispatch( - toastNotification(NotificationIdentifier.GALLERY_IMAGE_CREATED), - ); + this.store.dispatch(toastNotification('GALLERY_IMAGE_CREATED')); }); } } else { diff --git a/client/src/app/modules/gallery/gallery/gallery.component.ts b/client/src/app/modules/gallery/gallery/gallery.component.ts index ce4256ca..e5de327c 100644 --- a/client/src/app/modules/gallery/gallery/gallery.component.ts +++ b/client/src/app/modules/gallery/gallery/gallery.component.ts @@ -1,4 +1,9 @@ -import { Component, OnInit } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnInit, +} from '@angular/core'; import { GalleryService } from '../../../services/crud/gallery.service'; import { GalleryImage } from '../../../models/gallery-image'; import { ObjectType } from '../../../models/tag'; @@ -17,7 +22,6 @@ import { HasPermissionDirective } from '../../shared/directives/has-permission.d import { ConfirmPopupModule } from 'primeng/confirmpopup'; import { environment } from '../../../../environments/environment'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { Store } from '@ngrx/store'; import { ConfirmationService } from 'primeng/api'; import { GalleryImageSkeletonComponent } from '../gallery-image-skeleton/gallery-image-skeleton.component'; @@ -45,6 +49,7 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll'; templateUrl: './gallery.component.html', styleUrl: './gallery.component.scss', providers: [DialogService, ConfirmationService], + changeDetection: ChangeDetectionStrategy.OnPush, }) @UntilDestroy() export class GalleryComponent implements OnInit { @@ -66,6 +71,7 @@ export class GalleryComponent implements OnInit { private confirmationService: ConfirmationService, private route: ActivatedRoute, private translocoService: TranslocoService, + private cdr: ChangeDetectorRef, ) {} ngOnInit(): void { @@ -137,6 +143,7 @@ export class GalleryComponent implements OnInit { this.hasNextPage = images.hasNext; this.loadingFirstPage = LoadingState.DEFAULT; this.loadingAdditionalPage = LoadingState.DEFAULT; + this.cdr.detectChanges(); }), ) .subscribe(); @@ -159,6 +166,7 @@ export class GalleryComponent implements OnInit { if (galleryImage) { if (this.images.map((i) => i.id).indexOf(galleryImage.id) === -1) { this.images.unshift(galleryImage); + this.cdr.detectChanges(); } } }); @@ -189,9 +197,8 @@ export class GalleryComponent implements OnInit { deleteImage(image: GalleryImage) { this.galleryService.deleteGalleryImage(image.id).subscribe(() => { this.images = this.images.filter((i) => i.id !== image.id); - this.store.dispatch( - toastNotification(NotificationIdentifier.GALLERY_IMAGE_DELETED), - ); + this.store.dispatch(toastNotification('GALLERY_IMAGE_DELETED')); + this.cdr.detectChanges(); }); } @@ -210,6 +217,7 @@ export class GalleryComponent implements OnInit { this.images = this.images.map((i) => i.id === galleryImage.id ? galleryImage : i, ); + this.cdr.detectChanges(); } }); } diff --git a/client/src/app/modules/history/history-list/history-list.component.ts b/client/src/app/modules/history/history-list/history-list.component.ts index 864e6ec3..f6f451f2 100644 --- a/client/src/app/modules/history/history-list/history-list.component.ts +++ b/client/src/app/modules/history/history-list/history-list.component.ts @@ -1,4 +1,10 @@ -import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnInit, + ViewEncapsulation, +} from '@angular/core'; import { BreadcrumbModule } from 'primeng/breadcrumb'; import { CardModule } from 'primeng/card'; import { AsyncPipe, NgClass, NgIf } from '@angular/common'; @@ -25,6 +31,7 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { MessageModule } from 'primeng/message'; import { ScalesService } from '../../../services/crud/scales.service'; import { map } from 'rxjs/operators'; +import { TranslateSpecialGradesPipe } from '../../shared/pipes/translate-special-grades.pipe'; @Component({ selector: 'lc-history-list', @@ -48,6 +55,7 @@ import { map } from 'rxjs/operators'; templateUrl: './history-list.component.html', styleUrl: './history-list.component.scss', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class HistoryListComponent implements OnInit { public loadingStates = LoadingState; @@ -61,10 +69,12 @@ export class HistoryListComponent implements OnInit { constructor( private historyService: HistoryService, + private translateSpecialGradesPipe: TranslateSpecialGradesPipe, private router: Router, private store: Store, private transloco: TranslocoService, private scalesService: ScalesService, + private cdr: ChangeDetectorRef, ) {} loadFirstPage() { @@ -96,6 +106,7 @@ export class HistoryListComponent implements OnInit { this.hasNextPage = historyItems.hasNext; this.loadingFirstPage = LoadingState.DEFAULT; this.loadingAdditionalPage = LoadingState.DEFAULT; + this.cdr.detectChanges(); }); } } @@ -127,8 +138,13 @@ export class HistoryListComponent implements OnInit { if (event.type === HistoryItemType.UPDATED) { switch (event.objectType) { case ObjectType.Line: - /** t(history.grading_changed) */ - return this.transloco.translate('history.grading_changed'); + if (Number(event.newValue) >= 0) { + /** t(history.grading_changed) */ + return this.transloco.translate('history.grading_changed'); + } else { + /** t(history.project_status_changed) */ + return this.transloco.translate('history.project_status_changed'); + } default: return ''; } @@ -153,16 +169,33 @@ export class HistoryListComponent implements OnInit { Number(event.newValue), ), ]).pipe( - map((oldGrade, newGrade) => { + map(([oldGrade, newGrade]) => { + return [ + this.translateSpecialGradesPipe.transform(oldGrade), + this.translateSpecialGradesPipe.transform(newGrade), + ]; + }), + map(([oldGrade, newGrade]) => { if ( Number(event.oldValue) < 0 && - Number(event.oldValue) < Number(event.newValue) + Number(event.oldValue) < Number(event.newValue) && + Number(event.newValue) >= 0 ) { /** t(history.projectClimbed) */ return this.transloco.translate('history.projectClimbed', { line: line.name, newGrade, }); + } else if ( + Number(event.oldValue) < 0 && + Number(event.newValue) < 0 + ) { + /** t(history.projectStatusChanged) */ + return this.transloco.translate('history.projectStatusChanged', { + line: line.name, + oldGrade, + newGrade, + }); } else if ( Number(event.oldValue) === 0 && Number(event.oldValue) < Number(event.newValue) @@ -218,10 +251,12 @@ export class HistoryListComponent implements OnInit { return 'pi pi-plus'; } if (event.type === HistoryItemType.UPDATED) { - if (Number(event.oldValue) < Number(event.newValue)) { - return 'pi pi-arrow-up'; - } else if (Number(event.oldValue) > Number(event.newValue)) { - return 'pi pi-arrow-down'; + if (Number(event.newValue) >= 0) { + if (Number(event.oldValue) < Number(event.newValue)) { + return 'pi pi-arrow-up'; + } else if (Number(event.oldValue) > Number(event.newValue)) { + return 'pi pi-arrow-down'; + } } return 'pi pi-cog'; } diff --git a/client/src/app/modules/line-path-editor/line-path-form/line-path-form.component.ts b/client/src/app/modules/line-path-editor/line-path-form/line-path-form.component.ts index 6da183de..517d1317 100644 --- a/client/src/app/modules/line-path-editor/line-path-form/line-path-form.component.ts +++ b/client/src/app/modules/line-path-editor/line-path-form/line-path-form.component.ts @@ -5,7 +5,6 @@ import { LoadingState } from '../../../enums/loading-state'; import { Store } from '@ngrx/store'; import { ActivatedRoute, Router } from '@angular/router'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { LinePath } from '../../../models/line-path'; import { LinePathsService } from '../../../services/crud/line-paths.service'; import { LinesService } from '../../../services/crud/lines.service'; @@ -128,9 +127,7 @@ export class LinePathFormComponent implements OnInit { this.linePathsService .addLinePath(linePath, this.topoImageId) .subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.LINE_PATH_ADDED), - ); + this.store.dispatch(toastNotification('LINE_PATH_ADDED')); // this.router.navigate(['/topo', this.cragSlug, this.sectorSlug, this.areaSlug, 'topo-images']); this.loadingState = LoadingState.DEFAULT; this.refreshData(); diff --git a/client/src/app/modules/line/line-form/line-form.component.ts b/client/src/app/modules/line/line-form/line-form.component.ts index 95f8d584..bf7727a5 100644 --- a/client/src/app/modules/line/line-form/line-form.component.ts +++ b/client/src/app/modules/line/line-form/line-form.component.ts @@ -9,7 +9,6 @@ import { ConfirmationService } from 'primeng/api'; import { catchError } from 'rxjs/operators'; import { forkJoin, of } from 'rxjs'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { environment } from '../../../../environments/environment'; import { marker } from '@jsverse/transloco-keys-manager/marker'; import { Line } from '../../../models/line'; @@ -468,9 +467,7 @@ export class LineFormComponent implements OnInit { if (this.line) { line.slug = this.line.slug; this.linesService.updateLine(line).subscribe((line) => { - this.store.dispatch( - toastNotification(NotificationIdentifier.LINE_UPDATED), - ); + this.store.dispatch(toastNotification('LINE_UPDATED')); this.router.navigate([ '/topo', this.cragSlug, @@ -482,9 +479,7 @@ export class LineFormComponent implements OnInit { }); } else { this.linesService.createLine(line, this.areaSlug).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.LINE_CREATED), - ); + this.store.dispatch(toastNotification('LINE_CREATED')); this.router.navigate([ '/topo', this.cragSlug, @@ -529,9 +524,7 @@ export class LineFormComponent implements OnInit { */ public deleteLine() { this.linesService.deleteLine(this.line).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.LINE_DELETED), - ); + this.store.dispatch(toastNotification('LINE_DELETED')); this.router.navigate([ '/topo', this.cragSlug, diff --git a/client/src/app/modules/maps/map-marker-form-array/map-marker-form-array.component.ts b/client/src/app/modules/maps/map-marker-form-array/map-marker-form-array.component.ts index c4abc6d9..dec86c85 100644 --- a/client/src/app/modules/maps/map-marker-form-array/map-marker-form-array.component.ts +++ b/client/src/app/modules/maps/map-marker-form-array/map-marker-form-array.component.ts @@ -16,7 +16,6 @@ import { marker as translocoMarker } from '@jsverse/transloco-keys-manager/marke import { MapMarkerType } from '../../../enums/map-marker-type'; import { Store } from '@ngrx/store'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; @Component({ selector: 'lc-map-marker-form-array', @@ -75,17 +74,13 @@ export class MapMarkerFormArrayComponent implements ControlValueAccessor { addMarker(marker: MapMarker) { this.markers.push(marker); this.onChange(); - this.store.dispatch( - toastNotification(NotificationIdentifier.MAP_MARKER_ADDED), - ); + this.store.dispatch(toastNotification('MAP_MARKER_ADDED')); } removeMarker(marker: MapMarker) { this.markers.splice(this.markers.indexOf(marker), 1); this.onChange(); - this.store.dispatch( - toastNotification(NotificationIdentifier.MAP_MARKER_REMOVED), - ); + this.store.dispatch(toastNotification('MAP_MARKER_REMOVED')); } setDisabledState(isDisabled: boolean) { diff --git a/client/src/app/modules/menu-pages/menu-items-form/menu-items-form.component.ts b/client/src/app/modules/menu-pages/menu-items-form/menu-items-form.component.ts index 3884e262..d0390a94 100644 --- a/client/src/app/modules/menu-pages/menu-items-form/menu-items-form.component.ts +++ b/client/src/app/modules/menu-pages/menu-items-form/menu-items-form.component.ts @@ -23,7 +23,6 @@ import { environment } from '../../../../environments/environment'; import { catchError } from 'rxjs/operators'; import { forkJoin, Observable, of } from 'rxjs'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { MenuItem } from '../../../models/menu-item'; import { MenuItemsService } from '../../../services/crud/menu-items.service'; import { MenuItemType } from '../../../enums/menu-item-type'; @@ -266,18 +265,14 @@ export class MenuItemsFormComponent implements OnInit { if (this.menuItem) { menuItem.id = this.menuItem.id; this.menuItemsService.updateMenuItem(menuItem).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.MENU_ITEM_UPDATED), - ); + this.store.dispatch(toastNotification('MENU_ITEM_UPDATED')); this.router.navigate(['/menu-items']); this.loadingState = LoadingState.DEFAULT; this.store.dispatch(reloadMenus()); }); } else { this.menuItemsService.createMenuItem(menuItem).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.MENU_ITEM_CREATED), - ); + this.store.dispatch(toastNotification('MENU_ITEM_CREATED')); this.router.navigate(['/menu-items']); this.loadingState = LoadingState.DEFAULT; this.store.dispatch(reloadMenus()); @@ -319,9 +314,7 @@ export class MenuItemsFormComponent implements OnInit { */ public deleteMenuItem() { this.menuItemsService.deleteMenuItem(this.menuItem).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.MENU_ITEM_DELETED), - ); + this.store.dispatch(toastNotification('MENU_ITEM_DELETED')); this.store.dispatch(reloadMenus()); this.router.navigate(['/menu-items']); this.loadingState = LoadingState.DEFAULT; diff --git a/client/src/app/modules/menu-pages/menu-pages-form/menu-pages-form.component.ts b/client/src/app/modules/menu-pages/menu-pages-form/menu-pages-form.component.ts index 703cd2e0..19b6aeb1 100644 --- a/client/src/app/modules/menu-pages/menu-pages-form/menu-pages-form.component.ts +++ b/client/src/app/modules/menu-pages/menu-pages-form/menu-pages-form.component.ts @@ -19,7 +19,6 @@ import { environment } from '../../../../environments/environment'; import { catchError } from 'rxjs/operators'; import { of } from 'rxjs'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { MenuPage } from '../../../models/menu-page'; import { MenuPagesService } from '../../../services/crud/menu-pages.service'; import { ButtonModule } from 'primeng/button'; @@ -158,18 +157,14 @@ export class MenuPagesFormComponent implements OnInit { if (this.menuPage) { menuPage.slug = this.menuPage.slug; this.menuPagesService.updateMenuPage(menuPage).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.MENU_PAGE_UPDATED), - ); + this.store.dispatch(toastNotification('MENU_PAGE_UPDATED')); this.router.navigate(['/pages']); this.loadingState = LoadingState.DEFAULT; this.store.dispatch(reloadMenus()); }); } else { this.menuPagesService.createMenuPage(menuPage).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.MENU_PAGE_CREATED), - ); + this.store.dispatch(toastNotification('MENU_PAGE_CREATED')); this.router.navigate(['/pages']); this.loadingState = LoadingState.DEFAULT; this.store.dispatch(reloadMenus()); @@ -211,9 +206,7 @@ export class MenuPagesFormComponent implements OnInit { */ public deleteMenuPage() { this.menuPagesService.deleteMenuPage(this.menuPage).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.MENU_PAGE_DELETED), - ); + this.store.dispatch(toastNotification('MENU_PAGE_DELETED')); this.store.dispatch(reloadMenus()); this.router.navigate(['/pages']); this.loadingState = LoadingState.DEFAULT; diff --git a/client/src/app/modules/region/region-form/region-form.component.ts b/client/src/app/modules/region/region-form/region-form.component.ts index 57a31cf9..879b1993 100644 --- a/client/src/app/modules/region/region-form/region-form.component.ts +++ b/client/src/app/modules/region/region-form/region-form.component.ts @@ -21,7 +21,6 @@ import { TranslocoDirective } from '@jsverse/transloco'; import { catchError } from 'rxjs/operators'; import { of } from 'rxjs'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { RegionService } from '../../../services/crud/region.service'; import { Region } from '../../../models/region'; import { CardModule } from 'primeng/card'; @@ -138,9 +137,7 @@ export class RegionFormComponent implements OnInit { region.description = this.regionForm.get('description').value; region.rules = this.regionForm.get('rules').value; this.regionsService.updateRegion(region).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.REGION_UPDATED), - ); + this.store.dispatch(toastNotification('REGION_UPDATED')); this.router.navigate(['/topo']); this.loadingState = LoadingState.DEFAULT; }); diff --git a/client/src/app/modules/scale/scale-form/scale-form.component.html b/client/src/app/modules/scale/scale-form/scale-form.component.html index 7093d535..604b9c20 100644 --- a/client/src/app/modules/scale/scale-form/scale-form.component.html +++ b/client/src/app/modules/scale/scale-form/scale-form.component.html @@ -5,8 +5,8 @@ {{ scale?.lineType | transloco }} {{ scale?.name }} + >{{ scale?.lineType | transloco }} {{ scale?.name }} +
@@ -45,12 +45,6 @@

-
+
+ + +
+
+ + +
-
-

{{ t("gradeBracketsLabel") }}

+ +
+ +

+ {{ t("gradeBracketsLabel") }} - {{ t("stackedChartBrackets") }} +

- {{ t("gradeBracketsDescription", { min: 2, max: 8 }) }} + {{ + t("gradeBracketsDescriptionStackedChart", { min: 2, max: 8 }) + }}
+
+
+ + +
+
+ + + +
+ + +
+ +
+ +

{{ t("gradeBracketsLabel") }} - {{ t("barChartBrackets") }}

+
+ {{ + t("gradeBracketsDescriptionBarChart", { min: 2, max: 14 }) + }} +
+
@@ -169,65 +256,94 @@

{{ t("gradeBracketsLabel") }}

-
- -
- + + + + + + +
-
-
- - - -
+ + + + + + + diff --git a/client/src/app/modules/scale/scale-form/scale-form.component.ts b/client/src/app/modules/scale/scale-form/scale-form.component.ts index 6769049e..c7f9aaf0 100644 --- a/client/src/app/modules/scale/scale-form/scale-form.component.ts +++ b/client/src/app/modules/scale/scale-form/scale-form.component.ts @@ -8,11 +8,14 @@ import { } from '@jsverse/transloco'; import { CardModule } from 'primeng/card'; import { + AbstractControl, FormArray, FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, + ValidationErrors, + ValidatorFn, Validators, } from '@angular/forms'; import { FormDirective } from '../../shared/forms/form.directive'; @@ -33,7 +36,6 @@ import { ConfirmationService } from 'primeng/api'; import { environment } from '../../../../environments/environment'; import { marker } from '@jsverse/transloco-keys-manager/marker'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { Store } from '@ngrx/store'; import { DropdownModule } from 'primeng/dropdown'; import { MessageModule } from 'primeng/message'; @@ -114,33 +116,71 @@ export class ScaleFormComponent implements OnInit { } buildForm() { + const allNamesFilled: ValidatorFn = ( + control: AbstractControl, + ): ValidationErrors | null => { + const formArray = control as FormArray; + return formArray.value.every( + (item: any) => item.name && item.name.trim() !== '', + ) + ? null + : { names_not_filled: true }; + }; + + const notUniqueValidator: ValidatorFn = ( + ctl: AbstractControl, + ): ValidationErrors | null => { + const formArray = ctl as FormArray; + return formArray.value.length === + new Set(formArray.value.map((v: any) => v.value)).size + ? null + : { not_unique: true }; + }; + + const semanticBracketErrorValidator: ValidatorFn = ( + ctl: AbstractControl, + ): ValidationErrors | null => { + const formArray = ctl as FormArray; + return formArray.value.length >= 2 && + formArray.value.reduce( + (p: boolean, c: any) => p && c.value > 0, + true, + ) && + formArray.value.at(-2).value + 1 === formArray.value.at(-1).value + ? null + : { semantic_error: true }; + }; + + const invalidLengthValidator = (min: number, max: number): ValidatorFn => { + return (ctl: AbstractControl): ValidationErrors | null => { + const formArray = ctl as FormArray; + return formArray.value.length >= min && formArray.value.length <= max + ? null + : { invalid_length: true }; + }; + }; + this.scaleForm = this.fb.group({ lineType: this.editMode ? undefined : [LineType.BOULDER, [Validators.required]], name: this.editMode ? undefined : ['', Validators.required], - grades: this.fb.array( + grades: this.fb.array([], [notUniqueValidator, allNamesFilled]), + stackedChartBrackets: this.fb.array( [], [ - (ctl) => - ctl.value.length === new Set(ctl.value.map((v) => v.value)).size - ? null - : { not_unique: true }, + notUniqueValidator, + semanticBracketErrorValidator, + invalidLengthValidator(2, 8), ], ), - gradeBrackets: this.fb.array( + barChartBrackets: this.fb.array( [], [ - (ctl) => - ctl.value.length >= 2 && - ctl.value.reduce((p, c) => p && c.value > 0, true) && - ctl.value.at(-2).value + 1 === ctl.value.at(-1).value - ? null - : { semantic_error: true }, - (ctl) => - ctl.value.length >= 2 && ctl.value.length <= 8 - ? null - : { invalid_length: true }, + notUniqueValidator, + semanticBracketErrorValidator, + invalidLengthValidator(2, 14), + allNamesFilled, ], ), }); @@ -156,13 +196,21 @@ export class ScaleFormComponent implements OnInit { }), ) .forEach((ctl) => this.gradeControls().push(ctl)); - this.scale.gradeBrackets + this.scale.gradeBrackets.stackedChartBrackets .map((value) => this.fb.group({ value: [value], }), ) - .forEach((ctl) => this.gradeBracketsControls().push(ctl)); + .forEach((ctl) => this.stackedChartBracketsControls().push(ctl)); + this.scale.gradeBrackets.barChartBrackets + .map((bracket) => + this.fb.group({ + value: [bracket.value], + name: [bracket.name], + }), + ) + .forEach((ctl) => this.barChartBracketsControls().push(ctl)); } else { this.gradeControls().push( this.fb.group({ name: marker('CLOSED_PROJECT'), value: -2 }), @@ -173,8 +221,14 @@ export class ScaleFormComponent implements OnInit { this.gradeControls().push( this.fb.group({ name: marker('UNGRADED'), value: 0 }), ); - this.gradeBracketsControls().push(this.fb.group({ value: 1 })); - this.gradeBracketsControls().push(this.fb.group({ value: 2 })); + this.stackedChartBracketsControls().push(this.fb.group({ value: 1 })); + this.stackedChartBracketsControls().push(this.fb.group({ value: 2 })); + this.barChartBracketsControls().push( + this.fb.group({ value: 1, name: marker('FIRST_BAR_CHART_BRACKET') }), + ); + this.barChartBracketsControls().push( + this.fb.group({ value: 2, name: marker('SECOND_BAR_CHART_BRACKET') }), + ); this.addGrade(); } this.scaleForm.enable(); @@ -184,23 +238,45 @@ export class ScaleFormComponent implements OnInit { return this.scaleForm.controls.grades as FormArray; } - gradeBracketsControls() { - return this.scaleForm.controls.gradeBrackets as FormArray; + stackedChartBracketsControls() { + return this.scaleForm.controls.stackedChartBrackets as FormArray; + } + + barChartBracketsControls() { + return this.scaleForm.controls.barChartBrackets as FormArray; } - reorderByValue() { - const data = this.gradeControls().value; + private reorderControlsByValue( + controls: FormArray, + includeName: boolean = true, + ) { + const data = controls.value; data.sort((a, b) => a.value - b.value); - this.gradeControls().clear(); + controls.clear(); data .filter((g) => Number.isInteger(g.value)) - .map((g) => - this.fb.group({ - name: [g.name], + .map((g) => { + const controls = { value: [g.value], - }), - ) - .forEach((ctl) => this.gradeControls().push(ctl)); + }; + if (includeName) { + controls['name'] = [g.name]; + } + return this.fb.group(controls); + }) + .forEach((ctl) => controls.push(ctl)); + } + + reorderGradesByValue() { + this.reorderControlsByValue(this.gradeControls()); + } + + reorderBarChartBracketsByValue() { + this.reorderControlsByValue(this.barChartBracketsControls()); + } + + reorderStackedChartBracketsByValue() { + this.reorderControlsByValue(this.stackedChartBracketsControls(), false); } addGrade() { @@ -215,42 +291,64 @@ export class ScaleFormComponent implements OnInit { this.gradeControls().removeAt(index); } - addBracket() { - const max = this.gradeBracketsControls().value.reduce( + private addBracket(controls: FormArray, includeName: boolean = true) { + const max = controls.value.reduce( (acc, v) => (v.value > acc ? v.value : acc), 0, ); - this.gradeBracketsControls().push( - this.fb.group({ name: [], value: [max + 1] }), - ); + const group = { + value: [max + 1], + }; + if (includeName) { + group['name'] = []; + } + controls.push(this.fb.group(group)); + } + + addStackedChartBracket() { + this.addBracket(this.stackedChartBracketsControls(), false); + } + + addBarChartBracket() { + this.addBracket(this.barChartBracketsControls()); } - deleteBracket(index: number) { - this.gradeBracketsControls().removeAt(index); + deleteStackedChartBracket(index: number) { + this.stackedChartBracketsControls().removeAt(index); + } + + deleteBarChartBracket(index: number) { + this.barChartBracketsControls().removeAt(index); } saveScale() { if (this.scaleForm.valid) { this.loadingState = LoadingState.LOADING; + this.scaleForm.disable(); if (this.editMode) { this.scale.grades = this.gradeControls().value; - this.scale.gradeBrackets = this.gradeBracketsControls().value.map( - (gb) => gb.value, - ); - + this.scale.gradeBrackets = { + stackedChartBrackets: this.stackedChartBracketsControls().value.map( + (gb) => gb.value, + ), + barChartBrackets: this.barChartBracketsControls().value.map((gb) => { + return { + value: gb.value, + name: gb.name, + }; + }), + }; this.scalesService.updateScale(this.scale).subscribe({ next: () => { - this.store.dispatch( - toastNotification(NotificationIdentifier.SCALE_UPDATED), - ); + this.store.dispatch(toastNotification('SCALE_UPDATED')); this.loadingState = LoadingState.DEFAULT; + this.scaleForm.enable(); }, error: () => { - this.store.dispatch( - toastNotification(NotificationIdentifier.SCALE_UPDATED_ERROR), - ); + this.store.dispatch(toastNotification('SCALE_UPDATED_ERROR')); this.loadingState = LoadingState.DEFAULT; + this.scaleForm.enable(); }, }); } else { @@ -258,22 +356,20 @@ export class ScaleFormComponent implements OnInit { scale.lineType = this.scaleForm.get('lineType').value.value; scale.name = this.scaleForm.get('name').value; scale.grades = this.gradeControls().value; - scale.gradeBrackets = this.gradeBracketsControls().value.map( + scale.gradeBrackets = this.stackedChartBracketsControls().value.map( (gb) => gb.value, ); this.scalesService.createScale(scale).subscribe({ next: () => { - this.store.dispatch( - toastNotification(NotificationIdentifier.SCALE_CREATED), - ); + this.store.dispatch(toastNotification('SCALE_CREATED')); this.loadingState = LoadingState.DEFAULT; + this.scaleForm.enable(); this.router.navigate(['/scales']); }, error: () => { - this.store.dispatch( - toastNotification(NotificationIdentifier.SCALE_CREATED_ERROR), - ); + this.store.dispatch(toastNotification('SCALE_CREATED_ERROR')); this.loadingState = LoadingState.DEFAULT; + this.scaleForm.enable(); }, }); } @@ -305,15 +401,11 @@ export class ScaleFormComponent implements OnInit { this.scalesService.deleteScale(this.scale).subscribe({ next: () => { this.router.navigate(['/scales']); - this.store.dispatch( - toastNotification(NotificationIdentifier.SCALE_DELETED), - ); + this.store.dispatch(toastNotification('SCALE_DELETED')); this.loadingState = LoadingState.DEFAULT; }, error: () => { - this.store.dispatch( - toastNotification(NotificationIdentifier.SCALE_DELETED_ERROR), - ); + this.store.dispatch(toastNotification('SCALE_DELETED_ERROR')); this.loadingState = LoadingState.DEFAULT; }, }); diff --git a/client/src/app/modules/sector/sector-form/sector-form.component.ts b/client/src/app/modules/sector/sector-form/sector-form.component.ts index 273acd84..b4d692c9 100644 --- a/client/src/app/modules/sector/sector-form/sector-form.component.ts +++ b/client/src/app/modules/sector/sector-form/sector-form.component.ts @@ -15,7 +15,6 @@ import { ConfirmationService, SelectItem } from 'primeng/api'; import { catchError, map } from 'rxjs/operators'; import { forkJoin, of } from 'rxjs'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { environment } from '../../../../environments/environment'; import { marker } from '@jsverse/transloco-keys-manager/marker'; import { Sector } from '../../../models/sector'; @@ -226,9 +225,7 @@ export class SectorFormComponent implements OnInit { if (this.sector) { sector.slug = this.sector.slug; this.sectorsService.updateSector(sector).subscribe((sector) => { - this.store.dispatch( - toastNotification(NotificationIdentifier.SECTOR_UPDATED), - ); + this.store.dispatch(toastNotification('SECTOR_UPDATED')); this.router.navigate(['/topo', this.cragSlug, sector.slug]); this.loadingState = LoadingState.DEFAULT; }); @@ -236,9 +233,7 @@ export class SectorFormComponent implements OnInit { this.sectorsService .createSector(sector, this.cragSlug) .subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.SECTOR_CREATED), - ); + this.store.dispatch(toastNotification('SECTOR_CREATED')); this.router.navigate(['/topo', this.cragSlug, 'sectors']); this.loadingState = LoadingState.DEFAULT; }); @@ -279,9 +274,7 @@ export class SectorFormComponent implements OnInit { */ public deleteSector() { this.sectorsService.deleteSector(this.sector).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.SECTOR_DELETED), - ); + this.store.dispatch(toastNotification('SECTOR_DELETED')); this.router.navigate(['/topo', this.cragSlug, 'sectors']); this.loadingState = LoadingState.DEFAULT; }); diff --git a/client/src/app/modules/shared/components/grade-distribution-bar-chart/grade-distribution-bar-chart.component.ts b/client/src/app/modules/shared/components/grade-distribution-bar-chart/grade-distribution-bar-chart.component.ts index 5c7f70e3..d2942edd 100644 --- a/client/src/app/modules/shared/components/grade-distribution-bar-chart/grade-distribution-bar-chart.component.ts +++ b/client/src/app/modules/shared/components/grade-distribution-bar-chart/grade-distribution-bar-chart.component.ts @@ -10,13 +10,12 @@ import { debounceTime, forkJoin, fromEvent, Observable } from 'rxjs'; import { TranslocoDirective, TranslocoService } from '@jsverse/transloco'; import { ChartModule } from 'primeng/chart'; import { NgForOf, NgIf } from '@angular/common'; -import { marker } from '@jsverse/transloco-keys-manager/marker'; import { MOBILE_BREAKPOINT } from '../../../../utility/misc/breakpoints'; import { Store } from '@ngrx/store'; import { selectBarChartColor } from '../../../../ngrx/selectors/instance-settings.selectors'; import { map, take } from 'rxjs/operators'; import { getRgbObject } from '../../../../utility/misc/color'; -import { Grade, GradeDistribution } from '../../../../models/scale'; +import { GradeDistribution } from '../../../../models/scale'; import { ScalesService } from '../../../../services/crud/scales.service'; import { LineType } from '../../../../enums/line-type'; import { SharedModule } from 'primeng/api'; @@ -158,19 +157,26 @@ export class GradeDistributionBarChartComponent implements OnChanges, OnInit { this.scalesService.getScale(lineType as LineType, gradeScale), ]).pipe( map(([barChartColor, scale]) => { - const genericProjectGrade: Grade = { - name: marker('GENERIC_PROJECT'), - value: 0, - }; - // Condensed scale is needed if screen is too small to host all grades - let gradesInUsedScale = scale.grades.sort( + const usedScale = condensed + ? scale.gradeBrackets.barChartBrackets + : scale.grades; + + // Because of the way the condensed scale is built, we need to replace the last grade value + // with the maximum grade value of the full scale (if not condensed, this has no effect) + usedScale[usedScale.length - 1].value = Math.max( + ...scale.grades.map((grade) => grade.value), + ); + + // Sort grades in ascending order + let gradesInUsedScale = usedScale.sort( (a, b) => a.value - b.value, ); + + // Filter out projects gradesInUsedScale = gradesInUsedScale.filter( (grade) => grade.value > 0, ); - gradesInUsedScale.unshift(genericProjectGrade); // Init a counting map const gradeValues = gradesInUsedScale.map((grade) => grade.value); @@ -191,6 +197,24 @@ export class GradeDistributionBarChartComponent implements OnChanges, OnInit { } }); + // Apply condensedSortingMap to gradeDistribution + const mappedGradeDistribution = {}; + for (const gradeValue in this.gradeDistribution[lineType][ + gradeScale + ]) { + if (Number(gradeValue) <= 0) { + continue; + } + const condensedGradeValue = condensedSortingMap[gradeValue]; + if (condensedGradeValue) { + if (!mappedGradeDistribution[condensedGradeValue]) { + mappedGradeDistribution[condensedGradeValue] = 0; + } + mappedGradeDistribution[condensedGradeValue] += + this.gradeDistribution[lineType][gradeScale][gradeValue]; + } + } + // Build chart data const labels = gradesInUsedScale.map((grade) => grade.value > 0 @@ -198,20 +222,17 @@ export class GradeDistributionBarChartComponent implements OnChanges, OnInit { : this.translocoService.translate(grade.name), ); const counts = gradeValues.map( - (gradeValue) => - this.gradeDistribution[lineType][gradeScale][gradeValue] ?? 0, + (gradeValue) => mappedGradeDistribution[gradeValue] ?? 0, ); const maxCount = Math.max(...counts); const backgroundColors = counts.map((count) => { const rgbObject = getRgbObject(barChartColor); return `rgba(${rgbObject.r}, ${rgbObject.g}, ${rgbObject.b}, ${(count / maxCount) * 0.5 + 0.5})`; }); - const includeProjectsInChart = false; - if (!includeProjectsInChart) { - labels.shift(); - counts.shift(); - backgroundColors.shift(); - } + const projectCount = + (this.gradeDistribution[lineType][gradeScale]['-2'] ?? 0) + + (this.gradeDistribution[lineType][gradeScale]['-1'] ?? 0) + + (this.gradeDistribution[lineType][gradeScale]['0'] ?? 0); return { lineType: lineType as LineType, gradeScale, @@ -225,7 +246,7 @@ export class GradeDistributionBarChartComponent implements OnChanges, OnInit { }, ], }, - projectCount: counts[0], + projectCount: projectCount, totalCount: counts.reduce((a, b) => a + b, 0), }; }), diff --git a/client/src/app/modules/shared/components/leveled-grade-distribution/leveled-grade-distribution.component.html b/client/src/app/modules/shared/components/leveled-grade-distribution/leveled-grade-distribution.component.html index 6335ffe1..19df03ac 100644 --- a/client/src/app/modules/shared/components/leveled-grade-distribution/leveled-grade-distribution.component.html +++ b/client/src/app/modules/shared/components/leveled-grade-distribution/leveled-grade-distribution.component.html @@ -6,78 +6,45 @@ > - - -
- - - {{ stackChartData.length > 1 ? data.gradeScale : "" }} - {{ t("lines") }}: - - - - - {{ data.bracketLabels[i] }} - - - - - {{ t("leveledGradeDistributionProjects") }} - +
+ + +
    +
  1. + {{ data.total - data.projects }} + {{ stackChartData.length > 1 ? data.gradeScale : "" }} + {{ t("lines") }}: +
  2. +
  3. + + {{ meterValue.value }} {{ meterValue.label }} +
  4. +
  5. + + {{ data.projects }} + {{ t("leveledGradeDistributionProjects") }} +
  6. +
+
+
diff --git a/client/src/app/modules/shared/components/leveled-grade-distribution/leveled-grade-distribution.component.scss b/client/src/app/modules/shared/components/leveled-grade-distribution/leveled-grade-distribution.component.scss index c2d22353..b9bc65ea 100644 --- a/client/src/app/modules/shared/components/leveled-grade-distribution/leveled-grade-distribution.component.scss +++ b/client/src/app/modules/shared/components/leveled-grade-distribution/leveled-grade-distribution.component.scss @@ -1,45 +1,3 @@ :host { width: 100%; - display: block; -} - -.grade-distribution-tags { - flex-wrap: wrap; - p-tag { - white-space: nowrap; - } -} - -.grade-distribution-stack { - overflow: hidden; - display: flex; - flex-wrap: wrap; - row-gap: 1rem; - padding-left: 6px; - - div { - padding: 0.25rem; - color: #ffffff; - font-size: 0.75rem; - font-weight: 700; - display: inline-flex; - white-space: nowrap; - min-width: fit-content; - justify-content: center; - align-items: center; - - &.level-total { - margin-left: -6px; - border-radius: 6px 0 0 6px; - } - - &.level-max { - border-radius: 0 6px 6px 0; - } - - &.level-projects { - border-radius: 6px; - margin-left: 0.5rem; - } - } } diff --git a/client/src/app/modules/shared/components/leveled-grade-distribution/leveled-grade-distribution.component.ts b/client/src/app/modules/shared/components/leveled-grade-distribution/leveled-grade-distribution.component.ts index cbe9b27e..07686984 100644 --- a/client/src/app/modules/shared/components/leveled-grade-distribution/leveled-grade-distribution.component.ts +++ b/client/src/app/modules/shared/components/leveled-grade-distribution/leveled-grade-distribution.component.ts @@ -11,9 +11,8 @@ type StackChartData = { lineType: LineType; gradeScale: string; projects: number; - brackets: number[]; - bracketLabels: string[]; total: number; + meterValues: { color: string; value: number; label: string }[]; }; /** @@ -27,9 +26,15 @@ type StackChartData = { export class LeveledGradeDistributionComponent implements OnInit { @Input() fetchingObservable: Observable; - public stackChartData = null; + public stackChartData: any = null; public gradeDistribution: GradeDistribution; public gradeDistributionEmpty = true; + value = [ + { label: 'Apps', color: '#34d399', value: 16 }, + { label: 'Messages', color: '#fbbf24', value: 8 }, + { label: 'Media', color: '#60a5fa', value: 24 }, + { label: 'System', color: '#c084fc', value: 10 }, + ]; constructor( private scalesService: ScalesService, @@ -47,6 +52,16 @@ export class LeveledGradeDistributionComponent implements OnInit { * Sorts the grades in buckets and calculates the total count for each bucket. */ buildGradeDistribution() { + const colors = [ + 'var(--yellow-500)', + 'var(--blue-500)', + 'var(--red-500)', + 'var(--green-500)', + 'var(--orange-500)', + 'var(--teal-500)', + 'var(--indigo-500)', + 'var(--bluegray-500)', + ]; const stackChartData: StackChartData[] = []; const observables: Observable[] = []; @@ -61,7 +76,9 @@ export class LeveledGradeDistributionComponent implements OnInit { ), ]).pipe( map(([scale, gradeNameByValueMap]) => { - const labels = Array(scale.gradeBrackets.length).fill(''); + const labels = Array( + scale.gradeBrackets.stackedChartBrackets.length, + ).fill(''); const nextGradeName = Object.fromEntries( scale.grades @@ -73,16 +90,23 @@ export class LeveledGradeDistributionComponent implements OnInit { }), ); - for (let i = 0; i < scale.gradeBrackets.length; i++) { + for ( + let i = 0; + i < scale.gradeBrackets.stackedChartBrackets.length; + i++ + ) { if (i == 0) { labels[i] = - `${this.translocoService.translate(marker('leveledGradeDistributionUntil'))} ${gradeNameByValueMap[scale.gradeBrackets[i]]}`; - } else if (i == scale.gradeBrackets.length - 1) { + `${this.translocoService.translate(marker('leveledGradeDistributionUntil'))} ${gradeNameByValueMap[scale.gradeBrackets.stackedChartBrackets[i]]}`; + } else if ( + i == + scale.gradeBrackets.stackedChartBrackets.length - 1 + ) { labels[i] = - `${this.translocoService.translate(marker('leveledGradeDistributionFrom'))} ${gradeNameByValueMap[scale.gradeBrackets[i]]}`; + `${this.translocoService.translate(marker('leveledGradeDistributionFrom'))} ${gradeNameByValueMap[scale.gradeBrackets.stackedChartBrackets[i]]}`; } else { labels[i] = - `${nextGradeName[scale.gradeBrackets[i - 1]]} - ${gradeNameByValueMap[scale.gradeBrackets[i]]}`; + `${nextGradeName[scale.gradeBrackets.stackedChartBrackets[i - 1]]} - ${gradeNameByValueMap[scale.gradeBrackets.stackedChartBrackets[i]]}`; } } @@ -90,9 +114,15 @@ export class LeveledGradeDistributionComponent implements OnInit { lineType: lineType as LineType, gradeScale, projects: 0, - brackets: Array(scale.gradeBrackets.length).fill(0), - bracketLabels: labels, total: 0, + meterValues: Array.from( + { length: scale.gradeBrackets.stackedChartBrackets.length }, + (_, i) => ({ + color: colors[i % colors.length], + value: 0, + label: null, + }), + ), }; for (const gradeValue of Object.keys( this.gradeDistribution[lineType][gradeScale], @@ -103,18 +133,34 @@ export class LeveledGradeDistributionComponent implements OnInit { if (gradeValue <= 0) { data.projects += count; } else { - for (let i = 0; i < scale.gradeBrackets.length; i++) { - const bracket = scale.gradeBrackets[i]; - if (i == scale.gradeBrackets.length - 1) { - data.brackets[i] += count; + for ( + let i = 0; + i < scale.gradeBrackets.stackedChartBrackets.length; + i++ + ) { + const bracket = scale.gradeBrackets.stackedChartBrackets[i]; + if ( + i == + scale.gradeBrackets.stackedChartBrackets.length - 1 + ) { + data.meterValues[i].value += count; } else if (gradeValue <= bracket) { - data.brackets[i] += count; + data.meterValues[i].value += count; break; } } } } + // Add the labels to the data + data.meterValues.forEach((meterValue, i) => { + meterValue.label = labels[i]; + }); + // Drop all meter values that are zero + data.meterValues = data.meterValues.filter( + (meterValue) => meterValue.value > 0, + ); stackChartData.push(data); + console.log(data); }), ), ); diff --git a/client/src/app/modules/shared/shared.module.ts b/client/src/app/modules/shared/shared.module.ts index 68dfd5c0..00e2315a 100644 --- a/client/src/app/modules/shared/shared.module.ts +++ b/client/src/app/modules/shared/shared.module.ts @@ -27,6 +27,7 @@ import { ChartModule } from 'primeng/chart'; import { ChipModule } from 'primeng/chip'; import { TranslateSpecialGradesPipe } from './pipes/translate-special-grades.pipe'; import { LineGradePipe } from './pipes/line-grade.pipe'; +import { MeterGroupModule } from 'primeng/metergroup'; /** * Module for shared components, pipes etc. @@ -63,6 +64,7 @@ import { LineGradePipe } from './pipes/line-grade.pipe'; SkeletonModule, ChartModule, ChipModule, + MeterGroupModule, ], exports: [ DatePipe, diff --git a/client/src/app/modules/todo/todo-button/todo-button.component.ts b/client/src/app/modules/todo/todo-button/todo-button.component.ts index 4e0dca7d..71450ea0 100644 --- a/client/src/app/modules/todo/todo-button/todo-button.component.ts +++ b/client/src/app/modules/todo/todo-button/todo-button.component.ts @@ -4,7 +4,6 @@ import { TranslocoDirective } from '@jsverse/transloco'; import { Store } from '@ngrx/store'; import { TodosService } from '../../../services/crud/todos.service'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { ButtonModule } from 'primeng/button'; import { NgClass, NgIf } from '@angular/common'; import { SharedModule } from 'primeng/api'; @@ -37,14 +36,10 @@ export class TodoButtonComponent { this.todosService.createTodo(this.line).subscribe( () => { this.store.dispatch(todoAdded({ todoLineId: this.line.id })); - this.store.dispatch( - toastNotification(NotificationIdentifier.TODO_ADDED), - ); + this.store.dispatch(toastNotification('TODO_ADDED')); }, () => { - this.store.dispatch( - toastNotification(NotificationIdentifier.TODO_ADD_ERROR), - ); + this.store.dispatch(toastNotification('TODO_ADD_ERROR')); }, ); } else { diff --git a/client/src/app/modules/todo/todo-list/todo-list.component.ts b/client/src/app/modules/todo/todo-list/todo-list.component.ts index f5f2639f..c15601d0 100644 --- a/client/src/app/modules/todo/todo-list/todo-list.component.ts +++ b/client/src/app/modules/todo/todo-list/todo-list.component.ts @@ -1,4 +1,6 @@ import { + ChangeDetectionStrategy, + ChangeDetectorRef, Component, HostListener, OnInit, @@ -36,7 +38,6 @@ import { TickButtonComponent } from '../../ascent/tick-button/tick-button.compon import { MenuItemsService } from '../../../services/crud/menu-items.service'; import { Crag } from '../../../models/crag'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { ScalesService } from '../../../services/crud/scales.service'; import { LineType } from '../../../enums/line-type'; import { SharedModule } from '../../shared/shared.module'; @@ -72,6 +73,7 @@ import { RegionService } from '../../../services/crud/region.service'; templateUrl: './todo-list.component.html', styleUrl: './todo-list.component.scss', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, }) @UntilDestroy() export class TodoListComponent implements OnInit { @@ -116,6 +118,7 @@ export class TodoListComponent implements OnInit { private translocoService: TranslocoService, private regionService: RegionService, protected scalesService: ScalesService, + private cdr: ChangeDetectorRef, ) {} ngOnInit() { @@ -259,10 +262,9 @@ export class TodoListComponent implements OnInit { deleteTodo(todo: Todo) { this.todosService.deleteTodo(todo).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.TODO_DELETED), - ); + this.store.dispatch(toastNotification('TODO_DELETED')); this.todos = this.todos.filter((t) => t.id !== todo.id); + this.cdr.detectChanges(); }); } @@ -331,6 +333,7 @@ export class TodoListComponent implements OnInit { this.hasNextPage = todos.hasNext; this.loadingFirstPage = LoadingState.DEFAULT; this.loadingAdditionalPage = LoadingState.DEFAULT; + this.cdr.detectChanges(); }); } } diff --git a/client/src/app/modules/todo/todo-priority-button/todo-priority-button.component.ts b/client/src/app/modules/todo/todo-priority-button/todo-priority-button.component.ts index 821bcad2..0be2d260 100644 --- a/client/src/app/modules/todo/todo-priority-button/todo-priority-button.component.ts +++ b/client/src/app/modules/todo/todo-priority-button/todo-priority-button.component.ts @@ -10,7 +10,6 @@ import { marker } from '@jsverse/transloco-keys-manager/marker'; import { TodosService } from '../../../services/crud/todos.service'; import { Store } from '@ngrx/store'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; @Component({ selector: 'lc-todo-priority-button', @@ -37,9 +36,7 @@ export class TodoPriorityButtonComponent implements OnInit { .updateTodoPriority(this.todo, priority) .subscribe((todo) => { this.todo.priority = todo.priority; - this.store.dispatch( - toastNotification(NotificationIdentifier.TODO_PRIORITY_UPDATED), - ); + this.store.dispatch(toastNotification('TODO_PRIORITY_UPDATED')); }); } diff --git a/client/src/app/modules/topo-images/topo-image-form/topo-image-form.component.ts b/client/src/app/modules/topo-images/topo-image-form/topo-image-form.component.ts index 99955fb1..56d88004 100644 --- a/client/src/app/modules/topo-images/topo-image-form/topo-image-form.component.ts +++ b/client/src/app/modules/topo-images/topo-image-form/topo-image-form.component.ts @@ -14,7 +14,6 @@ import { TranslocoService } from '@jsverse/transloco'; import { catchError } from 'rxjs/operators'; import { of } from 'rxjs'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { marker } from '@jsverse/transloco-keys-manager/marker'; import { TopoImage } from '../../../models/topo-image'; import { TopoImagesService } from '../../../services/crud/topo-images.service'; @@ -149,9 +148,7 @@ export class TopoImageFormComponent implements OnInit { if (this.topoImage) { topoImage.id = this.topoImage.id; this.topoImagesService.updateTopoImage(topoImage).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.TOPO_IMAGE_UPDATED), - ); + this.store.dispatch(toastNotification('TOPO_IMAGE_UPDATED')); this.router.navigate([ '/topo', this.cragSlug, @@ -165,9 +162,7 @@ export class TopoImageFormComponent implements OnInit { this.topoImagesService .addTopoImage(topoImage, this.areaSlug) .subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.TOPO_IMAGE_ADDED), - ); + this.store.dispatch(toastNotification('TOPO_IMAGE_ADDED')); this.router.navigate([ '/topo', this.cragSlug, diff --git a/client/src/app/modules/topo-images/topo-image-list/topo-image-list.component.ts b/client/src/app/modules/topo-images/topo-image-list/topo-image-list.component.ts index 82b9282c..ceed1202 100644 --- a/client/src/app/modules/topo-images/topo-image-list/topo-image-list.component.ts +++ b/client/src/app/modules/topo-images/topo-image-list/topo-image-list.component.ts @@ -11,7 +11,6 @@ import { selectIsMobile } from '../../../ngrx/selectors/device.selectors'; import { TopoImage } from '../../../models/topo-image'; import { TopoImagesService } from '../../../services/crud/topo-images.service'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { LinePath } from '../../../models/line-path'; import { LinePathsService } from '../../../services/crud/line-paths.service'; import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog'; @@ -248,9 +247,7 @@ export class TopoImageListComponent implements OnInit { this.topoImagesService .deleteTopoImage(this.areaSlug, topoImage) .subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.TOPO_IMAGE_DELETED), - ); + this.store.dispatch(toastNotification('TOPO_IMAGE_DELETED')); this.topoImages.splice(this.topoImages.indexOf(topoImage), 1); this.topoImages = [...this.topoImages]; topoImage.loadingState = LoadingState.DEFAULT; @@ -299,9 +296,7 @@ export class TopoImageListComponent implements OnInit { public deleteLinePath(linePath: LinePath, topoImage: TopoImage) { linePath.loadingState = LoadingState.LOADING; this.linePathsService.deleteLinePath(linePath).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.LINE_PATH_DELETED), - ); + this.store.dispatch(toastNotification('LINE_PATH_DELETED')); topoImage.linePaths.splice(topoImage.linePaths.indexOf(linePath), 1); linePath.konvaLine.destroy(); linePath.konvaRect.destroy(); diff --git a/client/src/app/modules/user/user-list/user-list.component.ts b/client/src/app/modules/user/user-list/user-list.component.ts index 7b5c6563..301442d0 100644 --- a/client/src/app/modules/user/user-list/user-list.component.ts +++ b/client/src/app/modules/user/user-list/user-list.component.ts @@ -36,7 +36,6 @@ import { ChipModule } from 'primeng/chip'; import { MenuModule } from 'primeng/menu'; import { take } from 'rxjs/operators'; import { toastNotification } from '../../../ngrx/actions/notifications.actions'; -import { NotificationIdentifier } from '../../../utility/notifications/notification-identifier.enum'; import { environment } from '../../../../environments/environment'; import { ConfirmPopupModule } from 'primeng/confirmpopup'; import { ConfirmDialogModule } from 'primeng/confirmdialog'; @@ -210,9 +209,7 @@ export class UserListComponent implements OnInit { resendUserCreatedMail(user: User) { this.usersService.resendUserCreateMail(user).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.CREATE_USER_MAIL_SENT), - ); + this.store.dispatch(toastNotification('CREATE_USER_MAIL_SENT')); }); } @@ -241,18 +238,14 @@ export class UserListComponent implements OnInit { deleteUser(user: User) { this.usersService.deleteUser(user).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.USER_DELETED), - ); + this.store.dispatch(toastNotification('USER_DELETED')); this.refreshData(); }); } promoteUser(user: User, promotionTarget: UserPromotionTargets) { this.usersService.promoteUser(user.id, promotionTarget).subscribe(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.USER_PROMOTED), - ); + this.store.dispatch(toastNotification('USER_PROMOTED')); this.refreshData(); }); } diff --git a/client/src/app/ngrx/actions/notifications.actions.ts b/client/src/app/ngrx/actions/notifications.actions.ts index 5eca3513..51ea8c9a 100644 --- a/client/src/app/ngrx/actions/notifications.actions.ts +++ b/client/src/app/ngrx/actions/notifications.actions.ts @@ -1,15 +1,15 @@ import { createAction } from '@ngrx/store'; -import { NotificationIdentifier } from '../../utility/notifications/notification-identifier.enum'; import { HashMap } from '@jsverse/transloco'; +import { NotificationKey } from '../../utility/notifications'; export const toastNotification = createAction( '[Notifications] Toast Notification', ( - identifier: NotificationIdentifier, + notificationKey: NotificationKey, titleParams: HashMap = {}, messageParams: HashMap = {}, ) => ({ - identifier, + notificationKey, titleParams, messageParams, }), diff --git a/client/src/app/ngrx/effects/app-level-alerts.effects.ts b/client/src/app/ngrx/effects/app-level-alerts.effects.ts index ad73b246..d0fee172 100644 --- a/client/src/app/ngrx/effects/app-level-alerts.effects.ts +++ b/client/src/app/ngrx/effects/app-level-alerts.effects.ts @@ -1,5 +1,3 @@ -// noinspection JSUnusedGlobalSymbols - import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; @@ -11,7 +9,6 @@ import { showCookieAlert, } from '../actions/app-level-alerts.actions'; import { toastNotification } from '../actions/notifications.actions'; -import { NotificationIdentifier } from '../../utility/notifications/notification-identifier.enum'; /** * Declares effects for the app level alerts. @@ -47,9 +44,7 @@ export class AppLevelAlertsEffects { ofType(cookiesAccepted), tap(() => { localStorage.setItem('cookiesAccepted', JSON.stringify(true)); - this.store.dispatch( - toastNotification(NotificationIdentifier.COOKIES_ALLOWED), - ); + this.store.dispatch(toastNotification('COOKIES_ALLOWED')); }), ), { dispatch: false }, diff --git a/client/src/app/ngrx/effects/auth.effects.ts b/client/src/app/ngrx/effects/auth.effects.ts index a5f650d7..26cb8b75 100644 --- a/client/src/app/ngrx/effects/auth.effects.ts +++ b/client/src/app/ngrx/effects/auth.effects.ts @@ -36,7 +36,6 @@ import { bigIntTimer } from '../../utility/observables/bigint-timer'; import { showRefreshTokenAboutToExpireAlert } from '../actions/app-level-alerts.actions'; import { unixToDate } from '../../utility/operators/unix-to-date'; import { toastNotification } from '../actions/notifications.actions'; -import { NotificationIdentifier } from '../../utility/notifications/notification-identifier.enum'; import { LoginResponse } from '../../models/login-response'; import { differenceInMilliseconds, isAfter, subMilliseconds } from 'date-fns'; @@ -67,9 +66,7 @@ export class AuthEffects { map(() => AuthActions.forgotPasswordSuccess()), catchError((err) => { if (err === 'USER_NOT_ACTIVATED') { - this.store.dispatch( - toastNotification(NotificationIdentifier.USER_NOT_ACTIVATED), - ); + this.store.dispatch(toastNotification('USER_NOT_ACTIVATED')); } return of(AuthActions.forgotPasswordError()); }), @@ -87,9 +84,7 @@ export class AuthEffects { ofType(AuthActions.forgotPasswordSuccess), tap(() => { this.router.navigate(['/', 'forgot-password-check-mailbox']); - this.store.dispatch( - toastNotification(NotificationIdentifier.FORGOT_PASSWORD_SUCCESS), - ); + this.store.dispatch(toastNotification('FORGOT_PASSWORD_SUCCESS')); }), ), { dispatch: false }, @@ -117,9 +112,7 @@ export class AuthEffects { }), catchError((err) => { if (err === 'USER_NOT_ACTIVATED') { - this.store.dispatch( - toastNotification(NotificationIdentifier.USER_NOT_ACTIVATED), - ); + this.store.dispatch(toastNotification('USER_NOT_ACTIVATED')); } return of(AuthActions.resetPasswordError()); }), @@ -137,9 +130,7 @@ export class AuthEffects { ofType(AuthActions.resetPasswordSuccess), tap(() => { this.router.navigate(['']); - this.store.dispatch( - toastNotification(NotificationIdentifier.RESET_PASSWORD_SUCCESS), - ); + this.store.dispatch(toastNotification('RESET_PASSWORD_SUCCESS')); }), ), { dispatch: false }, @@ -168,9 +159,7 @@ export class AuthEffects { this.actions$.pipe( ofType(AuthActions.loginError), map(() => { - this.store.dispatch( - toastNotification(NotificationIdentifier.LOGIN_ERROR), - ); + this.store.dispatch(toastNotification('LOGIN_ERROR')); }), ), { dispatch: false }, @@ -185,9 +174,7 @@ export class AuthEffects { ofType(AuthActions.loginSuccess), map((action) => { this.router.navigate(['']); - this.store.dispatch( - toastNotification(NotificationIdentifier.LOGIN_SUCCESS), - ); + this.store.dispatch(toastNotification('LOGIN_SUCCESS')); return newAuthCredentials({ loginResponse: action.loginResponse, fromAutoLogin: false, @@ -327,9 +314,7 @@ export class AuthEffects { if (err.status === 0) { // We just notify here and don't force logout the user as there might be some unsaved work and the server might recover. this.store.dispatch( - toastNotification( - NotificationIdentifier.UNKNOWN_AUTHENTICATION_ERROR, - ), + toastNotification('UNKNOWN_AUTHENTICATION_ERROR'), ); } return of(AuthActions.refreshAccessTokenFailed()); @@ -380,13 +365,9 @@ export class AuthEffects { map((action) => { if (!action.silent) { if (!action.isAutoLogout) { - this.store.dispatch( - toastNotification(NotificationIdentifier.LOGOUT_SUCCESS), - ); + this.store.dispatch(toastNotification('LOGOUT_SUCCESS')); } else { - this.store.dispatch( - toastNotification(NotificationIdentifier.AUTO_LOGOUT_SUCCESS), - ); + this.store.dispatch(toastNotification('AUTO_LOGOUT_SUCCESS')); } } return AuthActions.cleanupCredentials({ @@ -485,13 +466,9 @@ export class AuthEffects { if (!action.silent) { // We notify about a successful logout, although it wasn't successful as we throw away the token locally. if (!action.isAutoLogout) { - this.store.dispatch( - toastNotification(NotificationIdentifier.LOGOUT_SUCCESS), - ); + this.store.dispatch(toastNotification('LOGOUT_SUCCESS')); } else { - this.store.dispatch( - toastNotification(NotificationIdentifier.AUTO_LOGOUT_SUCCESS), - ); + this.store.dispatch(toastNotification('AUTO_LOGOUT_SUCCESS')); } } return AuthActions.cleanupCredentials({ diff --git a/client/src/app/ngrx/effects/notifications.effects.ts b/client/src/app/ngrx/effects/notifications.effects.ts index ebc38539..ff3305bd 100644 --- a/client/src/app/ngrx/effects/notifications.effects.ts +++ b/client/src/app/ngrx/effects/notifications.effects.ts @@ -20,7 +20,7 @@ export class NotificationsEffects { ofType(toastNotification), tap((action) => { this.notificationsService.toast( - action.identifier, + action.notificationKey, action.titleParams, action.messageParams, ); diff --git a/client/src/app/services/core/app-notifications.service.ts b/client/src/app/services/core/app-notifications.service.ts index 47222c2a..a81c4229 100644 --- a/client/src/app/services/core/app-notifications.service.ts +++ b/client/src/app/services/core/app-notifications.service.ts @@ -1,11 +1,14 @@ import { Injectable } from '@angular/core'; import { select, Store } from '@ngrx/store'; -import { NotificationType } from '../../utility/notifications/notification-type.enum'; import { HashMap, TranslocoService } from '@jsverse/transloco'; -import { NotificationIdentifier } from '../../utility/notifications/notification-identifier.enum'; import { AppState } from '../../ngrx/reducers'; import { selectIsMobile } from '../../ngrx/selectors/device.selectors'; import { MessageService } from 'primeng/api'; +import { + getNotification, + NotificationKey, + NotificationType, +} from '../../utility/notifications'; /** * Wrapper service for the angular2-notifications. @@ -18,374 +21,6 @@ import { MessageService } from 'primeng/api'; export class AppNotificationsService { private isMobile: boolean; - private notificationTypeMap: Map = - new Map([ - [ - NotificationIdentifier.INSTANCE_SETTINGS_ERROR_MIGRATION_IMPOSSIBLE, - NotificationType.ERROR, - ], - /** - * t(notifications.INSTANCE_SETTINGS_ERROR_MIGRATION_IMPOSSIBLE_TITLE) - * t(notifications.INSTANCE_SETTINGS_ERROR_MIGRATION_IMPOSSIBLE_MESSAGE) - **/ - [NotificationIdentifier.MAP_MARKER_ADDED, NotificationType.SUCCESS], - /** - * t(notifications.MAP_MARKER_ADDED_TITLE) - * t(notifications.MAP_MARKER_ADDED_MESSAGE) - **/ - [NotificationIdentifier.MAP_MARKER_REMOVED, NotificationType.SUCCESS], - /** - * t(notifications.MAP_MARKER_REMOVED_TITLE) - * t(notifications.MAP_MARKER_REMOVED_MESSAGE) - **/ - [NotificationIdentifier.GALLERY_IMAGE_UPDATED, NotificationType.SUCCESS], - /** - * t(notifications.GALLERY_IMAGE_UPDATED_TITLE) - * t(notifications.GALLERY_IMAGE_UPDATED_MESSAGE) - **/ - [NotificationIdentifier.GALLERY_IMAGE_DELETED, NotificationType.SUCCESS], - /** - * t(notifications.GALLERY_IMAGE_DELETED_TITLE) - * t(notifications.GALLERY_IMAGE_DELETED_MESSAGE) - **/ - [NotificationIdentifier.GALLERY_IMAGE_CREATED, NotificationType.SUCCESS], - /** - * t(notifications.GALLERY_IMAGE_CREATED_TITLE) - * t(notifications.GALLERY_IMAGE_CREATED_MESSAGE) - **/ - [ - NotificationIdentifier.PROJECT_CLIMBED_MESSAGE_SENT, - NotificationType.SUCCESS, - ], - /** - * t(notifications.PROJECT_CLIMBED_MESSAGE_SENT_TITLE) - * t(notifications.PROJECT_CLIMBED_MESSAGE_SENT_MESSAGE) - **/ - [NotificationIdentifier.TODO_ADD_ERROR, NotificationType.ERROR], - /** - * t(notifications.TODO_ADD_ERROR_TITLE) - * t(notifications.TODO_ADD_ERROR_MESSAGE) - **/ - [NotificationIdentifier.TODO_DELETED, NotificationType.SUCCESS], - /** - * t(notifications.TODO_DELETED_TITLE) - * t(notifications.TODO_DELETED_MESSAGE) - **/ - [NotificationIdentifier.TODO_PRIORITY_UPDATED, NotificationType.SUCCESS], - /** - * t(notifications.TODO_PRIORITY_UPDATED_TITLE) - * t(notifications.TODO_PRIORITY_UPDATED_MESSAGE) - **/ - [NotificationIdentifier.TODO_ADDED, NotificationType.SUCCESS], - /** - * t(notifications.TODO_ADDED_TITLE) - * t(notifications.TODO_ADDED_MESSAGE) - **/ - [NotificationIdentifier.LINE_UPDATED, NotificationType.SUCCESS], - /** - * t(notifications.LINE_UPDATED_TITLE) - * t(notifications.LINE_UPDATED_MESSAGE) - **/ - [NotificationIdentifier.ASCENT_DELETED, NotificationType.SUCCESS], - /** - * t(notifications.ASCENT_DELETED_TITLE) - * t(notifications.ASCENT_DELETED_MESSAGE) - **/ - [NotificationIdentifier.ASCENT_ADDED, NotificationType.SUCCESS], - /** - * t(notifications.ASCENT_ADDED_TITLE) - * t(notifications.ASCENT_ADDED_MESSAGE) - **/ - [NotificationIdentifier.ASCENT_UPDATED, NotificationType.SUCCESS], - /** - * t(notifications.ASCENT_UPDATED_TITLE) - * t(notifications.ASCENT_UPDATED_MESSAGE) - **/ - [NotificationIdentifier.LOGIN_ERROR, NotificationType.ERROR], - /** - * t(notifications.LOGIN_ERROR_TITLE) - * t(notifications.LOGIN_ERROR_MESSAGE) - **/ - [NotificationIdentifier.USER_PROMOTED, NotificationType.SUCCESS], - /** - * t(notifications.USER_PROMOTED_TITLE) - * t(notifications.USER_PROMOTED_MESSAGE) - **/ - [NotificationIdentifier.USER_DELETED, NotificationType.SUCCESS], - /** - * t(notifications.USER_DELETED_TITLE) - * t(notifications.USER_DELETED_MESSAGE) - **/ - [NotificationIdentifier.CREATE_USER_MAIL_SENT, NotificationType.SUCCESS], - /** - * t(notifications.CREATE_USER_MAIL_SENT_TITLE) - * t(notifications.CREATE_USER_MAIL_SENT_MESSAGE) - **/ - [ - NotificationIdentifier.ACCOUNT_SETTINGS_UPDATED, - NotificationType.SUCCESS, - ], - /** - * t(notifications.ACCOUNT_SETTINGS_UPDATED_TITLE) - * t(notifications.ACCOUNT_SETTINGS_UPDATED_MESSAGE) - **/ - [NotificationIdentifier.USER_REGISTERED, NotificationType.SUCCESS], - /** - * t(notifications.USER_REGISTERED_TITLE) - * t(notifications.USER_REGISTERED_MESSAGE) - **/ - [ - NotificationIdentifier.INSTANCE_SETTINGS_UPDATED, - NotificationType.SUCCESS, - ], - /** - * t(notifications.INSTANCE_SETTINGS_UPDATED_TITLE) - * t(notifications.INSTANCE_SETTINGS_UPDATED_MESSAGE) - **/ - [NotificationIdentifier.MENU_ITEM_DELETED, NotificationType.SUCCESS], - /** - * t(notifications.MENU_ITEM_DELETED_TITLE) - * t(notifications.MENU_ITEM_DELETED_MESSAGE) - **/ - [NotificationIdentifier.MENU_ITEM_CREATED, NotificationType.SUCCESS], - /** - * t(notifications.MENU_ITEM_CREATED_TITLE) - * t(notifications.MENU_ITEM_CREATED_MESSAGE) - **/ - [NotificationIdentifier.MENU_ITEM_UPDATED, NotificationType.SUCCESS], - /** - * t(notifications.MENU_ITEM_UPDATED_TITLE) - * t(notifications.MENU_ITEM_UPDATED_MESSAGE) - **/ - [NotificationIdentifier.MENU_PAGE_DELETED, NotificationType.SUCCESS], - /** - * t(notifications.MENU_PAGE_DELETED_TITLE) - * t(notifications.MENU_PAGE_DELETED_MESSAGE) - **/ - [NotificationIdentifier.MENU_PAGE_CREATED, NotificationType.SUCCESS], - /** - * t(notifications.MENU_PAGE_CREATED_TITLE) - * t(notifications.MENU_PAGE_CREATED_MESSAGE) - **/ - [NotificationIdentifier.MENU_PAGE_UPDATED, NotificationType.SUCCESS], - /** - * t(notifications.MENU_PAGE_UPDATED_TITLE) - * t(notifications.MENU_PAGE_UPDATED_MESSAGE) - **/ - [NotificationIdentifier.TOPO_IMAGE_UPDATED, NotificationType.SUCCESS], - /** - * t(notifications.TOPO_IMAGE_UPDATED_TITLE) - * t(notifications.TOPO_IMAGE_UPDATED_MESSAGE) - **/ - [NotificationIdentifier.REGION_UPDATED, NotificationType.SUCCESS], - /** - * t(notifications.REGION_UPDATED_TITLE) - * t(notifications.REGION_UPDATED_MESSAGE) - **/ - [NotificationIdentifier.POST_DELETED, NotificationType.SUCCESS], - /** - * t(notifications.POST_DELETED_TITLE) - * t(notifications.POST_DELETED_MESSAGE) - **/ - [NotificationIdentifier.POST_UPDATED, NotificationType.SUCCESS], - /** - * t(notifications.POST_UPDATED_TITLE) - * t(notifications.POST_UPDATED_MESSAGE) - **/ - [NotificationIdentifier.POST_CREATED, NotificationType.SUCCESS], - /** - * t(notifications.POST_CREATED_TITLE) - * t(notifications.POST_CREATED_MESSAGE) - **/ - [NotificationIdentifier.LINE_CREATED, NotificationType.SUCCESS], - /** - * t(notifications.LINE_CREATED_TITLE) - * t(notifications.LINE_CREATED_MESSAGE) - **/ - [NotificationIdentifier.LINE_PATH_DELETED, NotificationType.SUCCESS], - /** - * t(notifications.LINE_PATH_DELETED_TITLE) - * t(notifications.LINE_PATH_DELETED_MESSAGE) - **/ - [NotificationIdentifier.LINE_PATH_ADDED, NotificationType.SUCCESS], - /** - * t(notifications.LINE_PATH_ADDED_TITLE) - * t(notifications.LINE_PATH_ADDED_MESSAGE) - **/ - [NotificationIdentifier.TOPO_IMAGE_DELETED, NotificationType.SUCCESS], - /** - * t(notifications.TOPO_IMAGE_DELETED_TITLE) - * t(notifications.TOPO_IMAGE_DELETED_MESSAGE) - **/ - [NotificationIdentifier.TOPO_IMAGE_ADDED, NotificationType.SUCCESS], - /** - * t(notifications.TOPO_IMAGE_ADDED_TITLE) - * t(notifications.TOPO_IMAGE_ADDED_MESSAGE) - **/ - [NotificationIdentifier.AREA_DELETED, NotificationType.SUCCESS], - /** - * t(notifications.AREA_DELETED_TITLE) - * t(notifications.AREA_DELETED_MESSAGE) - **/ - [NotificationIdentifier.AREA_UPDATED, NotificationType.SUCCESS], - /** - * t(notifications.AREA_UPDATED_TITLE) - * t(notifications.AREA_UPDATED_MESSAGE) - **/ - [NotificationIdentifier.AREA_CREATED, NotificationType.SUCCESS], - /** - * t(notifications.AREA_CREATED_TITLE) - * t(notifications.AREA_CREATED_MESSAGE) - **/ - [NotificationIdentifier.SECTOR_DELETED, NotificationType.SUCCESS], - /** - * t(notifications.SECTOR_DELETED_TITLE) - * t(notifications.SECTOR_DELETED_MESSAGE) - **/ - [NotificationIdentifier.SECTOR_UPDATED, NotificationType.SUCCESS], - /** - * t(notifications.SECTOR_UPDATED_TITLE) - * t(notifications.SECTOR_UPDATED_MESSAGE) - **/ - [NotificationIdentifier.SECTOR_CREATED, NotificationType.SUCCESS], - /** - * t(notifications.SECTOR_CREATED_TITLE) - * t(notifications.SECTOR_CREATED_MESSAGE) - **/ - [NotificationIdentifier.CRAG_DELETED, NotificationType.SUCCESS], - /** - * t(notifications.CRAG_DELETED_TITLE) - * t(notifications.CRAG_DELETED_MESSAGE) - **/ - [NotificationIdentifier.CRAG_UPDATED, NotificationType.SUCCESS], - /** - * t(notifications.CRAG_UPDATED_TITLE) - * t(notifications.CRAG_UPDATED_MESSAGE) - **/ - [NotificationIdentifier.CRAG_CREATED, NotificationType.SUCCESS], - /** - * t(notifications.CRAG_CREATED_TITLE) - * t(notifications.CRAG_CREATED_MESSAGE) - **/ - [NotificationIdentifier.USER_NOT_ACTIVATED, NotificationType.ERROR], - /** - * t(notifications.USER_NOT_ACTIVATED_TITLE) - * t(notifications.USER_NOT_ACTIVATED_MESSAGE) - **/ - [NotificationIdentifier.LOGIN_SUCCESS, NotificationType.SUCCESS], - /** - * t(notifications.LOGIN_SUCCESS_TITLE) - * t(notifications.LOGIN_SUCCESS_MESSAGE) - **/ - [ - NotificationIdentifier.CHANGE_PASSWORD_SUCCESS, - NotificationType.SUCCESS, - ], - /** - * t(notifications.CHANGE_PASSWORD_SUCCESS_TITLE) - * t(notifications.CHANGE_PASSWORD_SUCCESS_MESSAGE) - **/ - [ - NotificationIdentifier.FORGOT_PASSWORD_SUCCESS, - NotificationType.SUCCESS, - ], - /** - * t(notifications.FORGOT_PASSWORD_SUCCESS_TITLE) - * t(notifications.FORGOT_PASSWORD_SUCCESS_MESSAGE) - **/ - [NotificationIdentifier.RESET_PASSWORD_SUCCESS, NotificationType.SUCCESS], - /** - * t(notifications.RESET_PASSWORD_SUCCESS_TITLE) - * t(notifications.RESET_PASSWORD_SUCCESS_MESSAGE) - **/ - [NotificationIdentifier.COOKIES_ALLOWED, NotificationType.INFO], - /** - * t(notifications.COOKIES_ALLOWED_TITLE) - * t(notifications.COOKIES_ALLOWED_MESSAGE) - **/ - [NotificationIdentifier.LOGOUT_SUCCESS, NotificationType.INFO], - /** - * t(notifications.LOGOUT_SUCCESS_TITLE) - * t(notifications.LOGOUT_SUCCESS_MESSAGE) - **/ - [NotificationIdentifier.AUTO_LOGOUT_SUCCESS, NotificationType.INFO], - /** - * t(notifications.AUTO_LOGOUT_SUCCESS_TITLE) - * t(notifications.AUTO_LOGOUT_SUCCESS_MESSAGE) - **/ - [ - NotificationIdentifier.UNKNOWN_AUTHENTICATION_ERROR, - NotificationType.ERROR, - ], - /** - * t(notifications.UNKNOWN_AUTHENTICATION_ERROR_TITLE) - * t(notifications.UNKNOWN_AUTHENTICATION_ERROR_MESSAGE) - **/ - [NotificationIdentifier.UNKNOWN_ERROR, NotificationType.ERROR], - /** - * t(notifications.UNKNOWN_ERROR_TITLE) - * t(notifications.UNKNOWN_ERROR_MESSAGE) - **/ - [ - NotificationIdentifier.LOG_OUT_TO_USE_THIS_FUNCTION, - NotificationType.INFO, - ], - /** - * t(notifications.LOG_OUT_TO_USE_THIS_FUNCTION_TITLE) - * t(notifications.LOG_OUT_TO_USE_THIS_FUNCTION_MESSAGE) - **/ - [NotificationIdentifier.ARCHIVED, NotificationType.SUCCESS], - /** - * t(notifications.ARCHIVED_TITLE) - * t(notifications.ARCHIVED_MESSAGE) - **/ - [NotificationIdentifier.UNARCHIVED, NotificationType.SUCCESS], - /** - * t(notifications.UNARCHIVED_TITLE) - * t(notifications.UNARCHIVED_MESSAGE) - **/ - [NotificationIdentifier.ARCHIVED_ERROR, NotificationType.ERROR], - /** - * t(notifications.ARCHIVED_ERROR_TITLE) - * t(notifications.ARCHIVED_ERROR_MESSAGE) - **/ - [NotificationIdentifier.UNARCHIVED_ERROR, NotificationType.ERROR], - /** - * t(notifications.UNARCHIVED_ERROR_TITLE) - * t(notifications.UNARCHIVED_ERROR_MESSAGE) - **/ - [NotificationIdentifier.SCALE_CREATED, NotificationType.SUCCESS], - /** - * t(notifications.SCALE_CREATED_TITLE) - * t(notifications.SCALE_CREATED_MESSAGE) - **/ - [NotificationIdentifier.SCALE_CREATED_ERROR, NotificationType.ERROR], - /** - * t(notifications.SCALE_CREATED_ERROR_TITLE) - * t(notifications.SCALE_CREATED_ERROR_MESSAGE) - **/ - [NotificationIdentifier.SCALE_UPDATED, NotificationType.SUCCESS], - /** - * t(notifications.SCALE_UPDATED_TITLE) - * t(notifications.SCALE_UPDATED_MESSAGE) - **/ - [NotificationIdentifier.SCALE_UPDATED_ERROR, NotificationType.ERROR], - /** - * t(notifications.SCALE_UPDATED_ERROR_TITLE) - * t(notifications.SCALE_UPDATED_ERROR_MESSAGE) - **/ - [NotificationIdentifier.SCALE_DELETED, NotificationType.SUCCESS], - /** - * t(notifications.SCALE_DELETED_TITLE) - * t(notifications.SCALE_DELETED_MESSAGE) - **/ - [NotificationIdentifier.SCALE_DELETED_ERROR, NotificationType.ERROR], - /** - * t(notifications.SCALE_DELETED_ERROR_TITLE) - * t(notifications.SCALE_DELETED_ERROR_MESSAGE) - **/ - ]); - constructor( private messageService: MessageService, private translocoService: TranslocoService, @@ -399,16 +34,16 @@ export class AppNotificationsService { /** * Pushes a toast notification: either immediately or in a deferred way if currently language files are loading. * - * @param notificationIdentifier Identifier of the notification to push. + * @param notificationKey Key of the notification to push. * @param titleParams Translation params for the title string. * @param messageParams Translation params for the message string. */ public toast( - notificationIdentifier: NotificationIdentifier, + notificationKey: NotificationKey, titleParams: HashMap = {}, messageParams: HashMap = {}, ): void { - this.doToast(notificationIdentifier, titleParams, messageParams); + this.doToast(notificationKey, titleParams, messageParams); } /** @@ -478,24 +113,25 @@ export class AppNotificationsService { /** * Pushes a toast notification. * - * @param notificationIdentifier Identifier of the notification to push. + * @param notificationKey Key of the notification to push. * @param titleParams Translation params for the title string. * @param messageParams Translation params for the message string. */ private doToast( - notificationIdentifier: NotificationIdentifier, + notificationKey: NotificationKey, titleParams: HashMap = {}, messageParams: HashMap = {}, ) { + const notificationDefinition = getNotification(notificationKey); const title = this.translocoService.translate( - 'notifications.' + notificationIdentifier.toString() + '_TITLE', + notificationDefinition.title, titleParams, ); const message = this.translocoService.translate( - 'notifications.' + notificationIdentifier.toString() + '_MESSAGE', + notificationDefinition.message, messageParams, ); - switch (this.notificationTypeMap.get(notificationIdentifier)) { + switch (notificationDefinition.type) { case NotificationType.ERROR: this.error(title, message); break; diff --git a/client/src/app/services/core/error-handler.service.ts b/client/src/app/services/core/error-handler.service.ts index 2967ae44..5ddbd48b 100644 --- a/client/src/app/services/core/error-handler.service.ts +++ b/client/src/app/services/core/error-handler.service.ts @@ -1,9 +1,9 @@ import { Injectable } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; import { Store } from '@ngrx/store'; -import { NotificationIdentifier } from '../../utility/notifications/notification-identifier.enum'; import { AppState } from '../../ngrx/reducers'; import { toastNotification } from '../../ngrx/actions/notifications.actions'; +import { NOTIFICATIONS } from '../../utility/notifications'; /** * A simple error handling service for logging and messaging. @@ -20,16 +20,9 @@ export class ErrorHandlerService { * @param error The error to handle. */ public handleHttpError(error: HttpErrorResponse) { - const errIdentifier = - error.error['labnodeErrCode'] || error.error['message'] || null; - if (errIdentifier && errIdentifier in NotificationIdentifier) { - this.store.dispatch( - toastNotification( - NotificationIdentifier[ - errIdentifier as keyof typeof NotificationIdentifier - ], - ), - ); + const errIdentifier = error.error['message'] || null; + if (errIdentifier && errIdentifier in NOTIFICATIONS) { + this.store.dispatch(toastNotification(errIdentifier)); } console.error(error); } diff --git a/client/src/app/styles/grades.scss b/client/src/app/styles/grades.scss index 1e4b164b..9247dbbd 100644 --- a/client/src/app/styles/grades.scss +++ b/client/src/app/styles/grades.scss @@ -1,24 +1,3 @@ -.level-total { - background-color: var(--gray-500) !important; -} - -$colors: var(--yellow-500), var(--blue-500), var(--red-500), var(--green-500), - var(--orange-500), var(--teal-500), var(--indigo-500), var(--bluegray-500); - -@for $i from 1 to length($colors) { - .level-#{$i} { - background-color: nth($colors, $i) !important; - } -} - -.level-max { - background-color: var(--gray-900) !important; -} - -.level-projects { - background-color: var(--gray-500) !important; -} - .neutral-badge { background-color: var(--gray-50) !important; color: #000 !important; diff --git a/client/src/app/utility/notifications.ts b/client/src/app/utility/notifications.ts new file mode 100644 index 00000000..ec92c62e --- /dev/null +++ b/client/src/app/utility/notifications.ts @@ -0,0 +1,388 @@ +import { marker } from '@jsverse/transloco-keys-manager/marker'; + +/** + * Possible types of a notification. + */ +export enum NotificationType { + INFO, + ERROR, + WARNING, + SUCCESS, +} + +/** + * Definition of a notification. + */ +export type NotificationDefinition = { + type: NotificationType; + title: string; + message: string; +}; + +/** + * All available notifications. + */ +export const NOTIFICATIONS = { + MAP_MARKER_REMOVED: { + type: NotificationType.SUCCESS, + title: marker('notifications.MAP_MARKER_REMOVED_TITLE'), + message: marker('notifications.MAP_MARKER_REMOVED_MESSAGE'), + }, + MAP_MARKER_ADDED: { + type: NotificationType.SUCCESS, + title: marker('notifications.MAP_MARKER_ADDED_TITLE'), + message: marker('notifications.MAP_MARKER_ADDED_MESSAGE'), + }, + GALLERY_IMAGE_CREATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.GALLERY_IMAGE_CREATED_TITLE'), + message: marker('notifications.GALLERY_IMAGE_CREATED_MESSAGE'), + }, + GALLERY_IMAGE_UPDATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.GALLERY_IMAGE_UPDATED_TITLE'), + message: marker('notifications.GALLERY_IMAGE_UPDATED_MESSAGE'), + }, + GALLERY_IMAGE_DELETED: { + type: NotificationType.SUCCESS, + title: marker('notifications.GALLERY_IMAGE_DELETED_TITLE'), + message: marker('notifications.GALLERY_IMAGE_DELETED_MESSAGE'), + }, + PROJECT_CLIMBED_MESSAGE_SENT: { + type: NotificationType.SUCCESS, + title: marker('notifications.PROJECT_CLIMBED_MESSAGE_SENT_TITLE'), + message: marker('notifications.PROJECT_CLIMBED_MESSAGE_SENT_MESSAGE'), + }, + TODO_DELETED: { + type: NotificationType.SUCCESS, + title: marker('notifications.TODO_DELETED_TITLE'), + message: marker('notifications.TODO_DELETED_MESSAGE'), + }, + TODO_PRIORITY_UPDATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.TODO_PRIORITY_UPDATED_TITLE'), + message: marker('notifications.TODO_PRIORITY_UPDATED_MESSAGE'), + }, + TODO_ADD_ERROR: { + type: NotificationType.ERROR, + title: marker('notifications.TODO_ADD_ERROR_TITLE'), + message: marker('notifications.TODO_ADD_ERROR_MESSAGE'), + }, + TODO_ADDED: { + type: NotificationType.SUCCESS, + title: marker('notifications.TODO_ADDED_TITLE'), + message: marker('notifications.TODO_ADDED_MESSAGE'), + }, + ASCENT_ADDED: { + type: NotificationType.SUCCESS, + title: marker('notifications.ASCENT_ADDED_TITLE'), + message: marker('notifications.ASCENT_ADDED_MESSAGE'), + }, + ASCENT_UPDATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.ASCENT_UPDATED_TITLE'), + message: marker('notifications.ASCENT_UPDATED_MESSAGE'), + }, + ASCENT_DELETED: { + type: NotificationType.SUCCESS, + title: marker('notifications.ASCENT_DELETED_TITLE'), + message: marker('notifications.ASCENT_DELETED_MESSAGE'), + }, + ARCHIVED: { + type: NotificationType.SUCCESS, + title: marker('notifications.ARCHIVED_TITLE'), + message: marker('notifications.ARCHIVED_MESSAGE'), + }, + UNARCHIVED: { + type: NotificationType.SUCCESS, + title: marker('notifications.UNARCHIVED_TITLE'), + message: marker('notifications.UNARCHIVED_MESSAGE'), + }, + ARCHIVED_ERROR: { + type: NotificationType.ERROR, + title: marker('notifications.ARCHIVED_ERROR_TITLE'), + message: marker('notifications.ARCHIVED_ERROR_MESSAGE'), + }, + UNARCHIVED_ERROR: { + type: NotificationType.ERROR, + title: marker('notifications.UNARCHIVED_ERROR_TITLE'), + message: marker('notifications.UNARCHIVED_ERROR_MESSAGE'), + }, + LOGIN_ERROR: { + type: NotificationType.SUCCESS, + title: marker('notifications.LOGIN_ERROR_TITLE'), + message: marker('notifications.LOGIN_ERROR_MESSAGE'), + }, + USER_PROMOTED: { + type: NotificationType.SUCCESS, + title: marker('notifications.USER_PROMOTED_TITLE'), + message: marker('notifications.USER_PROMOTED_MESSAGE'), + }, + USER_DELETED: { + type: NotificationType.SUCCESS, + title: marker('notifications.USER_DELETED_TITLE'), + message: marker('notifications.USER_DELETED_MESSAGE'), + }, + CREATE_USER_MAIL_SENT: { + type: NotificationType.SUCCESS, + title: marker('notifications.CREATE_USER_MAIL_SENT_TITLE'), + message: marker('notifications.CREATE_USER_MAIL_SENT_MESSAGE'), + }, + ACCOUNT_SETTINGS_UPDATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.ACCOUNT_SETTINGS_UPDATED_TITLE'), + message: marker('notifications.ACCOUNT_SETTINGS_UPDATED_MESSAGE'), + }, + INSTANCE_SETTINGS_UPDATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.INSTANCE_SETTINGS_UPDATED_TITLE'), + message: marker('notifications.INSTANCE_SETTINGS_UPDATED_MESSAGE'), + }, + INSTANCE_SETTINGS_ERROR_MIGRATION_IMPOSSIBLE: { + type: NotificationType.ERROR, + title: marker( + 'notifications.INSTANCE_SETTINGS_ERROR_MIGRATION_IMPOSSIBLE_TITLE', + ), + message: marker( + 'notifications.INSTANCE_SETTINGS_ERROR_MIGRATION_IMPOSSIBLE_MESSAGE', + ), + }, + REGION_UPDATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.REGION_UPDATED_TITLE'), + message: marker('notifications.REGION_UPDATED_MESSAGE'), + }, + USER_NOT_ACTIVATED: { + type: NotificationType.ERROR, + title: marker('notifications.USER_NOT_ACTIVATED_TITLE'), + message: marker('notifications.USER_NOT_ACTIVATED_MESSAGE'), + }, + LOGIN_SUCCESS: { + type: NotificationType.SUCCESS, + title: marker('notifications.LOGIN_SUCCESS_TITLE'), + message: marker('notifications.LOGIN_SUCCESS_MESSAGE'), + }, + CHANGE_PASSWORD_SUCCESS: { + type: NotificationType.SUCCESS, + title: marker('notifications.CHANGE_PASSWORD_SUCCESS_TITLE'), + message: marker('notifications.CHANGE_PASSWORD_SUCCESS_MESSAGE'), + }, + FORGOT_PASSWORD_SUCCESS: { + type: NotificationType.SUCCESS, + title: marker('notifications.FORGOT_PASSWORD_SUCCESS_TITLE'), + message: marker('notifications.FORGOT_PASSWORD_SUCCESS_MESSAGE'), + }, + RESET_PASSWORD_SUCCESS: { + type: NotificationType.SUCCESS, + title: marker('notifications.RESET_PASSWORD_SUCCESS_TITLE'), + message: marker('notifications.RESET_PASSWORD_SUCCESS_MESSAGE'), + }, + COOKIES_ALLOWED: { + type: NotificationType.INFO, + title: marker('notifications.COOKIES_ALLOWED_TITLE'), + message: marker('notifications.COOKIES_ALLOWED_MESSAGE'), + }, + LOGOUT_SUCCESS: { + type: NotificationType.INFO, + title: marker('notifications.LOGOUT_SUCCESS_TITLE'), + message: marker('notifications.LOGOUT_SUCCESS_MESSAGE'), + }, + AUTO_LOGOUT_SUCCESS: { + type: NotificationType.INFO, + title: marker('notifications.AUTO_LOGOUT_SUCCESS_TITLE'), + message: marker('notifications.AUTO_LOGOUT_SUCCESS_MESSAGE'), + }, + UNKNOWN_AUTHENTICATION_ERROR: { + type: NotificationType.ERROR, + title: marker('notifications.UNKNOWN_AUTHENTICATION_ERROR_TITLE'), + message: marker('notifications.UNKNOWN_AUTHENTICATION_ERROR_MESSAGE'), + }, + UNKNOWN_ERROR: { + type: NotificationType.ERROR, + title: marker('notifications.UNKNOWN_ERROR_TITLE'), + message: marker('notifications.UNKNOWN_ERROR_MESSAGE'), + }, + LOG_OUT_TO_USE_THIS_FUNCTION: { + type: NotificationType.INFO, + title: marker('notifications.LOG_OUT_TO_USE_THIS_FUNCTION_TITLE'), + message: marker('notifications.LOG_OUT_TO_USE_THIS_FUNCTION_MESSAGE'), + }, + CRAG_CREATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.CRAG_CREATED_TITLE'), + message: marker('notifications.CRAG_CREATED_MESSAGE'), + }, + CRAG_UPDATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.CRAG_UPDATED_TITLE'), + message: marker('notifications.CRAG_UPDATED_MESSAGE'), + }, + CRAG_DELETED: { + type: NotificationType.SUCCESS, + title: marker('notifications.CRAG_DELETED_TITLE'), + message: marker('notifications.CRAG_DELETED_MESSAGE'), + }, + SECTOR_CREATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.SECTOR_CREATED_TITLE'), + message: marker('notifications.SECTOR_CREATED_MESSAGE'), + }, + SECTOR_UPDATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.SECTOR_UPDATED_TITLE'), + message: marker('notifications.SECTOR_UPDATED_MESSAGE'), + }, + SECTOR_DELETED: { + type: NotificationType.SUCCESS, + title: marker('notifications.SECTOR_DELETED_TITLE'), + message: marker('notifications.SECTOR_DELETED_MESSAGE'), + }, + AREA_CREATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.AREA_CREATED_TITLE'), + message: marker('notifications.AREA_CREATED_MESSAGE'), + }, + AREA_UPDATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.AREA_UPDATED_TITLE'), + message: marker('notifications.AREA_UPDATED_MESSAGE'), + }, + AREA_DELETED: { + type: NotificationType.SUCCESS, + title: marker('notifications.AREA_DELETED_TITLE'), + message: marker('notifications.AREA_DELETED_MESSAGE'), + }, + LINE_CREATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.LINE_CREATED_TITLE'), + message: marker('notifications.LINE_CREATED_MESSAGE'), + }, + LINE_UPDATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.LINE_UPDATED_TITLE'), + message: marker('notifications.LINE_UPDATED_MESSAGE'), + }, + LINE_DELETED: { + type: NotificationType.SUCCESS, + title: marker('notifications.LINE_DELETED_TITLE'), + message: marker('notifications.LINE_DELETED_MESSAGE'), + }, + POST_CREATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.POST_CREATED_TITLE'), + message: marker('notifications.POST_CREATED_MESSAGE'), + }, + POST_UPDATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.POST_UPDATED_TITLE'), + message: marker('notifications.POST_UPDATED_MESSAGE'), + }, + POST_DELETED: { + type: NotificationType.SUCCESS, + title: marker('notifications.POST_DELETED_TITLE'), + message: marker('notifications.POST_DELETED_MESSAGE'), + }, + TOPO_IMAGE_ADDED: { + type: NotificationType.SUCCESS, + title: marker('notifications.TOPO_IMAGE_ADDED_TITLE'), + message: marker('notifications.TOPO_IMAGE_ADDED_MESSAGE'), + }, + TOPO_IMAGE_UPDATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.TOPO_IMAGE_UPDATED_TITLE'), + message: marker('notifications.TOPO_IMAGE_UPDATED_MESSAGE'), + }, + TOPO_IMAGE_DELETED: { + type: NotificationType.SUCCESS, + title: marker('notifications.TOPO_IMAGE_DELETED_TITLE'), + message: marker('notifications.TOPO_IMAGE_DELETED_MESSAGE'), + }, + LINE_PATH_ADDED: { + type: NotificationType.SUCCESS, + title: marker('notifications.LINE_PATH_ADDED_TITLE'), + message: marker('notifications.LINE_PATH_ADDED_MESSAGE'), + }, + LINE_PATH_DELETED: { + type: NotificationType.SUCCESS, + title: marker('notifications.LINE_PATH_DELETED_TITLE'), + message: marker('notifications.LINE_PATH_DELETED_MESSAGE'), + }, + MENU_PAGE_DELETED: { + type: NotificationType.SUCCESS, + title: marker('notifications.MENU_PAGE_DELETED_TITLE'), + message: marker('notifications.MENU_PAGE_DELETED_MESSAGE'), + }, + MENU_PAGE_UPDATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.MENU_PAGE_UPDATED_TITLE'), + message: marker('notifications.MENU_PAGE_UPDATED_MESSAGE'), + }, + MENU_PAGE_CREATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.MENU_PAGE_CREATED_TITLE'), + message: marker('notifications.MENU_PAGE_CREATED_MESSAGE'), + }, + MENU_ITEM_DELETED: { + type: NotificationType.SUCCESS, + title: marker('notifications.MENU_ITEM_DELETED_TITLE'), + message: marker('notifications.MENU_ITEM_DELETED_MESSAGE'), + }, + MENU_ITEM_UPDATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.MENU_ITEM_UPDATED_TITLE'), + message: marker('notifications.MENU_ITEM_UPDATED_MESSAGE'), + }, + MENU_ITEM_CREATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.MENU_ITEM_CREATED_TITLE'), + message: marker('notifications.MENU_ITEM_CREATED_MESSAGE'), + }, + USER_REGISTERED: { + type: NotificationType.SUCCESS, + title: marker('notifications.USER_REGISTERED_TITLE'), + message: marker('notifications.USER_REGISTERED_MESSAGE'), + }, + SCALE_CREATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.SCALE_CREATED_TITLE'), + message: marker('notifications.SCALE_CREATED_MESSAGE'), + }, + SCALE_CREATED_ERROR: { + type: NotificationType.ERROR, + title: marker('notifications.SCALE_CREATED_ERROR_TITLE'), + message: marker('notifications.SCALE_CREATED_ERROR_MESSAGE'), + }, + SCALE_UPDATED: { + type: NotificationType.SUCCESS, + title: marker('notifications.SCALE_UPDATED_TITLE'), + message: marker('notifications.SCALE_UPDATED_MESSAGE'), + }, + SCALE_UPDATED_ERROR: { + type: NotificationType.ERROR, + title: marker('notifications.SCALE_UPDATED_ERROR_TITLE'), + message: marker('notifications.SCALE_UPDATED_ERROR_MESSAGE'), + }, + SCALE_DELETED: { + type: NotificationType.SUCCESS, + title: marker('notifications.SCALE_DELETED_TITLE'), + message: marker('notifications.SCALE_DELETED_MESSAGE'), + }, + SCALE_DELETED_ERROR: { + type: NotificationType.ERROR, + title: marker('notifications.SCALE_DELETED_ERROR_TITLE'), + message: marker('notifications.SCALE_DELETED_ERROR_MESSAGE'), + }, +} satisfies { [key: string]: NotificationDefinition }; + +export type NotificationKey = keyof typeof NOTIFICATIONS; + +/** + * Get a notification by key. In contrast to directly accessing the NOTIFICATIONS object, this function + * will throw an error if the key does not exist. + * @param key Key of the notification to get. + */ +export const getNotification = ( + key: NotificationKey, +): NotificationDefinition => { + return NOTIFICATIONS[key]; +}; diff --git a/client/src/app/utility/notifications/notification-identifier.enum.ts b/client/src/app/utility/notifications/notification-identifier.enum.ts deleted file mode 100644 index d255fa52..00000000 --- a/client/src/app/utility/notifications/notification-identifier.enum.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Defines identifiers for all notifications in the system. - */ -export enum NotificationIdentifier { - MAP_MARKER_REMOVED = 'MAP_MARKER_REMOVED', - MAP_MARKER_ADDED = 'MAP_MARKER_ADDED', - GALLERY_IMAGE_CREATED = 'GALLERY_IMAGE_CREATED', - GALLERY_IMAGE_UPDATED = 'GALLERY_IMAGE_UPDATED', - GALLERY_IMAGE_DELETED = 'GALLERY_IMAGE_DELETED', - PROJECT_CLIMBED_MESSAGE_SENT = 'PROJECT_CLIMBED_MESSAGE_SENT', - TODO_DELETED = 'TODO_DELETED', - TODO_PRIORITY_UPDATED = 'TODO_PRIORITY_UPDATED', - TODO_ADD_ERROR = 'TODO_ADD_ERROR', - TODO_ADDED = 'TODO_ADDED', - ASCENT_ADDED = 'ASCENT_ADDED', - ASCENT_UPDATED = 'ASCENT_UPDATED', - ASCENT_DELETED = 'ASCENT_DELETED', - ARCHIVED = 'ARCHIVED', - UNARCHIVED = 'UNARCHIVED', - ARCHIVED_ERROR = 'ARCHIVED_ERROR', - UNARCHIVED_ERROR = 'UNARCHIVED_ERROR', - LOGIN_ERROR = 'LOGIN_ERROR', - USER_PROMOTED = 'USER_PROMOTED', - USER_DELETED = 'USER_DELETED', - CREATE_USER_MAIL_SENT = 'CREATE_USER_MAIL_SENT', - ACCOUNT_SETTINGS_UPDATED = 'ACCOUNT_SETTINGS_UPDATED', - INSTANCE_SETTINGS_UPDATED = 'INSTANCE_SETTINGS_UPDATED', - INSTANCE_SETTINGS_ERROR_MIGRATION_IMPOSSIBLE = 'INSTANCE_SETTINGS_ERROR_MIGRATION_IMPOSSIBLE', - REGION_UPDATED = 'REGION_UPDATED', - USER_NOT_ACTIVATED = 'USER_NOT_ACTIVATED', - LOGIN_SUCCESS = 'LOGIN_SUCCESS', - CHANGE_PASSWORD_SUCCESS = 'CHANGE_PASSWORD_SUCCESS', - FORGOT_PASSWORD_SUCCESS = 'FORGOT_PASSWORD_SUCCESS', - RESET_PASSWORD_SUCCESS = 'RESET_PASSWORD_SUCCESS', - COOKIES_ALLOWED = 'COOKIES_ALLOWED', - LOGOUT_SUCCESS = 'LOGOUT_SUCCESS', - AUTO_LOGOUT_SUCCESS = 'AUTO_LOGOUT_SUCCESS', - UNKNOWN_AUTHENTICATION_ERROR = 'UNKNOWN_AUTHENTICATION_ERROR', - UNKNOWN_ERROR = 'UNKNOWN_ERROR', - LOG_OUT_TO_USE_THIS_FUNCTION = 'LOG_OUT_TO_USE_THIS_FUNCTION', - CRAG_CREATED = 'CRAG_CREATED', - CRAG_UPDATED = 'CRAG_UPDATED', - CRAG_DELETED = 'CRAG_DELETED', - SECTOR_CREATED = 'SECTOR_CREATED', - SECTOR_UPDATED = 'SECTOR_UPDATED', - SECTOR_DELETED = 'SECTOR_DELETED', - AREA_CREATED = 'AREA_CREATED', - AREA_UPDATED = 'AREA_UPDATED', - AREA_DELETED = 'AREA_DELETED', - LINE_CREATED = 'LINE_CREATED', - LINE_UPDATED = 'LINE_UPDATED', - LINE_DELETED = 'LINE_DELETED', - POST_CREATED = 'POST_CREATED', - POST_UPDATED = 'POST_UPDATED', - POST_DELETED = 'POST_DELETED', - TOPO_IMAGE_ADDED = 'TOPO_IMAGE_ADDED', - TOPO_IMAGE_UPDATED = 'TOPO_IMAGE_UPDATED', - TOPO_IMAGE_DELETED = 'TOPO_IMAGE_DELETED', - LINE_PATH_ADDED = 'LINE_PATH_ADDED', - LINE_PATH_DELETED = 'LINE_PATH_DELETED', - MENU_PAGE_DELETED = 'MENU_PAGE_DELETED', - MENU_PAGE_UPDATED = 'MENU_PAGE_UPDATED', - MENU_PAGE_CREATED = 'MENU_PAGE_CREATED', - MENU_ITEM_DELETED = 'MENU_ITEM_DELETED', - MENU_ITEM_UPDATED = 'MENU_ITEM_UPDATED', - MENU_ITEM_CREATED = 'MENU_ITEM_CREATED', - USER_REGISTERED = 'USER_REGISTERED', - SCALE_CREATED = 'SCALE_CREATED', - SCALE_CREATED_ERROR = 'SCALE_CREATED_ERROR', - SCALE_UPDATED = 'SCALE_UPDATED', - SCALE_UPDATED_ERROR = 'SCALE_UPDATED_ERROR', - SCALE_DELETED = 'SCALE_DELETED', - SCALE_DELETED_ERROR = 'SCALE_DELETED_ERROR', -} diff --git a/client/src/app/utility/notifications/notification-type.enum.ts b/client/src/app/utility/notifications/notification-type.enum.ts deleted file mode 100644 index ac0cc55b..00000000 --- a/client/src/app/utility/notifications/notification-type.enum.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Possible types of a notification. - */ -export enum NotificationType { - INFO, - ERROR, - WARNING, - SUCCESS, -} diff --git a/client/src/assets/i18n/de.json b/client/src/assets/i18n/de.json index cb3a5808..90f55a64 100644 --- a/client/src/assets/i18n/de.json +++ b/client/src/assets/i18n/de.json @@ -12,8 +12,8 @@ "users.list.noUsersFoundEmptyMessage": "Keine Benutzer gefunden.", "users.list.userListTitle": "Benutzer", "user.charts.gradeDistribution": "Gradverteilung", - "user.charts.lineType": "Linienart", "user.charts.completion": "Fortschritt", + "user.charts.lineType": "Linienart", "user.charts.noLinesInThisGradeRange": "Keine Linien in diesem Schwierigkeitsbereich.", "todos.priorityButton.mediumPriority": "Normale Priorität", "todos.priorityButton.lowPriority": "Niedrige Priorität", @@ -65,17 +65,22 @@ "scale.scaleForm.TRAD": "Traditionell", "scale.scaleForm.nameInputLabel": "Skalenname", "scale.scaleForm.required": "Pflichtfeld", - "scale.scaleForm.gradesErrorMsg": "Schwierigkeitswerte dürfen nicht mehrfach auftreten", "scale.scaleForm.gradeNameLabel": "Schwierigkeit (angezeigt)", "scale.scaleForm.gradeValueLabel": "Schwierigkeitswert (numerisch, intern)", + "scale.scaleForm.valuesNotUnique": "Die Schwierigkeitswerte müssen eindeutig sein.", + "scale.scaleForm.namesNotFilled": "Alle Namen müssen ausgefüllt sein.", + "scale.scaleForm.addGrade": "Schwierigkeit hinzufügen", + "scale.scaleForm.reorderGrades": "Felder nach Wert sortieren", "scale.scaleForm.gradeBracketsLabel": "Schwierigkeitsabschnitte", - "scale.scaleForm.gradeBracketsDescription": "Die Übersicht der Abschnitte wird wie folgt gebildet. Angenommen, die Abschnitte sind [5, 10, 15, 16]. Dann werden im ersten Abschnitt alle Grade mit Wert bis 5 landen, im nächsten alle zwischen 6 und 10, dann 11 bis 15 und zuletzt alles ab 16. Es ist erforderlich, dass die letzten beiden Werte aufeinanderfolgen und die Abschnitte sortiert sind. Projekte werden immer getrennt gruppiert. Es dürfen maximal {{max}} Abschnitte gesetzt werden.", + "scale.scaleForm.stackedChartBrackets": "Gestapeltes Balkendiagramm", + "scale.scaleForm.gradeBracketsDescriptionStackedChart": "Hier werden die Grad-Abschnitte für gestapelte Balkendiagramme definiert. Die Übersicht der Abschnitte wird wie folgt gebildet. Angenommen, die Abschnitte sind [5, 10, 15, 16]. Dann werden im ersten Abschnitt alle Grade mit Wert bis 5 landen, im nächsten alle zwischen 6 und 10, dann 11 bis 15 und zuletzt alles ab 16. Es ist erforderlich, dass die letzten beiden Werte aufeinanderfolgen und die Abschnitte sortiert sind. Projekte werden immer getrennt gruppiert. Es dürfen maximal {{max}} Abschnitte gesetzt werden.", + "scale.scaleForm.gradeBracketsInputLabel": "Abschnitt", "scale.scaleForm.gradeBracketsErrorMsg": "Vorgaben zu Abschnitten nicht erfüllt", "scale.scaleForm.gradeBracketsInvalidLength": "Es sind müssen mindestens {{min}} und maximal {{max}} Abschnitte konfiguriert werden.", - "scale.scaleForm.gradeBracketsInputLabel": "Abschnitt", - "scale.scaleForm.addGrade": "Schwierigkeit hinzufügen", "scale.scaleForm.addBracket": "Abschnitt hinzufügen", - "scale.scaleForm.reorderGrades": "Felder nach Wert sortieren", + "scale.scaleForm.barChartBrackets": "Balkendiagramm", + "scale.scaleForm.gradeBracketsDescriptionBarChart": "Hier werden die Grad-Abschnitte für Balkendiagramme definiert. Sie verhalten sich genau wie die Abschnitte der gestapelten Diagramme, haben aber zusätzloich eine frei wählbare Bezeichnung (z.B. \"> 8A\"). Hier dürfen maximal {{max}} Abschnitte gesetzt werden.", + "scale.scaleForm.barChartBracketNameLabel": "Name", "scale.scaleForm.saveScale": "Skala speichern", "scale.scaleForm.deleteScale": "Skala löschen", "scale.scaleForm.cancel": "Abbrechen", @@ -352,38 +357,40 @@ "ascentCount.ascents": "geloggte Begehungen", "archiveButton.archive": "Archivieren", "archiveButton.unarchive": "Wiederherstellen", - "clipboardSuccessToastTitle": "In Zwischenablage kopiert", - "clipboardSuccessToastDescription": "Der Text wurde erfolgreich in die Zwischenablage kopiert.", - "clipboardErrorToastTitle": "Fehler", - "clipboardErrorToastDescription": "Der Text konnte nicht in die Zwischenablage kopiert werden.", - "notifications.MAP_MARKER_ADDED_TITLE": "Marker hinzugefügt", - "notifications.MAP_MARKER_ADDED_MESSAGE": "Der Marker wurde erfolgreich hinzugefügt.", "notifications.MAP_MARKER_REMOVED_TITLE": "Marker entfernt", "notifications.MAP_MARKER_REMOVED_MESSAGE": "Der Marker wurde erfolgreich entfernt.", + "notifications.MAP_MARKER_ADDED_TITLE": "Marker hinzugefügt", + "notifications.MAP_MARKER_ADDED_MESSAGE": "Der Marker wurde erfolgreich hinzugefügt.", + "notifications.GALLERY_IMAGE_CREATED_TITLE": "Bild hinzugefügt", + "notifications.GALLERY_IMAGE_CREATED_MESSAGE": "Das Bild wurde erfolgreich hinzugefügt.", "notifications.GALLERY_IMAGE_UPDATED_TITLE": "Bild gespeichert", "notifications.GALLERY_IMAGE_UPDATED_MESSAGE": "Das Bild wurde erfolgreich gespeichert.", "notifications.GALLERY_IMAGE_DELETED_TITLE": "Bild gelöscht", "notifications.GALLERY_IMAGE_DELETED_MESSAGE": "Das Bild wurde erfolgreich gelöscht.", - "notifications.GALLERY_IMAGE_CREATED_TITLE": "Bild hinzugefügt", - "notifications.GALLERY_IMAGE_CREATED_MESSAGE": "Das Bild wurde erfolgreich hinzugefügt.", "notifications.PROJECT_CLIMBED_MESSAGE_SENT_TITLE": "Nachricht gesendet", "notifications.PROJECT_CLIMBED_MESSAGE_SENT_MESSAGE": "Deine Nachricht wurde erfolgreich versendet.", - "notifications.TODO_ADD_ERROR_TITLE": "Todo hinzufügen fehlgeschlagen", - "notifications.TODO_ADD_ERROR_MESSAGE": "Die Linie konnte nicht auf Deine Todo-Liste gesetzt werden.", "notifications.TODO_DELETED_TITLE": "Todo gelöscht", "notifications.TODO_DELETED_MESSAGE": "Das Todo wurde erfolgreich gelöscht.", "notifications.TODO_PRIORITY_UPDATED_TITLE": "Todo Priorität geändert", "notifications.TODO_PRIORITY_UPDATED_MESSAGE": "Die Priorität des Todos wurde erfolgreich geändert.", + "notifications.TODO_ADD_ERROR_TITLE": "Todo hinzufügen fehlgeschlagen", + "notifications.TODO_ADD_ERROR_MESSAGE": "Die Linie konnte nicht auf Deine Todo-Liste gesetzt werden.", "notifications.TODO_ADDED_TITLE": "Todo hinzugefügt", "notifications.TODO_ADDED_MESSAGE": "Die Linie wurde erfolgreich auf Deine Todo-Liste gesetzt.", - "notifications.LINE_UPDATED_TITLE": "Linie gespeichert", - "notifications.LINE_UPDATED_MESSAGE": "Die Linie wurde erfolgreich gespeichert.", - "notifications.ASCENT_DELETED_TITLE": "Begehung gelöscht", - "notifications.ASCENT_DELETED_MESSAGE": "Die Begehung wurde erfolgreicvh gelöscht.", "notifications.ASCENT_ADDED_TITLE": "Begehung geloggt", "notifications.ASCENT_ADDED_MESSAGE": "Deine Begehung wurde erfolgreich geloggt.", "notifications.ASCENT_UPDATED_TITLE": "Änderungen gespeichert", "notifications.ASCENT_UPDATED_MESSAGE": "Die Änderungen der Begehung wurden gepsichert.", + "notifications.ASCENT_DELETED_TITLE": "Begehung gelöscht", + "notifications.ASCENT_DELETED_MESSAGE": "Die Begehung wurde erfolgreicvh gelöscht.", + "notifications.ARCHIVED_TITLE": "Archiv", + "notifications.ARCHIVED_MESSAGE": "Objekt(e) erfolgreich archiviert", + "notifications.UNARCHIVED_TITLE": "Archiv", + "notifications.UNARCHIVED_MESSAGE": "Objekt(e) erfolgreich wiederhergestellt", + "notifications.ARCHIVED_ERROR_TITLE": "Archiv", + "notifications.ARCHIVED_ERROR_MESSAGE": "Fehler beim Archivieren", + "notifications.UNARCHIVED_ERROR_TITLE": "Archiv", + "notifications.UNARCHIVED_ERROR_MESSAGE": "Fehler beim Wiederherstellen", "notifications.LOGIN_ERROR_TITLE": "Login fehlgeschlagen", "notifications.LOGIN_ERROR_MESSAGE": "Sind E-Mail und Passwort korrekt?", "notifications.USER_PROMOTED_TITLE": "Benutzerrechte geändert", @@ -394,60 +401,12 @@ "notifications.CREATE_USER_MAIL_SENT_MESSAGE": "Die Mail wurde erneut versendet.", "notifications.ACCOUNT_SETTINGS_UPDATED_TITLE": "Account Einstellungen gespeichert", "notifications.ACCOUNT_SETTINGS_UPDATED_MESSAGE": "Die Account Einstellungen wurden erfolgreich gespeichert.", - "notifications.USER_REGISTERED_TITLE": "Account erstellt", - "notifications.USER_REGISTERED_MESSAGE": "Überprüfe Deine E-Mails zum Aktivieren des Accounts.", "notifications.INSTANCE_SETTINGS_UPDATED_TITLE": "Einstellungen gespeichert", "notifications.INSTANCE_SETTINGS_UPDATED_MESSAGE": "Die Instanzeinstellungen wurden erfolgreich gespeichert.", - "notifications.MENU_ITEM_DELETED_TITLE": "Menüpunkt gelöscht", - "notifications.MENU_ITEM_DELETED_MESSAGE": "Der Menüpunkt wurde erfolgreich gelöscht.", - "notifications.MENU_ITEM_CREATED_TITLE": "Menüpunkt erstellt", - "notifications.MENU_ITEM_CREATED_MESSAGE": "Der Menüpunkt wurde erfolgreich erstellt.", - "notifications.MENU_ITEM_UPDATED_TITLE": "Menüpunkt gespeichert", - "notifications.MENU_ITEM_UPDATED_MESSAGE": "Der Menüpunkt wurde erfolgreich gespeichert.", - "notifications.MENU_PAGE_DELETED_TITLE": "Menü Seite gelöscht", - "notifications.MENU_PAGE_DELETED_MESSAGE": "Die Menü Seite wurde erfolgreich gelöscht.", - "notifications.MENU_PAGE_CREATED_TITLE": "Menü Seite erstellt", - "notifications.MENU_PAGE_CREATED_MESSAGE": "Die Menü Seite wurde erfolgreich erstellt.", - "notifications.MENU_PAGE_UPDATED_TITLE": "Menü Seite gespeichert", - "notifications.MENU_PAGE_UPDATED_MESSAGE": "Die Menü Seite wurde erfolgreich gespeichert.", - "notifications.TOPO_IMAGE_UPDATED_TITLE": "Topo Bild gespeichert", - "notifications.TOPO_IMAGE_UPDATED_MESSAGE": "Das Topo Bild wurde erfolgreich gespeichert.", + "notifications.INSTANCE_SETTINGS_ERROR_MIGRATION_IMPOSSIBLE_TITLE": "Fehler beim Speichern", + "notifications.INSTANCE_SETTINGS_ERROR_MIGRATION_IMPOSSIBLE_MESSAGE": "Existierende Objekte verhindern eine Veränderung der Hierarchieebenen.", "notifications.REGION_UPDATED_TITLE": "Region gespeichert", "notifications.REGION_UPDATED_MESSAGE": "Deine Änderungen wurden erfolgreich gespeichert.", - "notifications.POST_DELETED_TITLE": "Post gelöscht", - "notifications.POST_DELETED_MESSAGE": "Der Post wurde erfolgreich gelöscht.", - "notifications.POST_UPDATED_TITLE": "Post gespeichert.", - "notifications.POST_UPDATED_MESSAGE": "Deine Änderungen wurden erfolgreich gespeichert.", - "notifications.POST_CREATED_TITLE": "Post erstellt", - "notifications.POST_CREATED_MESSAGE": "Der Post wurde erfolgreich erstellt.", - "notifications.LINE_CREATED_TITLE": "Linie erstellt", - "notifications.LINE_CREATED_MESSAGE": "Die Linie wurde erfolgreich erstellt.", - "notifications.LINE_PATH_DELETED_TITLE": "Linienverlauf gelöscht", - "notifications.LINE_PATH_DELETED_MESSAGE": "Der Linienverlauf wurde erfolgreich gelöscht.", - "notifications.LINE_PATH_ADDED_TITLE": "Linie eingezeichnet", - "notifications.LINE_PATH_ADDED_MESSAGE": "Die Linie wurde erfolgreich eingezeichnet.", - "notifications.TOPO_IMAGE_DELETED_TITLE": "Topo Bild gelöscht", - "notifications.TOPO_IMAGE_DELETED_MESSAGE": "Du hast das Topo Bild erfolgreich gelöscht.", - "notifications.TOPO_IMAGE_ADDED_TITLE": "Topo Bild hinzugefügt", - "notifications.TOPO_IMAGE_ADDED_MESSAGE": "Das Topo Bild wurde erfolgreich hinzugefügt.", - "notifications.AREA_DELETED_TITLE": "Bereich gelöscht", - "notifications.AREA_DELETED_MESSAGE": "Du hast den Bereich erfolgreich gelöscht.", - "notifications.AREA_UPDATED_TITLE": "Bereich gespeichert", - "notifications.AREA_UPDATED_MESSAGE": "Deine Änderungen wurden erfolgreich gespeichert.", - "notifications.AREA_CREATED_TITLE": "Bereich erstellt", - "notifications.AREA_CREATED_MESSAGE": "Der Bereich wurde erfolgreich erstellt.", - "notifications.SECTOR_DELETED_TITLE": "Sektor gelöscht", - "notifications.SECTOR_DELETED_MESSAGE": "Du hast den Sektor erfolgreich gelöscht.", - "notifications.SECTOR_UPDATED_TITLE": "Sektor gespeichert", - "notifications.SECTOR_UPDATED_MESSAGE": "Deine Änderungen wurden erfolgreich gespeichert.", - "notifications.SECTOR_CREATED_TITLE": "Sektor erstellt", - "notifications.SECTOR_CREATED_MESSAGE": "Der Sektor wurde erfolgreich erstellt.", - "notifications.CRAG_DELETED_TITLE": "Gebiet gelöscht", - "notifications.CRAG_DELETED_MESSAGE": "Du hast das Gebiet erfolgreich gelöscht.", - "notifications.CRAG_UPDATED_TITLE": "Gebiet gespeichert", - "notifications.CRAG_UPDATED_MESSAGE": "Deine Änderungen wurden erfolgreich gespeichert.", - "notifications.CRAG_CREATED_TITLE": "Gebiet erstellt", - "notifications.CRAG_CREATED_MESSAGE": "Das Gebiet wurde erfolgreich erstellt.", "notifications.USER_NOT_ACTIVATED_TITLE": "Benutzer nicht aktiviert", "notifications.USER_NOT_ACTIVATED_MESSAGE": "Der Benutzer ist noch nicht aktiviert.", "notifications.LOGIN_SUCCESS_TITLE": "Login erfolgreich", @@ -470,14 +429,60 @@ "notifications.UNKNOWN_ERROR_MESSAGE": "Hier ist etwas schief gelaufen.", "notifications.LOG_OUT_TO_USE_THIS_FUNCTION_TITLE": "Abmelden zum Nutzen", "notifications.LOG_OUT_TO_USE_THIS_FUNCTION_MESSAGE": "Diese Funktion kann nur von ausgeloggten Benutzern genutzt werden.", - "notifications.ARCHIVED_TITLE": "Archiv", - "notifications.ARCHIVED_MESSAGE": "Objekt(e) erfolgreich archiviert", - "notifications.UNARCHIVED_TITLE": "Archiv", - "notifications.UNARCHIVED_MESSAGE": "Objekt(e) erfolgreich wiederhergestellt", - "notifications.ARCHIVED_ERROR_TITLE": "Archiv", - "notifications.ARCHIVED_ERROR_MESSAGE": "Fehler beim Archivieren", - "notifications.UNARCHIVED_ERROR_TITLE": "Archiv", - "notifications.UNARCHIVED_ERROR_MESSAGE": "Fehler beim Wiederherstellen", + "notifications.CRAG_CREATED_TITLE": "Gebiet erstellt", + "notifications.CRAG_CREATED_MESSAGE": "Das Gebiet wurde erfolgreich erstellt.", + "notifications.CRAG_UPDATED_TITLE": "Gebiet gespeichert", + "notifications.CRAG_UPDATED_MESSAGE": "Deine Änderungen wurden erfolgreich gespeichert.", + "notifications.CRAG_DELETED_TITLE": "Gebiet gelöscht", + "notifications.CRAG_DELETED_MESSAGE": "Du hast das Gebiet erfolgreich gelöscht.", + "notifications.SECTOR_CREATED_TITLE": "Sektor erstellt", + "notifications.SECTOR_CREATED_MESSAGE": "Der Sektor wurde erfolgreich erstellt.", + "notifications.SECTOR_UPDATED_TITLE": "Sektor gespeichert", + "notifications.SECTOR_UPDATED_MESSAGE": "Deine Änderungen wurden erfolgreich gespeichert.", + "notifications.SECTOR_DELETED_TITLE": "Sektor gelöscht", + "notifications.SECTOR_DELETED_MESSAGE": "Du hast den Sektor erfolgreich gelöscht.", + "notifications.AREA_CREATED_TITLE": "Bereich erstellt", + "notifications.AREA_CREATED_MESSAGE": "Der Bereich wurde erfolgreich erstellt.", + "notifications.AREA_UPDATED_TITLE": "Bereich gespeichert", + "notifications.AREA_UPDATED_MESSAGE": "Deine Änderungen wurden erfolgreich gespeichert.", + "notifications.AREA_DELETED_TITLE": "Bereich gelöscht", + "notifications.AREA_DELETED_MESSAGE": "Du hast den Bereich erfolgreich gelöscht.", + "notifications.LINE_CREATED_TITLE": "Linie erstellt", + "notifications.LINE_CREATED_MESSAGE": "Die Linie wurde erfolgreich erstellt.", + "notifications.LINE_UPDATED_TITLE": "Linie gespeichert", + "notifications.LINE_UPDATED_MESSAGE": "Die Linie wurde erfolgreich gespeichert.", + "notifications.LINE_DELETED_TITLE": "Linie gelöscht", + "notifications.LINE_DELETED_MESSAGE": "Die Linie wurde erfolgreich gelöscht.", + "notifications.POST_CREATED_TITLE": "Post erstellt", + "notifications.POST_CREATED_MESSAGE": "Der Post wurde erfolgreich erstellt.", + "notifications.POST_UPDATED_TITLE": "Post gespeichert.", + "notifications.POST_UPDATED_MESSAGE": "Deine Änderungen wurden erfolgreich gespeichert.", + "notifications.POST_DELETED_TITLE": "Post gelöscht", + "notifications.POST_DELETED_MESSAGE": "Der Post wurde erfolgreich gelöscht.", + "notifications.TOPO_IMAGE_ADDED_TITLE": "Topo Bild hinzugefügt", + "notifications.TOPO_IMAGE_ADDED_MESSAGE": "Das Topo Bild wurde erfolgreich hinzugefügt.", + "notifications.TOPO_IMAGE_UPDATED_TITLE": "Topo Bild gespeichert", + "notifications.TOPO_IMAGE_UPDATED_MESSAGE": "Das Topo Bild wurde erfolgreich gespeichert.", + "notifications.TOPO_IMAGE_DELETED_TITLE": "Topo Bild gelöscht", + "notifications.TOPO_IMAGE_DELETED_MESSAGE": "Du hast das Topo Bild erfolgreich gelöscht.", + "notifications.LINE_PATH_ADDED_TITLE": "Linie eingezeichnet", + "notifications.LINE_PATH_ADDED_MESSAGE": "Die Linie wurde erfolgreich eingezeichnet.", + "notifications.LINE_PATH_DELETED_TITLE": "Linienverlauf gelöscht", + "notifications.LINE_PATH_DELETED_MESSAGE": "Der Linienverlauf wurde erfolgreich gelöscht.", + "notifications.MENU_PAGE_DELETED_TITLE": "Menü Seite gelöscht", + "notifications.MENU_PAGE_DELETED_MESSAGE": "Die Menü Seite wurde erfolgreich gelöscht.", + "notifications.MENU_PAGE_UPDATED_TITLE": "Menü Seite gespeichert", + "notifications.MENU_PAGE_UPDATED_MESSAGE": "Die Menü Seite wurde erfolgreich gespeichert.", + "notifications.MENU_PAGE_CREATED_TITLE": "Menü Seite erstellt", + "notifications.MENU_PAGE_CREATED_MESSAGE": "Die Menü Seite wurde erfolgreich erstellt.", + "notifications.MENU_ITEM_DELETED_TITLE": "Menüpunkt gelöscht", + "notifications.MENU_ITEM_DELETED_MESSAGE": "Der Menüpunkt wurde erfolgreich gelöscht.", + "notifications.MENU_ITEM_UPDATED_TITLE": "Menüpunkt gespeichert", + "notifications.MENU_ITEM_UPDATED_MESSAGE": "Der Menüpunkt wurde erfolgreich gespeichert.", + "notifications.MENU_ITEM_CREATED_TITLE": "Menüpunkt erstellt", + "notifications.MENU_ITEM_CREATED_MESSAGE": "Der Menüpunkt wurde erfolgreich erstellt.", + "notifications.USER_REGISTERED_TITLE": "Account erstellt", + "notifications.USER_REGISTERED_MESSAGE": "Überprüfe Deine E-Mails zum Aktivieren des Accounts.", "notifications.SCALE_CREATED_TITLE": "Erstellt", "notifications.SCALE_CREATED_MESSAGE": "Skala erfolreich erstellt", "notifications.SCALE_CREATED_ERROR_TITLE": "Fehler beim Erstellen", @@ -490,6 +495,10 @@ "notifications.SCALE_DELETED_MESSAGE": "Skala erfolreich entfernt", "notifications.SCALE_DELETED_ERROR_TITLE": "Fehler beim Entfernen", "notifications.SCALE_DELETED_ERROR_MESSAGE": "Beim Entfernen ist ein Fehler aufgetreten. Wird die Skala noch von Linien oder Hierarchieebenen genutzt?", + "clipboardSuccessToastTitle": "In Zwischenablage kopiert", + "clipboardSuccessToastDescription": "Der Text wurde erfolgreich in die Zwischenablage kopiert.", + "clipboardErrorToastTitle": "Fehler", + "clipboardErrorToastDescription": "Der Text konnte nicht in die Zwischenablage kopiert werden.", "sortAZ": "A..Z", "sortZA": "Z..A", "usersMenu.promoteToUser": "Zu normalem Benutzer ernennen", @@ -506,6 +515,7 @@ "user.charts": "Statistiken", "user.gallery": "Galerie", "ascents": "Begehungen", + "ALL": "Alle", "sortAscending": "Aufsteigend", "sortDescending": "Absteigend", "topoImage.askReallyWantToDeleteTopoImage": "Bist Du Dir sicher? Dies ist eine extrem destruktive Aktion und kann nicht rückgängig gemacht werden. Das Topo Bild und alle Linienverknüpfungen werden gelöscht. Die eigentlichen Linien samt Begehungen etc. bleiben aber bestehen.", @@ -518,7 +528,6 @@ "reorderLinePathsDialogItemsName": "Linien", "editTopoImageBrowserTitle": "Topo Bild bearbeiten", "addTopoImageBrowserTitle": "Topo Bild hinzufügen", - "ALL": "Alle", "orderByGrade": "Grad", "orderByTimeCreated": "Eintragungsdatum", "orderDescending": "absteigend", @@ -530,6 +539,9 @@ "allCrags": "Alle Gebiete", "allSectors": "Alle Sektoren", "allAreas": "Alle Bereiche", + "CLOSED_PROJECT": "Geschlossenes Projekt", + "OPEN_PROJECT": "Offenes Projekt", + "UNGRADED": "Nicht bewertet", "January": "Januar", "February": "Februar", "March": "März", @@ -542,12 +554,8 @@ "October": "Oktober", "November": "November", "December": "Dezember", - "CLOSED_PROJECT": "Geschlossenes Projekt", - "OPEN_PROJECT": "Offenes Projekt", - "UNGRADED": "Nicht bewertet", "leveledGradeDistributionUntil": "bis", "leveledGradeDistributionFrom": "ab", - "GENERIC_PROJECT": "Proj.", "copyCoordinatesToClipboard": "Koordinaten kopieren", "openCoordinatesInGoogleMaps": "In Google Maps öffnen", "reorderSectorsDialogTitle": "Sektoren ordnen", @@ -564,6 +572,8 @@ "sector.ranking": "Ranking", "sector.gallery": "Galerie", "sector.edit": "Bearbeiten", + "FIRST_BAR_CHART_BRACKET": "Erster Bereich", + "SECOND_BAR_CHART_BRACKET": "Zweiter Bereich", "scale.scaleForm.confirmDeleteMessage": "Skala wirklich unwiderruflich löschen?", "scale.scaleForm.acceptConfirmDelete": "Ja, unwiderruflich löschen", "region.infos": "Infos", @@ -632,7 +642,9 @@ "history.created_area": "Neuer Bereich", "history.created_line": "Neue Linie", "history.grading_changed": "Bewertung geändert", + "history.project_status_changed": "Projektstatus geändert", "history.projectClimbed": "Das Projekt {{line}} wurde geklettert und mit {{newGrade}} bewertet.", + "history.projectStatusChanged": "Der Projektstatus der Linie {{line}} hat sich von {{oldGrade}} zu {{newGrade}} geändert.", "history.lineGraded": "Die bisher nicht bewertete Linie {{line}} wurde mit {{newGrade}} bewertet.", "history.upgrade": "{{line}} wurde von {{oldGrade}} auf {{newGrade}} aufgewertet.", "history.downgrade": "{{line}} wurde von {{oldGrade}} auf {{newGrade}} abgewertet.", @@ -722,10 +734,10 @@ "URL": "URL", "BOTTOM": "Footer", "TOP": "Header", + "scale.scaleForm.gradesErrorMsg": "Schwierigkeitswerte dürfen nicht mehrfach auftreten", + "GENERIC_PROJECT": "Proj.", "instanceSettings.instanceSettingsForm.menuSettings": "Menü Einstellungen", "instanceSettings.instanceSettingsForm.invalidHttpUrl": "Ungültige URL", - "notifications.INSTANCE_SETTINGS_ERROR_MIGRATION_IMPOSSIBLE_TITLE": "Fehler beim Speichern", - "notifications.INSTANCE_SETTINGS_ERROR_MIGRATION_IMPOSSIBLE_MESSAGE": "Existierende Objekte verhindern eine Veränderung der Hierarchieebenen.", "maps.crags": "Gebiete", "maps.sectors": "Sektoren", "maps.areas": "Bereiche", diff --git a/client/src/assets/i18n/line/de.json b/client/src/assets/i18n/line/de.json index 8e3b09a6..483a1f46 100644 --- a/client/src/assets/i18n/line/de.json +++ b/client/src/assets/i18n/line/de.json @@ -1,6 +1,7 @@ { "lineList.newLineButtonLabel": "Neue Linie", "lineList.orderByLabel": "Sortieren nach:", + "lineList.lineType": "Linientyp", "lineList.hideArchive": "Archiv verstecken", "lineList.showArchive": "Archiv anzeigen", "lineList.noLinesFoundEmptyMessage": "Keine Linien gefunden", @@ -105,7 +106,6 @@ "lineBoolPropList.pockets": "Taschen", "lineBoolPropList.pinches": "Zangen", "lineForm.lineSecretLabel": "Geheim", - "lineList.lineType": "Linientyp", "lineList.ascentCount": "Begehungen", "lineList.videobeta": "Videobeta", "lineForm.lineVideoLabel": "Video", diff --git a/server/.idea/runConfigurations/pytest_all.xml b/server/.idea/runConfigurations/pytest_all.xml index 37ca980d..cbed1b62 100644 --- a/server/.idea/runConfigurations/pytest_all.xml +++ b/server/.idea/runConfigurations/pytest_all.xml @@ -1,6 +1,7 @@ +