Merge remote-tracking branch 'origin/master'

master
Max Ehrlicher-Schmidt 4 years ago
commit bc1d1d4cae

@ -0,0 +1,171 @@
<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 && !isLoading">
<h1 class="headline">
{{ getHeadline !== undefined ? getHeadline(data) : data[headlineDataPath] }}
<mat-icon>{{ headlineIconName }}</mat-icon>
</h1>
<ng-container *ngFor="let object of propertiesInfo">
<mat-card
class="inline-card"
*ngIf="
object.type === 'Group' &&
(!object.hideCondition || !object.hideCondition(data))
"
>
<mat-card-title class="card-header">
<h2>{{ object.title }}</h2>
<button
mat-button
*ngIf="data?.isLockedByMe && object.possibleObjects"
(click)="openSelectObjectDialog(object)"
>
<mat-icon>expand_more</mat-icon>
</button>
</mat-card-title>
<ng-container *ngFor="let prop of object.properties">
<app-cell
*ngIf="
prop.type !== 'NumRange' &&
prop.type !== 'Link' &&
prop.type !== 'DateRange'
"
[isList]="prop.list"
[editable]="data?.isLockedByMe && prop.acceptedForUpdating"
[required]="prop.requiredForUpdating && data?.isLockedByMe"
(validityChange)="validityChange(prop.dataPath, $event)"
[(value)]="data[prop.dataPath]"
[label]="prop.translation || prop.dataPath"
[inputType]="prop.type"
></app-cell>
<app-number-range-cell
*ngIf="prop.type === 'NumRange'"
[editable]="data?.isLockedByMe && prop.acceptedForUpdating"
(validityChange)="validityChange(prop.dataPath, $event)"
[(min)]="data[prop.dataPath + '.min']"
[(max)]="data[prop.dataPath + '.max']"
[label]="prop.translation || prop.dataPath"
></app-number-range-cell>
<app-date-range-cell
*ngIf="prop.type === 'DateRange'"
[editable]="data?.isLockedByMe && prop.acceptedForUpdating"
[required]="prop.requiredForUpdating && data?.isLockedByMe"
(validityChange)="validityChange(prop.dataPath, $event)"
[(from)]="data[prop.dataPath + '.from']"
[(to)]="data[prop.dataPath + '.to']"
></app-date-range-cell>
<a
mat-button
class="link-button"
color="primary"
*ngIf="prop.type === 'Link'"
[routerLink]="prop.link(data)"
>{{ prop.linkText }}
</a>
</ng-container>
</mat-card>
<mat-card
class="inline-card"
*ngIf="
object.type === 'ReferenceTable' &&
(!object.hideCondition || !object.hideCondition(data))
"
>
<mat-card-title
><h2>{{ object.title }}</h2>
<a
mat-button
*ngIf="object.linkToTable"
color="primary"
[routerLink]="object.linkToTable(data)"
[queryParams]="
object.linkToTableParams ? object.linkToTableParams(data) : {}
"
matTooltip="Zur Tabelle"
>
<mat-icon>subdirectory_arrow_right</mat-icon>
<mat-icon>table_chart</mat-icon>
</a>
</mat-card-title>
<app-reference-table
[dataServiceThatProvidesThePossibleData]="object.dataService"
[nameToShowInSelection]="object.nameToShowInSelection"
[columnInfo]="object.columnInfo"
[data]="data[object.dataPath]"
[editable]="data?.isLockedByMe"
[tableDataGQLType]="object.tableDataGQLType"
(referenceIds)="addReferenceIdsToObject($event, object)"
[editableReferences]="object.editableReferences"
></app-reference-table>
</mat-card>
</ng-container>
</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>
<div id="floating-fab-button-box">
<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-mini-fab
(click)="cancel()"
*ngIf="data?.isLockedByMe"
class="floating-fab-button"
[disabled]="isSavingOrLocking || isLoading"
>
<mat-icon>cancel</mat-icon>
</button>
<div
*ngIf="data?.isLockedByMe"
[matTooltip]="
countUnvalidProperties() > 0
? 'Ungültige oder nicht ausgefüllte Pflichtfelder (rot): ' +
countUnvalidProperties()
: 'Abspeichern'
"
>
<button
mat-fab
(click)="save()"
class="floating-fab-button"
[disabled]="
isSavingOrLocking || isLoading || countUnvalidProperties() > 0
"
>
<mat-icon>save</mat-icon>
</button>
</div>
<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>
</div>

