chore: move projects to src dir
This commit is contained in:
12
src/DigitalData.UserManager.API/.config/dotnet-tools.json
Normal file
12
src/DigitalData.UserManager.API/.config/dotnet-tools.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "9.0.3",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/DigitalData.UserManager.API/ClientApp/user_manager_ui/.gitignore
vendored
Normal file
42
src/DigitalData.UserManager.API/ClientApp/user_manager_ui/.gitignore
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
||||
|
||||
# Compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
/bazel-out
|
||||
|
||||
# Node
|
||||
/node_modules
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# IDEs and editors
|
||||
.idea/
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# Miscellaneous
|
||||
/.angular/cache
|
||||
.sass-cache/
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,27 @@
|
||||
# UserManagerUi
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.8.
|
||||
|
||||
## Development server
|
||||
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
||||
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"user_manager_ui": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss"
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"options": {
|
||||
"outputPath": "dist/user_manager_ui",
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||
"@angular/material/prebuilt-themes/indigo-pink.css",
|
||||
"src/styles.scss",
|
||||
"src/dd-mat-theme.scss"
|
||||
],
|
||||
"scripts": [
|
||||
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
|
||||
],
|
||||
"server": "src/main.server.ts",
|
||||
"prerender": true,
|
||||
"ssr": false
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "1mb",
|
||||
"maximumError": "3mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "2.5mb"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all",
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "user_manager_ui:build:production"
|
||||
},
|
||||
"development": {
|
||||
"proxyConfig": "proxy.conf.json",
|
||||
"buildTarget": "user_manager_ui:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"buildTarget": "user_manager_ui:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"@angular/material/prebuilt-themes/indigo-pink.css",
|
||||
"src/styles.scss",
|
||||
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||
"src/dd-mat-theme.scss"
|
||||
],
|
||||
"scripts": [
|
||||
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "user-manager-ui",
|
||||
"version": "4.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start:ssl": "ng serve --ssl -o",
|
||||
"start": "ng serve -o",
|
||||
"build": "ng build --configuration production",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"serve:ssr:user_manager_ui": "node dist/user_manager_ui/server/server.mjs"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^17.3.0",
|
||||
"@angular/cdk": "^17.3.10",
|
||||
"@angular/common": "^17.3.0",
|
||||
"@angular/compiler": "^17.3.0",
|
||||
"@angular/core": "^17.3.0",
|
||||
"@angular/forms": "^17.3.0",
|
||||
"@angular/material": "^17.3.10",
|
||||
"@angular/material-moment-adapter": "^17.3.10",
|
||||
"@angular/platform-browser": "^17.3.0",
|
||||
"@angular/platform-browser-dynamic": "^17.3.0",
|
||||
"@angular/platform-server": "^17.3.0",
|
||||
"@angular/router": "^17.3.0",
|
||||
"@angular/ssr": "^17.3.8",
|
||||
"@generic-ui/fabric": "^0.21.0",
|
||||
"@generic-ui/hermes": "^0.21.0",
|
||||
"@generic-ui/ngx-grid": "^0.21.0",
|
||||
"bootstrap": "^5.3.3",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"express": "^4.18.2",
|
||||
"rxjs": "~7.8.0",
|
||||
"sweetalert2": "^11.12.3",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^17.3.8",
|
||||
"@angular/cli": "^17.3.8",
|
||||
"@angular/compiler-cli": "^17.3.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^18.18.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"typescript": "~5.4.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"/api": {
|
||||
"target": "https://localhost:7052",
|
||||
"secure": false,
|
||||
"changeOrigin": true
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,56 @@
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
import { CommonEngine } from '@angular/ssr';
|
||||
import express from 'express';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
import bootstrap from './src/main.server';
|
||||
|
||||
// The Express app is exported so that it can be used by serverless Functions.
|
||||
export function app(): express.Express {
|
||||
const server = express();
|
||||
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
|
||||
const browserDistFolder = resolve(serverDistFolder, '../browser');
|
||||
const indexHtml = join(serverDistFolder, 'index.server.html');
|
||||
|
||||
const commonEngine = new CommonEngine();
|
||||
|
||||
server.set('view engine', 'html');
|
||||
server.set('views', browserDistFolder);
|
||||
|
||||
// Example Express Rest API endpoints
|
||||
// server.get('/api/**', (req, res) => { });
|
||||
// Serve static files from /browser
|
||||
server.get('*.*', express.static(browserDistFolder, {
|
||||
maxAge: '1y'
|
||||
}));
|
||||
|
||||
// All regular routes use the Angular engine
|
||||
server.get('*', (req, res, next) => {
|
||||
const { protocol, originalUrl, baseUrl, headers } = req;
|
||||
|
||||
commonEngine
|
||||
.render({
|
||||
bootstrap,
|
||||
documentFilePath: indexHtml,
|
||||
url: `${protocol}://${headers.host}${originalUrl}`,
|
||||
publicPath: browserDistFolder,
|
||||
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
|
||||
})
|
||||
.then((html) => res.send(html))
|
||||
.catch((err) => next(err));
|
||||
});
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
function run(): void {
|
||||
const port = process.env['PORT'] || 4000;
|
||||
|
||||
// Start up the Node server
|
||||
const server = app();
|
||||
server.listen(port, () => {
|
||||
console.log(`Node Express server listening on http://localhost:${port}`);
|
||||
});
|
||||
}
|
||||
|
||||
run();
|
||||
@@ -0,0 +1,4 @@
|
||||
<app-nav-menu></app-nav-menu>
|
||||
<main class="container-fluid">
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
@@ -0,0 +1,29 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AppComponent],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have the 'user_manager_ui' title`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('user_manager_ui');
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, user_manager_ui');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { ChangeDetectionStrategy, Component, HostListener, inject } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { NavMenuComponent } from './components/nav-menu/nav-menu.component'
|
||||
import { TransferService } from './services/button/transfer.service';
|
||||
import { UpdateService } from './services/button/update.service';
|
||||
import { RefreshService } from './services/button/refresh.service';
|
||||
import { DeletionService } from './services/button/deletion.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [RouterOutlet, NavMenuComponent],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.Default
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'app';
|
||||
protected transferService: TransferService = inject(TransferService)
|
||||
protected updateService: UpdateService = inject(UpdateService)
|
||||
protected refreshService: RefreshService = inject(RefreshService)
|
||||
protected deletionService: DeletionService = inject(DeletionService)
|
||||
|
||||
@HostListener('window:keydown.control.s', ['$event'])
|
||||
protected handleCtrlS(event: KeyboardEvent) {
|
||||
event.preventDefault();
|
||||
this.updateService.executeAllAsync().then(() => this.refreshService.executeAll());
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.control.r', ['$event'])
|
||||
protected handleCtrlR(event: KeyboardEvent) {
|
||||
event.preventDefault();
|
||||
this.refreshService.executeAll();
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.delete', ['$event'])
|
||||
protected handleDelete(event: KeyboardEvent) {
|
||||
event.preventDefault();
|
||||
this.deletionService.executeAll();
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.control.space', ['$event'])
|
||||
protected handleCtrlSpace(event: KeyboardEvent) {
|
||||
event.preventDefault();
|
||||
this.transferService.executeAll();
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.control.l', ['$event'])
|
||||
protected handleCtrlL(event: KeyboardEvent) {
|
||||
event.preventDefault();
|
||||
this.updateService.toggleEditability()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
|
||||
import { provideServerRendering } from '@angular/platform-server';
|
||||
import { appConfig } from './app.config';
|
||||
|
||||
const serverConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideServerRendering()
|
||||
]
|
||||
};
|
||||
|
||||
export const config = mergeApplicationConfig(appConfig, serverConfig);
|
||||
@@ -0,0 +1,33 @@
|
||||
import { APP_INITIALIZER, ApplicationConfig } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { routes } from './app.routes';
|
||||
import { provideClientHydration } from '@angular/platform-browser';
|
||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||
import { provideHttpClient, withFetch } from '@angular/common/http';
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
import { UrlService } from './services/api/url.service';
|
||||
import { API_URL } from './tokens';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideRouter(routes),
|
||||
provideClientHydration(),
|
||||
provideAnimationsAsync(),
|
||||
provideHttpClient(withFetch()),
|
||||
{
|
||||
provide: APP_BASE_HREF,
|
||||
useFactory: (urlService: UrlService) => urlService.getBaseHref(),
|
||||
deps: [UrlService]
|
||||
},
|
||||
{
|
||||
provide: API_URL,
|
||||
useFactory: (urlService: UrlService) => urlService.getApiUrl(),
|
||||
deps: [UrlService]
|
||||
},
|
||||
{
|
||||
provide: API_URL,
|
||||
useFactory: (urlService: UrlService) => urlService.getApiUrl(),
|
||||
deps: [UrlService]
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { HomeComponent } from './pages/home/home.component';
|
||||
import { AuthGuard } from './auth/auth.guard';
|
||||
import { UserComponent } from './pages/user/user.component';
|
||||
import { GroupComponent } from './pages/group/group.component';
|
||||
import { ModuleComponent } from './pages/module/module.component';
|
||||
import { UserAssignmentComponent } from './pages/user-assignment/user-assignment.component';
|
||||
import { UserRepresentationComponent } from './pages/user-representation/user-representation.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: '', component: HomeComponent },
|
||||
{ path: 'user-table', component: UserComponent, canActivate: [AuthGuard] },
|
||||
{ path: 'group-table', component: GroupComponent, canActivate: [AuthGuard] },
|
||||
{ path: 'module-table', component: ModuleComponent, canActivate: [AuthGuard] },
|
||||
{ path: 'user-assignment', component: UserAssignmentComponent, canActivate: [AuthGuard] },
|
||||
{ path: 'user-representation', component: UserRepresentationComponent, canActivate: [AuthGuard] }
|
||||
];
|
||||
@@ -0,0 +1,17 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { CanActivateFn } from '@angular/router';
|
||||
|
||||
import { authGuard } from './auth.guard';
|
||||
|
||||
describe('authGuard', () => {
|
||||
const executeGuard: CanActivateFn = (...guardParameters) =>
|
||||
TestBed.runInInjectionContext(() => authGuard(...guardParameters));
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(executeGuard).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
|
||||
import { AuthenticationService } from '../services/api/authentication.service'; // Adjust the path as necessary
|
||||
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||
import { LoginComponent } from '../components/login/login.component';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(
|
||||
public dialog: MatDialog,
|
||||
public authService: AuthenticationService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
async canActivate(
|
||||
next: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const isAuthenticated = await this.authService.isAuthenticated();
|
||||
|
||||
if (!isAuthenticated) {
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
|
||||
return isAuthenticated;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
openLogin(): MatDialogRef<LoginComponent, any> {
|
||||
const dialogRef = this.dialog.open(LoginComponent, {
|
||||
width: "35vw",
|
||||
data: {
|
||||
afterLogin: () => {
|
||||
dialogRef.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
return dialogRef;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="card">
|
||||
<div *ngIf="!hideTitle" class="card-header">
|
||||
<span *ngIf="title">{{ title }}</span>
|
||||
<ng-content select=".header-content"></ng-content>
|
||||
</div>
|
||||
<div [ngClass]="'card-body p-' + padding">
|
||||
<ng-content select=".body-content"></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CardComponent } from './card.component';
|
||||
|
||||
describe('CardComponent', () => {
|
||||
let component: CardComponent;
|
||||
let fixture: ComponentFixture<CardComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CardComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'card',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './card.component.html',
|
||||
styleUrls: ['./card.component.css']
|
||||
})
|
||||
export class CardComponent {
|
||||
@Input() title: string | null = null;
|
||||
@Input() col: number | null = null;
|
||||
@Input() padding: number = 0;
|
||||
@Input() hideTitle: boolean = false;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
.bi {
|
||||
vertical-align: -.125em;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="d-none">
|
||||
<symbol id="check2" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
|
||||
</symbol>
|
||||
<symbol id="circle-half" viewBox="0 0 16 16">
|
||||
<path d="M8 15A7 7 0 1 0 8 1v14zm0 1A8 8 0 1 1 8 0a8 8 0 0 1 0 16z" />
|
||||
</symbol>
|
||||
<symbol id="moon-stars-fill" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z" />
|
||||
<path
|
||||
d="M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z" />
|
||||
</symbol>
|
||||
<symbol id="sun-fill" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z" />
|
||||
</symbol>
|
||||
</svg>
|
||||
<div class="dropdown bd-mode-toggle">
|
||||
<button class="btn py-2 dropdown-toggle d-flex align-items-center" id="bd-theme" type="button" aria-expanded="false"
|
||||
data-bs-toggle="dropdown" aria-label="Toggle theme (auto)">
|
||||
<svg class="bi my-1 theme-icon-active" width="1em" height="1em" viewBox="0 0 16 16">
|
||||
<use href="#circle-half" [class.hide]="theme !== Themes.Auto"></use>
|
||||
<use href="#sun-fill" [class.hide]="theme !== Themes.Light"></use>
|
||||
<use href="#moon-stars-fill" [class.hide]="theme !== Themes.Dark
|
||||
"></use>
|
||||
</svg>
|
||||
<span class="visually-hidden" id="bd-theme-text">Toggle theme</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow" aria-labelledby="bd-theme-text">
|
||||
<li>
|
||||
<button type="button" (click)="onClick(Themes.Light)"
|
||||
[ngClass]="{'active': theme == Themes.Light, 'dropdown-item d-flex align-items-center': true}"
|
||||
data-bs-theme-value="light" aria-pressed="false">
|
||||
<svg class="bi me-2 opacity-50" width="1em" height="1em">
|
||||
<use href="#sun-fill"></use>
|
||||
</svg>
|
||||
Light
|
||||
<svg class="bi ms-auto d-none" width="1em" height="1em">
|
||||
<use href="#check2"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" (click)="onClick(Themes.Dark)"
|
||||
[ngClass]="{'active': theme == Themes.Dark, 'dropdown-item d-flex align-items-center': true}"
|
||||
data-bs-theme-value="dark" aria-pressed="false">
|
||||
<svg class="bi me-2 opacity-50" width="1em" height="1em">
|
||||
<use href="#moon-stars-fill"></use>
|
||||
</svg>
|
||||
Dark
|
||||
<svg class="bi ms-auto d-none" width="1em" height="1em">
|
||||
<use href="#check2"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" (click)="onClick(Themes.Auto)"
|
||||
[ngClass]="{'active': theme == Themes.Auto, 'dropdown-item d-flex align-items-center': true}"
|
||||
data-bs-theme-value="auto" aria-pressed="true">
|
||||
<svg class="bi me-2 opacity-50" width="1em" height="1em">
|
||||
<use href="#circle-half"></use>
|
||||
</svg>
|
||||
Auto
|
||||
<svg class="bi ms-auto d-none" width="1em" height="1em">
|
||||
<use href="#check2"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ColorModeBttnComponent } from './color-mode-bttn.component';
|
||||
|
||||
describe('ColorModeBttnComponent', () => {
|
||||
let component: ColorModeBttnComponent;
|
||||
let fixture: ComponentFixture<ColorModeBttnComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ColorModeBttnComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ColorModeBttnComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ColorModeService, GetLocalTheme, Theme } from '../../../services/button/color-mode.service';
|
||||
import { CommonModule } from '@angular/common'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
selector: 'app-color-mode-bttn',
|
||||
templateUrl: './color-mode-bttn.component.html',
|
||||
styleUrl: './color-mode-bttn.component.css'
|
||||
})
|
||||
export class ColorModeBttnComponent implements OnInit {
|
||||
constructor(private cModeService: ColorModeService) {
|
||||
this.theme = GetLocalTheme();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.cModeService.updateTheme();
|
||||
}
|
||||
|
||||
readonly Themes = Theme;
|
||||
|
||||
theme: Theme;
|
||||
onClick(theme: Theme) {
|
||||
this.theme = theme;
|
||||
let theTheme: Theme = theme;
|
||||
this.cModeService.setTheme(theTheme);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<table cdkDrag mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
|
||||
<!-- Checkbox Column -->
|
||||
<ng-container matColumnDef="select">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<mat-checkbox (change)="$event ? toggleAllRows() : null"
|
||||
[checked]="selection.hasValue() && isAllSelected()"
|
||||
[indeterminate]="selection.hasValue() && !isAllSelected()"
|
||||
[aria-label]="checkboxLabel()">
|
||||
</mat-checkbox>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let row" [draggable]="true">
|
||||
<mat-checkbox cdkDrag (click)="$event.stopPropagation()"
|
||||
(change)="$event ? selection.toggle(row) : null"
|
||||
[checked]="selection.isSelected(row)"
|
||||
[aria-label]="checkboxLabel(row)">
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Position Column -->
|
||||
<ng-container matColumnDef="position">
|
||||
<th mat-header-cell *matHeaderCellDef [draggable]="true"> No. </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.position}} </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Name Column -->
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef> Name </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.name}} </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Weight Column -->
|
||||
<ng-container matColumnDef="weight">
|
||||
<th mat-header-cell *matHeaderCellDef> Weight </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.weight}} </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Symbol Column -->
|
||||
<ng-container matColumnDef="symbol">
|
||||
<th mat-header-cell *matHeaderCellDef> Symbol </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.symbol}} </td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"
|
||||
(click)="selection.toggle(row)">
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MatTableComponent } from './mat-table.component';
|
||||
|
||||
describe('MatTableComponent', () => {
|
||||
let component: MatTableComponent;
|
||||
let fixture: ComponentFixture<MatTableComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MatTableComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MatTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {SelectionModel} from '@angular/cdk/collections';
|
||||
import {MatTableDataSource, MatTableModule} from '@angular/material/table';
|
||||
import {MatCheckboxModule} from '@angular/material/checkbox';
|
||||
|
||||
export interface PeriodicElement {
|
||||
name: string;
|
||||
position: number;
|
||||
weight: number;
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
const ELEMENT_DATA: PeriodicElement[] = [
|
||||
{position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
|
||||
{position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
|
||||
{position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
|
||||
{position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
|
||||
{position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
|
||||
{position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'},
|
||||
{position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'},
|
||||
{position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'},
|
||||
{position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'},
|
||||
{position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'},
|
||||
];
|
||||
|
||||
/**
|
||||
* @title Table with selection
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mt-table',
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatTableModule, MatCheckboxModule],
|
||||
templateUrl: './mat-table.component.html',
|
||||
styleUrl: './mat-table.component.css'
|
||||
})
|
||||
export class MatTableComponent {
|
||||
displayedColumns: string[] = ['select', 'position', 'name', 'weight', 'symbol'];
|
||||
dataSource = new MatTableDataSource<PeriodicElement>(ELEMENT_DATA);
|
||||
selection = new SelectionModel<PeriodicElement>(true, []);
|
||||
|
||||
/** Whether the number of selected elements matches the total number of rows. */
|
||||
isAllSelected() {
|
||||
const numSelected = this.selection.selected.length;
|
||||
const numRows = this.dataSource.data.length;
|
||||
return numSelected === numRows;
|
||||
}
|
||||
|
||||
/** Selects all rows if they are not all selected; otherwise clear selection. */
|
||||
toggleAllRows() {
|
||||
if (this.isAllSelected()) {
|
||||
this.selection.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
this.selection.select(...this.dataSource.data);
|
||||
}
|
||||
|
||||
/** The label for the checkbox on the passed row */
|
||||
checkboxLabel(row?: PeriodicElement): string {
|
||||
if (!row) {
|
||||
return `${this.isAllSelected() ? 'deselect' : 'select'} all`;
|
||||
}
|
||||
return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row ${row.position + 1}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs"
|
||||
[routerLinkActive]="['link-active']">
|
||||
<li *ngFor="let item of tabItems" class="nav-item">
|
||||
<a [ngClass]="item.title == activeTabTitle ? 'nav-link active' : 'nav-link'" aria-current="true"
|
||||
[routerLink]="[item.routerLink]">{{ item.title }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div [ngClass]="'card-body p-' + padding">
|
||||
<ng-content select=".body-content"></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TabCardComponent } from './tab-card.component';
|
||||
|
||||
describe('TabCardComponent', () => {
|
||||
let component: TabCardComponent;
|
||||
let fixture: ComponentFixture<TabCardComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TabCardComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TabCardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Component, Input, input } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CardComponent } from '../card/card.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'tab-card',
|
||||
standalone: true,
|
||||
imports: [CommonModule, RouterModule],
|
||||
templateUrl: './tab-card.component.html',
|
||||
styleUrl: './tab-card.component.css'
|
||||
})
|
||||
export class TabCardComponent extends CardComponent {
|
||||
@Input() tabItems: TabItem[] = []
|
||||
@Input() activeTabTitle: string | null = null;
|
||||
}
|
||||
|
||||
export interface TabItem {
|
||||
title: string
|
||||
routerLink: string
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Erstellen">
|
||||
<div class="container my-3">
|
||||
<div class="row">
|
||||
<div [ngClass]="formFieldBSClass">
|
||||
<mat-form-field>
|
||||
<mat-label>Gruppenname</mat-label>
|
||||
<input matInput [formControl]="groupname" (blur)="updateErrorMessage()" required />
|
||||
@if (groupname.invalid) {
|
||||
<mat-error>{{errorMessage()}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div [ngClass]="formFieldBSClass">
|
||||
<div [ngClass]="buttonBSClass">
|
||||
<button mat-fab extended (click)="create()">
|
||||
<mat-icon>playlist_add</mat-icon>
|
||||
Erstellen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div [ngClass]="formFieldBSClass">
|
||||
<mat-checkbox [formControl]="active" [disabled]="true">Active</mat-checkbox>
|
||||
</div>
|
||||
<div [ngClass]="formFieldBSClass">
|
||||
<button mat-fab extended (click)="delete()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="Import über AD">
|
||||
<app-group-dir-import></app-group-dir-import>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { GroupFormComponent } from './group-form.component';
|
||||
|
||||
describe('GroupFormComponent', () => {
|
||||
let component: GroupFormComponent;
|
||||
let fixture: ComponentFixture<GroupFormComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [GroupFormComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GroupFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { ChangeDetectionStrategy, Component, model, signal } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { merge } from 'rxjs';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { UserService } from '../../../services/api/user.service';
|
||||
import { RefreshService } from '../../../services/button/refresh.service';
|
||||
import { GroupDirImportComponent } from "../../group-dir-import/group-dir-import.component";
|
||||
import { GroupService } from '../../../services/api/group.service';
|
||||
import Swal from 'sweetalert2';
|
||||
|
||||
@Component({
|
||||
selector: 'app-group-form',
|
||||
standalone: true,
|
||||
imports: [MatFormFieldModule, MatInputModule, FormsModule, ReactiveFormsModule, MatIconModule, MatButtonModule, MatCheckboxModule, CommonModule, MatTabsModule, GroupDirImportComponent],
|
||||
templateUrl: './group-form.component.html',
|
||||
styleUrl: './group-form.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class GroupFormComponent {
|
||||
readonly groupname = new FormControl('', [Validators.required]);
|
||||
readonly active = new FormControl<boolean>(true);
|
||||
|
||||
errorMessage = signal('');
|
||||
|
||||
public readonly formFieldBSClass: string = "col d-flex justify-content-center mx-1 my-2"
|
||||
public readonly buttonBSClass: string = "d-flex justify-content-center mx-1 my-2"
|
||||
//public readonly checkBoxBSClass: (colCount: number | undefined) => string = (colCount: number = 2) => `col-${colCount} d-flex justify-content-left mx-1 my-2`
|
||||
|
||||
readonly checked = model(true);
|
||||
|
||||
constructor(private uService: UserService, private rService: RefreshService, private gService: GroupService) {
|
||||
merge(
|
||||
this.groupname.statusChanges, this.groupname.valueChanges)
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe(() => this.updateErrorMessage());
|
||||
}
|
||||
|
||||
updateErrorMessage() {
|
||||
if (this.groupname.hasError('required')) {
|
||||
this.errorMessage.set('Wert eingeben');
|
||||
} else {
|
||||
this.errorMessage.set('');
|
||||
}
|
||||
}
|
||||
|
||||
create() {
|
||||
if (this.groupname.valid) {
|
||||
this.gService.create({
|
||||
name: this.groupname.value!,
|
||||
adSync: false,
|
||||
internal: true,
|
||||
active: this.active.value!
|
||||
}).subscribe({
|
||||
next: () => {
|
||||
this.delete()
|
||||
this.rService.executeAll();
|
||||
Swal.fire({
|
||||
title: "Vorgang erfolgreich!",
|
||||
text: "Gruppe erfolgreich erstellt!",
|
||||
icon: "success"
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.groupname.setValue('')
|
||||
this.active.setValue(true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<div class="dd-container">
|
||||
<!-- id, name -->
|
||||
<div class="dd-row input-row">
|
||||
<mat-form-field class="w20p">
|
||||
<mat-label>Id</mat-label>
|
||||
<input matInput readonly [value]="group.id" />
|
||||
</mat-form-field>
|
||||
<mat-form-field class="w80p">
|
||||
<mat-label>Gruppe</mat-label>
|
||||
<input matInput [formControl]="name" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<!-- comment -->
|
||||
<div class="dd-row input-row">
|
||||
<mat-form-field>
|
||||
<mat-label>Kommentar</mat-label>
|
||||
<textarea matInput [formControl]="comment"></textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
<!-- active, internal, async -->
|
||||
<div class="dd-row input-row">
|
||||
<mat-slide-toggle [(ngModel)]="group.active">
|
||||
Aktiv
|
||||
</mat-slide-toggle>
|
||||
<mat-slide-toggle [(ngModel)]="group.internal" disabled>
|
||||
Interne Gruppe
|
||||
</mat-slide-toggle>
|
||||
<mat-slide-toggle [(ngModel)]="group.adSync" disabled>
|
||||
Mit Active Directory
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
<!-- addedWho, addedWhen, changedWho and changedWhen -->
|
||||
<div class="dd-row input-row">
|
||||
<mat-form-field>
|
||||
<mat-label>Hinzugefügt wer</mat-label>
|
||||
<input matInput readonly [value]="group.addedWho" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Hinzugefügt wann</mat-label>
|
||||
<input matInput readonly [value]="group.addedWhen | date:'dd.MM.yyyy'" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Geändert wer</mat-label>
|
||||
<input matInput readonly [value]="group.changedWho" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Geändert wann</mat-label>
|
||||
<input matInput readonly [value]="group.changedWhen | date:'dd.MM.yyyy'" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
<!-- save-button, delete-button -->
|
||||
<div class="dd-row button-row">
|
||||
<button mat-fab extended (click)="update()">
|
||||
<mat-icon>save</mat-icon>
|
||||
Speichern
|
||||
</button>
|
||||
<button mat-fab extended (click)="delete()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { GroupUpdateFormComponent } from './group-update-form.component';
|
||||
|
||||
describe('GroupUpdateFormComponent', () => {
|
||||
let component: GroupUpdateFormComponent;
|
||||
let fixture: ComponentFixture<GroupUpdateFormComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [GroupUpdateFormComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GroupUpdateFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
|
||||
import { Group } from '../../../services/api/api-models';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { GroupService } from '../../../services/api/group.service';
|
||||
import { RefreshService } from '../../../services/button/refresh.service';
|
||||
import Swal from 'sweetalert2';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { env } from '../../../../environments/environment'
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
|
||||
@Component({
|
||||
selector: 'app-group-update-form',
|
||||
standalone: true,
|
||||
imports: [MatFormFieldModule, MatInputModule, FormsModule, ReactiveFormsModule, MatIconModule, MatButtonModule, CommonModule, MatTabsModule, MatSelectModule, MatDividerModule, MatCheckboxModule, MatSlideToggleModule],
|
||||
templateUrl: './group-update-form.component.html',
|
||||
styleUrl: './group-update-form.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class GroupUpdateFormComponent {
|
||||
|
||||
readonly dialogRef: MatDialogRef<GroupUpdateFormComponent> = inject(MatDialogRef<GroupUpdateFormComponent>);
|
||||
|
||||
readonly group: Group = inject(MAT_DIALOG_DATA);
|
||||
|
||||
get allowedDateFormats(): Array<{ value: string, name: string }> {
|
||||
return env.constants.date_formats;
|
||||
}
|
||||
|
||||
get allowedLanguages(): Array<{ value: string, name: string }> {
|
||||
return env.constants.languages;
|
||||
}
|
||||
|
||||
readonly name = new FormControl(this.group.name);
|
||||
readonly comment = new FormControl(this.group.comment);
|
||||
|
||||
mailErrorMessage = signal('');
|
||||
errorMessage = signal('');
|
||||
|
||||
constructor(private uService: GroupService, private rService: RefreshService) {
|
||||
}
|
||||
|
||||
update() {
|
||||
|
||||
this.group.name = this.name.value!;
|
||||
this.group.comment = this.comment.value!;
|
||||
|
||||
this.uService.update(this.group).subscribe({
|
||||
next: () => {
|
||||
this.rService.executeAll();
|
||||
},
|
||||
error: err => {
|
||||
console.error(err)
|
||||
Swal.fire({
|
||||
title: "Interner Dienstfehler",
|
||||
text: "Bitte wenden Sie sich an das IT-Team, um den Fehler zu beheben.",
|
||||
icon: "error"
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
delete() {
|
||||
Swal.fire({
|
||||
text: "Sind Sie sicher, dass Sie diesen Datensatz löschen wollen?",
|
||||
icon: "question",
|
||||
showDenyButton: true,
|
||||
confirmButtonText: "Ja",
|
||||
denyButtonText: `Nein`
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
if (this.group.id) {
|
||||
this.uService.delete(this.group.id).subscribe({
|
||||
next: () => {
|
||||
this.rService.executeAll();
|
||||
this.dialogRef.close();
|
||||
},
|
||||
error: err => {
|
||||
Swal.fire({
|
||||
title: "Interner Dienstfehler",
|
||||
text: "Bitte wenden Sie sich an das IT-Team, um den Fehler zu beheben.",
|
||||
icon: "error"
|
||||
});
|
||||
},
|
||||
})
|
||||
}
|
||||
else
|
||||
Swal.fire({
|
||||
title: "Ein unerwarteter Fehler",
|
||||
text: "Die Benutzer-ID existiert nicht (Nullwert).",
|
||||
icon: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<div class="dd-container">
|
||||
<div class="dd-row input-row">
|
||||
<mat-form-field class="w40p">
|
||||
<mat-label>Geben Sie einen Datumsbereich ein</mat-label>
|
||||
<mat-date-range-input [formGroup]="range" [rangePicker]="picker" [disabled]="termless">
|
||||
<input matStartDate formControlName="start" placeholder="Start date">
|
||||
<input matEndDate formControlName="end" placeholder="End date">
|
||||
</mat-date-range-input>
|
||||
<mat-hint>{{dateFormatString()}}</mat-hint>
|
||||
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
|
||||
<mat-date-range-picker #picker></mat-date-range-picker>
|
||||
|
||||
@if (range.controls.start.hasError('matStartDateInvalid')) {
|
||||
<mat-error>Ungültiges Startdatum</mat-error>
|
||||
}
|
||||
@if (range.controls.end.hasError('matEndDateInvalid')) {
|
||||
<mat-error>Ungültiges Enddatum</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-slide-toggle [(ngModel)]="termless" class="w20p">
|
||||
Unbefristet
|
||||
</mat-slide-toggle>
|
||||
<button mat-fab extended (click)="create()" class="w20p">
|
||||
<mat-icon>playlist_add</mat-icon>
|
||||
Erstellen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
.dd-row mat-form-field {
|
||||
margin: 1rem 0rem 0rem 0rem;
|
||||
}
|
||||
|
||||
.dd-row mat-slide-toggle {
|
||||
margin: 1rem 0rem 1rem 0rem;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RepCreateFormComponent } from './rep-create-form.component';
|
||||
|
||||
describe('RepCreateFormComponent', () => {
|
||||
let component: RepCreateFormComponent;
|
||||
let fixture: ComponentFixture<RepCreateFormComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [RepCreateFormComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(RepCreateFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Component, inject, ChangeDetectionStrategy, signal, OnInit, computed } from '@angular/core';
|
||||
import { UserRep } from '../../../services/api/api-models';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { UserRepService } from '../../../services/api/user-representation.service';
|
||||
import { GroupUpdateFormComponent } from '../group-update-form/group-update-form.component';
|
||||
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { DateAdapter, MAT_DATE_LOCALE, provideNativeDateAdapter } from '@angular/material/core';
|
||||
import { MatDatepickerIntl, MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import Swal from 'sweetalert2';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { provideMomentDateAdapter } from '@angular/material-moment-adapter';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import 'moment/locale/de';
|
||||
|
||||
@Component({
|
||||
selector: 'app-rep-create-form',
|
||||
standalone: true,
|
||||
providers: [
|
||||
provideNativeDateAdapter(),
|
||||
{ provide: MAT_DATE_LOCALE, useValue: 'de-DE' },
|
||||
provideMomentDateAdapter()
|
||||
],
|
||||
imports: [MatFormFieldModule, MatDatepickerModule, FormsModule, ReactiveFormsModule, MatButtonModule, MatIconModule, MatInputModule, MatSlideToggleModule],
|
||||
templateUrl: './rep-create-form.component.html',
|
||||
styleUrl: './rep-create-form.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class RepCreateFormComponent implements OnInit {
|
||||
|
||||
readonly userRep: UserRep;
|
||||
readonly afterCreation: (any: any) => any;
|
||||
readonly userRepService: UserRepService = inject(UserRepService);
|
||||
readonly dialogRef: MatDialogRef<GroupUpdateFormComponent> = inject(MatDialogRef<GroupUpdateFormComponent>);
|
||||
readonly range = new FormGroup({
|
||||
start: new FormControl<Date | null>(null),
|
||||
end: new FormControl<Date | null>(null),
|
||||
});
|
||||
private readonly _locale = signal(inject<unknown>(MAT_DATE_LOCALE));
|
||||
private readonly _adapter = inject<DateAdapter<unknown, unknown>>(DateAdapter);
|
||||
private readonly _intl = inject(MatDatepickerIntl);
|
||||
termless: boolean = false;
|
||||
|
||||
constructor() {
|
||||
const dialogData: { userRep: UserRep, afterCreation: (any: any) => any } = inject(MAT_DIALOG_DATA)
|
||||
this.userRep = dialogData.userRep;
|
||||
this.afterCreation = dialogData.afterCreation;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
//update close button label
|
||||
this._intl.closeCalendarLabel = 'Kalender schließen';
|
||||
this._intl.changes.next();
|
||||
|
||||
//set local
|
||||
this._locale.set('de');
|
||||
this._adapter.setLocale(this._locale());
|
||||
}
|
||||
|
||||
readonly dateFormatString = computed(() => {
|
||||
if (this._locale() === 'de-DE') {
|
||||
return 'dd.mm.yyyy';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
create() {
|
||||
const validFrom = this.range.value.start;
|
||||
const validTo = this.range.value.end;
|
||||
|
||||
if ((!validFrom || !validTo) && !this.termless) {
|
||||
Swal.fire({
|
||||
icon: "error",
|
||||
title: "Oops...",
|
||||
text: "Bitte geben Sie einen gültigen Datumsbereich ein oder wählen Sie unbefristet!",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.termless) {
|
||||
this.userRep.validFrom = validFrom!;
|
||||
this.userRep.validTo = validTo!;
|
||||
}
|
||||
|
||||
this.userRepService.create(this.userRep).subscribe({
|
||||
next: (res) => {
|
||||
this.afterCreation({ successful: res });
|
||||
this.dialogRef.close();
|
||||
},
|
||||
error: (error) => {
|
||||
this.afterCreation({ error: error });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Erstellen">
|
||||
<div class="container my-3">
|
||||
<div class="row">
|
||||
<div [ngClass]="formFieldBSClass">
|
||||
<mat-form-field>
|
||||
<mat-label>Benutzername</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[formControl]="username"
|
||||
(blur)="updateErrorMessage()"
|
||||
required />
|
||||
@if (email.invalid) {
|
||||
<mat-error>{{errorMessage()}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div [ngClass]="formFieldBSClass">
|
||||
<mat-form-field>
|
||||
<mat-label>E-Mail</mat-label>
|
||||
<input
|
||||
matInput placeholder="user@example.com"
|
||||
[formControl]="email"
|
||||
(blur)="updateMailErrorMessage()"
|
||||
required />
|
||||
@if (email.invalid) {
|
||||
<mat-error>{{mailErrorMessage()}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div [ngClass]="formFieldBSClass">
|
||||
<div [ngClass]="buttonBSClass">
|
||||
<button mat-fab extended (click)="create()">
|
||||
<mat-icon>playlist_add</mat-icon>
|
||||
Erstellen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div [ngClass]="formFieldBSClass">
|
||||
<mat-form-field>
|
||||
<mat-label>Vorname</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[formControl]="name"
|
||||
(blur)="updateErrorMessage()"
|
||||
required />
|
||||
@if (email.invalid) {
|
||||
<mat-error>{{errorMessage()}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div [ngClass]="formFieldBSClass">
|
||||
<mat-form-field>
|
||||
<mat-label>Nachname</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[formControl]="surname"
|
||||
(blur)="updateErrorMessage()"
|
||||
required />
|
||||
@if (email.invalid) {
|
||||
<mat-error>{{errorMessage()}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div [ngClass]="formFieldBSClass">
|
||||
<button mat-fab extended (click)="delete()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="Import über AD">
|
||||
<app-user-group-dir-import></app-user-group-dir-import>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
@@ -0,0 +1,3 @@
|
||||
.col{
|
||||
margin: 50px;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UserFormComponent } from './user-form.component';
|
||||
|
||||
describe('UserFormComponent', () => {
|
||||
let component: UserFormComponent;
|
||||
let fixture: ComponentFixture<UserFormComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [UserFormComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(UserFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { merge } from 'rxjs';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { UserGroupDirImportComponent } from "../../user-group-dir-import/user-group-dir-import.component";
|
||||
import { UserService } from '../../../services/api/user.service';
|
||||
import { RefreshService } from '../../../services/button/refresh.service';
|
||||
import Swal from 'sweetalert2';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-form',
|
||||
standalone: true,
|
||||
imports: [MatFormFieldModule, MatInputModule, FormsModule, ReactiveFormsModule, MatIconModule, MatButtonModule, CommonModule, MatTabsModule, UserGroupDirImportComponent],
|
||||
templateUrl: './user-create-form.component.html',
|
||||
styleUrl: './user-create-form.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class UserCreateFormComponent {
|
||||
readonly email = new FormControl('', [Validators.required, Validators.email]);
|
||||
readonly username = new FormControl('', [Validators.required]);
|
||||
readonly name = new FormControl('', [Validators.required]);
|
||||
readonly surname = new FormControl('', [Validators.required]);
|
||||
|
||||
mailErrorMessage = signal('');
|
||||
errorMessage = signal('');
|
||||
|
||||
public readonly formFieldBSClass: string = "col d-flex justify-content-center mx-1 my-2"
|
||||
public readonly buttonBSClass: string = "d-flex justify-content-center mx-1 my-2"
|
||||
|
||||
constructor(private uService: UserService, private rService: RefreshService) {
|
||||
merge(
|
||||
this.email.statusChanges, this.email.valueChanges)
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe(() => this.updateMailErrorMessage());
|
||||
|
||||
merge(
|
||||
this.username.statusChanges, this.username.valueChanges,
|
||||
this.name.statusChanges, this.name.valueChanges,
|
||||
this.surname.statusChanges, this.surname.valueChanges)
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe(() => this.updateErrorMessage());
|
||||
}
|
||||
|
||||
updateMailErrorMessage() {
|
||||
if (this.email.hasError('required')) {
|
||||
this.mailErrorMessage.set('Wert eingeben');
|
||||
} else if (this.email.hasError('email')) {
|
||||
this.mailErrorMessage.set('Ungültige E-Mail');
|
||||
} else {
|
||||
this.mailErrorMessage.set('');
|
||||
}
|
||||
}
|
||||
|
||||
updateErrorMessage() {
|
||||
if (this.email.hasError('required')) {
|
||||
this.errorMessage.set('Wert eingeben');
|
||||
} else {
|
||||
this.errorMessage.set('');
|
||||
}
|
||||
}
|
||||
|
||||
create() {
|
||||
if (this.email.valid && this.username.valid && this.name.valid && this.surname.valid) {
|
||||
this.uService.create({
|
||||
email: this.email.value!,
|
||||
prename: this.name.value!,
|
||||
username: this.username.value!,
|
||||
name: this.surname.value!,
|
||||
}).subscribe({
|
||||
next: () => {
|
||||
this.delete()
|
||||
this.rService.executeAll();
|
||||
Swal.fire({
|
||||
title: "Vorgang erfolgreich!",
|
||||
text: "Benutzer erfolgreich erstellt!",
|
||||
icon: "success"
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
delete(){
|
||||
this.email.setValue('')
|
||||
this.username.setValue('')
|
||||
this.name.setValue('')
|
||||
this.surname.setValue('')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<div class="dd-container">
|
||||
<!-- id, username, e-mail -->
|
||||
<div class="dd-row input-row">
|
||||
<mat-form-field class="w10p">
|
||||
<mat-label>Id</mat-label>
|
||||
<input matInput readonly [value]="user.id" />
|
||||
</mat-form-field>
|
||||
<mat-form-field class="w30p">
|
||||
<mat-label>Benutzername</mat-label>
|
||||
<input matInput [formControl]="username" (blur)="updateErrorMessage()" required />
|
||||
@if (email.invalid) {
|
||||
<mat-error>{{errorMessage()}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field class="w60p">
|
||||
<mat-label>E-Mail</mat-label>
|
||||
<input matInput placeholder="user@example.com" [formControl]="email" (blur)="updateMailErrorMessage()"
|
||||
required />
|
||||
@if (email.invalid) {
|
||||
<mat-error>{{mailErrorMessage()}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<!-- firstname, surname -->
|
||||
<div class="dd-row input-row">
|
||||
<mat-form-field>
|
||||
<mat-label>Vorname</mat-label>
|
||||
<input matInput [formControl]="name" (blur)="updateErrorMessage()" required />
|
||||
@if (email.invalid) {
|
||||
<mat-error>{{errorMessage()}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Nachname</mat-label>
|
||||
<input matInput [formControl]="surname" (blur)="updateErrorMessage()" required />
|
||||
@if (email.invalid) {
|
||||
<mat-error>{{errorMessage()}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<!-- shortname -->
|
||||
<div class="dd-row input-row">
|
||||
<mat-form-field>
|
||||
<mat-label>Kürzel</mat-label>
|
||||
<input matInput [formControl]="shortname" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Datumsformat</mat-label>
|
||||
<mat-select [(value)]="user.dateFormat" [(ngModel)]="user.dateFormat">
|
||||
@for (format of allowedDateFormats; track format) {
|
||||
<mat-option [value]="format.value">{{format.name}}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Sprache</mat-label>
|
||||
<mat-select [(value)]="user.language" [(ngModel)]="user.language">
|
||||
@for (language of allowedLanguages; track language) {
|
||||
<mat-option [value]="language.value">{{language.name}}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<!-- comment -->
|
||||
<div class="dd-row input-row">
|
||||
<mat-form-field>
|
||||
<mat-label>Kommentar</mat-label>
|
||||
<textarea matInput [formControl]="comment"></textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
<!-- addedWho, addedWhen, changedWho and changedWhen -->
|
||||
<div class="dd-row input-row">
|
||||
<mat-form-field>
|
||||
<mat-label>Hinzugefügt wer</mat-label>
|
||||
<input matInput readonly [value]="user.addedWho" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Hinzugefügt wann</mat-label>
|
||||
<input matInput readonly [value]="user.addedWhen | date:'dd.MM.yyyy'" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Geändert wer</mat-label>
|
||||
<input matInput readonly [value]="user.changedWho" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Geändert wann</mat-label>
|
||||
<input matInput readonly [value]="user.changedWhen | date:'dd.MM.yyyy'" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
<!-- save-button, delete-button -->
|
||||
<div class="dd-row button-row">
|
||||
<button mat-fab extended (click)="update()">
|
||||
<mat-icon>save</mat-icon>
|
||||
Speichern
|
||||
</button>
|
||||
<button mat-fab extended (click)="delete()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UserUpdateFormComponent } from './user-update-form.component';
|
||||
|
||||
describe('UserUpdateFormComponent', () => {
|
||||
let component: UserUpdateFormComponent;
|
||||
let fixture: ComponentFixture<UserUpdateFormComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [UserUpdateFormComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(UserUpdateFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,142 @@
|
||||
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
|
||||
import { User } from '../../../services/api/api-models';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { merge } from 'rxjs';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { UserService } from '../../../services/api/user.service';
|
||||
import { RefreshService } from '../../../services/button/refresh.service';
|
||||
import Swal from 'sweetalert2';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { env } from '../../../../environments/environment'
|
||||
import {MatDividerModule} from '@angular/material/divider';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-update-form',
|
||||
standalone: true,
|
||||
imports: [MatFormFieldModule, MatInputModule, FormsModule, ReactiveFormsModule, MatIconModule, MatButtonModule, CommonModule, MatTabsModule, MatSelectModule, MatDividerModule],
|
||||
templateUrl: './user-update-form.component.html',
|
||||
styleUrl: './user-update-form.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class UserUpdateFormComponent {
|
||||
|
||||
readonly dialogRef: MatDialogRef<UserUpdateFormComponent> = inject(MatDialogRef<UserUpdateFormComponent>);
|
||||
|
||||
readonly user: User = inject(MAT_DIALOG_DATA);
|
||||
|
||||
get allowedDateFormats(): Array<{ value: string, name: string }> {
|
||||
return env.constants.date_formats;
|
||||
}
|
||||
|
||||
get allowedLanguages(): Array<{ value: string, name: string }> {
|
||||
return env.constants.languages;
|
||||
}
|
||||
|
||||
readonly username = new FormControl(this.user.username, [Validators.required]);
|
||||
readonly email = new FormControl(this.user.email, [Validators.required, Validators.email]);
|
||||
readonly name = new FormControl(this.user.prename, [Validators.required]);
|
||||
readonly surname = new FormControl(this.user.name, [Validators.required]);
|
||||
readonly shortname = new FormControl(this.user.shortname);
|
||||
readonly comment = new FormControl(this.user.comment);
|
||||
|
||||
mailErrorMessage = signal('');
|
||||
errorMessage = signal('');
|
||||
|
||||
constructor(private uService: UserService, private rService: RefreshService) {
|
||||
merge(
|
||||
this.email.statusChanges, this.email.valueChanges)
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe(() => this.updateMailErrorMessage());
|
||||
|
||||
merge(
|
||||
this.username.statusChanges, this.username.valueChanges,
|
||||
this.name.statusChanges, this.name.valueChanges,
|
||||
this.surname.statusChanges, this.surname.valueChanges)
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe(() => this.updateErrorMessage());
|
||||
}
|
||||
|
||||
updateMailErrorMessage() {
|
||||
if (this.email.hasError('required')) {
|
||||
this.mailErrorMessage.set('Wert eingeben');
|
||||
} else if (this.email.hasError('email')) {
|
||||
this.mailErrorMessage.set('Ungültige E-Mail');
|
||||
} else {
|
||||
this.mailErrorMessage.set('');
|
||||
}
|
||||
}
|
||||
|
||||
updateErrorMessage() {
|
||||
if (this.email.hasError('required')) {
|
||||
this.errorMessage.set('Wert eingeben');
|
||||
} else {
|
||||
this.errorMessage.set('');
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.email.valid && this.username.valid && this.name.valid && this.surname.valid) {
|
||||
this.user.email = this.email.value!;
|
||||
this.user.prename = this.name.value!;
|
||||
this.user.username = this.username.value!;
|
||||
this.user.name = this.surname.value!;
|
||||
this.user.shortname = this.shortname.value!;
|
||||
this.user.comment = this.comment.value!;
|
||||
|
||||
this.uService.update(this.user).subscribe({
|
||||
next: () => {
|
||||
this.rService.executeAll();
|
||||
},
|
||||
error: err => {
|
||||
console.error(err)
|
||||
Swal.fire({
|
||||
title: "Interner Dienstfehler",
|
||||
text: "Bitte wenden Sie sich an das IT-Team, um den Fehler zu beheben.",
|
||||
icon: "error"
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
delete() {
|
||||
Swal.fire({
|
||||
text: "Sind Sie sicher, dass Sie diesen Datensatz löschen wollen?",
|
||||
icon: "question",
|
||||
showDenyButton: true,
|
||||
confirmButtonText: "Ja",
|
||||
denyButtonText: `Nein`
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
if (this.user.id) {
|
||||
this.uService.delete(this.user.id).subscribe({
|
||||
next: () => {
|
||||
this.rService.executeAll();
|
||||
this.dialogRef.close();
|
||||
},
|
||||
error: err => {
|
||||
Swal.fire({
|
||||
title: "Interner Dienstfehler",
|
||||
text: "Bitte wenden Sie sich an das IT-Team, um den Fehler zu beheben.",
|
||||
icon: "error"
|
||||
});
|
||||
},
|
||||
})
|
||||
}
|
||||
else
|
||||
Swal.fire({
|
||||
title: "Ein unerwarteter Fehler",
|
||||
text: "Die Benutzer-ID existiert nicht (Nullwert).",
|
||||
icon: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="row p-0 m-0">
|
||||
<div class="col p-0 m-0">
|
||||
<nav class="navbar bg-body-tertiary">
|
||||
<form class="container-fluid justify-content-start">
|
||||
<button class="btn btn-outline-success me-2" type="button"
|
||||
(click)="addSelectedGroups()">Gruppen<br>Hinzufügen</button>
|
||||
</form>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row align-items-start p-0 m-0">
|
||||
<div class="col p-0 m-0">
|
||||
<app-dir-group-table #dirGroups [rowSelection]="dirGroupsRowSelection"></app-dir-group-table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { GroupDirImportComponent } from './group-dir-import.component';
|
||||
|
||||
describe('GroupDirImportComponent', () => {
|
||||
let component: GroupDirImportComponent;
|
||||
let fixture: ComponentFixture<GroupDirImportComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [GroupDirImportComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GroupDirImportComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject, OnInit, ViewChild } from '@angular/core';
|
||||
import { GuiRowSelection, GuiRowSelectionMode, GuiRowSelectionType } from '@generic-ui/ngx-grid';
|
||||
import Swal from 'sweetalert2';
|
||||
import { GroupService } from '../../services/api/group.service';
|
||||
import { Observable, forkJoin, of } from 'rxjs';
|
||||
import { catchError, finalize } from 'rxjs/operators';
|
||||
import { DirGroupTableComponent } from '../tables/dir-group-table/dir-group-table.component';
|
||||
import { DirGroup } from '../../services/api/api-models';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [DirGroupTableComponent],
|
||||
selector: 'app-group-dir-import',
|
||||
templateUrl: './group-dir-import.component.html',
|
||||
styleUrl: './group-dir-import.component.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class GroupDirImportComponent implements OnInit {
|
||||
|
||||
initWithoutData = () => { }
|
||||
|
||||
constructor(private gService: GroupService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
|
||||
@ViewChild('dirGroups') dirGroups!: DirGroupTableComponent;
|
||||
|
||||
dirGroupsRowSelection: GuiRowSelection = {
|
||||
enabled: true,
|
||||
type: GuiRowSelectionType.CHECKBOX,
|
||||
mode: GuiRowSelectionMode.MULTIPLE
|
||||
}
|
||||
|
||||
dirUsersRowSelection: GuiRowSelection = {
|
||||
enabled: true,
|
||||
type: GuiRowSelectionType.CHECKBOX,
|
||||
mode: GuiRowSelectionMode.MULTIPLE
|
||||
}
|
||||
|
||||
addSelectedGroups() {
|
||||
let requests = new Array<Observable<DirGroup | null>>();
|
||||
let numAdded: number = 0;
|
||||
for (let row of this.dirGroups.selectedRows) {
|
||||
requests.push(
|
||||
this.gService.createByDir({ samaccountname: row?.source?.samaccountname }).pipe(
|
||||
catchError((err) => {
|
||||
return of(null);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
forkJoin(requests).pipe(
|
||||
// finalize is executed after all requests are completed or when an error occurs
|
||||
finalize(() => {
|
||||
Swal.fire({
|
||||
icon: "success",
|
||||
title: "Abgeschlossen",
|
||||
text: `${numAdded} neue Gruppen hinzugefügt`,
|
||||
position: "center",
|
||||
showConfirmButton: false,
|
||||
timer: 3000
|
||||
});
|
||||
|
||||
this.dirGroups.safelyUnselectAll();
|
||||
})
|
||||
).subscribe({
|
||||
next: (results) => {
|
||||
numAdded += results.filter(result => result !== null).length;
|
||||
},
|
||||
error: (err) => {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<p>info works!</p>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { InfoComponent } from './info.component';
|
||||
|
||||
describe('InfoComponent', () => {
|
||||
let component: InfoComponent;
|
||||
let fixture: ComponentFixture<InfoComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [InfoComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(InfoComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-info',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './info.component.html',
|
||||
styleUrl: './info.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class InfoComponent {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
i {
|
||||
margin-left: -30px;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<div class="container p-0 m-0">
|
||||
<div class="row justify-content-center p-0 m-0">
|
||||
<div class="col p-0 m-0">
|
||||
<div class="card px-5">
|
||||
<div class="card-body mx-5 px-5">
|
||||
<form (ngSubmit)="login()">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Benutzername</label>
|
||||
<input type="text" class="form-control" [(ngModel)]="username" name="Username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Kennwort</label>
|
||||
<div class="d-flex">
|
||||
<input type="password" [type]="IsPwdHidden?'password':'text'" class="form-control" [(ngModel)]="password" name="Password" required>
|
||||
<i [ngClass]="'bi '+ (IsPwdHidden?'bi-eye-slash':'bi-eye') + ' mt-2'" (click)="onPasswordEyeClicked()"></i>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span [class.spinner-border]="waitRes" [class.spinner-border-sm]="waitRes"
|
||||
aria-hidden="true"></span>Anmeldung</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoginComponent } from './login.component';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [LoginComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject, Input } from '@angular/core';
|
||||
import { AuthenticationService } from '../../services/api/authentication.service';
|
||||
import Swal from 'sweetalert2';
|
||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule],
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrl: './login.component.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LoginComponent {
|
||||
|
||||
username: string = '';
|
||||
password: string = '';
|
||||
|
||||
waitRes: boolean = false;
|
||||
|
||||
IsPwdHidden: boolean = true;
|
||||
|
||||
constructor(private authService: AuthenticationService, @Inject(MAT_DIALOG_DATA) public data: any) {
|
||||
if (typeof (this.afterLogin) == typeof (data.afterLogin))
|
||||
this.afterLogin = data.afterLogin;
|
||||
}
|
||||
|
||||
@Input() afterLogin: () => void = () => { }
|
||||
|
||||
login(): void {
|
||||
this.waitRes = true;
|
||||
this.authService.login(this.username, this.password).subscribe({
|
||||
next: () => this.afterLogin(),
|
||||
error: (err) => {
|
||||
this.waitRes = false;
|
||||
Swal.fire({
|
||||
icon: "error",
|
||||
title: "Ungültiger Benutzername oder Passwort",
|
||||
text: "Bitte überprüfen Sie Ihre Anmeldedaten und versuchen Sie es erneut.",
|
||||
});
|
||||
},
|
||||
complete: () => this.waitRes = false
|
||||
})
|
||||
}
|
||||
onPasswordEyeClicked() {
|
||||
this.IsPwdHidden = !this.IsPwdHidden;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
a.navbar-brand {
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 14px;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
html {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.box-shadow {
|
||||
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
|
||||
}
|
||||
|
||||
.bd-placeholder-img {
|
||||
font-size: 1.125rem;
|
||||
text-anchor: middle;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.bd-placeholder-img-lg {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.b-example-divider {
|
||||
width: 100%;
|
||||
height: 3rem;
|
||||
background-color: rgba(0, 0, 0, .1);
|
||||
border: solid rgba(0, 0, 0, .15);
|
||||
border-width: 1px 0;
|
||||
box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.b-example-vr {
|
||||
flex-shrink: 0;
|
||||
width: 1.5rem;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.bi {
|
||||
vertical-align: -.125em;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.nav-scroller {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
height: 2.75rem;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.nav-scroller .nav {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
padding-bottom: 1rem;
|
||||
margin-top: -1px;
|
||||
overflow-x: auto;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.btn-bd-primary {
|
||||
--bd-violet-bg: #712cf9;
|
||||
--bd-violet-rgb: 112.520718, 44.062154, 249.437846;
|
||||
|
||||
--bs-btn-font-weight: 600;
|
||||
--bs-btn-color: var(--bs-white);
|
||||
--bs-btn-bg: var(--bd-violet-bg);
|
||||
--bs-btn-border-color: var(--bd-violet-bg);
|
||||
--bs-btn-hover-color: var(--bs-white);
|
||||
--bs-btn-hover-bg: #6528e0;
|
||||
--bs-btn-hover-border-color: #6528e0;
|
||||
--bs-btn-focus-shadow-rgb: var(--bd-violet-rgb);
|
||||
--bs-btn-active-color: var(--bs-btn-hover-color);
|
||||
--bs-btn-active-bg: #5a23c8;
|
||||
--bs-btn-active-border-color: #5a23c8;
|
||||
}
|
||||
|
||||
.bd-mode-toggle {
|
||||
z-index: 1500;
|
||||
}
|
||||
|
||||
.bd-mode-toggle .dropdown-menu .active .bi {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.turn-360:hover {
|
||||
animation: rotate 1s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.scale-pulse:hover {
|
||||
animation: pulse 1s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.move-left-right:hover {
|
||||
animation: move 0.8s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
25% {
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
75% {
|
||||
transform: translateX(10px);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary fs-5">
|
||||
<div class="container-fluid">
|
||||
<!-- DD Logo -->
|
||||
<a class="logo" href="https://digitaldata.works/">
|
||||
<img fetchpriority="high" width="200vw"
|
||||
[src]="(isDarkTheme)?'../../assets/img/DD_white.svg':'../../assets/img/digital_data.svg'">
|
||||
</a>
|
||||
<!-- Navbars -->
|
||||
<div *ngIf="isLogedIn()" class="navbar-collapse collapse d-sm-inline-flex justify-content-center"
|
||||
[ngClass]="{ show: isExpanded }">
|
||||
<ul class=" navbar-nav flex-grow">
|
||||
<li class="nav-item" [routerLinkActive]="['link-active']" [routerLinkActiveOptions]="{exact: true}">
|
||||
<a class="nav-link" [routerLink]="['/user-assignment']" [routerLinkActive]="'active'">Autorisierung</a>
|
||||
</li>
|
||||
<li class="nav-item" [routerLinkActive]="['link-active']" [routerLinkActiveOptions]="{exact: true}">
|
||||
<a class="nav-link" [routerLink]="['/user-table']" [routerLinkActive]="'active'">Benutzer</a>
|
||||
</li>
|
||||
<li class="nav-item" [routerLinkActive]="['link-active']" [routerLinkActiveOptions]="{exact: true}">
|
||||
<a class="nav-link" [routerLink]="['/group-table']" [routerLinkActive]="'active'">Gruppen</a>
|
||||
</li>
|
||||
<li class="nav-item" [routerLinkActive]="['link-active']" [routerLinkActiveOptions]="{exact: true}">
|
||||
<a class="nav-link" [routerLink]="['/user-representation']" [routerLinkActive]="'active'">Vertretung</a>
|
||||
</li>
|
||||
<li class="nav-item" [routerLinkActive]="['link-active']" [routerLinkActiveOptions]="{exact: true}">
|
||||
<a class="nav-link" [routerLink]="['/module-table']" [routerLinkActive]="'active'">Module</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Right menu -->
|
||||
<div class="navbar-collapse justify-content-end me-5">
|
||||
<a class="navbar-brand" [routerLink]="['/']">User Manager Portal</a>
|
||||
<button *ngIf="isLogedIn()" class="btn" (click)="creationService.openDialog()" [ngStyle]="{ 'visibility': creationService.isVisible ? 'visible' : 'hidden' }" matTooltip="strg + C" matTooltipPosition="below" matTooltipClass="pt-3" [matTooltipDisabled]="!creationService.isVisible">
|
||||
<mat-icon class="scale-pulse">add_to_photos</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="isLogedIn()" class="btn" (click)="this.updateService.toggleEditability()" [ngStyle]="{ 'visibility': updateService.isVisible ? 'visible' : 'hidden' }" matTooltip="strg + L" matTooltipPosition="below" [matTooltipDisabled]="!updateService.isVisible">
|
||||
<mat-icon class="scale-pulse">{{ updateService.isEditable ? 'lock_open' : 'lock' }}</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="isLogedIn()" class="btn" (click)="saveAsync()" [ngStyle]="{ 'visibility': updateService.isVisible ? 'visible' : 'hidden' }" matTooltip="strg + S" matTooltipPosition="below" matTooltipClass="pt-3" [matTooltipDisabled]="!updateService.isVisible">
|
||||
<mat-icon class="scale-pulse" [matBadge]="updateActCount === 0 ? '' : updateActCount">save</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="isLogedIn()" class="btn" (click)="deletionService.executeAll()" [ngStyle]="{ 'visibility': updateService.isVisible ? 'visible' : 'hidden' }" matTooltip="entf" matTooltipPosition="below" matTooltipClass="pt-3" [matTooltipDisabled]="!deletionService.isVisible">
|
||||
<mat-icon class="scale-pulse">delete_forever</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="isLogedIn()" class="btn" (click)="transferService.executeAll()" [ngStyle]="{ 'visibility': transferService.isVisible ? 'visible' : 'hidden' }" matTooltip="strg + ␣" matTooltipPosition="below" [matTooltipDisabled]="!transferService.isVisible">
|
||||
<mat-icon class="move-left-right">swap_horiz</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="isLogedIn()" class="btn" (click)="refreshService.executeAll()" [ngStyle]="{ 'visibility': refreshService.isVisible ? 'visible' : 'hidden' }" matTooltip="strg + R" matTooltipPosition="below" matTooltipClass="pt-3" [matTooltipDisabled]="!refreshService.isVisible">
|
||||
<mat-icon class="turn-360">sync</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="isLogedIn()" [ngStyle]="{ 'visibility': buttonVisibilityService.anyVisible ? 'visible' : 'hidden' }" class="btn" (click)="showInfo()">
|
||||
<mat-icon class="scale-pulse">contact_support</mat-icon>
|
||||
</button>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse"
|
||||
aria-label="Toggle navigation" [attr.aria-expanded]="isExpanded" (click)="toggle()">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<app-color-mode-bttn></app-color-mode-bttn>
|
||||
<button class="fs-5 btn d-flex align-items-center ms-2 me-0 pe-0" type="button" (click)="auth()">
|
||||
<img fetchpriority="high" src="../../assets/img/login_logo.svg" alt="" style="stroke: #a9a8ad;">
|
||||
{{isLogedIn() ? "Log out" : "Log in"}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
@@ -0,0 +1,98 @@
|
||||
import { ChangeDetectionStrategy, Component, QueryList, ViewChildren } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { AuthenticationService, IsLogedIn } from '../../services/api/authentication.service';
|
||||
import { LoginComponent } from '../login/login.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ColorModeBttnComponent } from '../common/color-mode-bttn/color-mode-bttn.component';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { RefreshService } from '../../services/button/refresh.service';
|
||||
import { CreationService } from '../../services/button/creation.service';
|
||||
import { DeletionService } from '../../services/button/deletion.service';
|
||||
import { ButtonVisibilityService } from '../../services/button/button-visibility.service';
|
||||
import { UpdateService, UpdateEvent } from '../../services/button/update.service';
|
||||
import { TransferService } from '../../services/button/transfer.service';
|
||||
import { MatBadgeModule } from '@angular/material/badge';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [RouterModule, CommonModule, ColorModeBttnComponent, MatIconModule, MatBadgeModule, MatSlideToggleModule, FormsModule, MatButtonModule, MatTooltipModule],
|
||||
selector: 'app-nav-menu',
|
||||
templateUrl: './nav-menu.component.html',
|
||||
styleUrls: ['./nav-menu.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.Default
|
||||
})
|
||||
export class NavMenuComponent {
|
||||
isLogedIn() {
|
||||
return IsLogedIn();
|
||||
}
|
||||
isExpanded = false;
|
||||
|
||||
updateActCount: number;
|
||||
|
||||
isChecked = true;
|
||||
|
||||
constructor(private dialog: MatDialog, private authService: AuthenticationService, public refreshService: RefreshService, public creationService: CreationService, public updateService: UpdateService, public transferService: TransferService, public buttonVisibilityService: ButtonVisibilityService, public deletionService: DeletionService) {
|
||||
this.authService.isAuthenticated()
|
||||
this.updateActCount = this.updateService.totalCount;
|
||||
this.updateService.addChangeListener(UpdateEvent.CountChange, () => {
|
||||
this.updateActCount = updateService.totalCount;
|
||||
})
|
||||
}
|
||||
|
||||
get isDarkTheme(): boolean {
|
||||
return (typeof window !== 'undefined') ? (localStorage.getItem('theme') === 'dark') : true;
|
||||
}
|
||||
|
||||
collapse() {
|
||||
this.isExpanded = false;
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.isExpanded = !this.isExpanded;
|
||||
}
|
||||
|
||||
async auth() {
|
||||
const isLoggedIn = await this.authService.isAuthenticated();
|
||||
if (isLoggedIn)
|
||||
this.authService.logout().subscribe();
|
||||
else {
|
||||
const dialogRef = this.dialog.open(LoginComponent, {
|
||||
width: "35vw",
|
||||
data: {
|
||||
afterLogin: () => {
|
||||
dialogRef.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ViewChildren(MatTooltip) tooltips: QueryList<MatTooltip> | undefined;
|
||||
|
||||
private __tooltip_timeout_set = false;
|
||||
|
||||
showInfo() {
|
||||
this.tooltips?.forEach(t => {
|
||||
t.show();
|
||||
});
|
||||
|
||||
if(!this.__tooltip_timeout_set){
|
||||
this.__tooltip_timeout_set = true;
|
||||
setTimeout(() => {
|
||||
this.__tooltip_timeout_set = false;
|
||||
this.tooltips?.forEach(t => {
|
||||
t.hide();
|
||||
});
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
async saveAsync() {
|
||||
await this.updateService.executeAllAsync().then(() => this.refreshService.executeAll())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<h2 class="card-header">Benutzer-Stammdaten</h2>
|
||||
<div class="card-body">
|
||||
<table class="table table-striped table-hover">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">DatumsFormat</th>
|
||||
<td>{{user.dateFormat}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Kommentar</th>
|
||||
<td>{{user.comment}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Hinzugefügt wer</th>
|
||||
<td colspan="2">{{user.addedWho}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Geändert wenn</th>
|
||||
<td colspan="2">{{user.changedWho}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UserSummaryComponent } from './user-summary.component';
|
||||
|
||||
describe('UserSummaryComponent', () => {
|
||||
let component: UserSummaryComponent;
|
||||
let fixture: ComponentFixture<UserSummaryComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [UserSummaryComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(UserSummaryComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { User } from '../../../services/api/api-models'
|
||||
import { MAT_BOTTOM_SHEET_DATA } from '@angular/material/bottom-sheet';
|
||||
|
||||
@Component({
|
||||
selector: 'user-summary',
|
||||
standalone: true,
|
||||
imports: [MatListModule],
|
||||
templateUrl: './user-summary.component.html',
|
||||
styleUrl: './user-summary.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class UserSummaryComponent {
|
||||
|
||||
constructor(@Inject(MAT_BOTTOM_SHEET_DATA) public user: User) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<gui-grid #grid
|
||||
[columns]="columns"
|
||||
[columnMenu]="columnMenu"
|
||||
[sorting]="sorting"
|
||||
[loading]="loading"
|
||||
[rowSelection] = "rowSelection"
|
||||
[rowDetail]="rowDetail"
|
||||
[autoResizeWidth]="autoResizeWidth"
|
||||
[paging]="paging"
|
||||
[searching]="searching"
|
||||
[cellEditing]="cellEditing"
|
||||
[virtualScroll]="true"
|
||||
[infoPanel]="infoPanel"
|
||||
[titlePanel]="titlePanel"
|
||||
[theme]="theme"
|
||||
[rowStyle] = "rowStyle"
|
||||
[rowClass] = "rowClass"
|
||||
(selectedRows)="onSelectedRows($event)"
|
||||
(click)="click && click(this)">
|
||||
</gui-grid>
|
||||
@@ -0,0 +1,36 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { BaseTableComponent } from './base-table.component';
|
||||
import { ApiService } from '../../../services/api/api-service';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
|
||||
describe('BaseTableComponent', () => {
|
||||
let component: BaseTableComponent<any, any>;
|
||||
let fixture: ComponentFixture<BaseTableComponent<any, any>>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ BaseTableComponent ],
|
||||
providers: [
|
||||
{
|
||||
provide: ApiService,
|
||||
useValue: jasmine.createSpyObj('ApiService', ['getAll', 'update', 'delete'])
|
||||
}
|
||||
],
|
||||
// Ignore any unknown elements and attributes
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(BaseTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
// Add more tests here to cover other functionalities
|
||||
});
|
||||
@@ -0,0 +1,172 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject, Input, OnDestroy, OnInit, ViewChild, input } from '@angular/core';
|
||||
import { ApiService } from '../../../services/api/api-service';
|
||||
import { GuiGridModule, GuiColumn, GuiColumnMenu, GuiSorting, GuiRowDetail, GuiPaging, GuiPagingDisplay, GuiSearching, GuiCellEdit, GuiInfoPanel, GuiTitlePanel, GuiRowSelection, GuiSelectedRow, GuiGridComponent, GuiGridApi, GuiTheme, GuiRowStyle, GuiRowClass } from '@generic-ui/ngx-grid';
|
||||
import { Subscription } from 'rxjs/internal/Subscription';
|
||||
import { ColorModeService, Theme } from '../../../services/button/color-mode.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, GuiGridModule],
|
||||
selector: 'app-base-table',
|
||||
templateUrl: './base-table.component.html',
|
||||
styleUrl: './base-table.component.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class BaseTableComponent<TModel, TApiService extends ApiService<TModel>> implements OnInit, OnDestroy {
|
||||
|
||||
service: TApiService;
|
||||
|
||||
columnMenu: GuiColumnMenu = {
|
||||
enabled: true,
|
||||
sort: true,
|
||||
columnsManager: false,
|
||||
filter: false
|
||||
};
|
||||
sorting: GuiSorting = {
|
||||
enabled: true,
|
||||
multiSorting: true
|
||||
};
|
||||
loading: boolean = false;
|
||||
autoResizeWidth: boolean = true;
|
||||
rowDetail: GuiRowDetail = {
|
||||
enabled: true,
|
||||
template: (item: TModel) => {
|
||||
return `
|
||||
<div></div>`;
|
||||
}
|
||||
};
|
||||
paging: GuiPaging = {
|
||||
enabled: true,
|
||||
page: 1,
|
||||
pageSize: 15,
|
||||
pageSizes: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50],
|
||||
pagerTop: true,
|
||||
pagerBottom: false,
|
||||
display: GuiPagingDisplay.ADVANCED
|
||||
};
|
||||
searching: GuiSearching = {
|
||||
enabled: true,
|
||||
placeholder: 'Suche'
|
||||
};
|
||||
maxHeight: any = 400;
|
||||
infoPanel: GuiInfoPanel = {
|
||||
enabled: true,
|
||||
infoDialog: false,
|
||||
columnsManager: true,
|
||||
schemaManager: true
|
||||
};
|
||||
titlePanel: GuiTitlePanel = {
|
||||
enabled: false,
|
||||
template: () => {
|
||||
return `
|
||||
<div class='title-panel-example' >List of contract workers</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
|
||||
theme: GuiTheme = typeof window !== 'undefined' ? (localStorage.getItem('theme') === 'dark' ? GuiTheme.DARK : GuiTheme.FABRIC) : GuiTheme.DARK;
|
||||
|
||||
private themeSubscription: Subscription = new Subscription();
|
||||
|
||||
private static count: number = 0;
|
||||
|
||||
public readonly id: number = (BaseTableComponent.count++)
|
||||
|
||||
constructor(@Inject(ApiService<TModel>) service: TApiService, columns: Array<GuiColumn>, private cModeService: ColorModeService) {
|
||||
this.service = service;
|
||||
if (this.columns.length == 0)
|
||||
this.columns = columns;
|
||||
|
||||
//assign row details
|
||||
if (this.rowDetailTemplate === null || this.rowDetailTemplate === undefined)
|
||||
this.rowDetail = {
|
||||
enabled: false,
|
||||
};
|
||||
else
|
||||
this.rowDetail = {
|
||||
enabled: true,
|
||||
template: (this.rowDetailTemplate)
|
||||
};
|
||||
}
|
||||
|
||||
@Input() rowDetailTemplate: null | ((item: TModel, index: number) => string) = null;
|
||||
|
||||
@Input() isCellEditable = false;
|
||||
|
||||
@Input() cellEditing: GuiCellEdit = {
|
||||
|
||||
enabled: this.isCellEditable,
|
||||
|
||||
rowEdit: (value: any, item: any, index: number) => {
|
||||
return Boolean(index % 2);
|
||||
},
|
||||
|
||||
cellEdit: (value: any, item: any, index: number) => {
|
||||
return Boolean(index % 5);
|
||||
}
|
||||
}
|
||||
|
||||
@Input() rowSelection: boolean | GuiRowSelection = true;
|
||||
|
||||
@Input() onSelectedRows: (rows: Array<GuiSelectedRow>) => void = (rows) => { };
|
||||
|
||||
@Input() initData: () => void = this.fetchData;
|
||||
|
||||
@Input() columns: Array<GuiColumn> = [];
|
||||
|
||||
@Input() rowStyle: GuiRowStyle = {}
|
||||
|
||||
@Input() rowClass: GuiRowClass = {}
|
||||
|
||||
@Input() click: ((table: BaseTableComponent<TModel, TApiService>) => void) | undefined;
|
||||
|
||||
selected: boolean = false;
|
||||
safelyUnselectAll() {
|
||||
this.selected = true
|
||||
if (this.api?.getSelectedRows() != undefined)
|
||||
if ((this.api?.getSelectedRows().length ?? 0 > 0) && this.selected) {
|
||||
this.api?.unselectAll()
|
||||
this.selected = false
|
||||
}
|
||||
}
|
||||
|
||||
@ViewChild('grid', { static: true }) mainGrid!: GuiGridComponent;
|
||||
|
||||
private get api(): GuiGridApi {
|
||||
return this.mainGrid.api;
|
||||
}
|
||||
|
||||
set source(data: any[]) {
|
||||
this.api.setSource(data)
|
||||
}
|
||||
|
||||
get selectedRows(): Array<GuiSelectedRow> {
|
||||
return this.api.getSelectedRows();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
const subscription = this.cModeService.themeChanges$.subscribe((theme: Theme) => {
|
||||
this.theme = theme === 'dark' ? GuiTheme.DARK : GuiTheme.FABRIC;
|
||||
});
|
||||
|
||||
this.themeSubscription.add(subscription);
|
||||
this.initData()
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.themeSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
fetchData(): void {
|
||||
this.service.getAll().subscribe({
|
||||
next: (response) => {
|
||||
this.source = response;
|
||||
this.loading = false;
|
||||
},
|
||||
error: (error) => { }
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DirGroupTableComponent } from './dir-group-table.component';
|
||||
|
||||
describe('AdGroupUserTableComponent', () => {
|
||||
let component: DirGroupTableComponent;
|
||||
let fixture: ComponentFixture<DirGroupTableComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DirGroupTableComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DirGroupTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { BaseTableComponent } from '../base-table/base-table.component';
|
||||
import { DirGroupService } from '../../../services/api/dir-group.service';
|
||||
import { DirGroup } from '../../../services/api/api-models';
|
||||
import { GuiGridModule } from '@generic-ui/ngx-grid';
|
||||
import { ColorModeService } from '../../../services/button/color-mode.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { env } from '../../../../environments/environment';
|
||||
import { GroupService } from '../../../services/api/group.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, GuiGridModule ],
|
||||
selector: 'app-dir-group-table',
|
||||
templateUrl: '../base-table/base-table.component.html',
|
||||
styleUrl: './dir-group-table.component.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DirGroupTableComponent extends BaseTableComponent<DirGroup, DirGroupService> {
|
||||
constructor(service: DirGroupService, cModeService: ColorModeService, private gService: GroupService) {
|
||||
super(service, env.columnNames.dirGroup, cModeService)
|
||||
}
|
||||
|
||||
override fetchData(): void {
|
||||
this.service.getAll().subscribe({
|
||||
next: async (response) => {
|
||||
const group_names = (await firstValueFrom(this.gService.getAll())).map(g => g.name);
|
||||
this.source = response.filter(dGroup => dGroup.samaccountname?.length && !group_names.includes(dGroup.samaccountname[0]));
|
||||
this.loading = false;
|
||||
},
|
||||
error: (error) => { }
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DirUserTableComponent } from './dir-user-table.component';
|
||||
|
||||
describe('DirUserTableComponent', () => {
|
||||
let component: DirUserTableComponent;
|
||||
let fixture: ComponentFixture<DirUserTableComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DirUserTableComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DirUserTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { DirUser } from '../../../services/api/api-models';
|
||||
import { DirUserService } from '../../../services/api/dir-user.service';
|
||||
import { BaseTableComponent } from '../base-table/base-table.component';
|
||||
import { GuiGridModule, GuiColumn } from '@generic-ui/ngx-grid';
|
||||
import { ColorModeService } from '../../../services/button/color-mode.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { env } from '../../../../environments/environment';
|
||||
import { UserService } from '../../../services/api/user.service';
|
||||
import { firstValueFrom } from 'rxjs/internal/firstValueFrom';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, GuiGridModule],
|
||||
selector: 'app-dir-user-table',
|
||||
templateUrl: '../base-table/base-table.component.html',
|
||||
styleUrl: './dir-user-table.component.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DirUserTableComponent extends BaseTableComponent<DirUser, DirUserService> {
|
||||
constructor(service: DirUserService, cModeService: ColorModeService, private uService: UserService) {
|
||||
super(service, env.columnNames.dirUser, cModeService)
|
||||
}
|
||||
|
||||
fetchDataByGroupName(groupName: string): void {
|
||||
this.service.getAll(groupName).subscribe({
|
||||
next: async (response: DirUser[]) => {
|
||||
|
||||
const usernames = (await firstValueFrom(this.uService.getAll())).map(u => u.username)
|
||||
this.source = response.filter(user => user.samaccountname?.length && !usernames.includes(user.samaccountname[0]));
|
||||
this.loading = false;
|
||||
},
|
||||
error: (error: any) => { }
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { GroupTableComponent } from './group-table.component';
|
||||
|
||||
describe('GroupTableComponent', () => {
|
||||
let component: GroupTableComponent;
|
||||
let fixture: ComponentFixture<GroupTableComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [GroupTableComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GroupTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { GroupService } from '../../../services/api/group.service';
|
||||
import { Group } from '../../../services/api/api-models';
|
||||
import { GuiGridModule, GuiColumn } from '@generic-ui/ngx-grid';
|
||||
import { BaseTableComponent } from '../base-table/base-table.component';
|
||||
import { ColorModeService } from '../../../services/button/color-mode.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { env } from '../../../../environments/environment';
|
||||
import { GroupOfUserService } from '../../../services/api/group-of-user.service';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, GuiGridModule],
|
||||
selector: 'app-group-table',
|
||||
templateUrl: '../base-table/base-table.component.html',
|
||||
styleUrl: './group-table.component.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class GroupTableComponent extends BaseTableComponent<Group, GroupService> {
|
||||
constructor(service: GroupService, cModeService: ColorModeService, private gouService: GroupOfUserService) {
|
||||
super(service, env.columnNames.group.basic, cModeService)
|
||||
}
|
||||
|
||||
fetchDataByUsername(username: string) {
|
||||
this.gouService.getByUsername(username)
|
||||
.then(gos_list => gos_list.map(gos => gos.group))
|
||||
.then(groups => this.source = groups)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { GroupUserTableComponent } from './group-user-table.component';
|
||||
|
||||
describe('GroupUserTableComponent', () => {
|
||||
let component: GroupUserTableComponent;
|
||||
let fixture: ComponentFixture<GroupUserTableComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [GroupUserTableComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GroupUserTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { GroupOfUserService } from '../../../services/api/group-of-user.service';
|
||||
import { GroupOfUser } from '../../../services/api/api-models';
|
||||
import { GuiGridModule } from '@generic-ui/ngx-grid';
|
||||
import { BaseTableComponent } from '../base-table/base-table.component';
|
||||
import { ColorModeService } from '../../../services/button/color-mode.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { env } from '../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, GuiGridModule ],
|
||||
selector: 'app-group-user-table',
|
||||
templateUrl: '../base-table/base-table.component.html',
|
||||
styleUrl: './group-user-table.component.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class GroupUserTableComponent extends BaseTableComponent<GroupOfUser, GroupOfUserService> {
|
||||
constructor(service: GroupOfUserService, cModeService: ColorModeService) {
|
||||
super(service, env.columnNames.groupOfUser, cModeService)
|
||||
this.initData = () => this.fetchDataWith(true,true);
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
fetchDataWith(withUser: boolean, withGroup: boolean){
|
||||
this.service.getAll(withUser, withGroup).subscribe ({
|
||||
next: (response) => {
|
||||
this.source = response;
|
||||
},
|
||||
error: (error) => {}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ModuleTableComponent } from './module-table.component';
|
||||
|
||||
describe('ModuleTableComponent', () => {
|
||||
let component: ModuleTableComponent;
|
||||
let fixture: ComponentFixture<ModuleTableComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ModuleTableComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ModuleTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { Module } from '../../../services/api/api-models';
|
||||
import { GuiGridModule } from '@generic-ui/ngx-grid';
|
||||
import { BaseTableComponent } from '../base-table/base-table.component';
|
||||
import { ModuleService } from '../../../services/api/module.service'
|
||||
import { ColorModeService } from '../../../services/button/color-mode.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { env } from '../../../../environments/environment';
|
||||
import { ModuleOfUserService } from '../../../services/api/module-of-user.service';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, GuiGridModule],
|
||||
selector: 'app-module-table',
|
||||
templateUrl: '../base-table/base-table.component.html',
|
||||
styleUrl: './module-table.component.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ModuleTableComponent extends BaseTableComponent<Module, ModuleService> {
|
||||
constructor(
|
||||
service: ModuleService, cModeService: ColorModeService, private mouService: ModuleOfUserService) {
|
||||
super(service, env.columnNames.module, cModeService)
|
||||
}
|
||||
|
||||
fetchDataByUsername(username: string) {
|
||||
this.mouService.getByUsername(username)
|
||||
.then(mou_list => mou_list.map(mou => mou.module))
|
||||
.then(modules => this.source = modules)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UserRepTableComponent } from './user-rep-table.component';
|
||||
|
||||
describe('UserRepTableComponent', () => {
|
||||
let component: UserRepTableComponent;
|
||||
let fixture: ComponentFixture<UserRepTableComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [UserRepTableComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(UserRepTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { UserRep } from '../../../services/api/api-models';
|
||||
import { UserRepService } from '../../../services/api/user-representation.service';
|
||||
import { BaseTableComponent } from '../base-table/base-table.component';
|
||||
import { GuiGridModule } from '@generic-ui/ngx-grid';
|
||||
import { ColorModeService } from '../../../services/button/color-mode.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { env } from '../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, GuiGridModule],
|
||||
selector: 'app-user-rep-table',
|
||||
templateUrl: '../base-table/base-table.component.html',
|
||||
styleUrl: './user-rep-table.component.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class UserRepTableComponent extends BaseTableComponent<UserRep, UserRepService> {
|
||||
|
||||
constructor(service: UserRepService, cModeService: ColorModeService) {
|
||||
super(service, env.columnNames.userRep, cModeService)
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
override fetchData(userId?: number, groupId?: number): void {
|
||||
this.service.getAll({ withUser: false, withRepGroup: true, withGroup: false, withRepUser: true, userId: userId, groupId: groupId }).subscribe({
|
||||
next: (response: UserRep[]) => {
|
||||
this.source = response;
|
||||
this.loading = false;
|
||||
},
|
||||
error: (error: any) => { }
|
||||
});
|
||||
}
|
||||
|
||||
public fetchByUser(id: number): void {
|
||||
this.fetchData(id, undefined);
|
||||
}
|
||||
|
||||
public fetchByGroup(id: number): void {
|
||||
this.fetchData(undefined, id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UserTableComponent } from './user-table.component';
|
||||
|
||||
describe('UserTableComponent', () => {
|
||||
let component: UserTableComponent;
|
||||
let fixture: ComponentFixture<UserTableComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [UserTableComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(UserTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { UserService } from '../../../services/api/user.service';
|
||||
import { ModuleOfUserService } from '../../../services/api/module-of-user.service';
|
||||
import { GroupOfUserService } from '../../../services/api/group-of-user.service';
|
||||
import { User } from '../../../services/api/api-models';
|
||||
import { GuiGridModule } from '@generic-ui/ngx-grid';
|
||||
import { BaseTableComponent } from '../base-table/base-table.component';
|
||||
import { ColorModeService } from '../../../services/button/color-mode.service'
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { env } from '../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, GuiGridModule],
|
||||
selector: 'app-user-table',
|
||||
templateUrl: '../base-table/base-table.component.html',
|
||||
styleUrl: './user-table.component.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class UserTableComponent extends BaseTableComponent<User, UserService> {
|
||||
|
||||
mosService: ModuleOfUserService;
|
||||
gosService: GroupOfUserService;
|
||||
|
||||
constructor(mosService: ModuleOfUserService,
|
||||
gosService: GroupOfUserService,
|
||||
service: UserService,
|
||||
cModeService: ColorModeService) {
|
||||
super(service, env.columnNames.user.basic, cModeService)
|
||||
this.mosService = mosService;
|
||||
this.gosService = gosService;
|
||||
}
|
||||
|
||||
fetchDataByModuleId(moduleId: number, assigned: boolean = true): void {
|
||||
this.service.getByModuleId(moduleId, assigned).subscribe({
|
||||
next: (users) => {
|
||||
this.source = users;
|
||||
},
|
||||
error: (error) => {}
|
||||
});
|
||||
}
|
||||
|
||||
fetchDataByGroupId(groupId: number, assigned: boolean = true): void {
|
||||
this.service.getByGroupId(groupId, assigned).subscribe({
|
||||
next: (users) => {
|
||||
this.source = users;
|
||||
},
|
||||
error: (error) => {}
|
||||
});
|
||||
}
|
||||
|
||||
async createModuleOfUsers(moduleId: number, users: User[]): Promise<any[]> {
|
||||
const creationPromises = users
|
||||
.filter(user => user.id && user.id != null)
|
||||
.map(user => this.mosService.create({ moduleId: moduleId, userId: user.id ?? -1, addedWho: "DEFAULT" }).toPromise());
|
||||
|
||||
return Promise.all(creationPromises);
|
||||
}
|
||||
|
||||
async createGroupOfUsers(groupId: number, users: User[]): Promise<any[]> {
|
||||
const creationPromises = users
|
||||
.filter(user => user.id && user.id != null)
|
||||
.map(user => this.gosService.create({ groupId: groupId, userId: user.id ?? -1, addedWho: "DEFAULT" }).toPromise());
|
||||
|
||||
return Promise.all(creationPromises);
|
||||
}
|
||||
|
||||
async deleteModuleOfUsers(moduleId: number, users: User[]): Promise<void> {
|
||||
const deletionPromises = users
|
||||
.filter(user => user.id)
|
||||
.map(user => this.mosService.deleteByModuleGroupId(moduleId, user.id ?? -1).toPromise());
|
||||
|
||||
try {
|
||||
const responses = await Promise.all(deletionPromises);
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteGroupOfUsers(groupId: number, users: User[]): Promise<void> {
|
||||
const deletionPromises = users
|
||||
.filter(user => user.id)
|
||||
.map(user => this.gosService.deleteByGroupUserId(groupId, user.id ?? -1).toPromise());
|
||||
|
||||
try {
|
||||
const responses = await Promise.all(deletionPromises);
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<div class="row p-0 m-0">
|
||||
<div class="col p-0 m-0">
|
||||
<nav class="navbar bg-body-tertiary">
|
||||
<form class="container-fluid justify-content-start">
|
||||
<button class="btn btn-outline-success me-2" type="button"
|
||||
(click)="addSelectedUsers()">Benutzer<br>Hinzufügen</button>
|
||||
</form>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row align-items-start p-0 m-0">
|
||||
<div class="col-6 p-0 m-0">
|
||||
<app-dir-group-table #dirGroups [rowSelection]="dirGroupsRowSelection" [onSelectedRows]="dirGroupOnSelectedRows"></app-dir-group-table>
|
||||
</div>
|
||||
<div class="col-6 p-0 m-0">
|
||||
<app-dir-user-table #dirUsers [initData]="initWithoutData" [rowSelection]="dirUsersRowSelection"></app-dir-user-table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UserGroupDirImportComponent } from './user-group-dir-import.component';
|
||||
|
||||
describe('UserGroupDirImportComponent', () => {
|
||||
let component: UserGroupDirImportComponent;
|
||||
let fixture: ComponentFixture<UserGroupDirImportComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [UserGroupDirImportComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(UserGroupDirImportComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user