diff --git a/angular.json b/angular.json index 0529da2..9e3a815 100644 --- a/angular.json +++ b/angular.json @@ -28,6 +28,7 @@ "src/assets" ], "styles": [ + "@angular/material/prebuilt-themes/azure-blue.css", "src/styles.css" ], "scripts": [] @@ -86,6 +87,7 @@ } ], "styles": [ + "@angular/material/prebuilt-themes/azure-blue.css", "src/styles.css" ], "scripts": [] diff --git a/bun.lockb b/bun.lockb index 1c972ff..ac14009 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package-lock.json b/package-lock.json index 0dac9dc..21da74a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,16 @@ "version": "0.0.0", "dependencies": { "@angular/animations": "^18.2.3", + "@angular/cdk": "^18.2.3", "@angular/common": "^18.2.3", "@angular/compiler": "^18.2.3", "@angular/core": "^18.2.3", "@angular/forms": "^18.2.3", + "@angular/material": "^18.2.3", "@angular/platform-browser": "^18.2.3", "@angular/platform-browser-dynamic": "^18.2.3", "@angular/router": "^18.2.3", + "angular-in-memory-web-api": "^0.18.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.14.3" @@ -385,6 +388,23 @@ "node": ">=10" } }, + "node_modules/@angular/cdk": { + "version": "18.2.3", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.3.tgz", + "integrity": "sha512-lUcpYTxPZuntJ1FK7V2ugapCGMIhT6TUDjIGgXfS9AxGSSKgwr8HNs6Ze9pcjYC44UhP40sYAZuiaFwmE60A2A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/cli": { "version": "18.2.3", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.3.tgz", @@ -531,6 +551,24 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material": { + "version": "18.2.3", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.3.tgz", + "integrity": "sha512-JFfvXaMHMhskncaxxus4sDvie9VYdMkfYgfinkLXpZlPFyn1IzjDw0c1BcrcsuD7UxQVZ/v5tucCgq1FQfGRpA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^18.0.0 || ^19.0.0", + "@angular/cdk": "18.2.3", + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "@angular/forms": "^18.0.0 || ^19.0.0", + "@angular/platform-browser": "^18.0.0 || ^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/platform-browser": { "version": "18.2.3", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.3.tgz", @@ -4763,6 +4801,20 @@ "ajv": "^8.8.2" } }, + "node_modules/angular-in-memory-web-api": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/angular-in-memory-web-api/-/angular-in-memory-web-api-0.18.0.tgz", + "integrity": "sha512-Eqkr9+x3d7K4dmn6Qs3ZVAfqBDPZN0N7Qel5i8eU/pe5r44J/pfxlNW+1LC2Sb2PdENEdvFzC8wx8qly5+kQyQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^18.0.0", + "@angular/core": "^18.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -6487,7 +6539,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -10467,7 +10519,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "entities": "^4.4.0" diff --git a/package.json b/package.json index 0671431..01682cb 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,12 @@ "private": true, "dependencies": { "@angular/animations": "^18.2.3", + "@angular/cdk": "^18.2.3", "@angular/common": "^18.2.3", "@angular/compiler": "^18.2.3", "@angular/core": "^18.2.3", "@angular/forms": "^18.2.3", + "@angular/material": "^18.2.3", "@angular/platform-browser": "^18.2.3", "@angular/platform-browser-dynamic": "^18.2.3", "@angular/router": "^18.2.3", @@ -39,4 +41,4 @@ "tailwindcss": "^3.4.10", "typescript": "~5.5.2" } -} +} \ No newline at end of file diff --git a/src/app/HotelItem/HotelItem.component.ts b/src/app/HotelItem/HotelItem.component.ts index 0e5d136..c34a644 100644 --- a/src/app/HotelItem/HotelItem.component.ts +++ b/src/app/HotelItem/HotelItem.component.ts @@ -1,17 +1,14 @@ -import { Component, Injectable, Input } from "@angular/core"; -import { ChildComponent } from "../Child/child.component"; +import { Component, Input } from "@angular/core"; import { Hotel } from "./hotel"; -import { CurrencyPipe, NgIf } from "@angular/common"; +import {CurrencyPipe, NgForOf, NgIf} from "@angular/common"; import { FormsModule } from "@angular/forms"; -import { StarRatingComponent } from "../star-rating/star-rating.component"; -import { HttpClient } from "@angular/common/http"; import { RouterLink } from "@angular/router"; @Component({ selector: 'app-hotel-item', standalone: true, templateUrl: './HotelItem.component.html', - imports: [ChildComponent, CurrencyPipe, FormsModule, StarRatingComponent, NgIf, RouterLink], + imports: [CurrencyPipe, FormsModule, NgIf, RouterLink, NgForOf], }) export class HotelItem { diff --git a/src/app/app.component.html b/src/app/app.component.html index 346d4ad..90c6b64 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 50508e2..2a3bdb6 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,20 +1,12 @@ import { Component, Injectable } from '@angular/core'; -import { HotelItem } from './HotelItem/HotelItem.component'; -import { SearchComponent } from './Search/search.component'; -import { AsyncPipe, NgFor, NgForOf, NgIf, UpperCasePipe } from '@angular/common'; -import { TextPipe } from '../text.pipe'; import { HotelService } from './Parent/services/hotel.service'; -import { inject } from '@angular/core'; -import { filter, from, last, map, Observable, scan } from 'rxjs'; -import { HttpClient } from '@angular/common/http'; -import { Hotel } from './HotelItem/hotel'; import { RouterOutlet } from '@angular/router'; @Injectable({providedIn: "root"}) @Component({ selector: 'app-root', standalone: true, - imports: [RouterOutlet, NgFor, NgForOf, NgIf, HotelItem, SearchComponent, UpperCasePipe, TextPipe, AsyncPipe], + imports: [RouterOutlet], templateUrl: './app.component.html', providers: [HotelService], styleUrl: './app.component.css' diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 55b7251..db633ab 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -8,6 +8,7 @@ import { registerLocaleData } from '@angular/common'; import { provideHttpClient } from '@angular/common/http'; import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; import { HotelData } from './api/api'; +import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; registerLocaleData(localeDe, 'de-DE'); registerLocaleData(localeCn, 'cn-CN'); @@ -16,5 +17,5 @@ export const appConfig: ApplicationConfig = { provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideHttpClient(), - importProvidersFrom(InMemoryWebApiModule.forRoot(HotelData))] + importProvidersFrom(InMemoryWebApiModule.forRoot(HotelData)), provideAnimationsAsync()] }; diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index eaaae27..9d06d6e 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,6 +1,7 @@ import { Routes } from '@angular/router'; import { HotelDetailsComponent } from './hotel-details/hotel-details.component'; import { HotelListComponent } from './hotel-list/hotel-list.component'; +import { HotelFormComponent } from './hotel-form/hotel-form.component'; export const routes: Routes = [ { @@ -11,4 +12,8 @@ export const routes: Routes = [ path: "hotels/:id", component: HotelDetailsComponent, }, + { + path: "create-hotel", + component: HotelFormComponent, + }, ]; diff --git a/src/app/hotel-details/hotel-details.component.html b/src/app/hotel-details/hotel-details.component.html index 3a1c644..2ba9075 100644 --- a/src/app/hotel-details/hotel-details.component.html +++ b/src/app/hotel-details/hotel-details.component.html @@ -1 +1,5 @@ - +
+
+ +
+
diff --git a/src/app/hotel-details/hotel-details.component.ts b/src/app/hotel-details/hotel-details.component.ts index 2d3c396..aac11b2 100644 --- a/src/app/hotel-details/hotel-details.component.ts +++ b/src/app/hotel-details/hotel-details.component.ts @@ -1,16 +1,17 @@ import { HttpClient } from '@angular/common/http'; import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; +import {ActivatedRoute, Router, RouterLink} from '@angular/router'; import { Hotel } from '../HotelItem/hotel'; -import { CurrencyPipe } from '@angular/common'; +import {CurrencyPipe, NgIf} from '@angular/common'; import { StarRatingComponent } from '../star-rating/star-rating.component'; import { HotelItem } from '../HotelItem/HotelItem.component'; import { catchError, EMPTY } from 'rxjs'; +import {HotelFormComponent} from "../hotel-form/hotel-form.component"; @Component({ selector: 'app-hotel-details', standalone: true, - imports: [CurrencyPipe, StarRatingComponent, HotelItem], + imports: [CurrencyPipe, StarRatingComponent, HotelItem, HotelFormComponent, NgIf, RouterLink], templateUrl: './hotel-details.component.html', styleUrl: './hotel-details.component.css' }) diff --git a/src/app/hotel-form/hotel-form.component.css b/src/app/hotel-form/hotel-form.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/hotel-form/hotel-form.component.html b/src/app/hotel-form/hotel-form.component.html new file mode 100644 index 0000000..7804948 --- /dev/null +++ b/src/app/hotel-form/hotel-form.component.html @@ -0,0 +1,58 @@ +
+
+ + +
+
Hotel Name is required.
+
Hotel Name must be at least 3 characters long.
+
+
+
+ + +
+
Hotel Description is required.
+
Hotel Description must be at least 3 characters long. +
+
+
+
+ + +
+
Hotel Price is required.
+
Hotel Price must be a positive number.
+
+
+
+ + +
+
Hotel Rating is required.
+
Hotel Rating must be at least 0.
+
Hotel Rating cannot be more than 5.
+
+
+
+
+ + +
+ +
+
\ No newline at end of file diff --git a/src/app/hotel-form/hotel-form.component.spec.ts b/src/app/hotel-form/hotel-form.component.spec.ts new file mode 100644 index 0000000..3f1f5bd --- /dev/null +++ b/src/app/hotel-form/hotel-form.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HotelFormComponent } from './hotel-form.component'; + +describe('HotelFormComponent', () => { + let component: HotelFormComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [HotelFormComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(HotelFormComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/hotel-form/hotel-form.component.ts b/src/app/hotel-form/hotel-form.component.ts new file mode 100644 index 0000000..a521159 --- /dev/null +++ b/src/app/hotel-form/hotel-form.component.ts @@ -0,0 +1,93 @@ +import { Component, inject, Inject, Input } from '@angular/core'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; +import { Hotel } from "../HotelItem/hotel"; +import { HttpClient } from '@angular/common/http'; +import { catchError } from "rxjs"; +import { Router, RouterLink } from "@angular/router"; +import { NgIf } from "@angular/common"; +import { MatButtonModule } from "@angular/material/button"; +import { MatIconModule } from "@angular/material/icon"; + +@Component({ + selector: 'app-hotel-form', + standalone: true, + imports: [ + ReactiveFormsModule, + RouterLink, + NgIf, + MatButtonModule, + MatIconModule + ], + templateUrl: './hotel-form.component.html', + styleUrl: './hotel-form.component.css' +}) +export class HotelFormComponent { + public form!: FormGroup; + + @Input() + public hotel!: Hotel; + + public router: Router = inject(Router); + + constructor(private http: HttpClient) {} + + ngOnInit() { + this.form = new FormGroup({ + name: new FormControl(this.hotel?.hotelName || '', [Validators.minLength(3), Validators.required]), + description: new FormControl(this.hotel?.description || '', [Validators.minLength(3), Validators.required]), + price: new FormControl(this.hotel?.price || '', [Validators.min(0), Validators.required]), + rating: new FormControl(this.hotel?.rating || '', [Validators.min(0), Validators.max(5), Validators.required]) + }); + } + + public delete(id: number) { + this.http.delete(`api/hotels/${this.hotel?.id}`).pipe( + catchError(err => { + console.error('Error deleting hotel', err); + return err; + }) + ).subscribe(() => { + console.log('Hotel deleted'); + }); + + this.router.navigate(['/']); + } + + public submit() { + if (this.form.valid) { + const hotelData: Hotel = { + id: this.hotel?.id || 0, + hotelName: this.form.value.name, + description: this.form.value.description, + price: this.form.value.price, + rating: this.form.value.rating, + imageUrl: this.hotel?.imageUrl || 'https://placehold.co/2000x1050/EEE/31343C', + tags: this.hotel?.tags || [] + }; + + if (this.hotel) { + this.http.put(`api/hotels/${this.hotel.id}`, hotelData).pipe( + catchError(err => { + console.error('Error updating hotel', err); + return err; + }) + ).subscribe(() => { + console.log('Hotel updated'); + }); + } + + else { + this.http.post('api/hotels', hotelData).pipe( + catchError(err => { + console.error('Error creating hotel', err); + return err; + }) + ).subscribe(() => { + console.log('Hotel created'); + }); + } + + this.router.navigate(['/']); + } + } +} diff --git a/src/app/hotel-list/hotel-list.component.html b/src/app/hotel-list/hotel-list.component.html index b4dd192..6f2d94c 100644 --- a/src/app/hotel-list/hotel-list.component.html +++ b/src/app/hotel-list/hotel-list.component.html @@ -1,9 +1,10 @@

{{'hello' | uppercase | text}}

- @if (hotels[0].hotelName) { + +
- } +
diff --git a/src/app/hotel-list/hotel-list.component.ts b/src/app/hotel-list/hotel-list.component.ts index 528c8ce..876c910 100644 --- a/src/app/hotel-list/hotel-list.component.ts +++ b/src/app/hotel-list/hotel-list.component.ts @@ -7,6 +7,8 @@ import { Hotel } from '../HotelItem/hotel'; import { HttpClient } from '@angular/common/http'; import { filter, from, last, map, Observable, scan } from 'rxjs'; import { HotelItem } from '../HotelItem/HotelItem.component'; +import { RouterLink } from '@angular/router'; +import { MatButtonModule } from '@angular/material/button'; interface User { name: string; @@ -16,7 +18,7 @@ interface User { @Component({ selector: 'app-hotel-list', standalone: true, - imports: [UpperCasePipe, TextPipe, SearchComponent, HotelItem, NgFor, NgIf], + imports: [UpperCasePipe, TextPipe, SearchComponent, HotelItem, NgFor, NgIf, RouterLink, MatButtonModule], templateUrl: './hotel-list.component.html', styleUrl: './hotel-list.component.css' }) @@ -33,41 +35,5 @@ export class HotelListComponent { this.http.get>("api/hotels").subscribe(res => { this.hotels = res; }); - - const users = [ - { name: "Max", age: 21 }, - { name: "Peter", age: 31 }, - { name: "Hans", age: 13 }, - { name: "Klaus", age: 51 }, - { name: "Dieter", age: 1 }, - { name: "Jan-Marlon", age: 3 }, - ] - - const stream: Observable = from(users); - - stream.pipe( - filter((user) => user.age > 18), - scan((acc, user) => acc + user.age, 0), - map((ageSum, index) => ageSum / (index + 1)), - last(), - ).subscribe(console.log); - - // const stream: Observable = from([5, 1, 2, 12, 5, 14, 17, 5, "testing"]); - - // stream.pipe( - // filter((value) => typeof value === "number"), - // tap((value) => console.log("Zahl:" + value)), - // filter((value: number) => value % 2 === 0), - // tap((value) => console.log("Gerade Zahl: " + value)), - // toArray(), - //).subscribe(console.log); } - - public test() { - console.log(this.search); - } - - // public foundHotels = this.hotels.pipe( - // filter((hotel) => hotel.hotelName.includes(this.search)), - // ); } diff --git a/src/index.html b/src/index.html index f3614f8..0ca266f 100644 --- a/src/index.html +++ b/src/index.html @@ -7,8 +7,10 @@ + + - + diff --git a/src/styles.css b/src/styles.css index 2adbbf3..3ccb4fd 100644 --- a/src/styles.css +++ b/src/styles.css @@ -6,3 +6,6 @@ .button { @apply border-black rounded px-3 py-1 border-[3px]; } + +html, body { height: 100%; } +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } \ No newline at end of file