Add gender field to users

Alters the user detail view and the list of speakers to
support the gender field.
Default selection of genders was set to "Female", "Male"
and "Diverse".
Adding genders to users is completely optional
This commit is contained in:
Sean Engelhardt 2019-01-22 12:57:47 +01:00
parent c67aef68d9
commit 07ca50441a
7 changed files with 81 additions and 30 deletions

View File

@ -2,6 +2,11 @@ import { Searchable } from '../base/searchable';
import { SearchRepresentation } from '../../../core/services/search.service';
import { BaseModel } from '../base/base-model';
/**
* Iterable pre selection of genders (sexes)
*/
export const genders = ['Female', 'Male', 'Diverse'];
/**
* Representation of a user in contrast to the operator.
* @ignore
@ -14,6 +19,7 @@ export class User extends BaseModel<User> implements Searchable {
public title: string;
public first_name: string;
public last_name: string;
public gender: string;
public structure_level: string;
public number: string;
public about_me: string;

View File

@ -4,9 +4,7 @@
<h2><span translate>List of speakers</span></h2>
</div>
<div class="menu-slot" *osPerms="'agenda.can_manage_list_of_speakers'">
<button type="button" mat-icon-button [matMenuTriggerFor]="speakerMenu">
<mat-icon>more_vert</mat-icon>
</button>
<button type="button" mat-icon-button [matMenuTriggerFor]="speakerMenu"><mat-icon>more_vert</mat-icon></button>
</div>
</os-head-bar>
@ -68,6 +66,7 @@
<span *ngIf="hasSpokenCount(item)" class="red-warning-text speaker-warning">
{{ hasSpokenCount(item) + 1 }}. <span translate>contribution</span>
</span>
<span *ngIf="item.gender">({{ item.gender | translate }})</span>
</span>
<mat-button-toggle-group *osPerms="'agenda.can_manage_list_of_speakers'">
<mat-button-toggle matTooltip="{{ 'Begin speech' | translate }}" (click)="onStartButton(item)">
@ -117,7 +116,6 @@
</mat-card>
<mat-menu #speakerMenu="matMenu">
<button mat-menu-item *ngIf="closedList" (click)="openSpeakerList()">
<mat-icon>mic</mat-icon>
<span translate>Open list of speakers</span>

View File

@ -46,6 +46,10 @@ export class ViewSpeaker extends BaseViewModel implements Selectable {
return this.user.full_name || this.user.username;
}
public get gender(): string {
return this.user.gender || '';
}
public constructor(speaker?: Speaker, user?: User) {
super();
this._speaker = speaker;

View File

@ -9,8 +9,7 @@
<!-- Title -->
<div class="title-slot">
<h2>
<span *ngIf="newUser" translate>New participant</span>
<span *ngIf="!newUser">{{ user.full_name }}</span>
<span *ngIf="newUser" translate>New participant</span> <span *ngIf="!newUser">{{ user.full_name }}</span>
</h2>
</div>
@ -39,61 +38,74 @@
[formGroup]="personalInfoForm"
(ngSubmit)="saveUser()"
*ngIf="user"
(keydown)="onKeyDown($event)">
(keydown)="onKeyDown($event)"
>
<!-- <h3 translate>Personal Data</h3> -->
<div *ngIf="isAllowed('seeName')">
<!-- Title -->
<mat-form-field
class="form16 distance force-min-with"
*ngIf="user.title || (editUser && isAllowed('manage'))">
*ngIf="user.title || (editUser && isAllowed('manage'))"
>
<input
type="text"
matInput
osAutofocus
placeholder="{{ 'Title' | translate }}"
formControlName="title"
[value]="user.title"/>
[value]="user.title"
/>
</mat-form-field>
<!-- First name -->
<mat-form-field
class="form37 distance force-min-with"
*ngIf="user.first_name || (editUser && isAllowed('manage'))">
*ngIf="user.first_name || (editUser && isAllowed('manage'))"
>
<input
type="text"
matInput
placeholder="{{ 'Given name' | translate }}"
formControlName="first_name"
[value]="user.first_name"/>
[value]="user.first_name"
/>
</mat-form-field>
<!-- Last name -->
<mat-form-field
class="form37 force-min-with"
*ngIf="user.last_name || (editUser && isAllowed('manage'))">
<mat-form-field class="form37 force-min-with" *ngIf="user.last_name || (editUser && isAllowed('manage'))">
<input
type="text"
matInput
placeholder="{{ 'Surname' | translate }}"
formControlName="last_name"
[value]="user.last_name"/>
[value]="user.last_name"
/>
</mat-form-field>
</div>
<div *ngIf="isAllowed('seePersonal')">
<!-- E-Mail -->
<mat-form-field *ngIf="user.email || editUser">
<mat-form-field class="form70 distance" *ngIf="user.email || editUser">
<input
type="email"
matInput
placeholder="{{ 'Email' | translate }}"
name="email"
formControlName="email"
[value]="user.email"/>
[value]="user.email"
/>
<mat-error *ngIf="personalInfoForm.get('email').hasError('email')" translate>
Please enter a valid email address
</mat-error>
</mat-form-field>
<!-- Gender -->
<mat-form-field class="form25 force-min-with" *ngIf="user.gender || editUser">
<mat-select placeholder="{{ 'Gender' | translate }}" formControlName="gender">
<mat-option>-</mat-option>
<mat-option *ngFor="let gender of genderList" [value]="gender">{{ gender | translate }}</mat-option>
</mat-select>
</mat-form-field>
</div>
<div>
@ -104,19 +116,22 @@
matInput
placeholder="{{ 'Structure level' | translate }}"
formControlName="structure_level"
[value]="user.structure_level"/>
[value]="user.structure_level"
/>
</mat-form-field>
<!-- Participant Number -->
<mat-form-field
class="form25 force-min-with"
*ngIf="user.participant_number || (editUser && isAllowed('manage'))">
*ngIf="user.participant_number || (editUser && isAllowed('manage'))"
>
<input
type="text"
matInput
placeholder="{{ 'Participant number' | translate }}"
formControlName="number"
[value]="user.participant_number"/>
[value]="user.participant_number"
/>
</mat-form-field>
</div>
@ -136,7 +151,8 @@
matInput
placeholder="{{ 'Initial password' | translate }}"
formControlName="default_password"
[value]="user.default_password"/>
[value]="user.default_password"
/>
<mat-hint align="end">Generate</mat-hint>
<button
type="button"
@ -144,7 +160,8 @@
matSuffix
mat-icon-button
[disabled]="!newUser"
(click)="generatePassword()">
(click)="generatePassword()"
>
<mat-icon>sync_problem</mat-icon>
</button>
</mat-form-field>
@ -166,7 +183,8 @@
matInput
placeholder="{{ 'Username' | translate }}"
formControlName="username"
[value]="user.username"/>
[value]="user.username"
/>
</mat-form-field>
</div>
@ -177,7 +195,8 @@
matInput
placeholder="{{ 'Comment' | translate }}"
formControlName="comment"
[value]="user.comment"/>
[value]="user.comment"
/>
<mat-hint translate>Only for internal notes.</mat-hint>
</mat-form-field>
</div>
@ -187,7 +206,8 @@
<mat-checkbox
formControlName="is_present"
matTooltip="{{ 'Designates whether this user is in the room.' | translate }}"
[value]="user.is_present">
[value]="user.is_present"
>
<span translate>Is present</span>
</mat-checkbox>
@ -199,7 +219,8 @@
matTooltip="{{
'Designates whether this user should be treated as active. Unselect this instead of deleting the account.'
| translate
}}">
}}"
>
<span translate>Is active</span>
</mat-checkbox>
@ -207,7 +228,8 @@
<mat-checkbox
formControlName="is_committee"
[value]="user.is_committee"
matTooltip="{{ 'Designates whether this user should be treated as a committee.' | translate }}">
matTooltip="{{ 'Designates whether this user should be treated as a committee.' | translate }}"
>
<span translate>Is a committee</span>
</mat-checkbox>
</div>

View File

@ -1,16 +1,18 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { MatSnackBar } from '@angular/material';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { genders } from 'app/shared/models/users/user';
import { ViewUser } from '../../models/view-user';
import { UserRepositoryService } from '../../services/user-repository.service';
import { Group } from '../../../../shared/models/users/group';
import { DataStoreService } from '../../../../core/services/data-store.service';
import { OperatorService } from '../../../../core/services/operator.service';
import { BaseViewComponent } from '../../../base/base-view';
import { TranslateService } from '@ngx-translate/core';
import { MatSnackBar } from '@angular/material';
import { Title } from '@angular/platform-browser';
import { PromptService } from '../../../../core/services/prompt.service';
/**
@ -62,6 +64,11 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
*/
public groups: Group[];
/**
* Hold the list of genders (sexes) publicly to dynamically iterate in the view
*/
public genderList = genders;
/**
* Constructor for user
*
@ -114,6 +121,7 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
/**
* Checks, if the given user id matches with the operator ones.
*
* @param userId The id to check, if it's the operator
* @returns If the user is the operator
*/
@ -178,6 +186,7 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
title: [''],
first_name: [''],
last_name: [''],
gender: [''],
structure_level: [''],
number: [''],
about_me: [''],
@ -226,6 +235,7 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
this.personalInfoForm.get('first_name'),
this.personalInfoForm.get('last_name'),
this.personalInfoForm.get('email'),
this.personalInfoForm.get('gender'),
this.personalInfoForm.get('structure_level'),
this.personalInfoForm.get('number'),
this.personalInfoForm.get('groups_id'),
@ -241,6 +251,7 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
allowedFormFields.push(
this.personalInfoForm.get('username'),
this.personalInfoForm.get('email'),
this.personalInfoForm.get('gender'),
this.personalInfoForm.get('about_me')
);
}

View File

@ -47,6 +47,10 @@ export class ViewUser extends BaseProjectableModel {
return this.user ? this.user.email : null;
}
public get gender(): string {
return this.user ? this.user.gender : null;
}
public get structure_level(): string {
return this.user ? this.user.structure_level : null;
}

View File

@ -64,6 +64,12 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
updateUser.username = viewUser.username;
}
// if the update user does not have a gender-field, send gender as empty string.
// This allow to delete a previously selected gender
if (!updateUser.gender) {
updateUser.gender = '';
}
return await this.dataSend.updateModel(updateUser);
}