feat: finished?

This commit is contained in:
Krzysztof Rudnicki 2024-03-09 21:09:32 +01:00
parent 71874b8616
commit 81612bbc39
17 changed files with 356 additions and 76 deletions

View File

@ -25,6 +25,7 @@
"@angular/router": "^17.0.0", "@angular/router": "^17.0.0",
"@angular/ssr": "^17.0.3", "@angular/ssr": "^17.0.3",
"express": "^4.18.2", "express": "^4.18.2",
"faker": "^6.6.6",
"json-server": "1.0.0-alpha.23", "json-server": "1.0.0-alpha.23",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"socket.io-client": "^4.7.4", "socket.io-client": "^4.7.4",
@ -36,7 +37,9 @@
"@angular-devkit/build-angular": "^17.0.3", "@angular-devkit/build-angular": "^17.0.3",
"@angular/cli": "^17.0.3", "@angular/cli": "^17.0.3",
"@angular/compiler-cli": "^17.0.0", "@angular/compiler-cli": "^17.0.0",
"@faker-js/faker": "^8.4.1",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/faker": "^6.6.9",
"@types/jasmine": "~5.1.0", "@types/jasmine": "~5.1.0",
"@types/node": "^18.18.0", "@types/node": "^18.18.0",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",

View File

@ -47,6 +47,9 @@ dependencies:
express: express:
specifier: ^4.18.2 specifier: ^4.18.2
version: 4.18.3 version: 4.18.3
faker:
specifier: ^6.6.6
version: 6.6.6
json-server: json-server:
specifier: 1.0.0-alpha.23 specifier: 1.0.0-alpha.23
version: 1.0.0-alpha.23 version: 1.0.0-alpha.23
@ -76,9 +79,15 @@ devDependencies:
'@angular/compiler-cli': '@angular/compiler-cli':
specifier: ^17.0.0 specifier: ^17.0.0
version: 17.2.4(@angular/compiler@17.2.4)(typescript@5.2.2) version: 17.2.4(@angular/compiler@17.2.4)(typescript@5.2.2)
'@faker-js/faker':
specifier: ^8.4.1
version: 8.4.1
'@types/express': '@types/express':
specifier: ^4.17.17 specifier: ^4.17.17
version: 4.17.21 version: 4.17.21
'@types/faker':
specifier: ^6.6.9
version: 6.6.9
'@types/jasmine': '@types/jasmine':
specifier: ~5.1.0 specifier: ~5.1.0
version: 5.1.4 version: 5.1.4
@ -2260,6 +2269,11 @@ packages:
dev: true dev: true
optional: true optional: true
/@faker-js/faker@8.4.1:
resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'}
dev: true
/@fastify/busboy@2.1.1: /@fastify/busboy@2.1.1:
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
engines: {node: '>=14'} engines: {node: '>=14'}
@ -3503,6 +3517,13 @@ packages:
'@types/serve-static': 1.15.5 '@types/serve-static': 1.15.5
dev: true dev: true
/@types/faker@6.6.9:
resolution: {integrity: sha512-Y9YYm5L//8ooiiknO++4Gr539zzdI0j3aXnOBjo1Vk+kTvffY10GuE2wn78AFPECwZ5MYGTjiDVw1naLLdDimw==}
deprecated: This is a stub types definition. faker provides its own type definitions, so you do not need this installed.
dependencies:
faker: 6.6.6
dev: true
/@types/http-errors@2.0.4: /@types/http-errors@2.0.4:
resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
dev: true dev: true
@ -4913,6 +4934,9 @@ packages:
tmp: 0.0.33 tmp: 0.0.33
dev: true dev: true
/faker@6.6.6:
resolution: {integrity: sha512-9tCqYEDHI5RYFQigXFwF1hnCwcWCOJl/hmll0lr5D2Ljjb0o4wphb69wikeJDz5qCEzXCoPvG6ss5SDP6IfOdg==}
/fast-deep-equal@3.1.3: /fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
dev: true dev: true

View File

@ -32,19 +32,16 @@ export class BackendService {
} }
} }
public sendMessage(message: UserInputRequest): Promise<GenericResponse> { public sendMessage(message: GenericRequest): Promise<GenericResponse> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (message.request_type === "user_input") { if (message.request_type === "user_input") {
this.userInputArray.push(message as UserInputRequest); this.userInputArray.push(message as UserInputRequest);
} }
const headers = new HttpHeaders().set("Content-Type", "application/json");
// Assuming `serializeToQueryParams` is a method that converts your message object into HttpParams. console.log(`request: `, JSON.stringify(message));
// You will need to implement this conversion based on your `GenericRequest` structure.
const params = this.serializeToQueryParams(message);
console.log(`request: `, params.toString()); this.http.post<GenericResponse>(this.address, JSON.stringify(message), { headers }).subscribe({
this.http.get<GenericResponse>(`${this.address}`, { params }).subscribe({
next: (response) => { next: (response) => {
console.log(`response: `, response); console.log(`response: `, response);
resolve(response); resolve(response);

View File

@ -1,5 +1,7 @@
<div class="page-container"> <div class="page-container">
<form [formGroup]="userInputForm" (ngSubmit)="onSubmit()" class="lawyering-form"> <form [formGroup]="userInputForm" (ngSubmit)="onSubmit()" class="lawyering-form">
<h1 class="center"> Oblicz koszta i czas sporu </h1>
<mat-form-field appearance="outline" class="custom-form-field"> <mat-form-field appearance="outline" class="custom-form-field">
<mat-label>Opis Sprawy</mat-label> <mat-label>Opis Sprawy</mat-label>
<textarea matInput formControlName="generic_input" class="text-area-input"></textarea> <textarea matInput formControlName="generic_input" class="text-area-input"></textarea>

View File

@ -6,6 +6,16 @@ body, html {
margin: 0; margin: 0;
} }
.center {
text-align: center;
}
.page-container {
display: flex;
justify-content: center;
align-items: center;
}
.lawyering-form { .lawyering-form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -28,7 +38,7 @@ body, html {
.checkbox-group { .checkbox-group {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
gap: 10px; // For spacing between checkboxes gap: 10px; // For spacing between checkboxes
} }
@ -38,6 +48,7 @@ body, html {
color: #ffffff; color: #ffffff;
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
width: 100%;
} }
} }
@ -51,5 +62,11 @@ body, html {
width: 100%; /* Makes the submit button full width on smaller screens */ width: 100%; /* Makes the submit button full width on smaller screens */
align-self: center; /* Center align if preferred */ align-self: center; /* Center align if preferred */
} }
.checkbox-group {
display: flex;
flex-direction: column;
gap: 10px; // For spacing between checkboxes
}
} }

View File

@ -5,6 +5,7 @@ import { MatCheckboxModule } from '@angular/material/checkbox'
import { MatButtonModule } from '@angular/material/button' import { MatButtonModule } from '@angular/material/button'
import { BackendService } from '../backend.service'; import { BackendService } from '../backend.service';
import { GenericRequest, UserInputRequest, userInput } from '../requests-responses'; import { GenericRequest, UserInputRequest, userInput } from '../requests-responses';
import { Router } from '@angular/router';
@Component({ @Component({
@ -23,10 +24,10 @@ export class CaseInputComponent {
userInputForm: FormGroup; userInputForm: FormGroup;
userInput: userInput | null = null; userInput: userInput | null = null;
constructor(private fb: FormBuilder, private readonly backendService: BackendService) { constructor(private fb: FormBuilder, private readonly backendService: BackendService, private readonly router: Router) {
this.userInputForm = this.fb.group({ this.userInputForm = this.fb.group({
generic_input: [''], generic_input: [''],
trial_value: [0], trial_value: [],
location: [''], location: [''],
experts_called: [false], experts_called: [false],
witnesses_called: [false] witnesses_called: [false]
@ -38,6 +39,7 @@ export class CaseInputComponent {
if(this.userInput !== null) { if(this.userInput !== null) {
const result = await this.backendService.sendMessage(new UserInputRequest(this.userInput)); const result = await this.backendService.sendMessage(new UserInputRequest(this.userInput));
console.log(`result: `, result); console.log(`result: `, result);
this.router.navigate(['koszt']);
} else { } else {
console.error(`caseInputComponent, onSubmit, userInput is null!`) console.error(`caseInputComponent, onSubmit, userInput is null!`)
} }

View File

@ -1,5 +1,15 @@
Przewidywalne minimalne koszta: <div class="legal-costs">
@if(costData !== null) { Przewidywalne minimalne koszta:
Koszta: {{costData.cost_of_trial}} zł <br> @if(costData !== null) {
Przewidywalny czas: {{costData.time_of_trial | date:'L'}} miesiące <div class="cost-details">
} Koszta: <span class="cost">{{costData.cost_of_trial}}</span><br>
Czas: <span class="time">{{costData.time_of_trial | date:'L'}}</span> miesięcy
</div>
}
</div>
<h1 class="bait"> Chcesz zminimalizować koszta aż o <strong> 90%? </strong> </h1>
<div class="center-button">
<button mat-raised-button color="primary" class="find-mediator" type="submit" (click)="onSubmit()" class="submit-button">Znajdź Mediatora</button>
</div>

View File

@ -0,0 +1,44 @@
.legal-costs {
font-family: 'Times New Roman', serif;
color: #2a2a2a; // Dark text for readability
background-color: #f5f5f5; // Light background to keep the content subtle
padding: 20px;
border: 1px solid #ddd; // Slight border for definition
border-radius: 5px; // Soften the edges for a modern look
font-size: 16px;
.cost-details {
margin-top: 10px;
padding: 10px;
background-color: #fff; // A clean background for the cost details
border-left: 5px solid #004085; // A striking left border for emphasis
font-size: 24px;
span {
font-weight: bold; // Make the dynamic content pop
}
.cost {
color: #007bff; // A professional blue for costs
}
.time {
color: #28a745; // A hopeful green for the time
}
}
}
.bait {
text-align: center;
}
.find-mediator {
width: 100%;
}
.center-button {
display: flex;
justify-content: center;
align-items: center;
}

View File

@ -1,11 +1,13 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { StatisticsOutputInterface } from '../requests-responses'; import { StatisticsOutputInterface } from '../requests-responses';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { Router } from '@angular/router';
@Component({ @Component({
selector: 'app-cost-view', selector: 'app-cost-view',
standalone: true, standalone: true,
imports: [DatePipe], imports: [DatePipe, MatButtonModule],
templateUrl: './cost-view.component.html', templateUrl: './cost-view.component.html',
styleUrl: './cost-view.component.scss' styleUrl: './cost-view.component.scss'
}) })
@ -15,6 +17,8 @@ export class CostViewComponent {
time_of_trial: Date.UTC(0, 6, 0, 0, 0, 0, 0) time_of_trial: Date.UTC(0, 6, 0, 0, 0, 0, 0)
}; };
constructor(private readonly router: Router) {}
public calculateTimeDifference(utcDateNumber: number): string { public calculateTimeDifference(utcDateNumber: number): string {
const currentDate = new Date(); const currentDate = new Date();
const targetDate = new Date(utcDateNumber); const targetDate = new Date(utcDateNumber);
@ -40,4 +44,8 @@ public calculateTimeDifference(utcDateNumber: number): string {
} }
} }
onSubmit() {
this.router.navigate(["mediatorzy"]);
}
} }

View File

@ -0,0 +1,20 @@
<h1 mat-dialog-title class="title">Wprowadź email drugiej strony</h1>
<mat-dialog-content>
<p class="title">Poinformujemy osobę o twojej chęci do podjęcia mediacji</p>
<form class="example-form">
<mat-form-field class="example-full-width">
<mat-label>Email</mat-label>
<input type="email" matInput
placeholder="np. jankowalski@email.com">
@if (emailFormControl.hasError('email') && !emailFormControl.hasError('required')) {
<mat-error>Please enter a valid email address</mat-error>
}
@if (emailFormControl.hasError('required')) {
<mat-error>Email is <strong>required</strong></mat-error>
}
</mat-form-field>
</form>
</mat-dialog-content>
<mat-dialog-actions>
<button class="center" mat-raised-button [mat-dialog-close]="data" cdkFocusInitial>Wyslij</button>
</mat-dialog-actions>

View File

@ -0,0 +1,7 @@
.center {
width: 100%;
}
.title {
text-align: center;
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EmailInputComponent } from './email-input.component';
describe('EmailInputComponent', () => {
let component: EmailInputComponent;
let fixture: ComponentFixture<EmailInputComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [EmailInputComponent]
})
.compileComponents();
fixture = TestBed.createComponent(EmailInputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,25 @@
import { Component, Inject } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from '@angular/material/dialog'
import { MatInputModule } from '@angular/material/input';
@Component({
selector: 'app-email-input',
standalone: true,
imports: [MatDialogModule, MatInputModule, MatButtonModule],
templateUrl: './email-input.component.html',
styleUrl: './email-input.component.scss'
})
export class EmailInputComponent {
constructor(
public dialogRef: MatDialogRef<EmailInputComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) {}
emailFormControl = new FormControl('', [Validators.required, Validators.email]);
onNoClick(): void {
this.dialogRef.close();
}
}

View File

@ -1,32 +1,29 @@
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> <div class="center">
@for(mediator of mediatorzy; track mediator) {
<!--- Note that these columns can be defined in any order. <mat-card class="example-card">
The actual rendered columns are set as a property on the row definition" --> <mat-card-header>
<div mat-card-avatar class="example-header-image" [style.background-image]="'url(' + generatePersonInfo() + ')'"></div>
<!-- Position Column --> <mat-card-title>{{ mediator.name }}</mat-card-title>
<ng-container matColumnDef="position"> <mat-card-subtitle>
<th mat-header-cell *matHeaderCellDef> No. </th> {{ mediator.specialization }}
<td mat-cell *matCellDef="let element"> {{element.position}} </td> <div class="ratings-row">
</ng-container> <div class="golden-star">
{{ convertToStars(mediator.ai_rating) }}
<!-- Name Column --> </div>
<ng-container matColumnDef="name"> Liczba opinii: {{ mediator.number_of_opinions }}
<th mat-header-cell *matHeaderCellDef> Name </th> </div>
<td mat-cell *matCellDef="let element"> {{element.name}} </td> </mat-card-subtitle>
</ng-container> </mat-card-header>
<mat-card-content>
<!-- Weight Column --> <p>
<ng-container matColumnDef="weight"> <mat-icon>location_on </mat-icon> {{ generateCity() }} {{ generateAddress() }} <br>
<th mat-header-cell *matHeaderCellDef> Weight </th> <mat-icon> attach_money</mat-icon> Cena za godzinę: {{ generateCost() }} zł<br>
<td mat-cell *matCellDef="let element"> {{element.weight}} </td> @if(generateOnline()) { class="available">Mediacja zdalna }
</ng-container> </p>
</mat-card-content>
<!-- Symbol Column --> <mat-card-actions>
<ng-container matColumnDef="symbol"> <button mat-raised-button (click)="umowSie()">UMÓW SIĘ</button>
<th mat-header-cell *matHeaderCellDef> Symbol </th> </mat-card-actions>
<td mat-cell *matCellDef="let element"> {{element.symbol}} </td> </mat-card>
</ng-container> }
</div>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

View File

@ -0,0 +1,32 @@
.example-card {
width: 350px;
}
.example-header-image {
background-size: cover;
width: 40px; /* Example size, adjust as needed */
height: 40px; /* Example size, adjust as needed */
border-radius: 50%; /* Makes the image round */
}
.golden-star {
color: gold;
}
.ratings-row {
display: flex;
align-items: center;
gap: 10px;
}
.available {
color: green;
}
.center {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
gap: 20px;
}

View File

@ -1,34 +1,102 @@
import { Component } from '@angular/core'; import { Component, Input } from '@angular/core';
import { MatTableModule } from '@angular/material/table' import { MatTableModule } from '@angular/material/table'
import { RecommendedMediatorsInterface } from '../requests-responses';
export interface PeriodicElement { import { MatCardModule } from '@angular/material/card'
name: string; import { MatButtonModule } from '@angular/material/button';
position: number; import { faker } from '@faker-js/faker';
weight: number; import {MatIconModule } from '@angular/material/icon'
symbol: string; import { MatDialog } from '@angular/material/dialog';
} import { EmailInputComponent } from '../email-input/email-input.component';
const ELEMENT_DATA: PeriodicElement[] = [
{position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
{position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
{position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
{position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
{position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
{position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'},
{position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'},
{position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'},
{position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'},
{position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'},
];
@Component({ @Component({
selector: 'app-mediators-list', selector: 'app-mediators-list',
standalone: true, standalone: true,
imports: [MatTableModule], imports: [MatTableModule, MatCardModule, MatButtonModule, MatIconModule],
templateUrl: './mediators-list.component.html', templateUrl: './mediators-list.component.html',
styleUrl: './mediators-list.component.scss' styleUrl: './mediators-list.component.scss'
}) })
export class MediatorsListComponent { export class MediatorsListComponent {
displayedColumns: string[] = ['position', 'name', 'weight', 'symbol']; @Input() mediatorzy: RecommendedMediatorsInterface[] = [
dataSource = ELEMENT_DATA; {
"name": "Mateusz Szpyruk",
"specialization": "Prawo podatkowe",
"localization": "Katowice",
"ai_rating": 99,
"user_rating": 99,
"number_of_opinions": 5
},
{
"name": "Jan Kowalski",
"specialization": "Prawo pracy",
"localization": "Katowice",
"ai_rating": 90,
"user_rating": 99,
"number_of_opinions": 5
},
{
"name": "Jan Kowalski",
"specialization": "Prawo pracy",
"localization": "Katowice",
"ai_rating": 76,
"user_rating": 99,
"number_of_opinions": 5
}
]
constructor(private readonly dialog: MatDialog) {}
generatePersonInfo() {
const personInfo = {
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
email: faker.internet.email(),
avatar: faker.image.avatar() // Generating person image
};
console.log(`faker.image.avatar(): `, faker.image.avatar());
const avatarUrl = personInfo.avatar;
return avatarUrl;
}
convertToStars(score: number): string {
if (score < 0 || score > 100) {
return 'Score must be between 0 and 100';
}
// Calculate the number of full stars
const fullStars = Math.floor(score / 20);
// Determine if there should be a half star
const halfStar = (score % 20) >= 10 ? 1 : 0;
// Calculate the number of empty stars
const emptyStars = 5 - fullStars - halfStar;
return '★'.repeat(fullStars) + '✩'.repeat(halfStar) + '☆'.repeat(emptyStars);
}
generateCity() {
return faker.location.city();
}
generateAddress() {
return faker.location.streetAddress();
}
generateCost() {
return faker.commerce.price();
}
generateOnline() {
return faker.datatype.boolean();
}
umowSie() {
const dialogRef = this.dialog.open(EmailInputComponent, {
width: '250px',
data: { /* Data passed to the modal */ }
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
// Optional: handle data returned from the modal
});
}
} }

View File

@ -48,7 +48,8 @@ export interface RecommendedMediatorsInterface {
"name": string, "name": string,
"specialization": string, "specialization": string,
"localization": string, "localization": string,
"score": number, "ai_rating": number,
"user_rating": number,
"number_of_opinions": number "number_of_opinions": number
} }