BugFixes image upload, image display, new DB structure for areasServed, licenedIn

This commit is contained in:
2024-05-13 17:31:01 -05:00
parent 5230ef1230
commit aff55c5433
34 changed files with 326 additions and 1131 deletions

View File

@@ -40,7 +40,7 @@
"on-change": "^5.0.1",
"primeflex": "^3.3.1",
"primeicons": "^6.0.1",
"primeng": "^17.10.0",
"primeng": "^17.16.1",
"quill": "^1.3.7",
"rxjs": "~7.8.1",
"tslib": "^2.3.0",

View File

@@ -59,6 +59,11 @@ export const routes: Routes = [
component: AccountComponent,
canActivate: [authGuard],
},
{
path: 'account/:id',
component: AccountComponent,
canActivate: [authGuard],
},
// #########
// Create, Update Listings
{

View File

@@ -1,7 +1,7 @@
<div class="surface-ground h-full">
<div class="px-6 py-5">
<div class="surface-card p-4 shadow-2 border-round">
<div class="flex justify-content-between align-items-center align-content-center">
<div class="flex justify-content-between align-items-center align-content-center mb-2">
<div class="font-medium text-3xl text-900 mb-3">{{ listing?.title }}</div>
<!-- <button pButton pRipple type="button" label="Go back to listings" icon="pi pi-user-plus" class="mr-3 p-button-rounded"></button> -->
<p-button icon="pi pi-times" [rounded]="true" severity="danger" (click)="back()"></p-button>
@@ -40,17 +40,19 @@
<div class="text-900 w-full md:w-10">{{ listing.price | currency }}</div>
</li>
</ul>
@if(listing && user && (user.id===listing?.userId || isAdmin())){
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/editCommercialPropertyListing', listing.id]"></button>
}
</div>
<div class="col-12 md:col-6">
<p-galleria [value]="propertyImages" [showIndicators]="true" [showThumbnails]="false" [responsiveOptions]="responsiveOptions" [containerStyle]="{ 'max-width': '640px' }" [numVisible]="5">
<ng-template pTemplate="item" let-item>
<img src="pictures/property/{{ listing.imagePath }}/{{ item }}" style="width: 100%" />
</ng-template>
</p-galleria>
@if(listing && user && (user.id===listing?.userId || isAdmin())){
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/editCommercialPropertyListing', listing.id]"></button>
}
</div>
@if (mailinfo){
<div class="col-12 md:col-6">
@if (mailinfo){
<div class="surface-card p-4 border-round p-fluid">
<div class="font-medium text-xl text-primary text-900 mb-3">Contact The Author of This Listing</div>
<div class="font-italic text-sm text-900 mb-5">Please Include your contact info below:</div>
@@ -80,8 +82,8 @@
</div>
<button pButton pRipple label="Submit" icon="pi pi-file" class="w-auto" (click)="mail()"></button>
</div>
}
</div>
}
</div>
}
</div>

View File

