Create Page template component

urls
Max Ehrlicher-Schmidt 4 years ago
parent 314fb3c33d
commit 14c080195e

@ -45,7 +45,8 @@ import {SidenavProfileComponent} from './components/sidenav-profile/sidenav-prof
import { NavService }from './components/menu-list-item/nav.service'; import { NavService }from './components/menu-list-item/nav.service';
import { TokenInterceptor } from './helper/token.interceptor'; import { TokenInterceptor } from './helper/token.interceptor';
import { BikeComponent } from './pages/dataPages/bike/bike.component'; import { BikeComponent } from './pages/dataPages/bike/bike.component';
import { TableComponent, DeleteConfirmationDialog } from './components/table/table.component' import { TableComponent, DeleteConfirmationDialog } from './components/table/table.component';
import { DataPageComponent } from './components/data-page/data-page.component'
@NgModule({ @NgModule({
@ -61,7 +62,8 @@ import { TableComponent, DeleteConfirmationDialog } from './components/table/tab
CellComponent, CellComponent,
DeleteConfirmationDialog, DeleteConfirmationDialog,
BikeComponent, BikeComponent,
TableComponent TableComponent,
DataPageComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

@ -0,0 +1,62 @@
<mat-spinner
*ngIf="isLoading"
[diameter]="32"
class="page-loading-spinner"
></mat-spinner>
<div class="page-wrapper" *ngIf="!isLoading && !data">
<h1>Seite konnte nicht gefunden werden :(</h1>
</div>
<div class="data-page-wrapper" *ngIf="data">
<h1>
{{ data[headlineDataPath] }}
</h1>
<div *ngFor="let prop of propertiesInfo">
<app-cell
[editable]="data.isLockedByMe && !prop.readonly"
[(value)]="data[prop.name]"
[label]="prop.translation || prop.name"
[inputType]="prop.type"
></app-cell>
</div>
</div>
<button
mat-mini-fab
(click)="reloadPageData()"
class="floating-fab-button-top"
[disabled]="isSavingOrLocking || isLoading"
color="primary"
matTooltip="Daten aktualisieren. Achtung! Alle ungespeicherten Änderungen gehen verloren."
>
<mat-icon>sync</mat-icon>
</button>
<button
mat-fab
(click)="lock()"
*ngIf="!data.isLockedByMe && !data.isLocked"
class="floating-fab-button"
color="primary"
[disabled]="isSavingOrLocking || isLoading"
>
<mat-icon>edit</mat-icon>
</button>
<button
mat-fab
(click)="save()"
*ngIf="data.isLockedByMe"
class="floating-fab-button"
[disabled]="isSavingOrLocking || isLoading"
>
<mat-icon>save</mat-icon>
</button>
<button
mat-fab
*ngIf="data.isLocked"
matTooltip="Dieser Eintrag wird gerade von einem anderen Bearbeiter editiert. Aktualisieren Sie die Seite, um den neuen Status abzurufen."
class="floating-fab-button"
>
<mat-icon>locked</mat-icon>
</button>

@ -0,0 +1,19 @@
.page-loading-spinner {
margin: 0 auto;
}
.data-page-wrapper {
height: 100%;
box-sizing: border-box;
overflow: auto;
padding: 2em;
}
.floating-fab-button {
position: absolute;
bottom: 2em;
right: 2em;
}
.floating-fab-button-top {
position: absolute;
top: 2em;
right: 2em;
}

@ -0,0 +1,102 @@
import {
AfterViewInit,
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { deepen } from 'src/app/helperFunctions/deepenObject';
import { flatten } from 'src/app/helperFunctions/flattenObject';
import { BikesService } from 'src/app/services/bikes.service';
import { SchemaService } from 'src/app/services/schema.service';
import { runInThisContext } from 'vm';
@Component({
selector: 'app-data-page',
templateUrl: './data-page.component.html',
styleUrls: ['./data-page.component.scss'],
})
export class DataPageComponent implements OnInit {
@Input()
propertiesInfo: {
name: string;
translation: string;
readonly?: boolean;
type?: string;
}[] = [];
@Input()
dataService: any;
@Input()
headlineDataPath: string;
@Input()
pageDataGQLType: string = 'CargoBike';
@Input()
pageDataGQLUpdateInputType: string = 'CargoBikeUpdateInput';
@Output() lockEvent = new EventEmitter();
@Output() saveEvent = new EventEmitter();
id: string;
data: any;
isLoading: boolean = false;
isSavingOrLocking: boolean = false;
constructor(
private route: ActivatedRoute,
private schemaService: SchemaService
) {}
ngOnInit(): void {
this.addPropertiesFromGQLSchemaToPropertiesInfo();
this.id = this.route.snapshot.paramMap.get('id');
this.reloadPageData();
this.dataService.pageData.subscribe((data) => {
this.data = flatten(data);
});
this.dataService.isLoadingPageData.subscribe(
(isLoading) => (this.isLoading = isLoading)
);
this.dataService.loadingRowIds.subscribe(loadingRowIds => {
console.log(loadingRowIds);
this.isSavingOrLocking = loadingRowIds.includes(this.id);
})
}
addPropertiesFromGQLSchemaToPropertiesInfo() {
for (const column of this.propertiesInfo) {
const typeInformation = this.schemaService.getTypeInformation(
this.pageDataGQLType,
column.name
);
column.type = column.type || typeInformation.type;
}
for (const column of this.propertiesInfo) {
const typeInformation = this.schemaService.getTypeInformation(
this.pageDataGQLUpdateInputType,
column.name
);
column.readonly = column.readonly || !typeInformation.isPartOfType;
}
}
lock() {
this.lockEvent.emit(deepen(this.data));
}
save() {
this.saveEvent.emit(
this.schemaService.filterObject(
this.pageDataGQLUpdateInputType,
deepen(this.data)
)
);
}
reloadPageData() {
this.dataService.loadPageData({ id: this.id });
}
}

@ -136,7 +136,7 @@
<div class="button-wrapper" *ngIf="!element.newObject"> <div class="button-wrapper" *ngIf="!element.newObject">
<button <button
mat-icon-button mat-icon-button
(click)="edit(element)" (click)="lock(element)"
*ngIf=" *ngIf="
!element.isLockedByMe && !element.isLockedByMe &&
!element.isLocked && !element.isLocked &&

@ -7,7 +7,7 @@ import {
ViewChild, ViewChild,
} from '@angular/core'; } from '@angular/core';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { BikesService, CargoBikeResult } from 'src/app/services/bikes.service'; import { CargoBikeResult } from 'src/app/services/bikes.service';
import { flatten } from 'src/app/helperFunctions/flattenObject'; import { flatten } from 'src/app/helperFunctions/flattenObject';
import { deepen } from 'src/app/helperFunctions/deepenObject'; import { deepen } from 'src/app/helperFunctions/deepenObject';
import { SchemaService } from 'src/app/services/schema.service'; import { SchemaService } from 'src/app/services/schema.service';
@ -65,17 +65,16 @@ export class TableComponent {
relockingInterval = null; relockingInterval = null;
@Input() @Input()
relockingIntervalDuration = 1000 * 60 * 1; relockingIntervalDuration = 1000 * 10 * 1; // TODO: set back to 60s
filter = { includesString: '', onlyUnsaved: false }; filter = { includesString: '', onlyUnsaved: false };
initialFilter = this.filter; initialFilter = this.filter;
isLoaded = false; isLoaded = false;
@Output() createEvent = new EventEmitter(); @Output() createEvent = new EventEmitter();
@Output() editEvent = new EventEmitter(); @Output() lockEvent = new EventEmitter();
@Output() saveEvent = new EventEmitter(); @Output() saveEvent = new EventEmitter();
@Output() cancelEvent = new EventEmitter(); @Output() cancelEvent = new EventEmitter();
@Output() deleteEvent = new EventEmitter(); @Output() deleteEvent = new EventEmitter();
@Output() relockEvent = new EventEmitter();
constructor(private schemaService: SchemaService, public dialog: MatDialog) {} constructor(private schemaService: SchemaService, public dialog: MatDialog) {}
@ -128,6 +127,7 @@ export class TableComponent {
} else if (!(oldRow.isLockedByMe && row.isLockedByMe)) { } else if (!(oldRow.isLockedByMe && row.isLockedByMe)) {
tempDataSource.push(flatten(row)); tempDataSource.push(flatten(row));
} else if (!!oldRow) { } else if (!!oldRow) {
//old row is getting edited
tempDataSource.push(oldRow); tempDataSource.push(oldRow);
} }
} }
@ -143,7 +143,7 @@ export class TableComponent {
this.relockingInterval = setInterval(() => { this.relockingInterval = setInterval(() => {
for (const row of this.data.data) { for (const row of this.data.data) {
if (row.isLockedByMe) { if (row.isLockedByMe) {
this.relock(row); this.lock(row);
} }
} }
}, this.relockingIntervalDuration); }, this.relockingIntervalDuration);
@ -253,12 +253,8 @@ export class TableComponent {
this.createEvent.emit(newRow); this.createEvent.emit(newRow);
} }
edit(row: any) { lock(row: any) {
this.editEvent.emit(row); this.lockEvent.emit(deepen(row));
}
relock(row: any) {
this.relockEvent.emit(row);
} }
countUnsavedRows(): number { countUnsavedRows(): number {

@ -1,20 +1,9 @@
<mat-spinner <app-data-page
*ngIf="isLoading" [dataService]="dataService"
[diameter]="32" [propertiesInfo]="propertiesInfo"
class="page-loading-spinner" [headlineDataPath]="headlineDataPath"
></mat-spinner> [pageDataGQLType]="pageDataGQLType"
<div class="page-wrapper" *ngIf="!isLoading && !data"> [pageDataGQLUpdateInputType]="pageDataGQLUpdateInputType"
<h1>Seite konnte nicht gefunden werden :(</h1> (lockEvent)="lock($event)"
</div> (saveEvent)="save($event)"
<div class="page-wrapper" *ngIf="data"> ></app-data-page>
<h1>{{ data[headlineDataPath] }}</h1>
<div *ngFor="let prop of propertiesInfo">
<app-cell
[editable]="data.isLockedByMe && !prop.readonly"
[(value)]="data[prop.name]"
[label]="prop.translation || prop.name"
[inputType]="prop.type"
></app-cell>
</div>
</div>

@ -1,6 +0,0 @@
.page-loading-spinner {
margin: 0 auto;
}
.page-wrapper {
padding: 1em;
}

@ -10,12 +10,7 @@ import { SchemaService } from 'src/app/services/schema.service';
styleUrls: ['./bike.component.scss'], styleUrls: ['./bike.component.scss'],
}) })
export class BikeComponent implements OnInit { export class BikeComponent implements OnInit {
propertiesInfo: { propertiesInfo = [
name: string;
translation: string;
readonly?: boolean;
type?: string;
}[] = [
{ name: 'name', translation: 'Name' }, { name: 'name', translation: 'Name' },
{ name: 'id', translation: 'ID', readonly: true }, { name: 'id', translation: 'ID', readonly: true },
{ name: 'group', translation: 'Gruppe' }, { name: 'group', translation: 'Gruppe' },
@ -105,46 +100,22 @@ export class BikeComponent implements OnInit {
]; ];
headlineDataPath = 'name'; headlineDataPath = 'name';
pageDataGQLType: string = "CargoBike"; pageDataGQLType: string = 'CargoBike';
pageDataGQLUpdateInputType: string = "CargoBikeUpdateInput" pageDataGQLUpdateInputType: string = 'CargoBikeUpdateInput';
id: string; dataService: any;
data: any;
isLoading: boolean = false;
constructor( constructor(private bikesService: BikesService) {}
private route: ActivatedRoute,
private bikesService: BikesService,
private schemaService: SchemaService
) {
this.addPropertiesFromGQLSchemaToPropertiesInfo();
}
ngOnInit(): void { ngOnInit(): void {
this.id = this.route.snapshot.paramMap.get('id'); this.dataService = this.bikesService;
this.bikesService.loadPageData({ id: this.id });
this.bikesService.pageData.subscribe((data) => {
this.data = flatten(data);
});
this.bikesService.loadingBike.subscribe(
(isLoading) => (this.isLoading = isLoading)
);
} }
addPropertiesFromGQLSchemaToPropertiesInfo() { lock(row: any) {
for (const column of this.propertiesInfo) { this.bikesService.lockBike({id: row.id});
const typeInformation = this.schemaService.getTypeInformation(
this.pageDataGQLType,
column.name
);
column.type = column.type || typeInformation.type;
}
for (const column of this.propertiesInfo) {
const typeInformation = this.schemaService.getTypeInformation(
this.pageDataGQLUpdateInputType,
column.name
);
column.readonly = column.readonly || !typeInformation.isPartOfType;
} }
save(row: any) {
this.bikesService.updateBike({bike: row})
} }
} }

@ -5,8 +5,7 @@
[tableDataGQLCreateInputType]="tableDataGQLCreateInputType" [tableDataGQLCreateInputType]="tableDataGQLCreateInputType"
[tableDataGQLUpdateInputType]="tableDataGQLUpdateInputType" [tableDataGQLUpdateInputType]="tableDataGQLUpdateInputType"
(createEvent)="create($event)" (createEvent)="create($event)"
(editEvent)="edit($event)" (lockEvent)="lock($event)"
(relockEvent)="relock($event)"
(saveEvent)="save($event)" (saveEvent)="save($event)"
(cancelEvent)="cancel($event)" (cancelEvent)="cancel($event)"
(deleteEvent)="delete($event)" (deleteEvent)="delete($event)"

@ -128,14 +128,10 @@ export class BikesComponent {
this.bikesService.createBike({ bike: row }); this.bikesService.createBike({ bike: row });
} }
edit(row: any) { lock(row: any) {
this.bikesService.lockBike({ id: row.id }); this.bikesService.lockBike({ id: row.id });
} }
relock(row: any) {
this.bikesService.relockBike({ id: row.id });
}
save(row: any) { save(row: any) {
this.bikesService.updateBike({ bike: row }); this.bikesService.updateBike({ bike: row });
} }

@ -33,8 +33,7 @@ export class BikesService {
tableData: BehaviorSubject<CargoBikeResult[]> = new BehaviorSubject([]); tableData: BehaviorSubject<CargoBikeResult[]> = new BehaviorSubject([]);
loadingRowIds: BehaviorSubject<string[]> = new BehaviorSubject([]); loadingRowIds: BehaviorSubject<string[]> = new BehaviorSubject([]);
pageData: BehaviorSubject<any> = new BehaviorSubject([]); pageData: BehaviorSubject<any> = new BehaviorSubject([]);
loadingBike: BehaviorSubject<boolean> = new BehaviorSubject(false); isLoadingPageData: BehaviorSubject<boolean> = new BehaviorSubject(false);
constructor( constructor(
private getCargoBikesGQL: GetCargoBikesGQL, private getCargoBikesGQL: GetCargoBikesGQL,
@ -44,7 +43,7 @@ export class BikesService {
private lockCargoBikeGQL: LockCargoBikeGQL, private lockCargoBikeGQL: LockCargoBikeGQL,
private unlockCargoBikeGQL: UnlockCargoBikeGQL, private unlockCargoBikeGQL: UnlockCargoBikeGQL,
private createCargoBikeGQL: CreateCargoBikeGQL, private createCargoBikeGQL: CreateCargoBikeGQL,
private deleteCargoBikeGQL: DeleteCargoBikeGQL, private deleteCargoBikeGQL: DeleteCargoBikeGQL
) {} ) {}
addLoadingRowId(id: string) { addLoadingRowId(id: string) {
@ -57,25 +56,25 @@ export class BikesService {
this.loadingRowIds.value.splice(index, 1); this.loadingRowIds.value.splice(index, 1);
} }
}); });
this.loadingRowIds.next(this.loadingRowIds.value);
} }
loadTableData() { loadTableData() {
this.getCargoBikesGQL.fetch().subscribe((result) => { this.getCargoBikesGQL.fetch().subscribe((result) => {
this.tableData.next(result.data.cargoBikes); this.tableData.next(result.data.cargoBikes);
}); });
} }
loadPageData(variables: GetCargoBikeByIdQueryVariables) { loadPageData(variables: GetCargoBikeByIdQueryVariables) {
this.pageData.next(null); this.pageData.next(null);
this.loadingBike.next(true); this.isLoadingPageData.next(true);
this.getCargoBikeByIdGQL this.getCargoBikeByIdGQL
.fetch(variables) .fetch(variables)
.subscribe((result) => { .subscribe((result) => {
this.pageData.next(result.data.cargoBikeById); this.pageData.next(result.data.cargoBikeById);
}) })
.add(() => { .add(() => {
this.loadingBike.next(false); this.isLoadingPageData.next(false);
}); });
} }
@ -84,12 +83,7 @@ export class BikesService {
this.reloadCargoBikeByIdGQL this.reloadCargoBikeByIdGQL
.fetch(variables) .fetch(variables)
.subscribe((result) => { .subscribe((result) => {
const newBike = result.data.cargoBikeById; this.updateDataRowFromResponse(result.data.cargoBikeById)
this.tableData.next(
this.tableData.value.map((bike) =>
newBike.id === bike.id ? newBike : bike
)
);
}) })
.add(() => { .add(() => {
this.removeLoadingRowId(variables.id); this.removeLoadingRowId(variables.id);
@ -97,14 +91,10 @@ export class BikesService {
} }
createBike(variables: CreateCargoBikeMutationVariables) { createBike(variables: CreateCargoBikeMutationVariables) {
this.createCargoBikeGQL this.createCargoBikeGQL.mutate(variables).subscribe((result) => {
.mutate(variables)
.subscribe((result) => {
const newBike = result.data.createCargoBike; const newBike = result.data.createCargoBike;
this.tableData.next( this.tableData.next([newBike, ...this.tableData.value]);
[newBike, ...this.tableData.value] });
);
})
} }
updateBike(variables: UpdateCargoBikeMutationVariables) { updateBike(variables: UpdateCargoBikeMutationVariables) {
@ -112,12 +102,7 @@ export class BikesService {
this.updateCargoBikeGQL this.updateCargoBikeGQL
.mutate(variables) .mutate(variables)
.subscribe((result) => { .subscribe((result) => {
const newBike = result.data.updateCargoBike; this.updateDataRowFromResponse(result.data.updateCargoBike);
this.tableData.next(
this.tableData.value.map((bike) =>
newBike.id === bike.id ? newBike : bike
)
);
}) })
.add(() => { .add(() => {
this.removeLoadingRowId(variables.bike.id); this.removeLoadingRowId(variables.bike.id);
@ -129,12 +114,7 @@ export class BikesService {
this.lockCargoBikeGQL this.lockCargoBikeGQL
.mutate(variables) .mutate(variables)
.subscribe((result) => { .subscribe((result) => {
const lockedBike = result.data.lockCargoBike; this.updateDataRowFromResponse(result.data.lockCargoBike);
this.tableData.next(
this.tableData.value.map((bike) =>
lockedBike.id === bike.id ? lockedBike : bike
)
);
}) })
.add(() => { .add(() => {
this.removeLoadingRowId(variables.id); this.removeLoadingRowId(variables.id);
@ -146,12 +126,7 @@ export class BikesService {
this.unlockCargoBikeGQL this.unlockCargoBikeGQL
.mutate(variables) .mutate(variables)
.subscribe((result) => { .subscribe((result) => {
const unlockedBike = result.data.unlockCargoBike; this.updateDataRowFromResponse(result.data.unlockCargoBike);
this.tableData.next(
this.tableData.value.map((bike) =>
unlockedBike.id === bike.id ? unlockedBike : bike
)
);
}) })
.add(() => { .add(() => {
this.removeLoadingRowId(variables.id); this.removeLoadingRowId(variables.id);
@ -164,7 +139,9 @@ export class BikesService {
.mutate(variables) .mutate(variables)
.subscribe((result) => { .subscribe((result) => {
if (result.data.deleteCargoBike) { if (result.data.deleteCargoBike) {
this.tableData.next([...this.tableData.value].filter(bike => bike.id !== variables.id)); this.tableData.next(
[...this.tableData.value].filter((bike) => bike.id !== variables.id)
);
} }
}) })
.add(() => { .add(() => {
@ -172,7 +149,13 @@ export class BikesService {
}); });
} }
relockBike(variables: LockCargoBikeMutationVariables) { private updateDataRowFromResponse(rowFromResponse: any) {
this.lockCargoBikeGQL.mutate(variables).subscribe(); const newTableData = this.tableData.value.map((row) =>
rowFromResponse.id === row.id ? rowFromResponse : row
);
this.tableData.next(newTableData);
if ((rowFromResponse.id === this.pageData?.value?.id)) {
this.pageData.next(rowFromResponse);
}
} }
} }

Loading…
Cancel
Save