validatedCity, mask for input, confirmatonService, Version Info,

This commit is contained in:
2024-08-07 13:11:26 +02:00
parent 8698aa3e66
commit 3795a5a30c
44 changed files with 360313 additions and 119860 deletions

View File

@@ -24,7 +24,9 @@ import { ConfirmationService } from './confirmation.service';
<svg class="mx-auto mb-4 text-gray-400 w-12 h-12 dark:text-gray-200" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11V6m0 8h.01M19 10a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">{{ confirmationService.message$ | async }}</h3>
@let confirmation = (confirmationService.confirmation$ | async);
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">{{ confirmation.message }}</h3>
@if(confirmation.buttons==='both'){
<button
(click)="confirmationService.accept()"
type="button"
@@ -39,6 +41,7 @@ import { ConfirmationService } from './confirmation.service';
>
No, cancel
</button>
}
</div>
</div>
</div>

View File

@@ -1,19 +1,25 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
export interface Confirmation {
message: string;
buttons?: 'both' | 'none';
button_accept_label?: string;
button_reject_label?: string;
}
@Injectable({
providedIn: 'root',
})
export class ConfirmationService {
private modalVisibleSubject = new BehaviorSubject<boolean>(false);
private messageSubject = new BehaviorSubject<string>('');
private confirmationSubject = new BehaviorSubject<Confirmation>({ message: '' });
private resolvePromise!: (value: boolean) => void;
modalVisible$: Observable<boolean> = this.modalVisibleSubject.asObservable();
message$: Observable<string> = this.messageSubject.asObservable();
confirmation$: Observable<Confirmation> = this.confirmationSubject.asObservable();
showConfirmation(message: string): Promise<boolean> {
this.messageSubject.next(message);
showConfirmation(confirmation: Confirmation): Promise<boolean> {
confirmation.buttons = confirmation.buttons ? confirmation.buttons : 'both';
this.confirmationSubject.next(confirmation);
this.modalVisibleSubject.next(true);
return new Promise<boolean>(resolve => {
this.resolvePromise = resolve;

View File

@@ -1,7 +1,7 @@
import { CdkDrag, CdkDragEnd, CdkDragMove, DragDropModule, DragRef, moveItemInArray } from '@angular/cdk/drag-drop';
import { _getShadowRoot } from '@angular/cdk/platform';
import { CommonModule } from '@angular/common';
import { Component, ElementRef, input, output, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { ChangeDetectorRef, Component, ElementRef, Input, input, output, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { CommercialPropertyListing } from '../../../../../bizmatch-server/src/models/db.model';
import { environment } from '../../../environments/environment';
@Component({
@@ -14,12 +14,11 @@ import { environment } from '../../../environments/environment';
export class DragDropMixedComponent {
@ViewChild('_container') _container!: ElementRef<HTMLDivElement>;
@ViewChildren(CdkDrag) _drags!: QueryList<CdkDrag>;
@Input() ts: number;
listing = input<CommercialPropertyListing>();
imageOrderChanged = output<string[]>();
imageToDelete = output<string>();
env = environment;
ts = new Date().getTime();
items: string[] = []; //[1, 2, 3, 4, 5, 6, 7, 8, 9];
private _cachedItems: string[] = []; //[1, 2, 3, 4, 5, 6, 7, 8, 9];
@@ -34,12 +33,15 @@ export class DragDropMixedComponent {
};
private _containerStyle: CSSStyleDeclaration | null = null;
public isAnimationActive = false;
constructor(private cdr: ChangeDetectorRef) {}
ngOnChanges() {
this.items = this.listing()?.imageOrder;
this._cachedItems = this.items.slice();
}
ngAfterViewInit() {
// Führen Sie einen zusätzlichen Change Detection-Zyklus durch
this.cdr.detectChanges();
}
getImageUrl(image: string): string {
return `${this.env.imageBaseUrl}/pictures/property/${this.listing().imagePath}/${this.listing().serialId}/${image}?_ts=${this.ts}`;
}

View File

@@ -48,23 +48,15 @@ export class ImageCropAndUploadComponent {
this.imageChangedEvent = null;
this.croppedImage = null;
this.showModal = false;
this.fileInput.nativeElement.value = '';
this.uploadFinished.emit({ success: false, type: this.uploadParams.type });
}
uploadImage() {
async uploadImage() {
if (this.croppedImage) {
this.imageService.uploadImage(this.croppedImage, this.uploadParams.type, this.uploadParams.imagePath, this.uploadParams.serialId).subscribe(
response => {
console.log('Upload successful', response);
this.closeModal();
this.uploadFinished.emit({ success: true, type: this.uploadParams.type });
},
error => {
console.error('Upload failed', error);
this.closeModal();
this.uploadFinished.emit({ success: false, type: this.uploadParams.type });
},
);
await this.imageService.uploadImage(this.croppedImage, this.uploadParams.type, this.uploadParams.imagePath, this.uploadParams.serialId);
this.closeModal();
this.uploadFinished.emit({ success: true, type: this.uploadParams.type });
}
}

View File

@@ -39,7 +39,7 @@
(ngModelChange)="setCity($event)"
>
@for (city of cities$ | async; track city.id) {
<ng-option [value]="city">{{ city.city }} - {{ selectOptions.getStateInitials(city.state) }}</ng-option>
<ng-option [value]="city">{{ city.city }} - {{ city.state }}</ng-option>
}
</ng-select>
</div>

View File

@@ -98,7 +98,7 @@ export class SearchModalComponent {
setCity(city) {
if (city) {
this.criteria.city = city.city;
this.criteria.state = city.state_code;
this.criteria.state = city.state;
} else {
this.criteria.city = null;
this.criteria.radius = null;

View File

@@ -0,0 +1,29 @@
<div>
<label for="type" class="block text-sm font-bold text-gray-700 mb-1 relative w-fit"
>{{ label }} @if(validationMessage){
<div
attr.data-tooltip-target="tooltip-{{ name }}"
class="absolute inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white bg-red-500 border-2 border-white rounded-full -top-2 dark:border-gray-900 hover:cursor-pointer"
>
!
</div>
<app-tooltip id="tooltip-{{ name }}" [text]="validationMessage"></app-tooltip>
}
</label>
<ng-select
class="custom"
[multiple]="false"
[hideSelected]="true"
[trackByFn]="trackByFn"
[minTermLength]="2"
[loading]="cityLoading"
typeToSearchText="Please enter 2 or more characters"
[typeahead]="cityInput$"
ngModel="{{ value?.city }} {{ value ? '-' : '' }} {{ value?.state }}"
(ngModelChange)="onInputChange($event)"
>
@for (city of cities$ | async; track city.id) {
<ng-option [value]="city">{{ city.city }} - {{ city.state }}</ng-option>
}
</ng-select>
</div>

View File

@@ -0,0 +1,9 @@
:host ::ng-deep .ng-select.custom .ng-select-container {
// --tw-bg-opacity: 1;
// background-color: rgb(249 250 251 / var(--tw-bg-opacity));
height: 42px;
border-radius: 0.5rem;
.ng-value-container .ng-input {
top: 10px;
}
}

View File

@@ -0,0 +1,68 @@
import { CommonModule } from '@angular/common';
import { Component, forwardRef, Input } from '@angular/core';
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
import { NgSelectModule } from '@ng-select/ng-select';
import { catchError, concat, distinctUntilChanged, Observable, of, Subject, switchMap, tap } from 'rxjs';
import { GeoResult } from '../../../../../bizmatch-server/src/models/main.model';
import { City } from '../../../../../bizmatch-server/src/models/server.model';
import { GeoService } from '../../services/geo.service';
import { SelectOptionsService } from '../../services/select-options.service';
import { BaseInputComponent } from '../base-input/base-input.component';
import { TooltipComponent } from '../tooltip/tooltip.component';
import { ValidationMessagesService } from '../validation-messages.service';
@Component({
selector: 'app-validated-city',
standalone: true,
imports: [CommonModule, FormsModule, NgSelectModule, TooltipComponent],
templateUrl: './validated-city.component.html',
styleUrl: './validated-city.component.scss',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ValidatedCityComponent),
multi: true,
},
],
})
export class ValidatedCityComponent extends BaseInputComponent {
@Input() items;
cities$: Observable<GeoResult[]>;
cityInput$ = new Subject<string>();
countyInput$ = new Subject<string>();
cityLoading = false;
constructor(validationMessagesService: ValidationMessagesService, private geoService: GeoService, public selectOptions: SelectOptionsService) {
super(validationMessagesService);
}
override ngOnInit() {
super.ngOnInit();
this.loadCities();
}
onInputChange(event: City): void {
this.value = event; //{ ...event, longitude: parseFloat(event.longitude), latitude: parseFloat(event.latitude) };
this.onChange(this.value);
}
private loadCities() {
this.cities$ = concat(
of([]), // default items
this.cityInput$.pipe(
distinctUntilChanged(),
tap(() => (this.cityLoading = true)),
switchMap(term =>
this.geoService.findCitiesStartingWith(term).pipe(
catchError(() => of([])), // empty list on error
// map(cities => cities.map(city => city.city)), // transform the list of objects to a list of city names
tap(() => (this.cityLoading = false)),
),
),
),
);
}
trackByFn(item: GeoResult) {
return item.id;
}
compareFn = (item, selected) => {
return item.id === selected.id;
};
}

View File

@@ -19,5 +19,7 @@
(blur)="onTouched()"
[attr.name]="name"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
[mask]="mask"
[dropSpecialCharacters]="false"
/>
</div>

View File

@@ -1,6 +1,7 @@
import { CommonModule } from '@angular/common';
import { Component, forwardRef, Input } from '@angular/core';
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask';
import { BaseInputComponent } from '../base-input/base-input.component';
import { TooltipComponent } from '../tooltip/tooltip.component';
import { ValidationMessagesService } from '../validation-messages.service';
@@ -9,17 +10,19 @@ import { ValidationMessagesService } from '../validation-messages.service';
selector: 'app-validated-input',
templateUrl: './validated-input.component.html',
standalone: true,
imports: [CommonModule, FormsModule, TooltipComponent],
imports: [CommonModule, FormsModule, TooltipComponent, NgxMaskDirective, NgxMaskPipe],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ValidatedInputComponent),
multi: true,
},
provideNgxMask(),
],
})
export class ValidatedInputComponent extends BaseInputComponent {
@Input() kind: 'text' | 'number' | 'email' | 'tel' = 'text';
@Input() mask: string;
constructor(validationMessagesService: ValidationMessagesService) {
super(validationMessagesService);
}