diff --git a/src/app/config/app.config.ts b/src/app/config/app.config.ts index 66564e1493b8a4e15452d2e132b59bae510aa98b..429914b95deb33760178e56026c1ced23dca200d 100644 --- a/src/app/config/app.config.ts +++ b/src/app/config/app.config.ts @@ -72,7 +72,7 @@ export class AppConfig { this.applyBackgroundColors(); } - private setCustomTexts(customTexts: KeyValuePairs): void { + setCustomTexts(customTexts: KeyValuePairs): void { const ctSettings = {}; Object.keys(customTextsDefault).forEach(k => { ctSettings[k] = customTextsDefault[k].defaultvalue; @@ -85,7 +85,7 @@ export class AppConfig { this.cts.addCustomTexts(ctSettings); } - private setAppConfig(appConfig: Map<string, string>): void { + setAppConfig(appConfig: Map<string, string>): void { this.app_title = this.cts.getCustomText('app_title'); if (!this.app_title) this.app_title = 'IQB-Testcenter'; this.intro_html = this.cts.getCustomText('app_intro1'); @@ -142,7 +142,7 @@ export class AppConfig { this.trusted_impressum_html = this.sanitizer.bypassSecurityTrustHtml(this.impressum_html); } - private applyBackgroundColors(): void { + applyBackgroundColors(): void { document.documentElement.style.setProperty('--tc-body-background', this.background_body); document.documentElement.style.setProperty('--tc-box-background', this.background_box); } @@ -173,12 +173,17 @@ export class AppConfig { return false; } + static isWarningExpired(warningDay: string, warningHour: string): boolean { + const calcTimePoint = new Date(warningDay); + calcTimePoint.setHours(calcTimePoint.getHours() + Number(warningHour)); + const now = new Date(Date.now()); + return calcTimePoint < now; + } + getWarningMessage(): string { if (this.global_warning_expired_day) { - const calcTimePoint = new Date(this.global_warning_expired_day); - calcTimePoint.setHours(calcTimePoint.getHours() + Number(this.global_warning_expired_hour)); - const now = new Date(Date.now()); - return calcTimePoint < now ? this.global_warning : ''; + return AppConfig.isWarningExpired(this.global_warning_expired_day, this.global_warning_expired_hour) ? + this.global_warning : ''; } return this.global_warning; } diff --git a/src/app/superadmin/backend.service.ts b/src/app/superadmin/backend.service.ts index 810754e2841e2ba20e3ec007753504673dd60caf..7fba22e381b3d65c46837d0bf0f55e7aeb384a1e 100644 --- a/src/app/superadmin/backend.service.ts +++ b/src/app/superadmin/backend.service.ts @@ -130,4 +130,13 @@ export class BackendService { return []; })); } + + setAppConfig(newConfig: Map<string, string>): Observable<boolean> { + return this.http + .patch<boolean>(`${this.serverUrl}system/config/app`, { newConfig }) + .pipe(catchError((err: ApiError) => { + console.warn(`setAppConfig Api-Error: ${err.code} ${err.info}`); + return of(false); + })); + } } diff --git a/src/app/superadmin/settings/app-config.component.html b/src/app/superadmin/settings/app-config.component.html new file mode 100644 index 0000000000000000000000000000000000000000..88b455d15fc5b1646868eac256f55fe38e8c0255 --- /dev/null +++ b/src/app/superadmin/settings/app-config.component.html @@ -0,0 +1,48 @@ +<form [formGroup]="configForm" fxFlex fxLayout="column" fxLayoutAlign="start stretch"> + <p>Warnung auf der Startseite</p> + <div class="block-ident" fxLayout="column"> + <mat-form-field> + <mat-label>Text</mat-label> + <input matInput formControlName="globalWarningText" placeholder="Warnung"> + </mat-form-field> + <div fxLayout="row wrap" fxLayoutAlign="start center" fxLayoutGap="20px"> + <p>Zeige Warnung bis</p> + <mat-form-field> + <mat-label>Datum</mat-label> + <input matInput formControlName="globalWarningExpiredDay" [matDatepicker]="picker"> + <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle> + <mat-datepicker #picker></mat-datepicker> + </mat-form-field> + <mat-form-field> + <mat-select placeholder="Stunde" formControlName="globalWarningExpiredHour"> + <mat-option *ngFor="let m of expiredHours | keyvalue" [value]="m.key"> + {{m.value}} + </mat-option> + </mat-select> + </mat-form-field> + <p *ngIf="warningIsExpired" class="warning-warning">Zeitpunkt ist in der Vergangenheit.</p> + </div> + </div> + <mat-form-field fxLayout="column"> + <mat-label>Name der Anwendung</mat-label> + <input matInput formControlName="appTitle" placeholder="Name"> + </mat-form-field> + <mat-form-field fxLayout="column" fxLayoutAlign="start stretch"> + <mat-label>Html-Inhalt für die Startseite rechts</mat-label> + <textarea matInput formControlName="introHtml" + cdkTextareaAutosize + cdkAutosizeMinRows="6"></textarea> + </mat-form-field> + <mat-form-field fxLayout="column" fxLayoutAlign="start stretch"> + <mat-label>Html-Inhalt für die Impressum-/Datenschutzseite</mat-label> + <textarea matInput formControlName="impressumHtml" + cdkTextareaAutosize + cdkAutosizeMinRows="6"></textarea> + </mat-form-field> + <div fxLayout="row" fxLayoutGap="30px" fxLayoutAlign="start start"> + <button mat-raised-button color="primary" [disabled]="!dataChanged" (click)="saveData()"> + Speichern + </button> + <div *ngIf="dataChanged" fxFlex>Nach dem Speichern bitte die Seite neu laden, damit die Änderungen wirksam werden!</div> + </div> +</form> diff --git a/src/app/superadmin/settings/app-config.component.ts b/src/app/superadmin/settings/app-config.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..41b216bb135c1632663a28785a24673b26838ece --- /dev/null +++ b/src/app/superadmin/settings/app-config.component.ts @@ -0,0 +1,117 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { Subscription } from 'rxjs'; +import { AppConfig } from '../../config/app.config'; +import { MainDataService } from '../../maindata.service'; +import { BackendService } from '../backend.service'; + +@Component({ + selector: 'app-app-config', + templateUrl: 'app-config.component.html', + styles: [ + '.example-chip-list {width: 100%;}', + '.block-ident {margin-left: 40px}', + '.warning-warning { color: darkgoldenrod }' + ] +}) + +export class AppConfigComponent implements OnInit, OnDestroy { + configForm: FormGroup; + dataChanged = false; + private configDataChangedSubscription: Subscription = null; + warningIsExpired = false; + expiredHours = { + '': '', + '01': '01:00 Uhr', + '02': '02:00 Uhr', + '03': '03:00 Uhr', + '04': '04:00 Uhr', + '05': '05:00 Uhr', + '06': '06:00 Uhr', + '07': '07:00 Uhr', + '08': '08:00 Uhr', + '09': '09:00 Uhr', + 10: '10:00 Uhr', + 11: '11:00 Uhr', + 12: '12:00 Uhr', + 13: '13:00 Uhr', + 14: '14:00 Uhr', + 15: '15:00 Uhr', + 16: '16:00 Uhr', + 17: '17:00 Uhr', + 18: '18:00 Uhr', + 19: '19:00 Uhr', + 20: '20:00 Uhr', + 21: '21:00 Uhr', + 22: '22:00 Uhr', + 23: '23:00 Uhr' + }; + + constructor( + private fb: FormBuilder, + private snackBar: MatSnackBar, + private mds: MainDataService, + private bs: BackendService + ) { } + + ngOnInit(): void { + setTimeout(() => { + const appConfig = this.mds.appConfig.getAppConfig(); + this.configForm = this.fb.group({ + appTitle: this.fb.control(appConfig.get('app_title')), + introHtml: this.fb.control(appConfig.get('intro_html')), + impressumHtml: this.fb.control(appConfig.get('impressum_html')), + globalWarningText: this.fb.control(appConfig.get('global_warning')), + globalWarningExpiredDay: this.fb.control(appConfig.get('global_warning_expired_day')), + globalWarningExpiredHour: this.fb.control(appConfig.get('global_warning_expired_hour')) + }); + this.warningIsExpired = AppConfig.isWarningExpired( + appConfig.get('global_warning_expired_day'), + appConfig.get('global_warning_expired_hour') + ); + this.configDataChangedSubscription = this.configForm.valueChanges.subscribe(() => { + this.warningIsExpired = AppConfig.isWarningExpired( + this.configForm.get('globalWarningExpiredDay').value, + this.configForm.get('globalWarningExpiredHour').value + ); + this.dataChanged = true; + }); + }); + } + + saveData(): void { + const appConfig = new Map<string, string>(); + appConfig.set('app_title', this.configForm.get('appTitle').value); + // todo appConfig.set('mainLogo', this.mainLogo); + // todo appConfig.set('background_body', this.background_body); + // todo appConfig.set('background_box', this.background_box); + appConfig.set('intro_html', this.configForm.get('introHtml').value); + appConfig.set('impressum_html', this.configForm.get('impressumHtml').value); + appConfig.set('global_warning', this.configForm.get('globalWarningText').value); + appConfig.set('global_warning_expired_day', this.configForm.get('globalWarningExpiredDay').value); + appConfig.set('global_warning_expired_hour', this.configForm.get('globalWarningExpiredHour').value); + + this.mds.appConfig.setAppConfig(appConfig); + this.mds.appConfig.applyBackgroundColors(); + this.bs.setAppConfig(appConfig).subscribe(isOk => { + if (isOk) { + this.snackBar.open( + 'Konfigurationsdaten der Anwendung gespeichert', 'Info', { duration: 3000 } + ); + this.dataChanged = false; + this.mds.appConfig.setAppConfig(appConfig); + this.mds.appConfig.applyBackgroundColors(); + } else { + this.snackBar.open('Konnte Konfigurationsdaten der Anwendung nicht speichern', 'Fehler', { duration: 3000 }); + } + }, + () => { + this.snackBar.open('Konnte Konfigurationsdaten der Anwendung nicht speichern', 'Fehler', { duration: 3000 }); + }); + } + + ngOnDestroy(): void { + if (this.configDataChangedSubscription !== null) this.configDataChangedSubscription.unsubscribe(); + } +} diff --git a/src/app/superadmin/settings/edit-custom-texts.component.ts b/src/app/superadmin/settings/edit-custom-texts.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..f68327e9eb3649b682deae39ccfd6e8766096812 --- /dev/null +++ b/src/app/superadmin/settings/edit-custom-texts.component.ts @@ -0,0 +1,21 @@ +import { Component } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { FormBuilder } from '@angular/forms'; +import { BackendService as MainDataService } from '../../backend.service'; +import { BackendService } from '../backend.service'; + +@Component({ + selector: 'app-custom-texts', + template: `<p> + coming soon + </p>` +}) + +export class EditCustomTextsComponent { + constructor( + private fb: FormBuilder, + private snackBar: MatSnackBar, + private mbs: MainDataService, + private bs: BackendService + ) { } +} diff --git a/src/app/superadmin/settings/settings.component.ts b/src/app/superadmin/settings/settings.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..6258464d54886f9bcc7081b590b4e8500756184f --- /dev/null +++ b/src/app/superadmin/settings/settings.component.ts @@ -0,0 +1,26 @@ +import { Component } from '@angular/core'; + +@Component({ + template: ` + <div fxLayout="column" fxLayoutAlign="start stretch" class="admin-tab-content"> + <div fxLayout="row" class="div-row"> + <div fxFlex="48"> + <mat-label>Text-Ersetzungen</mat-label> + </div> + <div fxFlex="48"> + <app-custom-texts></app-custom-texts> + </div> + </div> + <div fxLayout="row" class="div-row"> + <div fxFlex="48"> + <mat-label>Konfiguration der Anwendung</mat-label> + </div> + <div fxFlex="48"> + <app-app-config></app-app-config> + </div> + </div> + </div> + `, + styles: ['.div-row {border-color: gray; border-width: 0 0 1px 0; border-style: solid; margin-top: 10px}'] +}) +export class SettingsComponent {} diff --git a/src/app/superadmin/superadmin-routing.module.ts b/src/app/superadmin/superadmin-routing.module.ts index c1e5dbb542ed3b3f9ca79546aff1ede85eb67dbf..2fd375db68cec267f6d8eb10c2ec538cbb04d2b6 100644 --- a/src/app/superadmin/superadmin-routing.module.ts +++ b/src/app/superadmin/superadmin-routing.module.ts @@ -3,6 +3,7 @@ import { Routes, RouterModule } from '@angular/router'; import { WorkspacesComponent } from './workspaces/workspaces.component'; import { UsersComponent } from './users/users.component'; import { SuperadminComponent } from './superadmin.component'; +import { SettingsComponent } from './settings/settings.component'; const routes: Routes = [ { @@ -12,6 +13,7 @@ const routes: Routes = [ { path: '', redirectTo: 'users', pathMatch: 'full' }, { path: 'users', component: UsersComponent }, { path: 'workspaces', component: WorkspacesComponent }, + { path: 'settings', component: SettingsComponent }, { path: '**', component: UsersComponent } ] } diff --git a/src/app/superadmin/superadmin.component.ts b/src/app/superadmin/superadmin.component.ts index c802b3675313c91c77f288d25ad9c1c8137152a0..316cbfa56d8f11523e4da80aec915c58aaf311d9 100644 --- a/src/app/superadmin/superadmin.component.ts +++ b/src/app/superadmin/superadmin.component.ts @@ -12,7 +12,8 @@ export class SuperadminComponent implements OnInit { navLinks = [ { path: 'users', label: 'Users' }, - { path: 'workspaces', label: 'Arbeitsbereiche' } + { path: 'workspaces', label: 'Arbeitsbereiche' }, + { path: 'settings', label: 'Einstellungen' } ]; ngOnInit():void { diff --git a/src/app/superadmin/superadmin.module.ts b/src/app/superadmin/superadmin.module.ts index 585122802ca9b0a7fc5db30bba9f2da763c55637..ddc22a272f35eb822814dda12060f31c2c01f90d 100644 --- a/src/app/superadmin/superadmin.module.ts +++ b/src/app/superadmin/superadmin.module.ts @@ -18,6 +18,8 @@ import { MatInputModule } from '@angular/material/input'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatGridListModule } from '@angular/material/grid-list'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MAT_DATE_LOCALE, MatNativeDateModule } from '@angular/material/core'; import { FlexLayoutModule } from '@angular/flex-layout'; import { WorkspacesComponent } from './workspaces/workspaces.component'; import { UsersComponent } from './users/users.component'; @@ -31,6 +33,9 @@ import { EditworkspaceComponent } from './workspaces/editworkspace/editworkspace import { SuperadminPasswordRequestComponent } from './superadmin-password-request/superadmin-password-request.component'; +import { SettingsComponent } from './settings/settings.component'; +import { AppConfigComponent } from './settings/app-config.component'; +import { EditCustomTextsComponent } from './settings/edit-custom-texts.component'; @NgModule({ declarations: [ @@ -41,7 +46,10 @@ import { NewworkspaceComponent, EditworkspaceComponent, WorkspacesComponent, - SuperadminPasswordRequestComponent + SettingsComponent, + SuperadminPasswordRequestComponent, + AppConfigComponent, + EditCustomTextsComponent ], imports: [ CommonModule, @@ -66,6 +74,8 @@ import { MatSnackBarModule, MatGridListModule, MatCardModule, + MatNativeDateModule, + MatDatepickerModule, FlexLayoutModule ], exports: [ @@ -78,7 +88,10 @@ import { EditworkspaceComponent ], providers: [ - BackendService + BackendService, + [ + { provide: MAT_DATE_LOCALE, useValue: 'de-DE' } + ] ] }) export class SuperadminModule { }