Fixes for #36 and #72

This commit is contained in:
Andreas Knuth 2024-08-14 19:47:19 +02:00
parent a8bb163acf
commit 7f756a71e8
8 changed files with 155 additions and 25 deletions

View File

@ -22,11 +22,11 @@ import { ModalService } from './modal.service';
styleUrl: './search-modal.component.scss', styleUrl: './search-modal.component.scss',
}) })
export class SearchModalComponent { export class SearchModalComponent {
cities$: Observable<GeoResult[]>; // cities$: Observable<GeoResult[]>;
counties$: Observable<CountyResult[]>; counties$: Observable<CountyResult[]>;
cityLoading = false; // cityLoading = false;
countyLoading = false; countyLoading = false;
cityInput$ = new Subject<string>(); // cityInput$ = new Subject<string>();
countyInput$ = new Subject<string>(); countyInput$ = new Subject<string>();
private criteriaChangeSubscription: Subscription; private criteriaChangeSubscription: Subscription;
public criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria; public criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria;
@ -54,7 +54,7 @@ export class SearchModalComponent {
this.criteria.start = 0; this.criteria.start = 0;
} }
}); });
this.loadCities(); // this.loadCities();
this.loadCounties(); this.loadCounties();
} }
@ -71,22 +71,22 @@ export class SearchModalComponent {
} }
} }
} }
private loadCities() { // private loadCities() {
this.cities$ = concat( // this.cities$ = concat(
of([]), // default items // of([]), // default items
this.cityInput$.pipe( // this.cityInput$.pipe(
distinctUntilChanged(), // distinctUntilChanged(),
tap(() => (this.cityLoading = true)), // tap(() => (this.cityLoading = true)),
switchMap(term => // switchMap(term =>
this.geoService.findCitiesStartingWith(term).pipe( // this.geoService.findCitiesStartingWith(term).pipe(
catchError(() => of([])), // empty list on error // catchError(() => of([])), // empty list on error
// map(cities => cities.map(city => city.city)), // transform the list of objects to a list of city names // // map(cities => cities.map(city => city.city)), // transform the list of objects to a list of city names
tap(() => (this.cityLoading = false)), // tap(() => (this.cityLoading = false)),
), // ),
), // ),
), // ),
); // );
} // }
private loadCounties() { private loadCounties() {
this.counties$ = concat( this.counties$ = concat(
of([]), // default items of([]), // default items

View File

@ -0,0 +1,32 @@
<div>
@if(label){
<label for="type" class="block text-sm font-bold text-gray-700 mb-1 relative w-fit {{ labelClasses }}"
>{{ 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]="countyLoading"
typeToSearchText="Please enter 2 or more characters"
[typeahead]="countyInput$"
ngModel="{{ value }}"
(ngModelChange)="onInputChange($event)"
[readonly]="readonly"
>
@for (county of counties$ | async; track county.id) {
<ng-option [value]="county">{{ county }}</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,70 @@
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, map, Observable, of, Subject, switchMap, tap } from 'rxjs';
import { CountyResult, 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-county',
standalone: true,
imports: [CommonModule, FormsModule, NgSelectModule, TooltipComponent],
templateUrl: './validated-county.component.html',
styleUrl: './validated-county.component.scss',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ValidatedCountyComponent),
multi: true,
},
],
})
export class ValidatedCountyComponent extends BaseInputComponent {
@Input() items;
@Input() labelClasses: string;
@Input() state: string;
@Input() readonly = false;
counties$: Observable<CountyResult[]>;
countyLoading = false;
countyInput$ = new Subject<string>();
constructor(validationMessagesService: ValidationMessagesService, private geoService: GeoService, public selectOptions: SelectOptionsService) {
super(validationMessagesService);
}
override ngOnInit() {
super.ngOnInit();
this.loadCounties();
}
onInputChange(event: City): void {
this.value = event; //{ ...event, longitude: parseFloat(event.longitude), latitude: parseFloat(event.latitude) };
this.onChange(this.value);
}
private loadCounties() {
this.counties$ = concat(
of([]), // default items
this.countyInput$.pipe(
distinctUntilChanged(),
tap(() => (this.countyLoading = true)),
switchMap(term =>
this.geoService.findCountiesStartingWith(term, this.state ? [this.state] : null).pipe(
catchError(() => of([])), // empty list on error
map(counties => counties.map(county => county.name)), // transform the list of objects to a list of city names
tap(() => (this.countyLoading = false)),
),
),
),
);
}
trackByFn(item: GeoResult) {
return item.id;
}
compareFn = (item, selected) => {
return item.id === selected.id;
};
}

View File

@ -158,10 +158,11 @@
@for (areasServed of user.areasServed; track areasServed; let i=$index){ @for (areasServed of user.areasServed; track areasServed; let i=$index){
<div class="grid grid-cols-1 md:grid-cols-2 md:gap-4 gap-1 mb-3 md:mb-1"> <div class="grid grid-cols-1 md:grid-cols-2 md:gap-4 gap-1 mb-3 md:mb-1">
<div> <div>
<ng-select [items]="selectOptions?.states" bindLabel="name" bindValue="value" [(ngModel)]="areasServed.state" name="state{{ i }}"> </ng-select> <ng-select [items]="selectOptions?.states" bindLabel="name" bindValue="value" [(ngModel)]="areasServed.state" (ngModelChange)="setState(i, $event)" name="state{{ i }}"> </ng-select>
</div> </div>
<div> <div>
<input type="text" id="county{{ i }}" name="county{{ i }}" [(ngModel)]="areasServed.county" class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" /> <!-- <input type="text" id="county{{ i }}" name="county{{ i }}" [(ngModel)]="areasServed.county" class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" /> -->
<app-validated-county name="county{{ i }}" [(ngModel)]="areasServed.county" labelClasses="text-gray-900 font-medium" [state]="areasServed.state" [readonly]="!areasServed.state"></app-validated-county>
</div> </div>
</div> </div>
} }

View File

@ -19,6 +19,7 @@ import { MessageComponent } from '../../../components/message/message.component'
import { MessageService } from '../../../components/message/message.service'; import { MessageService } from '../../../components/message/message.service';
import { TooltipComponent } from '../../../components/tooltip/tooltip.component'; import { TooltipComponent } from '../../../components/tooltip/tooltip.component';
import { ValidatedCityComponent } from '../../../components/validated-city/validated-city.component'; import { ValidatedCityComponent } from '../../../components/validated-city/validated-city.component';
import { ValidatedCountyComponent } from '../../../components/validated-county/validated-county.component';
import { ValidatedInputComponent } from '../../../components/validated-input/validated-input.component'; import { ValidatedInputComponent } from '../../../components/validated-input/validated-input.component';
import { ValidatedQuillComponent } from '../../../components/validated-quill/validated-quill.component'; import { ValidatedQuillComponent } from '../../../components/validated-quill/validated-quill.component';
import { ValidatedSelectComponent } from '../../../components/validated-select/validated-select.component'; import { ValidatedSelectComponent } from '../../../components/validated-select/validated-select.component';
@ -50,6 +51,7 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
ValidatedQuillComponent, ValidatedQuillComponent,
ValidatedCityComponent, ValidatedCityComponent,
TooltipComponent, TooltipComponent,
ValidatedCountyComponent,
], ],
providers: [TitleCasePipe], providers: [TitleCasePipe],
templateUrl: './account.component.html', templateUrl: './account.component.html',
@ -237,4 +239,9 @@ export class AccountComponent {
isAdmin() { isAdmin() {
return this.keycloakService.getUserRoles(true).includes('ADMIN'); return this.keycloakService.getUserRoles(true).includes('ADMIN');
} }
setState(index: number, state: string) {
if (state === null) {
this.user.areasServed[index].county = null;
}
}
} }

