diff --git a/src/app/admin/admin-routing.module.ts b/src/app/admin/admin-routing.module.ts index e53436813..e3d9e8f59 100644 --- a/src/app/admin/admin-routing.module.ts +++ b/src/app/admin/admin-routing.module.ts @@ -15,6 +15,7 @@ import { UsersComponent } from './users/users.component'; import { ApiKeyComponent } from './api-key/api-key.component'; import { ApiKeyListComponent } from './api-key/api-key-list/api-key-list.component'; import { ApiKeyEditComponent } from './api-key/api-key-edit/api-key-edit.component'; +import { AcceptUserComponent } from './users/accept-user/accept-user.component'; const adminRoutes: Routes = [ @@ -32,6 +33,7 @@ const adminRoutes: Routes = [ { path: 'new-user', component: UserEditComponent }, { path: ':user-id', component: UserDetailComponent }, { path: ':user-id/edit-user', component: UserEditComponent }, + { path: 'accept-user/:user-id/:org-id', component: AcceptUserComponent } ] }, { diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts index 932100701..251683700 100644 --- a/src/app/admin/admin.module.ts +++ b/src/app/admin/admin.module.ts @@ -32,6 +32,8 @@ import { ApiKeyComponent } from './api-key/api-key.component'; import { ApiKeyListComponent } from './api-key/api-key-list/api-key-list.component'; import { ApiKeyTableComponent } from './api-key/api-key-list/api-key-table/api-key-table.component'; import { ApiKeyEditComponent } from './api-key/api-key-edit/api-key-edit.component'; +import { AwaitingUsersTableComponent } from './users/user-list/awaiting-users-table/awaiting-users-table.component'; +import { AcceptUserComponent } from './users/accept-user/accept-user.component'; @NgModule({ declarations: [ @@ -54,6 +56,8 @@ import { ApiKeyEditComponent } from './api-key/api-key-edit/api-key-edit.compone ApiKeyListComponent, ApiKeyTableComponent, ApiKeyEditComponent, + AwaitingUsersTableComponent, + AcceptUserComponent, ], imports: [ AdminRoutingModule, diff --git a/src/app/admin/organisation/organisation.service.ts b/src/app/admin/organisation/organisation.service.ts index c480205e1..c350f2f03 100644 --- a/src/app/admin/organisation/organisation.service.ts +++ b/src/app/admin/organisation/organisation.service.ts @@ -9,6 +9,7 @@ import { } from './organisation.model'; import { map, shareReplay } from 'rxjs/operators'; import { UserMinimalService } from '../users/user-minimal.service'; +import { UpdateUserOrgsDto } from '../users/user.model'; @Injectable({ providedIn: 'root', @@ -16,10 +17,12 @@ import { UserMinimalService } from '../users/user-minimal.service'; export class OrganisationService { URL = 'organization'; URLMINIMAL = 'organization/minimal'; + URLMINIMAL_NEWKOMBIT = 'kombitCreation/minimal'; constructor( private restService: RestService, - private userMinimalService: UserMinimalService) { } + private userMinimalService: UserMinimalService + ) {} post(body: Organisation): Observable { return this.restService.post(this.URL, body); @@ -32,27 +35,32 @@ export class OrganisationService { } getOne(id: number): Observable { - return this.restService.get(this.URL, {}, id) - .pipe( - map( - (response: OrganisationResponse) => { - response.createdByName = this.userMinimalService.getUserNameFrom(response.createdBy); - response.updatedByName = this.userMinimalService.getUserNameFrom(response.updatedBy); - return response; - } - ) - ); + return this.restService.get(this.URL, {}, id).pipe( + map((response: OrganisationResponse) => { + response.createdByName = this.userMinimalService.getUserNameFrom( + response.createdBy + ); + response.updatedByName = this.userMinimalService.getUserNameFrom( + response.updatedBy + ); + return response; + }) + ); } getMinimal(): Observable { return this.restService.get(this.URLMINIMAL, {}).pipe(shareReplay(1)); } + getMinimalNoPerm(): Observable { + return this.restService.get(this.URLMINIMAL_NEWKOMBIT, {}).pipe(shareReplay(1)); + } + getMultiple( limit: number = 1000, offset: number = 0, orderByColumn?: string, - orderByDirection?: string, + orderByDirection?: string ): Observable { return this.restService.get(this.URL, { limit, diff --git a/src/app/admin/permission/permission.model.ts b/src/app/admin/permission/permission.model.ts index 00ca3c2d2..436d92e21 100644 --- a/src/app/admin/permission/permission.model.ts +++ b/src/app/admin/permission/permission.model.ts @@ -11,6 +11,12 @@ export class PermissionRequest { automaticallyAddNewApplications = true; } +export class PermissionRequestAcceptUser { + organizationId: number; + userId: number; + level: PermissionType; +} + export interface PermissionResponse { type: PermissionType; name: string; diff --git a/src/app/admin/permission/permission.service.ts b/src/app/admin/permission/permission.service.ts index fa4ae91c2..2e0e51620 100644 --- a/src/app/admin/permission/permission.service.ts +++ b/src/app/admin/permission/permission.service.ts @@ -5,6 +5,7 @@ import { PermissionGetManyResponse, PermissionResponse, PermissionRequest, + PermissionRequestAcceptUser, } from './permission.model'; import { map } from 'rxjs/operators'; import { UserMinimalService } from '../users/user-minimal.service'; @@ -25,6 +26,12 @@ export class PermissionService { }); } + createPermissionAcceptUser(body: PermissionRequestAcceptUser): Observable { + return this.restService.put(this.endpoint + '/acceptUser', body, undefined, { + observe: 'response', + }); + } + updatePermission( body: PermissionRequest, id: number diff --git a/src/app/admin/users/accept-user/accept-user.component.html b/src/app/admin/users/accept-user/accept-user.component.html new file mode 100644 index 000000000..1f4151bc3 --- /dev/null +++ b/src/app/admin/users/accept-user/accept-user.component.html @@ -0,0 +1,70 @@ +
+ + +
+
+
    +
  • + {{ error | translate }} +
  • +
+
+
+
+
+
+

+ {{ 'USERS.ACCEPT-USER.QUESTION-ACCEPT' | translate }} + {{ user.name }} +

+ + +
+ * + +
+
+ + +
+
+
+
+
+
+
diff --git a/src/app/admin/users/accept-user/accept-user.component.scss b/src/app/admin/users/accept-user/accept-user.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/admin/users/accept-user/accept-user.component.ts b/src/app/admin/users/accept-user/accept-user.component.ts new file mode 100644 index 000000000..0fd839df7 --- /dev/null +++ b/src/app/admin/users/accept-user/accept-user.component.ts @@ -0,0 +1,98 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { + PermissionRequestAcceptUser, + PermissionType, +} from '@app/admin/permission/permission.model'; +import { TranslateService } from '@ngx-translate/core'; +import { ErrorMessageService } from '@shared/error-message.service'; +import { Subscription } from 'rxjs'; +import { UserResponse } from '../user.model'; +import { UserService } from '../user.service'; +import { Location } from '@angular/common'; +import { PermissionService } from '@app/admin/permission/permission.service'; + +@Component({ + selector: 'app-accept-user', + templateUrl: './accept-user.component.html', + styleUrls: ['./accept-user.component.scss'], +}) +export class AcceptUserComponent implements OnInit { + public backButtonTitle: string; + permission = new PermissionRequestAcceptUser(); + public title: string; + userId: number; + subscription: Subscription; + user: UserResponse; + errorFields: string[]; + organizationId: number; + public formFailedSubmit = false; + errorMessages: any; + + constructor( + private userService: UserService, + private location: Location, + private translate: TranslateService, + private route: ActivatedRoute, + private errorMessageService: ErrorMessageService, + private permissionService: PermissionService + ) {} + + ngOnInit(): void { + this.userId = +this.route.snapshot.paramMap.get('user-id'); + this.organizationId = +this.route.snapshot.paramMap.get('org-id'); + if (this.userId) { + this.getUser(this.userId); + } + + this.translate + .get(['GEN.BACK', 'USERS.ACCEPT-USER.ACCEPT']) + .subscribe((translations) => { + this.backButtonTitle = translations['GEN.BACK']; + this.title = translations['USERS.ACCEPT-USER.ACCEPT']; + }); + this.permission.userId = this.userId; + this.permission.organizationId = this.organizationId; + } + + private getUser(id: number) { + this.subscription = this.userService + .getOne(id, false) + .subscribe((response) => { + this.user = response; + }); + } + + allowedLevels() { + return [ + PermissionType.OrganizationAdmin, + PermissionType.Write, + PermissionType.Read, + ]; + } + + private showError(err: HttpErrorResponse) { + const result = this.errorMessageService.handleErrorMessageWithFields(err); + this.errorFields = result.errorFields; + this.errorMessages = result.errorMessages; + } + + routeBack(): void { + this.location.back(); + } + + onSubmit(): void { + this.permissionService + .createPermissionAcceptUser(this.permission) + .subscribe( + () => { + this.routeBack(); + }, + (error: HttpErrorResponse) => { + console.log(error); + this.showError(error); + } + ); + } +} diff --git a/src/app/admin/users/new-kombit-user-page/new-user.component.html b/src/app/admin/users/new-kombit-user-page/new-user.component.html new file mode 100644 index 000000000..ca7cfedf9 --- /dev/null +++ b/src/app/admin/users/new-kombit-user-page/new-user.component.html @@ -0,0 +1,80 @@ + + +
+
+
+
+

+ {{ 'NEW_USER.FIRST_LOGIN' | translate }} +

+
+
+
    +
  • + {{ error | translate }} +
  • +
+
+
+
+ * + + +
+ +
+ * + + {{ 'NAV.ORGANISATIONS' | translate }} + + + + {{ org.name }} + + +
+
+
+ +
+
+
+
+
+
diff --git a/src/app/admin/users/new-kombit-user-page/new-user.component.scss b/src/app/admin/users/new-kombit-user-page/new-user.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/admin/users/new-kombit-user-page/new-user.component.ts b/src/app/admin/users/new-kombit-user-page/new-user.component.ts new file mode 100644 index 000000000..4114c024d --- /dev/null +++ b/src/app/admin/users/new-kombit-user-page/new-user.component.ts @@ -0,0 +1,151 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, OnInit } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { Router } from '@angular/router'; +import { Organisation } from '@app/admin/organisation/organisation.model'; +import { OrganisationService } from '@app/admin/organisation/organisation.service'; +import { + CreateNewKombitUserDto, + CreateNewKombitUserFromFrontend, +} from '@app/admin/users/user.model'; +import { UserService } from '@app/admin/users/user.service'; +import { TranslateService } from '@ngx-translate/core'; +import { ErrorMessageService } from '@shared/error-message.service'; +import { SharedVariableService } from '@shared/shared-variable/shared-variable.service'; +import { ReplaySubject, Subject, Subscription } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'app-new-user', + templateUrl: './new-user.component.html', + styleUrls: ['./new-user.component.scss'], +}) +export class NewUserComponent implements OnInit { + public organisationSubscription: Subscription; + public userSubscription: Subscription; + public organisations: Organisation[]; + public formFailedSubmit = false; + public errorFields: string[]; + public errorMessages: unknown; + public createNewKombitUserFromFrontend: CreateNewKombitUserFromFrontend = new CreateNewKombitUserFromFrontend(); + public organisationsFilterCtrl: FormControl = new FormControl(); + public filteredOrganisations: ReplaySubject< + Organisation[] + > = new ReplaySubject(1); + private onDestroy = new Subject(); + + constructor( + private organisationService: OrganisationService, + private sharedService: SharedVariableService, + private userService: UserService, + private router: Router, + private translate: TranslateService, + private errorMessageService: ErrorMessageService + ) {} + + ngOnInit(): void { + if (history.state.code === 200) { + this.translate.get([ + 'NEW_USER.FIRST_LOGIN', + 'USERS.EMAIL', + 'NAV.ORGANISATIONS', + 'NAV.BACK', + 'USERS.SAVE', + ]); + + this.getOrganisations(); + + this.organisationsFilterCtrl.valueChanges + .pipe(takeUntil(this.onDestroy)) + .subscribe(() => { + this.filterOrganisations(); + }); + } else { + this.router.navigate(['/not-found']); + } + } + + private filterOrganisations() { + if (!this.organisations) { + return; + } + + // get the search keyword + let search = this.organisationsFilterCtrl?.value?.trim(); + if (!search) { + this.filteredOrganisations.next(this.organisations.slice()); + return; + } else { + search = search.toLowerCase(); + } + + const filtered = this.organisations.filter((org) => { + return org.name.toLocaleLowerCase().indexOf(search) > -1; + }); + + this.filteredOrganisations.next(filtered); + } + + onSubmit(): void { + this.resetErrors(); + + const createNewKombitUserDTO = this.mapToDto(this.createNewKombitUserFromFrontend); + + this.userService.updateNewKombit(createNewKombitUserDTO).subscribe( + () => { + this.router.navigate(['/dashboard']); + }, + (error: HttpErrorResponse) => { + this.handleError(error); + this.formFailedSubmit = true; + } + ); + } + + private mapToDto(frontendModel: CreateNewKombitUserFromFrontend): CreateNewKombitUserDto { + const createNewKombitUserDTO = new CreateNewKombitUserDto(); + createNewKombitUserDTO.email = frontendModel.email; + createNewKombitUserDTO.requestedOrganizationIds = []; + + frontendModel.requestedOrganizations.forEach((organization) => { + createNewKombitUserDTO.requestedOrganizationIds.push( + organization.id + ); + }); + + return createNewKombitUserDTO; + } + + public compare( + o1: Organisation | undefined, + o2: Organisation | undefined + ): boolean { + return o1?.id === o2?.id; + } + + public getOrganisations() { + this.organisations = this.sharedService.getOrganizationInfo(); + if (!this.organisations) { + this.filteredOrganisations.next(this.organisations.slice()); + } else { + this.organisationSubscription = this.organisationService + .getMinimalNoPerm() + .subscribe((orgs) => { + this.organisations = orgs.data; + this.filteredOrganisations.next(this.organisations.slice()); + }); + } + } + + private resetErrors() { + this.errorFields = []; + this.errorMessages = undefined; + this.formFailedSubmit = false; + } + + handleError(error: HttpErrorResponse) { + const errors = this.errorMessageService.handleErrorMessageWithFields(error); + this.errorFields = errors.errorFields; + this.errorMessages = errors.errorMessages; + } +} diff --git a/src/app/admin/users/user-list/awaiting-users-table/awaiting-users-table.component.html b/src/app/admin/users/user-list/awaiting-users-table/awaiting-users-table.component.html new file mode 100644 index 000000000..9c780e4b7 --- /dev/null +++ b/src/app/admin/users/user-list/awaiting-users-table/awaiting-users-table.component.html @@ -0,0 +1,90 @@ +
+
+ +
+
+ {{ errorMessage | translate }} +
+ + + + + + + + + + + + + + + + + + + + + +
+ {{ 'USERS.NAME' | translate }} + +

{{ element.name }}

+
+ {{ 'USERS.EMAIL' | translate }} + +

{{ element.email }}

+
+ +
+ + +
\ No newline at end of file diff --git a/src/app/admin/users/user-list/awaiting-users-table/awaiting-users-table.component.scss b/src/app/admin/users/user-list/awaiting-users-table/awaiting-users-table.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/admin/users/user-list/awaiting-users-table/awaiting-users-table.component.ts b/src/app/admin/users/user-list/awaiting-users-table/awaiting-users-table.component.ts new file mode 100644 index 000000000..6c82c8e19 --- /dev/null +++ b/src/app/admin/users/user-list/awaiting-users-table/awaiting-users-table.component.ts @@ -0,0 +1,120 @@ +import { AfterViewInit, Component, Input, ViewChild } from '@angular/core'; +import { + RejectUserDto, + UserGetManyResponse, + UserResponse, +} from '../../user.model'; +import { UserService } from '../../user.service'; +import { SharedVariableService } from '@shared/shared-variable/shared-variable.service'; +import { environment } from '@environments/environment'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { TranslateService } from '@ngx-translate/core'; +import { merge, Observable, of as observableOf } from 'rxjs'; +import { catchError, map, startWith, switchMap } from 'rxjs/operators'; +import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; + +@Component({ + selector: 'app-awaiting-users-table', + templateUrl: './awaiting-users-table.component.html', + styleUrls: ['./awaiting-users-table.component.scss'], +}) +export class AwaitingUsersTableComponent implements AfterViewInit { + displayedColumns: string[] = ['name', 'email', 'menu']; + users: UserResponse[]; + + public pageSize = environment.tablePageSize; + resultsLength = 0; + public errorMessage: string; + isLoadingResults = true; + public organizationId: number; + message: string; + infoTitle: string; + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; + + @Input() permissionId?: number; + @Input() canSort = true; + + constructor( + public translate: TranslateService, + private userService: UserService, + private sharedService: SharedVariableService, + private deleteDialogService: DeleteDialogService + ) {} + + ngAfterViewInit() { + this.organizationId = this.sharedService.getSelectedOrganisationId(); + // If the user changes the sort order, reset back to the first page. + this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0)); + + merge(this.sort.sortChange, this.paginator.page) + .pipe( + startWith({}), + switchMap(() => { + this.isLoadingResults = true; + return this.getUsers(this.sort.active, this.sort.direction); + }), + map((data) => { + // Flip flag to show that loading has finished. + this.isLoadingResults = false; + this.resultsLength = data.count; + + return data.data; + }), + catchError(() => { + this.isLoadingResults = false; + return observableOf([]); + }) + ) + .subscribe((data) => (this.users = data)); + + this.translate + .get(['USERS.DIALOG.QUESTION-REJECT', 'USERS.DIALOG.HEAD-REJECT']) + .subscribe((translations) => { + this.message = translations['USERS.DIALOG.QUESTION-REJECT']; + this.infoTitle = translations['USERS.DIALOG.HEAD-REJECT']; + }); + } + + getUsers( + orderByColumn: string, + orderByDirection: string + ): Observable { + return this.userService.getAwaitingUsers( + this.paginator.pageSize, + this.paginator.pageIndex * this.paginator.pageSize, + this.organizationId, + orderByColumn, + orderByDirection + ); + } + + rejectUser(userId: number) { + this.deleteDialogService + .showSimpleDialog(this.message, false, true, false, this.infoTitle, true) + .subscribe((response) => { + + if (response) { + const rejectUserOrgDto: RejectUserDto = { + orgId: this.organizationId, + userIdToReject: userId + } + + this.userService + .rejectUser(rejectUserOrgDto) + .subscribe((response) => { + if (response) { + this.paginator.page.emit({ + pageIndex: this.paginator.pageIndex, + pageSize: this.paginator.pageSize, + length: this.resultsLength, + }); + } else { + this.errorMessage = response?.name; + } + }); + } + }); + } +} diff --git a/src/app/admin/users/user-list/user-list.component.html b/src/app/admin/users/user-list/user-list.component.html index 33c5d7767..8f357a5a5 100644 --- a/src/app/admin/users/user-list/user-list.component.html +++ b/src/app/admin/users/user-list/user-list.component.html @@ -1,11 +1,31 @@ - +
-
-
-
- -
-
+
+
+ + +
+ + +
+
+ +
+ + +
+
+
-
\ No newline at end of file +
+
diff --git a/src/app/admin/users/user-minimal.service.ts b/src/app/admin/users/user-minimal.service.ts index d7f22f385..8aa36f43d 100644 --- a/src/app/admin/users/user-minimal.service.ts +++ b/src/app/admin/users/user-minimal.service.ts @@ -9,6 +9,7 @@ import { UserMinimal, UserMinimalResponse } from './user-minimal.model'; export class UserMinimalService { URL = 'user/minimal'; + URL_NEW_KOMBIT = "kombitCreation/minimalUsers" private userMinimalList: UserMinimal[]; constructor( @@ -18,8 +19,12 @@ export class UserMinimalService { return this.restService.get(this.URL); } + getUserMinimalRestNewKombit(): Observable { + return this.restService.get(this.URL_NEW_KOMBIT); + } + setUserMinimalList() { - return this.getUserMinimalRest().subscribe( + return this.getUserMinimalRestNewKombit().subscribe( (response: UserMinimalResponse) => { localStorage.setItem( 'userMinimalList', diff --git a/src/app/admin/users/user-page/user-page.component.html b/src/app/admin/users/user-page/user-page.component.html new file mode 100644 index 000000000..2b1a2a9a6 --- /dev/null +++ b/src/app/admin/users/user-page/user-page.component.html @@ -0,0 +1,73 @@ + + +
+
+
+
+

+ {{ 'USER_PAGE.AWAITING_CONFIRMATION' | translate }} +

+ +

+ {{ 'USER_PAGE.APPLY_ORGANISATIONS' | translate }} +

+
+

{{ 'USER_PAGE.APPLIED_ORGANISATIONS' | translate }}

+

+ {{ org.name }} +

+

+ {{ 'USER_PAGE.NO_APPLIED_ORGS' | translate }} +

+
+ {{ 'USER_PAGE.QUESTION_APPLY_ORGANISATIONS' | translate }} +
+
+ +
+
+
    +
  • + {{ error | translate }} +
  • +
+
+
+ + {{ 'NAV.ORGANISATIONS' | translate }} + + {{ 'USER_PAGE.NO_ORGS' | translate }} + + + + + {{ org.name }} + + +
+
+ +
+
+
+
+
+
diff --git a/src/app/admin/users/user-page/user-page.component.scss b/src/app/admin/users/user-page/user-page.component.scss new file mode 100644 index 000000000..fac1b736a --- /dev/null +++ b/src/app/admin/users/user-page/user-page.component.scss @@ -0,0 +1,7 @@ +.newOrganisationsHeadLine { + margin-top: 15px; +} +.noOrgs { + font-style: italic; + font-size: 15px; +} diff --git a/src/app/admin/users/user-page/user-page.component.ts b/src/app/admin/users/user-page/user-page.component.ts new file mode 100644 index 000000000..5a025e801 --- /dev/null +++ b/src/app/admin/users/user-page/user-page.component.ts @@ -0,0 +1,210 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, OnInit } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { Organisation } from '@app/admin/organisation/organisation.model'; +import { OrganisationService } from '@app/admin/organisation/organisation.service'; +import { + UpdateUserOrgFromFrontend, + UpdateUserOrgsDto, + UserResponse, +} from '@app/admin/users/user.model'; +import { UserService } from '@app/admin/users/user.service'; +import { CurrentUserInfoResponse } from '@auth/auth.service'; +import { TranslateService } from '@ngx-translate/core'; +import { ErrorMessageService } from '@shared/error-message.service'; +import { SharedVariableService } from '@shared/shared-variable/shared-variable.service'; +import { ReplaySubject, Subject, Subscription } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'app-user-page', + templateUrl: './user-page.component.html', + styleUrls: ['./user-page.component.scss'], +}) +export class UserPageComponent implements OnInit { + public updateUserOrgs: UpdateUserOrgsDto = new UpdateUserOrgsDto(); + public updateUserOrgsFromFrontend: UpdateUserOrgFromFrontend = new UpdateUserOrgFromFrontend(); + public organisationSubscription: Subscription; + public formFailedSubmit = false; + public backButtonTitle: string; + public errorFields: string[]; + public errorMessages: unknown; + public title: string; + public awaitingConfirmation: boolean; + public requestOrganizationsList: Organisation[]; + public requestedUserOrganizations: Organisation[]; + public checkForNoUserOrganizations = false; + public checkForRemainingOrganizations = false; + public userInfo: CurrentUserInfoResponse; + public pressed = false; + public organisationsFilterCtrl: FormControl = new FormControl(); + public filteredOrganisations: ReplaySubject< + Organisation[] + > = new ReplaySubject(1); + private onDestroy = new Subject(); + + constructor( + private userService: UserService, + private translate: TranslateService, + private organizationService: OrganisationService, + private sharedService: SharedVariableService, + private errorMessageService: ErrorMessageService + ) {} + + ngOnInit(): void { + this.translate.get([ + 'USER_PAGE.AWAITING_CONFIRMATION', + 'USER_PAGE.APPLY_ORGANISATIONS', + 'USER_PAGE.APPLIED_ORGANISATIONS', + 'USER_PAGE.QUESTION_APPLY_ORGANISAIONS', + 'USER_PAGE.NO_APPLIED_ORGS', + 'USER_PAGE.NO_ORGS', + ]); + + this.userInfo = this.sharedService.getUserInfo(); + + this.userService + .getOneSimple(this.userInfo.user.id) + .subscribe((response: UserResponse) => { + //When used as user-page, check for users organizations so it's only possible to apply not already joined organizations + this.requestedUserOrganizations = response.requestedOrganizations; + if (this.userInfo.organizations.length > 0) { + this.compareRequestedAndAlreadyJoinedOrganizations( + this.requestedUserOrganizations, + this.userInfo.organizations + ); + } + + if (this.requestedUserOrganizations.length === 0) { + this.checkForNoUserOrganizations = true; + } + + this.awaitingConfirmation = response.awaitingConfirmation; + if (!this.awaitingConfirmation) { + this.translate + .get('GEN.BACK') + .subscribe((translation) => (this.backButtonTitle = translation)); + } + this.translate.get('USER_PAGE.USER_PAGE').subscribe((translation) => { + this.title = translation; + }); + + this.getOrganisations(); + }); + + this.organisationsFilterCtrl.valueChanges + .pipe(takeUntil(this.onDestroy)) + .subscribe(() => { + this.filterOrganisations(); + }); + } + private filterOrganisations() { + if (!this.requestOrganizationsList) { + return; + } + // get the search keyword + let search = this.organisationsFilterCtrl?.value?.trim(); + if (!search) { + this.filteredOrganisations.next(this.requestOrganizationsList.slice()); + return; + } else { + search = search.toLowerCase(); + } + const filtered = this.requestOrganizationsList.filter((org) => { + return org.name.toLocaleLowerCase().indexOf(search) > -1; + }); + this.filteredOrganisations.next(filtered); + } + + public compareRequestedAndAlreadyJoinedOrganizations( + requestedOrganizations: Organisation[], + alreadyJoinedOrganizations: Organisation[] + ) { + alreadyJoinedOrganizations.forEach((actOrg) => { + if (!requestedOrganizations.find((org) => org.id === actOrg.id)) { + this.requestedUserOrganizations.push(actOrg); + } + }); + } + + public compare( + o1: Organisation | undefined, + o2: Organisation | undefined + ): boolean { + return o1?.id === o2?.id; + } + + public getOrganisations() { + this.organisationSubscription = this.organizationService + .getMinimalNoPerm() + .subscribe((orgs) => { + this.requestOrganizationsList = orgs.data; + this.requestOrganizationsList = this.filterChosenOrganizations( + this.requestedUserOrganizations, + this.requestOrganizationsList + ); + this.filteredOrganisations.next(this.requestOrganizationsList.slice()); + }); + } + + public filterChosenOrganizations( + requestedUserOrganizations: Organisation[], + allOrganizations: Organisation[] + ): Organisation[] { + const filteredChosenOrganizations: Organisation[] = []; + + allOrganizations.forEach((allOrg) => { + if (!requestedUserOrganizations.find((org) => org.id === allOrg.id)) { + filteredChosenOrganizations.push(allOrg); + } + }); + if (filteredChosenOrganizations.length > 0) { + this.checkForRemainingOrganizations = true; + } else { + this.checkForRemainingOrganizations = false; + } + + return filteredChosenOrganizations; + } + + public mapToDto(body: UpdateUserOrgFromFrontend) { + this.updateUserOrgs.requestedOrganizationIds = []; + if (body.requestedOrganizations) { + body.requestedOrganizations.forEach((organization) => { + this.updateUserOrgs.requestedOrganizationIds.push(organization.id); + }); + } else { + this.formFailedSubmit = true; + } + } + + onSubmit(): void { + if (this.pressed === false) { + this.pressed = true; + + this.resetErrors(); + this.mapToDto(this.updateUserOrgsFromFrontend); + this.userService.updateUserOrgs(this.updateUserOrgs).subscribe( + () => { + window.location.reload(); + this.pressed = false; + }, + (error: HttpErrorResponse) => { + this.handleError(error); + this.formFailedSubmit = true; + this.pressed = false; + } + ); + } + } + private resetErrors() { + this.errorFields = []; + this.errorMessages = undefined; + this.formFailedSubmit = false; + } + handleError(error: HttpErrorResponse) { + const errors = this.errorMessageService.handleErrorMessageWithFields(error); + this.errorFields = errors.errorFields; + this.errorMessages = errors.errorMessages; + } +} diff --git a/src/app/admin/users/user.model.ts b/src/app/admin/users/user.model.ts index 2ac0b91cb..9d2d0bb08 100644 --- a/src/app/admin/users/user.model.ts +++ b/src/app/admin/users/user.model.ts @@ -1,33 +1,62 @@ +import { + Organisation, + OrganisationResponse, +} from '../organisation/organisation.model'; import { PermissionResponse } from '../permission/permission.model'; export class UserRequest { - id: number; - name: string; - email: string; - password: string; - active: boolean; - globalAdmin: boolean; + id: number; + name: string; + email: string; + password: string; + active: boolean; + globalAdmin: boolean; showWelcomeScreen: boolean; } export interface UserResponse { - id: number; - createdAt: string; - updatedAt: string; - createdBy: number; - updatedBy: number; - createdByName: string; - updatedByName: string; - name: string; - nameId: string; - email: string; - active: boolean; - lastLogin: Date; - permissions: PermissionResponse[]; - showWelcomeScreen: boolean; + id: number; + createdAt: string; + updatedAt: string; + createdBy: number; + updatedBy: number; + createdByName: string; + updatedByName: string; + name: string; + nameId: string; + email: string; + active: boolean; + lastLogin: Date; + permissions: PermissionResponse[]; + awaitingConfirmation: boolean; + showWelcomeScreen: boolean; + requestedOrganizations: OrganisationResponse[]; } export interface UserGetManyResponse { - data: UserResponse[]; - count: number; + data: UserResponse[]; + count: number; +} + +export class CreateNewKombitUserFromFrontend { + email: string; + requestedOrganizations: Organisation[]; +} + +export class UpdateUserOrgFromFrontend { + requestedOrganizations: Organisation[]; +} + +export class UpdateUserOrgsDto { + requestedOrganizationIds: number[]; +} + +export class RejectUserDto { + orgId: number; + userIdToReject: number; +} + +export class CreateNewKombitUserDto { + email: string; + requestedOrganizationIds: number[]; } diff --git a/src/app/admin/users/user.service.ts b/src/app/admin/users/user.service.ts index c60933e06..d7717536f 100644 --- a/src/app/admin/users/user.service.ts +++ b/src/app/admin/users/user.service.ts @@ -2,69 +2,127 @@ import { Injectable } from '@angular/core'; import { RestService } from '@shared/services/rest.service'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; +import { Organisation } from '../organisation/organisation.model'; import { UserMinimalService } from './user-minimal.service'; -import { UserResponse, UserRequest, UserGetManyResponse } from './user.model'; +import { + UserResponse, + UserRequest, + UserGetManyResponse, + CreateNewKombitUserDto, + UpdateUserOrgsDto, + RejectUserDto, +} from './user.model'; @Injectable({ - providedIn: 'root', + providedIn: 'root', }) export class UserService { - URL = 'user'; + URL = 'user'; + URL_NEW_KOMBIT = "kombitCreation" - constructor( - private restService: RestService, - private userMinimalService: UserMinimalService - ) {} + constructor( + private restService: RestService, + private userMinimalService: UserMinimalService + ) {} - post(body: UserRequest): Observable { - return this.restService.post(this.URL, body); - } + post(body: UserRequest): Observable { + return this.restService.post(this.URL, body); + } - put(body: UserRequest, id: number): Observable { - return this.restService.put(this.URL, body, id, { - observe: 'response', - }); - } + put(body: UserRequest, id: number): Observable { + return this.restService.put(this.URL, body, id, { + observe: 'response', + }); + } - getOne(id: number, extendedInfo = false): Observable { - return this.restService - .get(this.URL, { extendedInfo: extendedInfo }, id) - .pipe( - map((response: UserResponse) => { - response.createdByName = this.userMinimalService.getUserNameFrom( - response.createdBy - ); - response.updatedByName = this.userMinimalService.getUserNameFrom( - response.updatedBy - ); - return response; - }) - ); - } + getOne(id: number, extendedInfo = false): Observable { + return this.restService + .get(this.URL, { extendedInfo: extendedInfo }, id) + .pipe( + map((response: UserResponse) => { + response.createdByName = this.userMinimalService.getUserNameFrom( + response.createdBy + ); + response.updatedByName = this.userMinimalService.getUserNameFrom( + response.updatedBy + ); + return response; + }) + ); + } - getMultiple( - limit: number = 1000, - offset: number = 0, - orderByColumn?: string, - orderByDirection?: string, - permissionId?: number - ): Observable { - if (permissionId != null) { - return this.restService.get(`permission/${permissionId}/users`, { - limit: limit, - offset: offset, - }); - } else { - return this.restService.get(this.URL, { - limit: limit, - offset: offset, - orderOn: orderByColumn, - sort: orderByDirection, - }); - } + getMultiple( + limit: number = 1000, + offset: number = 0, + orderByColumn?: string, + orderByDirection?: string, + permissionId?: number + ): Observable { + if (permissionId != null) { + return this.restService.get(`permission/${permissionId}/users`, { + limit: limit, + offset: offset, + }); + } else { + return this.restService.get(this.URL, { + limit: limit, + offset: offset, + orderOn: orderByColumn, + sort: orderByDirection, + }); } + } - hideWelcome(id: number): Observable { - return this.restService.put(`${this.URL}/${id}/hide-welcome`, null, null); - } + hideWelcome(id: number): Observable { + return this.restService.put(`${this.URL}/${id}/hide-welcome`, null, null); + } + + getAwaitingUsers( + limit: number = 1000, + offset: number = 0, + organizationId: number, + orderByColumn?: string, + orderByDirection?: string + ): Observable { + return this.restService.get( + this.URL + '/awaitingUsers', + { + limit, + offset, + orderOn: orderByColumn, + sort: orderByDirection, + }, + organizationId + ); + } + + getOneSimple(id: number): Observable { + return this.restService.get(this.URL_NEW_KOMBIT, {}, id).pipe( + map((response: UserResponse) => { + return response; + }) + ); + } + updateNewKombit(body: CreateNewKombitUserDto): Observable { + return this.restService.put( + this.URL_NEW_KOMBIT + '/createNewKombitUser', + body, + undefined, + { + observe: 'response', + } + ); + } + + updateUserOrgs(body: UpdateUserOrgsDto): Observable { + return this.restService.put(this.URL_NEW_KOMBIT + '/updateUserOrgs', body, undefined, { + observe: 'response', + }); + } + + rejectUser(body: RejectUserDto): Observable { + return this.restService.put(this.URL + '/rejectUser', body, undefined, { + observe: 'response', + }); + } } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 97266391f..9dfc59f96 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -4,6 +4,8 @@ import { AuthComponent } from './auth/auth.component'; import { ErrorPageComponent } from './error-page/error-page.component'; import { SearchComponent } from './search/search.component'; import { AuthGuardService as AuthGuard } from './auth/auth-guard.service'; +import { NewUserComponent } from './admin/users/new-kombit-user-page/new-user.component'; +import { UserPageComponent } from './admin/users/user-page/user-page.component'; const routes: Routes = [ { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule), canActivate: [AuthGuard] }, @@ -17,6 +19,8 @@ const routes: Routes = [ { path: 'search', component: SearchComponent, canActivate: [AuthGuard] }, { path: 'not-found', component: ErrorPageComponent, data: { message: 'not-found', code: 404 } }, { path: 'not-authorized', component: ErrorPageComponent }, + { path: 'new-user', component: NewUserComponent, canActivate: [AuthGuard]}, + { path: 'user-page', component: UserPageComponent, canActivate: [AuthGuard]}, { path: '', redirectTo: '/applications', pathMatch: 'full' }, { path: '**', redirectTo: '/not-found', pathMatch: 'full' } ]; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index f691a7088..080559ece 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -7,7 +7,7 @@ import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { NavbarModule } from './navbar/navbar.module'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { ReactiveFormsModule } from '@angular/forms'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ProfilesModule } from './profiles/profiles.module'; import { AuthJwtInterceptor } from '@shared/helpers/auth-jwt.interceptor'; @@ -23,7 +23,12 @@ import { MatInputModule } from '@angular/material/input'; import { MatPaginatorIntl } from '@angular/material/paginator'; import { MatPaginatorIntlDa } from '@shared/helpers/mat-paginator-intl-da'; import { MatTooltipModule } from '@angular/material/tooltip'; +import { NewUserComponent } from './admin/users/new-kombit-user-page/new-user.component'; import { WelcomeDialogModule } from '@shared/components/welcome-dialog/welcome-dialog.module'; +import { NGMaterialModule } from '@shared/Modules/materiale.module'; +import { MatSelectSearchModule } from '@shared/components/mat-select-search/mat-select-search.module'; +import { UserPageComponent } from './admin/users/user-page/user-page.component'; +import { SharedModule } from '@shared/shared.module'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './assets/i18n/', '.json'); @@ -37,6 +42,8 @@ export function tokenGetter() { declarations: [ AppComponent, ErrorPageComponent, + NewUserComponent, + UserPageComponent ], imports: [ SharedVariableModule.forRoot(), @@ -56,10 +63,14 @@ export function tokenGetter() { }, }), NgbModule, + FormsModule, ReactiveFormsModule, BrowserAnimationsModule, + NGMaterialModule, GatewayModule, + MatSelectSearchModule, SearchModule, + SharedModule, HttpClientModule, MatInputModule, MatTooltipModule, diff --git a/src/app/applications/iot-devices/iot-device-detail/iot-device-detail.component.ts b/src/app/applications/iot-devices/iot-device-detail/iot-device-detail.component.ts index e70572fbc..dc62a7397 100644 --- a/src/app/applications/iot-devices/iot-device-detail/iot-device-detail.component.ts +++ b/src/app/applications/iot-devices/iot-device-detail/iot-device-detail.component.ts @@ -330,6 +330,7 @@ export class IoTDeviceDetailComponent implements OnInit, OnDestroy { true, false, '', + false, this.resetApiKeyConfirm, this.resetApiKeyCancel ) diff --git a/src/app/dashboard/dashboard.component.html b/src/app/dashboard/dashboard.component.html new file mode 100644 index 000000000..172e0559a --- /dev/null +++ b/src/app/dashboard/dashboard.component.html @@ -0,0 +1,73 @@ +
+ +
+
+
+ + +
+
+
+

+ +
+

{{'DASHBOARD.WELCOME' | translate}}

+

{{'DASHBOARD.WELCOME-SUB' | translate}}

+
+
+ +
+
+

{{'DASHBOARD.LIST.HEADLINE' | translate}} +

+
    +
  • {{'DASHBOARD.LIST.P1' | translate}}
  • +
  • {{'DASHBOARD.LIST.P2' | translate}}
  • +
  • {{'DASHBOARD.LIST.P3' | translate}}
  • +
  • {{'DASHBOARD.LIST.P4' | translate}}
  • +
  • {{'DASHBOARD.LIST.P5' | translate}}
  • +
  • {{'DASHBOARD.LIST.P6' | translate}}
  • +
  • {{'DASHBOARD.LIST.P7' | translate}}
  • +
+
+
+

{{'DASHBOARD.SUB-HEADER-1' | translate}}

+

{{'DASHBOARD.WELCOME-MESSAGE' | translate}}{{'DASHBOARD.LINK-1' | + translate}}{{'DASHBOARD.WELCOME-MESSAGE-3' | translate}} +

+

{{'DASHBOARD.SUB-HEADER-2' | translate}}

+

{{'DASHBOARD.WELCOME-MESSAGE-2' | translate}}{{'DASHBOARD.LINK-2' | + translate}} +

+
+
+
+

+ +
+
+
+
+
+
+
+ +
+

{{'DASHBOARD.WELCOME' | translate}}

+

{{'DASHBOARD.NO-ACCESS' | translate}}

+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/src/app/dashboard/dashboard.component.ts b/src/app/dashboard/dashboard.component.ts new file mode 100644 index 000000000..2c5f12f2f --- /dev/null +++ b/src/app/dashboard/dashboard.component.ts @@ -0,0 +1,95 @@ +import { Component, OnInit } from '@angular/core'; +import { Title } from '@angular/platform-browser'; +import { ActivatedRoute, Router } from '@angular/router'; +import { UserMinimalService } from '@app/admin/users/user-minimal.service'; +import { AuthService } from '@auth/auth.service'; +import { TranslateService } from '@ngx-translate/core'; +import { SharedVariableService } from '@shared/shared-variable/shared-variable.service'; + +@Component({ + selector: 'app-dashboard', + templateUrl: './dashboard.component.html', + styleUrls: ['./dashboard.component.scss'], +}) +export class DashboardComponent implements OnInit { + unauthorizedMessage: string; + kombitError: string; + noAccess: string; + isLoadingResults = true; + hasSomePermission: boolean; + isGlobalAdmin = false; + + constructor( + private route: ActivatedRoute, + private authService: AuthService, + private router: Router, + private sharedVariableService: SharedVariableService, + private translate: TranslateService, + private titleService: Title, + private userMinimalService: UserMinimalService + ) { + this.route.queryParams.subscribe(async (params) => { + this.translate.use('da'); + await this.translate + .get([ + 'DASHBOARD.NO-JOB-ACCESS', + 'TITLE.FRONTPAGE', + 'DASHBOARD.KOMBIT-LOGIN-ERROR', + 'DASHBOARD.USER-INACTIVE', + ]) + .toPromise() + .then((translations) => { + this.unauthorizedMessage = translations['DASHBOARD.NO-JOB-ACCESS']; + this.kombitError = translations['DASHBOARD.KOMBIT-LOGIN-ERROR']; + this.noAccess = translations['DASHBOARD.USER-INACTIVE']; + this.titleService.setTitle(translations['TITLE.FRONTPAGE']); + }); + // this is used when a user is returned from Kombit login + const jwt = params['jwt']; + if (jwt) { + this.authService.setSession(jwt); + const userInfo = await this.sharedVariableService.setUserInfo(); + if (!userInfo.user.email) { + this.router.navigate(['/new-user'], { + state: { code: 200 }, + }); + } else { + // Clear the URL from the parameter + this.router.navigate(['/dashboard']); + } + } else { + const error = params['error']; + if (error) { + if ( + error == 'MESSAGE.KOMBIT-LOGIN-FAILED' || + error == 'MESSAGE.API-KEY-AUTH-FAILED' + ) { + this.router.navigate(['/not-authorized'], { + state: { message: this.kombitError, code: 401 }, + }); + } + if (error == 'MESSAGE.USER-INACTIVE') { + this.router.navigate(['/not-authorized'], { + state: { message: this.noAccess, code: 401 }, + }); + } else { + this.router.navigate(['/not-authorized'], { + state: { message: this.unauthorizedMessage, code: 401 }, + }); + } + } + } + const userInfo = await this.sharedVariableService.setUserInfo(); + await this.sharedVariableService.setOrganizationInfo(); + this.userMinimalService.setUserMinimalList(); + this.hasSomePermission = this.sharedVariableService.getHasAnyPermission(); + this.isGlobalAdmin = this.sharedVariableService.isGlobalAdmin(); + this.isLoadingResults = false; + if (userInfo.user.awaitingConfirmation && userInfo.user.email) { + this.router.navigate(['/user-page']); + } + }); + } + + ngOnInit(): void {} +} diff --git a/src/app/navbar/navbar.component.html b/src/app/navbar/navbar.component.html index 968f95373..412185bb9 100644 --- a/src/app/navbar/navbar.component.html +++ b/src/app/navbar/navbar.component.html @@ -64,21 +64,6 @@ -
- diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts index caee49a66..ba57114f5 100644 --- a/src/app/navbar/navbar.component.ts +++ b/src/app/navbar/navbar.component.ts @@ -9,11 +9,12 @@ import { faUser, faQuestionCircle, } from '@fortawesome/free-solid-svg-icons'; -import { AuthService } from '@app/auth/auth.service'; +import { AuthService, CurrentUserInfoResponse } from '@app/auth/auth.service'; import { Router } from '@angular/router'; import { environment } from '@environments/environment'; import { SharedVariableService } from '@shared/shared-variable/shared-variable.service'; import { LoggedInService } from '@shared/services/loggedin.service'; +import { User } from '@shared/components/forms/form-body-application/form-body-application.component'; @Component({ selector: 'app-navbar', @@ -23,7 +24,9 @@ import { LoggedInService } from '@shared/services/loggedin.service'; export class NavbarComponent implements OnInit { isCollapsed = false; isLoginMode = true; + user: User; faBroadcastTower = faBroadcastTower; + userInfo: CurrentUserInfoResponse; faSlidersH = faSlidersH; faNetworkWired = faNetworkWired; faSignOutAlt = faSignOutAlt; @@ -42,7 +45,7 @@ export class NavbarComponent implements OnInit { translate.use('da'); } - ngOnInit(): void { } + ngOnInit(): void {} onLogout() { this.authService.logout(); @@ -66,6 +69,11 @@ export class NavbarComponent implements OnInit { return this.authService.isLoggedInWithKombit(); } + hasEmail(): string { + this.userInfo = this.sharedVariableService.getUserInfo(); + return this.userInfo.user.email; + } + public goToHelp() { window.open('https://os2iot.os2.eu/'); } diff --git a/src/app/shared/components/delete-dialog/delete-dialog.component.html b/src/app/shared/components/delete-dialog/delete-dialog.component.html index e16eb66e3..f710843bb 100644 --- a/src/app/shared/components/delete-dialog/delete-dialog.component.html +++ b/src/app/shared/components/delete-dialog/delete-dialog.component.html @@ -13,4 +13,7 @@

{{dialogModel.infoTitle | transl + diff --git a/src/app/shared/components/delete-dialog/delete-dialog.service.ts b/src/app/shared/components/delete-dialog/delete-dialog.service.ts index b931b9371..99d35156c 100644 --- a/src/app/shared/components/delete-dialog/delete-dialog.service.ts +++ b/src/app/shared/components/delete-dialog/delete-dialog.service.ts @@ -21,6 +21,7 @@ export class DeleteDialogService { showCancel = true, showOk = false, infoTitle = '', + showReject?: boolean, acceptText?: string, cancelText?: string ): Observable { @@ -31,7 +32,8 @@ export class DeleteDialogService { showOk, showAccept, showCancel, - message: message ? message : 'Er du sikker på at du vil slette?', + message: message ? message : this.translate.instant('DIALOG.DELETE.ARE-YOU-SURE'), + showReject, acceptText, cancelText }, diff --git a/src/app/shared/components/top-bar/top-bar.component.html b/src/app/shared/components/top-bar/top-bar.component.html index c63fceb5b..e8ed33feb 100644 --- a/src/app/shared/components/top-bar/top-bar.component.html +++ b/src/app/shared/components/top-bar/top-bar.component.html @@ -1,79 +1,161 @@
- -
-
-
- - - + +
+ -
-
-
- +
+ -
- - {{ 'SEARCH.PLACEHOLDER' | translate }} - - -
- - {{ 'TOPBAR.SEARCH.BUTTON' | translate }} -
-
-
-
+
+ + {{ 'SEARCH.PLACEHOLDER' | translate }} + + +
+ +
+
+
+
+
+
+
+
- -
+ diff --git a/src/app/shared/components/top-bar/top-bar.component.scss b/src/app/shared/components/top-bar/top-bar.component.scss index 13e5a41e2..67bdbfe8a 100644 --- a/src/app/shared/components/top-bar/top-bar.component.scss +++ b/src/app/shared/components/top-bar/top-bar.component.scss @@ -6,4 +6,37 @@ margin-right: 8px; } +button:focus { + outline: none; +} + +.userName { + pointer-events: none; +} +.userNameFont { + font-weight: bold; +} + +.mobileMedia { + @media (max-width: $screen-md-min) { + display: flex; + justify-content: flex-end; + flex-direction: column; + } +} + +::ng-deep .matMenuMargin { + margin-right: 5px; +} + +.marginLeft { + margin-left: 16px; +} + +.topbarWidth { + width: 100%; + display: flex; + align-items: center; +} + diff --git a/src/app/shared/components/top-bar/top-bar.component.ts b/src/app/shared/components/top-bar/top-bar.component.ts index cf691fea3..3c67167b5 100644 --- a/src/app/shared/components/top-bar/top-bar.component.ts +++ b/src/app/shared/components/top-bar/top-bar.component.ts @@ -3,7 +3,7 @@ import { TranslateService } from '@ngx-translate/core'; import { Location } from '@angular/common'; import { Sort } from '@shared/models/sort.model'; import { Router } from '@angular/router'; -import { faSearch, faChevronLeft } from '@fortawesome/free-solid-svg-icons'; +import { faSearch, faChevronLeft, faUser, faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; import { Application } from '@applications/application.model'; import { IotDevice } from '@applications/iot-devices/iot-device.model'; import { BackButton } from '@shared/models/back-button.model'; @@ -16,6 +16,10 @@ import { PermissionResponse } from '@app/admin/permission/permission.model'; import { UserResponse } from '@app/admin/users/user.model'; import { DropdownButton } from '@shared/models/dropdown-button.model'; import { MeService } from '@shared/services/me.service'; +import { SharedVariableService } from '@shared/shared-variable/shared-variable.service'; +import { AuthService } from '@auth/auth.service'; +import { LoggedInService } from '@shared/services/loggedin.service'; +import { environment } from '@environments/environment'; @Component({ selector: 'app-top-bar', @@ -30,6 +34,8 @@ export class TopBarComponent implements OnInit { @Input() public subTitle: string; faChevronLeft = faChevronLeft; + faQuestionCircle = faQuestionCircle + faUser = faUser; @Input() staticTitle: string; @Input() title: string; @@ -59,7 +65,10 @@ export class TopBarComponent implements OnInit { public translate: TranslateService, private location: Location, private router: Router, - private meService: MeService + private meService: MeService, + private sharedVariableService: SharedVariableService, + private authService: AuthService, + private loggedInService: LoggedInService ) { translate.use('da'); } @@ -112,4 +121,44 @@ export class TopBarComponent implements OnInit { onClickExtraDropdownOption(id: string) { this.extraDropdownOptions.emit(id); } + + public goToHelp() { + window.open('https://os2iot.os2.eu/'); + } + + getUsername(): string { + return this.sharedVariableService.getUsername(); + } + + onLogout() { + this.authService.logout(); + this.router.navigateByUrl('auth'); + this.loggedInService.emitChange(false); + } + + getKombitLogoutUrl() { + const jwt = this.authService.getJwt(); + if (this.authService.isLoggedInWithKombit()) { + return `${environment.baseUrl}auth/kombit/logout?secret_token=${jwt}`; + } else { + return ''; + } + } + + isLoggedInWithKombit() { + return this.authService.isLoggedInWithKombit(); + } + + hasEmail(): boolean { + if (this.sharedVariableService.getUserInfo().user.email) + { + return true + } + else return false; + } + + hasAnyPermission(): boolean { + return this.sharedVariableService.getHasAnyPermission(); + } } + diff --git a/src/app/shared/models/dialog.model.ts b/src/app/shared/models/dialog.model.ts index 14cf2b874..a693024b3 100644 --- a/src/app/shared/models/dialog.model.ts +++ b/src/app/shared/models/dialog.model.ts @@ -3,6 +3,7 @@ export class DialogModel { showOk = true; showAccept = true; showCancel = true; + showReject: boolean; message: string; acceptText: string; cancelText: string; diff --git a/src/app/shared/shared-variable/shared-variable.service.ts b/src/app/shared/shared-variable/shared-variable.service.ts index 9aad08090..6302ad869 100644 --- a/src/app/shared/shared-variable/shared-variable.service.ts +++ b/src/app/shared/shared-variable/shared-variable.service.ts @@ -54,7 +54,7 @@ export class SharedVariableService { setOrganizationInfo() { return this.organisationService - .getMinimal() + .getMinimalNoPerm() .pipe( tap((response: OrganisationGetMinimalResponse) => { localStorage.setItem( diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 273a64c6f..b9ccbcf42 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -915,6 +915,8 @@ } }, "USERS": { + "AWAITING-USERS": "Afventende brugere", + "EXISTING-USERS": "Tilknyttede brugere", "SAVE": "Gem", "NAME": "Navn", "EMAIL": "Email", @@ -924,9 +926,15 @@ "EDIT": "Redigér User", "CREATE": "Opret ny bruger", "LOGIN": "Sidst aktiv", + "DIALOG": { + "QUESTION-REJECT": "Er du sikker på, at du vil afvise brugeren?", + "HEAD-REJECT": "Afvis bruger" + }, "TABLE-ROW": { "EDIT": "Redigér", - "DELETE": "Slet" + "DELETE": "Slet", + "APPROVE": "Godkend", + "REJECT": "Afvis" }, "FORM": { "NAME": "Indtast det fulde navn på brugeren", @@ -957,6 +965,10 @@ "GLOBAL_ADMIN": { "TRUE": "Ja", "FALSE": "Nej" + }, + "ACCEPT-USER": { + "ACCEPT": "Godkend bruger", + "QUESTION-ACCEPT": "Vælg brugergruppe til " } }, "AUTH": { @@ -991,6 +1003,7 @@ "CLOSE": "Annuller", "OK": "Ok", "DIALOG-CONFIRM": "Ja, slet", + "DIALOG-REJECT": "Ja, afvis", "OPENDATADK": { "OK": "Send mail til OpenDataDk", "CLOSE": "Nej, tak", @@ -1030,6 +1043,19 @@ "LAST_PAGE": "Sidste side", "OF": "af" }, + "NEW_USER": { + "FIRST_LOGIN": "Siden dette er dit første login, bedes du udfylde din mail og de organisationer du ønsker at være medlem af." + }, + "USER_PAGE": { + "AWAITING_CONFIRMATION": "Din bruger er oprettet og afventer bekræftelse fra administratorer på de organisationer du har søgt tilknytning.", + "APPLY_ORGANISATIONS": "Søg tilknytning til andre organisationer", + "APPLIED_ORGANISATIONS": "Der er pt søgt eller allerede eksisterende tilknytning til følgende organisationer:", + "QUESTION_APPLY_ORGANISATIONS": "Ønsker du at søge tilknytning til andre organisationer?", + "USER_PAGE": "Organisationstilknytning", + "NO_APPLIED_ORGS": "Du har ikke søgt tilknytning til nogle organisationer..", + "NO_ORGS": "Der findes ikke yderligere organisationer.." + + }, "false": "Nej", "true": "Ja", "HTTP_PUSH": "HTTP Push", diff --git a/src/assets/scss/elements/_links.scss b/src/assets/scss/elements/_links.scss index 3a55cb8fe..c5cca092f 100644 --- a/src/assets/scss/elements/_links.scss +++ b/src/assets/scss/elements/_links.scss @@ -44,11 +44,6 @@ a { border-bottom: calculateRem(5) solid $color-link-focus-bg; } - &:visited, - &.link-visited { - color: $color-link-visited; - } - &:disabled, &.disabled { box-shadow: none !important;