diff --git a/api/src/model/entitites/log/email.log.entity.ts b/api/src/model/entitites/log/email.log.entity.ts index fecb27f..2e9eeb9 100644 --- a/api/src/model/entitites/log/email.log.entity.ts +++ b/api/src/model/entitites/log/email.log.entity.ts @@ -34,6 +34,9 @@ export class EmailLog { @Column({type: 'boolean'}) success: boolean; + @Column({type: 'text', default: null}) + context: boolean; + @AfterLoad() setType() { this.eventName = EmailEvent[this.type] diff --git a/api/src/model/entitites/user/user.settings.entity.ts b/api/src/model/entitites/user/user.settings.entity.ts index d021666..a9f3b08 100644 --- a/api/src/model/entitites/user/user.settings.entity.ts +++ b/api/src/model/entitites/user/user.settings.entity.ts @@ -20,6 +20,9 @@ export class UserSettings { @Column({ name: 'send_system_update_notification', default: true, type: 'boolean'}) sendSystemUpdateMails: boolean; + @Column({ name: 'ui_scale', default: 'm' }) + uiScale: 's' | 'm' | 'l'; + } \ No newline at end of file diff --git a/api/src/modules/key/key.module.ts b/api/src/modules/key/key.module.ts index ac2fe59..9c6eb3d 100644 --- a/api/src/modules/key/key.module.ts +++ b/api/src/modules/key/key.module.ts @@ -5,10 +5,11 @@ import { DatabaseModule } from 'src/shared/database/database.module'; import { AuthModule } from '../auth/auth.module'; import { SharedServiceModule } from 'src/shared/service/shared.service.module'; import { ConfigService } from '@nestjs/config'; +import { MailModule } from '../mail/mail.module'; @Module({ controllers: [KeyController], providers: [KeyService, ConfigService], - imports: [DatabaseModule, AuthModule, SharedServiceModule], + imports: [DatabaseModule, AuthModule, SharedServiceModule, MailModule], }) export class KeyModule {} diff --git a/api/src/modules/key/key.service.ts b/api/src/modules/key/key.service.ts index 8b46a3c..abb3a52 100644 --- a/api/src/modules/key/key.service.ts +++ b/api/src/modules/key/key.service.ts @@ -11,6 +11,7 @@ import { HelperService } from 'src/shared/service/system.helper.service'; import { FindOperator, IsNull, Not } from 'typeorm'; import { faker } from '@faker-js/faker'; import { ConfigService } from '@nestjs/config'; +import { MailService } from '../mail/mail.service'; @Injectable() export class KeyService { @@ -20,7 +21,8 @@ export class KeyService { private readonly handoverRepo: KeyHandoutRepository, private readonly activityService: ActivityHelperService, private readonly helper: HelperService, - private readonly configService: ConfigService + private readonly configService: ConfigService, + private readonly mailService: MailService, ) {} get isDevelopMode(): boolean { @@ -109,6 +111,21 @@ export class KeyService { ); this.activityService.logKeyHandover(user, key, key.cylinder[0].system, res); + try { + if (key && key.cylinder && key.cylinder[0].system) { + const managerOb: Key = await this.keyrepository.findOne({ + where: { id: keyID }, + relations: [ 'cylinder', 'cylinder.system', 'cylinder.system.managers', 'cylinder.system.managers.settings' ] + }); + console.log(managerOb.cylinder[0].system.managers) + managerOb.cylinder[0].system.managers.filter(m => m.settings.sendSystemUpdateMails).forEach(m => { + this.mailService.sendKeyHandoutMail({ to: m, key, handoutAction: res }) + }) + } + } catch (e){ + console.log(e) + } + return res; } diff --git a/api/src/modules/log/log.service.ts b/api/src/modules/log/log.service.ts index d532239..46a09c3 100644 --- a/api/src/modules/log/log.service.ts +++ b/api/src/modules/log/log.service.ts @@ -19,11 +19,12 @@ export class LogService { private async logEmail(data: EmailLogDto) { const log = this.emailLogRepo.create(data); + console.log(log) const logEntry = await this.emailLogRepo.save(log); } private async logAuthEvent(data: User) { - console.error("auth logging not implemented") + // console.error("auth logging not implemented") } } @@ -34,7 +35,8 @@ export enum LogType { export enum EmailEvent { GrantSystemAccess, - RemoveSystemAccess + RemoveSystemAccess, + KeyHandout } export interface EmailLogDto { diff --git a/api/src/modules/mail/mail.service.ts b/api/src/modules/mail/mail.service.ts index b9d3417..7cf44f1 100644 --- a/api/src/modules/mail/mail.service.ts +++ b/api/src/modules/mail/mail.service.ts @@ -3,7 +3,7 @@ import { Injectable } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { EmailEvent, LogService, LogType } from "../log/log.service"; import { KeySystem } from "src/model/entitites/system.entity"; -import { User } from "src/model/entitites"; +import { Key, KeyHandout, User } from "src/model/entitites"; @Injectable() export class MailService { @@ -14,6 +14,46 @@ export class MailService { ) { } + async sendKeyHandoutMail({to, key, handoutAction}: {to: User, key: Key, handoutAction: KeyHandout}) { + const keyAction = handoutAction.direction == 'out' ? 'wurde ausgegeben' : 'wurde zurückgegeben'; + const keyExtendedAction = handoutAction.direction == 'return' ? `wurde von ${handoutAction.customer.name} zurückgegeben` : `wurde an ${handoutAction.customer.name} ausgegeben`; + const subject = handoutAction.direction == 'out' ? 'Schlüssel ausgegeben' : 'Schlüssel zurückgegeben'; + const context = { + keyAction, + keyExtendedAction, + firstName: to.firstName, + keyNr: key.nr, + keyName: key.name, + url: 'https://keyvaultpro.de/keys?nr=' + key.nr + } + this.mailserService.sendMail({ + template: './key-handout-changed', + to: to.username, + from: this.configService.get('MAILER_FROM'), + subject: subject, + context + }).then(v => { + + this.logService.log(LogType.Mail, { + to: to.username, + success: true, + message: v.response, + type: EmailEvent.KeyHandout, + system: key.cylinder[0].system, + context: JSON.stringify(handoutAction) + }) + }).catch(e => { + this.logService.log(LogType.Mail, { + to, + success: false, + message: e.response, + type: EmailEvent.KeyHandout, + system: key.cylinder[0].system, + context: JSON.stringify(handoutAction) + }) + }) + } + async sendAccessGrantedMail({to, system}: {to: User, system: KeySystem}) { this.mailserService.sendMail({ template: './access', diff --git a/api/src/templates/key-handout-changed.hbs b/api/src/templates/key-handout-changed.hbs new file mode 100644 index 0000000..2078a34 --- /dev/null +++ b/api/src/templates/key-handout-changed.hbs @@ -0,0 +1,94 @@ + + + + + + Zugriff gewährt + + + +
+
+

Schlüssel {{ keyAction }}

+
+
+

Hallo {{firstName}},

+

der Schlüssel {{ keyName }} ({{ keyNr }}) wurde {{ keyExtendedAction }}

+ +
+ +
+ + diff --git a/client/angular.json b/client/angular.json index 92a55b1..a925bc3 100644 --- a/client/angular.json +++ b/client/angular.json @@ -34,7 +34,6 @@ "styles": [ "@angular/material/prebuilt-themes/azure-blue.css", "src/styles.scss", - "src/styles/ag.css", "node_modules/@ngxpert/hot-toast/src/styles/styles.css" ], "scripts": [] diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index 2f9fb2e..c079538 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts @@ -5,6 +5,7 @@ import { BehaviorSubject, Observable, tap, of, catchError } from 'rxjs'; import { IUser } from '../../model/interface/user.interface'; import { environment } from '../../../environments/environment'; import { HotToastService } from '@ngxpert/hot-toast'; +import { ApiService } from '../../shared/api.service'; @Injectable({ providedIn: 'root' @@ -16,6 +17,7 @@ export class AuthService { private http: HttpClient = inject(HttpClient); private router: Router = inject(Router); private toast: HotToastService = inject(HotToastService); + private api: ApiService = inject(ApiService); private _user: IUser | null = null; @@ -35,21 +37,27 @@ export class AuthService { return this.user != null && this.user.role == 'admin'; } - getMe() { + async getMe() { if (!this.getAccessToken()) { return false; } - return new Promise(resolve => { - this.http.get('/api/auth/me').subscribe({ - next: user => { - this._user = user; - resolve(true) - }, - error: () => { - resolve(false) - } - }) - }) + const user = await this.api.getMe(); + if (user) { + this._user = user; + return Promise.resolve(true); + } + return Promise.resolve(false) + // return new Promise(resolve => { + // this.http.get('/api/auth/me').subscribe({ + // next: user => { + // this._user = user; + // resolve(true) + // }, + // error: () => { + // resolve(false) + // } + // }) + // }) } diff --git a/client/src/app/modules/admin/all-users/all-users.component.html b/client/src/app/modules/admin/all-users/all-users.component.html index e87e1ba..f8e1ca2 100644 --- a/client/src/app/modules/admin/all-users/all-users.component.html +++ b/client/src/app/modules/admin/all-users/all-users.component.html @@ -1,7 +1,8 @@ -@if (gridOptions || true) { +@if (myTheme && gridOptions) { } \ No newline at end of file diff --git a/client/src/app/modules/admin/all-users/all-users.component.ts b/client/src/app/modules/admin/all-users/all-users.component.ts index ec18741..1ab6573 100644 --- a/client/src/app/modules/admin/all-users/all-users.component.ts +++ b/client/src/app/modules/admin/all-users/all-users.component.ts @@ -8,6 +8,7 @@ import { AuthService } from '../../../core/auth/auth.service'; import { DatePipe } from '@angular/common'; import { AG_GRID_LOCALE_DE } from '@ag-grid-community/locale'; import { MatButtonModule } from '@angular/material/button'; +import { AgGridContainerComponent } from '../../../shared/ag-grid/components/ag-grid-container/ag-grid-container.component'; @Component({ selector: 'app-all-users', @@ -16,7 +17,7 @@ import { MatButtonModule } from '@angular/material/button'; templateUrl: './all-users.component.html', styleUrl: './all-users.component.scss' }) -export class AllUsersComponent { +export class AllUsersComponent extends AgGridContainerComponent { private toast: HotToastService = inject(HotToastService); private api: ApiService = inject(ApiService); diff --git a/client/src/app/modules/cylinder/cylinder.component.html b/client/src/app/modules/cylinder/cylinder.component.html index e4e1820..ebcfad8 100644 --- a/client/src/app/modules/cylinder/cylinder.component.html +++ b/client/src/app/modules/cylinder/cylinder.component.html @@ -1,8 +1,11 @@ - +}
diff --git a/client/src/app/modules/cylinder/cylinder.component.ts b/client/src/app/modules/cylinder/cylinder.component.ts index 5e9a986..af2c3e1 100644 --- a/client/src/app/modules/cylinder/cylinder.component.ts +++ b/client/src/app/modules/cylinder/cylinder.component.ts @@ -9,6 +9,7 @@ import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { CreateCylinderComponent } from './components/create-cylinder/create-cylinder.component'; import { MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; +import { AgGridContainerComponent } from '../../shared/ag-grid/components/ag-grid-container/ag-grid-container.component'; @Component({ selector: 'app-cylinder', @@ -17,7 +18,7 @@ import { MatButtonModule } from '@angular/material/button'; templateUrl: './cylinder.component.html', styleUrl: './cylinder.component.scss' }) -export class CylinderComponent { +export class CylinderComponent extends AgGridContainerComponent { private api: ApiService = inject(ApiService); private datePipe = inject(DatePipe); private dialog = inject(MatDialog); @@ -28,6 +29,7 @@ export class CylinderComponent { constructor() { + super(); this.gridOptions.columnDefs = [ { field: 'name', headerName: 'Name', sort: 'asc', flex: 1, filter: true }, diff --git a/client/src/app/modules/dashboard/dashboard.component.html b/client/src/app/modules/dashboard/dashboard.component.html index bd910db..a9827b0 100644 --- a/client/src/app/modules/dashboard/dashboard.component.html +++ b/client/src/app/modules/dashboard/dashboard.component.html @@ -73,6 +73,7 @@ style="width: 100%; height: 100%;" [gridOptions]="gridOptions" (gridReady)="onGridReady($event)" + [theme]="myTheme" > diff --git a/client/src/app/modules/dashboard/dashboard.component.ts b/client/src/app/modules/dashboard/dashboard.component.ts index 4b3855c..65340cd 100644 --- a/client/src/app/modules/dashboard/dashboard.component.ts +++ b/client/src/app/modules/dashboard/dashboard.component.ts @@ -9,6 +9,7 @@ import { RouterModule } from '@angular/router'; import { AgLoadingComponent } from '../../shared/ag-grid/components/ag-loading/ag-loading.component'; import { AG_GRID_LOCALE_DE } from '@ag-grid-community/locale'; import { MatButtonModule } from '@angular/material/button'; +import { AgGridContainerComponent } from '../../shared/ag-grid/components/ag-grid-container/ag-grid-container.component'; @Component({ selector: 'app-dashboard', @@ -17,7 +18,7 @@ import { MatButtonModule } from '@angular/material/button'; templateUrl: './dashboard.component.html', styleUrl: './dashboard.component.scss' }) -export class DashboardComponent { +export class DashboardComponent extends AgGridContainerComponent { private api = inject(ApiService); private datePipe = inject(DatePipe); diff --git a/client/src/app/modules/keys/keys.component.html b/client/src/app/modules/keys/keys.component.html index 9e87b24..dbd5f38 100644 --- a/client/src/app/modules/keys/keys.component.html +++ b/client/src/app/modules/keys/keys.component.html @@ -1,8 +1,11 @@ - +}
diff --git a/client/src/app/modules/keys/keys.component.ts b/client/src/app/modules/keys/keys.component.ts index 369e4b2..390af43 100644 --- a/client/src/app/modules/keys/keys.component.ts +++ b/client/src/app/modules/keys/keys.component.ts @@ -1,7 +1,7 @@ import { Component, inject } from '@angular/core'; import { AG_GRID_LOCALE_DE } from '@ag-grid-community/locale'; import { AgGridAngular } from 'ag-grid-angular'; -import { GridOptions,GridApi, GridReadyEvent, CellEditingStoppedEvent, ICellEditorParams, FilterActionParams, FilterAction } from 'ag-grid-community'; +import { GridOptions,GridApi, GridReadyEvent, CellEditingStoppedEvent, ICellEditorParams, FilterActionParams, FilterAction, themeQuartz, Theme, ThemeDefaultParams } from 'ag-grid-community'; import { DatePipe } from '@angular/common'; import { ApiService } from '../../shared/api.service'; import { IKey } from '../../model/interface/key.interface'; @@ -20,6 +20,8 @@ import { MatTooltipModule } from '@angular/material/tooltip'; import { SelectKeyCylinderComponent } from './create/select-key-cylinder/select-key-cylinder.component'; import { ActivatedRoute, Route } from '@angular/router'; import { ModuleRegistry } from 'ag-grid-community'; +import { AgGridService } from '../../shared/ag-grid/ag-grid.service'; +import { AgGridContainerComponent } from '../../shared/ag-grid/components/ag-grid-container/ag-grid-container.component'; @Component({ selector: 'app-keys', @@ -28,12 +30,14 @@ import { ModuleRegistry } from 'ag-grid-community'; templateUrl: './keys.component.html', styleUrl: './keys.component.scss' }) -export class KeysComponent { +export class KeysComponent extends AgGridContainerComponent { private api: ApiService = inject(ApiService); private datePipe = inject(DatePipe); private toast: HotToastService = inject(HotToastService); private dialog: MatDialog = inject(MatDialog); - private route: ActivatedRoute = inject(ActivatedRoute) + private route: ActivatedRoute = inject(ActivatedRoute); + + // cylinders: any[] = []; @@ -97,7 +101,7 @@ export class KeysComponent { } ], loading: true, - rowHeight: 54, + // rowHeight: 54, loadingOverlayComponent: AgLoadingComponent, pagination: true, } @@ -108,6 +112,7 @@ export class KeysComponent { ngOnInit(): void { } + private setFilterToParams() { @@ -115,11 +120,20 @@ export class KeysComponent { if (Object.keys(params).includes('handedOut')) { this.gridApi.setFilterModel({ - handedOut: { - filterType: 'text', - type: params['handedOut'] - } - }) + handedOut: { + filterType: 'text', + type: params['handedOut'] + } + }) + } if (Object.keys(params).includes('nr')) { + console.log("SET " + params['nr'] ) + this.gridApi.setFilterModel({ + nr: { + filterType: 'text', + type: 'equals', + filter: params['nr'] + } + }) } diff --git a/client/src/app/modules/settings/settings.component.html b/client/src/app/modules/settings/settings.component.html index 9bfb8c1..e78b914 100644 --- a/client/src/app/modules/settings/settings.component.html +++ b/client/src/app/modules/settings/settings.component.html @@ -27,13 +27,13 @@ Email - Wird zum Login benötigt + Wird für den Emailversand benötigt
-
+
Emailbenachrichtigungen
Sende Emails bei:
@@ -48,6 +48,17 @@ Deaktivierung meines Users +
+
Oberfläche
+ + Skalierung + + Klein + Mittel + Groß + + Ändert die Schriftgröße in der Liste + diff --git a/client/src/app/modules/settings/settings.component.scss b/client/src/app/modules/settings/settings.component.scss index 902354b..963141b 100644 --- a/client/src/app/modules/settings/settings.component.scss +++ b/client/src/app/modules/settings/settings.component.scss @@ -47,3 +47,7 @@ font-size: 1.5rem; cursor: pointer; } + +.content { + overflow-y: auto; +} \ No newline at end of file diff --git a/client/src/app/modules/settings/settings.component.ts b/client/src/app/modules/settings/settings.component.ts index 0589f3c..21aea28 100644 --- a/client/src/app/modules/settings/settings.component.ts +++ b/client/src/app/modules/settings/settings.component.ts @@ -8,10 +8,11 @@ import { ApiService } from '../../shared/api.service'; import {MatSlideToggleModule} from '@angular/material/slide-toggle'; import { HotToastService } from '@ngxpert/hot-toast'; import {MatProgressBarModule} from '@angular/material/progress-bar'; +import { MatSelectModule } from '@angular/material/select'; @Component({ selector: 'app-settings', - imports: [MatProgressBarModule, MatFormFieldModule, MatInputModule, MatButtonModule, ReactiveFormsModule, FormsModule, MatSlideToggleModule], + imports: [MatProgressBarModule, MatFormFieldModule, MatInputModule, MatButtonModule, ReactiveFormsModule, FormsModule, MatSlideToggleModule, MatSelectModule], templateUrl: './settings.component.html', styleUrl: './settings.component.scss' }) @@ -36,6 +37,7 @@ export class SettingsComponent { sendSystemAccessMails: new FormControl(false), sendSystemUpdateMails: new FormControl(false), sendUserDisabledMails: new FormControl(false), + uiScale: new FormControl('s') }) open() { @@ -70,25 +72,20 @@ export class SettingsComponent { } - loadSettings() { + async loadSettings() { this.isLoading = true; - this.api.getSettings().subscribe({ - next: (r: any) => { - this.userSettings.patchValue(r) - this.isLoading = false; - } - }) + const settings = await this.api.userSettings; + this.userSettings.patchValue(settings); + this.isLoading = false; } - save() { + async save() { this.isLoading = true; - this.api.updateSettings(this.userSettings.value).subscribe({ - next: () => { - this.toast.success('Gespeichert') - this.isLoading = false; - this.userSettings.markAsPristine(); - } - }); + const res = await this.api.updateSettings(this.userSettings.value); + this.isLoading = false; + if (res) { + this.userSettings.markAsPristine(); + } } saveUser() { diff --git a/client/src/app/modules/system/system.component.html b/client/src/app/modules/system/system.component.html index 1e72ae7..1228c81 100644 --- a/client/src/app/modules/system/system.component.html +++ b/client/src/app/modules/system/system.component.html @@ -1,9 +1,11 @@ +@if (myTheme) { - +}
\ No newline at end of file diff --git a/client/src/app/modules/system/system.component.ts b/client/src/app/modules/system/system.component.ts index c20ab30..a395688 100644 --- a/client/src/app/modules/system/system.component.ts +++ b/client/src/app/modules/system/system.component.ts @@ -1,13 +1,15 @@ import { DatePipe } from '@angular/common'; import { Component, inject } from '@angular/core'; import { AgGridAngular } from 'ag-grid-angular'; -import { GridApi, GridOptions, GridReadyEvent } from 'ag-grid-community'; +import { GridApi, GridOptions, GridReadyEvent, Theme, ThemeDefaultParams } from 'ag-grid-community'; import { ApiService } from '../../shared/api.service'; import { HELPER } from '../../shared/helper.service'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { CreateSystemComponent } from './create/create.component'; import { AgSystemManagerComponent } from '../../shared/ag-grid/components/ag-system-manager/ag-system-manager.component'; +import { AgGridService } from '../../shared/ag-grid/ag-grid.service'; +import { AgGridContainerComponent } from '../../shared/ag-grid/components/ag-grid-container/ag-grid-container.component'; @Component({ selector: 'app-system', @@ -16,31 +18,31 @@ import { AgSystemManagerComponent } from '../../shared/ag-grid/components/ag-sys templateUrl: './system.component.html', styleUrl: './system.component.scss' }) -export class SystemComponent { +export class SystemComponent extends AgGridContainerComponent { private api: ApiService = inject(ApiService); private datePipe = inject(DatePipe); - private dialog: MatDialog = inject(MatDialog); + private dialog: MatDialog = inject(MatDialog); gridApi!: GridApi; - gridOptions: GridOptions = HELPER.getGridOptions(); + constructor() { + super(); this.gridOptions.columnDefs = [ { colId: 'name', field: 'name', headerName: 'Name', sort: 'asc', flex: 1}, { field: 'createdAt', headerName: 'Angelegt', cellRenderer: (data: any) => data.value ? this.datePipe.transform(new Date(data.value)) : '-' }, { field: 'updatedAt', headerName: 'Upgedated', cellRenderer: (data: any) => data.value ? this.datePipe.transform(new Date(data.value)) : '-' }, { - colId: 'actions' - , headerName: 'Aktionen' - , width: 120 - , cellRenderer: AgSystemManagerComponent - // , onCellClicked: (event) => { this.deleteKey(event.data.id)} - } - ] + colId: 'actions' + , headerName: 'Aktionen' + , width: 120 + , cellRenderer: AgSystemManagerComponent + // , onCellClicked: (event) => { this.deleteKey(event.data.id)} + } + ]; } - loadSystems() { this.api.getSystems().subscribe({ next: n => { diff --git a/client/src/app/shared/ag-grid/ag-grid.service.spec.ts b/client/src/app/shared/ag-grid/ag-grid.service.spec.ts new file mode 100644 index 0000000..1ce66a5 --- /dev/null +++ b/client/src/app/shared/ag-grid/ag-grid.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AgGridService } from './ag-grid.service'; + +describe('AgGridService', () => { + let service: AgGridService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AgGridService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/client/src/app/shared/ag-grid/ag-grid.service.ts b/client/src/app/shared/ag-grid/ag-grid.service.ts new file mode 100644 index 0000000..532ca4d --- /dev/null +++ b/client/src/app/shared/ag-grid/ag-grid.service.ts @@ -0,0 +1,66 @@ +import { inject, Injectable } from '@angular/core'; +import { ApiService } from '../api.service'; +import { Theme, ThemeDefaultParams, themeQuartz } from 'ag-grid-community'; + +@Injectable({ + providedIn: 'root', +}) +export class AgGridService { + private api = inject(ApiService); + + private baseConfig = { + + accentColor: "#125312", + backgroundColor: "#FFFFFF", + borderColor: "#D7E2E6", + borderRadius: 2, + browserColorScheme: "light", + cellHorizontalPaddingScale: 0.7, + chromeBackgroundColor: { + ref: "backgroundColor" + }, + columnBorder: false, + fontFamily: { + googleFont: "Roboto" + }, + headerFontSize: 16, + foregroundColor: "#555B62", + headerBackgroundColor: "#FFFFFF", + + headerFontWeight: 400, + headerTextColor: "#84868B", + rowBorder: true, + sidePanelBorder: true, + + wrapperBorder: false, + wrapperBorderRadius: 2, + spacing: 1, + cellHorizontalPadding: 10, + headerVerticalPaddingScale: 5 + + } + + private userScale = { + s: { + fontSize: 12, + rowVerticalPaddingScale: 4 + }, + m: { + fontSize: 16, + rowVerticalPaddingScale: 8 + }, + l: { + fontSize: 18, + rowVerticalPaddingScale: 12 + } + } + + async getGridConfig(): Promise> { + let settings = await this.api.userSettings; + const scale = (this.userScale as any)[(settings as any)['uiScale']]; + const conf = {...this.baseConfig, ...scale}; + return themeQuartz.withParams(conf); + } + + +} diff --git a/client/src/app/shared/ag-grid/components/ag-grid-container/ag-grid-container.component.html b/client/src/app/shared/ag-grid/components/ag-grid-container/ag-grid-container.component.html new file mode 100644 index 0000000..ba15647 --- /dev/null +++ b/client/src/app/shared/ag-grid/components/ag-grid-container/ag-grid-container.component.html @@ -0,0 +1 @@ +

ag-grid-container works!

diff --git a/client/src/app/shared/ag-grid/components/ag-grid-container/ag-grid-container.component.scss b/client/src/app/shared/ag-grid/components/ag-grid-container/ag-grid-container.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/client/src/app/shared/ag-grid/components/ag-grid-container/ag-grid-container.component.spec.ts b/client/src/app/shared/ag-grid/components/ag-grid-container/ag-grid-container.component.spec.ts new file mode 100644 index 0000000..012daf0 --- /dev/null +++ b/client/src/app/shared/ag-grid/components/ag-grid-container/ag-grid-container.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AgGridContainerComponent } from './ag-grid-container.component'; + +describe('AgGridContainerComponent', () => { + let component: AgGridContainerComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AgGridContainerComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(AgGridContainerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/shared/ag-grid/components/ag-grid-container/ag-grid-container.component.ts b/client/src/app/shared/ag-grid/components/ag-grid-container/ag-grid-container.component.ts new file mode 100644 index 0000000..39a6136 --- /dev/null +++ b/client/src/app/shared/ag-grid/components/ag-grid-container/ag-grid-container.component.ts @@ -0,0 +1,31 @@ +import { Component, inject } from '@angular/core'; +import { Theme, ThemeDefaultParams } from 'ag-grid-community'; +import { AgGridService } from '../../ag-grid.service'; +import { ApiService } from '../../../api.service'; +import { filter } from 'rxjs'; + +@Component({ + selector: 'app-ag-grid-container', + imports: [], + templateUrl: './ag-grid-container.component.html', + styleUrl: './ag-grid-container.component.scss', +}) +export class AgGridContainerComponent { + myTheme: Theme = null!; + private gridService: AgGridService = inject(AgGridService); + private apiService: ApiService = inject(ApiService); + + constructor() { + this.loadAgTheme(); + } + + + private async loadAgTheme() { + this.apiService.userSettings; + this.apiService.settings.subscribe(async (v) => { + if (v != null) { + this.myTheme = await this.gridService.getGridConfig() + } + }) + } +} diff --git a/client/src/app/shared/api.service.ts b/client/src/app/shared/api.service.ts index 41e1f99..98a6766 100644 --- a/client/src/app/shared/api.service.ts +++ b/client/src/app/shared/api.service.ts @@ -17,9 +17,26 @@ export class ApiService { public cylinders: BehaviorSubject = new BehaviorSubject([]); + public user: BehaviorSubject = new BehaviorSubject(null!); + public settings: BehaviorSubject = new BehaviorSubject(null!); + constructor() { } + getMe(): Promise { + return new Promise(resolve => { + this.http.get('/api/auth/me').subscribe({ + next: user => { + this.user.next(user); + resolve(user) + }, + error: () => { + resolve(null!) + } + }) + }) + } + getAllUsers(): Observable { return this.http.get('/api/user'); @@ -157,11 +174,44 @@ export class ApiService { return this.http.post('api/cylinder', data); } - getSettings(): Observable { - return this.http.get('api/user/settings') + private loadSettings(): Promise { + return new Promise(resolve => { + this.http.get('api/user/settings').subscribe({ + next: val => { + this.settings.next(val); + return resolve(val); + }, + error: () => { + this.toast.error("Einstellungen konnten nicht geladen werden"); + } + }) + }) } - updateSettings(settings: any): Observable { - return this.http.post('api/user/settings', settings) + get userSettings(): Promise { + if (!this.settings.value) { + return this.loadSettings(); + } + return new Promise(resolve => { + return resolve(this.settings.value); + }) + + } + + updateSettings(settings: any): Promise { + return new Promise(resolve => { + this.http.post('api/user/settings', settings).subscribe({ + next: async () => { + await this.loadSettings(); + this.toast.success('Einstellungen gespeichert') + return resolve(true); + }, + error: () => { + this.toast.error("Fehler beim Speichern der Einstellungen"); + return resolve(false) + } + }) + + }) } } diff --git a/client/src/app/shared/helper.service.ts b/client/src/app/shared/helper.service.ts index 9481fdf..b83f1d2 100644 --- a/client/src/app/shared/helper.service.ts +++ b/client/src/app/shared/helper.service.ts @@ -11,8 +11,6 @@ export class HELPER { columnDefs: [], loading: true, loadingOverlayComponent: AgLoadingComponent, - rowHeight: 54, - } } } \ No newline at end of file diff --git a/client/src/styles.scss b/client/src/styles.scss index 042d5b4..96cf9d0 100644 --- a/client/src/styles.scss +++ b/client/src/styles.scss @@ -1,8 +1,8 @@ /* You can add global styles to this file, and also import other style files */ /* Core Data Grid CSS */ -@import "ag-grid-community/styles/ag-grid.css"; +// @import "ag-grid-community/styles/ag-grid.css"; /* Quartz Theme Specific CSS */ -@import 'ag-grid-community/styles/ag-theme-quartz.css'; +// @import 'ag-grid-community/styles/ag-theme-quartz.css'; @tailwind base; @tailwind components; @@ -46,11 +46,11 @@ html, body { padding: 4px; box-sizing: border-box; border-radius: 6px; - background-size: 20px; + background-size: 16px; background-position: center; background-repeat: no-repeat; - width: 38px; - height: 38px; + width: 28px; + height: 28px; transition: box-shadow 0.1s ease-in-out; &:hover {