Compare commits
No commits in common. "main" and "constantin" have entirely different histories.
main
...
constantin
|
@ -1 +0,0 @@
|
||||||
flake-profile-4-link
|
|
|
@ -1 +0,0 @@
|
||||||
/nix/store/3hvzb7brklcrpdggn78fnmi20i49jnys-nix-shell-env
|
|
|
@ -1 +0,0 @@
|
||||||
/nix/store/amb6zvwfmy0l7yfdhrfaclcy4997fy03-nix-shell-env
|
|
|
@ -1 +0,0 @@
|
||||||
/nix/store/rsadgr5y0wa25gvk7j7zcml5pf9ym3h5-nix-shell-env
|
|
|
@ -1 +0,0 @@
|
||||||
/nix/store/amb6zvwfmy0l7yfdhrfaclcy4997fy03-nix-shell-env
|
|
1
.envrc
|
@ -1 +0,0 @@
|
||||||
use flake
|
|
1
.gitignore
vendored
|
@ -40,3 +40,4 @@ testem.log
|
||||||
# System files
|
# System files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
.angular
|
||||||
|
|
4
.vscode/extensions.json
vendored
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
|
|
||||||
"recommendations": ["angular.ng-template"]
|
|
||||||
}
|
|
20
.vscode/launch.json
vendored
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "ng serve",
|
|
||||||
"type": "chrome",
|
|
||||||
"request": "launch",
|
|
||||||
"preLaunchTask": "npm: start",
|
|
||||||
"url": "http://localhost:4200/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ng test",
|
|
||||||
"type": "chrome",
|
|
||||||
"request": "launch",
|
|
||||||
"preLaunchTask": "npm: test",
|
|
||||||
"url": "http://localhost:9876/debug.html"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
42
.vscode/tasks.json
vendored
|
@ -1,42 +0,0 @@
|
||||||
{
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"type": "npm",
|
|
||||||
"script": "start",
|
|
||||||
"isBackground": true,
|
|
||||||
"problemMatcher": {
|
|
||||||
"owner": "typescript",
|
|
||||||
"pattern": "$tsc",
|
|
||||||
"background": {
|
|
||||||
"activeOnStart": true,
|
|
||||||
"beginsPattern": {
|
|
||||||
"regexp": "(.*?)"
|
|
||||||
},
|
|
||||||
"endsPattern": {
|
|
||||||
"regexp": "bundle generation complete"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "npm",
|
|
||||||
"script": "test",
|
|
||||||
"isBackground": true,
|
|
||||||
"problemMatcher": {
|
|
||||||
"owner": "typescript",
|
|
||||||
"pattern": "$tsc",
|
|
||||||
"background": {
|
|
||||||
"activeOnStart": true,
|
|
||||||
"beginsPattern": {
|
|
||||||
"regexp": "(.*?)"
|
|
||||||
},
|
|
||||||
"endsPattern": {
|
|
||||||
"regexp": "bundle generation complete"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
23
angular.json
|
@ -16,16 +16,19 @@
|
||||||
"outputPath": "dist/hotel-manager",
|
"outputPath": "dist/hotel-manager",
|
||||||
"index": "src/index.html",
|
"index": "src/index.html",
|
||||||
"browser": "src/main.ts",
|
"browser": "src/main.ts",
|
||||||
"polyfills": ["zone.js"],
|
"polyfills": [
|
||||||
|
"zone.js"
|
||||||
|
],
|
||||||
"tsConfig": "tsconfig.app.json",
|
"tsConfig": "tsconfig.app.json",
|
||||||
"assets": [
|
"assets": [
|
||||||
{
|
{
|
||||||
"glob": "**/*",
|
"glob": "**/*",
|
||||||
"input": "public"
|
"input": "public"
|
||||||
},
|
}
|
||||||
"src/assets"
|
],
|
||||||
|
"styles": [
|
||||||
|
"src/styles.css"
|
||||||
],
|
],
|
||||||
"styles": ["src/styles.css"],
|
|
||||||
"scripts": []
|
"scripts": []
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
|
@ -70,7 +73,10 @@
|
||||||
"test": {
|
"test": {
|
||||||
"builder": "@angular-devkit/build-angular:karma",
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
"options": {
|
"options": {
|
||||||
"polyfills": ["zone.js", "zone.js/testing"],
|
"polyfills": [
|
||||||
|
"zone.js",
|
||||||
|
"zone.js/testing"
|
||||||
|
],
|
||||||
"tsConfig": "tsconfig.spec.json",
|
"tsConfig": "tsconfig.spec.json",
|
||||||
"assets": [
|
"assets": [
|
||||||
{
|
{
|
||||||
|
@ -78,11 +84,16 @@
|
||||||
"input": "public"
|
"input": "public"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"styles": ["src/styles.css"],
|
"styles": [
|
||||||
|
"src/styles.css"
|
||||||
|
],
|
||||||
"scripts": []
|
"scripts": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"cli": {
|
||||||
|
"analytics": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
bun.lockb
23
devenv.nix
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
name = "hotel-manager";
|
|
||||||
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
|
||||||
};
|
|
||||||
|
|
||||||
packages = [
|
|
||||||
pkgs.nodePackages."@angular/cli"
|
|
||||||
pkgs.git
|
|
||||||
pkgs.bun
|
|
||||||
];
|
|
||||||
|
|
||||||
services.ng-serve = {
|
|
||||||
description = "Angular Development Server";
|
|
||||||
start = ''
|
|
||||||
echo "Starting Angular server..."
|
|
||||||
ng serve --host 0.0.0.0 --port 4200
|
|
||||||
'';
|
|
||||||
restartIfChanged = true;
|
|
||||||
after = [ "network-online.target" ];
|
|
||||||
};
|
|
||||||
}
|
|
27
flake.lock
generated
|
@ -1,27 +0,0 @@
|
||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1725634671,
|
|
||||||
"narHash": "sha256-v3rIhsJBOMLR8e/RNWxr828tB+WywYIoajrZKFM+0Gg=",
|
|
||||||
"owner": "nixos",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "574d1eac1c200690e27b8eb4e24887f8df7ac27c",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nixos",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
31
flake.nix
|
@ -1,31 +0,0 @@
|
||||||
{
|
|
||||||
description = "Hotel-Manager";
|
|
||||||
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = { self, nixpkgs, ... }:
|
|
||||||
let
|
|
||||||
system = "x86_64-linux";
|
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
devShells.${system}.default =
|
|
||||||
pkgs.mkShell
|
|
||||||
{
|
|
||||||
buildInputs = [
|
|
||||||
pkgs.nodePackages."@angular/cli"
|
|
||||||
pkgs.git
|
|
||||||
pkgs.bun
|
|
||||||
];
|
|
||||||
|
|
||||||
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
|
|
||||||
|
|
||||||
shellHook = ''
|
|
||||||
export CARGO_HOME=$HOME/.cargo
|
|
||||||
export RUSTUP_HOME=$HOME/.rustup
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
6736
package-lock.json
generated
28
package.json
|
@ -10,23 +10,23 @@
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^19.0.0",
|
"@angular/animations": "^18.2.3",
|
||||||
"@angular/common": "^19.0.0",
|
"@angular/common": "^18.2.3",
|
||||||
"@angular/compiler": "^19.0.0",
|
"@angular/compiler": "^18.2.3",
|
||||||
"@angular/core": "^19.0.0",
|
"@angular/core": "^18.2.3",
|
||||||
"@angular/forms": "^19.0.0",
|
"@angular/forms": "^18.2.3",
|
||||||
"@angular/platform-browser": "^19.0.0",
|
"@angular/platform-browser": "^18.2.3",
|
||||||
"@angular/platform-browser-dynamic": "^19.0.0",
|
"@angular/platform-browser-dynamic": "^18.2.3",
|
||||||
"@angular/router": "^19.0.0",
|
"@angular/router": "^18.2.3",
|
||||||
"angular-in-memory-web-api": "^0.18.0",
|
"angular-in-memory-web-api": "^0.18.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.15.0"
|
"zone.js": "~0.14.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "^19.0.2",
|
"@angular-devkit/build-angular": "^18.2.3",
|
||||||
"@angular/cli": "^19.0.2",
|
"@angular/cli": "^18.2.3",
|
||||||
"@angular/compiler-cli": "^19.0.0",
|
"@angular/compiler-cli": "^18.2.3",
|
||||||
"@types/jasmine": "~5.1.0",
|
"@types/jasmine": "~5.1.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"jasmine-core": "~5.1.0",
|
"jasmine-core": "~5.1.0",
|
||||||
|
@ -35,8 +35,8 @@
|
||||||
"karma-coverage": "~2.2.0",
|
"karma-coverage": "~2.2.0",
|
||||||
"karma-jasmine": "~5.1.0",
|
"karma-jasmine": "~5.1.0",
|
||||||
"karma-jasmine-html-reporter": "~2.1.0",
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
"postcss": "^8.4.41",
|
"postcss": "^8.4.49",
|
||||||
"tailwindcss": "^3.4.10",
|
"tailwindcss": "^3.4.15",
|
||||||
"typescript": "~5.5.2"
|
"typescript": "~5.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 637 KiB After Width: | Height: | Size: 637 KiB |
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 143 KiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
BIN
public/assets/img/heisenberg.jpg
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
public/assets/img/huy.png
Normal file
After Width: | Height: | Size: 169 KiB |
BIN
public/assets/img/kjan.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
public/assets/img/rat.png
Normal file
After Width: | Height: | Size: 177 KiB |
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
||||||
"extends": ["local>Renovate/renovate-config"]
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
sonar.projectKey=Hotel-Manager
|
|
|
@ -1,6 +0,0 @@
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="px-6 py-2 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75"
|
|
||||||
>
|
|
||||||
{{ text }}
|
|
||||||
</button>
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-button',
|
|
||||||
standalone: true,
|
|
||||||
templateUrl: './button.component.html',
|
|
||||||
})
|
|
||||||
export class ButtonComponent {
|
|
||||||
public text = 'Test';
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
<h1 class="text-2xl font-bold mb-4">Child</h1>
|
|
||||||
<p class="text-lg mb-2">Balance: {{ balance }}</p>
|
|
||||||
<p [class.hidden]="!isBroke" class="text-red-500 mb-4">
|
|
||||||
I'm broke. Now I'm about as poor as Jan-Marlon.
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
(click)="takeMoney()"
|
|
||||||
class="px-6 py-2 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75"
|
|
||||||
>
|
|
||||||
Take Money
|
|
||||||
</button>
|
|
|
@ -1,25 +0,0 @@
|
||||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-child',
|
|
||||||
standalone: true,
|
|
||||||
templateUrl: './child.component.html',
|
|
||||||
})
|
|
||||||
export class ChildComponent {
|
|
||||||
@Input() public balance: number = 0;
|
|
||||||
@Output() balanceChange = new EventEmitter<number>();
|
|
||||||
|
|
||||||
public isBroke = false;
|
|
||||||
|
|
||||||
public takeMoney() {
|
|
||||||
if (this.balance <= 0) {
|
|
||||||
this.isBroke = true;
|
|
||||||
} else {
|
|
||||||
this.balance -= 50;
|
|
||||||
if (this.balance == 0) {
|
|
||||||
this.isBroke = true;
|
|
||||||
}
|
|
||||||
this.balanceChange.emit(this.balance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
<p class="text-2xl font-bold text-gray-800 mb-2">Name: {{ hotel.hotelName }}</p>
|
|
||||||
<p class="text-base text-gray-600 mb-4">Description: {{ hotel.description }}</p>
|
|
||||||
<p class="text-lg font-medium text-green-600 mb-4">
|
|
||||||
Price:
|
|
||||||
{{
|
|
||||||
hotel.price
|
|
||||||
| currency
|
|
||||||
: getCurrencyCode(selectedLanguage)
|
|
||||||
: "symbol"
|
|
||||||
: "2.2-2"
|
|
||||||
: selectedLanguage
|
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
<select
|
|
||||||
[ngModel]="selectedLanguage"
|
|
||||||
(ngModelChange)="languageChange($event)"
|
|
||||||
class="input-field block w-full p-2 border border-gray-300 rounded-md mb-4 focus:border-blue-500 focus:ring focus:ring-blue-200"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
*ngFor="let lang of langs"
|
|
||||||
[value]="lang.code"
|
|
||||||
[selected]="lang.lang == 'de'"
|
|
||||||
>
|
|
||||||
{{ lang.lang }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<img
|
|
||||||
src="{{ hotel.imageUrl }}"
|
|
||||||
alt="Hotel"
|
|
||||||
class="w-full rounded-full h-auto animate-shake shadow-lg mb-4"
|
|
||||||
/>
|
|
||||||
<a
|
|
||||||
*ngIf="!isDetail"
|
|
||||||
routerLink="/hotels/{{ hotel.id }}"
|
|
||||||
class="submit-button text-blue-600 hover:text-blue-800 hover:underline mb-4 block"
|
|
||||||
>Details</a
|
|
||||||
>
|
|
|
@ -1,63 +0,0 @@
|
||||||
import { Component, Injectable, Input } from '@angular/core';
|
|
||||||
import { ChildComponent } from '../Child/child.component';
|
|
||||||
import { Hotel } from './hotel';
|
|
||||||
import { CurrencyPipe, NgFor, 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,
|
|
||||||
NgFor,
|
|
||||||
],
|
|
||||||
})
|
|
||||||
export class HotelItem {
|
|
||||||
@Input() public hotel!: Hotel;
|
|
||||||
public selectedLanguage?: string;
|
|
||||||
|
|
||||||
@Input() public isDetail: boolean = false;
|
|
||||||
|
|
||||||
public languageChange(lang: string) {
|
|
||||||
this.selectedLanguage = lang;
|
|
||||||
console.log(this.selectedLanguage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCurrencyCode(langCode: string | undefined): string {
|
|
||||||
if (!langCode) return '';
|
|
||||||
|
|
||||||
for (let language of this.langs) {
|
|
||||||
if (language.code === langCode) {
|
|
||||||
return language.currency;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public langs = [
|
|
||||||
{
|
|
||||||
lang: 'en',
|
|
||||||
code: 'en-US',
|
|
||||||
currency: 'USD',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
lang: 'cn',
|
|
||||||
code: 'cn-CN',
|
|
||||||
currency: 'CNY',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
lang: 'de',
|
|
||||||
code: 'de-DE',
|
|
||||||
currency: 'EUR',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
<div class="m-3">
|
|
||||||
<h1>Parent</h1>
|
|
||||||
<p>Kontostand: {{ balance }}</p>
|
|
||||||
<button type="button" class="button" (click)="addFifty()">Add Money</button>
|
|
||||||
|
|
||||||
<app-child [(balance)]="balance"></app-child>
|
|
||||||
<app-child [(balance)]="balance"></app-child>
|
|
||||||
</div>
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { Component } from '@angular/core';
|
|
||||||
import { ChildComponent } from '../Child/child.component';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-parent',
|
|
||||||
standalone: true,
|
|
||||||
templateUrl: './parent.component.html',
|
|
||||||
imports: [ChildComponent],
|
|
||||||
})
|
|
||||||
export class ParentComponent {
|
|
||||||
public balance = 1000;
|
|
||||||
|
|
||||||
public addFifty() {
|
|
||||||
this.balance += 50;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Hotel } from '../../HotelItem/hotel';
|
|
||||||
import { from, Observable } from 'rxjs';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class HotelService {
|
|
||||||
public getHotels(): Observable<Hotel> {
|
|
||||||
return from([
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
hotelName: 'Buea süßes Leben',
|
|
||||||
description: 'Schöne Aussicht am Meer',
|
|
||||||
price: 230.5,
|
|
||||||
imageUrl: 'assets/img/1.jpg',
|
|
||||||
rating: 3.5,
|
|
||||||
tags: ['test'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
hotelName: 'Marrakesch',
|
|
||||||
description: 'Genießen Sie den Blick auf die Berge',
|
|
||||||
price: 145.5,
|
|
||||||
imageUrl: 'assets/img/2.jpg',
|
|
||||||
rating: 5,
|
|
||||||
tags: ['test'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
hotelName: 'Abuja neuer Palast',
|
|
||||||
description: 'Kompletter Aufenthalt mit Autoservice',
|
|
||||||
price: 120.12,
|
|
||||||
imageUrl: 'assets/img/3.jpg',
|
|
||||||
rating: 4,
|
|
||||||
tags: ['test'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
hotelName: 'Kapstadt Stadt',
|
|
||||||
description: 'Wunderschönes Ambiente für Ihren Aufenthalt',
|
|
||||||
price: 135.12,
|
|
||||||
imageUrl: 'assets/img/4.jpg',
|
|
||||||
rating: 2.5,
|
|
||||||
tags: ['test'],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
<input
|
|
||||||
[(ngModel)]="input"
|
|
||||||
(ngModelChange)="update($event)"
|
|
||||||
class="border border-gray-300 rounded-md p-2 w-full focus:border-blue-500 focus:ring focus:ring-blue-200"
|
|
||||||
type="search"
|
|
||||||
/>
|
|
|
@ -1,20 +0,0 @@
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { Component, NgModule } from '@angular/core';
|
|
||||||
import { FormsModule, NgForm, NgModel } from '@angular/forms';
|
|
||||||
import { Input, Output } from '@angular/core';
|
|
||||||
import { EventEmitter } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-search',
|
|
||||||
standalone: true,
|
|
||||||
templateUrl: './search.component.html',
|
|
||||||
imports: [FormsModule],
|
|
||||||
})
|
|
||||||
export class SearchComponent {
|
|
||||||
@Input() public input: string = '';
|
|
||||||
@Output() inputChange = new EventEmitter<string>();
|
|
||||||
|
|
||||||
public update(e: string) {
|
|
||||||
this.inputChange.emit(e);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
import { InMemoryDbService } from 'angular-in-memory-web-api';
|
|
||||||
|
|
||||||
import { Hotel } from '../HotelItem/hotel';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initial data for in memory web api
|
|
||||||
*
|
|
||||||
* @export
|
|
||||||
* @class HotelData
|
|
||||||
* @implements {InMemoryDbService}
|
|
||||||
*/
|
|
||||||
export class HotelData implements InMemoryDbService {
|
|
||||||
createDb(): Record<string, Hotel[]> {
|
|
||||||
const hotels: Hotel[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
hotelName: 'Buea sweet life',
|
|
||||||
description: 'Belle vue au bord de la mer',
|
|
||||||
price: 230.5,
|
|
||||||
imageUrl: 'assets/img/1.jpg',
|
|
||||||
rating: 3.5,
|
|
||||||
tags: ['nouveau'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
hotelName: 'Marakech',
|
|
||||||
description: 'Profitez de la vue sur les montagnes',
|
|
||||||
price: 145.5,
|
|
||||||
imageUrl: 'assets/img/2.jpg',
|
|
||||||
rating: 5,
|
|
||||||
tags: ['nouveau'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
hotelName: 'Abudja new look palace',
|
|
||||||
description: 'Séjour complet avec service de voitures',
|
|
||||||
price: 120.12,
|
|
||||||
imageUrl: 'assets/img/3.jpg',
|
|
||||||
rating: 4,
|
|
||||||
tags: ['nouveau'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
hotelName: 'Cape town city',
|
|
||||||
description: 'Magnifique cadre pour votre séjour',
|
|
||||||
price: 135.12,
|
|
||||||
imageUrl: 'assets/img/4.jpg',
|
|
||||||
rating: 2.5,
|
|
||||||
tags: ['nouveau'],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return { hotels };
|
|
||||||
}
|
|
||||||
|
|
||||||
genId(hotels: Hotel[]): number {
|
|
||||||
return hotels.length > 0
|
|
||||||
? Math.max(...hotels.map((hotel) => hotel.id)) + 1
|
|
||||||
: 1;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +1 @@
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
<router-outlet></router-outlet>
|
|
||||||
<!-- <p>ID: {{response.id}}</p> -->
|
|
||||||
<!-- <p>userId: {{response.userId}}</p> -->
|
|
||||||
<!-- <p>title: {{response.title}}</p> -->
|
|
||||||
<!-- <p>completed: {{response.completed}}</p> -->
|
|
|
@ -24,8 +24,6 @@ describe('AppComponent', () => {
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const compiled = fixture.nativeElement as HTMLElement;
|
const compiled = fixture.nativeElement as HTMLElement;
|
||||||
expect(compiled.querySelector('h1')?.textContent).toContain(
|
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, hotel-manager');
|
||||||
'Hello, hotel-manager',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,38 +1,70 @@
|
||||||
import { Component, Injectable } from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import { HotelItem } from './HotelItem/HotelItem.component';
|
import {HotelsComponent} from "./hotel/hotels.component";
|
||||||
import { SearchComponent } from './Search/search.component';
|
import {IdkComponent} from "./idek/idk.component";
|
||||||
import {
|
import {filter, from, map, reduce} from "rxjs";
|
||||||
AsyncPipe,
|
import {RouterOutlet} from "@angular/router";
|
||||||
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({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [HotelsComponent, IdkComponent, RouterOutlet],
|
||||||
RouterOutlet,
|
template: `
|
||||||
NgFor,
|
<router-outlet />
|
||||||
NgForOf,
|
`
|
||||||
NgIf,
|
|
||||||
HotelItem,
|
|
||||||
SearchComponent,
|
|
||||||
UpperCasePipe,
|
|
||||||
TextPipe,
|
|
||||||
AsyncPipe,
|
|
||||||
],
|
|
||||||
templateUrl: './app.component.html',
|
|
||||||
providers: [HotelService],
|
|
||||||
styleUrl: './app.component.css',
|
|
||||||
})
|
})
|
||||||
export class AppComponent {}
|
export class AppComponent implements OnInit {
|
||||||
|
ngOnInit() {
|
||||||
|
const users = [
|
||||||
|
{
|
||||||
|
name: 'John',
|
||||||
|
age: 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Alice',
|
||||||
|
age: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Bob',
|
||||||
|
age: 45,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Eve',
|
||||||
|
age: 29,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Charlie',
|
||||||
|
age: 52,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Dave',
|
||||||
|
age: 41,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mallory',
|
||||||
|
age: 37,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Trent',
|
||||||
|
age: 48,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Peggy',
|
||||||
|
age: 26,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Victor',
|
||||||
|
age: 39,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
from(users).pipe(
|
||||||
|
filter(user => user.age >= 18),
|
||||||
|
reduce((acc, user) => {
|
||||||
|
acc.age += user.age
|
||||||
|
acc.count++;
|
||||||
|
return acc;
|
||||||
|
}, {age: 0, count: 0}),
|
||||||
|
map(data => data.age / data.count),
|
||||||
|
).subscribe((data) => {console.log("avg age: ", data)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,25 +1,22 @@
|
||||||
import {
|
import {ApplicationConfig, importProvidersFrom, provideZoneChangeDetection} from '@angular/core';
|
||||||
ApplicationConfig,
|
|
||||||
importProvidersFrom,
|
|
||||||
provideZoneChangeDetection,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
import localeDe from '@angular/common/locales/de';
|
|
||||||
import localeCn from '@angular/common/locales/zh-Hans';
|
|
||||||
|
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
import { registerLocaleData } from '@angular/common';
|
import {registerLocaleData} from "@angular/common";
|
||||||
import { provideHttpClient } from '@angular/common/http';
|
import localeDe from "@angular/common/locales/de"
|
||||||
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
|
import localeCn from "@angular/common/locales/en"
|
||||||
import { HotelData } from './api/api';
|
import localeJap from "@angular/common/locales/en"
|
||||||
|
import {provideHttpClient} from "@angular/common/http";
|
||||||
|
import {InMemoryWebApiModule} from "angular-in-memory-web-api";
|
||||||
|
import {HotelDataService} from "./service/HotelData.service";
|
||||||
|
|
||||||
registerLocaleData(localeDe, 'de-DE');
|
registerLocaleData(localeDe, 'de-DE')
|
||||||
registerLocaleData(localeCn, 'cn-CN');
|
registerLocaleData(localeCn, 'cn-CN')
|
||||||
|
registerLocaleData(localeJap, 'ja-JP')
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||||
provideRouter(routes),
|
provideRouter(routes),
|
||||||
provideHttpClient(),
|
provideHttpClient(),
|
||||||
importProvidersFrom(InMemoryWebApiModule.forRoot(HotelData)),
|
importProvidersFrom(InMemoryWebApiModule.forRoot(HotelDataService))
|
||||||
],
|
]
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,26 +1,22 @@
|
||||||
import { Routes } from '@angular/router';
|
import {Routes} from '@angular/router';
|
||||||
import { HotelListComponent } from './hotel-list/hotel-list.component';
|
import {HotelsComponent} from "./hotel/hotels.component";
|
||||||
import { TestComponent } from './test/test.component';
|
import {HotelComponent} from "./hotel/hotel.component";
|
||||||
import { NewHotelComponent } from './new-hotel/new-hotel.component';
|
import {CreateHotelComponent} from "./hotel/create-hotel.component";
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: 'hotels',
|
||||||
component: HotelListComponent,
|
component: HotelsComponent,
|
||||||
|
title: 'Hotels',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'hotels/:id',
|
path: 'hotels/new',
|
||||||
loadComponent: () =>
|
component: CreateHotelComponent,
|
||||||
import('./hotel-details/hotel-details.component').then(
|
title: 'New Hotel'
|
||||||
(c) => c.HotelDetailsComponent,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'testing',
|
path: 'hotels/:hotelId',
|
||||||
component: TestComponent,
|
component: HotelComponent,
|
||||||
},
|
title: 'Hotel',
|
||||||
{
|
}
|
||||||
path: 'new',
|
|
||||||
component: NewHotelComponent,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
import {
|
|
||||||
AbstractControl,
|
|
||||||
FormGroup,
|
|
||||||
ValidationErrors,
|
|
||||||
ValidatorFn,
|
|
||||||
} from '@angular/forms';
|
|
||||||
|
|
||||||
export class CrossValidator {
|
|
||||||
public crossValidate() {
|
|
||||||
return (control: AbstractControl): ValidationErrors | null => {
|
|
||||||
if (!(control instanceof FormGroup)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const contactType = control.get('contactType')?.value;
|
|
||||||
|
|
||||||
if (contactType == ('None' || null)) return null;
|
|
||||||
|
|
||||||
let error = null;
|
|
||||||
if (contactType == 'Email') {
|
|
||||||
error =
|
|
||||||
control.get('email')?.value != control.get('emailConfirmation')?.value
|
|
||||||
? { mismatchEmail: true }
|
|
||||||
: null;
|
|
||||||
} else {
|
|
||||||
error =
|
|
||||||
control.get('phone')?.value != control.get('phoneConfirmation')?.value
|
|
||||||
? { mismatchPhone: true }
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Error: ', error);
|
|
||||||
return error;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
57
src/app/currency/currency.component.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import {Component, EventEmitter, Output} from "@angular/core";
|
||||||
|
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
||||||
|
import {Lang} from "../idek/lang";
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: "app-currency",
|
||||||
|
imports: [
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FormsModule
|
||||||
|
],
|
||||||
|
template: `
|
||||||
|
<select [ngModel]="currency" (ngModelChange)="setCurrency($event)">
|
||||||
|
@for (currency of currencies; track currency.code) {
|
||||||
|
<option value="{{currency.name}}">{{ currency.name }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class CurrencyComponent {
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
public currency: EventEmitter<Lang> = new EventEmitter();
|
||||||
|
|
||||||
|
public currencies: Lang[] = [
|
||||||
|
{
|
||||||
|
name: 'de',
|
||||||
|
code: 'de-DE',
|
||||||
|
currency: 'EUR'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'en',
|
||||||
|
code: 'en-US',
|
||||||
|
currency: 'USD'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'jap',
|
||||||
|
code: 'ja-JP',
|
||||||
|
currency: 'JPY'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cn',
|
||||||
|
code: 'cn-CN',
|
||||||
|
currency: 'CNY'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
public setCurrency(currencyInput: string): void {
|
||||||
|
for (const currency of this.currencies) {
|
||||||
|
if (currency.name === currencyInput) {
|
||||||
|
this.currency.emit(currency);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +0,0 @@
|
||||||
@if (hotel) {
|
|
||||||
<app-hotel-item [hotel]="hotel" [isDetail]="true"></app-hotel-item>
|
|
||||||
<app-hotel-form [hotel]="hotel"></app-hotel-form>
|
|
||||||
<button class="delete-button" (click)="delete()">😠 DELETE</button>
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { HotelDetailsComponent } from './hotel-details.component';
|
|
||||||
|
|
||||||
describe('HotelDetailsComponent', () => {
|
|
||||||
let component: HotelDetailsComponent;
|
|
||||||
let fixture: ComponentFixture<HotelDetailsComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [HotelDetailsComponent],
|
|
||||||
}).compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(HotelDetailsComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,61 +0,0 @@
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
import { Hotel } from '../HotelItem/hotel';
|
|
||||||
import { CurrencyPipe, NgFor, NgForOf, 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,
|
|
||||||
HotelFormComponent,
|
|
||||||
NgIf,
|
|
||||||
NgForOf,
|
|
||||||
],
|
|
||||||
templateUrl: './hotel-details.component.html',
|
|
||||||
styleUrl: './hotel-details.component.css',
|
|
||||||
})
|
|
||||||
export class HotelDetailsComponent implements OnInit {
|
|
||||||
public hotel: any;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private route: ActivatedRoute,
|
|
||||||
private http: HttpClient,
|
|
||||||
private router: Router,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
delete(): void {
|
|
||||||
if (confirm('Are u sure u want to delete this hotel')) {
|
|
||||||
this.http
|
|
||||||
.delete<Hotel>('/api/hotels/' + this.route.snapshot.paramMap.get('id'))
|
|
||||||
.subscribe();
|
|
||||||
this.router.navigate(['/']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
const routeParams = this.route.snapshot.paramMap;
|
|
||||||
const hotelId = routeParams.get('id');
|
|
||||||
|
|
||||||
this.http
|
|
||||||
.get<Hotel>('api/hotels/' + hotelId)
|
|
||||||
.pipe(
|
|
||||||
catchError(() => {
|
|
||||||
alert('Not Found');
|
|
||||||
this.router.navigate(['/']);
|
|
||||||
|
|
||||||
return EMPTY;
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.subscribe((res) => {
|
|
||||||
this.hotel = res;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
<p>hotel-form works!</p>
|
|
||||||
<h1>{{ errorMsg }}</h1>
|
|
||||||
<form [formGroup]="hotelForm">
|
|
||||||
<label for="name">Name</label>
|
|
||||||
<div class="text-red-500 text-5xl font-bold" *ngIf="errorMessages['name']">
|
|
||||||
{{ errorMessages["name"] }}
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="border-red-500 text-3xl border-3 rounded-full bg-gray-500 font-bold p-3 m-3 border-8"
|
|
||||||
id="name"
|
|
||||||
formControlName="name"
|
|
||||||
/>
|
|
||||||
<label for="description">Description</label>
|
|
||||||
<div class="text-red" *ngIf="errorMessages['description']">
|
|
||||||
{{ errorMessages["description"] }}
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="input-field"
|
|
||||||
[class.border-8]="hotelForm.get('description')?.invalid"
|
|
||||||
id="description"
|
|
||||||
formControlName="description"
|
|
||||||
/>
|
|
||||||
<label for="price">Price</label>
|
|
||||||
<div class="text-red" *ngIf="errorMessages['price']">
|
|
||||||
{{ errorMessages["price"] }}
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
class="input-field"
|
|
||||||
[class.border-8]="
|
|
||||||
hotelForm.get('price')?.invalid && hotelForm.get('price')?.touched
|
|
||||||
"
|
|
||||||
id="price"
|
|
||||||
formControlName="price"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
class="submit-button"
|
|
||||||
(click)="submit()"
|
|
||||||
[disabled]="!hotelForm.valid"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
😃 Submit
|
|
||||||
</button>
|
|
||||||
<a class="back-button" routerLink="/">😭 Cancel</a>
|
|
||||||
</form>
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { HotelFormComponent } from './hotel-form.component';
|
|
||||||
|
|
||||||
describe('HotelFormComponent', () => {
|
|
||||||
let component: HotelFormComponent;
|
|
||||||
let fixture: ComponentFixture<HotelFormComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [HotelFormComponent],
|
|
||||||
}).compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(HotelFormComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,90 +0,0 @@
|
||||||
import { Component, Input } from '@angular/core';
|
|
||||||
import { Hotel } from '../HotelItem/hotel';
|
|
||||||
import {
|
|
||||||
AbstractControl,
|
|
||||||
FormControl,
|
|
||||||
FormGroup,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
Validators,
|
|
||||||
} from '@angular/forms';
|
|
||||||
import { NgFor, NgIf } from '@angular/common';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { Router, RouterLink } from '@angular/router';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-hotel-form',
|
|
||||||
standalone: true,
|
|
||||||
imports: [ReactiveFormsModule, NgIf, NgFor, RouterLink],
|
|
||||||
templateUrl: './hotel-form.component.html',
|
|
||||||
styleUrl: './hotel-form.component.css',
|
|
||||||
})
|
|
||||||
export class HotelFormComponent {
|
|
||||||
@Input() public hotel!: Hotel;
|
|
||||||
public hotelForm!: FormGroup;
|
|
||||||
public errorMsg: string = 'testing';
|
|
||||||
errorMessages: Record<string, string> = {};
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public http: HttpClient,
|
|
||||||
public router: Router,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
private validationErrorMessages: Record<string, string> = {
|
|
||||||
required: 'This field is required',
|
|
||||||
minlength: 'The value is too short',
|
|
||||||
maxlength: 'The value is too long',
|
|
||||||
pattern: 'The value format is incorrect',
|
|
||||||
};
|
|
||||||
|
|
||||||
setMessage(value: AbstractControl): void {
|
|
||||||
if ((value.touched || value.dirty) && value.errors) {
|
|
||||||
this.errorMsg = Object.keys(value.errors)
|
|
||||||
.map((key) => {
|
|
||||||
console.log(this.validationErrorMessages[key]);
|
|
||||||
return this.validationErrorMessages[key] || `Unknown error: ${key}`;
|
|
||||||
})
|
|
||||||
.join(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateErrorMessages(): void {
|
|
||||||
this.errorMessages = {};
|
|
||||||
|
|
||||||
Object.keys(this.hotelForm.controls).forEach((field) => {
|
|
||||||
const control = this.hotelForm.get(field);
|
|
||||||
|
|
||||||
if (control && control.errors) {
|
|
||||||
this.errorMessages[field] = Object.keys(control.errors)
|
|
||||||
.map(
|
|
||||||
(errorKey) =>
|
|
||||||
this.validationErrorMessages[errorKey] ||
|
|
||||||
`Unknown error: ${errorKey}`,
|
|
||||||
)
|
|
||||||
.join(' ');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.hotelForm = new FormGroup({
|
|
||||||
name: new FormControl(this.hotel.hotelName, Validators.required),
|
|
||||||
description: new FormControl(this.hotel.description, Validators.required),
|
|
||||||
price: new FormControl(this.hotel.price, Validators.required),
|
|
||||||
});
|
|
||||||
|
|
||||||
// this.hotelForm.valueChanges.subscribe(data => this.setMessage(this.hotelForm.get("name")!));
|
|
||||||
// this.hotelForm.valueChanges.subscribe(data => this.setMessage(this.hotelForm.get("description")!));
|
|
||||||
// this.hotelForm.valueChanges.subscribe(data => this.setMessage(this.hotelForm.get("price")!));
|
|
||||||
//
|
|
||||||
this.hotelForm.valueChanges.subscribe(() => this.updateErrorMessages());
|
|
||||||
}
|
|
||||||
|
|
||||||
submit(): void {
|
|
||||||
this.hotel.hotelName = this.hotelForm.get('name')?.value;
|
|
||||||
this.hotel.description = this.hotelForm.get('description')?.value;
|
|
||||||
this.hotel.price = this.hotelForm.get('price')?.value;
|
|
||||||
console.log(this.hotelForm.value);
|
|
||||||
this.http.put('/api/hotels/' + this.hotel.id, this.hotel).subscribe();
|
|
||||||
this.router.navigate(['/']);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
<div class="container p-4 mx-auto max-w-4xl">
|
|
||||||
<button (click)="loadTest2()">Load Test2</button>
|
|
||||||
<ng-container #test2></ng-container>
|
|
||||||
<h1 class="mt-6">{{ "hello" | uppercase | text }}</h1>
|
|
||||||
<a class="submit-button" routerLink="/new">CREATE NEW HOTEL</a>
|
|
||||||
<app-search [(input)]="search"></app-search>
|
|
||||||
@if (hotels[0].hotelName) {
|
|
||||||
<div *ngFor="let hotel of hotels">
|
|
||||||
<app-hotel-item
|
|
||||||
*ngIf="hotel.hotelName.toLowerCase().includes(search.toLowerCase())"
|
|
||||||
[hotel]="hotel"
|
|
||||||
></app-hotel-item>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { HotelListComponent } from './hotel-list.component';
|
|
||||||
|
|
||||||
describe('HotelListComponent', () => {
|
|
||||||
let component: HotelListComponent;
|
|
||||||
let fixture: ComponentFixture<HotelListComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [HotelListComponent],
|
|
||||||
}).compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(HotelListComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,94 +0,0 @@
|
||||||
import { CommonModule, NgFor, NgIf, UpperCasePipe } from '@angular/common';
|
|
||||||
import { Component, inject, ViewChild, ViewContainerRef } from '@angular/core';
|
|
||||||
import { TextPipe } from '../../text.pipe';
|
|
||||||
import { SearchComponent } from '../Search/search.component';
|
|
||||||
import { HotelService } from '../Parent/services/hotel.service';
|
|
||||||
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';
|
|
||||||
|
|
||||||
interface User {
|
|
||||||
name: string;
|
|
||||||
age: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-hotel-list',
|
|
||||||
standalone: true,
|
|
||||||
imports: [
|
|
||||||
UpperCasePipe,
|
|
||||||
TextPipe,
|
|
||||||
SearchComponent,
|
|
||||||
HotelItem,
|
|
||||||
NgFor,
|
|
||||||
NgIf,
|
|
||||||
RouterLink,
|
|
||||||
],
|
|
||||||
templateUrl: './hotel-list.component.html',
|
|
||||||
styleUrl: './hotel-list.component.css',
|
|
||||||
})
|
|
||||||
export class HotelListComponent {
|
|
||||||
public search: string = '';
|
|
||||||
public hotelService: HotelService = inject(HotelService);
|
|
||||||
public response: any = null;
|
|
||||||
public hotels: Array<Hotel> = [{} as Hotel];
|
|
||||||
|
|
||||||
@ViewChild('test2', { read: ViewContainerRef })
|
|
||||||
private test2ViewContainerRef!: ViewContainerRef;
|
|
||||||
|
|
||||||
constructor(private http: HttpClient) { }
|
|
||||||
|
|
||||||
async loadTest2() {
|
|
||||||
for (let i = 0; i < 1; i++) {
|
|
||||||
const { Test2Component } = await import('../test2/test2.component');
|
|
||||||
|
|
||||||
this.test2ViewContainerRef.createComponent(Test2Component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.http.get<Array<Hotel>>('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<User> = 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<number | string> = 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)),
|
|
||||||
// );
|
|
||||||
}
|
|
29
src/app/hotel/create-hotel.component.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import {Component, inject} from "@angular/core";
|
||||||
|
import {Hotel} from "./hotel";
|
||||||
|
import {HotelService} from "../service/hotel.service";
|
||||||
|
import {EditHotelComponent} from "./edit-hotel.component";
|
||||||
|
import {Router} from "@angular/router";
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-create-hotel",
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
EditHotelComponent
|
||||||
|
],
|
||||||
|
template: `
|
||||||
|
<app-edit-hotel (updateHotel)="create($event)"></app-edit-hotel>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class CreateHotelComponent {
|
||||||
|
|
||||||
|
hotelService: HotelService = inject(HotelService);
|
||||||
|
|
||||||
|
router: Router = inject(Router);
|
||||||
|
|
||||||
|
create(hotel: Hotel) {
|
||||||
|
this.hotelService.createHotel(hotel).subscribe();
|
||||||
|
|
||||||
|
this.router.navigate(['/hotels']);
|
||||||
|
}
|
||||||
|
}
|
27
src/app/hotel/delete-hotel.component.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import {Component, inject, Input} from "@angular/core";
|
||||||
|
import {HotelService} from "../service/hotel.service";
|
||||||
|
import {Router} from "@angular/router";
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-delete-hotel',
|
||||||
|
standalone: true,
|
||||||
|
template: `
|
||||||
|
<button (click)="delete()">Delete Hotel</button>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class DeleteHotelComponent {
|
||||||
|
@Input()
|
||||||
|
id: number = 0;
|
||||||
|
|
||||||
|
hotelService: HotelService = inject(HotelService);
|
||||||
|
|
||||||
|
router: Router = inject(Router);
|
||||||
|
|
||||||
|
delete() {
|
||||||
|
if (confirm('Are you sure?')) {
|
||||||
|
this.hotelService.deleteById(this.id).subscribe()
|
||||||
|
this.router.navigate(['/hotels']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
118
src/app/hotel/edit-hotel.component.ts
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
||||||
|
import {
|
||||||
|
AbstractControl,
|
||||||
|
FormArray,
|
||||||
|
FormControl,
|
||||||
|
FormGroup,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
Validators
|
||||||
|
} from "@angular/forms";
|
||||||
|
import {Hotel} from "./hotel";
|
||||||
|
import {RouterLink} from "@angular/router";
|
||||||
|
import {NgForOf} from "@angular/common";
|
||||||
|
import {TagComponent} from "./tag.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-edit-hotel',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
RouterLink,
|
||||||
|
NgForOf,
|
||||||
|
TagComponent
|
||||||
|
],
|
||||||
|
template: `
|
||||||
|
<ng-form [formGroup]="form" (ngSubmit)="submit()">
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<br>
|
||||||
|
<input id="name" type="text" formControlName="name">
|
||||||
|
<br>
|
||||||
|
<label for="description">Description</label>
|
||||||
|
<br>
|
||||||
|
<input id="description" type="text" formControlName="description">
|
||||||
|
<br>
|
||||||
|
<label for="price">Price</label>
|
||||||
|
<br>
|
||||||
|
<input id="price" type="number" formControlName="price">
|
||||||
|
<br>
|
||||||
|
<label for="rating">Rating</label>
|
||||||
|
<br>
|
||||||
|
<input id="rating" type="number" formControlName="rating" min="0" max="5">
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<button (click)="addTag()">+</button>
|
||||||
|
<div formArrayName="tags">
|
||||||
|
<div *ngFor="let tag of getTags().controls; let i = index">
|
||||||
|
<input type="text" placeholder="tag" [formControlName]="i">
|
||||||
|
<button (click)="deleteTag(tag)">-</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<button type="submit" (click)="submit()">Submit</button>
|
||||||
|
</ng-form>
|
||||||
|
|
||||||
|
<button routerLink="/hotels">back</button>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class EditHotelComponent implements OnInit {
|
||||||
|
@Input()
|
||||||
|
hotel: Hotel | null = null;
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
updateHotel: EventEmitter<Hotel> = new EventEmitter
|
||||||
|
|
||||||
|
form!: FormGroup
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const tags = [];
|
||||||
|
|
||||||
|
for (const tag of this.hotel?.tags ?? []) {
|
||||||
|
tags.push(new FormControl(tag, [Validators.required]));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(tags)
|
||||||
|
this.form = new FormGroup({
|
||||||
|
name: new FormControl(this.hotel?.hotelName, [Validators.required]),
|
||||||
|
description: new FormControl(this.hotel?.description, [Validators.required]),
|
||||||
|
price: new FormControl(this.hotel?.price, [Validators.required]),
|
||||||
|
rating: new FormControl(this.hotel?.rating, [Validators.required, Validators.min(0), Validators.max(5)]),
|
||||||
|
tags: new FormArray(tags),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
if (!this.form.valid) {
|
||||||
|
console.error('Form invalid');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const hotel: Hotel = {
|
||||||
|
imageUrl: this.hotel?.imageUrl ?? "",
|
||||||
|
hotelName: this.form.value.name,
|
||||||
|
description: this.form.value.description,
|
||||||
|
price: this.form.value.price,
|
||||||
|
rating: this.form.value.rating,
|
||||||
|
id: this.hotel?.id ?? 0,
|
||||||
|
tags: this.form.value.tags ?? []
|
||||||
|
};
|
||||||
|
console.log(hotel)
|
||||||
|
this.updateHotel.emit(hotel)
|
||||||
|
|
||||||
|
this.hotel = hotel;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTags() {
|
||||||
|
return this.form.controls['tags'] as FormArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
addTag() {
|
||||||
|
this.getTags().push(new FormControl('', [Validators.required]));
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteTag(tagElement: AbstractControl) {
|
||||||
|
this.getTags().removeAt(this.getTags().controls.indexOf(tagElement));
|
||||||
|
}
|
||||||
|
}
|
61
src/app/hotel/hotel.component.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import {Component, inject, OnInit} from "@angular/core";
|
||||||
|
import {Hotel} from "./hotel"
|
||||||
|
import {CurrencyPipe, NgOptimizedImage} from "@angular/common";
|
||||||
|
import {Lang} from "../idek/lang";
|
||||||
|
import {StarComponent} from "../star/star.component";
|
||||||
|
import {ActivatedRoute} from "@angular/router";
|
||||||
|
import {HotelService} from "../service/hotel.service";
|
||||||
|
import {catchError, EMPTY} from "rxjs";
|
||||||
|
import {EditHotelComponent} from "./edit-hotel.component";
|
||||||
|
import {DeleteHotelComponent} from "./delete-hotel.component";
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'app-hotel',
|
||||||
|
imports: [
|
||||||
|
CurrencyPipe,
|
||||||
|
StarComponent,
|
||||||
|
NgOptimizedImage,
|
||||||
|
EditHotelComponent,
|
||||||
|
DeleteHotelComponent
|
||||||
|
],
|
||||||
|
template: `
|
||||||
|
<div style="border: white 2px; border-radius: 2px">
|
||||||
|
@if (hotel && !alert) {
|
||||||
|
<app-edit-hotel [hotel]="hotel" (updateHotel)="update($event)" />
|
||||||
|
<app-delete-hotel [id]="hotel.id" />
|
||||||
|
} @else if(alert) {
|
||||||
|
<h2>{{alert}}</h2>
|
||||||
|
} @else {
|
||||||
|
<h2>loading</h2>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class HotelComponent implements OnInit {
|
||||||
|
protected currency: Lang = {name: 'de', code: 'de-DE', currency: 'EUR'}
|
||||||
|
|
||||||
|
protected hotel!: Hotel;
|
||||||
|
|
||||||
|
protected alert: string|undefined
|
||||||
|
|
||||||
|
private route: ActivatedRoute = inject(ActivatedRoute);
|
||||||
|
|
||||||
|
private hotelService: HotelService = inject(HotelService);
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const hotelId = this.route.snapshot.params['hotelId'];
|
||||||
|
this.hotelService.getHotelById(hotelId)
|
||||||
|
.pipe(catchError((err) => {
|
||||||
|
this.alert = `Hotel could not be retrieved: ${err.message}`
|
||||||
|
return EMPTY;
|
||||||
|
}))
|
||||||
|
.subscribe({next: (hotel: Hotel) => {this.hotel = hotel}})
|
||||||
|
}
|
||||||
|
|
||||||
|
update(hotel: Hotel): void {
|
||||||
|
this.hotelService.updateHotelById(hotel).subscribe()
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,5 @@ export interface Hotel {
|
||||||
price: number;
|
price: number;
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
rating: number;
|
rating: number;
|
||||||
tags: Array<string>;
|
tags: string[];
|
||||||
email?: string;
|
|
||||||
phone?: string;
|
|
||||||
}
|
}
|
49
src/app/hotel/hotels.component.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import {Component, inject} from "@angular/core";
|
||||||
|
import {HotelComponent} from "./hotel.component";
|
||||||
|
import {Hotel} from "./hotel";
|
||||||
|
import {FormsModule} from "@angular/forms";
|
||||||
|
import {Lang} from "../idek/lang";
|
||||||
|
import {HotelService} from "../service/hotel.service";
|
||||||
|
import {filter, Observable, toArray} from "rxjs";
|
||||||
|
import {AsyncPipe} from "@angular/common";
|
||||||
|
import {CurrencyComponent} from "../currency/currency.component";
|
||||||
|
import {RouterLink} from "@angular/router";
|
||||||
|
import {StarComponent} from "../star/star.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
template: `
|
||||||
|
<app-currency (currency)="currency = $event"></app-currency>
|
||||||
|
<form>
|
||||||
|
<input name="search" [ngModel]="search" (ngModelChange)="searchEvent($event)">
|
||||||
|
</form>
|
||||||
|
<p routerLink="/hotels/new">Create Hotel</p>
|
||||||
|
@for (hotel of (matchingHotels | async); track hotel.id) {
|
||||||
|
<div>{{hotel.hotelName}}</div>
|
||||||
|
<img src="{{hotel.imageUrl}}" alt="{{hotel.hotelName}}" height="64" width="64">
|
||||||
|
<br>
|
||||||
|
<button routerLink="{{hotel.id}}">Details</button>
|
||||||
|
<hr>
|
||||||
|
} @empty {
|
||||||
|
<h1>no matching results for {{ search }}</h1>
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
imports: [FormsModule, HotelComponent, AsyncPipe, CurrencyComponent, RouterLink, StarComponent],
|
||||||
|
providers: [HotelService],
|
||||||
|
selector: 'app-hotels'
|
||||||
|
})
|
||||||
|
export class HotelsComponent {
|
||||||
|
|
||||||
|
public currency: Lang = {name: 'de', code: 'de-DE', currency: 'EUR'};
|
||||||
|
|
||||||
|
public search: string = '';
|
||||||
|
|
||||||
|
private hotelService: HotelService= inject(HotelService);
|
||||||
|
|
||||||
|
public matchingHotels: Observable<Hotel[]> = this.hotelService.getHotels();
|
||||||
|
|
||||||
|
public searchEvent(input: string) {
|
||||||
|
this.search = input.toLowerCase();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
33
src/app/hotel/tag.component.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import {Component, EventEmitter, Input, Output} from "@angular/core";
|
||||||
|
import {AbstractControl, ControlContainer, FormGroupDirective, ReactiveFormsModule} from "@angular/forms";
|
||||||
|
import {JsonPipe} from "@angular/common";
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'app-tag',
|
||||||
|
imports: [
|
||||||
|
ReactiveFormsModule,
|
||||||
|
JsonPipe
|
||||||
|
],
|
||||||
|
viewProviders: [
|
||||||
|
{
|
||||||
|
provide: ControlContainer,
|
||||||
|
useExisting: FormGroupDirective
|
||||||
|
}
|
||||||
|
],
|
||||||
|
template: `
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class TagComponent {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
tagElement!: AbstractControl;
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
deleteEvent: EventEmitter<AbstractControl> = new EventEmitter();
|
||||||
|
|
||||||
|
delete() {
|
||||||
|
this.deleteEvent.emit(this.tagElement);
|
||||||
|
}
|
||||||
|
}
|
53
src/app/idek/idk.component.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import {Component} from "@angular/core";
|
||||||
|
import {CurrencyPipe} from "@angular/common";
|
||||||
|
import {FormsModule} from "@angular/forms";
|
||||||
|
import {Lang} from "./lang";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-idk',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CurrencyPipe, FormsModule],
|
||||||
|
template: `
|
||||||
|
<select [ngModel]="currency" (ngModelChange)="setCurrency($event)">
|
||||||
|
@for (currency of currencies; track null) {
|
||||||
|
<option value="{{currency.name}}">{{currency.name}}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
{{ 234 | currency : currency.currency : 'symbol' : '2.2-2' : currency.code }}
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class IdkComponent {
|
||||||
|
public currency: Lang = {name: 'de', code: 'de-DE', currency: 'EUR'};
|
||||||
|
|
||||||
|
public currencies: Lang[] = [
|
||||||
|
{
|
||||||
|
name: 'de',
|
||||||
|
code: 'de-DE',
|
||||||
|
currency: 'EUR'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'en',
|
||||||
|
code: 'en-US',
|
||||||
|
currency: 'USD'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'jap',
|
||||||
|
code: 'ja-JP',
|
||||||
|
currency: 'JPY'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cn',
|
||||||
|
code: 'cn-CN',
|
||||||
|
currency: 'CNY'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
public setCurrency(currencyInput: string): void {
|
||||||
|
for (const currency of this.currencies) {
|
||||||
|
if (currency.name === currencyInput) {
|
||||||
|
this.currency = currency;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
src/app/idek/lang.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export interface Lang {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
currency: string;
|
||||||
|
}
|
11
src/app/idek/test.pipe.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import {Pipe, PipeTransform} from "@angular/core";
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'idk',
|
||||||
|
standalone: true,
|
||||||
|
})
|
||||||
|
export class TestPipe implements PipeTransform {
|
||||||
|
transform(value: string): string {
|
||||||
|
return value.toLowerCase().replaceAll('l', 'p');
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,120 +0,0 @@
|
||||||
<p>Create Hotel</p>
|
|
||||||
|
|
||||||
<form [formGroup]="hotelForm">
|
|
||||||
<div class="text-red-500 bg-white m-3" *ngIf="errorMessages['form']">
|
|
||||||
{{ errorMessages["form"] }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label for="name">Name</label>
|
|
||||||
<div class="text-red-500" *ngIf="errorMessages['name']">
|
|
||||||
{{ errorMessages["name"] }}
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="border-red-500"
|
|
||||||
[class.border-8]="
|
|
||||||
hotelForm.get('name')?.invalid && hotelForm.get('name')?.touched
|
|
||||||
"
|
|
||||||
id="name"
|
|
||||||
formControlName="name"
|
|
||||||
/>
|
|
||||||
<label for="description">Description</label>
|
|
||||||
<div class="text-red-500" *ngIf="errorMessages['description']">
|
|
||||||
{{ errorMessages["description"] }}
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="border-red-500"
|
|
||||||
[class.border-8]="
|
|
||||||
hotelForm.get('description')?.invalid &&
|
|
||||||
hotelForm.get('description')?.touched
|
|
||||||
"
|
|
||||||
id="description"
|
|
||||||
formControlName="description"
|
|
||||||
/>
|
|
||||||
<label for="imageUrl">Image</label>
|
|
||||||
<div class="text-red-500" *ngIf="errorMessages['imageUrl']">
|
|
||||||
{{ errorMessages["imageUrl"] }}
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="url"
|
|
||||||
class="border-red-500"
|
|
||||||
[class.border-8]="
|
|
||||||
hotelForm.get('imageUel')?.invalid && hotelForm.get('imageUrl')?.touched
|
|
||||||
"
|
|
||||||
id="imageUrl"
|
|
||||||
formControlName="imageUrl"
|
|
||||||
/>
|
|
||||||
<label for="price">Price</label>
|
|
||||||
<div class="text-red-500" *ngIf="errorMessages['price']">
|
|
||||||
{{ errorMessages["price"] }}
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
class="border-red-500"
|
|
||||||
[class.border-8]="hotelForm.get('price')?.invalid"
|
|
||||||
id="price"
|
|
||||||
formControlName="price"
|
|
||||||
/>
|
|
||||||
<label for="rating">Rating</label>
|
|
||||||
<div class="text-red-500" *ngIf="errorMessages['rating']">
|
|
||||||
{{ errorMessages["rating"] }}
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="rating"
|
|
||||||
class="border-red-500"
|
|
||||||
[class.border-8]="hotelForm.get('rating')?.invalid"
|
|
||||||
id="rating"
|
|
||||||
formControlName="rating"
|
|
||||||
/>
|
|
||||||
<button class="submit-button" (click)="addTag()">Add Tag</button>
|
|
||||||
@for (tag of getTags().controls; track tag) {
|
|
||||||
<input
|
|
||||||
type="tag"
|
|
||||||
class="border-red-500"
|
|
||||||
[class.border-8]="hotelForm.get('tag')?.invalid"
|
|
||||||
id="tag"
|
|
||||||
formControlName="tag"
|
|
||||||
/>
|
|
||||||
<button (click)="deleteTag(tag)" class="delete-button">delete</button>
|
|
||||||
}
|
|
||||||
|
|
||||||
<input type="radio" value="None" formControlName="contactType" />None
|
|
||||||
<input type="radio" value="Email" formControlName="contactType" />Email
|
|
||||||
<input type="radio" value="SMS" formControlName="contactType" />SMS
|
|
||||||
|
|
||||||
@if (hotelForm.get("contactType")?.value == "Email") {
|
|
||||||
<div class="mt-3">
|
|
||||||
<label for="email">Email</label>
|
|
||||||
<input type="email" formControlName="email" id="email" />
|
|
||||||
<label for="emailConfirmation">Email Confirmation</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
formControlName="emailConfirmation"
|
|
||||||
id="emailConfirmation"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (hotelForm.get("contactType")?.value == "SMS") {
|
|
||||||
<div class="mt-3">
|
|
||||||
<label for="phone">Phone Number</label>
|
|
||||||
<input type="tel" formControlName="phone" id="phone" />
|
|
||||||
<label for="phoneConfirmation">Phone Number Confirmation</label>
|
|
||||||
<input
|
|
||||||
type="tel"
|
|
||||||
formControlName="phoneConfirmation"
|
|
||||||
id="phoneConfirmation"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="submit-button"
|
|
||||||
(click)="submit()"
|
|
||||||
[disabled]="!hotelForm.valid"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
<a routerLink="/" class="delete-button">Cancel</a>
|
|
||||||
</form>
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { NewHotelComponent } from './new-hotel.component';
|
|
||||||
|
|
||||||
describe('NewHotelComponent', () => {
|
|
||||||
let component: NewHotelComponent;
|
|
||||||
let fixture: ComponentFixture<NewHotelComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [NewHotelComponent],
|
|
||||||
}).compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(NewHotelComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,139 +0,0 @@
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { Component } from '@angular/core';
|
|
||||||
import {
|
|
||||||
AbstractControl,
|
|
||||||
FormArray,
|
|
||||||
FormControl,
|
|
||||||
FormGroup,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
Validators,
|
|
||||||
} from '@angular/forms';
|
|
||||||
import { Router, RouterLink } from '@angular/router';
|
|
||||||
import { Hotel } from '../HotelItem/hotel';
|
|
||||||
import { NgFor, NgIf } from '@angular/common';
|
|
||||||
import { CrossValidator } from '../crossValidator.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-new-hotel',
|
|
||||||
standalone: true,
|
|
||||||
imports: [ReactiveFormsModule, NgIf, NgFor, RouterLink],
|
|
||||||
templateUrl: './new-hotel.component.html',
|
|
||||||
styleUrl: './new-hotel.component.css',
|
|
||||||
})
|
|
||||||
export class NewHotelComponent {
|
|
||||||
public hotelForm!: FormGroup;
|
|
||||||
public hotel!: Hotel;
|
|
||||||
errorMessages: Record<string, string> = {};
|
|
||||||
|
|
||||||
private validationErrorMessages: Record<string, string> = {
|
|
||||||
required: 'This field is required',
|
|
||||||
minlength: 'The value is too short',
|
|
||||||
maxlength: 'The value is too long',
|
|
||||||
pattern: 'The value format is incorrect',
|
|
||||||
};
|
|
||||||
|
|
||||||
private fullFormValidationErrorMessages: Record<string, string> = {
|
|
||||||
mismatch: 'The name and description have to be the same',
|
|
||||||
mismatchEmail: 'The Emails must be the same.',
|
|
||||||
mismatchPhone: 'The Phone numbers must be the same.',
|
|
||||||
};
|
|
||||||
|
|
||||||
updateErrorMessages(): void {
|
|
||||||
this.errorMessages = {};
|
|
||||||
|
|
||||||
Object.keys(this.hotelForm.controls).forEach((field) => {
|
|
||||||
const control = this.hotelForm.get(field);
|
|
||||||
|
|
||||||
if (control && control.errors && control.touched) {
|
|
||||||
this.errorMessages[field] = Object.keys(control.errors)
|
|
||||||
.map(
|
|
||||||
(errorKey) =>
|
|
||||||
this.validationErrorMessages[errorKey] ||
|
|
||||||
`Unknown error: ${errorKey}`,
|
|
||||||
)
|
|
||||||
.join(' ');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.hotelForm.errors) {
|
|
||||||
Object.keys(this.hotelForm.errors).forEach((errorKey) => {
|
|
||||||
const errorMessage =
|
|
||||||
this.fullFormValidationErrorMessages[errorKey] ||
|
|
||||||
`Unknown error: ${errorKey}`;
|
|
||||||
this.errorMessages['form'] = errorMessage;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(this.errorMessages);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
const tags = [];
|
|
||||||
|
|
||||||
for (const tag of this.hotel?.tags ?? []) {
|
|
||||||
tags.push(new FormControl(tag, [Validators.required]));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hotelForm = new FormGroup(
|
|
||||||
{
|
|
||||||
name: new FormControl('', Validators.required),
|
|
||||||
description: new FormControl('', Validators.required),
|
|
||||||
price: new FormControl(0, Validators.required),
|
|
||||||
imageUrl: new FormControl('', Validators.required),
|
|
||||||
rating: new FormControl(0, Validators.required),
|
|
||||||
contactType: new FormControl('None'),
|
|
||||||
email: new FormControl(''),
|
|
||||||
emailConfirmation: new FormControl(''),
|
|
||||||
phone: new FormControl(''),
|
|
||||||
phoneConfirmation: new FormControl(''),
|
|
||||||
tags: new FormArray(tags),
|
|
||||||
},
|
|
||||||
{ validators: new CrossValidator().crossValidate() },
|
|
||||||
);
|
|
||||||
|
|
||||||
this.hotelForm.valueChanges.subscribe(() => this.updateErrorMessages());
|
|
||||||
this.hotelForm.valueChanges.subscribe(() => this.formUpdated());
|
|
||||||
}
|
|
||||||
|
|
||||||
formUpdated() {
|
|
||||||
console.log(this.hotelForm.get('contactType')?.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
getTags() {
|
|
||||||
return this.hotelForm.controls['tags'] as FormArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteTag(tagElement: AbstractControl) {
|
|
||||||
this.getTags().removeAt(this.getTags().controls.indexOf(tagElement));
|
|
||||||
}
|
|
||||||
|
|
||||||
addTag() {
|
|
||||||
this.getTags().push(new FormControl('', [Validators.required]));
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public http: HttpClient,
|
|
||||||
public router: Router,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
submit(): void {
|
|
||||||
console.log(this.hotelForm.value);
|
|
||||||
const hotel: Hotel = {
|
|
||||||
id: 0,
|
|
||||||
hotelName: this.hotelForm.get('name')?.value,
|
|
||||||
description: this.hotelForm.get('description')?.value,
|
|
||||||
price: this.hotelForm.get('price')?.value,
|
|
||||||
imageUrl: this.hotelForm.get('imageUrl')?.value,
|
|
||||||
rating: this.hotelForm.get('rating')?.value,
|
|
||||||
tags: this.hotelForm.get('tags')?.value,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.hotelForm.get('contactType')?.value == 'Email') {
|
|
||||||
hotel.email = this.hotelForm.get('email')?.value;
|
|
||||||
} else {
|
|
||||||
hotel.phone = this.hotelForm.get('phone')?.value;
|
|
||||||
}
|
|
||||||
this.http.post('/api/hotels/', hotel).subscribe();
|
|
||||||
this.router.navigate(['/']);
|
|
||||||
}
|
|
||||||
}
|
|
49
src/app/service/HotelData.service.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import {InMemoryDbService} from "angular-in-memory-web-api";
|
||||||
|
import {Hotel} from "../hotel/hotel";
|
||||||
|
|
||||||
|
|
||||||
|
export class HotelDataService implements InMemoryDbService{
|
||||||
|
createDb(): Record<string, Hotel[]> {
|
||||||
|
const hotels: Hotel[] = [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"hotelName": "Buea süßes Leben",
|
||||||
|
"description": "Schöne Aussicht am Meer",
|
||||||
|
"price": 230.5,
|
||||||
|
"imageUrl": "assets/img/1.jpg",
|
||||||
|
"rating": 3.5,
|
||||||
|
"tags": ["Meer", "Berge"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"hotelName": "Marrakesch",
|
||||||
|
"description": "Genießen Sie den Blick auf die Berge",
|
||||||
|
"price": 145.5,
|
||||||
|
"imageUrl": "assets/img/2.jpg",
|
||||||
|
"rating": 5,
|
||||||
|
"tags": ["Meer", "Berge"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"hotelName": "Abuja neuer Palast",
|
||||||
|
"description": "Kompletter Aufenthalt mit Autoservice",
|
||||||
|
"price": 120.12,
|
||||||
|
"imageUrl": "assets/img/3.jpg",
|
||||||
|
"rating": 4,
|
||||||
|
"tags": ["Meer", "Berge"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"hotelName": "OUR Hotel",
|
||||||
|
"description": "Wunderschönes Ambiente für Ihren Aufenthalt",
|
||||||
|
"price": 135.12,
|
||||||
|
"imageUrl": "assets/img/4.jpg",
|
||||||
|
"rating": 2.5,
|
||||||
|
"tags": ["Meer", "Berge"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return { hotels };
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
31
src/app/service/hotel.service.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import {HttpClient} from "@angular/common/http";
|
||||||
|
import {inject, Injectable} from "@angular/core";
|
||||||
|
import {Observable} from "rxjs";
|
||||||
|
import {Hotel} from "../hotel/hotel";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: "root",
|
||||||
|
})
|
||||||
|
export class HotelService {
|
||||||
|
private httpClient: HttpClient = inject(HttpClient);
|
||||||
|
|
||||||
|
public getHotels(): Observable<Hotel[]> {
|
||||||
|
return this.httpClient.get<Hotel[]>('/api/hotels');
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHotelById(id: number): Observable<Hotel> {
|
||||||
|
return this.httpClient.get<Hotel>(`/api/hotels/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateHotelById(hotel: Hotel): Observable<Object> {
|
||||||
|
return this.httpClient.put<Hotel>(`/api/hotels/${hotel.id}`, hotel)
|
||||||
|
}
|
||||||
|
|
||||||
|
public createHotel(hotel: Hotel): Observable<Hotel> {
|
||||||
|
return this.httpClient.post<Hotel>(`/api/hotels`, hotel)
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteById(id: number): Observable<Hotel> {
|
||||||
|
return this.httpClient.delete<Hotel>(`/api/hotels/${id}`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,35 +0,0 @@
|
||||||
<div class="flex items-center">
|
|
||||||
<div *ngFor="let star of getRatingArray()" class="flex flex-inline">
|
|
||||||
<svg
|
|
||||||
[ngClass]="{ hidden: !(star == 0.5) }"
|
|
||||||
class="w-4 h-4 text-yellow-300 me-1"
|
|
||||||
aria-hidden="true"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 22 20"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M13 4.024v-.005c0-.053.002-.353-.217-.632a1.01 1.01 0 0 0-1.176-.315c-.192.076-.315.193-.35.225c-.052.05-.094.1-.122.134a4 4 0 0 0-.31.457c-.207.343-.484.84-.773 1.375a169 169 0 0 0-1.606 3.074h-.002l-4.599.367c-1.775.14-2.495 2.339-1.143 3.488L6.17 15.14l-1.06 4.406c-.412 1.72 1.472 3.078 2.992 2.157l3.94-2.388c.592-.359.958-.996.958-1.692v-13.6Zm-2.002 0v.025z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<svg
|
|
||||||
[ngClass]="{ hidden: !(star == 1) }"
|
|
||||||
class="w-4 h-4 text-yellow-300 me-1"
|
|
||||||
aria-hidden="true"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 22 20"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M20.924 7.625a1.523 1.523 0 0 0-1.238-1.044l-5.051-.734-2.259-4.577a1.534 1.534 0 0 0-2.752 0L7.365 5.847l-5.051.734A1.535 1.535 0 0 0 1.463 9.2l3.656 3.563-.863 5.031a1.532 1.532 0 0 0 2.226 1.616L11 17.033l4.518 2.375a1.534 1.534 0 0 0 2.226-1.617l-.863-5.03L20.537 9.2a1.523 1.523 0 0 0 .387-1.575Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="ms-1 text-sm font-medium text-gray-500 dark:text-gray-400">
|
|
||||||
{{ rating }}
|
|
||||||
</p>
|
|
||||||
<p class="ms-1 text-sm font-medium text-gray-500 dark:text-gray-400">
|
|
||||||
out of
|
|
||||||
</p>
|
|
||||||
<p class="ms-1 text-sm font-medium text-gray-500 dark:text-gray-400">5</p>
|
|
||||||
</div>
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { StarRatingComponent } from './star-rating.component';
|
|
||||||
|
|
||||||
describe('StarRatingComponent', () => {
|
|
||||||
let component: StarRatingComponent;
|
|
||||||
let fixture: ComponentFixture<StarRatingComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [StarRatingComponent],
|
|
||||||
}).compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(StarRatingComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { NgClass, NgFor } from '@angular/common';
|
|
||||||
import { Component, Input } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-star-rating',
|
|
||||||
standalone: true,
|
|
||||||
imports: [NgClass, NgFor],
|
|
||||||
templateUrl: './star-rating.component.html',
|
|
||||||
styleUrl: './star-rating.component.css',
|
|
||||||
})
|
|
||||||
export class StarRatingComponent {
|
|
||||||
@Input() public rating: number = 0;
|
|
||||||
|
|
||||||
public getRatingArray(): number[] {
|
|
||||||
let stars = [];
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
if (i < this.rating) {
|
|
||||||
if (i + 0.5 == this.rating) {
|
|
||||||
stars.push(0.5);
|
|
||||||
} else {
|
|
||||||
stars.push(1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stars.push(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stars;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +1,18 @@
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { Test2Component } from './test2.component';
|
import { StarComponent } from './star.component';
|
||||||
|
|
||||||
describe('Test2Component', () => {
|
describe('StarComponent', () => {
|
||||||
let component: Test2Component;
|
let component: StarComponent;
|
||||||
let fixture: ComponentFixture<Test2Component>;
|
let fixture: ComponentFixture<StarComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [Test2Component]
|
imports: [StarComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(Test2Component);
|
fixture = TestBed.createComponent(StarComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
28
src/app/star/star.component.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import {Component, Input} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-star',
|
||||||
|
standalone: true,
|
||||||
|
imports: [],
|
||||||
|
template: `
|
||||||
|
@for (_ of getList(); track null) {
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" color="yellow"><path fill="currentColor" d="m5.825 21l1.625-7.025L2 9.25l7.2-.625L12 2l2.8 6.625l7.2.625l-5.45 4.725L18.175 21L12 17.275z"/></svg>
|
||||||
|
}
|
||||||
|
@if (rating % 1 >= 0.5) {
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" color="yellow"><path fill="currentColor" d="M12 2L9.19 8.62L2 9.24l5.45 4.73L5.82 21L12 17.27z"/></svg>
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class StarComponent {
|
||||||
|
@Input()
|
||||||
|
public rating: number = 0;
|
||||||
|
|
||||||
|
public getList(): null[] {
|
||||||
|
let rating = this.rating;
|
||||||
|
if (rating % 1 !== 0) {
|
||||||
|
rating = this.rating - 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array(rating).fill(null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
<p>test works!</p>
|
|
||||||
|
|
||||||
<form [formGroup]="loginForm">
|
|
||||||
<label for="username">Username</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="username"
|
|
||||||
autocomplete="username"
|
|
||||||
formControlName="username"
|
|
||||||
/>
|
|
||||||
<label for="password">Password</label>
|
|
||||||
<input type="password" id="password" formControlName="password" />
|
|
||||||
<button (click)="submit()" type="submit">Login</button>
|
|
||||||
<button type="reset" (click)="reset()">Reset</button>
|
|
||||||
</form>
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { TestComponent } from './test.component';
|
|
||||||
|
|
||||||
describe('TestComponent', () => {
|
|
||||||
let component: TestComponent;
|
|
||||||
let fixture: ComponentFixture<TestComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [TestComponent],
|
|
||||||
}).compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(TestComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { Component } from '@angular/core';
|
|
||||||
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-test',
|
|
||||||
standalone: true,
|
|
||||||
imports: [ReactiveFormsModule],
|
|
||||||
templateUrl: './test.component.html',
|
|
||||||
styleUrl: './test.component.css',
|
|
||||||
})
|
|
||||||
export class TestComponent {
|
|
||||||
public loginForm!: FormGroup;
|
|
||||||
|
|
||||||
public ngOnInit(): void {
|
|
||||||
this.loginForm = this.setUpLoginForm();
|
|
||||||
|
|
||||||
console.log(this.loginForm);
|
|
||||||
}
|
|
||||||
|
|
||||||
setUpLoginForm(): FormGroup {
|
|
||||||
return new FormGroup({
|
|
||||||
username: new FormControl('Jan'),
|
|
||||||
password: new FormControl(''),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
reset(): void {
|
|
||||||
this.loginForm = this.setUpLoginForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
submit(): void {
|
|
||||||
console.log(this.loginForm.value);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
<iframe
|
|
||||||
src="https://funhtml5games.com?embed=flappy"
|
|
||||||
style="width: 800px; height: 520px; border: none"
|
|
||||||
frameborder="0"
|
|
||||||
scrolling="no"
|
|
||||||
></iframe>
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-test2',
|
|
||||||
imports: [],
|
|
||||||
templateUrl: './test2.component.html',
|
|
||||||
styleUrl: './test2.component.css'
|
|
||||||
})
|
|
||||||
export class Test2Component {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { Pipe, PipeTransform } from '@angular/core';
|
|
||||||
|
|
||||||
@Pipe({
|
|
||||||
name: 'jkcurrency',
|
|
||||||
standalone: true,
|
|
||||||
})
|
|
||||||
export class CurrencyPipe implements PipeTransform {
|
|
||||||
transform(value: string): string {
|
|
||||||
return value.replaceAll('L', 'P');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +1,16 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8">
|
||||||
<title>HotelManager</title>
|
<title>HotelManager</title>
|
||||||
<base href="/" />
|
<base href="/">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.min.js" integrity="sha512-ykZ1QQr0Jy/4ZkvKuqWn4iF3lqPZyij9iRv6sGqLRdTPkY69YX6+7wvVGmsdBbiIfN/8OdsI7HABjvEok6ZopQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
<link rel="stylesheet" href="./styles.css" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap-grid.min.css" integrity="sha512-i1b/nzkVo97VN5WbEtaPebBG8REvjWeqNclJ6AItj7msdVcaveKrlIIByDpvjk5nwHjXkIqGZscVxOrTb9tsMA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
</head>
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
<body class="bg-pink-600 p-32">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/material-design-icons/4.0.0/iconfont/material-icons.min.css" referrerpolicy="no-referrer" />
|
||||||
<app-root></app-root>
|
</head>
|
||||||
</body>
|
<body class="bg-pink-600">
|
||||||
|
<app-root></app-root>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -2,6 +2,5 @@ import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
import { appConfig } from './app/app.config';
|
import { appConfig } from './app/app.config';
|
||||||
import { AppComponent } from './app/app.component';
|
import { AppComponent } from './app/app.component';
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
bootstrapApplication(AppComponent, appConfig)
|
||||||
console.error(err),
|
.catch((err) => console.error(err));
|
||||||
);
|
|
||||||
|
|
|
@ -2,23 +2,3 @@
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
.button {
|
|
||||||
@apply border-black rounded px-3 py-1 border-[3px];
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-field {
|
|
||||||
@apply border-red-500 text-3xl rounded-full bg-gray-500 font-bold p-3 m-3 border-8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-button {
|
|
||||||
@apply border-black rounded bg-blue-500 border-[30px] m-3 text-3xl font-bold p-3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-button {
|
|
||||||
@apply border-black rounded-full bg-green-500 border-[30px] m-3 text-3xl font-bold p-3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-button {
|
|
||||||
@apply border-black rounded bg-red-500 border-[30px] m-3 text-3xl font-bold p-3;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { Pipe, PipeTransform } from '@angular/core';
|
|
||||||
|
|
||||||
@Pipe({
|
|
||||||
name: 'text',
|
|
||||||
standalone: true,
|
|
||||||
})
|
|
||||||
export class TextPipe implements PipeTransform {
|
|
||||||
transform(value: string): string {
|
|
||||||
return value.replaceAll('L', 'P');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +1,10 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
|
||||||
const colors = require("tailwindcss/colors");
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ["./src/**/*.{html,ts}"],
|
content: [
|
||||||
|
"./src/**/*.{html,ts}",
|
||||||
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {},
|
||||||
colors: {
|
|
||||||
...colors,
|
|
||||||
},
|
|
||||||
keyframes: {
|
|
||||||
shake: {
|
|
||||||
"0%, 100%": { transform: "translateX(0)" },
|
|
||||||
"25%": { transform: "translateX(-30px)" },
|
|
||||||
"75%": { transform: "translateX(30px)" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
shake: "shake 10s ease-in-out infinite",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
};
|
}
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
"outDir": "./out-tsc/app",
|
"outDir": "./out-tsc/app",
|
||||||
"types": []
|
"types": []
|
||||||
},
|
},
|
||||||
"files": ["src/main.ts"],
|
"files": [
|
||||||
"include": ["src/**/*.d.ts"]
|
"src/main.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,10 @@
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"module": "ES2022",
|
"module": "ES2022",
|
||||||
"lib": ["ES2022", "dom"]
|
"lib": [
|
||||||
|
"ES2022",
|
||||||
|
"dom"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
"enableI18nLegacyMessageIdFormat": false,
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
|
|
@ -4,7 +4,12 @@
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./out-tsc/spec",
|
"outDir": "./out-tsc/spec",
|
||||||
"types": ["jasmine"]
|
"types": [
|
||||||
|
"jasmine"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
|
"include": [
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|