@@ -10,7 +10,7 @@
<div class="flex align-items-start flex-column lg:flex-row lg:justify-content-between">
<div class="flex align-items-start flex-column md:flex-row">
@if(user.hasProfile){
<img src="pictures//profile/{{ user.id }}.avif" class="mr-5 mb-3 lg:mb-0" style="width: 90px" />
<img src="pictures//profile/{{ user.id }}.avif?_ts={{ ts }}" class="mr-5 mb-3 lg:mb-0" style="width: 90px" />
} @else {
<img src="assets/images/person_placeholder.jpg" class="mr-5 mb-3 lg:mb-0" style="width: 90px" />
}
@@ -34,7 +34,7 @@
<!-- <span class="font-medium text-500">Logo</span> -->
<div>
@if(user.hasCompanyLogo){
<img src="pictures/logo/{{ user.id }}.avif" class="mr-5 lg:mb-0" style="height: 60px; max-width: 100px" />
<img src="pictures/logo/{{ user.id }}.avif?_ts={{ ts }}" class="mr-5 lg:mb-0" style="height: 60px; max-width: 100px" />
}
<!-- <img *ngIf="!user.hasCompanyLogo" src="assets/images/placeholder.png"
class="mr-5 lg:mb-0" style="height:60px;max-width:100px" /> -->
@@ -74,10 +74,10 @@
<div class="text-900 w-full md:w-10" [innerHTML]="offeredServices"></div>
</li>
<li class="flex align-items-center py-3 px-2 flex-wrap">
<div class="text-500 w-full md:w-2 font-medium">Areas we serve</div>
<div class="text-500 w-full md:w-2 font-medium">Areas (Counties) we serve</div>
<div class="text-900 w-full md:w-10">
@for (area of user.areasServed; track area) {
<p-tag styleClass="mr-2" [value]="area" [rounded]="true"></p-tag>
<p-tag styleClass="mr-2" value="{{ area.county }}-{{ area.state }}" [rounded]="true"></p-tag>
}
<!-- <p-tag styleClass="mr-2" severity="success" value="Javascript" [rounded]="true"></p-tag>
<p-tag styleClass="mr-2" severity="danger" value="Python" [rounded]="true"></p-tag>
@@ -87,8 +87,8 @@
<li class="flex align-items-center py-3 px-2 flex-wrap">
<div class="text-500 w-full md:w-2 font-medium">Licensed In</div>
<div class="text-900 w-full md:w-10">
@for (license of userLicensedIn; track license) {
<div>{{ license.name }} : {{ license.value }}</div>
@for (license of user.licensedIn; track license) {
<div>{{ license.state }} {{ license.registerNo }}</div>
}
</div>
</li>
@@ -123,7 +123,7 @@
<div class="p-3 border-1 surface-border border-round surface-card">
<div class="text-900 mb-2 flex align-items-center">
@if (listing.imageOrder?.length>0){
<img src="pictures/property/{{ listing.imagePath }}/{{ listing.imageOrder[0] }}" class="mr-3" style="width: 45px; height: 45px" />
<img src="pictures/property/{{ listing.imagePath }}/{{ listing.imageOrder[0] }}&_ts={{ ts }}" class="mr-3" style="width: 45px; height: 45px" />
} @else {
<img src="assets/images/placeholder_properties.jpg" class="mr-3" style="width: 45px; height: 45px" />
}
@@ -141,7 +141,7 @@
</div>
</div>
@if( user?.id===(user$| async)?.id || isAdmin()){
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/account']"></button>
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/account', user.id]"></button>
}
</div>
}

View File

