first commit
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
<form [formGroup]="form">
|
||||
<div class="form-field-container">
|
||||
<mat-form-field appearance="outline" class="">
|
||||
<mat-label>Vorname</mat-label>
|
||||
<input matInput type="text" name="firstName" formControlName="firstName">
|
||||
</mat-form-field>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-field-container">
|
||||
<mat-form-field appearance="outline" class="">
|
||||
<mat-label>Nachname</mat-label>
|
||||
<input matInput type="text" name="lastName" formControlName="lastName">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="form-field-container">
|
||||
<mat-form-field appearance="outline" class="first">
|
||||
<mat-label>Rolle</mat-label>
|
||||
<mat-select name="teamRole" formControlName="teamRole">
|
||||
<mat-option [value]="1">Spieler</mat-option>
|
||||
<mat-option [value]="2">zweiter Kassenwart</mat-option>
|
||||
<mat-option [value]="3">Kapitän</mat-option>
|
||||
<mat-option [value]="4">Kassenwart</mat-option>
|
||||
<mat-option [value]="5">Trainer</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
@@ -0,0 +1,16 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-field-container {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
|
||||
.mat-body{
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PlayerInfoFormComponent } from './player-info-form.component';
|
||||
|
||||
describe('PlayerInfoFormComponent', () => {
|
||||
let component: PlayerInfoFormComponent;
|
||||
let fixture: ComponentFixture<PlayerInfoFormComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ PlayerInfoFormComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PlayerInfoFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { FormGroup, FormControl, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
|
||||
@Component({
|
||||
selector: 'app-player-info-form',
|
||||
templateUrl: './player-info-form.component.html',
|
||||
styleUrls: ['./player-info-form.component.scss'],
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, ReactiveFormsModule, MatFormFieldModule, MatOptionModule, MatSelectModule, MatInputModule]
|
||||
})
|
||||
export class PlayerInfoFormComponent {
|
||||
disableSelect = new FormControl(true);
|
||||
|
||||
form = new FormGroup({
|
||||
firstName: new FormControl('', [Validators.required]),
|
||||
lastName: new FormControl(null, [Validators.required]),
|
||||
teamRole: new FormControl({value: 1, disabled: false})
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<div class="mat-body" *ngIf="text">
|
||||
{{ text }}
|
||||
</div>
|
||||
|
||||
<form [formGroup]="form">
|
||||
<div>
|
||||
<mat-form-field appearance="outline" class="input_note">
|
||||
<mat-label>Anmerkung</mat-label>
|
||||
<input matInput type="text" name="note" formControlName="note">
|
||||
<mat-hint>Optionale Bemerkung / Erklärung</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<mat-form-field appearance="outline" class="first">
|
||||
<mat-label>Art der Buchung</mat-label>
|
||||
<mat-select name="type" formControlName="type" (selectionChange)="onTypeChange()">
|
||||
<mat-option *ngFor="let t of transactionTypes" [value]="t">
|
||||
{{ transactionType(t) }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-hint>{{ description }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="input_amount second" appearance="outline">
|
||||
<mat-label>Betrag</mat-label>
|
||||
<input matInput type="number" inputmode="decimal" min="0.01" max="10000" step="0.01" name="amount" formControlName="amount">
|
||||
<span matSuffix>€</span>
|
||||
<mat-hint *ngIf="!form.controls.amount.errors">als positiver Betrag, z. B. 12,50</mat-hint>
|
||||
<mat-error *ngIf="form.controls.amount.errors?.['max']">Bitte Betrag prüfen, maximal 10.000 €</mat-error>
|
||||
<mat-error *ngIf="form.controls.amount.errors?.['min']">Betrag muss größer als 0 € sein</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<div style="display: flex; justify-content: flex-end; margin-top: 12px;" *ngIf="showTotalSlider">
|
||||
<mat-slide-toggle formControlName="total">Betrag gleichmäßig aufteilen</mat-slide-toggle>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,28 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.second {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.input_note {
|
||||
width: 100%;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.mat-body{
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TransactionFormComponent } from './transaction-form.component';
|
||||
|
||||
describe('TransactionFormComponent', () => {
|
||||
let component: TransactionFormComponent;
|
||||
let fixture: ComponentFixture<TransactionFormComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ TransactionFormComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TransactionFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { FormGroup, FormControl, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { TransactionType } from '../../new-transaction/model/transaction-type.enum';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
|
||||
@Component({
|
||||
selector: 'app-transaction-form',
|
||||
templateUrl: './transaction-form.component.html',
|
||||
styleUrls: ['./transaction-form.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule
|
||||
, FormsModule
|
||||
, ReactiveFormsModule
|
||||
, MatFormFieldModule
|
||||
, MatOptionModule
|
||||
, MatSelectModule
|
||||
, MatInputModule
|
||||
, MatButtonModule
|
||||
, MatSlideToggleModule
|
||||
]
|
||||
})
|
||||
export class TransactionFormComponent {
|
||||
|
||||
@Input('transactionTypes') transactionTypes = [TransactionType.credit, TransactionType.fee, TransactionType.fine, TransactionType.levy, TransactionType.payment];
|
||||
|
||||
@Input('totalSlider') showTotalSlider: boolean = true;
|
||||
@Input('text') text: string | undefined;
|
||||
|
||||
description = 'Art wählen';
|
||||
|
||||
translations = {
|
||||
fee: 'Gebühr',
|
||||
fine: 'Strafe',
|
||||
levy: 'Umlage',
|
||||
payment: 'Zahlung',
|
||||
credit: 'Guthaben',
|
||||
expense: 'Ausgabe'
|
||||
}
|
||||
|
||||
|
||||
form = new FormGroup({
|
||||
note: new FormControl('', []),
|
||||
amount: new FormControl(null, [Validators.required, Validators.min(0.01), Validators.max(10000)]),
|
||||
type: new FormControl(null, [Validators.required]),
|
||||
total: new FormControl(false, [])
|
||||
});
|
||||
|
||||
transactionType(id: number | null): string {
|
||||
if (id == null) { return ''; }
|
||||
const key: any = TransactionType[id];
|
||||
const t = this.translations as any;
|
||||
return t[key];
|
||||
}
|
||||
|
||||
onTypeChange() {
|
||||
if (this.form.controls.type.value != null && this.form.controls.type.value < 10) {
|
||||
this.description = 'Spieler bekommt Geld'
|
||||
} else {
|
||||
this.description = 'Spieler zahlt Geld'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<h1 mat-dialog-title>{{ data.title }}</h1>
|
||||
<div mat-dialog-content>
|
||||
<p>{{ data.message }}</p>
|
||||
</div>
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-button (click)="cancel()">{{ data.cancelLabel || 'Abbrechen' }}</button>
|
||||
<button mat-flat-button color="warn" (click)="confirm()">{{ data.confirmLabel || 'Bestätigen' }}</button>
|
||||
</div>
|
||||
@@ -0,0 +1,33 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-confirm-dialog',
|
||||
templateUrl: './confirm-dialog.component.html',
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatButtonModule, MatDialogModule]
|
||||
})
|
||||
export class ConfirmDialogComponent {
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<ConfirmDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: ConfirmDialogData,
|
||||
) {}
|
||||
|
||||
confirm(): void {
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ConfirmDialogData {
|
||||
title: string;
|
||||
message: string;
|
||||
confirmLabel?: string;
|
||||
cancelLabel?: string;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<h1 style="text-align: center;">Spieler anlegen</h1>
|
||||
|
||||
<div>
|
||||
<app-player-info-form #formComponent></app-player-info-form>
|
||||
</div>
|
||||
|
||||
<div class="flex-row">
|
||||
<button mat-stroked-button color="warn" (click)="close()">Schließen</button>
|
||||
<button mat-stroked-button color="primary" [disabled]="disableSave" (click)="close(true)">Anlegen</button>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
:host {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
padding: 8px 14px 8px 14px;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CreatePlayerDialogComponent } from './create-player.dialog.component';
|
||||
|
||||
describe('CreatePlayerDialogComponent', () => {
|
||||
let component: CreatePlayerDialogComponent;
|
||||
let fixture: ComponentFixture<CreatePlayerDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ CreatePlayerDialogComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CreatePlayerDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { PlayerInfoFormComponent } from '../components/player-info-form/player-info-form.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-player.dialog',
|
||||
templateUrl: './create-player.dialog.component.html',
|
||||
styleUrls: ['./create-player.dialog.component.scss'],
|
||||
standalone: true,
|
||||
imports: [ CommonModule, PlayerInfoFormComponent, MatButtonModule ]
|
||||
})
|
||||
export class CreatePlayerDialogComponent {
|
||||
@ViewChild('formComponent') formComponent!: PlayerInfoFormComponent;
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<CreatePlayerDialogComponent>
|
||||
) {}
|
||||
|
||||
|
||||
get disableSave(): boolean {
|
||||
return !this.formComponent || !this.formComponent.form || this.formComponent?.form.invalid;
|
||||
}
|
||||
|
||||
close(save?: boolean) {
|
||||
if (save) {
|
||||
this.dialogRef.close(this.formComponent.form.value);
|
||||
return;
|
||||
}
|
||||
|
||||
this.dialogRef.close();
|
||||
|
||||
}
|
||||
}
|
||||
60
myteamwallet_frontend/src/app/shared/dialog/dialog.module.ts
Normal file
60
myteamwallet_frontend/src/app/shared/dialog/dialog.module.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NewTransactionDialogComponent } from './new-transaction/new-transaction.component';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { TeamTransactionDialogComponent } from './team-transaction-dialog/team-transaction-dialog.component';
|
||||
import { TransactionFormComponent } from './components/transaction-form/transaction-form.component';
|
||||
import { CreatePlayerDialogComponent } from './create-player-dialog/create-player.dialog.component';
|
||||
import { PlayerInfoFormComponent } from './components/player-info-form/player-info-form.component';
|
||||
import { PrivilegesInfoDialogComponent } from './privileges-info-dialog/privileges-info-dialog.component';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { ShowTeamTransactionsComponent } from './show-team-transactions/show-team-transactions.component';
|
||||
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
||||
import {MatTableModule} from '@angular/material/table';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
|
||||
|
||||
const components = [ ]
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatFormFieldModule,
|
||||
MatSelectModule,
|
||||
MatInputModule,
|
||||
MatButtonModule,
|
||||
MatSlideToggleModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatDialogModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatTableModule,
|
||||
MatSortModule,
|
||||
MatPaginatorModule,
|
||||
PlayerInfoFormComponent,
|
||||
TransactionFormComponent,
|
||||
TeamTransactionDialogComponent,
|
||||
PrivilegesInfoDialogComponent,
|
||||
CreatePlayerDialogComponent,
|
||||
ShowTeamTransactionsComponent,
|
||||
NewTransactionDialogComponent
|
||||
],
|
||||
exports: [
|
||||
PlayerInfoFormComponent,
|
||||
TransactionFormComponent,
|
||||
TeamTransactionDialogComponent,
|
||||
PrivilegesInfoDialogComponent,
|
||||
CreatePlayerDialogComponent,
|
||||
ShowTeamTransactionsComponent,
|
||||
NewTransactionDialogComponent
|
||||
]
|
||||
})
|
||||
export class DialogModule {
|
||||
}
|
||||
2
myteamwallet_frontend/src/app/shared/dialog/index.ts
Normal file
2
myteamwallet_frontend/src/app/shared/dialog/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './new-transaction/new-transaction.component';
|
||||
export * from './team-transaction-dialog/team-transaction-dialog.component';
|
||||
@@ -0,0 +1,8 @@
|
||||
export enum TransactionType {
|
||||
payment = 0,
|
||||
credit = 1,
|
||||
fine = 11,
|
||||
levy = 12,
|
||||
fee = 13,
|
||||
expense = 14
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<h1 style="text-align: center;">Buchung anlegen</h1>
|
||||
<mat-dialog-content>
|
||||
<div class="selected_players">
|
||||
<div class="header">Gewählte Spieler:</div>
|
||||
<ng-container *ngIf="!this.data.all">
|
||||
<div *ngFor="let p of data.players | slice:0:5" class="selected_players__entry">{{ p.firstName }} {{ p.lastName }}</div>
|
||||
<div *ngIf="data.players.length > 5">
|
||||
+ {{ data.players.length - 5 }} weitere
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="this.data.all">
|
||||
|
||||
<div class="selected_players__entry" style="text-align: center; margin: 8px;">Gesamte Mannschaft</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<app-transaction-form #formComponent class="form"></app-transaction-form>
|
||||
</mat-dialog-content>
|
||||
|
||||
<div class="summary">
|
||||
<div class="mat-body">Jeder Spieler bekommt <span style="font-style: italic;">{{ transactionType }} über {{ amount | currency:'EUR' }}</span></div>
|
||||
</div>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<button mat-button color="warn" (click)="close()">Schließen</button>
|
||||
<button mat-button color="primary" (click)="create()" [disabled]="!formValid">Anlegen</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,30 @@
|
||||
:host {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
padding: 2px 4px 4px 4px;
|
||||
}
|
||||
|
||||
.selected_players {
|
||||
padding: 2px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.header {
|
||||
background-color: #e7e7e7;
|
||||
margin: -2px -2px 0 -2px;
|
||||
padding: 2px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.summary {
|
||||
margin-top: 32px;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { forwardRef } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { NewTransactionDialogComponent } from './new-transaction.component';
|
||||
|
||||
describe('NewTransactionDialogComponent', () => {
|
||||
let component: NewTransactionDialogComponent;
|
||||
let fixture: ComponentFixture<NewTransactionDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ NewTransactionDialogComponent ],
|
||||
providers: [
|
||||
|
||||
{ provide: MatDialogRef, useValue: {} },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: {} },
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => NewTransactionDialogComponent),
|
||||
multi: true,
|
||||
}
|
||||
],
|
||||
imports: [
|
||||
MatDialogModule,
|
||||
MatFormFieldModule,
|
||||
ReactiveFormsModule,
|
||||
MatInputModule,
|
||||
NoopAnimationsModule,
|
||||
MatSelectModule,
|
||||
FormsModule,
|
||||
MatSlideToggleModule
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(NewTransactionDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,121 @@
|
||||
import { Component, Inject, OnInit, ViewChild } from '@angular/core';
|
||||
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { Player } from 'src/app/model';
|
||||
import { Team } from 'src/app/modules/teams/model/team';
|
||||
import { TransactionFormComponent } from '../components/transaction-form/transaction-form.component';
|
||||
import { TransactionType } from './model/transaction-type.enum';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.component';
|
||||
|
||||
const HIGH_AMOUNT_CONFIRM_THRESHOLD = 300;
|
||||
|
||||
@Component({
|
||||
selector: 'app-new-transaction',
|
||||
templateUrl: './new-transaction.component.html',
|
||||
styleUrls: ['./new-transaction.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule
|
||||
, TransactionFormComponent
|
||||
, MatButtonModule
|
||||
, MatDialogModule
|
||||
]
|
||||
})
|
||||
export class NewTransactionDialogComponent implements OnInit {
|
||||
|
||||
@ViewChild('formComponent') formComponent!: TransactionFormComponent;
|
||||
|
||||
|
||||
selectedTransactionType: number | null = null;
|
||||
|
||||
isTotal: boolean = false;
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<NewTransactionDialogComponent>,
|
||||
private dialog: MatDialog,
|
||||
@Inject(MAT_DIALOG_DATA) public data: DialogData,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
|
||||
close() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
create() {
|
||||
|
||||
const value = this.formComponent.form.value;
|
||||
if (!value || value.amount == null || value.total == null || value.type == null) { return; }
|
||||
let amount = value.total ? value.amount / this.data.players.length : value.amount;
|
||||
amount = Math.round(amount * 100) / 100;
|
||||
|
||||
let res: Transaction[] = [];
|
||||
for (let p of this.data.players) {
|
||||
res.push({
|
||||
amount,
|
||||
playerId: p.id,
|
||||
type: value.type as any,
|
||||
note: value.note as any
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (value.amount >= HIGH_AMOUNT_CONFIRM_THRESHOLD) {
|
||||
const confirmRef = this.dialog.open(ConfirmDialogComponent, {
|
||||
data: {
|
||||
title: 'Betrag prüfen',
|
||||
message: `Es wird${this.data.players.length > 1 ? ' für jede:n der ' + this.data.players.length + ' gewählten Spieler:innen' : ''} ein Betrag von ${value.amount} € gebucht. Bitte kurz prüfen, bevor du fortfährst.`,
|
||||
confirmLabel: 'Buchen'
|
||||
}
|
||||
});
|
||||
|
||||
confirmRef.afterClosed().subscribe(confirmed => {
|
||||
if (confirmed) {
|
||||
this.dialogRef.close(res);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.dialogRef.close(res);
|
||||
|
||||
}
|
||||
|
||||
get amount(): number {
|
||||
const value = this.formComponent?.form?.value;
|
||||
if (!value || value.amount == null) { return 0; }
|
||||
return Math.round((value.total ? value.amount / this.data.players.length : value.amount) * 100) / 100;
|
||||
}
|
||||
|
||||
get transactionType(): string {
|
||||
if (this.formComponent?.form?.value?.type == null) { return ''; }
|
||||
return this.formComponent.transactionType(this.formComponent.form.value.type);
|
||||
}
|
||||
|
||||
get formValid(): boolean {
|
||||
return this.formComponent?.form != null && this.formComponent.form.valid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
interface DialogData {
|
||||
team: Team,
|
||||
players: Player[];
|
||||
all?: boolean;
|
||||
}
|
||||
|
||||
export interface Transaction {
|
||||
playerId: Number;
|
||||
amount: number;
|
||||
type: TransactionType;
|
||||
note: string;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<h2 mat-dialog-title>Berechtigungen nach Teamrolle</h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
|
||||
<div class="mat-body description">
|
||||
Für jeden Spieler kann ein Einladungslink erstellt werden, mit dem sich der Spieler registrieren und verknüpfen kann.
|
||||
Jeder Link darf nur an einen Spieler gegeben werden, da jeder Spieler (pro Team) nur mit einem User-Account verbunden sein kann.
|
||||
Derzeit ist eine Verlinkung nur mit einer neuen Registrierung möglich. Jeder User-Account erhält pro Team die Rolle seines zugeordneten Spielers.
|
||||
</div>
|
||||
|
||||
<div class="mat-h4">Zweiter Kassenwart:</div>
|
||||
<div class="mat-body">
|
||||
<ul>
|
||||
<li>Transaktionen und Buchungen anlegen</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mat-h4">Kassenwart:</div>
|
||||
<div class="mat-body">
|
||||
<ul>
|
||||
<li>Transaktionen und Buchungen anlegen</li>
|
||||
<li>Spieler anlegen & verwalten</li>
|
||||
<li>Registrierungslinks erstellen</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mat-h4">Kapitän:</div>
|
||||
<div class="mat-body">
|
||||
<ul>
|
||||
<li>Transaktionen und Buchungen anlegen</li>
|
||||
<li>Spieler anlegen & verwalten</li>
|
||||
<li>Registrierungslinks erstellen</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mat-h4">Trainer:</div>
|
||||
<div class="mat-body">
|
||||
<ul>
|
||||
<li>Transaktionen und Buchungen anlegen</li>
|
||||
<li>Spieler anlegen & verwalten</li>
|
||||
<li>Registrierungslinks erstellen</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
</mat-dialog-content>
|
||||
@@ -0,0 +1,9 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 850px;
|
||||
}
|
||||
|
||||
.description{
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PrivilegesInfoDialogComponent } from './privileges-info-dialog.component';
|
||||
|
||||
describe('PrivilegesInfoDialogComponent', () => {
|
||||
let component: PrivilegesInfoDialogComponent;
|
||||
let fixture: ComponentFixture<PrivilegesInfoDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ PrivilegesInfoDialogComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PrivilegesInfoDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-privileges-info-dialog',
|
||||
templateUrl: './privileges-info-dialog.component.html',
|
||||
styleUrls: ['./privileges-info-dialog.component.scss'],
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatDialogModule]
|
||||
})
|
||||
export class PrivilegesInfoDialogComponent {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<div class="dialog-header">
|
||||
<div>
|
||||
<h2 mat-dialog-title>Alle Transaktionen</h2>
|
||||
<p class="dialog-subtitle">Buchungen des gesamten Teams</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
mat-icon-button
|
||||
mat-dialog-close
|
||||
class="close-button"
|
||||
aria-label="Dialog schließen">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<mat-dialog-content>
|
||||
<div class="dialog-state" *ngIf="isLoading">
|
||||
<mat-spinner diameter="44"></mat-spinner>
|
||||
<span>Transaktionen werden geladen …</span>
|
||||
</div>
|
||||
|
||||
<div class="dialog-state error-state" *ngIf="loadError">
|
||||
<mat-icon>error_outline</mat-icon>
|
||||
<strong>Transaktionen konnten nicht geladen werden.</strong>
|
||||
<button mat-stroked-button (click)="load()">Erneut versuchen</button>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="!isLoading && !loadError">
|
||||
<div class="table-toolbar">
|
||||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||
<mat-label>Transaktionen durchsuchen</mat-label>
|
||||
<mat-icon matPrefix>search</mat-icon>
|
||||
<input
|
||||
matInput
|
||||
(keyup)="applyFilter($event)"
|
||||
placeholder="Name, Betrag oder Notiz"
|
||||
#input>
|
||||
</mat-form-field>
|
||||
|
||||
<button
|
||||
mat-stroked-button
|
||||
class="note-toggle"
|
||||
(click)="toggleNote()"
|
||||
[class.active]="displayedColumns.includes('note')">
|
||||
<mat-icon>notes</mat-icon>
|
||||
{{ displayedColumns.includes('note') ? 'Notizen ausblenden' : 'Notizen anzeigen' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-shell">
|
||||
<div class="table-scroll">
|
||||
<table
|
||||
mat-table
|
||||
[dataSource]="dataSource"
|
||||
matSort
|
||||
matSortActive="date"
|
||||
matSortDirection="desc">
|
||||
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Betrag</th>
|
||||
<td mat-cell *matCellDef="let element" class="amount-cell">
|
||||
{{ element.amount | currency:'EUR' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="date">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Datum</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.date | date:'shortDate' }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="type">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Typ</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.type }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="note">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Notiz</th>
|
||||
<td mat-cell *matCellDef="let element" class="note-cell">{{ element.note || '–' }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="playerName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Spieler</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.playerName }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef aria-label="Aktionen"></th>
|
||||
<td mat-cell *matCellDef="let element" class="actions-cell">
|
||||
<button
|
||||
mat-icon-button
|
||||
*ngIf="!element.isTeamWalletTransaction && !element.note?.startsWith('Stornierung von Buchung #')"
|
||||
(click)="reverseTransaction(element)"
|
||||
matTooltip="Buchung stornieren"
|
||||
aria-label="Buchung stornieren">
|
||||
<mat-icon>undo</mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
<tr class="empty-row" *matNoDataRow>
|
||||
<td [attr.colspan]="displayedColumns.length">
|
||||
{{ input.value ? 'Keine passenden Transaktionen gefunden.' : 'Noch keine Transaktionen vorhanden.' }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<mat-paginator
|
||||
[pageSizeOptions]="[5, 10, 20]"
|
||||
[pageSize]="10"
|
||||
showFirstLastButtons
|
||||
aria-label="Seite der Transaktionen auswählen">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</ng-container>
|
||||
</mat-dialog-content>
|
||||
@@ -0,0 +1,172 @@
|
||||
:host {
|
||||
display: block;
|
||||
width: min(900px, 86vw);
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
padding: 24px 24px 16px;
|
||||
border-bottom: 1px solid var(--mat-divider-color, #e0e0e0);
|
||||
|
||||
h2[mat-dialog-title] {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1.25;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-subtitle {
|
||||
margin: 4px 0 0;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.close-button {
|
||||
flex: 0 0 auto;
|
||||
margin: -8px -8px 0 0;
|
||||
}
|
||||
|
||||
mat-dialog-content {
|
||||
display: flex;
|
||||
height: min(70vh, 620px);
|
||||
max-height: none;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 20px 24px 24px !important;
|
||||
}
|
||||
|
||||
.dialog-state {
|
||||
min-height: 260px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-state {
|
||||
mat-icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
color: #ba1a1a;
|
||||
font-size: 44px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
mat-form-field {
|
||||
flex: 1 1 360px;
|
||||
max-width: 520px;
|
||||
}
|
||||
}
|
||||
|
||||
.note-toggle {
|
||||
flex: 0 0 auto;
|
||||
|
||||
&.active {
|
||||
color: var(--mat-sys-primary, #343dff);
|
||||
background: #eef3ff;
|
||||
}
|
||||
}
|
||||
|
||||
.table-shell {
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--mat-divider-color, #e0e0e0);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.table-scroll {
|
||||
min-height: 0;
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
min-width: 690px;
|
||||
}
|
||||
|
||||
th.mat-mdc-header-cell {
|
||||
color: rgba(0, 0, 0, 0.68);
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td.mat-mdc-cell {
|
||||
border-bottom-color: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.amount-cell {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.note-cell {
|
||||
min-width: 180px;
|
||||
max-width: 320px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.actions-cell {
|
||||
width: 48px;
|
||||
padding-right: 8px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-row td {
|
||||
height: 128px;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
mat-paginator {
|
||||
flex: 0 0 auto;
|
||||
border-top: 1px solid var(--mat-divider-color, #e0e0e0);
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
:host {
|
||||
width: 92vw;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
padding: 20px 16px 14px;
|
||||
}
|
||||
|
||||
mat-dialog-content {
|
||||
height: min(72vh, 620px);
|
||||
padding: 16px !important;
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
|
||||
mat-form-field {
|
||||
flex-basis: auto;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
.note-toggle {
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ShowTeamTransactionsComponent } from './show-team-transactions.component';
|
||||
|
||||
describe('ShowTeamTransactionsComponent', () => {
|
||||
let component: ShowTeamTransactionsComponent;
|
||||
let fixture: ComponentFixture<ShowTeamTransactionsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ShowTeamTransactionsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ShowTeamTransactionsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,137 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, Inject, OnInit, ViewChild } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatSort, MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { Transaction } from 'src/app/model';
|
||||
import { TeamsService } from 'src/app/modules/teams/teams.service';
|
||||
import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-show-team-transactions',
|
||||
templateUrl: './show-team-transactions.component.html',
|
||||
styleUrls: ['./show-team-transactions.component.scss'],
|
||||
standalone: true,
|
||||
imports: [ CommonModule, MatDialogModule, MatTableModule, MatSortModule, MatPaginatorModule, MatFormFieldModule, MatInputModule, MatProgressSpinnerModule, MatButtonModule, MatIconModule, MatTooltipModule ]
|
||||
})
|
||||
export class ShowTeamTransactionsComponent implements OnInit {
|
||||
|
||||
isLoading: boolean = true;
|
||||
loadError: boolean = false;
|
||||
|
||||
displayedColumns: string[] = ['amount', 'date', 'type', 'playerName', 'actions'];
|
||||
dataSource: MatTableDataSource<Transaction> = new MatTableDataSource<Transaction>([]);
|
||||
|
||||
private paginator?: MatPaginator;
|
||||
private sort?: MatSort;
|
||||
|
||||
@ViewChild(MatPaginator)
|
||||
set matPaginator(paginator: MatPaginator | undefined) {
|
||||
this.paginator = paginator;
|
||||
if (paginator) {
|
||||
this.dataSource.paginator = paginator;
|
||||
}
|
||||
}
|
||||
|
||||
@ViewChild(MatSort)
|
||||
set matSort(sort: MatSort | undefined) {
|
||||
this.sort = sort;
|
||||
if (sort) {
|
||||
this.dataSource.sort = sort;
|
||||
}
|
||||
}
|
||||
constructor(private teamService: TeamsService,
|
||||
private dialog: MatDialog,
|
||||
private snackBar: MatSnackBar,
|
||||
@Inject(MAT_DIALOG_DATA) public data: DialogData,) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.load();
|
||||
}
|
||||
|
||||
load(): void {
|
||||
this.isLoading = true;
|
||||
this.loadError = false;
|
||||
this.teamService.loadTeamsTransactions(this.data.id).subscribe({
|
||||
next: data => {
|
||||
this.setupData(data);
|
||||
},
|
||||
error: () => {
|
||||
this.isLoading = false;
|
||||
this.loadError = true;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setupData(data: Transaction[]) {
|
||||
this.isLoading = false;
|
||||
this.dataSource = new MatTableDataSource<Transaction>(data);
|
||||
|
||||
if (this.paginator) {
|
||||
this.dataSource.paginator = this.paginator;
|
||||
}
|
||||
if (this.sort) {
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
}
|
||||
|
||||
applyFilter(event: Event) {
|
||||
const filterValue = (event.target as HTMLInputElement).value;
|
||||
this.dataSource.filter = filterValue.trim().toLowerCase();
|
||||
|
||||
if (this.dataSource.paginator) {
|
||||
this.dataSource.paginator.firstPage();
|
||||
}
|
||||
}
|
||||
|
||||
reverseTransaction(transaction: Transaction) {
|
||||
if (transaction.isTeamWalletTransaction || transaction.note?.startsWith('Stornierung von Buchung #')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmRef = this.dialog.open(ConfirmDialogComponent, {
|
||||
data: {
|
||||
title: 'Buchung stornieren',
|
||||
message: `Buchung über ${transaction.amount} € (${transaction.playerName}, ${transaction.type}) wirklich stornieren? Es wird eine Ausgleichsbuchung erstellt, die Originalbuchung bleibt sichtbar.`,
|
||||
confirmLabel: 'Stornieren'
|
||||
}
|
||||
});
|
||||
|
||||
confirmRef.afterClosed().subscribe(confirmed => {
|
||||
if (!confirmed) { return; }
|
||||
|
||||
this.teamService.reverseTransaction(transaction.id).subscribe({
|
||||
next: () => {
|
||||
this.snackBar.open('Buchung wurde storniert', undefined, { duration: 5000 });
|
||||
this.load();
|
||||
},
|
||||
error: (err) => {
|
||||
const message = err?.error?.message || 'Buchung konnte nicht storniert werden';
|
||||
this.snackBar.open(message, undefined, { duration: 5000, panelClass: 'snackbar_error' });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
toggleNote() {
|
||||
const index = this.displayedColumns.indexOf('note');
|
||||
if (index != -1) {
|
||||
this.displayedColumns = this.displayedColumns.filter(column => column !== 'note');
|
||||
} else {
|
||||
this.displayedColumns = [...this.displayedColumns, 'note'];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface DialogData {
|
||||
id: number;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<h1 style="text-align: center;">Buchung anlegen</h1>
|
||||
|
||||
<app-transaction-form #formComponent [transactionTypes]="[1, 14]" [totalSlider]="false" [text]="explanation"></app-transaction-form>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button mat-button color="warn" (click)="close()">Abbrechen</button>
|
||||
<button mat-button mat-flat-button color="primary" (click)="onOkClick()" [disabled]="formInvalid" >Anlegen</button>
|
||||
</mat-dialog-actions>
|
||||
<!-- <div class="flex-row" style="margin-top: 24px">
|
||||
|
||||
</div> -->
|
||||
@@ -0,0 +1,6 @@
|
||||
:host {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
padding: 2px 4px 4px 4px;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TeamTransactionDialogComponent } from './team-transaction-dialog.component';
|
||||
|
||||
describe('TeamTransactionDialogComponent', () => {
|
||||
let component: TeamTransactionDialogComponent;
|
||||
let fixture: ComponentFixture<TeamTransactionDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ TeamTransactionDialogComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TeamTransactionDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Component, Inject, ViewChild } from '@angular/core';
|
||||
import { FormGroup, FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { TransactionFormComponent } from '../components/transaction-form/transaction-form.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
|
||||
@Component({
|
||||
selector: 'app-team-transaction-dialog',
|
||||
templateUrl: './team-transaction-dialog.component.html',
|
||||
styleUrls: ['./team-transaction-dialog.component.scss'],
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, ReactiveFormsModule, MatFormFieldModule, TransactionFormComponent, MatButtonModule, MatDialogModule]
|
||||
})
|
||||
export class TeamTransactionDialogComponent {
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<TeamTransactionDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: DialogData,
|
||||
) {}
|
||||
|
||||
@ViewChild('formComponent') formComponent!: TransactionFormComponent;
|
||||
|
||||
explanation = 'Hier kann eine Ausgabe oder Einnahme des Teams gebucht werden.';
|
||||
|
||||
|
||||
onOkClick() {
|
||||
const form = this.formComponent.form;
|
||||
const transaction = { ...form.value, teamId: this.data.teamId };
|
||||
|
||||
if (transaction.amount != null && transaction.teamId != null && transaction.type != null) {
|
||||
this.dialogRef.close(transaction);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
get formInvalid(): boolean {
|
||||
return !this.formComponent || !this.formComponent.form.value || this.formComponent.form.value.amount == null ||
|
||||
this.formComponent.form.value.total == null || this.formComponent.form.value.type == null;
|
||||
}
|
||||
}
|
||||
|
||||
interface DialogData {
|
||||
teamId: number;
|
||||
}
|
||||
1
myteamwallet_frontend/src/app/shared/index.ts
Normal file
1
myteamwallet_frontend/src/app/shared/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './dialog';
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface Penalty {
|
||||
id: number;
|
||||
description: string;
|
||||
createdAt: string;
|
||||
amount: number;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<h2 mat-dialog-title>Strafenkatalog</h2>
|
||||
<mat-dialog-content>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Suche</mat-label>
|
||||
<input matInput type="text" [(ngModel)]="searchStr">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@if(penalties.length > 0) {
|
||||
<mat-list role="list">
|
||||
<mat-list-item role="listitem" *ngFor="let p of getPenalties">
|
||||
<span matListItemTitle>{{ p.description }}</span>
|
||||
<span matListItemLine>{{ p.amount | currency:'EUR' }}</span>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
} @else {
|
||||
<div>Nix da...</div>
|
||||
}
|
||||
</mat-dialog-content>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PenaltiesComponent } from './penalties.component';
|
||||
|
||||
describe('PenaltiesComponent', () => {
|
||||
let component: PenaltiesComponent;
|
||||
let fixture: ComponentFixture<PenaltiesComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PenaltiesComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PenaltiesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Component, Inject, inject } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { Penalty } from './model/penalty.interface';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
|
||||
@Component({
|
||||
selector: 'app-penalties',
|
||||
standalone: true,
|
||||
imports: [MatDialogModule, MatButtonModule, MatListModule, CommonModule, MatInputModule, FormsModule, ReactiveFormsModule],
|
||||
templateUrl: './penalties.component.html',
|
||||
styleUrl: './penalties.component.scss'
|
||||
})
|
||||
export class PenaltiesComponent {
|
||||
|
||||
searchStr = "";
|
||||
|
||||
private http: HttpClient = inject(HttpClient);
|
||||
public penalties: Penalty[] = [];
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<PenaltiesComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public teamId: string,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadPenalties();
|
||||
}
|
||||
|
||||
get getPenalties(): any[] {
|
||||
|
||||
return this.penalties.filter(p => p.description.toLocaleLowerCase().includes(this.searchStr.toLowerCase()))
|
||||
return [];
|
||||
}
|
||||
|
||||
loadPenalties() {
|
||||
const url = `${environment.apiUrl}penalty/` + this.teamId;
|
||||
this.http.get(url).subscribe({
|
||||
next: n => {
|
||||
this.penalties = n as Penalty[];
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
15
myteamwallet_frontend/src/app/shared/shared.module.ts
Normal file
15
myteamwallet_frontend/src/app/shared/shared.module.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { UnderConstructionComponent } from './under-construction/under-construction.component';
|
||||
import { DialogModule } from './dialog/dialog.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [ UnderConstructionComponent ],
|
||||
imports: [
|
||||
CommonModule,
|
||||
DialogModule
|
||||
],
|
||||
exports: [ UnderConstructionComponent ]
|
||||
})
|
||||
export class SharedModule { }
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
|
||||
<div class="mat-h1">Under Construction</div>
|
||||
<div class="mat-body">please stand by for news</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
:host {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UnderConstructionComponent } from './under-construction.component';
|
||||
|
||||
describe('UnderConstructionComponent', () => {
|
||||
let component: UnderConstructionComponent;
|
||||
let fixture: ComponentFixture<UnderConstructionComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ UnderConstructionComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(UnderConstructionComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-under-construction',
|
||||
templateUrl: './under-construction.component.html',
|
||||
styleUrls: ['./under-construction.component.scss']
|
||||
})
|
||||
export class UnderConstructionComponent {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user