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 { SearchRepresentation } from '../../../core/services/search.service';
import { BaseModel } from '../base/base-model'; 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. * Representation of a user in contrast to the operator.
* @ignore * @ignore
@ -14,6 +19,7 @@ export class User extends BaseModel<User> implements Searchable {
public title: string; public title: string;
public first_name: string; public first_name: string;
public last_name: string; public last_name: string;
public gender: string;
public structure_level: string; public structure_level: string;
public number: string; public number: string;
public about_me: string; public about_me: string;

View File

@ -4,9 +4,7 @@
<h2><span translate>List of speakers</span></h2> <h2><span translate>List of speakers</span></h2>
</div> </div>
<div class="menu-slot" *osPerms="'agenda.can_manage_list_of_speakers'"> <div class="menu-slot" *osPerms="'agenda.can_manage_list_of_speakers'">
<button type="button" mat-icon-button [matMenuTriggerFor]="speakerMenu"> <button type="button" mat-icon-button [matMenuTriggerFor]="speakerMenu"><mat-icon>more_vert</mat-icon></button>
<mat-icon>more_vert</mat-icon>
</button>
</div> </div>
</os-head-bar> </os-head-bar>
@ -68,6 +66,7 @@
<span *ngIf="hasSpokenCount(item)" class="red-warning-text speaker-warning"> <span *ngIf="hasSpokenCount(item)" class="red-warning-text speaker-warning">
{{ hasSpokenCount(item) + 1 }}. <span translate>contribution</span> {{ hasSpokenCount(item) + 1 }}. <span translate>contribution</span>
</span> </span>
<span *ngIf="item.gender">({{ item.gender | translate }})</span>
</span> </span>
<mat-button-toggle-group *osPerms="'agenda.can_manage_list_of_speakers'"> <mat-button-toggle-group *osPerms="'agenda.can_manage_list_of_speakers'">
<mat-button-toggle matTooltip="{{ 'Begin speech' | translate }}" (click)="onStartButton(item)"> <mat-button-toggle matTooltip="{{ 'Begin speech' | translate }}" (click)="onStartButton(item)">
@ -117,7 +116,6 @@
</mat-card> </mat-card>
<mat-menu #speakerMenu="matMenu"> <mat-menu #speakerMenu="matMenu">
<button mat-menu-item *ngIf="closedList" (click)="openSpeakerList()"> <button mat-menu-item *ngIf="closedList" (click)="openSpeakerList()">
<mat-icon>mic</mat-icon> <mat-icon>mic</mat-icon>
<span translate>Open list of speakers</span> <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; return this.user.full_name || this.user.username;
} }
public get gender(): string {
return this.user.gender || '';
}
public constructor(speaker?: Speaker, user?: User) { public constructor(speaker?: Speaker, user?: User) {
super(); super();
this._speaker = speaker; this._speaker = speaker;

View File

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

View File

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

View File

@ -47,6 +47,10 @@ export class ViewUser extends BaseProjectableModel {
return this.user ? this.user.email : null; return this.user ? this.user.email : null;
} }
public get gender(): string {
return this.user ? this.user.gender : null;
}
public get structure_level(): string { public get structure_level(): string {
return this.user ? this.user.structure_level : null; 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; 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); return await this.dataSend.updateModel(updateUser);
} }