materialized

This commit is contained in:
2021-01-19 19:43:47 +01:00
parent ca206a86c4
commit 9bfdb6b636
18 changed files with 338 additions and 98 deletions

View File

@ -1,8 +1,3 @@
<h1>{{title}}</h1>
<nav>
<a routerLink="/objekte">Meine Objekte</a>
<a routerLink="/mieters">Meine Mieter</a>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
<section class="mat-typography">
<app-navigation></app-navigation>
</section>

View File

@ -11,6 +11,21 @@ import { WohnungenComponent } from './wohnungen/wohnungen.component';
import { MieterDetailComponent } from './mieter-detail/mieter-detail.component';
import { FormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NavigationComponent } from './navigation/navigation.component';
import { LayoutModule } from '@angular/cdk/layout';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { MatTableModule } from '@angular/material/table';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';
import { MatCardModule } from '@angular/material/card';
import { MatGridListModule } from '@angular/material/grid-list'
import { MatFormFieldModule } from '@angular/material/form-field'
import { MatDatepickerModule } from '@angular/material/datepicker'
import { MatInputModule } from '@angular/material/input'
@NgModule({
declarations: [
@ -19,14 +34,29 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
ObjekteComponent,
MietersComponent,
WohnungenComponent,
MieterDetailComponent
MieterDetailComponent,
NavigationComponent
],
imports: [
BrowserModule,
FormsModule,
AppRoutingModule,
HttpClientModule,
BrowserAnimationsModule
BrowserAnimationsModule,
LayoutModule,
MatToolbarModule,
MatButtonModule,
MatSidenavModule,
MatIconModule,
MatListModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatCardModule,
MatGridListModule,
MatFormFieldModule,
MatDatepickerModule,
MatInputModule
],
providers: [],
bootstrap: [AppComponent]

View File

@ -1,16 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { ForderungService } from './forderung.service';
describe('ForderungService', () => {
let service: ForderungService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ForderungService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -1,24 +0,0 @@
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Forderung } from './forderung';
import { MessageService } from './message.service';
import { serviceBaseUrl } from './config';
@Injectable({
providedIn: 'root'
})
export class ForderungService {
constructor(private messageService: MessageService, private http: HttpClient) { }
getForderungenByMieter(mieterId: number): Promise<Forderung[]> {
this.messageService.add(`ForderungService: fetched forderungen by mieter ${mieterId}`);
return this.http.get<Forderung[]>(`${serviceBaseUrl}/hv/mieter/${mieterId}/forderungen`).toPromise()
}
getForderung(id: number): Promise<Forderung> {
this.messageService.add(`ForderungService: fetch forderung id=${id}`);
return this.http.get<Forderung>(`${serviceBaseUrl}/hv/forderung/${id}`).toPromise()
}
}

View File

@ -1,7 +1,11 @@
<div *ngIf="messageService.messages.length">
<h2>Messages</h2>
<button (click)="messageService.clear()">clear</button>
<div *ngFor='let message of messageService.messages'> {{message}} </div>
</div>
<section class="mat-typography">
<mat-card class="defaultCard" *ngIf="messageService.messages.length">
<mat-card-header>
<mat-card-title>Messages</mat-card-title>
</mat-card-header>
<mat-card-content>
<button (click)="messageService.clear()">clear</button>
<div *ngFor='let message of messageService.messages'> {{message}} </div>
</mat-card-content>
</mat-card>
</section>

View File

@ -0,0 +1,36 @@
.label {
font-weight: bold;
margin-right: 10px;
text-align: left;
width: 50%;
}
.content::after {
content: "\A";
white-space: pre;
}
.content {
text-align: right;
width: 50%;
}
.details {
width: 30%;
}
table {
width: 100%;
}
#wrapper {
width: 100%;
overflow: auto;
}
#first {
float: left;
width: 35%;
}
#second {
float: left;
width: 20%;
}
#third {
float: left;
width: 35%
}

View File

