Merge pull request #3891 from tsiegleauq/cu-user
User create update and details view Some open tasks left (features are quire heavy). See recent issues
This commit is contained in:
commit
b6ad0d759c
@ -15,7 +15,12 @@ import { ConstantsService } from './core/services/constants.service';
|
|||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
/**
|
/**
|
||||||
* Initialises the translation unit.
|
* Master-component of all apps.
|
||||||
|
*
|
||||||
|
* Inits the translation service, the operator, the login data and the constants.
|
||||||
|
*
|
||||||
|
* Handles the altering of Array.toString()
|
||||||
|
*
|
||||||
* @param autoupdateService
|
* @param autoupdateService
|
||||||
* @param notifyService
|
* @param notifyService
|
||||||
* @param translate
|
* @param translate
|
||||||
@ -35,5 +40,36 @@ export class AppComponent {
|
|||||||
const browserLang = translate.getBrowserLang();
|
const browserLang = translate.getBrowserLang();
|
||||||
// try to use the browser language if it is available. If not, uses english.
|
// try to use the browser language if it is available. If not, uses english.
|
||||||
translate.use(translate.getLangs().includes(browserLang) ? browserLang : 'en');
|
translate.use(translate.getLangs().includes(browserLang) ? browserLang : 'en');
|
||||||
|
// change default JS functions
|
||||||
|
this.overloadArrayToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to alter the normal Array.toString - function
|
||||||
|
*
|
||||||
|
* Will add a whitespace after a comma and shorten the output to
|
||||||
|
* three strings.
|
||||||
|
*
|
||||||
|
* TODO: There might be a better place for overloading functions than app.component
|
||||||
|
* TODO: Overloading can be extended to more functions.
|
||||||
|
*/
|
||||||
|
private overloadArrayToString(): void {
|
||||||
|
Array.prototype.toString = function(): string {
|
||||||
|
let string = '';
|
||||||
|
const iterations = Math.min(this.length, 3);
|
||||||
|
|
||||||
|
for (let i = 0; i <= iterations; i++) {
|
||||||
|
if (i < iterations) {
|
||||||
|
string += this[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < iterations - 1) {
|
||||||
|
string += ', ';
|
||||||
|
} else if (i === iterations && this.length > iterations) {
|
||||||
|
string += ', ...';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,31 +21,46 @@ export class DataSendService {
|
|||||||
public constructor(private http: HttpClient) {}
|
public constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save motion in the server
|
* Sends a post request with the model to the server.
|
||||||
*
|
* Usually for new Models
|
||||||
* @return Observable from
|
|
||||||
*/
|
*/
|
||||||
public saveModel(model: BaseModel): Observable<BaseModel> {
|
public createModel(model: BaseModel): Observable<BaseModel> {
|
||||||
if (!model.id) {
|
|
||||||
return this.http.post<BaseModel>('rest/' + model.collectionString + '/', model).pipe(
|
return this.http.post<BaseModel>('rest/' + model.collectionString + '/', model).pipe(
|
||||||
tap(
|
tap(
|
||||||
response => {
|
response => {
|
||||||
// TODO: Message, Notify, Etc
|
// TODO: Message, Notify, Etc
|
||||||
console.log('New Model added. Response : ', response);
|
console.log('New Model added. Response :\n', response);
|
||||||
},
|
},
|
||||||
error => console.log('error. ', error)
|
error => console.error('createModel has returned an Error:\n', error)
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return this.http.patch<BaseModel>('rest/' + model.collectionString + '/' + model.id, model).pipe(
|
|
||||||
tap(
|
|
||||||
response => {
|
|
||||||
console.log('Update model. Response : ', response);
|
|
||||||
},
|
|
||||||
error => console.log('error. ', error)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to change a model on the server.
|
||||||
|
*
|
||||||
|
* @param model the base model that is meant to be changed
|
||||||
|
* @param method the required http method. might be put or patch
|
||||||
|
*/
|
||||||
|
public updateModel(model: BaseModel, method: 'put' | 'patch'): Observable<BaseModel> {
|
||||||
|
const restPath = `rest/${model.collectionString}/${model.id}`;
|
||||||
|
let httpMethod;
|
||||||
|
|
||||||
|
if (method === 'patch') {
|
||||||
|
httpMethod = this.http.patch<BaseModel>(restPath, model);
|
||||||
|
} else if (method === 'put') {
|
||||||
|
httpMethod = this.http.put<BaseModel>(restPath, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpMethod.pipe(
|
||||||
|
tap(
|
||||||
|
response => {
|
||||||
|
// TODO: Message, Notify, Etc
|
||||||
|
console.log('Update model. Response :\n', response);
|
||||||
|
},
|
||||||
|
error => console.error('updateModel has returned an Error:\n', error)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -121,11 +121,11 @@ export class OperatorService extends OpenSlidesComponent {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks, if the operator has at least one of the given permissions.
|
* Checks, if the operator has at least one of the given permissions.
|
||||||
* @param permissions The permissions to check, if at least one matches.
|
* @param checkPerms The permissions to check, if at least one matches.
|
||||||
*/
|
*/
|
||||||
public hasPerms(...permissions: Permission[]): boolean {
|
public hasPerms(...checkPerms: Permission[]): boolean {
|
||||||
return permissions.some(permisison => {
|
return checkPerms.some(permission => {
|
||||||
return this.permissions.includes(permisison);
|
return this.permissions.includes(permission);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,20 @@ export class PermsDirective extends OpenSlidesComponent {
|
|||||||
*/
|
*/
|
||||||
private lastPermissionCheckResult = false;
|
private lastPermissionCheckResult = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternative to the permissions. Used in special case where a combination
|
||||||
|
* with *ngIf would be required.
|
||||||
|
*
|
||||||
|
* # Example:
|
||||||
|
*
|
||||||
|
* The div will render if the permission `user.can_manage` is set
|
||||||
|
* or if `this.ownPage` is `true`
|
||||||
|
* ```html
|
||||||
|
* <div *osPerms="'users.can_manage';or:ownPage"> something </div>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
private alternative: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs the directive once. Observes the operator for it's groups so the
|
* Constructs the directive once. Observes the operator for it's groups so the
|
||||||
* directive can perform changes dynamically
|
* directive can perform changes dynamically
|
||||||
@ -61,6 +75,16 @@ export class PermsDirective extends OpenSlidesComponent {
|
|||||||
this.updateView();
|
this.updateView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comes from the view.
|
||||||
|
* `;or:` turns into osPermsOr during runtime.
|
||||||
|
*/
|
||||||
|
@Input('osPermsOr')
|
||||||
|
public set osPermsAlt(value: boolean) {
|
||||||
|
this.alternative = value;
|
||||||
|
this.updateView();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows or hides certain content in the view.
|
* Shows or hides certain content in the view.
|
||||||
*/
|
*/
|
||||||
@ -68,7 +92,7 @@ export class PermsDirective extends OpenSlidesComponent {
|
|||||||
const hasPerms = this.checkPermissions();
|
const hasPerms = this.checkPermissions();
|
||||||
const permsChanged = hasPerms !== this.lastPermissionCheckResult;
|
const permsChanged = hasPerms !== this.lastPermissionCheckResult;
|
||||||
|
|
||||||
if (hasPerms && permsChanged) {
|
if ((hasPerms && permsChanged) || this.alternative) {
|
||||||
// clean up and add the template
|
// clean up and add the template
|
||||||
this.viewContainer.clear();
|
this.viewContainer.clear();
|
||||||
this.viewContainer.createEmbeddedView(this.template);
|
this.viewContainer.createEmbeddedView(this.template);
|
||||||
|
@ -60,6 +60,9 @@ export abstract class BaseModel<T = object> extends OpenSlidesComponent
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update the values of the base model with new values
|
||||||
|
*/
|
||||||
public patchValues(update: Partial<T>): void {
|
public patchValues(update: Partial<T>): void {
|
||||||
Object.assign(this, update);
|
Object.assign(this, update);
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,8 @@ import {
|
|||||||
MatSnackBarModule,
|
MatSnackBarModule,
|
||||||
MatTableModule,
|
MatTableModule,
|
||||||
MatPaginatorModule,
|
MatPaginatorModule,
|
||||||
MatSortModule
|
MatSortModule,
|
||||||
|
MatTooltipModule
|
||||||
} from '@angular/material';
|
} from '@angular/material';
|
||||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
import { MatChipsModule } from '@angular/material';
|
import { MatChipsModule } from '@angular/material';
|
||||||
@ -79,6 +80,7 @@ library.add(fas);
|
|||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
MatSnackBarModule,
|
MatSnackBarModule,
|
||||||
MatChipsModule,
|
MatChipsModule,
|
||||||
|
MatTooltipModule,
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
TranslateModule.forChild(),
|
TranslateModule.forChild(),
|
||||||
RouterModule,
|
RouterModule,
|
||||||
@ -106,6 +108,7 @@ library.add(fas);
|
|||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
MatSnackBarModule,
|
MatSnackBarModule,
|
||||||
MatChipsModule,
|
MatChipsModule,
|
||||||
|
MatTooltipModule,
|
||||||
NgxMatSelectSearchModule,
|
NgxMatSelectSearchModule,
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
|
@ -23,8 +23,9 @@
|
|||||||
|
|
||||||
<!-- Button on the right-->
|
<!-- Button on the right-->
|
||||||
<div *ngIf="editMotion">
|
<div *ngIf="editMotion">
|
||||||
<button (click)='cancelEditMotionButton()' class='on-transition-fade' mat-icon-button>
|
<button (click)='cancelEditMotionButton()' class='on-transition-fade' color="warn" mat-raised-button>
|
||||||
<fa-icon icon='times'></fa-icon>
|
<span translate>Abort</span>
|
||||||
|
<fa-icon class="icon-text-distance" icon='times'></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!editMotion">
|
<div *ngIf="!editMotion">
|
||||||
@ -39,7 +40,7 @@
|
|||||||
<button mat-menu-item translate>Export As...</button>
|
<button mat-menu-item translate>Export As...</button>
|
||||||
<button mat-menu-item translate>Project</button>
|
<button mat-menu-item translate>Project</button>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<button mat-menu-item class='deleteMotionButton' (click)='deleteMotionButton()' translate>DeleteMotion</button>
|
<button mat-menu-item class='red-warning-text' (click)='deleteMotionButton()' translate>DeleteMotion</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
|
|
||||||
|
@ -2,14 +2,6 @@ span {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-button {
|
|
||||||
background-color: rgb(77, 243, 86);
|
|
||||||
}
|
|
||||||
|
|
||||||
.deleteMotionButton {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.motion-title {
|
.motion-title {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
line-height: 100%;
|
line-height: 100%;
|
||||||
|
@ -1,13 +1,3 @@
|
|||||||
.custom-table-header {
|
|
||||||
// display: none;
|
|
||||||
width: 100%;
|
|
||||||
height: 60px;
|
|
||||||
line-height: 60px;
|
|
||||||
text-align: right;
|
|
||||||
background: white;
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** css hacks https://codepen.io/edge0703/pen/iHJuA */
|
/** css hacks https://codepen.io/edge0703/pen/iHJuA */
|
||||||
.innerTable {
|
.innerTable {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -40,7 +40,7 @@ export class CategoryRepositoryService extends BaseRepository<ViewCategory, Cate
|
|||||||
if (this.osInDataStore(viewCategory)) {
|
if (this.osInDataStore(viewCategory)) {
|
||||||
return this.update(update, viewCategory);
|
return this.update(update, viewCategory);
|
||||||
} else {
|
} else {
|
||||||
return this.dataSend.saveModel(viewCategory.category);
|
return this.dataSend.createModel(viewCategory.category);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ export class CategoryRepositoryService extends BaseRepository<ViewCategory, Cate
|
|||||||
updateCategory = new Category();
|
updateCategory = new Category();
|
||||||
}
|
}
|
||||||
updateCategory.patchValues(update);
|
updateCategory.patchValues(update);
|
||||||
return this.dataSend.saveModel(updateCategory);
|
return this.dataSend.updateModel(updateCategory, 'put');
|
||||||
}
|
}
|
||||||
|
|
||||||
public delete(viewCategory: ViewCategory): Observable<any> {
|
public delete(viewCategory: ViewCategory): Observable<any> {
|
||||||
|
@ -66,7 +66,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
* TODO: Remove the viewMotion and make it actually distignuishable from save()
|
* TODO: Remove the viewMotion and make it actually distignuishable from save()
|
||||||
*/
|
*/
|
||||||
public create(motion: Motion): Observable<any> {
|
public create(motion: Motion): Observable<any> {
|
||||||
return this.dataSend.saveModel(motion);
|
return this.dataSend.createModel(motion);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -81,7 +81,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
public update(update: Partial<Motion>, viewMotion: ViewMotion): Observable<any> {
|
public update(update: Partial<Motion>, viewMotion: ViewMotion): Observable<any> {
|
||||||
const motion = viewMotion.motion;
|
const motion = viewMotion.motion;
|
||||||
motion.patchValues(update);
|
motion.patchValues(update);
|
||||||
return this.dataSend.saveModel(motion);
|
return this.dataSend.updateModel(motion, 'patch');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import { AuthService } from 'app/core/services/auth.service';
|
import { AuthService } from 'app/core/services/auth.service';
|
||||||
import { OperatorService } from 'app/core/services/operator.service';
|
import { OperatorService } from 'app/core/services/operator.service';
|
||||||
@ -21,7 +22,8 @@ export class SiteComponent extends BaseComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* HTML element of the side panel
|
* HTML element of the side panel
|
||||||
*/
|
*/
|
||||||
@ViewChild('sideNav') public sideNav: MatSidenav;
|
@ViewChild('sideNav')
|
||||||
|
public sideNav: MatSidenav;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the username from the operator (should be known already)
|
* Get the username from the operator (should be known already)
|
||||||
@ -37,6 +39,7 @@ export class SiteComponent extends BaseComponent implements OnInit {
|
|||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
* @param authService
|
* @param authService
|
||||||
|
* @param router
|
||||||
* @param operator
|
* @param operator
|
||||||
* @param vp
|
* @param vp
|
||||||
* @param translate
|
* @param translate
|
||||||
@ -44,6 +47,7 @@ export class SiteComponent extends BaseComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
|
private router: Router,
|
||||||
public operator: OperatorService,
|
public operator: OperatorService,
|
||||||
public vp: ViewportService,
|
public vp: ViewportService,
|
||||||
public translate: TranslateService,
|
public translate: TranslateService,
|
||||||
@ -109,7 +113,11 @@ export class SiteComponent extends BaseComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
public editProfile(): void {}
|
public editProfile(): void {
|
||||||
|
if (this.operator.user) {
|
||||||
|
this.router.navigate([`./users/${this.operator.user.id}`]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
public changePassword(): void {}
|
public changePassword(): void {}
|
||||||
|
@ -0,0 +1,152 @@
|
|||||||
|
<mat-toolbar color='primary'>
|
||||||
|
|
||||||
|
<button *osPerms="'users.can_manage';or:ownPage" (click)='editUserButton()' [ngClass]="{'save-button': editUser}"
|
||||||
|
class='generic-mini-button on-transition-fade' mat-mini-fab>
|
||||||
|
<fa-icon *ngIf='!editUser' icon='pen'></fa-icon>
|
||||||
|
<fa-icon *ngIf='editUser' icon='check'></fa-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="on-transition-fade">
|
||||||
|
<div *ngIf='editUser'>
|
||||||
|
{{personalInfoForm.get('title').value}}
|
||||||
|
{{personalInfoForm.get('first_name').value}}
|
||||||
|
{{personalInfoForm.get('last_name').value}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf='!editUser'>
|
||||||
|
{{user.fullName}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class='spacer'></span>
|
||||||
|
|
||||||
|
<!-- Button on the right-->
|
||||||
|
<div *ngIf="editUser">
|
||||||
|
<button (click)='cancelEditMotionButton()' class='on-transition-fade' color="warn" mat-raised-button>
|
||||||
|
<span translate>Abort</span>
|
||||||
|
<fa-icon class="icon-text-distance" icon='times'></fa-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="!editUser">
|
||||||
|
<button class='on-transition-fade' mat-icon-button [matMenuTriggerFor]="userExtraMenu">
|
||||||
|
<fa-icon icon='ellipsis-v'></fa-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-menu #userExtraMenu="matMenu">
|
||||||
|
<button mat-menu-item class="red-warning-text" (click)='deleteUserButton()' translate>Delete User</button>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
</mat-toolbar>
|
||||||
|
|
||||||
|
<mat-card class="os-card" *osPerms="'users.can_see_name'">
|
||||||
|
<form [ngClass]="{'mat-form-field-enabled': editUser}" [formGroup]='personalInfoForm' (ngSubmit)='saveUser()' *ngIf="user">
|
||||||
|
<!-- <h3 translate>Personal Data</h3> -->
|
||||||
|
<div *ngIf='isAllowed("seeName")'>
|
||||||
|
<!-- Title -->
|
||||||
|
<mat-form-field class='form30 distance force-min-with' *ngIf='user.title || editUser && isAllowed("manage")'>
|
||||||
|
<input type='text' matInput placeholder='{{"Title" | translate}}' formControlName='title' [value]='user.title'>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<!-- First name -->
|
||||||
|
<mat-form-field class='form30 distance force-min-with' *ngIf='user.firstName || editUser && isAllowed("manage")'>
|
||||||
|
<input type='text' matInput placeholder='{{"First Name" | translate}}' formControlName='first_name'
|
||||||
|
[value]='user.firstName'>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<!-- Last name -->
|
||||||
|
<mat-form-field class='form30 force-min-with' *ngIf='user.lastName || editUser && isAllowed("manage")'>
|
||||||
|
<input type='text' matInput placeholder='{{"Last Name" | translate}}' formControlName='last_name'
|
||||||
|
[value]='user.lastName'>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf='isAllowed("seePersonal")'>
|
||||||
|
<!-- E-Mail -->
|
||||||
|
<mat-form-field class='form100' *ngIf="user.email || editUser">
|
||||||
|
<input type='email' matInput placeholder='{{"EMail" | translate}}' name="email" formControlName='email'
|
||||||
|
[value]='user.email'>
|
||||||
|
<mat-error *ngIf="personalInfoForm.get('email').hasError('email')">
|
||||||
|
Please enter a valid email address
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<!-- Strcuture Level -->
|
||||||
|
<mat-form-field class='form70 distance' *ngIf='user.structureLevel || editUser && isAllowed("manage")'>
|
||||||
|
<input type='text' matInput placeholder='{{"Structure Level" | translate}}' formControlName='structure_level'
|
||||||
|
[value]='user.structureLevel'>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<!-- Partizipant Number -->
|
||||||
|
<mat-form-field class='form20 force-min-with' *ngIf='user.participantNumber || editUser && isAllowed("manage")'>
|
||||||
|
<input type='text' matInput placeholder='{{"Participant Number" | translate}}' formControlName='number'
|
||||||
|
[value]='user.participantNumber'>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<!-- Groups -->
|
||||||
|
<mat-form-field class='form100' *ngIf="user.groups && user.groups.length > 0 || editUser">
|
||||||
|
<mat-select placeholder='{{"Groups" | translate}}' formControlName='groups_id' multiple>
|
||||||
|
<mat-option *ngFor="let group of groups" [value]="group.id">{{group}}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf='isAllowed("manage")'>
|
||||||
|
<!-- Initial Password -->
|
||||||
|
<mat-form-field class='form100'>
|
||||||
|
<input matInput placeholder='{{"Initial Password" | translate}}' formControlName='default_password'
|
||||||
|
[value]='user.initialPassword'>
|
||||||
|
<mat-hint align="end">Generate</mat-hint>
|
||||||
|
<button type="button" mat-button matSuffix mat-icon-button [disabled]='!newUser' (click)='generatePassword()'>
|
||||||
|
<fa-icon icon='magic'></fa-icon>
|
||||||
|
</button>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf='isAllowed("seePersonal")'>
|
||||||
|
<!-- About me -->
|
||||||
|
<!-- TODO: Needs Rich Text Editor -->
|
||||||
|
<mat-form-field class='form100' *ngIf="user.about || editUser">
|
||||||
|
<textarea formControlName='about_me' matInput placeholder='{{"About Me" | translate}}' [value]='user.about'></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf='isAllowed("seePersonal")'>
|
||||||
|
<!-- username -->
|
||||||
|
<mat-form-field class='form100' *ngIf="user.username || editUser">
|
||||||
|
<input type='text' matInput placeholder='{{"Username" | translate}}' formControlName='username' [value]='user.username'>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf='isAllowed("seeExtra")'>
|
||||||
|
<!-- Comment -->
|
||||||
|
<mat-form-field class='form100' *ngIf="user.comment || editUser">
|
||||||
|
<input matInput placeholder='{{"Comment"| translate}}' formControlName='comment' [value]='user.comment'>
|
||||||
|
<mat-hint translate>Only for internal notes.</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf='isAllowed("seeExtra")'>
|
||||||
|
<!-- Present? -->
|
||||||
|
<mat-checkbox formControlName='is_present' matTooltip='{{"Designates whether this user is in the room." | translate}} '
|
||||||
|
[value]='user.isPresent'>
|
||||||
|
<span translate>Is Present</span>
|
||||||
|
</mat-checkbox>
|
||||||
|
<!-- Active? -->
|
||||||
|
<mat-checkbox *osPerms="'users.can_see_extra_data'" formControlName='is_active' matTooltip='{{"Designates whether this user should be treated as active. Unselect this instead of deleting the account." | translate}}'
|
||||||
|
[value]='user.isActive'>
|
||||||
|
<span translate>Is Active</span>
|
||||||
|
</mat-checkbox>
|
||||||
|
<!-- Commitee? -->
|
||||||
|
<mat-checkbox formControlName='is_committee' matTooltip='{{"Designates whether this user should be treated as a committee." | translate}}'
|
||||||
|
[value]='user.isCommittee'>
|
||||||
|
<span translate>Is a committee</span>
|
||||||
|
</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</mat-card>
|
@ -0,0 +1,67 @@
|
|||||||
|
// hide certain stuff whem editing is disabled
|
||||||
|
.mat-form-field-disabled {
|
||||||
|
::ng-deep {
|
||||||
|
.mat-input-element {
|
||||||
|
color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-select-value {
|
||||||
|
color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field-underline {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-hint {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-select-value {
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// angular material does not have this class. This is virtually set using ngClass
|
||||||
|
.mat-form-field-enabled {
|
||||||
|
.form100 {
|
||||||
|
::ng-deep {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form70 {
|
||||||
|
::ng-deep {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.force-min-with {
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form30 {
|
||||||
|
::ng-deep {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form20 {
|
||||||
|
::ng-deep {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.distance {
|
||||||
|
padding-right: 5%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-checkbox {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UserDetailComponent } from './user-detail.component';
|
||||||
|
|
||||||
|
describe('UserDetailComponent', () => {
|
||||||
|
let component: UserDetailComponent;
|
||||||
|
let fixture: ComponentFixture<UserDetailComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [UserDetailComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(UserDetailComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,336 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Users detail component for both new and existing users
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'os-user-detail',
|
||||||
|
templateUrl: './user-detail.component.html',
|
||||||
|
styleUrls: ['./user-detail.component.scss']
|
||||||
|
})
|
||||||
|
export class UserDetailComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* Info form object
|
||||||
|
*/
|
||||||
|
public personalInfoForm: FormGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if this is the own page
|
||||||
|
*/
|
||||||
|
public ownPage = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Editing a user
|
||||||
|
*/
|
||||||
|
public editUser = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if a new user is created
|
||||||
|
*/
|
||||||
|
public newUser = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the operator has manage permissions
|
||||||
|
*/
|
||||||
|
public canManage = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewUser model
|
||||||
|
*/
|
||||||
|
public user: ViewUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should contain all Groups, loaded or observed from DataStore
|
||||||
|
*/
|
||||||
|
public groups: Group[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for user
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private repo: UserRepositoryService,
|
||||||
|
private DS: DataStoreService,
|
||||||
|
private op: OperatorService
|
||||||
|
) {
|
||||||
|
this.user = new ViewUser();
|
||||||
|
if (route.snapshot.url[0].path === 'new') {
|
||||||
|
this.newUser = true;
|
||||||
|
this.setEditMode(true);
|
||||||
|
} else {
|
||||||
|
this.route.params.subscribe(params => {
|
||||||
|
this.loadViewUser(params.id);
|
||||||
|
|
||||||
|
// will fail after reload - observable required
|
||||||
|
this.ownPage = this.opOwnsPage(Number(params.id));
|
||||||
|
|
||||||
|
// observe operator to find out if we see our own page or not
|
||||||
|
this.op.getObservable().subscribe(newOp => {
|
||||||
|
if (newOp) {
|
||||||
|
this.ownPage = this.opOwnsPage(Number(params.id));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.createForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sets the ownPage variable if the operator owns the page
|
||||||
|
*/
|
||||||
|
public opOwnsPage(userId: number): boolean {
|
||||||
|
if (this.op.user && this.op.user.id === userId) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should determine if the user (Operator) has the
|
||||||
|
* correct permission to perform the given action.
|
||||||
|
*
|
||||||
|
* actions might be:
|
||||||
|
* - seeName (title, 1st, last) (user.can_see_name or ownPage)
|
||||||
|
* - seeExtra (checkboxes, comment) (user.can_see_extra_data)
|
||||||
|
* - seePersonal (mail, username, about) (user.can_see_extra_data or ownPage)
|
||||||
|
* - manage (everything) (user.can_manage)
|
||||||
|
* - changePersonal (mail, username, about) (user.can_manage or ownPage)
|
||||||
|
*
|
||||||
|
* @param action the action the user tries to perform
|
||||||
|
*/
|
||||||
|
public isAllowed(action: string): boolean {
|
||||||
|
switch (action) {
|
||||||
|
case 'manage':
|
||||||
|
return this.op.hasPerms('users.can_manage');
|
||||||
|
case 'seeName':
|
||||||
|
return this.op.hasPerms('users.can_see_name', 'users.can_manage') || this.ownPage;
|
||||||
|
case 'seeExtra':
|
||||||
|
return this.op.hasPerms('users.can_see_extra_data', 'users.can_manage');
|
||||||
|
case 'seePersonal':
|
||||||
|
return this.op.hasPerms('users.can_see_extra_data', 'users.can_manage') || this.ownPage;
|
||||||
|
case 'changePersonal':
|
||||||
|
return this.op.hasPerms('user.cans_manage') || this.ownPage;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a user from users repository
|
||||||
|
* @param id the required ID
|
||||||
|
*/
|
||||||
|
public loadViewUser(id: number): void {
|
||||||
|
this.repo.getViewModelObservable(id).subscribe(newViewUser => {
|
||||||
|
// repo sometimes delivers undefined values
|
||||||
|
// also ensures edition cannot be interrupted by autpupdate
|
||||||
|
if (newViewUser && !this.editUser) {
|
||||||
|
this.user = newViewUser;
|
||||||
|
// personalInfoForm is undefined during 'new' and directly after reloading
|
||||||
|
if (this.personalInfoForm) {
|
||||||
|
this.patchFormValues();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initialize the form with default values
|
||||||
|
*/
|
||||||
|
public createForm(): void {
|
||||||
|
this.personalInfoForm = this.formBuilder.group({
|
||||||
|
username: [''],
|
||||||
|
title: [''],
|
||||||
|
first_name: [''],
|
||||||
|
last_name: [''],
|
||||||
|
structure_level: [''],
|
||||||
|
number: [''],
|
||||||
|
about_me: [''],
|
||||||
|
groups_id: [''],
|
||||||
|
is_present: [true],
|
||||||
|
is_committee: [false],
|
||||||
|
email: ['', Validators.email],
|
||||||
|
last_email_send: [''],
|
||||||
|
comment: [''],
|
||||||
|
is_active: [true],
|
||||||
|
default_password: ['']
|
||||||
|
});
|
||||||
|
|
||||||
|
// per default disable the whole form:
|
||||||
|
|
||||||
|
this.patchFormValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads values that require external references
|
||||||
|
* And allows async reading
|
||||||
|
*/
|
||||||
|
public patchFormValues(): void {
|
||||||
|
this.personalInfoForm.patchValue({
|
||||||
|
username: this.user.username,
|
||||||
|
groups_id: this.user.groupIds,
|
||||||
|
title: this.user.title,
|
||||||
|
first_name: this.user.firstName,
|
||||||
|
last_name: this.user.lastName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the form editable
|
||||||
|
* @param editable
|
||||||
|
*/
|
||||||
|
public makeFormEditable(editable: boolean): void {
|
||||||
|
if (this.personalInfoForm) {
|
||||||
|
const formControlNames = Object.keys(this.personalInfoForm.controls);
|
||||||
|
const allowedFormFields = [];
|
||||||
|
|
||||||
|
if (this.isAllowed('manage')) {
|
||||||
|
// editable content with manage rights
|
||||||
|
allowedFormFields.push(
|
||||||
|
this.personalInfoForm.get('username'),
|
||||||
|
this.personalInfoForm.get('title'),
|
||||||
|
this.personalInfoForm.get('first_name'),
|
||||||
|
this.personalInfoForm.get('last_name'),
|
||||||
|
this.personalInfoForm.get('email'),
|
||||||
|
this.personalInfoForm.get('structure_level'),
|
||||||
|
this.personalInfoForm.get('number'),
|
||||||
|
this.personalInfoForm.get('groups_id'),
|
||||||
|
this.personalInfoForm.get('comment'),
|
||||||
|
this.personalInfoForm.get('is_present'),
|
||||||
|
this.personalInfoForm.get('is_active'),
|
||||||
|
this.personalInfoForm.get('is_committee'),
|
||||||
|
this.personalInfoForm.get('about_me')
|
||||||
|
);
|
||||||
|
} else if (this.isAllowed('changePersonal')) {
|
||||||
|
// changeable personal data
|
||||||
|
// FIXME: Own E-Mail and Password is hidden (server?)
|
||||||
|
allowedFormFields.push(
|
||||||
|
this.personalInfoForm.get('username'),
|
||||||
|
this.personalInfoForm.get('email'),
|
||||||
|
this.personalInfoForm.get('about_me')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// treatment for the initial password field
|
||||||
|
if (!editable || this.newUser) {
|
||||||
|
allowedFormFields.push(this.personalInfoForm.get('default_password'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editable) {
|
||||||
|
allowedFormFields.forEach(formElement => {
|
||||||
|
formElement.enable();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
formControlNames.forEach(formControlName => {
|
||||||
|
this.personalInfoForm.get(formControlName).disable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for the generate Password button.
|
||||||
|
* Generates a password using 8 pseudo-random letters
|
||||||
|
* from the `characters` const.
|
||||||
|
*
|
||||||
|
* Removed the letter 'O' from the alphabet cause it's easy to confuse
|
||||||
|
* with the number '0'.
|
||||||
|
*/
|
||||||
|
public generatePassword(): void {
|
||||||
|
let pw = '';
|
||||||
|
const characters = 'ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
const amount = 8;
|
||||||
|
for (let i = 0; i < amount; i++) {
|
||||||
|
pw += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||||
|
}
|
||||||
|
this.personalInfoForm.patchValue({
|
||||||
|
default_password: pw
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save / Submit a user
|
||||||
|
*/
|
||||||
|
public saveUser(): void {
|
||||||
|
if (this.newUser) {
|
||||||
|
this.repo.create(this.personalInfoForm.value).subscribe(
|
||||||
|
response => {
|
||||||
|
this.newUser = false;
|
||||||
|
this.router.navigate([`./users/${response.id}`]);
|
||||||
|
// this.setEditMode(false);
|
||||||
|
// this.loadViewUser(response.id);
|
||||||
|
},
|
||||||
|
error => console.error('Creation of the user failed: ', error.error)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.repo.update(this.personalInfoForm.value, this.user).subscribe(
|
||||||
|
response => {
|
||||||
|
this.setEditMode(false);
|
||||||
|
this.loadViewUser(response.id);
|
||||||
|
},
|
||||||
|
error => console.error('Update of the user failed: ', error.error)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sets editUser variable and editable form
|
||||||
|
* @param edit
|
||||||
|
*/
|
||||||
|
public setEditMode(edit: boolean): void {
|
||||||
|
this.editUser = edit;
|
||||||
|
this.makeFormEditable(edit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* click on the edit button
|
||||||
|
*/
|
||||||
|
public editUserButton(): void {
|
||||||
|
if (this.editUser) {
|
||||||
|
this.saveUser();
|
||||||
|
} else {
|
||||||
|
this.setEditMode(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public cancelEditMotionButton(): void {
|
||||||
|
if (this.newUser) {
|
||||||
|
this.router.navigate(['./users/']);
|
||||||
|
} else {
|
||||||
|
this.setEditMode(false);
|
||||||
|
this.loadViewUser(this.user.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* click on the delete user button
|
||||||
|
*/
|
||||||
|
public deleteUserButton(): void {
|
||||||
|
this.repo.delete(this.user).subscribe(response => {
|
||||||
|
this.router.navigate(['./users/']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init function.
|
||||||
|
*/
|
||||||
|
public ngOnInit(): void {
|
||||||
|
this.makeFormEditable(this.editUser);
|
||||||
|
this.groups = this.DS.getAll(Group);
|
||||||
|
this.DS.changeObservable.subscribe(model => {
|
||||||
|
if (model instanceof Group) {
|
||||||
|
this.groups.push(model as Group);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,15 @@
|
|||||||
(ellipsisMenuItem)=onEllipsisItem($event)>
|
(ellipsisMenuItem)=onEllipsisItem($event)>
|
||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
|
|
||||||
|
<div class='custom-table-header on-transition-fade'>
|
||||||
|
<button mat-button>
|
||||||
|
<span translate>SORT</span>
|
||||||
|
</button>
|
||||||
|
<button mat-button>
|
||||||
|
<span translate>FILTER</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<mat-table class='os-listview-table on-transition-fade' [dataSource]="dataSource" matSort>
|
<mat-table class='os-listview-table on-transition-fade' [dataSource]="dataSource" matSort>
|
||||||
<!-- name column -->
|
<!-- name column -->
|
||||||
<ng-container matColumnDef="name">
|
<ng-container matColumnDef="name">
|
||||||
@ -12,12 +21,30 @@
|
|||||||
<!-- prefix column -->
|
<!-- prefix column -->
|
||||||
<ng-container matColumnDef="group">
|
<ng-container matColumnDef="group">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header> Group </mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header> Group </mat-header-cell>
|
||||||
<mat-cell *matCellDef="let user"> {{user.groups}} {{user.structureLevel}} </mat-cell>
|
<mat-cell *matCellDef="let user">
|
||||||
|
<div class='groupsCell'>
|
||||||
|
<span *ngIf="user.groups.length > 0">
|
||||||
|
<fa-icon icon="users"></fa-icon>
|
||||||
|
{{user.groups}}
|
||||||
|
</span>
|
||||||
|
<br *ngIf="user.groups && user.structureLevel">
|
||||||
|
<span *ngIf="user.structureLevel">
|
||||||
|
<fa-icon icon="flag"></fa-icon>
|
||||||
|
{{user.structureLevel}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Presence column -->
|
||||||
<ng-container matColumnDef="presence">
|
<ng-container matColumnDef="presence">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header> Presence </mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header> Presence </mat-header-cell>
|
||||||
<mat-cell *matCellDef="let user"> {{user.isActive}} </mat-cell>
|
<mat-cell *matCellDef="let user">
|
||||||
|
<div *ngIf="user.isActive">
|
||||||
|
<fa-icon icon="check-square"></fa-icon>
|
||||||
|
<span translate>Present</span>
|
||||||
|
</div>
|
||||||
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<mat-header-row *matHeaderRowDef="['name', 'group', 'presence']"></mat-header-row>
|
<mat-header-row *matHeaderRowDef="['name', 'group', 'presence']"></mat-header-row>
|
@ -0,0 +1,32 @@
|
|||||||
|
.groupsCell {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: normal;
|
||||||
|
|
||||||
|
fa-icon {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.os-listview-table {
|
||||||
|
.mat-column-name {
|
||||||
|
flex: 1 0 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-column-group {
|
||||||
|
flex: 2 0 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-column-presence {
|
||||||
|
flex: 0 0 60px;
|
||||||
|
|
||||||
|
fa-icon {
|
||||||
|
font-size: 100%;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,10 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
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 { ListViewBaseComponent } from '../../base/list-view-base';
|
import { ListViewBaseComponent } from '../../../base/list-view-base';
|
||||||
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for the user list view.
|
* Component for the user list view.
|
||||||
@ -13,7 +14,7 @@ import { ListViewBaseComponent } from '../../base/list-view-base';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'os-user-list',
|
selector: 'os-user-list',
|
||||||
templateUrl: './user-list.component.html',
|
templateUrl: './user-list.component.html',
|
||||||
styleUrls: ['./user-list.component.css']
|
styleUrls: ['./user-list.component.scss']
|
||||||
})
|
})
|
||||||
export class UserListComponent extends ListViewBaseComponent<ViewUser> implements OnInit {
|
export class UserListComponent extends ListViewBaseComponent<ViewUser> implements OnInit {
|
||||||
/**
|
/**
|
||||||
@ -46,7 +47,9 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
|||||||
public constructor(
|
public constructor(
|
||||||
private repo: UserRepositoryService,
|
private repo: UserRepositoryService,
|
||||||
protected titleService: Title,
|
protected titleService: Title,
|
||||||
protected translate: TranslateService
|
protected translate: TranslateService,
|
||||||
|
private router: Router,
|
||||||
|
private route: ActivatedRoute
|
||||||
) {
|
) {
|
||||||
super(titleService, translate);
|
super(titleService, translate);
|
||||||
}
|
}
|
||||||
@ -86,13 +89,13 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
|||||||
* @param row selected row
|
* @param row selected row
|
||||||
*/
|
*/
|
||||||
public selectUser(row: ViewUser): void {
|
public selectUser(row: ViewUser): void {
|
||||||
console.log('clicked the row for user: ', row);
|
this.router.navigate([`./${row.id}`], { relativeTo: this.route });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the click on the plus button
|
* Handles the click on the plus button
|
||||||
*/
|
*/
|
||||||
public onPlusButton(): void {
|
public onPlusButton(): void {
|
||||||
console.log('new User');
|
this.router.navigate(['./new'], { relativeTo: this.route });
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,39 +7,96 @@ export class ViewUser extends BaseViewModel {
|
|||||||
private _user: User;
|
private _user: User;
|
||||||
private _groups: Group[];
|
private _groups: Group[];
|
||||||
|
|
||||||
public get id(): number {
|
|
||||||
return this._user ? this._user.id : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get user(): User {
|
public get user(): User {
|
||||||
return this._user;
|
return this._user ? this._user : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get groups(): Group[] {
|
public get groups(): Group[] {
|
||||||
return this._groups;
|
return this._groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get id(): number {
|
||||||
|
return this.user ? this.user.id : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get username(): string {
|
||||||
|
return this.user ? this.user.username : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get title(): string {
|
||||||
|
return this.user ? this.user.title : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get firstName(): string {
|
||||||
|
return this.user ? this.user.first_name : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get lastName(): string {
|
||||||
|
return this.user ? this.user.last_name : null;
|
||||||
|
}
|
||||||
|
|
||||||
public get fullName(): string {
|
public get fullName(): string {
|
||||||
return this.user ? this.user.full_name : null;
|
return this.user ? this.user.full_name : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public get email(): string {
|
||||||
* TODO: Make boolean, use function over view component.
|
return this.user ? this.user.email : null;
|
||||||
*/
|
|
||||||
public get isActive(): string {
|
|
||||||
return this.user && this.user.is_active ? 'active' : 'inactive';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get structureLevel(): string {
|
public get structureLevel(): string {
|
||||||
return this.user ? this.user.structure_level : null;
|
return this.user ? this.user.structure_level : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get participantNumber(): string {
|
||||||
|
return this.user ? this.user.number : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get groupIds(): number[] {
|
||||||
|
return this.user ? this.user.groups_id : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required by the input selector
|
||||||
|
*/
|
||||||
|
public set groupIds(ids: number[]) {
|
||||||
|
if (this.user) {
|
||||||
|
this.user.groups_id = ids;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get initialPassword(): string {
|
||||||
|
return this.user ? this.user.default_password : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get comment(): string {
|
||||||
|
return this.user ? this.user.comment : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isPresent(): boolean {
|
||||||
|
return this.user ? this.user.is_present : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isActive(): boolean {
|
||||||
|
return this.user ? this.user.is_active : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isCommittee(): boolean {
|
||||||
|
return this.user ? this.user.is_committee : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get about(): string {
|
||||||
|
return this.user ? this.user.about_me : null;
|
||||||
|
}
|
||||||
|
|
||||||
public constructor(user?: User, groups?: Group[]) {
|
public constructor(user?: User, groups?: Group[]) {
|
||||||
super();
|
super();
|
||||||
this._user = user;
|
this._user = user;
|
||||||
this._groups = groups;
|
this._groups = groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* required by BaseViewModel. Don't confuse with the users title.
|
||||||
|
*/
|
||||||
public getTitle(): string {
|
public getTitle(): string {
|
||||||
return this.user ? this.user.toString() : null;
|
return this.user ? this.user.toString() : null;
|
||||||
}
|
}
|
||||||
@ -47,9 +104,7 @@ export class ViewUser extends BaseViewModel {
|
|||||||
/**
|
/**
|
||||||
* TODO: Implement
|
* TODO: Implement
|
||||||
*/
|
*/
|
||||||
public replaceGroup(newGroup: Group): void {
|
public replaceGroup(newGroup: Group): void {}
|
||||||
console.log('replace group - not yet implemented, ', newGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateValues(update: BaseModel): void {
|
public updateValues(update: BaseModel): void {
|
||||||
if (update instanceof Group) {
|
if (update instanceof Group) {
|
||||||
|
@ -6,6 +6,7 @@ import { User } from '../../../shared/models/users/user';
|
|||||||
import { Group } from '../../../shared/models/users/group';
|
import { Group } from '../../../shared/models/users/group';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { DataStoreService } from '../../../core/services/data-store.service';
|
import { DataStoreService } from '../../../core/services/data-store.service';
|
||||||
|
import { DataSendService } from '../../../core/services/data-send.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository service for users
|
* Repository service for users
|
||||||
@ -19,17 +20,29 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
|
|||||||
/**
|
/**
|
||||||
* Constructor calls the parent constructor
|
* Constructor calls the parent constructor
|
||||||
*/
|
*/
|
||||||
public constructor(DS: DataStoreService) {
|
public constructor(DS: DataStoreService, private dataSend: DataSendService) {
|
||||||
super(DS, User, [Group]);
|
super(DS, User, [Group]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ignore
|
* Updates a the selected user with the form values.
|
||||||
*
|
*
|
||||||
* TODO: used over not-yet-existing detail view
|
* @param update the forms values
|
||||||
|
* @param viewUser
|
||||||
*/
|
*/
|
||||||
public update(user: Partial<User>, viewUser: ViewUser): Observable<User> {
|
public update(update: Partial<User>, viewUser: ViewUser): Observable<any> {
|
||||||
return null;
|
const updateUser = new User();
|
||||||
|
// copy the ViewUser to avoid manipulation of parameters
|
||||||
|
updateUser.patchValues(viewUser.user);
|
||||||
|
updateUser.patchValues(update);
|
||||||
|
|
||||||
|
// if the user deletes the username, reset
|
||||||
|
// prevents the server of generating '<firstname> <lastname> +1' as username
|
||||||
|
if (updateUser.username === '') {
|
||||||
|
updateUser.username = viewUser.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.dataSend.updateModel(updateUser, 'put');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,17 +50,38 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
|
|||||||
*
|
*
|
||||||
* TODO: used over not-yet-existing detail view
|
* TODO: used over not-yet-existing detail view
|
||||||
*/
|
*/
|
||||||
public delete(user: ViewUser): Observable<User> {
|
public delete(viewUser: ViewUser): Observable<any> {
|
||||||
return null;
|
return this.dataSend.delete(viewUser.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ignore
|
* creates and saves a new user
|
||||||
*
|
*
|
||||||
* TODO: used over not-yet-existing detail view
|
* TODO: used over not-yet-existing detail view
|
||||||
|
* @param userData blank form value. Usually not yet a real user
|
||||||
*/
|
*/
|
||||||
public create(user: User): Observable<User> {
|
public create(userData: Partial<User>): Observable<any> {
|
||||||
return null;
|
const newUser = new User();
|
||||||
|
// collectionString of userData is still empty
|
||||||
|
newUser.patchValues(userData);
|
||||||
|
|
||||||
|
// if the username is not presend, delete.
|
||||||
|
// The server will generate a one
|
||||||
|
if (!newUser.username) {
|
||||||
|
delete newUser.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
// title must not be "null" during creation
|
||||||
|
if (!newUser.title) {
|
||||||
|
delete newUser.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
// null values will not be accepted for group_id
|
||||||
|
if (!newUser.groups_id) {
|
||||||
|
delete newUser.groups_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.dataSend.createModel(newUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
public createViewModel(user: User): ViewUser {
|
public createViewModel(user: User): ViewUser {
|
||||||
|
@ -1,8 +1,22 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
import { UserListComponent } from './user-list/user-list.component';
|
import { UserListComponent } from './components/user-list/user-list.component';
|
||||||
|
import { UserDetailComponent } from './components/user-detail/user-detail.component';
|
||||||
|
|
||||||
const routes: Routes = [{ path: '', component: UserListComponent }];
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: UserListComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'new',
|
||||||
|
component: UserDetailComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ':id',
|
||||||
|
component: UserDetailComponent
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild(routes)],
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
@ -3,10 +3,11 @@ import { CommonModule } from '@angular/common';
|
|||||||
|
|
||||||
import { UsersRoutingModule } from './users-routing.module';
|
import { UsersRoutingModule } from './users-routing.module';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
import { UserListComponent } from './user-list/user-list.component';
|
import { UserListComponent } from './components/user-list/user-list.component';
|
||||||
|
import { UserDetailComponent } from './components/user-detail/user-detail.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, UsersRoutingModule, SharedModule],
|
imports: [CommonModule, UsersRoutingModule, SharedModule],
|
||||||
declarations: [UserListComponent]
|
declarations: [UserListComponent, UserDetailComponent]
|
||||||
})
|
})
|
||||||
export class UsersModule {}
|
export class UsersModule {}
|
||||||
|
@ -1,12 +1,29 @@
|
|||||||
{
|
{
|
||||||
|
"Abort": "",
|
||||||
|
"About Me": "",
|
||||||
"Agenda": "Tagesordnung",
|
"Agenda": "Tagesordnung",
|
||||||
"Assignments": "Wahlen",
|
"Assignments": "Wahlen",
|
||||||
"Category": "",
|
"Category": "",
|
||||||
"Change Password": "Passwort ändern",
|
"Change Password": "Passwort ändern",
|
||||||
|
"Comment": "",
|
||||||
"Content": "",
|
"Content": "",
|
||||||
"Copyright by": "Copyright by",
|
"Copyright by": "Copyright by",
|
||||||
|
"Delete User": "",
|
||||||
"DeleteMotion": "",
|
"DeleteMotion": "",
|
||||||
|
"Designates whether this user is in the room": {
|
||||||
|
"0": ""
|
||||||
|
},
|
||||||
|
"Designates whether this user should be treated as a committee": {
|
||||||
|
"0": ""
|
||||||
|
},
|
||||||
|
"Designates whether this user should be treated as active": {
|
||||||
|
" Unselect this instead of deleting the account": {
|
||||||
|
"0": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EMail": "",
|
||||||
"Edit Profile": "Profil bearbeiten",
|
"Edit Profile": "Profil bearbeiten",
|
||||||
|
"Edit category details:": "",
|
||||||
"English": "Englisch",
|
"English": "Englisch",
|
||||||
"Export As": {
|
"Export As": {
|
||||||
"0": {
|
"0": {
|
||||||
@ -17,11 +34,18 @@
|
|||||||
},
|
},
|
||||||
"FILTER": "",
|
"FILTER": "",
|
||||||
"Files": "Dateien",
|
"Files": "Dateien",
|
||||||
|
"First Name": "",
|
||||||
"French": "Französisch",
|
"French": "Französisch",
|
||||||
"German": "Deutsch",
|
"German": "Deutsch",
|
||||||
|
"Groups": "",
|
||||||
"Home": "Startseite",
|
"Home": "Startseite",
|
||||||
"Identifier": "",
|
"Identifier": "",
|
||||||
|
"Initial Password": "",
|
||||||
"Installed plugins": "",
|
"Installed plugins": "",
|
||||||
|
"Is Active": "",
|
||||||
|
"Is Present": "",
|
||||||
|
"Is a committee": "",
|
||||||
|
"Last Name": "",
|
||||||
"Legal Notice": "Impressum",
|
"Legal Notice": "Impressum",
|
||||||
"License": "",
|
"License": "",
|
||||||
"Login": "",
|
"Login": "",
|
||||||
@ -30,30 +54,42 @@
|
|||||||
"Meta information": "",
|
"Meta information": "",
|
||||||
"Motion": "",
|
"Motion": "",
|
||||||
"Motions": "Anträge",
|
"Motions": "Anträge",
|
||||||
|
"Name": "",
|
||||||
|
"None": "",
|
||||||
"OK": "",
|
"OK": "",
|
||||||
"Offline mode: You can use OpenSlides but changes are not saved": {
|
"Offline mode: You can use OpenSlides but changes are not saved": {
|
||||||
"0": ""
|
"0": ""
|
||||||
},
|
},
|
||||||
|
"Only for internal notes": {
|
||||||
|
"0": ""
|
||||||
|
},
|
||||||
"Origin": "",
|
"Origin": "",
|
||||||
|
"Participant Number": "",
|
||||||
"Participants": "Teilnehmer",
|
"Participants": "Teilnehmer",
|
||||||
"Personal Note": "",
|
"Personal Note": "",
|
||||||
"Personal note": "",
|
"Personal note": "",
|
||||||
|
"Prefix": "",
|
||||||
|
"Present": "",
|
||||||
"Privacy Policy": "Datenschutz",
|
"Privacy Policy": "Datenschutz",
|
||||||
"Project": "",
|
"Project": "",
|
||||||
"Projector": "",
|
"Projector": "",
|
||||||
"Reason": "",
|
"Reason": "",
|
||||||
|
"Required": "",
|
||||||
"Reset State": "",
|
"Reset State": "",
|
||||||
"Reset recommendation": "",
|
"Reset recommendation": "",
|
||||||
"SORT": "",
|
"SORT": "",
|
||||||
"Selected Values": "",
|
"Selected Values": "",
|
||||||
"Settings": "Einstellungen",
|
"Settings": "Einstellungen",
|
||||||
"State": "",
|
"State": "",
|
||||||
|
"Structure Level": "",
|
||||||
"Submitters": "",
|
"Submitters": "",
|
||||||
"Supporters": "",
|
"Supporters": "",
|
||||||
"The assembly may decide:": "",
|
"The assembly may decide:": "",
|
||||||
"The event manager hasn't set up a privacy policy yet": {
|
"The event manager hasn't set up a privacy policy yet": {
|
||||||
"0": ""
|
"0": ""
|
||||||
},
|
},
|
||||||
|
"Title": "",
|
||||||
|
"Username": "",
|
||||||
"Welcome to OpenSlides": "Willkommen bei OpenSlides",
|
"Welcome to OpenSlides": "Willkommen bei OpenSlides",
|
||||||
"by": ""
|
"by": ""
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,29 @@
|
|||||||
{
|
{
|
||||||
|
"Abort": "",
|
||||||
|
"About Me": "",
|
||||||
"Agenda": "",
|
"Agenda": "",
|
||||||
"Assignments": "",
|
"Assignments": "",
|
||||||
"Category": "",
|
"Category": "",
|
||||||
"Change Password": "",
|
"Change Password": "",
|
||||||
|
"Comment": "",
|
||||||
"Content": "",
|
"Content": "",
|
||||||
"Copyright by": "",
|
"Copyright by": "",
|
||||||
|
"Delete User": "",
|
||||||
"DeleteMotion": "",
|
"DeleteMotion": "",
|
||||||
|
"Designates whether this user is in the room": {
|
||||||
|
"0": ""
|
||||||
|
},
|
||||||
|
"Designates whether this user should be treated as a committee": {
|
||||||
|
"0": ""
|
||||||
|
},
|
||||||
|
"Designates whether this user should be treated as active": {
|
||||||
|
" Unselect this instead of deleting the account": {
|
||||||
|
"0": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EMail": "",
|
||||||
"Edit Profile": "",
|
"Edit Profile": "",
|
||||||
|
"Edit category details:": "",
|
||||||
"English": "",
|
"English": "",
|
||||||
"Export As": {
|
"Export As": {
|
||||||
"0": {
|
"0": {
|
||||||
@ -17,11 +34,18 @@
|
|||||||
},
|
},
|
||||||
"FILTER": "",
|
"FILTER": "",
|
||||||
"Files": "",
|
"Files": "",
|
||||||
|
"First Name": "",
|
||||||
"French": "",
|
"French": "",
|
||||||
"German": "",
|
"German": "",
|
||||||
|
"Groups": "",
|
||||||
"Home": "",
|
"Home": "",
|
||||||
"Identifier": "",
|
"Identifier": "",
|
||||||
|
"Initial Password": "",
|
||||||
"Installed plugins": "",
|
"Installed plugins": "",
|
||||||
|
"Is Active": "",
|
||||||
|
"Is Present": "",
|
||||||
|
"Is a committee": "",
|
||||||
|
"Last Name": "",
|
||||||
"Legal Notice": "",
|
"Legal Notice": "",
|
||||||
"License": "",
|
"License": "",
|
||||||
"Login": "",
|
"Login": "",
|
||||||
@ -30,30 +54,42 @@
|
|||||||
"Meta information": "",
|
"Meta information": "",
|
||||||
"Motion": "",
|
"Motion": "",
|
||||||
"Motions": "",
|
"Motions": "",
|
||||||
|
"Name": "",
|
||||||
|
"None": "",
|
||||||
"OK": "",
|
"OK": "",
|
||||||
"Offline mode: You can use OpenSlides but changes are not saved": {
|
"Offline mode: You can use OpenSlides but changes are not saved": {
|
||||||
"0": ""
|
"0": ""
|
||||||
},
|
},
|
||||||
|
"Only for internal notes": {
|
||||||
|
"0": ""
|
||||||
|
},
|
||||||
"Origin": "",
|
"Origin": "",
|
||||||
|
"Participant Number": "",
|
||||||
"Participants": "",
|
"Participants": "",
|
||||||
"Personal Note": "",
|
"Personal Note": "",
|
||||||
"Personal note": "",
|
"Personal note": "",
|
||||||
|
"Prefix": "",
|
||||||
|
"Present": "",
|
||||||
"Privacy Policy": "",
|
"Privacy Policy": "",
|
||||||
"Project": "",
|
"Project": "",
|
||||||
"Projector": "",
|
"Projector": "",
|
||||||
"Reason": "",
|
"Reason": "",
|
||||||
|
"Required": "",
|
||||||
"Reset State": "",
|
"Reset State": "",
|
||||||
"Reset recommendation": "",
|
"Reset recommendation": "",
|
||||||
"SORT": "",
|
"SORT": "",
|
||||||
"Selected Values": "",
|
"Selected Values": "",
|
||||||
"Settings": "",
|
"Settings": "",
|
||||||
"State": "",
|
"State": "",
|
||||||
|
"Structure Level": "",
|
||||||
"Submitters": "",
|
"Submitters": "",
|
||||||
"Supporters": "",
|
"Supporters": "",
|
||||||
"The assembly may decide:": "",
|
"The assembly may decide:": "",
|
||||||
"The event manager hasn't set up a privacy policy yet": {
|
"The event manager hasn't set up a privacy policy yet": {
|
||||||
"0": ""
|
"0": ""
|
||||||
},
|
},
|
||||||
|
"Title": "",
|
||||||
|
"Username": "",
|
||||||
"Welcome to OpenSlides": "",
|
"Welcome to OpenSlides": "",
|
||||||
"by": ""
|
"by": ""
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,29 @@
|
|||||||
{
|
{
|
||||||
|
"Abort": "",
|
||||||
|
"About Me": "",
|
||||||
"Agenda": "",
|
"Agenda": "",
|
||||||
"Assignments": "",
|
"Assignments": "",
|
||||||
"Category": "",
|
"Category": "",
|
||||||
"Change Password": "",
|
"Change Password": "",
|
||||||
|
"Comment": "",
|
||||||
"Content": "",
|
"Content": "",
|
||||||
"Copyright by": "",
|
"Copyright by": "",
|
||||||
|
"Delete User": "",
|
||||||
"DeleteMotion": "",
|
"DeleteMotion": "",
|
||||||
|
"Designates whether this user is in the room": {
|
||||||
|
"0": ""
|
||||||
|
},
|
||||||
|
"Designates whether this user should be treated as a committee": {
|
||||||
|
"0": ""
|
||||||
|
},
|
||||||
|
"Designates whether this user should be treated as active": {
|
||||||
|
" Unselect this instead of deleting the account": {
|
||||||
|
"0": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EMail": "",
|
||||||
"Edit Profile": "",
|
"Edit Profile": "",
|
||||||
|
"Edit category details:": "",
|
||||||
"English": "",
|
"English": "",
|
||||||
"Export As": {
|
"Export As": {
|
||||||
"0": {
|
"0": {
|
||||||
@ -17,11 +34,18 @@
|
|||||||
},
|
},
|
||||||
"FILTER": "",
|
"FILTER": "",
|
||||||
"Files": "",
|
"Files": "",
|
||||||
|
"First Name": "",
|
||||||
"French": "",
|
"French": "",
|
||||||
"German": "",
|
"German": "",
|
||||||
|
"Groups": "",
|
||||||
"Home": "",
|
"Home": "",
|
||||||
"Identifier": "",
|
"Identifier": "",
|
||||||
|
"Initial Password": "",
|
||||||
"Installed plugins": "",
|
"Installed plugins": "",
|
||||||
|
"Is Active": "",
|
||||||
|
"Is Present": "",
|
||||||
|
"Is a committee": "",
|
||||||
|
"Last Name": "",
|
||||||
"Legal Notice": "",
|
"Legal Notice": "",
|
||||||
"License": "",
|
"License": "",
|
||||||
"Login": "",
|
"Login": "",
|
||||||
@ -30,30 +54,42 @@
|
|||||||
"Meta information": "",
|
"Meta information": "",
|
||||||
"Motion": "",
|
"Motion": "",
|
||||||
"Motions": "",
|
"Motions": "",
|
||||||
|
"Name": "",
|
||||||
|
"None": "",
|
||||||
"OK": "",
|
"OK": "",
|
||||||
"Offline mode: You can use OpenSlides but changes are not saved": {
|
"Offline mode: You can use OpenSlides but changes are not saved": {
|
||||||
"0": ""
|
"0": ""
|
||||||
},
|
},
|
||||||
|
"Only for internal notes": {
|
||||||
|
"0": ""
|
||||||
|
},
|
||||||
"Origin": "",
|
"Origin": "",
|
||||||
|
"Participant Number": "",
|
||||||
"Participants": "",
|
"Participants": "",
|
||||||
"Personal Note": "",
|
"Personal Note": "",
|
||||||
"Personal note": "",
|
"Personal note": "",
|
||||||
|
"Prefix": "",
|
||||||
|
"Present": "",
|
||||||
"Privacy Policy": "",
|
"Privacy Policy": "",
|
||||||
"Project": "",
|
"Project": "",
|
||||||
"Projector": "",
|
"Projector": "",
|
||||||
"Reason": "",
|
"Reason": "",
|
||||||
|
"Required": "",
|
||||||
"Reset State": "",
|
"Reset State": "",
|
||||||
"Reset recommendation": "",
|
"Reset recommendation": "",
|
||||||
"SORT": "",
|
"SORT": "",
|
||||||
"Selected Values": "",
|
"Selected Values": "",
|
||||||
"Settings": "",
|
"Settings": "",
|
||||||
"State": "",
|
"State": "",
|
||||||
|
"Structure Level": "",
|
||||||
"Submitters": "",
|
"Submitters": "",
|
||||||
"Supporters": "",
|
"Supporters": "",
|
||||||
"The assembly may decide:": "",
|
"The assembly may decide:": "",
|
||||||
"The event manager hasn't set up a privacy policy yet": {
|
"The event manager hasn't set up a privacy policy yet": {
|
||||||
"0": ""
|
"0": ""
|
||||||
},
|
},
|
||||||
|
"Title": "",
|
||||||
|
"Username": "",
|
||||||
"Welcome to OpenSlides": "",
|
"Welcome to OpenSlides": "",
|
||||||
"by": ""
|
"by": ""
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,19 @@ body {
|
|||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.save-button {
|
||||||
|
// needs to be important or will be overwritten locally
|
||||||
|
background-color: rgb(77, 243, 86) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-warning-text {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-text-distance {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.os-card {
|
.os-card {
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
@ -33,6 +46,16 @@ body {
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//custom table header for search button, filtering and more. Used in ListViews
|
||||||
|
.custom-table-header {
|
||||||
|
width: 100%;
|
||||||
|
height: 60px;
|
||||||
|
line-height: 60px;
|
||||||
|
text-align: right;
|
||||||
|
background: white;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
.os-listview-table {
|
.os-listview-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user