From 8e086df44009640d92ee52b514d39a8d82ef3c77 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 24 Jan 2019 16:48:03 +0100 Subject: [PATCH] view for changing the user presence by oarticipant number --- .../user-list/user-list.component.html | 257 +++++++++--------- .../user-list/user-list.component.ts | 19 +- .../presence-detail.component.html | 24 ++ .../presence-detail.component.spec.ts | 26 ++ .../presence-detail.component.ts | 133 +++++++++ .../users/services/user-repository.service.ts | 12 + .../app/site/users/users-routing.module.ts | 6 + client/src/app/site/users/users.module.ts | 4 +- 8 files changed, 355 insertions(+), 126 deletions(-) create mode 100644 client/src/app/site/users/presence-detail/presence-detail.component.html create mode 100644 client/src/app/site/users/presence-detail/presence-detail.component.spec.ts create mode 100644 client/src/app/site/users/presence-detail/presence-detail.component.ts diff --git a/client/src/app/site/users/components/user-list/user-list.component.html b/client/src/app/site/users/components/user-list/user-list.component.html index 8129b9305..d8bd8bb9f 100644 --- a/client/src/app/site/users/components/user-list/user-list.component.html +++ b/client/src/app/site/users/components/user-list/user-list.component.html @@ -15,142 +15,151 @@ - - - - - - - - - {{ isSelected(user) ? 'check_circle' : '' }} - - - - - - Projector - - - - - - - - Name - {{ user.full_name }} - - - - - Group - -
- - people - {{ user.groups }} - -
- - flag - {{ user.structure_level }} - -
-
-
- - - - Presence - -
- - Present - -
-
-
- - - - -
+ - + + + + + + {{ isSelected(user) ? 'check_circle' : '' }} + + - -
- + + + Projector + + + + - + + + Name + {{ user.full_name }} + - + + + Group + +
+ + people + {{ user.groups }} + +
+ + flag + {{ user.structure_level }} + +
+
+
- + + + Presence + +
+ + Present + +
+
+
-
-
- - -
- - + + - +
+ + - - - - - - - - -
- -
+
+ + +
+ + + + + + + + + + + + + + +
+
+
diff --git a/client/src/app/site/users/components/user-list/user-list.component.ts b/client/src/app/site/users/components/user-list/user-list.component.ts index 52fc7140d..7d6e39b6f 100644 --- a/client/src/app/site/users/components/user-list/user-list.component.ts +++ b/client/src/app/site/users/components/user-list/user-list.component.ts @@ -6,6 +6,7 @@ import { TranslateService } from '@ngx-translate/core'; import { CsvExportService } from '../../../../core/services/csv-export.service'; import { ChoiceService } from '../../../../core/services/choice.service'; +import { ConfigService } from 'app/core/services/config.service'; import { ListViewBaseComponent } from '../../../base/list-view-base'; import { GroupRepositoryService } from '../../services/group-repository.service'; import { PromptService } from '../../../../core/services/prompt.service'; @@ -24,6 +25,19 @@ import { UserSortListService } from '../../services/user-sort-list.service'; styleUrls: ['./user-list.component.scss'] }) export class UserListComponent extends ListViewBaseComponent implements OnInit { + /** + * Stores the observed configuration if the presence view is available to administrators + */ + private _presenceViewConfigured = false; + + /** + * TODO: Does not check for user manage rights itself + * @returns true if the presence view is available to administrators + */ + public get presenceViewConfigured(): boolean { + return this._presenceViewConfigured; + } + /** * /** * The usual constructor for components @@ -39,6 +53,7 @@ export class UserListComponent extends ListViewBaseComponent implement * @param groupRepo * @param filterService * @param sortService + * @param config ConfigService */ public constructor( titleService: Title, @@ -52,12 +67,14 @@ export class UserListComponent extends ListViewBaseComponent implement protected csvExport: CsvExportService, private promptService: PromptService, public filterService: UserFilterListService, - public sortService: UserSortListService + public sortService: UserSortListService, + config: ConfigService ) { super(titleService, translate, matSnackBar); // enable multiSelect for this listView this.canMultiSelect = true; + config.get('users_enable_presence_view').subscribe(state => (this._presenceViewConfigured = state)); } /** diff --git a/client/src/app/site/users/presence-detail/presence-detail.component.html b/client/src/app/site/users/presence-detail/presence-detail.component.html new file mode 100644 index 000000000..47129b380 --- /dev/null +++ b/client/src/app/site/users/presence-detail/presence-detail.component.html @@ -0,0 +1,24 @@ + + +

Presence

+
+ + + Check in or check out participants based on their participant numbers + +
+ + + + + + {{ lastChangedUser.full_name }}  is now +  {{ lastChangedUser.is_present ? 'present' : ('not present' | translate) }} + + {{ errorMsg | translate }} +
diff --git a/client/src/app/site/users/presence-detail/presence-detail.component.spec.ts b/client/src/app/site/users/presence-detail/presence-detail.component.spec.ts new file mode 100644 index 000000000..ac918896f --- /dev/null +++ b/client/src/app/site/users/presence-detail/presence-detail.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { E2EImportsModule } from 'e2e-imports.module'; +import { PresenceDetailComponent } from './presence-detail.component'; + +describe('PresenceDetailComponent', () => { + let component: PresenceDetailComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [E2EImportsModule], + declarations: [PresenceDetailComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PresenceDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/site/users/presence-detail/presence-detail.component.ts b/client/src/app/site/users/presence-detail/presence-detail.component.ts new file mode 100644 index 000000000..ecfb4cb0f --- /dev/null +++ b/client/src/app/site/users/presence-detail/presence-detail.component.ts @@ -0,0 +1,133 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { Subscription } from 'rxjs'; + +import { ConfigService } from 'app/core/services/config.service'; +import { OperatorService } from 'app/core/services/operator.service'; +import { UserRepositoryService } from '../services/user-repository.service'; +import { ViewUser } from '../models/view-user'; + +/** + * This component offers an input field for user numbers, and sets/unsets the + * 'is_present' status for the user associated with that number, giving a feedback + * by displaying the name and the new presence status of that user. + * + * The component is typically directly accessed via the router link + */ +@Component({ + selector: 'os-presence-detail', + templateUrl: './presence-detail.component.html' +}) +export class PresenceDetailComponent implements OnInit { + /** + * The form group for the input field + */ + public userForm: FormGroup; + + /** + * Contains the last user entered. Is null if there is no user or the last + * participant number has no unique valid user + */ + public lastChangedUser: ViewUser; + + /** + * Subscription to update {@link lastChangedUser} + */ + private _userSubscription: Subscription = null; + public errorMsg: string; + + /** + * Config variable if this view is enabled in the config + * TODO: Should be a temporary check, until the permission on users-routing.module is fixed + */ + private _enabledInConfig: boolean; + + /** + * permission check if user is allowed to access this view. + * TODO: Should be a temporary check, until the permission on users-routing.module is fixed + * + * @returns true if the user is allowed to use this view + */ + public get permission(): boolean { + return this.operator.hasPerms('users.can_manage') && this._enabledInConfig; + } + + /** + * Constructor. Subscribes to the configuration if this view should be enabled at all + * + * @param userRepo: UserRepositoryService for querying the users + * @param formBuilder FormBuilder input form + * @param operator OperatorService fetch the current user for a permission check + * @param config ConfigService checking if the feature is enabled + */ + public constructor( + private userRepo: UserRepositoryService, + private formBuilder: FormBuilder, + private operator: OperatorService, + config: ConfigService + ) { + config.get('users_enable_presence_view').subscribe(conf => (this._enabledInConfig = conf)); + } + + /** + * initializes the form control + */ + public ngOnInit(): void { + this.userForm = this.formBuilder.group({ + number: '' + }); + } + + /** + * Triggers the user finding and updating process. The user number will be taken from the {@link userForm}. + * Feedback will be relayed to the {@link errorMsg} and/or {@link lastChangedUser} variables + */ + public async changePresence(): Promise { + const number = this.userForm.get('number').value; + const users = this.userRepo.getUsersByNumber(number); + this.userForm.reset(); + if (users.length === 1) { + await this.userRepo.update({ is_present: !users[0].is_present }, users[0]); + this.subscribeUser(users[0].id); + } else if (!users.length) { + this.clearSubscription(); + this.errorMsg = 'Participant cannot be found'; + } else if (users.length > 1) { + this.clearSubscription(); + this.errorMsg = 'Participant number is not unique'; + } + } + + /** + * Subscribes this component to a user given by an id. The + * {@link lastChangedUser} will be updated accordingly. + * + * @param id the id of the user to be shown as lastChangedUser + */ + private subscribeUser(id: number): void { + this.clearSubscription(); + this.errorMsg = null; + this._userSubscription = this.userRepo + .getViewModelObservable(id) + .subscribe(user => (this.lastChangedUser = user)); + } + + /** + * Clears the currently displayed user and subscription, if any is present + */ + private clearSubscription(): void { + if (this._userSubscription) { + this._userSubscription.unsubscribe(); + } + this.lastChangedUser = null; + } + + /** + * triggers the submission on enter key + */ + public onKeyUp(event: KeyboardEvent): void { + if (event.key === 'Enter') { + this.changePresence(); + } + } +} diff --git a/client/src/app/site/users/services/user-repository.service.ts b/client/src/app/site/users/services/user-repository.service.ts index a19175259..e4103153b 100644 --- a/client/src/app/site/users/services/user-repository.service.ts +++ b/client/src/app/site/users/services/user-repository.service.ts @@ -213,7 +213,9 @@ export class UserRepositoryService extends BaseRepository { /** * Searches and returns Users by full name + * * @param name + * @returns all users matching that name */ public getUsersByName(name: string): ViewUser[] { const results: ViewUser[] = []; @@ -232,6 +234,16 @@ export class UserRepositoryService extends BaseRepository { return results; } + /** + * Searches and returns Users by participant number + * + * @param number: A participant number + * @returns all users matching that number + */ + public getUsersByNumber(number: string): ViewUser[] { + return this.getViewModelList().filter(user => user.participant_number === number); + } + /** * Creates a new User from a string * diff --git a/client/src/app/site/users/users-routing.module.ts b/client/src/app/site/users/users-routing.module.ts index 6f11914e7..c8fe3eda5 100644 --- a/client/src/app/site/users/users-routing.module.ts +++ b/client/src/app/site/users/users-routing.module.ts @@ -3,6 +3,7 @@ import { Routes, RouterModule } from '@angular/router'; import { GroupListComponent } from './components/group-list/group-list.component'; import { PasswordComponent } from './components/password/password.component'; +import { PresenceDetailComponent } from './presence-detail/presence-detail.component'; import { UserDetailComponent } from './components/user-detail/user-detail.component'; import { UserImportListComponent } from './components/user-import/user-import-list.component'; import { UserListComponent } from './components/user-list/user-list.component'; @@ -28,6 +29,11 @@ const routes: Routes = [ path: 'import', component: UserImportListComponent }, + { + path: 'presence', + component: PresenceDetailComponent + // FIXME: CRITICAL: restricted to basePerm: 'users.can_manage' and config 'users_enable_presence_view' + }, { path: 'groups', component: GroupListComponent diff --git a/client/src/app/site/users/users.module.ts b/client/src/app/site/users/users.module.ts index 16d0a7a40..9ac5accbf 100644 --- a/client/src/app/site/users/users.module.ts +++ b/client/src/app/site/users/users.module.ts @@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common'; import { GroupListComponent } from './components/group-list/group-list.component'; import { PasswordComponent } from './components/password/password.component'; +import { PresenceDetailComponent } from './presence-detail/presence-detail.component'; import { SharedModule } from '../../shared/shared.module'; import { UserDetailComponent } from './components/user-detail/user-detail.component'; import { UserImportListComponent } from './components/user-import/user-import-list.component'; @@ -16,7 +17,8 @@ import { UsersRoutingModule } from './users-routing.module'; UserDetailComponent, GroupListComponent, PasswordComponent, - UserImportListComponent + UserImportListComponent, + PresenceDetailComponent ] }) export class UsersModule {}