@ -1,38 +1,114 @@
<h1>Mieter: {{mieter.vorname}} {{mieter.nachname}} ({{mieter.id}})</h1>
<section class="mat-typography">
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
{{mieter?.vorname}} {{mieter?.nachname}}
</mat-card-title>
<mat-card-subtitle>
ID: {{mieter?.id}}
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div class="details">
<span class="label">Objekt</span><span class="content">{{mieter?.objekt_shortname}} ({{mieter?.objekt}})</span>
<span class="label">Wohnung</span><span class="content">{{mieter?.wohnung_shortname}} ({{mieter?.wohnung}})</span>
<span class="label">Anrede</span><span class="content">{{mieter?.anrede}}</span>
<span class="label">Strasse</span><span class="content">{{mieter?.strasse}}</span>
<span class="label">PLZ</span><span class="content">{{mieter?.plz}}</span>
<span class="label">Ort</span><span class="content">{{mieter?.ort}}</span>
<span class="label">Telefon</span><span class="content">{{mieter?.telefon}}</span>
<span class="label">Einzug</span><span class="content">{{mieter?.einzug}}</span>
<span class="label">Auszug</span><span class="content">{{mieter?.auszug}}</span>
</div>
</mat-card-content>
</mat-card>
<table>
<tr><td>Objekt:</td><td>{{mieter.objekt_shortname}} ({{mieter.objekt}})</td></tr>
<tr><td>Wohnung:</td><td>{{mieter.wohnung_shortname}} ({{mieter.wohnung}})</td></tr>
<tr><td>Vorname:</td><td>{{mieter.vorname}}</td></tr>
<tr><td>Nachname:</td><td>{{mieter.nachname}}</td></tr>
<tr><td>Anrede:</td><td>{{mieter.anrede}}</td></tr>
<tr><td>Strasse:</td><td>{{mieter.strasse}}</td></tr>
<tr><td>PLZ:</td><td>{{mieter.plz}}</td></tr>
<tr><td>Ort:</td><td>{{mieter.ort}}</td></tr>
<tr><td>Telefon:</td><td>{{mieter.telefon}}</td></tr>
<tr><td>Einzug:</td><td>{{mieter.einzug}}</td></tr>
<tr><td>Auszug:</td><td>{{mieter.auszug}}</td></tr>
</table>
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
Forderungen und Zahlungen
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div id="wrapper">
<div id="first">
<!-- <label style="text-align: left;">Jahr:
<input [(ngModel)]="year" (input)="onYearInput()" placeholder="Jahr"/>
</label>-->
<mat-form-field appearance="outline">
<mat-label>Jahr</mat-label>
<!-- <input matInput [(ngModel)]="year" (input)="onYearInput()"/> -->
<input matInput [(ngModel)]="year" (input)="onYearInput()"/>
</mat-form-field>
</div>
<div id="second">
<div class="saldo">
<span class="label">Forderungen</span><span class="content">{{saldo?.forderungen}}</span>
<span class="label">Zahlungen</span><span class="content">{{saldo?.zahlungen}}</span>
<span class="label">Saldo</span><span class="content">{{saldo?.saldo}}</span>
</div>
</div>
<div id="third">
<div>
<label>Jahr:
<input [(ngModel)]="year" (input)="onYearInput()" placeholder="Jahr"/>
</label>
</div>
<mat-form-field appearance="outline">
<!--
<input matInput [matDatepicker]="picker"/>
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
-->
<mat-label>Datum soll</mat-label> <input matInput/>
</mat-form-field>
<table>
<tr>
<th>Datum soll</th>
<th>Datum ist</th>
<th>Forderung</th>
<th>Zahlung</th>
<th>Kommentar</th>
</tr>
<tr *ngFor="let zahlungForderung of zahlungenForderungen">
<td>{{zahlungForderung.datum_soll}}</td>
<td>{{zahlungForderung.datum_ist}}</td>
<td>{{zahlungForderung.betrag_forderung}}</td>
<td>{{zahlungForderung.betrag_zahlung}}</td>
<td>{{zahlungForderung.kommentar}}</td>
</tr>
</table>
<mat-form-field appearance="outline">
<mat-label>Datum ist</mat-label> <input matInput/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Betrag Zahlung</mat-label> <input matInput/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Kommentar</mat-label> <input matInput/>
</mat-form-field>
<button mat-raised-button color="primary">Erfassen</button>
</div>
</div>
</mat-card-content>
</mat-card>
<mat-card class="defaultCard" *ngIf="dataSource">
<mat-card-header>
<mat-card-title>
Forderungen und Zahlungen - Übersicht
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div>
<table mat-table [dataSource]="dataSource">
<ng-container matColumnDef="datum_soll">
<th mat-header-cell *matHeaderCellDef>Datum soll</th>
<td mat-cell *matCellDef="let element">{{element.datum_soll}}</td>
</ng-container>
<ng-container matColumnDef="datum_ist">
<th mat-header-cell *matHeaderCellDef>Datum ist</th>
<td mat-cell *matCellDef="let element">{{element.datum_ist}}</td>
</ng-container>
<ng-container matColumnDef="betrag_forderung">
<th mat-header-cell *matHeaderCellDef>Datum Forderung</th>
<td mat-cell *matCellDef="let element">{{element.betrag_forderung}}</td>
</ng-container>
<ng-container matColumnDef="betrag_zahlung">
<th mat-header-cell *matHeaderCellDef>Zahlung</th>
<td mat-cell *matCellDef="let element">{{element.betrag_zahlung}}</td>
</ng-container>
<ng-container matColumnDef="kommentar">
<th mat-header-cell *matHeaderCellDef>Kommentar</th>
<td mat-cell *matCellDef="let element">{{element.kommentar}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</section>

View File

@ -1,12 +1,15 @@
import { Component, OnInit } from '@angular/core'
import { Component, Input, OnInit } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { Location } from '@angular/common'
import { Mieter } from '../mieter'
import { ZahlungForderung } from '../zahlung-forderung'
import { Saldo } from '../saldo'
import { MieterService} from '../mieter.service'
import { ZahlungForderungService} from '../zahlung-forderung.service'
import { MessageService } from '../message.service'
import { MatTableDataSource } from '@angular/material/table'
@Component({
selector: 'app-mieter-detail',
@ -18,7 +21,10 @@ export class MieterDetailComponent implements OnInit {
mieter: Mieter
year: string
zahlungenForderungen : ZahlungForderung[]
saldo: Saldo
displayedColumns: string[] = ["datum_soll", "datum_ist", "betrag_forderung", "betrag_zahlung", "kommentar"]
dataSource: MatTableDataSource<ZahlungForderung>
constructor(
private mieterService: MieterService,
@ -28,27 +34,29 @@ export class MieterDetailComponent implements OnInit {
private location: Location ) {
}
async getMieter() {
async getMieter(): Promise<void> {
const id = +this.route.snapshot.paramMap.get('id')
try {
this.mieter = await this.mieterService.getMieter(id)
this.zahlungenForderungen = await this.zahlungForderungService.getZahlungenForderungenByMieterAndYear(id, +this.year)
await this.getZahlungenForderungen()
} catch (err) {
this.messageService.add(JSON.stringify(err, undefined, 4))
}
}
async getZahlungenForderungen() {
async getZahlungenForderungen(): Promise<void> {
const id = this.mieter?.id ?? 0
try {
this.zahlungenForderungen = await this.zahlungForderungService.getZahlungenForderungenByMieterAndYear(id, +this.year)
this.saldo = await this.zahlungForderungService.getSaldoByMieterAndYear(id, +this.year)
} catch (err) {
this.messageService.add(JSON.stringify(err, undefined, 4))
}
}
onYearInput(): void {
this.getZahlungenForderungen()
async onYearInput(): Promise<void> {
await this.getZahlungenForderungen()
this.dataSource = new MatTableDataSource<ZahlungForderung>(this.zahlungenForderungen)
}
async ngOnInit(): Promise<void> {

View File

@ -1,3 +1,4 @@
<section class="mat-typography">
<h2>Meine Mieter</h2>
<ul>
<li *ngFor="let mieter of mieters"
@ -5,3 +6,4 @@
<span>{{mieter.vorname}} {{mieter.nachname}}</span>
</li>
</ul>
</section>

View File

@ -0,0 +1,17 @@
.sidenav-container {
height: 100%;
}
.sidenav {
width: 200px;
}
.sidenav .mat-toolbar {
background: inherit;
}
.mat-toolbar.mat-primary {
position: sticky;
top: 0;
z-index: 1;
}

View File

@ -0,0 +1,30 @@
<section class="mat-typography">
<mat-sidenav-container class="sidenav-container">
<mat-sidenav #drawer class="sidenav" fixedInViewport
[attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
[mode]="(isHandset$ | async) ? 'over' : 'side'"
[opened]="(isHandset$ | async) === false">
<mat-toolbar>Menu</mat-toolbar>
<mat-nav-list>
<a mat-list-item href="/objekte">Meine Objekte</a>
<a mat-list-item href="/mieters">Meine Mieter</a>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content>
<mat-toolbar color="primary">
<button
type="button"
aria-label="Toggle sidenav"
mat-icon-button
(click)="drawer.toggle()"
*ngIf="isHandset$ | async">
<mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
</button>
<span>Hausverwaltung</span>
</mat-toolbar>
<!-- Add Content Here -->
<router-outlet></router-outlet>
<app-messages></app-messages>
</mat-sidenav-content>
</mat-sidenav-container>
</section>

View File

@ -0,0 +1,40 @@
import { LayoutModule } from '@angular/cdk/layout';
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatToolbarModule } from '@angular/material/toolbar';
import { NavigationComponent } from './navigation.component';
describe('NavigationComponent', () => {
let component: NavigationComponent;
let fixture: ComponentFixture<NavigationComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [NavigationComponent],
imports: [
NoopAnimationsModule,
LayoutModule,
MatButtonModule,
MatIconModule,
MatListModule,
MatSidenavModule,
MatToolbarModule,
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NavigationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should compile', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,21 @@
import { Component } from '@angular/core';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { Observable } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.css']
})
export class NavigationComponent {
isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset)
.pipe(
map(result => result.matches),
shareReplay()
);
constructor(private breakpointObserver: BreakpointObserver) {}
}

View File

@ -1,3 +1,4 @@
<section class="mat-typography">
<h2>Meine Objekte</h2>
<ul>
<li *ngFor="let objekt of objekte"
@ -5,3 +6,4 @@
<span>{{objekt.shortname}}</span>
</li>
</ul>
</section>

5
hv-ui/src/app/saldo.ts Normal file
View File

@ -0,0 +1,5 @@
export interface Saldo {
forderungen: number
zahlungen: number
saldo: number
}

View File

@ -1,3 +1,4 @@
<section class="mat-typography">
<h2>Meine Wohnungen</h2>
<h3>Objekt: {{objekt.shortname}}</h3>
<p>
@ -8,3 +9,4 @@
<span>{{wohnung.shortname}}</span>: <span>{{wohnung.flaeche}}</span>
</li>
</ul>
</section>

View File

@ -4,6 +4,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'
import { ZahlungForderung } from './zahlung-forderung'
import { Forderung } from './forderung'
import { Saldo } from './saldo'
import { MessageService } from './message.service'
import { serviceBaseUrl } from './config'
@ -22,4 +23,10 @@ export class ZahlungForderungService {
this.messageService.add(`ZahlungForderungService: fetch forderung id=${id}`)
return this.http.get<Forderung>(`${serviceBaseUrl}/hv/forderung/${id}`).toPromise()
}
getSaldoByMieterAndYear(mieterId: number, year: number): Promise<Saldo> {
this.messageService.add(`ZahlungForderungService: fetched saldo by mieter ${mieterId} and year ${year}`)
return this.http.get<Saldo>(`${serviceBaseUrl}/hv/mieter/${mieterId}/saldo/${year}`).toPromise()
}
}

View File

@ -1,3 +1,8 @@
/* @import '@angular/material/prebuilt-themes/deeppurple-amber.css'; */
html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
.defaultCard {
margin: 5px;
}