diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 46c9583..3588363 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -11,11 +11,15 @@ import { UserDeleteComponent } from './user/user-delete/user-delete.component';
import { ShelterListComponent } from './shelter/shelter-list/shelter-list.component'
import {ShelterCreateComponent} from "./shelter/shelter-create/shelter-create.component";
import { CatListComponent } from './pet/cat/cat-list/cat-list.component';
-import { DogListComponent } from './pet/dog/dog-list/dog-list.component';
import { CatCreateComponent } from './pet/cat/cat-create/cat-create.component';
import { CatDetailComponent } from './pet/cat/cat-detail/cat-detail.component';
import { CatEditComponent } from './pet/cat/cat-edit/cat-edit.component';
import { CatDeleteComponent } from './pet/cat/cat-delete/cat-delete.component';
+import { DogListComponent } from './pet/dog/dog-list/dog-list.component';
+import { DogCreateComponent } from './pet/dog/dog-create/dog-create.component';
+import { DogDetailComponent } from './pet/dog/dog-detail/dog-detail.component';
+import { DogEditComponent } from './pet/dog/dog-edit/dog-edit.component';
+import { DogDeleteComponent } from './pet/dog/dog-delete/dog-delete.component';
const routes: Routes = [
{ path: 'users/create', component: UserRegisterComponent},
@@ -34,6 +38,10 @@ const routes: Routes = [
{ path: 'cats/:id/edit', component: CatEditComponent, canActivate: [LoggedInGuard]},
{ path: 'cats/:id/delete', component: CatDeleteComponent, canActivate: [LoggedInGuard]},
{ path: 'dogs', component: DogListComponent, canActivate: [LoggedInGuard]},
+ { path: 'dogs/create', component: DogCreateComponent, canActivate: [LoggedInGuard]},
+ { path: 'dogs/:id', component: DogDetailComponent, canActivate: [LoggedInGuard]},
+ { path: 'dogs/:id/edit', component: DogEditComponent, canActivate: [LoggedInGuard]},
+ { path: 'dogs/:id/delete', component: DogDeleteComponent, canActivate: [LoggedInGuard]},
];
@NgModule({
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 6a37913..f193570 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -29,7 +29,6 @@ import {ShelterListComponent} from "./shelter/shelter-list/shelter-list.componen
import {ShelterCreateComponent} from "./shelter/shelter-create/shelter-create.component";
import {ShelterDetailComponent} from "./shelter/shelter-detail/shelter-detail.component";
import { CatListComponent } from './pet/cat/cat-list/cat-list.component';
-import { DogListComponent } from './pet/dog/dog-list/dog-list.component';
import { CatService } from './pet/cat/cat.service';
import { DogService } from './pet/dog/dog.service';
import { CatCreateComponent } from './pet/cat/cat-create/cat-create.component';
@@ -37,6 +36,12 @@ import { CatDetailComponent } from './pet/cat/cat-detail/cat-detail.component';
import { CatEditComponent } from './pet/cat/cat-edit/cat-edit.component';
import { CatDeleteComponent } from './pet/cat/cat-delete/cat-delete.component';
import { CatSearchComponent } from './pet/cat/cat-search/cat-search.component';
+import { DogListComponent } from './pet/dog/dog-list/dog-list.component';
+import { DogCreateComponent } from './pet/dog/dog-create/dog-create.component';
+import { DogDetailComponent } from './pet/dog/dog-detail/dog-detail.component';
+import { DogDeleteComponent } from './pet/dog/dog-delete/dog-delete.component';
+import { DogEditComponent } from './pet/dog/dog-edit/dog-edit.component';
+import { DogSearchComponent } from './pet/dog/dog-search/dog-search.component';
@NgModule({
declarations: [
@@ -60,6 +65,11 @@ import { CatSearchComponent } from './pet/cat/cat-search/cat-search.component';
CatEditComponent,
CatSearchComponent,
DogListComponent,
+ DogCreateComponent,
+ DogDetailComponent,
+ DogDeleteComponent,
+ DogEditComponent,
+ DogSearchComponent,
],
imports: [
BrowserModule,
diff --git a/src/app/pet/dog/dog-create/dog-create.component.css b/src/app/pet/dog/dog-create/dog-create.component.css
new file mode 100644
index 0000000..72f16bc
--- /dev/null
+++ b/src/app/pet/dog/dog-create/dog-create.component.css
@@ -0,0 +1,4 @@
+.form-error {
+ color: red;
+ font-size: x-small;
+}
\ No newline at end of file
diff --git a/src/app/pet/dog/dog-create/dog-create.component.html b/src/app/pet/dog/dog-create/dog-create.component.html
new file mode 100644
index 0000000..1d290a6
--- /dev/null
+++ b/src/app/pet/dog/dog-create/dog-create.component.html
@@ -0,0 +1,198 @@
+
diff --git a/src/app/pet/dog/dog-create/dog-create.component.ts b/src/app/pet/dog/dog-create/dog-create.component.ts
new file mode 100644
index 0000000..a6fdbf0
--- /dev/null
+++ b/src/app/pet/dog/dog-create/dog-create.component.ts
@@ -0,0 +1,156 @@
+import {Component, OnInit} from '@angular/core';
+import {Router} from "@angular/router";
+import {DogService} from "../dog.service";
+import {Dog} from "../dog";
+import {AbstractControl, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators} from "@angular/forms";
+import {PagedResourceCollection} from "@lagoshny/ngx-hateoas-client";
+
+@Component({
+ selector: 'app-dog-create',
+ templateUrl: './dog-create.component.html',
+ styleUrls: ['./dog-create.component.css']
+})
+export class DogCreateComponent implements OnInit{
+
+ closeResult = '';
+ public isModalSaved: boolean = false;
+ public dogs: Dog[] = [];
+ public dog: Dog;
+ public dogForm: FormGroup;
+
+ constructor(
+ private router: Router,
+ private dogService: DogService,
+ ) {}
+
+ ngOnInit(): void {
+ this.dog = new Dog();
+ this.dogForm = new FormGroup({
+ chip: new FormControl(this.dog.chip, [
+ Validators.required,
+ ]),
+ name: new FormControl(this.dog.name, [
+ Validators.required,
+ ]),
+ dateOfBirth: new FormControl(this.dog.dateOfBirth, [
+ Validators.required,
+ this.dateValidator(),
+ ]),
+ colour: new FormControl(this.dog.colour, [
+ Validators.required,
+ this.colourValidator(),
+ ]),
+ size: new FormControl(this.dog.size, [
+ Validators.required,
+ this.numberValidator(),
+ ]),
+ sex: new FormControl(this.dog.sex, [
+ Validators.required,
+ this.sexValidator(),
+ ]),
+ race: new FormControl(this.dog.race, [
+ Validators.required,
+ ]),
+ dangerous: new FormControl(this.dog.dangerous, []),
+ barkingLevel: new FormControl(this.dog.barkingLevel, [
+ Validators.required,
+ ]),
+ });
+ this.loadDogList();
+ }
+
+ loadDogList() {
+ this.dogService
+ .getPage({
+ sort: { name: 'ASC' },
+ })
+ .subscribe((dogs: PagedResourceCollection) => {
+ this.dogs = dogs.resources.sort((a, b) =>
+ a.chip.localeCompare(b.chip)
+ );
+ });
+ }
+
+ dateValidator(): ValidatorFn {
+ return (control: AbstractControl): ValidationErrors | null => {
+ const dateValue = control.value;
+ const isValidDate = !isNaN(Date.parse(dateValue));
+ return isValidDate ? null : { invalidDate: { value: control.value } };
+ };
+ }
+
+ colourValidator(): ValidatorFn {
+ const hexColorRe: RegExp = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
+ return (control: AbstractControl): ValidationErrors | null => {
+ const isValidColour = hexColorRe.test(control.value);
+ return isValidColour ? null : { invalidColour: { value: control.value } };
+ };
+ }
+
+ numberValidator(): ValidatorFn {
+ return (control: AbstractControl): ValidationErrors | null => {
+ const numberValue = control.value;
+ const isValidNumber = !isNaN(numberValue) && numberValue > 0;
+ return isValidNumber ? null : { invalidNumber: { value: control.value } };
+ };
+ }
+
+ sexValidator(): ValidatorFn {
+ const validSexes = ['male', 'female'];
+ return (control: AbstractControl): ValidationErrors | null => {
+ const isValidSex = control.value !== null && control.value !== undefined && validSexes.includes(control.value.toLowerCase());
+ return isValidSex ? null : { invalidSex: { value: control.value } };
+ };
+ }
+
+ get chip() {
+ return this.dogForm.get('chip');
+ }
+
+ get name() {
+ return this.dogForm.get('name');
+ }
+
+ get dateOfBirth() {
+ return this.dogForm.get('dateOfBirth');
+ }
+
+ get adopted() {
+ return this.dogForm.get('adopted');
+ }
+
+ get colour() {
+ return this.dogForm.get('colour');
+ }
+
+ get size() {
+ return this.dogForm.get('size');
+ }
+
+ get sex() {
+ return this.dogForm.get('sex');
+ }
+
+ get race() {
+ return this.dogForm.get('race');
+ }
+
+ get dangerous() {
+ return this.dogForm.get('dangerous');
+ }
+
+ get barkingLevel() {
+ return this.dogForm.get('barkingLevel');
+ }
+
+ onSubmit(): void {
+ this.dog.adopted = false;
+ this.dogService
+ .createResource({ body: this.dog })
+ .subscribe((dog: Dog) => {
+ const uri = (dog as any).uri;
+ this.router.navigate([uri]).then();
+ });
+ }
+
+}
diff --git a/src/app/pet/dog/dog-delete/dog-delete.component.html b/src/app/pet/dog/dog-delete/dog-delete.component.html
new file mode 100644
index 0000000..ee8f0b6
--- /dev/null
+++ b/src/app/pet/dog/dog-delete/dog-delete.component.html
@@ -0,0 +1,16 @@
+
+
+
+
Are you sure you want to delete this record?
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/pet/dog/dog-delete/dog-delete.component.ts b/src/app/pet/dog/dog-delete/dog-delete.component.ts
new file mode 100644
index 0000000..2417326
--- /dev/null
+++ b/src/app/pet/dog/dog-delete/dog-delete.component.ts
@@ -0,0 +1,31 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+import { Dog } from '../dog';
+import { DogService } from '../dog.service';
+
+@Component({
+ selector: 'app-dog-delete',
+ templateUrl: './dog-delete.component.html'
+})
+export class DogDeleteComponent implements OnInit {
+ public dog: Dog = new Dog();
+ private id: string;
+
+ constructor(private route: ActivatedRoute,
+ private router: Router,
+ private dogService: DogService) {
+ }
+
+ ngOnInit(): void {
+ this.id = this.route.snapshot.paramMap.get('id');
+ this.dogService.getResource(this.id).subscribe(
+ dog => this.dog = dog);
+ }
+
+ delete(): void {
+ this.dogService.deleteResource(this.dog).subscribe(
+ () => {
+ this.router.navigate(['dogs']);
+ });
+ }
+}
diff --git a/src/app/pet/dog/dog-detail/dog-detail.component.html b/src/app/pet/dog/dog-detail/dog-detail.component.html
new file mode 100644
index 0000000..393202d
--- /dev/null
+++ b/src/app/pet/dog/dog-detail/dog-detail.component.html
@@ -0,0 +1,63 @@
+
+
+
{{dog.name}}
+
+
+
+
Date of birth
+
{{dog.dateOfBirth}}
+
+
+
+
Size
+
Small
+
Average
+
Large
+
Extra Large
+
+
+
+
+
Dangerous
+
+
+
+
+
Adopted
+
+
+
+
+
Barking level
+
No barking
+
Low barking
+
Average barking
+
Loud barking
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/pet/dog/dog-detail/dog-detail.component.ts b/src/app/pet/dog/dog-detail/dog-detail.component.ts
new file mode 100644
index 0000000..6e0bb88
--- /dev/null
+++ b/src/app/pet/dog/dog-detail/dog-detail.component.ts
@@ -0,0 +1,25 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { DogService } from '../dog.service';
+import { Dog } from '../dog';
+
+@Component({
+ selector: 'app-dog-detail',
+ templateUrl: './dog-detail.component.html'
+})
+export class DogDetailComponent implements OnInit {
+ public dog: Dog = new Dog();
+
+ constructor(private route: ActivatedRoute,
+ private dogService: DogService) {
+ }
+
+ ngOnInit(): void {
+ const id = this.route.snapshot.paramMap.get('id');
+ this.dogService.getResource(id).subscribe(
+ dog => {
+ this.dog = dog;
+ });
+ }
+
+}
diff --git a/src/app/pet/dog/dog-edit/dog-edit.component.html b/src/app/pet/dog/dog-edit/dog-edit.component.html
new file mode 100644
index 0000000..7472239
--- /dev/null
+++ b/src/app/pet/dog/dog-edit/dog-edit.component.html
@@ -0,0 +1,198 @@
+
diff --git a/src/app/pet/dog/dog-edit/dog-edit.component.ts b/src/app/pet/dog/dog-edit/dog-edit.component.ts
new file mode 100644
index 0000000..bec45db
--- /dev/null
+++ b/src/app/pet/dog/dog-edit/dog-edit.component.ts
@@ -0,0 +1,139 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { Router } from '@angular/router';
+import { Dog } from '../dog';
+import { DogService } from '../dog.service';
+import { AbstractControl, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms';
+
+@Component({
+ selector: 'app-dog-edit',
+ templateUrl: './dog-edit.component.html'
+})
+export class DogEditComponent implements OnInit {
+ public dog: Dog = new Dog();
+ public dogForm: FormGroup;
+
+ constructor(private route: ActivatedRoute,
+ private router: Router,
+ private dogService: DogService) {
+ }
+
+ ngOnInit(): void {
+ const id = this.route.snapshot.paramMap.get('id');
+ this.dogService.getResource(id).subscribe(
+ (dog: Dog) => this.dog = dog );
+ this.dogForm = new FormGroup({
+ chip: new FormControl(this.dog.chip, [
+ Validators.required,
+ ]),
+ name: new FormControl(this.dog.name, [
+ Validators.required,
+ ]),
+ dateOfBirth: new FormControl(this.dog.dateOfBirth, [
+ Validators.required,
+ this.dateValidator(),
+ ]),
+ colour: new FormControl(this.dog.colour, [
+ Validators.required,
+ this.colourValidator(),
+ ]),
+ size: new FormControl(this.dog.size, [
+ Validators.required,
+ this.numberValidator(),
+ ]),
+ sex: new FormControl(this.dog.sex, [
+ Validators.required,
+ this.sexValidator(),
+ ]),
+ race: new FormControl(this.dog.race, [
+ Validators.required,
+ ]),
+ dangerous: new FormControl(this.dog.dangerous, []),
+ barkingLevel: new FormControl(this.dog.barkingLevel, [
+ Validators.required,
+ ]),
+ });
+ }
+
+
+ dateValidator(): ValidatorFn {
+ return (control: AbstractControl): ValidationErrors | null => {
+ const dateValue = control.value;
+ const isValidDate = !isNaN(Date.parse(dateValue));
+ return isValidDate ? null : { invalidDate: { value: control.value } };
+ };
+ }
+
+ colourValidator(): ValidatorFn {
+ const hexColorRe: RegExp = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
+ return (control: AbstractControl): ValidationErrors | null => {
+ const isValidColour = hexColorRe.test(control.value);
+ return isValidColour ? null : { invalidColour: { value: control.value } };
+ };
+ }
+
+ numberValidator(): ValidatorFn {
+ return (control: AbstractControl): ValidationErrors | null => {
+ const numberValue = control.value;
+ const isValidNumber = !isNaN(numberValue) && numberValue > 0;
+ return isValidNumber ? null : { invalidNumber: { value: control.value } };
+ };
+ }
+
+ sexValidator(): ValidatorFn {
+ const validSexes = ['male', 'female'];
+ return (control: AbstractControl): ValidationErrors | null => {
+ const isValidSex = control.value !== null && control.value !== undefined && validSexes.includes(control.value.toLowerCase());
+ return isValidSex ? null : { invalidSex: { value: control.value } };
+ };
+ }
+
+ get chip() {
+ return this.dogForm.get('chip');
+ }
+
+ get name() {
+ return this.dogForm.get('name');
+ }
+
+ get dateOfBirth() {
+ return this.dogForm.get('dateOfBirth');
+ }
+
+ get adopted() {
+ return this.dogForm.get('adopted');
+ }
+
+ get colour() {
+ return this.dogForm.get('colour');
+ }
+
+ get size() {
+ return this.dogForm.get('size');
+ }
+
+ get sex() {
+ return this.dogForm.get('sex');
+ }
+
+ get race() {
+ return this.dogForm.get('race');
+ }
+
+ get dangerous() {
+ return this.dogForm.get('dangerous');
+ }
+
+ get barkingLevel() {
+ return this.dogForm.get('barkingLevel');
+ }
+
+
+ onSubmit(): void {
+ this.dogService.patchResource(this.dog).subscribe(
+ (patchedDog: Dog) => {
+ this.router.navigate(['dogs', patchedDog.id]);
+ });
+ }
+
+}
diff --git a/src/app/pet/dog/dog-list/dog-list.component.html b/src/app/pet/dog/dog-list/dog-list.component.html
index c9d0a98..717a80a 100644
--- a/src/app/pet/dog/dog-list/dog-list.component.html
+++ b/src/app/pet/dog/dog-list/dog-list.component.html
@@ -1,14 +1,52 @@
+
+
-
-
+
+
+
+
Date of birth
+
{{dog.dateOfBirth}}
+
+
+
+
+
diff --git a/src/app/pet/dog/dog-list/dog-list.component.ts b/src/app/pet/dog/dog-list/dog-list.component.ts
index 8015c16..d1edc63 100644
--- a/src/app/pet/dog/dog-list/dog-list.component.ts
+++ b/src/app/pet/dog/dog-list/dog-list.component.ts
@@ -41,4 +41,9 @@ export class DogListComponent implements OnInit {
(page: PagedResourceCollection
) => (this.dogs = page.resources)
);
}
+
+ detail(dog: Dog): void {
+ this.router.navigate(['/dogs', dog.id]);
+ }
+
}
diff --git a/src/app/pet/dog/dog-search/dog-search.component.html b/src/app/pet/dog/dog-search/dog-search.component.html
new file mode 100644
index 0000000..910bb08
--- /dev/null
+++ b/src/app/pet/dog/dog-search/dog-search.component.html
@@ -0,0 +1,12 @@
+
+
+
+
+ {{r.name}} - {{r.chip}}
+
+
\ No newline at end of file
diff --git a/src/app/pet/dog/dog-search/dog-search.component.ts b/src/app/pet/dog/dog-search/dog-search.component.ts
new file mode 100644
index 0000000..3774ca8
--- /dev/null
+++ b/src/app/pet/dog/dog-search/dog-search.component.ts
@@ -0,0 +1,42 @@
+import { Component, EventEmitter, Output } from '@angular/core';
+import { Observable, of, OperatorFunction } from 'rxjs';
+import { catchError, debounceTime, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators';
+import { ResourceCollection } from '@lagoshny/ngx-hateoas-client';
+import { Dog } from '../dog';
+import { DogService } from '../dog.service';
+
+@Component({
+ selector: 'app-dog-search',
+ templateUrl: './dog-search.component.html'
+})
+
+export class DogSearchComponent {
+ @Output() emitResults: EventEmitter = new EventEmitter();
+ searchFailed = false;
+ searching = false;
+
+ constructor(private dogService: DogService) {
+ }
+
+ autocomplete: OperatorFunction = (text$: Observable) =>
+ text$.pipe(
+ debounceTime(500),
+ distinctUntilChanged(),
+ tap(() => this.searching = true),
+ switchMap(term => term.length < 3 ? of([]) :
+ this.dogService.findByIdContaining(term).pipe(
+ map((collection: ResourceCollection) => collection.resources),
+ tap(() => this.searchFailed = false),
+ catchError(() => {
+ this.searchFailed = true;
+ return of([]);
+ })
+ )
+ ),
+ tap(() => this.searching = false )
+ )
+
+ select(item: any): void {
+ this.emitResults.emit(item as Dog);
+ }
+}
diff --git a/src/app/pet/dog/dog.service.ts b/src/app/pet/dog/dog.service.ts
index d1d96cd..fbafecd 100644
--- a/src/app/pet/dog/dog.service.ts
+++ b/src/app/pet/dog/dog.service.ts
@@ -1,6 +1,8 @@
import { Injectable } from "@angular/core";
import { PetService } from "../pet.service";
import { Dog } from "./dog";
+import { Observable, forkJoin, map } from "rxjs";
+import { ResourceCollection } from "@lagoshny/ngx-hateoas-client";
@Injectable({providedIn: 'root'})
export class DogService extends PetService {
@@ -9,4 +11,18 @@ export class DogService extends PetService {
super(Dog);
}
+ public findByIdContaining(query: string): Observable> {
+ let res1 = this.searchCollection('findByName', { params: { name: query } });
+ let res2 = this.searchCollection('findByChip', { params: { chip: query } });
+ return forkJoin([res1, res2]).pipe(
+ map(([result1, result2]) => {
+ const combinedResources = [...result1.resources, ...result2.resources];
+ return {
+ ...result1,
+ resources: combinedResources,
+ } as ResourceCollection;
+ })
+ );;
+ }
+
}
\ No newline at end of file
diff --git a/src/app/pet/dog/dog.ts b/src/app/pet/dog/dog.ts
index 6d348b5..545e33a 100644
--- a/src/app/pet/dog/dog.ts
+++ b/src/app/pet/dog/dog.ts
@@ -4,6 +4,6 @@ import { Pet } from '../pet';
@HateoasResource('dogs')
export class Dog extends Pet {
- meowingLevel: number;
+ barkingLevel: number;
}
\ No newline at end of file