@@ -6,7 +6,7 @@ import { MessageService } from 'primeng/api';
import { GalleriaModule } from 'primeng/galleria';
import { Observable } from 'rxjs';
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
import { KeyValue, ListingCriteria } from '../../../../../../bizmatch-server/src/models/main.model';
import { ListingCriteria } from '../../../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../../../environments/environment';
import { ImageService } from '../../../services/image.service';
import { ListingsService } from '../../../services/listings.service';
@@ -32,7 +32,7 @@ export class DetailsUserComponent {
commercialPropListings: CommercialPropertyListing[];
companyOverview: SafeHtml;
offeredServices: SafeHtml;
userLicensedIn: KeyValue[];
ts = new Date().getTime();
constructor(
private activatedRoute: ActivatedRoute,
private router: Router,
@@ -47,9 +47,7 @@ export class DetailsUserComponent {
async ngOnInit() {
this.user = await this.userService.getById(this.id);
this.userLicensedIn = this.user.licensedIn.map(l => {
return { name: l.split('|')[0], value: l.split('|')[1] };
});
const results = await Promise.all([await this.listingsService.getListingByUserId(this.id, 'business'), await this.listingsService.getListingByUserId(this.id, 'commercialProperty')]);
// Zuweisen der Ergebnisse zu den Member-Variablen der Klasse
this.businessListings = results[0];

View File

@@ -42,7 +42,7 @@
<div class="surface-card p-4 flex flex-column align-items-center md:flex-row md:align-items-stretch h-full">
<span>
@if(user.hasProfile){
<img src="pictures/profile/{{ user.id }}.avif" class="w-5rem" />
<img src="pictures/profile/{{ user.id }}.avif?_ts={{ ts }}" class="w-5rem" />
} @else {
<img src="assets/images/person_placeholder.jpg" class="w-5rem" />
}
@@ -55,7 +55,7 @@
</div>
<div class="px-4 py-3 text-right flex justify-content-between align-items-center">
@if(user.hasCompanyLogo){
<img src="pictures/logo/{{ user.id }}.avif" class="rounded-image" />
<img src="pictures/logo/{{ user.id }}.avif?_ts={{ ts }}" class="rounded-image" />
} @else {
<img src="assets/images/placeholder.png" class="rounded-image" />
}
@@ -65,7 +65,7 @@
</div>
}
</div>
<div class="mb-2 surface-200 flex align-items-center justify-content-center">
<div class="mb-2 surface-200 flex align-items-center justify-content-center paginator-bar">
<div class="mx-1 text-color">Total number of Professionals/Brokers: {{ totalRecords }}</div>
<p-paginator (onPageChange)="onPageChange($event)" [first]="first" [rows]="rows" [totalRecords]="totalRecords" [rowsPerPageOptions]="[12, 24, 48]"></p-paginator>
</div>

View File

@@ -39,7 +39,7 @@ import { getCriteriaStateObject, getSessionStorageHandler, resetCriteria } from
NgOptimizedImage,
],
templateUrl: './broker-listings.component.html',
styleUrl: './broker-listings.component.scss',
styleUrls: ['./broker-listings.component.scss', '../../pages.scss'],
})
export class BrokerListingsComponent {
environment = environment;
@@ -71,10 +71,11 @@ export class BrokerListingsComponent {
private route: ActivatedRoute,
) {
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
this.criteria.type = undefined;
this.route.data.subscribe(async () => {
if (this.router.getCurrentNavigation().extras.state) {
resetCriteria(this.criteria);
} else {
this.first = this.criteria.page * this.criteria.length;
}
this.init();
});

View File

@@ -62,8 +62,8 @@
<div class="grid">
@for (listing of listings; track listing.id) {
<div class="col-12 lg:col-3 p-3">
<div class="shadow-2 border-round surface-card mb-3 h-full flex-column justify-content-between flex">
<div class="p-4 h-full flex flex-column">
<div class="shadow-2 border-round surface-card h-full flex-column justify-content-between flex">
<div class="p-4 flex flex-column relative">
<div class="flex align-items-center">
<span [class]="selectOptions.getBgColorType(listing.type)" class="inline-flex border-circle align-items-center justify-content-center mr-3" style="width: 38px; height: 38px">
<i [class]="selectOptions.getIconAndTextColorType(listing.type)" class="pi text-xl"></i>
@@ -76,7 +76,7 @@
<p class="mt-0 mb-1 text-700 line-height-3">Net profit: {{ listing.cashFlow | currency }}</p>
<p class="mt-0 mb-1 text-700 line-height-3">Location: {{ selectOptions.getState(listing.state) }}</p>
<p class="mt-0 mb-1 text-700 line-height-3">Established: {{ listing.established }}</p>
<div class="mt-auto ml-auto">
<div class="icon-pos">
<img src="pictures/logo/{{ listing.userId }}.avif" (error)="imageErrorHandler(listing)" class="rounded-image" />
</div>
</div>
@@ -87,7 +87,7 @@
</div>
}
</div>
<div class="mb-2 surface-200 flex align-items-center justify-content-center">
<div class="mb-2 surface-200 flex align-items-center justify-content-center paginator-bar">
<div class="mx-1 text-color">Total number of Listings: {{ totalRecords }}</div>
<p-paginator (onPageChange)="onPageChange($event)" [first]="first" [rows]="rows" [totalRecords]="totalRecords" [rowsPerPageOptions]="[12, 24, 48]"></p-paginator>
</div>

View File

@@ -10,13 +10,17 @@
}
::ng-deep p-paginator div {
background-color: var(--surface-200) !important;
// background-color: var(--surface-400) !important;
}
.icon-pos {
position: absolute;
bottom: 1.5rem; /* Gleich dem Padding des Containers */
right: 1.5rem; /* Gleich dem Padding des Containers */
}
.rounded-image {
border-radius: 6px;
// width: 100px;
max-width: 100px;
height: 45px;
height: 35px;
border: 1px solid rgba(0, 0, 0, 0.2);
padding: 1px 1px;
object-fit: contain;

View File

@@ -39,7 +39,7 @@ import { getCriteriaStateObject, getSessionStorageHandler, resetCriteria } from
TooltipModule,
],
templateUrl: './business-listings.component.html',
styleUrl: './business-listings.component.scss',
styleUrls: ['./business-listings.component.scss', '../../pages.scss'],
})
export class BusinessListingsComponent {
environment = environment;
@@ -68,10 +68,11 @@ export class BusinessListingsComponent {
private route: ActivatedRoute,
) {
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
this.criteria.type = undefined;
this.route.data.subscribe(async () => {
if (this.router.getCurrentNavigation().extras.state) {
resetCriteria(this.criteria);
} else {
this.first = this.criteria.page * this.criteria.length;
}
this.init();
});

View File

@@ -91,7 +91,7 @@
}
</div>
<div class="mb-2 surface-200 flex align-items-center justify-content-center">
<div class="mb-2 surface-200 flex align-items-center justify-content-center paginator-bar">
<!-- @if(listings && listings.length>12){ -->
<div class="mx-1 text-color">Total number of Listings: {{ totalRecords }}</div>
<p-paginator (onPageChange)="onPageChange($event)" [first]="first" [rows]="rows" [totalRecords]="totalRecords" [rowsPerPageOptions]="[12, 24, 48]"></p-paginator>

View File

@@ -1,23 +1,23 @@
#sky-line {
background-image: url(../../../../assets/images/bw-sky.jpg);
height: 204px;
background-position: bottom;
background-size: cover;
margin-bottom: -1px;
background-image: url(../../../../assets/images/bw-sky.jpg);
height: 204px;
background-position: bottom;
background-size: cover;
margin-bottom: -1px;
}
.search{
background-color: #343F69;
.search {
background-color: #343f69;
}
::ng-deep p-paginator div {
background-color: var(--surface-200) !important;
// background-color: var(--surface-400) !important;
background-color: var(--surface-200) !important;
// background-color: var(--surface-400) !important;
}
.rounded-image {
border-radius: 6px;
// width: 100px;
max-width: 100px;
height: 45px;
border: 1px solid rgba(0,0,0,0.2);
padding: 1px 1px;
object-fit: contain;
}
border-radius: 6px;
// width: 100px;
max-width: 100px;
height: 45px;
border: 1px solid rgba(0, 0, 0, 0.2);
padding: 1px 1px;
object-fit: contain;
}

View File

@@ -24,7 +24,7 @@ import { getCriteriaStateObject, getSessionStorageHandler, resetCriteria } from
standalone: true,
imports: [CommonModule, StyleClassModule, ButtonModule, CheckboxModule, InputTextModule, DropdownModule, FormsModule, StyleClassModule, ToggleButtonModule, RouterModule, PaginatorModule, InputGroupModule],
templateUrl: './commercial-property-listings.component.html',
styleUrl: './commercial-property-listings.component.scss',
styleUrls: ['./commercial-property-listings.component.scss', '../../pages.scss'],
})
export class CommercialPropertyListingsComponent {
environment = environment;
@@ -41,7 +41,6 @@ export class CommercialPropertyListingsComponent {
statesSet = new Set();
state: string;
totalRecords: number = 0;
ts = new Date().getTime();
constructor(
public selectOptions: SelectOptionsService,
@@ -53,10 +52,11 @@ export class CommercialPropertyListingsComponent {
private route: ActivatedRoute,
) {
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
this.criteria.type = undefined;
this.route.data.subscribe(async () => {
if (this.router.getCurrentNavigation().extras.state) {
resetCriteria(this.criteria);
} else {
this.first = this.criteria.page * this.criteria.length;
}
this.init();
});

View File

@@ -1,6 +1,16 @@
.wrapper {
width: 1491px;
max-width: 100%;
height: 100%;
margin: auto;
}
width: 1491px;
max-width: 100%;
height: 100%;
margin: auto;
}
:host ::ng-deep .paginator-bar {
position: sticky;
bottom: 0px;
& > button {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Schatten-/Unschärfe-Effekt */
}
& .p-paginator {
padding: 0;
}
}

View File

@@ -62,17 +62,13 @@
<div class="mb-4">
<label for="areasServed" class="block font-medium text-900 mb-2">Areas We Serve</label>
<textarea id="areasServed" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="user.areasServed"></textarea>
</div>
<div>
<label for="companyOverview" class="block font-medium text-900 mb-2">Licensed In</label>
@for (licensedIn of userLicensedIn; track licensedIn.value){
@for (areasServed of user.areasServed; track areasServed){
<div class="grid">
<div class="flex col-12 md:col-6">
<p-dropdown
id="states"
[options]="selectOptions?.states"
[(ngModel)]="licensedIn.name"
[(ngModel)]="areasServed.state"
optionLabel="name"
optionValue="value"
[showClear]="true"
@@ -81,7 +77,35 @@
></p-dropdown>
</div>
<div class="flex col-12 md:col-6">
<input id="companyWebsite" type="text" pInputText [(ngModel)]="licensedIn.value" placeholder="Licence Number" />
<input id="county" type="text" pInputText [(ngModel)]="areasServed.county" placeholder="Area/County Served" />
</div>
</div>
}
</div>
<div class="field mb-5 col-12 md:col-6 flex align-items-center">
<p-button class="mr-1" icon="pi pi-plus" severity="success" (click)="addArea()"></p-button>
<p-button icon="pi pi-minus" severity="danger" (click)="removeArea()" [disabled]="user.areasServed?.length < 2"></p-button>
<span class="text-xs">&nbsp;(Add more Areas or remove existing ones.)</span>
<!-- <button pButton pRipple label="Add Licence" class="w-auto" (click)="addLicence()"></button> -->
</div>
<div>
<label for="companyOverview" class="block font-medium text-900 mb-2">Licensed In</label>
@for (licensedIn of user.licensedIn; track licensedIn){
<div class="grid">
<div class="flex col-12 md:col-6">
<p-dropdown
id="states"
[options]="selectOptions?.states"
[(ngModel)]="licensedIn.state"
optionLabel="name"
optionValue="value"
[showClear]="true"
placeholder="State"
[ngStyle]="{ width: '100%' }"
></p-dropdown>
</div>
<div class="flex col-12 md:col-6">
<input id="registerNo" type="text" pInputText [(ngModel)]="licensedIn.registerNo" placeholder="Licence Number" />
</div>
</div>
}

View File

@@ -10,7 +10,7 @@ import { FileUpload, FileUploadModule } from 'primeng/fileupload';
import { SelectButtonModule } from 'primeng/selectbutton';
import { lastValueFrom } from 'rxjs';
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
import { AutoCompleteCompleteEvent, Invoice, KeyValue, Subscription } from '../../../../../../bizmatch-server/src/models/main.model';
import { AutoCompleteCompleteEvent, Invoice, Subscription } from '../../../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../../../environments/environment';
import { ImageCropperComponent, stateOptions } from '../../../components/image-cropper/image-cropper.component';
import { GeoService } from '../../../services/geo.service';
@@ -36,14 +36,13 @@ export class AccountComponent {
user: User;
subscriptions: Array<Subscription>;
userSubscriptions: Array<Subscription> = [];
maxFileSize = 1000000;
maxFileSize = 15000000;
companyLogoUrl: string;
profileUrl: string;
type: 'company' | 'profile';
dialogRef: DynamicDialogRef | undefined;
environment = environment;
editorModules = TOOLBAR_OPTIONS;
userLicensedIn: KeyValue[];
constructor(
public userService: UserService,
private subscriptionService: SubscriptionsService,
@@ -57,30 +56,26 @@ export class AccountComponent {
public dialogService: DialogService,
) {}
async ngOnInit() {
const keycloakUser = this.userService.getKeycloakUser();
const email = keycloakUser.email;
try {
this.user = await this.userService.getByMail(email);
} catch (e) {
this.user = { email, firstname: keycloakUser.firstname, lastname: keycloakUser.lastname };
this.user = await this.userService.save(this.user);
if (this.id) {
this.user = await this.userService.getById(this.id);
} else {
const keycloakUser = this.userService.getKeycloakUser();
const email = keycloakUser.email;
try {
this.user = await this.userService.getByMail(email);
} catch (e) {
this.user = { email, firstname: keycloakUser.firstname, lastname: keycloakUser.lastname, areasServed: [], licensedIn: [] };
this.user = await this.userService.save(this.user);
}
}
this.userLicensedIn = this.user.licensedIn
? this.user.licensedIn.map(l => {
return { name: l.split('|')[0], value: l.split('|')[1] };
})
: [];
this.userSubscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions());
if (!this.user.licensedIn || this.user.licensedIn?.length === 0) {
this.user.licensedIn = [''];
}
this.profileUrl = this.user.hasProfile ? `pictures/profile/${this.user.id}.avif` : `/assets/images/placeholder.png`;
this.companyLogoUrl = this.user.hasCompanyLogo ? `pictures/logo/${this.user.id}.avif` : `/assets/images/placeholder.png`;
this.profileUrl = this.user.hasProfile ? `/pictures/profile/${this.user.id}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
this.companyLogoUrl = this.user.hasCompanyLogo ? `/pictures/logo/${this.user.id}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
}
printInvoice(invoice: Invoice) {}
async updateProfile(user: User) {
this.user.licensedIn = this.userLicensedIn.map(l => `${l.name}|${l.value}`);
await this.userService.save(this.user);
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Acount changes have been persisted', life: 3000 });
}
@@ -104,12 +99,17 @@ export class AccountComponent {
this.suggestions = result.map(r => `${r.city} - ${r.state_code}`).slice(0, 5);
}
addLicence() {
this.userLicensedIn.push({ name: '', value: '' });
this.user.licensedIn.push({ registerNo: '', state: '' });
}
removeLicence() {
this.userLicensedIn.splice(this.user.licensedIn.length - 2, 1);
this.user.licensedIn.splice(this.user.licensedIn.length - 1, 1);
}
addArea() {
this.user.areasServed.push({ county: '', state: '' });
}
removeArea() {
this.user.areasServed.splice(this.user.areasServed.length - 1, 1);
}
select(event: any, type: 'company' | 'profile') {
const imageUrl = URL.createObjectURL(event.files[0]);
this.type = type;
@@ -142,11 +142,12 @@ export class AccountComponent {
this.loadingService.stopLoading('uploadImage');
if (this.type === 'company') {
this.user.hasCompanyLogo = true; //
this.companyLogoUrl = `${environment.apiBaseUrl}/logo/${this.user.id}.avif?_ts=${new Date().getTime()}`;
this.companyLogoUrl = `pictures/logo/${this.user.id}.avif?_ts=${new Date().getTime()}`;
} else {
this.user.hasProfile = true;
this.profileUrl = `${environment.apiBaseUrl}/profile/${this.user.id}.avif?_ts=${new Date().getTime()}`;
this.profileUrl = `pictures/profile/${this.user.id}.avif?_ts=${new Date().getTime()}`;
}
await this.userService.save(this.user);
}
},
error => console.error('Fehler beim Upload:', error),

View File

@@ -66,27 +66,27 @@
<div class="mb-4 col-12 md:col-6">
<label for="price" class="block font-medium text-900 mb-2">Price</label>
<!-- <p-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price" ></p-inputNumber> -->
<app-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price"></app-inputNumber>
<p-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price"></p-inputNumber>
</div>
<div class="mb-4 col-12 md:col-6">
<label for="salesRevenue" class="block font-medium text-900 mb-2">Sales Revenue</label>
<app-inputNumber mode="currency" currency="USD" inputId="salesRevenue" [(ngModel)]="listing.salesRevenue"></app-inputNumber>
<p-inputNumber mode="currency" currency="USD" inputId="salesRevenue" [(ngModel)]="listing.salesRevenue"></p-inputNumber>
</div>
</div>
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="cashFlow" class="block font-medium text-900 mb-2">Cash Flow</label>
<app-inputNumber mode="currency" currency="USD" inputId="cashFlow" [(ngModel)]="listing.cashFlow"></app-inputNumber>
<p-inputNumber mode="currency" currency="USD" inputId="cashFlow" [(ngModel)]="listing.cashFlow"></p-inputNumber>
</div>
</div>
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="employees" class="block font-medium text-900 mb-2">Years Established Since</label>
<app-inputNumber mode="decimal" inputId="established" [(ngModel)]="listing.established"></app-inputNumber>
<label for="established" class="block font-medium text-900 mb-2">Years Established Since</label>
<p-inputNumber mode="decimal" inputId="established" [(ngModel)]="listing.established"></p-inputNumber>
</div>
<div class="mb-4 col-12 md:col-6">
<label for="employees" class="block font-medium text-900 mb-2">Employees</label>
<app-inputNumber mode="decimal" inputId="employees" [(ngModel)]="listing.employees"></app-inputNumber>
<p-inputNumber mode="decimal" inputId="employees" [(ngModel)]="listing.employees"></p-inputNumber>
</div>
</div>
<div class="grid">
@@ -119,7 +119,7 @@
</div>
<div class="mb-4 col-12 md:col-6">
<label for="internalListingNumber" class="block font-medium text-900 mb-2">Internal Listing Number</label>
<app-inputNumber mode="decimal" inputId="internalListingNumber" type="text" [(ngModel)]="listing.internalListingNumber"></app-inputNumber>
<p-inputNumber mode="decimal" inputId="internalListingNumber" type="text" [(ngModel)]="listing.internalListingNumber"></p-inputNumber>
</div>
</div>
<div class="mb-4">