solved pr comments

This commit is contained in:
Valentin 2021-11-09 22:22:01 +02:00
parent 379dccf394
commit 1158846091
22 changed files with 314 additions and 256 deletions

View File

@ -4,27 +4,28 @@ import { CompositeRouteGuard } from '@iqser/common-ui';
import { AuthGuard } from '../auth/auth.guard';
import { RedRoleGuard } from '../auth/red-role.guard';
import { AppStateGuard } from '../../state/app-state.guard';
import { NotificationsScreenComponent } from './screens/notifications/notifications-screen.component';
import { UserProfileScreenComponent } from './screens/user-profile/user-profile-screen.component';
import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component';
const routes = [
{ path: '', redirectTo: 'user-profile', pathMatch: 'full' },
{
path: 'user-profile',
component: UserProfileScreenComponent,
component: BaseAccountScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
requiredRoles: ['RED_USER'],
},
loadChildren: () => import('./screens/user-profile/user-profile.module').then(m => m.UserProfileModule),
},
{
path: 'notifications',
component: NotificationsScreenComponent,
component: BaseAccountScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
},
loadChildren: () => import('./screens/notifications/notifications.module').then(m => m.NotificationsModule),
},
];

View File

@ -1,5 +1,5 @@
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { Component } from '@angular/core';
import { ChangeDetectionStrategy, Component } from '@angular/core';
interface NavItem {
readonly label: string;
@ -10,6 +10,7 @@ interface NavItem {
selector: 'redaction-account-side-nav',
templateUrl: './account-side-nav.component.html',
styleUrls: ['./account-side-nav.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AccountSideNavComponent {
readonly items: NavItem[] = [

View File

@ -3,16 +3,12 @@ import { CommonModule } from '@angular/common';
import { SharedModule } from '../shared/shared.module';
import { AccountRoutingModule } from './account-routing.module';
import { AccountSideNavComponent } from './account-side-nav/account-side-nav.component';
import { NotificationsScreenComponent } from './screens/notifications/notifications-screen.component';
import { UserProfileScreenComponent } from './screens/user-profile/user-profile-screen.component';
const screens = [NotificationsScreenComponent, UserProfileScreenComponent];
const components = [AccountSideNavComponent, ...screens];
import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component';
import { NotificationPreferencesService } from './services/notification-preferences.service';
@NgModule({
declarations: [components],
providers: [],
declarations: [AccountSideNavComponent, BaseAccountScreenComponent],
imports: [CommonModule, SharedModule, AccountRoutingModule],
providers: [NotificationPreferencesService],
})
export class AccountModule {}

View File

@ -0,0 +1,20 @@
<section class="settings">
<div class="overlay-shadow"></div>
<redaction-account-side-nav></redaction-account-side-nav>
<div>
<div class="content-inner">
<div class="content-container full-height">
<div class="overlay-shadow"></div>
<div class="dialog">
<div class="dialog-header">
<div class="heading-l" [translate]="translations[path]"></div>
</div>
<router-outlet></router-outlet>
</div>
</div>
</div>
</div>
</section>

View File

@ -1,15 +1,16 @@
@use 'variables';
@use 'common-mixins';
.dialog {
min-height: unset;
}
@use 'apps/red-ui/src/assets/styles/variables';
@use 'libs/common-ui/src/assets/styles/common-mixins';
.content-container {
background-color: variables.$grey-2;
justify-content: center;
@include common-mixins.scroll-bar;
overflow: auto;
.dialog {
width: var(--width);
min-height: unset;
}
}
.full-height {
@ -21,7 +22,3 @@
height: calc(100% + 50px);
z-index: 1;
}
a {
color: black;
}

View File

@ -0,0 +1,27 @@
import { ChangeDetectionStrategy, Component, OnInit, ViewContainerRef } from '@angular/core';
import { Router } from '@angular/router';
import { notificationsTranslations } from '../translations/notifications-translations';
@Component({
selector: 'redaction-base-account-screen',
templateUrl: './base-account-screen-component.html',
styleUrls: ['./base-account-screen-component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BaseAccountScreenComponent implements OnInit {
readonly translations = notificationsTranslations;
path: string;
constructor(private readonly _router: Router, private readonly _hostRef: ViewContainerRef) {
this.path = this._router.url.split('/').pop();
}
ngOnInit(): void {
this._setDialogWidth();
}
private _setDialogWidth() {
const element = this._hostRef.element.nativeElement as HTMLElement;
element.style.setProperty('--width', this.path === 'user-profile' ? 'unset' : '100%');
}
}

View File

@ -0,0 +1,47 @@
export const EmailNotificationScheduleTypes = {
INSTANT: 'INSTANT',
DAILY: 'DAILY',
WEEKLY: 'WEEKLY',
} as const;
export type EmailNotificationScheduleType = keyof typeof EmailNotificationScheduleTypes;
export const EmailNotificationScheduleTypesValues = Object.values(EmailNotificationScheduleTypes);
export const NotificationCategories = {
inAppNotifications: 'inAppNotifications',
emailNotifications: 'emailNotifications',
} as const;
export const NotificationCategoriesValues = Object.values(NotificationCategories);
export const OwnDossiersNotificationsTypes = {
dossierStatusChanges: 'dossierStatusChanges',
requestToJoinTheDossier: 'requestToJoinTheDossier',
documentStatusChanges: 'documentStatusChanges',
documentIsSentForApproval: 'documentIsSentForApproval',
} as const;
export const OwnDossiersNotificationsTypesValues = Object.values(OwnDossiersNotificationsTypes);
export const ReviewerOnDossiersNotificationsTypes = {
whenIAmAssignedOnADocument: 'whenIAmAssignedOnADocument',
whenIAmUnassignedFromADocument: 'whenIAmUnassignedFromADocument',
whenADocumentIsApproved: 'whenADocumentIsApproved',
} as const;
export const ReviewerOnDossiersNotificationsTypesValues = Object.values(ReviewerOnDossiersNotificationsTypes);
export const ApproverOnDossiersNotificationsTypes = {
whenADocumentIsSentForApproval: 'whenADocumentIsSentForApproval',
whenADocumentIsAssignedToAReviewer: 'whenADocumentIsAssignedToAReviewer',
whenAReviewerIsUnassignedFromADocument: 'whenAReviewerIsUnassignedFromADocument',
} as const;
export const ApproverOnDossiersNotificationsTypesValues = Object.values(ApproverOnDossiersNotificationsTypes);
export const NotificationGroupsKeys = ['own', 'reviewer', 'approver'];
export const NotificationGroupsValues = [
OwnDossiersNotificationsTypesValues,
ReviewerOnDossiersNotificationsTypesValues,
ApproverOnDossiersNotificationsTypesValues,
];

View File

@ -1,64 +0,0 @@
<section class="settings">
<div class="overlay-shadow"></div>
<redaction-account-side-nav></redaction-account-side-nav>
<div>
<div class="content-inner">
<div class="content-container full-height">
<div class="overlay-shadow"></div>
<div class="dialog">
<div class="dialog-header">
<div class="heading-l" translate="notifications-screen.title"></div>
</div>
<form (submit)="save()" [formGroup]="formGroup" *ngIf="formGroup">
<div class="dialog-content">
<div *ngFor="let category of notificationCategories">
<div class="iqser-input-group header w-full">
<mat-slide-toggle color="primary" formControlName="{{ category }}Enabled">{{
translations[category] | translate
}}</mat-slide-toggle>
</div>
<div class="options-content" *ngIf="isCategoryActive(category)">
<div class="radio-container" *ngIf="category === 'emailNotifications'">
<div class="radio-button" *ngFor="let type of emailNotificationTypes">
<iqser-round-checkbox
[active]="getEmailNotificationType() === type"
(click)="setEmailNotificationType(type)"
>
</iqser-round-checkbox>
<span> {{ translations[type.toLocaleLowerCase()] | translate }} </span>
</div>
</div>
<div class="statement" translate="notifications-screen.options-title"></div>
<div class="group" *ngFor="let key of notificationGroupsKeys; let i = index">
<div class="group-title" [translate]="translations[key]"></div>
<div class="iqser-input-group">
<mat-checkbox
*ngFor="let preference of notificationGroupsValues[i]"
color="primary"
[checked]="isPreferenceChecked(category, preference)"
(change)="addRemovePreference($event.checked, category, preference)"
>
{{ translations[preference] | translate }}
</mat-checkbox>
</div>
</div>
</div>
</div>
</div>
<div class="dialog-actions">
<button [disabled]="formGroup.invalid" color="primary" mat-flat-button type="submit">
{{ 'user-profile-screen.actions.save' | translate }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</section>

View File

@ -1,73 +0,0 @@
@use 'variables';
@use 'common-mixins';
.content-container {
background-color: variables.$grey-2;
justify-content: center;
@include common-mixins.scroll-bar;
overflow: auto;
.dialog {
width: 100%;
min-height: unset;
.dialog-content {
flex-direction: column;
.header {
grid-column: span 2;
padding: 10px 10px;
margin-bottom: -1px;
border-top: 1px solid variables.$separator;
border-bottom: 1px solid variables.$separator;
}
.options-content {
padding: 10px 48px;
.statement {
opacity: 0.7;
color: variables.$grey-1;
font-weight: 500;
padding: 10px 0;
}
.radio-container {
display: flex;
padding: 10px 0 10px;
.radio-button {
display: flex;
align-items: center;
padding-right: 30px;
iqser-round-checkbox {
margin-right: 8px;
}
}
}
.group {
padding: 10px 0;
.group-title {
color: variables.$grey-1;
font-weight: 600;
}
.iqser-input-group {
margin-top: 5px;
}
}
}
}
}
}
.full-height {
display: flex;
flex-direction: row;
position: absolute;
bottom: 0;
width: 100%;
height: calc(100% + 50px);
z-index: 1;
}

View File

@ -0,0 +1,43 @@
<form (submit)="save()" [formGroup]="formGroup">
<div class="dialog-content">
<div *ngFor="let category of notificationCategories">
<div class="iqser-input-group header w-full">
<mat-slide-toggle color="primary" formControlName="{{ category }}Enabled">{{
translations[category] | translate
}}</mat-slide-toggle>
</div>
<div class="options-content" *ngIf="isCategoryActive(category)">
<div class="radio-container" *ngIf="category === 'emailNotifications'">
<div class="radio-button" *ngFor="let type of emailNotificationScheduleTypes">
<iqser-round-checkbox [active]="getEmailNotificationType() === type" (click)="setEmailNotificationType(type)">
</iqser-round-checkbox>
<span> {{ translations[type.toLocaleLowerCase()] | translate }} </span>
</div>
</div>
<div class="statement" translate="notifications-screen.options-title"></div>
<div class="group" *ngFor="let key of notificationGroupsKeys; let i = index">
<div class="group-title" [translate]="translations[key]"></div>
<div class="iqser-input-group">
<mat-checkbox
*ngFor="let preference of notificationGroupsValues[i]"
color="primary"
[checked]="isPreferenceChecked(category, preference)"
(change)="addRemovePreference($event.checked, category, preference)"
>
{{ translations[preference] | translate }}
</mat-checkbox>
</div>
</div>
</div>
</div>
</div>
<div class="dialog-actions">
<button [disabled]="formGroup.invalid" color="primary" mat-flat-button type="submit">
{{ 'user-profile-screen.actions.save' | translate }}
</button>
</div>
</form>

View File

@ -0,0 +1,51 @@
@use 'apps/red-ui/src/assets/styles/variables';
@use 'libs/common-ui/src/assets/styles/common-mixins';
.dialog-content {
flex-direction: column;
.header {
grid-column: span 2;
padding: 10px 10px;
margin-bottom: -1px;
border-top: 1px solid variables.$separator;
border-bottom: 1px solid variables.$separator;
}
.options-content {
padding: 10px 48px;
.statement {
opacity: 0.7;
color: variables.$grey-1;
font-weight: 500;
padding: 10px 0;
}
.radio-container {
display: flex;
padding: 10px 0 10px;
.radio-button {
display: flex;
align-items: center;
padding-right: 30px;
iqser-round-checkbox {
margin-right: 8px;
}
}
}
.group {
padding: 10px 0;
.group-title {
color: variables.$grey-1;
font-weight: 600;
}
.iqser-input-group {
margin-top: 5px;
}
}
}
}

View File

@ -1,24 +1,27 @@
import { Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { notificationsTranslations } from '../../translations/notifications-translations';
import { NotificationPreferencesService } from '../../../../services/notification-preferences.service';
import { notificationsTranslations } from '../../../translations/notifications-translations';
import { NotificationPreferencesService } from '../../../services/notification-preferences.service';
import { LoadingService, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import {
EmailNotificationScheduleTypesValues,
NotificationCategoriesValues,
NotificationGroupsKeys,
NotificationGroupsValues,
} from '../constants';
@Component({
selector: 'redaction-notifications-screen',
templateUrl: './notifications-screen.component.html',
styleUrls: ['./notifications-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NotificationsScreenComponent implements OnInit {
readonly emailNotificationTypes: string[] = ['INSTANT', 'DAILY', 'WEEKLY'];
readonly notificationCategories: string[] = ['inAppNotifications', 'emailNotifications'];
readonly notificationGroupsKeys: string[] = ['own', 'reviewer', 'approver'];
readonly notificationGroupsValues: string[][] = [
['dossierStatusChanges', 'requestToJoinTheDossier', 'documentStatusChanges', 'documentIsSentForApproval'],
['whenIAmAssignedOnADocument', 'whenIAmUnassignedFromADocument', 'whenADocumentIsApproved'],
['whenADocumentIsSentForApproval', 'whenADocumentIsAssignedToAReviewer', 'whenAReviewerIsUnassignedFromADocument'],
];
readonly emailNotificationScheduleTypes = EmailNotificationScheduleTypesValues;
readonly notificationCategories = NotificationCategoriesValues;
readonly notificationGroupsKeys = NotificationGroupsKeys;
readonly notificationGroupsValues = NotificationGroupsValues;
readonly translations = notificationsTranslations;
formGroup: FormGroup;
@ -28,7 +31,15 @@ export class NotificationsScreenComponent implements OnInit {
private readonly _formBuilder: FormBuilder,
private readonly _loadingService: LoadingService,
private readonly _notificationPreferencesService: NotificationPreferencesService,
) {}
) {
this.formGroup = this._formBuilder.group({
inAppNotificationsEnabled: [undefined],
emailNotificationsEnabled: [undefined],
emailNotificationType: [undefined],
emailNotifications: [undefined],
inAppNotifications: [undefined],
});
}
async ngOnInit(): Promise<void> {
await this._initializeForm();
@ -75,13 +86,7 @@ export class NotificationsScreenComponent implements OnInit {
this._loadingService.start();
const notificationPreferences = await this._notificationPreferencesService.getNotificationPreferences().toPromise();
this.formGroup = this._formBuilder.group({
inAppNotificationsEnabled: [notificationPreferences.inAppNotificationsEnabled],
emailNotificationsEnabled: [notificationPreferences.emailNotificationsEnabled],
emailNotificationType: [notificationPreferences.emailNotificationType],
emailNotifications: [notificationPreferences.emailNotifications],
inAppNotifications: [notificationPreferences.inAppNotifications],
});
this.formGroup.patchValue(notificationPreferences);
this._loadingService.stop();
}

View File

@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { SharedModule } from '../../../shared/shared.module';
import { NotificationsScreenComponent } from './notifications-screen/notifications-screen.component';
const routes = [{ path: '', component: NotificationsScreenComponent }];
@NgModule({
declarations: [NotificationsScreenComponent],
imports: [RouterModule.forChild(routes), CommonModule, SharedModule],
})
export class NotificationsModule {}

View File

@ -1,62 +0,0 @@
<section class="settings">
<div class="overlay-shadow"></div>
<redaction-account-side-nav></redaction-account-side-nav>
<div>
<div class="content-inner">
<div class="content-container full-height">
<div class="overlay-shadow"></div>
<div class="dialog">
<div class="dialog-header">
<div class="heading-l" translate="user-profile-screen.title"></div>
</div>
<form (submit)="save()" [formGroup]="formGroup">
<div class="dialog-content">
<div class="dialog-content-left">
<div class="iqser-input-group required">
<label translate="user-profile-screen.form.email"></label>
<input formControlName="email" name="email" type="email" />
</div>
<div class="iqser-input-group">
<label translate="user-profile-screen.form.first-name"></label>
<input formControlName="firstName" name="firstName" type="text" />
</div>
<div class="iqser-input-group">
<label translate="user-profile-screen.form.last-name"></label>
<input formControlName="lastName" name="lastName" type="text" />
</div>
<div class="iqser-input-group">
<label translate="top-bar.navigation-items.my-account.children.language.label"></label>
<mat-select formControlName="language">
<mat-option *ngFor="let language of languages" [value]="language">
{{ translations[language] | translate }}
</mat-option>
</mat-select>
</div>
<div class="iqser-input-group">
<a [href]="changePasswordUrl" target="_blank">
{{ 'user-profile-screen.actions.change-password' | translate }}</a
>
</div>
</div>
</div>
<div class="dialog-actions">
<button
[disabled]="formGroup.invalid || !(profileChanged || languageChanged)"
color="primary"
mat-flat-button
type="submit"
>
{{ 'user-profile-screen.actions.save' | translate }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,37 @@
<form (submit)="save()" [formGroup]="formGroup">
<div class="dialog-content">
<div class="dialog-content-left">
<div class="iqser-input-group required">
<label translate="user-profile-screen.form.email"></label>
<input formControlName="email" name="email" type="email" />
</div>
<div class="iqser-input-group">
<label translate="user-profile-screen.form.first-name"></label>
<input formControlName="firstName" name="firstName" type="text" />
</div>
<div class="iqser-input-group">
<label translate="user-profile-screen.form.last-name"></label>
<input formControlName="lastName" name="lastName" type="text" />
</div>
<div class="iqser-input-group">
<label translate="top-bar.navigation-items.my-account.children.language.label"></label>
<mat-select formControlName="language">
<mat-option *ngFor="let language of languages" [value]="language">
{{ translations[language] | translate }}
</mat-option>
</mat-select>
</div>
<div class="iqser-input-group">
<a [href]="changePasswordUrl" target="_blank"> {{ 'user-profile-screen.actions.change-password' | translate }}</a>
</div>
</div>
</div>
<div class="dialog-actions">
<button [disabled]="formGroup.invalid || !(profileChanged || languageChanged)" color="primary" mat-flat-button type="submit">
{{ 'user-profile-screen.actions.save' | translate }}
</button>
</div>
</form>

View File

@ -1,19 +1,20 @@
import { Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { languagesTranslations } from '../../translations/languages-translations';
import { UserService } from '../../../../services/user.service';
import { PermissionsService } from '../../../../services/permissions.service';
import { ConfigService } from '../../../../services/config.service';
import { LanguageService } from '../../../../i18n/language.service';
import { DomSanitizer } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { LoadingService } from '@iqser/common-ui';
import { IProfile } from '@red/domain';
import { languagesTranslations } from '../../../translations/languages-translations';
import { PermissionsService } from '../../../../../services/permissions.service';
import { UserService } from '../../../../../services/user.service';
import { ConfigService } from '../../../../../services/config.service';
import { LanguageService } from '../../../../../i18n/language.service';
@Component({
selector: 'redaction-user-profile-screen',
templateUrl: './user-profile-screen.component.html',
styleUrls: ['./user-profile-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserProfileScreenComponent implements OnInit {
formGroup: FormGroup;

View File

@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { SharedModule } from '../../../shared/shared.module';
import { UserProfileScreenComponent } from './user-profile-screen/user-profile-screen.component';
const routes = [{ path: '', component: UserProfileScreenComponent }];
@NgModule({
declarations: [UserProfileScreenComponent],
imports: [RouterModule.forChild(routes), CommonModule, SharedModule],
})
export class UserProfileModule {}

View File

@ -1,9 +1,10 @@
import { Injectable, Injector } from '@angular/core';
import { GenericService } from '@iqser/common-ui';
import { Observable, of } from 'rxjs';
import { UserService } from './user.service';
import { UserService } from '../../../services/user.service';
import { INotificationPreferences } from '@red/domain';
import { catchError, map } from 'rxjs/operators';
import { catchError } from 'rxjs/operators';
import { EmailNotificationScheduleTypes } from '../screens/notifications/constants';
@Injectable({
providedIn: 'root',
@ -14,10 +15,7 @@ export class NotificationPreferencesService extends GenericService<INotification
}
getNotificationPreferences(): Observable<INotificationPreferences> {
return super.get().pipe(
map(notificationPreferences => (Array.isArray(notificationPreferences) ? notificationPreferences[0] : notificationPreferences)),
catchError(() => of(this._defaultPreferences)),
);
return super.get<INotificationPreferences>().pipe(catchError(() => of(this._defaultPreferences)));
}
updateNotificationPreferences(notificationPreferences: INotificationPreferences): Observable<void> {
@ -26,7 +24,7 @@ export class NotificationPreferencesService extends GenericService<INotification
private get _defaultPreferences(): INotificationPreferences {
return {
emailNotificationType: 'INSTANT',
emailNotificationType: EmailNotificationScheduleTypes.INSTANT,
emailNotifications: [],
emailNotificationsEnabled: false,
inAppNotifications: [],

View File

@ -19,4 +19,6 @@ export const notificationsTranslations: { [key: string]: string } = {
approver: _('notifications-screen.groups.approver'),
own: _('notifications-screen.groups.own'),
reviewer: _('notifications-screen.groups.reviewer'),
notifications: _('notifications-screen.title'),
'user-profile': _('user-profile-screen.title'),
} as const;

@ -1 +1 @@
Subproject commit 712178ea34c998098cd2c079a0be1dd863dd266e
Subproject commit 64bf1b270deb96f399b1ab603cf9cd00ff700f36

View File

@ -1,5 +1,7 @@
import { EmailNotificationScheduleType } from '../../../../../apps/red-ui/src/app/modules/account/screens/notifications/constants';
export interface INotificationPreferences {
emailNotificationType: 'INSTANT' | 'DAILY' | 'WEEKLY';
emailNotificationType: EmailNotificationScheduleType;
emailNotifications: string[];
emailNotificationsEnabled: boolean;
inAppNotifications: string[];