diff --git a/src/app/components/admin-data-page/admin-data-page.component.html b/src/app/components/admin-data-page/admin-data-page.component.html new file mode 100644 index 0000000..d8a50f2 --- /dev/null +++ b/src/app/components/admin-data-page/admin-data-page.component.html @@ -0,0 +1,171 @@ + +
+

Seite konnte nicht gefunden werden :(

+
+ +
+

+ {{ getHeadline !== undefined ? getHeadline(data) : data[headlineDataPath] }} + {{ headlineIconName }} +

+ + + +

{{ object.title }}

+ +
+ + + + + {{ prop.linkText }} + + +
+ + +

{{ object.title }}

+ + subdirectory_arrow_right + table_chart + +
+ +
+
+
+ + + +
+ + +
+ +
+ +
diff --git a/src/app/components/admin-data-page/admin-data-page.component.scss b/src/app/components/admin-data-page/admin-data-page.component.scss new file mode 100644 index 0000000..34de403 --- /dev/null +++ b/src/app/components/admin-data-page/admin-data-page.component.scss @@ -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; + } +} diff --git a/src/app/components/admin-data-page/admin-data-page.component.ts b/src/app/components/admin-data-page/admin-data-page.component.ts new file mode 100644 index 0000000..5f309ee --- /dev/null +++ b/src/app/components/admin-data-page/admin-data-page.component.ts @@ -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; +} + +@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 = []; + + @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 }); + } +} diff --git a/src/app/components/sidenav-profile/sidenav-profile.component.ts b/src/app/components/sidenav-profile/sidenav-profile.component.ts index db42af7..46dd7c7 100644 --- a/src/app/components/sidenav-profile/sidenav-profile.component.ts +++ b/src/app/components/sidenav-profile/sidenav-profile.component.ts @@ -20,7 +20,8 @@ export class SidenavProfileComponent implements OnInit { if (user !== null){ this.name = user.user.name; 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; } });