diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json index 9fce1d6..82b37a0 100644 --- a/Frontend/package-lock.json +++ b/Frontend/package-lock.json @@ -13,6 +13,7 @@ "@angular/compiler": "~12.2.0", "@angular/core": "~12.2.0", "@angular/forms": "~12.2.0", + "@angular/material": "^12.2.8", "@angular/platform-browser": "~12.2.0", "@angular/platform-browser-dynamic": "~12.2.0", "@angular/router": "~12.2.0", @@ -288,6 +289,30 @@ "@angular/core": "12.2.6" } }, + "node_modules/@angular/cdk": { + "version": "12.2.8", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.8.tgz", + "integrity": "sha512-M0Y61o0yEVLMg+DSNsaDgiOifAV6OdumTgt2/kNoSuauPRWS0bkZJE58k3LR+cPi1Cho3UXELMKMOXZN9AhofA==", + "peer": true, + "dependencies": { + "tslib": "^2.2.0" + }, + "optionalDependencies": { + "parse5": "^5.0.0" + }, + "peerDependencies": { + "@angular/common": "^12.0.0 || ^13.0.0-0", + "@angular/core": "^12.0.0 || ^13.0.0-0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@angular/cdk/node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "optional": true, + "peer": true + }, "node_modules/@angular/cli": { "version": "12.2.6", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-12.2.6.tgz", @@ -495,6 +520,22 @@ "node": ">=0.10.0" } }, + "node_modules/@angular/material": { + "version": "12.2.8", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-12.2.8.tgz", + "integrity": "sha512-wRTaTZIGC9+2e8aft44V9Qqwp3PsR9AG0FeJ0spl8mdOlYEqMMyoRXjvMiWIjo2ywxHLoQgLXXsWn3ip2xnnVg==", + "dependencies": { + "tslib": "^2.2.0" + }, + "peerDependencies": { + "@angular/animations": "^12.0.0 || ^13.0.0-0", + "@angular/cdk": "12.2.8", + "@angular/common": "^12.0.0 || ^13.0.0-0", + "@angular/core": "^12.0.0 || ^13.0.0-0", + "@angular/forms": "^12.0.0 || ^13.0.0-0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, "node_modules/@angular/platform-browser": { "version": "12.2.6", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.6.tgz", @@ -16251,6 +16292,25 @@ "tslib": "^2.2.0" } }, + "@angular/cdk": { + "version": "12.2.8", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.8.tgz", + "integrity": "sha512-M0Y61o0yEVLMg+DSNsaDgiOifAV6OdumTgt2/kNoSuauPRWS0bkZJE58k3LR+cPi1Cho3UXELMKMOXZN9AhofA==", + "peer": true, + "requires": { + "parse5": "^5.0.0", + "tslib": "^2.2.0" + }, + "dependencies": { + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "optional": true, + "peer": true + } + } + }, "@angular/cli": { "version": "12.2.6", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-12.2.6.tgz", @@ -16386,6 +16446,14 @@ } } }, + "@angular/material": { + "version": "12.2.8", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-12.2.8.tgz", + "integrity": "sha512-wRTaTZIGC9+2e8aft44V9Qqwp3PsR9AG0FeJ0spl8mdOlYEqMMyoRXjvMiWIjo2ywxHLoQgLXXsWn3ip2xnnVg==", + "requires": { + "tslib": "^2.2.0" + } + }, "@angular/platform-browser": { "version": "12.2.6", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.6.tgz", diff --git a/Frontend/package.json b/Frontend/package.json index dfbcbba..319a687 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -1,41 +1,42 @@ { - "name": "com.p4ddy.rapla", - "version": "1.0.0", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build", - "watch": "ng build --watch --configuration development", - "test": "ng test" - }, - "private": true, - "dependencies": { - "@angular/animations": "~12.2.0", - "@angular/common": "~12.2.0", - "@angular/compiler": "~12.2.0", - "@angular/core": "~12.2.0", - "@angular/forms": "~12.2.0", - "@angular/platform-browser": "~12.2.0", - "@angular/platform-browser-dynamic": "~12.2.0", - "@angular/router": "~12.2.0", - "@ng-bootstrap/ng-bootstrap": "^10.0.0", - "bootstrap": "^5.1.1", - "rxjs": "~6.6.0", - "tslib": "^2.3.0", - "zone.js": "~0.11.4" - }, - "devDependencies": { - "@angular-devkit/build-angular": "~12.2.6", - "@angular/cli": "~12.2.6", - "@angular/compiler-cli": "~12.2.0", - "@types/jasmine": "~3.8.0", - "@types/node": "^12.20.27", - "jasmine-core": "~3.8.0", - "karma": "~6.3.0", - "karma-chrome-launcher": "~3.1.0", - "karma-coverage": "~2.0.3", - "karma-jasmine": "~4.0.0", - "karma-jasmine-html-reporter": "~1.7.0", - "typescript": "~4.3.5" - } + "name": "com.p4ddy.rapla", + "version": "1.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "private": true, + "dependencies": { + "@angular/animations": "~12.2.0", + "@angular/common": "~12.2.0", + "@angular/compiler": "~12.2.0", + "@angular/core": "~12.2.0", + "@angular/forms": "~12.2.0", + "@angular/material": "^12.2.8", + "@angular/platform-browser": "~12.2.0", + "@angular/platform-browser-dynamic": "~12.2.0", + "@angular/router": "~12.2.0", + "@ng-bootstrap/ng-bootstrap": "^10.0.0", + "bootstrap": "^5.1.1", + "rxjs": "~6.6.0", + "tslib": "^2.3.0", + "zone.js": "~0.11.4" + }, + "devDependencies": { + "@angular-devkit/build-angular": "~12.2.6", + "@angular/cli": "~12.2.6", + "@angular/compiler-cli": "~12.2.0", + "@types/jasmine": "~3.8.0", + "@types/node": "^12.20.27", + "jasmine-core": "~3.8.0", + "karma": "~6.3.0", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage": "~2.0.3", + "karma-jasmine": "~4.0.0", + "karma-jasmine-html-reporter": "~1.7.0", + "typescript": "~4.3.5" + } } diff --git a/Frontend/src/app/app.module.ts b/Frontend/src/app/app.module.ts index a0385c6..1a54f46 100644 --- a/Frontend/src/app/app.module.ts +++ b/Frontend/src/app/app.module.ts @@ -15,6 +15,8 @@ import {EventDetailComponent} from './components/event-detail/event-detail.compo import {HttpClientModule} from '@angular/common/http'; import {FormsModule} from '@angular/forms'; import {ApiService} from './services/api/api.service'; +import {MatDialogModule} from '@angular/material/dialog'; +import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; @NgModule({ declarations: [ diff --git a/Frontend/src/app/app.routing.ts b/Frontend/src/app/app.routing.ts index 98bbccf..59cbbdc 100644 --- a/Frontend/src/app/app.routing.ts +++ b/Frontend/src/app/app.routing.ts @@ -1,10 +1,12 @@ import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; +import { EventDetailComponent } from './components/event-detail/event-detail.component'; import {LandingpageComponent} from './pages/landingpage/landingpage.component'; import {PageNotFoundComponent} from './pages/page-not-found/page-not-found.component'; const routes: Routes = [ {path: '', component: LandingpageComponent, pathMatch: 'full'}, + {path: 'eventDetails/:id', component: EventDetailComponent}, {path: '**', component: PageNotFoundComponent} ]; diff --git a/Frontend/src/app/components/event-detail/event-detail.component.css b/Frontend/src/app/components/event-detail/event-detail.component.css index e69de29..19febf5 100644 --- a/Frontend/src/app/components/event-detail/event-detail.component.css +++ b/Frontend/src/app/components/event-detail/event-detail.component.css @@ -0,0 +1,41 @@ +.title { + text-align: center; + font-size: 2em; + font-weight: bold; +} + +footer { + text-align: center; +} + +.backBtn { + background-color: dimgrey; /* Green */ + border: none; + color: #E0E5E9; + padding: .15em .5em; + text-align: center; + display: inline-block; + border-radius: .5em; + margin: .5em; +} + +.info-table { + padding: .5em; +} + +.event-current-status, .event-changes { + display: flex; + justify-content: center; +} + +.changes-table th, .changes-table td { + padding-inline: .5em; +} + +.change-old-version { + color: red; +} + +.change-new-version { + color: green; +} diff --git a/Frontend/src/app/components/event-detail/event-detail.component.html b/Frontend/src/app/components/event-detail/event-detail.component.html index 6f418fb..915b223 100644 --- a/Frontend/src/app/components/event-detail/event-detail.component.html +++ b/Frontend/src/app/components/event-detail/event-detail.component.html @@ -1 +1,69 @@ -

Event details popover

+
+ +

Event: {{event.latest_event_summary}}

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Most recent details
Description: {{latestFullChange.new_description}}
Created: {{latestFullChange.new_created}}
Start: {{latestFullChange.new_start}}
End: {{latestFullChange.new_end}}
Last modified: {{latestFullChange.new_last_modified}}
Location: {{latestFullChange.new_location}}
Organizer: {{latestFullChange.new_organizer}}
Type: {{latestFullChange.new_categories}}
Is deleted: {{isDeleted}}
+

+
+ + + + + + + + + + + + + + + +
Changes
{{change.timestamp}}{{change.summary}}
{{change.oldVersion}}-->{{change.newVersion}}
+
+ + diff --git a/Frontend/src/app/components/event-detail/event-detail.component.ts b/Frontend/src/app/components/event-detail/event-detail.component.ts index b1722be..9e4bb3e 100644 --- a/Frontend/src/app/components/event-detail/event-detail.component.ts +++ b/Frontend/src/app/components/event-detail/event-detail.component.ts @@ -1,4 +1,8 @@ import {Component, OnInit} from '@angular/core'; +import {ActivatedRoute} from '@angular/router'; +import {Event} from '../../models/event'; +import {ApiService} from '../../services/api/api.service'; +import {Change} from '../../models/change'; @Component({ selector: 'app-event-detail', @@ -6,11 +10,124 @@ import {Component, OnInit} from '@angular/core'; styleUrls: ['./event-detail.component.css'] }) export class EventDetailComponent implements OnInit { + id: string = ''; + event: Event = {} as Event; + latestFullChange: Change = {} as Change; + isDeleted: boolean = false; + changeDetails: ChangeDetail[] = []; - constructor() { + constructor( + private route: ActivatedRoute, + private apiService: ApiService + ) { } ngOnInit(): void { + this.id = this.route.snapshot.paramMap.get('id') as string; + console.log(this.id); + + this.apiService.getEventById(this.id).subscribe(event => { + this.event = event; + + // Set latest full change + this.latestFullChange = this.event.changes[this.event.changes.length - 1]; + if (this.latestFullChange.is_deleted) { + this.isDeleted = true; + this.latestFullChange = this.event.changes[this.event.changes.length - 2]; + } + + this.generateChangeDetails(); + }); } + generateChangeDetails() { + let changes = this.event.changes; + + if (changes.length < 1) { + return; + } + + let firstChange = changes.shift(); + this.changeDetails.push({ + timestamp: firstChange!.change_timestamp, + summary: 'Created', + hasDetails: false + }); + + let lastChange = firstChange!; + + for (let change of changes) { + let cTs = change.change_timestamp; + let cSummary = ''; + let cOld = ''; + let cNew = ''; + let cDetails = false; + let appendChange = true; + + if (change.is_deleted) { + cSummary = 'Deleted'; + } else if (change.new_summary !== lastChange.new_summary) { + cSummary = 'Name changed'; + cOld = lastChange.new_summary; + cNew = change.new_summary; + cDetails = true; + } else if (change.new_description !== lastChange.new_description) { + cSummary = 'Description changed'; + cOld = lastChange.new_description; + cNew = change.new_description; + cDetails = true; + } else if (change.new_start !== lastChange.new_start) { + cSummary = 'Start changed'; + cOld = lastChange.new_start.toString(); + cNew = change.new_start.toString(); + cDetails = true; + } else if (change.new_end !== lastChange.new_end) { + cSummary = 'End changed'; + cOld = lastChange.new_end.toString(); + cNew = change.new_end.toString(); + cDetails = true; + } else if (change.new_location !== lastChange.new_location) { + cSummary = 'Location changed'; + cOld = lastChange.new_location; + cNew = change.new_location; + cDetails = true; + } else if (change.new_organizer !== lastChange.new_organizer) { + cSummary = 'Organizer changed'; + cOld = lastChange.new_organizer; + cNew = change.new_organizer; + cDetails = true; + } else if (change.new_categories !== lastChange.new_categories) { + cSummary = 'Type changed'; + cOld = lastChange.new_categories; + cNew = change.new_categories; + cDetails = true; + } else { + appendChange = false; + } + + if(appendChange) { + this.changeDetails.push({ + timestamp: cTs, + summary: cSummary, + oldVersion: cOld, + newVersion: cNew, + hasDetails: cDetails + }); + } + + lastChange = change; + } + } + + closeTab(): void { + window.close(); + } +} + +export interface ChangeDetail { + timestamp: Date; + summary: string; + hasDetails: boolean; + oldVersion?: string; + newVersion?: string; } diff --git a/Frontend/src/app/components/event/event.component.css b/Frontend/src/app/components/event/event.component.css index dbbd498..b7e3b7c 100644 --- a/Frontend/src/app/components/event/event.component.css +++ b/Frontend/src/app/components/event/event.component.css @@ -9,7 +9,7 @@ } .event-card-exam { - background-color: orangered; + background-color: darkred; } .event-card-blocker { diff --git a/Frontend/src/app/components/event/event.component.html b/Frontend/src/app/components/event/event.component.html index 6b7743a..9920dda 100644 --- a/Frontend/src/app/components/event/event.component.html +++ b/Frontend/src/app/components/event/event.component.html @@ -1,4 +1,4 @@ -
+
Titel: {{event.latest_event_summary}} Start: {{event.latest_start_date}} Gelöscht: {{event.changes[event.changes.length-1].is_deleted}} diff --git a/Frontend/src/app/components/event/event.component.ts b/Frontend/src/app/components/event/event.component.ts index 2fc8df9..9764b28 100644 --- a/Frontend/src/app/components/event/event.component.ts +++ b/Frontend/src/app/components/event/event.component.ts @@ -1,5 +1,7 @@ import {Component, Input, OnInit} from '@angular/core'; import {Event} from '../../models/event'; +import {Change} from '../../models/change'; +import {Router} from '@angular/router'; @Component({ selector: 'app-event', @@ -10,16 +12,20 @@ export class EventComponent implements OnInit { @Input() event: Event = {} as Event; eventTypeClass: string = ''; - constructor() { + latestFullChange: Change = {} as Change; + + constructor( + private router: Router + ) { } ngOnInit(): void { - let latestFullChange = this.event.changes[this.event.changes.length - 1]; - if(latestFullChange.is_deleted) { - latestFullChange = this.event.changes[this.event.changes.length - 2]; + this.latestFullChange = this.event.changes[this.event.changes.length - 1]; + if (this.latestFullChange.is_deleted) { + this.latestFullChange = this.event.changes[this.event.changes.length - 2]; } - switch(latestFullChange.new_categories) { + switch (this.latestFullChange.new_categories) { case 'Prüfung': this.eventTypeClass = 'event-card-exam'; break; @@ -34,4 +40,7 @@ export class EventComponent implements OnInit { } } + openEventDetails() { + this.router.navigate([]).then(result => { window.open('/eventDetails/'+this.event.event_uid, '_blank'); }); + } } diff --git a/Frontend/src/app/services/api/api.service.ts b/Frontend/src/app/services/api/api.service.ts index 4453b3c..e950469 100644 --- a/Frontend/src/app/services/api/api.service.ts +++ b/Frontend/src/app/services/api/api.service.ts @@ -33,4 +33,13 @@ export class ApiService { } return new Observable(); } + + getEventById(id: string): Observable { + try { + return this.http.get((this.apiUrl + '/' + id)); + } catch (exception) { + console.log('Error fetching events from API'); + } + return new Observable(); + } }