View File

@ -166,7 +166,17 @@
</div> </div>
</div> --> </div> -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<app-validated-input label="Broker Licensing" name="brokerLicencing" [(ngModel)]="listing.brokerLicencing"></app-validated-input> <div>
<!-- <app-validated-input label="Broker Licensing" name="brokerLicencing" [(ngModel)]="listing.brokerLicencing"></app-validated-input> -->
<label for="brokerLicencing" class="block text-sm font-bold text-gray-700 mb-1">Broker Licensing (please maintain your license in your account)</label>
<!-- @if(listingUser){ -->
<ng-select [(ngModel)]="listing.brokerLicencing" name="brokerLicencing">
@for (licensedIn of listingUser?.licensedIn; track listingUser?.licensedIn) {
<ng-option [value]="licensedIn.registerNo">{{ licensedIn.state }} {{ licensedIn.registerNo }}</ng-option>
}
</ng-select>
</div>
<!-- } -->
<app-validated-input <app-validated-input
label="Internal Listing Number" label="Internal Listing Number"
name="internalListingNumber" name="internalListingNumber"

View File

@ -71,6 +71,7 @@ export class EditBusinessListingComponent {
quillModules = { quillModules = {
toolbar: [['bold', 'italic', 'underline', 'strike'], [{ list: 'ordered' }, { list: 'bullet' }], [{ header: [1, 2, 3, 4, 5, 6, false] }], [{ color: [] }, { background: [] }], ['clean']], toolbar: [['bold', 'italic', 'underline', 'strike'], [{ list: 'ordered' }, { list: 'bullet' }], [{ header: [1, 2, 3, 4, 5, 6, false] }], [{ color: [] }, { background: [] }], ['clean']],
}; };
listingUser: User;
constructor( constructor(
public selectOptions: SelectOptionsService, public selectOptions: SelectOptionsService,
private router: Router, private router: Router,
@ -102,12 +103,12 @@ export class EditBusinessListingComponent {
async ngOnInit() { async ngOnInit() {
const token = await this.keycloakService.getToken(); const token = await this.keycloakService.getToken();
const keycloakUser = map2User(token); const keycloakUser = map2User(token);
this.listingUser = await this.userService.getByMail(keycloakUser.email);
if (this.mode === 'edit') { if (this.mode === 'edit') {
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'business')); this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'business'));
} else { } else {
this.listing = createDefaultBusinessListing(); this.listing = createDefaultBusinessListing();
const listingUser = await this.userService.getByMail(keycloakUser.email); this.listing.email = this.listingUser.email;
this.listing.email = listingUser.email;
this.listing.imageName = emailToDirName(keycloakUser.email); this.listing.imageName = emailToDirName(keycloakUser.email);
if (this.data) { if (this.data) {
this.listing.title = this.data?.title; this.listing.title = this.data?.title;