Fix business filtering logic and add docker sync guide

This commit is contained in:
2026-01-12 13:58:45 +01:00
parent 4f8fd77f7d
commit adeefb199c
21 changed files with 538 additions and 69 deletions

View File

@@ -5,7 +5,7 @@ import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { UntilDestroy } from '@ngneat/until-destroy';
import { Subject, takeUntil } from 'rxjs';
import { BusinessListing, SortByOptions, User } from '../../../../../../bizmatch-server/src/models/db.model';
import { LISTINGS_PER_PAGE, ListingType, UserListingCriteria, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
import { LISTINGS_PER_PAGE, ListingType, UserListingCriteria, emailToDirName, KeycloakUser } from '../../../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../../../environments/environment';
import { BreadcrumbItem, BreadcrumbsComponent } from '../../../components/breadcrumbs/breadcrumbs.component';
import { CustomerSubTypeComponent } from '../../../components/customer-sub-type/customer-sub-type.component';
@@ -20,7 +20,8 @@ import { ListingsService } from '../../../services/listings.service';
import { SearchService } from '../../../services/search.service';
import { SelectOptionsService } from '../../../services/select-options.service';
import { UserService } from '../../../services/user.service';
import { assignProperties, resetUserListingCriteria } from '../../../utils/utils';
import { AuthService } from '../../../services/auth.service';
import { assignProperties, resetUserListingCriteria, map2User } from '../../../utils/utils';
@UntilDestroy()
@Component({
selector: 'app-broker-listings',
@@ -56,6 +57,7 @@ export class BrokerListingsComponent implements OnInit, OnDestroy {
page = 1;
pageCount = 1;
sortBy: SortByOptions = null; // Neu: Separate Property
currentUser: KeycloakUser | null = null; // Current logged-in user
constructor(
public altText: AltTextService,
public selectOptions: SelectOptionsService,
@@ -70,6 +72,7 @@ export class BrokerListingsComponent implements OnInit, OnDestroy {
private modalService: ModalService,
private criteriaChangeService: CriteriaChangeService,
private filterStateService: FilterStateService,
private authService: AuthService,
) {
this.loadSortBy();
}
@@ -77,7 +80,11 @@ export class BrokerListingsComponent implements OnInit, OnDestroy {
const storedSortBy = sessionStorage.getItem('professionalsSortBy');
this.sortBy = storedSortBy && storedSortBy !== 'null' ? (storedSortBy as SortByOptions) : null;
}
ngOnInit(): void {
async ngOnInit(): Promise<void> {
// Get current logged-in user
const token = await this.authService.getToken();
this.currentUser = map2User(token);
// Subscribe to FilterStateService for criteria changes
this.filterStateService
.getState$('brokerListings')
@@ -144,4 +151,86 @@ export class BrokerListingsComponent implements OnInit, OnDestroy {
this.criteria = assignProperties(this.criteria, modalResult.criteria);
}
}
/**
* Check if professional/user is already in current user's favorites
*/
isFavorite(professional: User): boolean {
if (!this.currentUser?.email || !professional.favoritesForUser) return false;
return professional.favoritesForUser.includes(this.currentUser.email);
}
/**
* Toggle favorite status for a professional
*/
async toggleFavorite(event: Event, professional: User): Promise<void> {
event.stopPropagation();
event.preventDefault();
if (!this.currentUser?.email) {
// User not logged in - redirect to login
this.router.navigate(['/login']);
return;
}
try {
if (this.isFavorite(professional)) {
// Remove from favorites
await this.listingsService.removeFavorite(professional.id, 'user');
professional.favoritesForUser = professional.favoritesForUser.filter(
email => email !== this.currentUser!.email
);
} else {
// Add to favorites
await this.listingsService.addToFavorites(professional.id, 'user');
if (!professional.favoritesForUser) {
professional.favoritesForUser = [];
}
professional.favoritesForUser.push(this.currentUser.email);
}
this.cdRef.detectChanges();
} catch (error) {
console.error('Error toggling favorite:', error);
}
}
/**
* Share professional profile
*/
async shareProfessional(event: Event, user: User): Promise<void> {
event.stopPropagation();
event.preventDefault();
const url = `${window.location.origin}/details-user/${user.id}`;
const title = `${user.firstname} ${user.lastname} - ${user.companyName}`;
// Try native share API first (works on mobile and some desktop browsers)
if (navigator.share) {
try {
await navigator.share({
title: title,
text: `Check out this professional: ${title}`,
url: url,
});
} catch (err) {
// User cancelled or share failed - fall back to clipboard
this.copyToClipboard(url);
}
} else {
// Fallback: open Facebook share dialog
const encodedUrl = encodeURIComponent(url);
window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}`, '_blank', 'width=600,height=400');
}
}
/**
* Copy URL to clipboard and show feedback
*/
private copyToClipboard(url: string): void {
navigator.clipboard.writeText(url).then(() => {
console.log('Link copied to clipboard!');
}).catch(err => {
console.error('Failed to copy link:', err);
});
}
}