Hydra EntryPoint erstellt. vocab.jsonld angepasst
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: '<h1>Hello World from Angular!</h1>',
|
||||
styles: []
|
||||
})
|
||||
export class AppComponent { }
|
||||
15
client-web/src/app/app.config.ts
Normal file
15
client-web/src/app/app.config.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
|
||||
import {provideRouter, RouteReuseStrategy} from '@angular/router';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
import {provideHttpClient} from '@angular/common/http';
|
||||
import {CustomRouteReuseStrategy} from './route-reuse.strategy';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
provideRouter(routes),
|
||||
provideHttpClient(),
|
||||
{ provide: RouteReuseStrategy, useClass: CustomRouteReuseStrategy }
|
||||
]
|
||||
};
|
||||
71
client-web/src/app/app.css
Normal file
71
client-web/src/app/app.css
Normal file
@@ -0,0 +1,71 @@
|
||||
.baustein-list {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.baustein-item {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.baustein-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.baustein-name {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.baustein-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
padding-right: 80rem;
|
||||
}
|
||||
|
||||
.btn-control {
|
||||
border: 1px solid #ccc;
|
||||
background-color: #f9f9f9;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-control:hover:not(:disabled) {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.btn-control:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.baustein-name.notIncluded {
|
||||
color: darkgray;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
table {
|
||||
table-layout: auto;
|
||||
border-collapse: collapse;
|
||||
width: 500px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
font-size: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
tr:hover {background-color: #ddd;}
|
||||
|
||||
th {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
text-align: left;
|
||||
background-color: #2244AA;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
6
client-web/src/app/app.html
Normal file
6
client-web/src/app/app.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<main class="main">
|
||||
<div class="content" style="text-align:left; margin-top:50px;">
|
||||
<app-navbar></app-navbar>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</main>
|
||||
@@ -1,11 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [BrowserModule],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
11
client-web/src/app/app.routes.ts
Normal file
11
client-web/src/app/app.routes.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { App} from './app';
|
||||
import { RisikoobjektView} from './risikoobjektView/risikoobjektView';
|
||||
import {Produktbaum} from './produktbaum/produktbaum';
|
||||
|
||||
export const routes: Routes = [
|
||||
// { path: '', redirectTo: '/produktbaum', pathMatch: 'full' },
|
||||
{ path: '', component: Produktbaum },
|
||||
// { path: 'produktbaum', component: Produktbaum },
|
||||
{ path: 'risikoobjekte', component: RisikoobjektView },
|
||||
];
|
||||
23
client-web/src/app/app.spec.ts
Normal file
23
client-web/src/app/app.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { App } from './app';
|
||||
|
||||
describe('App', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [App],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(App);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, OMDSAngularWebClient');
|
||||
});
|
||||
});
|
||||
23
client-web/src/app/app.ts
Normal file
23
client-web/src/app/app.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import {Component, inject, Injectable, signal} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {QueryEngine} from '@comunica/query-sparql';
|
||||
import {DataFactory} from 'rdf-data-factory';
|
||||
import {Store} from 'n3';
|
||||
import jsonld from 'jsonld';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {Navbar} from './navbar/navbar';
|
||||
import {RouterOutlet} from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [CommonModule, FormsModule, Navbar, RouterOutlet],
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.css'
|
||||
})
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class App {
|
||||
protected readonly title = signal('OMDSAngularWebClient');
|
||||
|
||||
|
||||
}
|
||||
99
client-web/src/app/navbar/navbar.css
Normal file
99
client-web/src/app/navbar/navbar.css
Normal file
@@ -0,0 +1,99 @@
|
||||
/* Navbar Grund-Container */
|
||||
.navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: #ffffff;
|
||||
padding: 10px 40px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||||
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
border-bottom: 3px solid #007bff; /* Passend zum Blau der Buttons */
|
||||
}
|
||||
|
||||
/* Logo Bereich */
|
||||
.navbar-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar-logo img {
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
/* Falls das Logo bei 98px zu groß wirkt, kannst du hier max-height nutzen: */
|
||||
max-height: 70px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* Navigations-Links */
|
||||
.navbar-links {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.navbar-links li {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.navbar-links a {
|
||||
text-decoration: none;
|
||||
color: #495057;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
padding: 10px 15px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Hover-Effekt */
|
||||
.navbar-links a:hover {
|
||||
color: #007bff;
|
||||
background-color: #f0f7ff;
|
||||
}
|
||||
|
||||
/* Aktiver Link (Angular RouterLinkActive Style) */
|
||||
/* Falls du [routerLinkActive]="'active'" im HTML nutzt: */
|
||||
.navbar-links a.active {
|
||||
color: #007bff;
|
||||
background-color: #e7f1ff;
|
||||
}
|
||||
|
||||
/* Kleiner Indikator-Strich unter dem Text beim Hover */
|
||||
.navbar-links a::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background: #007bff;
|
||||
transition: all 0.3s ease;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.navbar-links a:hover::after {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
/* Mobile Optimierung */
|
||||
@media (max-width: 768px) {
|
||||
.navbar {
|
||||
flex-direction: column;
|
||||
padding: 15px;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.navbar-links {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.navbar-logo img {
|
||||
max-height: 50px;
|
||||
}
|
||||
}
|
||||
10
client-web/src/app/navbar/navbar.html
Normal file
10
client-web/src/app/navbar/navbar.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<nav class="navbar">
|
||||
<div class="navbar-logo">
|
||||
<img ngSrc="assets/logo_kapdion.gif" alt="Kapdion Logo" height="98" width="180" priority fetchpriority="high" />
|
||||
</div>
|
||||
<ul class="navbar-links">
|
||||
<li><a routerLink="/">Home</a></li>
|
||||
<li><a routerLink="/risikoobjekte">Risikoobjekte</a></li>
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
23
client-web/src/app/navbar/navbar.spec.ts
Normal file
23
client-web/src/app/navbar/navbar.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Navbar } from './navbar';
|
||||
|
||||
describe('Navbar', () => {
|
||||
let component: Navbar;
|
||||
let fixture: ComponentFixture<Navbar>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [Navbar]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(Navbar);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
16
client-web/src/app/navbar/navbar.ts
Normal file
16
client-web/src/app/navbar/navbar.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {RouterLink} from '@angular/router';
|
||||
import {NgOptimizedImage} from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-navbar',
|
||||
imports: [
|
||||
RouterLink,
|
||||
NgOptimizedImage
|
||||
],
|
||||
templateUrl: './navbar.html',
|
||||
styleUrl: './navbar.css',
|
||||
})
|
||||
export class Navbar {
|
||||
|
||||
}
|
||||
184
client-web/src/app/produktbaum/produktbaum.css
Normal file
184
client-web/src/app/produktbaum/produktbaum.css
Normal file
@@ -0,0 +1,184 @@
|
||||
/* Grundlayout & Schrift */
|
||||
.main {
|
||||
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 900px;
|
||||
margin: 50px auto;
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* Titel & Buttons oben */
|
||||
h1, h2 {
|
||||
color: #2c3e50;
|
||||
border-bottom: 2px solid #eee;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
button:not(.btn-control) {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 18px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 20px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
button:not(.btn-control):hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: #ccc !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Der Produktbaum (Hierarchie) */
|
||||
.baustein-list {
|
||||
list-style: none;
|
||||
padding-left: 25px;
|
||||
border-left: 1px dashed #ced4da;
|
||||
}
|
||||
|
||||
.baustein-item {
|
||||
margin: 10px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Verbindungslinien-Effekt */
|
||||
.baustein-item::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: -25px;
|
||||
width: 20px;
|
||||
border-top: 1px dashed #ced4da;
|
||||
}
|
||||
|
||||
.baustein-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
border: 1px solid #e9ecef;
|
||||
padding: 10px 15px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.baustein-row:hover {
|
||||
border-color: #adb5bd;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
/* Baustein Status */
|
||||
.baustein-name {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.notIncluded {
|
||||
color: #adb5bd !important;
|
||||
font-style: italic;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* +/- Buttons */
|
||||
.baustein-buttons {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.btn-control {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dee2e6;
|
||||
background: #f8f9fa;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn-control:hover:not(:disabled) {
|
||||
background: #e2e6ea;
|
||||
}
|
||||
|
||||
/* Details, Tabellen & Inputs */
|
||||
.extraInfo-container {
|
||||
background: #fdfdfe;
|
||||
border: 1px solid #eef2f7;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
font-size: 0.85rem;
|
||||
color: #6c757d;
|
||||
text-transform: uppercase;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 6px 0;
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="number"] {
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 3px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Fehlermeldungen */
|
||||
.meldungen-container {
|
||||
color: #dc3545;
|
||||
background-color: #fff5f5;
|
||||
padding: 8px;
|
||||
border-left: 4px solid #dc3545;
|
||||
margin-top: 10px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Risikoobjekte */
|
||||
.risikoobjekt-container {
|
||||
margin-top: 15px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.risikoobjekt-item-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 5px 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
99
client-web/src/app/produktbaum/produktbaum.html
Normal file
99
client-web/src/app/produktbaum/produktbaum.html
Normal file
@@ -0,0 +1,99 @@
|
||||
<main class="main">
|
||||
<div class="content" style="text-align:left; margin-top:50px;">
|
||||
<h1>Angular</h1>
|
||||
|
||||
<button (click)="apriori()">Produktwissen abfragen</button>
|
||||
<button (click)="calculate(verkaufsprodukte[0])">calculate</button>
|
||||
|
||||
<h2>Produktbaum</h2>
|
||||
|
||||
<ng-template #bausteinTemplate let-bausteine let-risikoobjekt="risikoobjekt">
|
||||
<ul class="baustein-list">
|
||||
<li *ngFor="let baustein of bausteine" class="baustein-item">
|
||||
<div class="baustein-row">
|
||||
<span
|
||||
class="baustein-name"
|
||||
[ngClass]="{ 'notIncluded': baustein.occByParent < 1 }"
|
||||
(click)="toggleAttributes(baustein)"
|
||||
style="cursor:pointer;"
|
||||
>
|
||||
<div *ngIf="baustein.meldungen.length > 0" style="color: red" > {{baustein.bez}} </div>
|
||||
<div *ngIf="baustein.meldungen.length <= 0"> {{baustein.bez}} </div>
|
||||
</span>
|
||||
<div *ngIf="baustein.showAttribute" class="extraInfo-container" style="margin-left: 20px; margin-top: 5px;">
|
||||
<table>
|
||||
<div *ngFor="let attr of baustein.attribute" class="attribute-item">
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ attr.bez }}:</strong>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input
|
||||
*ngIf="getInputType(attr) === 'checkbox'"
|
||||
type="checkbox"
|
||||
[checked]="attr.value !== undefined ? attr.value : (attr.default !== undefined ? attr.default : false)"
|
||||
(change)="setBooleanAttributValue($event, attr)"
|
||||
[disabled]="attr.aenderbar !== undefined ? !attr.aenderbar : false"
|
||||
/>
|
||||
|
||||
<input
|
||||
*ngIf="getInputType(attr) !== 'checkbox'"
|
||||
[(ngModel)]="attr.value"
|
||||
[type]="getInputType(attr)"
|
||||
[placeholder]="attr.value ?? attr.default ?? ''"
|
||||
(ngModelChange)="attributValueListener(attr)"
|
||||
[disabled]="attr.aenderbar !== undefined ? !attr.aenderbar : false"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
</table>
|
||||
|
||||
<div *ngFor="let meldung of baustein.meldungen" class="meldungen-container">
|
||||
<strong> {{meldung.errorMsg}} </strong>
|
||||
</div>
|
||||
|
||||
<div *ngIf="risikoobjektService.risikoobjekte().length > 0" class="risikoobjekt-container">
|
||||
<strong>Mit dem Baustein assoziiertes Risikoobjekt</strong>
|
||||
<div *ngFor="let ro of risikoobjektService.risikoobjekte()" class="risikoobjekt-item-container">
|
||||
<label [for]="'check-' + '{{ro.id}}'">
|
||||
{{ro.handelsbezeichnung}} ({{ro.baujahr}})
|
||||
</label>
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
[id]="'check-' + '{{ro.id}}'"
|
||||
(change)="risikoobjektListener(baustein, ro, $event)"
|
||||
[checked]="baustein.risikoobjekte.includes(ro)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="baustein-buttons">
|
||||
<button
|
||||
(click)="removeProdukt(baustein)"
|
||||
[disabled]="baustein.occByParent <= baustein.minOcc"
|
||||
class="btn-control">-</button>
|
||||
<button
|
||||
(click)="addProdukt(baustein)"
|
||||
[disabled]="baustein.actualOcc.value >= baustein.maxOcc && baustein.occByParent >= baustein.maxOcc"
|
||||
class="btn-control">+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container
|
||||
*ngIf="baustein.unterbausteine.length > 0"
|
||||
[ngTemplateOutlet]="bausteinTemplate"
|
||||
[ngTemplateOutletContext]="{ $implicit: baustein.unterbausteine }">
|
||||
</ng-container>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-template>
|
||||
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="bausteinTemplate"
|
||||
[ngTemplateOutletContext]="{ $implicit: verkaufsprodukte, risikoobjekt: risikoobjekte}">
|
||||
</ng-container>
|
||||
</div>
|
||||
</main>
|
||||
23
client-web/src/app/produktbaum/produktbaum.spec.ts
Normal file
23
client-web/src/app/produktbaum/produktbaum.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Produktbaum } from './produktbaum';
|
||||
|
||||
describe('Produktbaum', () => {
|
||||
let component: Produktbaum;
|
||||
let fixture: ComponentFixture<Produktbaum>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [Produktbaum]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(Produktbaum);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
952
client-web/src/app/produktbaum/produktbaum.ts
Normal file
952
client-web/src/app/produktbaum/produktbaum.ts
Normal file
@@ -0,0 +1,952 @@
|
||||
import {Component, inject, Injectable, Input, input, signal} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {QueryEngine} from '@comunica/query-sparql';
|
||||
import {DataFactory} from 'rdf-data-factory';
|
||||
import {Store} from 'n3';
|
||||
import jsonld from 'jsonld';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {RouterOutlet} from '@angular/router';
|
||||
import {FahrzeugType, Risikoobjekt, RisikoobjektView} from '../risikoobjektView/risikoobjektView';
|
||||
import {RisikoobjektService} from '../services/risikoobjekt.service';
|
||||
|
||||
interface Produktbaustein {
|
||||
id : string;
|
||||
bez: string;
|
||||
type: string;
|
||||
praemienfaktor: string;
|
||||
minOcc: number;
|
||||
maxOcc: number;
|
||||
occByParent: number;
|
||||
actualOcc: { value: number };
|
||||
showAttribute: boolean;
|
||||
parent: Produktbaustein;
|
||||
unterbausteine: Produktbaustein[];
|
||||
risikoobjekte: (FahrzeugType)[];
|
||||
attribute: Attribut[];
|
||||
meldungen: Meldung[];
|
||||
}
|
||||
|
||||
interface Meldung {
|
||||
id: string
|
||||
errorType: number;
|
||||
errorMsg: string;
|
||||
kommtVonPlausi: string;
|
||||
}
|
||||
|
||||
interface Attribut {
|
||||
id: string
|
||||
bez: string;
|
||||
aenderbar: boolean;
|
||||
pflichtfeld: boolean;
|
||||
produktId: string;
|
||||
value?: string | number | boolean;
|
||||
default?: string | boolean | number;
|
||||
type: string
|
||||
|
||||
}
|
||||
|
||||
interface BooleanAttribut extends Attribut{
|
||||
default: boolean;
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
interface StringAttribut extends Attribut{
|
||||
default: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface IntAttribut extends Attribut{
|
||||
default: number;
|
||||
value: number;
|
||||
max: number;
|
||||
min: number;
|
||||
}
|
||||
|
||||
interface DecimalAttribut extends Attribut{
|
||||
default: number;
|
||||
value: number;
|
||||
max: number;
|
||||
min: number;
|
||||
}
|
||||
|
||||
interface Plausi {
|
||||
beschreibung: string;
|
||||
query: string;
|
||||
art: string;
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [CommonModule, FormsModule],
|
||||
templateUrl: './produktbaum.html',
|
||||
styleUrl: './produktbaum.css'
|
||||
})
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class Produktbaum {
|
||||
protected risikoobjektService = inject(RisikoobjektService);
|
||||
risikoobjekte = this.risikoobjektService.risikoobjekte;
|
||||
|
||||
protected readonly title = signal('OMDSAngularWebClient');
|
||||
|
||||
http = inject(HttpClient);
|
||||
produkte : Produktbaustein[] = [];
|
||||
verkaufsprodukte : Produktbaustein[] = [];
|
||||
attribute : Attribut[] = [];
|
||||
plausis : Plausi[] = [];
|
||||
meldungen : Meldung[] = []
|
||||
aprioriProdukte : Produktbaustein[] = [];
|
||||
apriori(){
|
||||
this.http.post<any[]>('produktApi/ProductsRequest', { "stichtag": "2022-02-11" })
|
||||
.subscribe(result => {
|
||||
this.produkte = [];
|
||||
this.attribute = [];
|
||||
this.plausis = [];
|
||||
this.meldungen = [];
|
||||
|
||||
const prod = 'http://vvo.pisanoapi.at/ProdElement';
|
||||
const plaus = 'http://vvo.pisanoapi.at/Plausi';
|
||||
const att = 'http://vvo.pisanoapi.at/Elem';
|
||||
const meld = 'http://vvo.pisanoapi.at/Meldung';
|
||||
|
||||
result.forEach(p => {
|
||||
const id : string = this.extractValue(p['@id']);
|
||||
if (id.startsWith(meld)) {
|
||||
this.meldungen.push(this.buildMeldung(p))
|
||||
}else if (id.startsWith(plaus)) {
|
||||
const tmpPlaus = this.buildPlausi(p);
|
||||
this.plausis.push(tmpPlaus);
|
||||
}else if (id.startsWith(att)){
|
||||
this.attribute.push(this.buildAttribut(p))
|
||||
}else if (id.startsWith(prod)){
|
||||
this.produkte.push(this.buildProdukt(p))
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
this.produkte = this.sortProdukte(this.produkte);
|
||||
|
||||
for (const att of this.attribute){
|
||||
for (const produkt of this.produkte) {
|
||||
if (produkt.id.startsWith(att.produktId)) {
|
||||
produkt.attribute.push(att);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.buildTree();
|
||||
this.verkaufsprodukte = this.produkte.filter(p => p.parent === undefined);
|
||||
this.aprioriProdukte = structuredClone(this.produkte);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
buildAttribut(p: any) : (DecimalAttribut | IntAttribut | BooleanAttribut | StringAttribut){
|
||||
const keyMapping: { [key: string]: string } = {
|
||||
'@id': 'id',
|
||||
'http://vvo.pisanoapi.at/bez': 'bez',
|
||||
'http://vvo.pisanoapi.at/required': 'required',
|
||||
'http://vvo.pisanoapi.at/value': 'value',
|
||||
'http://vvo.pisanoapi.at/max': 'max',
|
||||
'http://vvo.pisanoapi.at/min': 'min',
|
||||
'http://vvo.pisanoapi.at/default': 'default',
|
||||
'http://vvo.pisanoapi.at/aenderbar': 'aenderbar',
|
||||
'http://vvo.pisanoapi.at/ProdElement': 'produktId',
|
||||
};
|
||||
|
||||
const attribut: any = {};
|
||||
|
||||
for (const key in p) {
|
||||
const mappedKey = keyMapping[key];
|
||||
|
||||
if (mappedKey){
|
||||
let value = this.extractValue(p[key]);
|
||||
|
||||
if (mappedKey === 'id' && typeof value === 'string') {
|
||||
if (value.startsWith("vvo:")){
|
||||
value = value.substring(4)
|
||||
}else {
|
||||
value = value.substring(24);
|
||||
}
|
||||
attribut.type = value.substring(4)
|
||||
}
|
||||
|
||||
if (mappedKey === 'produktId' && typeof value === 'string') {
|
||||
if (value.startsWith("vvo:")){
|
||||
value = value.substring(15);
|
||||
}else {
|
||||
value = value.substring(35);
|
||||
}
|
||||
}
|
||||
|
||||
if (mappedKey === 'value') {
|
||||
value = this.parseDynamicValue(value);
|
||||
}
|
||||
|
||||
if (mappedKey === 'default') {
|
||||
value = this.parseDynamicValue(value)
|
||||
if (typeof value === "boolean" && value) {
|
||||
attribut.value = true
|
||||
}
|
||||
}
|
||||
|
||||
if (mappedKey === 'aenderbar') {
|
||||
value = this.parseDynamicValue(value)
|
||||
}
|
||||
|
||||
|
||||
attribut[mappedKey] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return attribut as (DecimalAttribut | IntAttribut | BooleanAttribut | StringAttribut);
|
||||
}
|
||||
|
||||
buildMeldung(p: any) : Meldung{
|
||||
const keyMapping: { [key: string]: string } = {
|
||||
'@id': 'id',
|
||||
'http://vvo.pisanoapi.at/errorMsg': 'errorMsg',
|
||||
'http://vvo.pisanoapi.at/errorType': 'errorType',
|
||||
'vvo:errorMsg': 'errorMsg',
|
||||
'vvo:errorType': 'errorType'
|
||||
};
|
||||
|
||||
const meldung: any = {};
|
||||
|
||||
for (const key in p) {
|
||||
const mappedKey = keyMapping[key];
|
||||
|
||||
if (mappedKey){
|
||||
let value = this.extractValue(p[key]);
|
||||
|
||||
if (mappedKey === 'id' && typeof value === 'string') {
|
||||
if (value.startsWith("vvo:")){
|
||||
value = value.substring(4);
|
||||
}else {
|
||||
value = value.substring(24);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
meldung[mappedKey] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return meldung as Meldung;
|
||||
}
|
||||
|
||||
parseDynamicValue(value: string): string | boolean | number {
|
||||
if (value.toLowerCase() === 'true') return true;
|
||||
if (value.toLowerCase() === 'false') return false;
|
||||
|
||||
if (!isNaN(Number(value)) && value.trim() !== '') {
|
||||
const num = Number(value);
|
||||
return Number.isInteger(num) ? num : parseFloat(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
extractValue(data: any): any {
|
||||
if (!data) return undefined;
|
||||
|
||||
const container = Array.isArray(data) ? data[0] : data;
|
||||
|
||||
if (container && typeof container === 'object' && container['@value'] !== undefined) {
|
||||
return container['@value'].trim();
|
||||
}else if (container && typeof container === 'object' && container['@id'] !== undefined) {
|
||||
return container['@id'].trim();
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
buildPlausi(p: any): Plausi {
|
||||
const keyMapping: { [key: string]: string } = {
|
||||
'@id': 'id',
|
||||
'http://vvo.pisanoapi.at/beschreibung': 'beschreibung',
|
||||
'http://vvo.pisanoapi.at/query': 'query',
|
||||
'http://vvo.pisanoapi.at/art': 'art',
|
||||
'vvo:beschreibung': 'beschreibung',
|
||||
'vvo:query': 'query',
|
||||
'vvo:art': 'art'
|
||||
};
|
||||
|
||||
const plausi: any = {};
|
||||
|
||||
for (const key in p) {
|
||||
const mappedKey = keyMapping[key];
|
||||
|
||||
if (mappedKey){
|
||||
let value = this.extractValue(p[key]);
|
||||
|
||||
plausi[mappedKey] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return plausi as Plausi;
|
||||
}
|
||||
|
||||
buildFahrzeug(p: any): FahrzeugType {
|
||||
const keyMapping: { [key: string]: string } = {
|
||||
'@id': 'id',
|
||||
'http://vvo.pisanoapi.at/bez': 'handelsbezeichnung',
|
||||
'http://vvo.pisanoapi.at/baujahr': 'baujahr',
|
||||
'http://vvo.pisanoapi.at/erstzulassung': 'erstzulassung',
|
||||
'http://vvo.pisanoapi.at/leistung': 'leistung',
|
||||
'http://vvo.pisanoapi.at/listenpreis': 'listenpreis',
|
||||
'http://vvo.pisanoapi.at/sonderausstattung': 'sonderausstattung',
|
||||
'http://vvo.pisanoapi.at/kennzeichen': 'kennzeichen'
|
||||
|
||||
};
|
||||
|
||||
const fahrzeug: any = {};
|
||||
|
||||
for (const key in p) {
|
||||
const mappedKey = keyMapping[key];
|
||||
|
||||
if (mappedKey){
|
||||
let value = this.extractValue(p[key]);
|
||||
|
||||
|
||||
if (mappedKey === 'id' && typeof value === 'string') {
|
||||
value = value.substring(24);
|
||||
}
|
||||
|
||||
if (mappedKey === 'baujahr' && typeof value === 'string') {
|
||||
value = parseInt(value, 10);
|
||||
if (isNaN(value)) value = undefined;
|
||||
}
|
||||
|
||||
if (mappedKey === 'erstzulassung' && typeof value === 'string') {
|
||||
value = new Date(value.substring(0, 10));
|
||||
}
|
||||
|
||||
fahrzeug[mappedKey] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return fahrzeug as FahrzeugType;
|
||||
}
|
||||
|
||||
buildProdukt(p: any): Produktbaustein {
|
||||
const keyMapping: { [key: string]: string } = {
|
||||
'@id': 'id',
|
||||
'id': 'id',
|
||||
'http://vvo.pisanoapi.at/bez': 'bez',
|
||||
'http://vvo.pisanoapi.at/minOccurrence': 'minOcc',
|
||||
'http://vvo.pisanoapi.at/maxOccurrence': 'maxOcc',
|
||||
'http://vvo.pisanoapi.at/type': 'type',
|
||||
'http://vvo.pisanoapi.at/Parent': 'parent',
|
||||
'http://vvo.pisanoapi.at/Meldung': 'meldungen',
|
||||
'http://vvo.pisanoapi.at/VersichertesInteresseType' : 'risikoobjekte',
|
||||
'http://vvo.pisanoapi.at/praemienfaktor' : 'praemienfaktor',
|
||||
|
||||
};
|
||||
|
||||
const produkt: any = {};
|
||||
produkt.meldungen = [];
|
||||
produkt.risikoobjekte = [];
|
||||
|
||||
for (const key in p) {
|
||||
const mappedKey = keyMapping[key];
|
||||
|
||||
if (mappedKey){
|
||||
let value = this.extractValue(p[key]);
|
||||
|
||||
if (mappedKey === 'risikoobjekte') {
|
||||
const values = Array.isArray(p[key]) ? p[key] : [p[key]];
|
||||
|
||||
for (const v of values) {
|
||||
const value = this.extractValue(v);
|
||||
|
||||
const ro = this.risikoobjektService.risikoobjekte().find(
|
||||
r => r.id === value.substring(24)
|
||||
);
|
||||
|
||||
if (ro) {
|
||||
produkt.risikoobjekte.push(ro);
|
||||
} else {
|
||||
console.log("Risiko Objekt nicht gefunden: " + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((mappedKey === 'minOcc' || mappedKey === 'maxOcc') && typeof value === 'string') {
|
||||
value = parseInt(value, 10);
|
||||
if (isNaN(value)) value = undefined;
|
||||
}
|
||||
|
||||
if (mappedKey === 'id' && typeof value === 'string') {
|
||||
// if(value.includes("-")) {
|
||||
// value = value.substring(0, value.indexOf("-"))
|
||||
// }
|
||||
if (value.startsWith("vvo:")) {
|
||||
value = value.substring(15)
|
||||
}else {
|
||||
value = value.substring(35);
|
||||
}
|
||||
}
|
||||
|
||||
if (mappedKey === 'parent' && typeof value === 'string') {
|
||||
if (value.startsWith("vvo:")){
|
||||
value = this.produkte.find(p => p.id === value.substring(15));
|
||||
}else {
|
||||
value = this.produkte.find(p => p.id === value.substring(35));
|
||||
}
|
||||
}
|
||||
|
||||
if (mappedKey === 'meldungen' && typeof value === 'string') {
|
||||
if (value.startsWith("vvo:")){
|
||||
value = this.meldungen.find(p => p.id === value.substring(4));
|
||||
}else {
|
||||
value = this.meldungen.find(p => p.id === value.substring(24));
|
||||
}
|
||||
|
||||
if (value !== undefined){
|
||||
produkt[mappedKey].push(value)
|
||||
}
|
||||
}
|
||||
|
||||
if (mappedKey !== "meldungen" && mappedKey !== "risikoobjekte") {
|
||||
produkt[mappedKey] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
produkt.unterbausteine = [];
|
||||
produkt.attribute = [];
|
||||
|
||||
produkt.occByParent = (produkt.parent === undefined || produkt.minOcc > 0) ? 1 : 0;
|
||||
produkt.actualOcc = { value: produkt.occByParent };
|
||||
|
||||
return produkt as Produktbaustein;
|
||||
}
|
||||
|
||||
buildTree() {
|
||||
|
||||
|
||||
for (const baustein of this.produkte) {
|
||||
if (baustein.parent !== undefined){
|
||||
let parent : (Produktbaustein | undefined) = this.produkte.find(p => p === baustein.parent);
|
||||
if (parent !== undefined && !parent.unterbausteine.includes(baustein)) {
|
||||
if (parent.occByParent < 1) {
|
||||
baustein.occByParent = 0
|
||||
}
|
||||
parent.unterbausteine.push(baustein);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.addMissingAprioriItems();
|
||||
}
|
||||
|
||||
async addProdukt(produkt : Produktbaustein) {
|
||||
for (const unterprodukt of produkt.parent.unterbausteine){
|
||||
if (unterprodukt.id === produkt.id){
|
||||
unterprodukt.occByParent++;
|
||||
}
|
||||
}
|
||||
if (produkt.occByParent >= 2) {
|
||||
const parent = this.produkte.find(p => p === produkt.parent)
|
||||
parent?.unterbausteine.push(this.cloneProdukt(produkt, undefined));
|
||||
|
||||
}else {
|
||||
produkt.actualOcc.value++;
|
||||
}
|
||||
|
||||
this.addRequiredChildren(produkt)
|
||||
await this.constructPlausi()
|
||||
}
|
||||
|
||||
addParent(produkt : Produktbaustein) {
|
||||
if (produkt.parent.occByParent < 1) {
|
||||
produkt.parent.occByParent = 1
|
||||
|
||||
this.addParent(produkt.parent)
|
||||
}
|
||||
}
|
||||
|
||||
addRequiredChildren(produkt : Produktbaustein) {
|
||||
for (const unter of produkt.unterbausteine) {
|
||||
if (unter.minOcc > 0 && unter.occByParent < 1) {
|
||||
unter.occByParent = 1
|
||||
if (unter.unterbausteine.length > 0) {
|
||||
this.addRequiredChildren(unter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async constructPlausi() {
|
||||
|
||||
|
||||
for (const plausi of this.plausis.filter(p => p.art === "graph")) {
|
||||
|
||||
for (const prod of this.produkte) {
|
||||
for (const meld of prod.meldungen) {
|
||||
if (meld.kommtVonPlausi === plausi.beschreibung) {
|
||||
prod.meldungen.splice(prod.meldungen.indexOf(meld), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let model = await this.buildRDFModel(this.verkaufsprodukte[0]);
|
||||
model = await this.pruefePlausi(plausi, model);
|
||||
const newTree = await this.modelToJsonld(model);
|
||||
|
||||
// this.produkte = [];
|
||||
// this.attribute = [];
|
||||
// this.meldungen = [];
|
||||
|
||||
const prod = 'http://vvo.pisanoapi.at/ProdElement';
|
||||
const plaus = 'vvo:Plausi';
|
||||
const att = 'vvo:Elem';
|
||||
const meld = 'http://vvo.pisanoapi.at/Meldung';
|
||||
|
||||
let uniqueIds: any[] = [];
|
||||
let wholeprodukts: any[] = []
|
||||
|
||||
newTree.forEach((p: any) => {if(p !== undefined) p.forEach((q: any) => {
|
||||
if (!uniqueIds.includes(q["@id"])) {
|
||||
uniqueIds.push(q["@id"]);
|
||||
}
|
||||
})});
|
||||
|
||||
for (let id of uniqueIds) {
|
||||
let produkt: any[] = [];
|
||||
|
||||
newTree.forEach((p: any) => p.filter((p: any) => p["@id"] === id).forEach((i: any) => {
|
||||
produkt.push(i)
|
||||
}));
|
||||
wholeprodukts.push(produkt);
|
||||
|
||||
}
|
||||
|
||||
for (const q of wholeprodukts) {
|
||||
const merged = Object.assign({}, ...q);
|
||||
|
||||
if (q[0]["@id"].startsWith(meld)) {
|
||||
this.meldungen.push(this.buildMeldung(merged))
|
||||
} else if (q[0]["@id"].startsWith(plaus)) {
|
||||
const tmpPlaus = this.buildPlausi(merged);
|
||||
this.plausis.push(tmpPlaus);
|
||||
} else if (q[0]["@id"].startsWith(att)) {
|
||||
this.attribute.push(this.buildAttribut(merged))
|
||||
}
|
||||
}
|
||||
|
||||
for (const q of wholeprodukts) {
|
||||
const merged = Object.assign({}, ...q);
|
||||
|
||||
if (q[0]["@id"].includes(prod)) {
|
||||
let newProd = this.buildProdukt(merged);
|
||||
if (newProd.bez === undefined) {
|
||||
const prod = this.produkte.find(p => newProd.id.includes(p.id));
|
||||
if (prod !== undefined) {
|
||||
//für andere sachen als meldung genauso, wenn wir Plausis wollen, die mehr machen als Meldungen hinzufügen
|
||||
for (const newMeldung of newProd.meldungen) {
|
||||
newMeldung.kommtVonPlausi = plausi.beschreibung
|
||||
prod.meldungen.push(newMeldung)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const att of this.attribute) {
|
||||
for (const produkt of this.produkte) {
|
||||
if (produkt.id.startsWith(att.produktId) && !produkt.attribute.includes(att)) {
|
||||
produkt.attribute.push(att);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.buildTree();
|
||||
this.verkaufsprodukte = this.produkte.filter(p => p.parent === undefined);
|
||||
}
|
||||
}
|
||||
|
||||
cloneProdukt(produkt : Produktbaustein, newParent: (Produktbaustein | undefined)) : Produktbaustein {
|
||||
const cloneProdukt = structuredClone(produkt);
|
||||
|
||||
if (newParent === undefined) {
|
||||
cloneProdukt.parent = produkt.parent
|
||||
}else {
|
||||
cloneProdukt.parent = newParent;
|
||||
}
|
||||
cloneProdukt.unterbausteine = []
|
||||
cloneProdukt.actualOcc = produkt.actualOcc;
|
||||
cloneProdukt.actualOcc.value++;
|
||||
|
||||
for (const child of produkt.unterbausteine) {
|
||||
cloneProdukt.unterbausteine.push(this.cloneProdukt(child, cloneProdukt))
|
||||
}
|
||||
|
||||
this.produkte.push(cloneProdukt);
|
||||
return cloneProdukt;
|
||||
}
|
||||
|
||||
async removeProdukt(produkt : Produktbaustein) {
|
||||
if (produkt.occByParent >= 2){
|
||||
const parent = this.produkte.find(p => p === produkt.parent)
|
||||
if (parent !== undefined){
|
||||
const i = parent?.unterbausteine.indexOf(produkt);
|
||||
const j = this.produkte.indexOf(produkt);
|
||||
if (i > -1) {
|
||||
parent.unterbausteine.splice(i, 1);
|
||||
this.produkte.splice(j, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
produkt.actualOcc.value--;
|
||||
|
||||
for (const unterprodukt of produkt.parent.unterbausteine){
|
||||
if (unterprodukt.id === produkt.id){
|
||||
unterprodukt.occByParent--;
|
||||
}
|
||||
}
|
||||
|
||||
if (produkt.unterbausteine.length > 0) {
|
||||
this.removeChildren(produkt);
|
||||
}
|
||||
|
||||
await this.constructPlausi()
|
||||
}
|
||||
|
||||
removeChildren(produkt : Produktbaustein) {
|
||||
const tmp : string[] = [];
|
||||
const toDelete : Produktbaustein[] = [];
|
||||
|
||||
for (const child of produkt.unterbausteine){
|
||||
if (!tmp.includes(child.id)){
|
||||
child.actualOcc.value = child.actualOcc.value - child.occByParent;
|
||||
tmp.push(child.id);
|
||||
}
|
||||
|
||||
if (child.occByParent > 1){
|
||||
toDelete.push(child)
|
||||
|
||||
for (const unterbaustein of produkt.unterbausteine){
|
||||
unterbaustein.occByParent--;
|
||||
}
|
||||
}
|
||||
child.occByParent = 0;
|
||||
|
||||
this.removeChildren(child);
|
||||
}
|
||||
|
||||
for (const e of toDelete){
|
||||
produkt.unterbausteine.splice(produkt.unterbausteine.indexOf(e) ,1)
|
||||
}
|
||||
}
|
||||
|
||||
async calculate(verkaufsprodukt: Produktbaustein) {
|
||||
const request = (await this.modelToJsonld(await this.buildRDFModel(verkaufsprodukt)));
|
||||
this.produkte = [];
|
||||
this.http.post<any[]>('produktApi/CalculateRequest', request )
|
||||
.subscribe(result => {
|
||||
this.produkte = [];
|
||||
this.attribute = [];
|
||||
|
||||
const prod = 'http://vvo.pisanoapi.at/ProdElement';
|
||||
const plaus = 'http://vvo.pisanoapi.at/Plausi';
|
||||
const att = 'http://vvo.pisanoapi.at/Elem';
|
||||
const meld = 'http://vvo.pisanoapi.at/Meldung';
|
||||
const fz = 'http://vvo.pisanoapi.at/FahrzeugType';
|
||||
|
||||
result.forEach(p => {
|
||||
const id : string = this.extractValue(p['@id']);
|
||||
if (id.startsWith(meld)) {
|
||||
this.meldungen.push(this.buildMeldung(p))
|
||||
}else if (id.startsWith(plaus)) {
|
||||
const tmpPlaus = this.buildPlausi(p);
|
||||
this.plausis.push(tmpPlaus);
|
||||
}else if (id.startsWith(att)){
|
||||
this.attribute.push(this.buildAttribut(p))
|
||||
}else if (id.startsWith(fz)){
|
||||
const fahrzeug = this.buildFahrzeug(p);
|
||||
|
||||
let fahrzeugAlt =
|
||||
this.risikoobjektService.risikoobjekte().find(r =>
|
||||
r.handelsbezeichnung === fahrzeug.handelsbezeichnung
|
||||
&& new Date(r.erstzulassung).getTime() === fahrzeug.erstzulassung.getTime())
|
||||
|
||||
if (fahrzeugAlt !== undefined){
|
||||
//alle attribute die sich geädert haben könnten
|
||||
fahrzeugAlt.id = fahrzeug.id;
|
||||
fahrzeugAlt.baujahr = fahrzeug.baujahr;
|
||||
} else {
|
||||
console.log("neues fahrzeug")
|
||||
this.risikoobjektService.addRisikoobjekt(fahrzeug);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
result.forEach(p => {
|
||||
const id : string = this.extractValue(p['@id']);
|
||||
|
||||
if (id.startsWith(prod)){
|
||||
this.produkte.push(this.buildProdukt(p))
|
||||
}
|
||||
})
|
||||
|
||||
this.produkte = this.sortProdukte(this.produkte);
|
||||
|
||||
for (const att of this.attribute){
|
||||
for (const produkt of this.produkte) {
|
||||
if (produkt.id.startsWith(att.produktId)) {
|
||||
produkt.attribute.push(att);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.buildTree();
|
||||
this.verkaufsprodukte = this.produkte.filter(p => p.parent === undefined);
|
||||
});
|
||||
}
|
||||
|
||||
sortProdukte(produkte: Produktbaustein[]) : Produktbaustein[] {
|
||||
produkte.sort((a, b) => {
|
||||
return a.bez < b.bez ? -1 : a.bez > b.bez ? 1 : 0
|
||||
});
|
||||
|
||||
return produkte;
|
||||
}
|
||||
|
||||
async buildRDFModel(verkaufsprodukt : Produktbaustein) {
|
||||
const model = new Store();
|
||||
const baseIri = 'http://vvo.pisanoapi.at/';
|
||||
|
||||
const idCount = new Map<string, number>();
|
||||
this.createIdCount(idCount, verkaufsprodukt);
|
||||
|
||||
const iriMap = new Map<Produktbaustein, string>();
|
||||
this.createIriMap(verkaufsprodukt, baseIri, idCount, iriMap);
|
||||
|
||||
this.addProduktToModel(verkaufsprodukt, model, baseIri, null, iriMap);
|
||||
|
||||
return model
|
||||
}
|
||||
|
||||
async modelToJsonld(model: Store) : Promise<any[]> {
|
||||
let result = []
|
||||
const jsonldData = model.getQuads(null, null, null, null).map(q => ({
|
||||
'@id': q.subject.value,
|
||||
[q.predicate.value]: [
|
||||
q.object.termType === 'Literal'
|
||||
? { '@value': q.object.value }
|
||||
: { '@id': q.object.value }
|
||||
]
|
||||
}));
|
||||
|
||||
const context = { vvo: 'http://vvo.pisanoapi.at/' };
|
||||
result.push(await jsonld.expand(jsonldData));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
createIdCount(idCount: Map<string, number>, vp: Produktbaustein): void {
|
||||
const id = vp.id;
|
||||
idCount.set(id, (idCount.get(id) ?? 0) + 1);
|
||||
for (const child of vp.unterbausteine) {
|
||||
this.createIdCount(idCount, child);
|
||||
}
|
||||
}
|
||||
|
||||
createIriMap(
|
||||
produkt: Produktbaustein,
|
||||
baseIri: string,
|
||||
idCount: Map<string, number>,
|
||||
iriMap: Map<Produktbaustein, string>
|
||||
): void {
|
||||
const baseElemIri = baseIri + 'ProdElement';
|
||||
const id = produkt.id;
|
||||
const count = idCount.get(id) ?? 1;
|
||||
|
||||
for (let i = 1; i <= count; i++) {
|
||||
let tmpIri = `${baseElemIri}${id}-${i}`;
|
||||
if (id.includes("-")){
|
||||
tmpIri = `${baseElemIri}${id}`;
|
||||
}
|
||||
if (![...iriMap.values()].includes(tmpIri)) {
|
||||
iriMap.set(produkt, tmpIri);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const child of produkt.unterbausteine) {
|
||||
this.createIriMap(child, baseIri, idCount, iriMap);
|
||||
}
|
||||
}
|
||||
|
||||
addProduktToModel(
|
||||
produkt: Produktbaustein,
|
||||
model: Store,
|
||||
baseIri: string,
|
||||
parentId: string | null,
|
||||
iriMap: Map<Produktbaustein, string>
|
||||
) {
|
||||
const DF = new DataFactory()
|
||||
const vpIri = DF.namedNode(iriMap.get(produkt)!);
|
||||
|
||||
if (produkt.occByParent >= 1) {
|
||||
model.addQuad(vpIri, DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), DF.namedNode("http://vvo.pisanoapi.at/ProdElement"))
|
||||
model.addQuad(vpIri, DF.namedNode(baseIri + 'bez'), DF.literal(produkt.bez));
|
||||
|
||||
if (produkt.minOcc !== undefined)
|
||||
model.addQuad(vpIri, DF.namedNode(baseIri + 'minOccurrence'), DF.literal(produkt.minOcc.toString()));
|
||||
if (produkt.maxOcc !== undefined)
|
||||
model.addQuad(vpIri, DF.namedNode(baseIri + 'maxOccurrence'), DF.literal(produkt.maxOcc.toString()));
|
||||
|
||||
// if (produkt.verkaufsoffenVon)
|
||||
// model.addQuad(vpIri, DF.namedNode(baseIri + 'salesFrom'), DF.literal(produkt.verkaufsoffenVon));
|
||||
// if (produkt.verkaufsoffenBis)
|
||||
// model.addQuad(vpIri, DF.namedNode(baseIri + 'salesTo'), DF.literal(produkt.verkaufsoffenBis));
|
||||
if (produkt.type)
|
||||
model.addQuad(vpIri, DF.namedNode(baseIri + 'type'), DF.literal(produkt.type));
|
||||
// if (produkt.risikoobjektErforderlich)
|
||||
// model.addQuad(vpIri, DF.namedNode(baseIri + 'risikoobjektType'), DF.literal('FahrzeugType'));
|
||||
if (produkt.praemienfaktor)
|
||||
model.addQuad(vpIri, DF.namedNode(baseIri + 'praemienfaktor'), DF.literal(produkt.praemienfaktor));
|
||||
if (parentId)
|
||||
model.addQuad(vpIri, DF.namedNode(baseIri + 'Parent'), DF.namedNode(parentId));
|
||||
}
|
||||
|
||||
for (const at of produkt.attribute ?? []) {
|
||||
let typeIri = baseIri;
|
||||
if (at.type.toLowerCase().includes("decimal")){
|
||||
typeIri += "ElemDecimal";
|
||||
}else if (at.type.toLowerCase().includes("int")){
|
||||
typeIri += "ElemInt";
|
||||
}else if (at.type.toLowerCase().includes("bool")){
|
||||
typeIri += "ElemBoolean";
|
||||
}else {
|
||||
typeIri += "ElemString";
|
||||
}
|
||||
|
||||
const atIri = DF.namedNode(`${baseIri}${at.id}`);
|
||||
model.addQuad(atIri, DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), DF.namedNode(typeIri));
|
||||
|
||||
model.addQuad(atIri, DF.namedNode(baseIri + 'bez'), DF.literal(at.bez));
|
||||
model.addQuad(atIri, DF.namedNode(baseIri + 'ProdElement'), vpIri);
|
||||
|
||||
if (typeof at.value === "string") {
|
||||
model.addQuad(atIri, DF.namedNode(baseIri + 'value'), DF.literal(at.value));
|
||||
}else if (typeof at.value === "boolean"){
|
||||
model.addQuad(atIri, DF.namedNode(baseIri + 'value'), DF.literal(at.value ? "true" : "false"));
|
||||
} else if (at.value !== undefined) {
|
||||
model.addQuad(atIri, DF.namedNode(baseIri + 'value'), DF.literal(at.value.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
let i = 1;
|
||||
for (const ro of produkt.risikoobjekte) {
|
||||
const fahrzeugIri = DF.namedNode(baseIri + "FahrzeugType")
|
||||
const spezFahrzeugIri = DF.namedNode(fahrzeugIri.value + ro.handelsbezeichnung + ro.baujahr);
|
||||
|
||||
model.addQuad(vpIri, DF.namedNode(baseIri + "VersichertesInteresseType"), spezFahrzeugIri);
|
||||
|
||||
const existingQuads = model.getQuads(spezFahrzeugIri, null, null, null);
|
||||
if (existingQuads.length === 0) {
|
||||
model.addQuad(spezFahrzeugIri, DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), fahrzeugIri);
|
||||
|
||||
if (ro.handelsbezeichnung) {
|
||||
model.addQuad(spezFahrzeugIri, DF.namedNode(baseIri + 'bez'), DF.literal(ro.handelsbezeichnung));
|
||||
}
|
||||
if (ro.baujahr !== undefined) {
|
||||
model.addQuad(spezFahrzeugIri, DF.namedNode(baseIri + 'baujahr'), DF.literal(ro.baujahr.toString()));
|
||||
}
|
||||
if (ro.erstzulassung) {
|
||||
model.addQuad(spezFahrzeugIri, DF.namedNode(baseIri + 'erstzulassung'), DF.literal( ro.erstzulassung.toString()));
|
||||
}
|
||||
if (ro.kennzeichen) {
|
||||
model.addQuad(spezFahrzeugIri, DF.namedNode(baseIri + 'kennzeichen'), DF.literal( ro.kennzeichen));
|
||||
}
|
||||
if (ro.leistung) {
|
||||
model.addQuad(spezFahrzeugIri, DF.namedNode(baseIri + 'leistung'), DF.literal( ro.leistung.toString()));
|
||||
}
|
||||
if (ro.listenpreis) {
|
||||
model.addQuad(spezFahrzeugIri, DF.namedNode(baseIri + 'listenpreis'), DF.literal( ro.listenpreis.toString()));
|
||||
}
|
||||
if (ro.sonderausstattung) {
|
||||
model.addQuad(spezFahrzeugIri, DF.namedNode(baseIri + 'sonderausstattung'), DF.literal( ro.sonderausstattung.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
for (const child of produkt.unterbausteine) {
|
||||
const unterIri = iriMap.get(child)!;
|
||||
if (produkt.occByParent >= 1) {
|
||||
model.addQuad(vpIri, DF.namedNode(baseIri + 'Baustein'), DF.namedNode(unterIri));
|
||||
}
|
||||
this.addProduktToModel(child, model, baseIri, vpIri.value, iriMap);
|
||||
}
|
||||
}
|
||||
|
||||
async pruefePlausi(plausi: Plausi, model: Store): Promise<Store> {
|
||||
const engine = new QueryEngine();
|
||||
if (plausi.art === "graph"){
|
||||
const result = await engine.queryQuads(plausi.query, { sources: [model],});
|
||||
const quads = await result.toArray();
|
||||
|
||||
const erg : Store = new Store();
|
||||
for (const quad of quads) {
|
||||
erg.addQuad(quad.subject, quad.predicate, quad.object);
|
||||
}
|
||||
return erg;
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
toggleAttributes(baustein: Produktbaustein) {
|
||||
baustein.showAttribute = !baustein.showAttribute;
|
||||
}
|
||||
|
||||
getInputType(attr: Attribut): string {
|
||||
const a = attr as BooleanAttribut | StringAttribut | IntAttribut | DecimalAttribut;
|
||||
|
||||
if (typeof a.value === 'boolean' || typeof a.default === 'boolean') return 'checkbox';
|
||||
if (typeof a.value === 'number' || typeof a.default === 'number') return 'number';
|
||||
return 'text';
|
||||
}
|
||||
|
||||
attributValueListener(attr: Attribut) {
|
||||
console.log(attr.value)
|
||||
}
|
||||
setBooleanAttributValue(event: Event, attr: Attribut) {
|
||||
attr.value = (event.target as HTMLInputElement).checked
|
||||
}
|
||||
|
||||
addMissingAprioriItems(){
|
||||
for (const aprod of this.aprioriProdukte) {
|
||||
let flag = false;
|
||||
|
||||
for (const prod of this.produkte) {
|
||||
if (prod.bez === aprod.bez) {
|
||||
flag = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!flag) {
|
||||
aprod.occByParent = 0
|
||||
this.removeChildren(aprod)
|
||||
const newParent = this.produkte.find(p => p.bez === aprod.parent.bez)
|
||||
if (newParent !== undefined) {
|
||||
aprod.parent = newParent
|
||||
}
|
||||
this.produkte.push(this.cloneProdukt(aprod, undefined))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
risikoobjektListener(baustein: Produktbaustein, ro: FahrzeugType, event: Event) {
|
||||
if ((event.target as HTMLInputElement).checked) {
|
||||
baustein.risikoobjekte.push(ro)
|
||||
} else {
|
||||
baustein.risikoobjekte.splice(baustein.risikoobjekte.indexOf(ro), 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
137
client-web/src/app/risikoobjektView/risikoobjektView.css
Normal file
137
client-web/src/app/risikoobjektView/risikoobjektView.css
Normal file
@@ -0,0 +1,137 @@
|
||||
/* Container Layout */
|
||||
.container {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
max-width: 1200px;
|
||||
margin: 40px auto;
|
||||
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* Linke Spalte: Konfiguration */
|
||||
.left-pane {
|
||||
flex: 2;
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* Rechte Spalte: Liste der Objekte */
|
||||
.right-pane {
|
||||
flex: 1;
|
||||
background: #fdfdfe;
|
||||
padding: 25px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e9ecef;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
/* Titel-Styling */
|
||||
h2 {
|
||||
color: #2c3e50;
|
||||
border-bottom: 2px solid #eee;
|
||||
padding-bottom: 10px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
grid-column: 1 / -1; /* Überschrift über beide Spalten im Grid */
|
||||
color: #007bff;
|
||||
font-size: 1.1rem;
|
||||
margin: 20px 0 10px 0;
|
||||
}
|
||||
|
||||
/* Formular Styling */
|
||||
.grid-form {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr; /* Zwei Spalten für Labels und Inputs */
|
||||
gap: 15px 20px;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.grid-form label {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Inputs & Select */
|
||||
input, select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
font-size: 0.95rem;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus, select:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Sonderfall: Alleinstehende Inputs ohne Label (Typ 1, 2 etc.) */
|
||||
.grid-form input:not([id]) {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
/* Submit Button */
|
||||
.btn-submit {
|
||||
background-color: #28a745; /* Grün für "Hinzufügen" */
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
margin-top: 25px;
|
||||
width: 100%;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.btn-submit:hover:not(:disabled) {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.btn-submit:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Validierungshinweis */
|
||||
h4 {
|
||||
font-size: 0.85rem;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Rechte Liste Styling */
|
||||
.right-pane ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.right-pane li {
|
||||
background: white;
|
||||
margin-bottom: 8px;
|
||||
padding: 10px 15px;
|
||||
border-radius: 5px;
|
||||
border-left: 4px solid #007bff;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
font-size: 0.9rem;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
/* Responsive Anpassung */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
}
|
||||
.grid-form {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
94
client-web/src/app/risikoobjektView/risikoobjektView.html
Normal file
94
client-web/src/app/risikoobjektView/risikoobjektView.html
Normal file
@@ -0,0 +1,94 @@
|
||||
<div class="container">
|
||||
<div class="left-pane">
|
||||
|
||||
<h2>Konfiguration</h2>
|
||||
|
||||
<label>Typ auswählen:</label>
|
||||
<select [(ngModel)]="selectedType">
|
||||
<option value="">-- bitte wählen --</option>
|
||||
<option *ngFor="let t of types" [value]="t">{{ t }}</option>
|
||||
</select>
|
||||
|
||||
<div class="form-container" >
|
||||
<form #f="ngForm" class="grid-form">
|
||||
<ng-container *ngIf="selectedType === 'Versichertes objekt SachPrivat'">
|
||||
<h3>Formular für Typ 1</h3>
|
||||
<input placeholder="Feld A1">
|
||||
<input placeholder="Feld A2">
|
||||
<input placeholder="Feld A3">
|
||||
<input placeholder="Feld A4">
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="selectedType === 'Versicherte Liegenschaft'">
|
||||
<h3>Formular für Typ 2</h3>
|
||||
<input placeholder="Feld B1">
|
||||
<input placeholder="Feld B2">
|
||||
<input placeholder="Feld B3">
|
||||
<input placeholder="Feld B4">
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="selectedType === 'Fahrzeug'" >
|
||||
<h3>Angaben zum neuen Fahrzeug</h3>
|
||||
|
||||
<label for="baujahr">Baujahr:</label>
|
||||
<input [(ngModel)]="formData.Baujahr" id="baujahr" type="number" name="Baujahr" placeholder="Baujahr" ngModel required>
|
||||
|
||||
<label for="leistung">Leistung in kW:</label>
|
||||
<input [(ngModel)]="formData.Leistung" id="leistung" type="number" name="Leistung" placeholder="Leistung" ngModel required>
|
||||
|
||||
<label for="listenpreis">Listenpreis:</label>
|
||||
<input [(ngModel)]="formData.Listenpreis" id="listenpreis" type="number" name="Listenpreis" placeholder="Listenpreis" ngModel required>
|
||||
|
||||
<label for="sonderausstattung">Sonderausstattung:</label>
|
||||
<input [(ngModel)]="formData.Sonderausstattung" id="sonderausstattung" type="number" name="Sonderausstattung" placeholder="Sonderausstattung" ngModel required>
|
||||
|
||||
<label for="handelsbezeichnung">Handelsbezeichnung:</label>
|
||||
<input [(ngModel)]="formData.Handelsbezeichnung" id="handelsbezeichnung" name="Handelsbezeichnung" placeholder="Handelsbezeichnung" ngModel required>
|
||||
|
||||
<label for="erstzulassung">Erstzulassung:</label>
|
||||
<input [(ngModel)]="formData.Erstzulassung" id="erstzulassung" type="date" name="Erstzulassung" placeholder="Erstzulassung" ngModel required>
|
||||
|
||||
<label for="kennzeichen">Kennzeichen:</label>
|
||||
<input [(ngModel)]="formData.Kennzeichen" id="kennzeichen" name="Kennzeichen" placeholder="Kennzeichen" ngModel required>
|
||||
|
||||
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="selectedType === 'Risiko Gebaeude'">
|
||||
<h3>Formular für Typ 4</h3>
|
||||
<input placeholder="Feld D1">
|
||||
<input placeholder="Feld D2">
|
||||
<input placeholder="Feld D3">
|
||||
<input placeholder="Feld D4">
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="selectedType === 'Versicherte Person'">
|
||||
<h3>Formular für Typ 5</h3>
|
||||
<input placeholder="Feld E1">
|
||||
<input placeholder="Feld E2">
|
||||
<input placeholder="Feld E3">
|
||||
<input placeholder="Feld E4">
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="selectedType === 'Risiko Haushalt'">
|
||||
<h3>Formular für Typ 6</h3>
|
||||
<input placeholder="Feld F1">
|
||||
<input placeholder="Feld F2">
|
||||
<input placeholder="Feld F3">
|
||||
<input placeholder="Feld F4">
|
||||
</ng-container>
|
||||
</form>
|
||||
<button class="btn-submit" (click)="addRisikoobjekt()" [disabled]="!f.valid">Hinzufügen</button>
|
||||
<h4 style="color: red" [hidden]="f.valid">Es müssen alle Felder ausgefüllt werden</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-pane">
|
||||
<h2>Risikoobjekte</h2>
|
||||
|
||||
<ul>
|
||||
<li *ngFor="let r of risikoobjekte()">
|
||||
{{ r.handelsbezeichnung }}({{r.baujahr}})
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
23
client-web/src/app/risikoobjektView/risikoobjektView.spec.ts
Normal file
23
client-web/src/app/risikoobjektView/risikoobjektView.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RisikoobjektView } from './risikoobjektView';
|
||||
|
||||
describe('Risikoobjekt', () => {
|
||||
let component: RisikoobjektView;
|
||||
let fixture: ComponentFixture<RisikoobjektView>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [RisikoobjektView]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(RisikoobjektView);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
81
client-web/src/app/risikoobjektView/risikoobjektView.ts
Normal file
81
client-web/src/app/risikoobjektView/risikoobjektView.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import {Component, inject} from '@angular/core';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {NgForOf, NgIf} from '@angular/common';
|
||||
import {RisikoobjektService} from '../services/risikoobjekt.service';
|
||||
|
||||
export interface Risikoobjekt {
|
||||
|
||||
}
|
||||
|
||||
export interface FahrzeugType extends Risikoobjekt{
|
||||
id?: string
|
||||
baujahr: number
|
||||
handelsbezeichnung: string
|
||||
erstzulassung: Date
|
||||
kennzeichen: string
|
||||
leistung: number
|
||||
listenpreis: number
|
||||
sonderausstattung: number
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-risikoobjekt',
|
||||
imports: [
|
||||
FormsModule,
|
||||
NgForOf,
|
||||
NgIf
|
||||
],
|
||||
templateUrl: './risikoobjektView.html',
|
||||
styleUrl: './risikoobjektView.css',
|
||||
})
|
||||
|
||||
|
||||
export class RisikoobjektView {
|
||||
private risikoobjektService = inject(RisikoobjektService);
|
||||
risikoobjekte = this.risikoobjektService.risikoobjekte;
|
||||
|
||||
types = [
|
||||
"Versichertes objekt SachPrivat",
|
||||
"Versicherte Liegenschaft",
|
||||
"Fahrzeug",
|
||||
"Risiko Gebaeude",
|
||||
"Versicherte Person",
|
||||
"Risiko Haushalt"
|
||||
];
|
||||
|
||||
selectedType: string = '';
|
||||
|
||||
formData = {
|
||||
Baujahr: 0,
|
||||
Handelsbezeichnung: '',
|
||||
Erstzulassung: new Date(),
|
||||
Kennzeichen: '',
|
||||
Leistung: 0,
|
||||
Listenpreis: 0,
|
||||
Sonderausstattung: 0
|
||||
};
|
||||
|
||||
addRisikoobjekt(){
|
||||
if (this.formData.Baujahr === 0 ||
|
||||
this.formData.Handelsbezeichnung === ''
|
||||
|| this.formData.Erstzulassung === null
|
||||
|| this.formData.Kennzeichen === ''
|
||||
|| this.formData.Leistung === 0
|
||||
|| this.formData.Listenpreis === 0
|
||||
|| this.formData.Sonderausstattung === 0){
|
||||
|
||||
}
|
||||
|
||||
const fahrzeug : FahrzeugType = {
|
||||
baujahr: this.formData.Baujahr,
|
||||
handelsbezeichnung: this.formData.Handelsbezeichnung.replace(/ /g, '_'),
|
||||
erstzulassung: this.formData.Erstzulassung,
|
||||
kennzeichen: this.formData.Kennzeichen,
|
||||
leistung: this.formData.Leistung,
|
||||
listenpreis: this.formData.Listenpreis,
|
||||
sonderausstattung: this.formData.Sonderausstattung
|
||||
}
|
||||
|
||||
this.risikoobjektService.addRisikoobjekt(fahrzeug);
|
||||
}
|
||||
}
|
||||
27
client-web/src/app/route-reuse.strategy.ts
Normal file
27
client-web/src/app/route-reuse.strategy.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { RouteReuseStrategy, ActivatedRouteSnapshot, DetachedRouteHandle } from '@angular/router';
|
||||
|
||||
export class CustomRouteReuseStrategy implements RouteReuseStrategy {
|
||||
private handlers: { [key: string]: DetachedRouteHandle } = {};
|
||||
|
||||
// Soll die Route gespeichert werden?
|
||||
shouldDetach(route: ActivatedRouteSnapshot): boolean {
|
||||
// Hier kannst du filtern, welche Route gespeichert werden soll (z.B. 'produktbaum')
|
||||
return route.routeConfig?.path === 'produktbaum';
|
||||
}
|
||||
|
||||
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
|
||||
this.handlers[route.routeConfig?.path!] = handle;
|
||||
}
|
||||
|
||||
shouldAttach(route: ActivatedRouteSnapshot): boolean {
|
||||
return !!this.handlers[route.routeConfig?.path!];
|
||||
}
|
||||
|
||||
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
|
||||
return this.handlers[route.routeConfig?.path!];
|
||||
}
|
||||
|
||||
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
|
||||
return future.routeConfig === curr.routeConfig;
|
||||
}
|
||||
}
|
||||
27
client-web/src/app/services/risikoobjekt.service.ts
Normal file
27
client-web/src/app/services/risikoobjekt.service.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Injectable, signal } from '@angular/core';
|
||||
import { FahrzeugType } from '../risikoobjektView/risikoobjektView';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class RisikoobjektService {
|
||||
private risikoobjekteSignal = signal<FahrzeugType[]>([]);
|
||||
|
||||
get risikoobjekte() {
|
||||
return this.risikoobjekteSignal.asReadonly();
|
||||
}
|
||||
|
||||
addRisikoobjekt(risikoobjekt: FahrzeugType) {
|
||||
this.risikoobjekteSignal.update(current => [...current, risikoobjekt]);
|
||||
}
|
||||
|
||||
removeRisikoobjekt(risikoobjekt: FahrzeugType) {
|
||||
this.risikoobjekteSignal.update(current =>
|
||||
current.filter(ro => ro !== risikoobjekt)
|
||||
);
|
||||
}
|
||||
|
||||
clearRisikoobjekte() {
|
||||
this.risikoobjekteSignal.set([]);
|
||||
}
|
||||
}
|
||||
BIN
client-web/src/assets/logo_kapdion.gif
Normal file
BIN
client-web/src/assets/logo_kapdion.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@@ -1,11 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>WebFrontend</title>
|
||||
<base href="/">
|
||||
<meta charset="utf-8">
|
||||
<title>OMDSAngularWebClient</title>
|
||||
<base href="/produktwissen-app/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { AppModule } from './app/app.module';
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { appConfig } from './app/app.config';
|
||||
import { App } from './app/app';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
||||
bootstrapApplication(App, appConfig)
|
||||
.catch((err) => console.error(err));
|
||||
|
||||
8
client-web/src/proxy.conf.json
Normal file
8
client-web/src/proxy.conf.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"/produktwissen-app/produktApi": {
|
||||
"target": "http://localhost:9090/",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"logLevel": "debug"
|
||||
}
|
||||
}
|
||||
1
client-web/src/styles.css
Normal file
1
client-web/src/styles.css
Normal file
@@ -0,0 +1 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
Reference in New Issue
Block a user