@ -0,0 +1,43 @@
.page-loading-spinner {
margin: 0 auto;
}
.data-page-wrapper {
height: 100%;
box-sizing: border-box;
overflow: auto;
padding: 2em;
.inline-card {
display: inline-table;
min-width: 20em;
margin: 1em;
.link-button {
padding: 0;
margin-top: -16px;
}
}
}
#floating-fab-button-box {
z-index: 999;
position: absolute;
bottom: 2em;
right: 2em;
display: flex;
flex-direction: column;
align-items: center;
.floating-fab-button {
margin-top: 0.5em;
}
}
.floating-fab-button-top {
position: absolute;
top: 2em;
right: 2em;
}
.headline {
display: flex;
align-items: center;
.mat-icon {
margin-left: 8px;
}
}

@ -0,0 +1,246 @@
import {
Component,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute } from '@angular/router';
import { deepen } from '../../helperFunctions/deepenObject';
import { flatten } from '../../helperFunctions/flattenObject';
import { SchemaService } from '../../services/schema.service';
import { SelectObjectDialogComponent } from '../select-object-dialog/select-object-dialog.component';
interface PropertyTypeInfo {
dataPath: string;
translation: string;
acceptedForUpdating?: boolean;
requiredForUpdating?: boolean;
required?: boolean;
type?: string;
}
interface PropertyGroupInfo {
type: string;
title: string;
properties: PropertyTypeInfo[];
}
interface ReferenceTableInfo {
type: string;
title: string;
dataPath: string;
dataService: any;
columnInfo: PropertyTypeInfo[];
nameToShowInSelection: any;
propertyNameOfUpdateInput: string;
referenceIds: Array<string>;
}
@Component({
selector: 'app-admin-data-page',
templateUrl: './admin-data-page.component.html',
styleUrls: ['./admindata-page.component.scss'],
})
export class DataPageComponent implements OnInit, OnDestroy {
@Input()
propertiesInfo: Array<any> = [];
@Input()
dataService: any;
/** specifies which property should be shown in the headline */
@Input()
headlineDataPath: string;
/** specifies which string should be shown in the headline. If this is provided headlineDataPath is ignored*/
@Input()
getHeadline: (any) => string;
@Input()
headlineIconName: string = 'help_outline';
@Input()
pageDataGQLType: string;
@Input()
pageDataGQLUpdateInputType: string;
@Input()
propertyNameOfUpdateInput: string;
relockingInterval = null;
@Input()
relockingIntervalDuration = 1000 * 60 * 1;
@Output() lockEvent = new EventEmitter();
@Output() saveEvent = new EventEmitter();
@Output() cancelEvent = new EventEmitter();
id: string;
data: any = null;
isLoading: boolean = false;
isSavingOrLocking: boolean = false;
propertyValidity = {};
constructor(
private route: ActivatedRoute,
private schemaService: SchemaService,
public dialog: MatDialog
) {}
ngOnInit(): void {
this.addPropertiesFromGQLSchemaToObject(this.propertiesInfo);
this.id = this.route.snapshot.paramMap.get('id');
this.reloadPageData();
this.dataService.pageData.subscribe((data) => {
if (data == null) {
this.data = null;
} else if (this.data?.isLockedByMe && data?.isLockedByMe) {
// dont overwrite data when in edit mode and relock is performed
return;
} else {
this.data = flatten(data);
}
});
this.dataService.isLoadingPageData.subscribe(
(isLoading) => (this.isLoading = isLoading)
);
this.dataService.loadingRowIds.subscribe((loadingRowIds) => {
this.isSavingOrLocking = loadingRowIds.includes(this.id);
});
this.relockingInterval = setInterval(() => {
if (this.data?.isLockedByMe) {
this.lock();
}
}, this.relockingIntervalDuration);
}
ngOnDestroy() {
clearInterval(this.relockingInterval);
}
addPropertiesFromGQLSchemaToObject(infoObject: any) {
for (const prop of infoObject) {
if (prop.type === 'Link') {
continue;
}
if (prop.type === 'Group') {
this.addPropertiesFromGQLSchemaToObject(prop.properties);
} else if (prop.type === 'ReferenceTable') {
prop.tableDataGQLType =
prop.tableDataGQLType ||
this.schemaService.getTypeInformation(
this.pageDataGQLType,
prop.dataPath
).type;
if (!prop.type) {
console.error(
"Didn't found type for: " +
prop.dataPath +
' on ' +
this.pageDataGQLType
);
}
prop.referenceIds = [];
} else {
const typeInformation = this.schemaService.getTypeInformation(
this.pageDataGQLType,
prop.dataPath
);
prop.type = prop.type || typeInformation.type;
prop.list = typeInformation.isList;
if (!prop.type) {
console.error(
"Didn't found type for: " +
prop.dataPath +
' on ' +
this.pageDataGQLType
);
}
prop.required =
prop.required != null ? prop.required : typeInformation.isRequired;
const updateTypeInformation = this.schemaService.getTypeInformation(
this.pageDataGQLUpdateInputType,
prop.dataPath
);
prop.acceptedForUpdating =
prop.acceptedForUpdating != null
? prop.acceptedForUpdating
: updateTypeInformation.isPartOfType;
prop.requiredForUpdating =
prop.requiredForUpdating != null
? prop.requiredForUpdating
: prop.required || typeInformation.isRequired;
}
}
}
lock() {
this.lockEvent.emit(deepen(this.data));
}
validityChange(propName: string, isValid: Event) {
this.propertyValidity[propName] = isValid;
}
countUnvalidProperties() {
let unvalidFieldsCount = 0;
for (const prop in this.propertyValidity) {
if (!this.propertyValidity[prop]) {
unvalidFieldsCount++;
}
}
return unvalidFieldsCount;
}
save() {
this.saveEvent.emit(
this.schemaService.filterObject(
this.pageDataGQLUpdateInputType,
deepen(this.data)
)
);
}
cancel() {
this.cancelEvent.emit(deepen(this.data));
}
openSelectObjectDialog(object: any) {
const dialogRef = this.dialog.open(SelectObjectDialogComponent, {
width: 'auto',
autoFocus: false,
data: {
nameToShowInSelection: object.nameToShowInSelection,
currentlySelectedObjectId: object.currentlySelectedObjectId(this.data),
possibleObjects: object.possibleObjects,
},
});
dialogRef.afterClosed().subscribe((selectedObject) => {
if (selectedObject) {
this.data[object.propertyNameOfReferenceId] = selectedObject.id;
const newObjectFlattened = flatten(selectedObject);
for (const newProperty in newObjectFlattened) {
this.data[object.propertyPrefixToOverwrite + '.' + newProperty] =
newObjectFlattened[newProperty];
}
} else if (selectedObject === null) {
this.data[object.propertyNameOfReferenceId] = null;
for (const prop in this.data) {
if (prop.startsWith(object.propertyPrefixToOverwrite)) {
this.data[prop] = null;
}
}
}
});
}
addReferenceIdsToObject(ids: string[], object) {
this.data[object.propertyNameOfUpdateInput] = ids;
}
reloadPageData() {
this.dataService.loadPageData({ id: this.id });
}
}

@ -20,7 +20,8 @@ export class SidenavProfileComponent implements OnInit {
if (user !== null){ if (user !== null){
this.name = user.user.name; this.name = user.user.name;
this.email = user.user.email; this.email = user.user.email;
if (user.user.attributes === null) return; if (user.user.attributes === null || (Object.keys(user.user.attributes).length === 0 && user.user.attributes.constructor === Object)) return;
console.log("profile url: " + JSON.stringify(user.user.attributes));
this.profileURL = user.user.attributes.profile_url; this.profileURL = user.user.attributes.profile_url;
} }
}); });

Loading…
Cancel
Save