Compare commits
7 Commits
8e1c071645
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cb3c12351 | |||
| 84aaa9ed99 | |||
| 7224a732e2 | |||
| d7c9fc9265 | |||
| 347875390f | |||
| 6b55c438f6 | |||
| 59682c3091 |
Generated
+68
@@ -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",
|
||||
|
||||
+40
-39
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,14 @@ import {PageNotFoundComponent} from './pages/page-not-found/page-not-found.compo
|
||||
import {WeekComponent} from './components/week/week.component';
|
||||
import {DayComponent} from './components/day/day.component';
|
||||
import {EventComponent} from './components/event/event.component';
|
||||
import {ChangeComponent} from './components/change/change.component';
|
||||
import {DatepickerComponent} from './components/datepicker/datepicker.component';
|
||||
import {EventDetailComponent} from './components/event-detail/event-detail.component';
|
||||
import {HttpClientModule} from '@angular/common/http';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {ApiService} from './services/api/api.service';
|
||||
import {MatTooltipModule} from '@angular/material/tooltip';
|
||||
import {UtilitiesService} from './services/utils/utilities.service';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -24,7 +26,6 @@ import {ApiService} from './services/api/api.service';
|
||||
WeekComponent,
|
||||
DayComponent,
|
||||
EventComponent,
|
||||
ChangeComponent,
|
||||
DatepickerComponent,
|
||||
EventDetailComponent
|
||||
],
|
||||
@@ -33,9 +34,11 @@ import {ApiService} from './services/api/api.service';
|
||||
AppRouting,
|
||||
HttpClientModule,
|
||||
NgbModule,
|
||||
FormsModule
|
||||
FormsModule,
|
||||
MatTooltipModule,
|
||||
BrowserAnimationsModule
|
||||
],
|
||||
providers: [ApiService],
|
||||
providers: [ApiService, UtilitiesService],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
|
||||
@@ -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}
|
||||
];
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<p>Change details</p>
|
||||
@@ -1,25 +0,0 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {ChangeComponent} from './change.component';
|
||||
|
||||
describe('ChangeComponent', () => {
|
||||
let component: ChangeComponent;
|
||||
let fixture: ComponentFixture<ChangeComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ChangeComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ChangeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-change',
|
||||
templateUrl: './change.component.html',
|
||||
styleUrls: ['./change.component.css']
|
||||
})
|
||||
export class ChangeComponent implements OnInit {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
}
|
||||
|
||||
.switchWeekButton {
|
||||
background-color: dimgrey; /* Green */
|
||||
background-color: dimgrey;
|
||||
border: none;
|
||||
color: #E0E5E9;
|
||||
padding: .15em .5em;
|
||||
@@ -13,6 +13,24 @@
|
||||
margin: .5em;
|
||||
}
|
||||
|
||||
.showDeletedBtn {
|
||||
border: none;
|
||||
color: #E0E5E9;
|
||||
padding: .15em .5em;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
border-radius: .5em;
|
||||
margin: .5em;
|
||||
}
|
||||
|
||||
.show {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.hide {
|
||||
background-color: darkred;
|
||||
}
|
||||
|
||||
.datePickerDropdown {
|
||||
background-color: dimgrey;
|
||||
border: none;
|
||||
|
||||
@@ -17,5 +17,6 @@
|
||||
</option>
|
||||
</select>
|
||||
<button (click)="switchToToday()" class="switchWeekButton">Today</button>
|
||||
<button (click)="switchShowDeletedEvents()" class="showDeletedBtn {{switchDeletedBtnClass}}">{{showingDeletedBtnText}}</button>
|
||||
<button (click)="switchToNextWeek()" class="switchWeekButton">Next week >></button>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,11 @@ export class DatepickerComponent implements OnInit {
|
||||
|
||||
itIsWednesday: boolean = false;
|
||||
|
||||
internalShowDeletedEvents: boolean = true;
|
||||
@Output() showDeletedEvents = new EventEmitter<boolean>();
|
||||
switchDeletedBtnClass: string = 'show';
|
||||
showingDeletedBtnText: string = 'Showing deleted events';
|
||||
|
||||
@Output() selectedMonday = new EventEmitter<string>();
|
||||
|
||||
constructor() {
|
||||
@@ -23,6 +28,7 @@ export class DatepickerComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.fillDatePickers();
|
||||
this.getDetailsFromLocalStorage();
|
||||
this.handleDateChange();
|
||||
}
|
||||
|
||||
@@ -123,6 +129,7 @@ export class DatepickerComponent implements OnInit {
|
||||
handleDateChange(): void {
|
||||
let mondayDate = this.findMondayInWeek(this.selectedYear, this.selectedMonth, this.selectedDay);
|
||||
this.selectedMonday.emit(mondayDate);
|
||||
this.saveDetailsToLocalStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,6 +170,9 @@ export class DatepickerComponent implements OnInit {
|
||||
return new Date(year, month, 0).getDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the currently selected date to the next week
|
||||
*/
|
||||
switchToNextWeek() {
|
||||
let currentDate = new Date(parseInt(this.selectedYear), parseInt(this.selectedMonth)-1, parseInt(this.selectedDay));
|
||||
let newDate = currentDate;
|
||||
@@ -173,6 +183,9 @@ export class DatepickerComponent implements OnInit {
|
||||
this.handleDateChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the currently selected date to the previous week
|
||||
*/
|
||||
switchToPreviousWeek() {
|
||||
let currentDate = new Date(parseInt(this.selectedYear), parseInt(this.selectedMonth)-1, parseInt(this.selectedDay));
|
||||
let newDate = currentDate;
|
||||
@@ -183,6 +196,9 @@ export class DatepickerComponent implements OnInit {
|
||||
this.handleDateChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the currently selected date to today
|
||||
*/
|
||||
switchToToday() {
|
||||
let currentDate = new Date();
|
||||
this.selectedYear = currentDate.getFullYear().toString();
|
||||
@@ -190,4 +206,61 @@ export class DatepickerComponent implements OnInit {
|
||||
this.selectedDay = currentDate.getDate().toString().padStart(2, '0');
|
||||
this.handleDateChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches whether to show deleted events in the overview
|
||||
*/
|
||||
switchShowDeletedEvents() {
|
||||
if(this.internalShowDeletedEvents) {
|
||||
this.internalShowDeletedEvents = false;
|
||||
this.showDeletedEvents.emit(false);
|
||||
this.switchDeletedBtnClass = 'hide';
|
||||
this.showingDeletedBtnText = 'Deleted events hidden';
|
||||
} else {
|
||||
this.internalShowDeletedEvents = true;
|
||||
this.showDeletedEvents.emit(true);
|
||||
this.switchDeletedBtnClass = 'show';
|
||||
this.showingDeletedBtnText = 'Showing deleted events';
|
||||
}
|
||||
|
||||
this.saveDetailsToLocalStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves preferences like the selected week to the local storage
|
||||
*/
|
||||
saveDetailsToLocalStorage() {
|
||||
localStorage.setItem('has_saved_details', 'true');
|
||||
localStorage.setItem('selected_year', this.selectedYear);
|
||||
localStorage.setItem('selected_month', this.selectedMonth);
|
||||
localStorage.setItem('selected_day', this.selectedDay);
|
||||
localStorage.setItem('show_deleted', this.internalShowDeletedEvents.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets saved preferences from the local storage if there are any
|
||||
*/
|
||||
getDetailsFromLocalStorage() {
|
||||
if((localStorage.getItem('has_saved_details') ?? 'false') === 'true') {
|
||||
this.selectedYear = localStorage.getItem('selected_year')!;
|
||||
this.selectedMonth = localStorage.getItem('selected_month')!;
|
||||
this.selectedDay = localStorage.getItem('selected_day')!;
|
||||
this.internalShowDeletedEvents = localStorage.getItem('show_deleted')! === 'true';
|
||||
this.showDeletedEvents.emit(this.internalShowDeletedEvents);
|
||||
this.checkShowDeletedEventsBtn();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the "show deleted events" button has the correct style
|
||||
*/
|
||||
checkShowDeletedEventsBtn() {
|
||||
if(this.internalShowDeletedEvents) {
|
||||
this.switchDeletedBtnClass = 'show';
|
||||
this.showingDeletedBtnText = 'Showing deleted events';
|
||||
} else {
|
||||
this.switchDeletedBtnClass = 'hide';
|
||||
this.showingDeletedBtnText = 'Deleted events hidden';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<h1>{{weekDayName}}</h1>
|
||||
<div *ngFor="let event of events">
|
||||
<app-event [event]="event"></app-event>
|
||||
<app-event [event]="event" *ngIf="!(!showDeletedEvents && event.changes[event.changes.length-1].is_deleted)"></app-event>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {Event} from '../../models/event';
|
||||
export class DayComponent implements OnInit {
|
||||
@Input() events: Event[] = [];
|
||||
@Input() dayOfWeek: number = 0;
|
||||
@Input() showDeletedEvents: boolean = true;
|
||||
|
||||
weekDayName: string = '';
|
||||
|
||||
@@ -17,6 +18,9 @@ export class DayComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.weekDayName = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'][this.dayOfWeek];
|
||||
this.events = this.events.sort((a, b) => {
|
||||
return new Date(a.latest_start_date.toString()).getTime() - new Date(b.latest_start_date.toString()).getTime()
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1 +1,70 @@
|
||||
<p>Event details popover</p>
|
||||
<header>
|
||||
<button (click)="closeTab()" class="backBtn"><< Back</button>
|
||||
<p class="title">Event: {{event.latest_event_summary}}</p>
|
||||
</header>
|
||||
<div class="event-current-status">
|
||||
<table class="info-table">
|
||||
<tr>
|
||||
<th>Most recent details</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Description:</td>
|
||||
<td> {{latestFullChange.new_description}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Created:</td>
|
||||
<td> {{getDateTime(latestFullChange.new_created.toString())}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Start:</td>
|
||||
<td> {{getDateTime(latestFullChange.new_start.toString())}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>End:</td>
|
||||
<td> {{getDateTime(latestFullChange.new_end.toString())}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last modified:</td>
|
||||
<td> {{getDateTime(latestFullChange.new_last_modified.toString())}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Location:</td>
|
||||
<td> {{latestFullChange.new_location}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Organizer:</td>
|
||||
<td> {{latestFullChange.new_organizer}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Type:</td>
|
||||
<td> {{latestFullChange.new_categories}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Is deleted:</td>
|
||||
<td> {{isDeleted}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><br>
|
||||
<div class="event-changes">
|
||||
<table class="changes-table">
|
||||
<tr>
|
||||
<th>Changes</th>
|
||||
</tr>
|
||||
<tbody *ngFor="let change of changeDetails">
|
||||
<tr>
|
||||
<td class="change-timestamp">{{getDateTime(change.timestamp.toString())}}</td>
|
||||
<td class="change-summary">{{change.summary}}</td>
|
||||
</tr>
|
||||
<tr *ngIf="change.hasDetails">
|
||||
<td></td>
|
||||
<td class="change-old-version">{{change.oldVersion}}</td>
|
||||
<td>--></td>
|
||||
<td class="change-new-version">{{change.newVersion}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<footer class="fixed-bottom start-50 translate-middle">
|
||||
Copyright © 2021 - Patrick Müller
|
||||
</footer>
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
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';
|
||||
import {UtilitiesService} from '../../services/utils/utilities.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-event-detail',
|
||||
@@ -6,11 +11,136 @@ 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,
|
||||
private utilities: UtilitiesService
|
||||
) {
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks which details have changed in the changes and formats them properly to be displayed in the component
|
||||
*/
|
||||
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;
|
||||
|
||||
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 {
|
||||
cSummary = 'Internal change';
|
||||
}
|
||||
|
||||
this.changeDetails.push({
|
||||
timestamp: cTs,
|
||||
summary: cSummary,
|
||||
oldVersion: cOld,
|
||||
newVersion: cNew,
|
||||
hasDetails: cDetails
|
||||
});
|
||||
|
||||
lastChange = change;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the current tab so the user gets back to the event overview
|
||||
*/
|
||||
closeTab(): void {
|
||||
window.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted date for the given date
|
||||
* @param date The date as a string to format
|
||||
*/
|
||||
getDateTime(date: string): string {
|
||||
return this.utilities.getDateTimeFromString(date);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ChangeDetail {
|
||||
timestamp: Date;
|
||||
summary: string;
|
||||
hasDetails: boolean;
|
||||
oldVersion?: string;
|
||||
newVersion?: string;
|
||||
}
|
||||
|
||||
@@ -4,14 +4,23 @@
|
||||
margin: .5em;
|
||||
}
|
||||
|
||||
.event-card:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.event-card-lecture {
|
||||
background-color: #0A62D0;
|
||||
}
|
||||
|
||||
.event-card-exam {
|
||||
background-color: orangered;
|
||||
background-color: darkred;
|
||||
}
|
||||
|
||||
.event-card-blocker {
|
||||
background-color: dimgrey;
|
||||
}
|
||||
|
||||
.click-hint {
|
||||
font-size: 80%;
|
||||
color: black;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<div class="event-card {{eventTypeClass}}">
|
||||
Titel: {{event.latest_event_summary}}
|
||||
Start: {{event.latest_start_date}}
|
||||
Gelöscht: {{event.changes[event.changes.length-1].is_deleted}}
|
||||
<div class="event-card {{eventTypeClass}}" (click)="openEventDetails()">
|
||||
{{event.latest_event_summary}}<br>
|
||||
Start: {{getTime()}}<br>
|
||||
Deleted: {{event.changes[event.changes.length-1].is_deleted}}<br>
|
||||
<div class="click-hint">Click for change details</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {Event} from '../../models/event';
|
||||
import {Change} from '../../models/change';
|
||||
import {Router} from '@angular/router';
|
||||
import {UtilitiesService} from '../../services/utils/utilities.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-event',
|
||||
@@ -10,16 +13,21 @@ export class EventComponent implements OnInit {
|
||||
@Input() event: Event = {} as Event;
|
||||
eventTypeClass: string = '';
|
||||
|
||||
constructor() {
|
||||
latestFullChange: Change = {} as Change;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private utilities: UtilitiesService
|
||||
) {
|
||||
}
|
||||
|
||||
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 +42,17 @@ export class EventComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new tab with the event details
|
||||
*/
|
||||
openEventDetails() {
|
||||
this.router.navigate([]).then(result => { window.open('/eventDetails/'+this.event.event_uid, '_blank'); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the starting time of the event
|
||||
*/
|
||||
getTime(): string {
|
||||
return this.utilities.getTimeFromString(this.latestFullChange.new_start.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div id="week" class="container-fluid">
|
||||
<div class="row">
|
||||
<app-day *ngFor="let day of eventsPerDay" [events]="day" [dayOfWeek]="eventsPerDay.indexOf(day)" class="col"></app-day>
|
||||
<app-day *ngFor="let day of eventsPerDay" [events]="day" [dayOfWeek]="eventsPerDay.indexOf(day)" [showDeletedEvents]="showDeletedEvents" class="col"></app-day>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {Event} from '../../models/event';
|
||||
})
|
||||
export class WeekComponent implements OnInit {
|
||||
@Input() events: Event[] = [];
|
||||
@Input() showDeletedEvents: boolean = true;
|
||||
eventsPerDay: Event[][] = [];
|
||||
|
||||
constructor() {
|
||||
@@ -21,6 +22,10 @@ export class WeekComponent implements OnInit {
|
||||
this.splitEventsIntoDays();
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the given events into their days so we have a list of events for every day of the week
|
||||
* to pass into the day components
|
||||
*/
|
||||
splitEventsIntoDays() {
|
||||
// Pre-fill list with 7 empty days
|
||||
this.eventsPerDay = [];
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import {Observable} from 'rxjs';
|
||||
|
||||
/**
|
||||
* Mock class for unit testing
|
||||
*/
|
||||
export abstract class AbstractMockObservableService {
|
||||
protected _observable: Observable<any> | undefined;
|
||||
protected _fakeContent: any;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<header>
|
||||
<p>TINF19B4 RaPla Changes</p>
|
||||
<app-datepicker (selectedMonday)="dateChangedFromPicker($event)"></app-datepicker>
|
||||
<app-datepicker (selectedMonday)="dateChangedFromPicker($event)" (showDeletedEvents)="showDeletedChangedFromPicker($event)"></app-datepicker>
|
||||
</header>
|
||||
<div id="content">
|
||||
<app-week [events]="events"></app-week>
|
||||
<app-week [events]="events" [showDeletedEvents]="showDeletedEvents"></app-week>
|
||||
</div>
|
||||
<footer class="fixed-bottom start-50 translate-middle">
|
||||
Copyright © 2021 - Patrick Müller
|
||||
|
||||
@@ -12,6 +12,7 @@ export class LandingpageComponent implements OnInit {
|
||||
eventCount: number = 0;
|
||||
|
||||
selectedDate: string = '';
|
||||
showDeletedEvents: boolean = true;
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService
|
||||
@@ -21,6 +22,9 @@ export class LandingpageComponent implements OnInit {
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the events for the currently selected date from the API
|
||||
*/
|
||||
getEvents() {
|
||||
this.apiService.getEvents(this.selectedDate).subscribe(events => {
|
||||
this.events = events;
|
||||
@@ -28,8 +32,22 @@ export class LandingpageComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the selected date is changed in the datepicker component.
|
||||
* Fetches the events for the new date
|
||||
* @param newDate The new date to get the events for
|
||||
*/
|
||||
dateChangedFromPicker(newDate: string) {
|
||||
this.selectedDate = newDate;
|
||||
this.getEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the state of "show deleted events" is changed in the datepicker component.
|
||||
* Passes the new value to the underlying components
|
||||
* @param newShowDeleted The new state of "show deleted events"
|
||||
*/
|
||||
showDeletedChangedFromPicker(newShowDeleted: boolean) {
|
||||
this.showDeletedEvents = newShowDeleted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,12 @@ export class ApiService {
|
||||
/____/
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets all events and their corresponding changes for the given week.
|
||||
* Best to supply only dates which are a monday, although other days are possible as well
|
||||
* @param week The week to fetch the events for
|
||||
* @return Observable<Event[]> A list of events
|
||||
*/
|
||||
getEvents(week: string): Observable<Event[]> {
|
||||
try {
|
||||
let params = new HttpParams();
|
||||
@@ -33,4 +39,18 @@ export class ApiService {
|
||||
}
|
||||
return new Observable<Event[]>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the event details for the specified event
|
||||
* @param id The UID of the event as saved in SQL
|
||||
* @return Observable<Event> The single event
|
||||
*/
|
||||
getEventById(id: string): Observable<Event> {
|
||||
try {
|
||||
return this.http.get<Event>((this.apiUrl + '/' + id));
|
||||
} catch (exception) {
|
||||
console.log('Error fetching events from API');
|
||||
}
|
||||
return new Observable<Event>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UtilitiesService } from './utilities.service';
|
||||
|
||||
describe('UtilitiesService', () => {
|
||||
let service: UtilitiesService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(UtilitiesService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UtilitiesService {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted time for the given date
|
||||
* @param date The date to format the time for
|
||||
* @return string the formatted time in the format HH:MM (24h format)
|
||||
*/
|
||||
getTimeFromDate(date: Date): string {
|
||||
return date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted time for the given date
|
||||
* @param dateString The date as a string to format the time for
|
||||
* @return string the formatted time in the format HH:MM (24h format)
|
||||
*/
|
||||
getTimeFromString(dateString: string): string {
|
||||
let date = new Date(dateString);
|
||||
return this.getTimeFromDate(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the given date
|
||||
* @param date The date to format
|
||||
* @return string the formatted date in the format DD.mm.YY, HH:MM (24h format)
|
||||
*/
|
||||
getDateTimeFromDate(date: Date): string {
|
||||
let year = date.getFullYear().toString();
|
||||
let month = date.getMonth().toString().padStart(2, '0');
|
||||
let day = date.getDate().toString().padStart(2, '0');
|
||||
let hour = date.getHours().toString().padStart(2, '0');
|
||||
let minute = date.getMinutes().toString().padStart(2, '0');
|
||||
|
||||
return day + '.' + month + '.' + year + ', ' + hour + ':' + minute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the given date
|
||||
* @param dateString The date as a string to format
|
||||
* @return string the formatted date in the format DD.mm.YY, HH:MM (24h format)
|
||||
*/
|
||||
getDateTimeFromString(dateString: string): string {
|
||||
let date = new Date(dateString);
|
||||
return this.getDateTimeFromDate(date);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user