Reworked frontend to be CRUD compliant and added tests
This commit is contained in:
@@ -2,6 +2,7 @@ plugins {
|
|||||||
id 'org.springframework.boot' version '2.7.3'
|
id 'org.springframework.boot' version '2.7.3'
|
||||||
id 'io.spring.dependency-management' version '1.0.13.RELEASE'
|
id 'io.spring.dependency-management' version '1.0.13.RELEASE'
|
||||||
id 'java'
|
id 'java'
|
||||||
|
id 'org.asciidoctor.jvm.convert' version '3.3.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'dev.cato447'
|
group = 'dev.cato447'
|
||||||
@@ -28,6 +29,14 @@ dependencies {
|
|||||||
runtimeOnly 'org.postgresql:postgresql'
|
runtimeOnly 'org.postgresql:postgresql'
|
||||||
annotationProcessor 'org.projectlombok:lombok'
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
testImplementation group: 'org.springframework.restdocs', name: 'spring-restdocs-mockmvc', version: '2.0.6.RELEASE'
|
||||||
|
}
|
||||||
|
|
||||||
|
asciidoctor {
|
||||||
|
|
||||||
|
sourceDir('build/generated-snippets')
|
||||||
|
outputDir('build/docs')
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('test') {
|
tasks.named('test') {
|
||||||
|
|||||||
@@ -1,13 +1,61 @@
|
|||||||
package dev.cato447.semesteressen;
|
package dev.cato447.semesteressen;
|
||||||
|
|
||||||
|
import dev.cato447.semesteressen.domain.Event;
|
||||||
|
import dev.cato447.semesteressen.repository.EventRepository;
|
||||||
|
import dev.cato447.semesteressen.service.EventService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.restdocs.RestDocumentationContextProvider;
|
||||||
|
import org.springframework.restdocs.RestDocumentationExtension;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||||
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*;
|
||||||
|
import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
|
||||||
|
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
@ExtendWith({RestDocumentationExtension.class, SpringExtension.class})
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
class SemesterEssenApplicationTests {
|
class SemesterEssenApplicationTests {
|
||||||
|
|
||||||
|
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private EventRepository eventRepository;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp(WebApplicationContext webApplicationContext,
|
||||||
|
RestDocumentationContextProvider restDocumentation) {
|
||||||
|
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
|
||||||
|
.apply(documentationConfiguration(restDocumentation))
|
||||||
|
.alwaysDo(document("{method-name}",preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint())))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void contextLoads() {
|
public void eventExample() throws Exception {
|
||||||
|
|
||||||
|
List<Event> eventList = new LinkedList<>();
|
||||||
|
|
||||||
|
eventList.add(new Event("Erstiessen #1", false, LocalDateTime.parse("2022-11-12T19:00:00"), 25));
|
||||||
|
|
||||||
|
when(eventRepository.findAll()).thenReturn(eventList);
|
||||||
|
|
||||||
|
this.mockMvc.perform(get("/api/events")).andExpect(status().isOk())
|
||||||
|
.andDo(document("event"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
"src/assets"
|
"src/assets"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
|
"./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
|
||||||
"src/styles.css"
|
"src/styles.css"
|
||||||
],
|
],
|
||||||
"scripts": []
|
"scripts": []
|
||||||
@@ -90,6 +91,7 @@
|
|||||||
"src/assets"
|
"src/assets"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
|
"./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
|
||||||
"src/styles.css"
|
"src/styles.css"
|
||||||
],
|
],
|
||||||
"scripts": []
|
"scripts": []
|
||||||
|
|||||||
66
frontend/package-lock.json
generated
66
frontend/package-lock.json
generated
@@ -9,10 +9,12 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^14.2.0",
|
"@angular/animations": "^14.2.0",
|
||||||
|
"@angular/cdk": "^14.2.2",
|
||||||
"@angular/common": "^14.2.0",
|
"@angular/common": "^14.2.0",
|
||||||
"@angular/compiler": "^14.2.0",
|
"@angular/compiler": "^14.2.0",
|
||||||
"@angular/core": "^14.2.0",
|
"@angular/core": "^14.2.0",
|
||||||
"@angular/forms": "^14.2.0",
|
"@angular/forms": "^14.2.0",
|
||||||
|
"@angular/material": "^14.2.2",
|
||||||
"@angular/platform-browser": "^14.2.0",
|
"@angular/platform-browser": "^14.2.0",
|
||||||
"@angular/platform-browser-dynamic": "^14.2.0",
|
"@angular/platform-browser-dynamic": "^14.2.0",
|
||||||
"@angular/router": "^14.2.0",
|
"@angular/router": "^14.2.0",
|
||||||
@@ -343,6 +345,28 @@
|
|||||||
"@angular/core": "14.2.3"
|
"@angular/core": "14.2.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@angular/cdk": {
|
||||||
|
"version": "14.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-14.2.2.tgz",
|
||||||
|
"integrity": "sha512-PXEnhX+QDOsmHVVnqTuoGaK7Wn9hFd5kWAmHTTU7lZr3XVu/AtDcEU+LB19wOFU0fY+kSYHMgN+BYo1TiR8vbw==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"parse5": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/common": "^14.0.0 || ^15.0.0",
|
||||||
|
"@angular/core": "^14.0.0 || ^15.0.0",
|
||||||
|
"rxjs": "^6.5.3 || ^7.4.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
|
||||||
|
},
|
||||||
"node_modules/@angular/cli": {
|
"node_modules/@angular/cli": {
|
||||||
"version": "14.2.3",
|
"version": "14.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-14.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-14.2.3.tgz",
|
||||||
@@ -475,6 +499,23 @@
|
|||||||
"rxjs": "^6.5.3 || ^7.4.0"
|
"rxjs": "^6.5.3 || ^7.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@angular/material": {
|
||||||
|
"version": "14.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@angular/material/-/material-14.2.2.tgz",
|
||||||
|
"integrity": "sha512-jVCaESSTTkLjRvMzSQj294s0Lz1YMVFkl0svrMtWgkUMXHEfx2Vjw6FXdrVrBXlxEIrpfhkTEXVN2DC1kkAkQw==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/animations": "^14.0.0 || ^15.0.0",
|
||||||
|
"@angular/cdk": "14.2.2",
|
||||||
|
"@angular/common": "^14.0.0 || ^15.0.0",
|
||||||
|
"@angular/core": "^14.0.0 || ^15.0.0",
|
||||||
|
"@angular/forms": "^14.0.0 || ^15.0.0",
|
||||||
|
"@angular/platform-browser": "^14.0.0 || ^15.0.0",
|
||||||
|
"rxjs": "^6.5.3 || ^7.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@angular/platform-browser": {
|
"node_modules/@angular/platform-browser": {
|
||||||
"version": "14.2.3",
|
"version": "14.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.2.3.tgz",
|
||||||
@@ -12101,6 +12142,23 @@
|
|||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@angular/cdk": {
|
||||||
|
"version": "14.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-14.2.2.tgz",
|
||||||
|
"integrity": "sha512-PXEnhX+QDOsmHVVnqTuoGaK7Wn9hFd5kWAmHTTU7lZr3XVu/AtDcEU+LB19wOFU0fY+kSYHMgN+BYo1TiR8vbw==",
|
||||||
|
"requires": {
|
||||||
|
"parse5": "^5.0.0",
|
||||||
|
"tslib": "^2.3.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@angular/cli": {
|
"@angular/cli": {
|
||||||
"version": "14.2.3",
|
"version": "14.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-14.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-14.2.3.tgz",
|
||||||
@@ -12179,6 +12237,14 @@
|
|||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@angular/material": {
|
||||||
|
"version": "14.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@angular/material/-/material-14.2.2.tgz",
|
||||||
|
"integrity": "sha512-jVCaESSTTkLjRvMzSQj294s0Lz1YMVFkl0svrMtWgkUMXHEfx2Vjw6FXdrVrBXlxEIrpfhkTEXVN2DC1kkAkQw==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@angular/platform-browser": {
|
"@angular/platform-browser": {
|
||||||
"version": "14.2.3",
|
"version": "14.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.2.3.tgz",
|
||||||
|
|||||||
@@ -11,10 +11,12 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^14.2.0",
|
"@angular/animations": "^14.2.0",
|
||||||
|
"@angular/cdk": "^14.2.2",
|
||||||
"@angular/common": "^14.2.0",
|
"@angular/common": "^14.2.0",
|
||||||
"@angular/compiler": "^14.2.0",
|
"@angular/compiler": "^14.2.0",
|
||||||
"@angular/core": "^14.2.0",
|
"@angular/core": "^14.2.0",
|
||||||
"@angular/forms": "^14.2.0",
|
"@angular/forms": "^14.2.0",
|
||||||
|
"@angular/material": "^14.2.2",
|
||||||
"@angular/platform-browser": "^14.2.0",
|
"@angular/platform-browser": "^14.2.0",
|
||||||
"@angular/platform-browser-dynamic": "^14.2.0",
|
"@angular/platform-browser-dynamic": "^14.2.0",
|
||||||
"@angular/router": "^14.2.0",
|
"@angular/router": "^14.2.0",
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
import { EventListComponent } from './event-list/event-list.component';
|
import { EventListComponent } from './views/event-list/event-list.component';
|
||||||
import { EventFormComponent } from './event-form/event-form.component';
|
import { EventFormComponent } from './views/event-create/event-create.component';
|
||||||
|
import { EventUpdateComponent } from './views/event-update/event-update.component';
|
||||||
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: "events", component: EventListComponent},
|
{path: '', pathMatch: 'full', redirectTo: 'event-list'},
|
||||||
{path: "create", component: EventFormComponent}
|
{path: "create-event", component: EventFormComponent},
|
||||||
|
{path: "event-list", component: EventListComponent},
|
||||||
|
{path: "event-edit/:id", component: EventUpdateComponent},
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
<h2 class="card-title text-center text-white py-3">{{ title }}</h2>
|
<h2 class="card-title text-center text-white py-3">{{ title }}</h2>
|
||||||
<ul class="text-center list-inline py-3">
|
<ul class="text-center list-inline py-3">
|
||||||
<li class="list-inline-item">
|
<li class="list-inline-item">
|
||||||
<a routerLink="/events" class="btn btn-info">Show Events</a>
|
<a routerLink="/event-list" class="btn btn-info">Show Events</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="list-inline-item">
|
<li class="list-inline-item">
|
||||||
<a routerLink="/create" class="btn btn-info">Create Events</a>
|
<a routerLink="/create-event" class="btn btn-info">Create Events</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,24 +1,31 @@
|
|||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
import { EventListComponent } from './event-list/event-list.component';
|
import { EventListComponent } from './views/event-list/event-list.component';
|
||||||
import { EventFormComponent } from './event-form/event-form.component';
|
import { EventFormComponent } from './views/event-create/event-create.component';
|
||||||
import { EventService } from './service/event-service.service';
|
import { EventService } from './shared/service/event-service.service';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { EventUpdateComponent } from './views/event-update/event-update.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
EventListComponent,
|
EventListComponent,
|
||||||
EventFormComponent
|
EventFormComponent,
|
||||||
|
EventUpdateComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
FormsModule
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
BrowserAnimationsModule
|
||||||
],
|
],
|
||||||
providers: [EventService],
|
providers: [EventService],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
<div class="card my-5">
|
|
||||||
<div class="card-body">
|
|
||||||
<form (ngSubmit)="onSubmit()" #eventForm="ngForm">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="name">Name</label>
|
|
||||||
<input type="text" [(ngModel)]="event.name"
|
|
||||||
class="form-control"
|
|
||||||
id="name"
|
|
||||||
name="name"
|
|
||||||
placeholder="Enter event name"
|
|
||||||
required #name="ngModel">
|
|
||||||
</div>
|
|
||||||
<div [hidden]="!name.pristine" class="alert alert-danger">Event name is required</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="published">Is published</label>
|
|
||||||
<input type="checkbox" [(ngModel)]="event.published"
|
|
||||||
class="form-control"
|
|
||||||
id="published"
|
|
||||||
name="published"
|
|
||||||
#published="ngModel">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="dateTime">DateTime</label>
|
|
||||||
<input type="text" [(ngModel)]="event.dateTime"
|
|
||||||
class="form-control"
|
|
||||||
id="dateTime"
|
|
||||||
name="dateTime"
|
|
||||||
placeholder="yyyy-MM-ddTHH:MM:SS"
|
|
||||||
required #dateTime="ngModel">
|
|
||||||
</div>
|
|
||||||
<div [hidden]="!dateTime.pristine" class="alert alert-danger">DateTime is requiered</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="maxParticipants">Maximum Participants</label>
|
|
||||||
<input type="number" [(ngModel)]="event.maxParticipants"
|
|
||||||
class="form-control"
|
|
||||||
id="maxParticipants"
|
|
||||||
name="maxParticipants"
|
|
||||||
placeholder=10
|
|
||||||
required #maxParticipants="ngModel">
|
|
||||||
</div>
|
|
||||||
<div [hidden]="!maxParticipants.pristine" class="alert alert-danger">Max Participants is requiered</div>
|
|
||||||
<button type="submit" [disabled]="!eventForm.form.valid"
|
|
||||||
class="btn btn-info">Create new event</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
import { EventService } from '../service/event-service.service';
|
|
||||||
import { Event } from '../module/event';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-event-form',
|
|
||||||
templateUrl: './event-form.component.html',
|
|
||||||
styleUrls: ['./event-form.component.css']
|
|
||||||
})
|
|
||||||
export class EventFormComponent {
|
|
||||||
|
|
||||||
event: Event;
|
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private router: Router, private eventService: EventService) {
|
|
||||||
this.event = new Event();
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubmit() {
|
|
||||||
this.eventService.save(this.event).subscribe(result => this.gotoUserList());
|
|
||||||
}
|
|
||||||
|
|
||||||
gotoUserList() {
|
|
||||||
this.router.navigate(['/events'])
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<div class="card my-5">
|
|
||||||
<div class="card-body">
|
|
||||||
<table class="table table-bordered table-striped">
|
|
||||||
<thead class="thead-dark">
|
|
||||||
<tr>
|
|
||||||
<th scope="col">#</th>
|
|
||||||
<th scope="col">Name</th>
|
|
||||||
<th scope="col">Published</th>
|
|
||||||
<th scope="col">Datetime</th>
|
|
||||||
<th scope="col">MaxParticipants</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr *ngFor="let event of events">
|
|
||||||
<td>{{ event.id }}</td>
|
|
||||||
<td>{{ event.name }}</td>
|
|
||||||
<td>{{ event.published }}</td>
|
|
||||||
<td>{{ event.dateTime}}</td>
|
|
||||||
<td>{{ event.maxParticipants }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { Event } from '../module/event';
|
|
||||||
import { EventService } from '../service/event-service.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-event-list',
|
|
||||||
templateUrl: './event-list.component.html',
|
|
||||||
styleUrls: ['./event-list.component.css']
|
|
||||||
})
|
|
||||||
export class EventListComponent implements OnInit {
|
|
||||||
|
|
||||||
events: Event[];
|
|
||||||
|
|
||||||
constructor(private eventService: EventService) {
|
|
||||||
this.events = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.eventService.findAll().subscribe(data => {
|
|
||||||
this.events = data;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { Event } from './event';
|
|
||||||
|
|
||||||
describe('Event', () => {
|
|
||||||
it('should create an instance', () => {
|
|
||||||
expect(new Event()).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export class Event {
|
|
||||||
id!: string;
|
|
||||||
name!: string;
|
|
||||||
published!: boolean;
|
|
||||||
dateTime!: string;
|
|
||||||
maxParticipants!: number;
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
|
||||||
import { Event } from '../module/event';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class EventService {
|
|
||||||
|
|
||||||
private eventUrl: string;
|
|
||||||
|
|
||||||
constructor(private http: HttpClient) {
|
|
||||||
this.eventUrl = "http://localhost:8080/api/events"
|
|
||||||
}
|
|
||||||
|
|
||||||
public findAll(): Observable<Event[]> {
|
|
||||||
return this.http.get<Event[]>(this.eventUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
public save(event: Event) {
|
|
||||||
return this.http.post<Event>(this.eventUrl, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7
frontend/src/app/shared/models/event.ts
Normal file
7
frontend/src/app/shared/models/event.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export interface Event {
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
published: boolean,
|
||||||
|
dateTime: string,
|
||||||
|
maxParticipants: number
|
||||||
|
}
|
||||||
57
frontend/src/app/shared/service/event-service.service.ts
Normal file
57
frontend/src/app/shared/service/event-service.service.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
|
import { Event } from '../models/event';
|
||||||
|
import { Observable, throwError } from 'rxjs';
|
||||||
|
import { retry, catchError } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class EventService {
|
||||||
|
|
||||||
|
apiUrl = "http://localhost:8080/api";
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
httpOptions = {
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
getEvents(): Observable<Event[]> {
|
||||||
|
return this.http.get<Event[]>(this.apiUrl + '/events').pipe(retry(1), catchError(this.handleError));
|
||||||
|
}
|
||||||
|
|
||||||
|
getEvent(id: any): Observable<Event> {
|
||||||
|
return this.http.get<Event>(this.apiUrl + '/events/' + id).pipe(retry(1), catchError(this.handleError));
|
||||||
|
}
|
||||||
|
|
||||||
|
createEvent(event: any): Observable<Event> {
|
||||||
|
return this.http.post<Event>(this.apiUrl + '/events', JSON.stringify(event), this.httpOptions).pipe(retry(1), catchError(this.handleError));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateEvent(id: any, event: any): Observable<Event> {
|
||||||
|
return this.http.put<Event>(this.apiUrl + '/events/' + id, JSON.stringify(event), this.httpOptions).pipe(retry(1), catchError(this.handleError));
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteEvent(id: any): Observable<Event> {
|
||||||
|
return this.http.delete<Event>(this.apiUrl + '/events/' + id, this.httpOptions).pipe(retry(1), catchError(this.handleError));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
handleError(error: any) {
|
||||||
|
let errorMessage = '';
|
||||||
|
if (error.error instanceof ErrorEvent) {
|
||||||
|
// Get client-side error
|
||||||
|
errorMessage = error.error.message;
|
||||||
|
} else {
|
||||||
|
// Get server-side error
|
||||||
|
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
|
||||||
|
}
|
||||||
|
window.alert(errorMessage);
|
||||||
|
return throwError(() => {
|
||||||
|
return errorMessage;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<div class="container custom-container">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h3 class="mb-3 text-center">Create Event</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" [(ngModel)]="eventDetails.name" class="form-control" placeholder="Name">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="checkbox" [(ngModel)]="eventDetails.published" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" [(ngModel)]="eventDetails.dateTime" class="form-control" placeholder="yyyy-MM-ddThh:mm:ss">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="number" [(ngModel)]="eventDetails.maxParticipants" class="form-control" placeholder=0>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<button class="btn btn-success btn-lg btn-block" (click)="addEvent(eventDetails)">Create Event</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { EventFormComponent } from './event-form.component';
|
import { EventFormComponent } from './event-create.component';
|
||||||
|
|
||||||
describe('EventFormComponent', () => {
|
describe('EventFormComponent', () => {
|
||||||
let component: EventFormComponent;
|
let component: EventFormComponent;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { EventService } from '../../shared/service/event-service.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-event-create',
|
||||||
|
templateUrl: './event-create.component.html',
|
||||||
|
styleUrls: ['./event-create.component.css']
|
||||||
|
})
|
||||||
|
export class EventFormComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() eventDetails = {name: '', published: false, dateTime: '', maxParticipants: 0};
|
||||||
|
|
||||||
|
constructor(private route: ActivatedRoute, private router: Router, private eventService: EventService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {}
|
||||||
|
|
||||||
|
addEvent(dataEvent: any){
|
||||||
|
this.eventService.createEvent(this.eventDetails).subscribe((data: {}) => {this.router.navigate(['/event-list'])});
|
||||||
|
}
|
||||||
|
}
|
||||||
43
frontend/src/app/views/event-list/event-list.component.html
Normal file
43
frontend/src/app/views/event-list/event-list.component.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<div class="container custom-container-2">
|
||||||
|
<!-- Show it when there is no employee -->
|
||||||
|
<div class="no-data text-center" *ngIf="Event.length == 0">
|
||||||
|
<p>There are no events yet!</p>
|
||||||
|
<button class="btn btn-outline-primary" routerLink="/create">
|
||||||
|
Add Event
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- Events list table, it hides when there is no Events -->
|
||||||
|
<div *ngIf="Event.length !== 0">
|
||||||
|
<h3 class="mb-3 text-center">Event List</h3>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Event Id</th>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col">Published</th>
|
||||||
|
<th scope="col">Datetime</th>
|
||||||
|
<th scope="col">MaxParticipants</th>
|
||||||
|
<th scope="col">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let event of Event">
|
||||||
|
<td>{{ event.id }}</td>
|
||||||
|
<td>{{ event.name }}</td>
|
||||||
|
<td>{{ event.published }}</td>
|
||||||
|
<td>{{ event.dateTime }}</td>
|
||||||
|
<td>{{ event.maxParticipants }}</td>
|
||||||
|
<td>
|
||||||
|
<span class="edit" routerLink="/event-edit/{{ event.id }}"
|
||||||
|
>Edit</span>
|
||||||
|
<span class="delete" (click)="deleteEvent(event.id)"
|
||||||
|
>Delete</span
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
34
frontend/src/app/views/event-list/event-list.component.ts
Normal file
34
frontend/src/app/views/event-list/event-list.component.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Event } from '../../shared/models/event';
|
||||||
|
import { EventService } from '../../shared/service/event-service.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-event-list',
|
||||||
|
templateUrl: './event-list.component.html',
|
||||||
|
styleUrls: ['./event-list.component.css'],
|
||||||
|
})
|
||||||
|
export class EventListComponent implements OnInit {
|
||||||
|
|
||||||
|
Event: any = [];
|
||||||
|
|
||||||
|
constructor(private eventService: EventService) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadEvents() {
|
||||||
|
return this.eventService.getEvents().subscribe((data: {}) => {
|
||||||
|
this.Event = data;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteEvent(id: any){
|
||||||
|
if(window.confirm('Are you sure, you want to delete?')) {
|
||||||
|
this.eventService.deleteEvent(id).subscribe((data) => {
|
||||||
|
this.loadEvents();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<div class="container custom-container">
|
||||||
|
<div class="col-md-12">
|
||||||
|
|
||||||
|
<h3 class="mb-3 text-center">Update Event</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" [(ngModel)]="eventData.name" class="form-control" placeholder="Name">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="checkbox" [(ngModel)]="eventData.published" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" [(ngModel)]="eventData.dateTime" class="form-control" placeholder="yyyy-MM-ddThh:mm:ss">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="number" [(ngModel)]="eventData.maxParticipants" class="form-control" placeholder=0>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<button class="btn btn-success btn-lg btn-block" (click)="updateEvent()">Update Event</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { EventUpdateComponent } from './event-update.component';
|
||||||
|
|
||||||
|
describe('EventUpdateComponent', () => {
|
||||||
|
let component: EventUpdateComponent;
|
||||||
|
let fixture: ComponentFixture<EventUpdateComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ EventUpdateComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(EventUpdateComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { EventService } from 'src/app/shared/service/event-service.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-event-update',
|
||||||
|
templateUrl: './event-update.component.html',
|
||||||
|
styleUrls: ['./event-update.component.css']
|
||||||
|
})
|
||||||
|
export class EventUpdateComponent implements OnInit {
|
||||||
|
|
||||||
|
id = this.actRoute.snapshot.params['id'];
|
||||||
|
eventData: any = {};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public eventService: EventService,
|
||||||
|
public actRoute: ActivatedRoute,
|
||||||
|
public router: Router,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.eventService.getEvent(this.id).subscribe((data: {}) => {
|
||||||
|
this.eventData = data;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updateEvent() {
|
||||||
|
if(window.confirm('Are you sure, you want to update?')) {
|
||||||
|
this.eventService.updateEvent(this.id, this.eventData).subscribe((data) => {
|
||||||
|
this.router.navigate(['/event-list'])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,8 +10,11 @@
|
|||||||
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
|
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
|
||||||
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
|
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
|
||||||
crossorigin="anonymous">
|
crossorigin="anonymous">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="mat-typography">
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1 +1,4 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
/* You can add global styles to this file, and also import other style files */
|
||||||
|
|
||||||
|
html, body { height: 100%; }
|
||||||
|
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"lib": ["es5", "es6", "dom", "dom.iterable"],
|
||||||
"outDir": "./out-tsc/app",
|
"outDir": "./out-tsc/app",
|
||||||
"types": []
|
"types": []
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user